summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--source/slang/slang-emit.cpp23
-rw-r--r--source/slang/slang-ir-insts.h3
-rw-r--r--source/slang/slang-ir-wrap-structured-buffers.cpp354
-rw-r--r--source/slang/slang-ir-wrap-structured-buffers.h15
-rw-r--r--source/slang/slang-ir.cpp7
-rw-r--r--source/slang/slang.vcxproj4
-rw-r--r--source/slang/slang.vcxproj.filters8
-rw-r--r--tests/compute/matrix-layout-structured-buffer.slang17
-rw-r--r--tests/compute/matrix-layout-structured-buffer.slang.1.expected.txt12
-rw-r--r--tests/compute/matrix-layout-structured-buffer.slang.2.expected.txt12
-rw-r--r--tests/compute/matrix-layout-structured-buffer.slang.3.expected.txt12
-rw-r--r--tests/compute/matrix-layout-structured-buffer.slang.expected.txt24
-rw-r--r--tests/compute/structured-buffer-of-matrices.slang41
-rw-r--r--tests/compute/structured-buffer-of-matrices.slang.expected.txt4
14 files changed, 509 insertions, 27 deletions
diff --git a/source/slang/slang-emit.cpp b/source/slang/slang-emit.cpp
index 2a216994b..6e3ce9c56 100644
--- a/source/slang/slang-emit.cpp
+++ b/source/slang/slang-emit.cpp
@@ -15,6 +15,7 @@
#include "slang-ir-ssa.h"
#include "slang-ir-union.h"
#include "slang-ir-validate.h"
+#include "slang-ir-wrap-structured-buffers.h"
#include "slang-legalize-types.h"
#include "slang-lower-to-ir.h"
#include "slang-mangle.h"
@@ -384,6 +385,28 @@ Result linkAndOptimizeIR(
#endif
validateIRModuleIfEnabled(compileRequest, irModule);
+ // For HLSL (and fxc/dxc) only, we need to "wrap" any
+ // structured buffers defined over matrix types so
+ // that they instead use an intermediate `struct`.
+ // This is required to get those targets to respect
+ // the options for matrix layout set via `#pragma`
+ // or command-line options.
+ //
+ switch(target)
+ {
+ case CodeGenTarget::HLSL:
+ {
+ wrapStructuredBuffersOfMatrices(irModule);
+#if 0
+ dumpIRIfEnabled(compileRequest, irModule, "STRUCTURED BUFFERS WRAPPED");
+#endif
+ validateIRModuleIfEnabled(compileRequest, irModule);
+ }
+ break;
+
+ default:
+ break;
+ }
// For GLSL only, we will need to perform "legalization" of
// the entry point and any entry-point parameters.
diff --git a/source/slang/slang-ir-insts.h b/source/slang/slang-ir-insts.h
index 11fdad50d..26b86107b 100644
--- a/source/slang/slang-ir-insts.h
+++ b/source/slang/slang-ir-insts.h
@@ -1770,6 +1770,9 @@ struct IRBuilder
IRInst* const* operands);
IRType* getType(
IROp op);
+ IRType* getType(
+ IROp op,
+ IRInst* operand0);
/// Create an empty basic block.
///
diff --git a/source/slang/slang-ir-wrap-structured-buffers.cpp b/source/slang/slang-ir-wrap-structured-buffers.cpp
new file mode 100644
index 000000000..6b1bc5f2f
--- /dev/null
+++ b/source/slang/slang-ir-wrap-structured-buffers.cpp
@@ -0,0 +1,354 @@
+// slang-ir-wrap-structured-buffers.cpp
+#include "slang-ir-wrap-structured-buffers.h"
+
+#include "slang-ir.h"
+#include "slang-ir-insts.h"
+
+namespace Slang
+{
+
+// Suppose a programmer wrote HLSL/Slang code like this:
+//
+// StructuredBuffer<float4x4> gBuffer;
+//
+// ... gBuffer[index] ...
+// ... gBuffer.Load(index) ...
+//
+// Further suppose that they specified that row-major
+// matrix layout should be used. It might surprise such
+// a user that the fxc and dxc compilers both use
+// column-major layout for the matrices in `gBuffer`,
+// and there is no way to change that fact.
+//
+// In contrast, we want Slang to respect the matrix layout
+// setting given by the user in all cases, so we need to
+// find a way to force fxc and dxc to do what we want.
+//
+// Fortunately, fxc and dxc *do* respect the row-major
+// layout setting for code like the following:
+//
+// struct Wrapper { float4x4 wrapped; }
+// StructuredBuffer<Wrapper> gBuffer;
+//
+// ... gBuffer[index].wrapped ...
+// ... gBuffer.Load(index).wrapped ...
+//
+// The role of the pass in this file is to transform IR
+// for code like the first case into IR for code like the
+// second case.
+//
+// The main thing that makes this pass challenging is
+// recognizing calls to `StructuredBuffer<T>` builtins
+// so that we can transform them to append the `.wrapped`
+// field reference.
+
+// As is typical, we will wrap up our IR pass in a
+// "context" structure type.
+//
+struct WrapStructuredBuffersContext
+{
+ IRModule* m_module = nullptr;
+
+ // We process a module by processing all its instructions, recursively.
+ //
+ void processModule()
+ {
+ processInstRec(m_module->moduleInst);
+ }
+
+ void processInstRec(IRInst* inst)
+ {
+ processInst(inst);
+
+ for(auto child : inst->getChildren())
+ processInstRec(child);
+ }
+
+ void processInst(IRInst* inst)
+ {
+ // We will frame our processing as a search for types
+ // of the form `*StructuredBuffer<matrixType>` where
+ // `matrixType` is any matrix type.
+ //
+ // If the instruction we are looking at doesn't have
+ // the right form, then we will skip it.
+ //
+ auto oldStructuredBufferType = as<IRHLSLStructuredBufferTypeBase>(inst);
+ if(!oldStructuredBufferType)
+ return;
+ auto matrixType = as<IRMatrixType>(oldStructuredBufferType->getElementType());
+ if(!matrixType)
+ return;
+
+ // Having found a `*StructuredBuffer<M>` we will now
+ // need an IR builder to help us construct the wrapper code.
+ //
+ SharedIRBuilder sharedBuilderStorage;
+ auto sharedBuilder = &sharedBuilderStorage;
+ sharedBuilder->module = m_module;
+ sharedBuilder->session = m_module->getSession();
+ IRBuilder builderStorage;
+ auto builder = &builderStorage;
+ builder->sharedBuilder = sharedBuilder;
+
+ // We begin by constructing a structure type that wraps
+ // our `matrixType`, into something like:
+ //
+ // struct Wrapper { matrixType wrapped; }
+ //
+ builder->setInsertBefore(oldStructuredBufferType);
+ auto wrappedFieldKey = getWrappedFieldKey(builder);
+ auto wrapperStruct = getWrapperStruct(builder, matrixType);
+
+ // Now that we have our wrapper struct, we can construct a type
+ // that is `*StructuredBuffer<wrapperStruct>` and use it to
+ // replace `*StructuredBuffer<matrixType>`
+ //
+ // Note: we are constructing a *new* type instead of modifying
+ // the old one in-place because eventually when we do deduplication
+ // of types/constants more consistently it might cause problems
+ // to modify a tyep in a way that changes its hash code.
+ //
+ // TODO: the above statement is still slippery, since we are still
+ // replacing one type A with another type B globally, and doing
+ // so could affect any type that in turn referenced A...
+ //
+ auto newStructuredBufferType = builder->getType(oldStructuredBufferType->op, wrapperStruct);
+ oldStructuredBufferType->replaceUsesWith(newStructuredBufferType);
+
+ // Any values that used our old structured bufer type
+ // now have the new structured buffer type, but that
+ // means that operations on them might be getting
+ // applied incorrectly.
+ //
+ // For example, if we had a call like:
+ //
+ // float4x4 m = gBuffer.Load(someIndex);
+ //
+ // the result of the `Load` call is now `wrapperStruct`
+ // and we can't assign that to a matrix-type variable.
+ //
+ // we need to invetigate values of our structured
+ // buffer type, and then investigate operations that
+ // are using those values, to see if we can find the
+ // ones we need to rewrite.
+ //
+ // We can find values of `newStructuredBufferType` by
+ // scanning through its IR uses, since values of that
+ // type are using it as a (type) operand.
+ //
+ for( auto typeUse = newStructuredBufferType->firstUse; typeUse; typeUse = typeUse->nextUse )
+ {
+ // There might be uses of `newStructuredBufferType` where
+ // it isn't being used as the type of a value, so we
+ // start by weeding out the ones we don't care about.
+ //
+ auto valueOfStructuredBufferType = typeUse->getUser();
+ if(valueOfStructuredBufferType->getFullType() != newStructuredBufferType)
+ continue;
+
+ // Now we have some `valueOfStructuredBufferType`. In our running
+ // example, this might be `gBuffer`, which is an `IRGlobalParam`.
+ //
+ // We don't need to change anything about `gBuffer` itself, since
+ // replacing `oldStructuredBufferType` with `newStructuredBufferType`
+ // already replaced the type of `gBuffer`.
+ //
+ // Instead, we want to look for instructions that *use* the buffer,
+ // because these could be calls to intrinsic functions like
+ // `RWStructuredBuffer.Load`
+ //
+ for( auto valueUse = valueOfStructuredBufferType->firstUse; valueUse; valueUse = valueUse->nextUse )
+ {
+ // we are only interested in instructions that are calls,
+ // with at least one argument, where the first argument
+ // is our `valueOfStructuredBufferType`. These
+ // are calls that could potentially be intrinsic
+ // operations on `*StructuredBuffer`.
+ //
+ auto call = as<IRCall>(valueUse->getUser());
+ if(!call)
+ continue;
+ if(call->getArgCount() == 0)
+ continue;
+ if(call->getArg(0) != valueOfStructuredBufferType)
+ continue;
+
+ // At this point we have a candidate `call` instruction,
+ // but we need to determine whether it is a call to
+ // one of the `*StructuredBuffer` intrinsics that we want
+ // to rewrite, or if it is another user-defined function
+ // that we should leave along (even if that user-defined
+ // function happens to return a matrix).
+ //
+ // For now we will do this in a somewhat ad-hoc fashion.
+ // We know that the `Load` and `operator[]` operations
+ // on `*StructuredBuffer` are generic, and unlike user-defined
+ // generic functions they will not have been specialized
+ // before we get here.
+ //
+ // We will thus use the fact that the callee of the call
+ // is a `specialize` instruction to let us know that it
+ // is an intrinsic, and thus should be one of the functions
+ // we care about.
+ //
+ // TODO: Figure out if there is a more robust way to make
+ // this check. It is possible that structured buffer
+ // access should be modeled with explicit IR opcodes
+ // rather than just as builtin functions.
+ //
+ auto callee = call->getCallee();
+ if(!as<IRSpecialize>(callee))
+ continue;
+
+ // At this point it seems likely we have one of the calls
+ // we want to rewrite, but there are still intrinsics
+ // like `GetDimensions` that we want to leave alone.
+ //
+ // For now we will look at the return type of the call,
+ // where we care about two cases.
+ //
+ builder->setInsertBefore(call->getNextInst());
+ auto oldResultType = call->getDataType();
+
+ // First we care about the case for `Load`, which
+ // will return the element type, which would be
+ // a matrix type.
+ //
+ if( as<IRMatrixType>(oldResultType) )
+ {
+ // We know that the call to `Load` should now
+ // return our wrapper struct type, so we will
+ // go ahead and modify its type to be correct.
+ //
+ auto newResultType = wrapperStruct;
+ builder->setDataType(call, newResultType);
+
+ // Next, we need to make sure to extract the
+ // field from the wrapper struct, so that
+ // we get back to a value of the expected
+ // type.
+ //
+ // This logic takes something like:
+ //
+ // WrapperStruct call = gBuffer.Load(index);
+ //
+ // and follows it with:
+ //
+ // float4x4 newVal = call.wrapped;
+ //
+ auto newVal = builder->emitFieldExtract(oldResultType, call, wrappedFieldKey);
+
+ // Any code that used the value of `call` should
+ // now use `newVal` instead...
+ //
+ call->replaceUsesWith(newVal);
+ //
+ // ... except for one important gotcha, which is
+ // that `newVal` itself used `call`, and replacing
+ // `call` with `newVal` results in `newVal` using
+ // itself as one of its operands.
+ //
+ // It is a bit of a kludge, but we fix the situation
+ // by just setting the appropriate operand again.
+ //
+ // TODO: it might be helpful to have a variant
+ // of `replaceUsesWith` that can handle cases like
+ // this.
+ //
+ newVal->setOperand(0, call);
+ }
+ //
+ // The second interesting case is the `ref` accessor
+ // in `operator[]` for a `RWStructuredBuffer`, which
+ // at the IR level returns a *pointer* to the buffer
+ // element type.
+ //
+ else if(auto oldPtrType = as<IRPtrTypeBase>(oldResultType))
+ {
+ auto pointeeType = oldPtrType->getValueType();
+ if( as<IRMatrixType>(pointeeType) )
+ {
+ // At this point we know that the intrinsic
+ // operation returned a pointer to a matrix,
+ // which seems like a good indications that
+ // it is our `operator[]` and it should now
+ // return a pointer to the wrapper struct
+ // instead.
+ //
+ // The logic here is almost identical to the
+ // non-pointer case above, so please refer
+ // there if you want the comments.
+
+ auto newResultType = builder->getPtrType(oldPtrType->op, wrapperStruct);
+ builder->setDataType(call, newResultType);
+
+ auto newVal = builder->emitFieldAddress(oldResultType, call, wrappedFieldKey);
+ call->replaceUsesWith(newVal);
+ newVal->setOperand(0, call);
+ }
+ }
+ }
+ }
+ }
+
+ /// Get the struture field "key" to use for generated wrappers
+ IRStructKey* getWrappedFieldKey(IRBuilder* builder)
+ {
+ // We will re-use the same field key for all of the
+ // wrapper structs we might create, so that all of
+ // the new field access operations will use the same
+ // field name to make their purpose more clear.
+ //
+ // TODO: It might be useful to give the field key
+ // a name that is indicative of its purpose so
+ // that people won't be confused why their code
+ // has been transformed and now there is this
+ // `._S2` in the middle of their expressions.
+
+ if( !m_wrappedFieldKey )
+ {
+ m_wrappedFieldKey = builder->createStructKey();
+ }
+ return m_wrappedFieldKey;
+ }
+
+ /// Lazily created and cached field "key" to use for wrapper structs.
+ IRStructKey* m_wrappedFieldKey = nullptr;
+
+ /// Get the wrapper struct to use for a particular `matrixType`.
+ IRStructType* getWrapperStruct(IRBuilder* builder, IRMatrixType* matrixType)
+ {
+ // TODO: Because our type de-duplication isn't perfect right now,
+ // it is possible that there could be more than one equivalent
+ // matrix type, and thus more than one equivalent structured buffer
+ // type using that matrix type. As a result, if we just generate
+ // one new `struct` per IR structured-buffer type, we could end up
+ // with conflicts when a buffer with one IR type gets passed to
+ // a function with another (equivalent) IR type.
+ //
+ // The right fix here is to cache and look up these structure
+ // tyeps based on the scalar type (opcode) and row/column counts
+ // of the given `matrixType`.
+ //
+ // For now I will ignore this issue and hope that we can address
+ // the more fundamental issue of type deduplication before this
+ // choice comes back to bite me.
+
+ auto wrappedFieldKey = getWrappedFieldKey(builder);
+
+ auto wrapperStruct = builder->createStructType();
+ builder->createStructField(wrapperStruct, wrappedFieldKey, matrixType);
+ return wrapperStruct;
+ }
+};
+
+void wrapStructuredBuffersOfMatrices(
+ IRModule* module)
+{
+ WrapStructuredBuffersContext context;
+ context.m_module = module;
+ context.processModule();
+}
+
+}
diff --git a/source/slang/slang-ir-wrap-structured-buffers.h b/source/slang/slang-ir-wrap-structured-buffers.h
new file mode 100644
index 000000000..28ab4c4f1
--- /dev/null
+++ b/source/slang/slang-ir-wrap-structured-buffers.h
@@ -0,0 +1,15 @@
+// slang-ir-wrap-structured-buffers.h
+#pragma once
+
+namespace Slang
+{
+ struct IRModule;
+
+ /// Wrap structured buffer types of matrices for
+ /// targets that compute incorrect layouts for such
+ /// types.
+ ///
+ void wrapStructuredBuffersOfMatrices(
+ IRModule* module);
+}
+
diff --git a/source/slang/slang-ir.cpp b/source/slang/slang-ir.cpp
index a8f42c326..4f33e08ee 100644
--- a/source/slang/slang-ir.cpp
+++ b/source/slang/slang-ir.cpp
@@ -2120,6 +2120,13 @@ namespace Slang
return getType(op, 0, nullptr);
}
+ IRType* IRBuilder::getType(
+ IROp op,
+ IRInst* operand0)
+ {
+ return getType(op, 1, &operand0);
+ }
+
IRBasicType* IRBuilder::getBasicType(BaseType baseType)
{
return (IRBasicType*)getType(
diff --git a/source/slang/slang.vcxproj b/source/slang/slang.vcxproj
index de1108a1c..6c35986a2 100644
--- a/source/slang/slang.vcxproj
+++ b/source/slang/slang.vcxproj
@@ -234,6 +234,7 @@
<ClInclude Include="slang-ir-type-set.h" />
<ClInclude Include="slang-ir-union.h" />
<ClInclude Include="slang-ir-validate.h" />
+ <ClInclude Include="slang-ir-wrap-structured-buffers.h" />
<ClInclude Include="slang-ir.h" />
<ClInclude Include="slang-legalize-types.h" />
<ClInclude Include="slang-lexer.h" />
@@ -316,6 +317,7 @@
<ClCompile Include="slang-ir-type-set.cpp" />
<ClCompile Include="slang-ir-union.cpp" />
<ClCompile Include="slang-ir-validate.cpp" />
+ <ClCompile Include="slang-ir-wrap-structured-buffers.cpp" />
<ClCompile Include="slang-ir.cpp" />
<ClCompile Include="slang-legalize-types.cpp" />
<ClCompile Include="slang-lexer.cpp" />
@@ -379,4 +381,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
-</Project> \ No newline at end of file
+</Project> \ No newline at end of file
diff --git a/source/slang/slang.vcxproj.filters b/source/slang/slang.vcxproj.filters
index d5abb0289..75bf8abc0 100644
--- a/source/slang/slang.vcxproj.filters
+++ b/source/slang/slang.vcxproj.filters
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Header Files">
@@ -246,6 +246,9 @@
<ClInclude Include="slang-visitor.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="slang-ir-wrap-structured-buffers.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="slang-check-conformance.cpp">
@@ -461,6 +464,9 @@
<ClCompile Include="slang.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="slang-ir-wrap-structured-buffers.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="..\core\core.natvis">
diff --git a/tests/compute/matrix-layout-structured-buffer.slang b/tests/compute/matrix-layout-structured-buffer.slang
index 36596424d..bb7dbb381 100644
--- a/tests/compute/matrix-layout-structured-buffer.slang
+++ b/tests/compute/matrix-layout-structured-buffer.slang
@@ -1,18 +1,9 @@
// matrix-layout-structured-buffer.slang
-// This test is set up to confirm that `StructuredBuffer` types are
-// always laid out column-major by fxc/dxc, even when row-major layout has been
-// requested globally.
-//
-// This behavior should be considered a bug in Slang because either:
-//
-// 1. we should report reflection layout information that acknowledges this behavior, or
-// 2. we should alter our HLSL output passed to fxc/dxc to provide consistent
-// behavior that matches our reflection data.
-//
-// For now this test exists to document the situation. It's output can/should
-// be updated if we decide to fix the underlying problem by taking option (2).
-//
+// This test confirms that we apply the matrix layout
+// mode requested by the user, even in the case of structured
+// buffers of matrices, where fxc/dxc do *not* respect
+// the matrix layout mode by default.
//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -xslang -matrix-layout-row-major
//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -xslang -matrix-layout-column-major
diff --git a/tests/compute/matrix-layout-structured-buffer.slang.1.expected.txt b/tests/compute/matrix-layout-structured-buffer.slang.1.expected.txt
new file mode 100644
index 000000000..18d8be654
--- /dev/null
+++ b/tests/compute/matrix-layout-structured-buffer.slang.1.expected.txt
@@ -0,0 +1,12 @@
+80010300
+90111301
+88060B02
+95160C03
+81020404
+910F1405
+86070906
+96170D07
+82000508
+8F101209
+87080A0A
+97150E0B
diff --git a/tests/compute/matrix-layout-structured-buffer.slang.2.expected.txt b/tests/compute/matrix-layout-structured-buffer.slang.2.expected.txt
new file mode 100644
index 000000000..fd74697bb
--- /dev/null
+++ b/tests/compute/matrix-layout-structured-buffer.slang.2.expected.txt
@@ -0,0 +1,12 @@
+80040100
+91151201
+8A020B02
+8F130C03
+84080504
+950D1605
+82060306
+93171007
+88000908
+8D110E09
+860A070A
+970F140B \ No newline at end of file
diff --git a/tests/compute/matrix-layout-structured-buffer.slang.3.expected.txt b/tests/compute/matrix-layout-structured-buffer.slang.3.expected.txt
new file mode 100644
index 000000000..18d8be654
--- /dev/null
+++ b/tests/compute/matrix-layout-structured-buffer.slang.3.expected.txt
@@ -0,0 +1,12 @@
+80010300
+90111301
+88060B02
+95160C03
+81020404
+910F1405
+86070906
+96170D07
+82000508
+8F101209
+87080A0A
+97150E0B
diff --git a/tests/compute/matrix-layout-structured-buffer.slang.expected.txt b/tests/compute/matrix-layout-structured-buffer.slang.expected.txt
index 18d8be654..e0bbef746 100644
--- a/tests/compute/matrix-layout-structured-buffer.slang.expected.txt
+++ b/tests/compute/matrix-layout-structured-buffer.slang.expected.txt
@@ -1,12 +1,12 @@
-80010300
-90111301
-88060B02
-95160C03
-81020404
-910F1405
-86070906
-96170D07
-82000508
-8F101209
-87080A0A
-97150E0B
+80040100
+91151201
+8A020B02
+8F130C03
+84080504
+950D1605
+82060306
+93171007
+88000908
+8D110E09
+860A070A
+970F140B
diff --git a/tests/compute/structured-buffer-of-matrices.slang b/tests/compute/structured-buffer-of-matrices.slang
new file mode 100644
index 000000000..e87cf8c59
--- /dev/null
+++ b/tests/compute/structured-buffer-of-matrices.slang
@@ -0,0 +1,41 @@
+// structured-buffer-of-matrices.slang
+
+// This test confirms that we have implemented a workaround
+// for the issue where the Microsoft HLSL compilers (fxc and dxc)
+// ignore matrix layout settings (like `#pragma pack_matrx`)
+// in the case where a matrix type is used directly in a
+// structured buffer (e.g., `StructuredBuffer<float4x4>`),
+// and instead always use column-major layout.
+//
+// With our fix in place, this test should produce the same
+// output on all targets, correctly respecting the user's
+// request for row-major layout via command-line option.
+
+//TEST(compute):COMPARE_COMPUTE_EX:-cpu -compute -compile-arg -O3 -xslang -matrix-layout-row-major
+//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -xslang -matrix-layout-row-major
+//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -dx12 -xslang -matrix-layout-row-major
+//TEST(compute, vulkan):COMPARE_COMPUTE_EX:-vk -compute -xslang -matrix-layout-row-major
+
+// Note: we are using a buffer of floating-point matrices, but fill it with integer
+// data, to allow this test to work on the Vulkan targets, which do not currently
+// support integer matrices, because of GLSL limitations.
+
+//TEST_INPUT:ubuffer(data=[0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15], stride=64):name gInput
+RWStructuredBuffer<float4x4> gInput;
+
+
+//TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):out,name gOutput
+RWStructuredBuffer<int> gOutput;
+
+int test(int val)
+{
+ return asint(gInput[0][(val+1)&3][(val+3)&3]);
+}
+
+[numthreads(4, 1, 1)]
+void computeMain(uint3 tid : SV_DispatchThreadID)
+{
+ int value = tid.x;
+ value = test(value);
+ gOutput[tid.x] = value;
+} \ No newline at end of file
diff --git a/tests/compute/structured-buffer-of-matrices.slang.expected.txt b/tests/compute/structured-buffer-of-matrices.slang.expected.txt
new file mode 100644
index 000000000..1e59fb2f4
--- /dev/null
+++ b/tests/compute/structured-buffer-of-matrices.slang.expected.txt
@@ -0,0 +1,4 @@
+7
+8
+D
+2