58 std::vector<Kakshya::DataVariant>,
59 std::vector<Kakshya::DataVariant>> {
62 std::vector<Kakshya::DataVariant>,
63 std::vector<Kakshya::DataVariant>>;
89 uint32_t image_binding = 1,
90 std::vector<GpuBufferBinding> aux_bindings = {},
92 uint32_t output_binding = 0)
93 : Base(
std::move(config))
94 , m_output_format(output_format)
96 , m_aux_bindings(
std::move(aux_bindings))
98 m_image_slots.push_back({ .binding = { .set = 0,
99 .binding = image_binding,
100 .direction = GpuBufferBinding::Direction::INPUT,
101 .element_type = image_access } });
102 if (m_output_mode != OutputMode::SCALAR) {
103 m_image_slots.push_back({ .binding = { .set = 0,
104 .binding = output_binding,
105 .direction = GpuBufferBinding::Direction::OUTPUT,
106 .element_type = GpuBufferBinding::ElementType::IMAGE_STORAGE } });
126 const std::vector<GpuBufferBinding>& bindings,
129 , m_output_format(Portal::Graphics::
ImageFormat::RGBA8)
130 , m_output_mode(
mode)
132 for (
const auto&
b : bindings) {
133 const bool is_image =
b.element_type == GpuBufferBinding::ElementType::IMAGE_STORAGE
134 ||
b.element_type == GpuBufferBinding::ElementType::IMAGE_SAMPLED;
136 m_image_slots.push_back({ .binding =
b });
138 m_aux_bindings.push_back(
b);
172 m_output_dim_override = { w,
h };
177 m_output_dim_override = std::nullopt;
186 m_avoid_output_alias = enabled;
199 for (
auto& s : m_image_slots) {
200 if (s.binding.binding == binding_index)
203 error<std::runtime_error>(Journal::Component::Yantra, Journal::Context::BufferProcessing,
204 std::source_location::current(),
205 "TextureExecutionContext: no image slot at binding {}", binding_index);
225 if (!ensure_gpu_ready())
226 return Portal::Graphics::INVALID_FENCE;
229 return dispatch_core_async(
channels, structure_info);
242 result.
primary = readback_primary(last_effective_element_count());
243 readback_aux(result);
261 return collect_gpu_outputs(raw, {}, {});
278 if (ctx.
mode != ExecutionMode::CHAINED && ctx.
mode != ExecutionMode::CHAINED_INDIRECT)
279 return Base::execute(
input, ctx);
281 if (!ensure_gpu_ready()) {
282 error<std::runtime_error>(
283 Journal::Component::Yantra,
284 Journal::Context::BufferProcessing,
285 std::source_location::current(),
286 "TextureExecutionContext: GPU initialisation failed");
289 auto [ch_copies, structure_info] = extract_inputs(
input);
291 if (ctx.
mode == ExecutionMode::CHAINED_INDIRECT) {
292 dispatch_core_chained_indirect(ch_copies, structure_info, ctx);
294 dispatch_core_chained(ch_copies, structure_info, ctx);
317 const std::shared_ptr<Core::VKImage>&
image,
318 vk::Sampler sampler =
nullptr)
320 auto& slot = input_slot();
321 if (slot.binding.element_type == GpuBufferBinding::ElementType::IMAGE_STORAGE) {
322 stage_image_at(slot.binding.binding,
image, GpuBufferBinding::ElementType::IMAGE_STORAGE);
326 : Portal::Graphics::SamplerForge::instance().get_default_linear();
327 stage_image_at(slot.binding.binding,
image, GpuBufferBinding::ElementType::IMAGE_SAMPLED, s);
330 m_pending_container =
nullptr;
348 vk::Sampler sampler =
nullptr)
350 auto& slot = input_slot();
351 auto img = container_image(container, layer);
354 : Portal::Graphics::SamplerForge::instance().get_default_linear();
355 stage_image_at(slot.binding.binding, img, GpuBufferBinding::ElementType::IMAGE_SAMPLED, s);
357 m_pending_container =
nullptr;
377 auto& slot = output_slot();
378 if (m_avoid_output_alias) {
379 auto primary = acquire_output_image(slot,
width,
height);
380 const auto input = input_slot().image;
381 slot.image = primary && primary ==
input
383 : std::move(primary);
386 }
else if (!slot.image || slot.width !=
width || slot.height !=
height) {
387 slot.image = acquire_output_image(slot,
width,
height);
391 stage_image_at(slot.binding.binding, slot.image, GpuBufferBinding::ElementType::IMAGE_STORAGE);
405 std::vector<GpuBufferBinding> bindings;
406 for (
const auto& slot : m_image_slots)
407 bindings.push_back(slot.binding);
408 bindings.insert(bindings.end(), m_aux_bindings.begin(), m_aux_bindings.end());
420 m_pending_container = resolve_texture_container(
input);
430 const std::vector<std::vector<double>>& ,
433 if (m_pending_container) {
434 const auto sampler = Portal::Graphics::SamplerForge::instance().get_default_linear();
435 const auto img = container_image(*m_pending_container, m_pending_layer);
437 auto& in_slot = input_slot();
438 if (in_slot.binding.element_type == GpuBufferBinding::ElementType::IMAGE_STORAGE) {
439 stage_image_at(in_slot.binding.binding, img, GpuBufferBinding::ElementType::IMAGE_STORAGE);
441 stage_image_at(in_slot.binding.binding, img, GpuBufferBinding::ElementType::IMAGE_SAMPLED, sampler);
445 if (m_output_mode == OutputMode::CONTAINER
446 || m_output_mode == OutputMode::IMAGE) {
447 const uint32_t ow = m_output_dim_override
448 ? m_output_dim_override->first
449 : m_pending_container->get_width();
450 const uint32_t oh = m_output_dim_override
451 ? m_output_dim_override->second
452 : m_pending_container->get_height();
453 prepare_output_image(ow, oh);
463 size_t total_elements,
466 const auto& ws = gpu_config().workgroup_size;
467 if (m_output_dim_override) {
468 const uint32_t w = m_output_dim_override->first;
469 const uint32_t
h = m_output_dim_override->second;
471 (w + ws[0] - 1) / ws[0],
472 (
h + ws[1] - 1) / ws[1],
476 if (m_pending_container) {
477 const uint32_t w = m_pending_container->get_width();
478 const uint32_t
h = m_pending_container->get_height();
480 (w + ws[0] - 1) / ws[0],
481 (
h + ws[1] - 1) / ws[1],
485 return Base::calculate_dispatch_size(total_elements, structure_info);
495 const std::vector<std::vector<double>>& ,
498 if (m_output_mode == OutputMode::SCALAR)
501 auto img = get_output_image(output_slot().binding.binding);
503 error<std::runtime_error>(
504 Journal::Component::Yantra,
505 Journal::Context::BufferProcessing,
506 std::source_location::current(),
507 "TextureExecutionContext: no output image at declared output binding after dispatch");
510 Portal::Graphics::TextureLoom::instance().transition_layout(
511 img, vk::ImageLayout::eGeneral, vk::ImageLayout::eShaderReadOnlyOptimal);
513 if (m_output_mode == OutputMode::IMAGE)
516 const uint32_t w = m_output_dim_override
517 ? m_output_dim_override->first
518 : (m_pending_container ? m_pending_container->get_width() : img->get_width());
519 const uint32_t
h = m_output_dim_override
520 ? m_output_dim_override->second
521 : (m_pending_container ? m_pending_container->get_height() : img->get_height());
523 if (!m_output_container) {
524 m_output_container = std::make_shared<Kakshya::TextureContainer>(
525 w,
h, m_output_format);
528 if (!m_download_staging) {
529 m_download_staging = Buffers::create_image_staging_buffer(
530 m_output_container->byte_size());
533 m_output_container->from_image(img, m_download_staging, 0);
536 result.
container = std::static_pointer_cast<Kakshya::SignalSourceContainer>(
552 static constexpr size_t k_slot_cache_budget_bytes =
size_t { 256 } << 20;
569 std::shared_ptr<Core::VKImage>
image;
578 bool m_avoid_output_alias {
false };
579 uint32_t m_pending_layer {};
614 const bool array = layers > 1;
620 .kind = array ? ImageKey::Kind::SAMPLED_ARRAY : ImageKey::Kind::SAMPLED_2D,
623 const size_t staging_bytes = container.
byte_size() * (array ? layers : 1U);
624 if (!m_upload_staging) {
625 m_upload_staging = Buffers::create_image_staging_buffer(staging_bytes);
626 }
else if (staging_bytes > m_upload_staging->get_size_bytes()) {
627 m_upload_staging->resize(
628 static_cast<size_t>(
static_cast<float>(staging_bytes) * Buffers::k_buffer_growth_factor),
false);
631 auto image = Portal::Graphics::TextureLoom::instance()
632 .acquire_cached_image(m_input_cache, key);
636 const bool uploaded = array
639 return uploaded ?
image :
nullptr;
657 .format = m_output_format,
658 .kind = ImageKey::Kind::STORAGE_2D,
661 return cache.
acquire(Portal::Graphics::TextureLoom::instance(), key);
670 for (
auto& s : m_image_slots) {
671 if (s.binding.direction == GpuBufferBinding::Direction::INPUT)
675 error<std::runtime_error>(
676 Journal::Component::Yantra,
677 Journal::Context::BufferProcessing,
678 std::source_location::current(),
679 "TextureExecutionContext: no input image slot declared");
689 for (
auto& s : m_image_slots) {
690 if (s.binding.direction == GpuBufferBinding::Direction::OUTPUT)
694 error<std::runtime_error>(
695 Journal::Component::Yantra,
696 Journal::Context::BufferProcessing,
697 std::source_location::current(),
698 "TextureExecutionContext: no output image slot declared (SCALAR mode?)");
711 return std::dynamic_pointer_cast<Kakshya::TextureContainer>(
input.container.value());
Core::GlobalInputConfig input
uint32_t get_height() const
ImageFormat get_format() const
bool upload_image(const std::shared_ptr< Core::VKImage > &image, uint32_t layer, const std::shared_ptr< Buffers::VKBuffer > &staging) const
Upload one layer into an existing VKImage instead of creating one.
uint32_t get_width() const
uint32_t get_layer_count() const
bool upload_image_array(const std::shared_ptr< Core::VKImage > &image, const std::shared_ptr< Buffers::VKBuffer > &staging) const
Upload all layers into an existing VKImage instead of creating one.
size_t byte_size() const
Total byte count of the pixel buffer.
SignalSourceContainer wrapping GPU texture data as addressable pixel bytes.
std::shared_ptr< Core::VKImage > acquire(TextureLoom &loom, const ImageKey &key)
Cache images keyed by immutable allocation properties.
Type-parameterised shell over GpuDispatchCore.
void set_avoid_output_alias(bool enabled)
Select a distinct cached output image when the input is the current output image.
std::shared_ptr< Kakshya::TextureContainer > m_output_container
ImageSlot & output_slot()
Find the declared output image slot.
std::shared_ptr< Buffers::VKBuffer > m_download_staging
output_type execute(const input_type &input, const ExecutionContext &ctx) override
Override for CHAINED mode: skips collect_gpu_outputs().
TextureExecutionContext(GpuComputeConfig config, Portal::Graphics::ImageFormat output_format=Portal::Graphics::ImageFormat::RGBA8, OutputMode mode=OutputMode::CONTAINER, uint32_t image_binding=1, std::vector< GpuBufferBinding > aux_bindings={}, GpuBufferBinding::ElementType image_access=GpuBufferBinding::ElementType::IMAGE_SAMPLED, uint32_t output_binding=0)
output_type collect_container_result()
Collect the output TextureContainer after a signaled async dispatch.
std::shared_ptr< Buffers::VKBuffer > m_upload_staging
void on_before_gpu_dispatch(const std::vector< std::vector< double > > &, const DataStructureInfo &) override
Stages the pending TextureContainer at the declared input image binding and, in CONTAINER or IMAGE mo...
void set_output_dimensions(uint32_t w, uint32_t h)
Override the output storage image dimensions for the next dispatch.
void set_input_layer(uint32_t layer)
Set the array layer staged from the input TextureContainer on the next dispatch_async call.
std::shared_ptr< Kakshya::TextureContainer > m_pending_container
output_type collect_gpu_outputs(const GpuChannelResult &, const std::vector< std::vector< double > > &, const DataStructureInfo &) override
In CONTAINER mode: downloads the storage image at the declared output binding into a new TextureConta...
static std::shared_ptr< Kakshya::TextureContainer > resolve_texture_container(const input_type &input)
Extracts a TextureContainer pointer from datum.container if present.
void prepare_output_image(uint32_t width, uint32_t height)
Allocates the output storage image and stages it at the declared output binding.
std::vector< ImageSlot > m_image_slots
Portal::Graphics::FenceID dispatch_async(const input_type &input)
Non-blocking dispatch.
ImageSlot & input_slot()
Find the declared input image slot.
std::vector< GpuBufferBinding > m_aux_bindings
void stage_container(const Kakshya::TextureContainer &container, uint32_t layer=0, vk::Sampler sampler=nullptr)
Stage a TextureContainer layer at the declared input image binding.
std::shared_ptr< Core::VKImage > acquire_output_image(ImageSlot &slot, uint32_t width, uint32_t height, bool alternate=false)
Return this slot's cached image for these creation parameters, creating and caching one when there is...
std::optional< std::pair< uint32_t, uint32_t > > m_output_dim_override
Portal::Graphics::ImageCacheEntry m_input_cache
GpuChannelResult collect_result()
Collect SSBO readback after a signaled async dispatch.
void clear_output_dimensions()
std::array< uint32_t, 3 > calculate_dispatch_size(size_t total_elements, const DataStructureInfo &structure_info) const override
Derives dispatch size from the staged container dimensions when available; falls back to element-coun...
Portal::Graphics::ImageFormat m_output_format
void stage_image(const std::shared_ptr< Core::VKImage > &image, vk::Sampler sampler=nullptr)
Stage an arbitrary VKImage at the declared input image binding.
std::pair< std::vector< std::vector< double > >, DataStructureInfo > extract_inputs(const input_type &input) override
Stashes the TextureContainer from datum.container for use in on_before_gpu_dispatch; returns empty ch...
GpuBufferBinding & slot_binding(uint32_t binding_index)
Direct access to an image slot's binding descriptor by index.
TextureExecutionContext(GpuComputeConfig config, const std::vector< GpuBufferBinding > &bindings, OutputMode mode=OutputMode::SCALAR)
Construct from an explicit, fully-specified binding list.
std::vector< GpuBufferBinding > declare_buffer_bindings() const override
Declare the image bindings from m_image_slots, plus any aux SSBO bindings provided at construction.
std::shared_ptr< Core::VKImage > container_image(const Kakshya::TextureContainer &container, uint32_t layer)
The container's pixels as a GPU image, without allocating one per call.
GpuExecutionContext specialisation for image compute shaders.
ImageFormat
User-friendly image format enum.
bool is_image(const fs::path &filepath)
ElementType
Element type the shader expects in this binding.
uint32_t binding
Binding index within the set.
Declares a single storage buffer or image binding a compute shader expects.
Plain-data description of the compute shader to dispatch.
Image retained by the producing owner.
Immutable properties that determine whether an image can be reused.
Dimensions, modality, source type, and optional Composite field schema.
std::optional< std::shared_ptr< Kakshya::SignalSourceContainer > > container
Optional reference to container, required for regions.
Input/Output container for computation pipeline data flow with structure preservation.
ExecutionMode mode
Execution mode controlling scheduling behavior.
Context information controlling how a compute operation executes.
std::vector< float > primary
Erased output of a GPU dispatch: reconstructed float data plus any raw auxiliary outputs keyed by bin...
Portal::Graphics::ImageCacheSet alternate_cache
std::shared_ptr< Core::VKImage > image
Portal::Graphics::ImageCacheSet cache
One declared image binding: its GpuBufferBinding descriptor, the currently staged VKImage,...