| 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 | |||
| 17 | #include "../common.h" | ||
| 18 | #include "../log.h" | ||
| 19 | |||
| 20 | #include "FBMatrix.h" | ||
| 21 | #include "ChannelOutputSetup.h" | ||
| 22 | #include "overlays/PixelOverlay.h" | ||
| 23 | |||
| 24 | #include "../Plugin.h" | ||
| 25 | |||
| 26 | class FBMatrixPlugin : public FPPPlugins::Plugin, public FPPPlugins::ChannelOutputPlugin { | ||
| 27 | public: | ||
| 28 | 9 | FBMatrixPlugin() : | |
| 29 |
2/4✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
|
27 | FPPPlugins::Plugin("FBMatrix") { |
| 30 | 9 | } | |
| 31 | 11 | virtual ChannelOutput* createChannelOutput(unsigned int startChannel, unsigned int channelCount) override { | |
| 32 |
2/6✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 11 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
22 | return new FBMatrixOutput(startChannel, channelCount); |
| 33 | } | ||
| 34 | }; | ||
| 35 | |||
| 36 | extern "C" { | ||
| 37 | 9 | FPPPlugins::Plugin* createPlugin() { | |
| 38 |
2/6✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 9 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
18 | return new FBMatrixPlugin(); |
| 39 | } | ||
| 40 | } | ||
| 41 | |||
| 42 | /* | ||
| 43 | * | ||
| 44 | */ | ||
| 45 | 11 | FBMatrixOutput::FBMatrixOutput(unsigned int startChannel, | |
| 46 | 11 | unsigned int channelCount) : | |
| 47 | 11 | ChannelOutput(startChannel, channelCount) { | |
| 48 |
1/2✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
|
11 | LogDebug(VB_CHANNELOUT, "FBMatrixOutput::FBMatrixOutput(%u, %u)\n", |
| 49 | startChannel, channelCount); | ||
| 50 | 11 | } | |
| 51 | |||
| 52 | /* | ||
| 53 | * | ||
| 54 | */ | ||
| 55 | 44 | FBMatrixOutput::~FBMatrixOutput() { | |
| 56 | 22 | LogDebug(VB_CHANNELOUT, "FBMatrixOutput::~FBMatrixOutput()\n"); | |
| 57 | |||
| 58 | // Close() normally does this, but Init() can return failure after the | ||
| 59 | // listener is registered, and a failed output is deleted without a Close(). | ||
| 60 | // A listener left pointing at a freed FBMatrixOutput is the very bug this | ||
| 61 | // is here to prevent, one level up. | ||
| 62 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 9 times.
|
22 | if (!m_modelListenerName.empty()) { |
| 63 | 4 | PixelOverlayManager::INSTANCE.removeModelListener(modelName, m_modelListenerName); | |
| 64 | 4 | m_modelListenerName.clear(); | |
| 65 | } | ||
| 66 | |||
| 67 |
2/2✓ Branch 0 taken 9 times.
✓ Branch 1 taken 2 times.
|
22 | if (buffer) |
| 68 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
18 | delete[] buffer; |
| 69 | 44 | } | |
| 70 | |||
| 71 | 11 | int FBMatrixOutput::Init(Json::Value config) { | |
| 72 | 11 | LogDebug(VB_CHANNELOUT, "FBMatrixOutput::Init()\n"); | |
| 73 | |||
| 74 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 8 times.
|
11 | if (config.isMember("modelName")) |
| 75 |
2/4✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
|
3 | modelName = config["modelName"].asString(); |
| 76 | |||
| 77 | 11 | LogDebug(VB_CHANNELOUT, "FBMatrix::Init: modelName='%s', empty=%d\n", modelName.c_str(), modelName == ""); | |
| 78 | |||
| 79 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 3 times.
|
11 | if (modelName == "") { |
| 80 | 8 | int width = 0; | |
| 81 | 8 | int height = 0; | |
| 82 | 8 | std::string device; | |
| 83 | |||
| 84 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | if ((config.isMember("width")) && |
| 85 |
4/8✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 8 times.
✗ Branch 7 not taken.
|
16 | (config.isMember("height")) && |
| 86 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
8 | (config.isMember("device"))) { |
| 87 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
8 | width = config["width"].asInt(); |
| 88 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
8 | height = config["height"].asInt(); |
| 89 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
8 | device = config["device"].asString(); |
| 90 | |||
| 91 |
3/6✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
|
24 | replaceStart(device, "/dev/"); |
| 92 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | modelName = "FB - "; |
| 93 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | modelName += device; |
| 94 | |||
| 95 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
8 | LogDebug(VB_CHANNELOUT, "FBMatrix: Checking for existing model '%s', hasModel=%d\n", |
| 96 | modelName.c_str(), PixelOverlayManager::INSTANCE.getModel(modelName) != nullptr); | ||
| 97 | |||
| 98 | // Delete existing model if it exists, so we can recreate with PixelSize | ||
| 99 |
3/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 7 times.
|
8 | if (PixelOverlayManager::INSTANCE.getModel(modelName)) { |
| 100 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | LogDebug(VB_CHANNELOUT, "FBMatrix: Removing existing model '%s' to recreate with PixelSize\n", modelName.c_str()); |
| 101 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | PixelOverlayManager::INSTANCE.removeAutoOverlayModel(modelName); |
| 102 | } | ||
| 103 | |||
| 104 |
5/10✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 8 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 8 times.
✗ Branch 9 not taken.
|
8 | if (width && height && (device != "")) { |
| 105 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | Json::Value val; |
| 106 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
8 | val["Name"] = modelName; |
| 107 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
8 | val["Type"] = "FB"; |
| 108 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
8 | val["Width"] = width; |
| 109 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
8 | val["Height"] = height; |
| 110 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
8 | val["Device"] = device; |
| 111 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
8 | val["autoCreated"] = true; |
| 112 | |||
| 113 | // Pass through pixelSize if specified in config | ||
| 114 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 8 times.
|
8 | if (config.isMember("pixelSize")) { |
| 115 | ✗ | int ps = config["pixelSize"].asInt(); | |
| 116 | ✗ | val["PixelSize"] = ps; | |
| 117 | ✗ | LogDebug(VB_CHANNELOUT, "FBMatrix: Setting PixelSize=%d for model '%s'\n", ps, modelName.c_str()); | |
| 118 | } else { | ||
| 119 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | LogDebug(VB_CHANNELOUT, "FBMatrix: No pixelSize in config for model '%s'\n", modelName.c_str()); |
| 120 | } | ||
| 121 | |||
| 122 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
8 | PixelOverlayManager::INSTANCE.addModel(val); |
| 123 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | m_autoCreatedFBModelName = modelName; |
| 124 | 8 | } else { | |
| 125 | ✗ | LogErr(VB_CHANNELOUT, "Empty Pixel Overlay Model name\n"); | |
| 126 | ✗ | AddOutputConfigWarning(35, "VirtualMatrix: Empty Pixel Overlay Model name"); | |
| 127 | ✗ | return 0; | |
| 128 | } | ||
| 129 | } else { | ||
| 130 | ✗ | LogErr(VB_CHANNELOUT, "Empty Pixel Overlay Model name\n"); | |
| 131 | ✗ | AddOutputConfigWarning(35, "VirtualMatrix: Empty Pixel Overlay Model name"); | |
| 132 | ✗ | return 0; | |
| 133 | } | ||
| 134 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | } |
| 135 | |||
| 136 | // Track the model rather than caching a bare pointer: an auto-created FB | ||
| 137 | // model is destroyed by removeAutoOverlayModel() whenever the outputs are | ||
| 138 | // reloaded, and the channel output thread can be inside PrepData()/ | ||
| 139 | // SendData() at that moment. | ||
| 140 |
2/4✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 11 times.
✗ Branch 3 not taken.
|
11 | m_modelListenerName = "FBMatrix" + std::to_string((uintptr_t)this); |
| 141 |
1/2✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
|
11 | PixelOverlayManager::INSTANCE.addModelListener(modelName, m_modelListenerName, |
| 142 | 11 | [this](PixelOverlayModel* m) { | |
| 143 |
1/2✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
|
11 | std::lock_guard<std::mutex> lock(m_modelLock); |
| 144 | // buffer is sized for the model we saw in Init(), and SendData() | ||
| 145 | // hands it to model->setData(), which copies the *model's* | ||
| 146 | // width * height worth of bytes out of it. A model re-created | ||
| 147 | // under the same name with other dimensions (a second | ||
| 148 | // VirtualMatrix on the same device, whose Init() replaces | ||
| 149 | // "FB - <device>") would turn that into a heap over-read, so only | ||
| 150 | // follow a replacement that still matches this output. | ||
| 151 |
7/8✓ Branch 0 taken 5 times.
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 4 times.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 10 times.
|
11 | if (m && (m->getWidth() != width || m->getHeight() != height)) { |
| 152 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | LogWarn(VB_CHANNELOUT, "FBMatrix: model '%s' is now %dx%d, this output is %dx%d; detaching\n", |
| 153 | modelName.c_str(), m->getWidth(), m->getHeight(), width, height); | ||
| 154 | 1 | m = nullptr; | |
| 155 | } | ||
| 156 | 11 | model = m; | |
| 157 | 11 | }); | |
| 158 | 11 | model = PixelOverlayManager::INSTANCE.getModel(modelName); | |
| 159 | |||
| 160 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 10 times.
|
11 | if (!model) { |
| 161 | 1 | LogErr(VB_CHANNELOUT, "Invalid Pixel Overlay Model: '%s'\n", modelName.c_str()); | |
| 162 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | AddOutputConfigWarning(35, "VirtualMatrix: Invalid Pixel Overlay Model: " + modelName); |
| 163 | 1 | return 0; | |
| 164 | } | ||
| 165 | |||
| 166 | // buffer is 3 byte RGB and SendData() hands it to setData(), which reads | ||
| 167 | // the model's own bytes per pixel (4 for an RGBW submodel) | ||
| 168 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 9 times.
|
10 | if (model->getBytesPerPixel() != 3) { |
| 169 | 1 | LogErr(VB_CHANNELOUT, "Pixel Overlay Model '%s' is not RGB\n", modelName.c_str()); | |
| 170 |
3/6✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
|
1 | AddOutputConfigWarning(35, "VirtualMatrix: model " + modelName + " is not an RGB model"); |
| 171 | 1 | model = nullptr; | |
| 172 | 1 | return 0; | |
| 173 | } | ||
| 174 | |||
| 175 | 9 | width = model->getWidth(); | |
| 176 | 9 | height = model->getHeight(); | |
| 177 | |||
| 178 | // Calculate channel count from the model size | ||
| 179 | 9 | m_channelCount = width * height * 3; | |
| 180 | |||
| 181 | 9 | inverted = config["invert"].asInt(); | |
| 182 | 9 | flipHorizontal = config["flipHorizontal"].asInt(); | |
| 183 | |||
| 184 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (m_channelCount != (width * height * 3)) { |
| 185 | ✗ | LogErr(VB_CHANNELOUT, "Error, channel count is incorrect\n"); | |
| 186 | ✗ | AddOutputConfigWarning(35, "VirtualMatrix: Invalid Pixel Overlay Model: " + modelName); | |
| 187 | ✗ | return 0; | |
| 188 | } | ||
| 189 | |||
| 190 | 9 | buffer = new unsigned char[width * height * 3]; | |
| 191 | |||
| 192 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | if (PixelOverlayManager::INSTANCE.isAutoCreatePixelOverlayModels()) { |
| 193 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | std::string dd = "VirtualMatrix"; |
| 194 |
3/4✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 8 times.
|
9 | if (config.isMember("description")) { |
| 195 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | dd = config["description"].asString(); |
| 196 | } | ||
| 197 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | std::string desc = dd; |
| 198 | 9 | int count = 0; | |
| 199 |
3/4✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 9 times.
|
10 | while (PixelOverlayManager::INSTANCE.getModel(desc) != nullptr) { |
| 200 | 1 | count++; | |
| 201 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | desc = dd + "-" + std::to_string(count); |
| 202 | } | ||
| 203 |
4/8✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 9 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 9 times.
✗ Branch 7 not taken.
|
45 | PixelOverlayManager::INSTANCE.addAutoOverlayModel(desc, |
| 204 | m_startChannel, m_channelCount, 3, | ||
| 205 | "H", "TL", | ||
| 206 | 9 | height, 1); | |
| 207 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | m_autoCreatedModelName = desc; |
| 208 | 9 | } | |
| 209 |
2/4✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
|
9 | return ChannelOutput::Init(config); |
| 210 | } | ||
| 211 | |||
| 212 | /* | ||
| 213 | * | ||
| 214 | */ | ||
| 215 | 9 | int FBMatrixOutput::Close(void) { | |
| 216 | 9 | LogDebug(VB_CHANNELOUT, "FBMatrixOutput::Close()\n"); | |
| 217 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | if (!m_modelListenerName.empty()) { |
| 218 | 9 | PixelOverlayManager::INSTANCE.removeModelListener(modelName, m_modelListenerName); | |
| 219 | 9 | m_modelListenerName.clear(); | |
| 220 | } | ||
| 221 | { | ||
| 222 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | std::lock_guard<std::mutex> lock(m_modelLock); |
| 223 | 9 | model = nullptr; | |
| 224 | 9 | } | |
| 225 |
1/2✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
|
9 | if (!m_autoCreatedModelName.empty()) { |
| 226 | 9 | PixelOverlayManager::INSTANCE.removeAutoOverlayModel(m_autoCreatedModelName); | |
| 227 | } | ||
| 228 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 1 times.
|
9 | if (!m_autoCreatedFBModelName.empty()) { |
| 229 | 8 | PixelOverlayManager::INSTANCE.removeAutoOverlayModel(m_autoCreatedFBModelName); | |
| 230 | } | ||
| 231 | 9 | return ChannelOutput::Close(); | |
| 232 | } | ||
| 233 | |||
| 234 | 1202 | void FBMatrixOutput::PrepData(unsigned char* channelData) { | |
| 235 |
1/2✓ Branch 0 taken 1202 times.
✗ Branch 1 not taken.
|
1202 | std::lock_guard<std::mutex> lock(m_modelLock); |
| 236 |
3/4✓ Branch 0 taken 1135 times.
✓ Branch 1 taken 67 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1135 times.
|
1202 | if (!model || !buffer) { |
| 237 | 67 | return; | |
| 238 | } | ||
| 239 | 1135 | channelData += m_startChannel; | |
| 240 | |||
| 241 |
1/2✓ Branch 0 taken 1135 times.
✗ Branch 1 not taken.
|
1135 | LogExcess(VB_CHANNELOUT, "FBMatrixOutput::SendData(%p)\n", |
| 242 | channelData); | ||
| 243 | |||
| 244 | 1135 | int stride = width * 3; | |
| 245 | |||
| 246 |
2/2✓ Branch 0 taken 41760 times.
✓ Branch 1 taken 1135 times.
|
42895 | for (int y = 0; y < height; y++) { |
| 247 | 41760 | unsigned char* src = channelData + (y * stride); | |
| 248 | // Vertical flip: write source row y into the mirrored destination row | ||
| 249 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 41760 times.
|
41760 | int dstRow = inverted ? (height - 1 - y) : y; |
| 250 | 41760 | unsigned char* dst = buffer + (dstRow * stride); | |
| 251 | |||
| 252 |
2/2✓ Branch 0 taken 608 times.
✓ Branch 1 taken 41152 times.
|
41760 | if (flipHorizontal) { |
| 253 | // Horizontal flip: reverse the pixel order within the row | ||
| 254 | 608 | unsigned char* sp = src; | |
| 255 | 608 | unsigned char* dp = dst + ((width - 1) * 3); | |
| 256 |
2/2✓ Branch 0 taken 38912 times.
✓ Branch 1 taken 608 times.
|
39520 | for (int x = 0; x < width; x++) { |
| 257 | 38912 | dp[0] = sp[0]; | |
| 258 | 38912 | dp[1] = sp[1]; | |
| 259 | 38912 | dp[2] = sp[2]; | |
| 260 | 38912 | sp += 3; | |
| 261 | 38912 | dp -= 3; | |
| 262 | } | ||
| 263 | } else { | ||
| 264 | 41152 | memcpy(dst, src, stride); | |
| 265 | } | ||
| 266 | } | ||
| 267 |
2/2✓ Branch 0 taken 1135 times.
✓ Branch 1 taken 67 times.
|
1202 | } |
| 268 | |||
| 269 | 1188 | int FBMatrixOutput::SendData(unsigned char* channelData) { | |
| 270 |
1/2✓ Branch 0 taken 1188 times.
✗ Branch 1 not taken.
|
1188 | std::lock_guard<std::mutex> lock(m_modelLock); |
| 271 |
3/4✓ Branch 0 taken 1123 times.
✓ Branch 1 taken 65 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1123 times.
|
1188 | if (!model || !buffer) { |
| 272 | 65 | return m_channelCount; | |
| 273 | } | ||
| 274 |
2/4✓ Branch 0 taken 1123 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1123 times.
✗ Branch 3 not taken.
|
1123 | if (model->getState() == 0) { |
| 275 | // Pass-through mode: the model isn't being driven by an active | ||
| 276 | // overlay/effect, so push the current sequence channels into it | ||
| 277 | // and draw to the framebuffer right here. | ||
| 278 |
1/2✓ Branch 0 taken 1123 times.
✗ Branch 1 not taken.
|
1123 | model->setData(buffer); |
| 279 | // doOverlay() takes the whole sequence buffer: a submodel's parent | ||
| 280 | // writes at its own absolute channels, and channelData here is this | ||
| 281 | // output's slice, starting m_startChannel in. (A framebuffer model | ||
| 282 | // ignores the argument and draws its own data.) | ||
| 283 |
1/2✓ Branch 0 taken 1123 times.
✗ Branch 1 not taken.
|
1123 | model->doOverlay(channelData - m_startChannel); |
| 284 | } | ||
| 285 | // else: the model is acting as an overlay (e.g., a Pixel Overlay | ||
| 286 | // effect is writing into channelData via flushOverlayBuffer). Don't | ||
| 287 | // call setData here — that would overwrite the effect's data with | ||
| 288 | // m_seqData every frame and leave the framebuffer mostly black. | ||
| 289 | // doOverlays() in ProcessSequenceData() drives the framebuffer in | ||
| 290 | // that case via PixelOverlayModelFB::doOverlay(). | ||
| 291 | 1123 | return m_channelCount; | |
| 292 | 1188 | } | |
| 293 | |||
| 294 | 22 | void FBMatrixOutput::GetRequiredChannelRanges(const std::function<void(int, int)>& addRange) { | |
| 295 | 22 | addRange(m_startChannel, m_startChannel + (width * height * 3) - 1); | |
| 296 | 22 | } | |
| 297 | |||
| 298 | /* | ||
| 299 | * | ||
| 300 | */ | ||
| 301 | 9 | void FBMatrixOutput::DumpConfig(void) { | |
| 302 | 9 | LogDebug(VB_CHANNELOUT, "VirtualMatrixOutput::DumpConfig()\n"); | |
| 303 | 9 | LogDebug(VB_CHANNELOUT, " model : %s\n", modelName.c_str()); | |
| 304 | 9 | LogDebug(VB_CHANNELOUT, " width : %d\n", width); | |
| 305 | 9 | LogDebug(VB_CHANNELOUT, " height : %d\n", height); | |
| 306 | 9 | LogDebug(VB_CHANNELOUT, " flipV : %d\n", inverted); | |
| 307 | 9 | LogDebug(VB_CHANNELOUT, " flipH : %d\n", flipHorizontal); | |
| 308 | 9 | } | |
| 309 |