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