Move StagingBuffer+FrameAssemblyDesc into libattachmentSupport
This is in preparation for using StagingBuffer to implement StimulusFrame and StimulusBuffer.
This commit is contained in:
@@ -58,7 +58,6 @@ if(ENABLE_STIMBUFFAPI_livoxGen1)
|
||||
|
||||
add_library(livoxGen1 SHARED
|
||||
livoxGen1.cpp
|
||||
stagingBuffer.cpp
|
||||
pcloudStimulusProducer.cpp
|
||||
ioUringAssemblyEngine.cpp
|
||||
openClCollatingAndMeshingEngine.cpp
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
#ifndef _LIVOX_GEN1_FRAME_ASSEMBLY_DESC_H
|
||||
#define _LIVOX_GEN1_FRAME_ASSEMBLY_DESC_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
|
||||
namespace smo {
|
||||
namespace stim_buff {
|
||||
|
||||
class FrameAssemblyDesc
|
||||
{
|
||||
public:
|
||||
struct SlotDesc
|
||||
{
|
||||
size_t offsetBytes; // offset from frame base
|
||||
uint8_t* vaddr; // direct pointer into StagingBuffer memory
|
||||
size_t nBytes; // slot capacity in bytes
|
||||
};
|
||||
|
||||
public:
|
||||
FrameAssemblyDesc() = default;
|
||||
|
||||
FrameAssemblyDesc(
|
||||
size_t n, size_t slotSize,
|
||||
size_t frameStride,
|
||||
std::vector<SlotDesc> slotList)
|
||||
: numSlots(n), slotSizeBytes(slotSize),
|
||||
frameStrideBytes(frameStride),
|
||||
slots(std::move(slotList)) {}
|
||||
|
||||
inline std::string stringify() const {
|
||||
std::ostringstream oss;
|
||||
oss << "FrameAssemblyDesc{"
|
||||
<< "numSlots=" << numSlots
|
||||
<< ", slotSizeBytes=" << slotSizeBytes
|
||||
<< ", frameStrideBytes=" << frameStrideBytes
|
||||
<< ", slots=[";
|
||||
const size_t preview = slots.size() < 4 ? slots.size() : 4;
|
||||
for (size_t i = 0; i < preview; ++i) {
|
||||
oss << "{off=" << slots[i].offsetBytes
|
||||
<< ", nBytes=" << slots[i].nBytes
|
||||
<< ", vaddr=" << (const void*)slots[i].vaddr << "}";
|
||||
if (i + 1 < preview) oss << ",";
|
||||
}
|
||||
if (slots.size() > preview) oss << ", ...";
|
||||
oss << "]}";
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
public:
|
||||
size_t numSlots;
|
||||
size_t slotSizeBytes;
|
||||
size_t frameStrideBytes;
|
||||
std::vector<SlotDesc> slots;
|
||||
};
|
||||
|
||||
} // namespace stim_buff
|
||||
} // namespace smo
|
||||
|
||||
#endif // _LIVOX_GEN1_FRAME_ASSEMBLY_DESC_H
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#include <asynchronousLoop.h>
|
||||
#include <callback.h>
|
||||
#include <spinLock.h>
|
||||
#include "frameAssemblyDesc.h"
|
||||
#include <user/frameAssemblyDesc.h>
|
||||
|
||||
namespace smo {
|
||||
namespace stim_buff {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#include "openClCollatingAndMeshingEngine.h"
|
||||
#include "pcloudStimulusProducer.h"
|
||||
#include "openClKernels.h"
|
||||
#include "frameAssemblyDesc.h"
|
||||
#include <user/frameAssemblyDesc.h>
|
||||
#include "ioUringAssemblyEngine.h"
|
||||
|
||||
namespace smo {
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
#include <callback.h>
|
||||
#include <spinLock.h>
|
||||
#include <user/stimulusFrame.h>
|
||||
#include "stagingBuffer.h"
|
||||
#include "frameAssemblyDesc.h"
|
||||
#include <user/stagingBuffer.h>
|
||||
#include <user/frameAssemblyDesc.h>
|
||||
|
||||
namespace smo {
|
||||
namespace stim_buff {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include <asynchronousLoop.h>
|
||||
#include <user/stimulusFrame.h>
|
||||
#include "pcloudStimulusProducer.h"
|
||||
#include "frameAssemblyDesc.h"
|
||||
#include <user/frameAssemblyDesc.h>
|
||||
|
||||
namespace smo {
|
||||
namespace stim_buff {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include <livoxProto1/device.h>
|
||||
#include <asynchronousContinuation.h>
|
||||
#include <callback.h>
|
||||
#include "stagingBuffer.h"
|
||||
#include <user/stagingBuffer.h>
|
||||
#include "ioUringAssemblyEngine.h"
|
||||
#include "openClCollatingAndMeshingEngine.h"
|
||||
#include "pcloudXyzStimulusBuffer.h"
|
||||
|
||||
@@ -1,231 +0,0 @@
|
||||
#include "stagingBuffer.h"
|
||||
#include <unistd.h>
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <sys/mman.h>
|
||||
#include <vector>
|
||||
|
||||
namespace smo {
|
||||
namespace stim_buff {
|
||||
|
||||
// Static defaults for io_uring
|
||||
const StagingBuffer::IOEngineConstraints
|
||||
StagingBuffer::IOEngineConstraints::ioUringConstraints(
|
||||
// slotStartAlignmentByteVal (page alignment for DMA)
|
||||
static_cast<size_t>(sysconf(_SC_PAGE_SIZE)),
|
||||
// slotPadToNBytes (MTU 1500 - UDP/IP header 28)
|
||||
1472,
|
||||
// frameStartAlignmentByteVal (page alignment for DMA)
|
||||
static_cast<size_t>(sysconf(_SC_PAGE_SIZE)),
|
||||
// framePadToNBytes (MTU 1500 - UDP/IP header 28)
|
||||
static_cast<size_t>(sysconf(_SC_PAGE_SIZE))
|
||||
);
|
||||
|
||||
// Static defaults for OpenCL input
|
||||
const StagingBuffer::IOEngineConstraints
|
||||
StagingBuffer::IOEngineConstraints::openClInputConstraints(
|
||||
// slotStartAlignmentByteVal (page alignment)
|
||||
static_cast<size_t>(sysconf(_SC_PAGE_SIZE)),
|
||||
// slotPadToNBytes (XYZI point size)
|
||||
16,
|
||||
// frameStartAlignmentByteVal (page alignment)
|
||||
static_cast<size_t>(sysconf(_SC_PAGE_SIZE)),
|
||||
// framePadToNBytes (pointer size)
|
||||
static_cast<size_t>(sysconf(_SC_PAGE_SIZE))
|
||||
);
|
||||
|
||||
// Helper function to calculate maximum alignment needed for first slot
|
||||
// (must satisfy both frame and slot alignment)
|
||||
static size_t calculateMaxAlignment(
|
||||
size_t frameStartAlignmentByteVal,
|
||||
size_t slotStartAlignmentByteVal)
|
||||
{
|
||||
if (frameStartAlignmentByteVal >= slotStartAlignmentByteVal)
|
||||
{
|
||||
if (frameStartAlignmentByteVal % slotStartAlignmentByteVal == 0)
|
||||
{ return frameStartAlignmentByteVal; }
|
||||
else
|
||||
{
|
||||
// Need LCM, but for simplicity use the larger alignment
|
||||
// In practice, alignments are usually powers of 2, so this should work
|
||||
return std::max(
|
||||
frameStartAlignmentByteVal, slotStartAlignmentByteVal);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (slotStartAlignmentByteVal % frameStartAlignmentByteVal == 0)
|
||||
{ return slotStartAlignmentByteVal; }
|
||||
else
|
||||
{
|
||||
return std::max(
|
||||
frameStartAlignmentByteVal, slotStartAlignmentByteVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StagingBuffer::computeSlotStrideAndBufferSize()
|
||||
{
|
||||
// Slot stride is the maximum of alignment and padding
|
||||
slotStrideNBytes = std::max(
|
||||
inputConstraints.slotStartAlignmentByteVal,
|
||||
inputConstraints.slotPadToNBytes);
|
||||
|
||||
// Calculate maximum alignment needed for first slot (must satisfy both frame and slot alignment)
|
||||
size_t maxAlignment = calculateMaxAlignment(
|
||||
inputConstraints.frameStartAlignmentByteVal,
|
||||
inputConstraints.slotStartAlignmentByteVal);
|
||||
|
||||
// Calculate minimum buffer size
|
||||
size_t minBufferSize = std::max(
|
||||
inputConstraints.framePadToNBytes,
|
||||
inputConstraints.slotPadToNBytes);
|
||||
|
||||
// Calculate total size needed for nDgramsPerFrame slots
|
||||
size_t slotAreaSize = nDgramsPerFrame * slotStrideNBytes;
|
||||
|
||||
// Add padding space at buffer start for alignment offset (worst case: max alignment - 1)
|
||||
size_t alignmentPadding = maxAlignment - 1;
|
||||
|
||||
// Total size needed: alignment padding + slot area, then ensure minimum is met
|
||||
size_t rawSize = alignmentPadding + slotAreaSize;
|
||||
if (rawSize < minBufferSize)
|
||||
{ rawSize = minBufferSize; }
|
||||
|
||||
// Align up to the maximum alignment to ensure we can always find a valid offset
|
||||
bufferNBytes = ((rawSize + maxAlignment - 1) / maxAlignment) * maxAlignment;
|
||||
}
|
||||
|
||||
// Static member function to calculate offset and validate invariants
|
||||
size_t StagingBuffer::calculateFirstSlotOffsetAndValidate(
|
||||
uint8_t* buffer,
|
||||
size_t bufferNBytes,
|
||||
size_t nDgramsPerFrame,
|
||||
size_t slotStrideNBytes,
|
||||
const StagingBuffer::IOEngineConstraints& inputConstraints)
|
||||
{
|
||||
// Calculate maximum alignment needed for first slot
|
||||
size_t maxAlignment = calculateMaxAlignment(
|
||||
inputConstraints.frameStartAlignmentByteVal,
|
||||
inputConstraints.slotStartAlignmentByteVal);
|
||||
|
||||
// Calculate offset to align first slot to both frame and slot alignment
|
||||
uintptr_t bufferAddr = reinterpret_cast<uintptr_t>(buffer);
|
||||
uintptr_t alignedAddr = ((bufferAddr + maxAlignment - 1) / maxAlignment)
|
||||
* maxAlignment;
|
||||
size_t firstSlotOffsetNBytes = alignedAddr - bufferAddr;
|
||||
|
||||
// Validate invariants with exceptions
|
||||
uint8_t* firstSlotAddr = buffer + firstSlotOffsetNBytes;
|
||||
if (
|
||||
reinterpret_cast<uintptr_t>(firstSlotAddr)
|
||||
% inputConstraints.frameStartAlignmentByteVal != 0)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__)
|
||||
+ ": StagingBuffer: first slot address not aligned to "
|
||||
+ std::to_string(inputConstraints.frameStartAlignmentByteVal));
|
||||
}
|
||||
|
||||
if (
|
||||
reinterpret_cast<uintptr_t>(firstSlotAddr)
|
||||
% inputConstraints.slotStartAlignmentByteVal != 0)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__)
|
||||
+ ": StagingBuffer: first slot address not aligned to "
|
||||
+ std::to_string(inputConstraints.slotStartAlignmentByteVal));
|
||||
}
|
||||
|
||||
size_t minBufferSize = std::max(
|
||||
inputConstraints.framePadToNBytes,
|
||||
inputConstraints.slotPadToNBytes);
|
||||
if (bufferNBytes < minBufferSize)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__)
|
||||
+ ": StagingBuffer: buffer size less than minimum required (max of "
|
||||
+ std::to_string(inputConstraints.framePadToNBytes)
|
||||
+ " and "
|
||||
+ std::to_string(inputConstraints.slotPadToNBytes)
|
||||
+ ")");
|
||||
}
|
||||
|
||||
if (firstSlotOffsetNBytes + nDgramsPerFrame * slotStrideNBytes
|
||||
> bufferNBytes)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__)
|
||||
+ ": StagingBuffer: buffer size insufficient to hold "
|
||||
+ std::to_string(nDgramsPerFrame)
|
||||
+ " slots with proper alignment and padding");
|
||||
}
|
||||
|
||||
return firstSlotOffsetNBytes;
|
||||
}
|
||||
|
||||
StagingBuffer::StagingBuffer(
|
||||
const IOEngineConstraints& inputEngineConstraints_,
|
||||
const IOEngineConstraints& /*outputEngineConstraints*/,
|
||||
size_t nDgramsPerFrame)
|
||||
: buffer(nullptr, MmapDeleter(0)), bufferNBytes(0),
|
||||
nDgramsPerFrame(nDgramsPerFrame), slotStrideNBytes(0),
|
||||
firstSlotOffsetNBytes(0),
|
||||
inputConstraints(inputEngineConstraints_),
|
||||
assemblingFlag(false)
|
||||
{
|
||||
if (nDgramsPerFrame == 0)
|
||||
{
|
||||
throw std::invalid_argument(std::string(__func__)
|
||||
+ ": StagingBuffer: nDgramsPerFrame must be > 0");
|
||||
}
|
||||
|
||||
computeSlotStrideAndBufferSize();
|
||||
|
||||
/* Allocate buffer using mmap() for io_uring registration
|
||||
* MAP_ANONYMOUS | MAP_PRIVATE creates anonymous, non-file-backed memory
|
||||
*/
|
||||
void* mmapped = mmap(
|
||||
nullptr, bufferNBytes,
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_ANONYMOUS | MAP_PRIVATE,
|
||||
-1, 0);
|
||||
|
||||
if (mmapped == MAP_FAILED)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__)
|
||||
+ ": StagingBuffer: mmap() failed");
|
||||
}
|
||||
|
||||
buffer = std::unique_ptr<uint8_t, MmapDeleter>(
|
||||
static_cast<uint8_t*>(mmapped), MmapDeleter(bufferNBytes));
|
||||
currentNBytes.store(0);
|
||||
|
||||
// Lock the buffer in memory to prevent swapping
|
||||
if (mlock(buffer.get(), bufferNBytes) != 0)
|
||||
{
|
||||
throw std::runtime_error(std::string(__func__)
|
||||
+ ": StagingBuffer: mlock() failed");
|
||||
}
|
||||
|
||||
// Calculate offset and validate invariants (helper function in .cpp)
|
||||
firstSlotOffsetNBytes = StagingBuffer::calculateFirstSlotOffsetAndValidate(
|
||||
buffer.get(), bufferNBytes, nDgramsPerFrame,
|
||||
slotStrideNBytes, inputConstraints);
|
||||
|
||||
// Build FrameAssemblyDesc once
|
||||
std::vector<FrameAssemblyDesc::SlotDesc> slots;
|
||||
slots.reserve(nDgramsPerFrame);
|
||||
uint8_t *frameBase = buffer.get() + firstSlotOffsetNBytes;
|
||||
for (size_t i = 0; i < nDgramsPerFrame; ++i)
|
||||
{
|
||||
size_t off = i * slotStrideNBytes;
|
||||
FrameAssemblyDesc::SlotDesc s{
|
||||
off, frameBase + off, inputConstraints.slotPadToNBytes};
|
||||
|
||||
slots.push_back(s);
|
||||
}
|
||||
|
||||
frameDesc = std::make_shared<FrameAssemblyDesc>(
|
||||
nDgramsPerFrame, inputConstraints.slotPadToNBytes, bufferNBytes,
|
||||
std::move(slots));
|
||||
}
|
||||
|
||||
} // namespace stim_buff
|
||||
} // namespace smo
|
||||
@@ -1,185 +0,0 @@
|
||||
#ifndef STAGINGBUFFER_H
|
||||
#define STAGINGBUFFER_H
|
||||
|
||||
#include <memory>
|
||||
#include <cstdint>
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/uio.h>
|
||||
|
||||
#include "frameAssemblyDesc.h"
|
||||
|
||||
namespace smo {
|
||||
namespace stim_buff {
|
||||
|
||||
/**
|
||||
* StagingBuffer manages a large buffer to guide io_uring in assembling some
|
||||
* number of Livox Avia pcloud UDP dgrams into a single stim frame.
|
||||
*
|
||||
* The buffer operates in a cycle:
|
||||
* 1. io_uring assembles UDP dgrams into the buffer until it's full
|
||||
* 2. Buffer is handed off to the stimbuff layer to be appended to the stimbuff.
|
||||
* 3. When the stimbuff layer has appended the current assembled frame, the
|
||||
* assembly buffer is reset and cycle repeats.
|
||||
*/
|
||||
class StagingBuffer
|
||||
{
|
||||
public:
|
||||
class IOEngineConstraints
|
||||
{
|
||||
public:
|
||||
IOEngineConstraints(
|
||||
size_t slotStartAlignmentByteVal_,
|
||||
size_t slotPadToNBytes_,
|
||||
size_t frameStartAlignmentByteVal_,
|
||||
size_t framePadToNBytes_)
|
||||
: slotStartAlignmentByteVal(slotStartAlignmentByteVal_),
|
||||
slotPadToNBytes(slotPadToNBytes_),
|
||||
frameStartAlignmentByteVal(frameStartAlignmentByteVal_),
|
||||
framePadToNBytes(framePadToNBytes_)
|
||||
{}
|
||||
|
||||
~IOEngineConstraints() = default;
|
||||
|
||||
size_t slotStartAlignmentByteVal, slotPadToNBytes,
|
||||
frameStartAlignmentByteVal, framePadToNBytes;
|
||||
|
||||
// Static defaults for io_uring and OpenCL
|
||||
static const IOEngineConstraints ioUringConstraints;
|
||||
static const IOEngineConstraints openClInputConstraints;
|
||||
|
||||
inline std::string stringify() const
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "IOEngineConstraints{"
|
||||
<< "slotStartAlignmentByteVal=" << slotStartAlignmentByteVal
|
||||
<< ", slotPadToNBytes=" << slotPadToNBytes
|
||||
<< ", frameStartAlignmentByteVal=" << frameStartAlignmentByteVal
|
||||
<< ", framePadToNBytes=" << framePadToNBytes
|
||||
<< "}";
|
||||
return oss.str();
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
/** EXPLANATION:
|
||||
* We use the input and output engine constraints to determine the total
|
||||
* amount of memory required internally to assemble a single frame with
|
||||
* the given number of points per frame.
|
||||
*/
|
||||
explicit StagingBuffer(
|
||||
const IOEngineConstraints& inputEngineConstraints,
|
||||
const IOEngineConstraints& outputEngineConstraints,
|
||||
size_t nDgramsPerFrame);
|
||||
~StagingBuffer() = default;
|
||||
|
||||
// Non-copyable, movable
|
||||
StagingBuffer(const StagingBuffer&) = delete;
|
||||
StagingBuffer& operator=(const StagingBuffer&) = delete;
|
||||
StagingBuffer(StagingBuffer&&) = default;
|
||||
StagingBuffer& operator=(StagingBuffer&&) = default;
|
||||
|
||||
public:
|
||||
/** EXPLANATION:
|
||||
* Returns an input-engine-agnostic descriptor describing per-frame packet
|
||||
* slot layout. Different input engines should be able to convert this into
|
||||
* engine-specific metadata. E.g: io_uring's SQE descriptor.
|
||||
*/
|
||||
operator std::shared_ptr<FrameAssemblyDesc>() const { return frameDesc; }
|
||||
// operator OpenClSharedBufferDescriptor() const;
|
||||
|
||||
bool isAssembling() const { return assemblingFlag.load(); }
|
||||
void startAssembly() { assemblingFlag.store(true); }
|
||||
void stopAssembly() { assemblingFlag.store(false); }
|
||||
|
||||
/** EXPLANATION:
|
||||
* Returns an iovec for io_uring registration.
|
||||
* The buffer is mmap()-allocated and suitable for IORING_REGISTER_BUFFERS.
|
||||
*/
|
||||
struct iovec getIoUringRegisterIoVec() const
|
||||
{
|
||||
struct iovec iov;
|
||||
iov.iov_base = buffer.get();
|
||||
iov.iov_len = bufferNBytes;
|
||||
return iov;
|
||||
}
|
||||
|
||||
/** EXPLANATION:
|
||||
* Returns an iovec for OpenCL engine buffer access.
|
||||
* The buffer is mmap()-allocated and suitable for CL_MEM_USE_HOST_PTR.
|
||||
*/
|
||||
struct iovec getClEngineIovec() const
|
||||
{
|
||||
struct iovec iov;
|
||||
iov.iov_base = buffer.get();
|
||||
iov.iov_len = bufferNBytes;
|
||||
return iov;
|
||||
}
|
||||
|
||||
inline std::string stringify() const
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "StagingBuffer{"
|
||||
<< "nDgramsPerFrame=" << nDgramsPerFrame
|
||||
<< ", bufferNBytes=" << bufferNBytes
|
||||
<< ", slotStrideNBytes=" << slotStrideNBytes
|
||||
<< ", constraints=" << inputConstraints.stringify()
|
||||
<< "}";
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
private:
|
||||
void computeSlotStrideAndBufferSize();
|
||||
static size_t calculateFirstSlotOffsetAndValidate(
|
||||
uint8_t* buffer,
|
||||
size_t bufferNBytes,
|
||||
size_t nDgramsPerFrame,
|
||||
size_t slotStrideNBytes,
|
||||
const IOEngineConstraints& inputConstraints);
|
||||
|
||||
// Custom deleter for mmap-allocated buffer
|
||||
struct MmapDeleter
|
||||
{
|
||||
size_t size;
|
||||
MmapDeleter(size_t s) : size(s) {}
|
||||
|
||||
void operator()(uint8_t* ptr) const
|
||||
{
|
||||
if (ptr != nullptr && size > 0)
|
||||
{
|
||||
munlock(ptr, size);
|
||||
munmap(ptr, size);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Buffer data - mmap-allocated for io_uring registration
|
||||
// Using unique_ptr<uint8_t, MmapDeleter> instead of array syntax
|
||||
// since we have a custom deleter that knows the size
|
||||
std::unique_ptr<uint8_t, MmapDeleter> buffer;
|
||||
size_t bufferNBytes;
|
||||
|
||||
// Layout/invariants
|
||||
size_t nDgramsPerFrame;
|
||||
|
||||
public:
|
||||
size_t slotStrideNBytes;
|
||||
size_t firstSlotOffsetNBytes; // offset from buffer start to first slot
|
||||
|
||||
private:
|
||||
IOEngineConstraints inputConstraints;
|
||||
|
||||
// Descriptor (computed once; reused across frames)
|
||||
mutable std::shared_ptr<FrameAssemblyDesc> frameDesc;
|
||||
|
||||
// Current state
|
||||
std::atomic<size_t> currentNBytes;
|
||||
std::atomic<bool> assemblingFlag;
|
||||
};
|
||||
|
||||
} // namespace stim_buff
|
||||
} // namespace smo
|
||||
|
||||
#endif // STAGINGBUFFER_H
|
||||
Reference in New Issue
Block a user