Stimulus[Buffer|Frame]: initial impl, unoptimized for mem use

This commit is contained in:
2025-11-16 16:09:35 -04:00
parent a4493b26a1
commit 3f04d1b387
9 changed files with 152 additions and 98 deletions
+42 -77
View File
@@ -2,13 +2,15 @@
#define _SP_MC_RING_BUFFER_H
#include <vector>
#include <memory>
#include <cstddef>
#include <stdexcept>
#include <algorithm>
#include <string>
#include <new>
#include <user/stimulusFrame.h>
#include <user/sequenceLock.h>
namespace smo {
namespace stim_buff {
/**
* @brief Single-producer, multi-consumer ring buffer w/per-slot sequence locks
@@ -20,120 +22,83 @@ namespace smo {
*/
class SpMcRingBuffer
{
public:
class InputEngineConstraints
{
public:
InputEngineConstraints(
size_t slotStartAlignmentNBytes_,
size_t slotPadToNBytes_)
: slotStartAlignmentNBytes(slotStartAlignmentNBytes_),
slotPadToNBytes(slotPadToNBytes_)
{}
~InputEngineConstraints() = default;
// Input-engine layout/constraints
size_t slotStartAlignmentNBytes; // power-of-2 alignment (e.g., 4096)
size_t slotPadToNBytes; // minimum size per slot
};
public:
/** EXPLANATION:
* Constructor initializes the ring buffer with the given constraints and
* number of slots. Calculates stride and allocates data buffer and sequence
* locks array.
* number of buffers. Allocates slots vector with properly constructed
* StimulusFrame instances.
*/
explicit SpMcRingBuffer(
size_t nSlots_,
const InputEngineConstraints& constraints_)
: nSlots(nSlots_), strideNBytes(0), bufferNBytes(0),
constraints(constraints_)
size_t nBuffers_,
const StagingBuffer::IOEngineConstraints& inputEngineConstraints,
const StagingBuffer::IOEngineConstraints& outputEngineConstraints,
size_t nSlotsPerStimFrame)
: nBuffers(nBuffers_),
// Default-construct all frames
slots(nBuffers)
{
if (nSlots == 0)
if (nBuffers == 0)
{
throw std::invalid_argument(std::string(__func__)
+ ": SpMcRingBuffer: nSlots must be > 0");
+ ": SpMcRingBuffer: nBuffers must be > 0");
}
computeStrideAndBufferSize();
// Allocate data buffer: bufferNBytes (aligned up to alignment)
data.resize(bufferNBytes);
// Initialize sequence locks array: one lock per slot
// Use unique_ptr array since SequenceLock is not copyable or movable
sequenceLocks = std::make_unique<SequenceLock[]>(nSlots);
// Re-invoke constructors w/placement new on default-constructed frames
for (size_t i = 0; i < nBuffers; ++i)
{
slots[i].~StimulusFrame(); // Destroy default-constructed object
new (&slots[i]) StimulusFrame(
inputEngineConstraints, outputEngineConstraints,
nSlotsPerStimFrame);
}
}
~SpMcRingBuffer() = default;
// Non-copyable, movable
// Non-copyable, non-movable (slots are non-movable)
SpMcRingBuffer(const SpMcRingBuffer&) = delete;
SpMcRingBuffer& operator=(const SpMcRingBuffer&) = delete;
SpMcRingBuffer(SpMcRingBuffer&&) = default;
SpMcRingBuffer& operator=(SpMcRingBuffer&&) = default;
SpMcRingBuffer(SpMcRingBuffer&&) = delete;
SpMcRingBuffer& operator=(SpMcRingBuffer&&) = delete;
public:
/**
* @brief Get a reference to data at the specified slot
* @brief Get a reference to the StimulusFrame at the specified slot
*
* @tparam T The type of data stored in the slot
* @param slotIndex The index of the slot (0-based)
* @return Reference to T at the slot
* @throws std::out_of_range if slotIndex >= nSlots
* @return Reference to StimulusFrame at the slot
* @throws std::out_of_range if slotIndex >= nBuffers
*/
template<typename T>
T& getDataAtSlot(size_t slotIndex)
StimulusFrame& getDataAtSlot(size_t slotIndex)
{
if (slotIndex >= nSlots)
if (slotIndex >= nBuffers)
{
throw std::out_of_range(std::string(__func__)
+ ": SpMcRingBuffer: slotIndex must be < nSlots");
+ ": SpMcRingBuffer: slotIndex must be < nBuffers");
}
size_t offset = slotIndex * strideNBytes;
return *reinterpret_cast<T*>(data.data() + offset);
return slots[slotIndex];
}
SequenceLock& getSequenceLockAtSlot(size_t slotIndex)
{
if (slotIndex >= nSlots)
if (slotIndex >= nBuffers)
{
throw std::out_of_range(std::string(__func__)
+ ": SpMcRingBuffer: slotIndex must be < nSlots");
+ ": SpMcRingBuffer: slotIndex must be < nBuffers");
}
return sequenceLocks[slotIndex];
return slots[slotIndex].lock;
}
private:
void computeStrideAndBufferSize()
{
// Stride is the maximum of alignment and padding
strideNBytes = std::max(
constraints.slotStartAlignmentNBytes,
constraints.slotPadToNBytes);
// Buffer size is nSlots * strideNBytes, aligned up to alignment
size_t rawSize = nSlots * strideNBytes;
bufferNBytes = ((rawSize + constraints.slotStartAlignmentNBytes - 1)
/ constraints.slotStartAlignmentNBytes)
* constraints.slotStartAlignmentNBytes;
}
// Buffer data
std::vector<uint8_t> data;
// Sequence locks array: one lock per slot
// Use unique_ptr array since SequenceLock is not copyable or movable
std::unique_ptr<SequenceLock[]> sequenceLocks;
public:
// Layout/invariants
size_t nSlots;
size_t strideNBytes;
size_t bufferNBytes;
InputEngineConstraints constraints;
size_t nBuffers;
private:
// Frames vector: each frame contains a sequence lock and staging buffer
std::vector<StimulusFrame> slots;
};
} // namespace stim_buff
} // namespace smo
#endif // _SP_MC_RING_BUFFER_H
+11
View File
@@ -31,6 +31,9 @@ public:
class IOEngineConstraints
{
public:
// Default constructor creates uninitialized constraints
IOEngineConstraints() = default;
IOEngineConstraints(
size_t slotStartAlignmentByteVal_,
size_t slotPadToNBytes_,
@@ -65,6 +68,12 @@ public:
};
public:
/** EXPLANATION:
* Default constructor creates uninitialized buffer.
* Must be properly initialized using placement new with the parameterized constructor.
*/
StagingBuffer() = default;
/** EXPLANATION:
* We use the input and output engine constraints to determine the total
* amount of memory required internally to assemble a single frame with
@@ -144,6 +153,8 @@ private:
struct MmapDeleter
{
size_t size;
// Default constructor for use with default-constructed StagingBuffer
MmapDeleter() : size(0) {}
MmapDeleter(size_t s) : size(s) {}
void operator()(uint8_t* ptr) const
+6 -8
View File
@@ -5,6 +5,7 @@
#include <vector>
#include <memory>
#include <user/spMcRingBuffer.h>
#include <user/stagingBuffer.h>
#include "stimulusFrame.h"
#include "deviceAttachmentSpec.h"
@@ -28,15 +29,16 @@ public:
const std::shared_ptr<device::DeviceAttachmentSpec>
&deviceAttachmentSpec,
int histbuffMs,
const SpMcRingBuffer::InputEngineConstraints& ringBufferConstraints)
const StagingBuffer::IOEngineConstraints& inputEngineConstraints,
const StagingBuffer::IOEngineConstraints& outputEngineConstraints,
size_t nSlotsPerStimFrame)
: parent(parent),
deviceAttachmentSpec(deviceAttachmentSpec),
histbuffMs(histbuffMs),
frames_(static_cast<size_t>(histbuffMs / CONFIG_STIMBUFF_FRAME_PERIOD_MS)),
ringBufferConstraints(ringBufferConstraints),
ringBuffer(
static_cast<size_t>(histbuffMs / CONFIG_STIMBUFF_FRAME_PERIOD_MS),
ringBufferConstraints)
inputEngineConstraints, outputEngineConstraints,
nSlotsPerStimFrame)
{}
virtual ~StimulusBuffer() = default;
@@ -51,10 +53,6 @@ public:
StimulusProducer& parent;
std::shared_ptr<device::DeviceAttachmentSpec> deviceAttachmentSpec;
int histbuffMs;
std::vector<StimulusFrame> frames_;
protected:
SpMcRingBuffer::InputEngineConstraints ringBufferConstraints;
SpMcRingBuffer ringBuffer;
};
+27
View File
@@ -2,6 +2,8 @@
#define _ATTACHMENT_SUPPORT_STIMULUS_FRAME_H
#include <cstdint>
#include <user/stagingBuffer.h>
#include <user/sequenceLock.h>
namespace smo {
namespace stim_buff {
@@ -61,7 +63,32 @@ typedef uint64_t SimultaneityStamp;
class StimulusFrame
{
public:
/** EXPLANATION:
* Default constructor creates uninitialized frame.
* Must be properly initialized using placement new with the parameterized constructor.
*/
StimulusFrame() = default;
StimulusFrame(
const StagingBuffer::IOEngineConstraints& inputEngineConstraints,
const StagingBuffer::IOEngineConstraints& outputEngineConstraints,
size_t nSlots)
: stagingBuffer(
inputEngineConstraints, outputEngineConstraints, nSlots)
{}
~StimulusFrame() = default;
// Non-copyable, movable
StimulusFrame(const StimulusFrame&) = delete;
StimulusFrame& operator=(const StimulusFrame&) = delete;
StimulusFrame(StimulusFrame&&) = default;
StimulusFrame& operator=(StimulusFrame&&) = default;
public:
SequenceLock lock;
SimultaneityStamp simultaneityStamp;
StagingBuffer stagingBuffer;
};
} // namespace stim_buff
+6 -2
View File
@@ -3,6 +3,7 @@
#include <memory>
#include <user/stimulusBuffer.h>
#include <user/stagingBuffer.h>
namespace smo {
namespace stim_buff {
@@ -21,9 +22,12 @@ public:
StimulusProducer& parent,
const std::shared_ptr<device::DeviceAttachmentSpec>& deviceAttachmentSpec,
int histbuffMs,
const SpMcRingBuffer::InputEngineConstraints& ringBufferConstraints)
const StagingBuffer::IOEngineConstraints& inputEngineConstraints,
const StagingBuffer::IOEngineConstraints& outputEngineConstraints,
size_t nSlotsPerStimFrame)
: StimulusBuffer(
parent, deviceAttachmentSpec, histbuffMs, ringBufferConstraints)
parent, deviceAttachmentSpec, histbuffMs,
inputEngineConstraints, outputEngineConstraints, nSlotsPerStimFrame)
{}
~MeshStimulusBuffer() = default;
@@ -3,6 +3,7 @@
#include <memory>
#include <user/stimulusBuffer.h>
#include <user/stagingBuffer.h>
namespace smo {
namespace stim_buff {
@@ -21,9 +22,12 @@ public:
StimulusProducer& parent,
const std::shared_ptr<device::DeviceAttachmentSpec>& deviceAttachmentSpec,
int histbuffMs,
const SpMcRingBuffer::InputEngineConstraints& ringBufferConstraints)
const StagingBuffer::IOEngineConstraints& inputEngineConstraints,
const StagingBuffer::IOEngineConstraints& outputEngineConstraints,
size_t nSlotsPerStimFrame)
: StimulusBuffer(
parent, deviceAttachmentSpec, histbuffMs, ringBufferConstraints)
parent, deviceAttachmentSpec, histbuffMs,
inputEngineConstraints, outputEngineConstraints, nSlotsPerStimFrame)
{}
~PcloudAmbienceStimulusBuffer() = default;
@@ -3,6 +3,7 @@
#include <memory>
#include <user/stimulusBuffer.h>
#include <user/stagingBuffer.h>
namespace smo {
namespace stim_buff {
@@ -22,9 +23,12 @@ public:
const std::shared_ptr<device::DeviceAttachmentSpec>
&deviceAttachmentSpec,
int histbuffMs,
const SpMcRingBuffer::InputEngineConstraints& ringBufferConstraints)
const StagingBuffer::IOEngineConstraints& inputEngineConstraints,
const StagingBuffer::IOEngineConstraints& outputEngineConstraints,
size_t nSlotsPerStimFrame)
: StimulusBuffer(
parent, deviceAttachmentSpec, histbuffMs, ringBufferConstraints)
parent, deviceAttachmentSpec, histbuffMs,
inputEngineConstraints, outputEngineConstraints, nSlotsPerStimFrame)
{}
~PcloudIntensityStimulusBuffer() = default;
@@ -7,8 +7,9 @@
#include <componentThread.h>
#include <asynchronousLoop.h>
#include <user/stimulusFrame.h>
#include "pcloudStimulusProducer.h"
#include <user/frameAssemblyDesc.h>
#include <livoxProto1/device.h>
#include "pcloudStimulusProducer.h"
namespace smo {
namespace stim_buff {
@@ -16,8 +17,15 @@ namespace stim_buff {
extern const SmoCallbacks* smoHooksPtr;
// OpenCL kernels are used to collate and produce our StimFrames.
static SpMcRingBuffer::InputEngineConstraints openClInputConstraints(
static_cast<size_t>(sysconf(_SC_PAGE_SIZE)), sizeof(void *));
static StagingBuffer::IOEngineConstraints openClInputConstraints(
// slotStartAlignmentByteVal (page alignment)
sizeof(float) * 3,
// slotPadToNBytes (pointer size)
sizeof(void *),
// frameStartAlignmentByteVal (page alignment)
static_cast<size_t>(sysconf(_SC_PAGE_SIZE)),
// framePadToNBytes (pointer size)
static_cast<size_t>(sysconf(_SC_PAGE_SIZE)));
static StagingBuffer::IOEngineConstraints openClIntensityInputConstraints(
// slotStartAlignmentByteVal (page alignment)
@@ -27,7 +35,17 @@ static StagingBuffer::IOEngineConstraints openClIntensityInputConstraints(
// frameStartAlignmentByteVal (page alignment)
static_cast<size_t>(sysconf(_SC_PAGE_SIZE)),
// framePadToNBytes (pointer size)
sizeof(void *));
static_cast<size_t>(sysconf(_SC_PAGE_SIZE)));
static StagingBuffer::IOEngineConstraints openClAmbienceInputConstraints(
// slotStartAlignmentByteVal (page alignment)
sizeof(float),
// slotPadToNBytes (pointer size)
sizeof(void *),
// frameStartAlignmentByteVal (page alignment)
static_cast<size_t>(sysconf(_SC_PAGE_SIZE)),
// framePadToNBytes (pointer size)
static_cast<size_t>(sysconf(_SC_PAGE_SIZE)));
PcloudStimulusProducer::PcloudStimulusProducer(
const std::shared_ptr<device::DeviceAttachmentSpec> &deviceAttachmentSpec,
@@ -37,6 +55,7 @@ PcloudStimulusProducer::PcloudStimulusProducer(
: StimulusProducer(
deviceAttachmentSpec,
device->componentThread->getIoService()),
nDgramsPerStagingBufferFrame(nDgramsPerStagingBufferFrame),
device(device),
formatDesc(formatDesc),
openClCollatingAndMeshingEngine(*this),
@@ -46,6 +65,10 @@ assemblyBuffer(
nDgramsPerStagingBufferFrame),
ioUringAssemblyEngine(*this, nDgramsPerStagingBufferFrame),
collationBuffer(
StagingBuffer::IOEngineConstraints::openClInputConstraints,
StagingBuffer::IOEngineConstraints::openClInputConstraints,
nDgramsPerStagingBufferFrame),
tempStimulusFrame(
StagingBuffer::IOEngineConstraints::openClInputConstraints,
StagingBuffer::IOEngineConstraints::openClInputConstraints,
nDgramsPerStagingBufferFrame)
@@ -181,30 +204,47 @@ PcloudStimulusProducer::getOrCreateAttachedStimulusBuffer(
// Parse qualeIfaceApi to determine buffer type
const std::string& qualeIfaceApi = deviceAttachmentSpec->qualeIfaceApi;
// Calculate nPointsPerStimFrame based on return mode
size_t nPointsPerDgram = livoxProto1::Device::getNPointsPerDgram(
static_cast<int>(device->currentReturnMode));
size_t nPointsPerStimFrame = this->nDgramsPerStagingBufferFrame
* nPointsPerDgram;
if (qualeIfaceApi == "mesh")
{
std::cout << __func__ << ": $$$$$$$ Creating MeshStimulusBuffer" << std::endl;
auto meshBuffer = std::make_shared<MeshStimulusBuffer>(
*this, deviceAttachmentSpec, histbuffMs, openClInputConstraints);
*this, deviceAttachmentSpec, histbuffMs,
openClInputConstraints, openClInputConstraints,
nPointsPerStimFrame);
std::cout << __func__ << ": $$$$$$$ Created MeshStimulusBuffer" << std::endl;
meshStimulusBuffer = meshBuffer;
attachedStimulusBuffers.push_back(meshBuffer);
return meshBuffer;
}
else if (qualeIfaceApi == "pcloudIntensity")
{
std::cout << __func__ << ": $$$$$$$ Creating PcloudIntensityStimulusBuffer" << std::endl;
auto intensityBuffer = std::make_shared<PcloudIntensityStimulusBuffer>(
*this, deviceAttachmentSpec, histbuffMs,
openClInputConstraints);
openClIntensityInputConstraints, openClIntensityInputConstraints,
nPointsPerStimFrame);
std::cout << __func__ << ": $$$$$$$ Created PcloudIntensityStimulusBuffer" << std::endl;
intensityStimulusBuffer = intensityBuffer;
attachedStimulusBuffers.push_back(intensityBuffer);
return intensityBuffer;
}
else if (qualeIfaceApi == "pcloudAmbience")
{
std::cout << __func__ << ": $$$$$$$ Creating PcloudAmbienceStimulusBuffer" << std::endl;
auto ambienceBuffer = std::make_shared<PcloudAmbienceStimulusBuffer>(
*this, deviceAttachmentSpec, histbuffMs, openClInputConstraints);
*this, deviceAttachmentSpec, histbuffMs,
openClAmbienceInputConstraints, openClAmbienceInputConstraints,
nDgramsPerStagingBufferFrame);
std::cout << __func__ << ": $$$$$$$ Created PcloudAmbienceStimulusBuffer" << std::endl;
ambienceStimulusBuffer = ambienceBuffer;
attachedStimulusBuffers.push_back(ambienceBuffer);
return ambienceBuffer;
@@ -79,6 +79,7 @@ protected:
public:
void produceFrameReq(smo::Callback<produceFrameReqCbFn> callback);
size_t nDgramsPerStagingBufferFrame;
std::shared_ptr<livoxProto1::Device> device;
PcloudFormatDesc formatDesc;
OpenClCollatingAndMeshingEngine openClCollatingAndMeshingEngine;