// slang-emit-glsl.cpp #include "slang-emit-glsl.h" #include "../core/slang-writer.h" #include "slang-emit-source-writer.h" #include "slang-mangled-lexer.h" #include "slang-legalize-types.h" #include namespace Slang { GLSLSourceEmitter::GLSLSourceEmitter(const Desc& desc) : Super(desc) { m_glslExtensionTracker = dynamicCast(desc.extensionTracker); SLANG_ASSERT(m_glslExtensionTracker); } SlangResult GLSLSourceEmitter::init() { SLANG_RETURN_ON_FAIL(Super::init()); // Deal with cases where a particular stage requires certain GLSL versions // and/or extensions. switch (m_entryPointStage) { case Stage::AnyHit: case Stage::Callable: case Stage::ClosestHit: case Stage::Intersection: case Stage::Miss: case Stage::RayGeneration: { _requireRayTracing(); break; } default: break; } return SLANG_OK; } void GLSLSourceEmitter::_requireRayTracing() { // There is more than one extension that provides ray-tracing capabilities, // and we need to pick which one to enable. // // By default, we will use the `GL_EXT_ray_tracing` extension, but if // the user has explicitly opted in to the `GL_NV_ray_tracing` extension // we will use that one instead. // if( getTargetCaps().implies(CapabilityAtom::GL_NV_ray_tracing) ) { m_glslExtensionTracker->requireExtension(UnownedStringSlice::fromLiteral("GL_NV_ray_tracing")); } else { m_glslExtensionTracker->requireExtension(UnownedStringSlice::fromLiteral("GL_EXT_ray_tracing")); m_glslExtensionTracker->requireSPIRVVersion(SemanticVersion(1, 4)); } m_glslExtensionTracker->requireVersion(ProfileVersion::GLSL_460); } void GLSLSourceEmitter::_requireGLSLExtension(const UnownedStringSlice& name) { m_glslExtensionTracker->requireExtension(name); } void GLSLSourceEmitter::_requireGLSLVersion(ProfileVersion version) { if (getSourceLanguage() != SourceLanguage::GLSL) return; m_glslExtensionTracker->requireVersion(version); } void GLSLSourceEmitter::_requireSPIRVVersion(const SemanticVersion& version) { m_glslExtensionTracker->requireSPIRVVersion(version); } void GLSLSourceEmitter::_requireGLSLVersion(int version) { switch (version) { #define CASE(NUMBER) \ case NUMBER: _requireGLSLVersion(ProfileVersion::GLSL_##NUMBER); break CASE(110); CASE(120); CASE(130); CASE(140); CASE(150); CASE(330); CASE(400); CASE(410); CASE(420); CASE(430); CASE(440); CASE(450); CASE(460); #undef CASE } } void GLSLSourceEmitter::_emitGLSLStructuredBuffer(IRGlobalParam* varDecl, IRHLSLStructuredBufferTypeBase* structuredBufferType) { // Shader storage buffer is an OpenGL 430 feature // // TODO: we should require either the extension or the version... _requireGLSLVersion(430); m_writer->emit("layout(std430"); auto layout = getVarLayout(varDecl); if (layout) { LayoutResourceKind kind = LayoutResourceKind::DescriptorTableSlot; EmitVarChain chain(layout); const UInt index = getBindingOffset(&chain, kind); const UInt space = getBindingSpace(&chain, kind); m_writer->emit(", binding = "); m_writer->emit(index); if (space) { m_writer->emit(", set = "); m_writer->emit(space); } } m_writer->emit(") "); /* If the output type is a buffer, and we can determine it is only readonly we can prefix before buffer with 'readonly' The actual structuredBufferType could be HLSLStructuredBufferType - This is unambiguously read only HLSLRWStructuredBufferType - Read write HLSLRasterizerOrderedStructuredBufferType - Allows read/write access HLSLAppendStructuredBufferType - Write HLSLConsumeStructuredBufferType - TODO (JS): Its possible that this can be readonly, but we currently don't support on GLSL */ if (as(structuredBufferType)) { m_writer->emit("readonly "); } m_writer->emit("buffer "); // Generate a dummy name for the block m_writer->emit("_S"); m_writer->emit(m_uniqueIDCounter++); m_writer->emit(" {\n"); m_writer->indent(); auto elementType = structuredBufferType->getElementType(); emitType(elementType, "_data[]"); m_writer->emit(";\n"); m_writer->dedent(); m_writer->emit("} "); m_writer->emit(getName(varDecl)); emitArrayBrackets(varDecl->getDataType()); m_writer->emit(";\n"); } void GLSLSourceEmitter::_emitGLSLByteAddressBuffer(IRGlobalParam* varDecl, IRByteAddressBufferTypeBase* byteAddressBufferType) { // TODO: A lot of this logic is copy-pasted from `emitIRStructuredBuffer_GLSL`. // It might be worthwhile to share the common code to avoid regressions sneaking // in when one or the other, but not both, gets updated. // Shader storage buffer is an OpenGL 430 feature // // TODO: we should require either the extension or the version... _requireGLSLVersion(430); m_writer->emit("layout(std430"); auto layout = getVarLayout(varDecl); if (layout) { LayoutResourceKind kind = LayoutResourceKind::DescriptorTableSlot; EmitVarChain chain(layout); const UInt index = getBindingOffset(&chain, kind); const UInt space = getBindingSpace(&chain, kind); m_writer->emit(", binding = "); m_writer->emit(index); if (space) { m_writer->emit(", set = "); m_writer->emit(space); } } m_writer->emit(") "); /* If the output type is a buffer, and we can determine it is only readonly we can prefix before buffer with 'readonly' HLSLByteAddressBufferType - This is unambiguously read only HLSLRWByteAddressBufferType - Read write HLSLRasterizerOrderedByteAddressBufferType - Allows read/write access */ if (as(byteAddressBufferType)) { m_writer->emit("readonly "); } m_writer->emit("buffer "); // Generate a dummy name for the block m_writer->emit("_S"); m_writer->emit(m_uniqueIDCounter++); m_writer->emit("\n{\n"); m_writer->indent(); m_writer->emit("uint _data[];\n"); m_writer->dedent(); m_writer->emit("} "); m_writer->emit(getName(varDecl)); emitArrayBrackets(varDecl->getDataType()); m_writer->emit(";\n"); } void GLSLSourceEmitter::_emitGLSLParameterGroup(IRGlobalParam* varDecl, IRUniformParameterGroupType* type) { auto varLayout = getVarLayout(varDecl); SLANG_RELEASE_ASSERT(varLayout); EmitVarChain blockChain(varLayout); EmitVarChain containerChain = blockChain; EmitVarChain elementChain = blockChain; auto typeLayout = varLayout->getTypeLayout()->unwrapArray(); if (auto parameterGroupTypeLayout = as(typeLayout)) { containerChain = EmitVarChain(parameterGroupTypeLayout->getContainerVarLayout(), &blockChain); elementChain = EmitVarChain(parameterGroupTypeLayout->getElementVarLayout(), &blockChain); typeLayout = parameterGroupTypeLayout->getElementVarLayout()->getTypeLayout(); } /* With resources backed by 'buffer' on glsl, we want to output 'readonly' if that is a good match for the underlying type. If uniform it's implicit it's readonly Here this only happens with isShaderRecord which is a 'constant buffer' (ie implicitly readonly) or IRGLSLShaderStorageBufferType which is read write. */ _emitGLSLLayoutQualifier(LayoutResourceKind::DescriptorTableSlot, &containerChain); _emitGLSLLayoutQualifier(LayoutResourceKind::PushConstantBuffer, &containerChain); bool isShaderRecord = _emitGLSLLayoutQualifier(LayoutResourceKind::ShaderRecord, &containerChain); if (isShaderRecord) { // TODO: A shader record in vk can be potentially read-write. Currently slang doesn't support write access // and readonly buffer generates SPIRV validation error. m_writer->emit("buffer "); } else if (as(type)) { // Is writable m_writer->emit("layout(std430) buffer "); } // TODO: what to do with HLSL `tbuffer` style buffers? else { // uniform is implicitly read only m_writer->emit("layout(std140) uniform "); } // Generate a dummy name for the block m_writer->emit("_S"); m_writer->emit(m_uniqueIDCounter++); m_writer->emit("\n{\n"); m_writer->indent(); auto elementType = type->getElementType(); emitType(elementType, "_data"); m_writer->emit(";\n"); m_writer->dedent(); m_writer->emit("} "); m_writer->emit(getName(varDecl)); // If the underlying variable was an array (or array of arrays, etc.) // we need to emit all those array brackets here. emitArrayBrackets(varDecl->getDataType()); m_writer->emit(";\n"); } void GLSLSourceEmitter::_emitGLSLImageFormatModifier(IRInst* var, IRTextureType* resourceType) { // If the user specified a format manually, using `[format(...)]`, // then we will respect that format and emit a matching `layout` modifier. // if (auto formatDecoration = var->findDecoration()) { auto format = formatDecoration->getFormat(); if (format == ImageFormat::unknown) { // If the user explicitly opts out of having a format, then // the output shader will require the extension to support // load/store from format-less images. // // TODO: We should have a validation somewhere in the compiler // that atomic operations are only allowed on images with // explicit formats (and then only on specific formats). // This is really an argument that format should be part of // the image *type* (with a "base type" for images with // unknown format). // _requireGLSLExtension(UnownedStringSlice::fromLiteral("GL_EXT_shader_image_load_formatted")); } else { // If there is an explicit format specified, then we // should emit a `layout` modifier using the GLSL name // for the format. // m_writer->emit("layout("); m_writer->emit(getGLSLNameForImageFormat(format)); m_writer->emit(")\n"); } // No matter what, if an explicit `[format(...)]` was given, // then we don't need to emit anything else. // return; } // When no explicit format is specified, we need to either // emit the image as having an unknown format, or else infer // a format from the type. // // For now our default behavior is to infer (so that unmodified // HLSL input is more likely to generate valid SPIR-V that // runs anywhere), but we provide a flag to opt into // treating images without explicit formats as having // unknown format. // if (m_compileRequest->useUnknownImageFormatAsDefault) { _requireGLSLExtension(UnownedStringSlice::fromLiteral("GL_EXT_shader_image_load_formatted")); return; } // At this point we have a resource type like `RWTexture2D` // and we want to infer a reasonable format from the element // type `X` that was specified. // // E.g., if `X` is `float` then we can infer a format like `r32f`, // and so forth. The catch of course is that it is possible to // specify a shader parameter with a type like `RWTexture2D` but // provide an image at runtime with a format like `rgba8`, so // this inference is never guaranteed to give perfect results. // // If users don't like our inferred result, they need to use a // `[format(...)]` attribute to manually specify what they want. // // TODO: We should consider whether we can expand the space of // allowed types for `X` in `RWTexture2D` to include special // pseudo-types that act just like, e.g., `float4`, but come // with attached/implied format information. // auto elementType = resourceType->getElementType(); Int vectorWidth = 1; if (auto elementVecType = as(elementType)) { if (auto intLitVal = as(elementVecType->getElementCount())) { vectorWidth = (Int)intLitVal->getValue(); } else { vectorWidth = 0; } elementType = elementVecType->getElementType(); } if (auto elementBasicType = as(elementType)) { m_writer->emit("layout("); switch (vectorWidth) { default: m_writer->emit("rgba"); break; case 3: { // TODO: GLSL doesn't support 3-component formats so for now we are going to // default to rgba // // The SPIR-V spec (https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.pdf) // section 3.11 on Image Formats it does not list rgbf32. // // It seems SPIR-V can support having an image with an unknown-at-compile-time // format, so long as the underlying API supports it. Ideally this would mean that we can // just drop all these qualifiers when emitting GLSL for Vulkan targets. // // This raises the question of what to do more long term. For Vulkan hopefully we can just // drop the layout. For OpenGL targets it would seem reasonable to have well-defined rules // for inferring the format (and just document that 3-component formats map to 4-component formats, // but that shouldn't matter because the API wouldn't let the user allocate those 3-component formats anyway), // and add an attribute for specifying the format manually if you really want to override our // inference (e.g., to specify r11fg11fb10f). m_writer->emit("rgba"); //Emit("rgb"); break; } case 2: m_writer->emit("rg"); break; case 1: m_writer->emit("r"); break; } switch (elementBasicType->getBaseType()) { default: case BaseType::Float: m_writer->emit("32f"); break; case BaseType::Half: m_writer->emit("16f"); break; case BaseType::UInt: m_writer->emit("32ui"); break; case BaseType::Int: m_writer->emit("32i"); break; case BaseType::Int8: m_writer->emit("8i"); break; case BaseType::Int16: m_writer->emit("16i"); break; case BaseType::Int64: m_writer->emit("64i"); break; case BaseType::UInt8: m_writer->emit("8ui"); break; case BaseType::UInt16: m_writer->emit("16ui"); break; case BaseType::UInt64: m_writer->emit("64ui"); break; // TODO: Here are formats that are available in GLSL, // but that are not handled by the above cases. // // r11f_g11f_b10f // // rgba16 // rgb10_a2 // rgba8 // rg16 // rg8 // r16 // r8 // // rgba16_snorm // rgba8_snorm // rg16_snorm // rg8_snorm // r16_snorm // r8_snorm // // rgb10_a2ui } m_writer->emit(")\n"); } } bool GLSLSourceEmitter::_emitGLSLLayoutQualifier(LayoutResourceKind kind, EmitVarChain* chain) { if (!chain) return false; if (!chain->varLayout->findOffsetAttr(kind)) return false; UInt index = getBindingOffset(chain, kind); UInt space = getBindingSpace(chain, kind); switch (kind) { case LayoutResourceKind::Uniform: { // Explicit offsets require a GLSL extension (which // is not universally supported, it seems) or a new // enough GLSL version (which we don't want to // universally require), so for right now we // won't actually output explicit offsets for uniform // shader parameters. // // TODO: We should fix this so that we skip any // extra work for parameters that are laid out as // expected by the default rules, but do *something* // for parameters that need non-default layout. // // Using the `GL_ARB_enhanced_layouts` feature is one // option, but we should also be able to do some // things by introducing padding into the declaration // (padding insertion would probably be best done at // the IR level). bool useExplicitOffsets = false; if (useExplicitOffsets) { _requireGLSLExtension(UnownedStringSlice::fromLiteral("GL_ARB_enhanced_layouts")); m_writer->emit("layout(offset = "); m_writer->emit(index); m_writer->emit(")\n"); } } break; case LayoutResourceKind::VaryingInput: case LayoutResourceKind::VaryingOutput: m_writer->emit("layout(location = "); m_writer->emit(index); if( space ) { m_writer->emit(", index = "); m_writer->emit(space); } m_writer->emit(")\n"); break; case LayoutResourceKind::SpecializationConstant: m_writer->emit("layout(constant_id = "); m_writer->emit(index); m_writer->emit(")\n"); break; case LayoutResourceKind::ConstantBuffer: case LayoutResourceKind::ShaderResource: case LayoutResourceKind::UnorderedAccess: case LayoutResourceKind::SamplerState: case LayoutResourceKind::DescriptorTableSlot: m_writer->emit("layout(binding = "); m_writer->emit(index); if (space) { m_writer->emit(", set = "); m_writer->emit(space); } m_writer->emit(")\n"); break; case LayoutResourceKind::PushConstantBuffer: m_writer->emit("layout(push_constant)\n"); break; case LayoutResourceKind::ShaderRecord: if( getTargetCaps().implies(CapabilityAtom::GL_NV_ray_tracing) ) { m_writer->emit("layout(shaderRecordNV)\n"); } else { m_writer->emit("layout(shaderRecordEXT)\n"); } break; } return true; } void GLSLSourceEmitter::_emitGLSLLayoutQualifiers(IRVarLayout* layout, EmitVarChain* inChain, LayoutResourceKind filter) { if (!layout) return; switch (getSourceLanguage()) { default: return; case SourceLanguage::GLSL: break; } EmitVarChain chain(layout, inChain); for (auto info : layout->getOffsetAttrs()) { // Skip info that doesn't match our filter if (filter != LayoutResourceKind::None && filter != info->getResourceKind()) { continue; } _emitGLSLLayoutQualifier(info->getResourceKind(), &chain); } } void GLSLSourceEmitter::_emitGLSLTextureOrTextureSamplerType(IRTextureTypeBase* type, char const* baseName) { if (type->getElementType()->getOp() == kIROp_HalfType) { // Texture access is always as float types if half is specified } else { _emitGLSLTypePrefix(type->getElementType(), true); } m_writer->emit(baseName); switch (type->GetBaseShape()) { case TextureFlavor::Shape::Shape1D: m_writer->emit("1D"); break; case TextureFlavor::Shape::Shape2D: m_writer->emit("2D"); break; case TextureFlavor::Shape::Shape3D: m_writer->emit("3D"); break; case TextureFlavor::Shape::ShapeCube: m_writer->emit("Cube"); break; case TextureFlavor::Shape::ShapeBuffer: m_writer->emit("Buffer"); break; default: SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled resource shape"); break; } if (type->isMultisample()) { m_writer->emit("MS"); } if (type->isArray()) { m_writer->emit("Array"); } } void GLSLSourceEmitter::_emitGLSLTypePrefix(IRType* type, bool promoteHalfToFloat) { switch (type->getOp()) { case kIROp_FloatType: // no prefix break; case kIROp_Int8Type: m_writer->emit("i8"); break; case kIROp_Int16Type: m_writer->emit("i16"); break; case kIROp_IntType: m_writer->emit("i"); break; case kIROp_Int64Type: { _requireBaseType(BaseType::Int64); m_writer->emit("i64"); break; } case kIROp_UInt8Type: m_writer->emit("u8"); break; case kIROp_UInt16Type: m_writer->emit("u16"); break; case kIROp_UIntType: m_writer->emit("u"); break; case kIROp_UInt64Type: { _requireBaseType(BaseType::UInt64); m_writer->emit("u64"); break; } case kIROp_BoolType: m_writer->emit("b"); break; case kIROp_HalfType: { _requireBaseType(BaseType::Half); if (promoteHalfToFloat) { // no prefix } else { m_writer->emit("f16"); } break; } case kIROp_DoubleType: m_writer->emit("d"); break; case kIROp_VectorType: _emitGLSLTypePrefix(cast(type)->getElementType(), promoteHalfToFloat); break; case kIROp_MatrixType: _emitGLSLTypePrefix(cast(type)->getElementType(), promoteHalfToFloat); break; default: SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled GLSL type prefix"); break; } } void GLSLSourceEmitter::_requireBaseType(BaseType baseType) { m_glslExtensionTracker->requireBaseTypeExtension(baseType); } void GLSLSourceEmitter::_maybeEmitGLSLFlatModifier(IRType* valueType) { auto tt = valueType; if (auto vecType = as(tt)) tt = vecType->getElementType(); if (auto vecType = as(tt)) tt = vecType->getElementType(); switch (tt->getOp()) { default: break; case kIROp_IntType: case kIROp_UIntType: case kIROp_UInt64Type: m_writer->emit("flat "); break; } } void GLSLSourceEmitter::emitLoopControlDecorationImpl(IRLoopControlDecoration* decl) { if (decl->getMode() == kIRLoopControl_Unroll) { // https://github.com/KhronosGroup/GLSL/blob/master/extensions/ext/GL_EXT_control_flow_attributes.txt m_glslExtensionTracker->requireExtension(UnownedStringSlice::fromLiteral("GL_EXT_control_flow_attributes")); m_writer->emit("[[unroll]]\n"); } else if (decl->getMode() == kIRLoopControl_Loop) { m_glslExtensionTracker->requireExtension(UnownedStringSlice::fromLiteral("GL_EXT_control_flow_attributes")); m_writer->emit("[[dont_unroll]]\n"); } } void GLSLSourceEmitter::_emitSpecialFloatImpl(IRType* type, const char* valueExpr) { if( type->getOp() != kIROp_FloatType ) { emitType(type); } m_writer->emit("("); m_writer->emit(valueExpr); m_writer->emit(")"); } void GLSLSourceEmitter::emitSimpleValueImpl(IRInst* inst) { switch (inst->getOp()) { case kIROp_IntLit: { auto litInst = static_cast(inst); IRBasicType* type = as(inst->getDataType()); if (type) { switch (type->getBaseType()) { default: case BaseType::Int8: { emitType(type); m_writer->emit("("); m_writer->emit(litInst->value.intVal); m_writer->emit(")"); return; } case BaseType::Int16: { m_writer->emit(litInst->value.intVal); m_writer->emit("S"); return; } case BaseType::Int: { m_writer->emit(litInst->value.intVal); return; } case BaseType::UInt8: { emitType(type); m_writer->emit("("); m_writer->emit(UInt(litInst->value.intVal)); m_writer->emit("U)"); return; } case BaseType::UInt16: { m_writer->emit(UInt(litInst->value.intVal)); m_writer->emit("US"); return; } case BaseType::UInt: { m_writer->emit(UInt(litInst->value.intVal)); m_writer->emit("U"); return; } case BaseType::Int64: { m_writer->emitInt64(int64_t(litInst->value.intVal)); m_writer->emit("L"); return; } case BaseType::UInt64: { SLANG_COMPILE_TIME_ASSERT(sizeof(litInst->value.intVal) >= sizeof(uint64_t)); m_writer->emitUInt64(uint64_t(litInst->value.intVal)); m_writer->emit("UL"); return; } } } break; } case kIROp_FloatLit: { IRConstant* constantInst = static_cast(inst); auto type = constantInst->getDataType(); IRConstant::FloatKind kind = constantInst->getFloatKind(); switch (kind) { case IRConstant::FloatKind::Nan: { _emitSpecialFloatImpl(type, "0.0 / 0.0"); return; } case IRConstant::FloatKind::PositiveInfinity: { _emitSpecialFloatImpl(type, "1.0 / 0.0"); return; } case IRConstant::FloatKind::NegativeInfinity: { _emitSpecialFloatImpl(type, "-1.0 / 0.0"); return; } default: { m_writer->emit(((IRConstant*) inst)->value.floatVal); switch( type->getOp() ) { case kIROp_HalfType: m_writer->emit("HF"); break; case kIROp_DoubleType: m_writer->emit("LF"); break; default: break; } return; } } break; } default: break; } Super::emitSimpleValueImpl(inst); } void GLSLSourceEmitter::emitParameterGroupImpl(IRGlobalParam* varDecl, IRUniformParameterGroupType* type) { _emitGLSLParameterGroup(varDecl, type); } void GLSLSourceEmitter::emitEntryPointAttributesImpl(IRFunc* irFunc, IREntryPointDecoration* entryPointDecor) { SLANG_ASSERT(entryPointDecor); auto profile = entryPointDecor->getProfile(); auto stage = profile.getStage(); switch (stage) { case Stage::Compute: { Int sizeAlongAxis[kThreadGroupAxisCount]; getComputeThreadGroupSize(irFunc, sizeAlongAxis); m_writer->emit("layout("); char const* axes[] = { "x", "y", "z" }; for (int ii = 0; ii < kThreadGroupAxisCount; ++ii) { if (ii != 0) m_writer->emit(", "); m_writer->emit("local_size_"); m_writer->emit(axes[ii]); m_writer->emit(" = "); m_writer->emit(sizeAlongAxis[ii]); } m_writer->emit(") in;\n"); } break; case Stage::Geometry: { if (auto decor = irFunc->findDecoration()) { auto count = getIntVal(decor->getCount()); m_writer->emit("layout(max_vertices = "); m_writer->emit(Int(count)); m_writer->emit(") out;\n"); } if (auto decor = irFunc->findDecoration()) { auto count = getIntVal(decor->getCount()); m_writer->emit("layout(invocations = "); m_writer->emit(Int(count)); m_writer->emit(") in;\n"); } // These decorations were moved from the parameters to the entry point by ir-glsl-legalize. // The actual parameters have become potentially multiple global parameters. if (auto decor = irFunc->findDecoration()) { switch (decor->getOp()) { case kIROp_TriangleInputPrimitiveTypeDecoration: m_writer->emit("layout(triangles) in;\n"); break; case kIROp_LineInputPrimitiveTypeDecoration: m_writer->emit("layout(lines) in;\n"); break; case kIROp_LineAdjInputPrimitiveTypeDecoration: m_writer->emit("layout(lines_adjacency) in;\n"); break; case kIROp_PointInputPrimitiveTypeDecoration: m_writer->emit("layout(points) in;\n"); break; case kIROp_TriangleAdjInputPrimitiveTypeDecoration: m_writer->emit("layout(triangles_adjacency) in;\n"); break; default: { SLANG_ASSERT(!"Unknown primitive type"); } } } if (auto decor = irFunc->findDecoration()) { IRType* type = decor->getStreamType(); switch (type->getOp()) { case kIROp_HLSLPointStreamType: m_writer->emit("layout(points) out;\n"); break; case kIROp_HLSLLineStreamType: m_writer->emit("layout(line_strip) out;\n"); break; case kIROp_HLSLTriangleStreamType: m_writer->emit("layout(triangle_strip) out;\n"); break; default: SLANG_ASSERT(!"Unknown stream out type"); } } } break; case Stage::Pixel: { if (irFunc->findDecoration()) { // https://www.khronos.org/opengl/wiki/Early_Fragment_Test m_writer->emit("layout(early_fragment_tests) in;\n"); } break; } // TODO: There are other stages that will need this kind of handling. default: break; } } void GLSLSourceEmitter::_emitGLSLPerVertexVaryingFragmentInput(IRGlobalParam* param, IRType* type) { // Note: The logic here is almost identical to the default // emit logic for global shader parameters. The main difference // is that we emit a parameter of type `X` as an array of // type `X[3]` to account for the per-vertex-ness of the // parameter. // // Need to emit appropriate modifiers here. // We expect/require all shader parameters to // have some kind of layout information associated with them. // auto layout = getVarLayout(param); SLANG_ASSERT(layout); emitVarModifiers(layout, param, type); emitRateQualifiers(param); auto name = getName(param); StringSliceLoc nameAndLoc(name.getUnownedSlice()); NameDeclaratorInfo nameDeclarator(&nameAndLoc); LiteralSizedArrayDeclaratorInfo arrayDeclarator(&nameDeclarator, 3); // Note: We are invoking `_emitType` here directly because there // is no overload of `emitType` that works with a declarator. // _emitType(type, &arrayDeclarator); emitSemantics(param); emitLayoutSemantics(param); m_writer->emit(";\n\n"); } bool GLSLSourceEmitter::tryEmitGlobalParamImpl(IRGlobalParam* varDecl, IRType* varType) { // There are a number of types that are (or can be) // "first-class" in D3D HLSL, but are second-class in GLSL in // that they require explicit global declarations for each value/object, // and don't support declaration as ordinary variables. // // This includes constant buffers (`uniform` blocks) and well as // structured and byte-address buffers (both mapping to `buffer` blocks). // // We intercept these types, and arrays thereof, to produce the required // global declarations. This assumes that earlier "legalization" passes // already performed the work of pulling fields with these types out of // aggregates. // // Note: this also assumes that these types are not used as function // parameters/results, local variables, etc. Additional legalization // steps are required to guarantee these conditions. // if (auto paramBlockType = as(unwrapArray(varType))) { _emitGLSLParameterGroup(varDecl, paramBlockType); return true; } if (auto structuredBufferType = as(unwrapArray(varType))) { _emitGLSLStructuredBuffer(varDecl, structuredBufferType); return true; } if (auto byteAddressBufferType = as(unwrapArray(varType))) { _emitGLSLByteAddressBuffer(varDecl, byteAddressBufferType); return true; } // We want to skip the declaration of any system-value variables // when outputting GLSL (well, except in the case where they // actually *require* redeclaration...). // // Note: these won't be variables the user declare explicitly // in their code, but rather variables that we generated as // part of legalizing the varying input/output signature of // an entry point for GL/Vulkan. // // TODO: This could be handled more robustly by attaching an // appropriate decoration to these variables to indicate their // purpose. // if (auto linkageDecoration = varDecl->findDecoration()) { if (linkageDecoration->getMangledName().startsWith("gl_")) { // The variable represents an OpenGL system value, // so we will assume that it doesn't need to be declared. // // TODO: handle case where we *should* declare the variable. return true; } } // When emitting unbounded-size resource arrays with GLSL we need // to use the `GL_EXT_nonuniform_qualifier` extension to ensure // that they are not treated as "implicitly-sized arrays" which // are arrays that have a fixed size that just isn't specified // at the declaration site (instead being inferred from use sites). // // While the extension primarily introduces the `nonuniformEXT` // qualifier that we use to implement `NonUniformResourceIndex`, // it also changes the GLSL language semantics around (resource) array // declarations that don't specify a size. // if (as(varType)) { if (isResourceType(unwrapArray(varType))) { _requireGLSLExtension(UnownedStringSlice::fromLiteral("GL_EXT_nonuniform_qualifier")); } } // A varying fragment input parameter with the `pervertex` modifier // needs to be emitted as an array. // if( auto interpolationModeDecor = varDecl->findDecoration() ) { if( interpolationModeDecor->getMode() == IRInterpolationMode::PerVertex ) { if( m_entryPointStage == Stage::Fragment ) { _emitGLSLPerVertexVaryingFragmentInput(varDecl, varType); return true; } } } // Do the default thing return false; } void GLSLSourceEmitter::emitImageFormatModifierImpl(IRInst* varDecl, IRType* varType) { // As a special case, if we are emitting a GLSL declaration // for an HLSL `RWTexture*` then we need to emit a `format` layout qualifier. if(auto resourceType = as(unwrapArray(varType))) { switch (resourceType->getAccess()) { case SLANG_RESOURCE_ACCESS_READ_WRITE: case SLANG_RESOURCE_ACCESS_RASTER_ORDERED: { _emitGLSLImageFormatModifier(varDecl, resourceType); } break; default: break; } } } void GLSLSourceEmitter::emitLayoutQualifiersImpl(IRVarLayout* layout) { // Layout-related modifiers need to come before the declaration, // so deal with them here. _emitGLSLLayoutQualifiers(layout, nullptr); // try to emit an appropriate leading qualifier for (auto rr : layout->getOffsetAttrs()) { switch (rr->getResourceKind()) { case LayoutResourceKind::Uniform: case LayoutResourceKind::ShaderResource: case LayoutResourceKind::DescriptorTableSlot: m_writer->emit("uniform "); break; case LayoutResourceKind::VaryingInput: { m_writer->emit("in "); } break; case LayoutResourceKind::VaryingOutput: { m_writer->emit("out "); } break; case LayoutResourceKind::RayPayload: { if( getTargetCaps().implies(CapabilityAtom::GL_NV_ray_tracing) ) { m_writer->emit("rayPayloadInNV "); } else { m_writer->emit("rayPayloadInEXT "); } } break; case LayoutResourceKind::CallablePayload: { if( getTargetCaps().implies(CapabilityAtom::GL_NV_ray_tracing) ) { m_writer->emit("callableDataInNV "); } else { m_writer->emit("callableDataInEXT "); } } break; case LayoutResourceKind::HitAttributes: { if( getTargetCaps().implies(CapabilityAtom::GL_NV_ray_tracing) ) { m_writer->emit("hitAttributeNV "); } else { m_writer->emit("hitAttributeEXT "); } } break; default: continue; } break; } } static const char* _getGLSLVectorCompareFunctionName(IROp op) { // Glsl vector comparisons use functions... // https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/equal.xhtml switch (op) { case kIROp_Eql: return "equal"; case kIROp_Neq: return "notEqual"; case kIROp_Greater: return "greaterThan"; case kIROp_Less: return "lessThan"; case kIROp_Geq: return "greaterThanEqual"; case kIROp_Leq: return "lessThanEqual"; default: return nullptr; } } void GLSLSourceEmitter::_maybeEmitGLSLCast(IRType* castType, IRInst* inst) { // Wrap in cast if a cast type is specified if (castType) { emitType(castType); m_writer->emit("("); // Emit the operand emitOperand(inst, getInfo(EmitOp::General)); m_writer->emit(")"); } else { // Emit the operand emitOperand(inst, getInfo(EmitOp::General)); } } void GLSLSourceEmitter::_emitLegalizedBoolVectorBinOp(IRInst* inst, IRVectorType* type, const EmitOpInfo& op, const EmitOpInfo& inOuterPrec) { auto elementCount = type->getElementCount(); EmitOpInfo outerPrec = inOuterPrec; auto prec = getInfo(EmitOp::Postfix); bool needClose = maybeEmitParens(outerPrec, prec); emitType(type); m_writer->emit("(uvec"); emitSimpleValue(elementCount); m_writer->emit("("); emitOperand(inst->getOperand(0), getInfo(EmitOp::General)); m_writer->emit(")"); m_writer->emit(op.op); m_writer->emit("uvec"); emitSimpleValue(elementCount); m_writer->emit("("); emitOperand(inst->getOperand(1), getInfo(EmitOp::General)); m_writer->emit("))"); maybeCloseParens(needClose); } bool GLSLSourceEmitter::_tryEmitLogicalBinOp(IRInst* inst, const EmitOpInfo& bitOp, const EmitOpInfo& inOuterPrec) { // Logical operation on scalar `bool` values are directly // supported by GLSL. They have short-circuiting behavior, // but we need not worry about that because our logic // for folding sub-expressions into their use sites will // never fold a sub-expression that would have side effects. // // Thus we fall back to the default handling for scalar // cases (which should only arise for `bool` operands). // IRType* type = inst->getDataType(); auto vectorType = as(type); if(!vectorType) return false; // For vector cases, we need to convert the operands to // a type that supports vector operations, and then use // bit operations there. // _emitLegalizedBoolVectorBinOp(inst, vectorType, bitOp, inOuterPrec); return true; } bool GLSLSourceEmitter::_tryEmitBitBinOp(IRInst* inst, const EmitOpInfo& bitOp, const EmitOpInfo& boolOp, const EmitOpInfo& inOuterPrec) { // The bitwise binary operations are supported in GLSL, // but do not support `bool` or vector-of-`bool` operands. // // We start by checking if we have a `bool`-based case, // and fall back to the default emit logic if not. // IRType* type = inst->getDataType(); IRType* elementType = type; auto vectorType = as(type); if(vectorType) elementType = vectorType->getElementType(); if(!as(elementType)) return false; // If we have a vector case, then it will be handled // by casting the `bool` vectors to vectors of // integers and doing the bitwise op there, where // it should yield an equivalent result. // if(vectorType) { _emitLegalizedBoolVectorBinOp(inst, vectorType, bitOp, inOuterPrec); } else { // In the scalar case, we will translate // bitwise operations on `bool` values to // the equivalent logical operation, knowing // that our appraoch to folding of sub-expressions // into use sites will avoid any potential issues // around short-circuiting behavior. // auto prec = boolOp; EmitOpInfo outerPrec = inOuterPrec; bool needClose = maybeEmitParens(outerPrec, prec); emitOperand(inst->getOperand(0), leftSide(outerPrec, prec)); m_writer->emit(prec.op); emitOperand(inst->getOperand(1), rightSide(outerPrec, prec)); maybeCloseParens(needClose); } return true; } bool GLSLSourceEmitter::tryEmitInstExprImpl(IRInst* inst, const EmitOpInfo& inOuterPrec) { switch (inst->getOp()) { case kIROp_constructVectorFromScalar: { // Simple constructor call EmitOpInfo outerPrec = inOuterPrec; bool needClose = false; auto prec = getInfo(EmitOp::Postfix); needClose = maybeEmitParens(outerPrec, prec); emitType(inst->getDataType()); m_writer->emit("("); emitOperand(inst->getOperand(0), getInfo(EmitOp::General)); m_writer->emit(")"); maybeCloseParens(needClose); // Handled return true; } case kIROp_Mul: { // Component-wise multiplication needs to be special cased, // because GLSL uses infix `*` to express inner product // when working with matrices. // Are we targetting GLSL, and are both operands matrices? if (as(inst->getOperand(0)->getDataType()) && as(inst->getOperand(1)->getDataType())) { m_writer->emit("matrixCompMult("); emitOperand(inst->getOperand(0), getInfo(EmitOp::General)); m_writer->emit(", "); emitOperand(inst->getOperand(1), getInfo(EmitOp::General)); m_writer->emit(")"); return true; } break; } case kIROp_Select: { if (inst->getOperand(0)->getDataType()->getOp() != kIROp_BoolType) { // For GLSL, emit a call to `mix` if condition is a vector m_writer->emit("mix("); emitOperand(inst->getOperand(2), leftSide(getInfo(EmitOp::General), getInfo(EmitOp::General))); m_writer->emit(", "); emitOperand(inst->getOperand(1), leftSide(getInfo(EmitOp::General), getInfo(EmitOp::General))); m_writer->emit(", "); emitOperand(inst->getOperand(0), leftSide(getInfo(EmitOp::General), getInfo(EmitOp::General))); m_writer->emit(")"); return true; } break; } case kIROp_BitCast: { auto toType = extractBaseType(inst->getDataType()); auto fromType = extractBaseType(inst->getOperand(0)->getDataType()); switch (toType) { default: diagnoseUnhandledInst(inst); break; case BaseType::UInt: if (fromType == BaseType::Float) { m_writer->emit("floatBitsToUint"); } else { emitType(inst->getDataType()); } break; case BaseType::Int: if (fromType == BaseType::Float) { m_writer->emit("floatBitsToInt"); } else { emitType(inst->getDataType()); } break; case BaseType::UInt16: if (fromType == BaseType::Half) { m_writer->emit("uint16_t(packHalf2x16(vec2("); emitOperand(inst->getOperand(0), getInfo(EmitOp::General)); m_writer->emit(", 0.0)))"); return true; } else { emitType(inst->getDataType()); } break; case BaseType::Int16: if (fromType == BaseType::Half) { m_writer->emit("int16_t(packHalf2x16(vec2("); emitOperand(inst->getOperand(0), getInfo(EmitOp::General)); m_writer->emit(", 0.0)))"); return true; } else { emitType(inst->getDataType()); } break; case BaseType::Half: switch (fromType) { case BaseType::Int16: case BaseType::UInt16: case BaseType::Int: case BaseType::UInt: m_writer->emit("float16_t(unpackHalf2x16(uint("); emitOperand(inst->getOperand(0), getInfo(EmitOp::General)); m_writer->emit(")).x)"); return true; default: emitType(inst->getDataType()); break; } break; case BaseType::Float: switch (fromType) { case BaseType::Int: m_writer->emit("intBitsToFloat"); break; case BaseType::UInt: m_writer->emit("uintBitsToFloat"); break; default: emitType(inst->getDataType()); break; } break; case BaseType::Bool: m_writer->emit("bool"); break; } m_writer->emit("("); emitOperand(inst->getOperand(0), getInfo(EmitOp::General)); m_writer->emit(")"); return true; } case kIROp_And: return _tryEmitLogicalBinOp(inst, getInfo(EmitOp::BitAnd), inOuterPrec); case kIROp_Or: return _tryEmitLogicalBinOp(inst, getInfo(EmitOp::BitOr), inOuterPrec); case kIROp_Not: { IRInst* operand = inst->getOperand(0); if (auto vectorType = as(operand->getDataType())) { EmitOpInfo outerPrec = inOuterPrec; bool needClose = false; // Handle as a function call auto prec = getInfo(EmitOp::Postfix); needClose = maybeEmitParens(outerPrec, prec); m_writer->emit("not("); emitOperand(operand, getInfo(EmitOp::General)); m_writer->emit(")"); maybeCloseParens(needClose); return true; } return false; } // When emitting a bitwise operation in GLSL, we need to special-case the handling // of `bool` and vectors of `bool` so that they produce valid results by operating // on the single-bit truth value. // // In the case of a vector we will convert to `uint` vectors and perform the // bitwise op on them before converting back to `bool` vectors. // // In the scalar case we will apply the corresponding logical operation to // the `bool` operands. // case kIROp_BitAnd: return _tryEmitBitBinOp(inst, getInfo(EmitOp::BitAnd), getInfo(EmitOp::And), inOuterPrec); case kIROp_BitOr: return _tryEmitBitBinOp(inst, getInfo(EmitOp::BitOr), getInfo(EmitOp::Or), inOuterPrec); case kIROp_BitXor: // Note: on scalar `bool` operands, a bitwise XOR (`^`) is equivalent to a not-equal (`!=`) comparison. return _tryEmitBitBinOp(inst, getInfo(EmitOp::BitXor), getInfo(EmitOp::Neq), inOuterPrec); // Comparisons case kIROp_Eql: case kIROp_Neq: case kIROp_Greater: case kIROp_Less: case kIROp_Geq: case kIROp_Leq: { // If the comparison is between vectors use GLSL vector comparisons IRInst* left = inst->getOperand(0); IRInst* right = inst->getOperand(1); auto leftVectorType = as(left->getDataType()); auto rightVectorType = as(right->getDataType()); // If either side is a vector handle as a vector if (leftVectorType || rightVectorType) { const char* funcName = _getGLSLVectorCompareFunctionName(inst->getOp()); SLANG_ASSERT(funcName); // Determine the vector type const auto vecType = leftVectorType ? leftVectorType : rightVectorType; // Handle as a function call auto prec = getInfo(EmitOp::Postfix); EmitOpInfo outerPrec = inOuterPrec; bool needClose = maybeEmitParens(outerPrec, outerPrec); m_writer->emit(funcName); m_writer->emit("("); _maybeEmitGLSLCast((leftVectorType ? nullptr : vecType), left); m_writer->emit(","); _maybeEmitGLSLCast((rightVectorType ? nullptr : vecType), right); m_writer->emit(")"); maybeCloseParens(needClose); return true; } // Use the default break; } case kIROp_FRem: { IRInst* left = inst->getOperand(0); IRInst* right = inst->getOperand(1); // Handle as a function call auto prec = getInfo(EmitOp::Postfix); EmitOpInfo outerPrec = inOuterPrec; bool needClose = maybeEmitParens(outerPrec, outerPrec); // TODO: the GLSL `mod` function amounts to a floating-point // modulus rather than a floating-point remainder. We need // to fix this to emit the right SPIR-V opcode, but there is // no built-in GLSL function that maps to the opcode we want. // m_writer->emit("mod("); emitOperand(left, getInfo(EmitOp::General)); m_writer->emit(","); emitOperand(right, getInfo(EmitOp::General)); m_writer->emit(")"); maybeCloseParens(needClose); return true; } // TODO: We should also special-case `kIROp_IRem` here, // so that we emit a remainder instead of a modulus. As for // `FRem` there is no direct GLSL translation, so we will // leave things with the default behavior for now. case kIROp_StringLit: { IRStringLit* lit = cast(inst); const UnownedStringSlice slice = lit->getStringSlice(); m_writer->emit(int32_t(getStableHashCode32(slice.begin(), slice.getLength()))); return true; } case kIROp_GetStringHash: { // On GLSL target, the `String` type is just an `int` // that is the hash of the string, so we can emit // the first operand to `getStringHash` directly. // EmitOpInfo outerPrec = inOuterPrec; emitOperand(inst->getOperand(0), outerPrec); return true; } case kIROp_StructuredBufferLoad: { auto outerPrec = inOuterPrec; auto prec = getInfo(EmitOp::Postfix); bool needClose = maybeEmitParens(outerPrec, prec); emitOperand(inst->getOperand(0), leftSide(outerPrec, prec)); m_writer->emit("._data["); emitOperand(inst->getOperand(1), getInfo(EmitOp::General)); m_writer->emit("]"); maybeCloseParens(needClose); return true; } case kIROp_StructuredBufferStore: { auto outerPrec = inOuterPrec; auto assignPrec = getInfo(EmitOp::Assign); bool assignNeedsClose = maybeEmitParens(outerPrec, assignPrec); { auto subscriptPrec = getInfo(EmitOp::Postfix); bool subscriptNeedsClose = maybeEmitParens(assignPrec, subscriptPrec); emitOperand(inst->getOperand(0), leftSide(assignPrec, subscriptPrec)); m_writer->emit("._data["); emitOperand(inst->getOperand(1), getInfo(EmitOp::General)); m_writer->emit("]"); maybeCloseParens(subscriptNeedsClose); } m_writer->emit(" = "); emitOperand(inst->getOperand(2), rightSide(assignPrec, outerPrec)); maybeCloseParens(assignNeedsClose); return true; } default: break; } // Not handled return false; } void GLSLSourceEmitter::handleRequiredCapabilitiesImpl(IRInst* inst) { // Does this function declare any requirements on GLSL version or // extensions, which should affect our output? for (auto decoration : inst->getDecorations()) { switch (decoration->getOp()) { default: break; case kIROp_RequireGLSLExtensionDecoration: { _requireGLSLExtension(((IRRequireGLSLExtensionDecoration*)decoration)->getExtensionName()); break; } case kIROp_RequireGLSLVersionDecoration: { _requireGLSLVersion(int(((IRRequireGLSLVersionDecoration*)decoration)->getLanguageVersion())); break; } case kIROp_RequireSPIRVVersionDecoration: { auto intValue = static_cast(decoration)->getSPIRVVersion(); SemanticVersion version; version.setFromInteger(SemanticVersion::IntegerType(intValue)); _requireSPIRVVersion(version); break; } } } } void GLSLSourceEmitter::emitPreprocessorDirectivesImpl() { auto effectiveProfile = m_effectiveProfile; if (effectiveProfile.getFamily() == ProfileFamily::GLSL) { _requireGLSLVersion(effectiveProfile.getVersion()); } // HACK: We aren't picking GLSL versions carefully right now, // and so we might end up only requiring the initial 1.10 version, // even though even basic functionality needs a higher version. // // For now, we'll work around this by just setting the minimum required // version to a high one: // // TODO: Either correctly compute a minimum required version, or require // the user to specify a version as part of the target. m_glslExtensionTracker->requireVersion(ProfileVersion::GLSL_450); auto requiredProfileVersion = m_glslExtensionTracker->getRequiredProfileVersion(); switch (requiredProfileVersion) { #define CASE(TAG, VALUE) \ case ProfileVersion::TAG: m_writer->emit("#version " #VALUE "\n"); return CASE(GLSL_110, 110); CASE(GLSL_120, 120); CASE(GLSL_130, 130); CASE(GLSL_140, 140); CASE(GLSL_150, 150); CASE(GLSL_330, 330); CASE(GLSL_400, 400); CASE(GLSL_410, 410); CASE(GLSL_420, 420); CASE(GLSL_430, 430); CASE(GLSL_440, 440); CASE(GLSL_450, 450); CASE(GLSL_460, 460); #undef CASE default: break; } // No information is available for us to guess a profile, // so it seems like we need to pick one out of thin air. // // Ideally we should infer a minimum required version based // on the constructs we have seen used in the user's code // // For now we just fall back to a reasonably recent version. m_writer->emit("#version 420\n"); } void GLSLSourceEmitter::emitLayoutDirectivesImpl(TargetRequest* targetReq) { // Reminder: the meaning of row/column major layout // in our semantics is the *opposite* of what GLSL // calls them, because what they call "columns" // are what we call "rows." // switch (targetReq->getDefaultMatrixLayoutMode()) { case kMatrixLayoutMode_RowMajor: default: m_writer->emit("layout(column_major) uniform;\n"); m_writer->emit("layout(column_major) buffer;\n"); break; case kMatrixLayoutMode_ColumnMajor: m_writer->emit("layout(row_major) uniform;\n"); m_writer->emit("layout(row_major) buffer;\n"); break; } } void GLSLSourceEmitter::emitVectorTypeNameImpl(IRType* elementType, IRIntegerValue elementCount) { if (elementCount > 1) { _emitGLSLTypePrefix(elementType); m_writer->emit("vec"); m_writer->emit(elementCount); } else { emitSimpleType(elementType); } } void GLSLSourceEmitter::emitSimpleTypeImpl(IRType* type) { switch (type->getOp()) { case kIROp_Int64Type: { _requireBaseType(BaseType::Int64); m_writer->emit(getDefaultBuiltinTypeName(type->getOp())); return; } case kIROp_UInt64Type: { _requireBaseType(BaseType::UInt64); m_writer->emit(getDefaultBuiltinTypeName(type->getOp())); return; } case kIROp_VoidType: case kIROp_BoolType: case kIROp_Int8Type: case kIROp_Int16Type: case kIROp_IntType: case kIROp_UInt8Type: case kIROp_UInt16Type: case kIROp_UIntType: case kIROp_FloatType: case kIROp_DoubleType: { _requireBaseType(cast(type)->getBaseType()); m_writer->emit(getDefaultBuiltinTypeName(type->getOp())); return; } case kIROp_HalfType: { _requireBaseType(BaseType::Half); m_writer->emit("float16_t"); return; } case kIROp_StructType: m_writer->emit(getName(type)); return; case kIROp_VectorType: { auto vecType = (IRVectorType*)type; emitVectorTypeNameImpl(vecType->getElementType(), getIntVal(vecType->getElementCount())); return; } case kIROp_MatrixType: { auto matType = (IRMatrixType*)type; _emitGLSLTypePrefix(matType->getElementType()); m_writer->emit("mat"); emitVal(matType->getRowCount(), getInfo(EmitOp::General)); // TODO(tfoley): only emit the next bit // for non-square matrix m_writer->emit("x"); emitVal(matType->getColumnCount(), getInfo(EmitOp::General)); return; } case kIROp_SamplerStateType: case kIROp_SamplerComparisonStateType: { auto samplerStateType = cast(type); switch (samplerStateType->getOp()) { case kIROp_SamplerStateType: m_writer->emit("sampler"); break; case kIROp_SamplerComparisonStateType: m_writer->emit("samplerShadow"); break; default: SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled sampler state flavor"); break; } return; } case kIROp_StringType: m_writer->emit("int"); return; default: break; } // TODO: Ideally the following should be data-driven, // based on meta-data attached to the definitions of // each of these IR opcodes. if (auto texType = as(type)) { switch (texType->getAccess()) { case SLANG_RESOURCE_ACCESS_READ_WRITE: case SLANG_RESOURCE_ACCESS_RASTER_ORDERED: _emitGLSLTextureOrTextureSamplerType(texType, "image"); break; default: _emitGLSLTextureOrTextureSamplerType(texType, "texture"); break; } return; } else if (auto textureSamplerType = as(type)) { _emitGLSLTextureOrTextureSamplerType(textureSamplerType, "sampler"); return; } else if (auto imageType = as(type)) { _emitGLSLTextureOrTextureSamplerType(imageType, "image"); return; } else if (auto structuredBufferType = as(type)) { // TODO: We desugar global variables with structured-buffer type into GLSL // `buffer` declarations, but we don't currently handle structured-buffer types // in other contexts (e.g., as function parameters). The simplest thing to do // would be to emit a `StructuredBuffer` as `Foo[]` and `RWStructuredBuffer` // as `in out Foo[]`, but that is starting to get into the realm of transformations // that should really be handled during legalization, rather than during emission. // SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "structured buffer type used unexpectedly"); return; } else if (auto untypedBufferType = as(type)) { switch (untypedBufferType->getOp()) { case kIROp_RaytracingAccelerationStructureType: { // Note: We have the problem here that we want to do `_requireRayTracing()`, // but just based on the use of a ray-tracing acceleration structure we // cannot know which extension the user means to use. The current options are: // // * GL_NV_ray_tracing // * GL_EXT_ray_tracing // * GL_EXT_ray_query // // The first two options there are basically equivalent extensions with // different GLSL syntax. We end up requiring the user to opt in to // `GL_NV_ray_tracing` using target capabilities, and will always default // to `GL_EXT_ray_tracing` otherwise. // if( getTargetCaps().implies(CapabilityAtom::GL_NV_ray_tracing) ) { // If the user has explicitly opted in to `GL_NV_ray_tracing`, // then we don't need to explicitly request the extentsion again. // We know that the acceleration structure type will translate // to the one from that extension: // _requireRayTracing(); m_writer->emit("accelerationStructureNV"); } else { // If the user does *not* opt into a specific extension, then we // have the problem that either `GL_EXT_ray_tracing` or `GL_EXT_ray-query` // could provide the `accelerationSturctureEXT` type, but there // can be drivers that provide only one and not the other. // // For now we will just kludge this by assuming that any driver // that supports one of these extensions supports the other. // // TODO: Revisit that decision once the driver landscape is more stable/clear. // _requireRayTracing(); m_writer->emit("accelerationStructureEXT"); } break; } // TODO: These "translations" are obviously wrong for GLSL. case kIROp_HLSLByteAddressBufferType: m_writer->emit("ByteAddressBuffer"); break; case kIROp_HLSLRWByteAddressBufferType: m_writer->emit("RWByteAddressBuffer"); break; case kIROp_HLSLRasterizerOrderedByteAddressBufferType: m_writer->emit("RasterizerOrderedByteAddressBuffer"); break; default: SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled buffer type"); break; } return; } auto decorated = getResolvedInstForDecorations(type); if(auto targetIntrinsicDecor = findBestTargetIntrinsicDecoration(decorated)) { m_writer->emit(targetIntrinsicDecor->getDefinition()); return; } SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled type"); } void GLSLSourceEmitter::emitRateQualifiersImpl(IRRate* rate) { if (as(rate)) { m_writer->emit("const "); } else if (as(rate)) { m_writer->emit("shared "); } } static UnownedStringSlice _getInterpolationModifierText(IRInterpolationMode mode, Stage stage, bool isInput) { switch (mode) { case IRInterpolationMode::NoInterpolation: return UnownedStringSlice::fromLiteral("flat"); case IRInterpolationMode::NoPerspective: return UnownedStringSlice::fromLiteral("noperspective"); case IRInterpolationMode::Linear: return UnownedStringSlice::fromLiteral("smooth"); case IRInterpolationMode::Sample: return UnownedStringSlice::fromLiteral("sample"); case IRInterpolationMode::Centroid: return UnownedStringSlice::fromLiteral("centroid"); case IRInterpolationMode::PerVertex: if( stage == Stage::Fragment ) { if( isInput ) { return UnownedStringSlice::fromLiteral("pervertexNV"); } } return UnownedStringSlice::fromLiteral("flat"); default: return UnownedStringSlice(); } } void GLSLSourceEmitter::emitInterpolationModifiersImpl(IRInst* varInst, IRType* valueType, IRVarLayout* layout) { bool anyModifiers = false; auto stage = layout->getStage(); auto isInput = layout->findOffsetAttr(LayoutResourceKind::VaryingInput) != nullptr; for (auto dd : varInst->getDecorations()) { if (dd->getOp() != kIROp_InterpolationModeDecoration) continue; auto decoration = (IRInterpolationModeDecoration*)dd; const UnownedStringSlice slice = _getInterpolationModifierText(decoration->getMode(), stage, isInput); if (slice.getLength()) { m_writer->emit(slice); m_writer->emitChar(' '); anyModifiers = true; } switch( decoration->getMode() ) { default: break; case IRInterpolationMode::PerVertex: if( stage == Stage::Fragment ) { if( isInput ) { _requireGLSLVersion(ProfileVersion::GLSL_450); _requireGLSLExtension(UnownedStringSlice::fromLiteral("GL_NV_fragment_shader_barycentric")); } } break; } } // If the user didn't explicitly qualify a varying // with integer type, then we need to explicitly // add the `flat` modifier for GLSL. if (!anyModifiers) { // Only emit a default `flat` for fragment // stage varying inputs. // // TODO: double-check that this works for // signature matching even if the producing // stage didn't use `flat`. // // If this ends up being a problem we can instead // output everything with `flat` except for // fragment *outputs* (and maybe vertex inputs). // if (layout && layout->getStage() == Stage::Fragment && layout->usesResourceKind(LayoutResourceKind::VaryingInput)) { _maybeEmitGLSLFlatModifier(valueType); } } } void GLSLSourceEmitter::emitVarDecorationsImpl(IRInst* varDecl) { // Deal with Vulkan raytracing layout stuff *before* we // do the check for whether `layout` is null, because // the payload won't automatically get a layout applied // (it isn't part of the user-visible interface...) // if (varDecl->findDecoration()) { m_writer->emit("layout(location = "); m_writer->emit(getRayPayloadLocation(varDecl)); m_writer->emit(")\n"); if( getTargetCaps().implies(CapabilityAtom::GL_NV_ray_tracing) ) { m_writer->emit("rayPayloadNV\n"); } else { m_writer->emit("rayPayloadEXT\n"); } } if (varDecl->findDecoration()) { m_writer->emit("layout(location = "); m_writer->emit(getCallablePayloadLocation(varDecl)); m_writer->emit(")\n"); if( getTargetCaps().implies(CapabilityAtom::GL_NV_ray_tracing) ) { m_writer->emit("callableDataNV\n"); } else { m_writer->emit("callableDataEXT\n"); } } if (varDecl->findDecoration()) { if( getTargetCaps().implies(CapabilityAtom::GL_NV_ray_tracing) ) { m_writer->emit("hitAttributeNV\n"); } else { m_writer->emit("hitAttributeEXT\n"); } } if (varDecl->findDecoration()) { m_writer->emit("coherent\n"); } } void GLSLSourceEmitter::emitMatrixLayoutModifiersImpl(IRVarLayout* layout) { // When a variable has a matrix type, we want to emit an explicit // layout qualifier based on what the layout has been computed to be. // auto typeLayout = layout->getTypeLayout()->unwrapArray(); if (auto matrixTypeLayout = as(typeLayout)) { // Reminder: the meaning of row/column major layout // in our semantics is the *opposite* of what GLSL // calls them, because what they call "columns" // are what we call "rows." // switch (matrixTypeLayout->getMode()) { case kMatrixLayoutMode_ColumnMajor: m_writer->emit("layout(row_major)\n"); break; case kMatrixLayoutMode_RowMajor: m_writer->emit("layout(column_major)\n"); break; } } } } // namespace Slang