MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
RenderProcessor.hpp
Go to the documentation of this file.
1#pragma once
2
7#include "ShaderProcessor.hpp"
8
9namespace MayaFlux::Core {
10class VKImage;
11}
12
13namespace MayaFlux::Buffers {
14
15/**
16 * @class RenderShaderProcessor
17 * @brief Graphics rendering processor - inherits from ShaderProcessor
18 *
19 * Overrides pipeline creation to use RenderFlow instead of ComputePress.
20 * Records draw commands but does NOT submit/present.
21 */
22class MAYAFLUX_API RenderProcessor : public ShaderProcessor {
23public:
24 RenderProcessor(const ShaderConfig& config);
25
27 {
28 cleanup();
29 }
30
31 void set_geometry_shader(const std::string& geometry_path);
32 void set_tess_control_shader(const std::string& tess_control_path);
33 void set_tess_eval_shader(const std::string& tess_eval_path);
34 void set_fragment_shader(const std::string& fragment_path);
35 void set_target_window(const std::shared_ptr<Core::Window>& window, const std::shared_ptr<VKBuffer>& buffer);
36
37 /** @brief Get the window targeted by this processor. */
38 std::shared_ptr<Core::Window> get_target_window() const { return m_target_window; }
39
41
42 void on_attach(const std::shared_ptr<Buffer>& buffer) override;
43
44 /**
45 * @brief Remove a buffer from this processor and its target window.
46 *
47 * Processor-wide pipeline and window state remain active for other buffers.
48 */
49 void on_detach(const std::shared_ptr<Buffer>& buffer) override;
50
51 /** @brief Change an attached buffer's visibility without stopping its processing. */
52 void set_visible(bool visible, const std::shared_ptr<VKBuffer>& buffer);
53
54 /** @brief Whether this processor contributes to presentation. */
55 bool is_visible(const std::shared_ptr<VKBuffer>& buffer) const;
56
57 /**
58 * @brief Record the buffer's prepared geometry into a fresh secondary command buffer.
59 *
60 * Does not advance feeds, upload geometry, or dispatch milling. Both normal
61 * processing and presentation refreshes use this operation. A buffer that has
62 * not prepared geometry yet records nothing.
63 */
64 void record_draw(const std::shared_ptr<VKBuffer>& buffer);
65
66 /// Set primitive topology (e.g., triangle list, line list, point list)
68 {
69 if (m_primitive_topology == topology) {
70 return;
71 }
72 m_primitive_topology = topology;
73 m_needs_pipeline_rebuild = true;
74 }
75
76 /// Set polygon mode (e.g., fill, line, point)
78 {
79 if (m_polygon_mode == mode) {
80 return;
81 }
82 m_polygon_mode = mode;
83 m_needs_pipeline_rebuild = true;
84 }
85
86 /// Set cull mode (e.g., none, front, back)
88 {
89 if (m_cull_mode == mode) {
90 return;
91 }
92 m_cull_mode = mode;
93 m_needs_pipeline_rebuild = true;
94 }
95
96 /**
97 * @brief Bind a texture to a descriptor binding point
98 * @param binding Binding index (matches shader layout(binding = N))
99 * @param texture VKImage texture to bind
100 * @param sampler Optional sampler (uses default linear if null)
101 */
102 void bind_texture(
103 uint32_t binding,
104 const std::shared_ptr<Core::VKImage>& texture,
105 vk::Sampler sampler = nullptr);
106
107 /**
108 * @brief Bind a texture to a named descriptor
109 * @param descriptor_name Logical name (must be in config.bindings)
110 * @param texture VKImage texture to bind
111 * @param sampler Optional sampler (uses default linear if null)
112 */
113 void bind_texture(
114 const std::string& descriptor_name,
115 const std::shared_ptr<Core::VKImage>& texture,
116 vk::Sampler sampler = nullptr);
117
118 /**
119 * @brief Check if pipeline is created
120 */
121 bool is_pipeline_ready() const { return m_pipeline_id != Portal::Graphics::INVALID_RENDER_PIPELINE; }
122
123 /**
124 * @brief Set vertex range for drawing subset of buffer
125 * @param first_vertex Starting vertex index in buffer
126 * @param vertex_count Number of vertices to draw
127 *
128 * Enables drawing a specific range of vertices from the bound buffer.
129 * Used for composite geometry where multiple collections are aggregated
130 * into a single buffer but rendered with different topologies.
131 *
132 * Default: draws all vertices (first_vertex=0, vertex_count=0 means "use layout count")
133 */
134 void set_vertex_range(uint32_t first_vertex, uint32_t vertex_count);
135
136 /**
137 * @brief Override the vertex layout used when building the pipeline for buffer
138 * @param buffer Target buffer (key into m_buffer_info)
139 * @param layout Layout specific to this processor's topology
140 *
141 * Called by CompositeGeometryBuffer to give each RenderProcessor its own
142 * topology-specific layout rather than the shared aggregate on the VKBuffer.
143 * Triggers a pipeline rebuild on the next execute_shader() call.
144 */
145 void set_buffer_vertex_layout(
146 const std::shared_ptr<VKBuffer>& buffer,
147 const Kakshya::VertexLayout& layout);
148
149 /**
150 * @brief Set blend mode for color attachment
151 * @param config Blend attachment configuration
152 */
154 {
155 if (m_blend_attachment.has_value() && *m_blend_attachment == config) {
156 return;
157 }
158 m_blend_attachment = config;
159 m_needs_pipeline_rebuild = true;
160 }
161
162 /** @brief Enable standard alpha blending (src_alpha, one_minus_src_alpha). Does not touch depth state. */
163 void enable_alpha_blending();
164
165 /** @brief Disable blending on the color attachment, restoring opaque writes. Does not touch depth state. */
166 void disable_alpha_blending();
167
168 /**
169 * @brief Toggle standard alpha blending.
170 * @param enabled true enables src_alpha/one_minus_src_alpha blending, false restores opaque.
171 *
172 * Blending and depth are independent. Toggling blend never changes depth
173 * test or depth write; use enable_depth_test / disable_depth_test for that.
174 */
175 void set_alpha_blending(bool enabled);
176
177 /** @brief Query whether the color attachment currently has blending enabled. */
178 [[nodiscard]] bool is_alpha_blending_enabled() const
179 {
180 return m_blend_attachment.has_value() && m_blend_attachment->blend_enable;
181 }
182
183 /**
184 * @brief Enable depth testing for this processor's pipeline
185 * @param compare_op Depth comparison operation (default: LESS)
186 *
187 * Marks the owning buffer as requiring a depth attachment.
188 * Pipeline will be created with D32_SFLOAT depth format.
189 */
190 void enable_depth_test(Portal::Graphics::CompareOp compare_op = Portal::Graphics::CompareOp::LESS);
191
192 /**
193 * @brief Disable depth testing and depth writes for this processor's pipeline.
194 *
195 * Leaves the buffer's depth attachment requirement intact so the pass still
196 * has a depth buffer available; only the per-pipeline test and write are off.
197 */
198 void disable_depth_test();
199
200 /**
201 * @brief Set static view transform (evaluated once)
202 * @param vt View and projection matrices
203 * @param enable_depth Enable the depth test if it is not already on.
204 * False leaves depth state untouched, which is what volume
205 * proxy geometry wants: a box that writes depth occludes every
206 * later volume drawn at the same geometry.
207 * @param cull Face culling mode. BACK suits opaque geometry. Volume
208 * rendering rasterises back faces so the entry clip works from
209 * inside the bounds.
210 */
211 void set_view_transform(
213 bool enable_depth = true,
214 Portal::Graphics::CullMode cull = Portal::Graphics::CullMode::BACK);
215
216 /**
217 * @brief Set dynamic view transform source (evaluated every frame)
218 * @param fn Callable returning ViewTransform, invoked each execute_shader
219 * @param enable_depth Enable the depth test if it is not already on.
220 * @param cull Face culling mode.
221 */
222 void set_view_transform_source(
223 std::function<Kinesis::ViewTransform()> fn,
224 bool enable_depth = true,
225 Portal::Graphics::CullMode cull = Portal::Graphics::CullMode::BACK);
226
227 /** @brief Get current static view transform, if set */
228 const std::optional<Kinesis::ViewTransform>& get_view_transform() const { return m_view_transform; }
229
230 /** @brief Get current dynamic view transform source, if set */
231 const std::function<Kinesis::ViewTransform()>& get_view_transform_source() const { return m_view_transform_source; }
232
233 /** @brief Set a static geometry transform. */
234 void set_geometry_transform(const glm::mat4& transform);
235
236 /** @brief Set a geometry transform source evaluated on each render tick. */
237 void set_geometry_transform_source(std::function<glm::mat4()> fn);
238
239 /** @brief Get the static geometry transform, if set. */
240 const std::optional<glm::mat4>& get_geometry_transform() const { return m_geometry_transform; }
241
242 /** @brief Get the geometry transform source, if set. */
243 const std::function<glm::mat4()>& get_geometry_transform_source() const { return m_geometry_transform_source; }
244
245 /**
246 * @brief Set number of instances for the next draw call.
247 * @param count Instance count. 1 is the default (non-instanced draw).
248 */
249 void set_instance_count(uint32_t count) { m_instance_count = count; }
250 [[nodiscard]] uint32_t get_instance_count() const { return m_instance_count; }
251
252 /**
253 * @brief Reduce supplied spans to triangles before drawing them.
254 *
255 * When enabled, the topology given to set_primitive_topology() describes
256 * the spans handed to set_runs() rather than the pipeline: it is built as
257 * TRIANGLE_LIST and no geometry stage is used. Points become quads and line
258 * segments ribbons, all drawn by one non-indexed draw. The vertex layout is
259 * preserved, so the vertex shader is unaffected.
260 *
261 * Resolved from RenderConfig::triangulate at configuration time, because
262 * shader and geometry-stage selection depend on it and happen there.
263 */
264 void set_triangulate(bool enabled);
265 [[nodiscard]] bool is_triangulate() const { return m_triangulate; }
266
267 /**
268 * @brief Clip this buffer's draws to an NDC region instead of the full framebuffer.
269 *
270 * Resolved from RenderConfig::scissor at configuration time. Unset means
271 * the full framebuffer, unchanged from before this existed. The pixel
272 * rect is recomputed from live swapchain dimensions every frame in
273 * execute_shader, so a set scissor stays correct across a resize.
274 */
275 void set_scissor(std::optional<Kinesis::Scissor> scissor) { m_scissor = scissor; }
276 [[nodiscard]] const std::optional<Kinesis::Scissor>& get_scissor() const { return m_scissor; }
277
278 /**
279 * @brief Spans to draw on subsequent frames.
280 * @param runs Spans in recording order.
281 *
282 * Used when triangulating. Without triangulation they are used only when more
283 * than one span is supplied and every span has the topology given to
284 * set_primitive_topology(): each span is then drawn on its own, so strip and fan
285 * geometry from several sources is not joined into one primitive. Any other
286 * combination draws the whole range as a single primitive, as before.
287 *
288 * Left empty, triangulation covers the whole buffer as one span at the
289 * topology given to set_primitive_topology(), honouring any range set by
290 * set_vertex_range(). Supplying spans is how a caller whose geometry mixes
291 * topologies overrides that.
292 *
293 * Not thread safe against a concurrent processing_function call; call from
294 * the thread driving the graphics tick.
295 */
296 void set_runs(std::vector<Portal::Graphics::DrawRun> runs);
297
298 /** @brief Spans as last supplied to set_runs(). */
299 [[nodiscard]] const std::vector<Portal::Graphics::DrawRun>& get_runs() const { return m_runs; }
300
301 /** @brief Shaping parameters for triangulation. Takes effect next frame. */
302 void set_mill_spec(const Portal::Graphics::MillSpec& spec);
303 [[nodiscard]] const Portal::Graphics::MillSpec& get_mill_spec() const { return m_mill_spec; }
304
305 /** @brief Vertices the last triangulation produced. Diagnostic only. */
306 [[nodiscard]] uint32_t milled_vertex_count() const;
307
308protected:
309 void initialize_pipeline(const std::shared_ptr<VKBuffer>& buffer) override;
310
311 /**
312 * @brief Prepare this buffer's geometry and record its draw commands.
313 *
314 * Publishes the render transform and advances triangulation before recording.
315 * Valid empty geometry still records a secondary command buffer so normal
316 * processing can present an empty frame. Presentation refreshes call
317 * record_draw() directly to reuse the prepared geometry.
318 */
319 void execute_shader(const std::shared_ptr<VKBuffer>& buffer) override;
320 void initialize_descriptors(const std::shared_ptr<VKBuffer>& buffer) override;
321
322 bool on_before_execute(Portal::Graphics::CommandBufferID cmd_id, const std::shared_ptr<VKBuffer>& buffer) override;
323
324 [[nodiscard]] bool tracks_pipeline_layout() const override { return true; }
325
326 void cleanup() override;
327
328 /**
329 * @brief Resolve the view and geometry transforms and write them to the UBO.
330 * @return The render transform just published.
331 *
332 * Host-side only, no command recording. Runs before the triangulation
333 * dispatch so that reads this frame's camera. Prefer pushing the returned
334 * value into a dispatch over binding the UBO, which has no
335 * per-frame-in-flight copies.
336 */
337 const Kinesis::RenderTransform& publish_render_transform();
338
339 /** @brief Transform published for this frame by publish_render_transform(). */
341 {
342 return m_published_render_transform;
343 }
344
345 /** @brief Resolve the active scissor against a live framebuffer size. Full framebuffer when unset. */
346 [[nodiscard]] vk::Rect2D resolve_scissor_rect(uint32_t width, uint32_t height) const noexcept;
347
348private:
349 struct BufferState {
351 bool use_reflection {};
352 std::unique_ptr<Portal::Graphics::PrimitiveMill> mill;
353 std::shared_ptr<VKBuffer> draw_source;
354 uint32_t first_vertex {};
355 uint32_t vertex_count {};
356 uint32_t index_count {};
357 bool geometry_prepared {};
358 };
359
360 Portal::Graphics::RenderPipelineID m_pipeline_id = Portal::Graphics::INVALID_RENDER_PIPELINE;
361 Portal::Graphics::ShaderID m_geometry_shader_id = Portal::Graphics::INVALID_SHADER;
362 Portal::Graphics::ShaderID m_tess_control_shader_id = Portal::Graphics::INVALID_SHADER;
363 Portal::Graphics::ShaderID m_tess_eval_shader_id = Portal::Graphics::INVALID_SHADER;
364 Portal::Graphics::ShaderID m_fragment_shader_id = Portal::Graphics::INVALID_SHADER;
365 Portal::Graphics::DescriptorSetID m_render_transform_descriptor_set_id {
366 Portal::Graphics::INVALID_DESCRIPTOR_SET
367 };
368 std::shared_ptr<Core::Window> m_target_window;
369
370 std::unordered_map<std::shared_ptr<VKBuffer>, BufferState> m_buffer_info;
371 /** @brief Non-owning exceptions to default visibility, removed on detach. */
372 std::vector<const VKBuffer*> m_hidden_buffers;
373 Registry::Service::DisplayService* m_display_service = nullptr;
374
375 Portal::Graphics::PrimitiveTopology m_primitive_topology { Portal::Graphics::PrimitiveTopology::TRIANGLE_LIST };
376 Portal::Graphics::PolygonMode m_polygon_mode { Portal::Graphics::PolygonMode::FILL };
377 Portal::Graphics::CullMode m_cull_mode { Portal::Graphics::CullMode::NONE };
378
380 std::shared_ptr<Core::VKImage> texture;
381 vk::Sampler sampler;
382 };
383 std::unordered_map<uint32_t, TextureBinding> m_texture_bindings;
384
385 std::optional<Portal::Graphics::BlendAttachmentConfig> m_blend_attachment;
386
388
389 bool m_depth_enabled {};
390 std::shared_ptr<VKBuffer> m_render_transform_ubo;
391
392 /**
393 * @brief A replaced pipeline kept alive until the frames that may still use it finish
394 */
397 std::shared_ptr<Core::Window> window;
398 std::shared_ptr<VKBuffer> transform_ubo;
399 uint64_t after_frame;
400 };
401 std::vector<RetiredPipeline> m_retired;
402 uint32_t m_binding_wait_ticks {};
403 std::set<std::pair<uint32_t, uint32_t>> m_awaited_bindings;
404
405 /**
406 * @brief Move the current pipeline to the retired list and drop its buffer associations
407 */
408 void retire_current_pipeline();
409
410 /**
411 * @brief Destroy retired pipelines whose window has completed every frame submitted before retirement
412 *
413 * Never blocks. Without a completion signal, retired pipelines live until cleanup().
414 */
415 void collect_retired_pipelines();
416 bool m_view_transform_active {};
417 uint32_t m_first_vertex { 0 };
418 uint32_t m_vertex_count { 0 };
419 uint32_t m_instance_count { 1 };
420
421 std::optional<Kinesis::ViewTransform> m_view_transform;
423 Kinesis::RenderTransform m_published_render_transform {};
424 std::optional<glm::mat4> m_geometry_transform;
425 std::function<glm::mat4()> m_geometry_transform_source;
426
427 bool m_triangulate {};
428 std::optional<Kinesis::Scissor> m_scissor;
429 std::vector<Portal::Graphics::DrawRun> m_runs;
431
432 uint32_t m_last_milled_vertex_count {};
433
434 /**
435 * @brief Topology the pipeline is built with, as opposed to the topology
436 * m_runs are declared in.
437 */
438 [[nodiscard]] Portal::Graphics::PrimitiveTopology pipeline_topology() const;
439
440 /**
441 * @brief Mill m_runs into this attachment's output.
442 * @return Number of vertices prepared for drawing.
443 */
444 uint32_t mill_runs(const std::shared_ptr<VKBuffer>& buffer, Portal::Graphics::PrimitiveMill& mill);
445
446 /** @brief Prepare and retain the source and draw range for one attached buffer. */
447 void prepare_geometry(const std::shared_ptr<VKBuffer>& buffer, BufferState& state);
448
449 /** @brief Record an already validated attachment without repeating its lookup. */
450 void record_draw(const std::shared_ptr<VKBuffer>& buffer, const BufferState& state);
451
452 BufferState* get_or_cache_buffer_state(const std::shared_ptr<VKBuffer>& buffer);
453};
454
455} // namespace MayaFlux::Buffers
std::shared_ptr< NetworkGeometryBuffer > buffer
std::weak_ptr< Core::Window > window
Definition Picker.cpp:23
uint32_t mode
PrimitiveTopology topology
tvdb_value_type_t vt
size_t count
uint32_t width
uint32_t height
void set_cull_mode(Portal::Graphics::CullMode mode)
Set cull mode (e.g., none, front, back)
void set_primitive_topology(Portal::Graphics::PrimitiveTopology topology)
Set primitive topology (e.g., triangle list, line list, point list)
void set_blend_attachment(const Portal::Graphics::BlendAttachmentConfig &config)
Set blend mode for color attachment.
std::shared_ptr< Core::Window > get_target_window() const
Get the window targeted by this processor.
const std::vector< Portal::Graphics::DrawRun > & get_runs() const
Spans as last supplied to set_runs().
void set_scissor(std::optional< Kinesis::Scissor > scissor)
Clip this buffer's draws to an NDC region instead of the full framebuffer.
std::optional< Portal::Graphics::BlendAttachmentConfig > m_blend_attachment
const std::function< glm::mat4()> & get_geometry_transform_source() const
Get the geometry transform source, if set.
const std::optional< Kinesis::ViewTransform > & get_view_transform() const
Get current static view transform, if set.
const std::optional< Kinesis::Scissor > & get_scissor() const
const std::optional< glm::mat4 > & get_geometry_transform() const
Get the static geometry transform, if set.
std::vector< Portal::Graphics::DrawRun > m_runs
void set_instance_count(uint32_t count)
Set number of instances for the next draw call.
std::function< Kinesis::ViewTransform()> m_view_transform_source
bool is_pipeline_ready() const
Check if pipeline is created.
std::optional< Kinesis::ViewTransform > m_view_transform
Portal::Graphics::DepthStencilConfig m_depth_stencil
std::unordered_map< uint32_t, TextureBinding > m_texture_bindings
Portal::Graphics::RenderPipelineID get_render_pipeline_id() const
void set_polygon_mode(Portal::Graphics::PolygonMode mode)
Set polygon mode (e.g., fill, line, point)
std::optional< Kinesis::Scissor > m_scissor
const Kinesis::RenderTransform & published_render_transform() const
Transform published for this frame by publish_render_transform().
std::set< std::pair< uint32_t, uint32_t > > m_awaited_bindings
std::unordered_map< std::shared_ptr< VKBuffer >, BufferState > m_buffer_info
const Portal::Graphics::MillSpec & get_mill_spec() const
std::function< glm::mat4()> m_geometry_transform_source
std::vector< RetiredPipeline > m_retired
bool is_alpha_blending_enabled() const
Query whether the color attachment currently has blending enabled.
const std::function< Kinesis::ViewTransform()> & get_view_transform_source() const
Get current dynamic view transform source, if set.
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
std::shared_ptr< VKBuffer > m_render_transform_ubo
bool tracks_pipeline_layout() const override
Whether the owner builds its pipeline layout from this processor's bindings, staged descriptors and p...
Abstract base class for shader-based buffer processing.
Mills spans of mixed primitive topology into one TRIANGLE_LIST vertex buffer on the GPU.
PolygonMode
Rasterization polygon mode.
PrimitiveTopology
Vertex assembly primitive topology.
CompareOp
Depth/stencil comparison operation.
std::unique_ptr< Portal::Graphics::PrimitiveMill > mill
A replaced pipeline kept alive until the frames that may still use it finish.
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.
Per-attachment blend configuration.
Depth and stencil test configuration.
How a PrimitiveMill shapes spans.
Backend display and presentation service interface.