1 /**
2 * Copyright (C) Francesco Fusco. All rights reserved.
3 * License: https://github.com/Fushko/gammy#license
4 */
5
6 #include "dspctl-dxgi.h"
7 #include "defs.h"
8 #include "cfg.h"
9 #include "utils.h"
10
GDI()11 GDI::GDI()
12 {
13
14 }
15
~GDI()16 GDI::~GDI()
17 {
18 for (const auto& hdc : hdcs) {
19 DeleteDC(hdc);
20 }
21 }
22
numDisplays()23 int GDI::numDisplays()
24 {
25 DISPLAY_DEVICE dsp;
26 dsp.cb = sizeof(DISPLAY_DEVICE);
27 int i = 0;
28 int attached_dsp = 0;
29 while (EnumDisplayDevices(NULL, i++, &dsp, 0) && dsp.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP)
30 attached_dsp++;
31 return attached_dsp;
32 }
33
createDCs(const std::wstring & primary_screen_name)34 void GDI::createDCs(const std::wstring &primary_screen_name)
35 {
36 const int num_dsp = numDisplays();
37 hdcs.reserve(num_dsp);
38
39 for (int i = 0; i < num_dsp; ++i) {
40 DISPLAY_DEVICE dsp;
41 dsp.cb = sizeof(DISPLAY_DEVICE);
42 EnumDisplayDevices(NULL, i, &dsp, 0);
43
44 HDC dc = CreateDC(NULL, dsp.DeviceName, NULL, 0);
45 hdcs.push_back(dc);
46
47 // If we don't have the name, just pick the first one.
48 if ((i == 0 && primary_screen_name.empty()) || dsp.DeviceName == primary_screen_name) {
49 primary_dc_idx = i;
50 width = GetDeviceCaps(dc, HORZRES);
51 height = GetDeviceCaps(dc, VERTRES);
52 buf.resize(width * height * 4);
53 setBitmapInfo(width, height);
54 }
55 }
56
57 LOGD << "HDCs: " << hdcs.size();
58 LOGD << "GDI res: " << width << '*' << height;
59 }
60
setGamma(int brt_step,int temp_step)61 void GDI::setGamma(int brt_step, int temp_step)
62 {
63 const double r_mult = interpTemp(temp_step, 0),
64 g_mult = interpTemp(temp_step, 1),
65 b_mult = interpTemp(temp_step, 2);
66
67 WORD ramp[3][256];
68 const double brt_mult = remap(brt_step, 0, brt_steps_max, 0, 255);
69
70 for (WORD i = 0; i < 256; ++i) {
71 const int val = i * brt_mult;
72 ramp[0][i] = WORD(val * r_mult);
73 ramp[1][i] = WORD(val * g_mult);
74 ramp[2][i] = WORD(val * b_mult);
75 }
76
77 /* As auto brt is currently supported only on the primary screen,
78 * We set this ramp to the screens whose image brightness is not controlled. */
79 WORD ramp_full_brt[3][256];
80 if (hdcs.size() > 1) {
81 for (WORD i = 0; i < 256; ++i) {
82 const int val = i * 255;
83 ramp_full_brt[0][i] = WORD(val * r_mult);
84 ramp_full_brt[1][i] = WORD(val * g_mult);
85 ramp_full_brt[2][i] = WORD(val * b_mult);
86 }
87 }
88
89 int i = 0;
90 for (const auto &dc : hdcs) {
91 bool r;
92 if (i == primary_dc_idx)
93 r = SetDeviceGammaRamp(dc, ramp);
94 else
95 r = SetDeviceGammaRamp(dc, ramp_full_brt);
96
97 LOGV << "screen " << i << " gamma set: " << r;
98 i++;
99 }
100 }
101
setInitialGamma(bool set_previous)102 void GDI::setInitialGamma([[maybe_unused]] bool set_previous)
103 {
104 // @TODO: restore previous gamma
105 GDI::setGamma(brt_steps_max, 0);
106 }
107
setBitmapInfo(int width,int height)108 void GDI::setBitmapInfo(int width, int height)
109 {
110 ZeroMemory(&info, sizeof(BITMAPINFOHEADER));
111 info.biSize = sizeof(BITMAPINFOHEADER);
112 info.biWidth = width;
113 info.biHeight = -height;
114 info.biPlanes = 1;
115 info.biBitCount = 32;
116 info.biCompression = BI_RGB;
117 info.biSizeImage = width * height * 4;
118 info.biClrUsed = 0;
119 info.biClrImportant = 0;
120 }
121
getScreenBrightness()122 int GDI::getScreenBrightness() noexcept
123 {
124 HDC dc = GetDC(NULL);
125 HBITMAP bmp = CreateCompatibleBitmap(dc, width, height);
126 HDC tmp = CreateCompatibleDC(dc);
127 HGDIOBJ obj = SelectObject(tmp, bmp);
128
129 BitBlt(tmp, 0, 0, width, height, dc, 0, 0, SRCCOPY);
130 GetDIBits(tmp, bmp, 0, height, buf.data(), LPBITMAPINFO(&info), DIB_RGB_COLORS);
131
132 SelectObject(tmp, obj);
133 DeleteObject(bmp);
134 DeleteObject(obj);
135 DeleteDC(tmp);
136 DeleteDC(dc);
137
138 return calcBrightness(buf.data(), buf.size(), 4, 1024);
139 }
140
DXGI()141 DXGI::DXGI()
142 {
143 if (!(useDXGI = init())) {
144 LOGD << "DXGI unavailable.";
145 primary_screen_name.clear();
146 }
147
148 GDI::createDCs(this->primary_screen_name);
149 }
150
~DXGI()151 DXGI::~DXGI()
152 {
153 if (duplication && useDXGI)
154 duplication->ReleaseFrame();
155 if (output1)
156 output1->Release();
157 if (d3d_context)
158 d3d_context->Release();
159 if (d3d_device)
160 d3d_device->Release();
161 }
162
init()163 bool DXGI::init()
164 {
165 std::vector<IDXGIAdapter1*> gpus;
166 std::vector<IDXGIOutput*> outputs;
167 IDXGIAdapter1 *gpu;
168
169 // Retrieve a IDXGIFactory to enumerate the adapters
170 {
171 IDXGIFactory1 *factory = nullptr;
172 HRESULT hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), reinterpret_cast<void**>(&factory));
173
174 if (hr != S_OK) {
175 LOGE << "Failed to retrieve the IDXGIFactory";
176 return false;
177 }
178
179 int i = 0;
180 while (factory->EnumAdapters1(i++, &gpu) != DXGI_ERROR_NOT_FOUND)
181 gpus.push_back(gpu);
182
183 factory->Release();
184 }
185
186 // Get GPU info
187 IF_PLOG(plog::debug) {
188 DXGI_ADAPTER_DESC1 desc;
189 for (UINT i = 0; i < gpus.size(); ++i) {
190 gpu = gpus[i];
191 HRESULT hr = gpu->GetDesc1(&desc);
192 if (hr != S_OK) {
193 LOGE << "Failed to get description for GPU: " << i;
194 continue;
195 }
196 LOGD << "GPU " << i << ": " << desc.Description;
197 }
198 }
199
200 // Get the monitors attached to the GPUs
201 {
202 int i = 0;
203 for (auto &gpu : gpus) {
204 int j = 0;
205 IDXGIOutput *output;
206 while (gpu->EnumOutputs(j, &output) != DXGI_ERROR_NOT_FOUND) {
207 LOGD << "Found monitor " << j << " on GPU " << i;
208 outputs.push_back(output);
209 ++j;
210 }
211 ++i;
212 }
213 }
214
215 if (outputs.empty()) {
216 LOGE << "No outputs found";
217 return false;
218 }
219
220 // Get monitor info
221 {
222 int i = 0;
223 for (auto &output : outputs) {
224 DXGI_OUTPUT_DESC desc;
225 HRESULT hr = output->GetDesc(&desc);
226 if (hr != S_OK) {
227 LOGE << "Failed to get description for output " << i;
228 continue;
229 }
230 LOGD << "Output: " << desc.DeviceName << ", attached to desktop: " << desc.AttachedToDesktop;
231
232 if (i == 0) {
233 this->primary_screen_name = desc.DeviceName;
234 }
235 ++i;
236 }
237 }
238
239 // Create a Direct3D device to access the OutputDuplication interface
240 {
241 // Just get the first GPU for now
242 IDXGIAdapter1 *d3d_adapter = gpus[0];
243
244 if (!d3d_adapter) {
245 LOGE << "The stored adapter is nullptr";
246 return false;
247 }
248
249 D3D_FEATURE_LEVEL d3d_feature_level;
250
251 HRESULT hr = D3D11CreateDevice(
252 d3d_adapter, // Adapter: The adapter (video card) we want to use. We may use NULL to pick the default adapter.
253 D3D_DRIVER_TYPE_UNKNOWN, // DriverType: We use the GPU as backing device.
254 nullptr, // Software: we're using a D3D_DRIVER_TYPE_HARDWARE so it's not applicable.
255 NULL, // Flags: maybe we need to use D3D11_CREATE_DEVICE_BGRA_SUPPORT because desktop duplication is using this.
256 nullptr, // Feature Levels: what version to use.
257 0, // Number of feature levels.
258 D3D11_SDK_VERSION, // The SDK version, use D3D11_SDK_VERSION
259 &d3d_device, // OUT: the ID3D11Device object.
260 &d3d_feature_level, // OUT: the selected feature level.
261 &d3d_context); // OUT: the ID3D11DeviceContext that represents the above features.
262
263 d3d_context->Release();
264 d3d_adapter->Release();
265
266 if (hr != S_OK) {
267 LOGE << "Failed to create D3D11 Device.";
268 if (hr == E_INVALIDARG) {
269 LOGE << "Got INVALID arg passed into D3D11CreateDevice.";
270 }
271 return false;
272 }
273 }
274
275 // Currently, auto brightness is only supported on the first screen.
276 const int output_idx = 0;
277
278 // Set texture properties
279 {
280 DXGI_OUTPUT_DESC desc;
281 HRESULT hr = outputs[output_idx]->GetDesc(&desc);
282
283 if (hr != S_OK) {
284 LOGE << "Failed to get description for screen " << output_idx;
285 return false;
286 }
287
288 tex_desc.Width = desc.DesktopCoordinates.right;
289 tex_desc.Height = desc.DesktopCoordinates.bottom;
290 tex_desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; // 4 bits per pixel
291 tex_desc.Usage = D3D11_USAGE_STAGING;
292 tex_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
293 tex_desc.MipLevels = 1;
294 tex_desc.ArraySize = 1;
295 tex_desc.SampleDesc.Count = 1;
296 tex_desc.SampleDesc.Quality = 0;
297 tex_desc.BindFlags = 0;
298 tex_desc.MiscFlags = 0;
299 LOGD << "DXGI res: " << tex_desc.Width << "*" << tex_desc.Height;
300 }
301
302 // Initialize output duplication
303 {
304 HRESULT hr = outputs[output_idx]->QueryInterface(__uuidof(IDXGIOutput1), reinterpret_cast<void**>(&output1));
305
306 if (hr != S_OK) {
307 LOGE << "Failed to query IDXGIOutput1 interface";
308 return false;
309 }
310
311 hr = output1->DuplicateOutput(d3d_device, &duplication);
312
313 if (hr != S_OK) {
314 LOGE << "DuplicateOutput failed";
315 return false;
316 }
317
318 output1->Release();
319 d3d_device->Release();
320 }
321
322 for (const auto &gpu : gpus)
323 gpu->Release();
324
325 for (const auto &output : outputs)
326 output->Release();
327
328 return true;
329 }
330
getScreenBrightness()331 int DXGI::getScreenBrightness() noexcept
332 {
333 if (!useDXGI) {
334 Sleep(cfg["brt_polling_rate"].get<int>());
335 return GDI::getScreenBrightness();
336 }
337
338 DXGI_OUTDUPL_FRAME_INFO frame_info;
339 IDXGIResource *desktop_res;
340 while (duplication->AcquireNextFrame(INFINITE, &frame_info, &desktop_res) != S_OK)
341 this->restart();
342
343 ID3D11Texture2D *tex;
344 desktop_res->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&tex));
345 desktop_res->Release();
346 tex->Release();
347
348 ID3D11Texture2D *staging_tex;
349 d3d_device->CreateTexture2D(&this->tex_desc, nullptr, &staging_tex);
350 d3d_context->CopyResource(staging_tex, tex);
351 duplication->ReleaseFrame();
352
353 D3D11_MAPPED_SUBRESOURCE map;
354
355 while (d3d_context->Map(staging_tex, 0, D3D11_MAP_READ, D3D11_MAP_FLAG_DO_NOT_WAIT, &map) == DXGI_ERROR_WAS_STILL_DRAWING)
356 Sleep(cfg["brt_polling_rate"].get<int>());
357
358 d3d_context->Unmap(staging_tex, 0);
359 staging_tex->Release();
360 d3d_context->Release();
361
362 return calcBrightness(reinterpret_cast<uint8_t*>(map.pData), map.DepthPitch, 4, 1024);
363 }
364
restart()365 void DXGI::restart()
366 {
367 HRESULT hr = output1->DuplicateOutput(d3d_device, &duplication);
368
369 switch (hr) {
370 case S_OK:
371 LOGD << "Output duplication restarted.";
372 break;
373 case E_INVALIDARG:
374 LOGE << "E_INVALIDARG";
375 break;
376 case E_ACCESSDENIED:
377 LOGE << "E_ACCESSDENIED";
378 break;
379 case DXGI_ERROR_UNSUPPORTED:
380 LOGE << "E_DXGI_ERROR_UNSUPPORTED";
381 break;
382 case DXGI_ERROR_NOT_CURRENTLY_AVAILABLE:
383 LOGE << "DXGI_ERROR_NOT_CURRENTLY_AVAILABLE";
384 break;
385 case DXGI_ERROR_SESSION_DISCONNECTED:
386 LOGE << "DXGI_ERROR_SESSION_DISCONNECTED";
387 break;
388 }
389
390 Sleep(2500);
391 output1->Release();
392 }
393