Esempio n. 1
0
        public static string ImageLoadOrStore(CodeGenContext context, AstOperation operation)
        {
            AstTextureOperation texOp = (AstTextureOperation)operation;

            bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;

            // TODO: Bindless texture support. For now we just return 0/do nothing.
            if (isBindless)
            {
                return(texOp.Inst switch
                {
                    Instruction.ImageStore => "// imageStore(bindless)",
                    Instruction.ImageLoad => NumberFormatter.FormatFloat(0),
                    _ => NumberFormatter.FormatInt(0)
                });
Esempio n. 2
0
        public static string TextureSample(CodeGenContext context, AstOperation operation)
        {
            AstTextureOperation texOp = (AstTextureOperation)operation;

            bool isBindless    = (texOp.Flags & TextureFlags.Bindless) != 0;
            bool isGather      = (texOp.Flags & TextureFlags.Gather) != 0;
            bool intCoords     = (texOp.Flags & TextureFlags.IntCoords) != 0;
            bool hasLodBias    = (texOp.Flags & TextureFlags.LodBias) != 0;
            bool hasLodLevel   = (texOp.Flags & TextureFlags.LodLevel) != 0;
            bool hasOffset     = (texOp.Flags & TextureFlags.Offset) != 0;
            bool hasOffsets    = (texOp.Flags & TextureFlags.Offsets) != 0;
            bool isArray       = (texOp.Type & TextureType.Array) != 0;
            bool isMultisample = (texOp.Type & TextureType.Multisample) != 0;
            bool isShadow      = (texOp.Type & TextureType.Shadow) != 0;

            string texCall = intCoords ? "texelFetch" : "texture";

            if (isGather)
            {
                texCall += "Gather";
            }
            else if (hasLodLevel && !intCoords)
            {
                texCall += "Lod";
            }

            if (hasOffset)
            {
                texCall += "Offset";
            }
            else if (hasOffsets)
            {
                texCall += "Offsets";
            }

            string samplerName = OperandManager.GetSamplerName(context.Config.Type, texOp);

            texCall += "(" + samplerName;

            int coordsCount = texOp.Type.GetCoordsCount();

            int pCount = coordsCount;

            int arrayIndexElem = -1;

            if (isArray)
            {
                arrayIndexElem = pCount++;
            }

            //The sampler 1D shadow overload expects a
            //dummy value on the middle of the vector, who knows why...
            bool hasDummy1DShadowElem = texOp.Type == (TextureType.Texture1D | TextureType.Shadow);

            if (hasDummy1DShadowElem)
            {
                pCount++;
            }

            if (isShadow && !isGather)
            {
                pCount++;
            }

            //On textureGather*, the comparison value is
            //always specified as an extra argument.
            bool hasExtraCompareArg = isShadow && isGather;

            if (pCount == 5)
            {
                pCount = 4;

                hasExtraCompareArg = true;
            }

            int srcIndex = isBindless ? 1 : 0;

            string Src(VariableType type)
            {
                return(GetSoureExpr(context, texOp.GetSource(srcIndex++), type));
            }

            void Append(string str)
            {
                texCall += ", " + str;
            }

            VariableType coordType = intCoords ? VariableType.S32 : VariableType.F32;

            string AssemblePVector(int count)
            {
                if (count > 1)
                {
                    string[] elems = new string[count];

                    for (int index = 0; index < count; index++)
                    {
                        if (arrayIndexElem == index)
                        {
                            elems[index] = Src(VariableType.S32);

                            if (!intCoords)
                            {
                                elems[index] = "float(" + elems[index] + ")";
                            }
                        }
                        else if (index == 1 && hasDummy1DShadowElem)
                        {
                            elems[index] = NumberFormatter.FormatFloat(0);
                        }
                        else
                        {
                            elems[index] = Src(coordType);
                        }
                    }

                    string prefix = intCoords ? "i" : string.Empty;

                    return(prefix + "vec" + count + "(" + string.Join(", ", elems) + ")");
                }
                else
                {
                    return(Src(coordType));
                }
            }

            Append(AssemblePVector(pCount));

            if (hasExtraCompareArg)
            {
                Append(Src(VariableType.F32));
            }

            if (isMultisample)
            {
                Append(Src(VariableType.S32));
            }
            else if (hasLodLevel)
            {
                Append(Src(coordType));
            }

            string AssembleOffsetVector(int count)
            {
                if (count > 1)
                {
                    string[] elems = new string[count];

                    for (int index = 0; index < count; index++)
                    {
                        elems[index] = Src(VariableType.S32);
                    }

                    return("ivec" + count + "(" + string.Join(", ", elems) + ")");
                }
                else
                {
                    return(Src(VariableType.S32));
                }
            }

            if (hasOffset)
            {
                Append(AssembleOffsetVector(coordsCount));
            }
            else if (hasOffsets)
            {
                texCall += $", ivec{coordsCount}[4](";

                texCall += AssembleOffsetVector(coordsCount) + ", ";
                texCall += AssembleOffsetVector(coordsCount) + ", ";
                texCall += AssembleOffsetVector(coordsCount) + ", ";
                texCall += AssembleOffsetVector(coordsCount) + ")";
            }

            if (hasLodBias)
            {
                Append(Src(VariableType.F32));
            }

            //textureGather* optional extra component index,
            //not needed for shadow samplers.
            if (isGather && !isShadow)
            {
                Append(Src(VariableType.S32));
            }

            texCall += ")" + (isGather || !isShadow ? GetMask(texOp.ComponentMask) : "");

            return(texCall);
        }
Esempio n. 3
0
        public static string ImageLoadOrStore(CodeGenContext context, AstOperation operation)
        {
            AstTextureOperation texOp = (AstTextureOperation)operation;

            bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;

            // TODO: Bindless texture support. For now we just return 0/do nothing.
            if (isBindless)
            {
                return(texOp.Inst == Instruction.ImageLoad ? NumberFormatter.FormatFloat(0) : "// imageStore(bindless)");
            }

            bool isArray   = (texOp.Type & SamplerType.Array) != 0;
            bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;

            string texCall = texOp.Inst == Instruction.ImageLoad ? "imageLoad" : "imageStore";

            int srcIndex = isBindless ? 1 : 0;

            string Src(VariableType type)
            {
                return(GetSoureExpr(context, texOp.GetSource(srcIndex++), type));
            }

            string indexExpr = null;

            if (isIndexed)
            {
                indexExpr = Src(VariableType.S32);
            }

            string imageName = OperandManager.GetImageName(context.Config.Stage, texOp, indexExpr);

            texCall += "(" + imageName;

            int coordsCount = texOp.Type.GetDimensions();

            int pCount = coordsCount + (isArray ? 1 : 0);

            void Append(string str)
            {
                texCall += ", " + str;
            }

            string ApplyScaling(string vector)
            {
                int index = context.FindImageDescriptorIndex(texOp);
                TextureUsageFlags flags = TextureUsageFlags.NeedsScaleValue;

                if ((context.Config.Stage == ShaderStage.Fragment || context.Config.Stage == ShaderStage.Compute) &&
                    texOp.Inst == Instruction.ImageLoad &&
                    !isBindless &&
                    !isIndexed)
                {
                    // Image scales start after texture ones.
                    int scaleIndex = context.TextureDescriptors.Count + index;

                    if (pCount == 3 && isArray)
                    {
                        // The array index is not scaled, just x and y.
                        vector = "ivec3(Helper_TexelFetchScale((" + vector + ").xy, " + scaleIndex + "), (" + vector + ").z)";
                    }
                    else if (pCount == 2 && !isArray)
                    {
                        vector = "Helper_TexelFetchScale(" + vector + ", " + scaleIndex + ")";
                    }
                    else
                    {
                        flags |= TextureUsageFlags.ResScaleUnsupported;
                    }
                }
                else
                {
                    flags |= TextureUsageFlags.ResScaleUnsupported;
                }

                if (!isBindless)
                {
                    context.ImageDescriptors[index] = context.ImageDescriptors[index].SetFlag(flags);
                }

                return(vector);
            }

            if (pCount > 1)
            {
                string[] elems = new string[pCount];

                for (int index = 0; index < pCount; index++)
                {
                    elems[index] = Src(VariableType.S32);
                }

                Append(ApplyScaling("ivec" + pCount + "(" + string.Join(", ", elems) + ")"));
            }
            else
            {
                Append(Src(VariableType.S32));
            }

            if (texOp.Inst == Instruction.ImageStore)
            {
                VariableType type = texOp.Format.GetComponentType();

                string[] cElems = new string[4];

                for (int index = 0; index < 4; index++)
                {
                    if (srcIndex < texOp.SourcesCount)
                    {
                        cElems[index] = Src(type);
                    }
                    else
                    {
                        cElems[index] = type switch
                        {
                            VariableType.S32 => NumberFormatter.FormatInt(0),
                            VariableType.U32 => NumberFormatter.FormatUint(0),
                            _ => NumberFormatter.FormatFloat(0)
                        };
                    }
                }

                string prefix = type switch
                {
                    VariableType.S32 => "i",
                    VariableType.U32 => "u",
                    _ => string.Empty
                };

                Append(prefix + "vec4(" + string.Join(", ", cElems) + ")");
            }

            texCall += ")" + (texOp.Inst == Instruction.ImageLoad ? GetMask(texOp.Index) : "");

            return(texCall);
        }
Esempio n. 4
0
        public static string ImageLoadOrStore(CodeGenContext context, AstOperation operation)
        {
            AstTextureOperation texOp = (AstTextureOperation)operation;

            bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;

            bool isArray   = (texOp.Type & SamplerType.Array) != 0;
            bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;

            string texCall = texOp.Inst == Instruction.ImageLoad ? "imageLoad" : "imageStore";

            int srcIndex = isBindless ? 1 : 0;

            string Src(VariableType type)
            {
                return(GetSoureExpr(context, texOp.GetSource(srcIndex++), type));
            }

            string indexExpr = null;

            if (isIndexed)
            {
                indexExpr = Src(VariableType.S32);
            }

            string imageName = OperandManager.GetImageName(context.Config.Stage, texOp, indexExpr);

            texCall += "(" + imageName;

            int coordsCount = texOp.Type.GetDimensions();

            int pCount = coordsCount + (isArray ? 1 : 0);

            void Append(string str)
            {
                texCall += ", " + str;
            }

            if (pCount > 1)
            {
                string[] elems = new string[pCount];

                for (int index = 0; index < pCount; index++)
                {
                    elems[index] = Src(VariableType.S32);
                }

                Append("ivec" + pCount + "(" + string.Join(", ", elems) + ")");
            }
            else
            {
                Append(Src(VariableType.S32));
            }

            if (texOp.Inst == Instruction.ImageStore)
            {
                VariableType type = texOp.Format.GetComponentType();

                string[] cElems = new string[4];

                for (int index = 0; index < 4; index++)
                {
                    if (srcIndex < texOp.SourcesCount)
                    {
                        cElems[index] = Src(type);
                    }
                    else
                    {
                        cElems[index] = type switch
                        {
                            VariableType.S32 => NumberFormatter.FormatInt(0),
                            VariableType.U32 => NumberFormatter.FormatUint(0),
                            _ => NumberFormatter.FormatFloat(0)
                        };
                    }
                }

                string prefix = type switch
                {
                    VariableType.S32 => "i",
                    VariableType.U32 => "u",
                    _ => string.Empty
                };

                Append(prefix + "vec4(" + string.Join(", ", cElems) + ")");
            }

            texCall += ")" + (texOp.Inst == Instruction.ImageLoad ? GetMask(texOp.Index) : "");

            return(texCall);
        }
Esempio n. 5
0
        public static string ImageStore(CodeGenContext context, AstOperation operation)
        {
            AstTextureOperation texOp = (AstTextureOperation)operation;

            bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;

            bool isArray   = (texOp.Type & SamplerType.Array) != 0;
            bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;

            string texCall = "imageStore";

            int srcIndex = isBindless ? 1 : 0;

            string Src(VariableType type)
            {
                return(GetSoureExpr(context, texOp.GetSource(srcIndex++), type));
            }

            string indexExpr = null;

            if (isIndexed)
            {
                indexExpr = Src(VariableType.S32);
            }

            string imageName = OperandManager.GetImageName(context.Config.Stage, texOp, indexExpr);

            texCall += "(" + imageName;

            int coordsCount = texOp.Type.GetDimensions();

            int pCount = coordsCount;

            int arrayIndexElem = -1;

            if (isArray)
            {
                arrayIndexElem = pCount++;
            }

            void Append(string str)
            {
                texCall += ", " + str;
            }

            if (pCount > 1)
            {
                string[] elems = new string[pCount];

                for (int index = 0; index < pCount; index++)
                {
                    elems[index] = Src(VariableType.S32);
                }

                Append("ivec" + pCount + "(" + string.Join(", ", elems) + ")");
            }
            else
            {
                Append(Src(VariableType.S32));
            }

            string[] cElems = new string[4];

            for (int index = 0; index < 4; index++)
            {
                if (srcIndex < texOp.SourcesCount)
                {
                    cElems[index] = Src(VariableType.F32);
                }
                else
                {
                    cElems[index] = NumberFormatter.FormatFloat(0);
                }
            }

            Append("vec4(" + string.Join(", ", cElems) + ")");

            texCall += ")";

            return(texCall);
        }
Esempio n. 6
0
        public static string TextureSample(CodeGenContext context, AstOperation operation)
        {
            AstTextureOperation texOp = (AstTextureOperation)operation;

            bool isBindless     = (texOp.Flags & TextureFlags.Bindless) != 0;
            bool isGather       = (texOp.Flags & TextureFlags.Gather) != 0;
            bool hasDerivatives = (texOp.Flags & TextureFlags.Derivatives) != 0;
            bool intCoords      = (texOp.Flags & TextureFlags.IntCoords) != 0;
            bool hasLodBias     = (texOp.Flags & TextureFlags.LodBias) != 0;
            bool hasLodLevel    = (texOp.Flags & TextureFlags.LodLevel) != 0;
            bool hasOffset      = (texOp.Flags & TextureFlags.Offset) != 0;
            bool hasOffsets     = (texOp.Flags & TextureFlags.Offsets) != 0;

            bool isArray       = (texOp.Type & SamplerType.Array) != 0;
            bool isIndexed     = (texOp.Type & SamplerType.Indexed) != 0;
            bool isMultisample = (texOp.Type & SamplerType.Multisample) != 0;
            bool isShadow      = (texOp.Type & SamplerType.Shadow) != 0;

            // This combination is valid, but not available on GLSL.
            // For now, ignore the LOD level and do a normal sample.
            // TODO: How to implement it properly?
            if (hasLodLevel && isArray && isShadow)
            {
                hasLodLevel = false;
            }

            string texCall = intCoords ? "texelFetch" : "texture";

            if (isGather)
            {
                texCall += "Gather";
            }
            else if (hasDerivatives)
            {
                texCall += "Grad";
            }
            else if (hasLodLevel && !intCoords)
            {
                texCall += "Lod";
            }

            if (hasOffset)
            {
                texCall += "Offset";
            }
            else if (hasOffsets)
            {
                texCall += "Offsets";
            }

            int srcIndex = isBindless ? 1 : 0;

            string Src(VariableType type)
            {
                return(GetSoureExpr(context, texOp.GetSource(srcIndex++), type));
            }

            string indexExpr = null;

            if (isIndexed)
            {
                indexExpr = Src(VariableType.S32);
            }

            string samplerName = OperandManager.GetSamplerName(context.Config.Stage, texOp, indexExpr);

            texCall += "(" + samplerName;

            int coordsCount = texOp.Type.GetDimensions();

            int pCount = coordsCount;

            int arrayIndexElem = -1;

            if (isArray)
            {
                arrayIndexElem = pCount++;
            }

            // The sampler 1D shadow overload expects a
            // dummy value on the middle of the vector, who knows why...
            bool hasDummy1DShadowElem = texOp.Type == (SamplerType.Texture1D | SamplerType.Shadow);

            if (hasDummy1DShadowElem)
            {
                pCount++;
            }

            if (isShadow && !isGather)
            {
                pCount++;
            }

            // On textureGather*, the comparison value is
            // always specified as an extra argument.
            bool hasExtraCompareArg = isShadow && isGather;

            if (pCount == 5)
            {
                pCount = 4;

                hasExtraCompareArg = true;
            }

            void Append(string str)
            {
                texCall += ", " + str;
            }

            VariableType coordType = intCoords ? VariableType.S32 : VariableType.F32;

            string AssemblePVector(int count)
            {
                if (count > 1)
                {
                    string[] elems = new string[count];

                    for (int index = 0; index < count; index++)
                    {
                        if (arrayIndexElem == index)
                        {
                            elems[index] = Src(VariableType.S32);

                            if (!intCoords)
                            {
                                elems[index] = "float(" + elems[index] + ")";
                            }
                        }
                        else if (index == 1 && hasDummy1DShadowElem)
                        {
                            elems[index] = NumberFormatter.FormatFloat(0);
                        }
                        else
                        {
                            elems[index] = Src(coordType);
                        }
                    }

                    string prefix = intCoords ? "i" : string.Empty;

                    return(prefix + "vec" + count + "(" + string.Join(", ", elems) + ")");
                }
                else
                {
                    return(Src(coordType));
                }
            }

            string ApplyScaling(string vector)
            {
                if (intCoords)
                {
                    int index = context.FindTextureDescriptorIndex(texOp);

                    if ((context.Config.Stage == ShaderStage.Fragment || context.Config.Stage == ShaderStage.Compute) &&
                        (texOp.Flags & TextureFlags.Bindless) == 0 &&
                        texOp.Type != SamplerType.Indexed &&
                        pCount == 2)
                    {
                        return("Helper_TexelFetchScale(" + vector + ", " + index + ")");
                    }
                    else
                    {
                        // Resolution scaling cannot be applied to this texture right now.
                        // Flag so that we know to blacklist scaling on related textures when binding them.

                        TextureDescriptor descriptor = context.TextureDescriptors[index];
                        descriptor.Flags |= TextureUsageFlags.ResScaleUnsupported;
                        context.TextureDescriptors[index] = descriptor;
                    }
                }

                return(vector);
            }

            Append(ApplyScaling(AssemblePVector(pCount)));

            string AssembleDerivativesVector(int count)
            {
                if (count > 1)
                {
                    string[] elems = new string[count];

                    for (int index = 0; index < count; index++)
                    {
                        elems[index] = Src(VariableType.F32);
                    }

                    return("vec" + count + "(" + string.Join(", ", elems) + ")");
                }
                else
                {
                    return(Src(VariableType.F32));
                }
            }

            if (hasExtraCompareArg)
            {
                Append(Src(VariableType.F32));
            }

            if (hasDerivatives)
            {
                Append(AssembleDerivativesVector(coordsCount)); // dPdx
                Append(AssembleDerivativesVector(coordsCount)); // dPdy
            }

            if (isMultisample)
            {
                Append(Src(VariableType.S32));
            }
            else if (hasLodLevel)
            {
                Append(Src(coordType));
            }

            string AssembleOffsetVector(int count)
            {
                if (count > 1)
                {
                    string[] elems = new string[count];

                    for (int index = 0; index < count; index++)
                    {
                        elems[index] = Src(VariableType.S32);
                    }

                    return("ivec" + count + "(" + string.Join(", ", elems) + ")");
                }
                else
                {
                    return(Src(VariableType.S32));
                }
            }

            if (hasOffset)
            {
                Append(AssembleOffsetVector(coordsCount));
            }
            else if (hasOffsets)
            {
                texCall += $", ivec{coordsCount}[4](";

                texCall += AssembleOffsetVector(coordsCount) + ", ";
                texCall += AssembleOffsetVector(coordsCount) + ", ";
                texCall += AssembleOffsetVector(coordsCount) + ", ";
                texCall += AssembleOffsetVector(coordsCount) + ")";
            }

            if (hasLodBias)
            {
                Append(Src(VariableType.F32));
            }

            // textureGather* optional extra component index,
            // not needed for shadow samplers.
            if (isGather && !isShadow)
            {
                Append(Src(VariableType.S32));
            }

            texCall += ")" + (isGather || !isShadow ? GetMask(texOp.Index) : "");

            return(texCall);
        }
Esempio n. 7
0
        public static string ImageLoadOrStore(CodeGenContext context, AstOperation operation)
        {
            AstTextureOperation texOp = (AstTextureOperation)operation;

            bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;

            // TODO: Bindless texture support. For now we just return 0/do nothing.
            if (isBindless)
            {
                switch (texOp.Inst)
                {
                    case Instruction.ImageStore:
                        return "// imageStore(bindless)";
                    case Instruction.ImageLoad:
                        NumberFormatter.TryFormat(0, texOp.Format.GetComponentType(), out string imageConst);
                        return imageConst;
                    default:
                        return NumberFormatter.FormatInt(0);
                }
            }

            bool isArray   = (texOp.Type & SamplerType.Array)   != 0;
            bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;

            string texCall;

            if (texOp.Inst == Instruction.ImageAtomic)
            {
                texCall = (texOp.Flags & TextureFlags.AtomicMask) switch {
                    TextureFlags.Add        => "imageAtomicAdd",
                    TextureFlags.Minimum    => "imageAtomicMin",
                    TextureFlags.Maximum    => "imageAtomicMax",
                    TextureFlags.Increment  => "imageAtomicAdd", // TODO: Clamp value.
                    TextureFlags.Decrement  => "imageAtomicAdd", // TODO: Clamp value.
                    TextureFlags.BitwiseAnd => "imageAtomicAnd",
                    TextureFlags.BitwiseOr  => "imageAtomicOr",
                    TextureFlags.BitwiseXor => "imageAtomicXor",
                    TextureFlags.Swap       => "imageAtomicExchange",
                    TextureFlags.CAS        => "imageAtomicCompSwap",
                    _                       => "imageAtomicAdd",
                };
            }
            else
            {
                texCall = texOp.Inst == Instruction.ImageLoad ? "imageLoad" : "imageStore";
            }

            int srcIndex = isBindless ? 1 : 0;

            string Src(VariableType type)
            {
                return GetSoureExpr(context, texOp.GetSource(srcIndex++), type);
            }

            string indexExpr = null;

            if (isIndexed)
            {
                indexExpr = Src(VariableType.S32);
            }

            string imageName = OperandManager.GetImageName(context.Config.Stage, texOp, indexExpr);

            texCall += "(" + imageName;

            int coordsCount = texOp.Type.GetDimensions();

            int pCount = coordsCount + (isArray ? 1 : 0);

            void Append(string str)
            {
                texCall += ", " + str;
            }

            string ApplyScaling(string vector)
            {
                if (context.Config.Stage.SupportsRenderScale() &&
                    texOp.Inst == Instruction.ImageLoad &&
                    !isBindless &&
                    !isIndexed)
                {
                    // Image scales start after texture ones.
                    int scaleIndex = context.Config.GetTextureDescriptors().Length + context.Config.FindImageDescriptorIndex(texOp);

                    if (pCount == 3 && isArray)
                    {
                        // The array index is not scaled, just x and y.
                        vector = "ivec3(Helper_TexelFetchScale((" + vector + ").xy, " + scaleIndex + "), (" + vector + ").z)";
                    }
                    else if (pCount == 2 && !isArray)
                    {
                        vector = "Helper_TexelFetchScale(" + vector + ", " + scaleIndex + ")";
                    }
                }

                return vector;
            }

            if (pCount > 1)
            {
                string[] elems = new string[pCount];

                for (int index = 0; index < pCount; index++)
                {
                    elems[index] = Src(VariableType.S32);
                }

                Append(ApplyScaling("ivec" + pCount + "(" + string.Join(", ", elems) + ")"));
            }
            else
            {
                Append(Src(VariableType.S32));
            }

            if (texOp.Inst == Instruction.ImageStore)
            {
                VariableType type = texOp.Format.GetComponentType();

                string[] cElems = new string[4];

                for (int index = 0; index < 4; index++)
                {
                    if (srcIndex < texOp.SourcesCount)
                    {
                        cElems[index] = Src(type);
                    }
                    else
                    {
                        cElems[index] = type switch
                        {
                            VariableType.S32 => NumberFormatter.FormatInt(0),
                            VariableType.U32 => NumberFormatter.FormatUint(0),
                            _                => NumberFormatter.FormatFloat(0)
                        };
                    }
                }

                string prefix = type switch
                {
                    VariableType.S32 => "i",
                    VariableType.U32 => "u",
                    _                => string.Empty
                };

                Append(prefix + "vec4(" + string.Join(", ", cElems) + ")");
            }

            if (texOp.Inst == Instruction.ImageAtomic)
            {
                VariableType type = texOp.Format.GetComponentType();

                if ((texOp.Flags & TextureFlags.AtomicMask) == TextureFlags.CAS)
                {
                    Append(Src(type)); // Compare value.
                }

                string value = (texOp.Flags & TextureFlags.AtomicMask) switch
                {
                    TextureFlags.Increment => NumberFormatter.FormatInt(1, type), // TODO: Clamp value
                    TextureFlags.Decrement => NumberFormatter.FormatInt(-1, type), // TODO: Clamp value
                    _ => Src(type)
                };

                Append(value);

                texCall += ")";

                if (type != VariableType.S32)
                {
                    texCall = "int(" + texCall + ")";
                }
            }
            else
            {
                texCall += ")" + (texOp.Inst == Instruction.ImageLoad ? GetMask(texOp.Index) : "");
            }

            return texCall;
        }