Skip to content

Commit 6bc3ec2

Browse files
Spruill-1Copilot
andcommitted
Merge origin/main: graph viewer DPI fix + DXGI DDA / WGC source providers
Integrates Zachary's coworker contributions (commits cac0fc1, 3cb000b) that landed on main while Phase 7 was in flight: * cac0fc1 — Fix DPI of the graph viewer. Switches the graph SwapChainPanel from DIP-sized backbuffer to physical-pixel-sized via CompositionScaleX/Y, with a CompositionScaleChanged handler. New m_graphPanelDipsWidth / m_graphPanelDipsHeight + UpdateGraphPanelScale members on MainWindow. NodeGraphController.SetViewportSize now takes DIPs. Auto-merged cleanly into our render path; no Phase 7 code touched these methods. * 3cb000b — DXGI DDA source + Windows Graphics Capture source. Adds Effects/DxgiDuplicationSourceProvider.{h,cpp} (engine-side) and Effects/WindowsGraphicsCaptureSourceProvider.{h,cpp} (app- side, uses the WinUI graphics capture picker). SourceNodeFactory grew picker-aware overloads. New 'DXGI Desktop Duplication' and 'Windows Graphics Capture' source effects in the Add Node flyout. Conflict resolution: 1. MainWindow.xaml.cpp — the conflict block was just OnRenderTick / RenderFrame method bodies that Phase 4 (commit f25537b) had already moved to MainWindow.RenderTick.cpp. Their commits didn't touch those methods (DPI fix landed in OnPreviewPanelLoaded / ResizeGraphPanel / CreateGraphSwapChain, all of which auto- merged). Resolution: drop the dead method bodies, keep ours. 2. ShaderLab.vcxproj.filters — they did a structural reorganization (flat layout, no Filter folders). Took theirs as the base; added our missing entries (MainWindow.GraphFileIo.cpp / RenderTick.cpp / WorkingSpace.cpp); removed stale ShaderLab\McpHttpServer.{h,cpp} entries that no longer exist (we moved them to Engine\Mcp\ in the Phase 7 foundation commit). ShaderLab.vcxproj, ShaderLabEngine.vcxproj, MainWindow.xaml.h all auto-merged cleanly thanks to the disjoint addition surfaces (their new files vs our new files). Build clean, 113/113 tests pass, all 3 headless smokes green (PNG capture, FP32 pixel readback, script batch). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2 parents 8a94690 + 807bf20 commit 6bc3ec2

12 files changed

Lines changed: 1213 additions & 342 deletions
Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
#include "pch_engine.h"
2+
#include "DxgiDuplicationSourceProvider.h"
3+
4+
namespace ShaderLab::Effects
5+
{
6+
namespace
7+
{
8+
std::wstring HResultMessage(HRESULT hr)
9+
{
10+
return std::format(L"HRESULT 0x{:08X}", static_cast<uint32_t>(hr));
11+
}
12+
13+
winrt::com_ptr<IDXGIFactory1> GetFactoryFromDevice(ID3D11Device* d3dDevice)
14+
{
15+
if (!d3dDevice)
16+
return nullptr;
17+
18+
winrt::com_ptr<IDXGIDevice> dxgiDevice;
19+
if (FAILED(d3dDevice->QueryInterface(IID_PPV_ARGS(dxgiDevice.put()))))
20+
return nullptr;
21+
22+
winrt::com_ptr<IDXGIAdapter> adapter;
23+
if (FAILED(dxgiDevice->GetAdapter(adapter.put())))
24+
return nullptr;
25+
26+
winrt::com_ptr<IDXGIFactory1> factory;
27+
if (FAILED(adapter->GetParent(IID_PPV_ARGS(factory.put()))))
28+
return nullptr;
29+
30+
return factory;
31+
}
32+
33+
winrt::com_ptr<IDXGIFactory1> CreateFactory()
34+
{
35+
winrt::com_ptr<IDXGIFactory1> factory;
36+
if (FAILED(CreateDXGIFactory1(IID_PPV_ARGS(factory.put()))))
37+
return nullptr;
38+
return factory;
39+
}
40+
}
41+
42+
DxgiDuplicationSourceProvider::~DxgiDuplicationSourceProvider()
43+
{
44+
Close();
45+
}
46+
47+
std::vector<DxgiOutputInfo> DxgiDuplicationSourceProvider::EnumerateOutputs(ID3D11Device* d3dDevice)
48+
{
49+
std::vector<DxgiOutputInfo> outputs;
50+
auto factory = d3dDevice ? GetFactoryFromDevice(d3dDevice) : CreateFactory();
51+
if (!factory)
52+
return outputs;
53+
54+
for (uint32_t adapterIndex = 0;; ++adapterIndex)
55+
{
56+
winrt::com_ptr<IDXGIAdapter1> adapter;
57+
if (factory->EnumAdapters1(adapterIndex, adapter.put()) == DXGI_ERROR_NOT_FOUND)
58+
break;
59+
if (!adapter)
60+
continue;
61+
62+
for (uint32_t outputIndex = 0;; ++outputIndex)
63+
{
64+
winrt::com_ptr<IDXGIOutput> output;
65+
HRESULT hr = adapter->EnumOutputs(outputIndex, output.put());
66+
if (hr == DXGI_ERROR_NOT_FOUND)
67+
break;
68+
if (FAILED(hr) || !output)
69+
continue;
70+
71+
DXGI_OUTPUT_DESC desc{};
72+
if (FAILED(output->GetDesc(&desc)))
73+
continue;
74+
75+
DxgiOutputInfo info;
76+
info.adapterIndex = adapterIndex;
77+
info.outputIndex = outputIndex;
78+
info.deviceName = desc.DeviceName;
79+
info.desktopCoordinates = desc.DesktopCoordinates;
80+
info.attachedToDesktop = desc.AttachedToDesktop != FALSE;
81+
outputs.push_back(std::move(info));
82+
}
83+
}
84+
85+
return outputs;
86+
}
87+
88+
bool DxgiDuplicationSourceProvider::Open(
89+
ID2D1DeviceContext5* dc,
90+
ID3D11Device* d3dDevice,
91+
uint32_t adapterIndex,
92+
uint32_t outputIndex,
93+
bool rawFP16)
94+
{
95+
Close();
96+
97+
if (!dc || !d3dDevice)
98+
{
99+
m_lastError = L"Missing D2D or D3D device";
100+
return false;
101+
}
102+
103+
m_adapterIndex = adapterIndex;
104+
m_outputIndex = outputIndex;
105+
m_rawFP16 = rawFP16;
106+
m_d3dDevice.copy_from(d3dDevice);
107+
108+
auto factory = GetFactoryFromDevice(d3dDevice);
109+
if (!factory)
110+
{
111+
m_lastError = L"Failed to get DXGI factory";
112+
return false;
113+
}
114+
115+
winrt::com_ptr<IDXGIAdapter1> adapter;
116+
HRESULT hr = factory->EnumAdapters1(adapterIndex, adapter.put());
117+
if (FAILED(hr) || !adapter)
118+
{
119+
m_lastError = L"Failed to enumerate DXGI adapter: " + HResultMessage(hr);
120+
return false;
121+
}
122+
123+
winrt::com_ptr<IDXGIOutput> output;
124+
hr = adapter->EnumOutputs(outputIndex, output.put());
125+
if (FAILED(hr) || !output)
126+
{
127+
m_lastError = L"Failed to enumerate DXGI output: " + HResultMessage(hr);
128+
return false;
129+
}
130+
131+
DXGI_OUTPUT_DESC outputDesc{};
132+
output->GetDesc(&outputDesc);
133+
m_outputName = outputDesc.DeviceName;
134+
135+
if (rawFP16)
136+
{
137+
winrt::com_ptr<IDXGIOutput5> output5;
138+
hr = output->QueryInterface(IID_PPV_ARGS(output5.put()));
139+
if (SUCCEEDED(hr) && output5)
140+
{
141+
DXGI_FORMAT formats[] = { DXGI_FORMAT_R16G16B16A16_FLOAT, DXGI_FORMAT_B8G8R8A8_UNORM };
142+
hr = output5->DuplicateOutput1(
143+
d3dDevice,
144+
0,
145+
static_cast<UINT>(std::size(formats)),
146+
formats,
147+
m_duplication.put());
148+
}
149+
}
150+
151+
if (!m_duplication)
152+
{
153+
winrt::com_ptr<IDXGIOutput1> output1;
154+
hr = output->QueryInterface(IID_PPV_ARGS(output1.put()));
155+
if (FAILED(hr) || !output1)
156+
{
157+
m_lastError = L"DXGI output does not support DuplicateOutput: " + HResultMessage(hr);
158+
return false;
159+
}
160+
161+
hr = output1->DuplicateOutput(d3dDevice, m_duplication.put());
162+
}
163+
if (FAILED(hr) || !m_duplication)
164+
{
165+
m_lastError = L"DuplicateOutput failed: " + HResultMessage(hr);
166+
return false;
167+
}
168+
169+
DXGI_OUTDUPL_DESC dupDesc{};
170+
m_duplication->GetDesc(&dupDesc);
171+
m_width = dupDesc.ModeDesc.Width;
172+
m_height = dupDesc.ModeDesc.Height;
173+
m_outputFormat = dupDesc.ModeDesc.Format == DXGI_FORMAT_R16G16B16A16_FLOAT
174+
? DXGI_FORMAT_R16G16B16A16_FLOAT
175+
: DXGI_FORMAT_B8G8R8A8_UNORM;
176+
177+
if (!CreateOutputTextureAndBitmap(dc, d3dDevice, m_width, m_height, m_outputFormat))
178+
{
179+
Close();
180+
return false;
181+
}
182+
183+
m_lastError.clear();
184+
return true;
185+
}
186+
187+
void DxgiDuplicationSourceProvider::Close()
188+
{
189+
m_bitmap = nullptr;
190+
m_outputTexture = nullptr;
191+
m_duplication = nullptr;
192+
m_d3dDevice = nullptr;
193+
m_width = 0;
194+
m_height = 0;
195+
m_outputFormat = DXGI_FORMAT_B8G8R8A8_UNORM;
196+
m_frameCount = 0;
197+
}
198+
199+
bool DxgiDuplicationSourceProvider::CreateOutputTextureAndBitmap(
200+
ID2D1DeviceContext5* dc,
201+
ID3D11Device* d3dDevice,
202+
uint32_t width,
203+
uint32_t height,
204+
DXGI_FORMAT format)
205+
{
206+
if (!dc || !d3dDevice || width == 0 || height == 0)
207+
return false;
208+
209+
D3D11_TEXTURE2D_DESC texDesc{};
210+
texDesc.Width = width;
211+
texDesc.Height = height;
212+
texDesc.MipLevels = 1;
213+
texDesc.ArraySize = 1;
214+
texDesc.Format = format;
215+
texDesc.SampleDesc.Count = 1;
216+
texDesc.Usage = D3D11_USAGE_DEFAULT;
217+
texDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
218+
219+
HRESULT hr = d3dDevice->CreateTexture2D(&texDesc, nullptr, m_outputTexture.put());
220+
if (FAILED(hr) || !m_outputTexture)
221+
{
222+
m_lastError = L"Failed to create duplication output texture: " + HResultMessage(hr);
223+
return false;
224+
}
225+
226+
winrt::com_ptr<IDXGISurface> surface;
227+
hr = m_outputTexture->QueryInterface(IID_PPV_ARGS(surface.put()));
228+
if (FAILED(hr) || !surface)
229+
{
230+
m_lastError = L"Failed to query output texture surface: " + HResultMessage(hr);
231+
return false;
232+
}
233+
234+
D2D1_BITMAP_PROPERTIES1 bitmapProps = D2D1::BitmapProperties1(
235+
D2D1_BITMAP_OPTIONS_NONE,
236+
D2D1::PixelFormat(format, D2D1_ALPHA_MODE_PREMULTIPLIED));
237+
238+
hr = dc->CreateBitmapFromDxgiSurface(surface.get(), &bitmapProps, m_bitmap.put());
239+
if (FAILED(hr) || !m_bitmap)
240+
{
241+
m_lastError = L"Failed to create D2D bitmap for duplication output: " + HResultMessage(hr);
242+
return false;
243+
}
244+
245+
return true;
246+
}
247+
248+
bool DxgiDuplicationSourceProvider::Reopen(ID2D1DeviceContext5* dc)
249+
{
250+
auto device = m_d3dDevice;
251+
auto adapterIndex = m_adapterIndex;
252+
auto outputIndex = m_outputIndex;
253+
auto rawFP16 = m_rawFP16;
254+
Close();
255+
return Open(dc, device.get(), adapterIndex, outputIndex, rawFP16);
256+
}
257+
258+
bool DxgiDuplicationSourceProvider::CaptureNextFrame(ID2D1DeviceContext5* dc)
259+
{
260+
if (!m_duplication || !m_d3dDevice)
261+
return false;
262+
263+
DXGI_OUTDUPL_FRAME_INFO frameInfo{};
264+
winrt::com_ptr<IDXGIResource> resource;
265+
HRESULT hr = m_duplication->AcquireNextFrame(0, &frameInfo, resource.put());
266+
if (hr == DXGI_ERROR_WAIT_TIMEOUT)
267+
return false;
268+
269+
if (hr == DXGI_ERROR_ACCESS_LOST)
270+
{
271+
Reopen(dc);
272+
return false;
273+
}
274+
275+
if (FAILED(hr) || !resource)
276+
{
277+
m_lastError = L"AcquireNextFrame failed: " + HResultMessage(hr);
278+
return false;
279+
}
280+
281+
bool copied = false;
282+
winrt::com_ptr<ID3D11Texture2D> frameTexture;
283+
hr = resource->QueryInterface(IID_PPV_ARGS(frameTexture.put()));
284+
if (SUCCEEDED(hr) && frameTexture && m_outputTexture)
285+
{
286+
D3D11_TEXTURE2D_DESC frameDesc{};
287+
frameTexture->GetDesc(&frameDesc);
288+
289+
DXGI_FORMAT frameFormat = frameDesc.Format == DXGI_FORMAT_R16G16B16A16_FLOAT
290+
? DXGI_FORMAT_R16G16B16A16_FLOAT
291+
: DXGI_FORMAT_B8G8R8A8_UNORM;
292+
293+
if (frameDesc.Width != m_width || frameDesc.Height != m_height || frameFormat != m_outputFormat)
294+
{
295+
m_width = frameDesc.Width;
296+
m_height = frameDesc.Height;
297+
m_outputFormat = frameFormat;
298+
m_outputTexture = nullptr;
299+
m_bitmap = nullptr;
300+
CreateOutputTextureAndBitmap(dc, m_d3dDevice.get(), m_width, m_height, m_outputFormat);
301+
}
302+
303+
winrt::com_ptr<ID3D11DeviceContext> context;
304+
m_d3dDevice->GetImmediateContext(context.put());
305+
if (context && m_outputTexture)
306+
{
307+
context->CopyResource(m_outputTexture.get(), frameTexture.get());
308+
copied = true;
309+
++m_frameCount;
310+
}
311+
}
312+
313+
m_duplication->ReleaseFrame();
314+
return copied;
315+
}
316+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#pragma once
2+
3+
#include "pch_engine.h"
4+
#include "../EngineExport.h"
5+
6+
namespace ShaderLab::Effects
7+
{
8+
struct SHADERLAB_API DxgiOutputInfo
9+
{
10+
uint32_t adapterIndex{ 0 };
11+
uint32_t outputIndex{ 0 };
12+
std::wstring deviceName;
13+
RECT desktopCoordinates{};
14+
bool attachedToDesktop{ false };
15+
};
16+
17+
class SHADERLAB_API DxgiDuplicationSourceProvider
18+
{
19+
public:
20+
DxgiDuplicationSourceProvider() = default;
21+
DxgiDuplicationSourceProvider(const DxgiDuplicationSourceProvider&) = delete;
22+
DxgiDuplicationSourceProvider& operator=(const DxgiDuplicationSourceProvider&) = delete;
23+
~DxgiDuplicationSourceProvider();
24+
25+
static std::vector<DxgiOutputInfo> EnumerateOutputs(ID3D11Device* d3dDevice = nullptr);
26+
27+
bool Open(
28+
ID2D1DeviceContext5* dc,
29+
ID3D11Device* d3dDevice,
30+
uint32_t adapterIndex,
31+
uint32_t outputIndex,
32+
bool rawFP16 = false);
33+
void Close();
34+
35+
bool IsOpen() const { return m_duplication != nullptr; }
36+
bool CaptureNextFrame(ID2D1DeviceContext5* dc);
37+
ID2D1Image* CurrentBitmap() const { return m_bitmap.get(); }
38+
39+
uint32_t Width() const { return m_width; }
40+
uint32_t Height() const { return m_height; }
41+
uint64_t FrameCount() const { return m_frameCount; }
42+
uint32_t AdapterIndex() const { return m_adapterIndex; }
43+
uint32_t OutputIndex() const { return m_outputIndex; }
44+
bool RawFP16() const { return m_rawFP16; }
45+
DXGI_FORMAT OutputFormat() const { return m_outputFormat; }
46+
const std::wstring& OutputName() const { return m_outputName; }
47+
const std::wstring& LastError() const { return m_lastError; }
48+
49+
private:
50+
bool CreateOutputTextureAndBitmap(ID2D1DeviceContext5* dc, ID3D11Device* d3dDevice, uint32_t width, uint32_t height, DXGI_FORMAT format);
51+
bool Reopen(ID2D1DeviceContext5* dc);
52+
53+
winrt::com_ptr<IDXGIOutputDuplication> m_duplication;
54+
winrt::com_ptr<ID3D11Device> m_d3dDevice;
55+
winrt::com_ptr<ID3D11Texture2D> m_outputTexture;
56+
winrt::com_ptr<ID2D1Bitmap1> m_bitmap;
57+
58+
uint32_t m_adapterIndex{ 0 };
59+
uint32_t m_outputIndex{ 0 };
60+
uint32_t m_width{ 0 };
61+
uint32_t m_height{ 0 };
62+
bool m_rawFP16{ false };
63+
DXGI_FORMAT m_outputFormat{ DXGI_FORMAT_B8G8R8A8_UNORM };
64+
uint64_t m_frameCount{ 0 };
65+
std::wstring m_outputName;
66+
std::wstring m_lastError;
67+
};
68+
}

0 commit comments

Comments
 (0)