Reputation: 171
I'm facing a memory issue in my SharpDX application when disposing of Direct3D buffers. Initially, I wasn’t using DisposeCollector
, and I was manually calling Dispose()
on the buffers. My application freezes when disposing a large number of buffers (around 200,000). After investigating, I found a SharpDX-based project on GitHub that uses DisposeCollector
. I was able to replicate a similar memory issue there, but with one key difference.
What I observed:
DisposeCollector
): The application freezes when
manually calling Dispose() on a large number of buffers.DisposeCollector
, but when I removed the DisposeCollector
and
manually called Dispose()
on the buffers (just like in my
application), I could replicate the memory issue. However, in the
GitHub project, the memory was eventually released on the next
redraw, and the application did not freeze.DisposeCollector
: I tried adding
DisposeCollector
to my application, but it still freezes in the same
way, even though it helps in the GitHub example.Additional Observations:
The issue only occurs with a large number of buffers (around 200,000). When handling fewer objects, the problem doesn’t occur.
The problem seems to be specific to NVIDIA GPUs. I tested the code on different graphics cards, and the issue only appears on NVIDIA hardware.
I tried using NVIDIA Nsight to debug, but unfortunately, the tool doesn’t support Direct3D 11, so I couldn't gather further insights.
My question is:
Why does my application freeze when manually disposing of buffers, while in the modified GitHub project (without DisposeCollector
), the memory is delayed in being freed but without freezing? Could this behavior be specific to how SharpDX handles memory management on NVIDIA GPUs, or is there something else at play?
Starting from the sample I found here, I edited D3DApp.cs by adding
switch (e.KeyCode)
{
// ...
case Keys.R:
skipRenderLoop = true;
for (var i = 0; i < quad.Length; i++)
{
var q = quad[i];
//RemoveAndDispose(ref q);
q.Dispose();
quad[i] = null;
}
break;
case Keys.T:
skipRenderLoop = false;
break;
}
similarly I also edited the render loop as follows:
RenderLoop.Run(Window, () =>
{
if (skipRenderLoop) // <-- STOP DRAWING TO SIMULATE THE BEHAVIOUR OF MY APP. Without the ToDispose (see other code snippet), the issue reamins.
return;
// Start of frame:
// Retrieve immediate context
var context = DeviceManager.Direct3DContext;
Matrix worldViewProjection = Matrix.Identity;
Matrix viewProjection = Matrix.Identity;
// Clear depth stencil view
context.ClearDepthStencilView(DepthStencilView, DepthStencilClearFlags.Depth | DepthStencilClearFlags.Stencil, 1.0f, 0);
// Clear render target view
context.ClearRenderTargetView(RenderTargetView, Color.White);
// Create viewProjection matrix
viewProjection = Matrix.Multiply(viewMatrix, projectionMatrix);
// If Keys.CtrlKey is down, auto rotate viewProjection based on time
if (ctrlKey)
{
var time = clock.ElapsedMilliseconds / 1000.0f;
viewProjection = Matrix.RotationY(time * 1.8f) * Matrix.RotationZ(time * 1f) * Matrix.RotationZ(time * 0.6f) * viewProjection;
}
// Create WorldViewProjection Matrix
worldViewProjection = worldMatrix * viewProjection;
// HLSL uses "column major" matrices so transpose from "row major" first
worldViewProjection.Transpose();
// Write the worldViewProjection to the constant buffer
context.UpdateSubresource(ref worldViewProjection, worldViewProjectionBuffer);
// Render the primitives
axisLines.Render();
triangle.Render();
for (var i = 0; i < quad.Length; i++)
{
var qr = quad[i];
if (qr == null)
continue;
const int ITEMS_PER_ROW = 7;
var dx = i % ITEMS_PER_ROW;
var dy = i / ITEMS_PER_ROW;
worldMatrix = Matrix.Translation(dx * 0.5f, dy * 0.5f, 0);
worldViewProjection = worldMatrix * viewProjection;
// HLSL uses "column major" matrices so transpose from "row major" first
worldViewProjection.Transpose();
// Write the worldViewProjection to the constant buffer
context.UpdateSubresource(ref worldViewProjection, worldViewProjectionBuffer);
qr.Render();
}
// Render FPS
fps.Render();
// Render instructions + position changes
textRenderer.Render();
// Present the frame
Present();
}
);
In order to prevent the usage of DisposeCollector
I also edited QuadRenderer.cs:
protected override void CreateDeviceDependentResources()
{
// Ensure that if already set the device resources
// are correctly disposed of before recreating
RemoveAndDispose(ref quadVertices);
RemoveAndDispose(ref quadIndices);
// Retrieve our SharpDX.Direct3D11.Device1 instance
var device = this.DeviceManager.Direct3DDevice;
// Create a quad (two triangles)
quadVertices = /*ToDispose*/(Buffer.Create(device, BindFlags.VertexBuffer, new[] { // <--- AVOID ToDispose()
/* Vertex Position Vertex Color */
new Vector4(0.25f, 0.5f, -0.5f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), // Top-left
new Vector4(0.75f, 0.5f, -0.5f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), // Top-right
new Vector4(0.75f, 0.0f, -0.5f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), // Base-right
new Vector4(0.25f, 0.0f, -0.5f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), // Base-left
}));
quadBinding = new VertexBufferBinding(quadVertices, Utilities.SizeOf<Vector4>() * 2, 0);
// v0 v1
// |-----|
// | \ A |
// | B \ |
// |-----|
// v3 v2
quadIndices = /*ToDispose*/(Buffer.Create(device, BindFlags.IndexBuffer, new ushort[] { // <--- AVOID ToDispose()
0, 1, 2, // A
2, 3, 0 // B
}));
}
Upvotes: 4
Views: 76