MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
RenderProcessor.cpp
Go to the documentation of this file.
1#include "RenderProcessor.hpp"
2
7
12
13namespace MayaFlux::Buffers {
14
16{
17 auto info_it = m_buffer_info.find(buffer);
18 if (info_it == m_buffer_info.end()) {
19 const auto layout = buffer->get_vertex_layout();
20 if (!layout)
21 return nullptr;
22 info_it = m_buffer_info.try_emplace(buffer).first;
23 info_it->second.semantic_layout = *layout;
24 }
25 return &info_it->second;
26}
27
34
35void RenderProcessor::set_fragment_shader(const std::string& fragment_path)
36{
38 m_fragment_shader_id = foundry.load_shader(fragment_path, Portal::Graphics::ShaderStage::FRAGMENT);
40}
41
42void RenderProcessor::set_geometry_shader(const std::string& geometry_path)
43{
45 m_geometry_shader_id = foundry.load_shader(geometry_path, Portal::Graphics::ShaderStage::GEOMETRY);
47}
48
49void RenderProcessor::set_tess_control_shader(const std::string& tess_control_path)
50{
52 m_tess_control_shader_id = foundry.load_shader(tess_control_path, Portal::Graphics::ShaderStage::TESS_CONTROL);
54}
55
56void RenderProcessor::set_tess_eval_shader(const std::string& tess_eval_path)
57{
59 m_tess_eval_shader_id = foundry.load_shader(tess_eval_path, Portal::Graphics::ShaderStage::TESS_EVALUATION);
61}
62
63void RenderProcessor::set_target_window(const std::shared_ptr<Core::Window>& window, const std::shared_ptr<VKBuffer>& buffer)
64{
66 window->register_rendering_buffer(buffer);
67}
68
69void RenderProcessor::set_visible(bool visible, const std::shared_ptr<VKBuffer>& buffer)
70{
71 if (!buffer)
72 return;
73
74 const auto it = std::ranges::find(m_hidden_buffers, buffer.get());
75 if (visible) {
76 if (it == m_hidden_buffers.end())
77 return;
78 m_hidden_buffers.erase(it);
79 } else {
80 if (it != m_hidden_buffers.end())
81 return;
82 m_hidden_buffers.push_back(buffer.get());
83 }
84 buffer->request_presentation_refresh(m_target_window);
85}
86
87bool RenderProcessor::is_visible(const std::shared_ptr<VKBuffer>& buffer) const
88{
89 return std::ranges::find(m_hidden_buffers, buffer.get()) == m_hidden_buffers.end();
90}
91
96
103
105{
106 if (enabled) {
108 } else {
110 }
111}
112
114{
115 auto next = m_depth_stencil;
116 next.depth_test_enable = false;
117 next.depth_write_enable = false;
118
119 if (next == m_depth_stencil) {
120 return;
121 }
122
123 m_depth_stencil = next;
125}
126
128{
129 auto next = m_depth_stencil;
130 next.depth_test_enable = true;
131 next.depth_write_enable = true;
132 next.depth_compare_op = compare_op;
133
134 const bool was_enabled = m_depth_enabled;
135 m_depth_enabled = true;
136
137 if (next == m_depth_stencil && was_enabled) {
138 return;
139 }
140
141 m_depth_stencil = next;
143}
144
147 bool enable_depth,
149{
151 m_view_transform_source = nullptr;
153
154 if (enable_depth && !m_depth_enabled) {
156 }
157
158 set_cull_mode(cull);
159}
160
162 std::function<Kinesis::ViewTransform()> fn,
163 bool enable_depth,
165{
166 m_view_transform_source = std::move(fn);
167 m_view_transform.reset();
169
170 if (enable_depth && !m_depth_enabled) {
172 }
173
174 set_cull_mode(cull);
175}
176
177void RenderProcessor::set_geometry_transform(const glm::mat4& transform)
178{
179 m_geometry_transform = transform;
181}
182
183void RenderProcessor::set_geometry_transform_source(std::function<glm::mat4()> fn)
184{
185 m_geometry_transform_source = std::move(fn);
186 m_geometry_transform.reset();
187}
188
190 uint32_t binding,
191 const std::shared_ptr<Core::VKImage>& texture,
192 vk::Sampler sampler)
193{
194 if (!texture || !texture->is_initialized() || !texture->get_image_view()) {
196 "Cannot bind null texture to binding {}", binding);
197 return;
198 }
199
200 if (!sampler) {
202 sampler = loom.get_default_sampler();
203 }
204
205 m_texture_bindings[binding] = { .texture = texture, .sampler = sampler };
207
209
210 auto& foundry = Portal::Graphics::get_shader_foundry();
211 auto cfg_it = std::ranges::find_if(m_config.bindings,
212 [binding](const auto& pair) {
213 return binding >= pair.second.binding
214 && binding < pair.second.binding + pair.second.count;
215 });
216
217 if (cfg_it != m_config.bindings.end() && !m_descriptor_set_ids.empty()) {
218 const uint32_t cfg_set = cfg_it->second.set;
219 if (cfg_set == 0) {
221 uint32_t array_idx = 0;
222 if (cfg_it->second.count > 1 && binding >= cfg_it->second.binding) {
223 array_idx = binding - cfg_it->second.binding;
224 }
225 foundry.update_descriptor_image(
227 cfg_it->second.binding,
228 texture->get_image_view(),
229 sampler,
230 vk::ImageLayout::eShaderReadOnlyOptimal,
231 array_idx);
232 }
233 } else {
234 auto ds_index = resolve_ds_index(cfg_set);
235 if (ds_index && *ds_index < m_descriptor_set_ids.size()) {
236 foundry.update_descriptor_image(
237 m_descriptor_set_ids[*ds_index],
238 cfg_it->second.binding,
239 texture->get_image_view(),
240 sampler,
241 vk::ImageLayout::eShaderReadOnlyOptimal);
242 }
243 }
244 }
245 }
246
248 "Bound texture to binding {}", binding);
249}
250
252 const std::string& descriptor_name,
253 const std::shared_ptr<Core::VKImage>& texture,
254 vk::Sampler sampler)
255{
256 auto binding_it = m_config.bindings.find(descriptor_name);
257 if (binding_it == m_config.bindings.end()) {
259 "No binding configured for descriptor '{}'", descriptor_name);
260 return;
261 }
262
263 bind_texture(binding_it->second.binding, texture, sampler);
264}
265
266void RenderProcessor::initialize_pipeline(const std::shared_ptr<VKBuffer>& buffer)
267{
270 "Vertex shader not loaded");
271 return;
272 }
273
276 "Fragment shader not loaded");
277 return;
278 }
279
280 if (!m_target_window) {
282 "Target window not set");
283 return;
284 }
285
286 const auto* state = get_or_cache_buffer_state(buffer);
287 if (!state) {
289 "initialize_pipeline: layout not yet available, deferring");
291 return;
292 }
293
295
297
299 pipeline_config.vertex_shader = m_shader_id;
300 pipeline_config.fragment_shader = m_fragment_shader_id;
301 pipeline_config.geometry_shader = m_geometry_shader_id;
303 pipeline_config.tess_eval_shader = m_tess_eval_shader_id;
304
305 pipeline_config.topology = pipeline_topology();
306 pipeline_config.rasterization.polygon_mode = m_polygon_mode;
307 pipeline_config.rasterization.cull_mode = m_cull_mode;
308
309 if (m_blend_attachment.has_value()) {
310 pipeline_config.blend_attachments.push_back(m_blend_attachment.value());
311 } else {
312 pipeline_config.blend_attachments.emplace_back();
313 }
314
315 pipeline_config.depth_stencil = m_depth_stencil;
316
317 pipeline_config.semantic_vertex_layout = state->semantic_layout;
318 pipeline_config.use_vertex_shader_reflection = state->use_reflection;
319
321
322 auto& descriptor_bindings = buffer->get_pipeline_context().descriptor_buffer_bindings;
323 std::map<std::pair<uint32_t, uint32_t>, Portal::Graphics::DescriptorBindingInfo> unified_bindings;
324
325 for (const auto& binding : descriptor_bindings) {
326 unified_bindings[{ binding.set, binding.binding }] = binding;
327 }
328
329 for (const auto& [name, binding] : m_config.bindings) {
330 auto key = std::make_pair(binding.set, binding.binding);
331 if (unified_bindings.find(key) == unified_bindings.end()) {
332 unified_bindings[key] = Portal::Graphics::DescriptorBindingInfo {
333 .set = binding.set,
334 .binding = binding.binding,
335 .type = binding.type,
336 .buffer_info = {},
337 .name = name,
338 .count = binding.count
339 };
340 }
341 }
342
343 std::vector<Portal::Graphics::DescriptorBindingInfo> unregistered;
345 if (shader_id == Portal::Graphics::INVALID_SHADER) {
346 continue;
347 }
348 for (const auto& used : Portal::Graphics::get_shader_foundry().get_active_descriptor_bindings(shader_id)) {
349 if (used.set == 0 || unified_bindings.contains({ used.set, used.binding })) {
350 continue;
351 }
352 unregistered.push_back(used);
353 }
354 }
355
356 for (auto it = m_awaited_bindings.begin(); it != m_awaited_bindings.end();) {
357 const auto slot = *it;
358 const bool pending = std::ranges::any_of(unregistered, [&slot](const auto& used) {
359 return used.set == slot.first && used.binding == slot.second;
360 });
361 if (pending) {
362 ++it;
363 continue;
364 }
366 "Set {} binding {} is now registered, the shader's binding is supplied",
367 slot.first, slot.second);
368 it = m_awaited_bindings.erase(it);
369 }
370
371 if (!unregistered.empty()) {
372 constexpr uint32_t k_binding_grace_ticks = 30;
374 && m_binding_wait_ticks < k_binding_grace_ticks;
375
376 for (const auto& used : unregistered) {
377 const bool first_seen = m_awaited_bindings.emplace(used.set, used.binding).second;
378 if (waiting && first_seen) {
380 "Waiting for set {} binding {} ({}), which the shader uses, to be registered before creating the pipeline",
381 used.set, used.binding, vk::to_string(used.type));
382 }
383 }
384
385 if (waiting) {
388 return;
389 }
390
391 for (const auto& used : unregistered) {
393 "Set {} binding {} ({}), which the shader uses, is not registered. "
394 "Creating the pipeline with an empty slot that is filled once a processor registers it",
395 used.set, used.binding, vk::to_string(used.type));
396 unified_bindings.emplace(std::make_pair(used.set, used.binding),
398 .set = used.set,
399 .binding = used.binding,
400 .type = used.type,
401 .buffer_info = {},
402 .name = used.name });
403 }
404 }
405 m_binding_wait_ticks = 0;
406
407 std::map<uint32_t, std::vector<Portal::Graphics::DescriptorBindingInfo>> bindings_by_set;
408 for (const auto& [key, binding] : unified_bindings) {
409 bindings_by_set[binding.set].push_back(binding);
410 }
411
412 for (const auto& [set_index, set_bindings] : bindings_by_set) {
413 pipeline_config.descriptor_sets.push_back(set_bindings);
414 }
415
416 vk::Format swapchain_format = static_cast<vk::Format>(
417 m_display_service->get_swapchain_format(m_target_window));
418
419 vk::Format depth_format = m_depth_enabled
420 ? vk::Format::eD32Sfloat
421 : vk::Format::eUndefined;
422
423 retire_current_pipeline();
424
425 m_pipeline_id = flow.create_pipeline(pipeline_config, { swapchain_format }, depth_format);
426
427 if (m_pipeline_id == Portal::Graphics::INVALID_RENDER_PIPELINE) {
429 "Failed to create render pipeline");
430 return;
431 }
432
433 if (m_depth_enabled) {
434 buffer->set_needs_depth_attachment(true);
435 }
436
437 m_needs_descriptor_rebuild = m_descriptor_set_ids.empty();
438 m_needs_pipeline_rebuild = false;
439
440 on_pipeline_created(m_pipeline_id);
441}
442
443void RenderProcessor::initialize_descriptors(const std::shared_ptr<VKBuffer>& buffer)
444{
445 if (m_pipeline_id == Portal::Graphics::INVALID_RENDER_PIPELINE) {
446 if (!m_needs_pipeline_rebuild) {
448 "Cannot allocate descriptor sets without pipeline");
449 }
450 return;
451 }
452
453 on_before_descriptors_create();
454
456 auto& foundry = Portal::Graphics::get_shader_foundry();
457
458 auto transform_layout = flow.get_view_transform_layout(m_pipeline_id);
459 if (transform_layout) {
460 m_render_transform_descriptor_set_id = foundry.allocate_descriptor_set(transform_layout);
461
462 m_render_transform_ubo = std::make_shared<VKBuffer>(
464 VKBuffer::Usage::UNIFORM,
466 ensure_initialized(m_render_transform_ubo);
467
468 foundry.update_descriptor_buffer(
469 m_render_transform_descriptor_set_id,
470 0,
471 vk::DescriptorType::eUniformBuffer,
472 m_render_transform_ubo->get_buffer(),
473 0,
475
477 "RenderTransform UBO allocated (pipeline {})", m_pipeline_id);
478 }
479
480 m_descriptor_set_ids = flow.allocate_pipeline_descriptors(m_pipeline_id, 1);
481
482 for (const auto& [binding, tex_binding] : m_texture_bindings) {
483 if (!tex_binding.texture
484 || !tex_binding.texture->is_initialized()
485 || !tex_binding.texture->get_image_view()) {
486 continue;
487 }
488
489 auto config_it = std::ranges::find_if(m_config.bindings,
490 [binding](const auto& pair) {
491 return binding >= pair.second.binding
492 && binding < pair.second.binding + pair.second.count;
493 });
494
495 if (config_it == m_config.bindings.end()) {
497 "No config for binding {}", binding);
498 continue;
499 }
500
501 const uint32_t set_index = config_it->second.set;
502
503 if (set_index == 0) {
504 if (m_render_transform_descriptor_set_id == Portal::Graphics::INVALID_DESCRIPTOR_SET) {
506 "Engine descriptor set not allocated for set=0 texture binding {}", binding);
507 continue;
508 }
509 foundry.update_descriptor_image(
510 m_render_transform_descriptor_set_id,
511 config_it->second.binding,
512 tex_binding.texture->get_image_view(),
513 tex_binding.sampler,
514 vk::ImageLayout::eShaderReadOnlyOptimal,
515 binding - config_it->second.binding);
516 continue;
517 }
518
519 auto ds_index = resolve_ds_index(set_index);
520 if (!ds_index) {
522 "Descriptor set index {} out of range", binding);
523 continue;
524 }
525
526 foundry.update_descriptor_image(
527 m_descriptor_set_ids[*ds_index],
528 config_it->second.binding,
529 tex_binding.texture->get_image_view(),
530 tex_binding.sampler,
531 vk::ImageLayout::eShaderReadOnlyOptimal,
532 binding - config_it->second.binding);
533 }
534
536 "Allocated {} descriptor sets and updated {} texture bindings",
537 m_descriptor_set_ids.size(), m_texture_bindings.size());
538
539 update_descriptors(buffer);
540 on_descriptors_created();
541}
542
543void RenderProcessor::set_vertex_range(uint32_t first_vertex, uint32_t vertex_count)
544{
545 m_first_vertex = first_vertex;
546 m_vertex_count = vertex_count;
547
549 "RenderProcessor: Set vertex range [offset={}, count={}]",
550 first_vertex, vertex_count);
551}
552
553void RenderProcessor::set_buffer_vertex_layout(
554 const std::shared_ptr<VKBuffer>& buffer,
555 const Kakshya::VertexLayout& layout)
556{
557 const bool known = m_buffer_info.contains(buffer);
558 auto& state = m_buffer_info[buffer];
559
560 const bool same_input = state.semantic_layout.stride_bytes == layout.stride_bytes
561 && std::ranges::equal(state.semantic_layout.attributes, layout.attributes,
562 [](const auto& a, const auto& b) {
563 return a.component_modality == b.component_modality
564 && a.offset_in_vertex == b.offset_in_vertex;
565 });
566 const bool changed = !known || state.use_reflection || !same_input;
567
568 state.semantic_layout = layout;
569 state.use_reflection = false;
570
571 if (changed) {
572 m_needs_pipeline_rebuild = true;
573 }
574}
575
576const Kinesis::RenderTransform& RenderProcessor::publish_render_transform()
577{
578 Kinesis::RenderTransform transform;
579 if (m_view_transform_active) {
580 transform.view_transform = m_view_transform_source
581 ? m_view_transform_source()
582 : m_view_transform.value_or(Kinesis::ViewTransform {});
583 }
584 transform.geometry = m_geometry_transform_source
585 ? m_geometry_transform_source()
586 : m_geometry_transform.value_or(glm::mat4 { 1.0F });
587
588 m_published_render_transform = transform;
589
590 if (m_render_transform_ubo && m_render_transform_ubo->get_mapped_ptr()) {
591 std::memcpy(
592 m_render_transform_ubo->get_mapped_ptr(),
593 &m_published_render_transform,
595 }
596
597 return m_published_render_transform;
598}
599
600void RenderProcessor::set_triangulate(bool enabled)
601{
602 if (m_triangulate == enabled) {
603 return;
604 }
605
606 m_triangulate = enabled;
607 m_needs_pipeline_rebuild = true;
608}
609
610vk::Rect2D RenderProcessor::resolve_scissor_rect(uint32_t width, uint32_t height) const noexcept
611{
612 if (!m_scissor) {
613 return { { 0, 0 }, { width, height } };
614 }
615
616 const auto& bounds = m_scissor->bounds;
617 const auto fw = static_cast<float>(width);
618 const auto fh = static_cast<float>(height);
619
620 const float x0 = std::clamp((bounds.min.x * 0.5F + 0.5F) * fw, 0.F, fw);
621 const float x1 = std::clamp((bounds.max.x * 0.5F + 0.5F) * fw, 0.F, fw);
622 const float y0 = std::clamp((1.F - bounds.max.y) * 0.5F * fh, 0.F, fh);
623 const float y1 = std::clamp((1.F - bounds.min.y) * 0.5F * fh, 0.F, fh);
624
625 const auto px = static_cast<int32_t>(x0);
626 const auto py = static_cast<int32_t>(y0);
627 const auto pw = static_cast<uint32_t>(x1 - x0);
628 const auto ph = static_cast<uint32_t>(y1 - y0);
629
630 return { { px, py }, { pw, ph } };
631}
632
633void RenderProcessor::set_runs(std::vector<Portal::Graphics::DrawRun> runs)
634{
635 m_runs = std::move(runs);
636}
637
638void RenderProcessor::set_mill_spec(const Portal::Graphics::MillSpec& spec)
639{
640 m_mill_spec = spec;
641 for (auto& [buffer, state] : m_buffer_info) {
642 if (state.mill)
643 state.mill->set_spec(spec);
644 }
645}
646
647uint32_t RenderProcessor::milled_vertex_count() const
648{
649 return m_last_milled_vertex_count;
650}
651
652Portal::Graphics::PrimitiveTopology RenderProcessor::pipeline_topology() const
653{
654 return m_triangulate
656 : m_primitive_topology;
657}
658
659uint32_t RenderProcessor::mill_runs(const std::shared_ptr<VKBuffer>& buffer, Portal::Graphics::PrimitiveMill& mill)
660{
661 std::span<const Portal::Graphics::DrawRun> runs = m_runs;
662
664 if (runs.empty()) {
665 const auto layout = buffer->get_vertex_layout();
666 const uint32_t count = m_vertex_count > 0
667 ? m_vertex_count
668 : (layout.has_value() ? layout->vertex_count : 0U);
669
670 if (count == 0) {
671 return 0;
672 }
673
674 whole = { .topology = m_primitive_topology,
675 .vertex_offset = m_first_vertex,
676 .vertex_count = count };
677 runs = { &whole, 1 };
678 }
679
680 glm::vec3 eye = m_view_transform_active
681 ? glm::vec3(glm::inverse(published_render_transform().view_transform.view)[3])
682 : glm::vec3(0.0F, 0.0F, 1.0e4F);
683
685 && (m_geometry_transform || m_geometry_transform_source)) {
686 const auto& geometry = published_render_transform().geometry;
687 const float determinant = glm::determinant(geometry);
688 if (std::isfinite(determinant) && determinant != 0.0F) {
689 const glm::vec4 local_eye = glm::inverse(geometry) * glm::vec4(eye, 1.0F);
690 if (std::isfinite(local_eye.x) && std::isfinite(local_eye.y)
691 && std::isfinite(local_eye.z) && std::isfinite(local_eye.w)
692 && local_eye.w != 0.0F) {
693 eye = glm::vec3(local_eye) / local_eye.w;
694 }
695 }
696 }
697
698 const Portal::Graphics::MillView view { .eye = eye };
699
700 return mill.mill(buffer, runs, view);
701}
702
703void RenderProcessor::prepare_geometry(const std::shared_ptr<VKBuffer>& buffer, BufferState& state)
704{
705 state.geometry_prepared = false;
706 state.first_vertex = m_first_vertex;
707 state.vertex_count = 0;
708 state.index_count = 0;
709
710 if (m_triangulate) {
711 if (!state.mill)
712 state.mill = std::make_unique<Portal::Graphics::PrimitiveMill>(m_mill_spec);
713 state.vertex_count = mill_runs(buffer, *state.mill);
714 m_last_milled_vertex_count = state.vertex_count;
715 state.first_vertex = 0;
716 if (state.vertex_count > 0) {
717 state.draw_source = state.mill->output();
718 } else if (state.draw_source != buffer) {
719 state.draw_source = buffer;
720 }
721 } else {
722 if (state.draw_source != buffer)
723 state.draw_source = buffer;
724 if (m_vertex_count > 0) {
725 state.vertex_count = m_vertex_count;
726 } else {
727 const auto layout = buffer->get_vertex_layout();
728 state.vertex_count = layout ? layout->vertex_count : 0U;
729 }
730 }
731
732 if (state.vertex_count > 0 && state.draw_source->has_index_buffer())
733 state.index_count = static_cast<uint32_t>(state.draw_source->get_index_buffer_size() / sizeof(uint32_t));
734 state.geometry_prepared = true;
735}
736
737bool RenderProcessor::on_before_execute(Portal::Graphics::CommandBufferID /*cmd_id*/, const std::shared_ptr<VKBuffer>& buffer)
738{
739 if (!is_visible(buffer))
740 return false;
741
742 if (!m_target_window) {
744 "Target window not set");
745 return false;
746 }
747 return m_target_window->is_graphics_registered();
748}
749
750void RenderProcessor::execute_shader(const std::shared_ptr<VKBuffer>& buffer)
751{
752 collect_retired_pipelines();
753
754 if (!is_visible(buffer) || !m_target_window
755 || !m_target_window->is_graphics_registered()) {
756 return;
757 }
758
759 if (m_pipeline_id == Portal::Graphics::INVALID_RENDER_PIPELINE) {
760 return;
761 }
762
763 auto* state = get_or_cache_buffer_state(buffer);
764 if (!state) {
766 "VKBuffer has no vertex layout set. Use buffer->set_vertex_layout()");
767 return;
768 }
769
770 publish_render_transform();
771 prepare_geometry(buffer, *state);
772 record_draw(buffer, *state);
773}
774
775void RenderProcessor::retire_current_pipeline()
776{
777 if (m_pipeline_id == Portal::Graphics::INVALID_RENDER_PIPELINE) {
778 return;
779 }
780
781 for (const auto& entry : m_buffer_info) {
782 entry.first->remove_pipeline_window(m_pipeline_id);
783 }
784
785 uint64_t after_frame = std::numeric_limits<uint64_t>::max();
786 if (m_display_service && m_display_service->get_submitted_frame_count && m_target_window) {
787 after_frame = m_display_service->get_submitted_frame_count(m_target_window);
788 }
789
790 m_retired.push_back({ .pipeline = m_pipeline_id,
791 .window = m_target_window,
792 .transform_ubo = std::move(m_render_transform_ubo),
793 .after_frame = after_frame });
794
796}
797
798void RenderProcessor::collect_retired_pipelines()
799{
800 if (m_retired.empty() || !m_display_service || !m_display_service->get_completed_frame_count) {
801 return;
802 }
803
805
806 std::erase_if(m_retired, [&](const RetiredPipeline& retired) {
807 if (retired.after_frame > m_display_service->get_completed_frame_count(retired.window)) {
808 return false;
809 }
810 flow.destroy_pipeline(retired.pipeline);
811 return true;
812 });
813}
814
815void RenderProcessor::record_draw(const std::shared_ptr<VKBuffer>& buffer)
816{
817 if (!m_initialized || !is_visible(buffer) || !m_target_window
818 || !m_target_window->is_graphics_registered()
820 return;
821
822 const auto it = m_buffer_info.find(buffer);
823 if (it == m_buffer_info.end() || !it->second.geometry_prepared)
824 return;
825 record_draw(buffer, it->second);
826}
827
828void RenderProcessor::record_draw(const std::shared_ptr<VKBuffer>& buffer, const BufferState& state)
829{
830 buffer->set_pipeline_window(m_pipeline_id, m_target_window);
831
832 auto& foundry = Portal::Graphics::get_shader_foundry();
834
835 vk::Format color_format = static_cast<vk::Format>(
836 m_display_service->get_swapchain_format(m_target_window));
837
838 vk::Format depth_format = m_depth_enabled
839 ? vk::Format::eD32Sfloat
840 : vk::Format::eUndefined;
841
842 auto cmd_id = foundry.begin_secondary_commands(color_format, depth_format);
843 auto cmd = foundry.get_command_buffer(cmd_id);
844
845 uint32_t width = 0, height = 0;
846 m_display_service->get_swapchain_extent(m_target_window, width, height);
847
848 if (width > 0 && height > 0) {
849 auto cmd = foundry.get_command_buffer(cmd_id);
850
851 vk::Viewport viewport {
852 0.0F,
853 static_cast<float>(height),
854 static_cast<float>(width),
855 -static_cast<float>(height),
856 0.0F,
857 1.0F
858 };
859 cmd.setViewport(0, 1, &viewport);
860
861 const vk::Rect2D scissor = resolve_scissor_rect(width, height);
862 cmd.setScissor(0, 1, &scissor);
863 }
864
865 flow.bind_pipeline(cmd_id, m_pipeline_id);
866
867 auto& engine_bindings = buffer->get_engine_context().ssbo_bindings;
868 for (const auto& binding : engine_bindings) {
869 if (binding.set == 0 && binding.binding == 0) {
871 "Engine SSBO at binding=0 is reserved for RenderTransform UBO");
872 continue;
873 }
874 if (m_render_transform_descriptor_set_id == Portal::Graphics::INVALID_DESCRIPTOR_SET) {
876 "Engine SSBO binding {} skipped: engine descriptor set not allocated", binding.binding);
877 continue;
878 }
879 foundry.update_descriptor_buffer(
880 m_render_transform_descriptor_set_id,
881 binding.binding,
882 binding.type,
883 binding.buffer_info.buffer,
884 binding.buffer_info.offset,
885 binding.buffer_info.range);
886 }
887
888 auto& descriptor_bindings = buffer->get_pipeline_context().descriptor_buffer_bindings;
889 if (!descriptor_bindings.empty()) {
890 for (const auto& binding : descriptor_bindings) {
891 auto ds_index = resolve_ds_index(binding.set);
892 if (!ds_index) {
894 "Descriptor set index {} out of range or reserved", binding.set);
895 continue;
896 }
897
898 foundry.update_descriptor_buffer(
899 m_descriptor_set_ids[*ds_index],
900 binding.binding,
901 binding.type,
902 binding.buffer_info.buffer,
903 binding.buffer_info.offset,
904 binding.buffer_info.range);
905 }
906 }
907
908 if (!m_descriptor_set_ids.empty()) {
909 flow.bind_descriptor_sets(cmd_id, m_pipeline_id, m_descriptor_set_ids);
910 }
911
912 if (m_render_transform_descriptor_set_id != Portal::Graphics::INVALID_DESCRIPTOR_SET) {
913 flow.bind_descriptor_sets(
914 cmd_id, m_pipeline_id,
915 { m_render_transform_descriptor_set_id },
916 0);
917 }
918
919 if (!m_descriptor_set_ids.empty()) {
920 flow.bind_descriptor_sets(
921 cmd_id, m_pipeline_id,
922 m_descriptor_set_ids,
923 1);
924 }
925
926 const auto push_data = resolve_push_constants(buffer);
927 if (!push_data.empty()) {
928 flow.push_constants(
929 cmd_id, m_pipeline_id, push_data.data(), push_data.size());
930 }
931
932 on_before_execute(cmd_id, buffer);
933
934 flow.bind_vertex_buffers(cmd_id, { state.draw_source });
935
936 if (state.vertex_count > 0) {
937 if (state.draw_source->has_index_buffer()) {
938 flow.bind_index_buffer(cmd_id, state.draw_source);
939 flow.draw_indexed(cmd_id, state.index_count, m_instance_count, 0, 0, 0);
940 } else if (!m_triangulate && m_runs.size() > 1
941 && std::ranges::all_of(m_runs, [this](const Portal::Graphics::DrawRun& run) {
942 return run.topology == m_primitive_topology;
943 })) {
944 for (const auto& run : m_runs) {
945 if (run.vertex_count > 0) {
946 flow.draw(cmd_id, run.vertex_count, m_instance_count, run.vertex_offset, 0);
947 }
948 }
949 } else {
950 flow.draw(cmd_id, state.vertex_count, m_instance_count, state.first_vertex, 0);
951 }
952 }
953
954 foundry.end_commands(cmd_id);
955
956 buffer->set_pipeline_command(m_pipeline_id, cmd_id);
957 m_target_window->track_frame_command(cmd_id);
958
960 "Recorded secondary command buffer {} for window '{}'",
961 cmd_id, m_target_window->get_create_info().title);
962}
963
964void RenderProcessor::on_attach(const std::shared_ptr<Buffer>& buffer)
965{
966 auto vk_buffer = std::dynamic_pointer_cast<VKBuffer>(buffer);
967 if (!vk_buffer)
968 return;
969
971 "RenderProcessor attached to VKBuffer (size: {} bytes, modality: {})",
972 vk_buffer->get_size_bytes(),
973 static_cast<int>(vk_buffer->get_modality()));
974
975 if (vk_buffer && vk_buffer->has_vertex_layout()) {
976 auto vertex_layout = vk_buffer->get_vertex_layout();
977 if (vertex_layout.has_value()) {
979 "RenderProcessor: Auto-injecting vertex layout "
980 "({} vertices, {} attributes)",
981 vertex_layout->vertex_count,
982 vertex_layout->attributes.size());
983
984 m_needs_pipeline_rebuild = true;
985 auto& state = m_buffer_info[vk_buffer];
986 state.semantic_layout = vertex_layout.value();
987 state.use_reflection = false;
988 }
989 }
990
991 if (m_depth_enabled) {
992 vk_buffer->set_needs_depth_attachment(true);
993 }
994
995 if (!m_display_service) {
996 m_display_service = Registry::BackendRegistry::instance()
998 }
999}
1000
1001void RenderProcessor::on_detach(const std::shared_ptr<Buffer>& buffer)
1002{
1003 auto vk_buffer = std::dynamic_pointer_cast<VKBuffer>(buffer);
1004 if (!vk_buffer)
1005 return;
1006
1007 vk_buffer->remove_pipeline_window(m_pipeline_id);
1008
1009 if (m_target_window) {
1010 bool has_other_pipeline = false;
1011 for (const auto& [id, window] : vk_buffer->get_render_pipelines()) {
1012 if (window == m_target_window) {
1013 has_other_pipeline = true;
1014 break;
1015 }
1016 }
1017
1018 if (!has_other_pipeline) {
1019 auto primary = vk_buffer->get_render_processor();
1020 has_other_pipeline = primary && primary.get() != this
1021 && primary->get_target_window() == m_target_window;
1022
1023 if (!has_other_pipeline) {
1024 for (const auto& render : vk_buffer->get_additional_render_processors()) {
1025 if (render.get() != this && render->get_target_window() == m_target_window) {
1026 has_other_pipeline = true;
1027 break;
1028 }
1029 }
1030 }
1031 }
1032
1033 if (!has_other_pipeline)
1034 m_target_window->unregister_rendering_buffer(vk_buffer);
1035 }
1036
1037 std::erase(m_hidden_buffers, vk_buffer.get());
1038 m_buffer_info.erase(vk_buffer);
1039 ShaderProcessor::on_detach(buffer);
1040}
1041
1042void RenderProcessor::cleanup()
1043{
1044 auto& foundry = Portal::Graphics::get_shader_foundry();
1045 auto& flow = Portal::Graphics::get_render_flow();
1046
1047 if (m_pipeline_id != Portal::Graphics::INVALID_RENDER_PIPELINE) {
1048 flow.destroy_pipeline(m_pipeline_id);
1050 }
1051
1052 for (const auto& retired : m_retired) {
1053 flow.destroy_pipeline(retired.pipeline);
1054 }
1055 m_retired.clear();
1056
1057 if (m_geometry_shader_id != Portal::Graphics::INVALID_SHADER) {
1058 foundry.destroy_shader(m_geometry_shader_id);
1059 m_geometry_shader_id = Portal::Graphics::INVALID_SHADER;
1060 }
1061
1062 if (m_tess_control_shader_id != Portal::Graphics::INVALID_SHADER) {
1063 foundry.destroy_shader(m_tess_control_shader_id);
1064 m_tess_control_shader_id = Portal::Graphics::INVALID_SHADER;
1065 }
1066
1067 if (m_tess_eval_shader_id != Portal::Graphics::INVALID_SHADER) {
1068 foundry.destroy_shader(m_tess_eval_shader_id);
1069 m_tess_eval_shader_id = Portal::Graphics::INVALID_SHADER;
1070 }
1071
1072 if (m_fragment_shader_id != Portal::Graphics::INVALID_SHADER) {
1073 foundry.destroy_shader(m_fragment_shader_id);
1074 m_fragment_shader_id = Portal::Graphics::INVALID_SHADER;
1075 }
1076
1077 for (auto& [buffer, state] : m_buffer_info) {
1078 state.geometry_prepared = false;
1079 state.draw_source.reset();
1080 state.mill.reset();
1081 state.first_vertex = 0;
1082 state.vertex_count = 0;
1083 state.index_count = 0;
1084 }
1085 m_last_milled_vertex_count = 0;
1086 m_runs.clear();
1087
1088 m_render_transform_ubo.reset();
1089 m_render_transform_descriptor_set_id = Portal::Graphics::INVALID_DESCRIPTOR_SET;
1090 m_view_transform_active = false;
1091
1092 if (m_target_window) {
1093 flow.unregister_window(m_target_window);
1094 m_target_window.reset();
1095 }
1096
1097 ShaderProcessor::cleanup();
1098
1100 "RenderProcessor cleanup complete");
1101}
1102
1103} // namespace MayaFlux::Buffers
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_RT_ERROR(comp, ctx,...)
#define MF_RT_TRACE(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
vk::CommandBuffer cmd
std::shared_ptr< NetworkGeometryBuffer > buffer
std::weak_ptr< Core::Window > window
Definition Picker.cpp:23
size_t a
size_t b
tvdb_value_type_t vt
std::string name
Definition VKDevice.cpp:123
size_t count
uint32_t ph
uint32_t py
uint32_t pw
uint32_t px
uint32_t width
uint32_t height
void set_cull_mode(Portal::Graphics::CullMode mode)
Set cull mode (e.g., none, front, back)
void bind_texture(uint32_t binding, const std::shared_ptr< Core::VKImage > &texture, vk::Sampler sampler=nullptr)
Bind a texture to a descriptor binding point.
void set_blend_attachment(const Portal::Graphics::BlendAttachmentConfig &config)
Set blend mode for color attachment.
Portal::Graphics::ShaderID m_fragment_shader_id
void disable_alpha_blending()
Disable blending on the color attachment, restoring opaque writes.
std::optional< Portal::Graphics::BlendAttachmentConfig > m_blend_attachment
Portal::Graphics::ShaderID m_geometry_shader_id
void set_tess_eval_shader(const std::string &tess_eval_path)
void enable_alpha_blending()
Enable standard alpha blending (src_alpha, one_minus_src_alpha).
std::function< Kinesis::ViewTransform()> m_view_transform_source
void set_tess_control_shader(const std::string &tess_control_path)
Portal::Graphics::RenderPipelineID m_pipeline_id
void enable_depth_test(Portal::Graphics::CompareOp compare_op=Portal::Graphics::CompareOp::LESS)
Enable depth testing for this processor's pipeline.
Portal::Graphics::ShaderID m_tess_control_shader_id
void set_geometry_transform_source(std::function< glm::mat4()> fn)
Set a geometry transform source evaluated on each render tick.
BufferState * get_or_cache_buffer_state(const std::shared_ptr< VKBuffer > &buffer)
std::optional< Kinesis::ViewTransform > m_view_transform
Portal::Graphics::DepthStencilConfig m_depth_stencil
void initialize_pipeline(const std::shared_ptr< VKBuffer > &buffer) override
std::unordered_map< uint32_t, TextureBinding > m_texture_bindings
void disable_depth_test()
Disable depth testing and depth writes for this processor's pipeline.
void set_visible(bool visible, const std::shared_ptr< VKBuffer > &buffer)
Change an attached buffer's visibility without stopping its processing.
Portal::Graphics::DescriptorSetID m_render_transform_descriptor_set_id
Portal::Graphics::PrimitiveTopology pipeline_topology() const
Topology the pipeline is built with, as opposed to the topology m_runs are declared in.
void set_view_transform_source(std::function< Kinesis::ViewTransform()> fn, bool enable_depth=true, Portal::Graphics::CullMode cull=Portal::Graphics::CullMode::BACK)
Set dynamic view transform source (evaluated every frame)
std::set< std::pair< uint32_t, uint32_t > > m_awaited_bindings
std::unordered_map< std::shared_ptr< VKBuffer >, BufferState > m_buffer_info
bool is_visible(const std::shared_ptr< VKBuffer > &buffer) const
Whether this processor contributes to presentation.
void set_geometry_shader(const std::string &geometry_path)
std::function< glm::mat4()> m_geometry_transform_source
RenderProcessor(const ShaderConfig &config)
void set_fragment_shader(const std::string &fragment_path)
Portal::Graphics::PolygonMode m_polygon_mode
void set_target_window(const std::shared_ptr< Core::Window > &window, const std::shared_ptr< VKBuffer > &buffer)
void set_view_transform(const Kinesis::ViewTransform &vt, bool enable_depth=true, Portal::Graphics::CullMode cull=Portal::Graphics::CullMode::BACK)
Set static view transform (evaluated once)
void set_alpha_blending(bool enabled)
Toggle standard alpha blending.
Portal::Graphics::CullMode m_cull_mode
void set_geometry_transform(const glm::mat4 &transform)
Set a static geometry transform.
std::shared_ptr< Core::Window > m_target_window
std::vector< const VKBuffer * > m_hidden_buffers
Non-owning exceptions to default visibility, removed on detach.
std::optional< glm::mat4 > m_geometry_transform
Portal::Graphics::ShaderID m_tess_eval_shader_id
std::optional< uint32_t > resolve_ds_index(uint32_t set) const
Resolve logical descriptor set index to actual index.
Portal::Graphics::ShaderID m_shader_id
size_t resolve_push_constant_size(const std::shared_ptr< VKBuffer > &buffer) const
Byte width of this processor's push constant block, extended to cover any fragment staged on the buff...
bool m_engine_owns_set_zero
Whether the engine reserves set=0 for global resources.
std::vector< Portal::Graphics::DescriptorSetID > m_descriptor_set_ids
Abstract base class for shader-based buffer processing.
uint32_t mill(const std::shared_ptr< Buffers::VKBuffer > &source, std::span< const DrawRun > runs, const MillView &view)
Mill runs out of source into the owned buffer.
Mills spans of mixed primitive topology into one TRIANGLE_LIST vertex buffer on the GPU.
void register_window_for_rendering(const std::shared_ptr< Core::Window > &window)
Register a window for dynamic rendering.
std::vector< DescriptorBindingInfo > get_active_descriptor_bindings(ShaderID shader_id)
Get the descriptor bindings a shader's entry point statically uses.
ShaderID load_shader(const std::string &content, std::optional< ShaderStage > stage=std::nullopt, const std::string &entry_point="main")
Universal shader loader - auto-detects source type.
Interface * get_service()
Query for a backend service.
static BackendRegistry & instance()
Get the global registry instance.
void run()
Definition main.cpp:21
@ BufferProcessing
Buffer processing (Buffers::BufferManager, processing chains)
@ Buffers
Buffers, Managers, processors and processing chains.
@ UNKNOWN
Unknown or undefined modality.
constexpr RenderPipelineID INVALID_RENDER_PIPELINE
MAYAFLUX_API TextureLoom & get_texture_manager()
Get the global texture manager instance.
constexpr ShaderID INVALID_SHADER
MAYAFLUX_API RenderFlow & get_render_flow()
Get the global render flow instance.
MAYAFLUX_API ShaderFoundry & get_shader_foundry()
Get the global shader compiler instance.
PrimitiveTopology
Vertex assembly primitive topology.
CompareOp
Depth/stencil comparison operation.
constexpr DescriptorSetID INVALID_DESCRIPTOR_SET
std::unique_ptr< Portal::Graphics::PrimitiveMill > mill
A replaced pipeline kept alive until the frames that may still use it finish.
std::string shader_path
Path to shader file.
std::unordered_map< std::string, ShaderBinding > bindings
uint32_t stride_bytes
Total bytes per vertex (stride in Vulkan terms) e.g., 3 floats (position) + 3 floats (normal) = 24 by...
std::vector< VertexAttributeLayout > attributes
All attributes that make up one vertex Ordered by shader location (0, 1, 2, ...)
Complete description of vertex data layout in a buffer.
Packed view, projection, and geometry matrices for rendering.
View and projection matrices as a named push constant slot.
static BlendAttachmentConfig alpha_blend()
Create standard alpha blending configuration.
Per-attachment blend configuration.
One contiguous span of vertices sharing a primitive topology.
@ WorldFacing
Turned to face the viewpoint, in world space.
How a PrimitiveMill shapes spans.
std::optional< Kakshya::VertexLayout > semantic_vertex_layout
std::vector< BlendAttachmentConfig > blend_attachments
Complete render pipeline configuration.
Backend display and presentation service interface.