Skip to content

Commit ea591de

Browse files
committed
Use retained debug bridge per test invocation
1 parent 0389951 commit ea591de

5 files changed

Lines changed: 83 additions & 50 deletions

File tree

tools/gfx-unit-test/scoped-core-debug-callback-test.cpp

Lines changed: 53 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,18 @@ void emitError(renderer_test::CoreToRHIDebugBridge& bridge, const char* message)
1111
bridge.handleMessage(rhi::DebugMessageType::Error, rhi::DebugMessageSource::Layer, message);
1212
}
1313

14-
renderer_test::CoreToRHIDebugBridge& getStaticBridgeAfterStackCallbackScope()
14+
renderer_test::CoreToRHIDebugBridge* getRetainedBridgeAfterStackCallbackScope()
1515
{
16-
static renderer_test::CoreToRHIDebugBridge bridge;
16+
auto bridge = renderer_test::createRetainedCoreToRHIDebugBridge();
1717

1818
renderer_test::CoreDebugCallback callback;
1919
{
20-
renderer_test::ScopedCoreDebugCallback scopedDebugCallback(bridge, &callback);
21-
emitError(bridge, "static scope");
20+
renderer_test::ScopedCoreDebugCallback scopedDebugCallback(*bridge, &callback);
21+
emitError(*bridge, "retained scope");
2222
}
23-
SLANG_CHECK(callback.getString() == "static scope\n");
23+
SLANG_CHECK(callback.getString() == "retained scope\n");
2424

25-
return bridge;
25+
return bridge.Ptr();
2626
}
2727
} // namespace
2828

@@ -99,61 +99,78 @@ SLANG_UNIT_TEST(scopedCoreDebugCallbackClearsBridgeOnException)
9999
SLANG_CHECK(secondCallback.getString() == "next iteration\n");
100100
}
101101

102-
SLANG_UNIT_TEST(scopedCoreDebugCallbackClearsStaticBridgeAfterStackCallback)
102+
SLANG_UNIT_TEST(scopedCoreDebugCallbackSeparatesRetainedBridgeScopes)
103103
{
104-
renderer_test::CoreToRHIDebugBridge& bridge = getStaticBridgeAfterStackCallbackScope();
105-
106-
emitError(bridge, "after stack callback");
104+
renderer_test::CoreToRHIDebugBridge* oldBridge = getRetainedBridgeAfterStackCallbackScope();
105+
auto nextBridge = renderer_test::createRetainedCoreToRHIDebugBridge();
107106

108107
renderer_test::CoreDebugCallback nextCallback;
109108
{
110-
renderer_test::ScopedCoreDebugCallback scopedDebugCallback(bridge, &nextCallback);
111-
emitError(bridge, "next invocation");
109+
renderer_test::ScopedCoreDebugCallback scopedDebugCallback(*nextBridge, &nextCallback);
110+
emitError(*oldBridge, "after stack callback");
111+
emitError(*nextBridge, "next invocation");
112112
}
113113
SLANG_CHECK(nextCallback.getString() == "next invocation\n");
114114
}
115115

116116
SLANG_UNIT_TEST(coreDebugBridgeHandlesConcurrentMessages)
117117
{
118118
static constexpr int kThreadCount = 4;
119-
static constexpr int kMessageCount = 64;
119+
static constexpr int kMessageCount = 1024;
120120

121121
renderer_test::CoreToRHIDebugBridge bridge;
122122
renderer_test::CoreDebugCallback callback;
123-
{
124-
renderer_test::ScopedCoreDebugCallback scopedDebugCallback(bridge, &callback);
123+
std::atomic<bool> startWriting(false);
124+
std::atomic<bool> keepReading(true);
125125

126-
std::atomic<bool> keepReading(true);
127-
std::thread readerThread(
126+
std::thread readerThread(
127+
[&]()
128+
{
129+
while (keepReading.load(std::memory_order_acquire))
130+
{
131+
callback.getString();
132+
}
133+
});
134+
135+
std::thread writerThreads[kThreadCount];
136+
for (int threadIndex = 0; threadIndex < kThreadCount; ++threadIndex)
137+
{
138+
writerThreads[threadIndex] = std::thread(
128139
[&]()
129140
{
130-
while (keepReading.load(std::memory_order_acquire))
141+
while (!startWriting.load(std::memory_order_acquire))
131142
{
132-
callback.getString();
143+
std::this_thread::yield();
133144
}
134-
});
135145

136-
std::thread writerThreads[kThreadCount];
137-
for (int threadIndex = 0; threadIndex < kThreadCount; ++threadIndex)
138-
{
139-
writerThreads[threadIndex] = std::thread(
140-
[&]()
146+
for (int messageIndex = 0; messageIndex < kMessageCount; ++messageIndex)
141147
{
142-
for (int messageIndex = 0; messageIndex < kMessageCount; ++messageIndex)
143-
{
144-
emitError(bridge, "x");
145-
}
146-
});
147-
}
148+
emitError(bridge, "x");
149+
}
150+
});
151+
}
148152

149-
for (auto& writerThread : writerThreads)
153+
{
154+
renderer_test::ScopedCoreDebugCallback scopedDebugCallback(bridge, &callback);
155+
startWriting.store(true, std::memory_order_release);
156+
for (int spinCount = 0; spinCount < 100000 && callback.getString().getLength() == 0;
157+
++spinCount)
150158
{
151-
writerThread.join();
159+
std::this_thread::yield();
152160
}
161+
SLANG_CHECK(callback.getString().getLength() > 0);
162+
}
153163

154-
keepReading.store(false, std::memory_order_release);
155-
readerThread.join();
164+
for (auto& writerThread : writerThreads)
165+
{
166+
writerThread.join();
156167
}
157168

158-
SLANG_CHECK(callback.getString().getLength() == kThreadCount * kMessageCount * 2);
169+
keepReading.store(false, std::memory_order_release);
170+
readerThread.join();
171+
172+
auto capturedLength = callback.getString().getLength();
173+
SLANG_CHECK(capturedLength > 0);
174+
SLANG_CHECK(capturedLength <= kThreadCount * kMessageCount * 2);
175+
SLANG_CHECK((capturedLength % 2) == 0);
159176
}

tools/render-test/render-test-main.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1769,9 +1769,9 @@ static SlangResult _innerMain(
17691769
}
17701770
}
17711771

1772-
static renderer_test::CoreToRHIDebugBridge debugCallback;
1772+
auto debugCallback = renderer_test::createRetainedCoreToRHIDebugBridge();
17731773
renderer_test::ScopedCoreDebugCallback scopedDebugCallback(
1774-
debugCallback,
1774+
*debugCallback,
17751775
stdWriters->getDebugCallback());
17761776

17771777
// Use the profile name set on options if set
@@ -1909,7 +1909,7 @@ static SlangResult _innerMain(
19091909
desc.deviceType = options.deviceType;
19101910

19111911
desc.enableValidation = options.enableDebugLayers;
1912-
desc.debugCallback = &debugCallback;
1912+
desc.debugCallback = debugCallback.Ptr();
19131913

19141914
desc.slang.lineDirectiveMode = SLANG_LINE_DIRECTIVE_MODE_NONE;
19151915
if (options.generateSPIRVDirectly)

tools/render-test/slang-support.h

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// slang-support.h
22
#pragma once
33

4+
#include "core/slang-basic.h"
45
#include "core/slang-std-writers.h"
56
#include "options.h"
67
#include "shader-input-layout.h"
@@ -17,7 +18,7 @@ namespace renderer_test
1718
/// RHI backends may invoke debug callbacks from backend or driver threads, so
1819
/// binding changes and forwarded messages are serialized.
1920
/// TODO: We should replace rhi::IDebugCallback with Slang::IDebugCallback.
20-
class CoreToRHIDebugBridge : public rhi::IDebugCallback
21+
class CoreToRHIDebugBridge : public Slang::RefObject, public rhi::IDebugCallback
2122
{
2223
public:
2324
void setCoreCallback(Slang::IDebugCallback* coreCallback)
@@ -48,6 +49,24 @@ class CoreToRHIDebugBridge : public rhi::IDebugCallback
4849
Slang::IDebugCallback* m_coreCallback = nullptr;
4950
};
5051

52+
/// Creates an RHI debug bridge that remains alive for process teardown.
53+
///
54+
/// Device descriptors store debug callbacks as raw pointers, and retained RHI
55+
/// state may emit messages after the harness invocation that created a device.
56+
/// Each invocation gets a distinct bridge so old emitters can only reach their
57+
/// own cleared bridge, not the next invocation's callback.
58+
inline Slang::RefPtr<CoreToRHIDebugBridge> createRetainedCoreToRHIDebugBridge()
59+
{
60+
static std::mutex* mutex = new std::mutex;
61+
static Slang::List<Slang::RefPtr<CoreToRHIDebugBridge>>* bridges =
62+
new Slang::List<Slang::RefPtr<CoreToRHIDebugBridge>>();
63+
64+
Slang::RefPtr<CoreToRHIDebugBridge> bridge = new CoreToRHIDebugBridge();
65+
std::lock_guard<std::mutex> lock(*mutex);
66+
bridges->add(bridge);
67+
return bridge;
68+
}
69+
5170
/// Binds an RHI debug bridge to a core callback for one active test invocation.
5271
///
5372
/// The bridge may be retained by RHI device state after this scope exits, but the

tools/slang-test/slang-test-main.cpp

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5556,9 +5556,6 @@ static SlangResult runUnitTestModule(
55565556
return SLANG_FAIL;
55575557

55585558
renderer_test::CoreDebugCallback coreDebugCallback;
5559-
// RHI state can outlive a unit-test invocation, so the bridge must outlive
5560-
// the stack callback whose binding is controlled below.
5561-
static renderer_test::CoreToRHIDebugBridge rhiDebugBridge;
55625559

55635560
UnitTestContext unitTestContext;
55645561
unitTestContext.slangGlobalSession = context->getSession();
@@ -5569,7 +5566,7 @@ static SlangResult runUnitTestModule(
55695566
context->options.enabledApis & _getAvailableRenderApiFlags(context);
55705567
unitTestContext.enableDebugLayers = context->options.enableDebugLayers;
55715568
unitTestContext.executableDirectory = context->exeDirectoryPath.getBuffer();
5572-
unitTestContext.debugCallback = &rhiDebugBridge;
5569+
unitTestContext.debugCallback = nullptr;
55735570

55745571
auto testCount = testModule->getTestCount();
55755572

@@ -5688,8 +5685,10 @@ static SlangResult runUnitTestModule(
56885685

56895686
// Clear any previous debug messages
56905687
coreDebugCallback.clear();
5688+
auto rhiDebugBridge = renderer_test::createRetainedCoreToRHIDebugBridge();
5689+
unitTestContext.debugCallback = rhiDebugBridge.Ptr();
56915690
renderer_test::ScopedCoreDebugCallback scopedDebugCallback(
5692-
rhiDebugBridge,
5691+
*rhiDebugBridge,
56935692
&coreDebugCallback);
56945693

56955694
try

tools/test-server/test-server-main.cpp

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -543,11 +543,9 @@ SlangResult TestServer::_executeUnitTest(const JSONRPCCall& call)
543543

544544
TestReporter testReporter;
545545
renderer_test::CoreDebugCallback coreDebugCallback;
546-
// RHI state can outlive an RPC invocation, so the bridge must outlive the
547-
// stack callback whose binding is controlled by the scoped helper.
548-
static renderer_test::CoreToRHIDebugBridge rhiDebugCallback;
546+
auto rhiDebugCallback = renderer_test::createRetainedCoreToRHIDebugBridge();
549547
renderer_test::ScopedCoreDebugCallback scopedDebugCallback(
550-
rhiDebugCallback,
548+
*rhiDebugCallback,
551549
&coreDebugCallback);
552550

553551
testModule->setTestReporter(&testReporter);
@@ -565,7 +563,7 @@ SlangResult TestServer::_executeUnitTest(const JSONRPCCall& call)
565563
unitTestContext.enabledApis = RenderApiFlags(args.enabledApis);
566564
unitTestContext.executableDirectory = m_exeDirectory.getBuffer();
567565
unitTestContext.enableDebugLayers = args.enableDebugLayers;
568-
unitTestContext.debugCallback = &rhiDebugCallback;
566+
unitTestContext.debugCallback = rhiDebugCallback.Ptr();
569567

570568
auto testCount = testModule->getTestCount();
571569
SLANG_ASSERT(testIndex >= 0 && testIndex < testCount);

0 commit comments

Comments
 (0)