Skip to content

Commit 1e9660d

Browse files
committed
Reimplement object lifetimes to fix orphan objects (GraphicsDevice)
* Expands and enhances documentation * Minor cleanups
1 parent 89857e1 commit 1e9660d

6 files changed

Lines changed: 178 additions & 150 deletions

File tree

sources/engine/Stride.Graphics/Direct3D/DebugHelpers.Direct3D.cs

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
using System.Diagnostics;
88
using System.Runtime.CompilerServices;
99
using System.Runtime.InteropServices;
10-
using System.Text;
1110
using Silk.NET.Core.Native;
1211
using Silk.NET.Direct3D11;
1312
using Silk.NET.Direct3D12;
1413
using Silk.NET.DXGI;
14+
using Stride.Core;
1515

1616
namespace Stride.Graphics;
1717

@@ -23,7 +23,7 @@ public static Guid* WKPDID_D3DDebugObjectName
2323
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2424
get
2525
{
26-
ReadOnlySpan<byte> data = new byte[] {
26+
ReadOnlySpan<byte> data = [
2727
0x22, 0x8C, 0x9B, 0x42,
2828
0x88, 0x91,
2929
0x0C, 0x4B,
@@ -35,7 +35,7 @@ public static Guid* WKPDID_D3DDebugObjectName
3535
0x85,
3636
0xC2,
3737
0x00
38-
};
38+
];
3939

4040
Debug.Assert(data.Length == Unsafe.SizeOf<Guid>());
4141

@@ -48,32 +48,31 @@ public static Guid* WKPDID_D3DDebugObjectName
4848
/// in some graphics debuggers.
4949
/// </summary>
5050
#if STRIDE_GRAPHICS_API_DIRECT3D11
51-
public static void SetDebugName(ID3D11DeviceChild* deviceChild, string name)
51+
public static void SetDebugName(this ComPtr<ID3D11DeviceChild> deviceChild, string name, ICollectorHolder owningObject)
5252
{
53-
var nameUtf8 = Encoding.UTF8.GetBytes(name);
53+
var nameMemory = SilkMarshal.StringToMemory(name, NativeStringEncoding.LPWStr);
54+
nameMemory.DisposeBy(owningObject);
5455

55-
deviceChild->SetPrivateData(WKPDID_D3DDebugObjectName, (uint) nameUtf8.Length, in nameUtf8[0]);
56+
deviceChild.SetPrivateData(WKPDID_D3DDebugObjectName, (uint) nameMemory.Length, nameMemory.AsPtr<char>());
5657
}
5758
#elif STRIDE_GRAPHICS_API_DIRECT3D12
58-
public static unsafe void SetDebugName(ID3D12DeviceChild* deviceChild, string name)
59+
public static unsafe void SetDebugName(this ComPtr<ID3D12DeviceChild> deviceChild, string name)
5960
{
60-
var ptrName = (char*) SilkMarshal.StringToPtr(name, NativeStringEncoding.LPWStr);
61-
62-
deviceChild->SetName(ptrName);
63-
64-
SilkMarshal.Free((nint) ptrName);
61+
using var nameMemory = SilkMarshal.StringToMemory(name, NativeStringEncoding.LPWStr);
62+
deviceChild->SetName(nameMemory.AsPtr<char>());
6563
}
6664
#endif
6765

6866
/// <summary>
6967
/// Associates a debug name to the private data of a DXGI object, useful to see a friendly name
7068
/// in some graphics debuggers.
7169
/// </summary>
72-
public static void SetDebugName(IDXGIObject* deviceChild, string name)
70+
public static void SetDebugName(this ComPtr<IDXGIObject> dxgiObject, string name, ICollectorHolder owningObject)
7371
{
74-
var nameUtf8 = Encoding.UTF8.GetBytes(name);
72+
var nameMemory = SilkMarshal.StringToMemory(name, NativeStringEncoding.LPWStr);
73+
nameMemory.DisposeBy(owningObject);
7574

76-
deviceChild->SetPrivateData(WKPDID_D3DDebugObjectName, (uint) nameUtf8.Length, in nameUtf8[0]);
75+
dxgiObject.SetPrivateData(WKPDID_D3DDebugObjectName, (uint)nameMemory.Length, nameMemory.AsPtr<char>());
7776
}
7877
}
7978

sources/engine/Stride.Graphics/Direct3D/GraphicsDevice.Direct3D.cs

Lines changed: 61 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using Stride.Core;
1313

1414
using QueryPtr = Stride.Core.UnsafeExtensions.Pointer<Silk.NET.Direct3D11.ID3D11Query>;
15+
using System.Runtime.CompilerServices;
1516

1617
namespace Stride.Graphics
1718
{
@@ -27,15 +28,17 @@ public unsafe partial class GraphicsDevice
2728
/// <summary>
2829
/// Gets the native Direct3D 11 device.
2930
/// </summary>
30-
public ID3D11Device* NativeDevice { get; private set; }
31+
public ComPtr<ID3D11Device> NativeDevice { get; private set; }
3132

3233
/// <summary>
3334
/// Gets the native Direct3D 11 device context.
3435
/// </summary>
35-
internal ID3D11DeviceContext* NativeDeviceContext { get; private set; }
36+
internal ComPtr<ID3D11DeviceContext> NativeDeviceContext { get; private set; }
3637

37-
private readonly Queue<QueryPtr> disjointQueries = new(4);
38-
private readonly Stack<QueryPtr> currentDisjointQueries = new(2);
38+
//private readonly Queue<QueryPtr> disjointQueries = new(4);
39+
//private readonly Stack<QueryPtr> currentDisjointQueries = new(2);
40+
private readonly Queue<ComPtr<ID3D11Query>> disjointQueries = new(4);
41+
private readonly Stack<ComPtr<ID3D11Query>> currentDisjointQueries = new(2);
3942

4043
internal GraphicsProfile RequestedProfile;
4144

@@ -45,7 +48,7 @@ public unsafe partial class GraphicsDevice
4548
/// <summary>
4649
/// The tick frquency of timestamp queries in Hertz.
4750
/// </summary>
48-
public long TimestampFrequency { get; private set; }
51+
public ulong TimestampFrequency { get; private set; }
4952

5053
/// <summary>
5154
/// Gets the current status of this device.
@@ -60,7 +63,7 @@ public GraphicsDeviceStatus GraphicsDeviceStatus
6063
return GraphicsDeviceStatus.Reset;
6164
}
6265

63-
var result = (DeviceRemoveReason) NativeDevice->GetDeviceRemovedReason();
66+
var result = (DeviceRemoveReason) NativeDevice.GetDeviceRemovedReason();
6467

6568
return result switch
6669
{
@@ -95,52 +98,50 @@ private enum DeviceRemoveReason : int
9598
/// <summary>
9699
/// Marks the graphics device context as active on the current thread.
97100
/// </summary>
98-
public unsafe void Begin()
101+
public void Begin()
99102
{
100103
FrameTriangleCount = 0;
101104
FrameDrawCalls = 0;
102105

103-
var queryResult = new QueryDataTimestampDisjoint();
106+
Unsafe.SkipInit(out QueryDataTimestampDisjoint queryResult);
104107

105-
ID3D11Query* currentDisjointQuery = null;
108+
ComPtr<ID3D11Query> currentDisjointQuery = null;
106109

107110
// Try to read back the oldest disjoint query and reuse it. If not ready, create a new one
108111
if (disjointQueries.Count > 0)
109112
{
110113
currentDisjointQuery = disjointQueries.Peek();
111114

112-
var asyncQuery = (ID3D11Asynchronous*) currentDisjointQuery;
113-
var dataSize = currentDisjointQuery->GetDataSize();
114-
HResult result = NativeDeviceContext->GetData(asyncQuery, ref queryResult, dataSize, (int) AsyncGetdataFlag.Donotflush);
115+
var asyncQuery = (ID3D11Asynchronous*) currentDisjointQuery.Handle;
116+
var dataSize = currentDisjointQuery.GetDataSize();
117+
HResult result = NativeDeviceContext.GetData(asyncQuery, ref queryResult, dataSize, (int) AsyncGetdataFlag.Donotflush);
115118

116119
if (result.IsFailure)
117120
result.Throw();
118121

119-
TimestampFrequency = (long) queryResult.Frequency;
122+
TimestampFrequency = queryResult.Frequency;
120123
currentDisjointQuery = disjointQueries.Dequeue();
121124
}
122125
else
123126
{
124127
var disjointQueryDescription = new QueryDesc { Query = Query.TimestampDisjoint };
125128

126-
HResult result = NativeDevice->CreateQuery(in disjointQueryDescription, ref currentDisjointQuery);
129+
HResult result = NativeDevice.CreateQuery(in disjointQueryDescription, ref currentDisjointQuery);
127130

128131
if (result.IsFailure)
129132
result.Throw();
130133
}
131134

132135
currentDisjointQueries.Push(currentDisjointQuery);
133136

134-
NativeDeviceContext->Begin((ID3D11Asynchronous*) currentDisjointQuery);
137+
NativeDeviceContext.Begin(currentDisjointQuery);
135138
}
136139

137140
/// <summary>
138-
/// Enables profiling.
141+
/// Enables or disables profiling.
139142
/// </summary>
140-
/// <param name="enabledFlag">if set to <c>true</c> [enabled flag].</param>
141-
public void EnableProfile(bool enabledFlag)
142-
{
143-
}
143+
/// <param name="enabledFlag"><see langword="true"/> to enable profiling; <see langword="false"/> to disable it.</param>
144+
public void EnableProfile(bool enabledFlag) { }
144145

145146
/// <summary>
146147
/// Unmarks the graphics device context as active on the current thread.
@@ -149,34 +150,31 @@ public unsafe void End()
149150
{
150151
// If this fails, it means Begin() / End() don't match, something is very wrong
151152
var currentDisjointQuery = currentDisjointQueries.Pop();
152-
153-
ID3D11Asynchronous* asyncQuery = null;
154-
155-
ID3D11Query* query = currentDisjointQuery;
156-
query->QueryInterface(SilkMarshal.GuidPtrOf<ID3D11Asynchronous>(), (void**) &asyncQuery);
157-
158-
NativeDeviceContext->End(asyncQuery);
153+
NativeDeviceContext.End(currentDisjointQuery);
159154
disjointQueries.Enqueue(currentDisjointQuery);
160155
}
161156

162157
/// <summary>
163-
/// Executes a deferred command list.
158+
/// Executes a deferred command list.
164159
/// </summary>
165-
/// <param name="commandList">The deferred command list.</param>
160+
/// <param name="commandList">The deferred command list to execute.</param>
166161
public void ExecuteCommandList(CompiledCommandList commandList)
167162
{
168163
throw new NotImplementedException();
169164
}
170165

171166
/// <summary>
172-
/// Executes multiple deferred command lists.
167+
/// Executes multiple deferred command lists.
173168
/// </summary>
174-
/// <param name="commandLists">The deferred command lists.</param>
169+
/// <param name="commandLists">The deferred command lists to execute.</param>
175170
public void ExecuteCommandLists(int count, CompiledCommandList[] commandLists)
176171
{
177172
throw new NotImplementedException();
178173
}
179174

175+
/// <summary>
176+
/// Sets the graphics device to simulate a situation in which the device is lost and then reset.
177+
/// </summary>
180178
public void SimulateReset()
181179
{
182180
simulateReset = true;
@@ -194,14 +192,14 @@ private string GetRendererName()
194192
}
195193

196194
/// <summary>
197-
/// Initializes the specified device.
195+
/// Initializes the graphics device.
198196
/// </summary>
199-
/// <param name="graphicsProfiles">The graphics profiles.</param>
197+
/// <param name="graphicsProfiles">The graphics profiles to try, in order of preference.</param>
200198
/// <param name="deviceCreationFlags">The device creation flags.</param>
201199
/// <param name="windowHandle">The window handle.</param>
202200
private unsafe void InitializePlatformDevice(GraphicsProfile[] graphicsProfiles, DeviceCreationFlags deviceCreationFlags, object windowHandle)
203201
{
204-
if (NativeDevice != null)
202+
if (NativeDevice.Handle != null)
205203
{
206204
// Destroy previous device
207205
ReleaseDevice();
@@ -212,14 +210,13 @@ private unsafe void InitializePlatformDevice(GraphicsProfile[] graphicsProfiles,
212210
// Profiling is supported through PIX markers
213211
IsProfilingSupported = true;
214212

215-
// Map GraphicsProfile to D3D11 FeatureLevel
216213
creationFlags = (CreateDeviceFlag) deviceCreationFlags;
217214

218-
// Create Device D3D11 with feature Level based on profile
215+
// Create D3D11 Device with feature Level based on profile
219216
for (int index = 0; index < graphicsProfiles.Length; index++)
220217
{
218+
// Map GraphicsProfiles to D3D11 FeatureLevels
221219
var graphicsProfile = graphicsProfiles[index];
222-
223220
var level = graphicsProfile.ToFeatureLevel();
224221

225222
// INTEL workaround: it seems Intel driver doesn't support properly feature level 9.x. Fallback to 10.
@@ -254,8 +251,8 @@ private unsafe void InitializePlatformDevice(GraphicsProfile[] graphicsProfiles,
254251
continue;
255252
}
256253

257-
NativeDevice = device;
258-
NativeDeviceContext = deviceContext;
254+
NativeDevice = new ComPtr<ID3D11Device> { Handle = device }.DisposeBy(this);
255+
NativeDeviceContext = new ComPtr<ID3D11DeviceContext> { Handle = deviceContext }.DisposeBy(this);
259256

260257
// INTEL workaround: force ShaderProfile to be 10+ as well
261258
if (Adapter.VendorId == 0x8086)
@@ -272,53 +269,68 @@ private unsafe void InitializePlatformDevice(GraphicsProfile[] graphicsProfiles,
272269
//((IUnknown)nativeDeviceContext).AddReference();
273270
if (IsDebugMode)
274271
{
275-
DebugHelpers.SetDebugName((ID3D11DeviceChild*) NativeDeviceContext, "ImmediateContext");
272+
using ComPtr<ID3D11DeviceChild> deviceChild = NativeDeviceContext.QueryInterface<ID3D11DeviceChild>();
273+
deviceChild.SetDebugName("ImmediateContext", owningObject: this);
276274
}
277275
}
278276

277+
/// <summary>
278+
/// Makes adjustments to the pipeline state specific to Direct3D 11.
279+
/// </summary>
280+
/// <param name="pipelineStateDescription">The pipeline state description to modify.</param>
279281
private void AdjustDefaultPipelineStateDescription(ref PipelineStateDescription pipelineStateDescription)
280282
{
281283
// On D3D, default state is Less instead of our LessEqual
282284
// Let's update default pipeline state so that it correspond to D3D state after a "ClearState()"
283285
pipelineStateDescription.DepthStencilState.DepthBufferFunction = CompareFunction.Less;
284286
}
285287

288+
/// <summary>
289+
/// Releases the graphics device and all its associated resources.
290+
/// </summary>
286291
protected void DestroyPlatformDevice()
287292
{
288293
ReleaseDevice();
289294
}
290295

296+
/// <summary>
297+
/// Disposes the graphics device and all its associated resources.
298+
/// </summary>
291299
private void ReleaseDevice()
292300
{
293301
foreach (var queryPtr in disjointQueries)
294302
{
295-
queryPtr.Value->Release();
303+
queryPtr.Release();
296304
}
297305
disjointQueries.Clear();
298306

299-
// Display D3D11 ref counting info
300307
ID3D11DeviceContext* immediateContext = null;
301-
NativeDevice->GetImmediateContext(ref immediateContext);
308+
NativeDevice.GetImmediateContext(ref immediateContext);
302309

303310
immediateContext->ClearState();
304311
immediateContext->Flush();
312+
immediateContext->Release();
305313

314+
// Display D3D11 ref counting info
306315
if (IsDebugMode)
307316
{
308-
ID3D11Debug* debugDevice = null;
309-
HResult result = NativeDevice->QueryInterface(SilkMarshal.GuidPtrOf<ID3D11Debug>(), (void**) &debugDevice);
317+
HResult result = NativeDevice.QueryInterface(out ComPtr<ID3D11Debug> debugDevice);
310318

311-
if (result.IsSuccess && debugDevice != null)
319+
if (result.IsSuccess && debugDevice.Handle != null)
312320
{
313-
debugDevice->ReportLiveDeviceObjects(RldoFlags.Detail);
314-
debugDevice->Release();
321+
debugDevice.ReportLiveDeviceObjects(RldoFlags.Detail);
322+
debugDevice.Release();
315323
}
316324
}
317325

318-
NativeDevice->Release();
326+
NativeDevice.RemoveDisposeBy(this);
327+
NativeDevice.Dispose();
319328
NativeDevice = null;
320329
}
321330

331+
/// <summary>
332+
/// Called when the graphics device is being destroyed.
333+
/// </summary>
322334
internal void OnDestroyed()
323335
{
324336
}

sources/engine/Stride.Graphics/Direct3D12/GraphicsDevice.Direct3D12.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public unsafe partial class GraphicsDevice
7373
/// <summary>
7474
/// The tick frquency of timestamp queries in Hertz.
7575
/// </summary>
76-
public ulong TimestampFrequency { get; private set; }
76+
public long TimestampFrequency { get; private set; }
7777

7878
/// <summary>
7979
/// Gets the status of this device.
@@ -137,9 +137,9 @@ public void Begin()
137137
}
138138

139139
/// <summary>
140-
/// Enables profiling.
140+
/// Enables or disables profiling.
141141
/// </summary>
142-
/// <param name="enabledFlag">if set to <c>true</c> [enabled flag].</param>
142+
/// <param name="enabledFlag"><see langword="true"/> to enable profiling; <see langword="false"/> to disable it.</param>
143143
public void EnableProfile(bool enabledFlag)
144144
{
145145
}

0 commit comments

Comments
 (0)