Skip to content

Commit 2b16ff5

Browse files
Merge MTLSharedEvent synchronization and Metal capture improvements #690
2 parents 822abde + 04e52b9 commit 2b16ff5

File tree

5 files changed

+51
-10
lines changed

5 files changed

+51
-10
lines changed

lib/mtl/capture.jl

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ Use [`beginScope()`](@ref) and [`endScope()`](@ref) to set the boundaries for a
1212
"""
1313
MTLCaptureScope
1414

15+
function MTLCaptureScope(queue::MTLDevice, manager=MTLCaptureManager())
16+
handle = @objc [manager::id{MTLCaptureManager} newCaptureScopeWithDevice:queue::id{MTLDevice}]::id{MTLCaptureScope}
17+
MTLCaptureScope(handle)
18+
end
19+
function MTLCaptureScope(queue::MTLCommandQueue, manager=MTLCaptureManager())
20+
handle = @objc [manager::id{MTLCaptureManager} newCaptureScopeWithCommandQueue:queue::id{MTLCommandQueue}]::id{MTLCaptureScope}
21+
MTLCaptureScope(handle)
22+
end
23+
1524
# @objcwrapper MTLCaptureScope <: NSObject
1625

1726
"""
@@ -59,7 +68,7 @@ function MTLCaptureDescriptor()
5968
end
6069

6170
# TODO: Add capture state
62-
function MTLCaptureDescriptor(obj::Union{MTLDevice,MTLCommandQueue, MTLCaptureScope},
71+
function MTLCaptureDescriptor(obj::Union{MTLDevice, MTLCommandQueue, MTLCaptureScope},
6372
destination::MTLCaptureDestination;
6473
folder::String=nothing)
6574
desc = MTLCaptureDescriptor()
@@ -110,7 +119,7 @@ end
110119
111120
Start GPU frame capture using the default capture object and specifying capture descriptor parameters directly.
112121
"""
113-
function startCapture(obj::Union{MTLDevice,MTLCommandQueue, MTLCaptureScope},
122+
function startCapture(obj::Union{MTLDevice, MTLCommandQueue, MTLCaptureScope},
114123
destination::MTLCaptureDestination=MTLCaptureDestinationGPUTraceDocument;
115124
folder::String=nothing)
116125
if destination == MTLCaptureDestinationGPUTraceDocument && folder === nothing
@@ -141,8 +150,17 @@ Stop GPU frame capture.
141150
"""
142151
function stopCapture(manager::MTLCaptureManager=MTLCaptureManager())
143152
@objc [manager::id{MTLCaptureManager} stopCapture]::Nothing
153+
# Spinlock until capture is fully complete. Otherwise, `stopCapture`
154+
# sometimes returns while the capture manager is still capturing,
155+
# causing test failures and potentially corrupted captures.
156+
while manager.isCapturing
157+
end
144158
end
145159

160+
"""
161+
supports_destination(manager::MTLCaptureManager, destination::MTLCaptureDestination)
162+
Checks if a given capture destination is supported.
163+
"""
146164
function supports_destination(manager::MTLCaptureManager, destination::MTLCaptureDestination)
147165
@objc [manager::id{MTLCaptureManager} supportsDestination:destination::MTLCaptureDestination]::Bool
148166
end

lib/mtl/events.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ function MTLSharedEvent(dev::MTLDevice)
2929
return obj
3030
end
3131

32+
function waitUntilSignaledValue(ev::MTLSharedEvent, value, timeoutMS=typemax(UInt64))
33+
@objc [ev::id{MTLSharedEvent} waitUntilSignaledValue:value::UInt64
34+
timeoutMS:timeoutMS::UInt64]::Bool
35+
end
3236

3337
## shared event handle
3438

src/state.jl

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,17 @@ function global_queue(dev::MTLDevice)
5555
end::MTLCommandQueue
5656
end
5757

58+
"""
59+
queue_event(queue::MTLCommandQueue)::MTLSharedEvent
60+
61+
Return the `MTLSharedEvent` used to synchronize a queue
62+
"""
63+
function queue_event(queue::MTLCommandQueue)
64+
get!(task_local_storage(), (:MTLSharedEvent, queue)) do
65+
MTLSharedEvent(queue.device)
66+
end::MTLSharedEvent
67+
end
68+
5869
# TODO: Increase performance (currently ~15us)
5970
"""
6071
synchronize(queue)
@@ -66,9 +77,13 @@ and simply wait for it to be completed. Since command buffers *should* execute i
6677
First-In-First-Out manner, this synchronizes the GPU.
6778
"""
6879
@autoreleasepool function synchronize(queue::MTLCommandQueue=global_queue(device()))
80+
ev = queue_event(queue)
81+
val = ev.signaledValue + 1
6982
cmdbuf = MTLCommandBuffer(queue)
83+
MTL.encode_signal!(cmdbuf, ev, val)
7084
commit!(cmdbuf)
71-
wait_completed(cmdbuf)
85+
MTL.waitUntilSignaledValue(ev, val)
86+
return
7287
end
7388

7489
"""

src/utilities.jl

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,13 @@ function captured(f; dest=MTL.MTLCaptureDestinationGPUTraceDocument,
137137
end
138138

139139
folder = capture_dir()
140-
startCapture(object, dest; folder)
140+
scope = MTLCaptureScope(object)
141+
startCapture(scope, dest; folder)
141142
try
142-
f()
143-
synchronize()
143+
beginScope(scope)
144+
f()
145+
endScope(scope)
146+
synchronize()
144147
finally
145148
@info "GPU frame capture saved to $folder; open the resulting trace in Xcode"
146149
stopCapture()

test/capturing.jl

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ else
77
# Verify Metal capture is enabled via environment variable
88
@test capturing
99

10-
@testset "capturing" begin
11-
1210
mktempdir() do tmpdir
1311
cd(tmpdir) do
1412

@@ -38,6 +36,10 @@ desc.captureObject = dev
3836
desc.destination = MTL.MTLCaptureDestinationGPUTraceDocument
3937
@test desc.destination == MTL.MTLCaptureDestinationGPUTraceDocument
4038

39+
# Capture Manager supports destination
40+
@test !supports_destination(manager, MTL.MTLCaptureDestinationDeveloperTools)
41+
@test supports_destination(manager, MTL.MTLCaptureDestinationGPUTraceDocument)
42+
4143
# Output URL
4244
@test desc.outputURL === nothing
4345
path = joinpath(tmpdir, "test.gputrace")
@@ -67,7 +69,7 @@ bufferA = MtlArray{Float32,1,SharedStorage}(undef, tuple(4))
6769
@test manager.isCapturing == false
6870
startCapture(manager, desc)
6971
@test manager.isCapturing
70-
@test_throws ErrorException startCapture(manager, desc)
72+
@test_throws "Capture manager is already capturing." startCapture(manager, desc)
7173
Metal.@sync @metal threads=4 tester(bufferA)
7274
stopCapture(manager)
7375
@test manager.isCapturing == false
@@ -85,5 +87,4 @@ end
8587
end # cd(tmpdir) do
8688
end # mktempdir() do tmpdir
8789

88-
end # @testset "capturing" begin
8990
end # if shader_validation (else branch)

0 commit comments

Comments
 (0)