MayaFlux 0.1.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
9
13
15
17
18bool RenderFlow::s_initialized = false;
19
20namespace {
21
22 /**
23 * @brief Translate semantic VertexLayout to Vulkan bindings/attributes
24 * @param layout Semantic vertex layout
25 * @return Tuple of (bindings, attributes)
26 */
27 static std::pair<
28 std::vector<Core::VertexBinding>,
29 std::vector<Core::VertexAttribute>>
30 translate_semantic_layout(const Kakshya::VertexLayout& layout)
31 {
32 using namespace MayaFlux::Portal::Graphics;
33
34 auto [vk_bindings, vk_attributes] = VertexLayoutTranslator::translate_layout(layout);
35
37 "Translated semantic vertex layout: {} bindings, {} attributes",
38 vk_bindings.size(), vk_attributes.size());
39
40 return { vk_bindings, vk_attributes };
41 }
42
43} // anonymous namespace
44
45//==============================================================================
46// Initialization
47//==============================================================================
48
50{
51 if (s_initialized) {
53 "RenderFlow already initialized (static flag)");
54 return true;
55 }
56
57 if (m_shader_foundry) {
59 "RenderFlow already initialized");
60 return true;
61 }
62
66 "ShaderFoundry must be initialized before RenderFlow");
67 return false;
68 }
69
72
73 if (!m_display_service) {
75 "DisplayService not found in BackendRegistry");
76 return false;
77 }
78
79 s_initialized = true;
80
82 "RenderFlow initialized");
83 return true;
84}
85
87{
88 if (!s_initialized) {
89 return;
90 }
91
93 "Shutting down RenderFlow...");
94
97 "Cannot shutdown RenderFlow: ShaderFoundry not initialized");
98 return;
99 }
100
101 auto device = m_shader_foundry->get_device();
102
103 if (!device) {
105 "Cannot shutdown RenderFlow: Vulkan device is null");
106 return;
107 }
108
109 for (auto& [id, state] : m_pipelines) {
110 if (state.pipeline) {
111 state.pipeline->cleanup(device);
112 }
113
114 if (state.layout) {
115 device.destroyPipelineLayout(state.layout);
116 }
117 }
118 m_pipelines.clear();
119
120 for (auto& [id, state] : m_render_passes) {
121 if (state.render_pass) {
122 state.render_pass->cleanup(device);
123 }
124 }
125 m_render_passes.clear();
126
127 m_window_associations.clear();
128
129 m_shader_foundry = nullptr;
130 m_display_service = nullptr;
131
132 s_initialized = false;
133
135 "RenderFlow shutdown complete");
136}
137
138//==============================================================================
139// Utility Conversions (Portal enums → Vulkan enums)
140//==============================================================================
141
142namespace {
143
144 vk::PrimitiveTopology to_vk_topology(PrimitiveTopology topology)
145 {
146 switch (topology) {
148 return vk::PrimitiveTopology::ePointList;
150 return vk::PrimitiveTopology::eLineList;
152 return vk::PrimitiveTopology::eLineStrip;
154 return vk::PrimitiveTopology::eTriangleList;
156 return vk::PrimitiveTopology::eTriangleStrip;
158 return vk::PrimitiveTopology::eTriangleFan;
159 default:
160 return vk::PrimitiveTopology::eTriangleList;
161 }
162 }
163
164 vk::PolygonMode to_vk_polygon_mode(PolygonMode mode)
165 {
166 switch (mode) {
168 return vk::PolygonMode::eFill;
170 return vk::PolygonMode::eLine;
172 return vk::PolygonMode::ePoint;
173 default:
174 return vk::PolygonMode::eFill;
175 }
176 }
177
178 vk::CullModeFlags to_vk_cull_mode(CullMode mode)
179 {
180 switch (mode) {
181 case CullMode::NONE:
182 return vk::CullModeFlagBits::eNone;
183 case CullMode::FRONT:
184 return vk::CullModeFlagBits::eFront;
185 case CullMode::BACK:
186 return vk::CullModeFlagBits::eBack;
188 return vk::CullModeFlagBits::eFrontAndBack;
189 default:
190 return vk::CullModeFlagBits::eBack;
191 }
192 }
193
194 vk::CompareOp to_vk_compare_op(CompareOp op)
195 {
196 switch (op) {
197 case CompareOp::NEVER:
198 return vk::CompareOp::eNever;
199 case CompareOp::LESS:
200 return vk::CompareOp::eLess;
201 case CompareOp::EQUAL:
202 return vk::CompareOp::eEqual;
204 return vk::CompareOp::eLessOrEqual;
206 return vk::CompareOp::eGreater;
208 return vk::CompareOp::eNotEqual;
210 return vk::CompareOp::eGreaterOrEqual;
212 return vk::CompareOp::eAlways;
213 default:
214 return vk::CompareOp::eLess;
215 }
216 }
217
218 vk::BlendFactor to_vk_blend_factor(BlendFactor factor)
219 {
220 switch (factor) {
222 return vk::BlendFactor::eZero;
223 case BlendFactor::ONE:
224 return vk::BlendFactor::eOne;
226 return vk::BlendFactor::eSrcColor;
228 return vk::BlendFactor::eOneMinusSrcColor;
230 return vk::BlendFactor::eDstColor;
232 return vk::BlendFactor::eOneMinusDstColor;
234 return vk::BlendFactor::eSrcAlpha;
236 return vk::BlendFactor::eOneMinusSrcAlpha;
238 return vk::BlendFactor::eDstAlpha;
240 return vk::BlendFactor::eOneMinusDstAlpha;
241 default:
242 return vk::BlendFactor::eOne;
243 }
244 }
245
246 vk::BlendOp to_vk_blend_op(BlendOp op)
247 {
248 switch (op) {
249 case BlendOp::ADD:
250 return vk::BlendOp::eAdd;
252 return vk::BlendOp::eSubtract;
254 return vk::BlendOp::eReverseSubtract;
255 case BlendOp::MIN:
256 return vk::BlendOp::eMin;
257 case BlendOp::MAX:
258 return vk::BlendOp::eMax;
259 default:
260 return vk::BlendOp::eAdd;
261 }
262 }
263} // anonymous namespace
264
265//==============================================================================
266// Render Pass Management
267//==============================================================================
268
270 const std::vector<RenderPassAttachment>& attachments)
271{
272 if (!is_initialized()) {
274 "RenderFlow not initialized");
275 return INVALID_RENDER_PASS;
276 }
277
278 if (attachments.empty()) {
280 "Cannot create render pass with no attachments");
281 return INVALID_RENDER_PASS;
282 }
283
284 auto render_pass = std::make_shared<Core::VKRenderPass>();
285
286 Core::RenderPassCreateInfo create_info {};
287
288 for (const auto& att : attachments) {
290 desc.format = att.format;
291 desc.samples = att.samples;
292 desc.load_op = att.load_op;
293 desc.store_op = att.store_op;
294 desc.initial_layout = att.initial_layout;
295 desc.final_layout = att.final_layout;
296 create_info.attachments.push_back(desc);
297 }
298
300 subpass.bind_point = vk::PipelineBindPoint::eGraphics;
301 for (uint32_t i = 0; i < attachments.size(); ++i) {
302 vk::AttachmentReference ref;
303 ref.attachment = i;
304 ref.layout = vk::ImageLayout::eColorAttachmentOptimal;
305 subpass.color_attachments.push_back(ref);
306 }
307 create_info.subpasses.push_back(subpass);
308
309 // create_info.dependencies.emplace_back(
310 // VK_SUBPASS_EXTERNAL, 0,
311 // vk::PipelineStageFlagBits::eColorAttachmentOutput,
312 // vk::PipelineStageFlagBits::eColorAttachmentOutput,
313 // vk::AccessFlagBits::eColorAttachmentWrite,
314 // vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite);
315
316 // create_info.dependencies.emplace_back(
317 // 0, VK_SUBPASS_EXTERNAL,
318 // vk::PipelineStageFlagBits::eColorAttachmentOutput,
319 // vk::PipelineStageFlagBits::eBottomOfPipe,
320 // vk::AccessFlagBits::eColorAttachmentWrite,
321 // vk::AccessFlagBits::eMemoryRead);
322
323 if (!render_pass->create(m_shader_foundry->get_device(), create_info)) {
325 "Failed to create VKRenderPass");
326 return INVALID_RENDER_PASS;
327 }
328
329 auto render_pass_id = m_next_render_pass_id.fetch_add(1);
330 RenderPassState state;
331 state.render_pass = render_pass;
332 state.attachments = attachments;
333 m_render_passes[render_pass_id] = std::move(state);
334
336 "Render pass created (ID: {}, {} attachments)",
337 render_pass_id, attachments.size());
338
339 return render_pass_id;
340}
341
343 vk::Format format,
344 bool load_clear)
345{
346 RenderPassAttachment color_attachment;
347 color_attachment.format = format;
348 color_attachment.load_op = load_clear ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad;
349 color_attachment.store_op = vk::AttachmentStoreOp::eStore;
350 color_attachment.initial_layout = vk::ImageLayout::eUndefined;
351 color_attachment.final_layout = vk::ImageLayout::ePresentSrcKHR;
352
353 return create_render_pass({ color_attachment });
354}
355
357{
358 auto it = m_render_passes.find(render_pass_id);
359 if (it == m_render_passes.end()) {
360 return;
361 }
362
363 if (it->second.render_pass) {
364 it->second.render_pass->cleanup(m_shader_foundry->get_device());
365 }
366
367 m_render_passes.erase(it);
368
370 "Destroyed render pass (ID: {})", render_pass_id);
371}
372
373//==============================================================================
374// Pipeline Creation
375//==============================================================================
376
378{
379 if (!is_initialized()) {
381 "RenderFlow not initialized");
383 }
384
385 if (config.vertex_shader == INVALID_SHADER) {
387 "Vertex shader required for graphics pipeline");
389 }
390
391 if (config.render_pass == INVALID_RENDER_PASS) {
393 "Render pass required for graphics pipeline");
394 return INVALID_RENDER_PASS;
395 }
396
397 auto rp_it = m_render_passes.find(config.render_pass);
398 if (rp_it == m_render_passes.end()) {
400 "Invalid render pass ID: {}", config.render_pass);
402 }
403
405
407 if (config.fragment_shader != INVALID_SHADER) {
409 }
410 if (config.geometry_shader != INVALID_SHADER) {
412 }
413 if (config.tess_control_shader != INVALID_SHADER) {
415 }
416 if (config.tess_eval_shader != INVALID_SHADER) {
418 }
419
420 if (config.semantic_vertex_layout.has_value()) {
422 "Pipeline using semantic VertexLayout ({} vertices, {} attributes)",
423 config.semantic_vertex_layout->vertex_count,
424 config.semantic_vertex_layout->attributes.size());
425
426 auto [vk_bindings, vk_attributes] = translate_semantic_layout(
427 config.semantic_vertex_layout.value());
428
429 vk_config.vertex_bindings = vk_bindings;
430 vk_config.vertex_attributes = vk_attributes;
431 vk_config.use_vertex_shader_reflection = false;
432
433 } else if (!config.vertex_bindings.empty() || !config.vertex_attributes.empty()) {
435 "Pipeline using explicit vertex config ({} bindings, {} attributes)",
436 config.vertex_bindings.size(), config.vertex_attributes.size());
437
438 for (const auto& binding : config.vertex_bindings) {
439 Core::VertexBinding vk_binding {};
440 vk_binding.binding = binding.binding;
441 vk_binding.stride = binding.stride;
442 vk_binding.input_rate = binding.per_instance ? vk::VertexInputRate::eInstance : vk::VertexInputRate::eVertex;
443 vk_config.vertex_bindings.push_back(vk_binding);
444 }
445
446 for (const auto& attr : config.vertex_attributes) {
447 Core::VertexAttribute vk_attr {};
448 vk_attr.location = attr.location;
449 vk_attr.binding = attr.binding;
450 vk_attr.format = attr.format;
451 vk_attr.offset = attr.offset;
452 vk_config.vertex_attributes.push_back(vk_attr);
453 }
454
455 vk_config.use_vertex_shader_reflection = false;
456 } else {
458 "Pipeline will use shader reflection for vertex input");
460 }
461
462 vk_config.topology = to_vk_topology(config.topology);
463 vk_config.primitive_restart_enable = false;
464
465 vk_config.polygon_mode = to_vk_polygon_mode(config.rasterization.polygon_mode);
466 vk_config.cull_mode = to_vk_cull_mode(config.rasterization.cull_mode);
467 vk_config.front_face = config.rasterization.front_face_ccw ? vk::FrontFace::eCounterClockwise : vk::FrontFace::eClockwise;
468 vk_config.line_width = config.rasterization.line_width;
470 vk_config.depth_bias_enable = config.rasterization.depth_bias;
471
474 vk_config.depth_compare_op = to_vk_compare_op(config.depth_stencil.depth_compare_op);
476
477 for (const auto& blend : config.blend_attachments) {
479 vk_blend.blend_enable = blend.blend_enable;
480 vk_blend.src_color_blend_factor = to_vk_blend_factor(blend.src_color_factor);
481 vk_blend.dst_color_blend_factor = to_vk_blend_factor(blend.dst_color_factor);
482 vk_blend.color_blend_op = to_vk_blend_op(blend.color_blend_op);
483 vk_blend.src_alpha_blend_factor = to_vk_blend_factor(blend.src_alpha_factor);
484 vk_blend.dst_alpha_blend_factor = to_vk_blend_factor(blend.dst_alpha_factor);
485 vk_blend.alpha_blend_op = to_vk_blend_op(blend.alpha_blend_op);
486 vk_config.color_blend_attachments.push_back(vk_blend);
487 }
488
489 std::vector<vk::DescriptorSetLayout> layouts;
490 for (const auto& desc_set : config.descriptor_sets) {
491 std::vector<vk::DescriptorSetLayoutBinding> bindings;
492 for (const auto& binding : desc_set) {
493 vk::DescriptorSetLayoutBinding vk_binding;
494 vk_binding.binding = binding.binding;
495 vk_binding.descriptorType = binding.type;
496 vk_binding.descriptorCount = 1;
497 vk_binding.stageFlags = vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment;
498 bindings.push_back(vk_binding);
499 }
500
501 vk::DescriptorSetLayoutCreateInfo layout_info;
502 layout_info.bindingCount = static_cast<uint32_t>(bindings.size());
503 layout_info.pBindings = bindings.data();
504
505 auto layout = m_shader_foundry->get_device().createDescriptorSetLayout(layout_info);
506 layouts.push_back(layout);
507 }
508 vk_config.descriptor_set_layouts = layouts;
509
510 if (config.push_constant_size > 0) {
511 vk::PushConstantRange range;
512 range.stageFlags = vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment;
513 range.offset = 0;
514 range.size = static_cast<uint32_t>(config.push_constant_size);
515 vk_config.push_constant_ranges.push_back(range);
516 }
517
518 vk_config.render_pass = rp_it->second.render_pass->get();
519 vk_config.subpass = config.subpass;
520
521 vk_config.dynamic_states = {
522 vk::DynamicState::eViewport,
523 vk::DynamicState::eScissor
524 };
525
526 auto pipeline = std::make_shared<Core::VKGraphicsPipeline>();
527 if (!pipeline->create(m_shader_foundry->get_device(), vk_config)) {
529 "Failed to create VKGraphicsPipeline");
530
531 for (auto layout : layouts) {
532 m_shader_foundry->get_device().destroyDescriptorSetLayout(layout);
533 }
535 }
536
537 auto pipeline_id = m_next_pipeline_id.fetch_add(1);
538 PipelineState state;
539 state.shader_ids = { config.vertex_shader, config.fragment_shader };
540 state.pipeline = pipeline;
541 state.layouts = layouts;
542 state.layout = pipeline->get_layout();
543 state.render_pass = config.render_pass;
544 m_pipelines[pipeline_id] = std::move(state);
545
547 "Graphics pipeline created (ID: {}, {} descriptor sets)",
548 pipeline_id, layouts.size());
549
550 return pipeline_id;
551}
552
554 ShaderID vertex_shader,
555 ShaderID fragment_shader,
556 RenderPassID render_pass)
557{
559 config.vertex_shader = vertex_shader;
560 config.fragment_shader = fragment_shader;
561 config.render_pass = render_pass;
562
566 config.rasterization.front_face_ccw = true;
567 config.depth_stencil.depth_test_enable = false;
568 config.depth_stencil.depth_write_enable = true;
569
570 config.blend_attachments.emplace_back();
571
572 return create_pipeline(config);
573}
574
576{
577 auto it = m_pipelines.find(pipeline_id);
578 if (it == m_pipelines.end()) {
579 return;
580 }
581
582 auto device = m_shader_foundry->get_device();
583
584 if (it->second.pipeline) {
585 it->second.pipeline->cleanup(device);
586 }
587
588 if (it->second.layout) {
589 device.destroyPipelineLayout(it->second.layout);
590 }
591
592 for (auto layout : it->second.layouts) {
593 if (layout) {
594 device.destroyDescriptorSetLayout(layout);
595 }
596 }
597
598 m_pipelines.erase(it);
599
601 "Destroyed graphics pipeline (ID: {})", pipeline_id);
602}
603
604//==============================================================================
605// Command Recording
606//==============================================================================
607
609 CommandBufferID cmd_id,
610 const std::shared_ptr<Core::Window>& window,
611 const std::array<float, 4>& clear_color)
612{
613 auto cmd = m_shader_foundry->get_command_buffer(cmd_id);
614 if (!cmd) {
616 "Invalid command buffer ID: {}", cmd_id);
617 return;
618 }
619
620 if (!window) {
622 "Cannot begin render pass for null window");
623 return;
624 }
625
626 auto assoc_it = m_window_associations.find(window);
627 if (assoc_it == m_window_associations.end()) {
629 "Window '{}' not registered for rendering. "
630 "Call register_window_for_rendering() first.",
631 window->get_create_info().title);
632 return;
633 }
634
635 auto rp_it = m_render_passes.find(assoc_it->second.render_pass_id);
636 if (rp_it == m_render_passes.end()) {
638 "Invalid render pass ID: {}", assoc_it->second.render_pass_id);
639 return;
640 }
641
643 if (!fb_handle) {
645 "No framebuffer available for window '{}'. "
646 "Ensure window is registered with GraphicsSubsystem.",
647 window->get_create_info().title);
648 return;
649 }
650
651 uint32_t width = 0, height = 0;
652 m_display_service->get_swapchain_extent(window, width, height);
653
654 if (width == 0 || height == 0) {
656 "Invalid swapchain extent for window '{}': {}x{}",
657 window->get_create_info().title, width, height);
658 return;
659 }
660
661 vk::RenderPassBeginInfo begin_info;
662 begin_info.renderPass = rp_it->second.render_pass->get();
663 begin_info.framebuffer = fb_handle->get();
664 begin_info.renderArea.offset = vk::Offset2D { 0, 0 };
665 begin_info.renderArea.extent = vk::Extent2D { width, height };
666
667 std::vector<vk::ClearValue> clear_values(rp_it->second.attachments.size());
668 for (auto& clear_value : clear_values) {
669 clear_value.color = vk::ClearColorValue(clear_color);
670 // clear_value.color = vk::ClearColorValue(std::array<float, 4> { 0.2f, 0.2f, 0.2f, 1.0f }); // Gray
671 }
672
673 begin_info.clearValueCount = static_cast<uint32_t>(clear_values.size());
674 begin_info.pClearValues = clear_values.data();
675
676 cmd.beginRenderPass(begin_info, vk::SubpassContents::eInline);
677
679 "Began render pass for window '{}' ({}x{})",
680 window->get_create_info().title, width, height);
681}
682
684{
685 auto cmd = m_shader_foundry->get_command_buffer(cmd_id);
686 if (!cmd) {
688 "Invalid command buffer ID: {}", cmd_id);
689 return;
690 }
691
692 cmd.endRenderPass();
693}
694
696{
697 auto pipeline_it = m_pipelines.find(pipeline_id);
698 if (pipeline_it == m_pipelines.end()) {
700 "Invalid pipeline ID: {}", pipeline_id);
701 return;
702 }
703
704 auto cmd = m_shader_foundry->get_command_buffer(cmd_id);
705 if (!cmd) {
707 "Invalid command buffer ID: {}", cmd_id);
708 return;
709 }
710
711 pipeline_it->second.pipeline->bind(cmd);
712}
713
715 CommandBufferID cmd_id,
716 const std::vector<std::shared_ptr<Buffers::VKBuffer>>& buffers,
717 uint32_t first_binding)
718{
719 auto cmd = m_shader_foundry->get_command_buffer(cmd_id);
720 if (!cmd) {
722 "Invalid command buffer ID: {}", cmd_id);
723 return;
724 }
725
726 std::vector<vk::Buffer> vk_buffers;
727 std::vector<vk::DeviceSize> offsets(buffers.size(), 0);
728
729 vk_buffers.reserve(buffers.size());
730 for (const auto& buf : buffers) {
731 vk_buffers.push_back(buf->get_buffer());
732
733 /* void* mapped = buf->get_mapped_ptr();
734 if (mapped) {
735 float* data = reinterpret_cast<float*>(mapped);
736 MF_PRINT(Journal::Component::Portal, Journal::Context::Rendering,
737 "BIND_VERTEX: All vertex data:");
738 for (int v = 0; v < 3; ++v) {
739 int offset = v * 6; // 24 bytes / 4 bytes per float = 6 floats per vertex
740 MF_PRINT(Journal::Component::Portal, Journal::Context::Rendering,
741 " Vertex {}: pos=({}, {}, {}), color=({}, {}, {})",
742 v,
743 data[offset], data[offset + 1], data[offset + 2],
744 data[offset + 3], data[offset + 4], data[offset + 5]);
745 }
746 } else {
747 MF_RT_ERROR(Journal::Component::Portal, Journal::Context::Rendering,
748 "BIND_VERTEX: Buffer not host-mapped!");
749 } */
750 }
751
752 cmd.bindVertexBuffers(first_binding, vk_buffers, offsets);
753}
754
756 CommandBufferID cmd_id,
757 const std::shared_ptr<Buffers::VKBuffer>& buffer,
758 vk::IndexType index_type)
759{
760 auto cmd = m_shader_foundry->get_command_buffer(cmd_id);
761 if (!cmd) {
763 "Invalid command buffer ID: {}", cmd_id);
764 return;
765 }
766
767 cmd.bindIndexBuffer(buffer->get_buffer(), 0, index_type);
768}
769
771 CommandBufferID cmd_id,
772 RenderPipelineID pipeline_id,
773 const std::vector<DescriptorSetID>& descriptor_sets)
774{
775 auto pipeline_it = m_pipelines.find(pipeline_id);
776 if (pipeline_it == m_pipelines.end()) {
778 "Invalid pipeline ID: {}", pipeline_id);
779 return;
780 }
781
782 auto cmd = m_shader_foundry->get_command_buffer(cmd_id);
783 if (!cmd) {
785 "Invalid command buffer ID: {}", cmd_id);
786 return;
787 }
788
789 std::vector<vk::DescriptorSet> vk_sets;
790 vk_sets.reserve(descriptor_sets.size());
791 for (auto ds_id : descriptor_sets) {
792 vk_sets.push_back(m_shader_foundry->get_descriptor_set(ds_id));
793 }
794
795 cmd.bindDescriptorSets(
796 vk::PipelineBindPoint::eGraphics,
797 pipeline_it->second.layout,
798 0,
799 vk_sets,
800 nullptr);
801}
802
804 CommandBufferID cmd_id,
805 RenderPipelineID pipeline_id,
806 const void* data,
807 size_t size)
808{
809 auto pipeline_it = m_pipelines.find(pipeline_id);
810 if (pipeline_it == m_pipelines.end()) {
812 "Invalid pipeline ID: {}", pipeline_id);
813 return;
814 }
815
816 auto cmd = m_shader_foundry->get_command_buffer(cmd_id);
817 if (!cmd) {
819 "Invalid command buffer ID: {}", cmd_id);
820 return;
821 }
822
823 cmd.pushConstants(
824 pipeline_it->second.layout,
825 vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment,
826 0,
827 static_cast<uint32_t>(size),
828 data);
829}
830
832 CommandBufferID cmd_id,
833 uint32_t vertex_count,
834 uint32_t instance_count,
835 uint32_t first_vertex,
836 uint32_t first_instance)
837{
838 auto cmd = m_shader_foundry->get_command_buffer(cmd_id);
839 if (!cmd) {
841 "Invalid command buffer ID: {}", cmd_id);
842 return;
843 }
844
845 cmd.draw(vertex_count, instance_count, first_vertex, first_instance);
846}
847
849 CommandBufferID cmd_id,
850 uint32_t index_count,
851 uint32_t instance_count,
852 uint32_t first_index,
853 int32_t vertex_offset,
854 uint32_t first_instance)
855{
856 auto cmd = m_shader_foundry->get_command_buffer(cmd_id);
857 if (!cmd) {
858 return;
859 }
860
861 cmd.drawIndexed(index_count, instance_count, first_index,
862 vertex_offset, first_instance);
863}
864
866 CommandBufferID cmd_id,
867 const std::shared_ptr<Core::Window>& window)
868{
869 auto cmd = m_shader_foundry->get_command_buffer(cmd_id);
870 if (!cmd) {
872 "Invalid command buffer ID: {}", cmd_id);
873 return;
874 }
875
876 if (!window) {
878 "Cannot present rendered image for null window");
879 return;
880 }
881
882 try {
883 cmd.end();
884 } catch (const std::exception& e) {
886 "Failed to end command buffer: {}", e.what());
887 return;
888 }
889
890 uint64_t cmd_bits = *reinterpret_cast<uint64_t*>(&cmd);
891 m_display_service->present_frame(window, cmd_bits);
892}
893
894//==========================================================================
895// Window Rendering Registration
896//==========================================================================
897
899 const std::shared_ptr<Core::Window>& window,
900 RenderPassID render_pass_id)
901{
902 if (!window) {
904 "Cannot register null window");
905 return;
906 }
907
908 auto rp_it = m_render_passes.find(render_pass_id);
909 if (rp_it == m_render_passes.end()) {
911 "Invalid render pass ID: {}", render_pass_id);
912 return;
913 }
914
915 if (!window->is_graphics_registered()) {
917 "Window '{}' not registered with graphics backend yet. "
918 "Ensure GraphicsSubsystem has registered this window.",
919 window->get_create_info().title);
920 }
921
922 if (!m_display_service->attach_render_pass(window, rp_it->second.render_pass)) {
924 "Failed to attach render pass to window '{}'",
925 window->get_create_info().title);
926 return;
927 }
928
929 WindowRenderAssociation association;
930 association.window = window;
931 association.render_pass_id = render_pass_id;
932
933 m_window_associations[window] = std::move(association);
934
936 "Registered window '{}' for rendering with render pass ID {}",
937 window->get_create_info().title,
938 render_pass_id);
939}
940
941void RenderFlow::unregister_window(const std::shared_ptr<Core::Window>& window)
942{
943 if (!window) {
944 return;
945 }
946
947 auto it = m_window_associations.find(window);
948
949 if (it != m_window_associations.end()) {
950 m_window_associations.erase(it);
951
953 "Unregistered window '{}' from rendering",
954 window->get_create_info().title);
955 }
956}
957
958bool RenderFlow::is_window_registered(const std::shared_ptr<Core::Window>& window) const
959{
960 return window && m_window_associations.contains(window);
961}
962
963std::vector<std::shared_ptr<Core::Window>> RenderFlow::get_registered_windows() const
964{
965 std::vector<std::shared_ptr<Core::Window>> windows;
966 windows.reserve(m_window_associations.size());
967
968 for (const auto& [key, association] : m_window_associations) {
969 if (auto window = association.window.lock()) {
970 windows.push_back(window);
971 }
972 }
973
974 return windows;
975}
976
977//==============================================================================
978// Convenience Methods
979//==============================================================================
980
981std::vector<DescriptorSetID> RenderFlow::allocate_pipeline_descriptors(
982 RenderPipelineID pipeline_id)
983{
984 auto pipeline_it = m_pipelines.find(pipeline_id);
985 if (pipeline_it == m_pipelines.end()) {
987 "Invalid pipeline ID: {}", pipeline_id);
988 return {};
989 }
990
991 std::vector<DescriptorSetID> descriptor_set_ids;
992 for (const auto& layout : pipeline_it->second.layouts) {
993 auto ds_id = m_shader_foundry->allocate_descriptor_set(layout);
994 if (ds_id == INVALID_DESCRIPTOR_SET) {
996 "Failed to allocate descriptor set for pipeline {}", pipeline_id);
997 return {};
998 }
999 descriptor_set_ids.push_back(ds_id);
1000 }
1001
1003 "Allocated {} descriptor sets for pipeline {}",
1004 descriptor_set_ids.size(), pipeline_id);
1005
1006 return descriptor_set_ids;
1007}
1008
1009} // 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::Framebuffer get() const
Get the framebuffer handle.
Wrapper for Vulkan framebuffer.
void bind_pipeline(CommandBufferID cmd_id, RenderPipelineID pipeline)
Bind graphics pipeline.
std::unordered_map< RenderPassID, RenderPassState > m_render_passes
void begin_render_pass(CommandBufferID cmd_id, const std::shared_ptr< Core::Window > &window, const std::array< float, 4 > &clear_color={ 0.0F, 0.0F, 0.0F, 1.0F })
Begin render pass.
void push_constants(CommandBufferID cmd_id, RenderPipelineID pipeline, const void *data, size_t size)
Push constants.
std::unordered_map< std::shared_ptr< Core::Window >, WindowRenderAssociation > m_window_associations
std::vector< DescriptorSetID > allocate_pipeline_descriptors(RenderPipelineID pipeline)
Allocate descriptor sets for pipeline.
void destroy_pipeline(RenderPipelineID pipeline_id)
RenderPassID create_simple_render_pass(vk::Format format=vk::Format::eB8G8R8A8Unorm, bool load_clear=true)
Create a simple single-color render pass.
RenderPassID create_render_pass(const std::vector< RenderPassAttachment > &attachments)
Create a render pass.
void present_rendered_image(CommandBufferID cmd_id, const std::shared_ptr< Core::Window > &window)
Present rendered image to window.
void destroy_render_pass(RenderPassID render_pass_id)
std::atomic< uint64_t > m_next_render_pass_id
void bind_vertex_buffers(CommandBufferID cmd_id, const std::vector< std::shared_ptr< Buffers::VKBuffer > > &buffers, uint32_t first_binding=0)
Bind vertex buffers.
std::vector< std::shared_ptr< Core::Window > > get_registered_windows() const
Get all registered windows.
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.
RenderPipelineID create_simple_pipeline(ShaderID vertex_shader, ShaderID fragment_shader, RenderPassID render_pass)
Create simple graphics pipeline (auto-configure most settings)
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 end_render_pass(CommandBufferID cmd_id)
End current render pass.
void bind_descriptor_sets(CommandBufferID cmd_id, RenderPipelineID pipeline, const std::vector< DescriptorSetID > &descriptor_sets)
Bind descriptor sets.
RenderPipelineID create_pipeline(const RenderPipelineConfig &config)
Create graphics pipeline with full configuration.
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 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
void register_window_for_rendering(const std::shared_ptr< Core::Window > &window, RenderPassID render_pass_id)
Associate a window with a render pass for rendering.
vk::DescriptorSet get_descriptor_set(DescriptorSetID descriptor_set_id)
Get Vulkan descriptor set handle from DescriptorSetID.
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
constexpr RenderPassID INVALID_RENDER_PASS
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::shared_ptr< VKShaderModule > fragment_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::AttachmentReference > color_attachments
std::vector< vk::DescriptorSetLayout > layouts
std::shared_ptr< Core::VKGraphicsPipeline > pipeline
std::shared_ptr< Core::VKRenderPass > render_pass
std::vector< RenderPassAttachment > attachments
Render pass attachment configuration.
std::vector< Core::VertexAttribute > vertex_attributes
std::vector< Core::VertexBinding > vertex_bindings
std::optional< Kakshya::VertexLayout > semantic_vertex_layout
std::vector< std::vector< DescriptorBindingConfig > > descriptor_sets
std::vector< BlendAttachmentConfig > blend_attachments
Complete render pipeline configuration.
std::function< void(const std::shared_ptr< void > &, uint64_t)> present_frame
Present a rendered frame to window.
std::function< void *(const std::shared_ptr< void > &)> get_current_framebuffer
Get current framebuffer for a window.
std::function< void(const std::shared_ptr< void > &, uint32_t &, uint32_t &)> get_swapchain_extent
Get swapchain extent for a window.
std::function< bool(const std::shared_ptr< void > &, const std::shared_ptr< void > &)> attach_render_pass
Attach a custom render pass to a window.
Backend display and presentation service interface.