diff --git a/Game.Assets/hl2/shaders/screenspaceeffect_gl460.vs b/Game.Assets/hl2/shaders/screenspaceeffect_gl460.vs index 86b8a62bb..318151362 100644 --- a/Game.Assets/hl2/shaders/screenspaceeffect_gl460.vs +++ b/Game.Assets/hl2/shaders/screenspaceeffect_gl460.vs @@ -1,12 +1,37 @@ #version 460 +// STATIC: "VERTEXCOLOR" "0..1" +// STATIC: "TRANSFORMVERTS" "0..1" layout(location = 0) in vec3 v_Position; +layout(location = 2) in vec4 v_Color; layout(location = 10) in vec2 v_TexCoord; +layout(std140, binding = 0) uniform source_matrices { + mat4 viewMatrix; + mat4 projectionMatrix; + mat4 modelMatrix; +}; + out vec2 vs_BaseTexCoord; +out vec4 vs_TexCoord0; +out vec4 vs_TexCoord1; +out vec4 vs_TexCoord2; +out vec4 vs_Color0; void main() { +#if TRANSFORMVERTS + gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(v_Position, 1.0); +#else gl_Position = vec4(v_Position, 1.0); +#endif vs_BaseTexCoord = vec2(v_TexCoord.x, 1.0 - v_TexCoord.y); + vs_TexCoord0 = vec4(vs_BaseTexCoord, 0.0, 1.0); + vs_TexCoord1 = vec4(0.0, 0.0, 0.0, 1.0); + vs_TexCoord2 = vs_TexCoord0; +#if VERTEXCOLOR + vs_Color0 = v_Color; +#else + vs_Color0 = vec4(1.0); +#endif } diff --git a/Source.ShaderAPI.Gl46/ShaderSystem.cs b/Source.ShaderAPI.Gl46/ShaderSystem.cs index e44fd105f..ff5a61004 100644 --- a/Source.ShaderAPI.Gl46/ShaderSystem.cs +++ b/Source.ShaderAPI.Gl46/ShaderSystem.cs @@ -467,18 +467,42 @@ private void ProcessSource(StringBuilder sb, string source, int fileIndex, int s } } - private unsafe uint CompileShader(int glType, ReadOnlySpan name, ReadOnlySpan defines, string typeName) { - using IFileHandle? handle = FileSystem.Open($"shaders/{name}", FileOpenOptions.Read, "game"); + private byte[]? LoadVcs(ReadOnlySpan name) { + int ext = name.LastIndexOf("_gl"); + if (ext < 0) + return null; + + using IFileHandle? handle = FileSystem.Open($"shaders/fxc/{name[..ext]}.vcs", FileOpenOptions.Read, "game"); if (handle == null) - return 0; + return null; - Span source = stackalloc byte[(int)handle.Stream.Length]; - handle.Stream.Read(source); + byte[] vcs = new byte[handle.Stream.Length]; + handle.Stream.ReadExactly(vcs); + string? source = VcsTranslator.Load(vcs); + if (source == null) { + Warning($"WARNING: Failed to translate shader shaders/fxc/{name[..ext]}.vcs\n"); + return null; + } + return Encoding.ASCII.GetBytes(source); + } + private unsafe uint CompileShader(int glType, ReadOnlySpan name, ReadOnlySpan defines, string typeName) { + using IFileHandle? handle = FileSystem.Open($"shaders/{name}", FileOpenOptions.Read, "game"); List sourceFiles = []; - byte[]? built = BuildShaderSource(source, defines, new string(name), sourceFiles); - if (built == null) - return 0; + byte[]? built; + if (handle == null) { + if ((built = LoadVcs(name)) == null) + return 0; + sourceFiles.Add(new(name)); + } + else { + Span source = stackalloc byte[(int)handle.Stream.Length]; + handle.Stream.Read(source); + + built = BuildShaderSource(source, defines, new string(name), sourceFiles); + if (built == null) + return 0; + } uint pShader = glCreateShader(glType); int len = built.Length; diff --git a/Source.ShaderAPI.Gl46/VcsTranslator.cs b/Source.ShaderAPI.Gl46/VcsTranslator.cs new file mode 100644 index 000000000..ef6c3f289 --- /dev/null +++ b/Source.ShaderAPI.Gl46/VcsTranslator.cs @@ -0,0 +1,495 @@ +using Source.Common; + +using System.Buffers; +using System.Buffers.Binary; +using System.Globalization; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +namespace Source.ShaderAPI.Gl46; + +enum ShaderOpcode +{ + Nop = 0, + Mov, + Add, + Sub, + Mad, + Mul, + Rcp, + Rsq, + Dp3, + Dp4, + Min, + Max, + Slt, + Sge, + Exp, + Log, + Lit, + Dst, + Lrp, + Frc, + M4x4, + M4x3, + M3x4, + M3x3, + M3x2, + Dcl = 31, + Pow, + Crs, + Sgn, + Abs, + Nrm, + SinCos, + Ifc = 41, + Else, + EndIf, + TexKill = 65, + Tex, + Def = 81, + Cmp = 88, + Dp2Add = 90, + Dsx, + Dsy, + TexLdd, + TexLdl = 95, + Phase = 0xFFFD, + Comment = 0xFFFE, + End = 0xFFFF +} + +enum RegType +{ + Temp, + Input, + Const, + Texture, + RastOut, + AttrOut, + Output, + ConstInt, + ColorOut, + DepthOut, + Sampler, + MiscType = 17 +} + +enum SrcMod +{ + None, + Neg, + Bias, + BiasNeg, + Sign, + SignNeg, + Comp, + X2, + X2Neg, + Abs = 11, + AbsNeg +} + +enum DeclUsage +{ + Position = 0, + Normal = 3, + TexCoord = 5, + Color = 10 +} + +enum ShaderComparison +{ + Gt = 1, + Eq, + Ge, + Lt, + Ne, + Le +} + +enum TexLoadControl +{ + None, + Project, + Bias +} + +enum SamplerTextureType +{ + Texture2D = 2, + Cube, + Volume +} + +internal sealed class VcsTranslator +{ + const string Xyzw = "xyzw"; + static readonly string Temps = string.Concat(Enumerable.Range(0, 32).Select(i => $", r{i} = vec4(0.0)")); + + const int ShaderVcsVersion = 6; + const int MaxShaderUnpackedBlockSize = 1 << 17; + const uint BlockCompressionMask = 0xC0000000; + const uint BlockSizeMask = 0x3FFFFFFF; + const uint BlockUncompressed = 0x80000000; + const uint BlockLzma = 0x40000000; + const uint EndOfBlocks = uint.MaxValue; + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct ShaderHeader + { + public int Version; + public int TotalCombos; + public int DynamicCombos; + public uint Flags; + public uint CentroidMask; + public uint NumStaticCombos; + public uint SourceCRC32; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct StaticComboRecord + { + public uint StaticComboID; + public uint FileOffset; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + struct StaticComboAliasRecord + { + public uint StaticComboID; + public uint SourceStaticCombo; + } + + static uint NextULONG(ReadOnlySpan data, ref int offset) { + uint value = BinaryPrimitives.ReadUInt32LittleEndian(data[offset..]); + offset += sizeof(uint); + return value; + } + + public static string? Load(ReadOnlySpan vcs) { + if (vcs.Length < Unsafe.SizeOf()) + return null; + ShaderHeader header = MemoryMarshal.Read(vcs); + if (header.Version != ShaderVcsVersion) + return null; + + int offset = Unsafe.SizeOf(); + ReadOnlySpan records = MemoryMarshal.Cast(vcs.Slice(offset, (int)header.NumStaticCombos * Unsafe.SizeOf())); + offset += records.Length * Unsafe.SizeOf(); + + int numDups = (int)NextULONG(vcs, ref offset); + ReadOnlySpan dups = MemoryMarshal.Cast(vcs.Slice(offset, numDups * Unsafe.SizeOf())); + + uint staticCombo = 0; + foreach (ref readonly StaticComboAliasRecord dup in dups) + if (dup.StaticComboID == 0) + staticCombo = dup.SourceStaticCombo; + + offset = -1; + foreach (ref readonly StaticComboRecord record in records) + if (record.StaticComboID == staticCombo) { + offset = (int)record.FileOffset; + break; + } + if (offset < 0) + return null; + + byte[] unpack = ArrayPool.Shared.Rent(MaxShaderUnpackedBlockSize); + try { + for (uint blockSize; (blockSize = NextULONG(vcs, ref offset)) != EndOfBlocks;) { + int packedSize = (int)(blockSize & BlockSizeMask); + ReadOnlySpan block; + switch (blockSize & BlockCompressionMask) { + case BlockUncompressed: + block = vcs.Slice(offset, packedSize); + break; + case BlockLzma: + block = unpack.AsSpan(0, (int)LZMA.Uncompress(vcs[offset..], unpack)); + break; + default: + return null; + } + offset += packedSize; + + for (int readPtr = 0; readPtr < block.Length;) { + uint comboID = NextULONG(block, ref readPtr); + int shaderSize = (int)NextULONG(block, ref readPtr); + if (comboID == 0) + return new VcsTranslator().Translate(MemoryMarshal.Cast(block.Slice(readPtr, shaderSize))); + readPtr += shaderSize; + } + } + return null; + } + finally { + ArrayPool.Shared.Return(unpack); + } + } + + bool pixel; + InlineArray256 defs; + InlineArray16 samplers; + InlineArray16 inNames; + InlineArray16 outNames; + readonly SortedSet io = []; + + const uint PixelShaderVersionType = 0xFFFF; + const uint VertexShaderVersionType = 0xFFFE; + const uint OpcodeMask = 0xFFFF; + const uint RegNumMask = 0x7FF; + + static RegType GetRegType(uint t) => (RegType)(((t >> 28) & 7) | ((t >> 8) & 0x18)); + static int RegNum(uint t) => (int)(t & RegNumMask); + static int Swizzle(uint t, int component) => (int)((t >> (16 + component * 2)) & 3); + static SrcMod GetSrcMod(uint t) => (SrcMod)((t >> 24) & 0xF); + static uint WriteMask(uint t) => (t >> 16) & 0xF; + static bool Saturate(uint t) => ((t >> 20) & 1) != 0; + static uint OpcodeControl(uint tok) => (tok >> 16) & 0xFF; + static int InstructionLength(uint tok) => (int)((tok >> 24) & 0xF); + static int CommentLength(uint tok) => (int)((tok >> 16) & 0x7FFF); + static DeclUsage GetDeclUsage(uint t) => (DeclUsage)(t & 0x1F); + static int UsageIndex(uint t) => (int)((t >> 16) & 0xF); + static SamplerTextureType GetSamplerType(uint t) => (SamplerTextureType)((t >> 27) & 0xF); + + string Use(string decl, string name) { + io.Add($"{decl} vec4 {name};"); + return name; + } + + string? Reg(uint t) { + int n = RegNum(t); + switch (GetRegType(t)) { + case RegType.Temp: return $"r{n}"; + case RegType.Input: return inNames[n & 15] ?? (pixel ? Use("in", $"vs_Color{n}") : $"a{n}"); + case RegType.Const: + if (defs[n]) + return $"c{n}"; + if (pixel) + return $"ps_const[{n}]"; + return n switch { + 0 => "vec4(0.0, 1.0, 2.0, 0.5)", + 1 => "vec4(1.0 / 2.2, 2.2, 0.0, 0.0)", + >= 4 and < 8 => $"mvpT[{n - 4}]", + >= 8 and < 12 => $"vpT[{n - 8}]", + 12 => "mvpT[2]", + 13 => "vpT[2]", + _ => $"vs_const[{n}]" + }; + case RegType.Texture: return pixel ? Use("in", $"vs_TexCoord{n}") : null; + case RegType.RastOut: return n == 0 ? "oPos" : n == 1 ? "oFog" : "oPts"; + case RegType.AttrOut: return Use("out", $"vs_Color{n}"); + case RegType.Output: return outNames[n & 15] ?? Use("out", $"vs_TexCoord{n}"); + case RegType.ColorOut: return $"oC{n}"; + case RegType.DepthOut: return "gl_FragDepth"; + case RegType.Sampler: return $"s{n}"; + case RegType.MiscType: return n == 0 ? "gl_FragCoord" : "vFace"; + default: return null; + } + } + + string Src(uint t) { + string r = $"{Reg(t)}.{Xyzw[Swizzle(t, 0)]}{Xyzw[Swizzle(t, 1)]}{Xyzw[Swizzle(t, 2)]}{Xyzw[Swizzle(t, 3)]}"; + return GetSrcMod(t) switch { + SrcMod.Neg => $"(-{r})", + SrcMod.Bias => $"({r} - 0.5)", + SrcMod.BiasNeg => $"(0.5 - {r})", + SrcMod.Sign => $"({r} * 2.0 - 1.0)", + SrcMod.SignNeg => $"(1.0 - {r} * 2.0)", + SrcMod.Comp => $"(1.0 - {r})", + SrcMod.X2 => $"({r} * 2.0)", + SrcMod.X2Neg => $"({r} * -2.0)", + SrcMod.Abs => $"abs({r})", + SrcMod.AbsNeg => $"(-abs({r}))", + _ => r + }; + } + + string Rows(uint vec, uint matrix, int rows, bool dot3) { + StringBuilder sb = new("vec4("); + string v = Src(vec); + for (int i = 0; i < 4; i++) { + if (i > 0) + sb.Append(", "); + if (i >= rows) + sb.Append("0.0"); + else if (dot3) + sb.Append($"dot({v}.xyz, {Src(matrix + (uint)i)}.xyz)"); + else + sb.Append($"dot({v}, {Src(matrix + (uint)i)})"); + } + return sb.Append(')').ToString(); + } + + string? Translate(ReadOnlySpan code) { + if (code.IsEmpty) + return null; + uint shaderType = code[0] >> 16; + if (shaderType != PixelShaderVersionType && shaderType != VertexShaderVersionType) + return null; + pixel = shaderType == PixelShaderVersionType; + + StringBuilder head = new("#version 460\n"); + if (pixel) + head.Append("layout(std140, binding = 6) uniform source_ps_constants { vec4 ps_const[256]; };\nlayout(location = 0) out vec4 oC0;\n"); + else + head.Append("layout(std140, binding = 0) uniform source_matrices { mat4 viewMatrix; mat4 projectionMatrix; mat4 modelMatrix; };\nlayout(std140, binding = 5) uniform source_vs_constants { vec4 vs_const[256]; };\n"); + + StringBuilder body = new("void main() {\n"); + body.Append(pixel + ? $"vec4 oC1, oC2, oC3, vFace = vec4(gl_FrontFacing ? 1.0 : -1.0){Temps};\n" + : $"mat4 mvpT = transpose(projectionMatrix * viewMatrix * modelMatrix), vpT = transpose(projectionMatrix * viewMatrix);\nvec4 oPos = vec4(0.0), oFog, oPts{Temps};\n"); + + for (int i = 1; i < code.Length;) { + uint tok = code[i++]; + ShaderOpcode op = (ShaderOpcode)(tok & OpcodeMask); + if (op == ShaderOpcode.End) + break; + int len = op == ShaderOpcode.Comment ? CommentLength(tok) : InstructionLength(tok); + ReadOnlySpan a = code.Slice(i, len); + i += len; + + if (op is ShaderOpcode.Nop or ShaderOpcode.Comment or ShaderOpcode.Phase) + continue; + + if (op == ShaderOpcode.Dcl) { + int n = RegNum(a[1]); + RegType type = GetRegType(a[1]); + DeclUsage usage = GetDeclUsage(a[0]); + int index = UsageIndex(a[0]); + if (type == RegType.Sampler) { + SamplerTextureType samplerType = GetSamplerType(a[0]); + samplers[n & 15] = samplerType; + head.Append($"layout(binding = {n}) uniform {samplerType switch { SamplerTextureType.Cube => "samplerCube", SamplerTextureType.Volume => "sampler3D", _ => "sampler2D" }} s{n};\n"); + } + else if (type == RegType.Input && pixel) + inNames[n & 15] = Use("in", usage == DeclUsage.Color ? $"vs_Color{index}" : $"vs_TexCoord{index}"); + else if (type == RegType.Input) { + int location = usage switch { DeclUsage.Position => 0, DeclUsage.Normal => 1, DeclUsage.Color => 2, DeclUsage.TexCoord => 10 + index, _ => -1 }; + if (location < 0) + body.Append($"vec4 a{n} = vec4(0.0);\n"); + else + head.Append($"layout(location = {location}) in vec4 a{n};\n"); + } + else if (type == RegType.Output && !pixel) + outNames[n & 15] = usage switch { + DeclUsage.Position => "oPos", + DeclUsage.TexCoord => Use("out", $"vs_TexCoord{index}"), + DeclUsage.Color => Use("out", $"vs_Color{index}"), + _ => "oFog" + }; + else if (type == RegType.Texture && pixel) + Use("in", $"vs_TexCoord{n}"); + continue; + } + + if (op == ShaderOpcode.Def) { + int n = RegNum(a[0]); + defs[n] = true; + head.Append(CultureInfo.InvariantCulture, $"const vec4 c{n} = vec4({BitConverter.UInt32BitsToSingle(a[1]):R}, {BitConverter.UInt32BitsToSingle(a[2]):R}, {BitConverter.UInt32BitsToSingle(a[3]):R}, {BitConverter.UInt32BitsToSingle(a[4]):R});\n"); + continue; + } + + if (op == ShaderOpcode.Ifc) { + string cmp = (ShaderComparison)OpcodeControl(tok) switch { + ShaderComparison.Gt => ">", + ShaderComparison.Eq => "==", + ShaderComparison.Ge => ">=", + ShaderComparison.Lt => "<", + ShaderComparison.Ne => "!=", + _ => "<=" + }; + body.Append($"if ({Src(a[0])}.x {cmp} {Src(a[1])}.x) {{\n"); + continue; + } + if (op == ShaderOpcode.Else) { + body.Append("} else {\n"); + continue; + } + if (op == ShaderOpcode.EndIf) { + body.Append("}\n"); + continue; + } + + string? dst = Reg(a[0]); + if (dst == null) + return null; + + if (op == ShaderOpcode.TexKill) { + body.Append($"if (any(lessThan({dst}.xyz, vec3(0.0)))) discard;\n"); + continue; + } + + string s1 = len > 1 ? Src(a[1]) : "", s2 = len > 2 ? Src(a[2]) : "", s3 = len > 3 ? Src(a[3]) : "", s4 = len > 4 ? Src(a[4]) : ""; + string smp = len > 2 ? $"s{RegNum(a[2])}" : ""; + string tc = len > 2 && samplers[RegNum(a[2]) & 15] is SamplerTextureType.Cube or SamplerTextureType.Volume ? ".xyz" : ".xy"; + + string? expr = op switch { + ShaderOpcode.Mov => s1, + ShaderOpcode.Add => $"{s1} + {s2}", + ShaderOpcode.Sub => $"{s1} - {s2}", + ShaderOpcode.Mad => $"{s1} * {s2} + {s3}", + ShaderOpcode.Mul => $"{s1} * {s2}", + ShaderOpcode.Rcp => $"vec4(1.0 / {s1}.x)", + ShaderOpcode.Rsq => $"vec4(inversesqrt(abs({s1}.x)))", + ShaderOpcode.Dp3 => $"vec4(dot({s1}.xyz, {s2}.xyz))", + ShaderOpcode.Dp4 => $"vec4(dot({s1}, {s2}))", + ShaderOpcode.Min => $"min({s1}, {s2})", + ShaderOpcode.Max => $"max({s1}, {s2})", + ShaderOpcode.Slt => $"vec4(lessThan({s1}, {s2}))", + ShaderOpcode.Sge => $"vec4(greaterThanEqual({s1}, {s2}))", + ShaderOpcode.Exp => $"vec4(exp2({s1}.x))", + ShaderOpcode.Log => $"vec4(log2(abs({s1}.x)))", + ShaderOpcode.Lrp => $"mix({s3}, {s2}, {s1})", + ShaderOpcode.Frc => $"fract({s1})", + ShaderOpcode.M4x4 => Rows(a[1], a[2], 4, false), + ShaderOpcode.M4x3 => Rows(a[1], a[2], 3, false), + ShaderOpcode.M3x4 => Rows(a[1], a[2], 4, true), + ShaderOpcode.M3x3 => Rows(a[1], a[2], 3, true), + ShaderOpcode.M3x2 => Rows(a[1], a[2], 2, true), + ShaderOpcode.Pow => $"vec4(pow(abs({s1}.x), {s2}.x))", + ShaderOpcode.Crs => $"vec4(cross({s1}.xyz, {s2}.xyz), 0.0)", + ShaderOpcode.Sgn => $"sign({s1})", + ShaderOpcode.Abs => $"abs({s1})", + ShaderOpcode.Nrm => $"{s1} * inversesqrt(dot({s1}.xyz, {s1}.xyz))", + ShaderOpcode.SinCos => $"vec4(cos({s1}.x), sin({s1}.x), 0.0, 0.0)", + ShaderOpcode.Tex => (TexLoadControl)OpcodeControl(tok) switch { + TexLoadControl.Project => $"textureProj({smp}, {s1})", + TexLoadControl.Bias => $"texture({smp}, {s1}{tc}, {s1}.w)", + _ => $"texture({smp}, {s1}{tc})" + }, + ShaderOpcode.Cmp => $"mix({s3}, {s2}, greaterThanEqual({s1}, vec4(0.0)))", + ShaderOpcode.Dp2Add => $"vec4(dot({s1}.xy, {s2}.xy) + {s3}.x)", + ShaderOpcode.Dsx => $"dFdx({s1})", + ShaderOpcode.Dsy => $"dFdy({s1})", + ShaderOpcode.TexLdd => $"textureGrad({smp}, {s1}{tc}, {s3}{tc}, {s4}{tc})", + ShaderOpcode.TexLdl => $"textureLod({smp}, {s1}{tc}, {s1}.w)", + _ => null + }; + if (expr == null) + return null; + + if (Saturate(a[0])) + expr = $"clamp({expr}, 0.0, 1.0)"; + + if (GetRegType(a[0]) == RegType.DepthOut) { + body.Append($"gl_FragDepth = ({expr}).x;\n"); + continue; + } + + uint mask = WriteMask(a[0]); + string m = "."; + for (int c = 0; c < 4; c++) + if ((mask & (1u << c)) != 0) + m += Xyzw[c]; + body.Append($"{dst}{m} = ({expr}){m};\n"); + } + + if (!pixel) + body.Append("gl_Position = oPos;\n"); + foreach (string decl in io) + head.Append(decl).Append('\n'); + return head.Append(body).Append("}\n").ToString(); + } +} diff --git a/Source.StdShader.Gl46/Screenspace_General.cs b/Source.StdShader.Gl46/Screenspace_General.cs new file mode 100644 index 000000000..c3cc1826b --- /dev/null +++ b/Source.StdShader.Gl46/Screenspace_General.cs @@ -0,0 +1,282 @@ +using Source.Common; +using Source.Common.MaterialSystem; +using Source.Common.ShaderAPI; +using Source.Common.ShaderLib; + +namespace Source.StdShader.Gl46; + +public class Screenspace_General : BaseVSShader +{ + public static string HelpString = "Help for screenspace_general"; + public static int Flags = (int)ShaderParamFlags.NotEditable; + public static List ShaderParams = []; + public static ShaderParam[] ShaderParamOverrides = new ShaderParam[(int)ShaderMaterialVars.Count]; + + public class ShaderParam + { + public readonly ShaderParamInfo Info; + public readonly int Index; + public ShaderParam(ShaderMaterialVars var, ShaderParamType type, ReadOnlySpan defaultParam, ReadOnlySpan help, int flags) { + Info.Name = "override"; + Info.Type = type; + Info.DefaultValue = new(defaultParam); + Info.Help = new(help); + Info.Flags = (ShaderParamFlags)flags; + + if (ShaderParamOverrides[(int)var] == null) { + + } + else { + AssertMsg(false, "ShaderParamOverrides at var index had null value"); + } + + ShaderParamOverrides[(int)var] = this; + Index = (int)var; + } + public ShaderParam(string name, ShaderParamType type, ReadOnlySpan defaultParam, ReadOnlySpan help, int flags = 0) { + Info.Name = name; + Info.Type = type; + Info.DefaultValue = new(defaultParam); + Info.Help = new(help); + Info.Flags = (ShaderParamFlags)flags; + Index = (int)ShaderMaterialVars.Count + ShaderParams.Count; + ShaderParams.Add(this); + } + public static implicit operator int(ShaderParam param) => param.Index; + public ReadOnlySpan GetName() => Info.Name; + public ShaderParamType GetType() => Info.Type; + public ReadOnlySpan GetDefaultValue() => Info.DefaultValue; + public int GetFlags() => (int)Info.Flags; + public ReadOnlySpan GetHelp() => Info.Help; + } + + public static readonly ShaderParam ALPHA_BLEND = new($"${nameof(ALPHA_BLEND)}", ShaderParamType.Integer, "0", ""); + public static readonly ShaderParam ALPHA_BLEND_COLOR_OVERLAY = new($"${nameof(ALPHA_BLEND_COLOR_OVERLAY)}", ShaderParamType.Integer, "0", ""); + public static readonly ShaderParam ALPHABLEND = new($"${nameof(ALPHABLEND)}", ShaderParamType.Integer, "0", "whether or not to enable alpha blend"); + public static readonly ShaderParam ALPHATESTED = new($"${nameof(ALPHATESTED)}", ShaderParamType.Float, "0", ""); + public static readonly ShaderParam BLENDOPMIN = new($"${nameof(BLENDOPMIN)}", ShaderParamType.Integer, "0", ""); + public static readonly ShaderParam C0_W = new($"${nameof(C0_W)}", ShaderParamType.Float, "0", ""); + public static readonly ShaderParam C0_X = new($"${nameof(C0_X)}", ShaderParamType.Float, "0", ""); + public static readonly ShaderParam C0_Y = new($"${nameof(C0_Y)}", ShaderParamType.Float, "0", ""); + public static readonly ShaderParam C0_Z = new($"${nameof(C0_Z)}", ShaderParamType.Float, "0", ""); + public static readonly ShaderParam C1_W = new($"${nameof(C1_W)}", ShaderParamType.Float, "0", ""); + public static readonly ShaderParam C1_X = new($"${nameof(C1_X)}", ShaderParamType.Float, "0", ""); + public static readonly ShaderParam C1_Y = new($"${nameof(C1_Y)}", ShaderParamType.Float, "0", ""); + public static readonly ShaderParam C1_Z = new($"${nameof(C1_Z)}", ShaderParamType.Float, "0", ""); + public static readonly ShaderParam C2_W = new($"${nameof(C2_W)}", ShaderParamType.Float, "0", ""); + public static readonly ShaderParam C2_X = new($"${nameof(C2_X)}", ShaderParamType.Float, "0", ""); + public static readonly ShaderParam C2_Y = new($"${nameof(C2_Y)}", ShaderParamType.Float, "0", ""); + public static readonly ShaderParam C2_Z = new($"${nameof(C2_Z)}", ShaderParamType.Float, "0", ""); + public static readonly ShaderParam C3_W = new($"${nameof(C3_W)}", ShaderParamType.Float, "0", ""); + public static readonly ShaderParam C3_X = new($"${nameof(C3_X)}", ShaderParamType.Float, "0", ""); + public static readonly ShaderParam C3_Y = new($"${nameof(C3_Y)}", ShaderParamType.Float, "0", ""); + public static readonly ShaderParam C3_Z = new($"${nameof(C3_Z)}", ShaderParamType.Float, "0", ""); + public static readonly ShaderParam COPYALPHA = new($"${nameof(COPYALPHA)}", ShaderParamType.Integer, "0", ""); + public static readonly ShaderParam CULL = new($"${nameof(CULL)}", ShaderParamType.Integer, "0", ""); + public static readonly ShaderParam DEPTHTEST = new($"${nameof(DEPTHTEST)}", ShaderParamType.Integer, "0", "Enable Depthtest"); + public static readonly ShaderParam DISABLE_COLOR_WRITES = new($"${nameof(DISABLE_COLOR_WRITES)}", ShaderParamType.Integer, "0", ""); + public static readonly ShaderParam INVVIEWPROJMAT = new($"${nameof(INVVIEWPROJMAT)}", ShaderParamType.Matrix, "0", ""); + public static readonly ShaderParam LINEARREAD_BASETEXTURE = new($"${nameof(LINEARREAD_BASETEXTURE)}", ShaderParamType.Integer, "0", ""); + public static readonly ShaderParam LINEARREAD_TEXTURE1 = new($"${nameof(LINEARREAD_TEXTURE1)}", ShaderParamType.Integer, "0", ""); + public static readonly ShaderParam LINEARREAD_TEXTURE2 = new($"${nameof(LINEARREAD_TEXTURE2)}", ShaderParamType.Integer, "0", ""); + public static readonly ShaderParam LINEARREAD_TEXTURE3 = new($"${nameof(LINEARREAD_TEXTURE3)}", ShaderParamType.Integer, "0", ""); + public static readonly ShaderParam LINEARWRITE = new($"${nameof(LINEARWRITE)}", ShaderParamType.Integer, "0", ""); + public static readonly ShaderParam MULTIPLYCOLOR = new($"${nameof(MULTIPLYCOLOR)}", ShaderParamType.Integer, "0", ""); + public static readonly ShaderParam PIXSHADER = new($"${nameof(PIXSHADER)}", ShaderParamType.String, "", ""); + public static readonly ShaderParam POINTSAMPLE_BASETEXTURE = new($"${nameof(POINTSAMPLE_BASETEXTURE)}", ShaderParamType.Integer, "0", ""); + public static readonly ShaderParam POINTSAMPLE_TEXTURE1 = new($"${nameof(POINTSAMPLE_TEXTURE1)}", ShaderParamType.Integer, "0", ""); + public static readonly ShaderParam POINTSAMPLE_TEXTURE2 = new($"${nameof(POINTSAMPLE_TEXTURE2)}", ShaderParamType.Integer, "0", ""); + public static readonly ShaderParam POINTSAMPLE_TEXTURE3 = new($"${nameof(POINTSAMPLE_TEXTURE3)}", ShaderParamType.Integer, "0", ""); + public static readonly ShaderParam TCSIZE0 = new($"${nameof(TCSIZE0)}", ShaderParamType.Integer, "2", ""); + public static readonly ShaderParam TCSIZE1 = new($"${nameof(TCSIZE1)}", ShaderParamType.Integer, "0", ""); + public static readonly ShaderParam TCSIZE2 = new($"${nameof(TCSIZE2)}", ShaderParamType.Integer, "0", ""); + public static readonly ShaderParam TCSIZE3 = new($"${nameof(TCSIZE3)}", ShaderParamType.Integer, "0", ""); + public static readonly ShaderParam TCSIZE4 = new($"${nameof(TCSIZE4)}", ShaderParamType.Integer, "0", ""); + public static readonly ShaderParam TCSIZE5 = new($"${nameof(TCSIZE5)}", ShaderParamType.Integer, "0", ""); + public static readonly ShaderParam TCSIZE6 = new($"${nameof(TCSIZE6)}", ShaderParamType.Integer, "0", ""); + public static readonly ShaderParam TCSIZE7 = new($"${nameof(TCSIZE7)}", ShaderParamType.Integer, "0", ""); + public static readonly ShaderParam TEXTURE1 = new($"${nameof(TEXTURE1)}", ShaderParamType.Texture, "", ""); + public static readonly ShaderParam TEXTURE2 = new($"${nameof(TEXTURE2)}", ShaderParamType.Texture, "", ""); + public static readonly ShaderParam TEXTURE3 = new($"${nameof(TEXTURE3)}", ShaderParamType.Texture, "", ""); + public static readonly ShaderParam VERTEXNORMAL = new($"${nameof(VERTEXNORMAL)}", ShaderParamType.Integer, "0", ""); + public static readonly ShaderParam VERTEXSHADER = new($"${nameof(VERTEXSHADER)}", ShaderParamType.String, "", ""); + public static readonly ShaderParam VERTEXTRANSFORM = new($"${nameof(VERTEXTRANSFORM)}", ShaderParamType.Integer, "0", ""); + public static readonly ShaderParam VIEWPROJMAT = new($"${nameof(VIEWPROJMAT)}", ShaderParamType.Matrix, "0", ""); + public static readonly ShaderParam WRITEALPHA = new($"${nameof(WRITEALPHA)}", ShaderParamType.Integer, "0", ""); + public static readonly ShaderParam WRITEDEPTH = new($"${nameof(WRITEDEPTH)}", ShaderParamType.Integer, "0", "whether or not to enable depth write"); + + static readonly ShaderParam[] TCSizes = [TCSIZE0, TCSIZE1, TCSIZE2, TCSIZE3, TCSIZE4, TCSIZE5, TCSIZE6, TCSIZE7]; + + protected override void OnInitShaderParams(IMaterialVar[] parms, ReadOnlySpan materialName) { + if (!parms[TCSIZE0].IsDefined()) + parms[TCSIZE0].SetIntValue(2); + } + + public override string? GetFallbackShader(IMaterialVar[] vars) => null; + public override int GetFlags() => Flags; + public override int GetNumParams() => base.GetNumParams() + ShaderParams.Count; + public override ReadOnlySpan GetParamName(int paramIndex) { + int baseClassParamCount = base.GetNumParams(); + if (paramIndex < baseClassParamCount) + return base.GetParamName(paramIndex); + else + return ShaderParams[paramIndex - baseClassParamCount].GetName(); + } + public override ReadOnlySpan GetParamHelp(int paramIndex) { + int baseClassParamCount = base.GetNumParams(); + if (paramIndex < baseClassParamCount) + return base.GetParamHelp(paramIndex); + else + return ShaderParams[paramIndex - baseClassParamCount].GetHelp(); + } + public override ShaderParamType GetParamType(int paramIndex) { + int baseClassParamCount = base.GetNumParams(); + if (paramIndex < baseClassParamCount) + return base.GetParamType(paramIndex); + else + return ShaderParams[paramIndex - baseClassParamCount].GetType(); + } + public override ReadOnlySpan GetParamDefault(int paramIndex) { + int baseClassParamCount = base.GetNumParams(); + if (paramIndex < baseClassParamCount) + return base.GetParamDefault(paramIndex); + else + return ShaderParams[paramIndex - baseClassParamCount].GetDefaultValue(); + } + + protected override void OnInitShaderInstance(IMaterialVar[] parms, ReadOnlySpan materialName) { + if (parms[(int)ShaderMaterialVars.BaseTexture].IsDefined()) + LoadTexture((int)ShaderMaterialVars.BaseTexture); + if (parms[TEXTURE1].IsDefined()) + LoadTexture(TEXTURE1); + if (parms[TEXTURE2].IsDefined()) + LoadTexture(TEXTURE2); + if (parms[TEXTURE3].IsDefined()) + LoadTexture(TEXTURE3); + } + + void EnableTextureSRGBRead(IMaterialVar[] parms, Sampler sampler, int textureVar, int linearReadVar) { + if (!parms[textureVar].IsDefined()) + return; + ShaderShadow!.EnableTexture(sampler, true); + ShaderShadow.EnableSRGBRead(sampler, !parms[linearReadVar].IsDefined() || parms[linearReadVar].GetIntValue() == 0); + } + + void SetPixelSize(IMaterialVar[] parms, Sampler sampler, int textureVar, int register) { + if (!parms[textureVar].IsDefined()) + return; + BindTexture(sampler, textureVar, -1); + + ITexture target = parms[textureVar].GetTextureValue()!; + Span pixelSize = [1.0f / target.GetActualWidth(), 1.0f / target.GetActualHeight(), 0.0f, 0.0f]; + ShaderAPI!.SetPixelShaderConstant(register, pixelSize, 1); + } + + protected override void OnDrawElements(IMaterialVar[] parms, IShaderDynamicAPI shaderAPI, VertexCompressionType vertexCompression) { + bool customVertexShader = parms[VERTEXSHADER].IsDefined(); + if (ShaderShadow != null) { + ShaderShadow.EnableDepthWrites(parms[WRITEDEPTH].GetIntValue() != 0); + if (parms[WRITEDEPTH].GetIntValue() != 0) { + ShaderShadow.EnableDepthTest(true); + ShaderShadow.DepthFunc(ShaderDepthFunc.Always); + } + ShaderShadow.EnableAlphaWrites(parms[WRITEALPHA].GetIntValue() != 0); + ShaderShadow.EnableDepthTest(parms[DEPTHTEST].GetIntValue() != 0); + ShaderShadow.EnableCulling(parms[CULL].GetIntValue() != 0); + + EnableTextureSRGBRead(parms, Sampler.Sampler0, (int)ShaderMaterialVars.BaseTexture, LINEARREAD_BASETEXTURE); + EnableTextureSRGBRead(parms, Sampler.Sampler1, TEXTURE1, LINEARREAD_TEXTURE1); + EnableTextureSRGBRead(parms, Sampler.Sampler2, TEXTURE2, LINEARREAD_TEXTURE2); + EnableTextureSRGBRead(parms, Sampler.Sampler3, TEXTURE3, LINEARREAD_TEXTURE3); + + int vertexColor = IsFlagSet(parms, MaterialVarFlags.VertexColor) ? 1 : 0; + VertexFormat fmt = VertexFormat.Position; + if (vertexColor != 0) + fmt |= VertexFormat.Color; + if (parms[VERTEXNORMAL].GetIntValue() != 0) + fmt |= VertexFormat.Normal; + + Span texCoordDimensions = stackalloc int[8]; + int texCoordCount = 0; + while (texCoordCount < 8 && parms[TCSizes[texCoordCount]].GetIntValue() != 0) { + texCoordDimensions[texCoordCount] = parms[TCSizes[texCoordCount]].GetIntValue(); + texCoordCount++; + } + ShaderShadow.VertexShaderVertexFormat(fmt, texCoordCount, texCoordDimensions[..texCoordCount], 0); + + if (IsFlagSet(parms, MaterialVarFlags.Additive)) + EnableAlphaBlending(ShaderBlendFactor.One, ShaderBlendFactor.One); + else if (parms[MULTIPLYCOLOR].GetIntValue() != 0) + EnableAlphaBlending(ShaderBlendFactor.Zero, ShaderBlendFactor.SrcColor); + else if (parms[ALPHABLEND].GetIntValue() != 0) + EnableAlphaBlending(ShaderBlendFactor.SrcAlpha, ShaderBlendFactor.OneMinusSrcAlpha); + else + ShaderShadow.EnableBlending(false); + + if (parms[BLENDOPMIN].GetIntValue() != 0) { + ShaderShadow.BlendOp(ShaderBlendOp.Min); + ShaderShadow.EnableBlending(true); + } + + ShaderShadow.EnableSRGBWrite(parms[LINEARWRITE].GetIntValue() == 0); + + if (!customVertexShader) + ShaderShadow.SetVertexShader("screenspaceeffect", vertexColor + parms[VERTEXTRANSFORM].GetIntValue() * 2); + else + ShaderShadow.SetVertexShader(parms[VERTEXSHADER].GetStringValue(), 0); + + if (parms[DISABLE_COLOR_WRITES].GetIntValue() != 0) + ShaderShadow.EnableColorWrites(false); + + ShaderShadow.EnableAlphaTest(true); + ShaderShadow.AlphaFunc(ShaderAlphaFunc.Greater, 0.0f); + + string pixelShader = parms[PIXSHADER].GetStringValue(); + if (HardwareConfig.SupportsPixelShaders_2_b() && pixelShader.Length > 6 && (pixelShader.EndsWith("_ps20", StringComparison.OrdinalIgnoreCase) || pixelShader.EndsWith("_vs20", StringComparison.OrdinalIgnoreCase))) + pixelShader += "b"; + ShaderShadow.SetPixelShader(pixelShader, 0); + + if (parms[ALPHA_BLEND_COLOR_OVERLAY].GetIntValue() != 0) + EnableAlphaBlending(ShaderBlendFactor.One, ShaderBlendFactor.OneMinusSrcAlpha); + if (parms[ALPHA_BLEND].GetIntValue() != 0) + EnableAlphaBlending(ShaderBlendFactor.SrcAlpha, ShaderBlendFactor.OneMinusSrcAlpha); + if (parms[COPYALPHA].GetIntValue() != 0) { + ShaderShadow.EnableBlending(false); + ShaderShadow.AlphaFunc(ShaderAlphaFunc.Always, 0.0f); + } + } + if (ShaderAPI != null) { + SetPixelSize(parms, Sampler.Sampler0, (int)ShaderMaterialVars.BaseTexture, 4); + SetPixelSize(parms, Sampler.Sampler1, TEXTURE1, 5); + SetPixelSize(parms, Sampler.Sampler2, TEXTURE2, 6); + SetPixelSize(parms, Sampler.Sampler3, TEXTURE3, 7); + + Span c0 = [ + parms[C0_X].GetFloatValue(), parms[C0_Y].GetFloatValue(), parms[C0_Z].GetFloatValue(), parms[C0_W].GetFloatValue(), + parms[C1_X].GetFloatValue(), parms[C1_Y].GetFloatValue(), parms[C1_Z].GetFloatValue(), parms[C1_W].GetFloatValue(), + parms[C2_X].GetFloatValue(), parms[C2_Y].GetFloatValue(), parms[C2_Z].GetFloatValue(), parms[C2_W].GetFloatValue(), + parms[C3_X].GetFloatValue(), parms[C3_Y].GetFloatValue(), parms[C3_Z].GetFloatValue(), parms[C3_W].GetFloatValue() + ]; + ShaderAPI.SetPixelShaderConstant(0, c0, 4); + + Span eyePos = stackalloc float[4]; + ShaderAPI.GetWorldSpaceCameraPosition(eyePos); + ShaderAPI.SetPixelShaderConstant(10, eyePos, 1); + + if (parms[VIEWPROJMAT].IsDefined()) { + System.Numerics.Matrix4x4 viewProj = parms[VIEWPROJMAT].GetMatrixValue(); + ShaderAPI.SetPixelShaderConstant(11, System.Runtime.InteropServices.MemoryMarshal.CreateReadOnlySpan(ref viewProj.M11, 16), 4); + } + if (parms[INVVIEWPROJMAT].IsDefined()) { + System.Numerics.Matrix4x4 invViewProj = parms[INVVIEWPROJMAT].GetMatrixValue(); + ShaderAPI.SetPixelShaderConstant(15, System.Runtime.InteropServices.MemoryMarshal.CreateReadOnlySpan(ref invViewProj.M11, 16), 4); + } + + ShaderAPI.SetVertexShaderIndex(0); + ShaderAPI.SetPixelShaderIndex(0); + if (!customVertexShader) + ShaderAPI.SetVertexShaderIndex(0); + } + Draw(); + } +}