From cac0fc17617e8d99f50d6b41b846618ff5fa2757 Mon Sep 17 00:00:00 2001 From: Zachary Northrup Date: Wed, 6 May 2026 00:25:01 -0700 Subject: [PATCH 1/2] Fix DPI of the graph viewer --- MainWindow.xaml.cpp | 81 ++++++++++++++++++++++++++++++++++----------- MainWindow.xaml.h | 5 ++- 2 files changed, 65 insertions(+), 21 deletions(-) diff --git a/MainWindow.xaml.cpp b/MainWindow.xaml.cpp index 7e2da75..4d2e19d 100644 --- a/MainWindow.xaml.cpp +++ b/MainWindow.xaml.cpp @@ -164,10 +164,16 @@ namespace winrt::ShaderLab::implementation PreviewPanel().Loaded([this](auto&&, auto&&) { OnPreviewPanelLoaded(); }); NodeGraphPanel().SizeChanged([this](auto&&, winrt::Microsoft::UI::Xaml::SizeChangedEventArgs const& e) { - // Use DIP dimensions directly — DXGI stretches to physical pixels. - auto w = static_cast((std::max)(1.0f, static_cast(e.NewSize().Width))); - auto h = static_cast((std::max)(1.0f, static_cast(e.NewSize().Height))); - ResizeGraphPanel(w, h); + ResizeGraphPanel( + static_cast(e.NewSize().Width), + static_cast(e.NewSize().Height)); + }); + NodeGraphPanel().CompositionScaleChanged([this](auto&&, auto&&) + { + UpdateGraphPanelScale(); + ResizeGraphPanel( + static_cast(NodeGraphPanel().ActualWidth()), + static_cast(NodeGraphPanel().ActualHeight())); }); NodeGraphContainer().PointerPressed({ this, &MainWindow::OnGraphPanelPointerPressed }); NodeGraphContainer().PointerMoved({ this, &MainWindow::OnGraphPanelPointerMoved }); @@ -3014,10 +3020,15 @@ namespace winrt::ShaderLab::implementation return; auto panel = NodeGraphPanel(); - m_graphPanelWidth = static_cast((std::max)(1.0f, static_cast(panel.ActualWidth()))); - m_graphPanelHeight = static_cast((std::max)(1.0f, static_cast(panel.ActualHeight()))); - if (m_graphPanelWidth == 0) m_graphPanelWidth = 400; - if (m_graphPanelHeight == 0) m_graphPanelHeight = 300; + m_graphPanelDipsWidth = (std::max)(1.0f, static_cast(panel.ActualWidth())); + m_graphPanelDipsHeight = (std::max)(1.0f, static_cast(panel.ActualHeight())); + if (m_graphPanelDipsWidth <= 1.0f) m_graphPanelDipsWidth = 400.0f; + if (m_graphPanelDipsHeight <= 1.0f) m_graphPanelDipsHeight = 300.0f; + + float scaleX = (std::max)(1.0f, static_cast(panel.CompositionScaleX())); + float scaleY = (std::max)(1.0f, static_cast(panel.CompositionScaleY())); + m_graphPanelWidth = static_cast((std::max)(1.0f, std::ceil(m_graphPanelDipsWidth * scaleX))); + m_graphPanelHeight = static_cast((std::max)(1.0f, std::ceil(m_graphPanelDipsHeight * scaleY))); DXGI_SWAP_CHAIN_DESC1 desc{}; desc.Width = m_graphPanelWidth; @@ -3043,6 +3054,7 @@ namespace winrt::ShaderLab::implementation // Bind swap chain to the panel. auto panelNative = panel.as(); panelNative->SetSwapChain(m_graphSwapChain.get()); + UpdateGraphPanelScale(); // Create render target. auto* dc = m_renderEngine.D2DDeviceContext(); @@ -3055,14 +3067,28 @@ namespace winrt::ShaderLab::implementation dc->CreateBitmapFromDxgiSurface(surface.get(), bmpProps, m_graphRenderTarget.put()); m_nodeGraphController.SetViewportSize( - static_cast(m_graphPanelWidth), - static_cast(m_graphPanelHeight)); + m_graphPanelDipsWidth, + m_graphPanelDipsHeight); } - void MainWindow::ResizeGraphPanel(uint32_t w, uint32_t h) + void MainWindow::ResizeGraphPanel(float widthDips, float heightDips) { + auto panel = NodeGraphPanel(); + m_graphPanelDipsWidth = (std::max)(1.0f, widthDips); + m_graphPanelDipsHeight = (std::max)(1.0f, heightDips); + + float scaleX = (std::max)(1.0f, static_cast(panel.CompositionScaleX())); + float scaleY = (std::max)(1.0f, static_cast(panel.CompositionScaleY())); + auto w = static_cast((std::max)(1.0f, std::ceil(m_graphPanelDipsWidth * scaleX))); + auto h = static_cast((std::max)(1.0f, std::ceil(m_graphPanelDipsHeight * scaleY))); + + m_nodeGraphController.SetViewportSize(m_graphPanelDipsWidth, m_graphPanelDipsHeight); + if (!m_graphSwapChain || (w == m_graphPanelWidth && h == m_graphPanelHeight)) + { + m_nodeGraphController.SetNeedsRedraw(); return; + } m_graphPanelWidth = w; m_graphPanelHeight = h; @@ -3081,9 +3107,26 @@ namespace winrt::ShaderLab::implementation D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED)); dc->CreateBitmapFromDxgiSurface(surface.get(), bmpProps, m_graphRenderTarget.put()); - m_nodeGraphController.SetViewportSize( - static_cast(m_graphPanelWidth), - static_cast(m_graphPanelHeight)); + m_nodeGraphController.SetNeedsRedraw(); + } + + void MainWindow::UpdateGraphPanelScale() + { + if (!m_graphSwapChain) + return; + + winrt::com_ptr swapChain2; + if (FAILED(m_graphSwapChain->QueryInterface(IID_PPV_ARGS(swapChain2.put())))) + return; + + auto panel = NodeGraphPanel(); + float scaleX = (std::max)(1.0f, static_cast(panel.CompositionScaleX())); + float scaleY = (std::max)(1.0f, static_cast(panel.CompositionScaleY())); + + DXGI_MATRIX_3X2_F matrix{}; + matrix._11 = 1.0f / scaleX; + matrix._22 = 1.0f / scaleY; + swapChain2->SetMatrixTransform(&matrix); } void MainWindow::InitializeTraceSwatchPanel() @@ -3237,18 +3280,16 @@ namespace winrt::ShaderLab::implementation winrt::com_ptr oldTarget; dc->GetTarget(oldTarget.put()); - // The graph swap chain is sized in DIPs (not physical pixels), so - // render at 96 DPI so D2D coordinates match the DIP-based pointer - // input. The XAML compositor handles scaling to physical pixels. float oldDpiX, oldDpiY; dc->GetDpi(&oldDpiX, &oldDpiY); - dc->SetDpi(96.0f, 96.0f); + float graphDpiX = 96.0f * (std::max)(1.0f, static_cast(NodeGraphPanel().CompositionScaleX())); + float graphDpiY = 96.0f * (std::max)(1.0f, static_cast(NodeGraphPanel().CompositionScaleY())); + dc->SetDpi(graphDpiX, graphDpiY); dc->SetTarget(m_graphRenderTarget.get()); dc->BeginDraw(); - D2D1_SIZE_F viewSize = { static_cast(m_graphPanelWidth), - static_cast(m_graphPanelHeight) }; + D2D1_SIZE_F viewSize = { m_graphPanelDipsWidth, m_graphPanelDipsHeight }; RenderGraphScene(dc, viewSize); dc->EndDraw(); diff --git a/MainWindow.xaml.h b/MainWindow.xaml.h index bf9ba83..eaa24ba 100644 --- a/MainWindow.xaml.h +++ b/MainWindow.xaml.h @@ -303,9 +303,12 @@ namespace winrt::ShaderLab::implementation winrt::com_ptr m_graphGridBrush; uint32_t m_graphPanelWidth{ 0 }; uint32_t m_graphPanelHeight{ 0 }; + float m_graphPanelDipsWidth{ 0.0f }; + float m_graphPanelDipsHeight{ 0.0f }; void InitializeGraphPanel(); - void ResizeGraphPanel(uint32_t w, uint32_t h); + void ResizeGraphPanel(float widthDips, float heightDips); + void UpdateGraphPanelScale(); void RenderNodeGraph(); D2D1_POINT_2F GraphPanelPointerToCanvas( winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& args); From 3cb000b7016f6b8ac4079837ce1d3dba80394233 Mon Sep 17 00:00:00 2001 From: Zachary Northrup Date: Wed, 6 May 2026 01:43:42 -0700 Subject: [PATCH 2/2] Add DXGI DDA source and WGC source with picker logic --- Effects/DxgiDuplicationSourceProvider.cpp | 316 ++++++++++++ Effects/DxgiDuplicationSourceProvider.h | 68 +++ Effects/SourceNodeFactory.cpp | 211 ++++++++ Effects/SourceNodeFactory.h | 22 + .../WindowsGraphicsCaptureSourceProvider.cpp | 265 ++++++++++ .../WindowsGraphicsCaptureSourceProvider.h | 61 +++ MainWindow.xaml.cpp | 70 ++- MainWindow.xaml.h | 1 + README.md | 1 + ShaderLab.vcxproj | 2 + ShaderLab.vcxproj.filters | 456 +++++------------- ShaderLabEngine.vcxproj | 6 +- 12 files changed, 1150 insertions(+), 329 deletions(-) create mode 100644 Effects/DxgiDuplicationSourceProvider.cpp create mode 100644 Effects/DxgiDuplicationSourceProvider.h create mode 100644 Effects/WindowsGraphicsCaptureSourceProvider.cpp create mode 100644 Effects/WindowsGraphicsCaptureSourceProvider.h diff --git a/Effects/DxgiDuplicationSourceProvider.cpp b/Effects/DxgiDuplicationSourceProvider.cpp new file mode 100644 index 0000000..a42a84b --- /dev/null +++ b/Effects/DxgiDuplicationSourceProvider.cpp @@ -0,0 +1,316 @@ +#include "pch_engine.h" +#include "DxgiDuplicationSourceProvider.h" + +namespace ShaderLab::Effects +{ + namespace + { + std::wstring HResultMessage(HRESULT hr) + { + return std::format(L"HRESULT 0x{:08X}", static_cast(hr)); + } + + winrt::com_ptr GetFactoryFromDevice(ID3D11Device* d3dDevice) + { + if (!d3dDevice) + return nullptr; + + winrt::com_ptr dxgiDevice; + if (FAILED(d3dDevice->QueryInterface(IID_PPV_ARGS(dxgiDevice.put())))) + return nullptr; + + winrt::com_ptr adapter; + if (FAILED(dxgiDevice->GetAdapter(adapter.put()))) + return nullptr; + + winrt::com_ptr factory; + if (FAILED(adapter->GetParent(IID_PPV_ARGS(factory.put())))) + return nullptr; + + return factory; + } + + winrt::com_ptr CreateFactory() + { + winrt::com_ptr factory; + if (FAILED(CreateDXGIFactory1(IID_PPV_ARGS(factory.put())))) + return nullptr; + return factory; + } + } + + DxgiDuplicationSourceProvider::~DxgiDuplicationSourceProvider() + { + Close(); + } + + std::vector DxgiDuplicationSourceProvider::EnumerateOutputs(ID3D11Device* d3dDevice) + { + std::vector outputs; + auto factory = d3dDevice ? GetFactoryFromDevice(d3dDevice) : CreateFactory(); + if (!factory) + return outputs; + + for (uint32_t adapterIndex = 0;; ++adapterIndex) + { + winrt::com_ptr adapter; + if (factory->EnumAdapters1(adapterIndex, adapter.put()) == DXGI_ERROR_NOT_FOUND) + break; + if (!adapter) + continue; + + for (uint32_t outputIndex = 0;; ++outputIndex) + { + winrt::com_ptr output; + HRESULT hr = adapter->EnumOutputs(outputIndex, output.put()); + if (hr == DXGI_ERROR_NOT_FOUND) + break; + if (FAILED(hr) || !output) + continue; + + DXGI_OUTPUT_DESC desc{}; + if (FAILED(output->GetDesc(&desc))) + continue; + + DxgiOutputInfo info; + info.adapterIndex = adapterIndex; + info.outputIndex = outputIndex; + info.deviceName = desc.DeviceName; + info.desktopCoordinates = desc.DesktopCoordinates; + info.attachedToDesktop = desc.AttachedToDesktop != FALSE; + outputs.push_back(std::move(info)); + } + } + + return outputs; + } + + bool DxgiDuplicationSourceProvider::Open( + ID2D1DeviceContext5* dc, + ID3D11Device* d3dDevice, + uint32_t adapterIndex, + uint32_t outputIndex, + bool rawFP16) + { + Close(); + + if (!dc || !d3dDevice) + { + m_lastError = L"Missing D2D or D3D device"; + return false; + } + + m_adapterIndex = adapterIndex; + m_outputIndex = outputIndex; + m_rawFP16 = rawFP16; + m_d3dDevice.copy_from(d3dDevice); + + auto factory = GetFactoryFromDevice(d3dDevice); + if (!factory) + { + m_lastError = L"Failed to get DXGI factory"; + return false; + } + + winrt::com_ptr adapter; + HRESULT hr = factory->EnumAdapters1(adapterIndex, adapter.put()); + if (FAILED(hr) || !adapter) + { + m_lastError = L"Failed to enumerate DXGI adapter: " + HResultMessage(hr); + return false; + } + + winrt::com_ptr output; + hr = adapter->EnumOutputs(outputIndex, output.put()); + if (FAILED(hr) || !output) + { + m_lastError = L"Failed to enumerate DXGI output: " + HResultMessage(hr); + return false; + } + + DXGI_OUTPUT_DESC outputDesc{}; + output->GetDesc(&outputDesc); + m_outputName = outputDesc.DeviceName; + + if (rawFP16) + { + winrt::com_ptr output5; + hr = output->QueryInterface(IID_PPV_ARGS(output5.put())); + if (SUCCEEDED(hr) && output5) + { + DXGI_FORMAT formats[] = { DXGI_FORMAT_R16G16B16A16_FLOAT, DXGI_FORMAT_B8G8R8A8_UNORM }; + hr = output5->DuplicateOutput1( + d3dDevice, + 0, + static_cast(std::size(formats)), + formats, + m_duplication.put()); + } + } + + if (!m_duplication) + { + winrt::com_ptr output1; + hr = output->QueryInterface(IID_PPV_ARGS(output1.put())); + if (FAILED(hr) || !output1) + { + m_lastError = L"DXGI output does not support DuplicateOutput: " + HResultMessage(hr); + return false; + } + + hr = output1->DuplicateOutput(d3dDevice, m_duplication.put()); + } + if (FAILED(hr) || !m_duplication) + { + m_lastError = L"DuplicateOutput failed: " + HResultMessage(hr); + return false; + } + + DXGI_OUTDUPL_DESC dupDesc{}; + m_duplication->GetDesc(&dupDesc); + m_width = dupDesc.ModeDesc.Width; + m_height = dupDesc.ModeDesc.Height; + m_outputFormat = dupDesc.ModeDesc.Format == DXGI_FORMAT_R16G16B16A16_FLOAT + ? DXGI_FORMAT_R16G16B16A16_FLOAT + : DXGI_FORMAT_B8G8R8A8_UNORM; + + if (!CreateOutputTextureAndBitmap(dc, d3dDevice, m_width, m_height, m_outputFormat)) + { + Close(); + return false; + } + + m_lastError.clear(); + return true; + } + + void DxgiDuplicationSourceProvider::Close() + { + m_bitmap = nullptr; + m_outputTexture = nullptr; + m_duplication = nullptr; + m_d3dDevice = nullptr; + m_width = 0; + m_height = 0; + m_outputFormat = DXGI_FORMAT_B8G8R8A8_UNORM; + m_frameCount = 0; + } + + bool DxgiDuplicationSourceProvider::CreateOutputTextureAndBitmap( + ID2D1DeviceContext5* dc, + ID3D11Device* d3dDevice, + uint32_t width, + uint32_t height, + DXGI_FORMAT format) + { + if (!dc || !d3dDevice || width == 0 || height == 0) + return false; + + D3D11_TEXTURE2D_DESC texDesc{}; + texDesc.Width = width; + texDesc.Height = height; + texDesc.MipLevels = 1; + texDesc.ArraySize = 1; + texDesc.Format = format; + texDesc.SampleDesc.Count = 1; + texDesc.Usage = D3D11_USAGE_DEFAULT; + texDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET; + + HRESULT hr = d3dDevice->CreateTexture2D(&texDesc, nullptr, m_outputTexture.put()); + if (FAILED(hr) || !m_outputTexture) + { + m_lastError = L"Failed to create duplication output texture: " + HResultMessage(hr); + return false; + } + + winrt::com_ptr surface; + hr = m_outputTexture->QueryInterface(IID_PPV_ARGS(surface.put())); + if (FAILED(hr) || !surface) + { + m_lastError = L"Failed to query output texture surface: " + HResultMessage(hr); + return false; + } + + D2D1_BITMAP_PROPERTIES1 bitmapProps = D2D1::BitmapProperties1( + D2D1_BITMAP_OPTIONS_NONE, + D2D1::PixelFormat(format, D2D1_ALPHA_MODE_PREMULTIPLIED)); + + hr = dc->CreateBitmapFromDxgiSurface(surface.get(), &bitmapProps, m_bitmap.put()); + if (FAILED(hr) || !m_bitmap) + { + m_lastError = L"Failed to create D2D bitmap for duplication output: " + HResultMessage(hr); + return false; + } + + return true; + } + + bool DxgiDuplicationSourceProvider::Reopen(ID2D1DeviceContext5* dc) + { + auto device = m_d3dDevice; + auto adapterIndex = m_adapterIndex; + auto outputIndex = m_outputIndex; + auto rawFP16 = m_rawFP16; + Close(); + return Open(dc, device.get(), adapterIndex, outputIndex, rawFP16); + } + + bool DxgiDuplicationSourceProvider::CaptureNextFrame(ID2D1DeviceContext5* dc) + { + if (!m_duplication || !m_d3dDevice) + return false; + + DXGI_OUTDUPL_FRAME_INFO frameInfo{}; + winrt::com_ptr resource; + HRESULT hr = m_duplication->AcquireNextFrame(0, &frameInfo, resource.put()); + if (hr == DXGI_ERROR_WAIT_TIMEOUT) + return false; + + if (hr == DXGI_ERROR_ACCESS_LOST) + { + Reopen(dc); + return false; + } + + if (FAILED(hr) || !resource) + { + m_lastError = L"AcquireNextFrame failed: " + HResultMessage(hr); + return false; + } + + bool copied = false; + winrt::com_ptr frameTexture; + hr = resource->QueryInterface(IID_PPV_ARGS(frameTexture.put())); + if (SUCCEEDED(hr) && frameTexture && m_outputTexture) + { + D3D11_TEXTURE2D_DESC frameDesc{}; + frameTexture->GetDesc(&frameDesc); + + DXGI_FORMAT frameFormat = frameDesc.Format == DXGI_FORMAT_R16G16B16A16_FLOAT + ? DXGI_FORMAT_R16G16B16A16_FLOAT + : DXGI_FORMAT_B8G8R8A8_UNORM; + + if (frameDesc.Width != m_width || frameDesc.Height != m_height || frameFormat != m_outputFormat) + { + m_width = frameDesc.Width; + m_height = frameDesc.Height; + m_outputFormat = frameFormat; + m_outputTexture = nullptr; + m_bitmap = nullptr; + CreateOutputTextureAndBitmap(dc, m_d3dDevice.get(), m_width, m_height, m_outputFormat); + } + + winrt::com_ptr context; + m_d3dDevice->GetImmediateContext(context.put()); + if (context && m_outputTexture) + { + context->CopyResource(m_outputTexture.get(), frameTexture.get()); + copied = true; + ++m_frameCount; + } + } + + m_duplication->ReleaseFrame(); + return copied; + } +} diff --git a/Effects/DxgiDuplicationSourceProvider.h b/Effects/DxgiDuplicationSourceProvider.h new file mode 100644 index 0000000..ec3189c --- /dev/null +++ b/Effects/DxgiDuplicationSourceProvider.h @@ -0,0 +1,68 @@ +#pragma once + +#include "pch_engine.h" +#include "../EngineExport.h" + +namespace ShaderLab::Effects +{ + struct SHADERLAB_API DxgiOutputInfo + { + uint32_t adapterIndex{ 0 }; + uint32_t outputIndex{ 0 }; + std::wstring deviceName; + RECT desktopCoordinates{}; + bool attachedToDesktop{ false }; + }; + + class SHADERLAB_API DxgiDuplicationSourceProvider + { + public: + DxgiDuplicationSourceProvider() = default; + DxgiDuplicationSourceProvider(const DxgiDuplicationSourceProvider&) = delete; + DxgiDuplicationSourceProvider& operator=(const DxgiDuplicationSourceProvider&) = delete; + ~DxgiDuplicationSourceProvider(); + + static std::vector EnumerateOutputs(ID3D11Device* d3dDevice = nullptr); + + bool Open( + ID2D1DeviceContext5* dc, + ID3D11Device* d3dDevice, + uint32_t adapterIndex, + uint32_t outputIndex, + bool rawFP16 = false); + void Close(); + + bool IsOpen() const { return m_duplication != nullptr; } + bool CaptureNextFrame(ID2D1DeviceContext5* dc); + ID2D1Image* CurrentBitmap() const { return m_bitmap.get(); } + + uint32_t Width() const { return m_width; } + uint32_t Height() const { return m_height; } + uint64_t FrameCount() const { return m_frameCount; } + uint32_t AdapterIndex() const { return m_adapterIndex; } + uint32_t OutputIndex() const { return m_outputIndex; } + bool RawFP16() const { return m_rawFP16; } + DXGI_FORMAT OutputFormat() const { return m_outputFormat; } + const std::wstring& OutputName() const { return m_outputName; } + const std::wstring& LastError() const { return m_lastError; } + + private: + bool CreateOutputTextureAndBitmap(ID2D1DeviceContext5* dc, ID3D11Device* d3dDevice, uint32_t width, uint32_t height, DXGI_FORMAT format); + bool Reopen(ID2D1DeviceContext5* dc); + + winrt::com_ptr m_duplication; + winrt::com_ptr m_d3dDevice; + winrt::com_ptr m_outputTexture; + winrt::com_ptr m_bitmap; + + uint32_t m_adapterIndex{ 0 }; + uint32_t m_outputIndex{ 0 }; + uint32_t m_width{ 0 }; + uint32_t m_height{ 0 }; + bool m_rawFP16{ false }; + DXGI_FORMAT m_outputFormat{ DXGI_FORMAT_B8G8R8A8_UNORM }; + uint64_t m_frameCount{ 0 }; + std::wstring m_outputName; + std::wstring m_lastError; + }; +} diff --git a/Effects/SourceNodeFactory.cpp b/Effects/SourceNodeFactory.cpp index d31288f..006a46e 100644 --- a/Effects/SourceNodeFactory.cpp +++ b/Effects/SourceNodeFactory.cpp @@ -89,6 +89,46 @@ namespace ShaderLab::Effects return node; } + EffectNode SourceNodeFactory::CreateDxgiDuplicateOutputSourceNode( + uint32_t adapterIndex, + uint32_t outputIndex, + const std::wstring& displayName) + { + EffectNode node; + node.type = NodeType::Source; + node.name = displayName.empty() + ? std::format(L"DXGI Output {}:{}", adapterIndex, outputIndex) + : displayName; + node.properties[L"IsDxgiDuplicateOutput"] = true; + node.properties[L"AdapterIndex"] = static_cast(adapterIndex); + node.properties[L"OutputIndex"] = static_cast(outputIndex); + node.properties[L"RawFP16"] = true; + node.outputPins.push_back({ L"Frame", 0 }); + + node.analysisOutput.type = AnalysisOutputType::Typed; + node.analysisOutput.fields.push_back({ L"Width", AnalysisFieldType::Float, {} }); + node.analysisOutput.fields.push_back({ L"Height", AnalysisFieldType::Float, {} }); + node.analysisOutput.fields.push_back({ L"Frames", AnalysisFieldType::Float, {} }); + return node; + } + + EffectNode SourceNodeFactory::CreateWindowsGraphicsCaptureSourceNode( + const std::wstring& displayName) + { + EffectNode node; + node.type = NodeType::Source; + node.name = displayName.empty() ? L"Windows Graphics Capture" : displayName; + node.properties[L"IsWindowsGraphicsCapture"] = true; + node.properties[L"RawFP16"] = true; + node.outputPins.push_back({ L"Frame", 0 }); + + node.analysisOutput.type = AnalysisOutputType::Typed; + node.analysisOutput.fields.push_back({ L"Width", AnalysisFieldType::Float, {} }); + node.analysisOutput.fields.push_back({ L"Height", AnalysisFieldType::Float, {} }); + node.analysisOutput.fields.push_back({ L"Frames", AnalysisFieldType::Float, {} }); + return node; + } + // ----------------------------------------------------------------------- // Source preparation (needs D2D device context) // ----------------------------------------------------------------------- @@ -103,6 +143,87 @@ namespace ShaderLab::Effects if (!dc || node.type != NodeType::Source) return; + auto isDxgiIt = node.properties.find(L"IsDxgiDuplicateOutput"); + if (isDxgiIt != node.properties.end()) + { + auto* boolVal = std::get_if(&isDxgiIt->second); + if (boolVal && *boolVal) + { + if (!d3dDevice) + return; + + uint32_t adapterIndex = 0; + uint32_t outputIndex = 0; + bool rawFP16 = true; + if (auto it = node.properties.find(L"AdapterIndex"); it != node.properties.end()) + if (auto* v = std::get_if(&it->second)) adapterIndex = *v; + if (auto it = node.properties.find(L"OutputIndex"); it != node.properties.end()) + if (auto* v = std::get_if(&it->second)) outputIndex = *v; + if (auto it = node.properties.find(L"RawFP16"); it != node.properties.end()) + if (auto* v = std::get_if(&it->second)) rawFP16 = *v; + + auto& provider = m_dxgiCaptureCache[node.id]; + if (provider && (provider->AdapterIndex() != adapterIndex || provider->OutputIndex() != outputIndex || provider->RawFP16() != rawFP16)) + provider.reset(); + if (!provider) + { + provider = std::make_unique(); + if (!provider->Open(dc, d3dDevice, adapterIndex, outputIndex, rawFP16)) + { + node.runtimeError = L"Failed to open DXGI output capture: " + provider->LastError(); + m_dxgiCaptureCache.erase(node.id); + return; + } + node.properties[L"OutputName"] = provider->OutputName(); + node.runtimeError.clear(); + } + node.cachedOutput = provider->CurrentBitmap(); + return; + } + } + + auto isWgcIt = node.properties.find(L"IsWindowsGraphicsCapture"); + if (isWgcIt != node.properties.end()) + { + auto* boolVal = std::get_if(&isWgcIt->second); + if (boolVal && *boolVal) + { + if (!d3dDevice) + return; + + auto& provider = m_wgcCaptureCache[node.id]; + if (!provider) + { + bool rawFP16 = true; + if (auto it = node.properties.find(L"RawFP16"); it != node.properties.end()) + if (auto* v = std::get_if(&it->second)) rawFP16 = *v; + + auto pendingIt = m_pendingWgcItems.find(node.id); + if (pendingIt == m_pendingWgcItems.end() || !pendingIt->second.has_value() || !pendingIt->second.value()) + { + node.runtimeError = L"Choose a window or monitor for this Windows Graphics Capture source."; + return; + } + + provider = std::make_unique(); + if (!provider->Open(dc, d3dDevice, pendingIt->second.value(), rawFP16)) + { + node.runtimeError = L"Failed to open Windows Graphics Capture source: " + provider->LastError(); + m_wgcCaptureCache.erase(node.id); + return; + } + if (!provider->ItemName().empty()) + { + node.name = provider->ItemName(); + node.properties[L"CaptureItemName"] = provider->ItemName(); + } + node.runtimeError.clear(); + } + node.cachedOutput = provider->CurrentBitmap(); + return; + } + } + // --- Video source --- auto isVideoIt = node.properties.find(L"IsVideo"); if (isVideoIt != node.properties.end()) @@ -249,6 +370,9 @@ namespace ShaderLab::Effects m_bitmapCache.clear(); m_floodCache.clear(); m_videoCache.clear(); + m_dxgiCaptureCache.clear(); + m_wgcCaptureCache.clear(); + m_pendingWgcItems.clear(); m_lastClockTime.clear(); } @@ -404,6 +528,93 @@ namespace ShaderLab::Effects return anyNewFrame; } + bool SourceNodeFactory::TickAndUploadLiveCaptures( + std::vector& nodes, + ID2D1DeviceContext5* dc) + { + bool anyNewFrame = false; + + auto updateAnalysis = [](Graph::EffectNode& node, uint32_t width, uint32_t height, uint64_t frames) + { + node.analysisOutput.type = Graph::AnalysisOutputType::Typed; + node.analysisOutput.fields.clear(); + Graph::AnalysisFieldValue widthFv; + widthFv.name = L"Width"; widthFv.type = Graph::AnalysisFieldType::Float; + widthFv.components[0] = static_cast(width); + node.analysisOutput.fields.push_back(std::move(widthFv)); + Graph::AnalysisFieldValue heightFv; + heightFv.name = L"Height"; heightFv.type = Graph::AnalysisFieldType::Float; + heightFv.components[0] = static_cast(height); + node.analysisOutput.fields.push_back(std::move(heightFv)); + Graph::AnalysisFieldValue framesFv; + framesFv.name = L"Frames"; framesFv.type = Graph::AnalysisFieldType::Float; + framesFv.components[0] = static_cast(frames); + node.analysisOutput.fields.push_back(std::move(framesFv)); + }; + + for (auto& [id, provider] : m_dxgiCaptureCache) + { + if (!provider || !provider->IsOpen()) continue; + + Graph::EffectNode* nodePtr = nullptr; + for (auto& node : nodes) + if (node.id == id) { nodePtr = &node; break; } + + if (provider->CaptureNextFrame(dc)) + { + anyNewFrame = true; + if (nodePtr) + { + nodePtr->cachedOutput = provider->CurrentBitmap(); + nodePtr->dirty = true; + } + } + + if (nodePtr) + updateAnalysis(*nodePtr, provider->Width(), provider->Height(), provider->FrameCount()); + } + + for (auto& [id, provider] : m_wgcCaptureCache) + { + if (!provider || !provider->IsOpen()) continue; + + Graph::EffectNode* nodePtr = nullptr; + for (auto& node : nodes) + if (node.id == id) { nodePtr = &node; break; } + + if (provider->CaptureNextFrame(dc)) + { + anyNewFrame = true; + if (nodePtr) + { + nodePtr->cachedOutput = provider->CurrentBitmap(); + nodePtr->dirty = true; + } + } + + if (nodePtr) + updateAnalysis(*nodePtr, provider->Width(), provider->Height(), provider->FrameCount()); + } + + return anyNewFrame; + } + + void SourceNodeFactory::RegisterGraphicsCaptureItem( + uint32_t nodeId, + winrt::Windows::Graphics::Capture::GraphicsCaptureItem const& item) + { + if (item) + { + m_pendingWgcItems[nodeId] = item; + m_wgcCaptureCache.erase(nodeId); + } + else + { + m_pendingWgcItems.erase(nodeId); + m_wgcCaptureCache.erase(nodeId); + } + } + VideoSourceProvider* SourceNodeFactory::GetVideoProvider(uint32_t nodeId) { auto it = m_videoCache.find(nodeId); diff --git a/Effects/SourceNodeFactory.h b/Effects/SourceNodeFactory.h index 9e60bca..a5510e0 100644 --- a/Effects/SourceNodeFactory.h +++ b/Effects/SourceNodeFactory.h @@ -5,6 +5,8 @@ #include "../Graph/EffectNode.h" #include "ImageLoader.h" #include "VideoSourceProvider.h" +#include "DxgiDuplicationSourceProvider.h" +#include "WindowsGraphicsCaptureSourceProvider.h" namespace ShaderLab::Effects { @@ -39,6 +41,14 @@ namespace ShaderLab::Effects const std::wstring& filePath, const std::wstring& displayName = L""); + static Graph::EffectNode CreateDxgiDuplicateOutputSourceNode( + uint32_t adapterIndex, + uint32_t outputIndex, + const std::wstring& displayName = L""); + + static Graph::EffectNode CreateWindowsGraphicsCaptureSourceNode( + const std::wstring& displayName = L"Windows Graphics Capture"); + // Returns true if the file extension indicates a video file. static bool IsVideoFile(const std::wstring& filePath); @@ -65,6 +75,14 @@ namespace ShaderLab::Effects ID2D1DeviceContext5* dc, double deltaSeconds); + bool TickAndUploadLiveCaptures( + std::vector& nodes, + ID2D1DeviceContext5* dc); + + void RegisterGraphicsCaptureItem( + uint32_t nodeId, + winrt::Windows::Graphics::Capture::GraphicsCaptureItem const& item); + // Get the video provider for a node (for UI controls). VideoSourceProvider* GetVideoProvider(uint32_t nodeId); @@ -83,6 +101,10 @@ namespace ShaderLab::Effects // Cached video providers: nodeId → video provider. std::unordered_map> m_videoCache; + std::unordered_map> m_dxgiCaptureCache; + std::unordered_map> m_wgcCaptureCache; + std::unordered_map> m_pendingWgcItems; + // Last clock-driven seek time per node — used to detect a paused // Clock so the video doesn't free-run when the bound Time stops // advancing. NaN means "no prior sample". diff --git a/Effects/WindowsGraphicsCaptureSourceProvider.cpp b/Effects/WindowsGraphicsCaptureSourceProvider.cpp new file mode 100644 index 0000000..9dda523 --- /dev/null +++ b/Effects/WindowsGraphicsCaptureSourceProvider.cpp @@ -0,0 +1,265 @@ +#include "pch_engine.h" +#include "WindowsGraphicsCaptureSourceProvider.h" +#include + +namespace ShaderLab::Effects +{ + namespace + { + std::wstring HResultMessage(HRESULT hr) + { + return std::format(L"HRESULT 0x{:08X}", static_cast(hr)); + } + } + + WindowsGraphicsCaptureSourceProvider::~WindowsGraphicsCaptureSourceProvider() + { + Close(); + } + + bool WindowsGraphicsCaptureSourceProvider::CreateDevice(ID3D11Device* d3dDevice) + { + if (!d3dDevice) + return false; + + winrt::com_ptr dxgiDevice; + HRESULT hr = d3dDevice->QueryInterface(IID_PPV_ARGS(dxgiDevice.put())); + if (FAILED(hr) || !dxgiDevice) + { + m_lastError = L"Failed to query IDXGIDevice for WGC: " + HResultMessage(hr); + return false; + } + + winrt::com_ptr<::IInspectable> inspectable; + hr = CreateDirect3D11DeviceFromDXGIDevice(dxgiDevice.get(), inspectable.put()); + if (FAILED(hr) || !inspectable) + { + m_lastError = L"CreateDirect3D11DeviceFromDXGIDevice failed: " + HResultMessage(hr); + return false; + } + + m_device = inspectable.as(); + return m_device != nullptr; + } + + bool WindowsGraphicsCaptureSourceProvider::Open( + ID2D1DeviceContext5* dc, + ID3D11Device* d3dDevice, + winrt::Windows::Graphics::Capture::GraphicsCaptureItem const& item, + bool rawFP16) + { + Close(); + + if (!dc || !d3dDevice || !item) + { + m_lastError = L"Missing D2D/D3D device or capture item"; + return false; + } + + if (!winrt::Windows::Graphics::Capture::GraphicsCaptureSession::IsSupported()) + { + m_lastError = L"Windows Graphics Capture is not supported on this system"; + return false; + } + + m_d3dDevice.copy_from(d3dDevice); + m_item = item; + m_itemName = item.DisplayName().c_str(); + m_rawFP16 = rawFP16; + m_outputFormat = rawFP16 ? DXGI_FORMAT_R16G16B16A16_FLOAT : DXGI_FORMAT_B8G8R8A8_UNORM; + + if (!CreateDevice(d3dDevice)) + return false; + + auto size = item.Size(); + if (size.Width <= 0 || size.Height <= 0) + { + m_lastError = L"Capture item has an invalid size"; + return false; + } + + m_width = static_cast(size.Width); + m_height = static_cast(size.Height); + + if (!CreateOutputTextureAndBitmap(dc, d3dDevice, m_width, m_height, m_outputFormat)) + { + if (!rawFP16) + { + Close(); + return false; + } + + m_rawFP16 = false; + m_outputFormat = DXGI_FORMAT_B8G8R8A8_UNORM; + if (!CreateOutputTextureAndBitmap(dc, d3dDevice, m_width, m_height, m_outputFormat)) + { + Close(); + return false; + } + } + + try + { + m_framePool = winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool::CreateFreeThreaded( + m_device, + CapturePixelFormat(), + 2, + size); + } + catch (...) + { + if (!rawFP16) + throw; + m_rawFP16 = false; + m_outputFormat = DXGI_FORMAT_B8G8R8A8_UNORM; + m_outputTexture = nullptr; + m_bitmap = nullptr; + if (!CreateOutputTextureAndBitmap(dc, d3dDevice, m_width, m_height, m_outputFormat)) + { + Close(); + return false; + } + m_framePool = winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool::CreateFreeThreaded( + m_device, + CapturePixelFormat(), + 2, + size); + } + m_session = m_framePool.CreateCaptureSession(item); + m_session.StartCapture(); + + m_lastError.clear(); + return true; + } + + void WindowsGraphicsCaptureSourceProvider::Close() + { + if (m_session) + m_session.Close(); + if (m_framePool) + m_framePool.Close(); + m_session = nullptr; + m_framePool = nullptr; + m_item = nullptr; + m_device = nullptr; + m_bitmap = nullptr; + m_outputTexture = nullptr; + m_d3dDevice = nullptr; + m_width = 0; + m_height = 0; + m_rawFP16 = false; + m_outputFormat = DXGI_FORMAT_B8G8R8A8_UNORM; + m_frameCount = 0; + } + + bool WindowsGraphicsCaptureSourceProvider::CreateOutputTextureAndBitmap( + ID2D1DeviceContext5* dc, + ID3D11Device* d3dDevice, + uint32_t width, + uint32_t height, + DXGI_FORMAT format) + { + if (!dc || !d3dDevice || width == 0 || height == 0) + return false; + + D3D11_TEXTURE2D_DESC texDesc{}; + texDesc.Width = width; + texDesc.Height = height; + texDesc.MipLevels = 1; + texDesc.ArraySize = 1; + texDesc.Format = format; + texDesc.SampleDesc.Count = 1; + texDesc.Usage = D3D11_USAGE_DEFAULT; + texDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET; + + HRESULT hr = d3dDevice->CreateTexture2D(&texDesc, nullptr, m_outputTexture.put()); + if (FAILED(hr) || !m_outputTexture) + { + m_lastError = L"Failed to create WGC output texture: " + HResultMessage(hr); + return false; + } + + winrt::com_ptr surface; + hr = m_outputTexture->QueryInterface(IID_PPV_ARGS(surface.put())); + if (FAILED(hr) || !surface) + { + m_lastError = L"Failed to query WGC output texture surface: " + HResultMessage(hr); + return false; + } + + D2D1_BITMAP_PROPERTIES1 bitmapProps = D2D1::BitmapProperties1( + D2D1_BITMAP_OPTIONS_NONE, + D2D1::PixelFormat(format, D2D1_ALPHA_MODE_PREMULTIPLIED)); + + hr = dc->CreateBitmapFromDxgiSurface(surface.get(), &bitmapProps, m_bitmap.put()); + if (FAILED(hr) || !m_bitmap) + { + m_lastError = L"Failed to create D2D bitmap for WGC output: " + HResultMessage(hr); + return false; + } + + return true; + } + + bool WindowsGraphicsCaptureSourceProvider::RecreateForSize( + ID2D1DeviceContext5* dc, + winrt::Windows::Graphics::SizeInt32 const& size) + { + if (!m_d3dDevice || !m_framePool || size.Width <= 0 || size.Height <= 0) + return false; + + m_width = static_cast(size.Width); + m_height = static_cast(size.Height); + m_outputTexture = nullptr; + m_bitmap = nullptr; + + if (!CreateOutputTextureAndBitmap(dc, m_d3dDevice.get(), m_width, m_height, m_outputFormat)) + return false; + + m_framePool.Recreate(m_device, CapturePixelFormat(), 2, size); + return true; + } + + winrt::Windows::Graphics::DirectX::DirectXPixelFormat WindowsGraphicsCaptureSourceProvider::CapturePixelFormat() const + { + return m_outputFormat == DXGI_FORMAT_R16G16B16A16_FLOAT + ? winrt::Windows::Graphics::DirectX::DirectXPixelFormat::R16G16B16A16Float + : winrt::Windows::Graphics::DirectX::DirectXPixelFormat::B8G8R8A8UIntNormalized; + } + + bool WindowsGraphicsCaptureSourceProvider::CaptureNextFrame(ID2D1DeviceContext5* dc) + { + if (!m_framePool || !m_d3dDevice || !m_outputTexture) + return false; + + auto frame = m_framePool.TryGetNextFrame(); + if (!frame) + return false; + + auto size = frame.ContentSize(); + if (static_cast(size.Width) != m_width || static_cast(size.Height) != m_height) + { + if (!RecreateForSize(dc, size)) + return false; + } + + auto surface = frame.Surface(); + auto access = surface.as<::Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess>(); + winrt::com_ptr frameTexture; + HRESULT hr = access->GetInterface(IID_PPV_ARGS(frameTexture.put())); + if (FAILED(hr) || !frameTexture) + { + m_lastError = L"Failed to get WGC frame texture: " + HResultMessage(hr); + return false; + } + + winrt::com_ptr context; + m_d3dDevice->GetImmediateContext(context.put()); + if (!context) + return false; + + context->CopyResource(m_outputTexture.get(), frameTexture.get()); + ++m_frameCount; + return true; + } +} diff --git a/Effects/WindowsGraphicsCaptureSourceProvider.h b/Effects/WindowsGraphicsCaptureSourceProvider.h new file mode 100644 index 0000000..249ec09 --- /dev/null +++ b/Effects/WindowsGraphicsCaptureSourceProvider.h @@ -0,0 +1,61 @@ +#pragma once + +#include "pch_engine.h" +#include "../EngineExport.h" +#include +#include +#include + +namespace ShaderLab::Effects +{ + class SHADERLAB_API WindowsGraphicsCaptureSourceProvider + { + public: + WindowsGraphicsCaptureSourceProvider() = default; + WindowsGraphicsCaptureSourceProvider(const WindowsGraphicsCaptureSourceProvider&) = delete; + WindowsGraphicsCaptureSourceProvider& operator=(const WindowsGraphicsCaptureSourceProvider&) = delete; + ~WindowsGraphicsCaptureSourceProvider(); + + bool Open( + ID2D1DeviceContext5* dc, + ID3D11Device* d3dDevice, + winrt::Windows::Graphics::Capture::GraphicsCaptureItem const& item, + bool rawFP16 = false); + void Close(); + + bool IsOpen() const { return m_session != nullptr; } + bool CaptureNextFrame(ID2D1DeviceContext5* dc); + ID2D1Image* CurrentBitmap() const { return m_bitmap.get(); } + + uint32_t Width() const { return m_width; } + uint32_t Height() const { return m_height; } + uint64_t FrameCount() const { return m_frameCount; } + bool RawFP16() const { return m_rawFP16; } + DXGI_FORMAT OutputFormat() const { return m_outputFormat; } + const std::wstring& ItemName() const { return m_itemName; } + const std::wstring& LastError() const { return m_lastError; } + + private: + bool CreateDevice(ID3D11Device* d3dDevice); + bool CreateOutputTextureAndBitmap(ID2D1DeviceContext5* dc, ID3D11Device* d3dDevice, uint32_t width, uint32_t height, DXGI_FORMAT format); + bool RecreateForSize(ID2D1DeviceContext5* dc, winrt::Windows::Graphics::SizeInt32 const& size); + winrt::Windows::Graphics::DirectX::DirectXPixelFormat CapturePixelFormat() const; + + winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice m_device{ nullptr }; + winrt::Windows::Graphics::Capture::GraphicsCaptureItem m_item{ nullptr }; + winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool m_framePool{ nullptr }; + winrt::Windows::Graphics::Capture::GraphicsCaptureSession m_session{ nullptr }; + + winrt::com_ptr m_d3dDevice; + winrt::com_ptr m_outputTexture; + winrt::com_ptr m_bitmap; + + uint32_t m_width{ 0 }; + uint32_t m_height{ 0 }; + bool m_rawFP16{ false }; + DXGI_FORMAT m_outputFormat{ DXGI_FORMAT_B8G8R8A8_UNORM }; + uint64_t m_frameCount{ 0 }; + std::wstring m_itemName; + std::wstring m_lastError; + }; +} diff --git a/MainWindow.xaml.cpp b/MainWindow.xaml.cpp index 4d2e19d..52a8bb5 100644 --- a/MainWindow.xaml.cpp +++ b/MainWindow.xaml.cpp @@ -9,6 +9,7 @@ #include "Rendering/EffectGraphFile.h" #include "Effects/ShaderLabEffects.h" #include "Effects/StatisticsEffect.h" +#include "Effects/DxgiDuplicationSourceProvider.h" #include "Version.h" #include @@ -2298,6 +2299,44 @@ namespace winrt::ShaderLab::implementation BrowseVideoForSourceNode(); }); subItem.Items().Append(videoSourceItem); + + auto dxgiCaptureGroup = MUX::MenuFlyoutSubItem(); + dxgiCaptureGroup.Text(L"DXGI DuplicateOutput"); + auto outputs = ::ShaderLab::Effects::DxgiDuplicationSourceProvider::EnumerateOutputs(); + for (const auto& output : outputs) + { + auto item = MUX::MenuFlyoutItem(); + std::wstring text = output.deviceName.empty() + ? std::format(L"Adapter {} Output {}", output.adapterIndex, output.outputIndex) + : std::format(L"{} ({},{})", output.deviceName, output.adapterIndex, output.outputIndex); + item.Text(winrt::hstring(text)); + item.Click([this, output, text](auto&&, auto&&) + { + auto node = ::ShaderLab::Effects::SourceNodeFactory::CreateDxgiDuplicateOutputSourceNode( + output.adapterIndex, + output.outputIndex, + L"DXGI " + text); + auto nodeId = m_nodeGraphController.AddNode(std::move(node), { 0.0f, 0.0f }); + OnNodeAdded(nodeId); + }); + dxgiCaptureGroup.Items().Append(item); + } + if (dxgiCaptureGroup.Items().Size() == 0) + { + auto unavailable = MUX::MenuFlyoutItem(); + unavailable.Text(L"No outputs available"); + unavailable.IsEnabled(false); + dxgiCaptureGroup.Items().Append(unavailable); + } + subItem.Items().Append(dxgiCaptureGroup); + + auto wgcSourceItem = MUX::MenuFlyoutItem(); + wgcSourceItem.Text(L"Windows Graphics Capture..."); + wgcSourceItem.Click([this](auto&&, auto&&) + { + AddWindowsGraphicsCaptureSourceAsync(); + }); + subItem.Items().Append(wgcSourceItem); } } @@ -2472,6 +2511,31 @@ namespace winrt::ShaderLab::implementation OnNodeAdded(nodeId); } + winrt::fire_and_forget MainWindow::AddWindowsGraphicsCaptureSourceAsync() + { + auto strong = get_strong(); + + winrt::Windows::Graphics::Capture::GraphicsCapturePicker picker; + picker.as<::IInitializeWithWindow>()->Initialize(m_hwnd); + + auto item = co_await picker.PickSingleItemAsync(); + if (!item) co_return; + + std::wstring name = item.DisplayName().empty() + ? L"Windows Graphics Capture" + : std::wstring(item.DisplayName().c_str()); + auto node = ::ShaderLab::Effects::SourceNodeFactory::CreateWindowsGraphicsCaptureSourceNode(name); + auto nodeId = m_nodeGraphController.AddNode(std::move(node), { 0.0f, 0.0f }); + m_sourceFactory.RegisterGraphicsCaptureItem(nodeId, item); + + if (auto* graphNode = m_graph.FindNode(nodeId); graphNode && m_renderEngine.D2DDeviceContext()) + { + m_sourceFactory.PrepareSourceNode(*graphNode, m_renderEngine.D2DDeviceContext(), 0.0, m_renderEngine.D3DDevice(), m_renderEngine.D3DContext()); + } + + OnNodeAdded(nodeId); + } + void MainWindow::OnNodeAdded(uint32_t /*nodeId*/) { m_graph.MarkAllDirty(); @@ -6410,9 +6474,9 @@ namespace winrt::ShaderLab::implementation if (dc) { try { - m_sourceFactory.TickAndUploadVideos( - const_cast&>(m_graph.Nodes()), - dc, deltaSec); + auto& nodes = const_cast&>(m_graph.Nodes()); + m_sourceFactory.TickAndUploadVideos(nodes, dc, deltaSec); + m_sourceFactory.TickAndUploadLiveCaptures(nodes, dc); } catch (...) {} } diff --git a/MainWindow.xaml.h b/MainWindow.xaml.h index eaa24ba..312856a 100644 --- a/MainWindow.xaml.h +++ b/MainWindow.xaml.h @@ -191,6 +191,7 @@ namespace winrt::ShaderLab::implementation void OnAddFloodSourceClicked( winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::RoutedEventArgs const& args); + winrt::fire_and_forget AddWindowsGraphicsCaptureSourceAsync(); void OnNodeAdded(uint32_t nodeId); void OnPreviewPointerDragged( winrt::Windows::Foundation::IInspectable const& sender, diff --git a/README.md b/README.md index 005ccc8..3b7a37f 100644 --- a/README.md +++ b/README.md @@ -417,6 +417,7 @@ GitHub Actions workflow `.github/workflows/release.yml` runs as a matrix (`x64`, | `windowscodecs.lib` | WIC image loading | | `mfplat.lib`, `mfreadwrite.lib`, `mfuuid.lib` | Media Foundation video source decoding | | `mscms.lib` | ICC profile reading (Image Color Management) | +| `windowsapp.lib` | Windows Graphics Capture interop (CreateDirect3D11DeviceFromDXGIDevice) | --- diff --git a/ShaderLab.vcxproj b/ShaderLab.vcxproj index 3ea630e..2717239 100644 --- a/ShaderLab.vcxproj +++ b/ShaderLab.vcxproj @@ -125,12 +125,14 @@ + + diff --git a/ShaderLab.vcxproj.filters b/ShaderLab.vcxproj.filters index eecea75..a503720 100644 --- a/ShaderLab.vcxproj.filters +++ b/ShaderLab.vcxproj.filters @@ -1,350 +1,156 @@ - + - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - - - {D9D6E242-F8AF-4e16-AE04-014B56B4B6F0} - - - {A1E28C87-1D94-4BAA-9B68-5C43B0E9F248} - + + - - - - - - Graph - - - Graph - - - Graph - - - Graph - - - Graph - - - Rendering - - - Rendering - - - Rendering - - - Rendering - - - Rendering - - - Rendering - - - Rendering - - - Rendering - - - Rendering - - - Rendering - - - Rendering - - - Effects - - - Effects - - - Effects - - - Effects - - - Effects - - - Effects - - - Effects - - - Effects - - - Effects - - - Effects - - - Controls - - - Controls - - - Controls - - - Controls - - - Controls - - - Controls - - - Controls - - - + - - - - - - - Rendering - - - Controls - - - Controls - - - Controls - - - Controls - - - Controls - - - Controls - + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - Assets - - - Assets - - - Assets - - - Assets - - - Assets - - - Assets - - - Assets - - - Assets - - - Assets - - - Assets - - - Assets - - Assets - - Assets - - Assets - - Assets - - Assets - - Assets - - Assets - - Assets - - Assets - - Assets - - - Assets - - - Assets - - - Assets - - - Assets - - - Assets - - Assets - - Assets - - Assets - - Assets - - Assets - - - Assets - - - Assets - - - Assets - - - Assets - - - Assets - - - Assets - - - Assets - - - Assets - - - Assets - - - Assets - - - Assets - - - Assets - - - Assets - - - Assets - - - Assets - - Assets - - Assets - - Assets - - Assets - - Assets - - - Assets - - - Assets - - - Assets - - - Assets - - - Assets - - - Assets - - - Assets - - - Assets - - - Assets - - - Assets - + - - - - + + - - - - - + - + \ No newline at end of file diff --git a/ShaderLabEngine.vcxproj b/ShaderLabEngine.vcxproj index 7c71030..d9be2b7 100644 --- a/ShaderLabEngine.vcxproj +++ b/ShaderLabEngine.vcxproj @@ -77,7 +77,7 @@ Windows - d3d11.lib;d2d1.lib;dxgi.lib;d3dcompiler.lib;dxguid.lib;windowscodecs.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;dwrite.lib;mscms.lib;%(AdditionalDependencies) + d3d11.lib;d2d1.lib;dxgi.lib;d3dcompiler.lib;dxguid.lib;windowscodecs.lib;mfplat.lib;mfreadwrite.lib;mfuuid.lib;dwrite.lib;mscms.lib;windowsapp.lib;%(AdditionalDependencies) $(OutDir)$(TargetName).lib @@ -123,6 +123,8 @@ + + @@ -150,6 +152,8 @@ + +