summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--source/slang/check.cpp176
-rw-r--r--source/slang/core.meta.slang2
-rw-r--r--source/slang/core.meta.slang.h2
-rw-r--r--source/slang/diagnostic-defs.h13
-rw-r--r--source/slang/emit.cpp153
-rw-r--r--source/slang/ir-inst-defs.h2
-rw-r--r--source/slang/ir-insts.h12
-rw-r--r--source/slang/ir.cpp13
-rw-r--r--source/slang/lower-to-ir.cpp57
-rw-r--r--source/slang/modifier-defs.h6
-rw-r--r--tests/render/tess.hlsl78
11 files changed, 496 insertions, 18 deletions
diff --git a/source/slang/check.cpp b/source/slang/check.cpp
index 046587d0f..1fa7f9b01 100644
--- a/source/slang/check.cpp
+++ b/source/slang/check.cpp
@@ -1845,6 +1845,38 @@ namespace Slang
return Stage::Unknown;
}
+ bool hasIntArgs(Attribute* attr, int numArgs)
+ {
+ if (int(attr->args.Count()) != numArgs)
+ {
+ return false;
+ }
+ for (int i = 0; i < numArgs; ++i)
+ {
+ if (!attr->args[i]->As<IntegerLiteralExpr>())
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ bool hasStringArgs(Attribute* attr, int numArgs)
+ {
+ if (int(attr->args.Count()) != numArgs)
+ {
+ return false;
+ }
+ for (int i = 0; i < numArgs; ++i)
+ {
+ if (!attr->args[i]->As<StringLiteralExpr>())
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
bool validateAttribute(RefPtr<Attribute> attr)
{
if(auto numThreadsAttr = attr.As<NumThreadsAttribute>())
@@ -1898,7 +1930,27 @@ namespace Slang
entryPointAttr->stage = stage;
}
- else
+ else if ((attr.As<DomainAttribute>()) ||
+ (attr.As<MaxTessFactorAttribute>()) ||
+ (attr.As<OutputTopologyAttribute>()) ||
+ (attr.As<PartitioningAttribute>()) ||
+ (attr.As<PatchConstantFuncAttribute>()))
+ {
+ // Let it go thru iff single string attribute
+ if (!hasStringArgs(attr, 1))
+ {
+ getSink()->diagnose(attr, Diagnostics::expectedSingleStringArg, attr->name);
+ }
+ }
+ else if (attr.As<OutputControlPointsAttribute>())
+ {
+ // Let it go thru iff single integral attribute
+ if (!hasIntArgs(attr, 1))
+ {
+ getSink()->diagnose(attr, Diagnostics::expectedSingleIntArg, attr->name);
+ }
+ }
+ else
{
if(attr->args.Count() == 0)
{
@@ -8231,18 +8283,89 @@ namespace Slang
return type;
}
+
+ FuncDecl* findFunctionDeclByName(EntryPointRequest* entryPoint, Name* name)
+ {
+ auto translationUnit = entryPoint->getTranslationUnit();
+ auto sink = &entryPoint->compileRequest->mSink;
+ auto translationUnitSyntax = translationUnit->SyntaxNode;
+
+ // Make sure we've got a query-able member dictionary
+ buildMemberDictionary(translationUnitSyntax);
+
+ // We will look up any global-scope declarations in the translation
+ // unit that match the name of our entry point.
+ Decl* firstDeclWithName = nullptr;
+ if (!translationUnitSyntax->memberDictionary.TryGetValue(name, firstDeclWithName))
+ {
+ // If there doesn't appear to be any such declaration, then we are done.
+
+ sink->diagnose(translationUnitSyntax, Diagnostics::entryPointFunctionNotFound, name);
+
+ return nullptr;
+ }
+
+ // We found at least one global-scope declaration with the right name,
+ // but (1) it might not be a function, and (2) there might be
+ // more than one function.
+ //
+ // We'll walk the linked list of declarations with the same name,
+ // to see what we find. Along the way we'll keep track of the
+ // first function declaration we find, if any:
+ FuncDecl* entryPointFuncDecl = nullptr;
+ for (auto ee = firstDeclWithName; ee; ee = ee->nextInContainerWithSameName)
+ {
+ // Is this declaration a function?
+ if (auto funcDecl = dynamic_cast<FuncDecl*>(ee))
+ {
+ // Skip non-primary declarations, so that
+ // we don't give an error when an entry
+ // point is forward-declared.
+ if (!isPrimaryDecl(funcDecl))
+ continue;
+
+ // is this the first one we've seen?
+ if (!entryPointFuncDecl)
+ {
+ // If so, this is a candidate to be
+ // the entry point function.
+ entryPointFuncDecl = funcDecl;
+ }
+ else
+ {
+ // Uh-oh! We've already seen a function declaration with this
+ // name before, so the whole thing is ambiguous. We need
+ // to diagnose and bail out.
+
+ sink->diagnose(translationUnitSyntax, Diagnostics::ambiguousEntryPoint, name);
+
+ // List all of the declarations that the user *might* mean
+ for (auto ff = firstDeclWithName; ff; ff = ff->nextInContainerWithSameName)
+ {
+ if (auto candidate = dynamic_cast<FuncDecl*>(ff))
+ {
+ sink->diagnose(candidate, Diagnostics::entryPointCandidate, candidate->getName());
+ }
+ }
+
+ // Bail out.
+ return nullptr;
+ }
+ }
+ }
+
+ return entryPointFuncDecl;
+ }
+
// Validate that an entry point function conforms to any additional
// constraints based on the stage (and profile?) it specifies.
void validateEntryPoint(
- EntryPointRequest* /*entryPoint*/)
+ EntryPointRequest* entryPoint)
{
- // TODO: We currently don't do any checking here, but this is the
+ // TODO: We currently do minimal checking here, but this is the
// right place to perform the following validation checks:
//
- // * Does the entry point specify all of the attributes required
- // by the chosen stage (e.g., a `[domain(...)]` attribute for]
- // a hull shader.
- //
+
// * Are the function input/output parameters and result type
// all valid for the chosen stage? (e.g., there shouldn't be
// an `OutputStream<X>` type in a vertex shader signature)
@@ -8263,6 +8386,43 @@ namespace Slang
// `Texture2D.Sample`, then that should produce an error because
// that function is specific to the fragment profile/stage.
//
+
+ if (entryPoint->getStage() == Stage::Hull)
+ {
+ auto translationUnit = entryPoint->getTranslationUnit();
+ auto sink = &entryPoint->compileRequest->mSink;
+ auto translationUnitSyntax = translationUnit->SyntaxNode;
+
+ auto attr = entryPoint->decl->FindModifier<PatchConstantFuncAttribute>();
+
+ if (attr)
+ {
+ if (attr->args.Count() != 1)
+ {
+ sink->diagnose(translationUnitSyntax, Diagnostics::badlyDefinedPatchConstantFunc, entryPoint->name);
+ return;
+ }
+
+ Expr* expr = attr->args[0];
+ StringLiteralExpr* stringLit = expr->As<StringLiteralExpr>();
+
+ if (!stringLit)
+ {
+ sink->diagnose(translationUnitSyntax, Diagnostics::badlyDefinedPatchConstantFunc, entryPoint->name);
+ return;
+ }
+
+ Name* name = entryPoint->compileRequest->getNamePool()->getName(stringLit->value);
+ FuncDecl* funcDecl = findFunctionDeclByName(entryPoint, name);
+ if (!funcDecl)
+ {
+ sink->diagnose(translationUnitSyntax, Diagnostics::attributeFunctionNotFound, name, "patchconstantfunc");
+ return;
+ }
+
+ attr->patchConstantFuncDecl = funcDecl;
+ }
+ }
}
// Given an `EntryPointRequest` specified via API or command line options,
@@ -8445,7 +8605,7 @@ namespace Slang
// set to `Batman` to know whether the setting for `B` is valid. In this limit
// the constraints can be mutually recursive (so `A : IMentor<B>`).
//
- // The only way to check things corectly is to validate each conformance under
+ // The only way to check things correctly is to validate each conformance under
// a set of assumptions (substitutions) that includes all the type substitutions,
// and possibly also all the other constraints *except* the one to be validated.
//
diff --git a/source/slang/core.meta.slang b/source/slang/core.meta.slang
index a29cf7a27..ab46e5676 100644
--- a/source/slang/core.meta.slang
+++ b/source/slang/core.meta.slang
@@ -1136,7 +1136,7 @@ __attributeTarget(FuncDecl)
attribute_syntax [outputcontrolpoints(count: int)] : OutputControlPointsAttribute;
__attributeTarget(FuncDecl)
-attribute_syntax [outputtopology(topology)] : OuptutTopologyAttribute;
+attribute_syntax [outputtopology(topology)] : OutputTopologyAttribute;
__attributeTarget(FuncDecl)
attribute_syntax [partitioning(mode)] : PartitioningAttribute;
diff --git a/source/slang/core.meta.slang.h b/source/slang/core.meta.slang.h
index b4bef2397..d3edba93d 100644
--- a/source/slang/core.meta.slang.h
+++ b/source/slang/core.meta.slang.h
@@ -1156,7 +1156,7 @@ SLANG_RAW("__attributeTarget(FuncDecl)\n")
SLANG_RAW("attribute_syntax [outputcontrolpoints(count: int)] : OutputControlPointsAttribute;\n")
SLANG_RAW("\n")
SLANG_RAW("__attributeTarget(FuncDecl)\n")
-SLANG_RAW("attribute_syntax [outputtopology(topology)] : OuptutTopologyAttribute;\n")
+SLANG_RAW("attribute_syntax [outputtopology(topology)] : OutputTopologyAttribute;\n")
SLANG_RAW("\n")
SLANG_RAW("__attributeTarget(FuncDecl)\n")
SLANG_RAW("attribute_syntax [partitioning(mode)] : PartitioningAttribute;\n")
diff --git a/source/slang/diagnostic-defs.h b/source/slang/diagnostic-defs.h
index b6f755489..8dd2f3b0b 100644
--- a/source/slang/diagnostic-defs.h
+++ b/source/slang/diagnostic-defs.h
@@ -206,10 +206,20 @@ DIAGNOSTIC(33071, Error, expectedAStringLiteral, "expected a string literal")
// Attributes
DIAGNOSTIC(31000, Error, unknownAttributeName, "unknown attribute '$0'")
DIAGNOSTIC(31001, Error, attributeArgumentCountMismatch, "attribute '$0' expects $1 arguments ($2 provided)")
-DIAGNOSTIC(31001, Error, attributeNotApplicable, "attribute '$0' is not valid here")
+DIAGNOSTIC(31002, Error, attributeNotApplicable, "attribute '$0' is not valid here")
+
+DIAGNOSTIC(31003, Error, badlyDefinedPatchConstantFunc, "hull shader '$0' has has badly defined 'patchconstantfunc' attribute.");
+
+DIAGNOSTIC(31004, Error, expectedSingleIntArg, "attribute '$0' expects a single int argument")
+DIAGNOSTIC(31005, Error, expectedSingleStringArg, "attribute '$0' expects a single string argument")
+
+DIAGNOSTIC(31006, Error, attributeFunctionNotFound, "Could not find function '$0' for attribute'$1'")
+
DIAGNOSTIC(31100, Error, unknownStageName, "unknown stage name '$0'")
+
+
// Enums
DIAGNOSTIC(32000, Error, invalidEnumTagType, "invalid tag type for 'enum': '$0'")
@@ -265,7 +275,6 @@ DIAGNOSTIC(39999, Error, invalidFloatingPOintLiteralSuffix, "invalid suffix '$0'
-
DIAGNOSTIC(38000, Error, entryPointFunctionNotFound, "no function found matching entry point name '$0'")
DIAGNOSTIC(38001, Error, ambiguousEntryPoint, "more than one function matches entry point name '$0'")
DIAGNOSTIC(38002, Note, entryPointCandidate, "see candidate declaration for entry point '$0'")
diff --git a/source/slang/emit.cpp b/source/slang/emit.cpp
index 1b8de806a..1a56c1d4d 100644
--- a/source/slang/emit.cpp
+++ b/source/slang/emit.cpp
@@ -3654,6 +3654,12 @@ struct EmitVisitor
}
break;
+ case kIROp_NotePatchConstantFunc:
+ {
+ // No-op
+ break;
+ }
+
case kIROp_Var:
{
auto ptrType = cast<IRPtrType>(inst->getDataType());
@@ -4129,7 +4135,98 @@ struct EmitVisitor
}
}
+
+
+ IRInst* findFirstInst(IRFunc* irFunc, IROp op)
+ {
+ for (auto block : irFunc->getBlocks())
+ {
+ for (auto inst : block->getChildren())
+ {
+ if (inst->op == op)
+ {
+ return inst;
+ }
+ }
+ }
+ return nullptr;
+ }
+
+ void emitAttributeSingleString(const char* name, FuncDecl* entryPoint, Attribute* attrib)
+ {
+ assert(attrib);
+
+ attrib->args.Count();
+ if (attrib->args.Count() != 1)
+ {
+ SLANG_DIAGNOSE_UNEXPECTED(getSink(), entryPoint->loc, "Attribute expects single parameter");
+ return;
+ }
+
+ Expr* expr = attrib->args[0];
+
+ auto stringLitExpr = expr->As<StringLiteralExpr>();
+ if (!stringLitExpr)
+ {
+ SLANG_DIAGNOSE_UNEXPECTED(getSink(), entryPoint->loc, "Attribute parameter expecting to be a string ");
+ return;
+ }
+
+ emit("[");
+ emit(name);
+ emit("(\"");
+ emit(stringLitExpr->value);
+ emit("\")]\n");
+ }
+
+ void emitAttributeSingleInt(const char* name, FuncDecl* entryPoint, Attribute* attrib)
+ {
+ assert(attrib);
+
+ attrib->args.Count();
+ if (attrib->args.Count() != 1)
+ {
+ SLANG_DIAGNOSE_UNEXPECTED(getSink(), entryPoint->loc, "Attribute expects single parameter");
+ return;
+ }
+
+ Expr* expr = attrib->args[0];
+
+ auto intLitExpr = expr->As<IntegerLiteralExpr>();
+ if (!intLitExpr)
+ {
+ SLANG_DIAGNOSE_UNEXPECTED(getSink(), entryPoint->loc, "Attribute expects an int");
+ return;
+ }
+
+ emit("[");
+ emit(name);
+ emit("(");
+ emit(intLitExpr->value);
+ emit(")]\n");
+ }
+
+ void emitFuncDeclPatchConstantFuncAttribute(IRFunc* irFunc, FuncDecl* entryPoint, PatchConstantFuncAttribute* attrib)
+ {
+ SLANG_UNUSED(attrib);
+
+ auto irPatchFunc = static_cast<IRNotePatchConstantFunc*>(findFirstInst(irFunc, kIROp_NotePatchConstantFunc));
+ assert(irPatchFunc);
+ if (!irPatchFunc)
+ {
+ SLANG_DIAGNOSE_UNEXPECTED(getSink(), entryPoint->loc, "Unable to find NotePatchConstantFunc instruction");
+ return;
+ }
+
+ const String irName = getIRName(irPatchFunc->getFunc());
+
+ emit("[patchconstantfunc(\"");
+ emit(irName);
+ emit("\")]\n");
+ }
+
void emitIREntryPointAttributes_HLSL(
+ IRFunc* irFunc,
EmitContext* ctx,
EntryPointLayout* entryPointLayout)
{
@@ -4199,8 +4296,54 @@ struct EmitVisitor
Emit(attrib->value);
emit(")]\n");
}
+ break;
+ }
+ case Stage::Domain:
+ {
+ FuncDecl* entryPoint = entryPointLayout->entryPoint;
+ /* [domain("isoline")] */
+ if (auto attrib = entryPoint->FindModifier<DomainAttribute>())
+ {
+ emitAttributeSingleString("domain", entryPoint, attrib);
+ }
+
+ break;
+ }
+ case Stage::Hull:
+ {
+ // Lists these are only attributes for hull shader
+ // https://docs.microsoft.com/en-us/windows/desktop/direct3d11/direct3d-11-advanced-stages-hull-shader-design
+
+ FuncDecl* entryPoint = entryPointLayout->entryPoint;
+
+ /* [domain("isoline")] */
+ if (auto attrib = entryPoint->FindModifier<DomainAttribute>())
+ {
+ emitAttributeSingleString("domain", entryPoint, attrib);
+ }
+ /* [domain("partitioning")] */
+ if (auto attrib = entryPoint->FindModifier<PartitioningAttribute>())
+ {
+ emitAttributeSingleString("partitioning", entryPoint, attrib);
+ }
+ /* [outputtopology("line")] */
+ if (auto attrib = entryPoint->FindModifier<OutputTopologyAttribute>())
+ {
+ emitAttributeSingleString("outputtopology", entryPoint, attrib);
+ }
+ /* [outputcontrolpoints(4)] */
+ if (auto attrib = entryPoint->FindModifier<OutputControlPointsAttribute>())
+ {
+ emitAttributeSingleInt("outputcontrolpoints", entryPoint, attrib);
+ }
+ /* [patchconstantfunc("HSConst")] */
+ if (auto attrib = entryPoint->FindModifier<PatchConstantFuncAttribute>())
+ {
+ emitFuncDeclPatchConstantFuncAttribute(irFunc, entryPoint, attrib);
+ }
+
+ break;
}
- break;
// TODO: There are other stages that will need this kind of handling.
default:
break;
@@ -4208,6 +4351,7 @@ struct EmitVisitor
}
void emitIREntryPointAttributes_GLSL(
+ IRFunc* /*irFunc*/,
EmitContext* /*ctx*/,
EntryPointLayout* entryPointLayout)
{
@@ -4310,17 +4454,18 @@ struct EmitVisitor
}
void emitIREntryPointAttributes(
+ IRFunc* irFunc,
EmitContext* ctx,
EntryPointLayout* entryPointLayout)
{
switch(getTarget(ctx))
{
case CodeGenTarget::HLSL:
- emitIREntryPointAttributes_HLSL(ctx, entryPointLayout);
+ emitIREntryPointAttributes_HLSL(irFunc, ctx, entryPointLayout);
break;
case CodeGenTarget::GLSL:
- emitIREntryPointAttributes_GLSL(ctx, entryPointLayout);
+ emitIREntryPointAttributes_GLSL(irFunc, ctx, entryPointLayout);
break;
}
}
@@ -4390,7 +4535,7 @@ struct EmitVisitor
auto entryPointLayout = asEntryPoint(func);
if (entryPointLayout)
{
- emitIREntryPointAttributes(ctx, entryPointLayout);
+ emitIREntryPointAttributes(func, ctx, entryPointLayout);
}
auto name = getIRFuncName(func);
diff --git a/source/slang/ir-inst-defs.h b/source/slang/ir-inst-defs.h
index 0b7d15fcc..69cbe9e9a 100644
--- a/source/slang/ir-inst-defs.h
+++ b/source/slang/ir-inst-defs.h
@@ -353,6 +353,8 @@ INST(SampleGrad, sampleGrad, 4, 0)
INST(GroupMemoryBarrierWithGroupSync, GroupMemoryBarrierWithGroupSync, 0, 0)
+INST(NotePatchConstantFunc, notePatchConstantFunc, 1, 0)
+
PSEUDO_INST(Pos)
PSEUDO_INST(PreInc)
diff --git a/source/slang/ir-insts.h b/source/slang/ir-insts.h
index 880856b36..e7ca08c6d 100644
--- a/source/slang/ir-insts.h
+++ b/source/slang/ir-insts.h
@@ -381,6 +381,13 @@ struct IRSwizzledStore : IRInst
IR_LEAF_ISA(SwizzledStore)
};
+
+struct IRNotePatchConstantFunc: IRInst
+{
+ IRInst* getFunc() { return getOperand(0); }
+ IR_LEAF_ISA(NotePatchConstantFunc)
+};
+
// An IR `var` instruction conceptually represents
// a stack allocation of some memory.
struct IRVar : IRInst
@@ -689,11 +696,16 @@ struct IRBuilder
IRBlock* createBlock();
IRBlock* emitBlock();
+
+
IRParam* createParam(
IRType* type);
IRParam* emitParam(
IRType* type);
+ IRNotePatchConstantFunc* emitNotePatchConstantFunc(
+ IRInst* func);
+
IRVar* emitVar(
IRType* type);
diff --git a/source/slang/ir.cpp b/source/slang/ir.cpp
index 2a8885b0e..83735d6df 100644
--- a/source/slang/ir.cpp
+++ b/source/slang/ir.cpp
@@ -1898,6 +1898,19 @@ namespace Slang
return inst;
}
+ IRNotePatchConstantFunc* IRBuilder::emitNotePatchConstantFunc(
+ IRInst* func)
+ {
+ auto inst = createInst<IRNotePatchConstantFunc>(
+ this,
+ kIROp_NotePatchConstantFunc,
+ nullptr,
+ func);
+
+ addInst(inst);
+ return inst;
+ }
+
IRInst* IRBuilder::emitFieldExtract(
IRType* type,
IRInst* base,
diff --git a/source/slang/lower-to-ir.cpp b/source/slang/lower-to-ir.cpp
index 27325ee38..07cb508b8 100644
--- a/source/slang/lower-to-ir.cpp
+++ b/source/slang/lower-to-ir.cpp
@@ -1142,6 +1142,27 @@ struct ValLoweringVisitor : ValVisitor<ValLoweringVisitor, LoweredValInfo, Lower
&irElementType);
}
+ IRType* lowerGenericIntrinsicType(DeclRefType* type, Type* elementType, IntVal* count)
+ {
+ auto intrinsicTypeModifier = type->declRef.getDecl()->FindModifier<IntrinsicTypeModifier>();
+ SLANG_ASSERT(intrinsicTypeModifier);
+ IROp op = IROp(intrinsicTypeModifier->irOp);
+ IRInst* irElementType = lowerType(context, elementType);
+
+ IRInst* irCount = lowerSimpleVal(context, count);
+
+ IRInst* const operands[2] =
+ {
+ irElementType,
+ irCount,
+ };
+
+ return getBuilder()->getType(
+ op,
+ SLANG_COUNT_OF(operands),
+ operands);
+ }
+
IRType* visitResourceType(ResourceType* type)
{
return lowerGenericIntrinsicType(type, type->elementType);
@@ -1162,6 +1183,14 @@ struct ValLoweringVisitor : ValVisitor<ValLoweringVisitor, LoweredValInfo, Lower
return lowerSimpleIntrinsicType(type);
}
+ IRType* visitHLSLPatchType(HLSLPatchType* type)
+ {
+ Type* elementType = type->getElementType();
+ IntVal* count = type->getElementCount();
+
+ return lowerGenericIntrinsicType(type, elementType, count);
+ }
+
// We do not expect to encounter the following types in ASTs that have
// passed front-end semantic checking.
#define UNEXPECTED_CASE(NAME) IRType* visit##NAME(NAME*) { SLANG_UNEXPECTED(#NAME); UNREACHABLE_RETURN(nullptr); }
@@ -4765,6 +4794,34 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
// the setter body.
}
+ {
+
+ auto attr = decl->FindModifier<PatchConstantFuncAttribute>();
+
+ // I needed to test for patchConstantFuncDecl here
+ // because it is only set if validateEntryPoint is called with Hull as the required stage
+ // If I just build domain shader, and then the attribute exists, but patchConstantFuncDecl is not set
+ // and thus leads to a crash.
+ if (attr && attr->patchConstantFuncDecl)
+ {
+ // We need to lower the function
+ FuncDecl* patchConstantFunc = attr->patchConstantFuncDecl;
+ assert(patchConstantFunc);
+
+ // Convert the patch constant function into IRInst
+ IRInst* irPatchConstantFunc = getSimpleVal(context, ensureDecl(subContext, patchConstantFunc));
+
+ // Emit the note patch constant func
+ subContext->irBuilder->emitIntrinsicInst(
+ nullptr,
+ kIROp_NotePatchConstantFunc,
+ 1,
+ &irPatchConstantFunc);
+
+ }
+ }
+
+ // Lower body
lowerStmt(subContext, decl->Body);
diff --git a/source/slang/modifier-defs.h b/source/slang/modifier-defs.h
index 8ccdc75b1..f0443c501 100644
--- a/source/slang/modifier-defs.h
+++ b/source/slang/modifier-defs.h
@@ -335,9 +335,11 @@ SIMPLE_SYNTAX_CLASS(CallAttribute, Attribute) // `[call]`
SIMPLE_SYNTAX_CLASS(MaxTessFactorAttribute, Attribute)
SIMPLE_SYNTAX_CLASS(OutputControlPointsAttribute, Attribute)
-SIMPLE_SYNTAX_CLASS(OuptutTopologyAttribute, Attribute)
+SIMPLE_SYNTAX_CLASS(OutputTopologyAttribute, Attribute)
SIMPLE_SYNTAX_CLASS(PartitioningAttribute, Attribute)
-SIMPLE_SYNTAX_CLASS(PatchConstantFuncAttribute, Attribute)
+SYNTAX_CLASS(PatchConstantFuncAttribute, Attribute)
+ FIELD(RefPtr<FuncDecl>, patchConstantFuncDecl)
+END_SYNTAX_CLASS()
SIMPLE_SYNTAX_CLASS(DomainAttribute, Attribute)
SIMPLE_SYNTAX_CLASS(EarlyDepthStencilAttribute, Attribute)
diff --git a/tests/render/tess.hlsl b/tests/render/tess.hlsl
new file mode 100644
index 000000000..1cd01ef33
--- /dev/null
+++ b/tests/render/tess.hlsl
@@ -0,0 +1,78 @@
+//TEST:COMPARE_HLSL: -target dxbc-assembly -profile hs_5_1 -entry HS -profile ds_5_1 -entry DS
+
+// tests/render/tess.hlsl
+
+// -profile vs_5_1 -o my-shader.vs.dxbc -entry VS -profile ps_5_1 -o my-shader.ps.dxbc -entry PS
+
+struct IA_OUTPUT
+{
+ float3 cpoint : CPOINT;
+};
+
+struct VS_OUTPUT
+{
+ float3 cpoint : CPOINT;
+};
+
+struct HS_CONSTANT_OUTPUT
+{
+ float edges[2] : SV_TessFactor;
+};
+
+struct HS_OUTPUT
+{
+ float3 cpoint : CPOINT;
+};
+
+struct DS_OUTPUT
+{
+ float4 position : SV_Position;
+};
+
+VS_OUTPUT VS(IA_OUTPUT input)
+{
+ VS_OUTPUT output;
+ output.cpoint = input.cpoint;
+ return output;
+}
+
+HS_CONSTANT_OUTPUT HSConst()
+{
+ HS_CONSTANT_OUTPUT output;
+
+ output.edges[0] = 1.0f; // Detail factor
+ output.edges[1] = 64.0f; // Density factor
+
+ return output;
+}
+
+[domain("isoline")]
+[partitioning("integer")]
+[outputtopology("line")]
+[outputcontrolpoints(4)]
+[patchconstantfunc("HSConst")]
+HS_OUTPUT HS(InputPatch<VS_OUTPUT, 4> ip, uint id : SV_OutputControlPointID)
+{
+ HS_OUTPUT output;
+ output.cpoint = ip[id].cpoint;
+ return output;
+}
+
+[domain("isoline")]
+DS_OUTPUT DS(HS_CONSTANT_OUTPUT input, OutputPatch<HS_OUTPUT, 4> op, float2 uv : SV_DomainLocation)
+{
+ DS_OUTPUT output;
+
+ float t = uv.x;
+
+ float3 pos = pow(1.0f - t, 3.0f) * op[0].cpoint + 3.0f * pow(1.0f - t, 2.0f) * t * op[1].cpoint + 3.0f * (1.0f - t) * pow(t, 2.0f) * op[2].cpoint + pow(t, 3.0f) * op[3].cpoint;
+
+ output.position = float4(pos, 1.0f);
+
+ return output;
+}
+
+float4 PS(DS_OUTPUT input) : SV_Target0
+{
+ return float4(0.0f, 0.0f, 0.0f, 1.0f);
+} \ No newline at end of file