mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Compare commits
4 Commits
fix/dailyn
...
fix/1588
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2284510852 | ||
|
|
7da778699b | ||
|
|
5bfc790ea2 | ||
|
|
fc13b85739 |
@@ -48,6 +48,8 @@ public sealed partial class App : Application
|
|||||||
/// <param name="serviceProvider">服务提供器</param>
|
/// <param name="serviceProvider">服务提供器</param>
|
||||||
public App(IServiceProvider serviceProvider)
|
public App(IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
|
// DispatcherShutdownMode = DispatcherShutdownMode.OnExplicitShutdown;
|
||||||
|
|
||||||
// Load app resource
|
// Load app resource
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
activation = serviceProvider.GetRequiredService<IActivation>();
|
activation = serviceProvider.GetRequiredService<IActivation>();
|
||||||
|
|||||||
@@ -51,6 +51,12 @@ internal sealed class HutaoException : Exception
|
|||||||
throw new InvalidCastException(message, innerException);
|
throw new InvalidCastException(message, innerException);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[DoesNotReturn]
|
||||||
|
public static InvalidOperationException InvalidOperation(string message, Exception? innerException = default)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(message, innerException);
|
||||||
|
}
|
||||||
|
|
||||||
[DoesNotReturn]
|
[DoesNotReturn]
|
||||||
public static NotSupportedException NotSupported(string? message = default, Exception? innerException = default)
|
public static NotSupportedException NotSupported(string? message = default, Exception? innerException = default)
|
||||||
{
|
{
|
||||||
|
|||||||
27
src/Snap.Hutao/Snap.Hutao/Core/ReadOnlySpan2D.cs
Normal file
27
src/Snap.Hutao/Snap.Hutao/Core/ReadOnlySpan2D.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Core;
|
||||||
|
|
||||||
|
internal readonly ref struct ReadOnlySpan2D<T>
|
||||||
|
where T : unmanaged
|
||||||
|
{
|
||||||
|
private readonly ref T reference;
|
||||||
|
private readonly int length;
|
||||||
|
private readonly int columns;
|
||||||
|
|
||||||
|
public unsafe ReadOnlySpan2D(void* pointer, int length, int columns)
|
||||||
|
{
|
||||||
|
reference = ref *(T*)pointer;
|
||||||
|
this.length = length;
|
||||||
|
this.columns = columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlySpan<T> this[int row]
|
||||||
|
{
|
||||||
|
get => MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref reference, row * columns), columns);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,6 +32,7 @@ internal sealed partial class IdentifyMonitorWindow : Window
|
|||||||
{
|
{
|
||||||
List<IdentifyMonitorWindow> windows = [];
|
List<IdentifyMonitorWindow> windows = [];
|
||||||
|
|
||||||
|
// TODO: the order here is not sync with unity.
|
||||||
IReadOnlyList<DisplayArea> displayAreas = DisplayArea.FindAll();
|
IReadOnlyList<DisplayArea> displayAreas = DisplayArea.FindAll();
|
||||||
for (int i = 0; i < displayAreas.Count; i++)
|
for (int i = 0; i < displayAreas.Count; i++)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Snap.Hutao.Win32.Foundation;
|
||||||
|
using Snap.Hutao.Win32.Graphics.Gdi;
|
||||||
|
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.Gdi32;
|
||||||
|
using static Snap.Hutao.Win32.User32;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Service.Game.Automation.ScreenCapture;
|
||||||
|
|
||||||
|
internal readonly struct GameScreenCaptureContext
|
||||||
|
{
|
||||||
|
public readonly GraphicsCaptureItem Item;
|
||||||
|
|
||||||
|
private readonly IDirect3DDevice direct3DDevice;
|
||||||
|
private readonly HWND hwnd;
|
||||||
|
|
||||||
|
public GameScreenCaptureContext(IDirect3DDevice direct3DDevice, HWND hwnd)
|
||||||
|
{
|
||||||
|
this.direct3DDevice = direct3DDevice;
|
||||||
|
this.hwnd = hwnd;
|
||||||
|
|
||||||
|
GraphicsCaptureItem.As<IGraphicsCaptureItemInterop>().CreateForWindow(hwnd, out Item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Direct3D11CaptureFramePool CreatePool()
|
||||||
|
{
|
||||||
|
return Direct3D11CaptureFramePool.CreateFreeThreaded(direct3DDevice, DeterminePixelFormat(hwnd), 2, Item.Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RecreatePool(Direct3D11CaptureFramePool framePool)
|
||||||
|
{
|
||||||
|
framePool.Recreate(direct3DDevice, DeterminePixelFormat(hwnd), 2, Item.Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GraphicsCaptureSession CreateSession(Direct3D11CaptureFramePool framePool)
|
||||||
|
{
|
||||||
|
GraphicsCaptureSession session = framePool.CreateCaptureSession(Item);
|
||||||
|
session.IsCursorCaptureEnabled = false;
|
||||||
|
session.IsBorderRequired = false;
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DirectXPixelFormat DeterminePixelFormat(HWND hwnd)
|
||||||
|
{
|
||||||
|
HDC hdc = GetDC(hwnd);
|
||||||
|
if (hdc != HDC.NULL)
|
||||||
|
{
|
||||||
|
int bitsPerPixel = GetDeviceCaps(hdc, GET_DEVICE_CAPS_INDEX.BITSPIXEL);
|
||||||
|
_ = ReleaseDC(hwnd, hdc);
|
||||||
|
if (bitsPerPixel >= 32)
|
||||||
|
{
|
||||||
|
return DirectXPixelFormat.R16G16B16A16Float;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DirectXPixelFormat.B8G8R8A8UIntNormalized;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Snap.Hutao.Core;
|
||||||
|
using Snap.Hutao.Core.ExceptionService;
|
||||||
|
using System.Buffers;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Service.Game.Automation.ScreenCapture;
|
||||||
|
|
||||||
|
internal sealed class GameScreenCaptureMemoryPool : MemoryPool<byte>
|
||||||
|
{
|
||||||
|
private static LazySlim<GameScreenCaptureMemoryPool> lazyShared = new(() => new());
|
||||||
|
|
||||||
|
private readonly object syncRoot = new();
|
||||||
|
private readonly LinkedList<GameScreenCaptureBuffer> unrentedBuffers = [];
|
||||||
|
private readonly LinkedList<GameScreenCaptureBuffer> rentedBuffers = [];
|
||||||
|
|
||||||
|
private int bufferCount;
|
||||||
|
|
||||||
|
public new static GameScreenCaptureMemoryPool Shared { get => lazyShared.Value; }
|
||||||
|
|
||||||
|
public override int MaxBufferSize { get => Array.MaxLength; }
|
||||||
|
|
||||||
|
public int BufferCount { get => bufferCount; }
|
||||||
|
|
||||||
|
public override IMemoryOwner<byte> Rent(int minBufferSize = -1)
|
||||||
|
{
|
||||||
|
ArgumentOutOfRangeException.ThrowIfLessThan(minBufferSize, 0);
|
||||||
|
|
||||||
|
lock (syncRoot)
|
||||||
|
{
|
||||||
|
foreach (GameScreenCaptureBuffer buffer in unrentedBuffers)
|
||||||
|
{
|
||||||
|
if (buffer.Memory.Length >= minBufferSize)
|
||||||
|
{
|
||||||
|
unrentedBuffers.Remove(buffer);
|
||||||
|
rentedBuffers.AddLast(buffer);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GameScreenCaptureBuffer newBuffer = new(this, minBufferSize);
|
||||||
|
rentedBuffers.AddLast(newBuffer);
|
||||||
|
++bufferCount;
|
||||||
|
return newBuffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
lock (syncRoot)
|
||||||
|
{
|
||||||
|
if (rentedBuffers.Count > 0)
|
||||||
|
{
|
||||||
|
HutaoException.InvalidOperation("There are still rented buffers.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class GameScreenCaptureBuffer : IMemoryOwner<byte>
|
||||||
|
{
|
||||||
|
private readonly GameScreenCaptureMemoryPool pool;
|
||||||
|
private readonly byte[] buffer;
|
||||||
|
|
||||||
|
public GameScreenCaptureBuffer(GameScreenCaptureMemoryPool pool, int bufferSize)
|
||||||
|
{
|
||||||
|
this.pool = pool;
|
||||||
|
buffer = GC.AllocateUninitializedArray<byte>(bufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Memory<byte> Memory { get => buffer.AsMemory(); }
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
lock (pool.syncRoot)
|
||||||
|
{
|
||||||
|
pool.rentedBuffers.Remove(this);
|
||||||
|
pool.unrentedBuffers.AddLast(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,10 +7,9 @@ using Snap.Hutao.Win32.Graphics.Direct3D;
|
|||||||
using Snap.Hutao.Win32.Graphics.Direct3D11;
|
using Snap.Hutao.Win32.Graphics.Direct3D11;
|
||||||
using Snap.Hutao.Win32.Graphics.Dxgi;
|
using Snap.Hutao.Win32.Graphics.Dxgi;
|
||||||
using Snap.Hutao.Win32.System.Com;
|
using Snap.Hutao.Win32.System.Com;
|
||||||
using Snap.Hutao.Win32.System.WinRT.Graphics.Capture;
|
|
||||||
using Windows.Graphics.Capture;
|
using Windows.Graphics.Capture;
|
||||||
using Windows.Graphics.DirectX;
|
|
||||||
using Windows.Graphics.DirectX.Direct3D11;
|
using Windows.Graphics.DirectX.Direct3D11;
|
||||||
|
using WinRT;
|
||||||
using static Snap.Hutao.Win32.ConstValues;
|
using static Snap.Hutao.Win32.ConstValues;
|
||||||
using static Snap.Hutao.Win32.D3D11;
|
using static Snap.Hutao.Win32.D3D11;
|
||||||
using static Snap.Hutao.Win32.Macros;
|
using static Snap.Hutao.Win32.Macros;
|
||||||
@@ -18,7 +17,8 @@ using static Snap.Hutao.Win32.Macros;
|
|||||||
namespace Snap.Hutao.Service.Game.Automation.ScreenCapture;
|
namespace Snap.Hutao.Service.Game.Automation.ScreenCapture;
|
||||||
|
|
||||||
[ConstructorGenerated]
|
[ConstructorGenerated]
|
||||||
internal sealed partial class GameScreenCaptureService
|
[Injection(InjectAs.Singleton, typeof(IGameScreenCaptureService))]
|
||||||
|
internal sealed partial class GameScreenCaptureService : IGameScreenCaptureService
|
||||||
{
|
{
|
||||||
private readonly ILogger<GameScreenCaptureService> logger;
|
private readonly ILogger<GameScreenCaptureService> logger;
|
||||||
|
|
||||||
@@ -39,6 +39,7 @@ internal sealed partial class GameScreenCaptureService
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("", "SH002")]
|
||||||
public unsafe bool TryStartCapture(HWND hwnd, [NotNullWhen(true)] out GameScreenCaptureSession? session)
|
public unsafe bool TryStartCapture(HWND hwnd, [NotNullWhen(true)] out GameScreenCaptureSession? session)
|
||||||
{
|
{
|
||||||
session = default;
|
session = default;
|
||||||
@@ -64,6 +65,8 @@ internal sealed partial class GameScreenCaptureService
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IUnknownMarshal.Release(pDXGIDevice);
|
||||||
|
|
||||||
hr = CreateDirect3D11DeviceFromDXGIDevice(pDXGIDevice, out Win32.System.WinRT.IInspectable* inspectable);
|
hr = CreateDirect3D11DeviceFromDXGIDevice(pDXGIDevice, out Win32.System.WinRT.IInspectable* inspectable);
|
||||||
if (FAILED(hr))
|
if (FAILED(hr))
|
||||||
{
|
{
|
||||||
@@ -71,29 +74,13 @@ internal sealed partial class GameScreenCaptureService
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
IDirect3DDevice direct3DDevice = WinRT.IInspectable.FromAbi((nint)inspectable).ObjRef.AsInterface<IDirect3DDevice>();
|
IUnknownMarshal.Release(inspectable);
|
||||||
GraphicsCaptureItem.As<IGraphicsCaptureItemInterop>().CreateForWindow(hwnd, out GraphicsCaptureItem item);
|
|
||||||
|
|
||||||
// Note
|
IDirect3DDevice direct3DDevice = IInspectable.FromAbi((nint)inspectable).ObjRef.AsInterface<IDirect3DDevice>();
|
||||||
Direct3D11CaptureFramePool framePool = Direct3D11CaptureFramePool.CreateFreeThreaded(direct3DDevice, DirectXPixelFormat.B8G8R8A8UIntNormalized, 2, item.Size);
|
|
||||||
|
GameScreenCaptureContext captureContext = new(direct3DDevice, hwnd);
|
||||||
|
session = new(captureContext, logger);
|
||||||
|
|
||||||
IUnknownMarshal.Release(pDXGIDevice);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
internal sealed class GameScreenCaptureSession : IDisposable
|
|
||||||
{
|
|
||||||
private readonly Direct3D11CaptureFramePool framePool;
|
|
||||||
private readonly GraphicsCaptureSession session;
|
|
||||||
|
|
||||||
public GameScreenCaptureSession(Direct3D11CaptureFramePool framePool)
|
|
||||||
{
|
|
||||||
this.session = session;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
session.Dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Snap.Hutao.Core;
|
||||||
|
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.WinRT.Graphics.Capture;
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using Windows.Graphics;
|
||||||
|
using Windows.Graphics.Capture;
|
||||||
|
using Windows.Graphics.DirectX.Direct3D11;
|
||||||
|
using WinRT;
|
||||||
|
using static Snap.Hutao.Win32.Macros;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Service.Game.Automation.ScreenCapture;
|
||||||
|
|
||||||
|
internal sealed class GameScreenCaptureSession : IDisposable
|
||||||
|
{
|
||||||
|
private readonly GameScreenCaptureContext captureContext;
|
||||||
|
private readonly Direct3D11CaptureFramePool framePool;
|
||||||
|
private readonly GraphicsCaptureSession session;
|
||||||
|
private readonly ILogger logger;
|
||||||
|
|
||||||
|
private TaskCompletionSource<IMemoryOwner<byte>>? frameRawPixelDataTaskCompletionSource;
|
||||||
|
private bool isFrameRawPixelDataRequested;
|
||||||
|
private SizeInt32 contentSize;
|
||||||
|
|
||||||
|
private bool isDisposed;
|
||||||
|
|
||||||
|
[SuppressMessage("", "SH002")]
|
||||||
|
public GameScreenCaptureSession(GameScreenCaptureContext captureContext, ILogger logger)
|
||||||
|
{
|
||||||
|
this.captureContext = captureContext;
|
||||||
|
this.logger = logger;
|
||||||
|
|
||||||
|
contentSize = captureContext.Item.Size;
|
||||||
|
|
||||||
|
captureContext.Item.Closed += OnItemClosed;
|
||||||
|
|
||||||
|
framePool = captureContext.CreatePool();
|
||||||
|
framePool.FrameArrived += OnFrameArrived;
|
||||||
|
|
||||||
|
session = captureContext.CreateSession(framePool);
|
||||||
|
session.StartCapture();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<IMemoryOwner<byte>> RequestFrameRawPixelDataAsync()
|
||||||
|
{
|
||||||
|
if (Volatile.Read(ref isFrameRawPixelDataRequested))
|
||||||
|
{
|
||||||
|
HutaoException.InvalidOperation("The frame raw pixel data has already been requested.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDisposed)
|
||||||
|
{
|
||||||
|
HutaoException.InvalidOperation("The session has been disposed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
frameRawPixelDataTaskCompletionSource = new();
|
||||||
|
Volatile.Write(ref isFrameRawPixelDataRequested, true);
|
||||||
|
|
||||||
|
return await frameRawPixelDataTaskCompletionSource.Task.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (isDisposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
session.Dispose();
|
||||||
|
framePool.Dispose();
|
||||||
|
isDisposed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnItemClosed(GraphicsCaptureItem sender, object args)
|
||||||
|
{
|
||||||
|
Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void OnFrameArrived(Direct3D11CaptureFramePool sender, object args)
|
||||||
|
{
|
||||||
|
// Simply ignore the frame if the frame raw pixel data is not requested.
|
||||||
|
if (!Volatile.Read(ref isFrameRawPixelDataRequested))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (Direct3D11CaptureFrame? frame = sender.TryGetNextFrame())
|
||||||
|
{
|
||||||
|
if (frame is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool needsReset = false;
|
||||||
|
|
||||||
|
if (frame.ContentSize != contentSize)
|
||||||
|
{
|
||||||
|
needsReset = true;
|
||||||
|
contentSize = frame.ContentSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
UnsafeProcessFrameSurface(frame.Surface);
|
||||||
|
}
|
||||||
|
catch (Exception ex) // TODO: test if it's device lost.
|
||||||
|
{
|
||||||
|
logger.LogError(ex, "Failed to process the frame surface.");
|
||||||
|
needsReset = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsReset)
|
||||||
|
{
|
||||||
|
captureContext.RecreatePool(sender);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void UnsafeProcessFrameSurface(IDirect3DSurface surface)
|
||||||
|
{
|
||||||
|
IDirect3DDxgiInterfaceAccess access = surface.As<IDirect3DDxgiInterfaceAccess>();
|
||||||
|
if (FAILED(access.GetInterface(in IDXGISurface.IID, out IDXGISurface* pDXGISurface)))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FAILED(pDXGISurface->GetDesc(out DXGI_SURFACE_DESC dxgiSurfaceDesc)))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should be the same device used to create the frame pool.
|
||||||
|
if (FAILED(pDXGISurface->GetDevice(in ID3D11Device.IID, out ID3D11Device* pD3D11Device)))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
D3D11_TEXTURE2D_DESC d3d11Texture2DDesc = default;
|
||||||
|
d3d11Texture2DDesc.Width = dxgiSurfaceDesc.Width;
|
||||||
|
d3d11Texture2DDesc.Height = dxgiSurfaceDesc.Height;
|
||||||
|
d3d11Texture2DDesc.ArraySize = 1;
|
||||||
|
|
||||||
|
// We have to copy out the resource to a CPU readable texture.
|
||||||
|
d3d11Texture2DDesc.CPUAccessFlags = D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_READ;
|
||||||
|
|
||||||
|
// DirectX will automatically convert any format to B8G8R8A8_UNORM.
|
||||||
|
d3d11Texture2DDesc.Format = DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM;
|
||||||
|
d3d11Texture2DDesc.MipLevels = 1;
|
||||||
|
d3d11Texture2DDesc.SampleDesc.Count = 1;
|
||||||
|
d3d11Texture2DDesc.Usage = D3D11_USAGE.D3D11_USAGE_STAGING;
|
||||||
|
|
||||||
|
if (FAILED(pD3D11Device->CreateTexture2D(ref d3d11Texture2DDesc, ref Unsafe.NullRef<D3D11_SUBRESOURCE_DATA>(), out ID3D11Texture2D* pD3D11Texture2D)))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FAILED(access.GetInterface(in ID3D11Resource.IID, out ID3D11Resource* pD3D11Resource)))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pD3D11Device->GetImmediateContext(out ID3D11DeviceContext* pD3D11DeviceContext);
|
||||||
|
pD3D11DeviceContext->CopyResource((ID3D11Resource*)pD3D11Texture2D, pD3D11Resource);
|
||||||
|
|
||||||
|
if (FAILED(pD3D11DeviceContext->Map((ID3D11Resource*)pD3D11Texture2D, 0U, D3D11_MAP.D3D11_MAP_READ, 0U, out D3D11_MAPPED_SUBRESOURCE d3d11MappedSubresource)))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The D3D11_MAPPED_SUBRESOURCE data is arranged as follows:
|
||||||
|
// |--------- Row pitch ----------|
|
||||||
|
// |---- Data width ----|- Blank -|
|
||||||
|
// ┌────────────────────┬─────────┐
|
||||||
|
// │ │ │
|
||||||
|
// │ Actual data │ Stride │
|
||||||
|
// │ │ │
|
||||||
|
// └────────────────────┴─────────┘
|
||||||
|
ReadOnlySpan2D<byte> subresource = new(d3d11MappedSubresource.pData, (int)d3d11Texture2DDesc.Height, (int)d3d11MappedSubresource.RowPitch);
|
||||||
|
|
||||||
|
int rowLength = contentSize.Width * 4;
|
||||||
|
IMemoryOwner<byte> buffer = GameScreenCaptureMemoryPool.Shared.Rent(contentSize.Height * rowLength);
|
||||||
|
|
||||||
|
for (int row = 0; row < contentSize.Height; row++)
|
||||||
|
{
|
||||||
|
subresource[row][..rowLength].CopyTo(buffer.Memory.Span.Slice(row * rowLength, rowLength));
|
||||||
|
}
|
||||||
|
|
||||||
|
ArgumentNullException.ThrowIfNull(frameRawPixelDataTaskCompletionSource);
|
||||||
|
frameRawPixelDataTaskCompletionSource.SetResult(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// 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 interface IGameScreenCaptureService
|
||||||
|
{
|
||||||
|
bool IsSupported();
|
||||||
|
|
||||||
|
bool TryStartCapture(HWND hwnd, [NotNullWhen(true)] out GameScreenCaptureSession? session);
|
||||||
|
}
|
||||||
@@ -66,7 +66,7 @@ internal sealed partial class TypedWishSummary : Wish
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string TotalOrangeFormatted
|
public string TotalOrangeFormatted
|
||||||
{
|
{
|
||||||
get => $"{TotalOrangePull} [{TotalOrangePercent,6:p2}]";
|
get => $"{TotalOrangePull} [{(TotalOrangePercent is double.NaN ? 0D : TotalOrangePercent),6:p2}]";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -74,7 +74,7 @@ internal sealed partial class TypedWishSummary : Wish
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string TotalPurpleFormatted
|
public string TotalPurpleFormatted
|
||||||
{
|
{
|
||||||
get => $"{TotalPurplePull} [{TotalPurplePercent,6:p2}]";
|
get => $"{TotalPurplePull} [{(TotalPurplePercent is double.NaN ? 0D : TotalPurplePercent),6:p2}]";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -82,7 +82,7 @@ internal sealed partial class TypedWishSummary : Wish
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string TotalBlueFormatted
|
public string TotalBlueFormatted
|
||||||
{
|
{
|
||||||
get => $"{TotalBluePull} [{TotalBluePercent,6:p2}]";
|
get => $"{TotalBluePull} [{(TotalBluePercent is double.NaN ? 0D : TotalBluePercent),6:p2}]";
|
||||||
}
|
}
|
||||||
|
|
||||||
public ColorSegmentCollection PullPercentSegmentSource
|
public ColorSegmentCollection PullPercentSegmentSource
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ internal readonly struct HWND
|
|||||||
|
|
||||||
public HWND(nint value) => Value = value;
|
public HWND(nint value) => Value = value;
|
||||||
|
|
||||||
public bool IsNull => Value is 0;
|
|
||||||
|
|
||||||
public static unsafe implicit operator HWND(nint value) => *(HWND*)&value;
|
public static unsafe implicit operator HWND(nint value) => *(HWND*)&value;
|
||||||
|
|
||||||
public static unsafe implicit operator nint(HWND value) => *(nint*)&value;
|
public static unsafe implicit operator nint(HWND value) => *(nint*)&value;
|
||||||
|
|||||||
@@ -5,5 +5,13 @@ namespace Snap.Hutao.Win32.Graphics.Gdi;
|
|||||||
|
|
||||||
internal readonly struct HDC
|
internal readonly struct HDC
|
||||||
{
|
{
|
||||||
|
public static readonly HDC NULL = 0;
|
||||||
|
|
||||||
public readonly nint Value;
|
public readonly nint Value;
|
||||||
|
|
||||||
|
public static unsafe implicit operator HDC(nint value) => *(HDC*)&value;
|
||||||
|
|
||||||
|
public static unsafe bool operator ==(HDC left, HDC right) => *(nint*)&left == *(nint*)&right;
|
||||||
|
|
||||||
|
public static unsafe bool operator !=(HDC left, HDC right) => *(nint*)&left != *(nint*)&right;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user