From 27a5d484513305791975f9d7e19ba6936022811a Mon Sep 17 00:00:00 2001 From: Hayodea Hekol Date: Sat, 18 Apr 2026 14:54:14 -0400 Subject: [PATCH] Lg1: Implement both light|darkAmbience stimBuffs & their production We now produce both light and dark ambience stimframes into stimbuffs for the LivoxGen1 lidar devices. --- devices/avia0.dapss | 29 ++- docs/design/intrin-thresholds.md | 9 +- docs/design/stencils.md | 15 +- docs/livox-gen1-lidar-dap-spec.md | 45 +++- include/user/deviceAttachmentSpec.h | 2 +- stimBuffApis/livoxGen1/livoxGen1.cpp | 6 +- stimBuffApis/livoxGen1/meshStimulusBuffer.h | 4 +- .../openClCollatingAndMeshingEngine.cpp | 219 ++++++++++++------ .../openClCollatingAndMeshingEngine.h | 37 ++- .../livoxGen1/pcloudAmbienceQualeIfaceApi.h | 118 ++++++++-- .../livoxGen1/pcloudAmbienceStimulusBuffer.h | 150 ------------ .../pcloudDarkAmbienceStimulusBuffer.h | 97 ++++++++ .../pcloudLightAmbienceStimulusBuffer.h | 97 ++++++++ .../livoxGen1/pcloudStimulusProducer.cpp | 164 +++++++++---- .../livoxGen1/pcloudStimulusProducer.h | 12 +- 15 files changed, 668 insertions(+), 336 deletions(-) delete mode 100644 stimBuffApis/livoxGen1/pcloudAmbienceStimulusBuffer.h create mode 100644 stimBuffApis/livoxGen1/pcloudDarkAmbienceStimulusBuffer.h create mode 100644 stimBuffApis/livoxGen1/pcloudLightAmbienceStimulusBuffer.h diff --git a/devices/avia0.dapss b/devices/avia0.dapss index c17a939..6e746e5 100644 --- a/devices/avia0.dapss +++ b/devices/avia0.dapss @@ -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|| diff --git a/docs/design/intrin-thresholds.md b/docs/design/intrin-thresholds.md index fb66dda..5f4c6f5 100644 --- a/docs/design/intrin-thresholds.md +++ b/docs/design/intrin-thresholds.md @@ -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 diff --git a/docs/design/stencils.md b/docs/design/stencils.md index 5afe425..002398e 100644 --- a/docs/design/stencils.md +++ b/docs/design/stencils.md @@ -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 diff --git a/docs/livox-gen1-lidar-dap-spec.md b/docs/livox-gen1-lidar-dap-spec.md index 83a71f3..652bd60 100644 --- a/docs/livox-gen1-lidar-dap-spec.md +++ b/docs/livox-gen1-lidar-dap-spec.md @@ -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 | diff --git a/include/user/deviceAttachmentSpec.h b/include/user/deviceAttachmentSpec.h index 049fd05..f1d9081 100644 --- a/include/user/deviceAttachmentSpec.h +++ b/include/user/deviceAttachmentSpec.h @@ -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. diff --git a/stimBuffApis/livoxGen1/livoxGen1.cpp b/stimBuffApis/livoxGen1/livoxGen1.cpp index f5784c3..b50471c 100644 --- a/stimBuffApis/livoxGen1/livoxGen1.cpp +++ b/stimBuffApis/livoxGen1/livoxGen1.cpp @@ -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; diff --git a/stimBuffApis/livoxGen1/meshStimulusBuffer.h b/stimBuffApis/livoxGen1/meshStimulusBuffer.h index 4a53492..1aad4cb 100644 --- a/stimBuffApis/livoxGen1/meshStimulusBuffer.h +++ b/stimBuffApis/livoxGen1/meshStimulusBuffer.h @@ -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 diff --git a/stimBuffApis/livoxGen1/openClCollatingAndMeshingEngine.cpp b/stimBuffApis/livoxGen1/openClCollatingAndMeshingEngine.cpp index c4225a6..5508964 100644 --- a/stimBuffApis/livoxGen1/openClCollatingAndMeshingEngine.cpp +++ b/stimBuffApis/livoxGen1/openClCollatingAndMeshingEngine.cpp @@ -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>( @@ -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> intensityStimFrame, - std::optional> 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::endl; - return false; - } - - unmapBuffer(ambienceClBuffer, mappedAmbienceBuffer); + std::cerr << __func__ << ": failed to map average intensity buffer" + << std::endl; + return false; } + unmapAverageIntensityBuffer(); } // Calculate global work size (just num slots in the frame) @@ -630,7 +664,7 @@ bool OpenClCollatingAndMeshingEngine::setupSlotCompactorsArgs( bool OpenClCollatingAndMeshingEngine::setupCollateDgramsArgs( std::optional> intensityStimFrame, - std::optional> ambienceStimFrame) + bool anyAmbienceAttached) { // Extract parameters for collateDgrams kernel uint32_t slotStride = static_cast( @@ -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()) + // 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(nDgramsPerFrame) + * sizeof(float); + if (anyAmbienceAttached && averageIntensityBufferSize < needBytes) { - StimulusFrame& ambienceFrame = ambienceStimFrame->get(); - const size_t needBytes = static_cast(nDgramsPerFrame) - * sizeof(float); - - if (ambienceFrame.slotDesc.nBytes < needBytes) - { - std::cerr << __func__ << ": ambience stim frame slot too small: " - << ambienceFrame.slotDesc.nBytes << " < " << needBytes - << std::endl; - return false; - } - ambienceClBufferArg = ambienceFrame.clBuffer - ->getAssociatedBufferHandleForDevice(computeDevice); + std::cerr << __func__ << ": average intensity buffer too small: " + << averageIntensityBufferSize << " < " << needBytes << std::endl; + return false; } 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> intensityStimFrame, - std::optional> 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(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(ambienceFrame.slotDesc.vaddr); + passbandCountOut = passbandCount; +} + class OpenClCollatingAndMeshingEngine::CompactCollateAndMeshFrameReq : public sscl::PostedAsynchronousContinuation { @@ -964,7 +1015,8 @@ private: sscl::AsynchronousLoop frameAssemblyResult; StimulusFrame& stimulusFrame; std::optional> intensityStimFrame; - std::optional> ambienceStimFrame; + std::optional lightAmbienceProductionDesc; + std::optional darkAmbienceProductionDesc; public: CompactCollateAndMeshFrameReq( @@ -972,7 +1024,8 @@ public: sscl::AsynchronousLoop& asyncLoop, StimulusFrame& stimulusFrame_, std::optional> intensityStimFrame_, - std::optional> ambienceStimFrame_, + std::optional lightAmbienceProductionDesc_, + std::optional darkAmbienceProductionDesc_, const std::shared_ptr& caller, sscl::Callback cb) : sscl::PostedAsynchronousContinuation( @@ -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> intensityStimFrame, - std::optional> ambienceStimFrame, + std::optional lightAmbienceProductionDesc, + std::optional darkAmbienceProductionDesc, sscl::Callback callback) { { @@ -1188,7 +1266,8 @@ void OpenClCollatingAndMeshingEngine::compactCollateAndMeshFrameReq( auto caller = smoHooksPtr->ComponentThread_getSelf(); auto request = std::make_shared( - *this, asyncLoop, stimulusFrame, intensityStimFrame, ambienceStimFrame, + *this, asyncLoop, stimulusFrame, intensityStimFrame, + std::move(lightAmbienceProductionDesc), std::move(darkAmbienceProductionDesc), caller, std::move(callback)); diff --git a/stimBuffApis/livoxGen1/openClCollatingAndMeshingEngine.h b/stimBuffApis/livoxGen1/openClCollatingAndMeshingEngine.h index 8440166..563307c 100644 --- a/stimBuffApis/livoxGen1/openClCollatingAndMeshingEngine.h +++ b/stimBuffApis/livoxGen1/openClCollatingAndMeshingEngine.h @@ -21,12 +21,24 @@ #include #include #include +#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 frame; + ParamComparator comparator; +}; + // Custom deleters for OpenCL handles struct ClProgramDeleter { @@ -80,7 +92,8 @@ public: void compactCollateAndMeshFrameReq( sscl::AsynchronousLoop& asyncLoop, StimulusFrame& stimulusFrame, std::optional> intensityStimFrame, - std::optional> ambienceStimFrame, + std::optional lightAmbienceProductionDesc, + std::optional darkAmbienceProductionDesc, sscl::Callback callback); private: @@ -93,16 +106,25 @@ private: compactKernelCbFn callback); bool startCollateKernel( std::optional> intensityStimFrame, - std::optional> ambienceStimFrame, + bool anyAmbienceAttached, collateKernelCbFn callback); void compactKernelComplete(bool isFinalizing=false); void collateKernelComplete( std::optional> intensityStimFrame, - std::optional> 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 clAssemblyBufferClBuffer; std::shared_ptr clCollationBufferClBuffer; + std::shared_ptr 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; @@ -174,7 +201,7 @@ private: StagingBuffer& assemblyBuff, uint32_t nSucceeded); bool setupCollateDgramsArgs( std::optional> intensityStimFrame, - std::optional> 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; diff --git a/stimBuffApis/livoxGen1/pcloudAmbienceQualeIfaceApi.h b/stimBuffApis/livoxGen1/pcloudAmbienceQualeIfaceApi.h index e9c59a6..be8bce9 100644 --- a/stimBuffApis/livoxGen1/pcloudAmbienceQualeIfaceApi.h +++ b/stimBuffApis/livoxGen1/pcloudAmbienceQualeIfaceApi.h @@ -1,12 +1,15 @@ #ifndef _LIVOX_GEN1_PCLOUD_AMBIENCE_QUALE_IFACE_API_H #define _LIVOX_GEN1_PCLOUD_AMBIENCE_QUALE_IFACE_API_H +#include #include #include -#include #include #include +#include #include +#include +#include namespace smo { namespace stim_buff { @@ -36,45 +39,110 @@ struct ParamComparator } }; -struct PcloudAmbiencePassbandComparators +inline bool paramsContain( + const std::vector>& params, + const std::string& name) { - std::optional lt; - std::optional 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& 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); + + if (gtVal == kParamNotSpecified) + { + throw std::runtime_error( + "pcloudLightAmbience qualeIfaceApi requires " + "'passband-count-gt-val' on its params."); + } + + return ParamComparator{ + .op = OP_CMP_GT, + .value = static_cast(gtVal), + }; +} + +/* 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& deviceAttachmentSpec) +{ + 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); - PcloudAmbiencePassbandComparators out; - if (gtVal != kParamNotSpecified) + if (ltVal == kParamNotSpecified) { - out.gt = ParamComparator{ - .op = OP_CMP_GT, - .value = static_cast(gtVal), - }; - } - if (ltVal != kParamNotSpecified) - { - out.lt = ParamComparator{ - .op = OP_CMP_LT, - .value = static_cast(ltVal), - }; + throw std::runtime_error( + "pcloudDarkAmbience qualeIfaceApi requires " + "'passband-count-lt-val' on its params."); } - return out; + return ParamComparator{ + .op = OP_CMP_LT, + .value = static_cast(ltVal), + }; +} + +/* 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>& 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(threshold.value) + : 0U, + .threshold = intrin::resolveThresholdValue( + threshold, nDgramsPerFrame), + }; } } // namespace stim_buff diff --git a/stimBuffApis/livoxGen1/pcloudAmbienceStimulusBuffer.h b/stimBuffApis/livoxGen1/pcloudAmbienceStimulusBuffer.h deleted file mode 100644 index e75dfaf..0000000 --- a/stimBuffApis/livoxGen1/pcloudAmbienceStimulusBuffer.h +++ /dev/null @@ -1,150 +0,0 @@ -#ifndef _LIVOX_GEN1_PCLOUD_AMBIENCE_STIMULUS_BUFFER_H -#define _LIVOX_GEN1_PCLOUD_AMBIENCE_STIMULUS_BUFFER_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "pcloudAmbienceQualeIfaceApi.h" - -namespace smo { -namespace stim_buff { - -class StimulusProducer; - -inline intrin::IntrinConfig parseAmbienceIntrinConfigFromParams( - std::string_view intrinKind, - const std::vector>& 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(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& 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 postrinInterestConfig; - std::optional negtrinInterestConfig; - std::optional passbandCountLtComparator; - std::optional passbandCountGtComparator; -}; - -} // namespace stim_buff -} // namespace smo - -#endif // _LIVOX_GEN1_PCLOUD_AMBIENCE_STIMULUS_BUFFER_H diff --git a/stimBuffApis/livoxGen1/pcloudDarkAmbienceStimulusBuffer.h b/stimBuffApis/livoxGen1/pcloudDarkAmbienceStimulusBuffer.h new file mode 100644 index 0000000..22b90f7 --- /dev/null +++ b/stimBuffApis/livoxGen1/pcloudDarkAmbienceStimulusBuffer.h @@ -0,0 +1,97 @@ +#ifndef _LIVOX_GEN1_PCLOUD_DARK_AMBIENCE_STIMULUS_BUFFER_H +#define _LIVOX_GEN1_PCLOUD_DARK_AMBIENCE_STIMULUS_BUFFER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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& 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 postrinInterestConfig; +}; + +} // namespace stim_buff +} // namespace smo + +#endif // _LIVOX_GEN1_PCLOUD_DARK_AMBIENCE_STIMULUS_BUFFER_H diff --git a/stimBuffApis/livoxGen1/pcloudLightAmbienceStimulusBuffer.h b/stimBuffApis/livoxGen1/pcloudLightAmbienceStimulusBuffer.h new file mode 100644 index 0000000..ff141b7 --- /dev/null +++ b/stimBuffApis/livoxGen1/pcloudLightAmbienceStimulusBuffer.h @@ -0,0 +1,97 @@ +#ifndef _LIVOX_GEN1_PCLOUD_LIGHT_AMBIENCE_STIMULUS_BUFFER_H +#define _LIVOX_GEN1_PCLOUD_LIGHT_AMBIENCE_STIMULUS_BUFFER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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& 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 negtrinInterestConfig; +}; + +} // namespace stim_buff +} // namespace smo + +#endif // _LIVOX_GEN1_PCLOUD_LIGHT_AMBIENCE_STIMULUS_BUFFER_H diff --git a/stimBuffApis/livoxGen1/pcloudStimulusProducer.cpp b/stimBuffApis/livoxGen1/pcloudStimulusProducer.cpp index c6b7d9a..734f524 100644 --- a/stimBuffApis/livoxGen1/pcloudStimulusProducer.cpp +++ b/stimBuffApis/livoxGen1/pcloudStimulusProducer.cpp @@ -54,20 +54,29 @@ static StagingBuffer::IOEngineConstraints openClIntensityInputConstraints( // framePadToNBytes (pointer size) static_cast(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(sysconf(_SC_PAGE_SIZE)), // framePadToNBytes (page alignment) static_cast(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(sysconf(_SC_PAGE_SIZE)), + static_cast(sysconf(_SC_PAGE_SIZE))); + PcloudStimulusProducer::PcloudStimulusProducer( const std::shared_ptr &deviceAttachmentSpec, std::shared_ptr &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(buffer)) { return buffer; } } - else if (qualeIfaceApi == "pcloudAmbience") + else if (qualeIfaceApi == "pcloudLightAmbience") { - if (std::dynamic_pointer_cast(buffer)) + if (std::dynamic_pointer_cast(buffer)) + { return buffer; } + } + else if (qualeIfaceApi == "pcloudDarkAmbience") + { + if (std::dynamic_pointer_cast(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( - *this, deviceAttachmentSpec, histbuffMs, - openClAmbienceInputConstraints, openClAmbienceInputConstraints, - *smoHooksPtr, CL_MEM_READ_WRITE, - this->nDgramsPerStagingBufferFrame); + auto lightAmbienceStimBuff = + std::make_shared( + *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( + *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> intensityStimFrame; - std::optional> ambienceStimFrame; + std::optional> lightAmbienceStimFrame; + std::optional> 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( - std::memory_order_acquire)) + // Check if light ambience buffer is attached and acquire frame if so + std::optional 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 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); diff --git a/stimBuffApis/livoxGen1/pcloudStimulusProducer.h b/stimBuffApis/livoxGen1/pcloudStimulusProducer.h index fc55faf..04c3f57 100644 --- a/stimBuffApis/livoxGen1/pcloudStimulusProducer.h +++ b/stimBuffApis/livoxGen1/pcloudStimulusProducer.h @@ -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 collationBufferMlockPinner; + StagingBuffer averageIntensityBuffer; + std::unique_ptr + averageIntensityBufferMlockPinner; LivoxPcloudFrameDumper pcloudFrameDumper; size_t tempStimulusFrameMem; StimulusFrame tempStimulusFrame; std::atomic> meshStimulusBuffer; std::atomic> intensityStimulusBuffer; - std::atomic> - ambienceStimulusBuffer; + std::atomic> + lightAmbienceStimulusBuffer; + std::atomic> + darkAmbienceStimulusBuffer; private: class ProduceFrameReq;