19 const auto layout =
buffer->get_vertex_layout();
25 return &info_it->second;
117 next.depth_write_enable =
false;
131 next.depth_write_enable =
true;
132 next.depth_compare_op = compare_op;
191 const std::shared_ptr<Core::VKImage>& texture,
194 if (!texture || !texture->is_initialized() || !texture->get_image_view()) {
196 "Cannot bind null texture to binding {}", binding);
202 sampler = loom.get_default_sampler();
212 [binding](
const auto& pair) {
213 return binding >= pair.second.binding
214 && binding < pair.second.binding + pair.second.count;
218 const uint32_t cfg_set = cfg_it->second.set;
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;
225 foundry.update_descriptor_image(
227 cfg_it->second.binding,
228 texture->get_image_view(),
230 vk::ImageLayout::eShaderReadOnlyOptimal,
236 foundry.update_descriptor_image(
238 cfg_it->second.binding,
239 texture->get_image_view(),
241 vk::ImageLayout::eShaderReadOnlyOptimal);
248 "Bound texture to binding {}", binding);
252 const std::string& descriptor_name,
253 const std::shared_ptr<Core::VKImage>& texture,
259 "No binding configured for descriptor '{}'", descriptor_name);
263 bind_texture(binding_it->second.binding, texture, sampler);
270 "Vertex shader not loaded");
276 "Fragment shader not loaded");
282 "Target window not set");
289 "initialize_pipeline: layout not yet available, deferring");
322 auto& descriptor_bindings =
buffer->get_pipeline_context().descriptor_buffer_bindings;
325 for (
const auto& binding : descriptor_bindings) {
326 unified_bindings[{ binding.set, binding.binding }] = binding;
330 auto key = std::make_pair(binding.set, binding.binding);
331 if (unified_bindings.find(key) == unified_bindings.end()) {
334 .binding = binding.binding,
335 .type = binding.type,
338 .count = binding.count
343 std::vector<Portal::Graphics::DescriptorBindingInfo> unregistered;
349 if (used.set == 0 || unified_bindings.contains({ used.set, used.binding })) {
352 unregistered.push_back(used);
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;
366 "Set {} binding {} is now registered, the shader's binding is supplied",
367 slot.first, slot.second);
371 if (!unregistered.empty()) {
372 constexpr uint32_t k_binding_grace_ticks = 30;
376 for (
const auto& used : unregistered) {
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));
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),
399 .binding = used.binding,
402 .
name = used.name });
405 m_binding_wait_ticks = 0;
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);
412 for (
const auto& [set_index, set_bindings] : bindings_by_set) {
413 pipeline_config.descriptor_sets.push_back(set_bindings);
416 vk::Format swapchain_format =
static_cast<vk::Format
>(
417 m_display_service->get_swapchain_format(m_target_window));
419 vk::Format depth_format = m_depth_enabled
420 ? vk::Format::eD32Sfloat
421 : vk::Format::eUndefined;
423 retire_current_pipeline();
425 m_pipeline_id = flow.create_pipeline(pipeline_config, { swapchain_format }, depth_format);
429 "Failed to create render pipeline");
433 if (m_depth_enabled) {
434 buffer->set_needs_depth_attachment(
true);
437 m_needs_descriptor_rebuild = m_descriptor_set_ids.empty();
438 m_needs_pipeline_rebuild =
false;
440 on_pipeline_created(m_pipeline_id);
443void RenderProcessor::initialize_descriptors(
const std::shared_ptr<VKBuffer>&
buffer)
446 if (!m_needs_pipeline_rebuild) {
448 "Cannot allocate descriptor sets without pipeline");
453 on_before_descriptors_create();
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);
462 m_render_transform_ubo = std::make_shared<VKBuffer>(
464 VKBuffer::Usage::UNIFORM,
466 ensure_initialized(m_render_transform_ubo);
468 foundry.update_descriptor_buffer(
469 m_render_transform_descriptor_set_id,
471 vk::DescriptorType::eUniformBuffer,
472 m_render_transform_ubo->get_buffer(),
477 "RenderTransform UBO allocated (pipeline {})", m_pipeline_id);
480 m_descriptor_set_ids = flow.allocate_pipeline_descriptors(m_pipeline_id, 1);
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()) {
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;
495 if (config_it == m_config.bindings.end()) {
497 "No config for binding {}", binding);
501 const uint32_t set_index = config_it->second.set;
503 if (set_index == 0) {
506 "Engine descriptor set not allocated for set=0 texture binding {}", binding);
509 foundry.update_descriptor_image(
510 m_render_transform_descriptor_set_id,
511 config_it->second.binding,
512 tex_binding.texture->get_image_view(),
514 vk::ImageLayout::eShaderReadOnlyOptimal,
515 binding - config_it->second.binding);
519 auto ds_index = resolve_ds_index(set_index);
522 "Descriptor set index {} out of range", binding);
526 foundry.update_descriptor_image(
527 m_descriptor_set_ids[*ds_index],
528 config_it->second.binding,
529 tex_binding.texture->get_image_view(),
531 vk::ImageLayout::eShaderReadOnlyOptimal,
532 binding - config_it->second.binding);
536 "Allocated {} descriptor sets and updated {} texture bindings",
537 m_descriptor_set_ids.size(), m_texture_bindings.size());
539 update_descriptors(
buffer);
540 on_descriptors_created();
543void RenderProcessor::set_vertex_range(uint32_t first_vertex, uint32_t vertex_count)
545 m_first_vertex = first_vertex;
546 m_vertex_count = vertex_count;
549 "RenderProcessor: Set vertex range [offset={}, count={}]",
550 first_vertex, vertex_count);
553void RenderProcessor::set_buffer_vertex_layout(
554 const std::shared_ptr<VKBuffer>&
buffer,
557 const bool known = m_buffer_info.contains(
buffer);
558 auto& state = m_buffer_info[
buffer];
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;
566 const bool changed = !known || state.use_reflection || !same_input;
568 state.semantic_layout = layout;
569 state.use_reflection =
false;
572 m_needs_pipeline_rebuild =
true;
579 if (m_view_transform_active) {
581 ? m_view_transform_source()
584 transform.
geometry = m_geometry_transform_source
585 ? m_geometry_transform_source()
586 : m_geometry_transform.value_or(glm::mat4 { 1.0F });
588 m_published_render_transform = transform;
590 if (m_render_transform_ubo && m_render_transform_ubo->get_mapped_ptr()) {
592 m_render_transform_ubo->get_mapped_ptr(),
593 &m_published_render_transform,
597 return m_published_render_transform;
600void RenderProcessor::set_triangulate(
bool enabled)
602 if (m_triangulate == enabled) {
606 m_triangulate = enabled;
607 m_needs_pipeline_rebuild =
true;
610vk::Rect2D RenderProcessor::resolve_scissor_rect(uint32_t
width, uint32_t
height)
const noexcept
616 const auto& bounds = m_scissor->bounds;
617 const auto fw =
static_cast<float>(
width);
618 const auto fh =
static_cast<float>(
height);
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);
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);
633void RenderProcessor::set_runs(std::vector<Portal::Graphics::DrawRun> runs)
635 m_runs = std::move(runs);
641 for (
auto& [
buffer, state] : m_buffer_info) {
643 state.mill->set_spec(spec);
647uint32_t RenderProcessor::milled_vertex_count()
const
649 return m_last_milled_vertex_count;
656 : m_primitive_topology;
661 std::span<const Portal::Graphics::DrawRun> runs = m_runs;
665 const auto layout =
buffer->get_vertex_layout();
666 const uint32_t
count = m_vertex_count > 0
668 : (layout.has_value() ? layout->vertex_count : 0U);
674 whole = { .
topology = m_primitive_topology,
675 .vertex_offset = m_first_vertex,
676 .vertex_count =
count };
677 runs = { &whole, 1 };
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);
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;
703void RenderProcessor::prepare_geometry(
const std::shared_ptr<VKBuffer>&
buffer,
BufferState& state)
712 state.
mill = std::make_unique<Portal::Graphics::PrimitiveMill>(m_mill_spec);
724 if (m_vertex_count > 0) {
727 const auto layout =
buffer->get_vertex_layout();
728 state.
vertex_count = layout ? layout->vertex_count : 0U;
742 if (!m_target_window) {
744 "Target window not set");
747 return m_target_window->is_graphics_registered();
750void RenderProcessor::execute_shader(
const std::shared_ptr<VKBuffer>&
buffer)
752 collect_retired_pipelines();
754 if (!is_visible(
buffer) || !m_target_window
755 || !m_target_window->is_graphics_registered()) {
763 auto* state = get_or_cache_buffer_state(
buffer);
766 "VKBuffer has no vertex layout set. Use buffer->set_vertex_layout()");
770 publish_render_transform();
771 prepare_geometry(
buffer, *state);
772 record_draw(
buffer, *state);
775void RenderProcessor::retire_current_pipeline()
781 for (
const auto& entry : m_buffer_info) {
782 entry.first->remove_pipeline_window(m_pipeline_id);
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);
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 });
798void RenderProcessor::collect_retired_pipelines()
800 if (m_retired.empty() || !m_display_service || !m_display_service->get_completed_frame_count) {
807 if (retired.
after_frame > m_display_service->get_completed_frame_count(retired.
window)) {
810 flow.destroy_pipeline(retired.
pipeline);
815void RenderProcessor::record_draw(
const std::shared_ptr<VKBuffer>&
buffer)
817 if (!m_initialized || !is_visible(
buffer) || !m_target_window
818 || !m_target_window->is_graphics_registered()
822 const auto it = m_buffer_info.find(
buffer);
823 if (it == m_buffer_info.end() || !it->second.geometry_prepared)
825 record_draw(
buffer, it->second);
828void RenderProcessor::record_draw(
const std::shared_ptr<VKBuffer>&
buffer,
const BufferState& state)
830 buffer->set_pipeline_window(m_pipeline_id, m_target_window);
835 vk::Format color_format =
static_cast<vk::Format
>(
836 m_display_service->get_swapchain_format(m_target_window));
838 vk::Format depth_format = m_depth_enabled
839 ? vk::Format::eD32Sfloat
840 : vk::Format::eUndefined;
842 auto cmd_id = foundry.begin_secondary_commands(color_format, depth_format);
843 auto cmd = foundry.get_command_buffer(cmd_id);
846 m_display_service->get_swapchain_extent(m_target_window,
width,
height);
849 auto cmd = foundry.get_command_buffer(cmd_id);
851 vk::Viewport viewport {
853 static_cast<float>(
height),
854 static_cast<float>(
width),
855 -
static_cast<float>(
height),
859 cmd.setViewport(0, 1, &viewport);
861 const vk::Rect2D scissor = resolve_scissor_rect(
width,
height);
862 cmd.setScissor(0, 1, &scissor);
865 flow.bind_pipeline(cmd_id, m_pipeline_id);
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");
876 "Engine SSBO binding {} skipped: engine descriptor set not allocated", binding.binding);
879 foundry.update_descriptor_buffer(
880 m_render_transform_descriptor_set_id,
883 binding.buffer_info.buffer,
884 binding.buffer_info.offset,
885 binding.buffer_info.range);
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);
894 "Descriptor set index {} out of range or reserved", binding.set);
898 foundry.update_descriptor_buffer(
899 m_descriptor_set_ids[*ds_index],
902 binding.buffer_info.buffer,
903 binding.buffer_info.offset,
904 binding.buffer_info.range);
908 if (!m_descriptor_set_ids.empty()) {
909 flow.bind_descriptor_sets(cmd_id, m_pipeline_id, m_descriptor_set_ids);
913 flow.bind_descriptor_sets(
914 cmd_id, m_pipeline_id,
915 { m_render_transform_descriptor_set_id },
919 if (!m_descriptor_set_ids.empty()) {
920 flow.bind_descriptor_sets(
921 cmd_id, m_pipeline_id,
922 m_descriptor_set_ids,
926 const auto push_data = resolve_push_constants(
buffer);
927 if (!push_data.empty()) {
929 cmd_id, m_pipeline_id, push_data.data(), push_data.size());
932 on_before_execute(cmd_id,
buffer);
934 flow.bind_vertex_buffers(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
942 return run.topology == m_primitive_topology;
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);
954 foundry.end_commands(cmd_id);
956 buffer->set_pipeline_command(m_pipeline_id, cmd_id);
957 m_target_window->track_frame_command(cmd_id);
960 "Recorded secondary command buffer {} for window '{}'",
961 cmd_id, m_target_window->get_create_info().title);
964void RenderProcessor::on_attach(
const std::shared_ptr<Buffer>&
buffer)
966 auto vk_buffer = std::dynamic_pointer_cast<VKBuffer>(
buffer);
971 "RenderProcessor attached to VKBuffer (size: {} bytes, modality: {})",
972 vk_buffer->get_size_bytes(),
973 static_cast<int>(vk_buffer->get_modality()));
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());
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;
991 if (m_depth_enabled) {
992 vk_buffer->set_needs_depth_attachment(
true);
995 if (!m_display_service) {
1001void RenderProcessor::on_detach(
const std::shared_ptr<Buffer>&
buffer)
1003 auto vk_buffer = std::dynamic_pointer_cast<VKBuffer>(
buffer);
1007 vk_buffer->remove_pipeline_window(m_pipeline_id);
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;
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;
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;
1033 if (!has_other_pipeline)
1034 m_target_window->unregister_rendering_buffer(vk_buffer);
1037 std::erase(m_hidden_buffers, vk_buffer.get());
1038 m_buffer_info.erase(vk_buffer);
1039 ShaderProcessor::on_detach(
buffer);
1042void RenderProcessor::cleanup()
1048 flow.destroy_pipeline(m_pipeline_id);
1052 for (
const auto& retired : m_retired) {
1053 flow.destroy_pipeline(retired.pipeline);
1058 foundry.destroy_shader(m_geometry_shader_id);
1063 foundry.destroy_shader(m_tess_control_shader_id);
1068 foundry.destroy_shader(m_tess_eval_shader_id);
1073 foundry.destroy_shader(m_fragment_shader_id);
1077 for (
auto& [
buffer, state] : m_buffer_info) {
1078 state.geometry_prepared =
false;
1079 state.draw_source.reset();
1081 state.first_vertex = 0;
1082 state.vertex_count = 0;
1083 state.index_count = 0;
1085 m_last_milled_vertex_count = 0;
1088 m_render_transform_ubo.reset();
1090 m_view_transform_active =
false;
1092 if (m_target_window) {
1093 flow.unregister_window(m_target_window);
1094 m_target_window.reset();
1097 ShaderProcessor::cleanup();
1100 "RenderProcessor cleanup complete");
#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,...)
std::shared_ptr< NetworkGeometryBuffer > buffer
std::weak_ptr< Core::Window > window
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.
bool m_view_transform_active
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
uint32_t m_binding_wait_ticks
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
bool m_needs_descriptor_rebuild
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.
bool m_needs_pipeline_rebuild
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.
@ 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.
CullMode
Face culling mode.
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
std::shared_ptr< VKBuffer > draw_source
Kakshya::VertexLayout semantic_layout
Portal::Graphics::RenderPipelineID pipeline
std::shared_ptr< Core::Window > window
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.
static BlendAttachmentConfig alpha_blend()
Create standard alpha blending configuration.
Per-attachment blend configuration.
PrimitiveTopology topology
One contiguous span of vertices sharing a primitive topology.
@ WorldFacing
Turned to face the viewpoint, in world space.
How a PrimitiveMill shapes spans.
ShaderID tess_eval_shader
Optional.
std::optional< Kakshya::VertexLayout > semantic_vertex_layout
ShaderID tess_control_shader
Optional.
bool use_vertex_shader_reflection
std::vector< BlendAttachmentConfig > blend_attachments
DepthStencilConfig depth_stencil
size_t push_constant_size
PrimitiveTopology topology
ShaderID geometry_shader
Optional.
RasterizationConfig rasterization
Complete render pipeline configuration.
Backend display and presentation service interface.