Lg1: Implement both light|darkAmbience stimBuffs & their production

We now produce both light and dark ambience stimframes into
stimbuffs for the LivoxGen1 lidar devices.
This commit is contained in:
2026-04-18 14:54:14 -04:00
parent 632a227985
commit 27a5d48451
15 changed files with 668 additions and 336 deletions
+17 -12
View File
@@ -4,16 +4,21 @@
+edev|avia0|mesh()|livoxGen1()|livoxProto1(SMO_IP)|3JEDK380010Z39||
+edev|avia0|pcloudIntensity()|livoxGen1()|livoxProto1(SMO_IP)|3JEDK380010Z39||
/* pcloudAmbience with:
* - postrin: be positively disposed to dim ambience (low passband counts),
* but don't be so positively drawn that your eyelids droop and your pupils
* dilate, in human terms (i.e: no distraction). And no stupefaction either.
* - negtrin: be negatively disposed to high ambience (high passband counts),
* to the point of feeling un-ignorable pain when it's sufficiently high.
/* pcloudLightAmbience with negtrin: be negatively disposed to high ambience
* (high passband counts above passband-count-gt-val), to the point of feeling
* un-ignorable pain when it's sufficiently high.
*/
+edev|avia0|
postrin(interest-pc=85)|
negtrin(interest-pc=85|distraction-pc=90|intolerable-pc=95)|
pcloudAmbience(passband-count-lt-val=8|passband-count-gt-val=120)|
livoxGen1()|livoxProto1(SMO_IP)|
3JEDK380010Z39||
+edev|avia0
|negtrin(interest-pc=85|distraction-pc=90|intolerable-pc=95)
|pcloudLightAmbience(passband-count-gt-val=120)
|livoxGen1()|livoxProto1(SMO_IP)|3JEDK380010Z39||
/* pcloudDarkAmbience with postrin: be positively disposed to dim ambience
* (passband counts below passband-count-lt-val), but not so drawn that eyelids
* droop — no distraction, no stupefaction.
*/
+edev|avia0
|postrin(interest-pc=85)
|pcloudDarkAmbience(passband-count-lt-val=8)
|livoxGen1()|livoxProto1(SMO_IP)|3JEDK380010Z39||
+5 -4
View File
@@ -18,8 +18,9 @@ negtrin is free.
Intrinsic threshold params (and the deprecated `from-stimbuff` marker)
must not appear on the qualeIfaceApi params themselves — policy
validation rejects them there. The nontrin qualeIfaceApi
(e.g. `pcloudAmbience`) still owns its own sensory params (such as
passband comparators); only the threshold classifications (`interest-*`,
(e.g. `pcloudLightAmbience` / `pcloudDarkAmbience`) still owns its own
sensory params (such as passband comparators); only the threshold
classifications (`interest-*`,
`distraction-*`, `stupefaction-*` / `stupefying-*`, `intolerable-*`) live
in the postrin/negtrin segments.
@@ -35,7 +36,7 @@ Example (negtrin segment attached to ambience, with passband comparator
on the nontrin qualeIfaceApi):
````
+edev|avia0|negtrin(interest-pc=85)|pcloudAmbience(passband-count-gt-val=120)|livoxGen1()|livoxProto1(SMO_IP)|3JEDK380010Z39||
+edev|avia0|negtrin(interest-pc=85)|pcloudLightAmbience(passband-count-gt-val=120)|livoxGen1()|livoxProto1(SMO_IP)|3JEDK380010Z39||
````
## Unit Suffix Rules
@@ -83,7 +84,7 @@ with the "importance" argument set to "INTERESTING".
**Example (negtrin segment attached to ambience):**
````
+edev|avia0|negtrin(interest-pc=85)|pcloudAmbience(passband-count-gt-val=120)|livoxGen1()|livoxProto1(SMO_IP)|3JEDK380010Z39||
+edev|avia0|negtrin(interest-pc=85)|pcloudLightAmbience(passband-count-gt-val=120)|livoxGen1()|livoxProto1(SMO_IP)|3JEDK380010Z39||
````
## Distraction Threshold Parameters
+8 -7
View File
@@ -19,7 +19,8 @@ descriptors tell it exactly which body spots are raising it.
This direct information also assists SMO in narrowing down the scope of its
DB searches for methods to handle (increase or decrease) the stimval. For
example, for a negtrin raised from a `negtrin(...)` segment attached to a
nontrin DAP line (e.g. `pcloudAmbience`), we don't search DB by trying to
nontrin DAP line (e.g. `pcloudLightAmbience` / `pcloudDarkAmbience`),
we don't search DB by trying to
match all possible body spots with their stimvals. Rather, we search for
all the body spots that are described in the stencil. This optimizes DB
searches and also makes negtrin relieving/postrin satisfying searches
@@ -90,15 +91,15 @@ raising new intrins.
|livoxProto1()|SERIAL
```
The Livox Gen1 **`pcloudAmbience`** sensory line does **not** use
`n-stencils`; ambience floats are delivered in the stimulus frame buffer.
If Livox adds `n-stencils` for its intrinsic pipelines, it would appear
inside the `postrin(...)` / `negtrin(...)` segments attached to
`pcloudAmbience`, not on `pcloudAmbience` itself.
The Livox Gen1 **`pcloudLightAmbience`** / **`pcloudDarkAmbience`** sensory
lines do **not** use `n-stencils`; each emits one `uint32` passband count
per stimframe. If Livox adds `n-stencils` for its intrinsic pipelines, it
would appear inside the `postrin(...)` / `negtrin(...)` segments attached to
the ambience line, not on the ambience qualeIfaceApi itself.
**Invalid (sensory qualeIfaceApi must not carry intrin-oriented params):**
```
+idev|my-device|pcloudAmbience(n-stencils=4)|livoxGen1-pcloud()|livoxProto1()|3JEDK380010Z39
+idev|my-device|pcloudLightAmbience(n-stencils=4)|livoxGen1-pcloud()|livoxProto1()|3JEDK380010Z39
```
## Notes
+33 -12
View File
@@ -31,24 +31,44 @@ Each stim-buff-api is designed to work with specific stim-iface libraries that u
**Stim-Buff-API**: `livoxGen1-pcloudIntensity`
**Quale-Iface-API**: `pcloudIntensity` - Processes intensity/reflectivity data from point clouds
### 2. Point Cloud Ambience Data Device (Interoceptor)
### 2. Point Cloud Ambience Data Devices (Interoceptors)
**Purpose**: Provides ambience data from the LiDAR point cloud as a **vector of per-dagram average intensities** (one `float` per UDP datagram slot in the staging frame, length `n-dgrams-per-frame`). The OpenCL collate kernel writes these values directly into the acquired ambience `StimulusFrame` buffer.
Ambience is split into two qualeIfaceApis, each producing a single `uint32`
per stimframe — the count of per-frame slots whose average intensity passes
the qualeIface's comparator. The OpenCL collate kernel stages per-slot
averages into an internal buffer; each attached ambience stimbuff reads that
buffer and applies its own comparator.
#### 2a. `pcloudLightAmbience` (pairs with `negtrin`)
**Syntax**:
```
+idev | avia0 | pcloudAmbience | livoxGen1-pcloud() | livoxProto1(command-timeout-ms=1000,retry-delay-ms=3000,smo-ip=192.168.1.50,smo-subnet-nbits=24) | 3JEDK380010Z39
+idev | avia0 | negtrin(...) | pcloudLightAmbience(passband-count-gt-val=120) | livoxGen1-pcloud() | livoxProto1(...) | 3JEDK380010Z39
```
**Stim-Buff-API**: `livoxGen1-pcloud`
**Quale-Iface-API**: `pcloudAmbience` - Delivers per-dagram average intensity floats (sensory stream only).
**Quale-Iface-API**: `pcloudLightAmbience` — Requires exactly one
`passband-count-gt-val` on its params. Rejects `passband-count-lt-val`. The
stimframe stimspot is the count of per-frame slots whose average intensity
exceeds `passband-count-gt-val`. Scene is "unbearably much, get away" — pairs
with `negtrin(...)`; `postrin(...)` on this line is rejected.
Passband comparators (`passband-count-lt-val`, `passband-count-gt-val`) belong on
`pcloudAmbience(...)` itself, and both may appear together — `lt` drives the
postrin path, `gt` drives the negtrin path. Intrinsic threshold params
(`interest-*`, `distraction-*`, `stupefaction-*`/`stupefying-*`, `intolerable-*`)
live inside `postrin(...)` / `negtrin(...)` segments attached to the same DAP
line. See `docs/design/intrin-thresholds.md`.
#### 2b. `pcloudDarkAmbience` (pairs with `postrin`)
**Syntax**:
```
+idev | avia0 | postrin(...) | pcloudDarkAmbience(passband-count-lt-val=8) | livoxGen1-pcloud() | livoxProto1(...) | 3JEDK380010Z39
```
**Quale-Iface-API**: `pcloudDarkAmbience` — Requires exactly one
`passband-count-lt-val` on its params. Rejects `passband-count-gt-val`. The
stimframe stimspot is the count of per-frame slots whose average intensity
falls below `passband-count-lt-val`. Scene is "too good, stay here" — pairs
with `postrin(...)`; `negtrin(...)` on this line is rejected.
Intrinsic threshold params (`interest-*`, `distraction-*`,
`stupefaction-*`/`stupefying-*`, `intolerable-*`) live inside `postrin(...)` /
`negtrin(...)` segments on the same DAP line. See
`docs/design/intrin-thresholds.md`.
### 3. Point Cloud Coordinate Data Device (Extrospector)
@@ -158,7 +178,8 @@ The `livoxProto1` provider accepts the following parameters:
| Stim Feature | Stim-Buff-API | Quale-Iface-API | Description |
|--------------|---------------|----------------|-------------|
| Point Cloud Intensity | `livoxGen1-pcloudIntensity` | `pcloudIntensity` | Light intensity/reflectivity data |
| Point Cloud Ambience | `livoxGen1-pcloud` | `pcloudAmbience` | Per-dagram average intensity vector (`float` × `n-dgrams-per-frame`). Accepts `postrin(...)` / `negtrin(...)` segments on the same DAP line for intrin thresholds; see `docs/design/intrin-thresholds.md`. |
| Point Cloud Light Ambience | `livoxGen1-pcloud` | `pcloudLightAmbience` | One `uint32` per stimframe: count of slots whose average intensity exceeds `passband-count-gt-val`. Pairs with `negtrin(...)`; rejects `postrin(...)` and `passband-count-lt-val`. |
| Point Cloud Dark Ambience | `livoxGen1-pcloud` | `pcloudDarkAmbience` | One `uint32` per stimframe: count of slots whose average intensity falls below `passband-count-lt-val`. Pairs with `postrin(...)`; rejects `negtrin(...)` and `passband-count-gt-val`. |
| Point Cloud Coordinates | `livoxGen1-pcloud` | `pcloud` | Spatial coordinate data |
| Gyroscope | `livoxGen1-gyro` | `gyro` | Angular velocity measurements |
| Accelerometer | `livoxGen1-accel` | `accel` | Linear acceleration measurements |
+1 -1
View File
@@ -13,7 +13,7 @@ namespace smo {
namespace device {
/* Carrier used by the DAP spec parser to pass one parenthesized segment
* (e.g. postrin(interest-pc=85) or pcloudAmbience(...)) up the reduction
* (e.g. postrin(interest-pc=85) or pcloudLightAmbience(...)) up the reduction
* stack; the spec_body reduction classifies segments and populates the
* DeviceAttachmentSpec. Defined here so both the parser header and consumers
* of the generated header see the type.
+4 -2
View File
@@ -513,7 +513,8 @@ static const StimBuffApiDesc livoxGen1ApiDesc = {
.exportedQualeIfaceApis = {
{.name = "mesh"},
{.name = "pcloudIntensity"},
{.name = "pcloudAmbience"},
{.name = "pcloudLightAmbience"},
{.name = "pcloudDarkAmbience"},
{.name = "gyro"},
{.name = "accel"}
},
@@ -662,7 +663,8 @@ extern "C" void livoxGen1_attachDeviceReq(
// Unknown qualeIfaceApi
std::cerr << __func__ << ": Unsupported qualeIfaceApi '"
<< qualeIfaceApi << "' for LivoxGen1. "
"Supported values: mesh, pcloudIntensity, pcloudAmbience"
"Supported values: mesh, pcloudIntensity, "
"pcloudLightAmbience, pcloudDarkAmbience"
<< std::endl;
cb.callbackFn(false, desc);
return;
+2 -2
View File
@@ -15,8 +15,8 @@ class StimulusProducer;
/**
* MeshStimulusBuffer is a specialized StimulusBuffer for mesh data.
* Intrinsic threshold params are not allowed on mesh qualeIfaceApi lines;
* attach postrin(...) / negtrin(...) specifiers to a pcloudAmbience nontrin
* spec instead.
* attach postrin(...) specifiers to a pcloudDarkAmbience line or
* negtrin(...) specifiers to a pcloudLightAmbience line instead.
*/
class MeshStimulusBuffer
: public StimulusBuffer
@@ -35,8 +35,10 @@ OpenClCollatingAndMeshingEngine::OpenClCollatingAndMeshingEngine(
computeDevice(nullptr),
clAssemblyBufferClBuffer(nullptr),
clCollationBufferClBuffer(nullptr),
clAverageIntensityBufferClBuffer(nullptr),
clAssemblyBuffer(nullptr),
clCollationBuffer(nullptr),
clAverageIntensityBuffer(nullptr),
shouldAcceptRequests(false),
compactIsRunning(false),
collateIsRunning(false),
@@ -45,8 +47,11 @@ assemblyBufferPtr(nullptr),
assemblyBufferSize(0),
collationBufferPtr(nullptr),
collationBufferSize(0),
averageIntensityBufferPtr(nullptr),
averageIntensityBufferSize(0),
mappedAssemblyBuffer(nullptr),
mappedCollationBuffer(nullptr),
mappedAverageIntensityBuffer(nullptr),
frameAssemblyDesc(nullptr)
{
}
@@ -85,11 +90,15 @@ bool OpenClCollatingAndMeshingEngine::setup()
// Get StagingBuffer memory pointers from parent
struct iovec assemblyIov = parent.assemblyBuffer.getClEngineIovec();
struct iovec collationIov = parent.collationBuffer.getClEngineIovec();
struct iovec averageIntensityIov = parent.averageIntensityBuffer
.getClEngineIovec();
assemblyBufferPtr = assemblyIov.iov_base;
assemblyBufferSize = assemblyIov.iov_len;
collationBufferPtr = collationIov.iov_base;
collationBufferSize = collationIov.iov_len;
averageIntensityBufferPtr = averageIntensityIov.iov_base;
averageIntensityBufferSize = averageIntensityIov.iov_len;
// Get FrameAssemblyDesc from assembly buffer
frameAssemblyDesc = static_cast<std::shared_ptr<FrameAssemblyDesc>>(
@@ -131,13 +140,33 @@ bool OpenClCollatingAndMeshingEngine::setup()
return false;
}
/* CL_MEM_WRITE_ONLY describes *kernel* access: the collate kernel only
* writes per-slot averages, never reads them. Host-side reads in
* produceAmbienceStimulusFrame go through clEnqueueMapBuffer(CL_MAP_READ)
* which is independent of this flag.
*/
auto wip_clAverageIntensityBufferClBuffer = smoHooksPtr
->ComputeManager_createUseHostPtrBuffer(
averageIntensityBufferPtr, averageIntensityBufferSize,
CL_MEM_WRITE_ONLY);
if (!wip_clAverageIntensityBufferClBuffer)
{
std::cerr << __func__ << ": failed to create average intensity buffer"
<< std::endl;
return false;
}
// Cache cl_mem handles for the device we're using
cl_mem wip_clAssemblyBuffer = wip_clAssemblyBufferClBuffer
->getAssociatedBufferHandleForDevice(wip_computeDevice);
cl_mem wip_clCollationBuffer = wip_clCollationBufferClBuffer
->getAssociatedBufferHandleForDevice(wip_computeDevice);
cl_mem wip_clAverageIntensityBuffer = wip_clAverageIntensityBufferClBuffer
->getAssociatedBufferHandleForDevice(wip_computeDevice);
if (!wip_clAssemblyBuffer || !wip_clCollationBuffer)
if (!wip_clAssemblyBuffer || !wip_clCollationBuffer
|| !wip_clAverageIntensityBuffer)
{
std::cerr << __func__ << ": failed to get buffer handles for device"
<< std::endl;
@@ -162,8 +191,10 @@ bool OpenClCollatingAndMeshingEngine::setup()
computeDevice = wip_computeDevice;
clAssemblyBufferClBuffer = wip_clAssemblyBufferClBuffer;
clCollationBufferClBuffer = wip_clCollationBufferClBuffer;
clAverageIntensityBufferClBuffer = wip_clAverageIntensityBufferClBuffer;
clAssemblyBuffer = wip_clAssemblyBuffer;
clCollationBuffer = wip_clCollationBuffer;
clAverageIntensityBuffer = wip_clAverageIntensityBuffer;
slotCompactorProgram = std::move(wip_slotCompactorProgram);
collateProgram = std::move(wip_collateProgram);
slotCompactorKernel = std::move(wip_slotCompactorKernel);
@@ -184,7 +215,7 @@ void OpenClCollatingAndMeshingEngine::finalize()
// Complete any running kernels
if (compactIsRunning) { compactKernelComplete(true); }
if (collateIsRunning) {
collateKernelComplete(std::nullopt, std::nullopt, true);
collateKernelComplete(std::nullopt, false, true);
}
{
@@ -227,6 +258,12 @@ void OpenClCollatingAndMeshingEngine::finalize()
// Release OpenCL buffers via smo hooks
if (smoHooksPtr && smoHooksPtr->ComputeManager_releaseUseHostPtrBuffer)
{
if (clAverageIntensityBufferClBuffer)
{
smoHooksPtr->ComputeManager_releaseUseHostPtrBuffer(
clAverageIntensityBufferClBuffer);
clAverageIntensityBufferClBuffer.reset();
}
if (clCollationBufferClBuffer)
{
smoHooksPtr->ComputeManager_releaseUseHostPtrBuffer(
@@ -242,6 +279,7 @@ void OpenClCollatingAndMeshingEngine::finalize()
}
// Reset cached cl_mem handles
clAverageIntensityBuffer = nullptr;
clCollationBuffer = nullptr;
clAssemblyBuffer = nullptr;
@@ -268,6 +306,8 @@ void OpenClCollatingAndMeshingEngine::finalize()
assemblyBufferSize = 0;
collationBufferPtr = nullptr;
collationBufferSize = 0;
averageIntensityBufferPtr = nullptr;
averageIntensityBufferSize = 0;
frameAssemblyDesc = nullptr;
}
@@ -364,7 +404,7 @@ bool OpenClCollatingAndMeshingEngine::startCompactKernel(
bool OpenClCollatingAndMeshingEngine::startCollateKernel(
std::optional<std::reference_wrapper<StimulusFrame>> intensityStimFrame,
std::optional<std::reference_wrapper<StimulusFrame>> ambienceStimFrame,
bool anyAmbienceAttached,
collateKernelCbFn callback)
{
// Store the caller's callback
@@ -374,11 +414,15 @@ bool OpenClCollatingAndMeshingEngine::startCollateKernel(
auto validateBuffers = [this]() {
struct iovec assemblyIov = parent.assemblyBuffer.getClEngineIovec();
struct iovec collationIov = parent.collationBuffer.getClEngineIovec();
struct iovec averageIntensityIov = parent.averageIntensityBuffer
.getClEngineIovec();
if (assemblyIov.iov_base != assemblyBufferPtr
|| assemblyIov.iov_len != assemblyBufferSize
|| collationIov.iov_base != collationBufferPtr
|| collationIov.iov_len != collationBufferSize)
|| collationIov.iov_len != collationBufferSize
|| averageIntensityIov.iov_base != averageIntensityBufferPtr
|| averageIntensityIov.iov_len != averageIntensityBufferSize)
{
throw std::runtime_error(
std::string(__func__) + ": buffer mismatch - buffers have changed");
@@ -386,9 +430,9 @@ bool OpenClCollatingAndMeshingEngine::startCollateKernel(
};
// Setup args callable
auto setupArgs = [this, intensityStimFrame, ambienceStimFrame]()
auto setupArgs = [this, intensityStimFrame, anyAmbienceAttached]()
{
return setupCollateDgramsArgs(intensityStimFrame, ambienceStimFrame);
return setupCollateDgramsArgs(intensityStimFrame, anyAmbienceAttached);
};
/** EXPLANATION:
@@ -439,27 +483,17 @@ bool OpenClCollatingAndMeshingEngine::startCollateKernel(
}
}
// Map/unmap ambience stim frame buffer (collate writes per-slot averages here)
if (ambienceStimFrame.has_value())
// Map/unmap average intensity staging buffer (collate writes per-slot
// averages here when any ambience stimbuff is attached).
if (anyAmbienceAttached)
{
StimulusFrame& ambienceFrame = ambienceStimFrame->get();
cl_mem ambienceClBuffer = ambienceFrame.clBuffer
->getAssociatedBufferHandleForDevice(computeDevice);
if (ambienceClBuffer)
if (!mapAverageIntensityBuffer(CL_MAP_WRITE_INVALIDATE_REGION))
{
void* mappedAmbienceBuffer = nullptr;
if (!mapBuffer(
ambienceClBuffer, ambienceFrame.slotDesc.nBytes,
CL_MAP_WRITE_INVALIDATE_REGION, mappedAmbienceBuffer))
{
std::cerr << __func__ << ": failed to map ambience buffer"
std::cerr << __func__ << ": failed to map average intensity buffer"
<< std::endl;
return false;
}
unmapBuffer(ambienceClBuffer, mappedAmbienceBuffer);
}
unmapAverageIntensityBuffer();
}
// Calculate global work size (just num slots in the frame)
@@ -630,7 +664,7 @@ bool OpenClCollatingAndMeshingEngine::setupSlotCompactorsArgs(
bool OpenClCollatingAndMeshingEngine::setupCollateDgramsArgs(
std::optional<std::reference_wrapper<StimulusFrame>> intensityStimFrame,
std::optional<std::reference_wrapper<StimulusFrame>> ambienceStimFrame)
bool anyAmbienceAttached)
{
// Extract parameters for collateDgrams kernel
uint32_t slotStride = static_cast<uint32_t>(
@@ -684,26 +718,20 @@ bool OpenClCollatingAndMeshingEngine::setupCollateDgramsArgs(
return false;
}
// Set ambience buffer argument (arg 3): acquired PcloudAmbience StimulusFrame
cl_mem ambienceClBufferArg = nullptr;
if (ambienceStimFrame.has_value())
{
StimulusFrame& ambienceFrame = ambienceStimFrame->get();
// Set ambience buffer argument (arg 3): per-slot average intensity
// staging buffer. Set when any ambience stimbuff is attached.
cl_mem averageIntensityClBufferArg =
anyAmbienceAttached ? clAverageIntensityBuffer : nullptr;
const size_t needBytes = static_cast<size_t>(nDgramsPerFrame)
* sizeof(float);
if (ambienceFrame.slotDesc.nBytes < needBytes)
if (anyAmbienceAttached && averageIntensityBufferSize < needBytes)
{
std::cerr << __func__ << ": ambience stim frame slot too small: "
<< ambienceFrame.slotDesc.nBytes << " < " << needBytes
<< std::endl;
std::cerr << __func__ << ": average intensity buffer too small: "
<< averageIntensityBufferSize << " < " << needBytes << std::endl;
return false;
}
ambienceClBufferArg = ambienceFrame.clBuffer
->getAssociatedBufferHandleForDevice(computeDevice);
}
err = clSetKernelArg(
collateKernel.get(), 3, sizeof(cl_mem), &ambienceClBufferArg);
collateKernel.get(), 3, sizeof(cl_mem), &averageIntensityClBufferArg);
if (err != CL_SUCCESS)
{
@@ -782,7 +810,7 @@ void OpenClCollatingAndMeshingEngine::compactKernelComplete(bool isFinalizing)
void OpenClCollatingAndMeshingEngine::collateKernelComplete(
std::optional<std::reference_wrapper<StimulusFrame>> intensityStimFrame,
std::optional<std::reference_wrapper<StimulusFrame>> ambienceStimFrame,
bool anyAmbienceAttached,
bool isFinalizing)
{
cl_map_flags mapFlags;
@@ -816,22 +844,12 @@ void OpenClCollatingAndMeshingEngine::collateKernelComplete(
}
}
// Sync GPU writes into ambience stim frame host backing store
if (ambienceStimFrame.has_value())
// Sync GPU writes into average intensity staging buffer host backing
// store so attached ambience stimbuffs can read the per-slot averages.
if (anyAmbienceAttached)
{
StimulusFrame& ambienceFrame = ambienceStimFrame->get();
cl_mem ambienceClBuffer = ambienceFrame.clBuffer
->getAssociatedBufferHandleForDevice(computeDevice);
if (ambienceClBuffer)
{
void* mappedAmbienceBuffer = nullptr;
if (mapBuffer(
ambienceClBuffer, ambienceFrame.slotDesc.nBytes,
CL_MAP_READ, mappedAmbienceBuffer))
{
unmapBuffer(ambienceClBuffer, mappedAmbienceBuffer);
}
if (mapAverageIntensityBuffer(mapFlags)) {
unmapAverageIntensityBuffer();
}
}
@@ -956,6 +974,39 @@ bool OpenClCollatingAndMeshingEngine::unmapCollationBuffer()
return true;
}
bool OpenClCollatingAndMeshingEngine::mapAverageIntensityBuffer(
cl_map_flags mapFlags)
{
return mapBuffer(
clAverageIntensityBuffer, averageIntensityBufferSize, mapFlags,
mappedAverageIntensityBuffer);
}
bool OpenClCollatingAndMeshingEngine::unmapAverageIntensityBuffer()
{
unmapBuffer(clAverageIntensityBuffer, mappedAverageIntensityBuffer);
mappedAverageIntensityBuffer = nullptr;
return true;
}
void OpenClCollatingAndMeshingEngine::produceAmbienceStimulusFrame(
StimulusFrame& ambienceFrame, const ParamComparator& comparator,
uint32_t nSucceeded)
{
const float* averages =
static_cast<const float*>(averageIntensityBufferPtr);
uint32_t passbandCount = 0;
for (uint32_t i = 0; i < nSucceeded; ++i) {
const float& average = averages[i];
if (comparator(average)) { ++passbandCount; }
}
uint32_t& passbandCountOut =
*reinterpret_cast<uint32_t*>(ambienceFrame.slotDesc.vaddr);
passbandCountOut = passbandCount;
}
class OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq
: public sscl::PostedAsynchronousContinuation<compactCollateAndMeshFrameReqCbFn>
{
@@ -964,7 +1015,8 @@ private:
sscl::AsynchronousLoop frameAssemblyResult;
StimulusFrame& stimulusFrame;
std::optional<std::reference_wrapper<StimulusFrame>> intensityStimFrame;
std::optional<std::reference_wrapper<StimulusFrame>> ambienceStimFrame;
std::optional<AmbienceProductionDesc> lightAmbienceProductionDesc;
std::optional<AmbienceProductionDesc> darkAmbienceProductionDesc;
public:
CompactCollateAndMeshFrameReq(
@@ -972,7 +1024,8 @@ public:
sscl::AsynchronousLoop& asyncLoop,
StimulusFrame& stimulusFrame_,
std::optional<std::reference_wrapper<StimulusFrame>> intensityStimFrame_,
std::optional<std::reference_wrapper<StimulusFrame>> ambienceStimFrame_,
std::optional<AmbienceProductionDesc> lightAmbienceProductionDesc_,
std::optional<AmbienceProductionDesc> darkAmbienceProductionDesc_,
const std::shared_ptr<sscl::ComponentThread>& caller,
sscl::Callback<compactCollateAndMeshFrameReqCbFn> cb)
: sscl::PostedAsynchronousContinuation<compactCollateAndMeshFrameReqCbFn>(
@@ -980,9 +1033,16 @@ public:
engine(engine_),
frameAssemblyResult(asyncLoop), stimulusFrame(stimulusFrame_),
intensityStimFrame(intensityStimFrame_),
ambienceStimFrame(ambienceStimFrame_)
lightAmbienceProductionDesc(std::move(lightAmbienceProductionDesc_)),
darkAmbienceProductionDesc(std::move(darkAmbienceProductionDesc_))
{}
bool anyAmbienceAttached() const
{
return lightAmbienceProductionDesc.has_value()
|| darkAmbienceProductionDesc.has_value();
}
public:
void callOriginalCallback(bool success)
{ callOriginalCb(success, std::ref(stimulusFrame)); }
@@ -1074,7 +1134,7 @@ public:
engine.collateKernelStartTime = std::chrono::high_resolution_clock::now();
bool success = engine.startCollateKernel(
context->intensityStimFrame, context->ambienceStimFrame,
context->intensityStimFrame, context->anyAmbienceAttached(),
std::bind(
&CompactCollateAndMeshFrameReq
::compactCollateAndMeshFrameReq4_collateDone_maybePosted,
@@ -1084,7 +1144,7 @@ public:
if (!success)
{
engine.collateKernelComplete(
context->intensityStimFrame, context->ambienceStimFrame);
context->intensityStimFrame, context->anyAmbienceAttached());
callOriginalCallback(false);
return;
@@ -1115,7 +1175,28 @@ public:
* completes/cleans up any in-flight operations.
*/
engine.collateKernelComplete(
context->intensityStimFrame, context->ambienceStimFrame);
context->intensityStimFrame, context->anyAmbienceAttached());
// Produce each attached ambience stimbuff's passband count from
// the per-slot averages the collate kernel staged.
uint32_t nSucceededForAmbience =
context->frameAssemblyResult.nSucceeded.load();
if (context->lightAmbienceProductionDesc.has_value())
{
engine.produceAmbienceStimulusFrame(
context->lightAmbienceProductionDesc->frame.get(),
context->lightAmbienceProductionDesc->comparator,
nSucceededForAmbience);
}
if (context->darkAmbienceProductionDesc.has_value())
{
engine.produceAmbienceStimulusFrame(
context->darkAmbienceProductionDesc->frame.get(),
context->darkAmbienceProductionDesc->comparator,
nSucceededForAmbience);
}
// Record collate kernel end time
engine.collateKernelEndTime = std::chrono::high_resolution_clock::now();
@@ -1154,13 +1235,9 @@ public:
(void)highIntensityCount;
#if 0
// Legacy debug: ambience floats live in ambienceStimFrame after collate
std::cout << __func__ << ": intensityRingBufferIndex="
<< (context->intensityStimFrame.has_value() ?
context->intensityStimFrame->get().ringBufferIndex : SIZE_MAX)
<< ", ambienceRingBufferIndex="
<< (context->ambienceStimFrame.has_value() ?
context->ambienceStimFrame->get().ringBufferIndex : SIZE_MAX)
<< ", pointsPerDgram=" << pointsPerDgram
<< ", nSucceeded=" << nSucceeded
<< ", totalPoints=" << totalPoints
@@ -1174,7 +1251,8 @@ public:
void OpenClCollatingAndMeshingEngine::compactCollateAndMeshFrameReq(
sscl::AsynchronousLoop& asyncLoop, StimulusFrame& stimulusFrame,
std::optional<std::reference_wrapper<StimulusFrame>> intensityStimFrame,
std::optional<std::reference_wrapper<StimulusFrame>> ambienceStimFrame,
std::optional<AmbienceProductionDesc> lightAmbienceProductionDesc,
std::optional<AmbienceProductionDesc> darkAmbienceProductionDesc,
sscl::Callback<compactCollateAndMeshFrameReqCbFn> callback)
{
{
@@ -1188,7 +1266,8 @@ void OpenClCollatingAndMeshingEngine::compactCollateAndMeshFrameReq(
auto caller = smoHooksPtr->ComponentThread_getSelf();
auto request = std::make_shared<CompactCollateAndMeshFrameReq>(
*this, asyncLoop, stimulusFrame, intensityStimFrame, ambienceStimFrame,
*this, asyncLoop, stimulusFrame, intensityStimFrame,
std::move(lightAmbienceProductionDesc), std::move(darkAmbienceProductionDesc),
caller,
std::move(callback));
@@ -21,12 +21,24 @@
#include <user/frameAssemblyDesc.h>
#include <user/compute.h>
#include <user/senseApiDesc.h>
#include "pcloudAmbienceQualeIfaceApi.h"
#define OCLCOLLMESH_ENGN_FINALIZE_DELAY_MS 1
namespace smo {
namespace stim_buff {
/* One "job" per attached ambience stimbuff: the StimulusFrame to write the
* uint32 passband count into, and the comparator to apply to the per-slot
* averages the collate kernel staged into averageIntensityBuffer. A job is
* only constructed when its corresponding ambience stimbuff is attached.
*/
struct AmbienceProductionDesc
{
std::reference_wrapper<StimulusFrame> frame;
ParamComparator comparator;
};
// Custom deleters for OpenCL handles
struct ClProgramDeleter
{
@@ -80,7 +92,8 @@ public:
void compactCollateAndMeshFrameReq(
sscl::AsynchronousLoop& asyncLoop, StimulusFrame& stimulusFrame,
std::optional<std::reference_wrapper<StimulusFrame>> intensityStimFrame,
std::optional<std::reference_wrapper<StimulusFrame>> ambienceStimFrame,
std::optional<AmbienceProductionDesc> lightAmbienceProductionDesc,
std::optional<AmbienceProductionDesc> darkAmbienceProductionDesc,
sscl::Callback<compactCollateAndMeshFrameReqCbFn> callback);
private:
@@ -93,16 +106,25 @@ private:
compactKernelCbFn callback);
bool startCollateKernel(
std::optional<std::reference_wrapper<StimulusFrame>> intensityStimFrame,
std::optional<std::reference_wrapper<StimulusFrame>> ambienceStimFrame,
bool anyAmbienceAttached,
collateKernelCbFn callback);
void compactKernelComplete(bool isFinalizing=false);
void collateKernelComplete(
std::optional<std::reference_wrapper<StimulusFrame>> intensityStimFrame,
std::optional<std::reference_wrapper<StimulusFrame>> ambienceStimFrame,
bool anyAmbienceAttached,
bool isFinalizing=false);
bool stop();
/* Apply `comparator` to the nSucceeded per-slot averages the collate
* kernel wrote into averageIntensityBuffer, and write the resulting
* uint32 passband count as the single stimspot of `ambienceFrame`.
*/
void produceAmbienceStimulusFrame(
StimulusFrame& ambienceFrame,
const ParamComparator& comparator,
uint32_t nSucceeded);
public:
// Get kernel execution durations in milliseconds
std::chrono::milliseconds getCompactKernelDuration() const;
@@ -121,9 +143,11 @@ private:
// OpenCL buffers (managed by ComputeManager)
std::shared_ptr<smo::compute::ClBuffer> clAssemblyBufferClBuffer;
std::shared_ptr<smo::compute::ClBuffer> clCollationBufferClBuffer;
std::shared_ptr<smo::compute::ClBuffer> clAverageIntensityBufferClBuffer;
// Cached cl_mem handles for the device we're using
cl_mem clAssemblyBuffer;
cl_mem clCollationBuffer;
cl_mem clAverageIntensityBuffer;
// State tracking
sscl::SpinLock shouldAcceptRequestsLock;
@@ -138,9 +162,12 @@ private:
size_t assemblyBufferSize;
void* collationBufferPtr;
size_t collationBufferSize;
void* averageIntensityBufferPtr;
size_t averageIntensityBufferSize;
// Mapped buffer pointers (for zero-copy synchronization)
void* mappedAssemblyBuffer;
void* mappedCollationBuffer;
void* mappedAverageIntensityBuffer;
// Frame descriptor (cached from setup)
std::shared_ptr<FrameAssemblyDesc> frameAssemblyDesc;
@@ -174,7 +201,7 @@ private:
StagingBuffer& assemblyBuff, uint32_t nSucceeded);
bool setupCollateDgramsArgs(
std::optional<std::reference_wrapper<StimulusFrame>> intensityStimFrame,
std::optional<std::reference_wrapper<StimulusFrame>> ambienceStimFrame);
bool anyAmbienceAttached);
// Generic buffer mapping/unmapping for zero-copy synchronization
bool mapBuffer(
@@ -186,6 +213,8 @@ private:
bool unmapAssemblyBuffer();
bool mapCollationBuffer(cl_map_flags mapFlags = CL_MAP_READ);
bool unmapCollationBuffer();
bool mapAverageIntensityBuffer(cl_map_flags mapFlags = CL_MAP_READ);
bool unmapAverageIntensityBuffer();
// Forward declaration for continuation class
class CompactCollateAndMeshFrameReq;
@@ -1,12 +1,15 @@
#ifndef _LIVOX_GEN1_PCLOUD_AMBIENCE_QUALE_IFACE_API_H
#define _LIVOX_GEN1_PCLOUD_AMBIENCE_QUALE_IFACE_API_H
#include <algorithm>
#include <cstdint>
#include <memory>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <user/deviceAttachmentSpec.h>
#include <user/intrin.h>
#include <user/intrinThresholdParams.h>
namespace smo {
namespace stim_buff {
@@ -36,45 +39,110 @@ struct ParamComparator
}
};
struct PcloudAmbiencePassbandComparators
inline bool paramsContain(
const std::vector<std::pair<std::string, std::string>>& params,
const std::string& name)
{
std::optional<ParamComparator> lt;
std::optional<ParamComparator> gt;
};
return std::any_of(
params.begin(), params.end(),
[&name](const auto& p) { return p.first == name; });
}
/* Both `passband-count-lt-val` and `passband-count-gt-val` are permitted
* simultaneously on a pcloudAmbience qualeIfaceApi: the lt comparator
* typically feeds a postrin(...) segment (triggering on unusually low
* counts), and the gt comparator typically feeds a negtrin(...) segment
* (triggering on unusually high counts).
/* pcloudLightAmbience requires exactly one `passband-count-gt-val` on its
* qualeIfaceApi params; `passband-count-lt-val` is a hard error. Feeds a
* negtrin(...) segment (scene is "unbearably much, get away").
*/
inline PcloudAmbiencePassbandComparators parsePcloudAmbiencePassbandComparators(
inline ParamComparator parsePcloudLightAmbienceGtComparator(
const std::shared_ptr<device::DeviceAttachmentSpec>& deviceAttachmentSpec)
{
const auto& params = deviceAttachmentSpec->qualeIfaceApiParams;
if (paramsContain(params, "passband-count-lt-val"))
{
throw std::runtime_error(
"pcloudLightAmbience qualeIfaceApi does not accept "
"'passband-count-lt-val'; use pcloudDarkAmbience for lt-val "
"pipelines.");
}
constexpr int kParamNotSpecified = -1;
const int gtVal = device::DeviceAttachmentSpec::parseOptionalParamAsInt(
params, "passband-count-gt-val", kParamNotSpecified);
const int ltVal = device::DeviceAttachmentSpec::parseOptionalParamAsInt(
params, "passband-count-lt-val", kParamNotSpecified);
PcloudAmbiencePassbandComparators out;
if (gtVal != kParamNotSpecified)
if (gtVal == kParamNotSpecified)
{
out.gt = ParamComparator{
throw std::runtime_error(
"pcloudLightAmbience qualeIfaceApi requires "
"'passband-count-gt-val' on its params.");
}
return ParamComparator{
.op = OP_CMP_GT,
.value = static_cast<uint32_t>(gtVal),
};
}
if (ltVal != kParamNotSpecified)
/* pcloudDarkAmbience requires exactly one `passband-count-lt-val` on its
* qualeIfaceApi params; `passband-count-gt-val` is a hard error. Feeds a
* postrin(...) segment (scene is "too good, stay here").
*/
inline ParamComparator parsePcloudDarkAmbienceLtComparator(
const std::shared_ptr<device::DeviceAttachmentSpec>& deviceAttachmentSpec)
{
out.lt = ParamComparator{
const auto& params = deviceAttachmentSpec->qualeIfaceApiParams;
if (paramsContain(params, "passband-count-gt-val"))
{
throw std::runtime_error(
"pcloudDarkAmbience qualeIfaceApi does not accept "
"'passband-count-gt-val'; use pcloudLightAmbience for gt-val "
"pipelines.");
}
constexpr int kParamNotSpecified = -1;
const int ltVal = device::DeviceAttachmentSpec::parseOptionalParamAsInt(
params, "passband-count-lt-val", kParamNotSpecified);
if (ltVal == kParamNotSpecified)
{
throw std::runtime_error(
"pcloudDarkAmbience qualeIfaceApi requires "
"'passband-count-lt-val' on its params.");
}
return ParamComparator{
.op = OP_CMP_LT,
.value = static_cast<uint32_t>(ltVal),
};
}
return out;
/* Shared parser used by both pcloudLightAmbience and pcloudDarkAmbience
* stimbuffs to decode a postrin(...) / negtrin(...) segment's threshold
* params into an IntrinConfig. Kept with the qualeIfaceApi helpers because
* it's ambience-specific (nDgramsPerFrame is the percentage base).
*/
inline intrin::IntrinConfig parseAmbienceIntrinConfigFromParams(
std::string_view intrinKind,
const std::vector<std::pair<std::string, std::string>>& params,
size_t nDgramsPerFrame)
{
intrin::validateIntrinSegmentParams(intrinKind, params);
const auto threshold = intrin::parseOptionalThresholdParam(
params,
intrin::kIntrinInterestPcNames,
intrin::kIntrinInterestThrNames,
/*defaultValue=*/0,
/*defaultUnit=*/intrin::ThresholdUnit::Absolute);
return intrin::IntrinConfig{
.percentage =
threshold.unit == intrin::ThresholdUnit::Percentage
? static_cast<uint32_t>(threshold.value)
: 0U,
.threshold = intrin::resolveThresholdValue(
threshold, nDgramsPerFrame),
};
}
} // namespace stim_buff
@@ -1,150 +0,0 @@
#ifndef _LIVOX_GEN1_PCLOUD_AMBIENCE_STIMULUS_BUFFER_H
#define _LIVOX_GEN1_PCLOUD_AMBIENCE_STIMULUS_BUFFER_H
#include <cstddef>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <user/deviceAttachmentSpec.h>
#include <user/intrin.h>
#include <user/intrinThresholdParams.h>
#include <user/stagingBuffer.h>
#include <user/stimulusBuffer.h>
#include "pcloudAmbienceQualeIfaceApi.h"
namespace smo {
namespace stim_buff {
class StimulusProducer;
inline intrin::IntrinConfig parseAmbienceIntrinConfigFromParams(
std::string_view intrinKind,
const std::vector<std::pair<std::string, std::string>>& params,
size_t nDgramsPerFrame)
{
intrin::validateIntrinSegmentParams(intrinKind, params);
const auto threshold = intrin::parseOptionalThresholdParam(
params,
intrin::kIntrinInterestPcNames,
intrin::kIntrinInterestThrNames,
/*defaultValue=*/0,
/*defaultUnit=*/intrin::ThresholdUnit::Absolute);
return intrin::IntrinConfig{
.percentage =
threshold.unit == intrin::ThresholdUnit::Percentage
? static_cast<uint32_t>(threshold.value)
: 0U,
.threshold = intrin::resolveThresholdValue(
threshold, nDgramsPerFrame),
};
}
/**
* Sensory PcloudAmbience buffer: per-dgram ambience floats. A DAP spec may
* optionally attach a postrin(...) and/or a negtrin(...) specifier to this
* qualeIfaceApi; when present, interest thresholds from those specifiers and
* passband-count comparators from this spec's own qualeIfaceApi params are
* combined to decide when an intrin event should fire.
*
* Convention: the postrin pipeline pairs with passband-count-lt-val (a sparse
* ambient scene being "too good, stay here"); the negtrin pipeline pairs with
* passband-count-gt-val (a dense ambient scene being "unbearably much, get
* away"). Both comparators may be specified simultaneously on this qualeIface.
*/
class PcloudAmbienceStimulusBuffer
: public StimulusBuffer
{
public:
explicit PcloudAmbienceStimulusBuffer(
StimulusProducer& parent,
const std::shared_ptr<device::DeviceAttachmentSpec>& deviceAttachmentSpec,
int histbuffMs,
const StagingBuffer::IOEngineConstraints& inputEngineConstraints,
const StagingBuffer::IOEngineConstraints& outputEngineConstraints,
const SmoCallbacks& callbacks,
cl_mem_flags flags,
size_t nDgramsPerFrame_)
: StimulusBuffer(
parent, deviceAttachmentSpec, histbuffMs,
inputEngineConstraints, outputEngineConstraints,
callbacks, flags),
nDgramsPerFrame(nDgramsPerFrame_)
{
intrin::validateNoIntrinParamsOnQualeIface(
deviceAttachmentSpec->qualeIfaceApi,
deviceAttachmentSpec->qualeIfaceApiParams);
const auto passbandComparators =
parsePcloudAmbiencePassbandComparators(deviceAttachmentSpec);
passbandCountLtComparator = passbandComparators.lt;
passbandCountGtComparator = passbandComparators.gt;
if (!deviceAttachmentSpec->postrin.empty())
{
postrinInterestConfig = parseAmbienceIntrinConfigFromParams(
"postrin",
deviceAttachmentSpec->postrinParams,
nDgramsPerFrame_);
if (!passbandCountLtComparator.has_value())
{
throw std::runtime_error(
"pcloudAmbience DAP spec declares a postrin(...) but no "
"'passband-count-lt-val' on the pcloudAmbience qualeIface "
"params to feed it.");
}
}
if (!deviceAttachmentSpec->negtrin.empty())
{
negtrinInterestConfig = parseAmbienceIntrinConfigFromParams(
"negtrin",
deviceAttachmentSpec->negtrinParams,
nDgramsPerFrame_);
if (!passbandCountGtComparator.has_value())
{
throw std::runtime_error(
"pcloudAmbience DAP spec declares a negtrin(...) but no "
"'passband-count-gt-val' on the pcloudAmbience qualeIface "
"params to feed it.");
}
}
}
~PcloudAmbienceStimulusBuffer() = default;
PcloudAmbienceStimulusBuffer(const PcloudAmbienceStimulusBuffer&) = delete;
PcloudAmbienceStimulusBuffer& operator=(
const PcloudAmbienceStimulusBuffer&) = delete;
PcloudAmbienceStimulusBuffer(PcloudAmbienceStimulusBuffer&&) = delete;
PcloudAmbienceStimulusBuffer& operator=(
PcloudAmbienceStimulusBuffer&&) = delete;
bool shouldTriggerPostrinEvent(uint32_t ambiencePassbandCount) const
{
if (!postrinInterestConfig.has_value()) { return false; }
return ambiencePassbandCount >= postrinInterestConfig->threshold;
}
bool shouldTriggerNegtrinEvent(uint32_t ambiencePassbandCount) const
{
if (!negtrinInterestConfig.has_value()) { return false; }
return ambiencePassbandCount >= negtrinInterestConfig->threshold;
}
public:
size_t nDgramsPerFrame;
std::optional<intrin::IntrinConfig> postrinInterestConfig;
std::optional<intrin::IntrinConfig> negtrinInterestConfig;
std::optional<ParamComparator> passbandCountLtComparator;
std::optional<ParamComparator> passbandCountGtComparator;
};
} // namespace stim_buff
} // namespace smo
#endif // _LIVOX_GEN1_PCLOUD_AMBIENCE_STIMULUS_BUFFER_H
@@ -0,0 +1,97 @@
#ifndef _LIVOX_GEN1_PCLOUD_DARK_AMBIENCE_STIMULUS_BUFFER_H
#define _LIVOX_GEN1_PCLOUD_DARK_AMBIENCE_STIMULUS_BUFFER_H
#include <cstddef>
#include <cstdint>
#include <memory>
#include <optional>
#include <user/deviceAttachmentSpec.h>
#include <user/intrin.h>
#include <user/intrinThresholdParams.h>
#include <user/stagingBuffer.h>
#include <user/stimulusBuffer.h>
#include "pcloudAmbienceQualeIfaceApi.h"
namespace smo {
namespace stim_buff {
class StimulusProducer;
/**
* Sensory pcloudDarkAmbience buffer: one uint32 stimspot per stimframe
* the count of per-frame slots whose average intensity falls below the
* qualeIface's passband-count-lt-val. A DAP spec may optionally attach a
* postrin(...) specifier (scene is "too good, stay here"); negtrin is not
* valid on this qualeIfaceApi.
*/
class PcloudDarkAmbienceStimulusBuffer
: public StimulusBuffer
{
public:
explicit PcloudDarkAmbienceStimulusBuffer(
StimulusProducer& parent,
const std::shared_ptr<device::DeviceAttachmentSpec>& deviceAttachmentSpec,
int histbuffMs,
const StagingBuffer::IOEngineConstraints& inputEngineConstraints,
const StagingBuffer::IOEngineConstraints& outputEngineConstraints,
const SmoCallbacks& callbacks,
cl_mem_flags flags,
size_t nDgramsPerFrame_)
: StimulusBuffer(
parent, deviceAttachmentSpec, histbuffMs,
inputEngineConstraints, outputEngineConstraints,
callbacks, flags),
nDgramsPerFrame(nDgramsPerFrame_)
{
intrin::validateNoIntrinParamsOnQualeIface(
deviceAttachmentSpec->qualeIfaceApi,
deviceAttachmentSpec->qualeIfaceApiParams);
passbandCountLtComparator =
parsePcloudDarkAmbienceLtComparator(deviceAttachmentSpec);
if (!deviceAttachmentSpec->negtrin.empty())
{
throw std::runtime_error(
"pcloudDarkAmbience DAP spec for device '"
+ deviceAttachmentSpec->deviceIdentifier
+ "' declares a negtrin(...); negtrin belongs on a "
"pcloudLightAmbience line, not pcloudDarkAmbience.");
}
if (!deviceAttachmentSpec->postrin.empty())
{
postrinInterestConfig = parseAmbienceIntrinConfigFromParams(
"postrin",
deviceAttachmentSpec->postrinParams,
nDgramsPerFrame_);
}
}
~PcloudDarkAmbienceStimulusBuffer() = default;
PcloudDarkAmbienceStimulusBuffer(
const PcloudDarkAmbienceStimulusBuffer&) = delete;
PcloudDarkAmbienceStimulusBuffer& operator=(
const PcloudDarkAmbienceStimulusBuffer&) = delete;
PcloudDarkAmbienceStimulusBuffer(
PcloudDarkAmbienceStimulusBuffer&&) = delete;
PcloudDarkAmbienceStimulusBuffer& operator=(
PcloudDarkAmbienceStimulusBuffer&&) = delete;
bool shouldTriggerPostrinEvent(uint32_t ambiencePassbandCount) const
{
if (!postrinInterestConfig.has_value()) { return false; }
return ambiencePassbandCount >= postrinInterestConfig->threshold;
}
public:
size_t nDgramsPerFrame;
ParamComparator passbandCountLtComparator;
std::optional<intrin::IntrinConfig> postrinInterestConfig;
};
} // namespace stim_buff
} // namespace smo
#endif // _LIVOX_GEN1_PCLOUD_DARK_AMBIENCE_STIMULUS_BUFFER_H
@@ -0,0 +1,97 @@
#ifndef _LIVOX_GEN1_PCLOUD_LIGHT_AMBIENCE_STIMULUS_BUFFER_H
#define _LIVOX_GEN1_PCLOUD_LIGHT_AMBIENCE_STIMULUS_BUFFER_H
#include <cstddef>
#include <cstdint>
#include <memory>
#include <optional>
#include <user/deviceAttachmentSpec.h>
#include <user/intrin.h>
#include <user/intrinThresholdParams.h>
#include <user/stagingBuffer.h>
#include <user/stimulusBuffer.h>
#include "pcloudAmbienceQualeIfaceApi.h"
namespace smo {
namespace stim_buff {
class StimulusProducer;
/**
* Sensory pcloudLightAmbience buffer: one uint32 stimspot per stimframe
* the count of per-frame slots whose average intensity exceeds the
* qualeIface's passband-count-gt-val. A DAP spec may optionally attach a
* negtrin(...) specifier (scene is "unbearably much, get away"); postrin
* is not valid on this qualeIfaceApi.
*/
class PcloudLightAmbienceStimulusBuffer
: public StimulusBuffer
{
public:
explicit PcloudLightAmbienceStimulusBuffer(
StimulusProducer& parent,
const std::shared_ptr<device::DeviceAttachmentSpec>& deviceAttachmentSpec,
int histbuffMs,
const StagingBuffer::IOEngineConstraints& inputEngineConstraints,
const StagingBuffer::IOEngineConstraints& outputEngineConstraints,
const SmoCallbacks& callbacks,
cl_mem_flags flags,
size_t nDgramsPerFrame_)
: StimulusBuffer(
parent, deviceAttachmentSpec, histbuffMs,
inputEngineConstraints, outputEngineConstraints,
callbacks, flags),
nDgramsPerFrame(nDgramsPerFrame_)
{
intrin::validateNoIntrinParamsOnQualeIface(
deviceAttachmentSpec->qualeIfaceApi,
deviceAttachmentSpec->qualeIfaceApiParams);
passbandCountGtComparator =
parsePcloudLightAmbienceGtComparator(deviceAttachmentSpec);
if (!deviceAttachmentSpec->postrin.empty())
{
throw std::runtime_error(
"pcloudLightAmbience DAP spec for device '"
+ deviceAttachmentSpec->deviceIdentifier
+ "' declares a postrin(...); postrin belongs on a "
"pcloudDarkAmbience line, not pcloudLightAmbience.");
}
if (!deviceAttachmentSpec->negtrin.empty())
{
negtrinInterestConfig = parseAmbienceIntrinConfigFromParams(
"negtrin",
deviceAttachmentSpec->negtrinParams,
nDgramsPerFrame_);
}
}
~PcloudLightAmbienceStimulusBuffer() = default;
PcloudLightAmbienceStimulusBuffer(
const PcloudLightAmbienceStimulusBuffer&) = delete;
PcloudLightAmbienceStimulusBuffer& operator=(
const PcloudLightAmbienceStimulusBuffer&) = delete;
PcloudLightAmbienceStimulusBuffer(
PcloudLightAmbienceStimulusBuffer&&) = delete;
PcloudLightAmbienceStimulusBuffer& operator=(
PcloudLightAmbienceStimulusBuffer&&) = delete;
bool shouldTriggerNegtrinEvent(uint32_t ambiencePassbandCount) const
{
if (!negtrinInterestConfig.has_value()) { return false; }
return ambiencePassbandCount >= negtrinInterestConfig->threshold;
}
public:
size_t nDgramsPerFrame;
ParamComparator passbandCountGtComparator;
std::optional<intrin::IntrinConfig> negtrinInterestConfig;
};
} // namespace stim_buff
} // namespace smo
#endif // _LIVOX_GEN1_PCLOUD_LIGHT_AMBIENCE_STIMULUS_BUFFER_H
+115 -39
View File
@@ -54,20 +54,29 @@ static StagingBuffer::IOEngineConstraints openClIntensityInputConstraints(
// framePadToNBytes (pointer size)
static_cast<size_t>(sysconf(_SC_PAGE_SIZE)));
/* IOEngineConstraints for PcloudAmbienceStimulusBuffer's StagingBuffer, which
* backs SpMcRingBuffer (one StimulusFrame per ring slot). Not the OpenCL
* collating engine's assembly/collation buffers those use assemblyBuffer /
* collationBuffer above. slotPadToNBytes here is the byte size of each ringbuff
* slot: nDgramsPerStagingBufferFrame floats (set in ctor).
/* IOEngineConstraints for Pcloud[Light|Dark]AmbienceStimulusBuffer's
* StagingBuffer, which backs SpMcRingBuffer (one StimulusFrame per ring
* slot a single uint32 passband count). slotPadToNBytes is sized in
* ctor to sizeof(uint32_t).
*/
static StagingBuffer::IOEngineConstraints openClAmbienceInputConstraints(
sizeof(float),
sizeof(float),
sizeof(uint32_t),
sizeof(uint32_t),
// frameStartAlignmentByteVal (page alignment)
static_cast<size_t>(sysconf(_SC_PAGE_SIZE)),
// framePadToNBytes (page alignment)
static_cast<size_t>(sysconf(_SC_PAGE_SIZE)));
/* IOEngineConstraints for OClCollMeshEngn's per-slot averageIntensityBuffer
* staging area. Holds nDgramsPerStagingBufferFrame floats; attached ambience
* stimbuffs read from it to compute passband counts after collate.
*/
static StagingBuffer::IOEngineConstraints openClAverageIntensityConstraints(
sizeof(float),
sizeof(float),
static_cast<size_t>(sysconf(_SC_PAGE_SIZE)),
static_cast<size_t>(sysconf(_SC_PAGE_SIZE)));
PcloudStimulusProducer::PcloudStimulusProducer(
const std::shared_ptr<device::DeviceAttachmentSpec> &deviceAttachmentSpec,
std::shared_ptr<livoxProto1::Device> &device,
@@ -90,6 +99,11 @@ collationBuffer(
StagingBuffer::IOEngineConstraints::openClInputConstraints,
nDgramsPerStagingBufferFrame),
collationBufferMlockPinner(collationBuffer.makeMlockPinner()),
averageIntensityBuffer(
openClAverageIntensityConstraints,
openClAverageIntensityConstraints,
nDgramsPerStagingBufferFrame),
averageIntensityBufferMlockPinner(averageIntensityBuffer.makeMlockPinner()),
pcloudFrameDumper(deviceAttachmentSpec),
tempStimulusFrameMem(0),
tempStimulusFrame(
@@ -99,10 +113,6 @@ tempStimulusFrame(
sizeof(tempStimulusFrameMem)},
*smoHooksPtr, 0, SIZE_MAX)
{
// See comment in openClAmbienceInputConstraints above.
openClAmbienceInputConstraints.slotPadToNBytes =
nDgramsPerStagingBufferFrame * sizeof(float);
if (smoHooksPtr->OptionParser_getOptions().verbose)
{
std::cout << __func__ << ": assembly buffer : "
@@ -133,7 +143,8 @@ bool PcloudStimulusProducer::supportsQualeIfaceApi(
const std::string& qualeIfaceApi)
{
return qualeIfaceApi == "mesh" || qualeIfaceApi == "pcloudIntensity" ||
qualeIfaceApi == "pcloudAmbience";
qualeIfaceApi == "pcloudLightAmbience" ||
qualeIfaceApi == "pcloudDarkAmbience";
}
bool PcloudStimulusProducer::exportsQualeIfaceApi(
@@ -224,9 +235,14 @@ PcloudStimulusProducer::getAttachedStimulusBuffer(
if (std::dynamic_pointer_cast<PcloudIntensityStimulusBuffer>(buffer))
{ return buffer; }
}
else if (qualeIfaceApi == "pcloudAmbience")
else if (qualeIfaceApi == "pcloudLightAmbience")
{
if (std::dynamic_pointer_cast<PcloudAmbienceStimulusBuffer>(buffer))
if (std::dynamic_pointer_cast<PcloudLightAmbienceStimulusBuffer>(buffer))
{ return buffer; }
}
else if (qualeIfaceApi == "pcloudDarkAmbience")
{
if (std::dynamic_pointer_cast<PcloudDarkAmbienceStimulusBuffer>(buffer))
{ return buffer; }
}
@@ -254,11 +270,19 @@ void PcloudStimulusProducer::destroyAttachedStimulusBuffer(
intensityBuff.reset();
intensityStimulusBuffer.store(nullptr, std::memory_order_release);
}
auto ambienceBuff = ambienceStimulusBuffer.load(std::memory_order_acquire);
if (ambienceBuff == buffer)
auto lightAmbienceBuff = lightAmbienceStimulusBuffer.load(
std::memory_order_acquire);
if (lightAmbienceBuff == buffer)
{
ambienceBuff.reset();
ambienceStimulusBuffer.store(nullptr, std::memory_order_release);
lightAmbienceBuff.reset();
lightAmbienceStimulusBuffer.store(nullptr, std::memory_order_release);
}
auto darkAmbienceBuff = darkAmbienceStimulusBuffer.load(
std::memory_order_acquire);
if (darkAmbienceBuff == buffer)
{
darkAmbienceBuff.reset();
darkAmbienceStimulusBuffer.store(nullptr, std::memory_order_release);
}
// Call base class implementation to remove from attachedStimulusBuffers
@@ -330,26 +354,45 @@ PcloudStimulusProducer::getOrCreateAttachedStimulusBuffer(
this->start();
return intensityBuffer;
}
else if (qualeIfaceApi == "pcloudAmbience")
else if (qualeIfaceApi == "pcloudLightAmbience")
{
auto ambienceStimBuff = std::make_shared<PcloudAmbienceStimulusBuffer>(
auto lightAmbienceStimBuff =
std::make_shared<PcloudLightAmbienceStimulusBuffer>(
*this, deviceAttachmentSpec, histbuffMs,
openClAmbienceInputConstraints, openClAmbienceInputConstraints,
*smoHooksPtr, CL_MEM_READ_WRITE,
this->nDgramsPerStagingBufferFrame);
this->stop();
addAttachedStimulusBufferIfNotExists(ambienceStimBuff);
ambienceStimulusBuffer.store(ambienceStimBuff, std::memory_order_release);
addAttachedStimulusBufferIfNotExists(lightAmbienceStimBuff);
lightAmbienceStimulusBuffer.store(
lightAmbienceStimBuff, std::memory_order_release);
this->start();
return ambienceStimBuff;
return lightAmbienceStimBuff;
}
else if (qualeIfaceApi == "pcloudDarkAmbience")
{
auto darkAmbienceStimBuff =
std::make_shared<PcloudDarkAmbienceStimulusBuffer>(
*this, deviceAttachmentSpec, histbuffMs,
openClAmbienceInputConstraints, openClAmbienceInputConstraints,
*smoHooksPtr, CL_MEM_READ_WRITE,
this->nDgramsPerStagingBufferFrame);
this->stop();
addAttachedStimulusBufferIfNotExists(darkAmbienceStimBuff);
darkAmbienceStimulusBuffer.store(
darkAmbienceStimBuff, std::memory_order_release);
this->start();
return darkAmbienceStimBuff;
}
else
{
throw std::runtime_error(
"Unsupported qualeIfaceApi: '" + qualeIfaceApi + "' for "
"PcloudStimulusProducer. "
"Supported values: mesh, pcloudIntensity, pcloudAmbience");
"Supported values: mesh, pcloudIntensity, "
"pcloudLightAmbience, pcloudDarkAmbience");
}
}
@@ -366,7 +409,8 @@ private:
sscl::AsynchronousLoop frameAssemblyResult;
StimulusFrame& stimulusFrame;
std::optional<std::reference_wrapper<StimulusFrame>> intensityStimFrame;
std::optional<std::reference_wrapper<StimulusFrame>> ambienceStimFrame;
std::optional<std::reference_wrapper<StimulusFrame>> lightAmbienceStimFrame;
std::optional<std::reference_wrapper<StimulusFrame>> darkAmbienceStimFrame;
public:
ProduceFrameReq(
@@ -446,28 +490,57 @@ public:
context->intensityStimFrame = std::nullopt;
}
// Check if ambience buffer is attached and acquire frame if so
if (auto ambienceBuff = pcloudProducer.ambienceStimulusBuffer.load(
// Check if light ambience buffer is attached and acquire frame if so
std::optional<AmbienceProductionDesc> lightAmbienceProductionDescDesc;
if (auto lightAmbienceBuff =
pcloudProducer.lightAmbienceStimulusBuffer.load(
std::memory_order_acquire))
{
size_t ambienceRingbuffIndex = ambienceBuff
size_t lightAmbienceRingbuffIndex = lightAmbienceBuff
->ringBuffer.getIndexToProduceInto();
StimulusFrame& ambienceStimFrame = ambienceBuff
->ringBuffer.getDataAtSlot(
ambienceRingbuffIndex);
StimulusFrame& lightAmbienceStimFrame = lightAmbienceBuff
->ringBuffer.getDataAtSlot(lightAmbienceRingbuffIndex);
ambienceStimFrame.lock.writeAcquire();
context->ambienceStimFrame = std::make_optional(
std::ref(ambienceStimFrame));
lightAmbienceStimFrame.lock.writeAcquire();
context->lightAmbienceStimFrame = std::make_optional(
std::ref(lightAmbienceStimFrame));
lightAmbienceProductionDescDesc = AmbienceProductionDesc{
std::ref(lightAmbienceStimFrame),
lightAmbienceBuff->passbandCountGtComparator};
}
else {
context->ambienceStimFrame = std::nullopt;
context->lightAmbienceStimFrame = std::nullopt;
}
// Check if dark ambience buffer is attached and acquire frame if so
std::optional<AmbienceProductionDesc> darkAmbienceProductionDescDesc;
if (auto darkAmbienceBuff =
pcloudProducer.darkAmbienceStimulusBuffer.load(
std::memory_order_acquire))
{
size_t darkAmbienceRingbuffIndex = darkAmbienceBuff
->ringBuffer.getIndexToProduceInto();
StimulusFrame& darkAmbienceStimFrame = darkAmbienceBuff
->ringBuffer.getDataAtSlot(darkAmbienceRingbuffIndex);
darkAmbienceStimFrame.lock.writeAcquire();
context->darkAmbienceStimFrame = std::make_optional(
std::ref(darkAmbienceStimFrame));
darkAmbienceProductionDescDesc = AmbienceProductionDesc{
std::ref(darkAmbienceStimFrame),
darkAmbienceBuff->passbandCountLtComparator};
}
else {
context->darkAmbienceStimFrame = std::nullopt;
}
pcloudProducer.openClCollatingAndMeshingEngine.compactCollateAndMeshFrameReq(
loop, stimulusFrame,
context->intensityStimFrame, context->ambienceStimFrame,
context->intensityStimFrame,
std::move(lightAmbienceProductionDescDesc),
std::move(darkAmbienceProductionDescDesc),
{context, std::bind(
&ProduceFrameReq::produceFrameReq3_compactCollateDone,
context.get(), context,
@@ -482,9 +555,12 @@ public:
if (context->intensityStimFrame.has_value()) {
context->intensityStimFrame->get().lock.writeRelease();
}
// Release ambience frame if it was used
if (context->ambienceStimFrame.has_value()) {
context->ambienceStimFrame->get().lock.writeRelease();
// Release ambience frames if they were used
if (context->lightAmbienceStimFrame.has_value()) {
context->lightAmbienceStimFrame->get().lock.writeRelease();
}
if (context->darkAmbienceStimFrame.has_value()) {
context->darkAmbienceStimFrame->get().lock.writeRelease();
}
sscl::SpinLock::Guard lock(pcloudProducer.shouldContinueLock);
@@ -14,7 +14,8 @@
#include "openClCollatingAndMeshingEngine.h"
#include "meshStimulusBuffer.h"
#include "pcloudIntensityStimulusBuffer.h"
#include "pcloudAmbienceStimulusBuffer.h"
#include "pcloudLightAmbienceStimulusBuffer.h"
#include "pcloudDarkAmbienceStimulusBuffer.h"
namespace smo {
namespace stim_buff {
@@ -96,14 +97,19 @@ public:
IoUringAssemblyEngine ioUringAssemblyEngine;
StagingBuffer collationBuffer;
std::unique_ptr<StagingBuffer::MlockPinner> collationBufferMlockPinner;
StagingBuffer averageIntensityBuffer;
std::unique_ptr<StagingBuffer::MlockPinner>
averageIntensityBufferMlockPinner;
LivoxPcloudFrameDumper pcloudFrameDumper;
size_t tempStimulusFrameMem;
StimulusFrame tempStimulusFrame;
std::atomic<std::shared_ptr<MeshStimulusBuffer>> meshStimulusBuffer;
std::atomic<std::shared_ptr<PcloudIntensityStimulusBuffer>>
intensityStimulusBuffer;
std::atomic<std::shared_ptr<PcloudAmbienceStimulusBuffer>>
ambienceStimulusBuffer;
std::atomic<std::shared_ptr<PcloudLightAmbienceStimulusBuffer>>
lightAmbienceStimulusBuffer;
std::atomic<std::shared_ptr<PcloudDarkAmbienceStimulusBuffer>>
darkAmbienceStimulusBuffer;
private:
class ProduceFrameReq;