Skip to content

Latest commit

History

108 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

DirectN

Direct Interop code for .NET Framework, .NET Core and .NET 5+ : DXGI, WIC, DirectX 9 to 12, Direct2D, Direct Write, Direct Composition, Media Foundation, WASAPI, CodecAPI, GDI, Spatial Audio, DVD, Windows Media Player, UWP DXInterop, etc.

The easiest way to use this is reference the nuget package: https://www.nuget.org/packages/DirectN/ (.NET Framework) or https://www.nuget.org/packages/DirectNCore/ (.NET Core and .NET 5+)

Or you can pick the C# source files you want (among the 9000 .cs provided ...), but it can be difficult because the dependencies between all these technologies make extracting a small portion quite difficult (and they are one of the reasons why DirectN exists...). However this is possible as demonstrated in this project: Windows Interface Composition Engine ("Wice") wich uses only ~700 files from DirectN.

DirectN allows you to port C/C++ code to C#, or to write C# code from scratch, more easily than with other existing interop libraries in this domain because one of its main objective is to use exactly the same names and types than the native concepts (interfaces, enums, structures, constants, methods, arguments, guids, etc.) . So you can read the official documentation, use existing C/C++ samples, and start coding with .NET right away.

By design, everything is in the same namespace (and in the same assembly if you use the whole package) so you don't need to know where is defined this or that interface, constants, etc.

All native COM interfaces are generated as .NET (COM) interfaces, not classes or fancy structs. COM wrappers are provided (ComObject, ComMemory, etc.), and some extension methods are also provided for some COM interfaces (the principle used can be extended to any COM method, but this part is not generated). They allow easier .NET programming, but they are not strictly needed. Most of this is possible because DirectN represent COM inheritance by .NET inheritance (so, DirectN.IWICImagingFactory2 derives from DirectN.IWICImagingFactory for example).

And last but not least, marking code using DirectN as unsafe is not required. 😅

For example, the exact same C# version of the "Capture screen using DirectX" code here: https://stackoverflow.com/questions/30021274/capture-screen-using-directx is this:

staticHRESULTDirect3D9TakeScreenshots(uintadapter,intcount){using(vard3d=newComObject<IDirect3D9>(Functions.Direct3DCreate9(Constants.D3D_SDK_VERSION))){varmode=new_D3DDISPLAYMODE();d3d.Object.GetAdapterDisplayMode(adapter,refmode).ThrowOnError();varparameters=new_D3DPRESENT_PARAMETERS_();parameters.Windowed=true;parameters.BackBufferCount=1;parameters.BackBufferHeight=mode.Height;parameters.BackBufferWidth=mode.Width;parameters.SwapEffect=_D3DSWAPEFFECT.D3DSWAPEFFECT_DISCARD;d3d.Object.CreateDevice(adapter,_D3DDEVTYPE.D3DDEVTYPE_HAL,IntPtr.Zero,Constants.D3DCREATE_SOFTWARE_VERTEXPROCESSING,refparameters,outvardev).ThrowOnError();using(vardevice=newComObject<IDirect3DDevice9>(dev)){dev.CreateOffscreenPlainSurface(mode.Width,mode.Height,_D3DFORMAT.D3DFMT_A8R8G8B8,_D3DPOOL.D3DPOOL_SYSTEMMEM,outvarsurf,IntPtr.Zero).ThrowOnError();using(varsurface=newComObject<IDirect3DSurface9>(surf)){varrc=new_D3DLOCKED_RECT();varrect=newtagRECT();surf.LockRect(refrc,refrect,0).ThrowOnError();varpitch=rc.Pitch;surf.UnlockRect();varshots=newbyte[count][];for(vari=0;i<count;i++){shots[i]=newbyte[pitch*mode.Height];}varsw=newStopwatch();sw.Start();for(vari=0;i<count;i++){dev.GetFrontBufferData(0,surf).ThrowOnError();surf.LockRect(refrc,refrect,0).ThrowOnError();Marshal.Copy(rc.pBits,shots[i],0,shots[i].Length);surf.UnlockRect().ThrowOnError();}Console.WriteLine("Elapsed: "+sw.Elapsed);for(vari=0;i<count;i++){SavePixelsToFile32bppPBGRA(mode.Width,mode.Height,(uint)pitch,shots[i],"cap"+i+".png",WICConstants.GUID_ContainerFormatPng).ThrowOnError();}}}}return0;}staticHRESULTSavePixelsToFile32bppPBGRA(uintwidth,uintheight,uintstride,byte[]pixels,stringfilePath,Guidformat){if(filePath==null)thrownewArgumentNullException(nameof(filePath));using(varfac=newComObject<IWICImagingFactory>((IWICImagingFactory)newWicImagingFactory())){fac.Object.CreateStream(outvarstream).ThrowOnError();using(newComObject<IWICStream>(stream)){constintGENERIC_WRITE=0x40000000;stream.InitializeFromFilename(filePath,GENERIC_WRITE).ThrowOnError();fac.Object.CreateEncoder(format,IntPtr.Zero,outvarencoder).ThrowOnError();using(newComObject<IWICBitmapEncoder>(encoder)){encoder.Initialize(stream,WICBitmapEncoderCacheOption.WICBitmapEncoderNoCache).ThrowOnError();encoder.CreateNewFrame(outvarframe,outvarbag).ThrowOnError();using(newComObject<IWICBitmapFrameEncode>(frame)){frame.Initialize(null).ThrowOnError();frame.SetSize(width,height).ThrowOnError();frame.SetPixelFormat(refformat).ThrowOnError();frame.WritePixels(height,stride,(int)(stride*height),pixels).ThrowOnError();frame.Commit().ThrowOnError();encoder.Commit().ThrowOnError();}}}}return0;}

And the C++ version is this:

HRESULTDirect3D9TakeScreenshots(UINT adapter, UINT count)
{
HRESULT hr = S_OK;
IDirect3D9 *d3d = nullptr;
IDirect3DDevice9 *device = nullptr;
IDirect3DSurface9 *surface = nullptr;
D3DPRESENT_PARAMETERS parameters = { 0 };
D3DDISPLAYMODE mode;
D3DLOCKED_RECT rc;
UINT pitch;
SYSTEMTIME st;
LPBYTE *shots = nullptr;
// init D3D and get screen size
d3d = Direct3DCreate9(D3D_SDK_VERSION);
HRCHECK(d3d->GetAdapterDisplayMode(adapter, &mode));
parameters.Windowed = TRUE;
parameters.BackBufferCount = 1;
parameters.BackBufferHeight = mode.Height;
parameters.BackBufferWidth = mode.Width;
parameters.SwapEffect = D3DSWAPEFFECT_DISCARD;
parameters.hDeviceWindow = NULL;
// create device & capture surfaceHRCHECK(d3d->CreateDevice(adapter, D3DDEVTYPE_HAL, NULL, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &parameters, &device));
HRCHECK(device->CreateOffscreenPlainSurface(mode.Width, mode.Height, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &surface, nullptr));
// compute the required buffer sizeHRCHECK(surface->LockRect(&rc, NULL, 0));
pitch = rc.Pitch;
HRCHECK(surface->UnlockRect());
// allocate screenshots buffers
shots = newLPBYTE[count];
for (UINT i = 0; i < count; i++)
{
shots[i] = newBYTE[pitch * mode.Height];
}
GetSystemTime(&st); // measure the time we spend doing <count> captureswprintf(L"%i:%i:%i.%i\n", st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
for (UINT i = 0; i < count; i++)
{
// get the dataHRCHECK(device->GetFrontBufferData(0, surface));
// copy it into our buffersHRCHECK(surface->LockRect(&rc, NULL, 0));
CopyMemory(shots[i], rc.pBits, rc.Pitch * mode.Height);
HRCHECK(surface->UnlockRect());
}
GetSystemTime(&st);
wprintf(L"%i:%i:%i.%i\n", st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
// save all screenshotsfor (UINT i = 0; i < count; i++)
{
WCHAR file[100];
wsprintf(file, L"cap%i.png", i);
HRCHECK(SavePixelsToFile32bppPBGRA(mode.Width, mode.Height, pitch, shots[i], file, GUID_ContainerFormatPng));
}
cleanup:
if (shots != nullptr)
{
for (UINT i = 0; i < count; i++)
{
delete shots[i];
}
delete[] shots;
}
RELEASE(surface);
RELEASE(device);
RELEASE(d3d);
return hr;
}
HRESULTSavePixelsToFile32bppPBGRA(UINT width, UINT height, UINT stride, LPBYTE pixels, LPWSTR filePath, constGUID &format)
{
if (!filePath || !pixels)
returnE_INVALIDARG;
HRESULT hr = S_OK;
IWICImagingFactory *factory = nullptr;
IWICBitmapEncoder *encoder = nullptr;
IWICBitmapFrameEncode *frame = nullptr;
IWICStream *stream = nullptr;
GUID pf = GUID_WICPixelFormat32bppPBGRA;
BOOL coInit = CoInitialize(nullptr);
HRCHECK(CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&factory)));
HRCHECK(factory->CreateStream(&stream));
HRCHECK(stream->InitializeFromFilename(filePath, GENERIC_WRITE));
HRCHECK(factory->CreateEncoder(format, nullptr, &encoder));
HRCHECK(encoder->Initialize(stream, WICBitmapEncoderNoCache));
HRCHECK(encoder->CreateNewFrame(&frame, nullptr)); // we don't use options hereHRCHECK(frame->Initialize(nullptr)); // we dont' use any options hereHRCHECK(frame->SetSize(width, height));
HRCHECK(frame->SetPixelFormat(&pf));
HRCHECK(frame->WritePixels(height, stride, stride * height, pixels));
HRCHECK(frame->Commit());
HRCHECK(encoder->Commit());
cleanup:
RELEASE(stream);
RELEASE(frame);
RELEASE(encoder);
RELEASE(factory);
if (coInit) CoUninitialize();
return hr;
}
  • Note 1: there are currently some issues with sructures that contain unions. This will be fixed soon. If you have a specific problem with that, just create an issue
  • Note 2: the functions.cs file has some issues. everything in the Generated folder is, well, automatically generated. If you have a specific problem with that, just create an issue
  • Note 3: the tool that builds the Generated folder is not open source.

.NET Core 3.1 => .NET 5 - DirectD311 minimal sample.

The "DirectN.MinimalD3D11" sample here https://github.com/smourier/DirectN/tree/master/DirectN/DirectN.MinimalD3D11 has been ported from here: https://gist.github.com/d7samurai/261c69490cce0620d0bfc93003cd1052 which features a minimal DirectD311 "uncluttered direct3d 11 setup and rendering primer for newcomers to the api" sample.

It's dependent on DirectN, .NET Core 3.1 or .NET 5, Windows Forms and ... that's it. Here is the output (believe me, it rotates):

Sample

About

Direct interop Code for .NET Framework, .NET Core and .NET 5+ : DXGI, WIC, DirectX 9 to 12, Direct2D, Direct Write, Direct Composition, Media Foundation, WASAPI, CodecAPI, GDI, Spatial Audio, DVD, Windows Media Player, UWP DXInterop, etc.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages