screen capture preview

This commit is contained in:
DismissedLight
2024-06-01 22:53:56 +08:00
parent bae9c8a46a
commit 53cda02071
17 changed files with 449 additions and 78 deletions

View File

@@ -2,7 +2,7 @@
"profiles": {
"Snap.Hutao": {
"commandName": "MsixPackage",
"nativeDebugging": false,
"nativeDebugging": true,
"doNotLaunchApp": false,
"allowLocalNetworkLoopbackProperty": true
},

View File

@@ -0,0 +1,86 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Win32.Foundation;
using Snap.Hutao.Win32.Graphics.Direct3D;
using Snap.Hutao.Win32.Graphics.Direct3D11;
using Snap.Hutao.Win32.Graphics.Dxgi;
using Snap.Hutao.Win32.System.Com;
using Windows.Graphics.DirectX.Direct3D11;
using WinRT;
using static Snap.Hutao.Win32.ConstValues;
using static Snap.Hutao.Win32.D3d11;
using static Snap.Hutao.Win32.Dxgi;
using static Snap.Hutao.Win32.Macros;
namespace Snap.Hutao.Service.Game.Automation.ScreenCapture;
internal static class DirectX
{
public static unsafe bool TryCreateDXGIFactory(uint flags, out IDXGIFactory6* factory, out HRESULT hr)
{
hr = CreateDXGIFactory2(flags, in IDXGIFactory6.IID, out factory);
return SUCCEEDED(hr);
}
public static unsafe bool TryGetHighPerformanceAdapter(IDXGIFactory6* factory, out IDXGIAdapter* adapter, out HRESULT hr)
{
hr = factory->EnumAdapterByGpuPreference(0U, DXGI_GPU_PREFERENCE.DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, in IDXGIAdapter.IID, out adapter);
if (FAILED(hr))
{
return false;
}
IUnknownMarshal.Release(adapter);
return true;
}
public static unsafe bool TryCreateD3D11Device(IDXGIAdapter* adapter, D3D11_CREATE_DEVICE_FLAG flags, out ID3D11Device* device, out HRESULT hr)
{
hr = D3D11CreateDevice(adapter, D3D_DRIVER_TYPE.D3D_DRIVER_TYPE_HARDWARE, default, flags, [], D3D11_SDK_VERSION, out device, out _, out _);
if (FAILED(hr))
{
return false;
}
IUnknownMarshal.Release(device);
return true;
}
public static unsafe bool TryAsDXGIDevice(ID3D11Device* device, out IDXGIDevice* dxgiDevice, out HRESULT hr)
{
hr = IUnknownMarshal.QueryInterface(device, in IDXGIDevice.IID, out dxgiDevice);
if (FAILED(hr))
{
return false;
}
IUnknownMarshal.Release(dxgiDevice);
return true;
}
public static unsafe bool TryCreateDirect3D11Device(IDXGIDevice* dxgiDevice, [NotNullWhen(true)] out IDirect3DDevice? direct3DDevice, out HRESULT hr)
{
hr = CreateDirect3D11DeviceFromDXGIDevice(dxgiDevice, out Win32.System.WinRT.IInspectable* inspectable);
if (FAILED(hr))
{
direct3DDevice = default;
return false;
}
direct3DDevice = IInspectable.FromAbi((nint)inspectable).ObjRef.AsInterface<IDirect3DDevice>();
return true;
}
public static unsafe bool TryCreateSwapChainForComposition(IDXGIFactory6* factory, ID3D11Device* device, ref readonly DXGI_SWAP_CHAIN_DESC1 desc, out IDXGISwapChain1* swapChain, out HRESULT hr)
{
hr = factory->CreateSwapChainForComposition((IUnknown*)device, in desc, default, out swapChain);
if (FAILED(hr))
{
return false;
}
//IUnknownMarshal.Release(swapChain);
return true;
}
}

View File

@@ -4,44 +4,106 @@
using Snap.Hutao.Win32.Foundation;
using Snap.Hutao.Win32.Graphics.Direct3D11;
using Snap.Hutao.Win32.Graphics.Dwm;
using Snap.Hutao.Win32.Graphics.Dxgi;
using Snap.Hutao.Win32.Graphics.Dxgi.Common;
using Snap.Hutao.Win32.Graphics.Gdi;
using Snap.Hutao.Win32.System.Com;
using Snap.Hutao.Win32.System.WinRT.Graphics.Capture;
using Windows.Graphics.Capture;
using Windows.Graphics.DirectX;
using Windows.Graphics.DirectX.Direct3D11;
using static Snap.Hutao.Win32.ConstValues;
using static Snap.Hutao.Win32.DwmApi;
using static Snap.Hutao.Win32.Gdi32;
using static Snap.Hutao.Win32.User32;
namespace Snap.Hutao.Service.Game.Automation.ScreenCapture;
internal readonly struct GameScreenCaptureContext
internal struct GameScreenCaptureContext : IDisposable
{
public readonly GraphicsCaptureItem Item;
public readonly bool PreviewEnabled;
private const uint CreateDXGIFactoryFlag =
#if DEBUG
DXGI_CREATE_FACTORY_DEBUG;
#else
0;
#endif
private const D3D11_CREATE_DEVICE_FLAG D3d11CreateDeviceFlag =
D3D11_CREATE_DEVICE_FLAG.D3D11_CREATE_DEVICE_BGRA_SUPPORT
#if DEBUG
| D3D11_CREATE_DEVICE_FLAG.D3D11_CREATE_DEVICE_DEBUG
#endif
;
private readonly IDirect3DDevice direct3DDevice;
private readonly HWND hwnd;
[SuppressMessage("", "SH002")]
public GameScreenCaptureContext(IDirect3DDevice direct3DDevice, HWND hwnd)
{
this.direct3DDevice = direct3DDevice;
this.hwnd = hwnd;
private unsafe IDXGIFactory6* factory;
private unsafe IDXGIAdapter* adapter;
private unsafe ID3D11Device* d3d11Device;
private unsafe IDXGIDevice* dxgiDevice;
private IDirect3DDevice? direct3DDevice;
private unsafe IDXGISwapChain1* swapChain;
[SuppressMessage("", "SH002")]
private unsafe GameScreenCaptureContext(HWND hwnd, bool preview)
{
this.hwnd = hwnd;
GraphicsCaptureItem.As<IGraphicsCaptureItemInterop>().CreateForWindow(hwnd, out Item);
PreviewEnabled = preview;
}
[SuppressMessage("", "SH002")]
public static unsafe GameScreenCaptureContextCreationResult Create(HWND hwnd, bool preview)
{
GameScreenCaptureContext context = new(hwnd, preview);
if (!DirectX.TryCreateDXGIFactory(CreateDXGIFactoryFlag, out context.factory, out HRESULT hr))
{
return new(GameScreenCaptureContextCreationResultKind.CreateDxgiFactoryFailed, hr);
}
if (!DirectX.TryGetHighPerformanceAdapter(context.factory, out context.adapter, out hr))
{
return new(GameScreenCaptureContextCreationResultKind.EnumAdapterByGpuPreferenceFailed, hr);
}
if (!DirectX.TryCreateD3D11Device(default, D3d11CreateDeviceFlag, out context.d3d11Device, out hr))
{
return new(GameScreenCaptureContextCreationResultKind.D3D11CreateDeviceFailed, hr);
}
if (!DirectX.TryAsDXGIDevice(context.d3d11Device, out context.dxgiDevice, out hr))
{
return new(GameScreenCaptureContextCreationResultKind.D3D11DeviceQueryDXGIDeviceFailed, hr);
}
if (!DirectX.TryCreateDirect3D11Device(context.dxgiDevice, out context.direct3DDevice, out hr))
{
return new(GameScreenCaptureContextCreationResultKind.CreateDirect3D11DeviceFromDXGIDeviceFailed, hr);
}
return new GameScreenCaptureContextCreationResult(GameScreenCaptureContextCreationResultKind.Success, context);
}
public Direct3D11CaptureFramePool CreatePool()
{
return Direct3D11CaptureFramePool.CreateFreeThreaded(direct3DDevice, DeterminePixelFormat(hwnd), 2, Item.Size);
(DirectXPixelFormat winrt, DXGI_FORMAT dx) = DeterminePixelFormat(hwnd);
CreateOrUpdateDXGISwapChain(dx);
return Direct3D11CaptureFramePool.CreateFreeThreaded(direct3DDevice, winrt, 2, Item.Size);
}
public void RecreatePool(Direct3D11CaptureFramePool framePool)
{
framePool.Recreate(direct3DDevice, DeterminePixelFormat(hwnd), 2, Item.Size);
(DirectXPixelFormat winrt, DXGI_FORMAT dx) = DeterminePixelFormat(hwnd);
CreateOrUpdateDXGISwapChain(dx);
framePool.Recreate(direct3DDevice, winrt, 2, Item.Size);
}
public GraphicsCaptureSession CreateSession(Direct3D11CaptureFramePool framePool)
public readonly GraphicsCaptureSession CreateSession(Direct3D11CaptureFramePool framePool)
{
GraphicsCaptureSession session = framePool.CreateCaptureSession(Item);
session.IsCursorCaptureEnabled = false;
@@ -49,7 +111,7 @@ internal readonly struct GameScreenCaptureContext
return session;
}
public bool TryGetClientBox(uint width, uint height, out D3D11_BOX clientBox)
public readonly bool TryGetClientBox(uint width, uint height, out D3D11_BOX clientBox)
{
clientBox = default;
@@ -88,8 +150,39 @@ internal readonly struct GameScreenCaptureContext
return clientBox.right <= width && clientBox.bottom <= height;
}
public unsafe readonly void AttachPreview(GameScreenCaptureDebugPreviewWindow? window)
{
if (PreviewEnabled && window is not null)
{
window.UpdateSwapChain(swapChain);
}
}
public unsafe readonly void UpdatePreview(GameScreenCaptureDebugPreviewWindow? window, IDirect3DSurface surface)
{
if (PreviewEnabled && window is not null)
{
window.UnsafeUpdatePreview(d3d11Device, surface);
}
}
public unsafe readonly void DetachPreview(GameScreenCaptureDebugPreviewWindow? window)
{
if (PreviewEnabled && window is not null)
{
window.UpdateSwapChain(null);
window.Close();
}
}
public unsafe readonly void Dispose()
{
IUnknownMarshal.Release(factory);
IUnknownMarshal.Release(swapChain);
}
[SuppressMessage("", "SH002")]
private static DirectXPixelFormat DeterminePixelFormat(HWND hwnd)
private static (DirectXPixelFormat WinRTFormat, DXGI_FORMAT DXFormat) DeterminePixelFormat(HWND hwnd)
{
HDC hdc = GetDC(hwnd);
if (hdc != HDC.NULL)
@@ -98,10 +191,31 @@ internal readonly struct GameScreenCaptureContext
_ = ReleaseDC(hwnd, hdc);
if (bitsPerPixel >= 32)
{
return DirectXPixelFormat.R16G16B16A16Float;
return (DirectXPixelFormat.R16G16B16A16Float, DXGI_FORMAT.DXGI_FORMAT_R16G16B16A16_FLOAT);
}
}
return DirectXPixelFormat.B8G8R8A8UIntNormalized;
return (DirectXPixelFormat.B8G8R8A8UIntNormalized, DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM);
}
private unsafe void CreateOrUpdateDXGISwapChain(DXGI_FORMAT format)
{
if (!PreviewEnabled)
{
return;
}
DXGI_SWAP_CHAIN_DESC1 desc = default;
desc.Width = (uint)Item.Size.Width;
desc.Height = (uint)Item.Size.Height;
desc.Format = format;
desc.SampleDesc.Count = 1;
desc.BufferUsage = DXGI_USAGE.DXGI_USAGE_RENDER_TARGET_OUTPUT;
desc.BufferCount = 2;
desc.Scaling = DXGI_SCALING.DXGI_SCALING_STRETCH;
desc.SwapEffect = DXGI_SWAP_EFFECT.DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
desc.AlphaMode = DXGI_ALPHA_MODE.DXGI_ALPHA_MODE_PREMULTIPLIED;
DirectX.TryCreateSwapChainForComposition(factory, d3d11Device, in desc, out swapChain, out HRESULT hr);
}
}

View File

@@ -0,0 +1,25 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Win32.Foundation;
namespace Snap.Hutao.Service.Game.Automation.ScreenCapture;
internal readonly struct GameScreenCaptureContextCreationResult
{
public readonly GameScreenCaptureContextCreationResultKind Kind;
public readonly HRESULT HResult;
public readonly GameScreenCaptureContext Context;
public GameScreenCaptureContextCreationResult(GameScreenCaptureContextCreationResultKind kind, HRESULT hResult)
{
Kind = kind;
HResult = hResult;
}
public GameScreenCaptureContextCreationResult(GameScreenCaptureContextCreationResultKind kind, GameScreenCaptureContext context)
{
Kind = kind;
Context = context;
}
}

View File

@@ -0,0 +1,14 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Game.Automation.ScreenCapture;
internal enum GameScreenCaptureContextCreationResultKind
{
Success,
CreateDxgiFactoryFailed,
EnumAdapterByGpuPreferenceFailed,
D3D11CreateDeviceFailed,
D3D11DeviceQueryDXGIDeviceFailed,
CreateDirect3D11DeviceFromDXGIDeviceFailed,
}

View File

@@ -0,0 +1,14 @@
<Window
x:Class="Snap.Hutao.Service.Game.Automation.ScreenCapture.GameScreenCaptureDebugPreviewWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:Snap.Hutao.Service.Game.Automation.ScreenCapture"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<SwapChainPanel
x:Name="Presenter"
AllowFocusOnInteraction="False"
AllowFocusWhenDisabled="False"/>
</Window>

View File

@@ -0,0 +1,47 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Snap.Hutao.Core.Windowing;
using Snap.Hutao.Win32.Graphics.Direct3D11;
using Snap.Hutao.Win32.Graphics.Dxgi;
using Snap.Hutao.Win32.System.WinRT.Graphics.Capture;
using Snap.Hutao.Win32.System.WinRT.Xaml;
using Windows.Graphics.DirectX.Direct3D11;
using WinRT;
namespace Snap.Hutao.Service.Game.Automation.ScreenCapture;
internal sealed partial class GameScreenCaptureDebugPreviewWindow : Window
{
private unsafe IDXGISwapChain1* swapChain1;
public GameScreenCaptureDebugPreviewWindow()
{
if (AppWindow.Presenter is OverlappedPresenter presenter)
{
presenter.IsMaximizable = false;
}
InitializeComponent();
this.InitializeController(Ioc.Default);
}
public unsafe void UpdateSwapChain(IDXGISwapChain1* swapChain1)
{
this.swapChain1 = swapChain1;
ISwapChainPanelNative native = Presenter.As<IInspectable>().ObjRef.AsInterface<ISwapChainPanelNative>();
native.SetSwapChain((IDXGISwapChain*)swapChain1);
}
public unsafe void UnsafeUpdatePreview(ID3D11Device* device, IDirect3DSurface surface)
{
IDirect3DDxgiInterfaceAccess access = surface.As<IDirect3DDxgiInterfaceAccess>();
swapChain1->GetBuffer(0, in ID3D11Texture2D.IID, out ID3D11Texture2D* buffer);
device->GetImmediateContext(out ID3D11DeviceContext* deviceContext);
access.GetInterface(in ID3D11Resource.IID, out ID3D11Resource* resource);
deviceContext->CopyResource((ID3D11Resource*)buffer, resource);
swapChain1->Present(0, default);
}
}

View File

@@ -3,17 +3,9 @@
using Snap.Hutao.Core;
using Snap.Hutao.Win32.Foundation;
using Snap.Hutao.Win32.Graphics.Direct3D;
using Snap.Hutao.Win32.Graphics.Direct3D11;
using Snap.Hutao.Win32.Graphics.Dxgi;
using Snap.Hutao.Win32.System.Com;
using Windows.Graphics.Capture;
using Windows.Graphics.DirectX.Direct3D11;
using WinRT;
using static Snap.Hutao.Win32.ConstValues;
using static Snap.Hutao.Win32.D3d11;
using static Snap.Hutao.Win32.Dxgi;
using static Snap.Hutao.Win32.Macros;
namespace Snap.Hutao.Service.Game.Automation.ScreenCapture;
@@ -55,61 +47,34 @@ internal sealed partial class GameScreenCaptureService : IGameScreenCaptureServi
}
[SuppressMessage("", "SH002")]
public unsafe bool TryStartCapture(HWND hwnd, [NotNullWhen(true)] out GameScreenCaptureSession? session)
public unsafe bool TryStartCapture(HWND hwnd, bool preview, [NotNullWhen(true)] out GameScreenCaptureSession? session)
{
session = default;
HRESULT hr;
hr = CreateDXGIFactory2(CreateDXGIFactoryFlag, in IDXGIFactory6.IID, out IDXGIFactory6* factory);
if (FAILED(hr))
GameScreenCaptureContextCreationResult result = GameScreenCaptureContext.Create(hwnd, preview);
switch (result.Kind)
{
logger.LogWarning("CreateDXGIFactory2 failed with code: {Code}", hr);
return false;
case GameScreenCaptureContextCreationResultKind.Success:
session = new(result.Context, logger);
return true;
case GameScreenCaptureContextCreationResultKind.CreateDxgiFactoryFailed:
logger.LogWarning("CreateDXGIFactory2 failed with code: {Code}", result.HResult);
return false;
case GameScreenCaptureContextCreationResultKind.EnumAdapterByGpuPreferenceFailed:
logger.LogWarning("IDXGIFactory6.EnumAdapterByGpuPreference failed with code: {Code}", result.HResult);
return false;
case GameScreenCaptureContextCreationResultKind.D3D11CreateDeviceFailed:
logger.LogWarning("D3D11CreateDevice failed with code: {Code}", result.HResult);
return false;
case GameScreenCaptureContextCreationResultKind.D3D11DeviceQueryDXGIDeviceFailed:
logger.LogWarning("ID3D11Device.QueryInterface<IDXGIDevice> failed with code: {Code}", result.HResult);
return false;
case GameScreenCaptureContextCreationResultKind.CreateDirect3D11DeviceFromDXGIDeviceFailed:
logger.LogWarning("CreateDirect3D11DeviceFromDXGIDevice failed with code: {Code}", result.HResult);
return false;
default:
return false;
}
IUnknownMarshal.Release(factory);
hr = factory->EnumAdapterByGpuPreference(0U, DXGI_GPU_PREFERENCE.DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, in IDXGIAdapter.IID, out IDXGIAdapter* adapter);
if (hr != HRESULT.DXGI_ERROR_NOT_FOUND)
{
logger.LogWarning("IDXGIFactory6.EnumAdapterByGpuPreference failed with code: {Code}", hr);
return false;
}
IUnknownMarshal.Release(adapter);
hr = D3D11CreateDevice(adapter, D3D_DRIVER_TYPE.D3D_DRIVER_TYPE_HARDWARE, default, D3d11CreateDeviceFlag, [], D3D11_SDK_VERSION, out ID3D11Device* pD3D11Device, out _, out _);
if (FAILED(hr))
{
logger.LogWarning("D3D11CreateDevice failed with code: {Code}", hr);
return false;
}
IUnknownMarshal.Release(pD3D11Device);
hr = IUnknownMarshal.QueryInterface(pD3D11Device, in IDXGIDevice.IID, out IDXGIDevice* pDXGIDevice);
if (FAILED(hr))
{
logger.LogWarning("ID3D11Device.QueryInterface<IDXGIDevice> failed with code: {Code}", hr);
return false;
}
IUnknownMarshal.Release(pDXGIDevice);
hr = CreateDirect3D11DeviceFromDXGIDevice(pDXGIDevice, out Win32.System.WinRT.IInspectable* inspectable);
if (FAILED(hr))
{
logger.LogWarning("CreateDirect3D11DeviceFromDXGIDevice failed with code: {Code}", hr);
return false;
}
IUnknownMarshal.Release(inspectable);
IDirect3DDevice direct3DDevice = IInspectable.FromAbi((nint)inspectable).ObjRef.AsInterface<IDirect3DDevice>();
GameScreenCaptureContext captureContext = new(direct3DDevice, hwnd);
session = new(captureContext, logger);
return true;
}
}

View File

@@ -7,6 +7,7 @@ using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Win32.Graphics.Direct3D11;
using Snap.Hutao.Win32.Graphics.Dxgi;
using Snap.Hutao.Win32.Graphics.Dxgi.Common;
using Snap.Hutao.Win32.System.Com;
using Snap.Hutao.Win32.System.WinRT.Graphics.Capture;
using System.Buffers;
using System.Runtime.CompilerServices;
@@ -24,6 +25,7 @@ internal sealed class GameScreenCaptureSession : IDisposable
private static readonly Half ByteMaxValue = 255;
private readonly GameScreenCaptureContext captureContext;
private readonly GameScreenCaptureDebugPreviewWindow? previewWindow;
private readonly Direct3D11CaptureFramePool framePool;
private readonly GraphicsCaptureSession session;
private readonly ILogger logger;
@@ -35,16 +37,23 @@ internal sealed class GameScreenCaptureSession : IDisposable
private bool isDisposed;
[SuppressMessage("", "SH002")]
public GameScreenCaptureSession(GameScreenCaptureContext captureContext, ILogger logger)
public unsafe GameScreenCaptureSession(GameScreenCaptureContext captureContext, ILogger logger)
{
this.captureContext = captureContext;
this.logger = logger;
contentSize = captureContext.Item.Size;
if (captureContext.PreviewEnabled)
{
previewWindow = new();
}
captureContext.Item.Closed += OnItemClosed;
framePool = captureContext.CreatePool();
captureContext.AttachPreview(previewWindow);
framePool.FrameArrived += OnFrameArrived;
session = captureContext.CreateSession(framePool);
@@ -76,6 +85,7 @@ internal sealed class GameScreenCaptureSession : IDisposable
return;
}
captureContext.DetachPreview(previewWindow);
session.Dispose();
framePool.Dispose();
isDisposed = true;
@@ -111,7 +121,8 @@ internal sealed class GameScreenCaptureSession : IDisposable
try
{
UnsafeProcessFrameSurface(frame.Surface);
captureContext.UpdatePreview(previewWindow, frame.Surface);
//UnsafeProcessFrameSurface(frame.Surface);
}
catch (Exception ex)
{
@@ -135,6 +146,8 @@ internal sealed class GameScreenCaptureSession : IDisposable
return;
}
IUnknownMarshal.Release(pDXGISurface);
if (FAILED(pDXGISurface->GetDesc(out DXGI_SURFACE_DESC dxgiSurfaceDesc)))
{
return;
@@ -151,6 +164,8 @@ internal sealed class GameScreenCaptureSession : IDisposable
return;
}
IUnknownMarshal.Release(pD3D11Device);
D3D11_TEXTURE2D_DESC d3d11Texture2DDesc = default;
d3d11Texture2DDesc.Width = textureWidth;
d3d11Texture2DDesc.Height = textureHeight;
@@ -166,12 +181,17 @@ internal sealed class GameScreenCaptureSession : IDisposable
return;
}
IUnknownMarshal.Release(pD3D11Texture2D);
if (FAILED(access.GetInterface(in ID3D11Resource.IID, out ID3D11Resource* pD3D11Resource)))
{
return;
}
IUnknownMarshal.Release(pD3D11Resource);
pD3D11Device->GetImmediateContext(out ID3D11DeviceContext* pD3D11DeviceContext);
IUnknownMarshal.Release(pD3D11DeviceContext);
if (boxAvailable)
{

View File

@@ -9,5 +9,5 @@ internal interface IGameScreenCaptureService
{
bool IsSupported();
bool TryStartCapture(HWND hwnd, [NotNullWhen(true)] out GameScreenCaptureSession? session);
bool TryStartCapture(HWND hwnd, bool preview, [NotNullWhen(true)] out GameScreenCaptureSession? session);
}

View File

@@ -142,6 +142,7 @@
<None Remove="Resource\Navigation\WikiWeapon.png" />
<None Remove="Resource\Segoe Fluent Icons.ttf" />
<None Remove="Resource\WelcomeView_Background.png" />
<None Remove="Service\Game\Automation\ScreenCapture\GameScreenCaptureDebugPreviewWindow.xaml" />
<None Remove="stylecop.json" />
<None Remove="View\Card\AchievementCard.xaml" />
<None Remove="View\Card\CardBlock.xaml" />
@@ -361,6 +362,11 @@
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnablePreviewMsixTooling)'=='true'">
<ProjectCapability Include="Msix" />
</ItemGroup>
<ItemGroup>
<Page Update="Service\Game\Automation\ScreenCapture\GameScreenCaptureDebugPreviewWindow.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Core\Windowing\NotifyIcon\NotifyIconContextMenu.xaml">
<Generator>MSBuild:Compile</Generator>

View File

@@ -119,6 +119,11 @@
Header="Compensation GachaLog Service Time For 15 Days"
IsClickEnabled="True"/>
<cwc:SettingsCard
Command="{Binding ScreenCaptureCommand}"
Header="Screen Capture Test"
IsClickEnabled="True"/>
<cwc:SettingsCard Header="Crash">
<StackPanel Orientation="Horizontal">
<Button Command="{Binding ExceptionCommand}" Content="Activate"/>

View File

@@ -7,9 +7,12 @@ using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Core.LifeCycle;
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Core.Windowing;
using Snap.Hutao.Service.Game.Automation.ScreenCapture;
using Snap.Hutao.Service.Notification;
using Snap.Hutao.View.Converter;
using Snap.Hutao.ViewModel.Guide;
using Snap.Hutao.Web.Hutao.HutaoAsAService;
using Snap.Hutao.Win32.Foundation;
namespace Snap.Hutao.ViewModel;
@@ -27,6 +30,7 @@ internal sealed partial class TestViewModel : Abstraction.ViewModel
private readonly ILogger<TestViewModel> logger;
private readonly IMemoryCache memoryCache;
private readonly ITaskContext taskContext;
private readonly IGameScreenCaptureService gameScreenCaptureService;
private UploadAnnouncement announcement = new();
@@ -142,4 +146,21 @@ internal sealed partial class TestViewModel : Abstraction.ViewModel
logger.LogInformation("Failed ImageCache download tasks: [{Tasks}]", set?.ToString(','));
}
}
[Command("ScreenCaptureCommand")]
private async Task ScreenCaptureAsync()
{
HWND hwnd = currentXamlWindowReference.GetWindowHandle();
if (gameScreenCaptureService.TryStartCapture(hwnd, true, out GameScreenCaptureSession? session))
{
using (session)
{
while (true)
{
await session.RequestFrameAsync();
await Task.Delay(1000);
}
}
}
}
}

View File

@@ -11,6 +11,8 @@ internal readonly partial struct HRESULT
public static readonly HRESULT S_OK = unchecked((int)0x00000000);
public static readonly HRESULT E_FAIL = unchecked((int)0x80004005);
public static readonly HRESULT DXGI_ERROR_NOT_FOUND = unchecked((int)0x887A0002);
public static readonly HRESULT DXGI_ERROR_DEVICE_REMOVED = unchecked((int)0x887A0005);
public static readonly HRESULT DXGI_ERROR_DEVICE_RESET = unchecked((int)0x887A0007);
public readonly int Value;

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Snap.Hutao.Win32.Foundation;
using Snap.Hutao.Win32.System.Com;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -20,6 +21,21 @@ internal unsafe struct IDXGIFactory6
}
}
public unsafe HRESULT CreateSwapChainForComposition(IUnknown* pDevice, ref readonly DXGI_SWAP_CHAIN_DESC1 desc, IDXGIOutput* pRestrictToOutput, out IDXGISwapChain1* pSwapChain)
{
fixed (DXGI_SWAP_CHAIN_DESC1* pDesc = &desc)
{
fixed (IDXGISwapChain1** ppSwapChain = &pSwapChain)
{
return ThisPtr->IDXGIFactory5Vftbl
.IDXGIFactory4Vftbl
.IDXGIFactory3Vftbl
.IDXGIFactory2Vftbl
.CreateSwapChainForComposition((IDXGIFactory2*)Unsafe.AsPointer(ref this), pDevice, pDesc, pRestrictToOutput, ppSwapChain);
}
}
}
[SuppressMessage("", "SA1313")]
public HRESULT EnumAdapterByGpuPreference<T>(uint Adapter, DXGI_GPU_PREFERENCE GpuPreference, ref readonly Guid iid, out T* pvAdapter)
where T : unmanaged

View File

@@ -8,7 +8,8 @@ using System.Runtime.InteropServices;
namespace Snap.Hutao.Win32.Graphics.Dxgi;
internal unsafe readonly struct IDXGISwapChain1
[SuppressMessage("", "SA1313")]
internal unsafe struct IDXGISwapChain1
{
public readonly Vftbl* ThisPtr;
@@ -21,6 +22,23 @@ internal unsafe readonly struct IDXGISwapChain1
}
}
public HRESULT Present(uint SyncInterval, uint Flags)
{
return ThisPtr->IDXGISwapChainVftbl.Present((IDXGISwapChain*)Unsafe.AsPointer(ref this), SyncInterval, Flags);
}
public HRESULT GetBuffer<T>(uint Buffer, ref readonly Guid iid, out T* pSurface)
where T : unmanaged
{
fixed (Guid* riid = &iid)
{
fixed (T** ppSurface = &pSurface)
{
return ThisPtr->IDXGISwapChainVftbl.GetBuffer((IDXGISwapChain*)Unsafe.AsPointer(ref this), Buffer, riid, (void**)ppSurface);
}
}
}
internal readonly struct Vftbl
{
internal readonly IDXGISwapChain.Vftbl IDXGISwapChainVftbl;

View File

@@ -0,0 +1,18 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Win32.Foundation;
using Snap.Hutao.Win32.Graphics.Dxgi;
using System.Runtime.InteropServices;
namespace Snap.Hutao.Win32.System.WinRT.Xaml;
[SuppressMessage("", "SYSLIB1096")]
[ComImport]
[Guid("63AAD0B8-7C24-40FF-85A8-640D944CC325")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface ISwapChainPanelNative
{
[PreserveSig]
unsafe HRESULT SetSwapChain(IDXGISwapChain* swapChain);
}