12 : m_config({ shader_path })
15 initialize_buffer_service();
16 initialize_compute_service();
20 : m_config(
std::move(config))
38 auto vk_buffer = std::dynamic_pointer_cast<VKBuffer>(
buffer);
41 "ShaderProcessor can only process VKBuffers");
58 "on_before_execute() reported failure, skipping shader execution");
92 auto vk_buffer = std::dynamic_pointer_cast<VKBuffer>(
buffer);
101 "ShaderProcessor attached to VKBuffer (size: {} bytes, modality: {})",
102 vk_buffer->get_size_bytes(),
103 static_cast<int>(vk_buffer->get_modality()));
108 auto vk_buffer = std::dynamic_pointer_cast<VKBuffer>(
buffer);
113 if (it->second == vk_buffer) {
124 return std::dynamic_pointer_cast<VKBuffer>(
buffer) !=
nullptr;
135 "Cannot bind null buffer to descriptor '{}'", descriptor_name);
142 uint32_t binding_index = 0;
143 while (std::ranges::any_of(
m_config.
bindings, [binding_index](
const auto& pair) {
144 return pair.second.set == 1 && pair.second.binding == binding_index;
150 default_binding.
set = 1;
151 default_binding.
binding = binding_index;
152 default_binding.
type = vk::DescriptorType::eStorageBuffer;
156 "Created default binding for '{}': set={}, binding={}",
157 descriptor_name, default_binding.
set, default_binding.
binding);
164 "Bound buffer to descriptor '{}' (size: {} bytes)",
165 descriptor_name,
buffer->get_size_bytes());
185 std::string descriptor_name;
187 descriptor_name =
"input";
189 descriptor_name =
"output";
199 const std::string& descriptor_name,
202 const std::shared_ptr<VKBuffer>& staging)
const
207 "download_bound: no buffer bound to descriptor '{}'", descriptor_name);
223 "feed: null source for '{}'",
name);
229 entry.source = std::move(source);
230 entry.is_storage =
true;
233 entry.format.reset();
234 entry.descriptor_name =
name;
235 entry.buffer.reset();
236 entry.mismatch_logged =
false;
244 if (field.name ==
name) {
246 entry.source = std::move(source);
247 entry.is_storage =
false;
248 entry.offset = layout.offsets[i];
250 entry.format = field.format;
251 entry.descriptor_name.clear();
252 entry.buffer.reset();
253 entry.mismatch_logged =
false;
260 "feed: '{}' matches no descriptor and no push constant field. "
261 "File-shader processors must supply an explicit offset.",
269 "feed: null source for '{}'",
name);
273 if (
size !=
sizeof(
float) &&
size !=
sizeof(
double)) {
275 "feed: '{}' requests {} bytes, only 4 or 8 are written",
name,
size);
280 entry.source = std::move(source);
281 entry.is_storage =
false;
284 entry.format.reset();
285 entry.descriptor_name.clear();
286 entry.buffer.reset();
287 entry.mismatch_logged =
false;
298 if (it->second.is_storage) {
312 std::vector<std::string> names;
315 names.push_back(
name);
323 if (entry.early != early) {
329 if (!entry.is_storage) {
330 if (
const auto* typed = std::get_if<Kakshya::GpuValue>(&
value)) {
332 if (!entry.mismatch_logged) {
334 "feed '{}' returned a value of a different format than its push constant field",
name);
335 entry.mismatch_logged =
true;
340 std::array<std::byte, 64> bytes {};
344 const size_t required = entry.offset + written;
345 if (data.size() < required) {
346 data.resize(required);
349 std::memcpy(data.data() + entry.offset, bytes.data(), written);
355 if (!entry.mismatch_logged) {
357 "feed '{}' writes a push constant but returned a DataVariant",
name);
358 entry.mismatch_logged =
true;
364 const size_t required = entry.offset + entry.size;
365 if (data.size() < required) {
366 data.resize(required);
369 if (entry.size ==
sizeof(
float)) {
370 const auto narrowed =
static_cast<float>(*scalar);
371 std::memcpy(data.data() + entry.offset, &narrowed,
sizeof(
float));
373 std::memcpy(data.data() + entry.offset,
scalar,
sizeof(
double));
378 auto* variant = std::get_if<Kakshya::DataVariant>(&
value);
380 if (!entry.mismatch_logged) {
382 "feed '{}' writes a storage descriptor but returned a push constant value",
name);
383 entry.mismatch_logged =
true;
391 if (!
ptr || bytes == 0) {
393 "feed '{}' produced no bytes",
name);
398 entry.buffer = std::make_shared<VKBuffer>(
399 static_cast<size_t>(
static_cast<float>(bytes) * 1.5F),
400 m_config.
bindings.at(entry.descriptor_name).type == vk::DescriptorType::eUniformBuffer
401 ? VKBuffer::Usage::UNIFORM
402 : VKBuffer::Usage::HOST_STORAGE,
408 "feed '{}' backing buffer created at {} bytes",
409 name, entry.buffer->get_size_bytes());
410 }
else if (entry.buffer->get_size_bytes() < bytes) {
411 const size_t grown = bytes * 3 / 2;
414 "feed '{}' backing buffer resized {} to {} bytes",
415 name, entry.buffer->get_size_bytes(), grown);
417 entry.buffer->resize(grown,
false);
452 "Shader hot-reloaded successfully (ID: {})",
m_shader_id);
481 "Push constant data size {} exceeds configured size {}",
494 for (
const auto& entry :
buffer->get_pipeline_context().push_constant_bindings) {
495 size = std::max(
size,
static_cast<size_t>(entry.offset) + entry.data.size());
499 if (!entry.is_storage) {
500 size = std::max(
size,
static_cast<size_t>(entry.offset) + entry.size);
512 for (
const auto& entry :
buffer->get_pipeline_context().push_constant_bindings) {
513 std::memcpy(
out.data() + entry.offset, entry.data.data(), entry.data.size());
519 std::vector<uint8_t> merged;
557 foundry.release_fence(
fence);
564 const std::shared_ptr<VKBuffer>&
buffer)
569 foundry.submit_and_wait(cmd_id);
577 "Deferred submission failed, no fence returned");
624 if (descriptor_name ==
"input")
626 if (descriptor_name ==
"output")
644 std::vector<std::string> names;
647 names.push_back(
name);
654 return std::ranges::all_of(
656 [
this](
const auto& pair) {
657 return m_bound_buffers.find(pair.first) != m_bound_buffers.end();
692 "Failed to load shader: {}",
700 "Shader loaded: {} (ID: {})",
706 const auto mix = [](uint64_t seed, uint64_t
value) {
707 return seed ^ (
value + 0x9e3779b97f4a7c15ULL + (seed << 6) + (seed >> 2));
709 const auto slot = [&mix](uint32_t set, uint32_t binding, vk::DescriptorType
type, uint32_t
count) {
710 uint64_t hash = mix(0, set);
711 hash = mix(hash, binding);
712 hash = mix(hash,
static_cast<uint64_t
>(
type));
713 return mix(hash,
count);
716 const auto& staged =
buffer->get_pipeline_context().descriptor_buffer_bindings;
717 const auto find_staged = [&staged](uint32_t set, uint32_t binding) {
718 return std::ranges::find_if(staged, [set, binding](
const auto& entry) {
719 return entry.set == set && entry.binding == binding;
725 const auto override_it = find_staged(binding.set, binding.binding);
726 sum += override_it != staged.end()
727 ? slot(binding.set, binding.binding, override_it->type, override_it->count)
728 : slot(binding.set, binding.binding, binding.type, binding.count);
731 for (
auto it = staged.begin(); it != staged.end(); ++it) {
732 const bool configured = std::ranges::any_of(
m_config.
bindings, [it](
const auto& pair) {
733 return pair.second.set == it->set && pair.second.binding == it->binding;
735 const bool repeated = find_staged(it->set, it->binding) != it;
736 if (!configured && !repeated) {
737 sum += slot(it->set, it->binding, it->type, it->count);
756 const uint32_t idx = set - 1;
773 auto& descriptor_bindings =
buffer->get_pipeline_context().descriptor_buffer_bindings;
775 std::set<std::pair<uint32_t, uint32_t>> updated_pairs;
777 for (
const auto& binding : descriptor_bindings) {
781 "Descriptor set index {} out of range or reserved", binding.set);
785 foundry.update_descriptor_buffer(
789 binding.buffer_info.buffer,
790 binding.buffer_info.offset,
791 binding.buffer_info.range);
793 updated_pairs.emplace(binding.set, binding.binding);
802 const auto& binding = binding_it->second;
803 auto key = std::make_pair(binding.set, binding.binding);
805 if (updated_pairs.count(key)) {
812 "Invalid descriptor set index {} for binding '{}'",
813 binding.set, descriptor_name);
817 foundry.update_descriptor_buffer(
823 buf->get_size_bytes());
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_RT_WARN(comp, ctx,...)
#define MF_RT_ERROR(comp, ctx,...)
#define MF_TRACE(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
#define MF_RT_DEBUG(comp, ctx,...)
std::shared_ptr< NetworkGeometryBuffer > buffer
std::vector< float > * out
vk::PhysicalDeviceType type
ProcessingToken m_processing_token
bool download_bound(const std::string &descriptor_name, void *data, size_t size, const std::shared_ptr< VKBuffer > &staging=nullptr) const
Download the buffer currently bound to a named descriptor.
virtual void initialize_pipeline(const std::shared_ptr< VKBuffer > &buffer)=0
const std::vector< uint8_t > & get_push_constant_data() const
Get current push constant data.
bool has_feed(const std::string &name) const
Whether a feed of this name is registered.
ShaderProcessor(const std::string &shader_path)
Construct processor with shader path.
Portal::Graphics::CommandBufferID m_last_command_buffer
bool are_bindings_complete() const
Check if all required bindings are satisfied.
std::optional< uint32_t > resolve_ds_index(uint32_t set) const
Resolve logical descriptor set index to actual index.
virtual void execute_shader(const std::shared_ptr< VKBuffer > &buffer)=0
std::unordered_map< std::string, std::shared_ptr< VKBuffer > > m_bound_buffers
virtual bool tracks_pipeline_layout() const
Whether the owner builds its pipeline layout from this processor's bindings, staged descriptors and p...
uint64_t layout_fingerprint(const std::shared_ptr< VKBuffer > &buffer) const
void unbind_buffer(const std::string &descriptor_name)
Unbind a buffer from a descriptor.
Portal::Graphics::ShaderID m_shader_id
std::vector< uint8_t > m_push_constant_data
std::vector< std::string > get_feed_names() const
Names of every registered feed.
virtual void on_after_execute(Portal::Graphics::CommandBufferID cmd_id, const std::shared_ptr< VKBuffer > &buffer)
Called after each process callback.
virtual void on_descriptors_created()
Called after descriptor sets are created.
virtual void initialize_descriptors(const std::shared_ptr< VKBuffer > &buffer)=0
bool m_needs_descriptor_rebuild
std::shared_ptr< VKBuffer > m_pending_buffer
Buffer retained for the outstanding submission.
std::shared_ptr< VKBuffer > get_bound_buffer(const std::string &descriptor_name) const
Get bound buffer for a descriptor name.
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...
virtual void update_descriptors(const std::shared_ptr< VKBuffer > &buffer)
void resolve_push_constants_into(std::vector< uint8_t > &out, const std::shared_ptr< VKBuffer > &buffer) const
resolve_push_constants() written into out, reusing its storage.
virtual void on_before_compile(const std::string &shader_path)
Called before shader compilation.
std::optional< uint64_t > m_created_layout
virtual bool on_before_execute(Portal::Graphics::CommandBufferID cmd_id, const std::shared_ptr< VKBuffer > &buffer)
Called before each process callback.
void on_detach(const std::shared_ptr< Buffer > &buffer) override
Called when this processor is detached from a buffer.
void auto_bind_buffer(const std::shared_ptr< VKBuffer > &buffer)
Auto-bind buffer based on attachment order.
bool has_binding(const std::string &descriptor_name) const
Check if a descriptor binding exists.
void set_push_constant_size()
Set push constant size from type.
void processing_function(const std::shared_ptr< Buffer > &buffer) override
The core processing function that must be implemented by derived classes.
virtual void on_pipeline_created(Portal::Graphics::ComputePipelineID pipeline_id)
Called after pipeline is created.
~ShaderProcessor() override
Portal::Graphics::FenceID m_pending_fence
Outstanding async submission, if any.
virtual bool is_in_place_operation(const std::string &descriptor_name) const
Check if shader modifies a specific buffer in-place.
virtual void on_shader_loaded(Portal::Graphics::ShaderID shader_id)
Called after shader is loaded.
void submit_recorded(Portal::Graphics::CommandBufferID cmd_id, const std::shared_ptr< VKBuffer > &buffer)
Submit a recorded command buffer honoring the submission mode.
void add_binding(const std::string &descriptor_name, const ShaderBinding &binding)
Add descriptor binding configuration.
bool m_deferred_submission
False submits synchronously, preserving pre-existing behaviour.
void note_created_layout(const std::shared_ptr< VKBuffer > &buffer)
void set_deferred_submission(bool deferred)
Submit asynchronously and resolve at the top of a later cycle.
bool hot_reload_shader()
Hot-reload shader from ShaderFoundry.
virtual void on_before_descriptors_create()
Called before descriptor sets are created.
void set_config(const ShaderConfig &config)
Update entire configuration.
void set_shader(const std::string &shader_path)
Update shader path and reload.
bool m_engine_owns_set_zero
Whether the engine reserves set=0 for global resources.
bool is_compatible_with(const std::shared_ptr< Buffer > &buffer) const override
Checks if this processor can handle the specified buffer type.
virtual void on_dispatch_complete(const std::shared_ptr< VKBuffer > &buffer)
Called once when an asynchronous submission is observed complete.
virtual BufferUsageHint get_buffer_usage_hint(const std::string &descriptor_name) const
Get buffer usage hint for a descriptor.
void pump_feeds(bool early=true)
Pull every feed and write its result.
bool resolve_pending_dispatch(bool block)
Resolve an outstanding asynchronous submission.
BufferUsageHint
Get buffer usage characteristics needed for safe data flow.
@ OUTPUT_WRITE
Shader writes output (modifies)
@ INPUT_READ
Shader reads input.
std::vector< std::string > get_binding_names() const
Get all configured descriptor names.
bool is_dispatch_pending() const
True while an asynchronous submission is outstanding.
std::unordered_map< std::string, Feed > m_feeds
void remove_feed(const std::string &name)
Remove a feed.
void set_specialization_constant(uint32_t constant_id, uint32_t value)
Set specialization constant.
std::function< FeedValue()> FeedSource
A callable pulled once per processing cycle.
bool m_needs_pipeline_rebuild
void on_attach(const std::shared_ptr< Buffer > &buffer) override
Called when this processor is attached to a buffer.
void bind_buffer(const std::string &descriptor_name, const std::shared_ptr< VKBuffer > &buffer)
Bind a VKBuffer to a named shader descriptor.
std::variant< double, Kakshya::GpuValue, Kakshya::DataVariant > FeedValue
What a feed callable returns.
std::vector< Portal::Graphics::DescriptorSetID > m_descriptor_set_ids
void clear_specialization_constants()
Clear all specialization constants.
void feed(const std::string &name, FeedSource source)
Supply a shader input from a callable, resolved by name.
std::vector< uint8_t > resolve_push_constants(const std::shared_ptr< VKBuffer > &buffer) const
This processor's push constant data with buffer-staged fragments overlaid at their declared offsets.
virtual void set_push_constant_data_raw(const void *data, size_t size)
Update push constant data (raw bytes)
void initialize_compute_service()
void initialize_buffer_service()
void ensure_initialized(const std::shared_ptr< VKBuffer > &buffer)
auto gpu_buffer() const
Get raw buffer info for GPU upload.
Type-erased accessor for NDData with semantic view construction.
@ GRAPHICS_BACKEND
Standard graphics processing backend configuration.
void download_from_gpu(const std::shared_ptr< VKBuffer > &source, void *data, size_t size, const std::shared_ptr< VKBuffer > &staging)
Download from GPU buffer to raw data (auto-detects host-visible vs device-local)
void upload_host_visible(const std::shared_ptr< VKBuffer > &target, const Kakshya::DataVariant &data, size_t dst_offset)
Upload data to a host-visible buffer.
@ BufferProcessing
Buffer processing (Buffers::BufferManager, processing chains)
@ Buffers
Buffers, Managers, processors and processing chains.
size_t gpu_data_format_bytes(GpuDataFormat fmt) noexcept
Byte size of one element of a GpuDataFormat.
@ UNKNOWN
Unknown or undefined modality.
GpuDataFormat gpu_value_format(const GpuValue &value) noexcept
The GpuDataFormat of the value held.
size_t gpu_value_write(const GpuValue &value, std::span< std::byte > out) noexcept
Copies the held value's bytes into out, unconverted.
uint64_t ComputePipelineID
constexpr ShaderID INVALID_SHADER
constexpr FenceID INVALID_FENCE
MAYAFLUX_API ShaderFoundry & get_shader_foundry()
Get the global shader compiler instance.
Kakshya::GpuBlockLayout push_constant_layout(const std::vector< PushConstantField > &fields)
Offsets and total size of a push constant block, under std430.
MAYAFLUX_API ComputePress & get_compute_press()
uint32_t binding
Binding point within set.
uint32_t set
Descriptor set index.
Describes how a VKBuffer binds to a shader descriptor.
std::string shader_path
Path to shader file.
std::unordered_map< uint32_t, uint32_t > specialization_constants
std::unordered_map< std::string, ShaderBinding > bindings
size_t push_constant_size
Portal::Graphics::ShaderStage stage
std::vector< Portal::Graphics::PushConstantField > pc_fields
Retained from a ShaderSpec so feeds can resolve a field name to an offset. Empty for file shaders.
Portal::Graphics::ShaderID shader_id