Some formatting and naming changes, splitting files and some minor improvements.

This commit is contained in:
Ottermandias 2023-02-23 14:48:46 +01:00
parent 1e471551d4
commit a2b62a8b6a
13 changed files with 928 additions and 997 deletions

View file

@ -4,23 +4,22 @@ using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Penumbra.GameData.Interop; using Penumbra.GameData.Interop;
using static Penumbra.GameData.Files.ShpkFile;
namespace Penumbra.GameData.Data; namespace Penumbra.GameData.Data;
public class DisassembledShader public partial class DisassembledShader
{ {
public struct ResourceBinding public struct ResourceBinding
{ {
public string Name; public string Name;
public ResourceType Type; public ResourceType Type;
public Format Format; public Format Format;
public ResourceDimension Dimension; public ResourceDimension Dimension;
public uint Slot; public uint Slot;
public uint Elements; public uint Elements;
public uint RegisterCount; public uint RegisterCount;
public VectorComponents[] Used; public VectorComponents[] Used;
public VectorComponents UsedDynamically; public VectorComponents UsedDynamically;
} }
// Abbreviated using the uppercased first char of their name // Abbreviated using the uppercased first char of their name
@ -30,7 +29,7 @@ public class DisassembledShader
ConstantBuffer = 0x43, // 'C' ConstantBuffer = 0x43, // 'C'
Sampler = 0x53, // 'S' Sampler = 0x53, // 'S'
Texture = 0x54, // 'T' Texture = 0x54, // 'T'
UAV = 0x55, // 'U' Uav = 0x55, // 'U'
} }
// Abbreviated using the uppercased first and last char of their name // Abbreviated using the uppercased first and last char of their name
@ -56,22 +55,22 @@ public class DisassembledShader
public struct InputOutput public struct InputOutput
{ {
public string Name; public string Name;
public uint Index; public uint Index;
public VectorComponents Mask; public VectorComponents Mask;
public uint Register; public uint Register;
public string SystemValue; public string SystemValue;
public Format Format; public Format Format;
public VectorComponents Used; public VectorComponents Used;
} }
[Flags] [Flags]
public enum VectorComponents : byte public enum VectorComponents : byte
{ {
X = 1, X = 1,
Y = 2, Y = 2,
Z = 4, Z = 4,
W = 8, W = 8,
All = 15, All = 15,
} }
@ -82,21 +81,31 @@ public class DisassembledShader
Vertex = 0x56, // 'V' Vertex = 0x56, // 'V'
} }
private static readonly Regex ResourceBindingSizeRegex = new(@"\s(\w+)(?:\[\d+\])?;\s*//\s*Offset:\s*0\s*Size:\s*(\d+)$", RegexOptions.Multiline | RegexOptions.NonBacktracking); [GeneratedRegex(@"\s(\w+)(?:\[\d+\])?;\s*//\s*Offset:\s*0\s*Size:\s*(\d+)$", RegexOptions.Multiline | RegexOptions.NonBacktracking)]
private static readonly Regex SM3ConstantBufferUsageRegex = new(@"c(\d+)(?:\[([^\]]+)\])?(?:\.([wxyz]+))?", RegexOptions.NonBacktracking); private static partial Regex ResourceBindingSizeRegex();
private static readonly Regex SM3TextureUsageRegex = new(@"^\s*texld\S*\s+[^,]+,[^,]+,\s*s(\d+)", RegexOptions.NonBacktracking);
private static readonly Regex SM5ConstantBufferUsageRegex = new(@"cb(\d+)\[([^\]]+)\]\.([wxyz]+)", RegexOptions.NonBacktracking);
private static readonly Regex SM5TextureUsageRegex = new(@"^\s*sample_\S*\s+[^.]+\.([wxyz]+),[^,]+,\s*t(\d+)\.([wxyz]+)", RegexOptions.NonBacktracking);
private static readonly char[] Digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
public readonly string RawDisassembly; [GeneratedRegex(@"c(\d+)(?:\[([^\]]+)\])?(?:\.([wxyz]+))?", RegexOptions.NonBacktracking)]
public readonly uint ShaderModel; private static partial Regex Sm3ConstantBufferUsageRegex();
public readonly ShaderStage Stage;
public readonly string BufferDefinitions; [GeneratedRegex(@"^\s*texld\S*\s+[^,]+,[^,]+,\s*s(\d+)", RegexOptions.NonBacktracking)]
private static partial Regex Sm3TextureUsageRegex();
[GeneratedRegex(@"cb(\d+)\[([^\]]+)\]\.([wxyz]+)", RegexOptions.NonBacktracking)]
private static partial Regex Sm5ConstantBufferUsageRegex();
[GeneratedRegex(@"^\s*sample_\S*\s+[^.]+\.([wxyz]+),[^,]+,\s*t(\d+)\.([wxyz]+)", RegexOptions.NonBacktracking)]
private static partial Regex Sm5TextureUsageRegex();
private static readonly char[] Digits = Enumerable.Range(0, 10).Select(c => (char) ('0' + c)).ToArray();
public readonly string RawDisassembly;
public readonly uint ShaderModel;
public readonly ShaderStage Stage;
public readonly string BufferDefinitions;
public readonly ResourceBinding[] ResourceBindings; public readonly ResourceBinding[] ResourceBindings;
public readonly InputOutput[] InputSignature; public readonly InputOutput[] InputSignature;
public readonly InputOutput[] OutputSignature; public readonly InputOutput[] OutputSignature;
public readonly string[] Instructions; public readonly string[] Instructions;
public DisassembledShader(string rawDisassembly) public DisassembledShader(string rawDisassembly)
{ {
@ -104,49 +113,38 @@ public class DisassembledShader
var lines = rawDisassembly.Split('\n'); var lines = rawDisassembly.Split('\n');
Instructions = Array.FindAll(lines, ln => !ln.StartsWith("//") && ln.Length > 0); Instructions = Array.FindAll(lines, ln => !ln.StartsWith("//") && ln.Length > 0);
var shaderModel = Instructions[0].Trim().Split('_'); var shaderModel = Instructions[0].Trim().Split('_');
Stage = (ShaderStage)(byte)char.ToUpper(shaderModel[0][0]); Stage = (ShaderStage)(byte)char.ToUpper(shaderModel[0][0]);
ShaderModel = (uint.Parse(shaderModel[1]) << 8) | uint.Parse(shaderModel[2]); ShaderModel = (uint.Parse(shaderModel[1]) << 8) | uint.Parse(shaderModel[2]);
var header = PreParseHeader(lines.AsSpan()[..Array.IndexOf(lines, Instructions[0])]); var header = PreParseHeader(lines.AsSpan()[..Array.IndexOf(lines, Instructions[0])]);
switch (ShaderModel >> 8) switch (ShaderModel >> 8)
{ {
case 3: case 3:
ParseSM3Header(header, out BufferDefinitions, out ResourceBindings, out InputSignature, out OutputSignature); ParseSm3Header(header, out BufferDefinitions, out ResourceBindings, out InputSignature, out OutputSignature);
ParseSM3ResourceUsage(Instructions, ResourceBindings); ParseSm3ResourceUsage(Instructions, ResourceBindings);
break; break;
case 5: case 5:
ParseSM5Header(header, out BufferDefinitions, out ResourceBindings, out InputSignature, out OutputSignature); ParseSm5Header(header, out BufferDefinitions, out ResourceBindings, out InputSignature, out OutputSignature);
ParseSM5ResourceUsage(Instructions, ResourceBindings); ParseSm5ResourceUsage(Instructions, ResourceBindings);
break; break;
default: default: throw new NotImplementedException();
throw new NotImplementedException();
} }
} }
public ResourceBinding? GetResourceBindingByName(ResourceType type, string name) public ResourceBinding? GetResourceBindingByName(ResourceType type, string name)
{ => ResourceBindings.FirstOrNull(b => b.Type == type && b.Name == name);
return ResourceBindings.Select(binding => new ResourceBinding?(binding)).FirstOrDefault(binding => binding!.Value.Type == type && binding!.Value.Name == name);
}
public ResourceBinding? GetResourceBindingBySlot(ResourceType type, uint slot) public ResourceBinding? GetResourceBindingBySlot(ResourceType type, uint slot)
{ => ResourceBindings.FirstOrNull(b => b.Type == type && b.Slot == slot);
return ResourceBindings.Select(binding => new ResourceBinding?(binding)).FirstOrDefault(binding => binding!.Value.Type == type && binding!.Value.Slot == slot);
}
public static DisassembledShader Disassemble(ReadOnlySpan<byte> shaderBlob) public static DisassembledShader Disassemble(ReadOnlySpan<byte> shaderBlob)
{ => new(D3DCompiler.Disassemble(shaderBlob));
return new DisassembledShader(D3DCompiler.Disassemble(shaderBlob));
}
private static void ParseSM3Header(Dictionary<string, string[]> header, out string bufferDefinitions, out ResourceBinding[] resourceBindings, out InputOutput[] inputSignature, out InputOutput[] outputSignature) private static void ParseSm3Header(Dictionary<string, string[]> header, out string bufferDefinitions,
out ResourceBinding[] resourceBindings, out InputOutput[] inputSignature, out InputOutput[] outputSignature)
{ {
if (header.TryGetValue("Parameters", out var rawParameters)) bufferDefinitions = header.TryGetValue("Parameters", out var rawParameters)
{ ? string.Join('\n', rawParameters)
bufferDefinitions = string.Join('\n', rawParameters); : string.Empty;
}
else
{
bufferDefinitions = string.Empty;
}
if (header.TryGetValue("Registers", out var rawRegisters)) if (header.TryGetValue("Registers", out var rawRegisters))
{ {
var (_, registers) = ParseTable(rawRegisters); var (_, registers) = ParseTable(rawRegisters);
@ -154,10 +152,8 @@ public class DisassembledShader
{ {
var type = (ResourceType)(byte)char.ToUpper(register[1][0]); var type = (ResourceType)(byte)char.ToUpper(register[1][0]);
if (type == ResourceType.Sampler) if (type == ResourceType.Sampler)
{
type = ResourceType.Texture; type = ResourceType.Texture;
} var size = uint.Parse(register[2]);
uint size = uint.Parse(register[2]);
return new ResourceBinding return new ResourceBinding
{ {
Name = register[0], Name = register[0],
@ -175,14 +171,15 @@ public class DisassembledShader
{ {
resourceBindings = Array.Empty<ResourceBinding>(); resourceBindings = Array.Empty<ResourceBinding>();
} }
inputSignature = Array.Empty<InputOutput>();
inputSignature = Array.Empty<InputOutput>();
outputSignature = Array.Empty<InputOutput>(); outputSignature = Array.Empty<InputOutput>();
} }
private static void ParseSM3ResourceUsage(string[] instructions, ResourceBinding[] resourceBindings) private static void ParseSm3ResourceUsage(string[] instructions, ResourceBinding[] resourceBindings)
{ {
var cbIndices = new Dictionary<uint, int>(); var cbIndices = new Dictionary<uint, int>();
var tIndices = new Dictionary<uint, int>(); var tIndices = new Dictionary<uint, int>();
{ {
var i = 0; var i = 0;
foreach (var binding in resourceBindings) foreach (var binding in resourceBindings)
@ -191,14 +188,13 @@ public class DisassembledShader
{ {
case ResourceType.ConstantBuffer: case ResourceType.ConstantBuffer:
for (var j = 0u; j < binding.RegisterCount; j++) for (var j = 0u; j < binding.RegisterCount; j++)
{
cbIndices[binding.Slot + j] = i; cbIndices[binding.Slot + j] = i;
}
break; break;
case ResourceType.Texture: case ResourceType.Texture:
tIndices[binding.Slot] = i; tIndices[binding.Slot] = i;
break; break;
} }
++i; ++i;
} }
} }
@ -206,43 +202,39 @@ public class DisassembledShader
{ {
var trimmed = instruction.Trim(); var trimmed = instruction.Trim();
if (trimmed.StartsWith("def") || trimmed.StartsWith("dcl")) if (trimmed.StartsWith("def") || trimmed.StartsWith("dcl"))
{
continue; continue;
}
foreach (Match cbMatch in SM3ConstantBufferUsageRegex.Matches(instruction)) foreach (Match cbMatch in Sm3ConstantBufferUsageRegex().Matches(instruction))
{ {
var buffer = uint.Parse(cbMatch.Groups[1].Value); var buffer = uint.Parse(cbMatch.Groups[1].Value);
if (cbIndices.TryGetValue(buffer, out var i)) if (cbIndices.TryGetValue(buffer, out var i))
{ {
var swizzle = cbMatch.Groups[3].Success ? ParseVectorComponents(cbMatch.Groups[3].Value) : VectorComponents.All; var swizzle = cbMatch.Groups[3].Success ? ParseVectorComponents(cbMatch.Groups[3].Value) : VectorComponents.All;
if (cbMatch.Groups[2].Success) if (cbMatch.Groups[2].Success)
{
resourceBindings[i].UsedDynamically |= swizzle; resourceBindings[i].UsedDynamically |= swizzle;
}
else else
{
resourceBindings[i].Used[buffer - resourceBindings[i].Slot] |= swizzle; resourceBindings[i].Used[buffer - resourceBindings[i].Slot] |= swizzle;
}
} }
} }
var tMatch = SM3TextureUsageRegex.Match(instruction);
var tMatch = Sm3TextureUsageRegex().Match(instruction);
if (tMatch.Success) if (tMatch.Success)
{ {
var texture = uint.Parse(tMatch.Groups[1].Value); var texture = uint.Parse(tMatch.Groups[1].Value);
if (tIndices.TryGetValue(texture, out var i)) if (tIndices.TryGetValue(texture, out var i))
{
resourceBindings[i].Used[0] = VectorComponents.All; resourceBindings[i].Used[0] = VectorComponents.All;
}
} }
} }
} }
private static void ParseSM5Header(Dictionary<string, string[]> header, out string bufferDefinitions, out ResourceBinding[] resourceBindings, out InputOutput[] inputSignature, out InputOutput[] outputSignature) private static void ParseSm5Header(Dictionary<string, string[]> header, out string bufferDefinitions,
out ResourceBinding[] resourceBindings, out InputOutput[] inputSignature, out InputOutput[] outputSignature)
{ {
if (header.TryGetValue("Resource Bindings", out var rawResBindings)) if (header.TryGetValue("Resource Bindings", out var rawResBindings))
{ {
var (head, resBindings) = ParseTable(rawResBindings); var (head, resBindings) = ParseTable(rawResBindings);
resourceBindings = Array.ConvertAll(resBindings, binding => { resourceBindings = Array.ConvertAll(resBindings, binding =>
{
var type = (ResourceType)(byte)char.ToUpper(binding[1][0]); var type = (ResourceType)(byte)char.ToUpper(binding[1][0]);
return new ResourceBinding return new ResourceBinding
{ {
@ -261,10 +253,11 @@ public class DisassembledShader
{ {
resourceBindings = Array.Empty<ResourceBinding>(); resourceBindings = Array.Empty<ResourceBinding>();
} }
if (header.TryGetValue("Buffer Definitions", out var rawBufferDefs)) if (header.TryGetValue("Buffer Definitions", out var rawBufferDefs))
{ {
bufferDefinitions = string.Join('\n', rawBufferDefs); bufferDefinitions = string.Join('\n', rawBufferDefs);
foreach (Match match in ResourceBindingSizeRegex.Matches(bufferDefinitions)) foreach (Match match in ResourceBindingSizeRegex().Matches(bufferDefinitions))
{ {
var name = match.Groups[1].Value; var name = match.Groups[1].Value;
var bytesSize = uint.Parse(match.Groups[2].Value); var bytesSize = uint.Parse(match.Groups[2].Value);
@ -272,7 +265,7 @@ public class DisassembledShader
if (pos >= 0) if (pos >= 0)
{ {
resourceBindings[pos].RegisterCount = (bytesSize + 0xF) >> 4; resourceBindings[pos].RegisterCount = (bytesSize + 0xF) >> 4;
resourceBindings[pos].Used = new VectorComponents[resourceBindings[pos].RegisterCount]; resourceBindings[pos].Used = new VectorComponents[resourceBindings[pos].RegisterCount];
} }
} }
} }
@ -281,30 +274,32 @@ public class DisassembledShader
bufferDefinitions = string.Empty; bufferDefinitions = string.Empty;
} }
static InputOutput ParseInputOutput(string[] inOut) => new() static InputOutput ParseInputOutput(string[] inOut)
{ => new()
Name = inOut[0], {
Index = uint.Parse(inOut[1]), Name = inOut[0],
Mask = ParseVectorComponents(inOut[2]), Index = uint.Parse(inOut[1]),
Register = uint.Parse(inOut[3]), Mask = ParseVectorComponents(inOut[2]),
SystemValue = string.Intern(inOut[4]), Register = uint.Parse(inOut[3]),
Format = (Format)(((byte)char.ToUpper(inOut[5][0]) << 8) | (byte)char.ToUpper(inOut[5][^1])), SystemValue = string.Intern(inOut[4]),
Used = ParseVectorComponents(inOut[6]), Format = (Format)(((byte)char.ToUpper(inOut[5][0]) << 8) | (byte)char.ToUpper(inOut[5][^1])),
}; Used = ParseVectorComponents(inOut[6]),
};
if (header.TryGetValue("Input signature", out var rawInputSig)) if (header.TryGetValue("Input signature", out var rawInputSig))
{ {
var (_, inputSig) = ParseTable(rawInputSig); var (_, inputSig) = ParseTable(rawInputSig);
inputSignature = Array.ConvertAll(inputSig, ParseInputOutput); inputSignature = Array.ConvertAll(inputSig, ParseInputOutput);
} }
else else
{ {
inputSignature = Array.Empty<InputOutput>(); inputSignature = Array.Empty<InputOutput>();
} }
if (header.TryGetValue("Output signature", out var rawOutputSig)) if (header.TryGetValue("Output signature", out var rawOutputSig))
{ {
var (_, outputSig) = ParseTable(rawOutputSig); var (_, outputSig) = ParseTable(rawOutputSig);
outputSignature = Array.ConvertAll(outputSig, ParseInputOutput); outputSignature = Array.ConvertAll(outputSig, ParseInputOutput);
} }
else else
{ {
@ -312,10 +307,10 @@ public class DisassembledShader
} }
} }
private static void ParseSM5ResourceUsage(string[] instructions, ResourceBinding[] resourceBindings) private static void ParseSm5ResourceUsage(string[] instructions, ResourceBinding[] resourceBindings)
{ {
var cbIndices = new Dictionary<uint, int>(); var cbIndices = new Dictionary<uint, int>();
var tIndices = new Dictionary<uint, int>(); var tIndices = new Dictionary<uint, int>();
{ {
var i = 0; var i = 0;
foreach (var binding in resourceBindings) foreach (var binding in resourceBindings)
@ -329,6 +324,7 @@ public class DisassembledShader
tIndices[binding.Slot] = i; tIndices[binding.Slot] = i;
break; break;
} }
++i; ++i;
} }
} }
@ -336,10 +332,9 @@ public class DisassembledShader
{ {
var trimmed = instruction.Trim(); var trimmed = instruction.Trim();
if (trimmed.StartsWith("def") || trimmed.StartsWith("dcl")) if (trimmed.StartsWith("def") || trimmed.StartsWith("dcl"))
{
continue; continue;
}
foreach (Match cbMatch in SM5ConstantBufferUsageRegex.Matches(instruction)) foreach (Match cbMatch in Sm5ConstantBufferUsageRegex().Matches(instruction))
{ {
var buffer = uint.Parse(cbMatch.Groups[1].Value); var buffer = uint.Parse(cbMatch.Groups[1].Value);
if (cbIndices.TryGetValue(buffer, out var i)) if (cbIndices.TryGetValue(buffer, out var i))
@ -348,9 +343,7 @@ public class DisassembledShader
if (int.TryParse(cbMatch.Groups[2].Value, out var vector)) if (int.TryParse(cbMatch.Groups[2].Value, out var vector))
{ {
if (vector < resourceBindings[i].Used.Length) if (vector < resourceBindings[i].Used.Length)
{
resourceBindings[i].Used[vector] |= swizzle; resourceBindings[i].Used[vector] |= swizzle;
}
} }
else else
{ {
@ -358,31 +351,24 @@ public class DisassembledShader
} }
} }
} }
var tMatch = SM5TextureUsageRegex.Match(instruction);
var tMatch = Sm5TextureUsageRegex().Match(instruction);
if (tMatch.Success) if (tMatch.Success)
{ {
var texture = uint.Parse(tMatch.Groups[2].Value); var texture = uint.Parse(tMatch.Groups[2].Value);
if (tIndices.TryGetValue(texture, out var i)) if (tIndices.TryGetValue(texture, out var i))
{ {
var outSwizzle = ParseVectorComponents(tMatch.Groups[1].Value); var outSwizzle = ParseVectorComponents(tMatch.Groups[1].Value);
var rawInSwizzle = tMatch.Groups[3].Value; var rawInSwizzle = tMatch.Groups[3].Value;
var inSwizzle = new StringBuilder(4); var inSwizzle = new StringBuilder(4);
if ((outSwizzle & VectorComponents.X) != 0) if ((outSwizzle & VectorComponents.X) != 0)
{
inSwizzle.Append(rawInSwizzle[0]); inSwizzle.Append(rawInSwizzle[0]);
}
if ((outSwizzle & VectorComponents.Y) != 0) if ((outSwizzle & VectorComponents.Y) != 0)
{
inSwizzle.Append(rawInSwizzle[1]); inSwizzle.Append(rawInSwizzle[1]);
}
if ((outSwizzle & VectorComponents.Z) != 0) if ((outSwizzle & VectorComponents.Z) != 0)
{
inSwizzle.Append(rawInSwizzle[2]); inSwizzle.Append(rawInSwizzle[2]);
}
if ((outSwizzle & VectorComponents.W) != 0) if ((outSwizzle & VectorComponents.W) != 0)
{
inSwizzle.Append(rawInSwizzle[3]); inSwizzle.Append(rawInSwizzle[3]);
}
resourceBindings[i].Used[0] |= ParseVectorComponents(inSwizzle.ToString()); resourceBindings[i].Used[0] |= ParseVectorComponents(inSwizzle.ToString());
} }
} }
@ -393,9 +379,9 @@ public class DisassembledShader
{ {
components = components.ToUpperInvariant(); components = components.ToUpperInvariant();
return (components.Contains('X') ? VectorComponents.X : 0) return (components.Contains('X') ? VectorComponents.X : 0)
| (components.Contains('Y') ? VectorComponents.Y : 0) | (components.Contains('Y') ? VectorComponents.Y : 0)
| (components.Contains('Z') ? VectorComponents.Z : 0) | (components.Contains('Z') ? VectorComponents.Z : 0)
| (components.Contains('W') ? VectorComponents.W : 0); | (components.Contains('W') ? VectorComponents.W : 0);
} }
private static Dictionary<string, string[]> PreParseHeader(ReadOnlySpan<string> header) private static Dictionary<string, string[]> PreParseHeader(ReadOnlySpan<string> header)
@ -405,17 +391,13 @@ public class DisassembledShader
void AddSection(string name, ReadOnlySpan<string> section) void AddSection(string name, ReadOnlySpan<string> section)
{ {
while (section.Length > 0 && section[0].Length <= 3) while (section.Length > 0 && section[0].Length <= 3)
{
section = section[1..]; section = section[1..];
}
while (section.Length > 0 && section[^1].Length <= 3) while (section.Length > 0 && section[^1].Length <= 3)
{
section = section[..^1]; section = section[..^1];
}
sections.Add(name, Array.ConvertAll(section.ToArray(), ln => ln.Length <= 3 ? string.Empty : ln[3..])); sections.Add(name, Array.ConvertAll(section.ToArray(), ln => ln.Length <= 3 ? string.Empty : ln[3..]));
} }
var lastSectionName = ""; var lastSectionName = "";
var lastSectionStart = 0; var lastSectionStart = 0;
for (var i = 1; i < header.Length - 1; ++i) for (var i = 1; i < header.Length - 1; ++i)
{ {
@ -423,11 +405,12 @@ public class DisassembledShader
if (header[i - 1].Length <= 3 && header[i + 1].Length <= 3 && (current = header[i].TrimEnd()).EndsWith(':')) if (header[i - 1].Length <= 3 && header[i + 1].Length <= 3 && (current = header[i].TrimEnd()).EndsWith(':'))
{ {
AddSection(lastSectionName, header[lastSectionStart..(i - 1)]); AddSection(lastSectionName, header[lastSectionStart..(i - 1)]);
lastSectionName = current[3..^1]; lastSectionName = current[3..^1];
lastSectionStart = i + 2; lastSectionStart = i + 2;
++i; // The next line cannot match ++i; // The next line cannot match
} }
} }
AddSection(lastSectionName, header[lastSectionStart..]); AddSection(lastSectionName, header[lastSectionStart..]);
return sections; return sections;
@ -442,9 +425,8 @@ public class DisassembledShader
{ {
var start = dashLine.IndexOf('-', i); var start = dashLine.IndexOf('-', i);
if (start < 0) if (start < 0)
{
break; break;
}
var end = dashLine.IndexOf(' ', start + 1); var end = dashLine.IndexOf(' ', start + 1);
if (end < 0) if (end < 0)
{ {
@ -462,20 +444,17 @@ public class DisassembledShader
{ {
var headerLine = lines[0]; var headerLine = lines[0];
for (var i = 0; i < columns.Count; ++i) for (var i = 0; i < columns.Count; ++i)
{
headers[i] = headerLine[columns[i]].Trim(); headers[i] = headerLine[columns[i]].Trim();
}
} }
var data = new List<string[]>(); var data = new List<string[]>();
foreach (var line in lines[2..]) foreach (var line in lines[2..])
{ {
var row = new string[columns.Count]; var row = new string[columns.Count];
for (var i = 0; i < columns.Count; ++i) for (var i = 0; i < columns.Count; ++i)
{
row[i] = line[columns[i]].Trim(); row[i] = line[columns[i]].Trim();
}
data.Add(row); data.Add(row);
} }
return (headers, data.ToArray()); return (headers, data.ToArray());
} }
} }

View file

@ -0,0 +1,90 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace Penumbra.GameData.Files;
public partial class MtrlFile
{
public unsafe struct ColorDyeSet
{
public struct Row
{
private ushort _data;
public ushort Template
{
get => (ushort)(_data >> 5);
set => _data = (ushort)((_data & 0x1F) | (value << 5));
}
public bool Diffuse
{
get => (_data & 0x01) != 0;
set => _data = (ushort)(value ? _data | 0x01 : _data & 0xFFFE);
}
public bool Specular
{
get => (_data & 0x02) != 0;
set => _data = (ushort)(value ? _data | 0x02 : _data & 0xFFFD);
}
public bool Emissive
{
get => (_data & 0x04) != 0;
set => _data = (ushort)(value ? _data | 0x04 : _data & 0xFFFB);
}
public bool Gloss
{
get => (_data & 0x08) != 0;
set => _data = (ushort)(value ? _data | 0x08 : _data & 0xFFF7);
}
public bool SpecularStrength
{
get => (_data & 0x10) != 0;
set => _data = (ushort)(value ? _data | 0x10 : _data & 0xFFEF);
}
}
public struct RowArray : IEnumerable<Row>
{
public const int NumRows = 16;
private fixed ushort _rowData[NumRows];
public ref Row this[int i]
{
get
{
fixed (ushort* ptr = _rowData)
{
return ref ((Row*)ptr)[i];
}
}
}
public IEnumerator<Row> GetEnumerator()
{
for (var i = 0; i < NumRows; ++i)
yield return this[i];
}
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
public ReadOnlySpan<byte> AsBytes()
{
fixed (ushort* ptr = _rowData)
{
return new ReadOnlySpan<byte>(ptr, NumRows * sizeof(ushort));
}
}
}
public RowArray Rows;
public string Name;
public ushort Index;
}
}

View file

@ -0,0 +1,135 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Numerics;
namespace Penumbra.GameData.Files;
public partial class MtrlFile
{
public unsafe struct ColorSet
{
public struct Row
{
public const int Size = 32;
private fixed ushort _data[16];
public Vector3 Diffuse
{
get => new(ToFloat(0), ToFloat(1), ToFloat(2));
set
{
_data[0] = FromFloat(value.X);
_data[1] = FromFloat(value.Y);
_data[2] = FromFloat(value.Z);
}
}
public Vector3 Specular
{
get => new(ToFloat(4), ToFloat(5), ToFloat(6));
set
{
_data[4] = FromFloat(value.X);
_data[5] = FromFloat(value.Y);
_data[6] = FromFloat(value.Z);
}
}
public Vector3 Emissive
{
get => new(ToFloat(8), ToFloat(9), ToFloat(10));
set
{
_data[8] = FromFloat(value.X);
_data[9] = FromFloat(value.Y);
_data[10] = FromFloat(value.Z);
}
}
public Vector2 MaterialRepeat
{
get => new(ToFloat(12), ToFloat(15));
set
{
_data[12] = FromFloat(value.X);
_data[15] = FromFloat(value.Y);
}
}
public Vector2 MaterialSkew
{
get => new(ToFloat(13), ToFloat(14));
set
{
_data[13] = FromFloat(value.X);
_data[14] = FromFloat(value.Y);
}
}
public float SpecularStrength
{
get => ToFloat(3);
set => _data[3] = FromFloat(value);
}
public float GlossStrength
{
get => ToFloat(7);
set => _data[7] = FromFloat(value);
}
public ushort TileSet
{
get => (ushort)(ToFloat(11) * 64f);
set => _data[11] = FromFloat(value / 64f);
}
private float ToFloat(int idx)
=> (float)BitConverter.UInt16BitsToHalf(_data[idx]);
private static ushort FromFloat(float x)
=> BitConverter.HalfToUInt16Bits((Half)x);
}
public struct RowArray : IEnumerable<Row>
{
public const int NumRows = 16;
private fixed byte _rowData[NumRows * Row.Size];
public ref Row this[int i]
{
get
{
fixed (byte* ptr = _rowData)
{
return ref ((Row*)ptr)[i];
}
}
}
public IEnumerator<Row> GetEnumerator()
{
for (var i = 0; i < NumRows; ++i)
yield return this[i];
}
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
public ReadOnlySpan<byte> AsBytes()
{
fixed (byte* ptr = _rowData)
{
return new ReadOnlySpan<byte>(ptr, NumRows * Row.Size);
}
}
}
public RowArray Rows;
public string Name;
public ushort Index;
public bool HasRows;
}
}

View file

@ -1,4 +1,3 @@
using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;

View file

@ -1,9 +1,7 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Numerics;
using System.Text; using System.Text;
using Lumina.Data.Parsing; using Lumina.Data.Parsing;
using Lumina.Extensions; using Lumina.Extensions;
@ -13,262 +11,10 @@ namespace Penumbra.GameData.Files;
public partial class MtrlFile : IWritable public partial class MtrlFile : IWritable
{ {
public struct UvSet
{
public string Name;
public ushort Index;
}
public unsafe struct ColorSet
{
public struct Row
{
public const int Size = 32;
private fixed ushort _data[16];
public Vector3 Diffuse
{
get => new(ToFloat(0), ToFloat(1), ToFloat(2));
set
{
_data[0] = FromFloat(value.X);
_data[1] = FromFloat(value.Y);
_data[2] = FromFloat(value.Z);
}
}
public Vector3 Specular
{
get => new(ToFloat(4), ToFloat(5), ToFloat(6));
set
{
_data[4] = FromFloat(value.X);
_data[5] = FromFloat(value.Y);
_data[6] = FromFloat(value.Z);
}
}
public Vector3 Emissive
{
get => new(ToFloat(8), ToFloat(9), ToFloat(10));
set
{
_data[8] = FromFloat(value.X);
_data[9] = FromFloat(value.Y);
_data[10] = FromFloat(value.Z);
}
}
public Vector2 MaterialRepeat
{
get => new(ToFloat(12), ToFloat(15));
set
{
_data[12] = FromFloat(value.X);
_data[15] = FromFloat(value.Y);
}
}
public Vector2 MaterialSkew
{
get => new(ToFloat(13), ToFloat(14));
set
{
_data[13] = FromFloat(value.X);
_data[14] = FromFloat(value.Y);
}
}
public float SpecularStrength
{
get => ToFloat(3);
set => _data[3] = FromFloat(value);
}
public float GlossStrength
{
get => ToFloat(7);
set => _data[7] = FromFloat(value);
}
public ushort TileSet
{
get => (ushort)(ToFloat(11) * 64f);
set => _data[11] = FromFloat(value / 64f);
}
private float ToFloat(int idx)
=> (float)BitConverter.UInt16BitsToHalf(_data[idx]);
private static ushort FromFloat(float x)
=> BitConverter.HalfToUInt16Bits((Half)x);
}
public struct RowArray : IEnumerable<Row>
{
public const int NumRows = 16;
private fixed byte _rowData[NumRows * Row.Size];
public ref Row this[int i]
{
get
{
fixed (byte* ptr = _rowData)
{
return ref ((Row*)ptr)[i];
}
}
}
public IEnumerator<Row> GetEnumerator()
{
for (var i = 0; i < NumRows; ++i)
yield return this[i];
}
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
public ReadOnlySpan<byte> AsBytes()
{
fixed (byte* ptr = _rowData)
{
return new ReadOnlySpan<byte>(ptr, NumRows * Row.Size);
}
}
}
public RowArray Rows;
public string Name;
public ushort Index;
public bool HasRows;
}
public unsafe struct ColorDyeSet
{
public struct Row
{
private ushort _data;
public ushort Template
{
get => (ushort)(_data >> 5);
set => _data = (ushort)((_data & 0x1F) | (value << 5));
}
public bool Diffuse
{
get => (_data & 0x01) != 0;
set => _data = (ushort)(value ? _data | 0x01 : _data & 0xFFFE);
}
public bool Specular
{
get => (_data & 0x02) != 0;
set => _data = (ushort)(value ? _data | 0x02 : _data & 0xFFFD);
}
public bool Emissive
{
get => (_data & 0x04) != 0;
set => _data = (ushort)(value ? _data | 0x04 : _data & 0xFFFB);
}
public bool Gloss
{
get => (_data & 0x08) != 0;
set => _data = (ushort)(value ? _data | 0x08 : _data & 0xFFF7);
}
public bool SpecularStrength
{
get => (_data & 0x10) != 0;
set => _data = (ushort)(value ? _data | 0x10 : _data & 0xFFEF);
}
}
public struct RowArray : IEnumerable<Row>
{
public const int NumRows = 16;
private fixed ushort _rowData[NumRows];
public ref Row this[int i]
{
get
{
fixed (ushort* ptr = _rowData)
{
return ref ((Row*)ptr)[i];
}
}
}
public IEnumerator<Row> GetEnumerator()
{
for (var i = 0; i < NumRows; ++i)
yield return this[i];
}
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
public ReadOnlySpan<byte> AsBytes()
{
fixed (ushort* ptr = _rowData)
{
return new ReadOnlySpan<byte>(ptr, NumRows * sizeof(ushort));
}
}
}
public RowArray Rows;
public string Name;
public ushort Index;
}
public struct Texture
{
public string Path;
public ushort Flags;
public bool DX11
=> (Flags & 0x8000) != 0;
}
public struct Constant
{
public uint Id;
public ushort ByteOffset;
public ushort ByteSize;
}
public struct ShaderPackageData
{
public string Name;
public ShaderKey[] ShaderKeys;
public Constant[] Constants;
public Sampler[] Samplers;
public float[] ShaderValues;
public uint Flags;
}
public readonly uint Version; public readonly uint Version;
public bool Valid
{ public bool Valid
get => CheckTextures();
{
foreach (var texture in Textures)
{
if (!texture.Path.Contains('/'))
{
return false;
}
}
return true;
}
}
public Texture[] Textures; public Texture[] Textures;
public UvSet[] UvSets; public UvSet[] UvSets;
@ -277,7 +23,7 @@ public partial class MtrlFile : IWritable
public ShaderPackageData ShaderPackage; public ShaderPackageData ShaderPackage;
public byte[] AdditionalData; public byte[] AdditionalData;
public ShpkFile? AssociatedShpk; public ShpkFile? AssociatedShpk;
public bool ApplyDyeTemplate(StmFile stm, int colorSetIdx, int rowIdx, StainId stainId) public bool ApplyDyeTemplate(StmFile stm, int colorSetIdx, int rowIdx, StainId stainId)
{ {
@ -324,15 +70,13 @@ public partial class MtrlFile : IWritable
public Span<float> GetConstantValues(Constant constant) public Span<float> GetConstantValues(Constant constant)
{ {
if ((constant.ByteOffset & 0x3) == 0 && (constant.ByteSize & 0x3) == 0 if ((constant.ByteOffset & 0x3) != 0
&& ((constant.ByteOffset + constant.ByteSize) >> 2) <= ShaderPackage.ShaderValues.Length) || (constant.ByteSize & 0x3) != 0
{ || (constant.ByteOffset + constant.ByteSize) >> 2 > ShaderPackage.ShaderValues.Length)
return ShaderPackage.ShaderValues.AsSpan().Slice(constant.ByteOffset >> 2, constant.ByteSize >> 2);
}
else
{
return null; return null;
}
return ShaderPackage.ShaderValues.AsSpan().Slice(constant.ByteOffset >> 2, constant.ByteSize >> 2);
} }
public List<(Sampler?, ShpkFile.Resource?)> GetSamplersByTexture() public List<(Sampler?, ShpkFile.Resource?)> GetSamplersByTexture()
@ -348,13 +92,7 @@ public partial class MtrlFile : IWritable
} }
return samplers; return samplers;
} }
// Activator.CreateInstance can't use a ctor with a default value so this has to be made explicit
public MtrlFile(byte[] data)
: this(data, null)
{
}
public MtrlFile(byte[] data, Func<string, ShpkFile?>? loadAssociatedShpk = null) public MtrlFile(byte[] data, Func<string, ShpkFile?>? loadAssociatedShpk = null)
{ {
@ -469,6 +207,41 @@ public partial class MtrlFile : IWritable
{ {
strings = strings[offset..]; strings = strings[offset..];
var end = strings.IndexOf((byte)'\0'); var end = strings.IndexOf((byte)'\0');
return Encoding.UTF8.GetString(strings[..end]); return Encoding.UTF8.GetString(end == -1 ? strings : strings[..end]);
}
private bool CheckTextures()
=> Textures.All(texture => texture.Path.Contains('/'));
public struct UvSet
{
public string Name;
public ushort Index;
}
public struct Texture
{
public string Path;
public ushort Flags;
public bool DX11
=> (Flags & 0x8000) != 0;
}
public struct Constant
{
public uint Id;
public ushort ByteOffset;
public ushort ByteSize;
}
public struct ShaderPackageData
{
public string Name;
public ShaderKey[] ShaderKeys;
public Constant[] Constants;
public Sampler[] Samplers;
public float[] ShaderValues;
public uint Flags;
} }
} }

View file

@ -0,0 +1,220 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using Lumina.Misc;
using Penumbra.GameData.Data;
namespace Penumbra.GameData.Files;
public partial class ShpkFile
{
public struct Shader
{
public DisassembledShader.ShaderStage Stage;
public DxVersion DirectXVersion;
public Resource[] Constants;
public Resource[] Samplers;
public Resource[] Uavs;
public byte[] AdditionalHeader;
private byte[] _byteData;
private DisassembledShader? _disassembly;
public byte[] Blob
{
get => _byteData;
set
{
if (_byteData == value)
return;
if (Stage != DisassembledShader.ShaderStage.Unspecified)
{
// Reject the blob entirely if we can't disassemble it or if we find inconsistencies.
var disasm = DisassembledShader.Disassemble(value);
if (disasm.Stage != Stage || (disasm.ShaderModel >> 8) + 6 != (uint)DirectXVersion)
throw new ArgumentException(
$"The supplied blob is a DirectX {(disasm.ShaderModel >> 8) + 6} {disasm.Stage} shader ; expected a DirectX {(uint)DirectXVersion} {Stage} shader.",
nameof(value));
if (disasm.ShaderModel >= 0x0500)
{
var samplers = new Dictionary<uint, string>();
var textures = new Dictionary<uint, string>();
foreach (var binding in disasm.ResourceBindings)
{
switch (binding.Type)
{
case DisassembledShader.ResourceType.Texture:
textures[binding.Slot] = NormalizeResourceName(binding.Name);
break;
case DisassembledShader.ResourceType.Sampler:
samplers[binding.Slot] = NormalizeResourceName(binding.Name);
break;
}
}
if (samplers.Count != textures.Count
|| !samplers.All(pair => textures.TryGetValue(pair.Key, out var texName) && pair.Value == texName))
throw new ArgumentException($"The supplied blob has inconsistent sampler and texture allocation.");
}
_byteData = value;
_disassembly = disasm;
}
else
{
_byteData = value;
_disassembly = null;
}
UpdateUsed();
}
}
public DisassembledShader? Disassembly
=> _disassembly;
public Resource? GetConstantById(uint id)
=> Constants.FirstOrNull(res => res.Id == id);
public Resource? GetConstantByName(string name)
=> Constants.FirstOrNull(res => res.Name == name);
public Resource? GetSamplerById(uint id)
=> Samplers.FirstOrNull(s => s.Id == id);
public Resource? GetSamplerByName(string name)
=> Samplers.FirstOrNull(s => s.Name == name);
public Resource? GetUavById(uint id)
=> Uavs.FirstOrNull(u => u.Id == id);
public Resource? GetUavByName(string name)
=> Uavs.FirstOrNull(u => u.Name == name);
public void UpdateResources(ShpkFile file)
{
if (_disassembly == null)
throw new InvalidOperationException();
var constants = new List<Resource>();
var samplers = new List<Resource>();
var uavs = new List<Resource>();
foreach (var binding in _disassembly.ResourceBindings)
{
switch (binding.Type)
{
case DisassembledShader.ResourceType.ConstantBuffer:
var name = NormalizeResourceName(binding.Name);
// We want to preserve IDs as much as possible, and to deterministically generate new ones in a way that's most compliant with the native ones, to maximize compatibility.
var id = GetConstantByName(name)?.Id ?? file.GetConstantByName(name)?.Id ?? Crc32.Get(name, 0xFFFFFFFFu);
constants.Add(new Resource
{
Id = id,
Name = name,
Slot = (ushort)binding.Slot,
Size = (ushort)binding.RegisterCount,
Used = binding.Used,
UsedDynamically = binding.UsedDynamically,
});
break;
case DisassembledShader.ResourceType.Texture:
name = NormalizeResourceName(binding.Name);
id = GetSamplerByName(name)?.Id ?? file.GetSamplerByName(name)?.Id ?? Crc32.Get(name, 0xFFFFFFFFu);
samplers.Add(new Resource
{
Id = id,
Name = name,
Slot = (ushort)binding.Slot,
Size = (ushort)binding.Slot,
Used = binding.Used,
UsedDynamically = binding.UsedDynamically,
});
break;
case DisassembledShader.ResourceType.Uav:
name = NormalizeResourceName(binding.Name);
id = GetUavByName(name)?.Id ?? file.GetUavByName(name)?.Id ?? Crc32.Get(name, 0xFFFFFFFFu);
uavs.Add(new Resource
{
Id = id,
Name = name,
Slot = (ushort)binding.Slot,
Size = (ushort)binding.Slot,
Used = binding.Used,
UsedDynamically = binding.UsedDynamically,
});
break;
}
}
Constants = constants.ToArray();
Samplers = samplers.ToArray();
Uavs = uavs.ToArray();
}
private void UpdateUsed()
{
if (_disassembly != null)
{
var cbUsage = new Dictionary<string, (DisassembledShader.VectorComponents[], DisassembledShader.VectorComponents)>();
var tUsage = new Dictionary<string, (DisassembledShader.VectorComponents[], DisassembledShader.VectorComponents)>();
var uUsage = new Dictionary<string, (DisassembledShader.VectorComponents[], DisassembledShader.VectorComponents)>();
foreach (var binding in _disassembly.ResourceBindings)
{
switch (binding.Type)
{
case DisassembledShader.ResourceType.ConstantBuffer:
cbUsage[NormalizeResourceName(binding.Name)] = (binding.Used, binding.UsedDynamically);
break;
case DisassembledShader.ResourceType.Texture:
tUsage[NormalizeResourceName(binding.Name)] = (binding.Used, binding.UsedDynamically);
break;
case DisassembledShader.ResourceType.Uav:
uUsage[NormalizeResourceName(binding.Name)] = (binding.Used, binding.UsedDynamically);
break;
}
}
static void CopyUsed(Resource[] resources,
Dictionary<string, (DisassembledShader.VectorComponents[], DisassembledShader.VectorComponents)> used)
{
for (var i = 0; i < resources.Length; ++i)
{
if (used.TryGetValue(resources[i].Name, out var usage))
{
resources[i].Used = usage.Item1;
resources[i].UsedDynamically = usage.Item2;
}
else
{
resources[i].Used = null;
resources[i].UsedDynamically = null;
}
}
}
CopyUsed(Constants, cbUsage);
CopyUsed(Samplers, tUsage);
CopyUsed(Uavs, uUsage);
}
else
{
ClearUsed(Constants);
ClearUsed(Samplers);
ClearUsed(Uavs);
}
}
private static string NormalizeResourceName(string resourceName)
{
var dot = resourceName.IndexOf('.');
if (dot >= 0)
return resourceName[..dot];
if (resourceName.Length > 1 && resourceName[^2] is '_' && resourceName[^1] is 'S' or 'T')
return resourceName[..^2];
return resourceName;
}
}
}

View file

@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace Penumbra.GameData.Files;
public partial class ShpkFile
{
public class StringPool
{
public MemoryStream Data;
public List<int> StartingOffsets;
public StringPool(ReadOnlySpan<byte> bytes)
{
Data = new MemoryStream();
Data.Write(bytes);
StartingOffsets = new List<int>
{
0,
};
for (var i = 0; i < bytes.Length; ++i)
{
if (bytes[i] == 0)
StartingOffsets.Add(i + 1);
}
if (StartingOffsets[^1] == bytes.Length)
StartingOffsets.RemoveAt(StartingOffsets.Count - 1);
else
Data.WriteByte(0);
}
public string GetString(int offset, int size)
=> Encoding.UTF8.GetString(Data.GetBuffer().AsSpan().Slice(offset, size));
public string GetNullTerminatedString(int offset)
{
var str = Data.GetBuffer().AsSpan()[offset..];
var size = str.IndexOf((byte)0);
if (size >= 0)
str = str[..size];
return Encoding.UTF8.GetString(str);
}
public (int, int) FindOrAddString(string str)
{
var dataSpan = Data.GetBuffer().AsSpan();
var bytes = Encoding.UTF8.GetBytes(str);
foreach (var offset in StartingOffsets)
{
if (offset + bytes.Length > Data.Length)
break;
var strSpan = dataSpan[offset..];
var match = true;
for (var i = 0; i < bytes.Length; ++i)
{
if (strSpan[i] != bytes[i])
{
match = false;
break;
}
}
if (match && strSpan[bytes.Length] == 0)
return (offset, bytes.Length);
}
Data.Seek(0L, SeekOrigin.End);
var newOffset = (int)Data.Position;
StartingOffsets.Add(newOffset);
Data.Write(bytes);
Data.WriteByte(0);
return (newOffset, bytes.Length);
}
}
}

View file

@ -8,21 +8,19 @@ public partial class ShpkFile
public byte[] Write() public byte[] Write()
{ {
if (SubViewKeys.Length != 2) if (SubViewKeys.Length != 2)
{
throw new InvalidDataException(); throw new InvalidDataException();
}
using var stream = new MemoryStream(); using var stream = new MemoryStream();
using var blobs = new MemoryStream(); using var blobs = new MemoryStream();
var strings = new StringPool(ReadOnlySpan<byte>.Empty); var strings = new StringPool(ReadOnlySpan<byte>.Empty);
using (var w = new BinaryWriter(stream)) using (var w = new BinaryWriter(stream))
{ {
w.Write(ShPkMagic); w.Write(ShPkMagic);
w.Write(Version); w.Write(Version);
w.Write(DirectXVersion switch w.Write(DirectXVersion switch
{ {
DXVersion.DirectX9 => DX9Magic, DxVersion.DirectX9 => Dx9Magic,
DXVersion.DirectX11 => DX11Magic, DxVersion.DirectX11 => Dx11Magic,
_ => throw new NotImplementedException(), _ => throw new NotImplementedException(),
}); });
var offsetsPosition = stream.Position; var offsetsPosition = stream.Position;
@ -35,7 +33,7 @@ public partial class ShpkFile
w.Write((uint)MaterialParams.Length); w.Write((uint)MaterialParams.Length);
w.Write((uint)Constants.Length); w.Write((uint)Constants.Length);
w.Write((uint)Samplers.Length); w.Write((uint)Samplers.Length);
w.Write((uint)UAVs.Length); w.Write((uint)Uavs.Length);
w.Write((uint)SystemKeys.Length); w.Write((uint)SystemKeys.Length);
w.Write((uint)SceneKeys.Length); w.Write((uint)SceneKeys.Length);
w.Write((uint)MaterialKeys.Length); w.Write((uint)MaterialKeys.Length);
@ -43,7 +41,7 @@ public partial class ShpkFile
w.Write((uint)Items.Length); w.Write((uint)Items.Length);
WriteShaderArray(w, VertexShaders, blobs, strings); WriteShaderArray(w, VertexShaders, blobs, strings);
WriteShaderArray(w, PixelShaders, blobs, strings); WriteShaderArray(w, PixelShaders, blobs, strings);
foreach (var materialParam in MaterialParams) foreach (var materialParam in MaterialParams)
{ {
@ -53,54 +51,50 @@ public partial class ShpkFile
} }
WriteResourceArray(w, Constants, strings); WriteResourceArray(w, Constants, strings);
WriteResourceArray(w, Samplers, strings); WriteResourceArray(w, Samplers, strings);
WriteResourceArray(w, UAVs, strings); WriteResourceArray(w, Uavs, strings);
foreach (var key in SystemKeys) foreach (var key in SystemKeys)
{ {
w.Write(key.Id); w.Write(key.Id);
w.Write(key.DefaultValue); w.Write(key.DefaultValue);
} }
foreach (var key in SceneKeys) foreach (var key in SceneKeys)
{ {
w.Write(key.Id); w.Write(key.Id);
w.Write(key.DefaultValue); w.Write(key.DefaultValue);
} }
foreach (var key in MaterialKeys) foreach (var key in MaterialKeys)
{ {
w.Write(key.Id); w.Write(key.Id);
w.Write(key.DefaultValue); w.Write(key.DefaultValue);
} }
foreach (var key in SubViewKeys) foreach (var key in SubViewKeys)
{
w.Write(key.DefaultValue); w.Write(key.DefaultValue);
}
foreach (var node in Nodes) foreach (var node in Nodes)
{ {
if (node.PassIndices.Length != 16 || node.SystemKeys.Length != SystemKeys.Length || node.SceneKeys.Length != SceneKeys.Length || node.MaterialKeys.Length != MaterialKeys.Length || node.SubViewKeys.Length != SubViewKeys.Length) if (node.PassIndices.Length != 16
{ || node.SystemKeys.Length != SystemKeys.Length
|| node.SceneKeys.Length != SceneKeys.Length
|| node.MaterialKeys.Length != MaterialKeys.Length
|| node.SubViewKeys.Length != SubViewKeys.Length)
throw new InvalidDataException(); throw new InvalidDataException();
}
w.Write(node.Id); w.Write(node.Id);
w.Write(node.Passes.Length); w.Write(node.Passes.Length);
w.Write(node.PassIndices); w.Write(node.PassIndices);
foreach (var key in node.SystemKeys) foreach (var key in node.SystemKeys)
{
w.Write(key); w.Write(key);
}
foreach (var key in node.SceneKeys) foreach (var key in node.SceneKeys)
{
w.Write(key); w.Write(key);
}
foreach (var key in node.MaterialKeys) foreach (var key in node.MaterialKeys)
{
w.Write(key); w.Write(key);
}
foreach (var key in node.SubViewKeys) foreach (var key in node.SubViewKeys)
{
w.Write(key); w.Write(key);
}
foreach (var pass in node.Passes) foreach (var pass in node.Passes)
{ {
w.Write(pass.Id); w.Write(pass.Id);
@ -160,21 +154,12 @@ public partial class ShpkFile
w.Write(blobSize); w.Write(blobSize);
w.Write((ushort)shader.Constants.Length); w.Write((ushort)shader.Constants.Length);
w.Write((ushort)shader.Samplers.Length); w.Write((ushort)shader.Samplers.Length);
w.Write((ushort)shader.UAVs.Length); w.Write((ushort)shader.Uavs.Length);
w.Write((ushort)0); w.Write((ushort)0);
WriteResourceArray(w, shader.Constants, strings); WriteResourceArray(w, shader.Constants, strings);
WriteResourceArray(w, shader.Samplers, strings); WriteResourceArray(w, shader.Samplers, strings);
WriteResourceArray(w, shader.UAVs, strings); WriteResourceArray(w, shader.Uavs, strings);
}
}
private static void WriteUInt32PairArray(BinaryWriter w, (uint, uint)[] array)
{
foreach (var (first, second) in array)
{
w.Write(first);
w.Write(second);
} }
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
namespace Penumbra.GameData;
public static class UtilityFunctions
{
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static T? FirstOrNull<T>(this IEnumerable<T> values, Func<T, bool> predicate) where T : struct
=> values.Cast<T?>().FirstOrDefault(v => predicate(v!.Value));
}

View file

@ -1,8 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Numerics; using System.Numerics;
using System.Reflection;
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.ImGuiFileDialog; using Dalamud.Interface.ImGuiFileDialog;
using ImGuiNET; using ImGuiNET;
@ -11,7 +11,6 @@ using OtterGui.Raii;
using Penumbra.GameData.Files; using Penumbra.GameData.Files;
using Penumbra.Mods; using Penumbra.Mods;
using Penumbra.String.Classes; using Penumbra.String.Classes;
using SixLabors.ImageSharp.PixelFormats;
namespace Penumbra.UI.Classes; namespace Penumbra.UI.Classes;
@ -176,9 +175,7 @@ public partial class ModEditWindow
} }
private static T? DefaultParseFile( byte[] bytes ) private static T? DefaultParseFile( byte[] bytes )
{ => Activator.CreateInstance( typeof( T ), BindingFlags.CreateInstance | BindingFlags.OptionalParamBinding, bytes ) as T;
return Activator.CreateInstance( typeof( T ), bytes ) as T;
}
private void UpdateCurrentFile( Mod.Editor.FileRegistry path ) private void UpdateCurrentFile( Mod.Editor.FileRegistry path )
{ {

View file

@ -11,6 +11,7 @@ using Dalamud.Interface.Internal.Notifications;
using ImGuiNET; using ImGuiNET;
using Lumina.Data.Parsing; using Lumina.Data.Parsing;
using OtterGui; using OtterGui;
using OtterGui.Classes;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.GameData.Files; using Penumbra.GameData.Files;
using Penumbra.String.Classes; using Penumbra.String.Classes;

View file

@ -10,6 +10,7 @@ using ImGuiNET;
using Lumina.Misc; using Lumina.Misc;
using OtterGui.Raii; using OtterGui.Raii;
using OtterGui; using OtterGui;
using OtterGui.Classes;
using Penumbra.GameData.Data; using Penumbra.GameData.Data;
using Penumbra.GameData.Files; using Penumbra.GameData.Files;
using Penumbra.Util; using Penumbra.Util;
@ -80,8 +81,8 @@ public partial class ModEditWindow
{ {
var extension = file.DirectXVersion switch var extension = file.DirectXVersion switch
{ {
ShpkFile.DXVersion.DirectX9 => ".cso", ShpkFile.DxVersion.DirectX9 => ".cso",
ShpkFile.DXVersion.DirectX11 => ".dxbc", ShpkFile.DxVersion.DirectX11 => ".dxbc",
_ => throw new NotImplementedException(), _ => throw new NotImplementedException(),
}; };
var defaultName = new string( objectName.Where( char.IsUpper ).ToArray() ).ToLower() + idx.ToString(); var defaultName = new string( objectName.Where( char.IsUpper ).ToArray() ).ToLower() + idx.ToString();
@ -148,7 +149,7 @@ public partial class ModEditWindow
ret |= DrawShaderPackageResourceArray( "Constant Buffers", "slot", true, shader.Constants, true ); ret |= DrawShaderPackageResourceArray( "Constant Buffers", "slot", true, shader.Constants, true );
ret |= DrawShaderPackageResourceArray( "Samplers", "slot", false, shader.Samplers, true ); ret |= DrawShaderPackageResourceArray( "Samplers", "slot", false, shader.Samplers, true );
ret |= DrawShaderPackageResourceArray( "Unordered Access Views", "slot", true, shader.UAVs, true ); ret |= DrawShaderPackageResourceArray( "Unordered Access Views", "slot", true, shader.Uavs, true );
if( shader.AdditionalHeader.Length > 0 ) if( shader.AdditionalHeader.Length > 0 )
{ {
@ -506,7 +507,7 @@ public partial class ModEditWindow
ret |= DrawShaderPackageResourceArray( "Constant Buffers", "type", true, file.Constants, disabled ); ret |= DrawShaderPackageResourceArray( "Constant Buffers", "type", true, file.Constants, disabled );
ret |= DrawShaderPackageResourceArray( "Samplers", "type", false, file.Samplers, disabled ); ret |= DrawShaderPackageResourceArray( "Samplers", "type", false, file.Samplers, disabled );
ret |= DrawShaderPackageResourceArray( "Unordered Access Views", "type", false, file.UAVs, disabled ); ret |= DrawShaderPackageResourceArray( "Unordered Access Views", "type", false, file.Uavs, disabled );
static bool DrawKeyArray( string arrayName, bool withId, ShpkFile.Key[] keys, bool _ ) static bool DrawKeyArray( string arrayName, bool withId, ShpkFile.Key[] keys, bool _ )
{ {