19 const std::string &texture_path_value,
20 const std::string &vertex_shader_path_value,
21 const std::string &fragment_shader_path_value,
22 size_t max_vertex_count) {
23 if (window ==
nullptr) {
24 throw mxvk::Exception(
"VK_PointSpriteBatch::load called with null window");
26 if (max_vertex_count == 0) {
27 throw mxvk::Exception(
"VK_PointSpriteBatch::load requires non-zero vertex capacity");
41 texture_path = texture_path_value;
42 vertex_shader_path = vertex_shader_path_value;
43 fragment_shader_path = fragment_shader_path_value;
44 max_vertices = max_vertex_count;
46 if (context.device == VK_NULL_HANDLE || context.physical_device == VK_NULL_HANDLE ||
47 context.graphics_queue == VK_NULL_HANDLE || context.command_pool == VK_NULL_HANDLE) {
48 throw mxvk::Exception(
"Cannot create point-sprite batch before Vulkan render resources are available");
50 if (color_attachment_format == VK_FORMAT_UNDEFINED || image_count == 0) {
51 throw mxvk::Exception(
"Cannot create point-sprite batch before swapchain resources are available");
55 create_vertex_buffer();
56 create_swapchain_resources();
61 if (!batch_loaded || window ==
nullptr) {
74 cleanup_swapchain_resources();
75 create_swapchain_resources();
79 cleanup_swapchain_resources();
80 destroy_vertex_buffer();
83 pipeline_cache = VK_NULL_HANDLE;
84 color_attachment_format = VK_FORMAT_UNDEFINED;
85 depth_attachment_format = VK_FORMAT_UNDEFINED;
88 vertex_shader_path.clear();
89 fragment_shader_path.clear();
96 if (!batch_loaded || vertex_buffer.mapped ==
nullptr || vertices ==
nullptr || count == 0) {
100 if (count > max_vertices) {
101 throw mxvk::Exception(
"VK_PointSpriteBatch::upload_vertices exceeds batch capacity");
105 active_vertices = count;
109 if (image_index >= uniform_buffers.size() || uniform_buffers[image_index].mapped ==
nullptr) {
113 UniformBufferObject ubo{};
115 std::memcpy(uniform_buffers[image_index].mapped, &ubo,
sizeof(ubo));
119 if (!batch_loaded || active_vertices == 0 || pipeline == VK_NULL_HANDLE || image_index >= descriptor_sets.size()) {
123 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
124 VkBuffer buffers[] = {vertex_buffer.buffer};
125 VkDeviceSize offsets[] = {0};
126 vkCmdBindVertexBuffers(cmd, 0, 1, buffers, offsets);
127 vkCmdBindDescriptorSets(
129 VK_PIPELINE_BIND_POINT_GRAPHICS,
133 &descriptor_sets[image_index],
136 vkCmdDraw(cmd,
static_cast<uint32_t
>(active_vertices), 1, 0, 0);
140 if (additive_blending == enabled) {
143 additive_blending = enabled;
151 if (depth_test_enabled == enabled) {
154 depth_test_enabled = enabled;
162 if (depth_write_enabled == enabled) {
165 depth_write_enabled = enabled;
172 void VK_PointSpriteBatch::create_vertex_buffer() {
176 VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
177 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
182 void VK_PointSpriteBatch::destroy_vertex_buffer() {
186 void VK_PointSpriteBatch::create_swapchain_resources() {
187 create_descriptor_set_layout();
188 create_uniform_buffers();
189 create_descriptor_pool();
190 create_descriptor_sets();
194 void VK_PointSpriteBatch::cleanup_swapchain_resources() {
195 if (context.device == VK_NULL_HANDLE) {
196 descriptor_sets.clear();
197 uniform_buffers.clear();
201 if (descriptor_pool != VK_NULL_HANDLE) {
202 vkDestroyDescriptorPool(context.device, descriptor_pool,
nullptr);
203 descriptor_pool = VK_NULL_HANDLE;
205 if (descriptor_set_layout != VK_NULL_HANDLE) {
206 vkDestroyDescriptorSetLayout(context.device, descriptor_set_layout,
nullptr);
207 descriptor_set_layout = VK_NULL_HANDLE;
209 destroy_uniform_buffers();
210 descriptor_sets.clear();
213 void VK_PointSpriteBatch::create_descriptor_set_layout() {
214 VkDescriptorSetLayoutBinding sampler_binding{};
215 sampler_binding.binding = 0;
216 sampler_binding.descriptorCount = 1;
217 sampler_binding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
218 sampler_binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
220 VkDescriptorSetLayoutBinding ubo_binding{};
221 ubo_binding.binding = 1;
222 ubo_binding.descriptorCount = 1;
223 ubo_binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
224 ubo_binding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
226 std::array<VkDescriptorSetLayoutBinding, 2> bindings{sampler_binding, ubo_binding};
227 VkDescriptorSetLayoutCreateInfo layout_info{};
228 layout_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
229 layout_info.bindingCount =
static_cast<uint32_t
>(bindings.size());
230 layout_info.pBindings = bindings.data();
231 if (vkCreateDescriptorSetLayout(context.device, &layout_info,
nullptr, &descriptor_set_layout) != VK_SUCCESS) {
232 throw mxvk::Exception(
"failed to create point-sprite descriptor set layout");
236 void VK_PointSpriteBatch::create_uniform_buffers() {
237 uniform_buffers.resize(image_count);
238 for (
auto &buffer : uniform_buffers) {
241 sizeof(UniformBufferObject),
242 VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
243 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
249 void VK_PointSpriteBatch::destroy_uniform_buffers() {
250 for (
auto &buffer : uniform_buffers) {
253 uniform_buffers.clear();
256 void VK_PointSpriteBatch::create_descriptor_pool() {
257 const uint32_t count =
static_cast<uint32_t
>(image_count);
258 std::array<VkDescriptorPoolSize, 2> pool_sizes{};
259 pool_sizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
260 pool_sizes[0].descriptorCount = count;
261 pool_sizes[1].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
262 pool_sizes[1].descriptorCount = count;
264 VkDescriptorPoolCreateInfo pool_info{};
265 pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
266 pool_info.poolSizeCount =
static_cast<uint32_t
>(pool_sizes.size());
267 pool_info.pPoolSizes = pool_sizes.data();
268 pool_info.maxSets = count;
269 if (vkCreateDescriptorPool(context.device, &pool_info,
nullptr, &descriptor_pool) != VK_SUCCESS) {
270 throw mxvk::Exception(
"failed to create point-sprite descriptor pool");
274 void VK_PointSpriteBatch::create_descriptor_sets() {
275 std::vector<VkDescriptorSetLayout> layouts(image_count, descriptor_set_layout);
276 VkDescriptorSetAllocateInfo alloc_info{};
277 alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
278 alloc_info.descriptorPool = descriptor_pool;
279 alloc_info.descriptorSetCount =
static_cast<uint32_t
>(image_count);
280 alloc_info.pSetLayouts = layouts.data();
282 descriptor_sets.resize(image_count, VK_NULL_HANDLE);
283 if (vkAllocateDescriptorSets(context.device, &alloc_info, descriptor_sets.data()) != VK_SUCCESS) {
284 throw mxvk::Exception(
"failed to allocate point-sprite descriptor sets");
287 for (
size_t i = 0; i < image_count; ++i) {
288 VkDescriptorImageInfo image_info{};
289 image_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
290 image_info.imageView = texture.view;
291 image_info.sampler = texture.sampler;
293 VkDescriptorBufferInfo buffer_info{};
294 buffer_info.buffer = uniform_buffers[i].buffer;
295 buffer_info.offset = 0;
296 buffer_info.range =
sizeof(UniformBufferObject);
298 std::array<VkWriteDescriptorSet, 2> writes{};
299 writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
300 writes[0].dstSet = descriptor_sets[i];
301 writes[0].dstBinding = 0;
302 writes[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
303 writes[0].descriptorCount = 1;
304 writes[0].pImageInfo = &image_info;
305 writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
306 writes[1].dstSet = descriptor_sets[i];
307 writes[1].dstBinding = 1;
308 writes[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
309 writes[1].descriptorCount = 1;
310 writes[1].pBufferInfo = &buffer_info;
312 vkUpdateDescriptorSets(context.device,
static_cast<uint32_t
>(writes.size()), writes.data(), 0,
nullptr);
316 void VK_PointSpriteBatch::create_pipeline() {
319 const std::vector<char> vert_code = read_shader_file(vertex_shader_path);
320 const std::vector<char> frag_code = read_shader_file(fragment_shader_path);
322 VkShaderModule frag_module = VK_NULL_HANDLE;
327 VkPipelineShaderStageCreateInfo vert_stage{};
328 vert_stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
329 vert_stage.stage = VK_SHADER_STAGE_VERTEX_BIT;
330 vert_stage.module = vert_module;
331 vert_stage.pName =
"main";
333 VkPipelineShaderStageCreateInfo frag_stage{};
334 frag_stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
335 frag_stage.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
336 frag_stage.module = frag_module;
337 frag_stage.pName =
"main";
338 std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages = {vert_stage, frag_stage};
340 VkVertexInputBindingDescription binding{};
342 binding.stride =
sizeof(PointSpriteVertex);
343 binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
345 std::array<VkVertexInputAttributeDescription, 3> attributes{};
346 attributes[0].binding = 0;
347 attributes[0].location = 0;
348 attributes[0].format = VK_FORMAT_R32G32B32_SFLOAT;
349 attributes[0].offset = offsetof(PointSpriteVertex, position);
350 attributes[1].binding = 0;
351 attributes[1].location = 1;
352 attributes[1].format = VK_FORMAT_R32_SFLOAT;
353 attributes[1].offset = offsetof(PointSpriteVertex, size);
354 attributes[2].binding = 0;
355 attributes[2].location = 2;
356 attributes[2].format = VK_FORMAT_R32G32B32A32_SFLOAT;
357 attributes[2].offset = offsetof(PointSpriteVertex, color);
359 VkPipelineVertexInputStateCreateInfo vertex_input{};
360 vertex_input.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
361 vertex_input.vertexBindingDescriptionCount = 1;
362 vertex_input.pVertexBindingDescriptions = &binding;
363 vertex_input.vertexAttributeDescriptionCount =
static_cast<uint32_t
>(attributes.size());
364 vertex_input.pVertexAttributeDescriptions = attributes.data();
366 VkPipelineInputAssemblyStateCreateInfo input_assembly{};
367 input_assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
368 input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
369 input_assembly.primitiveRestartEnable = VK_FALSE;
371 std::array<VkDynamicState, 2> dynamic_states = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
372 VkPipelineDynamicStateCreateInfo dynamic_state{};
373 dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
374 dynamic_state.dynamicStateCount =
static_cast<uint32_t
>(dynamic_states.size());
375 dynamic_state.pDynamicStates = dynamic_states.data();
377 VkPipelineViewportStateCreateInfo viewport_state{};
378 viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
379 viewport_state.viewportCount = 1;
380 viewport_state.scissorCount = 1;
382 VkPipelineRasterizationStateCreateInfo rasterizer{};
383 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
384 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
385 rasterizer.cullMode = VK_CULL_MODE_NONE;
386 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
387 rasterizer.lineWidth = 1.0f;
389 VkPipelineMultisampleStateCreateInfo multisampling{};
390 multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
391 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
393 VkPipelineDepthStencilStateCreateInfo depth_stencil{};
394 depth_stencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
395 depth_stencil.depthTestEnable = depth_test_enabled ? VK_TRUE : VK_FALSE;
396 depth_stencil.depthWriteEnable = depth_write_enabled ? VK_TRUE : VK_FALSE;
397 depth_stencil.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
399 VkPipelineColorBlendAttachmentState blend{};
400 blend.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
401 VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
402 blend.blendEnable = VK_TRUE;
403 blend.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
404 blend.dstColorBlendFactor = additive_blending ? VK_BLEND_FACTOR_ONE : VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
405 blend.colorBlendOp = VK_BLEND_OP_ADD;
406 blend.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
407 blend.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
408 blend.alphaBlendOp = VK_BLEND_OP_ADD;
410 VkPipelineColorBlendStateCreateInfo color_blending{};
411 color_blending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
412 color_blending.attachmentCount = 1;
413 color_blending.pAttachments = &blend;
415 VkPipelineLayoutCreateInfo layout_info{};
416 layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
417 layout_info.setLayoutCount = 1;
418 layout_info.pSetLayouts = &descriptor_set_layout;
419 if (vkCreatePipelineLayout(context.device, &layout_info,
nullptr, &pipeline_layout) != VK_SUCCESS) {
420 throw mxvk::Exception(
"failed to create point-sprite pipeline layout");
423 VkPipelineRenderingCreateInfo rendering_info{};
424 rendering_info.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
425 rendering_info.colorAttachmentCount = 1;
426 rendering_info.pColorAttachmentFormats = &color_attachment_format;
427 rendering_info.depthAttachmentFormat = depth_attachment_format;
428 rendering_info.stencilAttachmentFormat = VK_FORMAT_UNDEFINED;
430 VkGraphicsPipelineCreateInfo pipeline_info{};
431 pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
432 pipeline_info.pNext = &rendering_info;
433 pipeline_info.stageCount =
static_cast<uint32_t
>(shader_stages.size());
434 pipeline_info.pStages = shader_stages.data();
435 pipeline_info.pVertexInputState = &vertex_input;
436 pipeline_info.pInputAssemblyState = &input_assembly;
437 pipeline_info.pViewportState = &viewport_state;
438 pipeline_info.pRasterizationState = &rasterizer;
439 pipeline_info.pMultisampleState = &multisampling;
440 pipeline_info.pDepthStencilState = &depth_stencil;
441 pipeline_info.pColorBlendState = &color_blending;
442 pipeline_info.pDynamicState = &dynamic_state;
443 pipeline_info.layout = pipeline_layout;
444 pipeline_info.renderPass = VK_NULL_HANDLE;
445 if (vkCreateGraphicsPipelines(context.device, pipeline_cache, 1, &pipeline_info,
nullptr, &pipeline) != VK_SUCCESS) {
446 throw mxvk::Exception(
"failed to create point-sprite graphics pipeline");
449 if (frag_module != VK_NULL_HANDLE) {
450 vkDestroyShaderModule(context.device, frag_module,
nullptr);
452 vkDestroyShaderModule(context.device, vert_module,
nullptr);
456 vkDestroyShaderModule(context.device, frag_module,
nullptr);
457 vkDestroyShaderModule(context.device, vert_module,
nullptr);
460 void VK_PointSpriteBatch::destroy_pipeline() {
461 if (context.device == VK_NULL_HANDLE) {
462 pipeline = VK_NULL_HANDLE;
463 pipeline_layout = VK_NULL_HANDLE;
466 if (pipeline != VK_NULL_HANDLE) {
467 vkDestroyPipeline(context.device, pipeline,
nullptr);
468 pipeline = VK_NULL_HANDLE;
470 if (pipeline_layout != VK_NULL_HANDLE) {
471 vkDestroyPipelineLayout(context.device, pipeline_layout,
nullptr);
472 pipeline_layout = VK_NULL_HANDLE;
476 std::vector<char> VK_PointSpriteBatch::read_shader_file(
const std::string &path)
const {
~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.