MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
RenderFlow.cpp
Go to the documentation of this file.
1#include "RenderFlow.hpp"
2
4
7
11
13
15
16bool RenderFlow::s_initialized = false;
17
18namespace {
19
20 /**
21 * @brief Translate semantic VertexLayout to Vulkan bindings/attributes
22 * @param layout Semantic vertex layout
23 * @return Tuple of (bindings, attributes)
24 */
25 std::pair<
26 std::vector<Core::VertexBinding>,
27 std::vector<Core::VertexAttribute>>
28 translate_semantic_layout(const Kakshya::VertexLayout& layout)
29 {
30 using namespace MayaFlux::Portal::Graphics;
31
32 auto [vk_bindings, vk_attributes] = VertexLayoutTranslator::translate_layout(layout);
33
35 "Translated semantic vertex layout: {} bindings, {} attributes",
36 vk_bindings.size(), vk_attributes.size());
37
38 return { vk_bindings, vk_attributes };
39 }
40
41} // anonymous namespace
42
43//==============================================================================
44// Initialization
45//==============================================================================
46
48{
49 if (s_initialized) {
51 "RenderFlow already initialized (static flag)");
52 return true;
53 }
54
55 if (m_shader_foundry) {
57 "RenderFlow already initialized");
58 return true;
59 }
60
64 "ShaderFoundry must be initialized before RenderFlow");
65 return false;
66 }
67
70
71 if (!m_display_service) {
73 "DisplayService not found in BackendRegistry");
74 return false;
75 }
76
77 s_initialized = true;
78
80 "RenderFlow initialized");
81 return true;
82}
83
85{
86 if (!s_initialized) {
87 return;
88 }
89
91 "RenderFlow stopped");
92}
93
95{
96 if (!s_initialized) {
97 return;
98 }
99
101 "Shutting down RenderFlow...");
102
105 "Cannot shutdown RenderFlow: ShaderFoundry not initialized");
106 return;
107 }
108
109 auto device = m_shader_foundry->get_device();
110
111 if (!device) {
113 "Cannot shutdown RenderFlow: Vulkan device is null");
114 return;
115 }
116
118
119 m_window_associations.clear();
120
121 m_shader_foundry = nullptr;
122 m_display_service = nullptr;
123
124 s_initialized = false;
125
127 "RenderFlow shutdown complete");
128}
129
130//==============================================================================
131// Utility Conversions (Portal enums → Vulkan enums)
132//==============================================================================
133
134namespace {
135
136 vk::PrimitiveTopology to_vk_topology(PrimitiveTopology topology)
137 {
138 switch (topology) {
140 return vk::PrimitiveTopology::ePointList;
142 return vk::PrimitiveTopology::eLineList;
144 return vk::PrimitiveTopology::eLineStrip;
146 return vk::PrimitiveTopology::eTriangleList;
148 return vk::PrimitiveTopology::eTriangleStrip;
150 return vk::PrimitiveTopology::eTriangleFan;
151 default:
152 return vk::PrimitiveTopology::eTriangleList;
153 }
154 }
155
156 vk::PolygonMode to_vk_polygon_mode(PolygonMode mode)
157 {
158 switch (mode) {
160 return vk::PolygonMode::eFill;
162 return vk::PolygonMode::eLine;
164 return vk::PolygonMode::ePoint;
165 default:
166 return vk::PolygonMode::eFill;
167 }
168 }
169
170 vk::CullModeFlags to_vk_cull_mode(CullMode mode)
171 {
172 switch (mode) {
173 case CullMode::NONE:
174 return vk::CullModeFlagBits::eNone;
175 case CullMode::FRONT:
176 return vk::CullModeFlagBits::eFront;
177 case CullMode::BACK:
178 return vk::CullModeFlagBits::eBack;
180 return vk::CullModeFlagBits::eFrontAndBack;
181 default:
182 return vk::CullModeFlagBits::eBack;
183 }
184 }
185
186 vk::CompareOp to_vk_compare_op(CompareOp op)
187 {
188 switch (op) {
189 case CompareOp::NEVER:
190 return vk::CompareOp::eNever;
191 case CompareOp::LESS:
192 return vk::CompareOp::eLess;
193 case CompareOp::EQUAL:
194 return vk::CompareOp::eEqual;
196 return vk::CompareOp::eLessOrEqual;
198 return vk::CompareOp::eGreater;
200 return vk::CompareOp::eNotEqual;
202 return vk::CompareOp::eGreaterOrEqual;
204 return vk::CompareOp::eAlways;
205 default:
206 return vk::CompareOp::eLess;
207 }
208 }
209
210 vk::BlendFactor to_vk_blend_factor(BlendFactor factor)
211 {
212 switch (factor) {
214 return vk::BlendFactor::eZero;
215 case BlendFactor::ONE:
216 return vk::BlendFactor::eOne;
218 return vk::BlendFactor::eSrcColor;
220 return vk::BlendFactor::eOneMinusSrcColor;
222 return vk::BlendFactor::eDstColor;
224 return vk::BlendFactor::eOneMinusDstColor;
226 return vk::BlendFactor::eSrcAlpha;
228 return vk::BlendFactor::eOneMinusSrcAlpha;
230 return vk::BlendFactor::eDstAlpha;
232 return vk::BlendFactor::eOneMinusDstAlpha;
233 default:
234 return vk::BlendFactor::eOne;
235 }
236 }
237
238 vk::BlendOp to_vk_blend_op(BlendOp op)
239 {
240 switch (op) {
241 case BlendOp::ADD:
242 return vk::BlendOp::eAdd;
244 return vk::BlendOp::eSubtract;
246 return vk::BlendOp::eReverseSubtract;
247 case BlendOp::MIN:
248 return vk::BlendOp::eMin;
249 case BlendOp::MAX:
250 return vk::BlendOp::eMax;
251 default:
252 return vk::BlendOp::eAdd;
253 }
254 }
255} // anonymous namespace
256
257//==============================================================================
258// Pipeline Creation
259//==============================================================================
260
262 const RenderPipelineConfig& config,
263 const std::vector<vk::Format>& color_formats,
264 vk::Format depth_format)
265{
266 if (!is_initialized()) {
268 "RenderFlow not initialized");
270 }
271
272 if (config.mesh_shader == INVALID_SHADER && config.vertex_shader == INVALID_SHADER) {
274 "Pipeline requires either mesh shader or vertex shader");
276 }
277
278 if (color_formats.empty()) {
280 "At least one color format required for dynamic rendering pipeline");
282 }
283
285
287 if (config.fragment_shader != INVALID_SHADER) {
289 }
290 if (config.geometry_shader != INVALID_SHADER) {
292 }
293 if (config.tess_control_shader != INVALID_SHADER) {
295 }
296 if (config.tess_eval_shader != INVALID_SHADER) {
298 }
299
301 if (config.task_shader != INVALID_SHADER) {
303 }
304
305 if (config.fragment_shader != INVALID_SHADER) {
307 }
308
309 if (config.semantic_vertex_layout.has_value()) {
311 "Pipeline using semantic VertexLayout ({} vertices, {} attributes)",
312 config.semantic_vertex_layout->vertex_count,
313 config.semantic_vertex_layout->attributes.size());
314
315 auto [vk_bindings, vk_attributes] = translate_semantic_layout(
316 config.semantic_vertex_layout.value());
317
318 vk_config.vertex_bindings = vk_bindings;
319 vk_config.vertex_attributes = vk_attributes;
320 vk_config.use_vertex_shader_reflection = false;
321
322 } else if (!config.vertex_bindings.empty() || !config.vertex_attributes.empty()) {
324 "Pipeline using explicit vertex config ({} bindings, {} attributes)",
325 config.vertex_bindings.size(), config.vertex_attributes.size());
326
327 for (const auto& binding : config.vertex_bindings) {
328 Core::VertexBinding vk_binding {};
329 vk_binding.binding = binding.binding;
330 vk_binding.stride = binding.stride;
331 vk_binding.input_rate = binding.per_instance ? vk::VertexInputRate::eInstance : vk::VertexInputRate::eVertex;
332 vk_config.vertex_bindings.push_back(vk_binding);
333 }
334
335 for (const auto& attr : config.vertex_attributes) {
336 Core::VertexAttribute vk_attr {};
337 vk_attr.location = attr.location;
338 vk_attr.binding = attr.binding;
339 vk_attr.format = attr.format;
340 vk_attr.offset = attr.offset;
341 vk_config.vertex_attributes.push_back(vk_attr);
342 }
343
344 vk_config.use_vertex_shader_reflection = false;
345 } else {
347 "Pipeline will use shader reflection for vertex input");
349 }
350
351 vk_config.topology = to_vk_topology(config.topology);
352 vk_config.primitive_restart_enable = false;
353
354 vk_config.polygon_mode = to_vk_polygon_mode(config.rasterization.polygon_mode);
355 vk_config.cull_mode = to_vk_cull_mode(config.rasterization.cull_mode);
356 vk_config.front_face = config.rasterization.front_face_ccw ? vk::FrontFace::eCounterClockwise : vk::FrontFace::eClockwise;
357 vk_config.line_width = config.rasterization.line_width;
359 vk_config.depth_bias_enable = config.rasterization.depth_bias;
360
363 vk_config.depth_compare_op = to_vk_compare_op(config.depth_stencil.depth_compare_op);
365
366 for (const auto& blend : config.blend_attachments) {
368 vk_blend.blend_enable = blend.blend_enable;
369 vk_blend.src_color_blend_factor = to_vk_blend_factor(blend.src_color_factor);
370 vk_blend.dst_color_blend_factor = to_vk_blend_factor(blend.dst_color_factor);
371 vk_blend.color_blend_op = to_vk_blend_op(blend.color_blend_op);
372 vk_blend.src_alpha_blend_factor = to_vk_blend_factor(blend.src_alpha_factor);
373 vk_blend.dst_alpha_blend_factor = to_vk_blend_factor(blend.dst_alpha_factor);
374 vk_blend.alpha_blend_op = to_vk_blend_op(blend.alpha_blend_op);
375 vk_config.color_blend_attachments.push_back(vk_blend);
376 }
377
378 std::vector<vk::DescriptorSetLayout> layouts;
379 vk::DescriptorSetLayout state_vt_layout {};
380
381 {
382 std::vector<vk::DescriptorSetLayoutBinding> set0_bindings;
383
384 vk::DescriptorSetLayoutBinding vt_b;
385 vt_b.binding = 0;
386 vt_b.descriptorType = vk::DescriptorType::eUniformBuffer;
387 vt_b.descriptorCount = 1;
388 vt_b.stageFlags = vk::ShaderStageFlagBits::eVertex;
389 set0_bindings.push_back(vt_b);
390
391 if (!config.descriptor_sets.empty()) {
392 for (const auto& binding : config.descriptor_sets[0]) {
393 if (binding.set == 0 && binding.binding != 0) {
394 vk::DescriptorSetLayoutBinding vk_b;
395 vk_b.binding = binding.binding;
396 vk_b.descriptorType = binding.type;
397 vk_b.descriptorCount = binding.count > 0 ? binding.count : 1;
398 vk_b.stageFlags = vk::ShaderStageFlagBits::eVertex
399 | vk::ShaderStageFlagBits::eFragment;
400 set0_bindings.push_back(vk_b);
401 }
402 }
403 }
404
405 vk::DescriptorSetLayoutCreateInfo layout_info;
406 layout_info.bindingCount = static_cast<uint32_t>(set0_bindings.size());
407 layout_info.pBindings = set0_bindings.data();
408 vk::DescriptorSetLayout vt_layout = m_shader_foundry->get_device().createDescriptorSetLayout(layout_info);
409 layouts.push_back(vt_layout);
410 state_vt_layout = vt_layout;
411 }
412
413 for (const auto& desc_set : config.descriptor_sets) {
414 bool all_set_zero = std::ranges::all_of(desc_set,
415 [](const auto& b) { return b.set == 0; });
416 if (all_set_zero)
417 continue;
418
419 std::vector<vk::DescriptorSetLayoutBinding> bindings;
420 for (const auto& binding : desc_set) {
421 if (binding.set == 0)
422 continue;
423 vk::DescriptorSetLayoutBinding vk_b;
424 vk_b.binding = binding.binding;
425 vk_b.descriptorType = binding.type;
426 vk_b.descriptorCount = binding.count > 0 ? binding.count : 1;
427 vk_b.stageFlags = vk::ShaderStageFlagBits::eVertex
428 | vk::ShaderStageFlagBits::eFragment;
429 bindings.push_back(vk_b);
430 }
431
432 if (bindings.empty())
433 continue;
434
435 vk::DescriptorSetLayoutCreateInfo layout_info;
436 layout_info.bindingCount = static_cast<uint32_t>(bindings.size());
437 layout_info.pBindings = bindings.data();
438 layouts.push_back(
439 m_shader_foundry->get_device().createDescriptorSetLayout(layout_info));
440 }
441
442 vk_config.descriptor_set_layouts = layouts;
443
444 vk::ShaderStageFlags push_constant_stages;
445
446 if (config.mesh_shader != INVALID_SHADER) {
447 push_constant_stages = vk::ShaderStageFlagBits::eMeshEXT;
448 if (config.task_shader != INVALID_SHADER) {
449 push_constant_stages |= vk::ShaderStageFlagBits::eTaskEXT;
450 }
451 if (config.fragment_shader != INVALID_SHADER) {
452 push_constant_stages |= vk::ShaderStageFlagBits::eFragment;
453 }
454 } else {
455 push_constant_stages = vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment;
456 if (config.geometry_shader != INVALID_SHADER) {
457 push_constant_stages |= vk::ShaderStageFlagBits::eGeometry;
458 }
459 if (config.tess_control_shader != INVALID_SHADER) {
460 push_constant_stages |= vk::ShaderStageFlagBits::eTessellationControl;
461 }
462 if (config.tess_eval_shader != INVALID_SHADER) {
463 push_constant_stages |= vk::ShaderStageFlagBits::eTessellationEvaluation;
464 }
465 }
466
467 if (config.push_constant_size > 0) {
468 vk::PushConstantRange range;
469 range.stageFlags = push_constant_stages;
470 range.offset = 0;
471 range.size = static_cast<uint32_t>(config.push_constant_size);
472 vk_config.push_constant_ranges.push_back(range);
473 }
474
475 vk_config.color_attachment_formats = color_formats;
476 vk_config.depth_attachment_format = depth_format;
477
478 vk_config.dynamic_states = {
479 vk::DynamicState::eViewport,
480 vk::DynamicState::eScissor
481 };
482
483 auto pipeline = std::make_shared<Core::VKGraphicsPipeline>();
484 if (!pipeline->create(m_shader_foundry->get_device(), vk_config)) {
486 "Failed to create VKGraphicsPipeline for dynamic rendering");
487
488 for (auto layout : layouts) {
489 m_shader_foundry->get_device().destroyDescriptorSetLayout(layout);
490 }
492 }
493
494 auto pipeline_id = m_next_pipeline_id.fetch_add(1);
495 PipelineState state;
496 state.shader_ids = { config.vertex_shader, config.fragment_shader };
497 state.pipeline = pipeline;
498 state.layouts = layouts;
499 state.view_transform_layout = state_vt_layout;
500 state.layout = pipeline->get_layout();
501 state.push_constant_stages = push_constant_stages;
502 m_pipelines[pipeline_id] = std::move(state);
503
505 "Dynamic rendering pipeline created (ID: {}, {} color attachments)",
506 pipeline_id, color_formats.size());
507
508 return pipeline_id;
509}
510
512{
513 auto it = m_pipelines.find(pipeline_id);
514 if (it == m_pipelines.end()) {
515 return;
516 }
517
518 auto device = m_shader_foundry->get_device();
519
520 if (it->second.pipeline) {
521 it->second.pipeline->cleanup(device);
522 }
523
524 if (it->second.layout) {
525 device.destroyPipelineLayout(it->second.layout);
526 }
527
528 for (auto layout : it->second.layouts) {
529 if (layout) {
530 device.destroyDescriptorSetLayout(layout);
531 }
532 }
533
534 m_pipelines.erase(it);
535
537 "Destroyed graphics pipeline (ID: {})", pipeline_id);
538}
539
541{
542 auto device = m_shader_foundry->get_device();
543
544 for (auto& [id, state] : m_pipelines) {
545 if (state.pipeline) {
546 state.pipeline->cleanup(device);
547 }
548
549 for (auto layout : state.layouts) {
550 if (layout) {
551 device.destroyDescriptorSetLayout(layout);
552 }
553 }
554 }
555 m_pipelines.clear();
556
558 "Cleaned up all graphics pipelines");
559}
560
562 RenderPipelineID pipeline_id) const
563{
564 auto it = m_pipelines.find(pipeline_id);
565 if (it == m_pipelines.end()) {
566 return {};
567 }
568 return it->second.view_transform_layout;
569}
570
571//==============================================================================
572// Dynamic Rendering
573//==============================================================================
574
576 CommandBufferID cmd_id,
577 const std::shared_ptr<Core::Window>& window,
578 vk::Image swapchain_image,
579 const std::array<float, 4>& clear_color,
580 vk::ImageView depth_image_view)
581{
583 if (!cmd) {
585 "Invalid command buffer ID: {}", cmd_id);
586 return;
587 }
588
589 if (!window) {
591 "Cannot begin rendering for null window");
592 return;
593 }
594
595 if (!swapchain_image) {
597 "Cannot begin rendering with null swapchain image");
598 return;
599 }
600
601 auto it = m_window_associations.find(window);
602 if (it == m_window_associations.end()) {
604 "Window '{}' not registered for rendering. "
605 "Call register_window_for_rendering() first.",
606 window->get_create_info().title);
607 m_window_associations.emplace(window, WindowRenderAssociation { .window = window, .swapchain_image = swapchain_image });
608 } else {
609 it->second.swapchain_image = swapchain_image;
610 }
611
612 uint32_t width = 0, height = 0;
614
615 if (width == 0 || height == 0) {
617 "Invalid swapchain extent for window '{}': {}x{}",
618 window->get_create_info().title, width, height);
619 return;
620 }
621
622 vk::ImageView image_view = get_current_image_view(window);
623 if (!image_view) {
625 "Failed to get image view for window '{}'",
626 window->get_create_info().title);
627 return;
628 }
629
630 vk::ImageMemoryBarrier pre_barrier {};
631 pre_barrier.oldLayout = vk::ImageLayout::eUndefined;
632 pre_barrier.newLayout = vk::ImageLayout::eColorAttachmentOptimal;
633 pre_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
634 pre_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
635 pre_barrier.image = swapchain_image;
636 pre_barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
637 pre_barrier.subresourceRange.baseMipLevel = 0;
638 pre_barrier.subresourceRange.levelCount = 1;
639 pre_barrier.subresourceRange.baseArrayLayer = 0;
640 pre_barrier.subresourceRange.layerCount = 1;
641 pre_barrier.srcAccessMask = vk::AccessFlagBits::eNone;
642 pre_barrier.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
643
644 cmd.pipelineBarrier(
645 vk::PipelineStageFlagBits::eTopOfPipe,
646 vk::PipelineStageFlagBits::eColorAttachmentOutput,
647 vk::DependencyFlags {},
648 0, nullptr,
649 0, nullptr,
650 1, &pre_barrier);
651
652 vk::RenderingAttachmentInfo color_attachment {};
653 color_attachment.sType = vk::StructureType::eRenderingAttachmentInfo;
654 color_attachment.pNext = nullptr;
655 color_attachment.imageView = image_view;
656 color_attachment.imageLayout = vk::ImageLayout::eColorAttachmentOptimal;
657 color_attachment.resolveMode = vk::ResolveModeFlagBits::eNone;
658 color_attachment.resolveImageView = nullptr;
659 color_attachment.resolveImageLayout = vk::ImageLayout::eUndefined;
660 color_attachment.loadOp = vk::AttachmentLoadOp::eClear;
661 color_attachment.storeOp = vk::AttachmentStoreOp::eStore;
662
663 if (clear_color != default_color) {
664 color_attachment.clearValue.color = vk::ClearColorValue(clear_color);
665 } else {
666 color_attachment.clearValue.color = vk::ClearColorValue(window->get_create_info().clear_color);
667 }
668
669 vk::RenderingAttachmentInfo depth_attachment {};
670 if (depth_image_view) {
671 depth_attachment.sType = vk::StructureType::eRenderingAttachmentInfo;
672 depth_attachment.pNext = nullptr;
673 depth_attachment.imageView = depth_image_view;
674 depth_attachment.imageLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal;
675 depth_attachment.resolveMode = vk::ResolveModeFlagBits::eNone;
676 depth_attachment.resolveImageView = nullptr;
677 depth_attachment.resolveImageLayout = vk::ImageLayout::eUndefined;
678 depth_attachment.loadOp = vk::AttachmentLoadOp::eClear;
679 depth_attachment.storeOp = vk::AttachmentStoreOp::eDontCare;
680 depth_attachment.clearValue.depthStencil = vk::ClearDepthStencilValue { 1.0F, 0 };
681 }
682
683 vk::RenderingInfo rendering_info {};
684 rendering_info.sType = vk::StructureType::eRenderingInfo;
685 rendering_info.pNext = nullptr;
686 rendering_info.flags = vk::RenderingFlagBits::eContentsSecondaryCommandBuffers;
687 rendering_info.renderArea.offset = vk::Offset2D { 0, 0 };
688 rendering_info.renderArea.extent = vk::Extent2D { width, height };
689 rendering_info.layerCount = 1;
690 rendering_info.colorAttachmentCount = 1;
691 rendering_info.pColorAttachments = &color_attachment;
692 rendering_info.pDepthAttachment = depth_image_view ? &depth_attachment : nullptr;
693 rendering_info.pStencilAttachment = nullptr;
694
695 cmd.beginRendering(rendering_info);
696
698 "Began dynamic rendering for window '{}' ({}x{}, depth: {})",
699 window->get_create_info().title, width, height,
700 depth_image_view ? "yes" : "no");
701}
702
704 CommandBufferID cmd_id,
705 const std::shared_ptr<Core::Window>& window)
706{
708 if (!cmd) {
710 "Invalid command buffer ID: {}", cmd_id);
711 return;
712 }
713
714 cmd.endRendering();
715
716 auto it = m_window_associations.find(window);
717 if (it == m_window_associations.end() || !it->second.swapchain_image) {
719 "No swapchain image tracked for window '{}'",
720 window->get_create_info().title);
721 return;
722 }
723
724 vk::ImageMemoryBarrier post_barrier {};
725 post_barrier.oldLayout = vk::ImageLayout::eColorAttachmentOptimal;
726 post_barrier.newLayout = vk::ImageLayout::ePresentSrcKHR;
727 post_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
728 post_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
729 post_barrier.image = it->second.swapchain_image;
730 post_barrier.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
731 post_barrier.subresourceRange.baseMipLevel = 0;
732 post_barrier.subresourceRange.levelCount = 1;
733 post_barrier.subresourceRange.baseArrayLayer = 0;
734 post_barrier.subresourceRange.layerCount = 1;
735 post_barrier.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
736 post_barrier.dstAccessMask = vk::AccessFlagBits::eNone;
737
738 cmd.pipelineBarrier(
739 vk::PipelineStageFlagBits::eColorAttachmentOutput,
740 vk::PipelineStageFlagBits::eBottomOfPipe,
741 vk::DependencyFlags {},
742 0, nullptr,
743 0, nullptr,
744 1, &post_barrier);
745
746 it->second.swapchain_image = nullptr;
747
749 "Ended dynamic rendering for window '{}'",
750 window->get_create_info().title);
751}
752
753//==============================================================================
754// Command Recording
755//==============================================================================
756
758{
759 auto pipeline_it = m_pipelines.find(pipeline_id);
760 if (pipeline_it == m_pipelines.end()) {
762 "Invalid pipeline ID: {}", pipeline_id);
763 return;
764 }
765
767 if (!cmd) {
769 "Invalid command buffer ID: {}", cmd_id);
770 return;
771 }
772
773 pipeline_it->second.pipeline->bind(cmd);
774}
775
777 CommandBufferID cmd_id,
778 const std::vector<std::shared_ptr<Buffers::VKBuffer>>& buffers,
779 uint32_t first_binding)
780{
782 if (!cmd) {
784 "Invalid command buffer ID: {}", cmd_id);
785 return;
786 }
787
788 std::vector<vk::Buffer> vk_buffers;
789 std::vector<vk::DeviceSize> offsets(buffers.size(), 0);
790
791 vk_buffers.reserve(buffers.size());
792 for (const auto& buf : buffers) {
793 vk_buffers.push_back(buf->get_buffer());
794 }
795
796 cmd.bindVertexBuffers(first_binding, vk_buffers, offsets);
797}
798
800 CommandBufferID cmd_id,
801 const std::shared_ptr<Buffers::VKBuffer>& buffer,
802 vk::IndexType index_type)
803{
805 if (!cmd) {
807 "Invalid command buffer ID: {}", cmd_id);
808 return;
809 }
810
811 cmd.bindIndexBuffer(buffer->get_index_buffer(), 0, index_type);
812}
813
815 CommandBufferID cmd_id,
816 RenderPipelineID pipeline_id,
817 const std::vector<DescriptorSetID>& descriptor_sets,
818 uint32_t first_set)
819{
820 auto pipeline_it = m_pipelines.find(pipeline_id);
821 if (pipeline_it == m_pipelines.end()) {
823 "Invalid pipeline ID: {}", pipeline_id);
824 return;
825 }
826
828 if (!cmd) {
830 "Invalid command buffer ID: {}", cmd_id);
831 return;
832 }
833
834 std::vector<vk::DescriptorSet> vk_sets;
835 vk_sets.reserve(descriptor_sets.size());
836 for (auto ds_id : descriptor_sets) {
837 vk_sets.push_back(m_shader_foundry->get_descriptor_set(ds_id));
838 }
839
840 cmd.bindDescriptorSets(
841 vk::PipelineBindPoint::eGraphics,
842 pipeline_it->second.layout,
843 first_set,
844 vk_sets,
845 nullptr);
846}
847
849 CommandBufferID cmd_id,
850 RenderPipelineID pipeline_id,
851 const void* data,
852 size_t size,
853 uint32_t offset)
854{
855 auto pipeline_it = m_pipelines.find(pipeline_id);
856 if (pipeline_it == m_pipelines.end()) {
858 "Invalid pipeline ID: {}", pipeline_id);
859 return;
860 }
861
863 if (!cmd) {
865 "Invalid command buffer ID: {}", cmd_id);
866 return;
867 }
868
869 cmd.pushConstants(
870 pipeline_it->second.layout,
871 pipeline_it->second.push_constant_stages,
872 offset,
873 static_cast<uint32_t>(size),
874 data);
875}
876
878 CommandBufferID cmd_id,
879 uint32_t vertex_count,
880 uint32_t instance_count,
881 uint32_t first_vertex,
882 uint32_t first_instance)
883{
885 if (!cmd) {
887 "Invalid command buffer ID: {}", cmd_id);
888 return;
889 }
890
891 cmd.draw(vertex_count, instance_count, first_vertex, first_instance);
892}
893
895 CommandBufferID cmd_id,
896 uint32_t index_count,
897 uint32_t instance_count,
898 uint32_t first_index,
899 int32_t vertex_offset,
900 uint32_t first_instance)
901{
903 if (!cmd) {
904 return;
905 }
906
907 cmd.drawIndexed(index_count, instance_count, first_index,
908 vertex_offset, first_instance);
909}
910
912 CommandBufferID cmd_id,
913 uint32_t group_count_x,
914 uint32_t group_count_y,
915 uint32_t group_count_z)
916{
918 if (!cmd) {
920 "Invalid command buffer ID: {}", cmd_id);
921 return;
922 }
923
924 cmd.drawMeshTasksEXT(group_count_x, group_count_y, group_count_z);
925}
926
928 CommandBufferID cmd_id,
929 const std::shared_ptr<Buffers::VKBuffer>& buffer,
930 vk::DeviceSize offset,
931 uint32_t draw_count,
932 uint32_t stride)
933{
935 if (!cmd || !buffer)
936 return;
937
938 cmd.drawMeshTasksIndirectEXT(buffer->get_buffer(), offset, draw_count, stride);
939}
940
941//==========================================================================
942// Window Rendering Registration
943//==========================================================================
944
945void RenderFlow::register_window_for_rendering(const std::shared_ptr<Core::Window>& window)
946{
947 if (!window) {
949 "Cannot register null window");
950 return;
951 }
952
953 if (!window->is_graphics_registered()) {
955 "Window '{}' not registered with graphics backend yet.",
956 window->get_create_info().title);
957 }
958
959 if (!m_window_associations.contains(window)) {
960 WindowRenderAssociation association;
961 association.window = window;
962 m_window_associations[window] = std::move(association);
963
965 "Registered window '{}' for dynamic rendering",
966 window->get_create_info().title);
967 }
968}
969
970void RenderFlow::unregister_window(const std::shared_ptr<Core::Window>& window)
971{
972 if (!window) {
973 return;
974 }
975
976 auto it = m_window_associations.find(window);
977
978 if (it != m_window_associations.end()) {
979 m_window_associations.erase(it);
980
982 "Unregistered window '{}' from rendering",
983 window->get_create_info().title);
984 }
985}
986
987bool RenderFlow::is_window_registered(const std::shared_ptr<Core::Window>& window) const
988{
989 return window && m_window_associations.contains(window);
990}
991
992std::vector<std::shared_ptr<Core::Window>> RenderFlow::get_registered_windows() const
993{
994 std::vector<std::shared_ptr<Core::Window>> windows;
995 windows.reserve(m_window_associations.size());
996
997 for (const auto& [key, association] : m_window_associations) {
998 if (auto window = association.window.lock()) {
999 windows.push_back(window);
1000 }
1001 }
1002
1003 return windows;
1004}
1005
1006vk::ImageView RenderFlow::get_current_image_view(const std::shared_ptr<Core::Window>& window)
1007{
1008 auto view_ptr = m_display_service->get_current_image_view(window);
1009 if (!view_ptr) {
1010 return nullptr;
1011 }
1012 return *static_cast<vk::ImageView*>(view_ptr);
1013}
1014
1015//==============================================================================
1016// Convenience Methods
1017//==============================================================================
1018
1020 RenderPipelineID pipeline_id,
1021 uint32_t first_layout_index)
1022{
1023 auto pipeline_it = m_pipelines.find(pipeline_id);
1024 if (pipeline_it == m_pipelines.end()) {
1026 "Invalid pipeline ID: {}", pipeline_id);
1027 return {};
1028 }
1029
1030 const auto& layouts = pipeline_it->second.layouts;
1031 std::vector<DescriptorSetID> descriptor_set_ids;
1032
1033 for (uint32_t i = first_layout_index; i < layouts.size(); ++i) {
1034 auto ds_id = m_shader_foundry->allocate_descriptor_set(layouts[i]);
1035 if (ds_id == INVALID_DESCRIPTOR_SET) {
1037 "Failed to allocate descriptor set for pipeline {}", pipeline_id);
1038 return {};
1039 }
1040 descriptor_set_ids.push_back(ds_id);
1041 }
1042
1044 "Allocated {} descriptor sets for pipeline {} (starting at layout index {})",
1045 descriptor_set_ids.size(), pipeline_id, first_layout_index);
1046
1047 return descriptor_set_ids;
1048}
1049
1050} // namespace MayaFlux::Portal::Graphics
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_RT_ERROR(comp, ctx,...)
#define MF_TRACE(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
vk::CommandBuffer cmd
size_t b
uint32_t width
Range size
void bind_pipeline(CommandBufferID cmd_id, RenderPipelineID pipeline)
Bind graphics pipeline.
void push_constants(CommandBufferID cmd_id, RenderPipelineID pipeline, const void *data, size_t size, uint32_t offset=0)
Push constants.
void register_window_for_rendering(const std::shared_ptr< Core::Window > &window)
Register a window for dynamic rendering.
std::unordered_map< std::shared_ptr< Core::Window >, WindowRenderAssociation > m_window_associations
std::vector< DescriptorSetID > allocate_pipeline_descriptors(RenderPipelineID pipeline, uint32_t first_layout_index=0)
Allocate descriptor sets for pipeline.
void begin_rendering(CommandBufferID cmd_id, const std::shared_ptr< Core::Window > &window, vk::Image swapchain_image, const std::array< float, 4 > &clear_color=default_color, vk::ImageView depth_image_view=nullptr)
Begin dynamic rendering to a window.
vk::ImageView get_current_image_view(const std::shared_ptr< Core::Window > &window)
Get current image view for window.
void destroy_pipeline(RenderPipelineID pipeline_id)
Destroy a graphics pipeline.
void bind_vertex_buffers(CommandBufferID cmd_id, const std::vector< std::shared_ptr< Buffers::VKBuffer > > &buffers, uint32_t first_binding=0)
Bind vertex buffers.
vk::DescriptorSetLayout get_view_transform_layout(RenderPipelineID pipeline_id) const
Return the reserved ViewTransform UBO layout for a pipeline.
std::vector< std::shared_ptr< Core::Window > > get_registered_windows() const
Get all registered windows.
void end_rendering(CommandBufferID cmd_id, const std::shared_ptr< Core::Window > &window)
End dynamic rendering.
void draw_indexed(CommandBufferID cmd_id, uint32_t index_count, uint32_t instance_count=1, uint32_t first_index=0, int32_t vertex_offset=0, uint32_t first_instance=0)
Indexed draw command.
std::unordered_map< RenderPipelineID, PipelineState > m_pipelines
void bind_index_buffer(CommandBufferID cmd_id, const std::shared_ptr< Buffers::VKBuffer > &buffer, vk::IndexType index_type=vk::IndexType::eUint32)
Bind index buffer.
void draw_mesh_tasks(CommandBufferID cmd_id, uint32_t group_count_x, uint32_t group_count_y=1, uint32_t group_count_z=1)
Draw mesh tasks (mesh shading pipeline only)
bool is_window_registered(const std::shared_ptr< Core::Window > &window) const
Check if a window is registered for rendering.
void draw(CommandBufferID cmd_id, uint32_t vertex_count, uint32_t instance_count=1, uint32_t first_vertex=0, uint32_t first_instance=0)
Draw command.
void draw_mesh_tasks_indirect(CommandBufferID cmd_id, const std::shared_ptr< Buffers::VKBuffer > &buffer, vk::DeviceSize offset=0, uint32_t draw_count=1, uint32_t stride=sizeof(VkDrawMeshTasksIndirectCommandEXT))
Draw mesh tasks indirect.
void unregister_window(const std::shared_ptr< Core::Window > &window)
Unregister a window from rendering.
Registry::Service::DisplayService * m_display_service
std::atomic< uint64_t > m_next_pipeline_id
RenderPipelineID create_pipeline(const RenderPipelineConfig &config, const std::vector< vk::Format > &color_formats, vk::Format depth_format=vk::Format::eUndefined)
Create graphics pipeline for dynamic rendering (no render pass object)
void bind_descriptor_sets(CommandBufferID cmd_id, RenderPipelineID pipeline, const std::vector< DescriptorSetID > &descriptor_sets, uint32_t first_set=0)
Bind descriptor sets.
vk::DescriptorSet get_descriptor_set(DescriptorSetID descriptor_set_id)
Get Vulkan descriptor set handle from DescriptorSetID.
vk::Device get_device() const
Get logical device handle.
std::shared_ptr< Core::VKShaderModule > get_vk_shader_module(ShaderID shader_id)
bool is_initialized() const
Check if compiler is initialized.
vk::CommandBuffer get_command_buffer(CommandBufferID cmd_id)
Get Vulkan command buffer handle from CommandBufferID.
DescriptorSetID allocate_descriptor_set(vk::DescriptorSetLayout layout)
Allocate descriptor set for a pipeline.
static std::pair< std::vector< Core::VertexBinding >, std::vector< Core::VertexAttribute > > translate_layout(const Kakshya::VertexLayout &layout, uint32_t binding_index=0)
Translate a semantic vertex layout to Vulkan binding/attribute descriptions.
Interface * get_service()
Query for a backend service.
static BackendRegistry & instance()
Get the global registry instance.
@ Rendering
GPU rendering operations (graphics pipeline, frame rendering)
@ Portal
High-level user-facing API layer.
PolygonMode
Rasterization polygon mode.
constexpr RenderPipelineID INVALID_RENDER_PIPELINE
const std::array< float, 4 > default_color
constexpr ShaderID INVALID_SHADER
BlendOp
Blending operation.
PrimitiveTopology
Vertex assembly primitive topology.
CompareOp
Depth/stencil comparison operation.
constexpr DescriptorSetID INVALID_DESCRIPTOR_SET
std::vector< ColorBlendAttachment > color_blend_attachments
std::vector< vk::Format > color_attachment_formats
std::shared_ptr< VKShaderModule > task_shader
std::shared_ptr< VKShaderModule > fragment_shader
std::shared_ptr< VKShaderModule > mesh_shader
std::vector< vk::DynamicState > dynamic_states
std::vector< vk::PushConstantRange > push_constant_ranges
std::shared_ptr< VKShaderModule > vertex_shader
std::vector< vk::DescriptorSetLayout > descriptor_set_layouts
std::vector< VertexBinding > vertex_bindings
std::vector< VertexAttribute > vertex_attributes
std::shared_ptr< VKShaderModule > geometry_shader
std::shared_ptr< VKShaderModule > tess_evaluation_shader
std::shared_ptr< VKShaderModule > tess_control_shader
Configuration for creating a graphics pipeline.
std::vector< vk::DescriptorSetLayout > layouts
std::shared_ptr< Core::VKGraphicsPipeline > pipeline
std::vector< std::vector< DescriptorBindingInfo > > descriptor_sets
std::vector< Core::VertexAttribute > vertex_attributes
std::vector< Core::VertexBinding > vertex_bindings
std::optional< Kakshya::VertexLayout > semantic_vertex_layout
std::vector< BlendAttachmentConfig > blend_attachments
Complete render pipeline configuration.
std::function< void *(const std::shared_ptr< void > &)> get_current_image_view
Get current swapchain image view for rendering.
std::function< void(const std::shared_ptr< void > &, uint32_t &, uint32_t &)> get_swapchain_extent
Get swapchain extent for a window.
Backend display and presentation service interface.