MayaFlux 0.3.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
42
43void RenderProcessor::set_fragment_shader(const std::string& fragment_path)
44{
46 m_fragment_shader_id = foundry.load_shader(fragment_path, Portal::Graphics::ShaderStage::FRAGMENT);
48}
49
50void RenderProcessor::set_geometry_shader(const std::string& geometry_path)
51{
53 m_geometry_shader_id = foundry.load_shader(geometry_path, Portal::Graphics::ShaderStage::GEOMETRY);
55}
56
57void RenderProcessor::set_tess_control_shader(const std::string& tess_control_path)
58{
60 m_tess_control_shader_id = foundry.load_shader(tess_control_path, Portal::Graphics::ShaderStage::TESS_CONTROL);
62}
63
64void RenderProcessor::set_tess_eval_shader(const std::string& tess_eval_path)
65{
67 m_tess_eval_shader_id = foundry.load_shader(tess_eval_path, Portal::Graphics::ShaderStage::TESS_EVALUATION);
69}
70
71void RenderProcessor::set_target_window(const std::shared_ptr<Core::Window>& window, const std::shared_ptr<VKBuffer>& buffer)
72{
73 m_target_window = window;
74 window->register_rendering_buffer(buffer);
75}
76
86
95
113
129
131 uint32_t binding,
132 const std::shared_ptr<Core::VKImage>& texture,
133 vk::Sampler sampler)
134{
135 if (!texture) {
137 "Cannot bind null texture to binding {}", binding);
138 return;
139 }
140
141 if (!sampler) {
143 sampler = loom.get_default_sampler();
144 }
145
146 m_texture_bindings[binding] = { .texture = texture, .sampler = sampler };
147
149
150 auto& foundry = Portal::Graphics::get_shader_foundry();
151 foundry.update_descriptor_image(
153 binding,
154 texture->get_image_view(),
155 sampler,
156 vk::ImageLayout::eShaderReadOnlyOptimal);
157 }
158
160 "Bound texture to binding {}", binding);
161}
162
164 const std::string& descriptor_name,
165 const std::shared_ptr<Core::VKImage>& texture,
166 vk::Sampler sampler)
167{
168 auto binding_it = m_config.bindings.find(descriptor_name);
169 if (binding_it == m_config.bindings.end()) {
171 "No binding configured for descriptor '{}'", descriptor_name);
172 return;
173 }
174
175 bind_texture(binding_it->second.binding, texture, sampler);
176}
177
178void RenderProcessor::initialize_pipeline(const std::shared_ptr<VKBuffer>& buffer)
179{
182 "Vertex shader not loaded");
183 return;
184 }
185
188 "Fragment shader not loaded");
189 return;
190 }
191
192 if (!m_target_window) {
194 "Target window not set");
195 return;
196 }
197
199
201
203 pipeline_config.vertex_shader = m_shader_id;
204 pipeline_config.fragment_shader = m_fragment_shader_id;
205 pipeline_config.geometry_shader = m_geometry_shader_id;
207 pipeline_config.tess_eval_shader = m_tess_eval_shader_id;
208
209 pipeline_config.topology = m_primitive_topology;
210 pipeline_config.rasterization.polygon_mode = m_polygon_mode;
211 pipeline_config.rasterization.cull_mode = m_cull_mode;
212
213 if (m_blend_attachment.has_value()) {
214 pipeline_config.blend_attachments.push_back(m_blend_attachment.value());
215 } else {
216 pipeline_config.blend_attachments.emplace_back();
217 }
218
219 pipeline_config.depth_stencil = m_depth_stencil;
220
221 if (m_buffer_info.find(buffer) == m_buffer_info.end()) {
222 if (buffer->has_vertex_layout()) {
223 auto vertex_layout = buffer->get_vertex_layout();
224 if (vertex_layout.has_value()) {
225 m_buffer_info[buffer] = {
226 .semantic_layout = vertex_layout.value(),
227 .use_reflection = false
228 };
229 }
230 }
231 }
232
234 if (!local_layout) {
236 "initialize_pipeline: layout not yet available, deferring");
237 return;
238 }
239
240 pipeline_config.semantic_vertex_layout = *local_layout;
241 pipeline_config.use_vertex_shader_reflection = m_buffer_info[buffer].use_reflection;
242
243 const auto& staging = buffer->get_pipeline_context().push_constant_staging;
244 if (!staging.empty()) {
245 pipeline_config.push_constant_size = staging.size();
246 } else {
247 pipeline_config.push_constant_size = std::max(m_config.push_constant_size, m_push_constant_data.size());
248 }
249
250 auto& descriptor_bindings = buffer->get_pipeline_context().descriptor_buffer_bindings;
251 std::map<std::pair<uint32_t, uint32_t>, Portal::Graphics::DescriptorBindingInfo> unified_bindings;
252
253 for (const auto& binding : descriptor_bindings) {
254 unified_bindings[{ binding.set, binding.binding }] = binding;
255 }
256
257 for (const auto& [name, binding] : m_config.bindings) {
258 auto key = std::make_pair(binding.set, binding.binding);
259 if (unified_bindings.find(key) == unified_bindings.end()) {
260 unified_bindings[key] = Portal::Graphics::DescriptorBindingInfo {
261 .set = binding.set,
262 .binding = binding.binding,
263 .type = binding.type,
264 .buffer_info = {},
265 .name = name
266 };
267 }
268 }
269
270 std::map<uint32_t, std::vector<Portal::Graphics::DescriptorBindingInfo>> bindings_by_set;
271 for (const auto& [key, binding] : unified_bindings) {
272 bindings_by_set[binding.set].push_back(binding);
273 }
274
275 for (const auto& [set_index, set_bindings] : bindings_by_set) {
276 pipeline_config.descriptor_sets.push_back(set_bindings);
277 }
278
279 vk::Format swapchain_format = static_cast<vk::Format>(
281
282 vk::Format depth_format = m_depth_enabled
283 ? vk::Format::eD32Sfloat
284 : vk::Format::eUndefined;
285
286 m_pipeline_id = flow.create_pipeline(pipeline_config, { swapchain_format }, depth_format);
287
290 "Failed to create render pipeline");
291 return;
292 }
293
294 if (m_depth_enabled) {
295 buffer->set_needs_depth_attachment(true);
296 }
297
298 m_needs_descriptor_rebuild = !pipeline_config.descriptor_sets.empty() && m_descriptor_set_ids.empty();
300
302}
303
304void RenderProcessor::initialize_descriptors(const std::shared_ptr<VKBuffer>& buffer)
305{
308 "Cannot allocate descriptor sets without pipeline");
309 return;
310 }
311
313
315
316 m_descriptor_set_ids = flow.allocate_pipeline_descriptors(m_pipeline_id);
317
318 if (m_descriptor_set_ids.empty()) {
320 "Failed to allocate descriptor sets for pipeline");
321 return;
322 }
323
324 for (const auto& [binding, tex_binding] : m_texture_bindings) {
325 auto config_it = std::ranges::find_if(m_config.bindings,
326 [binding](const auto& pair) {
327 return pair.second.binding == binding;
328 });
329
330 if (config_it == m_config.bindings.end()) {
332 "No config for binding {}", binding);
333 continue;
334 }
335 uint32_t set_index = config_it->second.set;
336
337 if (set_index >= m_descriptor_set_ids.size()) {
339 "Descriptor set index {} out of range", binding);
340 continue;
341 }
342
343 auto& foundry = Portal::Graphics::get_shader_foundry();
344 foundry.update_descriptor_image(
345 m_descriptor_set_ids[set_index],
346 config_it->second.binding,
347 tex_binding.texture->get_image_view(),
348 tex_binding.sampler,
349 vk::ImageLayout::eShaderReadOnlyOptimal);
350 }
351
353 "Allocated {} descriptor sets and updated {} texture bindings",
355
356 update_descriptors(buffer);
358}
359
360void RenderProcessor::set_vertex_range(uint32_t first_vertex, uint32_t vertex_count)
361{
362 m_first_vertex = first_vertex;
363 m_vertex_count = vertex_count;
364
366 "RenderProcessor: Set vertex range [offset={}, count={}]",
367 first_vertex, vertex_count);
368}
369
371 const std::shared_ptr<VKBuffer>& buffer,
372 const Kakshya::VertexLayout& layout)
373{
374 m_buffer_info[buffer] = {
375 .semantic_layout = layout,
376 .use_reflection = false
377 };
379}
380
381bool RenderProcessor::on_before_execute(Portal::Graphics::CommandBufferID /*cmd_id*/, const std::shared_ptr<VKBuffer>& /*buffer*/)
382{
383 if (!m_target_window) {
385 "Target window not set");
386 return false;
387 }
388 return m_target_window->is_graphics_registered();
389}
390
391void RenderProcessor::execute_shader(const std::shared_ptr<VKBuffer>& buffer)
392{
393 if (m_buffer_info.find(buffer) == m_buffer_info.end()) {
394 if (buffer->has_vertex_layout()) {
395 auto vertex_layout = buffer->get_vertex_layout();
396 if (vertex_layout.has_value()) {
397 m_buffer_info[buffer] = {
398 .semantic_layout = vertex_layout.value(),
399 .use_reflection = false
400 };
401 }
402 }
403 }
404
405 if (!m_target_window->is_graphics_registered()) {
406 return;
407 }
408
410 return;
411 }
412
414 if (!local_layout) {
416 "VKBuffer has no vertex layout set. Use buffer->set_vertex_layout()");
417 return;
418 }
419
420 buffer->set_pipeline_window(m_pipeline_id, m_target_window);
421
422 auto& foundry = Portal::Graphics::get_shader_foundry();
424
425 vk::Format color_format = static_cast<vk::Format>(
427
428 auto cmd_id = foundry.begin_secondary_commands(color_format);
429 auto cmd = foundry.get_command_buffer(cmd_id);
430
431 uint32_t width = 0, height = 0;
433
434 if (width > 0 && height > 0) {
435 auto cmd = foundry.get_command_buffer(cmd_id);
436
437 vk::Viewport viewport {
438 0.0F,
439 static_cast<float>(height),
440 static_cast<float>(width),
441 -static_cast<float>(height),
442 0.0F,
443 1.0F
444 };
445 cmd.setViewport(0, 1, &viewport);
446
447 vk::Rect2D scissor { { 0, 0 }, { width, height } };
448 cmd.setScissor(0, 1, &scissor);
449 }
450
451 flow.bind_pipeline(cmd_id, m_pipeline_id);
452
453 auto& descriptor_bindings = buffer->get_pipeline_context().descriptor_buffer_bindings;
454 if (!descriptor_bindings.empty()) {
455 for (const auto& binding : descriptor_bindings) {
456 if (binding.set >= m_descriptor_set_ids.size()) {
458 "Descriptor set index {} out of range", binding.set);
459 continue;
460 }
461
462 foundry.update_descriptor_buffer(
463 m_descriptor_set_ids[binding.set],
464 binding.binding,
465 binding.type,
466 binding.buffer_info.buffer,
467 binding.buffer_info.offset,
468 binding.buffer_info.range);
469 }
470 }
471
472 if (!m_descriptor_set_ids.empty()) {
473 flow.bind_descriptor_sets(cmd_id, m_pipeline_id, m_descriptor_set_ids);
474 }
475
476 if (m_view_transform_source || m_view_transform.has_value()) {
479 : m_view_transform.value();
480
481 auto& staging = buffer->get_pipeline_context().push_constant_staging;
482
483 if (!staging.empty()) {
484 if (staging.size() < sizeof(Kinesis::ViewTransform)) {
485 staging.resize(std::max(staging.size(), sizeof(Kinesis::ViewTransform)));
486 }
487 std::memcpy(staging.data(), &vt, sizeof(Kinesis::ViewTransform));
488 } else {
489 std::memcpy(m_push_constant_data.data(), &vt, sizeof(Kinesis::ViewTransform));
490 }
491 }
492
493 const auto& staging = buffer->get_pipeline_context();
494 if (!staging.push_constant_staging.empty()) {
495 flow.push_constants(
496 cmd_id,
498 staging.push_constant_staging.data(),
499 staging.push_constant_staging.size());
500 } else if (!m_push_constant_data.empty()) {
501 flow.push_constants(
502 cmd_id,
505 m_push_constant_data.size());
506 }
507
508 on_before_execute(cmd_id, buffer);
509
510 flow.bind_vertex_buffers(cmd_id, { buffer });
511
512 uint32_t draw_count = 0;
513 if (m_vertex_count > 0) {
514 draw_count = m_vertex_count;
515 } else {
516 auto current_layout = buffer->get_vertex_layout();
517 if (!current_layout.has_value() || current_layout->vertex_count == 0) {
519 "Vertex layout has zero vertices, skipping draw");
520 return;
521 }
522 draw_count = current_layout->vertex_count;
523 }
524
525 flow.draw(cmd_id, draw_count, 1, m_first_vertex, 0);
526
527 foundry.end_commands(cmd_id);
528
529 buffer->set_pipeline_command(m_pipeline_id, cmd_id);
530 m_target_window->track_frame_command(cmd_id);
531
533 "Recorded secondary command buffer {} for window '{}'",
534 cmd_id, m_target_window->get_create_info().title);
535}
536
537void RenderProcessor::on_attach(const std::shared_ptr<Buffer>& buffer)
538{
540
541 auto vk_buffer = std::dynamic_pointer_cast<VKBuffer>(buffer);
542 if (vk_buffer && vk_buffer->has_vertex_layout()) {
543 auto vertex_layout = vk_buffer->get_vertex_layout();
544 if (vertex_layout.has_value()) {
546 "RenderProcessor: Auto-injecting vertex layout "
547 "({} vertices, {} attributes)",
548 vertex_layout->vertex_count,
549 vertex_layout->attributes.size());
550
552 m_buffer_info[vk_buffer] = { .semantic_layout = vertex_layout.value(), .use_reflection = false };
553 }
554 }
555
556 if (m_depth_enabled) {
557 vk_buffer->set_needs_depth_attachment(true);
558 }
559
560 if (!m_display_service) {
563 }
564}
565
606
607} // 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,...)
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
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
void set_view_transform_source(std::function< Kinesis::ViewTransform()> fn)
Set dynamic view transform source (evaluated every frame)
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)
void set_push_constant_size()
Set push constant size from type.
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.
void on_attach(const std::shared_ptr< Buffer > &buffer) override
Called when this processor is attached to a buffer.
std::vector< Portal::Graphics::DescriptorSetID > m_descriptor_set_ids
Abstract base class for shader-based buffer processing.
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.
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.
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.