1#define WIN32_LEAN_AND_MEAN
2#define COBJMACROS
3#define UNICODE
4#define _UNICODE
5#include <windows.h>
6#include <d3d11.h>
7#include "capture.h"
8
9struct size { INT32 width, height; };
10struct object { void **vtable; };
11/* WinRT ABI calls stay here so the Win7 executable imports no newer runtime. */
12#define METHOD(o, slot, signature) ((signature)((struct object *)(o))->vtable[slot])
13typedef HRESULT (WINAPI *query_fn)(void *, const GUID *, void **);
14typedef ULONG (WINAPI *release_fn)(void *);
15typedef HRESULT (WINAPI *close_fn)(void *);
16typedef HRESULT (WINAPI *object_fn)(void *, void **);
17typedef HRESULT (WINAPI *size_fn)(void *, struct size *);
18typedef HRESULT (WINAPI *window_fn)(void *, HWND, const GUID *, void **);
19typedef HRESULT (WINAPI *pool_fn)(void *, void *, int, int, struct size, void **);
20typedef HRESULT (WINAPI *recreate_fn)(void *, void *, int, int, struct size);
21typedef HRESULT (WINAPI *session_fn)(void *, void *, void **);
22typedef HRESULT (WINAPI *bool_fn)(void *, BOOLEAN);
23typedef HRESULT (WINAPI *initialize_fn)(int);
24typedef void (WINAPI *uninitialize_fn)(void);
25typedef HRESULT (WINAPI *string_fn)(PCWSTR, UINT32, void **);
26typedef HRESULT (WINAPI *delete_string_fn)(void *);
27typedef HRESULT (WINAPI *factory_fn)(void *, const GUID *, void **);
28typedef HRESULT (WINAPI *device_fn)(IDXGIDevice *, void **);
29typedef HRESULT (WINAPI *attribute_fn)(HWND, DWORD, PVOID, DWORD);
30
31static const GUID item_iid = {0x79c3f95b,0x31f7,0x4ec2,{0xa4,0x64,0x63,0x2e,0xf5,0xd3,0x07,0x60}};
32static const GUID interop_iid = {0x3628e81b,0x3cac,0x4c60,{0xb7,0xf4,0x23,0xce,0x0e,0x0c,0x33,0x56}};
33static const GUID pool_iid = {0x589b103f,0x6bbc,0x5df5,{0xa9,0x91,0x02,0xe2,0x8b,0x3b,0x66,0xd5}};
34static const GUID cursor_iid = {0x2c39ae40,0x7d2e,0x5044,{0x80,0x4e,0x8b,0x67,0x99,0xd4,0xcf,0x9e}};
35static const GUID close_iid = {0x30d5a829,0x7fa4,0x4026,{0x83,0xbb,0xd7,0x5b,0xae,0x4e,0xa9,0x9e}};
36static const GUID dxgi_iid = {0x54ec77fa,0x1377,0x44e6,{0x8c,0x32,0x88,0xfd,0x5f,0x44,0xc8,0x4c}};
37static const GUID access_iid = {0xa9b3d012,0x3df2,0x4ee3,{0xb8,0xd1,0x86,0x95,0xf4,0x57,0xd3,0xc1}};
38static const GUID texture_iid = {0x6f15aaf2,0xd208,0x4e89,{0x9a,0xb4,0x48,0x95,0x35,0xd3,0x4f,0x9c}};
39
40struct shell_paint {
41 HWND hwnd;
42 HANDLE thread;
43 struct shell_paint *next;
44 HDC dc;
45 HBITMAP dib, selected;
46 uint8_t *bits;
47 size_t pixels;
48 DWORD started;
49};
50
51struct window {
52 HWND hwnd;
53 uint32_t id, width, height;
54 struct size pool_size;
55 void *pool, *session;
56 ID3D11Texture2D *staging;
57 uint8_t *before;
58 struct shell_paint *paint;
59 int initial, seen, shell;
60 DWORD waiting;
61};
62static struct window windows[SG_MAX_WINDOWS], desktop;
63static RECT bounds[SG_MAX_WINDOWS], capture_bounds[SG_MAX_WINDOWS];
64static uint32_t layout[SG_WINDOWS_WORDS + SG_MAX_WINDOWS * SG_WINDOW_WORDS];
65static uint32_t prior[SG_WINDOWS_WORDS + SG_MAX_WINDOWS * SG_WINDOW_WORDS];
66static uint32_t count, next_id;
67static size_t allocated, painted;
68static struct shell_paint *retired_paints;
69static int invalid;
70static HANDLE heap;
71/* Capture workers outlive session disposal; retain their DLLs until process exit. */
72static HMODULE runtime, d3d, dwm, composition;
73static ID3D11Device *device;
74static ID3D11DeviceContext *context;
75static void *capture_device, *interop, *pool_factory;
76static string_fn create_string;
77static delete_string_fn delete_string;
78static factory_fn get_factory;
79static uninitialize_fn uninitialize;
80static attribute_fn attribute;
81static DWORD retry, desktop_retry;
82
83static void release(void *object) {
84 if (object) METHOD(object, 2, release_fn)(object);
85}
86static void close_object(void *object) {
87 void *closable = NULL;
88 if (object && SUCCEEDED(METHOD(object, 0, query_fn)(object, &close_iid, &closable))) {
89 METHOD(closable, 6, close_fn)(closable);
90 release(closable);
91 }
92 release(object);
93}
94static void free_paint(struct shell_paint *paint) {
95 if (paint->thread) CloseHandle(paint->thread);
96 if (paint->selected) SelectObject(paint->dc, paint->selected);
97 if (paint->dib) DeleteObject(paint->dib);
98 if (paint->dc) DeleteDC(paint->dc);
99 painted -= paint->pixels;
100 HeapFree(heap, 0, paint);
101}
102static DWORD WINAPI paint_shell(void *data) {
103 struct shell_paint *paint = data;
104 BOOL success = PrintWindow(paint->hwnd, paint->dc, 2);
105 GdiFlush();
106 return success;
107}
108static void reap_paints(void) {
109 struct shell_paint **link = &retired_paints;
110 while (*link) {
111 struct shell_paint *paint = *link;
112 if (WaitForSingleObject(paint->thread, 0) == WAIT_OBJECT_0) {
113 *link = paint->next; free_paint(paint);
114 } else link = &paint->next;
115 }
116}
117static void detach_paint(struct window *window) {
118 struct shell_paint *paint = window->paint;
119 if (!paint) return;
120 window->paint = NULL;
121 if (!paint->thread || WaitForSingleObject(paint->thread, 0) == WAIT_OBJECT_0) free_paint(paint);
122 else { paint->next = retired_paints; retired_paints = paint; }
123}
124
125static void drop(struct window *window) {
126 close_object(window->session); close_object(window->pool);
127 if (window->staging) ID3D11Texture2D_Release(window->staging);
128 detach_paint(window);
129 if (window->before) { allocated -= (size_t)window->width * window->height; HeapFree(heap, 0, window->before); }
130 window->hwnd = NULL; window->session = window->pool = NULL;
131 window->staging = NULL; window->before = NULL;
132 window->width = window->height = 0;
133}
134static int factory(PCWSTR name, const GUID *iid, void **result) {
135 void *string = NULL;
136 UINT32 length = 0;
137 while (name[length]) ++length;
138 if (FAILED(create_string(name, length, &string))) return 0;
139 HRESULT status = get_factory(string, iid, result);
140 delete_string(string);
141 HMODULE module;
142 return SUCCEEDED(status) && GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_PIN,
143 (LPCWSTR)((struct object *)*result)->vtable[0], &module);
144}
145
146void sg_windows_stop(void) {
147 reap_paints();
148 uint32_t i;
149 for (i = 0; i < SG_MAX_WINDOWS; ++i) drop(windows + i);
150 count = 0;
151 retry = 0;
152 for (i = 0; i < sizeof(prior) / sizeof(prior[0]); ++i) prior[i] = 0;
153}
154
155void sg_capture_stop(void) {
156 sg_windows_stop();
157 drop(&desktop);
158 desktop_retry = 0;
159 release(pool_factory); release(interop); release(capture_device);
160 if (context) ID3D11DeviceContext_Release(context);
161 if (device) ID3D11Device_Release(device);
162 pool_factory = interop = capture_device = NULL; context = NULL; device = NULL;
163 if (uninitialize) uninitialize();
164 uninitialize = NULL;
165}
166
167int sg_capture_start(void) {
168 initialize_fn initialize;
169 typedef HRESULT (WINAPI *create_fn)(IDXGIAdapter *, D3D_DRIVER_TYPE, HMODULE, UINT, const D3D_FEATURE_LEVEL *, UINT, UINT, ID3D11Device **, D3D_FEATURE_LEVEL *, ID3D11DeviceContext **);
170 create_fn create;
171 device_fn wrap;
172 IDXGIDevice *dxgi = NULL;
173 if (device) return 1;
174 heap = GetProcessHeap();
175 if (!runtime) runtime = LoadLibraryW(L"combase.dll");
176 if (!d3d) d3d = LoadLibraryW(L"d3d11.dll");
177 if (!dwm) dwm = LoadLibraryW(L"dwmapi.dll");
178 if (!composition) composition = LoadLibraryW(L"dcomp.dll");
179 if (!runtime || !d3d || !dwm || !composition) goto failed;
180 initialize = (initialize_fn)GetProcAddress(runtime, "RoInitialize");
181 create_string = (string_fn)GetProcAddress(runtime, "WindowsCreateString");
182 delete_string = (delete_string_fn)GetProcAddress(runtime, "WindowsDeleteString");
183 get_factory = (factory_fn)GetProcAddress(runtime, "RoGetActivationFactory");
184 create = (create_fn)GetProcAddress(d3d, "D3D11CreateDevice");
185 wrap = (device_fn)GetProcAddress(d3d, "CreateDirect3D11DeviceFromDXGIDevice");
186 attribute = (attribute_fn)GetProcAddress(dwm, "DwmGetWindowAttribute");
187 if (!initialize || !create_string || !delete_string || !get_factory || !create || !wrap || !attribute || FAILED(initialize(1))) goto failed;
188 uninitialize = (uninitialize_fn)GetProcAddress(runtime, "RoUninitialize");
189 if (!factory(L"Windows.Graphics.Capture.GraphicsCaptureItem", &interop_iid, &interop) ||
190 !factory(L"Windows.Graphics.Capture.Direct3D11CaptureFramePool", &pool_iid, &pool_factory)) goto failed;
191 if (FAILED(create(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, D3D11_CREATE_DEVICE_BGRA_SUPPORT, NULL, 0, D3D11_SDK_VERSION, &device, NULL, &context)) &&
192 FAILED(create(NULL, D3D_DRIVER_TYPE_WARP, NULL, D3D11_CREATE_DEVICE_BGRA_SUPPORT, NULL, 0, D3D11_SDK_VERSION, &device, NULL, &context))) goto failed;
193 if (FAILED(ID3D11Device_QueryInterface(device, &dxgi_iid, (void **)&dxgi))) goto failed;
194 HRESULT status = wrap(dxgi, &capture_device);
195 IDXGIDevice_Release(dxgi);
196 if (FAILED(status)) goto failed;
197 return 1;
198failed:
199 sg_capture_stop();
200 return 0;
201}
202
203static BOOL CALLBACK enumerate(HWND hwnd, LPARAM unused) {
204 RECT rect;
205 DWORD cloaked = 0;
206 uint32_t i;
207 (void)unused;
208 if (!IsWindowVisible(hwnd) || IsIconic(hwnd)) return TRUE;
209 attribute(hwnd, 14, &cloaked, sizeof(cloaked));
210 if (cloaked || !GetWindowRect(hwnd, &rect) || rect.right <= rect.left || rect.bottom <= rect.top) return TRUE;
211 if (rect.left >= (LONG)layout[SG_WINDOWS_WIDTH] || rect.top >= (LONG)layout[SG_WINDOWS_HEIGHT] || rect.right <= 0 || rect.bottom <= 0) return TRUE;
212 if (count == SG_MAX_WINDOWS || (GetWindowLongW(hwnd, GWL_EXSTYLE) & WS_EX_LAYERED)) { invalid = 1; return FALSE; }
213 for (i = 0; i < SG_MAX_WINDOWS; ++i) if (windows[i].hwnd == hwnd) break;
214 if (i == SG_MAX_WINDOWS) {
215 for (i = 0; i < SG_MAX_WINDOWS; ++i) if (!windows[i].hwnd) break;
216 if (i == SG_MAX_WINDOWS) { invalid = 1; return FALSE; }
217 windows[i].hwnd = hwnd; windows[i].id = ++next_id; windows[i].initial = 1;
218 }
219 windows[i].seen = 1;
220 WCHAR class_name[32] = {0};
221 GetClassNameW(hwnd, class_name, 32);
222 windows[i].shell = hwnd == GetShellWindow() ||
223 !lstrcmpW(class_name, L"Shell_TrayWnd") || !lstrcmpW(class_name, L"Shell_SecondaryTrayWnd") || !lstrcmpW(class_name, L"WorkerW");
224 if ((!lstrcmpW(class_name, L"ApplicationFrameWindow") && !GetWindowTextLengthW(hwnd)) || !lstrcmpW(class_name, L"Windows.UI.Core.CoreWindow") || !lstrcmpW(class_name, L"Xaml_WindowedPopupClass") || !lstrcmpW(class_name, L"XamlExplorerHostIslandWindow")) { invalid = 1; return FALSE; }
225 if (!windows[i].shell && (GetWindowLongW(hwnd, GWL_STYLE) & WS_CAPTION) != WS_CAPTION) { invalid = 1; return FALSE; }
226 capture_bounds[count] = rect;
227 if (!windows[i].shell) attribute(hwnd, 9, &rect, sizeof(rect));
228 if (windows[i].before && !windows[i].initial &&
229 (windows[i].width != (uint32_t)(rect.right - rect.left) || windows[i].height != (uint32_t)(rect.bottom - rect.top))) {
230 windows[i].initial = 1; windows[i].waiting = GetTickCount();
231 }
232 bounds[count] = rect;
233 uint32_t *record = layout + SG_WINDOWS_WORDS + count++ * SG_WINDOW_WORDS;
234 record[SG_WINDOW_ID] = windows[i].id;
235 record[SG_WINDOW_X] = (uint32_t)rect.left; record[SG_WINDOW_Y] = (uint32_t)rect.top;
236 record[SG_WINDOW_WIDTH] = windows[i].before && !windows[i].shell ? windows[i].width : (uint32_t)(rect.right - rect.left);
237 record[SG_WINDOW_HEIGHT] = windows[i].before && !windows[i].shell ? windows[i].height : (uint32_t)(rect.bottom - rect.top);
238 for (i = SG_WINDOW_CAPTION_LEFT; i <= SG_WINDOW_CAPTION_BOTTOM; ++i) record[i] = 0;
239 if ((GetWindowLongW(hwnd, GWL_STYLE) & WS_CAPTION) == WS_CAPTION) {
240 TITLEBARINFO title = {0};
241 title.cbSize = sizeof(title);
242 if (GetTitleBarInfo(hwnd, &title) && title.rcTitleBar.right > title.rcTitleBar.left && !(title.rgstate[0] & STATE_SYSTEM_INVISIBLE)) {
243 record[SG_WINDOW_CAPTION_LEFT] = (uint32_t)(title.rcTitleBar.left + GetSystemMetrics(SM_CXSIZE));
244 record[SG_WINDOW_CAPTION_TOP] = (uint32_t)title.rcTitleBar.top;
245 record[SG_WINDOW_CAPTION_RIGHT] = (uint32_t)(title.rcTitleBar.right - GetSystemMetrics(SM_CXSIZE) * 3);
246 record[SG_WINDOW_CAPTION_BOTTOM] = (uint32_t)title.rcTitleBar.bottom;
247 LONG left = (LONG)record[SG_WINDOW_CAPTION_LEFT], right = (LONG)record[SG_WINDOW_CAPTION_RIGHT];
248 LONG y = (title.rcTitleBar.top + title.rcTitleBar.bottom) / 2;
249 for (int sample = 0; sample < 3; ++sample) {
250 LONG x = left + 1 + sample * (right - left - 2) / 2;
251 DWORD_PTR hit;
252 if (right <= left || !SendMessageTimeoutW(hwnd, WM_NCHITTEST, 0, MAKELPARAM(x, y), SMTO_ABORTIFHUNG | SMTO_BLOCK, 1, &hit) || hit != HTCAPTION) {
253 for (i = SG_WINDOW_CAPTION_LEFT; i <= SG_WINDOW_CAPTION_BOTTOM; ++i) record[i] = 0;
254 break;
255 }
256 }
257 }
258 }
259 return TRUE;
260}
261
262static int start_window(struct window *window) {
263 void *item = NULL, *cursor = NULL;
264 struct size size;
265 int success = 0;
266 HWND target = window->hwnd ? window->hwnd : (HWND)MonitorFromPoint((POINT){0, 0}, MONITOR_DEFAULTTOPRIMARY);
267 if (FAILED(METHOD(interop, window->hwnd ? 3 : 4, window_fn)(interop, target, &item_iid, &item))) goto done;
268 if (FAILED(METHOD(item, 7, size_fn)(item, &size)) || size.width <= 0 || size.height <= 0 || size.width > (INT32)SG_MAX_DIMENSION || size.height > (INT32)SG_MAX_DIMENSION) goto done;
269 if (FAILED(METHOD(pool_factory, 6, pool_fn)(pool_factory, capture_device, DXGI_FORMAT_B8G8R8A8_UNORM, 2, size, &window->pool))) goto done;
270 if (FAILED(METHOD(window->pool, 10, session_fn)(window->pool, item, &window->session))) goto done;
271 if (FAILED(METHOD(window->session, 0, query_fn)(window->session, &cursor_iid, &cursor))) goto done;
272 if (FAILED(METHOD(cursor, 7, bool_fn)(cursor, FALSE)) || FAILED(METHOD(window->session, 6, close_fn)(window->session))) goto done;
273 window->pool_size = size;
274 window->waiting = GetTickCount();
275 success = 1;
276done:
277 release(cursor); release(item);
278 return success;
279}
280
281static int latest_frame(struct window *window, void **frame) {
282 void *next = NULL;
283 for (int i = 0; i < 2; ++i) {
284 if (FAILED(METHOD(window->pool, 7, object_fn)(window->pool, &next))) return 0;
285 if (!next) return 1;
286 close_object(*frame);
287 *frame = next;
288 }
289 return 1;
290}
291
292int sg_desktop_capture(uint8_t *pixels, uint32_t width, uint32_t height, int refresh) {
293 reap_paints();
294 void *frame = NULL, *surface = NULL, *access = NULL;
295 ID3D11Texture2D *texture = NULL;
296 D3D11_TEXTURE2D_DESC desc;
297 D3D11_MAPPED_SUBRESOURCE mapped;
298 struct size size;
299 int result = -1;
300 if (desktop_retry && (DWORD)(GetTickCount() - desktop_retry) < 1000) return -1;
301 if (!device && !sg_capture_start()) { desktop_retry = GetTickCount(); return -1; }
302 if (!desktop.pool && !start_window(&desktop)) goto done;
303 if (!latest_frame(&desktop, &frame)) goto done;
304 result = 0;
305 if (!frame) {
306 if (refresh && desktop.staging && desktop.width == width && desktop.height == height) goto readback;
307 if ((DWORD)(GetTickCount() - desktop.waiting) > 500 && (!desktop.staging || desktop.width != width || desktop.height != height)) result = -1;
308 goto done;
309 }
310 if (FAILED(METHOD(frame, 8, size_fn)(frame, &size))) { result = -1; goto done; }
311 if (size.width != (INT32)width || size.height != (INT32)height) { result = -1; goto done; }
312 if (desktop.pool_size.width != size.width || desktop.pool_size.height != size.height) {
313 if (FAILED(METHOD(desktop.pool, 6, recreate_fn)(desktop.pool, capture_device, DXGI_FORMAT_B8G8R8A8_UNORM, 2, size))) { result = -1; goto done; }
314 desktop.pool_size = size;
315 goto done;
316 }
317 result = -1;
318 if (FAILED(METHOD(frame, 6, object_fn)(frame, &surface)) ||
319 FAILED(METHOD(surface, 0, query_fn)(surface, &access_iid, &access)) ||
320 FAILED(METHOD(access, 3, query_fn)(access, &texture_iid, (void **)&texture))) goto done;
321 ID3D11Texture2D_GetDesc(texture, &desc);
322 if (desc.Width < width || desc.Height < height) goto done;
323 if (desktop.width != width || desktop.height != height) {
324 if (desktop.staging) ID3D11Texture2D_Release(desktop.staging);
325 desktop.staging = NULL;
326 desc.Width = width; desc.Height = height;
327 desc.BindFlags = 0; desc.MiscFlags = 0; desc.Usage = D3D11_USAGE_STAGING; desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
328 if (FAILED(ID3D11Device_CreateTexture2D(device, &desc, NULL, &desktop.staging))) goto done;
329 desktop.width = width; desktop.height = height;
330 }
331 D3D11_BOX box = {0, 0, 0, width, height, 1};
332 ID3D11DeviceContext_CopySubresourceRegion(context, (ID3D11Resource *)desktop.staging, 0, 0, 0, 0, (ID3D11Resource *)texture, 0, &box);
333readback:
334 result = -1;
335 if (FAILED(ID3D11DeviceContext_Map(context, (ID3D11Resource *)desktop.staging, 0, D3D11_MAP_READ, 0, &mapped))) goto done;
336 for (uint32_t y = 0; y < height; ++y) {
337 uint8_t *src = (uint8_t *)mapped.pData + (size_t)y * mapped.RowPitch;
338 uint8_t *dst = pixels + (size_t)y * width * 4;
339 for (uint32_t x = 0; x < width * 4; ++x) dst[x] = src[x];
340 }
341 ID3D11DeviceContext_Unmap(context, (ID3D11Resource *)desktop.staging, 0);
342 result = 1;
343done:
344 if (texture) ID3D11Texture2D_Release(texture);
345 release(access); release(surface); close_object(frame);
346 if (result < 0) { drop(&desktop); desktop_retry = GetTickCount(); }
347 else desktop_retry = 0;
348 return result;
349}
350
351static int atlas_fits(void) {
352 uint32_t width = layout[SG_WINDOWS_WIDTH], height = 0, x = 0, row_height = 0, last_id = 0;
353 for (uint32_t i = 0; i < count; ++i) {
354 uint32_t w = layout[SG_WINDOWS_WORDS + i * SG_WINDOW_WORDS + SG_WINDOW_WIDTH];
355 if (w > width) width = w;
356 }
357 for (uint32_t i = 0; i < count; ++i) {
358 uint32_t *next = NULL;
359 for (uint32_t n = 0; n < count; ++n) {
360 uint32_t *record = layout + SG_WINDOWS_WORDS + n * SG_WINDOW_WORDS;
361 if (record[SG_WINDOW_ID] > last_id && (!next || record[SG_WINDOW_ID] < next[SG_WINDOW_ID])) next = record;
362 }
363 uint32_t w = next[SG_WINDOW_WIDTH], h = next[SG_WINDOW_HEIGHT];
364 if (!w || !h || w > SG_MAX_DIMENSION || h > SG_MAX_DIMENSION) return 0;
365 if (x + w > width) { x = 0; height += row_height; row_height = 0; }
366 x += w;
367 if (h > row_height) row_height = h;
368 last_id = next[SG_WINDOW_ID];
369 }
370 height += row_height;
371 return width <= SG_MAX_DIMENSION && height <= SG_MAX_DIMENSION && (size_t)width * height <= SG_MAX_PIXELS;
372}
373
374int sg_windows_poll(sg_emit emit, int refresh, const uint8_t *pixels, uint32_t width, uint32_t height) {
375 uint32_t i, n, words[SG_SURFACE_WORDS] = {0};
376 if (retry && (DWORD)(GetTickCount() - retry) < 1000 && !refresh) return 1;
377 retry = 0;
378 count = 0; invalid = 0;
379 layout[SG_WINDOWS_WIDTH] = width;
380 layout[SG_WINDOWS_HEIGHT] = height;
381 for (i = 0; i < SG_MAX_WINDOWS; ++i) windows[i].seen = 0;
382 HWND foreground = GetForegroundWindow();
383 if (foreground && foreground != GetShellWindow() && foreground != FindWindowW(L"Shell_TrayWnd", NULL) &&
384 (GetWindowLongW(foreground, GWL_STYLE) & WS_CAPTION) != WS_CAPTION) invalid = 1;
385 if (!invalid) EnumWindows(enumerate, 0);
386 for (i = 0; i < SG_MAX_WINDOWS; ++i) if (windows[i].hwnd && !windows[i].seen) drop(windows + i);
387 if (!invalid && !atlas_fits()) invalid = 1;
388 layout[SG_WINDOWS_COUNT] = invalid ? 0 : count;
389 n = SG_WINDOWS_WORDS + layout[SG_WINDOWS_COUNT] * SG_WINDOW_WORDS;
390 int changed = refresh;
391 for (i = 0; i < n; ++i) if (layout[i] != prior[i]) changed = 1;
392 if (changed && !emit(SG_WINDOWS, layout, n, NULL, 0)) return 0;
393 for (i = 0; i < n; ++i) prior[i] = layout[i];
394 if (invalid) { sg_windows_stop(); retry = GetTickCount(); return 1; }
395 for (i = 0; i < SG_MAX_WINDOWS * 2; ++i) {
396 struct window *window = windows + i % SG_MAX_WINDOWS;
397 if (window->shell != (i < SG_MAX_WINDOWS)) continue;
398 void *frame = NULL, *surface = NULL, *access = NULL;
399 ID3D11Texture2D *texture = NULL;
400 D3D11_TEXTURE2D_DESC desc;
401 D3D11_BOX box;
402 D3D11_MAPPED_SUBRESOURCE mapped;
403 struct size size;
404 struct sg_rect rect;
405 uint32_t y;
406 int success = 1, mapped_texture = 0;
407 if (!window->hwnd) continue;
408 uint32_t *record = NULL;
409 for (n = 0; n < count; ++n) {
410 uint32_t *candidate = layout + SG_WINDOWS_WORDS + n * SG_WINDOW_WORDS;
411 if (candidate[SG_WINDOW_ID] == window->id) { record = candidate; break; }
412 }
413 if (!record) continue;
414 if (window->shell) {
415 size.width = capture_bounds[n].right - capture_bounds[n].left;
416 size.height = capture_bounds[n].bottom - capture_bounds[n].top;
417 mapped.pData = NULL;
418 } else {
419 if (!window->pool && !start_window(window)) goto frame_failed;
420 if (!latest_frame(window, &frame)) goto frame_failed;
421 if (!frame) {
422 if (window->initial && (DWORD)(GetTickCount() - window->waiting) > 500) goto frame_failed;
423 continue;
424 }
425 if (FAILED(METHOD(frame, 8, size_fn)(frame, &size))) goto frame_failed;
426 if (window->pool_size.width != size.width || window->pool_size.height != size.height) {
427 if (size.width <= 0 || size.height <= 0 || size.width > (INT32)SG_MAX_DIMENSION || size.height > (INT32)SG_MAX_DIMENSION) goto frame_failed;
428 if (FAILED(METHOD(window->pool, 6, recreate_fn)(window->pool, capture_device, DXGI_FORMAT_B8G8R8A8_UNORM, 2, size))) goto frame_failed;
429 window->pool_size = size;
430 window->initial = 1; window->waiting = GetTickCount();
431 goto frame_done;
432 }
433 if (FAILED(METHOD(frame, 6, object_fn)(frame, &surface)) ||
434 FAILED(METHOD(surface, 0, query_fn)(surface, &access_iid, &access)) ||
435 FAILED(METHOD(access, 3, query_fn)(access, &texture_iid, (void **)&texture))) goto frame_failed;
436 }
437 if (size.width <= 0 || size.height <= 0 || size.width > (INT32)SG_MAX_DIMENSION || size.height > (INT32)SG_MAX_DIMENSION) goto frame_failed;
438 if (texture) {
439 ID3D11Texture2D_GetDesc(texture, &desc);
440 if (desc.Width < (UINT)size.width || desc.Height < (UINT)size.height) goto frame_failed;
441 LONG left = 0, top = 0;
442 LONG right = bounds[n].right - bounds[n].left, bottom = bounds[n].bottom - bounds[n].top;
443 if (size.width == capture_bounds[n].right - capture_bounds[n].left && size.height == capture_bounds[n].bottom - capture_bounds[n].top) {
444 left = bounds[n].left - capture_bounds[n].left; top = bounds[n].top - capture_bounds[n].top;
445 right += left; bottom += top;
446 } else if (size.width != right || size.height != bottom) goto frame_failed;
447 if (left < 0 || top < 0 || right > size.width || bottom > size.height || right <= left || bottom <= top) goto frame_failed;
448 box = (D3D11_BOX){(UINT)left, (UINT)top, 0, (UINT)right, (UINT)bottom, 1};
449 size.width = right - left; size.height = bottom - top;
450 }
451 if (record[SG_WINDOW_WIDTH] != (uint32_t)size.width || record[SG_WINDOW_HEIGHT] != (uint32_t)size.height) {
452 record[SG_WINDOW_WIDTH] = (uint32_t)size.width; record[SG_WINDOW_HEIGHT] = (uint32_t)size.height;
453 if (!atlas_fits()) goto frame_failed;
454 if (!emit(SG_WINDOWS, layout, SG_WINDOWS_WORDS + count * SG_WINDOW_WORDS, NULL, 0)) { success = 0; goto frame_done; }
455 for (n = 0; n < SG_WINDOWS_WORDS + count * SG_WINDOW_WORDS; ++n) prior[n] = layout[n];
456 }
457 if (window->width != (uint32_t)size.width || window->height != (uint32_t)size.height) {
458 if (window->staging) ID3D11Texture2D_Release(window->staging);
459 window->staging = NULL;
460 detach_paint(window);
461 if (window->before) { allocated -= (size_t)window->width * window->height; HeapFree(heap, 0, window->before); window->before = NULL; }
462 if (allocated + (size_t)size.width * size.height > SG_MAX_PIXELS) goto frame_failed;
463 window->width = (uint32_t)size.width; window->height = (uint32_t)size.height;
464 window->before = HeapAlloc(heap, 0, (size_t)window->width * window->height * 4);
465 if (!window->before) goto frame_failed;
466 allocated += (size_t)window->width * window->height;
467 if (window->shell) {
468 BITMAPINFO info = {0};
469 size_t pixels = (size_t)window->width * window->height;
470 if (painted + pixels > SG_MAX_PIXELS) goto frame_failed;
471 window->paint = HeapAlloc(heap, HEAP_ZERO_MEMORY, sizeof(struct shell_paint));
472 if (!window->paint) goto frame_failed;
473 struct shell_paint *paint = window->paint;
474 paint->pixels = pixels; painted += pixels; paint->hwnd = window->hwnd;
475 paint->dc = CreateCompatibleDC(NULL);
476 info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); info.bmiHeader.biWidth = size.width;
477 info.bmiHeader.biHeight = -size.height; info.bmiHeader.biPlanes = 1; info.bmiHeader.biBitCount = 32;
478 paint->dib = CreateDIBSection(paint->dc, &info, DIB_RGB_COLORS, (void **)&paint->bits, NULL, 0);
479 if (!paint->dc || !paint->dib) goto frame_failed;
480 paint->selected = SelectObject(paint->dc, paint->dib);
481 paint->started = GetTickCount();
482 paint->thread = CreateThread(NULL, 0, paint_shell, paint, 0, NULL);
483 if (!paint->thread) goto frame_failed;
484 } else {
485 desc.Width = window->width; desc.Height = window->height;
486 desc.BindFlags = 0; desc.MiscFlags = 0; desc.Usage = D3D11_USAGE_STAGING; desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
487 if (FAILED(ID3D11Device_CreateTexture2D(device, &desc, NULL, &window->staging))) goto frame_failed;
488 }
489 window->initial = 1;
490 }
491 if (window->shell) {
492 if (WaitForSingleObject(window->paint->thread, 0) != WAIT_OBJECT_0) {
493 if ((DWORD)(GetTickCount() - window->paint->started) >= 500) goto frame_failed;
494 return 1;
495 }
496 DWORD painted_ok;
497 if (!GetExitCodeThread(window->paint->thread, &painted_ok) || !painted_ok) goto frame_failed;
498 for (y = 0; y < window->height; ++y) {
499 LONG screen_y = (LONG)record[SG_WINDOW_Y] + (LONG)y;
500 if (screen_y < 0 || screen_y >= (LONG)layout[SG_WINDOWS_HEIGHT]) continue;
501 LONG x = (LONG)record[SG_WINDOW_X], right = x + (LONG)window->width;
502 if (x < 0) x = 0;
503 if (right > (LONG)layout[SG_WINDOWS_WIDTH]) right = (LONG)layout[SG_WINDOWS_WIDTH];
504 while (x < right) {
505 LONG stop = right;
506 for (n = 0; n < count; ++n) {
507 uint32_t *other = layout + SG_WINDOWS_WORDS + n * SG_WINDOW_WORDS;
508 if (other[SG_WINDOW_ID] == window->id) break;
509 LONG left = capture_bounds[n].left - 2, top = capture_bounds[n].top - 2;
510 LONG edge = capture_bounds[n].right + 2;
511 if (screen_y < top || screen_y >= capture_bounds[n].bottom + 2) continue;
512 if (left <= x && edge > x) { x = edge; stop = x; break; }
513 if (left > x && left < stop) stop = left;
514 }
515 if (x >= stop) continue;
516 uint8_t *dst = window->paint->bits + ((size_t)y * window->width + (x - (LONG)record[SG_WINDOW_X])) * 4;
517 const uint8_t *src = pixels + ((size_t)screen_y * layout[SG_WINDOWS_WIDTH] + x) * 4;
518 for (LONG byte = 0; byte < (stop - x) * 4; ++byte) dst[byte] = src[byte];
519 x = stop;
520 }
521 }
522 mapped.pData = window->paint->bits; mapped.RowPitch = window->width * 4;
523 } else {
524 ID3D11DeviceContext_CopySubresourceRegion(context, (ID3D11Resource *)window->staging, 0, 0, 0, 0, (ID3D11Resource *)texture, 0, &box);
525 if (FAILED(ID3D11DeviceContext_Map(context, (ID3D11Resource *)window->staging, 0, D3D11_MAP_READ, 0, &mapped))) goto frame_failed;
526 mapped_texture = 1;
527 for (y = 8; y + 8 < window->height; ++y) {
528 const uint8_t *row = (const uint8_t *)mapped.pData + (size_t)y * mapped.RowPitch;
529 for (uint32_t x = 8; x + 8 < window->width; ++x) if (row[x * 4 + 3] != 255) goto frame_failed;
530 }
531 }
532 rect.x = window->width; rect.y = window->height; rect.width = rect.height = 0;
533 if (refresh || window->initial) { rect.x = rect.y = 0; rect.width = window->width; rect.height = window->height; }
534 else for (y = 0; y < window->height; ++y) {
535 struct sg_rect row;
536 if (sg_difference(window->before + (size_t)y * window->width * 4, (uint8_t *)mapped.pData + (size_t)y * mapped.RowPitch, window->width, 1, &row)) {
537 if (row.x < rect.x) rect.x = row.x;
538 if (row.x + row.width > rect.width) rect.width = row.x + row.width;
539 if (y < rect.y) rect.y = y;
540 rect.height = y + 1;
541 }
542 }
543 if (rect.width && rect.height) {
544 if (!refresh && !window->initial) { rect.width -= rect.x; rect.height -= rect.y; }
545 words[SG_SURFACE_ID] = window->id; words[SG_SURFACE_WIDTH] = window->width; words[SG_SURFACE_HEIGHT] = window->height;
546 words[SG_SURFACE_X] = rect.x; words[SG_SURFACE_Y] = rect.y; words[SG_SURFACE_RECT_WIDTH] = rect.width; words[SG_SURFACE_RECT_HEIGHT] = rect.height;
547 success = emit(SG_SURFACE, words, SG_SURFACE_WORDS, (uint8_t *)mapped.pData + (size_t)rect.y * mapped.RowPitch + rect.x * 4, mapped.RowPitch);
548 for (y = rect.y; y < rect.y + rect.height; ++y) {
549 uint32_t x;
550 uint8_t *src = (uint8_t *)mapped.pData + (size_t)y * mapped.RowPitch + rect.x * 4;
551 uint8_t *dst = window->before + ((size_t)y * window->width + rect.x) * 4;
552 for (x = 0; x < rect.width * 4; ++x) dst[x] = src[x];
553 }
554 window->initial = 0;
555 }
556 goto frame_done;
557frame_failed:
558 invalid = 1;
559frame_done:
560 if (mapped_texture) ID3D11DeviceContext_Unmap(context, (ID3D11Resource *)window->staging, 0);
561 if (texture) ID3D11Texture2D_Release(texture);
562 release(access); release(surface); close_object(frame);
563 if (!success) return 0;
564 if (invalid) { drop(window); break; }
565 }
566 if (invalid) {
567 sg_windows_stop();
568 retry = GetTickCount();
569 uint32_t disabled[SG_WINDOWS_WORDS] = {layout[SG_WINDOWS_WIDTH], layout[SG_WINDOWS_HEIGHT], 0};
570 if (!emit(SG_WINDOWS, disabled, SG_WINDOWS_WORDS, NULL, 0)) return 0;
571 prior[SG_WINDOWS_COUNT] = 0;
572 }
573 return 1;
574}