https://github.com/shader-slang/slang
Raw File
Tip revision: 45ae0eb9ada14b8ead3c9785e16cc234a4d31ef0 authored by Yong He on 01 November 2021, 17:53:42 UTC
Disable aarch64 build for releases. (#2000)
Tip revision: 45ae0eb
slang-emit-glsl.cpp
// 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 <assert.h>

namespace Slang {

GLSLSourceEmitter::GLSLSourceEmitter(const Desc& desc) :
    Super(desc)
{
    m_glslExtensionTracker = dynamicCast<GLSLExtensionTracker>(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<IRHLSLStructuredBufferType>(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<IRHLSLByteAddressBufferType>(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<IRParameterGroupTypeLayout>(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<IRGLSLShaderStorageBufferType>(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<IRFormatDecoration>())
    {
        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<X>`
    // 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<float4>` 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<X>` 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<IRVectorType>(elementType))
    {
        if (auto intLitVal = as<IRIntLit>(elementVecType->getElementCount()))
        {
            vectorWidth = (Int)intLitVal->getValue();
        }
        else
        {
            vectorWidth = 0;
        }
        elementType = elementVecType->getElementType();
    }
    if (auto elementBasicType = as<IRBasicType>(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;

                // 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
                //
                // rgba16i
                // rgba8i
                // rg16i
                // rg8i
                // r16i
                // r8i
                //
                // rgba16ui
                // rgb10_a2ui
                // rgba8ui
                // rg16ui
                // rg8ui
                // r16ui
                // r8ui
        }
        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<IRVectorType>(type)->getElementType(), promoteHalfToFloat);
            break;

        case kIROp_MatrixType:
            _emitGLSLTypePrefix(cast<IRMatrixType>(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<IRVectorType>(tt))
        tt = vecType->getElementType();
    if (auto vecType = as<IRMatrixType>(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<IRConstant*>(inst);

            IRBasicType* type = as<IRBasicType>(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<IRConstant*>(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<IRMaxVertexCountDecoration>())
            {
                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<IRInstanceDecoration>())
            {
                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<IRGeometryInputPrimitiveTypeDecoration>())
            {
                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<IRStreamOutputTypeDecoration>())
            {
                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<IREarlyDepthStencilDecoration>())
            {
                // 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<IRUniformParameterGroupType>(unwrapArray(varType)))
    {
        _emitGLSLParameterGroup(varDecl, paramBlockType);
        return true;
    }
    if (auto structuredBufferType = as<IRHLSLStructuredBufferTypeBase>(unwrapArray(varType)))
    {
        _emitGLSLStructuredBuffer(varDecl, structuredBufferType);
        return true;
    }
    if (auto byteAddressBufferType = as<IRByteAddressBufferTypeBase>(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<IRLinkageDecoration>())
    {
        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<IRUnsizedArrayType>(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<IRInterpolationModeDecoration>() )
    {
        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<IRTextureType>(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<IRVectorType>(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<IRVectorType>(type);
    if(vectorType)
        elementType = vectorType->getElementType();
    if(!as<IRBoolType>(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<IRMatrixType>(inst->getOperand(0)->getDataType())
                && as<IRMatrixType>(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<IRVectorType>(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<IRVectorType>(left->getDataType());
            auto rightVectorType = as<IRVectorType>(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<IRStringLit>(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<IRRequireSPIRVVersionDecoration*>(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<IRBasicType>(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<IRSamplerStateTypeBase>(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<IRTextureType>(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<IRTextureSamplerType>(type))
    {
        _emitGLSLTextureOrTextureSamplerType(textureSamplerType, "sampler");
        return;
    }
    else if (auto imageType = as<IRGLSLImageType>(type))
    {
        _emitGLSLTextureOrTextureSamplerType(imageType, "image");
        return;
    }
    else if (auto structuredBufferType = as<IRHLSLStructuredBufferTypeBase>(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<Foo>` as `Foo[]` and `RWStructuredBuffer<Foo>`
        // 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<IRUntypedBufferResourceType>(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<IRConstExprRate>(rate))
    {
        m_writer->emit("const ");
        
    }
    else if (as<IRGroupSharedRate>(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<IRVulkanRayPayloadDecoration>())
    {
        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<IRVulkanCallablePayloadDecoration>())
    {
        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<IRVulkanHitAttributesDecoration>())
    {
        if( getTargetCaps().implies(CapabilityAtom::GL_NV_ray_tracing) )
        {
            m_writer->emit("hitAttributeNV\n");
        }
        else
        {
            m_writer->emit("hitAttributeEXT\n");
        }
    }

    if (varDecl->findDecoration<IRGloballyCoherentDecoration>())
    {
        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<IRMatrixTypeLayout>(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
back to top