GCC Code Coverage Report


Directory: ./
File: framebuffer/FrameBuffer.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 453 486 93.2%
Functions: 32 33 97.0%
Branches: 263 413 63.7%

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 LGPL v2.1 as described in the
10 * included LICENSE.LGPL file.
11 */
12
13 #include "fpp-pch.h"
14
15 #include "fpp-json.h"
16
17 #include <sys/ioctl.h>
18 #include <sys/mman.h>
19 #include <sys/socket.h>
20 #include <sys/uio.h>
21 #include <algorithm>
22 #include <chrono>
23 #include <cstring>
24 #include <ctime>
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <mutex>
28 #include <stdlib.h>
29 #include <string>
30 #include <thread>
31 #include <unistd.h>
32
33 #include "Warnings.h"
34 #include "common.h"
35 #include "log.h"
36 #include "settings.h"
37 #include "commands/Commands.h"
38
39 #include "FrameBuffer.h"
40 #include "IOCTLFrameBuffer.h"
41 #include "KMSFrameBuffer.h"
42 #include "SocketFrameBuffer.h"
43 #include "X11FrameBuffer.h"
44
45 #define FB_CURRENT_PAGE_PTR (m_pageBuffers[m_cPage])
46
47 /*
48 * Usage:
49 *
50 * AutoSync Off:
51 *
52 * Client -> FBCopyData();
53 * Client -> FBSyncDisplay();
54 * FrameBuffer::DrawLoop() calls transition effect
55 * - None -> NextPage(false); copy output buffer, SyncDisplay();
56 * - Others -> loop(copy output buffer, SyncDisplay())
57 * (effects only work on a single m_cPage page)
58 *
59 * AutoSync On:
60 * Client:
61 * page = Page(true);
62 * if IsDirty(page) then can't do anything, else do...
63 * NextPage(true)
64 * copy data to BufferPage(page)
65 * SetDirty(page)
66 * FrameBuffer::SyncLoop*():
67 * - if page m_cPage is dirty, then output and increment else just sleep/wait
68 *
69 */
70
71 void StartDrawLoopThread(FrameBuffer* fb);
72
73 /*
74 *
75 */
76 75 FrameBuffer::FrameBuffer() {
77
1/2
✓ Branch 0 taken 75 times.
✗ Branch 1 not taken.
75 LogDebug(VB_CHANNELOUT, "FrameBuffer::FrameBuffer()\n");
78
79 75 m_typeSeed = (unsigned int)time(NULL);
80 75 }
81
82 /*
83 *
84 */
85 72 void FrameBuffer::StopDrawLoop(void) {
86
2/2
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 33 times.
72 if (!m_drawThread) {
87 39 m_runLoop = false;
88 39 return;
89 }
90
91 // Flip the predicate under the same lock DrawLoop() waits on, otherwise a
92 // notify that lands between its m_runLoop check and its wait() is lost and
93 // the join() below never returns.
94 {
95
1/2
✓ Branch 0 taken 33 times.
✗ Branch 1 not taken.
33 std::unique_lock<std::mutex> lock(m_bufferLock);
96 33 m_runLoop = false;
97 33 }
98 33 m_drawSignal.notify_all();
99
100 33 m_drawThread->join();
101
1/2
✓ Branch 0 taken 33 times.
✗ Branch 1 not taken.
33 delete m_drawThread;
102 33 m_drawThread = nullptr;
103 }
104
105 72 FrameBuffer::~FrameBuffer() {
106 // Subclasses stop the loop in their own destructors (where their state is
107 // still alive); this covers a bare FrameBuffer and is a no-op once they have.
108 72 StopDrawLoop();
109
110
2/2
✓ Branch 0 taken 33 times.
✓ Branch 1 taken 3 times.
72 if (m_outputBuffer) {
111 66 free(m_outputBuffer);
112 66 m_outputBuffer = nullptr;
113 }
114
2/2
✓ Branch 0 taken 33 times.
✓ Branch 1 taken 3 times.
72 if (m_stagingBuffer) {
115 66 free(m_stagingBuffer);
116 66 m_stagingBuffer = nullptr;
117 }
118
119 // Only the base teardown - the vtable is already demoted to FrameBuffer here,
120 // so this can never reach a subclass override. Subclasses call their own
121 // DestroyFrameBuffer() from their destructor; this is the no-subclass case
122 // and a no-op once one has run (it clears m_fbFd).
123
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 36 times.
72 if (m_fbFd > -1) {
124 ✗ FrameBuffer::DestroyFrameBuffer();
125 }
126
127
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 36 times.
72 if (m_dirtyPages) {
128 ✗ delete[] m_dirtyPages;
129 ✗ m_dirtyPages = nullptr;
130 }
131 72 }
132
133 /*
134 *
135 */
136 75 int FrameBuffer::FBInit(const Json::Value& config) {
137
2/2
✓ Branch 0 taken 61 times.
✓ Branch 1 taken 14 times.
75 if (config.isMember("Name"))
138
2/4
✓ Branch 0 taken 61 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 61 times.
✗ Branch 3 not taken.
61 m_name = config["Name"].asString();
139
140
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 75 times.
75 if (config.isMember("X"))
141 ✗ m_xOffset = config["X"].asInt();
142
143
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 75 times.
75 if (config.isMember("Y"))
144 ✗ m_yOffset = config["Y"].asInt();
145
146
1/2
✓ Branch 0 taken 75 times.
✗ Branch 1 not taken.
75 if (config.isMember("Width"))
147 75 m_width = config["Width"].asInt();
148
149
1/2
✓ Branch 0 taken 75 times.
✗ Branch 1 not taken.
75 if (config.isMember("Height"))
150 75 m_height = config["Height"].asInt();
151
152
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 62 times.
75 if (config.isMember("PixelSize"))
153 13 m_pixelSize = config["PixelSize"].asInt();
154
155
1/2
✓ Branch 0 taken 75 times.
✗ Branch 1 not taken.
75 if (config.isMember("Device"))
156
2/4
✓ Branch 0 taken 75 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 75 times.
✗ Branch 3 not taken.
75 m_device = config["Device"].asString();
157
158
2/2
✓ Branch 0 taken 27 times.
✓ Branch 1 taken 48 times.
75 if (config.isMember("TransitionType"))
159
2/4
✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 27 times.
✗ Branch 3 not taken.
27 m_transitionType = (ImageTransitionType)(atoi(config["TransitionType"].asString().c_str()));
160
161
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 74 times.
75 if (config.isMember("Pages")) {
162 // Clamp at the one place the page count enters from config. Only
163 // m_pageBuffers[0..MAX_PAGES-1] are ever assigned, and NextPage() walks
164 // m_cPage modulo m_pages, so an out-of-range count hands the draw path
165 // an index with no buffer behind it.
166 1 int p = config["Pages"].asInt();
167
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 if (p < 1 || p > MAX_PAGES) {
168
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 LogWarn(VB_CHANNELOUT, "FrameBuffer: Pages=%d out of range, using %d\n",
169 p, p < 1 ? 1 : MAX_PAGES);
170
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 p = p < 1 ? 1 : MAX_PAGES;
171 }
172 1 m_pages = p;
173 }
174
175
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 61 times.
75 if (config.isMember("BitsPerPixel"))
176 14 m_bpp = config["BitsPerPixel"].asInt();
177
178
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 75 times.
75 if (config.isMember("AutoSync"))
179 ✗ m_autoSync = config["AutoSync"].asBool();
180
181
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 61 times.
75 if (config.isMember("VariableRefresh"))
182 14 m_variableRefresh = config["VariableRefresh"].asBool();
183
184
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 62 times.
75 if (m_pixelSize > 0) {
185 13 m_pixelsWide = m_width / m_pixelSize;
186 13 m_pixelsHigh = m_height / m_pixelSize;
187
188 // Adjust our Width/Height to be whole numbers
189 13 m_width = m_pixelSize * m_pixelsWide;
190 13 m_height = m_pixelSize * m_pixelsHigh;
191 } else {
192 62 m_pixelsWide = m_width;
193 62 m_pixelsHigh = m_height;
194 }
195
196 75 int result = InitializeFrameBuffer();
197
2/2
✓ Branch 0 taken 38 times.
✓ Branch 1 taken 37 times.
75 if (m_pixelSize == 0) {
198 38 m_pixelSize = 1;
199 }
200
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 72 times.
75 if (!result)
201 3 return 0;
202
203 72 ClipToPage();
204
205
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 72 times.
72 if (m_autoSync) {
206 ✗ m_dirtyPages = new volatile uint8_t[m_pages];
207 ✗ for (int i = 0; i < m_pages; i++)
208 ✗ m_dirtyPages[i] = 0;
209 }
210
211 72 m_outputBuffer = (uint8_t*)calloc(1, m_pageSize);
212 72 m_stagingBuffer = (uint8_t*)calloc(1, m_pageSize);
213
2/4
✓ Branch 0 taken 72 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 72 times.
72 if (!m_outputBuffer || !m_stagingBuffer) {
214 ✗ LogErr(VB_CHANNELOUT, "Error, unable to allocate outputBuffer buffer\n");
215 ✗ DestroyFrameBuffer();
216 ✗ return 0;
217 }
218
219 72 LogDebug(VB_CHANNELOUT, "Initialized %s frame buffer at %dx%d resolution.\n",
220 m_device.c_str(), m_width, m_height);
221
222 72 Dump();
223 72 m_runLoop = true;
224
2/6
✓ Branch 0 taken 72 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 72 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
72 m_drawThread = new std::thread(StartDrawLoopThread, this);
225 72 return 1;
226 }
227
228 74 FrameBuffer* FrameBuffer::createFrameBuffer(const Json::Value& config) {
229 74 FrameBuffer* fb = nullptr;
230 74 std::string device;
231
2/4
✓ Branch 0 taken 74 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 74 times.
✗ Branch 3 not taken.
74 if (config.isMember("Device")) {
232
2/4
✓ Branch 0 taken 74 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 74 times.
✗ Branch 3 not taken.
74 device = config["Device"].asString();
233 }
234
235
1/2
✓ Branch 0 taken 74 times.
✗ Branch 1 not taken.
74 LogDebug(VB_CHANNELOUT, "FrameBuffer::createFrameBuffer for device='%s'\n", device.c_str());
236
237 #ifdef USE_X11
238 fb = new X11FrameBuffer();
239 if (fb->FBInit(config)) {
240 return fb;
241 }
242 delete fb;
243 fb = nullptr;
244 #endif
245 #ifdef USE_FRAMEBUFFER_SOCKET
246 fb = new SocketFrameBuffer();
247 if (fb->FBInit(config)) {
248 return fb;
249 }
250 delete fb;
251 fb = nullptr;
252 #endif
253 #ifdef HAS_KMS_FB
254 // DPI needs KMS for direct pixel output
255 // For HDMI: Try KMS first for full-resolution displays (better performance)
256 // Small models will fall through to IOCTL which has auto-scaling
257 74 bool isDPI = (device.find("DPI") != std::string::npos);
258 74 bool isSmallModel = false;
259
260 // Check if this is a small model that needs scaling
261
5/10
✓ Branch 0 taken 74 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 74 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 74 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 74 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 74 times.
✗ Branch 9 not taken.
74 if (config.isMember("Width") && config.isMember("Height")) {
262
2/4
✓ Branch 0 taken 74 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 74 times.
✗ Branch 3 not taken.
74 int width = config["Width"].asInt();
263
2/4
✓ Branch 0 taken 74 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 74 times.
✗ Branch 3 not taken.
74 int height = config["Height"].asInt();
264 // Consider anything smaller than 1280x720 as "small" needing potential scaling
265
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 74 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
74 isSmallModel = (width < 1280 || height < 720);
266 }
267
268 // #2713: named DRM connectors (DSI/HDMI/DPI/DisplayPort) can only be driven
269 // correctly via KMS (it scales any model size onto the panel and page-flips,
270 // so it scans out reliably). IOCTL fbdev cannot present a buffer smaller than
271 // the panel mode and does not reliably scan out on vc4, so never route a
272 // connector-named device to IOCTL.
273
2/2
✓ Branch 0 taken 59 times.
✓ Branch 1 taken 2 times.
61 bool isConnector = isDPI ||
274
2/2
✓ Branch 0 taken 51 times.
✓ Branch 1 taken 8 times.
120 device.find("DSI") != std::string::npos ||
275
3/4
✓ Branch 0 taken 61 times.
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 51 times.
194 device.find("HDMI") != std::string::npos ||
276 51 device.find("DisplayPort") != std::string::npos;
277
4/6
✓ Branch 0 taken 51 times.
✓ Branch 1 taken 23 times.
✓ Branch 2 taken 51 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 51 times.
74 bool tryKMS = isConnector || device.empty() || !isSmallModel;
278
279
2/2
✓ Branch 0 taken 23 times.
✓ Branch 1 taken 51 times.
74 if (tryKMS) {
280
3/8
✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 23 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 23 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
23 fb = new KMSFrameBuffer();
281
3/4
✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
✓ Branch 3 taken 1 times.
23 if (fb->FBInit(config)) {
282 22 return fb;
283 }
284
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 delete fb;
285 1 fb = nullptr;
286 }
287 #endif
288 #ifdef HAS_IOCTL_FB
289
3/8
✓ Branch 0 taken 52 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 52 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 52 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
52 fb = new IOCTLFrameBuffer();
290
3/4
✓ Branch 0 taken 52 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 50 times.
✓ Branch 3 taken 2 times.
52 if (fb->FBInit(config)) {
291 50 return fb;
292 }
293
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 delete fb;
294 2 fb = nullptr;
295 #endif
296 2 return nullptr;
297 74 }
298
299 static constexpr int WARNING_ID_FB_CROPPED = 67;
300
301 // Everything that writes a page - FBCopyData() and the transitions - must stay
302 // inside it, and the page is whatever the device gave us (m_rowStride bytes by
303 // m_pageSize / m_rowStride rows), which need not be what the model asked for:
304 // IOCTLFrameBuffer deliberately keeps the framebuffer at its native size. Clip
305 // once here, after the backend has sized the page, rather than in each loop.
306 72 void FrameBuffer::ClipToPage() {
307 72 m_drawCols = m_pixelsWide;
308 72 m_drawRows = m_pixelsHigh;
309 72 int bytesPerPixel = m_bpp / 8;
310
3/6
✓ Branch 0 taken 72 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 72 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 72 times.
72 if (m_rowStride <= 0 || bytesPerPixel <= 0 || m_pixelSize <= 0) {
311 ✗ return;
312 }
313 72 int pageCols = m_rowStride / bytesPerPixel;
314 72 int pageRows = m_pageSize / m_rowStride;
315
316 72 m_drawCols = std::min(m_pixelsWide, pageCols / m_pixelSize);
317 72 m_drawRows = std::min(m_pixelsHigh, pageRows / m_pixelSize);
318 // The transitions work in device pixels over the same page-sized buffers.
319 72 m_width = std::min(m_width, pageCols);
320 72 m_height = std::min(m_height, pageRows);
321
322
3/4
✓ Branch 0 taken 71 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 71 times.
72 if (m_drawCols < m_pixelsWide || m_drawRows < m_pixelsHigh) {
323
4/8
✓ 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.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
2 m_clipWarning = "Framebuffer " + m_device + ": the " + std::to_string(m_pixelsWide) + "x" +
324
4/8
✓ 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.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
4 std::to_string(m_pixelsHigh) + " model does not fit the " + std::to_string(pageCols) + "x" +
325
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.
4 std::to_string(pageRows) + " display, so only the top-left " + std::to_string(m_drawCols) +
326
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.
3 "x" + std::to_string(m_drawRows) + " is shown";
327
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogWarn(VB_CHANNELOUT, "%s\n", m_clipWarning.c_str());
328 // Lasts as long as this framebuffer; DestroyFrameBuffer() takes it down.
329
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 WarningHolder::AddWarning(WARNING_ID_FB_CROPPED, m_clipWarning);
330 }
331 }
332
333 36 void FrameBuffer::DestroyFrameBuffer(void) {
334
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 36 times.
36 if (!m_clipWarning.empty()) {
335 ✗ WarningHolder::RemoveWarning(WARNING_ID_FB_CROPPED, m_clipWarning);
336 ✗ m_clipWarning.clear();
337 }
338
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 21 times.
36 if (m_buffer) {
339 15 memset(m_buffer, 0, m_bufferSize);
340 15 munmap(m_buffer, m_bufferSize);
341 15 m_buffer = nullptr;
342 }
343
344
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 20 times.
36 if (m_fbFd != -1) {
345 16 close(m_fbFd);
346 16 m_fbFd = -1;
347 }
348 36 }
349
350 /*
351 *
352 */
353 2311 void FrameBuffer::FBCopyData(const uint8_t* buffer, int draw) {
354 2311 int drow = 0;
355 2311 const uint8_t* sR = buffer + 0; // Input always RGB
356 2311 const uint8_t* sG = buffer + 1;
357 2311 const uint8_t* sB = buffer + 2;
358 2311 uint8_t* ob = m_stagingBuffer;
359
360
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 2311 times.
✓ Branch 2 taken 2311 times.
✗ Branch 3 not taken.
2311 std::unique_lock<std::mutex> lock(draw ? m_bufferLock : m_stagingLock);
361
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2311 times.
2311 if (draw) {
362 ✗ ob = (m_pixelSize == 1) ? FB_CURRENT_PAGE_PTR : m_outputBuffer;
363 }
364 uint8_t* dR;
365 uint8_t* dG;
366 uint8_t* dB;
367 2311 int add = m_bpp / 8;
368
369 // Rows and columns past m_drawRows/m_drawCols do not fit the page (see
370 // ClipToPage()); the source is still walked at its full m_pixelsWide.
371 2311 int skip = (m_pixelsWide - m_drawCols) * 3;
372
2/2
✓ Branch 0 taken 79114 times.
✓ Branch 1 taken 2311 times.
81425 for (int y = 0; y < m_drawRows; y++) {
373 // Output is BGR(A)
374 79114 dB = ob + (drow * m_pixelSize * m_rowStride);
375 79114 dG = dB + 1;
376 79114 dR = dB + 2;
377
378
2/2
✓ Branch 0 taken 5454112 times.
✓ Branch 1 taken 79114 times.
5533226 for (int x = 0; x < m_drawCols; x++) {
379
2/2
✓ Branch 0 taken 95288160 times.
✓ Branch 1 taken 5454112 times.
100742272 for (int sc = 0; sc < m_pixelSize; sc++) {
380 95288160 *dR = *sR;
381 95288160 *dG = *sG;
382 95288160 *dB = *sB;
383
384 95288160 dR += add;
385 95288160 dG += add;
386 95288160 dB += add;
387 }
388
389 5454112 sR += 3;
390 5454112 sG += 3;
391 5454112 sB += 3;
392 }
393 79114 sR += skip;
394 79114 sG += skip;
395 79114 sB += skip;
396
397 79114 dB = ob + (drow * m_pixelSize * m_rowStride);
398
2/2
✓ Branch 0 taken 1355248 times.
✓ Branch 1 taken 79114 times.
1434362 for (int sc = 1; sc < m_pixelSize; sc++) {
399 1355248 memcpy(dB + (sc * m_rowStride), dB, m_rowStride);
400 }
401
402 79114 drow++;
403 }
404
405
1/2
✓ Branch 0 taken 2311 times.
✗ Branch 1 not taken.
2311 lock.unlock();
406
1/2
✓ Branch 0 taken 2311 times.
✗ Branch 1 not taken.
2311 if (!draw) {
407 2311 m_imageReady = true;
408 }
409 2311 }
410
411 /*
412 *
413 */
414 72 void FrameBuffer::Dump(void) {
415 72 LogDebug(VB_CHANNELOUT, "Device : %s\n", m_device.c_str());
416 72 LogDebug(VB_CHANNELOUT, "Width : %d\n", m_width);
417 72 LogDebug(VB_CHANNELOUT, "Height : %d\n", m_height);
418 72 LogDebug(VB_CHANNELOUT, "PixelSize : %d\n", m_pixelSize);
419 72 LogDebug(VB_CHANNELOUT, "Pixels Wide : %d\n", m_pixelsWide);
420 72 LogDebug(VB_CHANNELOUT, "Pixels High : %d\n", m_pixelsHigh);
421 72 LogDebug(VB_CHANNELOUT, "bpp : %d\n", m_bpp);
422 72 LogDebug(VB_CHANNELOUT, "Transition Type: %d\n", (int)m_transitionType);
423 72 }
424
425 /*
426 *
427 */
428 2313 void FrameBuffer::FBStartDraw(ImageTransitionType transitionType) {
429
1/2
✓ Branch 0 taken 2313 times.
✗ Branch 1 not taken.
2313 if (transitionType == IT_Default)
430 2313 transitionType = m_transitionType;
431
432
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2313 times.
2313 if (transitionType == IT_Random)
433 ✗ transitionType = (ImageTransitionType)(rand_r(&m_typeSeed) % ((int)IT_MAX - 1) + 1);
434
435 2313 m_nextTransitionType = transitionType;
436
437 2313 m_drawSignal.notify_all();
438 2313 }
439
440 /*
441 *
442 */
443 72 void StartDrawLoopThread(FrameBuffer* fb) {
444 72 fb->DrawLoop();
445 33 }
446
447 72 void FrameBuffer::DrawLoop(void) {
448
2/4
✓ Branch 0 taken 72 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 72 times.
✗ Branch 3 not taken.
72 SetThreadName("FPP-FBDrawLoop");
449
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 72 times.
72 if (m_autoSync) {
450 ✗ SyncLoop();
451 ✗ return;
452 }
453
454
1/2
✓ Branch 0 taken 72 times.
✗ Branch 1 not taken.
72 std::unique_lock<std::mutex> lock(m_bufferLock);
455
2/2
✓ Branch 0 taken 5049 times.
✓ Branch 1 taken 33 times.
5082 while (m_runLoop) {
456 // Taken before drawing, not cleared after: an image that arrives while
457 // a transition runs (they take most of a second) was marked done along
458 // with the one being drawn, and never reached the screen.
459
2/2
✓ Branch 0 taken 1764 times.
✓ Branch 1 taken 3285 times.
5049 if (m_imageReady.exchange(false)) {
460
1/2
✓ Branch 0 taken 1764 times.
✗ Branch 1 not taken.
1764 lock.unlock();
461 {
462
1/2
✓ Branch 0 taken 1764 times.
✗ Branch 1 not taken.
1764 std::lock_guard<std::mutex> staged(m_stagingLock);
463 1764 memcpy(m_outputBuffer, m_stagingBuffer, m_pageSize);
464 1764 }
465 // m_nextTransitionType = IT_WipeFromHCenter;
466
14/15
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 4 times.
✓ Branch 6 taken 7 times.
✓ Branch 7 taken 4 times.
✓ Branch 8 taken 4 times.
✓ Branch 9 taken 4 times.
✓ Branch 10 taken 4 times.
✓ Branch 11 taken 4 times.
✓ Branch 12 taken 4 times.
✓ Branch 13 taken 4 times.
✓ Branch 14 taken 1709 times.
1764 switch (m_nextTransitionType) {
467 ✗ case IT_Random:
468 ✗ m_nextTransitionType = (ImageTransitionType)(rand_r(&m_typeSeed) % ((int)IT_MAX - 1) + 1);
469 ✗ m_imageReady = true;
470 ✗ lock.lock();
471 ✗ continue;
472 break;
473
474 4 case IT_SlideUp:
475
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 FBDrawSlideUp();
476 4 break;
477
478 4 case IT_SlideDown:
479
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 FBDrawSlideDown();
480 4 break;
481
482 4 case IT_SlideLeft:
483
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 FBDrawSlideLeft();
484 4 break;
485
486 4 case IT_SlideRight:
487
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 FBDrawSlideRight();
488 4 break;
489
490 4 case IT_WipeUp:
491
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 FBDrawWipeUp();
492 4 break;
493
494 7 case IT_WipeDown:
495
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7 FBDrawWipeDown();
496 7 break;
497
498 4 case IT_WipeLeft:
499
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 FBDrawWipeLeft();
500 4 break;
501
502 4 case IT_WipeRight:
503
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 FBDrawWipeRight();
504 4 break;
505
506 4 case IT_WipeToHCenter:
507
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 FBDrawWipeToHCenter();
508 4 break;
509
510 4 case IT_WipeFromHCenter:
511
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 FBDrawWipeFromHCenter();
512 4 break;
513
514 4 case IT_HorzBlindsOpen:
515
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 FBDrawHorzBlindsOpen();
516 4 break;
517
518 4 case IT_HorzBlindsClose:
519
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 FBDrawHorzBlindsClose();
520 4 break;
521
522 4 case IT_Mosaic:
523
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 FBDrawMosaic();
524 4 break;
525
526 1709 default:
527
1/2
✓ Branch 0 taken 1709 times.
✗ Branch 1 not taken.
1709 FBDrawNormal();
528 1709 break;
529 }
530
531
1/2
✓ Branch 0 taken 1764 times.
✗ Branch 1 not taken.
1764 lock.lock();
532
2/2
✓ Branch 0 taken 55 times.
✓ Branch 1 taken 1709 times.
1764 if (m_nextTransitionType != IT_Normal) {
533 // Whatever a transition's step sizes left undrawn, it ends
534 // with the whole image on screen.
535 55 memcpy(FB_CURRENT_PAGE_PTR, m_outputBuffer, m_pageSize);
536
1/2
✓ Branch 0 taken 55 times.
✗ Branch 1 not taken.
55 SyncDisplay();
537 }
538 1764 continue;
539 1764 }
540
541 // FBStartDraw() notifies without m_bufferLock, which a transition holds
542 // for its whole length, so a notify can land between the check above
543 // and the wait; the timeout bounds how long such an image waits.
544
1/2
✓ Branch 0 taken 3246 times.
✗ Branch 1 not taken.
3285 m_drawSignal.wait_for(lock, std::chrono::milliseconds(50),
545
4/4
✓ Branch 0 taken 6498 times.
✓ Branch 1 taken 33 times.
✓ Branch 2 taken 1203 times.
✓ Branch 3 taken 5295 times.
6531 [this] { return !m_runLoop || m_imageReady; });
546 }
547 33 }
548
549 2489 void FrameBuffer::NextPage(bool producer) {
550
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2488 times.
2489 if (m_pages == 1)
551 1 return;
552
553 // LogInfo(VB_CHANNELOUT, "%s - %d - NextPage() for %s\n", m_device.c_str(), m_cPage, producer ? "producer" : "consumer");
554
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2488 times.
2488 if (producer)
555 ✗ m_pPage = (m_pPage + 1) % m_pages;
556 else
557 2488 m_cPage = (m_cPage + 1) % m_pages;
558 }
559
560 /*
561 *
562 */
563 1709 void FrameBuffer::FBDrawNormal(void) {
564 1709 m_bufferLock.lock();
565
566 1709 NextPage();
567 1709 memcpy(FB_CURRENT_PAGE_PTR, m_outputBuffer, m_pageSize);
568 1709 SyncDisplay(true);
569 1709 m_bufferLock.unlock();
570 1709 }
571
572 /*
573 *
574 */
575 4 void FrameBuffer::FBDrawSlideUp(void) {
576 4 int rEach = 2;
577 4 int sleepTime = 800 * 1000 * rEach / m_height;
578
579
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 if ((m_height % 4) == 0)
580 2 rEach = 4;
581
582 4 m_bufferLock.lock();
583
2/2
✓ Branch 0 taken 48 times.
✓ Branch 1 taken 4 times.
52 for (int i = m_height - rEach; i >= 0;) {
584 48 memcpy(FB_CURRENT_PAGE_PTR + (m_rowStride * i), m_outputBuffer, m_rowStride * (m_height - i));
585 48 SyncDisplay();
586
587 48 usleep(sleepTime);
588
589 48 i -= rEach;
590 }
591 4 m_bufferLock.unlock();
592 4 }
593
594 /*
595 *
596 */
597 4 void FrameBuffer::FBDrawSlideDown(void) {
598 4 int rEach = 2;
599 4 int sleepTime = 800 * 1000 * rEach / m_height;
600
601
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 if ((m_height % 4) == 0)
602 2 rEach = 4;
603
604 4 m_bufferLock.lock();
605
2/2
✓ Branch 0 taken 48 times.
✓ Branch 1 taken 4 times.
52 for (int i = rEach; i <= m_height;) {
606 48 memcpy(FB_CURRENT_PAGE_PTR, m_outputBuffer + (m_rowStride * (m_height - i)), m_rowStride * i);
607 48 SyncDisplay();
608
609 48 usleep(sleepTime);
610
611 48 i += rEach;
612 }
613 4 m_bufferLock.unlock();
614 4 }
615
616 /*
617 *
618 */
619 4 void FrameBuffer::FBDrawSlideLeft(void) {
620 4 int colsEach = 2;
621 4 int sleepTime = 800 * 1000 * colsEach / m_width;
622
623
2/2
✓ Branch 0 taken 112 times.
✓ Branch 1 taken 4 times.
116 for (int x = m_width - colsEach; x >= 0; x -= colsEach) {
624 112 DrawSquare(x, 0, m_width - x, m_height, 0, 0);
625 112 SyncDisplay();
626
627 112 usleep(sleepTime);
628 }
629 4 }
630
631 /*
632 *
633 */
634 4 void FrameBuffer::FBDrawSlideRight(void) {
635 4 int colsEach = 2;
636 4 int sleepTime = 800 * 1000 * colsEach / m_width;
637
638
2/2
✓ Branch 0 taken 112 times.
✓ Branch 1 taken 4 times.
116 for (int x = 2; x <= m_width; x += colsEach) {
639 112 DrawSquare(0, 0, x, m_height, m_width - x, 0);
640 112 SyncDisplay();
641
642 112 usleep(sleepTime);
643 }
644 4 }
645
646 /*
647 *
648 */
649 4 void FrameBuffer::FBDrawWipeUp(void) {
650 4 int rEach = 2;
651
652
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 if ((m_height % 4) == 0)
653 2 rEach = 4;
654
655 4 int sleepTime = 800 * 1000 * rEach / m_height;
656
657 4 m_bufferLock.lock();
658
2/2
✓ Branch 0 taken 48 times.
✓ Branch 1 taken 4 times.
52 for (int i = m_height - rEach; i >= 0;) {
659 48 CopyRows(i, rEach);
660 48 SyncDisplay();
661
662 48 usleep(sleepTime);
663
664 48 i -= rEach;
665 }
666 4 m_bufferLock.unlock();
667 4 }
668
669 /*
670 *
671 */
672 7 void FrameBuffer::FBDrawWipeDown(void) {
673 7 int rEach = 2;
674
675
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 2 times.
7 if ((m_height % 4) == 0)
676 5 rEach = 4;
677
678 7 int sleepTime = 800 * 1000 * rEach / m_height;
679
680 7 m_bufferLock.lock();
681
2/2
✓ Branch 0 taken 74 times.
✓ Branch 1 taken 7 times.
81 for (int i = 0; i < m_height;) {
682 74 CopyRows(i, rEach);
683 74 SyncDisplay();
684
685 74 usleep(sleepTime);
686
687 74 i += rEach;
688 }
689 7 m_bufferLock.unlock();
690 7 }
691
692 /*
693 *
694 */
695 4 void FrameBuffer::FBDrawWipeLeft(void) {
696 4 int BPP = m_bpp / 8;
697
698 4 m_bufferLock.lock();
699
700
2/2
✓ Branch 0 taken 224 times.
✓ Branch 1 taken 4 times.
228 for (int x = m_width - 1; x >= 0; x--) {
701
2/2
✓ Branch 0 taken 7264 times.
✓ Branch 1 taken 224 times.
7488 for (int y = 0; y < m_height; y++) {
702 7264 memcpy(FB_CURRENT_PAGE_PTR + (m_rowStride * y) + (x * BPP),
703 7264 m_outputBuffer + (m_rowStride * y) + (x * BPP), BPP);
704 }
705 224 SyncDisplay();
706 }
707
708 4 m_bufferLock.unlock();
709 4 }
710
711 /*
712 *
713 */
714 4 void FrameBuffer::FBDrawWipeRight(void) {
715 4 int BPP = m_bpp / 8;
716
717 4 m_bufferLock.lock();
718
719
2/2
✓ Branch 0 taken 224 times.
✓ Branch 1 taken 4 times.
228 for (int x = 0; x < m_width; x++) {
720
2/2
✓ Branch 0 taken 7264 times.
✓ Branch 1 taken 224 times.
7488 for (int y = 0; y < m_height; y++) {
721 7264 memcpy(FB_CURRENT_PAGE_PTR + (m_rowStride * y) + (x * BPP),
722 7264 m_outputBuffer + (m_rowStride * y) + (x * BPP), BPP);
723 }
724 224 SyncDisplay();
725 }
726
727 4 m_bufferLock.unlock();
728 4 }
729
730 /*
731 *
732 */
733 4 void FrameBuffer::FBDrawWipeToHCenter(void) {
734 4 int rEach = 2;
735 4 int mid = m_height / 2;
736
737
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 if ((m_height % 4) == 0)
738 2 rEach = 4;
739
740 4 int sleepTime = 800 * 1000 * rEach / m_height * 2;
741
742 4 m_bufferLock.lock();
743
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 4 times.
28 for (int i = 0; i < mid; i += rEach) {
744 24 CopyRows(i, rEach);
745 24 CopyRows(m_height - i - rEach, rEach);
746 24 SyncDisplay();
747
748 24 usleep(sleepTime);
749 }
750 4 m_bufferLock.unlock();
751 4 }
752
753 /*
754 *
755 */
756 4 void FrameBuffer::FBDrawWipeFromHCenter(void) {
757 4 int mid = m_height / 2;
758 4 int rowsEachUpdate = 2;
759 4 int sleepTime = 800 * 1000 * rowsEachUpdate / m_height * 2;
760
761 4 m_bufferLock.lock();
762
763 4 int t = mid - 2;
764
2/2
✓ Branch 0 taken 34 times.
✓ Branch 1 taken 4 times.
38 for (int b = mid; b < m_height; b += rowsEachUpdate, t -= rowsEachUpdate) {
765 34 CopyRows(b, rowsEachUpdate);
766 34 CopyRows(t, rowsEachUpdate);
767
768 34 SyncDisplay();
769
770 34 usleep(sleepTime);
771 }
772
773 4 m_bufferLock.unlock();
774 4 }
775
776 /*
777 *
778 */
779 4 void FrameBuffer::FBDrawHorzBlindsOpen(void) {
780 4 int rowsEachUpdate = 2;
781 4 int blindSize = 32;
782 4 int sleepTime = 800 * 1000 * rowsEachUpdate / blindSize;
783
784 4 m_bufferLock.lock();
785
2/2
✓ Branch 0 taken 64 times.
✓ Branch 1 taken 4 times.
68 for (int i = blindSize - rowsEachUpdate; i >= 0; i -= rowsEachUpdate) {
786
2/2
✓ Branch 0 taken 66 times.
✓ Branch 1 taken 64 times.
130 for (int y = i; y < m_height; y += blindSize) {
787 66 CopyRows(y, rowsEachUpdate);
788 }
789
790 64 SyncDisplay();
791
792 64 usleep(sleepTime);
793 }
794 4 m_bufferLock.unlock();
795 4 }
796
797 /*
798 *
799 */
800 4 void FrameBuffer::FBDrawHorzBlindsClose(void) {
801 4 int rowsEachUpdate = 2;
802 4 int blindSize = 32;
803 4 int sleepTime = 800 * 1000 * rowsEachUpdate / blindSize;
804
805 4 m_bufferLock.lock();
806
2/2
✓ Branch 0 taken 64 times.
✓ Branch 1 taken 4 times.
68 for (int i = 0; i < blindSize; i += rowsEachUpdate) {
807
2/2
✓ Branch 0 taken 66 times.
✓ Branch 1 taken 64 times.
130 for (int y = i; y < m_height; y += blindSize) {
808 66 CopyRows(y, rowsEachUpdate);
809 }
810
811 64 SyncDisplay();
812
813 64 usleep(sleepTime);
814 }
815 4 m_bufferLock.unlock();
816 4 }
817
818 /*
819 *
820 */
821 4 void FrameBuffer::FBDrawMosaic(void) {
822 4 int squareSize = 32;
823 4 int xSquares = (m_width + squareSize - 1) / squareSize;
824 4 int ySquares = (m_height + squareSize - 1) / squareSize;
825 4 std::vector<std::vector<int>> squares;
826
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
4 squares.resize(ySquares, std::vector<int>(xSquares, 0));
827
828 4 int count = xSquares * ySquares;
829 4 int sleepTime = 800 * 1000 / count;
830
831
832
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 m_bufferLock.lock();
833
834
2/2
✓ Branch 0 taken 120 times.
✓ Branch 1 taken 4 times.
124 for (int i = 0; i < (count * 10); i++) {
835 120 int square = rand_r(&m_typeSeed) % count;
836 120 int x = square % xSquares;
837 120 int y = square / xSquares;
838
839
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 108 times.
120 if (!squares[y][x]) {
840
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 DrawSquare(x * squareSize, y * squareSize, squareSize, squareSize);
841 12 squares[y][x] = 1;
842
843
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 SyncDisplay();
844
845
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 usleep(sleepTime);
846 }
847 }
848
849
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 4 times.
10 for (int y = 0; y < ySquares; y++) {
850
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 6 times.
18 for (int x = 0; x < xSquares; x++) {
851
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 if (!squares[y][x]) {
852 ✗ DrawSquare(x * squareSize, y * squareSize, squareSize,
853 squareSize);
854 ✗ squares[y][x] = 1;
855
856 ✗ SyncDisplay();
857
858 ✗ usleep(sleepTime);
859 }
860 }
861 }
862
863 4 m_bufferLock.unlock();
864 4 }
865
866 /////////////////////////////////////////////////////////////////////////////
867
868 /*
869 *
870 */
871 236 void FrameBuffer::DrawSquare(int dx, int dy, int w, int h, int sx, int sy) {
872
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 224 times.
236 if (sx == -1)
873 12 sx = dx;
874
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 224 times.
236 if (sy == -1)
875 12 sy = dy;
876 // Both rectangles stay on the page: a square past the right or bottom
877 // edge ran into the next row, and off the end of the buffers.
878
1/2
✓ Branch 0 taken 236 times.
✗ Branch 1 not taken.
236 w = std::min({ w, m_width - dx, m_width - sx });
879
1/2
✓ Branch 0 taken 236 times.
✗ Branch 1 not taken.
236 h = std::min({ h, m_height - dy, m_height - sy });
880
6/12
✓ Branch 0 taken 236 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 236 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 236 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 236 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 236 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 236 times.
236 if (dx < 0 || dy < 0 || sx < 0 || sy < 0 || w <= 0 || h <= 0)
881 ✗ return;
882
883 236 int dRowOffset = dx * m_bpp / 8;
884 236 int sRowOffset = sx * m_bpp / 8;
885 236 int bytesWide = w * m_bpp / 8;
886
887
2/2
✓ Branch 0 taken 7524 times.
✓ Branch 1 taken 236 times.
7760 for (int i = 0; i < h; i++) {
888 7524 memcpy(FB_CURRENT_PAGE_PTR + (m_rowStride * (dy + i)) + dRowOffset,
889 7524 m_outputBuffer + (m_rowStride * (sy + i)) + sRowOffset, bytesWide);
890 }
891 }
892
893 // Rows [y, y + rows) of the new image onto the page, clipped to it: the
894 // row-band transitions step in fixed sizes that need not divide the height.
895 370 void FrameBuffer::CopyRows(int y, int rows) {
896
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 368 times.
370 if (y < 0) {
897 2 rows += y;
898 2 y = 0;
899 }
900 370 rows = std::min(rows, m_height - y);
901
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 368 times.
370 if (rows <= 0)
902 2 return;
903 368 memcpy(FB_CURRENT_PAGE_PTR + (m_rowStride * y), m_outputBuffer + (m_rowStride * y), m_rowStride * rows);
904 }
905
906 13 void FrameBuffer::ClearAllPages() {
907
2/2
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 13 times.
52 for (int x = 0; x < 3; x++) {
908
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 13 times.
39 if (m_pageBuffers[x]) {
909 26 memset(m_pageBuffers[x], 0, m_pageSize);
910 }
911 }
912 13 }
913
914 ✗ void FrameBuffer::Clear() {
915 ✗ memset(FB_CURRENT_PAGE_PTR, 0, m_pageSize);
916 ✗ }
917
918 845 uint8_t* FrameBuffer::BufferPage(int page) {
919 845 return m_pageBuffers[page];
920 }
921