using Intromat.Views; using ReactiveUI; using Splat; namespace Intromat.ViewModels { public sealed class ShaderFileViewModel : FileViewModel { private string _source = string.Empty; static ShaderFileViewModel() { Locator.CurrentMutable.Register(() => new ShaderFileView(), typeof(IViewFor)); } public ShaderFileViewModel(MainViewModel mainVm, ModuleViewModel module, FolderViewModel parent, string name) : base(module, parent, name, "hlsl") { } public static ShaderFileViewModel CreateDefault(MainViewModel mainVm, ModuleViewModel module, FolderViewModel parent, string name) { var shaderVm = new ShaderFileViewModel(mainVm, module, parent, name); shaderVm.Source = @" // Pixel UberShader int idot(int3 x, int3 y) { int3 tmp = x * y; return tmp.x + tmp.y + tmp.z; } int idot(int4 x, int4 y) { int4 tmp = x * y; return tmp.x + tmp.y + tmp.z + tmp.w; } int iround(float x) { return int (round(x)); } int2 iround(float2 x) { return int2(round(x)); } int3 iround(float3 x) { return int3(round(x)); } int4 iround(float4 x) { return int4(round(x)); } int itrunc(float x) { return int (trunc(x)); } int2 itrunc(float2 x) { return int2(trunc(x)); } int3 itrunc(float3 x) { return int3(trunc(x)); } int4 itrunc(float4 x) { return int4(trunc(x)); } SamplerState samp[8] : register(s0); Texture2DArray Tex[8] : register(t0); cbuffer PSBlock : register(b0) { int4 color[4]; int4 k[4]; int4 alphaRef; float4 texdim[8]; int4 czbias[2]; int4 cindscale[2]; int4 cindmtx[6]; int4 cfogcolor; int4 cfogi; float4 cfogf[2]; float4 czslope; float4 cefbscale; }; struct VS_OUTPUT { float4 pos : POSITION; float4 colors_0 : COLOR0; float4 colors_1 : COLOR1; float3 tex[8] : TEXCOORD0; float4 clipPos : TEXCOORD8; }; cbuffer UBERBlock : register(b4) { uint bpmem_genmode; uint bpmem_tevorder[8]; uint2 bpmem_combiners[16]; uint bpmem_tevksel[8]; int4 konstLookup[32]; float4 debug; }; uint bitfieldExtract(uint val, int off, int size) { // This built-in function is only support in OpenGL 4.0 and ES 3.1 // Hopefully the shader compiler will get our meaning and emit the right instruction uint mask = uint((1 << size) - 1); return uint(val >> off) & mask; } int4 sampleTexture(uint sampler_num, float2 uv) { // This is messy, but DirectX, OpenGl 3.3 and Opengl ES 3.0 doesn't support dynamic indexing of the sampler array // With any luck the shader compiler will optimise this if the hardware supports dynamic indexing. switch(sampler_num & 0x7u) { case 0u: return int4(Tex[0].Sample(samp[0], float3(uv, 0.0)) * 255.0); case 1u: return int4(Tex[1].Sample(samp[1], float3(uv, 0.0)) * 255.0); case 2u: return int4(Tex[2].Sample(samp[2], float3(uv, 0.0)) * 255.0); case 3u: return int4(Tex[3].Sample(samp[3], float3(uv, 0.0)) * 255.0); case 4u: return int4(Tex[4].Sample(samp[4], float3(uv, 0.0)) * 255.0); case 5u: return int4(Tex[5].Sample(samp[5], float3(uv, 0.0)) * 255.0); case 6u: return int4(Tex[6].Sample(samp[6], float3(uv, 0.0)) * 255.0); case 7u: return int4(Tex[7].Sample(samp[7], float3(uv, 0.0)) * 255.0); } } void main( out float4 ocol0 : SV_Target0, in float4 rawpos : SV_Position, in float4 colors_0 : COLOR0, in float4 colors_1 : COLOR1 , in float3 tex[8] : TEXCOORD0, in float4 clipPos : TEXCOORD8 ) { int3 ColorInput[16]; // ColorInput initial state: ColorInput[0] = color[0].rgb; ColorInput[1] = color[0].aaa; ColorInput[2] = color[1].rgb; ColorInput[3] = color[1].aaa; ColorInput[4] = color[2].rgb; ColorInput[5] = color[2].aaa; ColorInput[6] = color[3].rgb; ColorInput[7] = color[3].aaa; ColorInput[8] = int3(0, 0, 0); // TexColor.rgb (uninitilized) ColorInput[9] = int3(0, 0, 0); // TexColor.aaa (uninitilized) ColorInput[10] = int3(0, 0, 0); // RasColor.rgb (uninitilized) ColorInput[11] = int3(0, 0, 0); // RasColor.aaa (uninitilized) ColorInput[12] = int3(255, 255, 255); // One constant ColorInput[13] = int3(128, 128, 128); // Half constant ColorInput[14] = int3(0, 0, 0); // KonstColor.rgb (unititilized) ColorInput[15] = int3(0, 0, 0); // Zero constant int AlphaInput[8]; // AlphaInput's intial state: AlphaInput[0] = color[0].a; AlphaInput[1] = color[1].a; AlphaInput[2] = color[2].a; AlphaInput[3] = color[3].a; AlphaInput[4] = 0; // TexColor.a (uninitilized) AlphaInput[5] = 0; // RasColor.a (uninitilized) AlphaInput[6] = 0; // KostColor.a (uninitilized) AlphaInput[7] = 0; // Zero constant int AlphaBump = 0; int4 icolors_0 = int4(colors_0 * 255.0); int4 icolors_1 = int4(colors_1 * 255.0); int4 TevResult = color[0]; uint num_stages = bitfieldExtract(bpmem_genmode, 10, 4); // Main tev loop [loop] for(uint stage = 0u; stage < num_stages; stage++) { uint cc = bpmem_combiners[stage].x; uint ac = bpmem_combiners[stage].y; uint order = bpmem_tevorder[stage>>1]; if ((stage & 1u) == 1u) order = order >> 12; // TODO: Indirect textures // Sample texture for stage int4 texColor; if((order & 64u) != 0u) { // Texture is enabled uint sampler_num = bitfieldExtract(order, 0, 3); uint tex_coord = bitfieldExtract(order, 3, 3); // TODO: there is an optional perspective divide here (not to mention all of indirect) int2 fixedPoint_uv = itrunc(tex[tex_coord].xy * texdim[sampler_num].zw * 128.0); float2 uv = (float2(fixedPoint_uv) / 128.0) * texdim[sampler_num].xy; texColor = sampleTexture(sampler_num, uv); } else { // Texture is disabled texColor = int4(255, 255, 255, 255); } // TODO: color channel swapping ColorInput[8] = texColor.rgb; ColorInput[9] = texColor.aaa; AlphaInput[4] = texColor.a; // Set Konst for stage uint tevksel = bpmem_tevksel[stage>>1]; int4 konst; if ((stage & 1u) == 0u) konst = int4(konstLookup[bitfieldExtract(tevksel, 4, 5)].rgb, konstLookup[bitfieldExtract(tevksel, 9, 5)].a); else konst = int4(konstLookup[bitfieldExtract(tevksel, 14, 5)].rgb, konstLookup[bitfieldExtract(tevksel, 19, 5)].a); ColorInput[14] = konst.rgb; AlphaInput[6] = konst.a; // Set Ras for stage int4 ras; switch (bitfieldExtract(order, 7, 3)) { case 0u: // Color 0 ras = icolors_0; break; case 1u: // Color 1 ras = icolors_1; break; case 5u: // Alpha Bump ras = int4(AlphaBump, AlphaBump, AlphaBump, AlphaBump); break; case 6u: // Normalzied Alpha Bump int normalized = AlphaBump | AlphaBump >> 5; ras = int4(normalized, normalized, normalized, normalized); break; default: ras = int4(0, 0, 0, 0); break; } // TODO: color channel swapping ColorInput[10] = ras.rgb; ColorInput[11] = ras.aaa; AlphaInput[5] = ras.a; // Color Combiner { uint a = bitfieldExtract(cc, 12, 4); uint b = bitfieldExtract(cc, 8, 4); uint c = bitfieldExtract(cc, 4, 4); uint d = bitfieldExtract(cc, 0, 4); uint bias = bitfieldExtract(cc, 16, 2); bool op = bool(bitfieldExtract(cc, 18, 1)); bool _clamp = bool(bitfieldExtract(cc, 19, 1)); uint shift = bitfieldExtract(cc, 20, 2); uint dest = bitfieldExtract(cc, 22, 2); int3 A = ColorInput[a] & int3(255, 255, 255); int3 B = ColorInput[b] & int3(255, 255, 255); int3 C = ColorInput[c] & int3(255, 255, 255); int3 D = ColorInput[d]; // 10 bits + sign int3 result; if(bias != 3u) { // Normal mode // Lerp A and B with C C += C >> 7; // Scale C from 0..255 to 0..256 int3 lerp = (A << 8) + (B - A)*C; if (shift != 3u) { lerp = lerp << shift; lerp = lerp + (op ? 127 : 128); } result = lerp >> 8; // Add/Subtract D (and bias) if (bias == 1u) result += 128; else if (bias == 2u) result -= 128; if(!op) // Add result = D + result; else // Subtract result = D - result; // Most of the Shift was moved inside the lerp for improved percision // But we still do the divide by 2 here if (shift == 3u) result = result >> 1; } else { // Compare mode // Not implemented result = int3(255, 0, 0); } // Clamp result if (_clamp) result = clamp(result, 0, 255); else result = clamp(result, -1024, 1023); if (stage == num_stages) { // If this is the last stage // Write result to output TevResult.rgb = result; //break; } else { // Write result to the correct input register of the next stage ColorInput[dest<<1] = result; } } // Alpha Combiner { uint a = bitfieldExtract(ac, 13, 3); uint b = bitfieldExtract(ac, 10, 3); uint c = bitfieldExtract(ac, 7, 3); uint d = bitfieldExtract(ac, 4, 3); uint bias = bitfieldExtract(ac, 16, 2); bool op = bool(bitfieldExtract(ac, 18, 1)); bool _clamp = bool(bitfieldExtract(ac, 19, 1)); uint shift = bitfieldExtract(ac, 20, 2); uint dest = bitfieldExtract(ac, 22, 2); int A = AlphaInput[a] & 255; int B = AlphaInput[b] & 255; int C = AlphaInput[c] & 255; int D = AlphaInput[d]; // 10 bits + sign int result; if(bias != 3u) { // Normal mode // Lerp A and B with C C += C >> 7; // Scale C from 0..255 to 0..256 int lerp = (A << 8) + (B - A)*C; if (shift != 3u) { lerp = lerp << shift; lerp = lerp + (op ? 127 : 128); } result = lerp >> 8; // Add/Subtract D (and bias) if (bias == 1u) result += 128; else if (bias == 2u) result -= 128; if(!op) // Add result = D + result; else // Subtract result = D - result; // Most of the Shift was moved inside the lerp for improved percision // But we still do the divide by 2 here if (shift == 3u) result = result >> 1; } else { // Compare mode // Not implemented result = 255; } // Clamp result if (_clamp) result = clamp(result, 0, 255); else result = clamp(result, -1024, 1023); if (stage == num_stages) { // If this is the last stage // Write result to output TevResult.a = result; } else { // Write result to the correct input register of the next stage AlphaInput[dest] = result; ColorInput[(dest << 1) + 1u] = int3(result, result, result); } } } // Main tev loop ocol0 = float4(TevResult) / 255.0; } "; return shaderVm; } public string Source { get => _source; set => this.RaiseAndSetIfChanged(ref _source, value); } public override ReactiveObject CurrentViewModel => this; } }