summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/user-guide/03-convenience-features.md60
-rw-r--r--docs/user-guide/toc.html1
-rw-r--r--source/slang/core.meta.slang44
-rw-r--r--source/slang/slang-check-decl.cpp2
-rw-r--r--source/slang/slang-check-expr.cpp4
-rw-r--r--source/slang/slang-diagnostic-defs.h2
-rw-r--r--source/slang/slang-emit.cpp3
-rw-r--r--source/slang/slang-ir-inst-defs.h27
-rw-r--r--source/slang/slang-ir-insts.h22
-rw-r--r--source/slang/slang-ir-legalize-empty-array.cpp196
-rw-r--r--source/slang/slang-ir-legalize-empty-array.h11
-rw-r--r--tests/diagnostics/array-neg-size.slang (renamed from tests/diagnostics/array-zero-size.slang)2
-rw-r--r--tests/language-feature/0-array-1.slang31
-rw-r--r--tests/language-feature/0-array.slang64
-rw-r--r--tests/language-feature/cond-field.slang64
15 files changed, 511 insertions, 22 deletions
diff --git a/docs/user-guide/03-convenience-features.md b/docs/user-guide/03-convenience-features.md
index f49fdce90..3f659015b 100644
--- a/docs/user-guide/03-convenience-features.md
+++ b/docs/user-guide/03-convenience-features.md
@@ -446,9 +446,65 @@ int caller()
}
```
+## `Conditional<T, bool condition>` Type
+
+A `Conditional` type can be used to define struct fields that can be specialized away. If `condition` is `false`, the field will be removed
+by the compiler from the target code. This is useful for scenarios where a developer would like to make sure a field is not defined in a
+specialized shader variant when it is not used by the shader.
+
+For example, a common use case is to define the vertex shader output / fragment shader input:
+
+```slang
+interface IVertex
+{
+ property float3 position{get;}
+ property Optional<float3> normal{get;}
+ property Optional<float3> color{get;}
+}
+
+struct Vertex<bool hasNormal, bool hasColor> : IVertex
+{
+ private float3 m_position;
+ private Conditional<float3, hasNormal> m_normal;
+ private Conditional<float3, hasColor> m_color;
+
+ __init(float3 position, float3 normal, float3 color)
+ {
+ m_position = position;
+ m_normal = normal;
+ m_color = color;
+ }
+
+ property float3 position
+ {
+ get { return m_position; }
+ }
+ property Optional<float3> normal
+ {
+ get { return m_normal; }
+ }
+ property Optional<float3> color
+ {
+ get { return m_color; }
+ }
+}
+```
+
+In this example, `Vertex` type is parameterized on `hasNormal` and `hasColor`. If `hasNormal` is false, the `m_normal` field will be eliminated in the target code, allowing a specialized vertex shader to declare minimum output fields. For example, a vertex shader
+can be defined as follows:
+
+```slang
+[shader("vertex")]
+Vertex<hasNormal, hasColor> vertMain<bool hasNormal, bool hasColor>(VertexIn inputVertex)
+{
+ ...
+}
+```
+
+
## `if_let` syntax
-Slang supports `if (let name = expr)` syntax to simplify the code when working with `Optional<T>` value. The syntax is similar to Rust's
-`if let` syntax, the value expression must be an `Optional<T>` type, for example:
+Slang supports `if (let name = expr)` syntax to simplify the code when working with `Optional<T>` or `Conditional<T, hasValue>` value. The syntax is similar to Rust's
+`if let` syntax, the value expression must be an `Optional<T>` or `Conditional<T, hasValue>` type, for example:
```csharp
Optional<int> getOptInt() { ... }
diff --git a/docs/user-guide/toc.html b/docs/user-guide/toc.html
index 5e8ecc207..1621d1da5 100644
--- a/docs/user-guide/toc.html
+++ b/docs/user-guide/toc.html
@@ -42,6 +42,7 @@
<li data-link="convenience-features#subscript-operator"><span>Subscript Operator</span></li>
<li data-link="convenience-features#tuple-types"><span>Tuple Types</span></li>
<li data-link="convenience-features#optionalt-type"><span>`Optional&lt;T&gt;` type</span></li>
+<li data-link="convenience-features#conditionalt-bool-condition-type"><span>`Conditional&lt;T, bool condition&gt;` Type</span></li>
<li data-link="convenience-features#if_let-syntax"><span>`if_let` syntax</span></li>
<li data-link="convenience-features#reinterprett-operation"><span>`reinterpret&lt;T&gt;` operation</span></li>
<li data-link="convenience-features#pointers-limited"><span>Pointers (limited)</span></li>
diff --git a/source/slang/core.meta.slang b/source/slang/core.meta.slang
index 484f51bfc..51e2f326e 100644
--- a/source/slang/core.meta.slang
+++ b/source/slang/core.meta.slang
@@ -1513,6 +1513,50 @@ bool operator!=(__none_t noneVal, Optional<T> val)
return val.hasValue;
}
+struct Conditional<T, bool hasValue>
+{
+ internal T storage[hasValue];
+
+ __implicit_conversion($(kConversionCost_ValToOptional))
+ [__unsafeForceInlineEarly]
+ __init(T val) { if (hasValue) storage[0] = val;}
+
+ [__unsafeForceInlineEarly]
+ public Optional<T> get()
+ {
+ if (hasValue)
+ {
+ return Optional<T>(storage[0]);
+ }
+ else
+ {
+ return none;
+ }
+ }
+
+ [__unsafeForceInlineEarly]
+ [mutating]
+ public void set(T value)
+ {
+ if (hasValue)
+ storage[0] = value;
+ }
+}
+
+extension<T> Optional<T>
+{
+ __implicit_conversion($(kConversionCost_ImplicitDereference))
+ __generic<bool condHasValue>
+ [__unsafeForceInlineEarly]
+ __init(Conditional<T, condHasValue> condVal)
+ {
+ if (condHasValue)
+ this = Optional<T>(condVal.storage[0]);
+ else
+ this = none;
+ }
+}
+
//@public:
/// A variadic generic storing the product of several types.
diff --git a/source/slang/slang-check-decl.cpp b/source/slang/slang-check-decl.cpp
index e3b05ec00..aa84a057f 100644
--- a/source/slang/slang-check-decl.cpp
+++ b/source/slang/slang-check-decl.cpp
@@ -10120,7 +10120,7 @@ void SemanticsVisitor::validateArraySizeForVariable(VarDeclBase* varDecl)
// TODO(tfoley): How to handle the case where bound isn't known?
auto elementCount = arrayType->getElementCount();
- if (GetMinBound(elementCount) <= 0)
+ if (GetMinBound(elementCount) < 0)
{
getSink()->diagnose(varDecl, Diagnostics::invalidArraySize);
return;
diff --git a/source/slang/slang-check-expr.cpp b/source/slang/slang-check-expr.cpp
index 1e052a553..66c2f9796 100644
--- a/source/slang/slang-check-expr.cpp
+++ b/source/slang/slang-check-expr.cpp
@@ -2414,10 +2414,10 @@ Expr* SemanticsExprVisitor::visitIndexExpr(IndexExpr* subscriptExpr)
nullptr,
ConstantFoldingKind::SpecializationConstant);
- // Validate that array size is greater than zero
+ // Validate that array size is non-negative.
if (auto constElementCount = as<ConstantIntVal>(elementCount))
{
- if (constElementCount->getValue() <= 0)
+ if (constElementCount->getValue() < 0)
{
getSink()->diagnose(
subscriptExpr->indexExprs[0],
diff --git a/source/slang/slang-diagnostic-defs.h b/source/slang/slang-diagnostic-defs.h
index 465602a33..3786a0cf3 100644
--- a/source/slang/slang-diagnostic-defs.h
+++ b/source/slang/slang-diagnostic-defs.h
@@ -657,7 +657,7 @@ DIAGNOSTIC(
Error,
cannotConvertArrayOfSmallerToLargerSize,
"Cannot convert array of size $0 to array of size $1 as this would truncate data")
-DIAGNOSTIC(30025, Error, invalidArraySize, "array size must be larger than zero.")
+DIAGNOSTIC(30025, Error, invalidArraySize, "array size must be non-negative.")
DIAGNOSTIC(
30026,
Error,
diff --git a/source/slang/slang-emit.cpp b/source/slang/slang-emit.cpp
index 20459c722..88533d938 100644
--- a/source/slang/slang-emit.cpp
+++ b/source/slang/slang-emit.cpp
@@ -56,6 +56,7 @@
#include "slang-ir-layout.h"
#include "slang-ir-legalize-array-return-type.h"
#include "slang-ir-legalize-binary-operator.h"
+#include "slang-ir-legalize-empty-array.h"
#include "slang-ir-legalize-global-values.h"
#include "slang-ir-legalize-image-subscript.h"
#include "slang-ir-legalize-mesh-outputs.h"
@@ -1158,6 +1159,8 @@ Result linkAndOptimizeIR(
addUserTypeHintDecorations(irModule);
}
+ legalizeEmptyArray(irModule, sink);
+
// We don't need the legalize pass for C/C++ based types
if (options.shouldLegalizeExistentialAndResourceTypes)
{
diff --git a/source/slang/slang-ir-inst-defs.h b/source/slang/slang-ir-inst-defs.h
index 7a281bac4..eac337deb 100644
--- a/source/slang/slang-ir-inst-defs.h
+++ b/source/slang/slang-ir-inst-defs.h
@@ -428,19 +428,20 @@ INST(Load, load, 1, 0)
INST(Store, store, 2, 0)
// Atomic Operations
-INST(AtomicLoad, atomicLoad, 1, 0)
-INST(AtomicStore, atomicStore, 2, 0)
-INST(AtomicExchange, atomicExchange, 2, 0)
-INST(AtomicCompareExchange, atomicCompareExchange, 3, 0)
-INST(AtomicAdd, atomicAdd, 2, 0)
-INST(AtomicSub, atomicSub, 2, 0)
-INST(AtomicAnd, atomicAnd, 2, 0)
-INST(AtomicOr, atomicOr, 2, 0)
-INST(AtomicXor, atomicXor, 2, 0)
-INST(AtomicMin, atomicMin, 2, 0)
-INST(AtomicMax, atomicMax, 2, 0)
-INST(AtomicInc, atomicInc, 1, 0)
-INST(AtomicDec, atomicDec, 1, 0)
+ INST(AtomicLoad, atomicLoad, 1, 0)
+ INST(AtomicStore, atomicStore, 2, 0)
+ INST(AtomicExchange, atomicExchange, 2, 0)
+ INST(AtomicCompareExchange, atomicCompareExchange, 3, 0)
+ INST(AtomicAdd, atomicAdd, 2, 0)
+ INST(AtomicSub, atomicSub, 2, 0)
+ INST(AtomicAnd, atomicAnd, 2, 0)
+ INST(AtomicOr, atomicOr, 2, 0)
+ INST(AtomicXor, atomicXor, 2, 0)
+ INST(AtomicMin, atomicMin, 2, 0)
+ INST(AtomicMax, atomicMax, 2, 0)
+ INST(AtomicInc, atomicInc, 1, 0)
+ INST(AtomicDec, atomicDec, 1, 0)
+INST_RANGE(AtomicOperation, AtomicLoad, AtomicDec)
// Produced and removed during backward auto-diff pass as a temporary placeholder representing the
// currently accumulated derivative to pass to some dOut argument in a nested call.
diff --git a/source/slang/slang-ir-insts.h b/source/slang/slang-ir-insts.h
index b5c1a6475..a1f66d142 100644
--- a/source/slang/slang-ir-insts.h
+++ b/source/slang/slang-ir-insts.h
@@ -2500,7 +2500,14 @@ struct IRLoad : IRInst
IRInst* getPtr() { return ptr.get(); }
};
-struct IRAtomicLoad : IRInst
+struct IRAtomicOperation : IRInst
+{
+ IR_PARENT_ISA(AtomicOperation);
+
+ IRInst* getPtr() { return getOperand(0); }
+};
+
+struct IRAtomicLoad : IRAtomicOperation
{
IRUse ptr;
IR_LEAF_ISA(AtomicLoad)
@@ -2521,7 +2528,7 @@ struct IRStore : IRInst
IRUse* getValUse() { return &val; }
};
-struct IRAtomicStore : IRInst
+struct IRAtomicStore : IRAtomicOperation
{
IRUse ptr;
IRUse val;
@@ -3074,6 +3081,17 @@ struct IREach : IRInst
IRInst* getElement() { return getOperand(0); }
};
+struct IRMakeArray : IRInst
+{
+ IR_LEAF_ISA(MakeArray)
+};
+
+struct IRMakeArrayFromElement : IRInst
+{
+ IR_LEAF_ISA(MakeArrayFromElement)
+};
+
+
// An Instruction that creates a tuple value.
struct IRMakeTuple : IRInst
{
diff --git a/source/slang/slang-ir-legalize-empty-array.cpp b/source/slang/slang-ir-legalize-empty-array.cpp
new file mode 100644
index 000000000..561327565
--- /dev/null
+++ b/source/slang/slang-ir-legalize-empty-array.cpp
@@ -0,0 +1,196 @@
+#include "slang-ir-legalize-empty-array.h"
+
+#include "slang-ir-insts.h"
+#include "slang-ir-util.h"
+#include "slang-ir.h"
+
+namespace Slang
+{
+struct EmptyArrayLoweringContext
+{
+ IRModule* module;
+ DiagnosticSink* sink;
+
+ InstWorkList workList;
+ InstHashSet workListSet;
+
+ Dictionary<IRInst*, IRInst*> replacements;
+
+ EmptyArrayLoweringContext(IRModule* module)
+ : module(module), workList(module), workListSet(module)
+ {
+ }
+
+ void addToWorkList(IRInst* inst)
+ {
+ for (auto ii = inst->getParent(); ii; ii = ii->getParent())
+ {
+ if (as<IRGeneric>(ii))
+ return;
+ }
+
+ if (workListSet.contains(inst))
+ return;
+
+ workList.add(inst);
+ workListSet.add(inst);
+ }
+
+ bool isEmptyArray(IRType* t)
+ {
+ const auto lenLit = composeGetters<IRIntLit>(t, &IRArrayType::getElementCount);
+ return lenLit ? getIntVal(lenLit) == 0 : false;
+ };
+
+ bool hasEmptyArrayType(IRInst* i) { return isEmptyArray(i->getDataType()); }
+
+ bool hasEmptyArrayPtrType(IRInst* i)
+ {
+ const auto ptr = as<IRPtrTypeBase>(i->getDataType());
+ return ptr && isEmptyArray(ptr->getValueType());
+ }
+
+ // Visit each instruction and replace it with legalized instructiosn if necessary.
+ void processInst(IRInst* inst)
+ {
+ IRInst* replacement = nullptr;
+
+ IRBuilder builder(module);
+ builder.setInsertBefore(inst);
+ replacement = instMatch<IRInst*>(
+ inst,
+ nullptr,
+ // The following match instructions which take a 0-sized array as an
+ // operand and produces a result value for the inst.
+ [&](IRGetElement* getElement)
+ {
+ const auto base = getElement->getBase();
+ return hasEmptyArrayType(base) ? builder.emitUndefined(getElement->getDataType())
+ : nullptr;
+ },
+ [&](IRGetElementPtr* gep)
+ {
+ const auto base = gep->getBase();
+ return hasEmptyArrayPtrType(base) || base->getOp() == kIROp_undefined
+ ? builder.emitUndefined(gep->getDataType())
+ : nullptr;
+ },
+ [&](IRFieldAddress* gep)
+ {
+ const auto base = gep->getBase();
+ return base->getOp() == kIROp_undefined ? builder.emitUndefined(gep->getDataType())
+ : nullptr;
+ },
+ [&](IRLoad* load)
+ {
+ return load->getOperand(0)->getOp() == kIROp_undefined
+ ? builder.emitUndefined(load->getDataType())
+ : nullptr;
+ },
+ [&](IRImageLoad* load)
+ {
+ return load->getOperand(0)->getOp() == kIROp_undefined
+ ? builder.emitUndefined(load->getDataType())
+ : nullptr;
+ },
+ [&](IRStore* store)
+ {
+ if (store->getPtr()->getOp() == kIROp_undefined)
+ store->removeAndDeallocate();
+ return nullptr;
+ },
+ [&](IRAtomicStore* store)
+ {
+ if (store->getPtr()->getOp() == kIROp_undefined)
+ store->removeAndDeallocate();
+ return nullptr;
+ },
+ [&](IRImageStore* store)
+ {
+ if (store->getImage()->getOp() == kIROp_undefined)
+ store->removeAndDeallocate();
+ return nullptr;
+ },
+ [&](IRImageSubscript* subscript)
+ {
+ return subscript->getImage()->getOp() == kIROp_undefined
+ ? builder.emitUndefined(subscript->getDataType())
+ : nullptr;
+ },
+ [&](IRAtomicOperation* atomic)
+ {
+ return atomic->getOperand(0)->getOp() == kIROp_undefined
+ ? builder.emitUndefined(atomic->getDataType())
+ : nullptr;
+ },
+ // The following should match any instruction which can construct a 0-sized array.
+ [&](IRMakeArray* makeArray) {
+ return hasEmptyArrayType(makeArray->getDataType()) ? builder.getVoidValue()
+ : nullptr;
+ },
+ [&](IRMakeArrayFromElement* makeArray) {
+ return hasEmptyArrayType(makeArray->getDataType()) ? builder.getVoidValue()
+ : nullptr;
+ });
+
+ // If we did get a replacement, add that to our mapping and return
+ // it, otherwise return the original (to maybe be updated later)
+ if (replacement)
+ {
+ inst->replaceUsesWith(replacement);
+ inst->removeAndDeallocate();
+ addToWorkList(replacement);
+ for (auto use = replacement->firstUse; use; use = use->nextUse)
+ {
+ addToWorkList(use->getUser());
+ }
+ }
+ else if (isEmptyArray((IRType*)inst))
+ {
+ replacements.add(inst, builder.getVoidType());
+ }
+ }
+
+ void processModule()
+ {
+ addToWorkList(module->getModuleInst());
+
+ while (workList.getCount() != 0)
+ {
+ IRInst* inst = workList.getLast();
+
+ workList.removeLast();
+ workListSet.remove(inst);
+
+ // Run this inst through the replacer
+ processInst(inst);
+
+ for (auto child = inst->getLastChild(); child; child = child->getPrevInst())
+ {
+ addToWorkList(child);
+ }
+ }
+
+ // Apply all deferred replacements
+ //
+ // It's important to defer this as if we were updating things
+ // on-the-fly we would be losing information about what was
+ // actually a 0-array or not.
+ for (const auto& [old, replacement] : replacements)
+ {
+ if (old != replacement)
+ {
+ old->replaceUsesWith(replacement);
+ old->removeAndDeallocate();
+ }
+ }
+ }
+};
+
+void legalizeEmptyArray(IRModule* module, DiagnosticSink* sink)
+{
+ EmptyArrayLoweringContext context(module);
+ context.sink = sink;
+ context.processModule();
+}
+} // namespace Slang
diff --git a/source/slang/slang-ir-legalize-empty-array.h b/source/slang/slang-ir-legalize-empty-array.h
new file mode 100644
index 000000000..657c18403
--- /dev/null
+++ b/source/slang/slang-ir-legalize-empty-array.h
@@ -0,0 +1,11 @@
+#pragma once
+
+namespace Slang
+{
+struct IRModule;
+class DiagnosticSink;
+
+// Legalize 0-sized arrays to `void` types.
+void legalizeEmptyArray(IRModule* module, DiagnosticSink* sink);
+
+} // namespace Slang
diff --git a/tests/diagnostics/array-zero-size.slang b/tests/diagnostics/array-neg-size.slang
index f67f163fd..660f0b6c5 100644
--- a/tests/diagnostics/array-zero-size.slang
+++ b/tests/diagnostics/array-neg-size.slang
@@ -10,4 +10,4 @@ void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
bar();
}
//CHECK: ([[# @LINE+1]]): error 30025
-func bar() -> int[0];
+func bar() -> int[-1];
diff --git a/tests/language-feature/0-array-1.slang b/tests/language-feature/0-array-1.slang
new file mode 100644
index 000000000..327f71444
--- /dev/null
+++ b/tests/language-feature/0-array-1.slang
@@ -0,0 +1,31 @@
+//TEST:SIMPLE(filecheck=SPV): -target spirv
+//TEST:SIMPLE(filecheck=HLSL): -target hlsl -profile cs_6_0 -entry computeMain
+
+struct MyData
+{
+ int a[0][0][0];
+}
+
+uniform MyData* myData;
+uniform int * output;
+
+[numthreads(1, 1, 1)]
+void computeMain()
+{
+ // These are all ill-formed, but we want to still ensure our backend
+ // can handle them gracefully without crashing.
+ // In actual user code, any access to 0-sized arrays should be protected
+ // by a `if` statement that checks the size before accessing.
+ // The condition would then evaluate to false and causing all the accessing
+ // code to be optimized out.
+ InterlockedAdd(myData.a[0][0][0], 1);
+ myData.a[0][0][0] += 1;
+ output[0] = myData.a[0][0][0];
+}
+
+//SPV: OpEntryPoint
+//SPV-NOT: OpAtomic
+//SPV-NOT: OpStore
+//SPV-NOT: OpLoad
+
+//HLSL: computeMain
diff --git a/tests/language-feature/0-array.slang b/tests/language-feature/0-array.slang
new file mode 100644
index 000000000..7379faace
--- /dev/null
+++ b/tests/language-feature/0-array.slang
@@ -0,0 +1,64 @@
+//TEST(compute):COMPARE_COMPUTE(filecheck-buffer=CHECK):
+//TEST(compute):COMPARE_COMPUTE(filecheck-buffer=CHECK): -vk
+
+interface IVertex
+{
+ property float3 position{get;}
+ property Optional<float3> normal{get;}
+ property Optional<float3> color{get;}
+}
+
+struct Vertex<bool hasNormal, bool hasColor> : IVertex
+{
+ private float3 m_position;
+ private float3 m_normal[hasNormal];
+ private float3 m_color[hasColor];
+
+ __init(float3 position, float3 normal, float3 color)
+ {
+ m_position = position;
+ if (hasNormal) m_normal[0] = normal;
+ if (hasColor) m_color[0] = color;
+ }
+
+ property float3 position
+ {
+ get { return m_position; }
+ }
+ property Optional<float3> normal
+ {
+ get { if (hasNormal) return m_normal[0]; else return none; }
+ }
+ property Optional<float3> color
+ {
+ get { if (hasColor) return m_color[0]; else return none; }
+ }
+}
+
+//TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):out,name=outputBuffer
+RWStructuredBuffer<int> outputBuffer;
+
+void test<V:IVertex>(V vert)
+{
+ // CHECK: 0
+ // CHECK: 0
+ // CHECK: 1
+ // CHECK: 3
+ if (let normal = vert.normal)
+ {
+ outputBuffer[0] = 1;
+ outputBuffer[1] = (int)normal.x;
+ }
+
+ if (let color = vert.color)
+ {
+ outputBuffer[2] = 1;
+ outputBuffer[3] = (int)color.x;
+ }
+}
+
+[numthreads(1,1,1)]
+void computeMain()
+{
+ test<Vertex<false, true>>(Vertex<false, true>(1.0, 2.0, 3.0));
+} \ No newline at end of file
diff --git a/tests/language-feature/cond-field.slang b/tests/language-feature/cond-field.slang
new file mode 100644
index 000000000..5e9f4a06e
--- /dev/null
+++ b/tests/language-feature/cond-field.slang
@@ -0,0 +1,64 @@
+//TEST(compute):COMPARE_COMPUTE(filecheck-buffer=CHECK):
+//TEST(compute):COMPARE_COMPUTE(filecheck-buffer=CHECK): -vk
+
+interface IVertex
+{
+ property float3 position{get;}
+ property Optional<float3> normal{get;}
+ property Optional<float3> color{get;}
+}
+
+struct Vertex<bool hasNormal, bool hasColor> : IVertex
+{
+ private float3 m_position;
+ private Conditional<float3, hasNormal> m_normal;
+ private Conditional<float3, hasColor> m_color;
+
+ __init(float3 position, float3 normal, float3 color)
+ {
+ m_position = position;
+ m_normal = normal;
+ m_color = color;
+ }
+
+ property float3 position
+ {
+ get { return m_position; }
+ }
+ property Optional<float3> normal
+ {
+ get { return m_normal; }
+ }
+ property Optional<float3> color
+ {
+ get { return m_color; }
+ }
+}
+
+//TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):out,name=outputBuffer
+RWStructuredBuffer<int> outputBuffer;
+
+void test<V:IVertex>(V vert)
+{
+ // CHECK: 0
+ // CHECK: 0
+ // CHECK: 1
+ // CHECK: 3
+ if (let normal = vert.normal)
+ {
+ outputBuffer[0] = 1;
+ outputBuffer[1] = (int)normal.x;
+ }
+
+ if (let color = vert.color)
+ {
+ outputBuffer[2] = 1;
+ outputBuffer[3] = (int)color.x;
+ }
+}
+
+[numthreads(1,1,1)]
+void computeMain()
+{
+ test<Vertex<false, true>>(Vertex<false, true>(1.0, 2.0, 3.0));
+} \ No newline at end of file