Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 65 additions & 2 deletions tools/clang/unittests/HLSLExec/HlslExecTestUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -725,12 +725,15 @@ void addRootView(st::ShaderOp *Op, UINT Index, const char *ResName) {
std::shared_ptr<st::ShaderOpTestResult>
runShaderOp(ID3D12Device *Device, dxc::SpecificDllLoader &DxcSupport,
std::unique_ptr<st::ShaderOp> Op,
st::ShaderOpTest::TInitCallbackFn InitCallback) {
st::ShaderOpTest::TInitCallbackFn InitCallback,
st::ShaderOpTest::TCommandCallbackFn PostDispatchCallback) {
auto OpSet = std::make_shared<st::ShaderOpSet>();
OpSet->ShaderOps.push_back(std::move(Op));

return st::RunShaderOpTestAfterParse(
Device, DxcSupport, nullptr, std::move(InitCallback), std::move(OpSet));
Device, DxcSupport, nullptr, std::move(InitCallback),
/*pShaderCallback=*/nullptr, std::move(PostDispatchCallback),
std::move(OpSet));
}

void compileShader(dxc::SpecificDllLoader &DxcSupport, const char *Source,
Expand Down Expand Up @@ -796,3 +799,63 @@ void compileShader(dxc::SpecificDllLoader &DxcSupport, const char *Source,
VERIFY_SUCCEEDED(HR);
}
}

#if defined(DIRECT3D_LINEAR_ALGEBRA)
UINT getLinAlgMatrixByteSize(ID3D12Device *Device, UINT NumRows,
UINT NumColumns,
D3D12_LINEAR_ALGEBRA_DATATYPE DataType,
D3D12_LINEAR_ALGEBRA_MATRIX_LAYOUT Layout,
UINT Stride) {
CComPtr<ID3D12DevicePreview> DevicePreview;
VERIFY_SUCCEEDED(Device->QueryInterface(IID_PPV_ARGS(&DevicePreview)));

D3D12_LINEAR_ALGEBRA_MATRIX_CONVERSION_DEST_INFO Info = {};
Info.DestSize = 0;
Info.DestLayout = Layout;
Info.DestStride = Stride;
Info.NumRows = NumRows;
Info.NumColumns = NumColumns;
Info.DestDataType = DataType;
DevicePreview->GetLinearAlgebraMatrixConversionDestinationInfo(&Info);
return Info.DestSize;
}

void recordLinAlgMatrixConversion(
ID3D12GraphicsCommandList *List, ID3D12Resource *SrcBuffer, UINT SrcSize,
ID3D12Resource *DestBuffer, UINT DestSize, UINT NumRows, UINT NumColumns,
D3D12_LINEAR_ALGEBRA_DATATYPE DataType,
D3D12_LINEAR_ALGEBRA_MATRIX_LAYOUT SrcLayout, UINT SrcStride,
D3D12_LINEAR_ALGEBRA_MATRIX_LAYOUT DestLayout, UINT DestStride) {
CComPtr<ID3D12GraphicsCommandListPreview> PreviewList;
VERIFY_SUCCEEDED(List->QueryInterface(IID_PPV_ARGS(&PreviewList)));

// Per the linear-algebra spec, ConvertLinearAlgebraMatrix (legacy barriers)
// requires the source buffer in NON_PIXEL_SHADER_RESOURCE and the destination
// in UNORDERED_ACCESS. The caller passes both in UNORDERED_ACCESS (the
// ShaderOp default UAV state), so transition the source to the required read
// state; the destination is already in UNORDERED_ACCESS.
D3D12_RESOURCE_BARRIER Barrier = {};
Barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
Barrier.Transition.pResource = SrcBuffer;
Barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
Barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
Barrier.Transition.StateAfter =
D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE;
List->ResourceBarrier(1, &Barrier);

D3D12_LINEAR_ALGEBRA_MATRIX_CONVERSION_INFO Info = {};
Info.DestInfo.DestSize = DestSize;
Info.DestInfo.DestLayout = DestLayout;
Info.DestInfo.DestStride = DestStride;
Info.DestInfo.NumRows = NumRows;
Info.DestInfo.NumColumns = NumColumns;
Info.DestInfo.DestDataType = DataType;
Info.SrcInfo.SrcSize = SrcSize;
Info.SrcInfo.SrcDataType = DataType;
Info.SrcInfo.SrcLayout = SrcLayout;
Info.SrcInfo.SrcStride = SrcStride;
Info.DataDesc.DestVA = DestBuffer->GetGPUVirtualAddress();
Info.DataDesc.SrcVA = SrcBuffer->GetGPUVirtualAddress();
PreviewList->ConvertLinearAlgebraMatrix(&Info, 1);
}
#endif // defined(DIRECT3D_LINEAR_ALGEBRA)
40 changes: 36 additions & 4 deletions tools/clang/unittests/HLSLExec/HlslExecTestUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,15 +96,47 @@ void addSRVBuffer(st::ShaderOp *Op, const char *Name, UINT64 Width,
void addRootView(st::ShaderOp *Op, UINT Index, const char *ResName);

/// Run a programmatically-built ShaderOp and return the result.
std::shared_ptr<st::ShaderOpTestResult>
runShaderOp(ID3D12Device *Device, dxc::SpecificDllLoader &DxcSupport,
std::unique_ptr<st::ShaderOp> Op,
st::ShaderOpTest::TInitCallbackFn InitCallback = nullptr);
std::shared_ptr<st::ShaderOpTestResult> runShaderOp(
ID3D12Device *Device, dxc::SpecificDllLoader &DxcSupport,
std::unique_ptr<st::ShaderOp> Op,
st::ShaderOpTest::TInitCallbackFn InitCallback = nullptr,
st::ShaderOpTest::TCommandCallbackFn PostDispatchCallback = nullptr);

/// Compiles an HLSL shader using the DXC API to verify it is well-formed.
/// Fails the test on compile error.
void compileShader(dxc::SpecificDllLoader &DxcSupport, const char *Source,
const char *Target, const std::string &Args,
bool VerboseLogging = false);

// Host-side linear-algebra matrix-conversion helpers. These need the D3D12
// linear-algebra API (the D3D12_LINEAR_ALGEBRA_* types and the conversion
// methods on ID3D12DevicePreview / ID3D12GraphicsCommandListPreview), which is
// gated behind the preview SDK's DIRECT3D_LINEAR_ALGEBRA feature macro. When
// absent, these helpers and the tests using them are compiled out (they Skip at
// runtime).
#if defined(DIRECT3D_LINEAR_ALGEBRA)
/// Query the number of bytes required to store an NumRows x NumColumns matrix
/// of the given datatype in the specified device layout.
UINT getLinAlgMatrixByteSize(ID3D12Device *Device, UINT NumRows,
UINT NumColumns,
D3D12_LINEAR_ALGEBRA_DATATYPE DataType,
D3D12_LINEAR_ALGEBRA_MATRIX_LAYOUT Layout,
UINT Stride);

/// Record a GPU matrix layout conversion onto \p List using
/// ID3D12GraphicsCommandListPreview::ConvertLinearAlgebraMatrix. Both
/// \p SrcBuffer (in \p SrcLayout) and \p DestBuffer (receiving \p DestLayout)
/// must be passed in the D3D12_RESOURCE_STATE_UNORDERED_ACCESS state; the
/// conversion requires the source in NON_PIXEL_SHADER_RESOURCE, so this helper
/// transitions it and leaves the destination in UNORDERED_ACCESS. The caller is
/// responsible for ensuring that writes to \p SrcBuffer have completed before
/// this conversion reads it.
void recordLinAlgMatrixConversion(
ID3D12GraphicsCommandList *List, ID3D12Resource *SrcBuffer, UINT SrcSize,
ID3D12Resource *DestBuffer, UINT DestSize, UINT NumRows, UINT NumColumns,
D3D12_LINEAR_ALGEBRA_DATATYPE DataType,
D3D12_LINEAR_ALGEBRA_MATRIX_LAYOUT SrcLayout, UINT SrcStride,
D3D12_LINEAR_ALGEBRA_MATRIX_LAYOUT DestLayout, UINT DestStride);
#endif // defined(DIRECT3D_LINEAR_ALGEBRA)

#endif // HLSLEXECTESTUTILS_H
75 changes: 66 additions & 9 deletions tools/clang/unittests/HLSLExec/LinAlgTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -349,9 +349,7 @@ class DxilConf_SM610_LinAlg {
// Matrix Vector Arithmetic
TEST_METHOD(MatVecMul_Thread_16x16_F16);
TEST_METHOD(MatVecMulAdd_Thread_16x16_F16);
#if 0
TEST_METHOD(OuterProduct_Thread_16x16_F16);
#endif

// Query Accumulator Layout
TEST_METHOD(QueryAccumLayout);
Expand Down Expand Up @@ -1321,7 +1319,29 @@ void DxilConf_SM610_LinAlg::MatVecMulAdd_Thread_16x16_F16() {
ComponentType::F16);
}

#if 0
// Map a DXIL ComponentType to the D3D12 linear-algebra datatype used by the
// host-side matrix conversion API.
#if defined(DIRECT3D_LINEAR_ALGEBRA)
static D3D12_LINEAR_ALGEBRA_DATATYPE toLinAlgDataType(ComponentType CT) {
switch (CT) {
case ComponentType::F16:
return D3D12_LINEAR_ALGEBRA_DATATYPE_FLOAT16;
case ComponentType::F32:
return D3D12_LINEAR_ALGEBRA_DATATYPE_FLOAT32;
case ComponentType::I16:
return D3D12_LINEAR_ALGEBRA_DATATYPE_SINT16;
case ComponentType::U16:
return D3D12_LINEAR_ALGEBRA_DATATYPE_UINT16;
case ComponentType::I32:
return D3D12_LINEAR_ALGEBRA_DATATYPE_SINT32;
case ComponentType::U32:
return D3D12_LINEAR_ALGEBRA_DATATYPE_UINT32;
default:
VERIFY_IS_TRUE(false, "Unsupported component type for linalg conversion");
return D3D12_LINEAR_ALGEBRA_DATATYPE_FLOAT16;
}
}

static const char OuterProductShader[] = R"(
#define USE_A 0
#define SCOPE_THREAD 0
Expand All @@ -1348,8 +1368,11 @@ static const char OuterProductShader[] = R"(
Mat;
__builtin_LinAlg_MatrixOuterProduct(Mat, VecA, VecB);

// Outer product accumulators are stored in the OuterProductOptimal layout
// with stride 0 and no alignment requirement (align 0), matching the
// dx::linalg header's thread-scoped InterlockedAccumulate.
__builtin_LinAlg_MatrixAccumulateToDescriptor(
Mat, Output, 0, STRIDE, LAYOUT, 128);
Mat, Output, 0, STRIDE, LAYOUT, 0);
}
)";

Expand All @@ -1359,7 +1382,18 @@ static void runOuterProduct(ID3D12Device *Device,
const size_t NumVecElements = Params.M + Params.N;
const size_t InBuffSize = NumVecElements * elementSize(Params.CompType);
const size_t NumMatElements = Params.totalElements();
const size_t OutBufferSize = Params.totalBytes();
const D3D12_LINEAR_ALGEBRA_DATATYPE DataType =
toLinAlgDataType(Params.CompType);

const UINT OutBufferSize = getLinAlgMatrixByteSize(
Device, Params.M, Params.N, DataType,
D3D12_LINEAR_ALGEBRA_MATRIX_LAYOUT_OUTER_PRODUCT_OPTIMAL, /*Stride=*/0);

const UINT RowMajorStride =
static_cast<UINT>(Params.N * elementSize(Params.CompType));
const UINT RowMajorSize = getLinAlgMatrixByteSize(
Device, Params.M, Params.N, DataType,
D3D12_LINEAR_ALGEBRA_MATRIX_LAYOUT_ROW_MAJOR, RowMajorStride);

std::string Args = buildCompilerArgs(Params);

Expand All @@ -1371,7 +1405,8 @@ static void runOuterProduct(ID3D12Device *Device,
auto Op = createComputeOp(OuterProductShader, "cs_6_10", "UAV(u0), UAV(u1)",
Args.c_str());
addUAVBuffer(Op.get(), "Input", InBuffSize, false, "byname");
addUAVBuffer(Op.get(), "Output", OutBufferSize, true);
addUAVBuffer(Op.get(), "Output", OutBufferSize, /*ReadBack=*/false);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I'm wrong, but I see you using GetResource on the "Output" buffer, in the code below. Doesn't that mean that this ReadBack bool should remain true?

@Icohedron Icohedron Jul 7, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "Output" buffer is not being read by the CPU, so the ReadBack bool is false. The "Output" buffer is also in the opaque outer-product-optimal layout, so we shouldn't even try to interpret it without first converting it to a layout we do know how to read.

The GetResource just obtains a handle to be able to refer to the "Output" buffer so that we can record a ConvertLinearAlgebraMatrix command into the command list to tell the GPU to convert it into row-major order and store it into "OutputRowMajor", which the CPU does read back.

addUAVBuffer(Op.get(), "OutputRowMajor", RowMajorSize, /*ReadBack=*/true);
addRootView(Op.get(), 0, "Input");
addRootView(Op.get(), 1, "Output");

Expand All @@ -1383,27 +1418,49 @@ static void runOuterProduct(ID3D12Device *Device,
NumVecElements,
/*StartingVal=*/2, /*Increment=*/false),
"Saw unsupported component type");
},
[OutBufferSize, RowMajorSize, RowMajorStride, DataType,
Params](ID3D12GraphicsCommandList *List, st::ShaderOpTest *Test) {
ID3D12Resource *OptimalBuffer = nullptr;
ID3D12Resource *RowMajorBuffer = nullptr;
Test->GetResource("Output", &OptimalBuffer);
Test->GetResource("OutputRowMajor", &RowMajorBuffer);
recordLinAlgMatrixConversion(
List, OptimalBuffer, OutBufferSize, RowMajorBuffer, RowMajorSize,
Params.M, Params.N, DataType,
D3D12_LINEAR_ALGEBRA_MATRIX_LAYOUT_OUTER_PRODUCT_OPTIMAL,
/*SrcStride=*/0, D3D12_LINEAR_ALGEBRA_MATRIX_LAYOUT_ROW_MAJOR,
RowMajorStride);
});

MappedData OutData;
Result->Test->GetReadBackData("Output", &OutData);
Result->Test->GetReadBackData("OutputRowMajor", &OutData);

VERIFY_IS_TRUE(verifyComponentBuffer(Params.CompType, OutData.data(),
Expected, NumMatElements, Verbose));
}
#endif // defined(DIRECT3D_LINEAR_ALGEBRA)

void DxilConf_SM610_LinAlg::OuterProduct_Thread_16x16_F16() {
#if defined(DIRECT3D_LINEAR_ALGEBRA)
MatrixParams Params = {};
Params.CompType = ComponentType::F16;
Params.M = 16;
Params.N = 16;
Params.Scope = MatrixScope::Thread;
Params.Layout = LinalgMatrixLayout::RowMajor;
Params.Layout = LinalgMatrixLayout::OuterProductOptimal;
Params.NumThreads = 1;
Params.Enable16Bit = true;
runOuterProduct(D3DDevice, DxcSupport, Params, VerboseLogging);
#else
WEX::Logging::Log::Comment(
L"Skipping OuterProduct_Thread_16x16_F16: built against a D3D12 SDK "
L"without the linear-algebra matrix-conversion API "
L"(DIRECT3D_LINEAR_ALGEBRA undefined); the host-side conversion helpers "
L"are compiled out.");
WEX::Logging::Log::Result(WEX::Logging::TestResults::Skipped);
#endif // defined(DIRECT3D_LINEAR_ALGEBRA)
}
#endif

static const char QueryAccumLayoutShader[] = R"(
RWByteAddressBuffer Output : register(u0);
Expand Down
38 changes: 31 additions & 7 deletions tools/clang/unittests/HLSLExec/ShaderOpTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -928,6 +928,13 @@ void ShaderOpTest::GetReadBackData(LPCSTR pResourceName, MappedData *pData) {
pData->reset(D.ReadBack, sizeInBytes);
}

void ShaderOpTest::GetResource(LPCSTR pResourceName,
ID3D12Resource **ppResource) {
pResourceName = m_pShaderOp->Strings.insert(pResourceName); // Unique
auto It = m_ResourceData.find(pResourceName);
*ppResource = It == m_ResourceData.end() ? nullptr : It->second.Resource.p;
}

static void SetDescriptorHeaps(ID3D12GraphicsCommandList *pList,
std::vector<ID3D12DescriptorHeap *> &heaps) {
if (heaps.empty())
Expand All @@ -951,6 +958,8 @@ void ShaderOpTest::RunCommandList() {
SetRootValues(pList, m_pShaderOp->IsCompute());
pList->Dispatch(m_pShaderOp->DispatchX, m_pShaderOp->DispatchY,
m_pShaderOp->DispatchZ);
if (m_PostDispatchCallbackFn)
m_PostDispatchCallbackFn(pList, this);
} else {
pList->SetPipelineState(m_pPSO);
SetDescriptorHeaps(pList, m_DescriptorHeaps);
Expand Down Expand Up @@ -1150,6 +1159,10 @@ void ShaderOpTest::SetInitCallback(TInitCallbackFn InitCallbackFn) {
void ShaderOpTest::SetShaderCallback(TShaderCallbackFn ShaderCallbackFn) {
m_ShaderCallbackFn = ShaderCallbackFn;
}
void ShaderOpTest::SetPostDispatchCallback(
TCommandCallbackFn PostDispatchCallbackFn) {
m_PostDispatchCallbackFn = PostDispatchCallbackFn;
}

void ShaderOpTest::SetupRenderTarget(ShaderOp *pShaderOp, ID3D12Device *pDevice,
ID3D12CommandQueue *pCommandQueue,
Expand Down Expand Up @@ -2750,12 +2763,12 @@ bool ShaderOpParser::ReadAtElementName(IXmlReader *pReader, LPCWSTR pName) {
}
}

std::shared_ptr<ShaderOpTestResult>
RunShaderOpTestAfterParse(ID3D12Device *pDevice,
dxc::SpecificDllLoader &support, LPCSTR pName,
st::ShaderOpTest::TInitCallbackFn pInitCallback,
st::ShaderOpTest::TShaderCallbackFn pShaderCallback,
std::shared_ptr<st::ShaderOpSet> ShaderOpSet) {
std::shared_ptr<ShaderOpTestResult> RunShaderOpTestAfterParse(
ID3D12Device *pDevice, dxc::SpecificDllLoader &support, LPCSTR pName,
st::ShaderOpTest::TInitCallbackFn pInitCallback,
st::ShaderOpTest::TShaderCallbackFn pShaderCallback,
st::ShaderOpTest::TCommandCallbackFn pPostDispatchCallback,
std::shared_ptr<st::ShaderOpSet> ShaderOpSet) {
st::ShaderOp *pShaderOp;
if (pName == nullptr) {
if (ShaderOpSet->ShaderOps.size() != 1) {
Expand Down Expand Up @@ -2786,6 +2799,7 @@ RunShaderOpTestAfterParse(ID3D12Device *pDevice,
test->SetSpecificDllLoader(&support);
test->SetInitCallback(pInitCallback);
test->SetShaderCallback(pShaderCallback);
test->SetPostDispatchCallback(pPostDispatchCallback);
test->SetDevice(pDevice);
test->RunShaderOp(pShaderOp);

Expand All @@ -2797,13 +2811,23 @@ RunShaderOpTestAfterParse(ID3D12Device *pDevice,
return result;
}

std::shared_ptr<ShaderOpTestResult>
RunShaderOpTestAfterParse(ID3D12Device *pDevice,
dxc::SpecificDllLoader &support, LPCSTR pName,
st::ShaderOpTest::TInitCallbackFn pInitCallback,
st::ShaderOpTest::TShaderCallbackFn pShaderCallback,
std::shared_ptr<st::ShaderOpSet> ShaderOpSet) {
return RunShaderOpTestAfterParse(pDevice, support, pName, pInitCallback,
pShaderCallback, nullptr, ShaderOpSet);
}

std::shared_ptr<ShaderOpTestResult>
RunShaderOpTestAfterParse(ID3D12Device *pDevice,
dxc::SpecificDllLoader &support, LPCSTR pName,
st::ShaderOpTest::TInitCallbackFn pInitCallback,
std::shared_ptr<st::ShaderOpSet> ShaderOpSet) {
return RunShaderOpTestAfterParse(pDevice, support, pName, pInitCallback,
nullptr, ShaderOpSet);
nullptr, nullptr, ShaderOpSet);
}

std::shared_ptr<ShaderOpTestResult>
Expand Down
Loading
Loading