From a6f5a420c11be2cf4e89d6bf90f80c588a915182 Mon Sep 17 00:00:00 2001 From: Arthur Vasseur Date: Sat, 15 Aug 2026 13:47:53 +0200 Subject: [PATCH 1/2] Ast/ResolveTransformer: Validate access policy values --- .../Transformations/ResolveTransformer.cpp | 4 +++ src/NZSL/Lang/LangData.hpp | 11 ++++++++ tests/src/Tests/ErrorsTests.cpp | 26 +++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/src/NZSL/Ast/Transformations/ResolveTransformer.cpp b/src/NZSL/Ast/Transformations/ResolveTransformer.cpp index 69d93c87..d64cc85b 100644 --- a/src/NZSL/Ast/Transformations/ResolveTransformer.cpp +++ b/src/NZSL/Ast/Transformations/ResolveTransformer.cpp @@ -889,6 +889,8 @@ namespace nzsl::Ast throw CompilerTextureUnexpectedAccessError{ sourceLocation, "" }; AccessPolicy access = static_cast(std::get(accessValue)); + if (LangData::s_accessPolicies.find(access) == LangData::s_accessPolicies.end()) + throw CompilerTextureUnexpectedAccessError{ sourceLocation, std::to_string(Nz::SafeCast(access)) }; std::optional formatOpt; if (parameterCount >= 3) @@ -932,6 +934,8 @@ namespace nzsl::Ast throw CompilerStorageUnexpectedAccessError{ sourceLocation, "" }; access = static_cast(std::get(accessValue)); + if (LangData::s_accessPolicies.find(access) == LangData::s_accessPolicies.end()) + throw CompilerStorageUnexpectedAccessError{ sourceLocation, std::to_string(Nz::SafeCast(access)) }; } StructType structType = std::get(exprType); diff --git a/src/NZSL/Lang/LangData.hpp b/src/NZSL/Lang/LangData.hpp index cc6f118c..cc467b88 100644 --- a/src/NZSL/Lang/LangData.hpp +++ b/src/NZSL/Lang/LangData.hpp @@ -18,6 +18,17 @@ namespace nzsl::LangData { + struct AccessPolicyData + { + std::string_view identifier; + }; + + constexpr auto s_accessPolicies = frozen::make_unordered_map({ + { AccessPolicy::ReadOnly, { "readonly" } }, + { AccessPolicy::ReadWrite, { "readwrite" } }, + { AccessPolicy::WriteOnly, { "writeonly" } }, + }); + struct AttributeData { std::string_view identifier; diff --git a/tests/src/Tests/ErrorsTests.cpp b/tests/src/Tests/ErrorsTests.cpp index d10699ba..eb9f1466 100644 --- a/tests/src/Tests/ErrorsTests.cpp +++ b/tests/src/Tests/ErrorsTests.cpp @@ -735,6 +735,32 @@ fn main() } )"), "(14,2 -> 18): CIdentifierAlreadyUsed error: identifier Viewer is already used"); + CHECK_THROWS_WITH(Compile(R"( +[nzsl_version("1.1")] +module; + +external +{ + [binding(0)] tex: texture2D[f32, rgba8] +} + +)"), "(7,20 -> 40): CTextureUnexpectedAccess error: texture type require readonly, readwrite or writeonly qualifier (got 38)"); + + CHECK_THROWS_WITH(Compile(R"( +[nzsl_version("1.1")] +module; + +struct Foo +{ +} + +external +{ + [binding(0)] foo: storage[Foo, rgba8] +} + +)"), "(11,20 -> 38): CStorageUnexpectedAccess error: storage type access qualifiers must be readonly, readwrite or writeonly (got 38)"); + } /************************************************************************/ From 1ee9bd44ae6488909f6f97c370f21964c233357e Mon Sep 17 00:00:00 2001 From: Arthur Vasseur Date: Sat, 15 Aug 2026 13:49:07 +0200 Subject: [PATCH 2/2] Add SampleLevel intrinsic for explicit LOD sampling --- include/NZSL/Ast/Enums.hpp | 3 +- include/NZSL/SpirV/SpirvAstVisitor.hpp | 1 + .../ConstantPropagationTransformer.cpp | 1 + .../Transformations/ResolveTransformer.cpp | 3 ++ src/NZSL/GlslWriter.cpp | 1 + src/NZSL/Lang/LangData.hpp | 1 + src/NZSL/LangWriter.cpp | 7 +++ src/NZSL/SpirV/SpirvAstVisitor.cpp | 28 +++++++++++ src/NZSL/SpirV/SpirvGenData.hpp | 1 + tests/src/Tests/ComputeTests.cpp | 48 +++++++++++++++++++ 10 files changed, 93 insertions(+), 1 deletion(-) diff --git a/include/NZSL/Ast/Enums.hpp b/include/NZSL/Ast/Enums.hpp index b1f59ecd..5956e977 100644 --- a/include/NZSL/Ast/Enums.hpp +++ b/include/NZSL/Ast/Enums.hpp @@ -149,7 +149,7 @@ namespace nzsl::Ast enum class IntrinsicType { - // Next free index: 64 + // Next free index: 65 Abs = 31, ArcCos = 21, ArcCosh = 22, @@ -201,6 +201,7 @@ namespace nzsl::Ast Round = 28, RoundEven = 29, TextureRead = 2, + TextureSampleExplicitLod = 64, TextureSampleImplicitLod = 44, TextureSampleImplicitLodDepthComp = 43, TextureWrite = 45, diff --git a/include/NZSL/SpirV/SpirvAstVisitor.hpp b/include/NZSL/SpirV/SpirvAstVisitor.hpp index 7415d42d..e99bfb9b 100644 --- a/include/NZSL/SpirV/SpirvAstVisitor.hpp +++ b/include/NZSL/SpirV/SpirvAstVisitor.hpp @@ -77,6 +77,7 @@ namespace nzsl // Should be private but are referenced in SpirvGenData.hpp void BuildArraySizeIntrinsic(const Ast::IntrinsicExpression& node); void BuildSelectIntrinsic(const Ast::IntrinsicExpression& node); + void BuildTextureSampleExplicitLodIntrinsic(const Ast::IntrinsicExpression& node); static SpirvGlslStd450Op SelectAbs(const Ast::IntrinsicExpression& node); static SpirvGlslStd450Op SelectClamp(const Ast::IntrinsicExpression& node); static SpirvGlslStd450Op SelectLerp(const Ast::IntrinsicExpression& node); diff --git a/src/NZSL/Ast/Transformations/ConstantPropagationTransformer.cpp b/src/NZSL/Ast/Transformations/ConstantPropagationTransformer.cpp index c47f0996..bb0d4bdc 100644 --- a/src/NZSL/Ast/Transformations/ConstantPropagationTransformer.cpp +++ b/src/NZSL/Ast/Transformations/ConstantPropagationTransformer.cpp @@ -674,6 +674,7 @@ namespace nzsl::Ast case IntrinsicType::FwidthFine: case IntrinsicType::TextureRead: case IntrinsicType::TextureWrite: + case IntrinsicType::TextureSampleExplicitLod: case IntrinsicType::TextureSampleImplicitLod: case IntrinsicType::TextureSampleImplicitLodDepthComp: break; diff --git a/src/NZSL/Ast/Transformations/ResolveTransformer.cpp b/src/NZSL/Ast/Transformations/ResolveTransformer.cpp index d64cc85b..0271bfb9 100644 --- a/src/NZSL/Ast/Transformations/ResolveTransformer.cpp +++ b/src/NZSL/Ast/Transformations/ResolveTransformer.cpp @@ -1405,6 +1405,8 @@ namespace nzsl::Ast methodType.methodIndex = 0; else if (identifierEntry.identifier == "SampleDepthComp") methodType.methodIndex = 1; + else if (identifierEntry.identifier == "SampleLevel") + methodType.methodIndex = 2; else throw CompilerUnknownMethodError{ identifierEntry.sourceLocation, ToString(resolvedType, indexedExpr->sourceLocation), identifierEntry.identifier }; @@ -2059,6 +2061,7 @@ namespace nzsl::Ast { case 0: intrinsicType = IntrinsicType::TextureSampleImplicitLod; break; case 1: intrinsicType = IntrinsicType::TextureSampleImplicitLodDepthComp; break; + case 2: intrinsicType = IntrinsicType::TextureSampleExplicitLod; break; default: throw AstInvalidMethodIndexError{ callFuncExpr.sourceLocation, methodType.methodIndex, ToString(objectType, callFuncExpr.sourceLocation) }; } diff --git a/src/NZSL/GlslWriter.cpp b/src/NZSL/GlslWriter.cpp index d02fb1a3..021e0ef5 100644 --- a/src/NZSL/GlslWriter.cpp +++ b/src/NZSL/GlslWriter.cpp @@ -2162,6 +2162,7 @@ namespace nzsl case Ast::IntrinsicType::RoundEven: Append("roundEven"); break; case Ast::IntrinsicType::Sign: Append("sign"); break; case Ast::IntrinsicType::TextureRead: Append("imageLoad"); break; + case Ast::IntrinsicType::TextureSampleExplicitLod: Append("textureLod"); break; case Ast::IntrinsicType::TextureSampleImplicitLod: Append("texture"); break; case Ast::IntrinsicType::TextureWrite: Append("imageStore"); break; case Ast::IntrinsicType::Trunc: Append("trunc"); break; diff --git a/src/NZSL/Lang/LangData.hpp b/src/NZSL/Lang/LangData.hpp index cc467b88..e0e5edaa 100644 --- a/src/NZSL/Lang/LangData.hpp +++ b/src/NZSL/Lang/LangData.hpp @@ -292,6 +292,7 @@ namespace nzsl::LangData { Ast::IntrinsicType::Tan, Build("tan", false, ReturnType::Param0Type, Params{}) }, { Ast::IntrinsicType::Tanh, Build("tanh", false, ReturnType::Param0Type, Params{}) }, { Ast::IntrinsicType::TextureRead, Build("textureRead", true, ReturnType::Param0TextureValue, Params{}) }, + { Ast::IntrinsicType::TextureSampleExplicitLod, Build("textureSampleExplicitLod", true, ReturnType::Param0SampledValue, Params{}) }, { Ast::IntrinsicType::TextureSampleImplicitLod, Build("textureSampleImplicitLod", true, ReturnType::Param0SampledValue, Params{}, ShaderStageType::Fragment) }, { Ast::IntrinsicType::TextureSampleImplicitLodDepthComp, Build("textureSampleImplicitLodDepthComp", true, ReturnType::Param0SampledValue, Params{}, ShaderStageType::Fragment) }, { Ast::IntrinsicType::TextureWrite, Build("textureWrite", true, ReturnType::None, Params{}) }, diff --git a/src/NZSL/LangWriter.cpp b/src/NZSL/LangWriter.cpp index 1a7aab4f..76b08679 100644 --- a/src/NZSL/LangWriter.cpp +++ b/src/NZSL/LangWriter.cpp @@ -1352,6 +1352,13 @@ namespace nzsl method = true; break; + case Ast::IntrinsicType::TextureSampleExplicitLod: + assert(!node.parameters.empty()); + Visit(node.parameters.front(), true); + Append(".SampleLevel"); + method = true; + break; + case Ast::IntrinsicType::TextureSampleImplicitLodDepthComp: assert(!node.parameters.empty()); Visit(node.parameters.front(), true); diff --git a/src/NZSL/SpirV/SpirvAstVisitor.cpp b/src/NZSL/SpirV/SpirvAstVisitor.cpp index 765c54a3..af8bf8db 100644 --- a/src/NZSL/SpirV/SpirvAstVisitor.cpp +++ b/src/NZSL/SpirV/SpirvAstVisitor.cpp @@ -1391,6 +1391,34 @@ namespace nzsl PushResultId(resultId); } + void SpirvAstVisitor::BuildTextureSampleExplicitLodIntrinsic(const Ast::IntrinsicExpression& node) + { + if (node.parameters.size() != 3) + throw std::runtime_error("textureSampleExplicitLod intrinsic: unexpected parameter count"); + + std::uint32_t resultTypeId = m_writer.GetTypeId(ResolveAlias(EnsureExpressionType(node))); + + std::uint32_t samplerId = EvaluateExpression(*node.parameters[0]); + std::uint32_t coordinatesId = EvaluateExpression(*node.parameters[1]); + std::uint32_t lodId = EvaluateExpression(*node.parameters[2]); + + HandleSourceLocation(node.sourceLocation); + + std::uint32_t resultId = m_writer.AllocateResultId(); + + m_currentBlock->AppendVariadic(SpirvOp::OpImageSampleExplicitLod, [&](auto&& append) + { + append(resultTypeId); + append(resultId); + append(samplerId); + append(coordinatesId); + append(SpirvImageOperands::Lod); + append(lodId); + }); + + PushResultId(resultId); + } + SpirvGlslStd450Op SpirvAstVisitor::SelectAbs(const Ast::IntrinsicExpression& node) { if (node.parameters.size() != 1) diff --git a/src/NZSL/SpirV/SpirvGenData.hpp b/src/NZSL/SpirV/SpirvGenData.hpp index a825f677..b6b9cff4 100644 --- a/src/NZSL/SpirV/SpirvGenData.hpp +++ b/src/NZSL/SpirV/SpirvGenData.hpp @@ -138,6 +138,7 @@ NAZARA_WARNING_CLANG_GCC_DISABLE("-Wmissing-field-initializers") { Ast::IntrinsicType::Tan, { SpirvGlslStd450Op::Tan } }, { Ast::IntrinsicType::Tanh, { SpirvGlslStd450Op::Tanh } }, { Ast::IntrinsicType::TextureRead, { SpirvOp::OpImageRead } }, + { Ast::IntrinsicType::TextureSampleExplicitLod, { &SpirvAstVisitor::BuildTextureSampleExplicitLodIntrinsic } }, { Ast::IntrinsicType::TextureSampleImplicitLod, { SpirvOp::OpImageSampleImplicitLod } }, { Ast::IntrinsicType::TextureSampleImplicitLodDepthComp, { SpirvOp::OpImageSampleDrefImplicitLod } }, { Ast::IntrinsicType::TextureWrite, { SpirvOp::OpImageWrite } }, diff --git a/tests/src/Tests/ComputeTests.cpp b/tests/src/Tests/ComputeTests.cpp index a8d8cf9b..8a16cfe8 100644 --- a/tests/src/Tests/ComputeTests.cpp +++ b/tests/src/Tests/ComputeTests.cpp @@ -286,4 +286,52 @@ layout(rgba8_snorm) uniform writeonly image2D tex_rgba8_snorm; %6 = OpTypePointer StorageClass(UniformConstant) %5 %8 = OpTypeImage %1 Dim(Dim2D) 2 0 0 2 ImageFormat(Rgba8Snorm))", {}, {}, true); } + + SECTION("Explicit LOD sampling") + { + std::string_view nzslSource = R"( +[nzsl_version("1.1")] +module; + +[auto_binding] +external +{ + tex: sampler2D[f32], + output_tex: texture2D[f32, writeonly, rgba8] +} + +struct Input +{ + [builtin(global_invocation_indices)] indices: vec3[u32] +} + +[entry(compute)] +[workgroup(8, 8, 1)] +fn main(input: Input) +{ + let coords = vec2[i32](input.indices.xy); + let value = tex.SampleLevel(vec2[f32](0.5, 0.5), 0.0); + output_tex.Write(coords, value); +} +)"; + + nzsl::Ast::ModulePtr shaderModule = nzsl::Parse(nzslSource); + ResolveModule(*shaderModule); + + nzsl::GlslWriter::Environment glslEnv; + glslEnv.glES = true; + glslEnv.glMajorVersion = 3; + glslEnv.glMinorVersion = 1; + + ExpectGLSL(*shaderModule, R"( + vec4 value = textureLod(tex, vec2(0.5, 0.5), 0.0); +)", {}, glslEnv); + + ExpectNZSL(*shaderModule, R"( + let value: vec4[f32] = tex.SampleLevel(vec2[f32](0.5, 0.5), 0.0); +)"); + + ExpectSPIRV(*shaderModule, R"( +OpImageSampleExplicitLod %27 %39 %40 ImageOperands(2) ImageOperands(26))", {}, {}, true); + } }