GCC Code Coverage Report


Directory: ./
File: channeloutput/RGBMatrix.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 0 255 0.0%
Functions: 0 13 0.0%
Branches: 0 495 0.0%

Line Branch Exec Source
1 /*
2 * This file is part of the Falcon Player (FPP) and is Copyright (C)
3 * 2013-2022 by the Falcon Player Developers.
4 *
5 * The Falcon Player (FPP) is free software, and is covered under
6 * multiple Open Source licenses. Please see the included 'LICENSES'
7 * file for descriptions of what files are covered by each license.
8 *
9 * This source file is covered under the GPL v2 as described in the
10 * included LICENSE.GPL file.
11 */
12
13 #include "fpp-pch.h"
14
15 #include "fpp-json.h"
16 #include "common.h"
17 #include "Sequence.h"
18
19 #include <cmath>
20 #include <unistd.h>
21
22 #include "../Warnings.h"
23 #include "ChannelOutputSetup.h"
24 #include "../log.h"
25
26 #include "GammaLUT.h"
27 #include "RGBMatrix.h"
28 #include "overlays/PixelOverlay.h"
29
30 #include "Plugin.h"
31 class RGBMatrixPlugin : public FPPPlugins::Plugin, public FPPPlugins::ChannelOutputPlugin {
32 public:
33 ✗ RGBMatrixPlugin() :
34 ✗ FPPPlugins::Plugin("RGBMatrix") {
35 ✗ }
36 ✗ virtual ChannelOutput* createChannelOutput(unsigned int startChannel, unsigned int channelCount) override {
37 ✗ return new RGBMatrixOutput(startChannel, channelCount);
38 }
39 };
40
41 extern "C" {
42 ✗ FPPPlugins::Plugin* createPlugin() {
43 ✗ return new RGBMatrixPlugin();
44 }
45 }
46
47 // The library's own pin-mapping table. Framebuffer's constructor abort()s when
48 // a mapping is asked for more parallel chains than it has pins for, so the
49 // limit has to be checked against the same table before handing it options --
50 // a hand-kept copy here had already drifted (it said regular-pi1 had 2).
51 #include "../lib/hardware-mapping.h"
52
53 // Mirrors Framebuffer::InitHardwareMapping(): 0 for an unknown mapping name,
54 // otherwise the number of parallel chains the mapping has data pins for.
55 ✗ static int MaxParallelChains(const char* name) {
56 ✗ if (name == nullptr || *name == '\0') {
57 ✗ name = "regular";
58 }
59 ✗ for (const HardwareMapping* h = matrix_hardware_mappings; h->name; ++h) {
60 ✗ if (strcasecmp(h->name, name) != 0) {
61 ✗ continue;
62 }
63 ✗ if (h->max_parallel_chains) {
64 ✗ return h->max_parallel_chains;
65 }
66 ✗ int n = 0;
67 ✗ n += (h->p0_r1 | h->p0_g1 | h->p0_r2 | h->p0_g2) ? 1 : 0;
68 ✗ n += (h->p1_r1 | h->p1_g1 | h->p1_r2 | h->p1_g2) ? 1 : 0;
69 ✗ n += (h->p2_r1 | h->p2_g1 | h->p2_r2 | h->p2_g2) ? 1 : 0;
70 ✗ n += (h->p3_r1 | h->p3_g1 | h->p3_r2 | h->p3_g2) ? 1 : 0;
71 ✗ n += (h->p4_r1 | h->p4_g1 | h->p4_r2 | h->p4_g2) ? 1 : 0;
72 ✗ n += (h->p5_r1 | h->p5_g1 | h->p5_r2 | h->p5_g2) ? 1 : 0;
73 ✗ return n;
74 }
75 ✗ return 0;
76 }
77
78 /////////////////////////////////////////////////////////////////////////////
79
80 /*
81 *
82 */
83 ✗ RGBMatrixOutput::RGBMatrixOutput(unsigned int startChannel,
84 ✗ unsigned int channelCount) :
85 ChannelOutput(startChannel, channelCount),
86 ✗ m_canvas(NULL),
87 ✗ m_rgbmatrix(NULL),
88 ✗ m_colorOrder("RGB"),
89 ✗ m_panelWidth(32),
90 ✗ m_panelHeight(16),
91 ✗ m_panels(0),
92 ✗ m_width(0),
93 ✗ m_height(0),
94 ✗ m_rows(0),
95 ✗ m_outputs(0),
96 ✗ m_longestChain(0),
97 ✗ m_invertedData(0) {
98 ✗ LogDebug(VB_CHANNELOUT, "RGBMatrixOutput::RGBMatrixOutput(%u, %u)\n",
99 startChannel, channelCount);
100 ✗ }
101
102 /*
103 *
104 */
105 ✗ RGBMatrixOutput::~RGBMatrixOutput() {
106 ✗ LogDebug(VB_CHANNELOUT, "RGBMatrixOutput::~RGBMatrixOutput()\n");
107
108 ✗ if (m_rgbmatrix)
109 ✗ delete m_rgbmatrix;
110 ✗ if (m_matrix)
111 ✗ delete m_matrix;
112 ✗ if (m_panelMatrix)
113 ✗ delete m_panelMatrix;
114 ✗ }
115
116 /*
117 *
118 */
119 ✗ int RGBMatrixOutput::Init(Json::Value config) {
120 ✗ LogDebug(VB_CHANNELOUT, "RGBMatrixOutput::Init(JSON)\n");
121 ✗ std::string model = GetFileContents("/proc/device-tree/model");
122 ✗ if (startsWith(model, "Raspberry Pi 5") ||
123 ✗ startsWith(model, "Raspberry Pi Compute Module 5")) {
124 ✗ LogErr(VB_CHANNELOUT, "RGBMatrix does work on Raspberry Pi 5\n");
125 ✗ AddOutputConfigWarning(50, "LED panel output: not supported on the Raspberry Pi 5 / CM5");
126 ✗ return 0;
127 }
128 ✗ if (m_channelCount > FPPD_MAX_CHANNELS) {
129 ✗ LogErr(VB_CHANNELOUT, "RGBMatrixOutput::Init: channelCount %u exceeds maximum of %u\n",
130 m_channelCount, FPPD_MAX_CHANNELS);
131 ✗ AddOutputConfigWarning(50, "LED panel output: channel count exceeds the maximum supported");
132 ✗ return 0;
133 }
134
135 ✗ m_panelWidth = config["panelWidth"].asInt();
136 ✗ m_panelHeight = config["panelHeight"].asInt();
137
138 ✗ std::string modules = GetFileContents("/proc/modules");
139 ✗ if (modules.contains("snd_bcm2835")) {
140 ✗ LogInfo(VB_CHANNELOUT, "snd_bcm2835 is loaded, attempting to unload.");
141 ✗ system("rmmod snd_bcm2835");
142 ✗ system("modprobe snd_dummy");
143 }
144 ✗ modules = GetFileContents("/proc/modules");
145 ✗ if (modules.contains("snd_bcm2835")) {
146 ✗ LogWarn(VB_CHANNELOUT, "snd_bcm2835 is stil loaded. Cannot proceed.");
147 ✗ std::string nvresults;
148 ✗ urlPut("http://127.0.0.1/api/settings/rebootFlag", "1", nvresults);
149 ✗ std::string errStr = "RGBMatrix cannot run with snd_bcm2835 enabled. Please reboot.";
150 ✗ AddOutputConfigWarning(50, errStr);
151 ✗ errStr += "\n";
152 ✗ LogErr(VB_CHANNELOUT, errStr);
153 ✗ return false;
154 ✗ }
155
156 ✗ if (!m_panelWidth)
157 ✗ m_panelWidth = 32;
158
159 ✗ if (!m_panelHeight)
160 ✗ m_panelHeight = 16;
161
162 ✗ m_invertedData = config["invertedData"].asInt();
163 ✗ m_colorOrder = config["colorOrder"].asString();
164
165 ✗ m_panelMatrix =
166 ✗ new PanelMatrix(m_panelWidth, m_panelHeight, m_invertedData);
167
168 ✗ if (!m_panelMatrix) {
169 ✗ LogErr(VB_CHANNELOUT, "Unable to create PanelMatrix\n");
170 ✗ return 0;
171 }
172
173 ✗ for (int i = 0; i < config["panels"].size(); i++) {
174 ✗ Json::Value p = config["panels"][i];
175 ✗ char orientation = 'N';
176 ✗ std::string o = p["orientation"].asString();
177
178 ✗ if (!o.empty())
179 ✗ orientation = o[0];
180
181 ✗ if (p["colorOrder"].asString() == "")
182 ✗ p["colorOrder"] = m_colorOrder;
183
184 // a refused panel (out of range) must not count toward the outputs or chains below
185 ✗ if (!m_panelMatrix->AddPanel(p["outputNumber"].asInt(),
186 ✗ p["panelNumber"].asInt(), orientation,
187 ✗ p["xOffset"].asInt(), p["yOffset"].asInt(),
188 ✗ ColorOrderFromString(p["colorOrder"].asString()))) {
189 ✗ continue;
190 }
191
192 ✗ if (p["outputNumber"].asInt() > m_outputs)
193 ✗ m_outputs = p["outputNumber"].asInt();
194
195 ✗ if (p["panelNumber"].asInt() > m_longestChain)
196 ✗ m_longestChain = p["panelNumber"].asInt();
197 ✗ }
198
199 // Both of these are 0-based, so bump them up by 1 for comparisons
200 ✗ m_outputs++;
201 ✗ m_longestChain++;
202
203 ✗ m_panels = m_panelMatrix->PanelCount();
204
205 ✗ int gpioSlowdown = 1;
206 ✗ if (config.isMember("gpioSlowdown"))
207 ✗ gpioSlowdown = config["gpioSlowdown"].asInt();
208
209 ✗ m_rows = m_panelHeight;
210
211 ✗ m_width = m_panelMatrix->Width();
212 ✗ m_height = m_panelMatrix->Height();
213
214 ✗ m_channelCount = m_width * m_height * 3;
215
216 ✗ RGBMatrix::Options options;
217 ✗ rgb_matrix::RuntimeOptions runtimeOptions;
218
219 ✗ runtimeOptions.gpio_slowdown = gpioSlowdown;
220 ✗ runtimeOptions.daemon = 0;
221 ✗ runtimeOptions.drop_privileges = 0;
222 ✗ runtimeOptions.do_gpio_init = true;
223
224 ✗ if (config["wiringPinout"].asString() != "")
225 ✗ options.hardware_mapping = strdup(config["wiringPinout"].asString().c_str());
226
227 ✗ options.chain_length = m_longestChain;
228 ✗ options.parallel = m_outputs;
229 ✗ options.rows = m_panelHeight;
230 ✗ options.cols = m_panelWidth;
231
232 ✗ if (config.isMember("brightness"))
233 ✗ options.brightness = config["brightness"].asInt();
234 else
235 ✗ options.brightness = 100;
236
237 ✗ int colorDepth = 8;
238 ✗ if (config.isMember("panelColorDepth")) {
239 ✗ colorDepth = config["panelColorDepth"].asInt();
240 }
241 ✗ if (colorDepth > 11 || colorDepth < 6) {
242 ✗ colorDepth = 11;
243 }
244 ✗ options.pwm_bits = colorDepth;
245
246 ✗ LogDebug(VB_CHANNELOUT, " chain: %d parallel: %d rows: %d cols: %d brightness: %d colordepth: %d\n",
247 options.chain_length, options.parallel, options.rows, options.cols,
248 options.brightness, options.pwm_bits);
249
250 ✗ if (config.isMember("cpuPWM")) {
251 ✗ options.disable_hardware_pulsing = config["cpuPWM"].asBool();
252 ✗ if (options.disable_hardware_pulsing) {
253 ✗ LogDebug(VB_CHANNELOUT, "Disabling use of Hardware PWM for OE pin\n");
254 }
255 }
256 ✗ if (config.isMember("panelInterleave")) {
257 ✗ options.multiplexing = std::atoi(config["panelInterleave"].asString().c_str());
258
259 ✗ int panelScan = config["panelScan"].asInt();
260 ✗ if (panelScan == 0) {
261 // 1/8 scan by default
262 ✗ panelScan = m_panelHeight / 2;
263 }
264 ✗ if (panelScan == (m_panelHeight / 2)) {
265 ✗ options.multiplexing = 0;
266 }
267 }
268 /* mortification77
269
270 panelRowAddressType Definitions
271
272 0 = default
273 1 = AB-addressed panels
274 2 = direct row select
275 3 = ABC-addressed panels
276 4 = ABC Shift + DE direct (Default: 0).
277
278 */
279
280 ✗ if (config.isMember("panelRowAddressType")) {
281 ✗ options.row_address_type = config["panelRowAddressType"].asInt();
282 }
283
284 ✗ if (config.isMember("panelType")) {
285 ✗ switch (config["panelType"].asInt()) {
286 ✗ case 1:
287 ✗ options.panel_type = "FM6126A";
288 ✗ break;
289 ✗ case 2:
290 ✗ options.panel_type = "FM6127";
291 ✗ break;
292 ✗ default:
293 ✗ break;
294 }
295 }
296
297 // parallel is the number of outputs (chains) in use; chain_length is the
298 // panels on each and is not limited by the pinout.
299 ✗ int maxParallel = MaxParallelChains(options.hardware_mapping);
300 ✗ if (maxParallel == 0) {
301 ✗ LogErr(VB_CHANNELOUT, "Unknown LED panel wiring pinout '%s'\n", options.hardware_mapping);
302 ✗ return 0;
303 }
304 ✗ if (options.parallel > maxParallel) {
305 ✗ LogErr(VB_CHANNELOUT, "The %s pinout supports %d output%s, but panels are assigned to %d\n",
306 options.hardware_mapping, maxParallel, maxParallel > 1 ? "s" : "", options.parallel);
307 ✗ return 0;
308 }
309
310 ✗ m_rgbmatrix = RGBMatrix::CreateFromOptions(options, runtimeOptions);
311 ✗ if (!m_rgbmatrix) {
312 ✗ LogErr(VB_CHANNELOUT, "Unable to create RGBMatrix instance\n");
313 ✗ AddOutputConfigWarning(50, "LED panel output: unable to create RGBMatrix instance from options");
314 ✗ return 0;
315 }
316
317 ✗ m_canvas = m_rgbmatrix->CreateFrameCanvas();
318 ✗ FrameCanvas* c2 = m_rgbmatrix->CreateFrameCanvas();
319 ✗ m_rgbmatrix->SwapOnVSync(c2);
320
321 ✗ m_matrix = new Matrix(m_startChannel, m_width, m_height);
322
323 ✗ if (config.isMember("subMatrices")) {
324 ✗ for (int i = 0; i < config["subMatrices"].size(); i++) {
325 ✗ Json::Value sm = config["subMatrices"][i];
326
327 ✗ m_matrix->AddSubMatrix(
328 ✗ sm["enabled"].asInt(),
329 ✗ sm["startChannel"].asInt() - 1,
330 ✗ sm["width"].asInt(),
331 ✗ sm["height"].asInt(),
332 ✗ sm["xOffset"].asInt(),
333 ✗ sm["yOffset"].asInt());
334 ✗ }
335 }
336
337 ✗ GammaLUT::Build(m_gammaCurve, GammaLUT::ParseConfig(config, 2.2f));
338 ✗ if (PixelOverlayManager::INSTANCE.isAutoCreatePixelOverlayModels()) {
339 ✗ std::string dd = "LED Panels";
340 ✗ if (config.isMember("LEDPanelMatrixName") && !config["LEDPanelMatrixName"].asString().empty()) {
341 ✗ dd = config["LEDPanelMatrixName"].asString();
342 }
343 ✗ if (config.isMember("description")) {
344 ✗ dd = config["description"].asString();
345 }
346 ✗ std::string desc = dd;
347 ✗ int count = 0;
348 ✗ while (PixelOverlayManager::INSTANCE.getModel(desc) != nullptr) {
349 ✗ count++;
350 ✗ desc = dd + "-" + std::to_string(count);
351 }
352 ✗ PixelOverlayManager::INSTANCE.addAutoOverlayModel(desc,
353 m_startChannel, m_channelCount, 3,
354 ✗ "H", m_invertedData ? "BL" : "TL",
355 ✗ m_height, 1);
356 ✗ m_autoCreatedModelName = desc;
357 ✗ }
358 ✗ return ChannelOutput::Init(config);
359 ✗ }
360 ✗ void RGBMatrixOutput::GetRequiredChannelRanges(const std::function<void(int, int)>& addRange) {
361 ✗ addRange(m_startChannel, m_startChannel + m_channelCount - 1);
362 ✗ }
363
364 /*
365 *
366 */
367 ✗ int RGBMatrixOutput::Close(void) {
368 ✗ LogDebug(VB_CHANNELOUT, "RGBMatrixOutput::Close()\n");
369
370 ✗ delete m_rgbmatrix;
371 ✗ m_rgbmatrix = nullptr;
372 ✗ m_canvas = nullptr;
373
374 ✗ if (!m_autoCreatedModelName.empty()) {
375 ✗ PixelOverlayManager::INSTANCE.removeAutoOverlayModel(m_autoCreatedModelName);
376 }
377
378 ✗ return ChannelOutput::Close();
379 }
380
381 ✗ void RGBMatrixOutput::OverlayTestData(unsigned char* channelData, int cycleNum, float percentOfCycle, int testType, const Json::Value& config) {
382 ✗ for (int output = 0; output < m_outputs; output++) {
383 ✗ int panelsOnOutput = m_panelMatrix->m_outputPanels[output].size();
384 ✗ for (int i = 0; i < panelsOnOutput; i++) {
385 ✗ int panel = m_panelMatrix->m_outputPanels[output][i];
386
387 ✗ m_panelMatrix->m_panels[panel].drawTestPattern(channelData + m_startChannel, cycleNum, percentOfCycle, testType);
388 }
389 }
390 ✗ }
391
392 /*
393 *
394 */
395 ✗ void RGBMatrixOutput::PrepData(unsigned char* channelData) {
396 ✗ LogExcess(VB_CHANNELOUT, "RGBMatrixOutput::PrepData(%p)\n",
397 channelData);
398 ✗ m_matrix->OverlaySubMatrices(channelData);
399
400 unsigned char r;
401 unsigned char g;
402 unsigned char b;
403
404 ✗ channelData += m_startChannel;
405
406 ✗ for (int output = 0; output < m_outputs; output++) {
407 ✗ int panelsOnOutput = m_panelMatrix->m_outputPanels[output].size();
408
409 ✗ for (int i = 0; i < panelsOnOutput; i++) {
410 ✗ int panel = m_panelMatrix->m_outputPanels[output][i];
411
412 ✗ int chain = (m_longestChain - 1) - m_panelMatrix->m_panels[panel].chain;
413 ✗ for (int y = 0; y < m_panelHeight; y++) {
414 ✗ int px = chain * m_panelWidth;
415 ✗ for (int x = 0; x < m_panelWidth; x++) {
416 ✗ r = m_gammaCurve[channelData[m_panelMatrix->m_panels[panel].pixelMap[(y * m_panelWidth + x) * 3]]];
417 ✗ g = m_gammaCurve[channelData[m_panelMatrix->m_panels[panel].pixelMap[(y * m_panelWidth + x) * 3 + 1]]];
418 ✗ b = m_gammaCurve[channelData[m_panelMatrix->m_panels[panel].pixelMap[(y * m_panelWidth + x) * 3 + 2]]];
419
420 ✗ m_canvas->SetPixel(px, y + (output * m_panelHeight), r, g, b);
421
422 ✗ px++;
423 }
424 }
425 }
426 }
427 ✗ }
428
429 /*
430 *
431 */
432 ✗ int RGBMatrixOutput::SendData(unsigned char* channelData) {
433 ✗ LogExcess(VB_CHANNELOUT, "RGBMatrixOutput::RawSendData(%p)\n",
434 channelData);
435
436 ✗ m_canvas = m_rgbmatrix->SwapOnVSync(m_canvas);
437 ✗ return m_channelCount;
438 }
439
440 /*
441 *
442 */
443 ✗ void RGBMatrixOutput::DumpConfig(void) {
444 ✗ LogDebug(VB_CHANNELOUT, "RGBMatrixOutput::DumpConfig()\n");
445 ✗ LogDebug(VB_CHANNELOUT, " panels : %d\n", m_panels);
446 ✗ LogDebug(VB_CHANNELOUT, " width : %d\n", m_width);
447 ✗ LogDebug(VB_CHANNELOUT, " height : %d\n", m_height);
448 ✗ }
449