MayaFlux 0.4.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
14
15namespace MayaFlux::Buffers {
16
18 std::unordered_map<std::shared_ptr<VKBuffer>, RenderProcessor::VertexInfo>& buffer_info,
19 const std::shared_ptr<VKBuffer>& buffer)
20{
21 auto info_it = buffer_info.find(buffer);
22 if (info_it == buffer_info.end()) {
23 if (buffer->has_vertex_layout()) {
24 auto vertex_layout = buffer->get_vertex_layout();
25 if (vertex_layout.has_value()) {
26 buffer_info[buffer] = { .semantic_layout = vertex_layout.value(), .use_reflection = false };
27 info_it = buffer_info.find(buffer);
28 }
29 }
30 if (info_it == buffer_info.end()) {
31 return nullptr;
32 }
33 }
34 return &info_it->second.semantic_layout;
35}
36
43
44void RenderProcessor::set_fragment_shader(const std::string& fragment_path)
45{
47 m_fragment_shader_id = foundry.load_shader(fragment_path, Portal::Graphics::ShaderStage::FRAGMENT);
49}
50
51void RenderProcessor::set_geometry_shader(const std::string& geometry_path)
52{
54 m_geometry_shader_id = foundry.load_shader(geometry_path, Portal::Graphics::ShaderStage::GEOMETRY);
56}
57
58void RenderProcessor::set_tess_control_shader(const std::string& tess_control_path)
59{
61 m_tess_control_shader_id = foundry.load_shader(tess_control_path, Portal::Graphics::ShaderStage::TESS_CONTROL);
63}
64
65void RenderProcessor::set_tess_eval_shader(const std::string& tess_eval_path)
66{
68 m_tess_eval_shader_id = foundry.load_shader(tess_eval_path, Portal::Graphics::ShaderStage::TESS_EVALUATION);
70}
71
72void RenderProcessor::set_target_window(const std::shared_ptr<Core::Window>& window, const std::shared_ptr<VKBuffer>& buffer)
73{
74 m_target_window = window;
75 window->register_rendering_buffer(buffer);
76}
77
87
96
108
120
122 uint32_t binding,
123 const std::shared_ptr<Core::VKImage>& texture,
124 vk::Sampler sampler)
125{
126 if (!texture || !texture->is_initialized() || !texture->get_image_view()) {
128 "Cannot bind null texture to binding {}", binding);
129 return;
130 }
131
132 if (!sampler) {
134 sampler = loom.get_default_sampler();
135 }
136
137 m_texture_bindings[binding] = { .texture = texture, .sampler = sampler };
139
141
142 auto& foundry = Portal::Graphics::get_shader_foundry();
143 auto cfg_it = std::ranges::find_if(m_config.bindings,
144 [binding](const auto& pair) {
145 return binding >= pair.second.binding
146 && binding < pair.second.binding + pair.second.count;
147 });
148
149 if (cfg_it != m_config.bindings.end() && !m_descriptor_set_ids.empty()) {
150 const uint32_t cfg_set = cfg_it->second.set;
151 if (cfg_set == 0) {
153 uint32_t array_idx = 0;
154 if (cfg_it->second.count > 1 && binding >= cfg_it->second.binding) {
155 array_idx = binding - cfg_it->second.binding;
156 }
157 foundry.update_descriptor_image(
159 cfg_it->second.binding,
160 texture->get_image_view(),
161 sampler,
162 vk::ImageLayout::eShaderReadOnlyOptimal,
163 array_idx);
164 }
165 } else {
166 auto ds_index = resolve_ds_index(cfg_set);
167 if (ds_index && *ds_index < m_descriptor_set_ids.size()) {
168 foundry.update_descriptor_image(
169 m_descriptor_set_ids[*ds_index],
170 cfg_it->second.binding,
171 texture->get_image_view(),
172 sampler,
173 vk::ImageLayout::eShaderReadOnlyOptimal);
174 }
175 }
176 }
177 }
178
180 "Bound texture to binding {}", binding);
181}
182
184 const std::string& descriptor_name,
185 const std::shared_ptr<Core::VKImage>& texture,
186 vk::Sampler sampler)
187{
188 auto binding_it = m_config.bindings.find(descriptor_name);
189 if (binding_it == m_config.bindings.end()) {
191 "No binding configured for descriptor '{}'", descriptor_name);
192 return;
193 }
194
195 bind_texture(binding_it->second.binding, texture, sampler);
196}
197
198void RenderProcessor::initialize_pipeline(const std::shared_ptr<VKBuffer>& buffer)
199{
202 "Vertex shader not loaded");
203 return;
204 }
205
208 "Fragment shader not loaded");
209 return;
210 }
211
212 if (!m_target_window) {
214 "Target window not set");
215 return;
216 }
217
219
221
223 pipeline_config.vertex_shader = m_shader_id;
224 pipeline_config.fragment_shader = m_fragment_shader_id;
225 pipeline_config.geometry_shader = m_geometry_shader_id;
227 pipeline_config.tess_eval_shader = m_tess_eval_shader_id;
228
229 pipeline_config.topology = m_primitive_topology;
230 pipeline_config.rasterization.polygon_mode = m_polygon_mode;
231 pipeline_config.rasterization.cull_mode = m_cull_mode;
232
233 if (m_blend_attachment.has_value()) {
234 pipeline_config.blend_attachments.push_back(m_blend_attachment.value());
235 } else {
236 pipeline_config.blend_attachments.emplace_back();
237 }
238
239 pipeline_config.depth_stencil = m_depth_stencil;
240
241 if (m_buffer_info.find(buffer) == m_buffer_info.end()) {
242 if (buffer->has_vertex_layout()) {
243 auto vertex_layout = buffer->get_vertex_layout();
244 if (vertex_layout.has_value()) {
245 m_buffer_info[buffer] = {
246 .semantic_layout = vertex_layout.value(),
247 .use_reflection = false
248 };
249 }
250 }
251 }
252
254 if (!local_layout) {
256 "initialize_pipeline: layout not yet available, deferring");
257 return;
258 }
259
260 pipeline_config.semantic_vertex_layout = *local_layout;
261 pipeline_config.use_vertex_shader_reflection = m_buffer_info[buffer].use_reflection;
262
263 const auto& staging = buffer->get_pipeline_context().push_constant_staging;
264 if (!staging.empty()) {
265 pipeline_config.push_constant_size = staging.size();
266 } else {
267 pipeline_config.push_constant_size = std::max(m_config.push_constant_size, m_push_constant_data.size());
268 }
269
270 auto& descriptor_bindings = buffer->get_pipeline_context().descriptor_buffer_bindings;
271 std::map<std::pair<uint32_t, uint32_t>, Portal::Graphics::DescriptorBindingInfo> unified_bindings;
272
273 for (const auto& binding : descriptor_bindings) {
274 unified_bindings[{ binding.set, binding.binding }] = binding;
275 }
276
277 for (const auto& [name, binding] : m_config.bindings) {
278 auto key = std::make_pair(binding.set, binding.binding);
279 if (unified_bindings.find(key) == unified_bindings.end()) {
280 unified_bindings[key] = Portal::Graphics::DescriptorBindingInfo {
281 .set = binding.set,
282 .binding = binding.binding,
283 .type = binding.type,
284 .buffer_info = {},
285 .name = name,
286 .count = binding.count
287 };
288 }
289 }
290
291 std::map<uint32_t, std::vector<Portal::Graphics::DescriptorBindingInfo>> bindings_by_set;
292 for (const auto& [key, binding] : unified_bindings) {
293 bindings_by_set[binding.set].push_back(binding);
294 }
295
296 for (const auto& [set_index, set_bindings] : bindings_by_set) {
297 pipeline_config.descriptor_sets.push_back(set_bindings);
298 }
299
300 vk::Format swapchain_format = static_cast<vk::Format>(
302
303 vk::Format depth_format = m_depth_enabled
304 ? vk::Format::eD32Sfloat
305 : vk::Format::eUndefined;
306
307 m_pipeline_id = flow.create_pipeline(pipeline_config, { swapchain_format }, depth_format);
308
311 "Failed to create render pipeline");
312 return;
313 }
314
315 if (m_depth_enabled) {
316 buffer->set_needs_depth_attachment(true);
317 }
318
321
323}
324
325void RenderProcessor::initialize_descriptors(const std::shared_ptr<VKBuffer>& buffer)
326{
329 "Cannot allocate descriptor sets without pipeline");
330 return;
331 }
332
334
336 auto& foundry = Portal::Graphics::get_shader_foundry();
337
338 auto vt_layout = flow.get_view_transform_layout(m_pipeline_id);
339 if (vt_layout) {
340 m_view_transform_descriptor_set_id = foundry.allocate_descriptor_set(vt_layout);
341
342 m_view_transform_ubo = std::make_shared<VKBuffer>(
347
348 foundry.update_descriptor_buffer(
350 0,
351 vk::DescriptorType::eUniformBuffer,
352 m_view_transform_ubo->get_buffer(),
353 0,
354 sizeof(Kinesis::ViewTransform));
355
357 "ViewTransform UBO allocated (pipeline {})", m_pipeline_id);
358 }
359
360 m_descriptor_set_ids = flow.allocate_pipeline_descriptors(m_pipeline_id, 1);
361
362 for (const auto& [binding, tex_binding] : m_texture_bindings) {
363 if (!tex_binding.texture
364 || !tex_binding.texture->is_initialized()
365 || !tex_binding.texture->get_image_view()) {
366 continue;
367 }
368
369 auto config_it = std::ranges::find_if(m_config.bindings,
370 [binding](const auto& pair) {
371 return binding >= pair.second.binding
372 && binding < pair.second.binding + pair.second.count;
373 });
374
375 if (config_it == m_config.bindings.end()) {
377 "No config for binding {}", binding);
378 continue;
379 }
380
381 const uint32_t set_index = config_it->second.set;
382
383 if (set_index == 0) {
386 "Engine descriptor set not allocated for set=0 texture binding {}", binding);
387 continue;
388 }
389 foundry.update_descriptor_image(
391 config_it->second.binding,
392 tex_binding.texture->get_image_view(),
393 tex_binding.sampler,
394 vk::ImageLayout::eShaderReadOnlyOptimal,
395 binding - config_it->second.binding);
396 continue;
397 }
398
399 auto ds_index = resolve_ds_index(set_index);
400 if (!ds_index) {
402 "Descriptor set index {} out of range", binding);
403 continue;
404 }
405
406 foundry.update_descriptor_image(
407 m_descriptor_set_ids[*ds_index],
408 config_it->second.binding,
409 tex_binding.texture->get_image_view(),
410 tex_binding.sampler,
411 vk::ImageLayout::eShaderReadOnlyOptimal,
412 binding - config_it->second.binding);
413 }
414
416 "Allocated {} descriptor sets and updated {} texture bindings",
418
419 update_descriptors(buffer);
421}
422
423void RenderProcessor::set_vertex_range(uint32_t first_vertex, uint32_t vertex_count)
424{
425 m_first_vertex = first_vertex;
426 m_vertex_count = vertex_count;
427
429 "RenderProcessor: Set vertex range [offset={}, count={}]",
430 first_vertex, vertex_count);
431}
432
434 const std::shared_ptr<VKBuffer>& buffer,
435 const Kakshya::VertexLayout& layout)
436{
437 m_buffer_info[buffer] = {
438 .semantic_layout = layout,
439 .use_reflection = false
440 };
442}
443
444bool RenderProcessor::on_before_execute(Portal::Graphics::CommandBufferID /*cmd_id*/, const std::shared_ptr<VKBuffer>& /*buffer*/)
445{
446 if (!m_target_window) {
448 "Target window not set");
449 return false;
450 }
451 return m_target_window->is_graphics_registered();
452}
453
454void RenderProcessor::execute_shader(const std::shared_ptr<VKBuffer>& buffer)
455{
456 if (m_buffer_info.find(buffer) == m_buffer_info.end()) {
457 if (buffer->has_vertex_layout()) {
458 auto vertex_layout = buffer->get_vertex_layout();
459 if (vertex_layout.has_value()) {
460 m_buffer_info[buffer] = {
461 .semantic_layout = vertex_layout.value(),
462 .use_reflection = false
463 };
464 }
465 }
466 }
467
468 if (!m_target_window->is_graphics_registered()) {
469 return;
470 }
471
473 return;
474 }
475
477 if (!local_layout) {
479 "VKBuffer has no vertex layout set. Use buffer->set_vertex_layout()");
480 return;
481 }
482
483 buffer->set_pipeline_window(m_pipeline_id, m_target_window);
484
485 auto& foundry = Portal::Graphics::get_shader_foundry();
487
488 vk::Format color_format = static_cast<vk::Format>(
490
491 auto cmd_id = foundry.begin_secondary_commands(color_format);
492 auto cmd = foundry.get_command_buffer(cmd_id);
493
494 uint32_t width = 0, height = 0;
496
497 if (width > 0 && height > 0) {
498 auto cmd = foundry.get_command_buffer(cmd_id);
499
500 vk::Viewport viewport {
501 0.0F,
502 static_cast<float>(height),
503 static_cast<float>(width),
504 -static_cast<float>(height),
505 0.0F,
506 1.0F
507 };
508 cmd.setViewport(0, 1, &viewport);
509
510 vk::Rect2D scissor { { 0, 0 }, { width, height } };
511 cmd.setScissor(0, 1, &scissor);
512 }
513
514 flow.bind_pipeline(cmd_id, m_pipeline_id);
515
516 auto& engine_bindings = buffer->get_engine_context().ssbo_bindings;
517 for (const auto& binding : engine_bindings) {
518 if (binding.set == 0 && binding.binding == 0) {
520 "Engine SSBO at binding=0 is reserved for ViewTransform UBO");
521 continue;
522 }
525 "Engine SSBO binding {} skipped: engine descriptor set not allocated", binding.binding);
526 continue;
527 }
528 foundry.update_descriptor_buffer(
530 binding.binding,
531 binding.type,
532 binding.buffer_info.buffer,
533 binding.buffer_info.offset,
534 binding.buffer_info.range);
535 }
536
537 auto& descriptor_bindings = buffer->get_pipeline_context().descriptor_buffer_bindings;
538 if (!descriptor_bindings.empty()) {
539 for (const auto& binding : descriptor_bindings) {
540 auto ds_index = resolve_ds_index(binding.set);
541 if (!ds_index) {
543 "Descriptor set index {} out of range or reserved", binding.set);
544 continue;
545 }
546
547 foundry.update_descriptor_buffer(
548 m_descriptor_set_ids[*ds_index],
549 binding.binding,
550 binding.type,
551 binding.buffer_info.buffer,
552 binding.buffer_info.offset,
553 binding.buffer_info.range);
554 }
555 }
556
557 if (!m_descriptor_set_ids.empty()) {
558 flow.bind_descriptor_sets(cmd_id, m_pipeline_id, m_descriptor_set_ids);
559 }
560
561 {
567 }
568
569 if (m_view_transform_ubo && m_view_transform_ubo->get_mapped_ptr()) {
570 std::memcpy(
571 m_view_transform_ubo->get_mapped_ptr(),
572 &vt,
573 sizeof(Kinesis::ViewTransform));
574 }
575 }
576
578 flow.bind_descriptor_sets(
579 cmd_id, m_pipeline_id,
581 0);
582 }
583
584 if (!m_descriptor_set_ids.empty()) {
585 flow.bind_descriptor_sets(
586 cmd_id, m_pipeline_id,
588 1);
589 }
590
591 const auto& staging = buffer->get_pipeline_context();
592 if (!staging.push_constant_staging.empty()) {
593 flow.push_constants(
594 cmd_id,
596 staging.push_constant_staging.data(),
597 staging.push_constant_staging.size());
598 } else if (!m_push_constant_data.empty()) {
599 flow.push_constants(
600 cmd_id,
603 m_push_constant_data.size());
604 }
605
606 on_before_execute(cmd_id, buffer);
607
608 flow.bind_vertex_buffers(cmd_id, { buffer });
609
610 uint32_t draw_count = 0;
611 if (m_vertex_count > 0) {
612 draw_count = m_vertex_count;
613 } else {
614 auto current_layout = buffer->get_vertex_layout();
615 if (!current_layout.has_value() || current_layout->vertex_count == 0) {
617 "Vertex layout has zero vertices, skipping draw");
618 return;
619 }
620 draw_count = current_layout->vertex_count;
621 }
622
623 if (buffer->has_index_buffer()) {
624 const auto index_count = static_cast<uint32_t>(
625 buffer->get_index_buffer_size() / sizeof(uint32_t));
626 flow.bind_index_buffer(cmd_id, buffer);
627 flow.draw_indexed(cmd_id, index_count, 1, 0, 0, 0);
628 } else {
629 flow.draw(cmd_id, draw_count, 1, m_first_vertex, 0);
630 }
631
632 foundry.end_commands(cmd_id);
633
634 buffer->set_pipeline_command(m_pipeline_id, cmd_id);
635 m_target_window->track_frame_command(cmd_id);
636
638 "Recorded secondary command buffer {} for window '{}'",
639 cmd_id, m_target_window->get_create_info().title);
640}
641
642void RenderProcessor::on_attach(const std::shared_ptr<Buffer>& buffer)
643{
644 auto vk_buffer = std::dynamic_pointer_cast<VKBuffer>(buffer);
645 if (!vk_buffer)
646 return;
647
649 "RenderProcessor attached to VKBuffer (size: {} bytes, modality: {})",
650 vk_buffer->get_size_bytes(),
651 static_cast<int>(vk_buffer->get_modality()));
652
653 if (vk_buffer && vk_buffer->has_vertex_layout()) {
654 auto vertex_layout = vk_buffer->get_vertex_layout();
655 if (vertex_layout.has_value()) {
657 "RenderProcessor: Auto-injecting vertex layout "
658 "({} vertices, {} attributes)",
659 vertex_layout->vertex_count,
660 vertex_layout->attributes.size());
661
663 m_buffer_info[vk_buffer] = { .semantic_layout = vertex_layout.value(), .use_reflection = false };
664 }
665 }
666
667 if (m_depth_enabled) {
668 vk_buffer->set_needs_depth_attachment(true);
669 }
670
671 if (!m_display_service) {
674 }
675}
676
678{
679 auto& foundry = Portal::Graphics::get_shader_foundry();
681
683 flow.destroy_pipeline(m_pipeline_id);
685 }
686
688 foundry.destroy_shader(m_geometry_shader_id);
690 }
691
693 foundry.destroy_shader(m_tess_control_shader_id);
695 }
696
698 foundry.destroy_shader(m_tess_eval_shader_id);
700 }
701
703 foundry.destroy_shader(m_fragment_shader_id);
705 }
706
707 m_view_transform_ubo.reset();
710
711 if (m_target_window) {
712 flow.unregister_window(m_target_window);
713 m_target_window.reset();
714 }
715
717
719 "RenderProcessor cleanup complete");
720}
721
722} // 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_DEBUG(comp, ctx,...)
#define MF_RT_DEBUG(comp, ctx,...)
vk::CommandBuffer cmd
uint32_t width
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.
Portal::Graphics::DescriptorSetID m_view_transform_descriptor_set_id
void set_blend_attachment(const Portal::Graphics::BlendAttachmentConfig &config)
Set blend mode for color attachment.
Portal::Graphics::ShaderID m_fragment_shader_id
std::unordered_map< std::shared_ptr< VKBuffer >, VertexInfo > m_buffer_info
void set_buffer_vertex_layout(const std::shared_ptr< VKBuffer > &buffer, const Kakshya::VertexLayout &layout)
Override the vertex layout used when building the pipeline for buffer.
std::optional< Portal::Graphics::BlendAttachmentConfig > m_blend_attachment
Registry::Service::DisplayService * m_display_service
Portal::Graphics::ShaderID m_geometry_shader_id
void set_tess_eval_shader(const std::string &tess_eval_path)
bool on_before_execute(Portal::Graphics::CommandBufferID cmd_id, const std::shared_ptr< VKBuffer > &buffer) override
Called before each process callback.
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 on_attach(const std::shared_ptr< Buffer > &buffer) override
Called when this processor is attached to a buffer.
void initialize_descriptors(const std::shared_ptr< VKBuffer > &buffer) override
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 set_geometry_shader(const std::string &geometry_path)
void set_vertex_range(uint32_t first_vertex, uint32_t vertex_count)
Set vertex range for drawing subset of buffer.
Portal::Graphics::PrimitiveTopology m_primitive_topology
void set_view_transform(const Kinesis::ViewTransform &vt)
Set static view transform (evaluated once)
RenderProcessor(const ShaderConfig &config)
void set_fragment_shader(const std::string &fragment_path)
Portal::Graphics::PolygonMode m_polygon_mode
void execute_shader(const std::shared_ptr< VKBuffer > &buffer) override
void set_target_window(const std::shared_ptr< Core::Window > &window, const std::shared_ptr< VKBuffer > &buffer)
Portal::Graphics::CullMode m_cull_mode
const Kakshya::VertexLayout * get_or_cache_vertex_layout(std::unordered_map< std::shared_ptr< VKBuffer >, VertexInfo > &buffer_info, const std::shared_ptr< VKBuffer > &buffer)
std::shared_ptr< Core::Window > m_target_window
Portal::Graphics::ShaderID m_tess_eval_shader_id
std::shared_ptr< VKBuffer > m_view_transform_ubo
void set_view_transform_source(std::function< Kinesis::ViewTransform()> fn)
Set dynamic view transform source (evaluated every frame)
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
std::vector< uint8_t > m_push_constant_data
virtual void on_descriptors_created()
Called after descriptor sets are created.
virtual void update_descriptors(const std::shared_ptr< VKBuffer > &buffer)
virtual void on_pipeline_created(Portal::Graphics::ComputePipelineID pipeline_id)
Called after pipeline is created.
virtual void on_before_descriptors_create()
Called before descriptor sets are created.
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.
void ensure_initialized(const std::shared_ptr< VKBuffer > &buffer)
Definition VKBuffer.cpp:462
@ UNIFORM
Uniform buffer (host-visible)
void register_window_for_rendering(const std::shared_ptr< Core::Window > &window)
Register a window for dynamic rendering.
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.
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.
CompareOp
Depth/stencil comparison operation.
constexpr DescriptorSetID INVALID_DESCRIPTOR_SET
std::string shader_path
Path to shader file.
std::unordered_map< std::string, ShaderBinding > bindings
Complete description of vertex data layout in a buffer.
View and projection matrices as a named push constant slot.
static BlendAttachmentConfig alpha_blend()
Create standard alpha blending configuration.
Per-attachment blend configuration.
std::vector< std::vector< DescriptorBindingInfo > > descriptor_sets
std::optional< Kakshya::VertexLayout > semantic_vertex_layout
std::vector< BlendAttachmentConfig > blend_attachments
Complete render pipeline configuration.
std::function< int(const std::shared_ptr< void > &)> get_swapchain_format
Get actual swapchain format for a window.
std::function< void(const std::shared_ptr< void > &, uint32_t &, uint32_t &)> get_swapchain_extent
Get swapchain extent for a window.
Backend display and presentation service interface.