27 std::optional<Kakshya::VertexLayout>
layout;
28 std::optional<Kakshya::MeshData>
mesh;
29 std::optional<Kakshya::ImageData>
image;
71 if (!source || !source->is_initialized()) {
72 MF_RT_ERROR(Journal::Component::Buffers, Journal::Context::BufferProcessing,
73 "DataReadProcessor read_buffer requires an initialized source buffer");
86 if (
const auto volume = std::dynamic_pointer_cast<VolumeGridBuffer>(vk);
87 volume && volume->has_field(
name)) {
88 const size_t bytes = volume->get_field_bytes(
name);
91 .mapped_ptr =
nullptr,
97 if (
const auto network = std::dynamic_pointer_cast<NetworkGeometryBuffer>(vk);
99 const size_t bytes =
network->get_state_bytes(
name);
105 "DataReadProcessor read_field found no field named '{}' on the attached buffer",
name);
113 const auto& slots = vk->get_buffer_resources().back_buffers;
114 if (
index >= slots.size() || byte_count == 0) {
115 MF_RT_ERROR(Journal::Component::Buffers, Journal::Context::BufferProcessing,
116 "DataReadProcessor read_back_buffer needs an existing slot and a nonzero byte count");
127 if (std::dynamic_pointer_cast<TextureBuffer>(vk)) {
129 "DataReadProcessor read_vertices does not read TextureBuffer's display quad");
132 if (vk->has_index_buffer()) {
134 "DataReadProcessor read_vertices cannot read an indexed buffer; use read_mesh");
140 "DataReadProcessor requires a valid vertex layout within the primary allocation");
145 static_cast<size_t>(pending.
layout->vertex_count) * pending.
layout->stride_bytes,
153 const auto keep_cpu_mesh = [&pending](std::optional<Kakshya::MeshData> mesh) {
156 "DataReadProcessor read_mesh found no geometry to read");
159 pending.
mesh = std::move(mesh);
163 if (
const auto mesh_buffer = std::dynamic_pointer_cast<MeshBuffer>(vk)) {
164 return keep_cpu_mesh(mesh_buffer->get_mesh_data());
167 if (
const auto network_buffer = std::dynamic_pointer_cast<MeshNetworkBuffer>(vk)) {
168 const auto network = network_buffer->get_network();
169 return keep_cpu_mesh(
network ?
network->get_mesh_data() : std::nullopt);
172 if (
const auto geometry_buffer = std::dynamic_pointer_cast<GeometryBuffer>(vk)) {
173 const auto node = std::dynamic_pointer_cast<Nodes::GpuSync::MeshWriterNode>(
174 geometry_buffer->get_geometry_node());
175 return keep_cpu_mesh(node ? node->get_mesh_data() : std::nullopt);
178 std::optional<uint32_t> live_count;
179 if (
const auto compute_mesh = std::dynamic_pointer_cast<ComputeMeshBuffer>(vk)) {
180 live_count = compute_mesh->get_live_vertex_count();
181 }
else if (
const auto volume = std::dynamic_pointer_cast<VolumeGridBuffer>(vk)) {
182 live_count = volume->get_live_vertex_count();
185 if (!live_count || *live_count == 0) {
187 "DataReadProcessor read_mesh requires a mesh, geometry or mesh network buffer, "
188 "or a compute mesh or volume surface with live vertices");
202 const auto texture = std::dynamic_pointer_cast<TextureBuffer>(vk);
203 if (!texture || texture->get_pixel_data().empty()) {
205 "DataReadProcessor requires host pixel data for a host pixel read");
209 const auto format = texture->get_format();
211 texture->get_width(), texture->get_height(),
213 format, texture->get_pixel_data());
214 if (!pending.
image) {
216 "DataReadProcessor host pixel data does not match the texture's format and size");
228 std::shared_ptr<Core::VKImage>
image;
229 if (
const auto texture = std::dynamic_pointer_cast<TextureBuffer>(vk)) {
231 }
else if (
const auto node_texture = std::dynamic_pointer_cast<NodeTextureBuffer>(vk)) {
235 auto& loom = TextureLoom::instance();
236 if (!loom.is_initialized()) {
238 "DataReadProcessor texture pixel reads require an initialized TextureLoom");
242 const auto format = TextureLoom::readable_format(
image);
245 "DataReadProcessor texture pixel reads require an initialized TextureBuffer or NodeTextureBuffer "
246 "image that is a single-layer 2D image with a mapped format and transfer-source usage");
250 const auto original_layout =
image->get_current_layout();
251 if (original_layout != vk::ImageLayout::eShaderReadOnlyOptimal
252 && original_layout != vk::ImageLayout::eGeneral) {
254 "DataReadProcessor texture image has no readable pixel data");
258 const size_t byte_count = TextureLoom::calculate_image_size(
264 vk::PipelineStageFlags restore_stage = vk::PipelineStageFlagBits::eFragmentShader;
265 if (original_layout == vk::ImageLayout::eGeneral) {
266 restore_stage |= vk::PipelineStageFlagBits::eComputeShader;
270 return pending.
image.has_value();
290 std::unique_ptr<PendingRead> result;
292 [
kind](std::unique_ptr<PendingRead>& pending) {
295 return !pending->mesh;
297 return pending->layout.has_value();
299 return pending->mesh.has_value() || pending->triangle_soup;
301 return pending->image.has_value();
305 [
this, &result](std::unique_ptr<PendingRead>& pending) {
306 if (pending->transfer) {
310 pending->bytes.data(),
311 pending->bytes.size(),
314 result = std::move(pending);
330 if (pending->image) {
331 const auto* begin =
static_cast<const uint8_t*
>(pending->image->data());
332 return Kakshya::DataVariant { std::vector<uint8_t>(begin, begin + pending->image->byte_size()) };
341 if (!pending || !pending->layout) {
345 std::vector<Kakshya::DataVariant>
channels;
362 return std::move(pending->mesh);
375 return std::move(pending->image);
380 const auto vk_buffer = std::dynamic_pointer_cast<VKBuffer>(
buffer);
385 if (std::dynamic_pointer_cast<MeshBuffer>(
buffer)
386 || std::dynamic_pointer_cast<MeshNetworkBuffer>(
buffer)
387 || std::dynamic_pointer_cast<GeometryBuffer>(
buffer)
388 || std::dynamic_pointer_cast<NodeTextureBuffer>(
buffer)) {
398 error<std::invalid_argument>(
401 std::source_location::current(),
402 "DataReadProcessor requires a VKBuffer with readable primary storage");
409 m_result.drain([](std::unique_ptr<PendingRead>&) { });
418 std::shared_ptr<VKBuffer>& staging,
419 size_t available_bytes,
424 if (byte_count == 0) {
425 byte_count = available_bytes;
428 if (byte_count > available_bytes || !pending.
handle.
buffer) {
430 "DataReadProcessor requested {} bytes from a {} byte buffer",
431 byte_count, available_bytes);
435 pending.
bytes.resize(byte_count);
439 }
else if (byte_count != 0) {
442 "DataReadProcessor source has no mapped memory or transfer-source usage");
451 "DataReadProcessor failed to submit a device-local read");
463 "DataReadProcessor attached to an incompatible buffer");
471 const auto vk_buffer = std::static_pointer_cast<VKBuffer>(
buffer);
472 if (!vk_buffer->is_initialized()) {
476 std::optional<ReadRequest> request;
479 request.emplace(std::move(next));
486 auto pending = std::make_unique<PendingRead>();
487 pending->source = vk_buffer;
489 if (!request->fill(*
this, vk_buffer, *pending)) {
#define MF_RT_ERROR(comp, ctx,...)
Core::GlobalNetworkConfig network
std::shared_ptr< NetworkGeometryBuffer > buffer
ProcessingToken m_processing_token
std::shared_ptr< VKBuffer > m_texture_staging
void read_buffer(std::shared_ptr< VKBuffer > source, size_t byte_count=0)
Read another VKBuffer, such as a descriptor-bound or instance buffer.
void read_back_buffer(size_t index, size_t byte_count)
Read a raw back_buffers slot of the attached buffer.
std::atomic< bool > m_result_ready
std::optional< std::vector< Kakshya::DataVariant > > resolve_vertices()
Finish a read_vertices() read as typed channels.
static bool submit_read(PendingRead &pending, std::shared_ptr< VKBuffer > &staging, size_t available_bytes, size_t byte_count, bool mapped, bool transferable)
void on_attach(const std::shared_ptr< Buffer > &buffer) override
Called when this processor is attached to a buffer.
void processing_function(const std::shared_ptr< Buffer > &buffer) override
The core processing function that must be implemented by derived classes.
std::optional< Kakshya::DataVariant > resolve_bytes()
Finish the read as raw bytes.
std::atomic< bool > m_result_occupied
std::optional< Kakshya::ImageData > resolve_pixels()
Finish a pixel read as typed ImageData with its format and dimensions.
Memory::SweepList< std::unique_ptr< PendingRead > > m_result
bool has_result() const noexcept
Whether a requested read has been submitted.
void read_bytes(size_t byte_count=0)
Read the attached buffer's primary storage.
~DataReadProcessor() override
bool m_skip_while_pending
void read_texture_pixels()
Download the base level of a TextureBuffer or NodeTextureBuffer image.
Memory::SweepList< ReadRequest > m_request
std::shared_ptr< VKBuffer > m_staging
std::optional< Kakshya::MeshData > resolve_mesh()
Finish a read_mesh() read as MeshData.
std::function< bool(DataReadProcessor &, const std::shared_ptr< VKBuffer > &, PendingRead &)> ReadFill
void read_host_pixels()
Read the host pixel data a TextureBuffer retains.
std::unique_ptr< PendingRead > collect_result(ReadKind kind)
void read_vertices()
Read the attached buffer's declared vertices; the layout count may represent capacity.
bool is_pending() const noexcept
Whether a read is armed, or its result has not been resolved yet.
bool is_compatible_with(const std::shared_ptr< Buffer > &buffer) const override
Checks if this processor can handle the specified buffer type.
void read_mesh()
Read the attached mesh.
void on_detach(const std::shared_ptr< Buffer > &buffer) override
Called when this processor is detached from a buffer.
void read_field(std::string name)
Read the current read slot of a named field on the attached buffer.
Reads selected buffer storage into owned host data, one snapshot per request.
bool insert_interleaved(std::span< const uint8_t > vertex_bytes, const VertexLayout &layout)
Replace the channel list with the attributes of interleaved vertices.
Write counterpart to VertexAccess: splits interleaved vertex bytes into typed channels.
static uint32_t get_channel_count(ImageFormat format)
Get the number of color channels for a given format.
Portal-level texture creation and management.
void resolve_transfer(TransferHandle &handle)
Wait for a transfer and release its resources.
std::shared_ptr< VKBuffer > create_image_staging_buffer(size_t size)
Allocate a persistent host-visible staging buffer sized for repeated streaming uploads to an image of...
std::shared_ptr< void > TransferHandle
Opaque handle to an in-flight transfer.
std::shared_ptr< Core::VKImage > resolve_gpu_image(const T &buffer)
Resolve the GPU-resident image from any GpuImageSource buffer.
Portal::Graphics::GpuBufferHandle gpu_buffer_handle(const std::shared_ptr< VKBuffer > &buffer)
View a buffer's primary GPU storage without reading it back.
@ GRAPHICS_BACKEND
Standard graphics processing backend configuration.
std::optional< Kakshya::MeshData > triangle_soup_mesh(std::span< const uint8_t > vertex_bytes)
Wrap non-indexed triangle vertices as MeshData.
VKBufferResources::GenerationSlot generation_slot(const Portal::Graphics::GpuBufferHandle &handle)
Express a buffer handle as the slot the back_buffers transfer helpers take.
std::optional< Kakshya::VertexLayout > resolve_vertex_layout(const std::shared_ptr< VKBuffer > &source)
Resolve the vertex extent of a buffer's primary storage for reading.
void resolve_back_buffer_read(TransferHandle &handle, const VKBufferResources::GenerationSlot &slot, void *data, size_t size, std::shared_ptr< VKBuffer > &staging, size_t staging_offset)
Wait for a recorded download, then copy it out of staging.
bool is_transfer_source(const std::shared_ptr< VKBuffer > &buffer)
Whether a buffer's memory can be the source of a transfer.
TransferHandle download_back_buffer_async(const VKBufferResources::GenerationSlot &slot, size_t size, std::shared_ptr< VKBuffer > &staging, size_t staging_offset)
Record a slot-to-staging copy without waiting for completion.
@ BufferProcessing
Buffer processing (Buffers::BufferManager, processing chains)
@ Buffers
Buffers, Managers, processors and processing chains.
std::variant< std::vector< double >, std::vector< float >, std::vector< uint8_t >, std::vector< uint16_t >, std::vector< uint32_t >, std::vector< std::complex< float > >, std::vector< std::complex< double > >, std::vector< glm::vec2 >, std::vector< glm::vec3 >, std::vector< glm::vec4 >, std::vector< glm::mat4 > > DataVariant
Multi-type data storage for different precision needs.
std::optional< Kakshya::VertexLayout > layout
std::shared_ptr< VKBuffer > source
std::optional< Kakshya::ImageData > image
Portal::Graphics::GpuBufferHandle handle
std::vector< uint8_t > bytes
std::optional< Kakshya::MeshData > mesh
static std::optional< ImageData > from_bytes(uint32_t width, uint32_t height, uint32_t channels, ImageFormat format, std::span< const uint8_t > bytes)
Build an ImageData from raw pixel bytes in format's layout.
Vertex type for indexed triangle mesh primitives (TRIANGLE_LIST topology)
Non-owning view of a GPU storage buffer produced by a compute path.