FreeRDP
Loading...
Searching...
No Matches
shadow_rfx_dirty.c
1
19#include <freerdp/config.h>
20
21#include <winpr/crt.h>
22#include <winpr/assert.h>
23
24#include <freerdp/log.h>
25
26#include "shadow.h"
27#include "shadow_encoder.h"
28#include "shadow_rfx_dirty.h"
29
30#define TAG SERVER_TAG("shadow.rfx_dirty")
31
32#define RFX_TILE_SIZE 64ul
33
34/* The RFX context is fixed to BGRX32 (validated by shadow_client_send_rfx) */
35#define RFX_BYTES_PER_PIXEL 4ul
36
37/* Telemetry counters shared by all client send threads (observation only,
38 * racy reads are tolerated) */
39static UINT64 sFrames = 0;
40static UINT64 sTilesSeen = 0;
41static UINT64 sTilesSent = 0;
42static UINT64 sEmptyFrames = 0;
43static UINT64 sWireBytes = 0;
44
45void shadow_rfx_invalidate_snapshot(rdpShadowEncoder* encoder)
46{
47 WINPR_ASSERT(encoder);
48
49 free(encoder->gfxSnapshot);
50 encoder->gfxSnapshot = nullptr;
51 free(encoder->gfxTileEncoded);
52 encoder->gfxTileEncoded = nullptr;
53 encoder->gfxSnapshotWidth = 0;
54 encoder->gfxSnapshotHeight = 0;
55 encoder->gfxSnapshotStep = 0;
56 encoder->gfxSnapshotValid = FALSE;
57}
58
59static BOOL shadow_rfx_snapshot_ensure(rdpShadowEncoder* encoder, UINT16 width, UINT16 height,
60 UINT32 step)
61{
62 WINPR_ASSERT(encoder);
63
64 if (encoder->gfxSnapshotValid && (encoder->gfxSnapshotWidth == width) &&
65 (encoder->gfxSnapshotHeight == height) && (encoder->gfxSnapshotStep == step))
66 return TRUE;
67
68 shadow_rfx_invalidate_snapshot(encoder);
69
70 if (width == 0 || height == 0 || step == 0)
71 {
72 WLog_WARN(TAG, "invalid snapshot geometry (%" PRIu16 "x%" PRIu16 " step=%" PRIu32 ")",
73 width, height, step);
74 return FALSE;
75 }
76 if (step > SIZE_MAX / height)
77 {
78 WLog_WARN(TAG, "snapshot size overflows SIZE_MAX (%" PRIu32 "x%" PRIu16 ")", step, height);
79 return FALSE;
80 }
81
82 const UINT32 gridW = (width + RFX_TILE_SIZE - 1) / RFX_TILE_SIZE;
83 const UINT32 gridH = (height + RFX_TILE_SIZE - 1) / RFX_TILE_SIZE;
84
85 encoder->gfxSnapshot = calloc(height, step);
86 encoder->gfxTileEncoded = calloc(gridW, gridH * sizeof(BOOL));
87
88 if (!encoder->gfxSnapshot || !encoder->gfxTileEncoded)
89 {
90 WLog_WARN(TAG, "snapshot alloc failed (%ux%u step=%" PRIu32 ")", width, height, step);
91 shadow_rfx_invalidate_snapshot(encoder);
92 return FALSE;
93 }
94
95 encoder->gfxSnapshotWidth = width;
96 encoder->gfxSnapshotHeight = height;
97 encoder->gfxSnapshotStep = step;
98 encoder->gfxSnapshotValid = TRUE;
99 return TRUE;
100}
101
102int shadow_rfx_plan_tiles(rdpShadowEncoder* encoder, const BYTE* pSrcData, UINT32 nSrcStep,
103 const RDPGFX_SURFACE_COMMAND* cmd, UINT16 srcWidth, UINT16 srcHeight,
104 RFX_RECT** outRects, UINT16* outNumRects)
105{
106 WINPR_ASSERT(encoder);
107 WINPR_ASSERT(pSrcData);
108 WINPR_ASSERT(cmd);
109 WINPR_ASSERT(outRects);
110 WINPR_ASSERT(outNumRects);
111 WINPR_ASSERT(nSrcStep > 0);
112
113 *outRects = nullptr;
114 *outNumRects = 0;
115
116 UINT32 x1 = cmd->right;
117 UINT32 y1 = cmd->bottom;
118 /* the rect must not exceed the source image (surface) bounds */
119 if (x1 > srcWidth)
120 x1 = srcWidth;
121 if (y1 > srcHeight)
122 y1 = srcHeight;
123
124 if ((cmd->left >= x1) || (cmd->top >= y1))
125 return 0; /* empty rect: no tiles */
126
127 const UINT32 gridW = (srcWidth + RFX_TILE_SIZE - 1) / RFX_TILE_SIZE;
128 const UINT32 gridH = (srcHeight + RFX_TILE_SIZE - 1) / RFX_TILE_SIZE;
129 const UINT32 startTx = cmd->left / RFX_TILE_SIZE;
130 const UINT32 endTx = (x1 - 1) / RFX_TILE_SIZE;
131 const UINT32 startTy = cmd->top / RFX_TILE_SIZE;
132 const UINT32 endTy = (y1 - 1) / RFX_TILE_SIZE;
133
134 RFX_RECT* rects = calloc(gridW, gridH * sizeof(RFX_RECT));
135 if (!rects)
136 return -1;
137
138 const BOOL snapOk = shadow_rfx_snapshot_ensure(encoder, srcWidth, srcHeight, nSrcStep);
139 UINT16 numRects = 0;
140
141 /* per-tile dirty flags within one tile row (reused per band) */
142 BOOL* bandChanged = calloc(gridW, sizeof(BOOL));
143 if (!bandChanged)
144 {
145 free(rects);
146 return -1;
147 }
148
149 for (UINT32 ty = startTy; ty <= endTy; ty++)
150 {
151 const UINT32 tileY = ty * RFX_TILE_SIZE;
152 UINT32 tileH = RFX_TILE_SIZE;
153 if ((tileY + RFX_TILE_SIZE) > srcHeight)
154 tileH = srcHeight - tileY;
155
156 memset(bandChanged, 0, gridW * sizeof(BOOL));
157
158 /* band level fast path: when all rows covered by this tile row are
159 * identical to the snapshot, every tile in the row is unchanged
160 * (idle/small dirty frames then only need tileH row memcmps instead
161 * of one memcmp per tile row); fall back to per tile compares for
162 * rows that differ */
163 BOOL bandDirty = FALSE;
164
165 if (snapOk)
166 {
167 for (UINT32 r = 0; r < tileH; r++)
168 {
169 const size_t ofs =
170 ((size_t)(tileY + r)) * nSrcStep + ((size_t)cmd->left * RFX_BYTES_PER_PIXEL);
171 const UINT32 spanBytes = (x1 - cmd->left) * RFX_BYTES_PER_PIXEL;
172 if (memcmp(&pSrcData[ofs], &encoder->gfxSnapshot[ofs], spanBytes) != 0)
173 {
174 bandDirty = TRUE;
175 break;
176 }
177 }
178 }
179
180 for (UINT32 tx = startTx; tx <= endTx; tx++)
181 {
182 const UINT32 tileX = tx * RFX_TILE_SIZE;
183 UINT32 tileW = RFX_TILE_SIZE;
184 if ((tileX + RFX_TILE_SIZE) > srcWidth)
185 tileW = srcWidth - tileX;
186
187 BOOL changed = TRUE;
188
189 if (snapOk && encoder->gfxTileEncoded[ty * gridW + tx])
190 {
191 if (bandDirty)
192 {
193 changed = FALSE;
194 for (UINT32 r = 0; r < tileH; r++)
195 {
196 const size_t ofs = ((size_t)(tileY + r)) * nSrcStep +
197 ((size_t)tileX * RFX_BYTES_PER_PIXEL);
198 if (memcmp(&pSrcData[ofs], &encoder->gfxSnapshot[ofs],
199 (size_t)tileW * RFX_BYTES_PER_PIXEL) != 0)
200 {
201 changed = TRUE;
202 break;
203 }
204 }
205 }
206 else
207 {
208 changed = FALSE; /* whole band identical to snapshot */
209 }
210 }
211
212 if (changed)
213 {
214 rects[numRects].x = WINPR_ASSERTING_INT_CAST(UINT16, tileX);
215 rects[numRects].y = WINPR_ASSERTING_INT_CAST(UINT16, tileY);
216 rects[numRects].width = WINPR_ASSERTING_INT_CAST(UINT16, tileW);
217 rects[numRects].height = WINPR_ASSERTING_INT_CAST(UINT16, tileH);
218 numRects++;
219 }
220 }
221 }
222
223 free(bandChanged);
224
225 sTilesSeen += (UINT64)(endTx - startTx + 1) * (endTy - startTy + 1);
226 if (numRects == 0)
227 sEmptyFrames += 1;
228
229 *outRects = rects;
230 *outNumRects = numRects;
231 return 0;
232}
233
234void shadow_rfx_commit_tiles(rdpShadowEncoder* encoder, const BYTE* pSrcData, UINT32 nSrcStep,
235 const RFX_RECT* rects, UINT16 numRects, UINT32 wireBytes)
236{
237 WINPR_ASSERT(encoder);
238 WINPR_ASSERT(pSrcData);
239 WINPR_ASSERT(rects || (numRects == 0));
240 WINPR_ASSERT(nSrcStep > 0);
241
242 if (!encoder->gfxSnapshotValid)
243 return;
244
245 const UINT32 gridW = (encoder->gfxSnapshotWidth + RFX_TILE_SIZE - 1) / RFX_TILE_SIZE;
246 const UINT32 gridH = (encoder->gfxSnapshotHeight + RFX_TILE_SIZE - 1) / RFX_TILE_SIZE;
247
248 for (UINT16 i = 0; i < numRects; i++)
249 {
250 const UINT32 tileX = rects[i].x;
251 const UINT32 tileY = rects[i].y;
252 const UINT32 tx = tileX / RFX_TILE_SIZE;
253 const UINT32 ty = tileY / RFX_TILE_SIZE;
254
255 if ((tx < gridW) && (ty < gridH))
256 {
257 for (UINT32 r = 0; r < rects[i].height; r++)
258 {
259 const size_t ofs =
260 ((size_t)(tileY + r)) * nSrcStep + ((size_t)tileX * RFX_BYTES_PER_PIXEL);
261 CopyMemory(&encoder->gfxSnapshot[ofs], &pSrcData[ofs],
262 rects[i].width * RFX_BYTES_PER_PIXEL);
263 }
264
265 encoder->gfxTileEncoded[ty * gridW + tx] = TRUE;
266 }
267 }
268
269 sFrames += 1;
270 sTilesSent += numRects;
271 sWireBytes += wireBytes;
272
273 if ((sFrames % 120) == 0)
274 {
275 WLog_DBG(TAG,
276 "stats: frames=%" PRIu64 " tiles_seen=%" PRIu64 " tiles_sent=%" PRIu64
277 " tiles_skipped=%" PRIu64 " empty_frames=%" PRIu64 " wire_bytes=%" PRIu64,
278 sFrames, sTilesSeen, sTilesSent, sTilesSeen - sTilesSent, sEmptyFrames,
279 sWireBytes);
280 }
281}
Definition rfx.h:44