Захват рабочего стола в HDR
01.03.2022, 15:39. Показов 532. Ответов 0
Привет всем!
Имеется комп с Windows 11, видеокарта дискретная, RTX 3080Ti, на рабочем столе включен HDR, глубина цвета 10 бит. Разрешение рабочего стола большое - 11520х2160. Я пытаюсь с помощью DirectX захватывать картинку с рабочего стола, уменьшать её в 400 раз (20 раз по ширине и 20 по высоте), и затем копировать в ОЗУ для дальнейшей обработки. В SDR всё работает, в HDR - тоже. Но, поскольку я захватываю в формате RGB 8bit, т.е. без HDR, то DirectX на лету автоматически конвертирует мне изображение рабочего стола из HDR в SDR. Проблема в том, что он делает это сразу при захвате, и поэтому захват рабочего стола отжирает 15% видеокарты (!).
Я хочу оптимизировать код: сначала захватывать рабочий стол в исходном формате без конвертации (т.е. 10 бит на канал), затем в этом же формате уменьшать в 400 раз, и уже после этого конвертировать HDR в SDR, чтобы сэкономить ресурсы GPU.
Вот код захвата рабочего стола:
| C# | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
| CaptureResult captureDesktop()
{
var result = new CaptureResult();
if (dxgiDevice == null)
{
dxgiDevice = _device.QueryInterface<DXGI.Device>();
d2dFactory = new D2D.Factory1();
d2dDevice = new D2D.Device(d2dFactory, dxgiDevice);
frameDc = new D2D.DeviceContext(d2dDevice, D2D.DeviceContextOptions.None);
}
{
// acquire frame
var dresult = _outputDuplication.TryAcquireNextFrame(10000, out var finfo, out var frame);
if (dresult.Success)
try
{
//using (frame)
// get DXGI surface/bitmap from resource
//using (D2D.DeviceContext? )
using (DXGI.Surface? frameSurface = frame.QueryInterface<DXGI.Surface>())
using (D2D.Bitmap1? frameBitmap = new D2D.Bitmap1(frameDc, frameSurface))
{
int w = frameSurface.Description.Width / DownscaleFactor;
int h = frameSurface.Description.Height / DownscaleFactor;
if (texture != null)
{
if (texture.Description.Width != w || texture.Description.Height != h)
{
try { Interlocked.Exchange(ref texture, null)?.Dispose(); } catch { }
try { Interlocked.Exchange(ref textureDc, null)?.Dispose(); } catch { }
try { Interlocked.Exchange(ref textureSurface, null)?.Dispose(); } catch { }
try { Interlocked.Exchange(ref textureBitmap, null)?.Dispose(); } catch { }
texture = null;
}
}
if (texture == null)
{
var desc = new D3D11.Texture2DDescription
{
CpuAccessFlags = D3D11.CpuAccessFlags.None, // only GPU
BindFlags = D3D11.BindFlags.RenderTarget, // to use D2D
Format = DXGI.Format.B8G8R8A8_UNorm, //для SDR
Width = w,
Height = h,
OptionFlags = D3D11.ResourceOptionFlags.None,
MipLevels = 1,
ArraySize = 1,
SampleDescription = { Count = 1, Quality = 0 },
Usage = D3D11.ResourceUsage.Default
};
texture = new D3D11.Texture2D(_device, desc);
textureDc = new D2D.DeviceContext(d2dDevice, D2D.DeviceContextOptions.None);
textureSurface = texture.QueryInterface<DXGI.Surface>();
textureBitmap = new D2D.Bitmap1(textureDc, textureSurface);
}
// create a GPU resized texture/surface/bitmap
//using (D3D11.Texture2D? texture = new D3D11.Texture2D(_device, desc))
// using (D2D.DeviceContext? textureDc = new D2D.DeviceContext(d2dDevice, D2D.DeviceContextOptions.None)) // create a D2D device context
// using (DXGI.Surface? textureSurface = texture.QueryInterface<DXGI.Surface>()) // this texture is a DXGI surface
// using (D2D.Bitmap1? textureBitmap = new D2D.Bitmap1(textureDc, textureSurface)) // we can create a GPU bitmap on a DXGI surface
// associate the DC with the GPU texture/surface/bitmap
textureDc.Target = textureBitmap;
// this is were we draw on the GPU texture/surface
textureDc.BeginDraw();
// this will automatically resize
textureDc.DrawBitmap(
frameBitmap,
new Interop.RawRectangleF(0, 0, w, h),
1,
D2D.InterpolationMode.HighQualityCubic, // change this for quality vs speed
null,
null);
// commit draw
textureDc.EndDraw();
if (cpuBitmap == null || (int)cpuBitmap.Size.Width != w || (int)cpuBitmap.Size.Height != h)
{
if (cpuBitmap != null)
{
try { cpuBitmap.Unmap(); } catch { }
try { cpuBitmap.Dispose(); } catch { }
}
var bitmapProperties = new D2D.BitmapProperties1();
bitmapProperties.BitmapOptions = BitmapOptions.CannotDraw | BitmapOptions.CpuRead;
bitmapProperties.PixelFormat = textureBitmap.PixelFormat;
cpuBitmap = new Bitmap1(frameDc, new SharpDX.Size2(w, h), bitmapProperties);
mapOfCpuBitmap = cpuBitmap.Map(MapOptions.Read);
}
cpuBitmap.CopyFromBitmap(textureBitmap);
var cpuBuffer = getCpuBuffer(w, h);
unsafe
{
var cpuBufferBits = cpuBuffer.LockBits(new Rectangle(0, 0, cpuBuffer.Width, cpuBuffer.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
try
{
var byteCount = cpuBuffer.Width * cpuBuffer.Height * 4;
Buffer.MemoryCopy(mapOfCpuBitmap.DataPointer.ToPointer(), cpuBufferBits.Scan0.ToPointer(), byteCount, byteCount);
}
finally
{
cpuBuffer.UnlockBits(cpuBufferBits);
}
}
return new CaptureResult
{
Success = true,
Buffer = cpuBuffer,
Exception = null
};
}
}
finally
{
_outputDuplication.ReleaseFrame();
}
else
{
recreateResources();
}
}
return result;
} |
|
Здесь говорится, что при работе с HDR я должен использовать буфер DXGI_FORMAT_R16G16B16A16_FLOAT. Поставил такой формат - заработало:
| C# | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| var desc = new D3D11.Texture2DDescription
{
CpuAccessFlags = D3D11.CpuAccessFlags.None, // only GPU
BindFlags = D3D11.BindFlags.RenderTarget, // to use D2D
Format = DXGI.Format.R16G16B16A16_Float,
//Format = DXGI.Format.R10G10B10A2_UNorm, не работает :(
Width = w,
Height = h,
OptionFlags = D3D11.ResourceOptionFlags.None,
MipLevels = 1,
ArraySize = 1,
SampleDescription = { Count = 1, Quality = 0 },
Usage = D3D11.ResourceUsage.Default
};
texture = new D3D11.Texture2D(_device, desc);
textureDc = new D2D.DeviceContext(d2dDevice, D2D.DeviceContextOptions.None);
textureSurface = texture.QueryInterface<DXGI.Surface>();
textureBitmap = new D2D.Bitmap1(textureDc, textureSurface); |
|
Однако, при копировании уменьшенного результата в ОЗУ возникает проблема: если рассчитывать размер буфера в ОЗУ как 8 * ширина * высота, то всё падает с ошибкой.
| C# | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| if (cpuBitmap == null || (int)cpuBitmap.Size.Width != w || (int)cpuBitmap.Size.Height != h)
{
if (cpuBitmap != null)
{
try { cpuBitmap.Unmap(); } catch { }
try { cpuBitmap.Dispose(); } catch { }
}
var bitmapProperties = new D2D.BitmapProperties1();
bitmapProperties.BitmapOptions = BitmapOptions.CannotDraw | BitmapOptions.CpuRead;
bitmapProperties.PixelFormat = textureBitmap.PixelFormat;
cpuBitmap = new Bitmap1(frameDc, new SharpDX.Size2(w, h), bitmapProperties);
mapOfCpuBitmap = cpuBitmap.Map(MapOptions.Read);
}
cpuBitmap.CopyFromBitmap(textureBitmap); //Видимо, это не работает :(
var cpuBuffer = getCpuBuffer(w, h, canvasTextureDescription.Format);
unsafe
{
var cpuBufferBits = cpuBuffer.LockBits(new Rectangle(0, 0, cpuBuffer.Width, cpuBuffer.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
try
{
int bytesPerPixel = getBytesPerPixelCount(canvasTextureDescription.Format);
//bytesPerPixel = 8; Не работает, падает
//bytesPerPixel = 4; Работает, но копируются нули :(
var byteCount = cpuBuffer.Width * cpuBuffer.Height * bytesPerPixel;
Buffer.MemoryCopy(mapOfCpuBitmap.DataPointer.ToPointer(), cpuBufferBits.Scan0.ToPointer(), byteCount, byteCount);
}
finally
{
cpuBuffer.UnlockBits(cpuBufferBits);
}
} |
|
Если же рассчитывать размер копируемого буфера как 4 * ширина * высота, копирование происходит, но при этом копируются нули. Функция, создающая буфер в ОЗУ:
| C# | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| System.Drawing.Bitmap getCpuBuffer(int w, int h, DXGI.Format gpuPixelFormat)
{
bool r = false;
if (cpuBuffer == null)
r = true;
else if (cpuBuffer.Width != w || cpuBuffer.Height != h)
r = true;
System.Drawing.Imaging.PixelFormat cpuPixelFormat = default;
if (gpuPixelFormat == DXGI.Format.B8G8R8A8_UNorm)
cpuPixelFormat = System.Drawing.Imaging.PixelFormat.Format32bppArgb;
else if (gpuPixelFormat == DXGI.Format.R16G16B16A16_Float)
cpuPixelFormat = System.Drawing.Imaging.PixelFormat.Format64bppArgb;
else
throw new NotSupportedException();
if (r)
Interlocked.Exchange(ref cpuBuffer, new System.Drawing.Bitmap(w, h, cpuPixelFormat))?.Dispose();
return cpuBuffer;
} |
|
Т.е. формат цвета я указываю правильный - Format64bppArgb - по крайней мере, памяти выделяется ровно столько, сколько нужно.
Я посмотрел в окне "Память" содержимое cpuBitmap - там нули. Т.е., как я понимаю, здесь либо cpuBitmap.CopyFromBitmap(textureBitmap); не копирует данные из видеокарты в ОЗУ, либо в видеокарте данные изначально нули. Но тогда возникает ещё один вопрос - почему при копировании точно рассчитанного числа байт оно падает? Как будто один из буферов меньше рассчитанного размера.
Помогите, пожалуйста, разобраться с этим. Мне осталось только скопировать данные из видеокарты в ОЗУ.
Добавлено через 13 минут
Нашёл косяк почему падало: вместо | C# | 1
| var cpuBufferBits = cpuBuffer.LockBits(new Rectangle(0, 0, cpuBuffer.Width, cpuBuffer.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); |
|
нужно было указать
| C# | 1
| var cpuBufferBits = cpuBuffer.LockBits(new Rectangle(0, 0, cpuBuffer.Width, cpuBuffer.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, cpuBuffer.PixelFormat); |
|
но копируются по-прежнему нули, если посмотреть память по адресу mapOfCpuBitmap.DataPointer - там нули.
0
|