Skip to content

Commit f3899bd

Browse files
MarijnS95claude
andcommitted
[Metal] Make dispatchRays a self-contained encoder operation
The DX and Vulkan backends record a ray dispatch by creating an encoder and calling Encoder.dispatchRays() directly. Metal instead went through createRayTracingCommands, a device method that bound the descriptor heap and argument buffer, built and bound the IRDispatchRaysArgument, marked the RT resources resident, and only then called a thin dispatchRays(). Since Metal lowers raygen to a compute kernel, ray tracing now records through the shared createComputeCommands path (which already binds the heap and argument buffer and marks the common resources resident) by branching on P.isRayTracing(). The IRDispatchRaysArgument setup and RT-specific residency move into MTLComputeEncoder::dispatchRays, defined out-of-line so it can allocate via CB->Dev and keep the buffer alive in CB->KeepAliveOwned. The heap address for ResDescHeap, absent from the cross-backend signature, is remembered on the encoder via bindResourceHeap(). createRayTracingCommands is removed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 013ce93 commit f3899bd

1 file changed

Lines changed: 98 additions & 143 deletions

File tree

lib/API/MTL/MTLDevice.cpp

Lines changed: 98 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,9 @@ class MTLComputeEncoder : public offloadtest::ComputeEncoder {
609609
/// the next encoder transition (via endEncodingImpl).
610610
MTL::AccelerationStructureCommandEncoder *ASEnc = nullptr;
611611

612+
/// Bound resource heap, read back for IRDispatchRaysArgument::ResDescHeap.
613+
MTLDescriptorHeap *BoundResourceHeap = nullptr;
614+
612615
/// Accumulated barrier scope from commands recorded since the last barrier.
613616
MTL::BarrierScope PendingScope = MTL::BarrierScope(0);
614617

@@ -665,6 +668,14 @@ class MTLComputeEncoder : public offloadtest::ComputeEncoder {
665668

666669
MTL::ComputeCommandEncoder *getNative() const { return ComputeEnc; }
667670

671+
llvm::Error bindResourceHeap(MTLDescriptorHeap &Heap) {
672+
if (auto Err = ensureComputeEncoder())
673+
return Err;
674+
Heap.bind(ComputeEnc);
675+
BoundResourceHeap = &Heap;
676+
return llvm::Error::success();
677+
}
678+
668679
MTL::CommandEncoder *getActiveEncoder() const {
669680
if (ComputeEnc)
670681
return ComputeEnc;
@@ -787,40 +798,9 @@ class MTLComputeEncoder : public offloadtest::ComputeEncoder {
787798
// MTL::Device handle (used to allocate scratch and instance buffers).
788799
llvm::Error batchBuildAS(llvm::ArrayRef<ASBuildItem> Items) override;
789800

790-
// Dispatch threads using a raygen compute kernel synthesized by the
791-
// irconverter. All bindings (descriptor heap, top-level argument buffer,
792-
// IRDispatchRaysArgument at slot 3, visible/intersection function tables,
793-
// and the SBT buffer) must already be set on the active compute encoder by
794-
// the caller — this method only binds the pipeline state and issues the
795-
// dispatch.
796-
llvm::Error dispatchRays(const PipelineState &PSO, const ShaderBindingTable &,
797-
uint32_t Width, uint32_t Height,
798-
uint32_t Depth) override {
799-
if (!llvm::isa<MTLRayTracingPipelineState>(&PSO))
800-
return llvm::createStringError(
801-
std::errc::invalid_argument,
802-
"dispatchRays requires a RayTracing PipelineState.");
803-
const auto &RTPSO = llvm::cast<MTLRayTracingPipelineState>(PSO);
804-
if (!RTPSO.ComputePipeline)
805-
return llvm::createStringError(
806-
std::errc::invalid_argument,
807-
"RayTracing PipelineState has no compute pipeline state.");
808-
if (auto Err = ensureComputeEncoder())
809-
return Err;
810-
flushBarrier();
811-
insertDebugSignpost(
812-
llvm::formatv("DispatchRays [{0},{1},{2}]", Width, Height, Depth)
813-
.str());
814-
ComputeEnc->setComputePipelineState(RTPSO.ComputePipeline);
815-
816-
// DispatchRays(W, H, D) launches W*H*D rays; tid in the irconverter raygen
817-
// kernel is the per-ray index. Pass grid as raw (W, H, D) and let Metal
818-
// ceil-divide by ThreadsPerGroup to compute threadgroup count.
819-
const MTL::Size GridSize(Width, Height, Depth);
820-
ComputeEnc->dispatchThreads(GridSize, RTPSO.ThreadsPerGroup);
821-
addBarrierScope(MTL::BarrierScopeBuffers | MTL::BarrierScopeTextures);
822-
return llvm::Error::success();
823-
}
801+
llvm::Error dispatchRays(const PipelineState &PSO,
802+
const ShaderBindingTable &SBT, uint32_t Width,
803+
uint32_t Height, uint32_t Depth) override;
824804

825805
/// Lazily transition into an AccelerationStructureCommandEncoder; mirrors
826806
/// the existing compute↔blit lazy switch.
@@ -1742,7 +1722,8 @@ class MTLDevice : public offloadtest::Device {
17421722
const auto &PS = llvm::cast<MTLPipelineState>(IS.Pipeline.get());
17431723
MTLGPUDescriptorHandle Handle = {};
17441724
if (IS.DescHeap) {
1745-
IS.DescHeap->bind(NativeEncoder);
1725+
if (auto Err = Encoder.bindResourceHeap(*IS.DescHeap))
1726+
return Err;
17461727
Handle = IS.DescHeap->getGPUDescriptorHandleForHeapStart();
17471728
}
17481729

@@ -1772,7 +1753,15 @@ class MTLDevice : public offloadtest::Device {
17721753
NativeEncoder->useResource(llvm::cast<MTLBuffer>(B.get())->Buf,
17731754
MTL::ResourceUsageRead);
17741755

1775-
if (auto Err = Encoder.dispatch(*IS.Pipeline.get(),
1756+
if (P.isRayTracing()) {
1757+
if (auto Err =
1758+
Encoder.dispatchRays(*IS.Pipeline.get(), *IS.SBT.get(),
1759+
P.DispatchParameters.DispatchGroupCount[0],
1760+
P.DispatchParameters.DispatchGroupCount[1],
1761+
P.DispatchParameters.DispatchGroupCount[2]))
1762+
return Err;
1763+
} else if (auto Err =
1764+
Encoder.dispatch(*IS.Pipeline.get(),
17761765
P.DispatchParameters.DispatchGroupCount[0],
17771766
P.DispatchParameters.DispatchGroupCount[1],
17781767
P.DispatchParameters.DispatchGroupCount[2]))
@@ -1781,112 +1770,6 @@ class MTLDevice : public offloadtest::Device {
17811770
return llvm::Error::success();
17821771
}
17831772

1784-
llvm::Error createRayTracingCommands(Pipeline &P, InvocationState &IS) {
1785-
auto EncoderOrErr = IS.CB->createComputeEncoder();
1786-
if (!EncoderOrErr)
1787-
return EncoderOrErr.takeError();
1788-
auto &Encoder = llvm::cast<MTLComputeEncoder>(*EncoderOrErr.get());
1789-
MTL::ComputeCommandEncoder *NativeEncoder = Encoder.getNative();
1790-
1791-
const auto &RTPSO =
1792-
llvm::cast<MTLRayTracingPipelineState>(*IS.Pipeline.get());
1793-
const auto &SBT = llvm::cast<MTLShaderBindingTable>(*IS.SBT.get());
1794-
1795-
// Bind the global descriptor heap + top-level argument buffer the same
1796-
// way the compute path does; the raygen kernel and any visible-function
1797-
// callees consume them at the same slots (kIRDescriptorHeapBindPoint and
1798-
// kIRArgumentBufferBindPoint).
1799-
MTLGPUDescriptorHandle Handle = {};
1800-
if (IS.DescHeap) {
1801-
IS.DescHeap->bind(NativeEncoder);
1802-
Handle = IS.DescHeap->getGPUDescriptorHandleForHeapStart();
1803-
}
1804-
for (uint32_t Idx = 0u; Idx < P.Sets.size(); ++Idx) {
1805-
RTPSO.ArgBuffer->setRootDescriptorTable(Idx, Handle);
1806-
Handle.addOffset(P.Sets[Idx].Resources.size());
1807-
}
1808-
RTPSO.ArgBuffer->bind(NativeEncoder);
1809-
1810-
// Populate the per-dispatch IRDispatchRaysArgument: SBT region addresses
1811-
// (RayGen / Miss / HitGroup / Callable), GPU pointers to the global
1812-
// root-signature argument buffer + descriptor heaps, plus resource IDs
1813-
// for the visible / intersection function tables. The raygen kernel
1814-
// reads this struct from the buffer bound at kIRRayDispatchArgumentsBind-
1815-
// Point and any visible-function callees inherit it through the same
1816-
// pointer.
1817-
IRDispatchRaysArgument Args{};
1818-
Args.DispatchRaysDesc.RayGenerationShaderRecord = SBT.RayGenRegion;
1819-
Args.DispatchRaysDesc.MissShaderTable = SBT.MissRegion;
1820-
Args.DispatchRaysDesc.HitGroupTable = SBT.HitGroupRegion;
1821-
Args.DispatchRaysDesc.CallableShaderTable = SBT.CallableRegion;
1822-
Args.DispatchRaysDesc.Width = P.DispatchParameters.DispatchGroupCount[0];
1823-
Args.DispatchRaysDesc.Height = P.DispatchParameters.DispatchGroupCount[1];
1824-
Args.DispatchRaysDesc.Depth = P.DispatchParameters.DispatchGroupCount[2];
1825-
Args.GRS = RTPSO.ArgBuffer->getGPUAddress();
1826-
Args.ResDescHeap =
1827-
IS.DescHeap ? IS.DescHeap->getGPUDescriptorHandleForHeapStart().Ptr : 0;
1828-
Args.SmpDescHeap = 0;
1829-
Args.VisibleFunctionTable =
1830-
RTPSO.VFT ? RTPSO.VFT->gpuResourceID() : MTL::ResourceID{0};
1831-
Args.IntersectionFunctionTable =
1832-
RTPSO.IFT ? RTPSO.IFT->gpuResourceID() : MTL::ResourceID{0};
1833-
Args.IntersectionFunctionTables = 0;
1834-
1835-
const BufferCreateDesc ArgsBufDesc = BufferCreateDesc::uploadBuffer();
1836-
auto ArgsBufOrErr = offloadtest::createBufferWithData(
1837-
*IS.CB->Dev, "MTL Dispatch Rays Arguments", ArgsBufDesc, &Args,
1838-
sizeof(IRDispatchRaysArgument), nullptr, nullptr);
1839-
if (!ArgsBufOrErr)
1840-
return ArgsBufOrErr.takeError();
1841-
1842-
auto *MTLArgsBuf = llvm::cast<MTLBuffer>(ArgsBufOrErr->get());
1843-
IS.CB->KeepAliveOwned.push_back(std::move(*ArgsBufOrErr));
1844-
1845-
NativeEncoder->setBuffer(MTLArgsBuf->Buf, 0,
1846-
kIRRayDispatchArgumentsBindPoint);
1847-
NativeEncoder->useResource(MTLArgsBuf->Buf, MTL::ResourceUsageRead);
1848-
1849-
// Mark every dispatch-side resource resident: descriptor-table bundles,
1850-
// acceleration structures + their irconverter header/contribution
1851-
// buffers (so RayQuery/TraceRay can read them), the SBT buffer (the
1852-
// raygen kernel dereferences SBT addresses), and the visible /
1853-
// intersection function tables.
1854-
for (const auto &Table : IS.DescTables)
1855-
for (const auto &ResPair : Table.Resources)
1856-
for (const auto &ResSet : ResPair.second)
1857-
NativeEncoder->useResource(ResSet.Resource.get(),
1858-
MTL::ResourceUsageRead |
1859-
MTL::ResourceUsageWrite);
1860-
auto MarkASResident =
1861-
[&](std::unique_ptr<offloadtest::AccelerationStructure> &AS) {
1862-
auto *MTLAS = llvm::cast<MetalAccelerationStructure>(AS.get());
1863-
NativeEncoder->useResource(MTLAS->AccelStruct,
1864-
MTL::ResourceUsageRead);
1865-
};
1866-
for (auto &AS : IS.BLASes)
1867-
MarkASResident(AS);
1868-
for (auto &Entry : IS.TLASes)
1869-
MarkASResident(Entry.second);
1870-
for (auto &B : IS.ASDescriptorBuffers)
1871-
NativeEncoder->useResource(llvm::cast<MTLBuffer>(B.get())->Buf,
1872-
MTL::ResourceUsageRead);
1873-
if (SBT.Buffer)
1874-
NativeEncoder->useResource(SBT.Buffer, MTL::ResourceUsageRead);
1875-
if (RTPSO.VFT)
1876-
NativeEncoder->useResource(RTPSO.VFT, MTL::ResourceUsageRead);
1877-
if (RTPSO.IFT)
1878-
NativeEncoder->useResource(RTPSO.IFT, MTL::ResourceUsageRead);
1879-
1880-
if (auto Err =
1881-
Encoder.dispatchRays(*IS.Pipeline.get(), *IS.SBT.get(),
1882-
P.DispatchParameters.DispatchGroupCount[0],
1883-
P.DispatchParameters.DispatchGroupCount[1],
1884-
P.DispatchParameters.DispatchGroupCount[2]))
1885-
return Err;
1886-
Encoder.endEncoding();
1887-
return llvm::Error::success();
1888-
}
1889-
18901773
llvm::Error createRenderTarget(Pipeline &P, InvocationState &IS) {
18911774
if (!P.Bindings.RTargetBufferPtr)
18921775
return llvm::createStringError(
@@ -3094,7 +2977,7 @@ class MTLDevice : public offloadtest::Device {
30942977
IS.SBT = std::move(*SBTOrErr);
30952978
llvm::outs() << "Shader Binding Table created.\n";
30962979

3097-
if (auto Err = createRayTracingCommands(P, IS))
2980+
if (auto Err = createComputeCommands(P, IS))
30982981
return Err;
30992982
}
31002983

@@ -3303,6 +3186,78 @@ llvm::Error MTLComputeEncoder::batchBuildAS(llvm::ArrayRef<ASBuildItem> Items) {
33033186

33043187
return llvm::Error::success();
33053188
}
3189+
3190+
llvm::Error MTLComputeEncoder::dispatchRays(const PipelineState &PSO,
3191+
const ShaderBindingTable &SBT,
3192+
uint32_t Width, uint32_t Height,
3193+
uint32_t Depth) {
3194+
if (!llvm::isa<MTLRayTracingPipelineState>(&PSO))
3195+
return llvm::createStringError(
3196+
std::errc::invalid_argument,
3197+
"dispatchRays requires a RayTracing PipelineState.");
3198+
if (!llvm::isa<MTLShaderBindingTable>(&SBT))
3199+
return llvm::createStringError(
3200+
std::errc::invalid_argument,
3201+
"dispatchRays requires a Metal ShaderBindingTable.");
3202+
const auto &RTPSO = llvm::cast<MTLRayTracingPipelineState>(PSO);
3203+
const auto &MTLSBT = llvm::cast<MTLShaderBindingTable>(SBT);
3204+
if (!RTPSO.ComputePipeline)
3205+
return llvm::createStringError(
3206+
std::errc::invalid_argument,
3207+
"RayTracing PipelineState has no compute pipeline state.");
3208+
if (auto Err = ensureComputeEncoder())
3209+
return Err;
3210+
flushBarrier();
3211+
insertDebugSignpost(
3212+
llvm::formatv("DispatchRays [{0},{1},{2}]", Width, Height, Depth).str());
3213+
3214+
// Per-dispatch ray arguments, consumed at kIRRayDispatchArgumentsBindPoint.
3215+
IRDispatchRaysArgument Args{};
3216+
Args.DispatchRaysDesc.RayGenerationShaderRecord = MTLSBT.RayGenRegion;
3217+
Args.DispatchRaysDesc.MissShaderTable = MTLSBT.MissRegion;
3218+
Args.DispatchRaysDesc.HitGroupTable = MTLSBT.HitGroupRegion;
3219+
Args.DispatchRaysDesc.CallableShaderTable = MTLSBT.CallableRegion;
3220+
Args.DispatchRaysDesc.Width = Width;
3221+
Args.DispatchRaysDesc.Height = Height;
3222+
Args.DispatchRaysDesc.Depth = Depth;
3223+
Args.GRS = RTPSO.ArgBuffer->getGPUAddress();
3224+
Args.ResDescHeap =
3225+
BoundResourceHeap
3226+
? BoundResourceHeap->getGPUDescriptorHandleForHeapStart().Ptr
3227+
: 0;
3228+
Args.SmpDescHeap = 0;
3229+
Args.VisibleFunctionTable =
3230+
RTPSO.VFT ? RTPSO.VFT->gpuResourceID() : MTL::ResourceID{0};
3231+
Args.IntersectionFunctionTable =
3232+
RTPSO.IFT ? RTPSO.IFT->gpuResourceID() : MTL::ResourceID{0};
3233+
Args.IntersectionFunctionTables = 0;
3234+
3235+
const BufferCreateDesc ArgsBufDesc = BufferCreateDesc::uploadBuffer();
3236+
auto ArgsBufOrErr = offloadtest::createBufferWithData(
3237+
*CB->Dev, "MTL Dispatch Rays Arguments", ArgsBufDesc, &Args,
3238+
sizeof(IRDispatchRaysArgument), nullptr, nullptr);
3239+
if (!ArgsBufOrErr)
3240+
return ArgsBufOrErr.takeError();
3241+
auto *MTLArgsBuf = llvm::cast<MTLBuffer>(ArgsBufOrErr->get());
3242+
CB->KeepAliveOwned.push_back(std::move(*ArgsBufOrErr));
3243+
3244+
ComputeEnc->setBuffer(MTLArgsBuf->Buf, 0, kIRRayDispatchArgumentsBindPoint);
3245+
ComputeEnc->useResource(MTLArgsBuf->Buf, MTL::ResourceUsageRead);
3246+
3247+
if (MTLSBT.Buffer)
3248+
ComputeEnc->useResource(MTLSBT.Buffer, MTL::ResourceUsageRead);
3249+
if (RTPSO.VFT)
3250+
ComputeEnc->useResource(RTPSO.VFT, MTL::ResourceUsageRead);
3251+
if (RTPSO.IFT)
3252+
ComputeEnc->useResource(RTPSO.IFT, MTL::ResourceUsageRead);
3253+
3254+
ComputeEnc->setComputePipelineState(RTPSO.ComputePipeline);
3255+
// dispatchThreads automatically takes care of bounds checking.
3256+
const MTL::Size GridSize(Width, Height, Depth);
3257+
ComputeEnc->dispatchThreads(GridSize, RTPSO.ThreadsPerGroup);
3258+
addBarrierScope(MTL::BarrierScopeBuffers | MTL::BarrierScopeTextures);
3259+
return llvm::Error::success();
3260+
}
33063261
} // namespace
33073262

33083263
llvm::Error offloadtest::initializeMetalDevices(

0 commit comments

Comments
 (0)