| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | /* | ||
| 3 | * This file is part of the Falcon Player (FPP) and is Copyright (C) | ||
| 4 | * 2022 by the Falcon Player Developers. | ||
| 5 | * | ||
| 6 | * The Falcon Player (FPP) is free software, and is covered under | ||
| 7 | * multiple Open Source licenses. Please see the included 'LICENSES' | ||
| 8 | * file for descriptions of what files are covered by each license. | ||
| 9 | * | ||
| 10 | * This source file is covered under the CC-BY-ND as described in the | ||
| 11 | * included LICENSE.CC-BY-ND file. This file may be modified for | ||
| 12 | * personal use, but modified copies MAY NOT be redistributed in any form. | ||
| 13 | */ | ||
| 14 | |||
| 15 | #include <atomic> | ||
| 16 | #include <queue> | ||
| 17 | #include "fpp-json-fwd.h" | ||
| 18 | #include <string> | ||
| 19 | #include <thread> | ||
| 20 | #include <utility> | ||
| 21 | #include <vector> | ||
| 22 | |||
| 23 | #include "channeloutput/ChannelOutput.h" | ||
| 24 | #include "util/BBBPruUtils.h" | ||
| 25 | |||
| 26 | #include "../../channeloutput/Matrix.h" | ||
| 27 | #include "../../channeloutput/PanelMatrix.h" | ||
| 28 | |||
| 29 | // structure of the data at the start of the PRU ram | ||
| 30 | // that the pru program expects to see | ||
| 31 | typedef struct { | ||
| 32 | // in the DDR shared with the PRU | ||
| 33 | uint32_t address_dma; | ||
| 34 | |||
| 35 | union { | ||
| 36 | struct { | ||
| 37 | volatile uint16_t command; | ||
| 38 | volatile uint16_t result; | ||
| 39 | } __attribute__((__packed__)); | ||
| 40 | struct { | ||
| 41 | // Standard shift register based panels where the data is shifted out row by row and the PWM | ||
| 42 | // is handled by the PRU code via the OE pin | ||
| 43 | // write data length to start, 0xFFFF to abort. will be cleared when started | ||
| 44 | uint16_t pixelsPerStride; | ||
| 45 | uint16_t numStrides; | ||
| 46 | |||
| 47 | // 2 uint32_t for each stride | ||
| 48 | // first uint32_t is the brightness, number of clock ticks for on | ||
| 49 | // second uint32_t - bit 32 is flag to output black after this row, bits 25-31 is the address, lower 24 is extra "off" time | ||
| 50 | // (24 slots x 32 rows: enough split-pulse budget for 12 bit | ||
| 51 | // color on 1/32 scan panels; ends at 0x1808 in the PRU data | ||
| 52 | // RAM, clear of the config areas at 0x1DF8+) | ||
| 53 | uint32_t brightness[24 * 32 * 2]; | ||
| 54 | } __attribute__((__packed__)); | ||
| 55 | struct { | ||
| 56 | // Panels that handle the PWM themselves, all data is shifted out and the panel displays it automatically | ||
| 57 | uint16_t cmd; | ||
| 58 | uint8_t numBlocks; | ||
| 59 | uint8_t numRows; | ||
| 60 | uint16_t buffer[4]; // buffer to get registers aligned on boundary | ||
| 61 | // up to 6 registers (SM16380SH takes a sixth slot; the rest use | ||
| 62 | // five), up to 12 bytes per clock (16 outputs), 16 clocks each. | ||
| 63 | // Ends at 0x490 in the PRU data RAM, clear of the chip config at | ||
| 64 | // PWM_CHIP_CONFIG_OFFSET (0x1DF8). | ||
| 65 | uint8_t registers[6 * 12 * 16]; | ||
| 66 | } __attribute__((__packed__)); | ||
| 67 | } __attribute__((__packed__)); | ||
| 68 | } __attribute__((__packed__)) BBShiftPanelData; | ||
| 69 | |||
| 70 | class BBShiftPanelOutput; | ||
| 71 | |||
| 72 | // One captured register profile for a PWM driver chip: the per-colour word | ||
| 73 | // lists plus the grammar needed to upload them. The built-in tables live in | ||
| 74 | // BBShiftPanel.cpp; an imported profile (see BBShiftPanelManager:: | ||
| 75 | // buildRegisterProfile) fills the same shape so nothing downstream has to | ||
| 76 | // know where the words came from. | ||
| 77 | struct PWMChipSeqVariant { | ||
| 78 | int scan; | ||
| 79 | const uint16_t* r; | ||
| 80 | const uint16_t* g; | ||
| 81 | const uint16_t* b; | ||
| 82 | }; | ||
| 83 | |||
| 84 | struct PWMChipSeq { | ||
| 85 | const uint16_t* r; | ||
| 86 | const uint16_t* g; | ||
| 87 | const uint16_t* b; | ||
| 88 | int len; | ||
| 89 | int slots; // 5, or 6 for a chip with an extra pre-commit word | ||
| 90 | uint16_t extraWord; // that word; only read when slots == 6 | ||
| 91 | bool midLatch; // send the 11 clock LAT burst | ||
| 92 | uint8_t scanReg; // register address holding the scan row count | ||
| 93 | int defaultScan; // scan rate the tables above were captured at | ||
| 94 | const PWMChipSeqVariant* variants; | ||
| 95 | int variantCount; | ||
| 96 | }; | ||
| 97 | |||
| 98 | // The PRUSS drives every panel output on the cape as ONE frame: a single | ||
| 99 | // buffer whose byte lanes are the cape's data pins, one stride schedule, one | ||
| 100 | // shared memory ring, one pair of PRU cores. So several LED Panel Matrix | ||
| 101 | // outputs on the same cape (a two sided display, a column either side of a | ||
| 102 | // door) cannot each own that hardware - they share it here and own only their | ||
| 103 | // own pixel mapping into the shared frame. Since the lane for a given output | ||
| 104 | // is fixed by the cape pinout (see BBShiftPanelOutput::buildScatterMap), | ||
| 105 | // members driving different outputs scatter into disjoint bytes of the shared | ||
| 106 | // buffer and one bit-plane pass converts the combined result. | ||
| 107 | class BBShiftPanelManager { | ||
| 108 | public: | ||
| 109 | static BBShiftPanelManager INSTANCE; | ||
| 110 | |||
| 111 | // Registers a configured output. The first member fixes the frame | ||
| 112 | // geometry; later ones must match it and must not re-use an output that | ||
| 113 | // is already driven. Returns false (having warned) if they do not. | ||
| 114 | bool addMember(BBShiftPanelOutput* m, const Json::Value& config, const Json::Value& capeConfig); | ||
| 115 | void removeMember(BBShiftPanelOutput* m); | ||
| 116 | |||
| 117 | // One frame: every member scatters into the shared intermediate, then the | ||
| 118 | // last one to arrive triggers the single bit-plane pass, and the first | ||
| 119 | // SendData of the frame hands the result to the pump. | ||
| 120 | void prepMember(BBShiftPanelOutput* m, unsigned char* channelData); | ||
| 121 | void publishFrame(); | ||
| 122 | |||
| 123 | void startingOutput(); | ||
| 124 | void stoppingOutput(); | ||
| 125 | void closeMember(); | ||
| 126 | |||
| 127 | void dumpConfig(); | ||
| 128 | |||
| 129 | bool isPWMPanel() const; | ||
| 130 | uint32_t numOutputSlots() const { return m_numOutputSlots; } | ||
| 131 | uint32_t numOutputRows() const { return numRows; } | ||
| 132 | uint32_t outputRowLen() const { return rowLen; } | ||
| 133 | int longestChain() const { return m_longestChain; } | ||
| 134 | ✗ | int numOutputs() const { return m_numOutputs; } | |
| 135 | int colorDepth() const { return m_colorDepth; } | ||
| 136 | int brightness() const { return m_brightness; } | ||
| 137 | uint8_t outputPinFor(int o) const { return outputPin[o]; } | ||
| 138 | uint8_t outputBankFor(int o) const { return outputBank[o]; } | ||
| 139 | uint16_t* intermediate() const { return currentChannelData; } | ||
| 140 | ✗ | uint32_t intermediateSize() const { return rowLen * m_numOutputSlots * 6 * numRows; } | |
| 141 | |||
| 142 | // the shared worker pool; members use it for their scatter pass | ||
| 143 | void processTasks(std::atomic<int>& counter); | ||
| 144 | |||
| 145 | private: | ||
| 146 | // the frame-shape settings every matrix on the cape has to agree on | ||
| 147 | struct PanelParams { | ||
| 148 | int panelWidth = 0; | ||
| 149 | int panelHeight = 0; | ||
| 150 | int panelScan = 0; | ||
| 151 | int colorDepth = 12; | ||
| 152 | int addressingMode = 0; | ||
| 153 | int panelType = 0; | ||
| 154 | int dataLayout = 0; | ||
| 155 | bool pwmDirectRow = false; | ||
| 156 | bool pwmShiftRow = false; | ||
| 157 | bool outputByRow = false; | ||
| 158 | bool outputBlankData = false; | ||
| 159 | bool sharedPRUSS = false; | ||
| 160 | std::string panelInterleave; | ||
| 161 | // An imported PWM register profile, replacing the chip's built-in | ||
| 162 | // table. regProfile[0..2] are the R/G/B word lists (equal length, | ||
| 163 | // empty when nothing was imported) and regProfileName identifies it | ||
| 164 | // in the log. See BBShiftPanel::buildRegisterProfile(). | ||
| 165 | std::vector<uint16_t> regProfile[3]; | ||
| 166 | std::string regProfileName; | ||
| 167 | // FM6373 family OE timing, in DCLK periods (the reference | ||
| 168 | // implementation's units) except the row period, which is in us | ||
| 169 | int oeClkLength = 0; // 0 = keep the historical 600ns pulse | ||
| 170 | int oeFirstClkLength = 0; // 0 = keep the historical 2us opener | ||
| 171 | int oeRowPeriodUs = 20; | ||
| 172 | // FM6373 family register upload grammar, in DCLK clocks | ||
| 173 | int regVsyncClocks = 3; | ||
| 174 | int regMidClocks = 11; // 0 skips the middle burst | ||
| 175 | int regPreClocks = 14; | ||
| 176 | int regSpacerClocks = 8; // 0 is valid: emit no gap at all | ||
| 177 | }; | ||
| 178 | static PanelParams parsePanelParams(const Json::Value& config, const Json::Value& capeConfig); | ||
| 179 | |||
| 180 | bool parseCapeConfig(const Json::Value& capeConfig); | ||
| 181 | bool adoptPanelParams(const PanelParams& p); | ||
| 182 | bool checkCompatible(const PanelParams& p) const; | ||
| 183 | bool rebuild(); | ||
| 184 | void teardownHardware(); | ||
| 185 | void teardown(); | ||
| 186 | void computeGeometry(); | ||
| 187 | |||
| 188 | void PrepDataShift(); | ||
| 189 | bool PrepDataPWM(); | ||
| 190 | bool waitForPWMCommandTaken(); | ||
| 191 | |||
| 192 | void StopPRU(bool wait = true); | ||
| 193 | int StartPRU(); | ||
| 194 | |||
| 195 | void buildStrideSchedule(); | ||
| 196 | void sendPanelInitPackets(); | ||
| 197 | uint32_t computeMaxBrightnessCycles(); | ||
| 198 | void setupBrightnessValues(); | ||
| 199 | bool setupPWMRegisters(); | ||
| 200 | void setupGCLKConfig(); | ||
| 201 | void writeFM6373SeqWord(int idx); | ||
| 202 | void writeDP3364SeqWord(int idx); | ||
| 203 | |||
| 204 | std::vector<BBShiftPanelOutput*> m_members; | ||
| 205 | // members that have arrived in this frame's PrepData; the shared bit-plane | ||
| 206 | // pass runs when the count reaches the member count | ||
| 207 | int m_preppedThisFrame = 0; | ||
| 208 | // set by the bit-plane pass, cleared by the frame publish, so exactly one | ||
| 209 | // member's SendData hands the frame over | ||
| 210 | bool m_framePrepped = false; | ||
| 211 | // balanced Starting/StoppingOutput calls across the members | ||
| 212 | int m_activeMembers = 0; | ||
| 213 | int m_openMembers = 0; | ||
| 214 | |||
| 215 | BBBPru* pru = nullptr; | ||
| 216 | BBBPru* pwmPru = nullptr; | ||
| 217 | BBShiftPanelData* pruData = nullptr; | ||
| 218 | |||
| 219 | int m_panelWidth = 0; | ||
| 220 | int m_panelHeight = 0; | ||
| 221 | int m_panelScan = 0; | ||
| 222 | std::string m_panelInterleave = ""; | ||
| 223 | |||
| 224 | int m_addressingMode = 0; | ||
| 225 | // 0 = standard, 1 = FM6126A, 2 = FM6127 - panels that need their config | ||
| 226 | // registers clocked out before they display (see sendPanelInitPackets) | ||
| 227 | int m_panelType = 0; | ||
| 228 | // How the framebuffer is routed onto the two RGB lanes. 0 is the normal | ||
| 229 | // HUB75 arrangement: the lanes carry the top and bottom halves of the | ||
| 230 | // panel and each addressed row lights two physical rows. A full height | ||
| 231 | // panel (scan == height) addresses every row on its own instead, so its | ||
| 232 | // lanes carry the LEFT and RIGHT halves of one row - 1 and 2 select which | ||
| 233 | // way round. See buildScatterMap. | ||
| 234 | int m_dataLayout = 0; | ||
| 235 | // PWM panels: drive the row lines with a direct binary row number | ||
| 236 | // instead of the DP32020A style row shift register | ||
| 237 | bool m_pwmDirectRow = false; | ||
| 238 | // FM6373 family only: the opposite opt-in. That family shipped with the | ||
| 239 | // direct binary row number as its only transport, so it stays the default | ||
| 240 | // there and the shift register has to be asked for explicitly - see | ||
| 241 | // sendPWMConfig(). | ||
| 242 | bool m_pwmShiftRow = false; | ||
| 243 | // FM6373: next config sequence word for the per-frame rotating refresh | ||
| 244 | int m_pwmSeqIdx = 0; | ||
| 245 | // An imported register profile replacing the chip's built-in table. The | ||
| 246 | // word lists are owned here because m_profSeq points into them, so the | ||
| 247 | // two must not be copied apart. activePWMSeq() hands out &m_profSeq once | ||
| 248 | // m_haveProfile is set, and the built-in table otherwise. | ||
| 249 | std::vector<uint16_t> m_profWords[3]; | ||
| 250 | PWMChipSeq m_profSeq{}; | ||
| 251 | bool m_haveProfile = false; | ||
| 252 | std::string m_profName; | ||
| 253 | int m_oeClkLength = 0; | ||
| 254 | int m_oeFirstClkLength = 0; | ||
| 255 | int m_oeRowPeriodUs = 20; | ||
| 256 | int m_regVsyncClocks = 3; | ||
| 257 | int m_regMidClocks = 11; | ||
| 258 | int m_regPreClocks = 14; | ||
| 259 | int m_regSpacerClocks = 8; | ||
| 260 | void buildRegisterProfile(const PanelParams& p); | ||
| 261 | const PWMChipSeq* activePWMSeq() const; | ||
| 262 | // the pins this cape muxed to the PRU (only these are released on | ||
| 263 | // Close - a combo cape may leave pins for another driver to own) | ||
| 264 | std::vector<std::string> m_configuredPins; | ||
| 265 | std::string m_oePin = "P1-36"; | ||
| 266 | // sharing the PRUSS with a string driver on the other PRU: single PRU, | ||
| 267 | // SPLIT1 ring, and never clear the shared RAM or the other PRU's memory | ||
| 268 | bool m_sharedPRUSS = false; | ||
| 269 | int m_longestChain = 0; | ||
| 270 | // the OE on-time is a property of the cape, not of one matrix, so the | ||
| 271 | // hardware brightness is the highest any member asked for; a member that | ||
| 272 | // wanted less scales its own gamma table down to reach it (see | ||
| 273 | // BBShiftPanelOutput::setupGamma) | ||
| 274 | int m_brightness = 10; | ||
| 275 | int m_colorDepth = 12; | ||
| 276 | |||
| 277 | bool m_outputByRow = false; | ||
| 278 | bool m_outputBlankData = false; | ||
| 279 | |||
| 280 | int m_numOutputs = 8; // number of configured outputs (from cape config) | ||
| 281 | int m_numOutputSlots = 8; // 8 or 16 - determines data width per pixel (6 or 12 bytes) | ||
| 282 | uint8_t outputPin[16] = { 0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7 }; // PRU pin (0-7) per output | ||
| 283 | uint8_t outputBank[16] = { 0 }; // 0=first bank, 1=second bank per output | ||
| 284 | |||
| 285 | // the shared 16 bit intermediate every member scatters into | ||
| 286 | uint16_t* currentChannelData = nullptr; | ||
| 287 | |||
| 288 | // Four output buffers allows for double buffering and extras for the PRU to read from while the next frame is being built | ||
| 289 | // With 4MB of memory reserved for transfer to PRU, 12 P5 panels per output with 12bit color depth (total 3.5MB) | ||
| 290 | constexpr static int NUM_OUTPUT_BUFFERS = 4; | ||
| 291 | std::array<uint8_t*, NUM_OUTPUT_BUFFERS> outputBuffers = { nullptr, nullptr, nullptr, nullptr }; | ||
| 292 | uint8_t currOutputBuffer = 0; | ||
| 293 | // scan rows in the frame: the scan for split panels, the panel height | ||
| 294 | // for full height ones. PrepDataShift's per-row table is this big. | ||
| 295 | static constexpr uint32_t MAX_SCAN_ROWS = 64; | ||
| 296 | uint32_t numRows = 0; | ||
| 297 | uint32_t rowLen = 0; | ||
| 298 | uint32_t maxRowLen = 0; | ||
| 299 | |||
| 300 | // Schedule of strides within one pass of all rows. Bits whose on-time is | ||
| 301 | // a multiple of the stride shift-out time are split into multiple shorter | ||
| 302 | // pulses spread across the frame which multiplies the perceived refresh | ||
| 303 | // rate without changing the total light output (same total on-time). | ||
| 304 | struct StrideSlot { | ||
| 305 | uint8_t bit; // color depth bit this slot displays | ||
| 306 | bool primary; // first slot for this bit; the pixel mapping writes here | ||
| 307 | uint32_t onTime; // PRU cycles the display is on for this slot | ||
| 308 | }; | ||
| 309 | std::vector<StrideSlot> m_strideSchedule; | ||
| 310 | // per row: (dstOffset, srcOffset) frame buffer copies for the duplicated | ||
| 311 | // strides of split bits | ||
| 312 | std::vector<std::vector<std::pair<uint32_t, uint32_t>>> m_dupCopies; | ||
| 313 | |||
| 314 | // Real-time thread that streams the current frame buffer into a ring in | ||
| 315 | // the 32KB PRU shared memory. The PRU cannot read DDR fast enough to | ||
| 316 | // keep 16 outputs fed (~1.1-1.8us per 64 byte read) but reads shared | ||
| 317 | // memory in 18 predictable cycles. See runPumpThread() and | ||
| 318 | // pru/SMEMRing.hp for the layout. | ||
| 319 | std::thread m_pumpThread; | ||
| 320 | std::atomic<bool> m_pumpRunning{ false }; | ||
| 321 | // Set while the channel output thread is idle. Shift panels only hold an | ||
| 322 | // image for as long as the pump keeps re-shifting it, so a panel that is | ||
| 323 | // not being output to is simply dark - there is nothing to refresh and no | ||
| 324 | // reason to spend a core streaming a blank frame. The pump acts on this | ||
| 325 | // only at a frame boundary; see runPumpThread. | ||
| 326 | std::atomic<bool> m_pumpPaused{ false }; | ||
| 327 | // owned by the pump thread, read by StoppingOutput to wait for the park | ||
| 328 | std::atomic<bool> m_pumpParked{ false }; | ||
| 329 | // set once both cores have been halted, so the park is not attempted or | ||
| 330 | // undone twice | ||
| 331 | bool m_prusPaused = false; | ||
| 332 | void ParkPRUs(); | ||
| 333 | void UnparkPRUs(); | ||
| 334 | std::atomic<uint8_t*> m_frontBuffer{ nullptr }; | ||
| 335 | // PWM panels send one frame per DATA command; the pump streams once per | ||
| 336 | // sequence bump so the byte flow stays in step with the commands | ||
| 337 | std::atomic<uint32_t> m_pumpSeq{ 0 }; | ||
| 338 | uint32_t m_pumpedSeq = 0; | ||
| 339 | // A self-PWM panel refreshes from its own engine while its row scan free | ||
| 340 | // runs, so a gap in the shift out shows up as the image drifting | ||
| 341 | // vertically rather than as a glitch. The frame is therefore not handed | ||
| 342 | // to the PRU until the pump has the ring as full as it will go, and the | ||
| 343 | // pump is woken directly rather than found by polling. | ||
| 344 | std::mutex m_pumpMutex; | ||
| 345 | std::condition_variable m_pumpCV; | ||
| 346 | std::atomic<uint32_t> m_pumpPrimed{ 0 }; | ||
| 347 | uint32_t m_frameBytes = 0; | ||
| 348 | BBBPruSMEMRing m_ring; | ||
| 349 | bool m_heapBuffers = false; | ||
| 350 | void runPumpThread(); | ||
| 351 | |||
| 352 | bool singlePRU = false; | ||
| 353 | std::string m_refreshWarning; | ||
| 354 | // set while a PWM PRU has stopped taking commands (see | ||
| 355 | // waitForPWMCommandTaken); the warning comes down when it answers again | ||
| 356 | // or the hardware is torn down | ||
| 357 | bool m_pwmPruStalled = false; | ||
| 358 | std::string m_pwmStallWarning; | ||
| 359 | |||
| 360 | std::queue<std::function<void()>> bgTasks; | ||
| 361 | volatile bool bgThreadsRunning = false; | ||
| 362 | std::mutex bgTaskMutex; | ||
| 363 | |||
| 364 | // Serializes the output thread's per-frame calls (prepMember, | ||
| 365 | // publishFrame, starting/stoppingOutput) against membership changes. A | ||
| 366 | // config reload Init()s each replacement matrix outside outputThreadLock, | ||
| 367 | // after the earlier ones are already live, and adding a member rebuilds | ||
| 368 | // the shared PRU, frame buffers and every member's scatter map. | ||
| 369 | std::mutex m_memberLock; | ||
| 370 | std::condition_variable bgTaskCondVar; | ||
| 371 | std::atomic<int> bgThreadCount; | ||
| 372 | void runBackgroundTasks(); | ||
| 373 | void startBackgroundThreads(); | ||
| 374 | void stopBackgroundThreads(); | ||
| 375 | |||
| 376 | friend class BBShiftPanelOutput; | ||
| 377 | }; | ||
| 378 | |||
| 379 | class BBShiftPanelOutput : public ChannelOutput { | ||
| 380 | public: | ||
| 381 | BBShiftPanelOutput(unsigned int startChannel, unsigned int channelCount); | ||
| 382 | virtual ~BBShiftPanelOutput(); | ||
| 383 | |||
| 384 | ✗ | virtual std::string GetOutputType() const { | |
| 385 | ✗ | return "BB64 Panels"; | |
| 386 | } | ||
| 387 | |||
| 388 | virtual int Init(Json::Value config) override; | ||
| 389 | virtual int Close(void) override; | ||
| 390 | |||
| 391 | virtual int SendData(unsigned char* channelData) override; | ||
| 392 | virtual void PrepData(unsigned char* channelData) override; | ||
| 393 | virtual void DumpConfig(void) override; | ||
| 394 | |||
| 395 | virtual void GetRequiredChannelRanges(const std::function<void(int, int)>& addRange) override; | ||
| 396 | |||
| 397 | virtual void OverlayTestData(unsigned char* channelData, int cycleNum, float percentOfCycle, int testType, const Json::Value& config) override; | ||
| 398 | ✗ | virtual bool SupportsTesting() const { return true; } | |
| 399 | |||
| 400 | virtual void StartingOutput() override; | ||
| 401 | virtual void StoppingOutput() override; | ||
| 402 | |||
| 403 | // called by the manager | ||
| 404 | ✗ | int longestChain() const { return m_longestChain; } | |
| 405 | const PanelMatrix* panelMatrix() const { return m_panelMatrix; } | ||
| 406 | bool usesOutput(int o) const; | ||
| 407 | ✗ | int configuredBrightness() const { return m_brightness; } | |
| 408 | // (re)builds this member's map into the shared frame; the manager calls | ||
| 409 | // this whenever the shared geometry changes | ||
| 410 | bool buildScatterMap(); | ||
| 411 | void setupGamma(); | ||
| 412 | void scatterFrame(unsigned char* channelData); | ||
| 413 | |||
| 414 | private: | ||
| 415 | void releaseToManager(); | ||
| 416 | |||
| 417 | Matrix* m_matrix = nullptr; | ||
| 418 | PanelMatrix* m_panelMatrix = nullptr; | ||
| 419 | |||
| 420 | int m_longestChain = 0; | ||
| 421 | int m_invertedData = 0; | ||
| 422 | int m_brightness = 10; | ||
| 423 | float m_gamma = 2.2; | ||
| 424 | |||
| 425 | FPPColorOrder m_colorOrder; | ||
| 426 | |||
| 427 | int m_panels = 0; | ||
| 428 | int m_width = 0; | ||
| 429 | int m_height = 0; | ||
| 430 | |||
| 431 | uint16_t gammaCurve[256]; | ||
| 432 | |||
| 433 | // The gamma scatter map sorted by destination offset so the prep loop | ||
| 434 | // writes sequentially (streaming writes with random byte reads are much | ||
| 435 | // faster than random writes at panel counts where the data far exceeds | ||
| 436 | // the cache). Unmapped channels are excluded entirely. | ||
| 437 | std::vector<uint32_t> m_scatterOffsets; | ||
| 438 | std::vector<uint32_t> m_scatterSrc; | ||
| 439 | |||
| 440 | std::string m_autoCreatedModelName; | ||
| 441 | |||
| 442 | // set once the manager has accepted this matrix; an output whose Init | ||
| 443 | // failed must never reach the manager's per-frame accounting | ||
| 444 | bool m_registered = false; | ||
| 445 | // the manager releases the cape's pins when its last open member closes, | ||
| 446 | // so each member has to hand its share back exactly once - Close() may be | ||
| 447 | // called without a destructor following, or the object may be destroyed | ||
| 448 | // without Close() ever having run | ||
| 449 | bool m_closed = false; | ||
| 450 | |||
| 451 | // guards against the output being torn down (config reload) while the | ||
| 452 | // channel output thread is still inside PrepData/SendData | ||
| 453 | std::atomic<bool> m_stopping{ false }; | ||
| 454 | std::atomic<int> m_inFlight{ 0 }; | ||
| 455 | |||
| 456 | friend class BBShiftPanelManager; | ||
| 457 | }; | ||
| 458 |