smourier / 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, WinUI3, etc.
MIT License
311 stars 28 forks source link

How can I write dynamic vertex/index buffer? Are there any example ? #32

Closed vmx17 closed 1 year ago

vmx17 commented 1 year ago

In these days I've been trying to use DirectN to my drawing program for prototyping. Now I'm stuck to make dynamic vertex buffer. According to this article and that article, there is a way to translate vertex data via map.

I think this is a simple translation and resizing buffer matter when translate code from C++ to DirectN, please make me understand or point me the way to do. The initialization (written in the first article) seems succeeded with code below; (The code comes from the minimal example but DemoData is not in static array.)

var vertexBufferDesc = new D3D11_BUFFER_DESC();
var gc = GCHandle.Alloc(DemoData.VertexData, GCHandleType.Pinned);
vertexBufferDesc.ByteWidth = (uint)DemoData.VertexData.SizeOf();
vertexBufferDesc.Usage = D3D11_USAGE.D3D11_USAGE_DYNAMIC;  // changed
vertexBufferDesc.BindFlags = (uint)D3D11_BIND_FLAG.D3D11_BIND_VERTEX_BUFFER;
vertexBufferDesc.CPUAccessFlags = (uint)D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_WRITE; // changed

var vertexData = new D3D11_SUBRESOURCE_DATA();
vertexData.pSysMem = gc.AddrOfPinnedObject();

_vertexBuffer = _device.CreateBuffer(vertexBufferDesc, vertexData);
gc.Free();

where;

Before adding new vertex, it just work fine. Just re-sizing _vertexBuffer by vertexBufferDesc.ByteWidth cause error COM object that has been separated from its underlying RCW cannot be used.. So I tried with the code (as writtend in the second article) like below but I could not find out the way to map vertex data correctly. I think the way of "map" seems differ from the way of initialization. There article's code seems no vertex/index buffer update.;

// resize vertex buffer
private void MapVertexData() {
    var mapped_resource = new D3D11_MAPPED_SUBRESOURCE();
    mapped_resource = _deviceContext.Map(_vertexBuffer, 0, D3D11_MAP.D3D11_MAP_WRITE_DISCARD, 0);
    // NG! in below line. How to map in C#/DirectN?
    Buffer.BlockCopy(DemoData.VertexData, 0, mapped_resource.pData,0,DemoData.VertexData.SizeOf());

    _deviceContext.Unmap(_vertexBuffer, 0);
}

Though there are no _indexBuffer, situation is same.

smourier commented 1 year ago

Hi,

Buffer.BlockCopy only supports .NET arrays, while we're mostly dealing with IntPtr-type variables here , so you can do it like this:

var gc = GCHandle.Alloc(Data.VertexData, GCHandleType.Pinned);
var vertexData = new D3D11_SUBRESOURCE_DATA();
vertexData.pSysMem = gc.AddrOfPinnedObject();
gc.Free();

var map = _deviceContext.Map(_vertexBuffer, 0, D3D11_MAP.D3D11_MAP_WRITE_DISCARD);
CopyMemory(map.pData, vertexData.pSysMem, (IntPtr)Data.VertexData.SizeOf());
_deviceContext.Unmap(_vertexBuffer, 0);

And you need an IntPtr to IntPtr copy method wich doesn't exist by default in .NET, so:

[DllImport("kernel32", ExactSpelling = true, EntryPoint = "RtlMoveMemory")]
private static extern void CopyMemory(IntPtr destination, IntPtr source, IntPtr length);

PS: I've added CopyMemory support since it's useful: https://github.com/smourier/DirectN/commit/cace81ff0dd9c0c627acd6749088f975bdd34c1b

vmx17 commented 1 year ago

I'm really appreciated. It made reduce error rate but now it changed to System.AccessViolationException at CopyMemory(). The error occurs randomly, after adding several, or some ten's of vertex value. I'm re-checking the vertex data (now it can be accessed as ((App)Application.Current)).DrawManager.VertexData) is correctly sent to this method. The other issue, even after the mapping is done successfully, the screen does not change. I'll cut out the part to show the way to modify the data.
MapErrorAccessViolation

smourier commented 1 year ago

Hi,

DirectN consist merely of .NET bindings over the underlying technology (here Direct3D). It means it does almost nothing by itself but pass things back and forth.

So, AccesViolationException is a memory access error: pointer that points to invalid memory, memory copy of invalid size, writing read-only memory, etc. like we're used to do using C/C++, etc.

Make sure you've defined your _vertexBuffer properly if you want to write to it (D3D11_CPU_ACCESS_WRITE, etc.), make sure about the buffer size, etc. If you post a reproducible code, I can try to have a look at it.

As for the screen that doesn't change this is purely a DirectX issue.

vmx17 commented 1 year ago

Now I put my code here Though it still has some degraded, but the error can be reproduced at the same point. By the way, are there possibility to use C++ renderer and SwapChainPanel with C#? By making NuGet package, it seems work. Am I dreaming? Good night, anyway.

smourier commented 1 year ago

I don't reproduce but your _vertexBuffer is allocated with 576 bytes (vertexBufferDesc.ByteWidth = (uint)((App)Application.Current).DrawManager.VertexData.SizeOf()), and then you map it and copy 672 bytes to it with CopyMemory. That's called a buffer overflow and a good recipe for crashes

vmx17 commented 1 year ago

Hmm... I see. But before using "Map", I tried to remake _vertexBuffer with such code below but it cause error 'COM object that has been separated from its underlying RCW cannot be used.'. (Are there any way to avoid it?) Though I think this is a very basic question but in "DirectX way", is it genenerally get "buffer" sufficiently at initialization, and add/remove primitives in the limit of buffer? means, the buffer once aquired, we should NOT re-size it? I was trying to re-make buffers everytime with exact same size of its content. Or are there any right way to resize buffers? (I'm sorry this is the question for DirectX.) Now my repository contains code below but _device.CreateBuffer() cause the exception. (Also I put another feature and reduce some nuget packages.)

private void RemakeVBuffer()
{
        D3D11_BUFFER_DESC vertexBufferDesc;
        _vertexBuffer.Object.GetDesc(out vertexBufferDesc);
        var gc = GCHandle.Alloc(((App)Application.Current).DrawManager.VertexData, GCHandleType.Pinned);
        vertexBufferDesc.ByteWidth = (uint)((App)Application.Current).DrawManager.VertexData.SizeOf();

        var subResourceData = new D3D11_SUBRESOURCE_DATA();
        subResourceData.pSysMem = gc.AddrOfPinnedObject();
        if ((_vertexBuffer!=null) && !_vertexBuffer.IsDisposed) _vertexBuffer.Dispose();
                _vertexBuffer = _device.CreateBuffer(vertexBufferDesc, subResourceData);
        gc.Free();
}
smourier commented 1 year ago

In your code here https://github.com/vmx17/ProtoDraw/blob/main/ProtoDraw/Renderers/Dx11Renderer.cs#L139 you do this in a method:

var DXGIDev = new ComObject<IDXGIDevice1>(dxgiDevice);

This will create a disposable DXGIDev object (ComObject implements IDisposable) which instructs the .NET GC to call Disposeon it "sometimes in the future". When Dispose is called, it will release the underlying COM object. What happens is when you release the DXGI Device, you release the underlying D3D11 device too.

So the solution is to declare DXGIDev as a member instead, like this;

...
private IComObject<ID3D11DeviceContext> _deviceContext;
private IComObject<IDXGIDevice1> _dxgiDevice; // member
private IComObject<IDXGISwapChain1> _swapChain;
...

And do this:

var dxgiDevice = _device.As<IDXGIDevice1>(true);
_dxgiDevice = new ComObject<IDXGIDevice1>(dxgiDevice);
_swapChain = fac.CreateSwapChainForComposition<IDXGISwapChain1>(_dxgiDevice, desc);
vmx17 commented 1 year ago

Thank you so much. Finally(?) I could understand the reason of RCW error. Though dynamic buffering may be have performance issue, there seems a way to exist. I'can close this issue. Everyone around me was almost giving up WinUI3+DirectX desktop but this is a good progress. Thank you, again. (my prototype has still have funny matrix issue, but it should be solved with matrices in Render() method.) ProtoDraw

smourier commented 1 year ago

Actually, adding a WinUI3 + DirectN sample to this repo could be interesting.

I've added it here: https://github.com/smourier/DirectN/tree/master/DirectN/DirectN.WinUI3.MinimalD3D11

vmx17 commented 1 year ago

That's my first trial. Then I derived my least prototyping code from it. My small code is also working. By NOT using normalized axis, and using orthographic projection matrix it became very simple to sync pointer and drawing position. BTW, don't you have a plan to wrap DirectXMath? I think there are no wrap XMMatrix in DirectN. I'm now using JeremyAnsel.DirectX.DXMath.

smourier commented 1 year ago

DirectN is not even a "layer" on top of anything, it's merely .NET bindings over some Windows binaries. The advantages is there's not much code done by hand/a human so it requires much less maintenance.

DirectXMath is only C/C++ code, there's no binary (dll) shipped, so it's not accessible by a .NET bindings. The library you're using is a manual port from some DirectXMath functions (just like some DirectN code in the Manual folder), it's not really "DirectXMath" per se (and it's not up to date with latest DirectXMath precisely because it's a human port).

I manually added some particularly common classes in https://github.com/smourier/DirectN/tree/master/DirectN/DirectN/Manual such as XMFLOAT4 or XMFLOAT3, as well as D2DMATRIX* with some (matrix) utilities, but it's far from being equivalent. More could be added if someone proposes to work on it.

vmx17 commented 1 year ago

Au... so, XMath/C# function cannot utilize GPU (and it's the reason why I can see C# code). I should not use such functions for many data. The folder name 'Manual' represents such meaning... Thank you (and I'm sorry after closing).

pjmlp commented 1 year ago

Sorry for the drive by comment, I guess a better alternative to DirectXMath would be to use the .NET SIMD numeric classes.

smourier commented 1 year ago

Sorry for the drive by comment, I guess a better alternative to DirectXMath would be to use the .NET SIMD numeric classes.

Absolutely, but rewriting DirectXMath with SIMD may be quite a work. I don't know of any project that has done that.