Files
bluflame/intromat/Intromat/ViewModels/ShaderFileViewModel.cs
2026-04-18 22:31:51 +02:00

353 lines
10 KiB
C#

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<ShaderFileViewModel>));
}
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;
}
}