mirror of
https://github.com/cemu-project/cemu_graphic_packs.git
synced 2024-11-25 19:16:52 +01:00
611 lines
29 KiB
JavaScript
611 lines
29 KiB
JavaScript
|
const fs = require("fs");
|
||
|
const path = require("path");
|
||
|
const child_process = require("child_process");
|
||
|
|
||
|
// Main functions
|
||
|
|
||
|
var manualFixingShaders = 0;
|
||
|
var manualCheckingShaders = 0;
|
||
|
|
||
|
const crossHeader = [
|
||
|
// Vulkan
|
||
|
"#ifdef VULKAN\r",
|
||
|
"#define ATTR_LAYOUT(__vkSet, __location) layout(set = __vkSet, location = __location)\r",
|
||
|
"#define UNIFORM_BUFFER_LAYOUT(__glLocation, __vkSet, __vkLocation) layout(set = __vkSet, binding = __vkLocation, std140)\r",
|
||
|
"#define TEXTURE_LAYOUT(__glLocation, __vkSet, __vkLocation) layout(set = __vkSet, binding = __vkLocation)\r",
|
||
|
"#define SET_POSITION(_v) gl_Position = _v; gl_Position.z = (gl_Position.z + gl_Position.w) / 2.0\r",
|
||
|
"#define GET_FRAGCOORD() vec4(gl_FragCoord.xy*uf_fragCoordScale.xy,gl_FragCoord.zw)\r",
|
||
|
"#define gl_VertexID gl_VertexIndex",
|
||
|
"#define gl_InstanceID gl_InstanceIndex",
|
||
|
"#else\r",
|
||
|
// OpenGL
|
||
|
"#define ATTR_LAYOUT(__vkSet, __location) layout(location = __location)\r",
|
||
|
"#define UNIFORM_BUFFER_LAYOUT(__glLocation, __vkSet, __vkLocation) layout(binding = __glLocation, std140) \r",
|
||
|
"#define TEXTURE_LAYOUT(__glLocation, __vkSet, __vkLocation) layout(binding = __glLocation)\r",
|
||
|
"#define SET_POSITION(_v) gl_Position = _v\r",
|
||
|
"#define GET_FRAGCOORD() vec4(gl_FragCoord.xy*uf_fragCoordScale,gl_FragCoord.zw)\r",
|
||
|
"#endif\r"
|
||
|
];
|
||
|
|
||
|
const samplerTypes = ["sampler1D", "sampler2D", "sampler3D", "samplerCube", "sampler2DRect", "sampler1DArray", "sampler2DArray", "samplerCubeArray","samplerBuffer", "sampler2DMS", "sampler2DMSArray", "sampler1DShadow", "sampler2DShadow", "samplerCubeShadow", "sampler2DRectShadow", "sampler1DArrayShadow", "sampler2DArrayShadow", "samplerCubeArrayShadow"];
|
||
|
|
||
|
// Regex written on https://regex101.com/r/2aAtA1/5
|
||
|
const uniformUfRegex = /^(\s*uniform)\s+([biuvd]?vec[234]|bool|int|uint|float|double)\s+(uf_alphaTestRef|uf_verticesPerInstance|uf_streamoutBufferBase\[\d+\]|uf_tex\[\d+\]Scale|uf_pointSize|uf_fragCoordScale|uf_windowSpaceToClipSpaceTransform|uf_remappedPS\[\d+\]|uf_remappedVS\[\d+\]|uf_uniformRegisterVS\[\d+\]|uf_uniformRegisterPS\[\d+\])\s*;.*/m;
|
||
|
|
||
|
const layoutTextureRegex = /layout\s*\(binding\s*=\s*(\d+)\s*\)\s*/;
|
||
|
const layoutBufferRegex = /layout\s*\(binding\s*=\s*(\d+)\s*(?:,\s*std140|,\s*std430|,\s*shared|,\s*packed)\s*\)\s*/;
|
||
|
|
||
|
const ufVariablesOrder = [
|
||
|
"uf_remappedVS", "uf_remappedPS", "uf_uniformRegisterVS",
|
||
|
"uf_uniformRegisterPS", "uf_windowSpaceToClipSpaceTransform",
|
||
|
"uf_alphaTestRef", "uf_pointSize", "uf_fragCoordScale",
|
||
|
"uf_tex"/*"\dScale"*/, "uf_verticesPerInstance", "uf_streamoutBufferBase"
|
||
|
];
|
||
|
|
||
|
function convertRules(rulesText, dstPath) {
|
||
|
let rulesPresets = [];
|
||
|
let presetsValidation = {};
|
||
|
let textLines = rulesText.split("\n");
|
||
|
let prevUsedVersion = undefined;
|
||
|
|
||
|
let currentSection = "";
|
||
|
let currentPreset = {name: "", variables: []};
|
||
|
|
||
|
for (line in textLines) {
|
||
|
// Version number parsing
|
||
|
if (textLines[line].replace(/\s+/g, "").startsWith("version=")) {
|
||
|
if (textLines[line].replace(/\s+/g, "") != "version=3" && textLines[line].replace(/\s+/g, "") != "version=4") {
|
||
|
console.log("Why does this rules.txt file have something appended to the version variable?");
|
||
|
}
|
||
|
|
||
|
if (textLines[line].replace(/\s+/g, "").startsWith("version=3") || textLines[line].replace(/\s+/g, "").startsWith("version=4")) {
|
||
|
if (prevUsedVersion) console.warn("Why is there a second version definition in this file?");
|
||
|
prevUsedVersion = parseInt(textLines[line].replace(/\s+/g, "").split("version=")[1]);
|
||
|
textLines[line] = "version = 4";
|
||
|
}
|
||
|
else {
|
||
|
console.warn("What's this version? "+textLines[line]);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
// Parse preset variables
|
||
|
if (/^\[(.+)]/.test(textLines[line])) {
|
||
|
if (currentSection == "Preset") {
|
||
|
if (currentPreset.name == "") console.warn("Why does this preset not have a name?");
|
||
|
|
||
|
//console.log("Added new preset called "+currentPreset.name+" with the following variables: "+JSON.stringify(currentPreset));
|
||
|
rulesPresets.push({name: currentPreset.name, variables: [...currentPreset.variables]});
|
||
|
currentPreset = null;
|
||
|
}
|
||
|
currentSection = textLines[line].match(/^\[(.+)]/)[1];
|
||
|
if (currentSection == "Preset") {
|
||
|
currentPreset = {name: "", variables: []};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (currentSection == "Preset") {
|
||
|
let nameMatch = textLines[line].match(/^name[ ]?=[ ]?([^\n#]+)[ ]?/m);
|
||
|
|
||
|
if (nameMatch != null) {
|
||
|
if (currentPreset.name != "") console.warn("Why does this preset have it's name two times?");
|
||
|
else {
|
||
|
currentPreset.name = nameMatch[1].trim();
|
||
|
}
|
||
|
}
|
||
|
let presetVariable = textLines[line].match(/^(?<key>\$[a-zA-Z_]+)(?:(?<isInt>:int))?[ ]*=[ ]*(?<value>\d+\.\d+|\d+|0x[0-9a-fA-F]+|(?<expression>[+\-*\/\(\)\d]+))/m);
|
||
|
if (presetVariable != null) {
|
||
|
if (presetVariable.groups.expression != undefined) currentPreset.variables.push({key: presetVariable.groups.key, value: (presetVariable.groups.isInt != undefined ? 666 : 666.666667), isInt: (presetVariable.groups.isInt != undefined)});
|
||
|
else currentPreset.variables.push({key: presetVariable.groups.key, value: (presetVariable.groups.isInt != undefined ? parseInt(presetVariable.groups.value) : parseFloat(presetVariable.groups.value)), isInt: (presetVariable.groups.isInt != undefined)});
|
||
|
|
||
|
if (presetsValidation.hasOwnProperty(presetVariable.groups.key)) presetsValidation[presetVariable.groups.key]++;
|
||
|
else presetsValidation[presetVariable.groups.key] = 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (currentSection == "Preset") rulesPresets.push({name: currentPreset.name, variables: [...currentPreset.variables]});
|
||
|
|
||
|
// Check for oddities in presets
|
||
|
for (variable in presetsValidation) {
|
||
|
if (presetsValidation[variable] != rulesPresets.length) console.warn("This shader doesn't have all the variables in each preset...", presetsValidation, rulesPresets);
|
||
|
}
|
||
|
|
||
|
// Order variables
|
||
|
for (let i=0; i<rulesPresets.length; i++) {
|
||
|
rulesPresets[i].variables.sort((a, b) => {
|
||
|
return (b.key.length - a.key.length);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// Add a Default preset if it doesn't have any presets yet since the code relies on that.
|
||
|
if (rulesPresets.length == 0) rulesPresets.push({name: "Default", variables: []});
|
||
|
|
||
|
fs.writeFileSync(dstPath, textLines.join("\n"));
|
||
|
return [prevUsedVersion, rulesPresets];
|
||
|
}
|
||
|
|
||
|
function convertShader(shaderText, rulesPresets, folderArray, vulkanSet) {
|
||
|
let shaderOutput = [];
|
||
|
let shaderLines = shaderText.split("\n");
|
||
|
if (!shaderLines[0].startsWith("#")) console.warn("Why does "+path.join(process.cwd(), ...folderArray)+" not have a #version definition on it's first line?");
|
||
|
let sharedUFBlockAlternativeLine = undefined;
|
||
|
|
||
|
// First (header, GET_FRAGCOORD, SET_POSITION, ATTR_LAYOUT, GL_ARB_shading_language_packing and GL_ARB_arrays_of_arrays) pass
|
||
|
{
|
||
|
let newShaderLines = [];
|
||
|
let parsingHeader = true;
|
||
|
|
||
|
for (line in shaderLines) {
|
||
|
let currLine = shaderLines[line];
|
||
|
|
||
|
if (currLine.startsWith("#extension GL_ARB_shading_language_packing")) {
|
||
|
continue;
|
||
|
}
|
||
|
if (currLine.startsWith("#extension GL_ARB_arrays_of_arrays")) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (parsingHeader && !currLine.startsWith("#")) {
|
||
|
parsingHeader = false;
|
||
|
newShaderLines = newShaderLines.concat(crossHeader);
|
||
|
newShaderLines.push("// This shader was automatically converted to be cross-compatible with Vulkan and OpenGL.\r");
|
||
|
if (verboseOutput) {
|
||
|
newShaderLines.push("/*\r");
|
||
|
newShaderLines.push("Conversion output:\r");
|
||
|
newShaderLines.push("%CONVERSION_OUTPUT%\r");
|
||
|
newShaderLines.push("*/\r");
|
||
|
}
|
||
|
newShaderLines.push("\r");
|
||
|
sharedUFBlockAlternativeLine = newShaderLines.length;
|
||
|
}
|
||
|
|
||
|
if (/(R[\d+]f = )vec4\(gl_FragCoord\.xy\*uf_fragCoordScale,gl_FragCoord\.zw\);/.test(currLine)) {
|
||
|
newShaderLines.push(currLine.split("=")[0]+"= GET_FRAGCOORD();");
|
||
|
}
|
||
|
else if (/R[\d+]i = floatBitsToInt\(vec4\(gl_FragCoord\.xy\*uf_fragCoordScale,gl_FragCoord\.zw\)\);/.test(currLine)) {
|
||
|
newShaderLines.push(currLine.split("=")[0]+"= floatBitsToInt(GET_FRAGCOORD());");
|
||
|
}
|
||
|
else if (/^gl_Position[ ]?=[ ]?(.*);$/m.test(currLine)) {
|
||
|
newShaderLines.push(`SET_POSITION(${currLine.match(/^gl_Position = (.*);$/m)[1]});`);
|
||
|
}
|
||
|
else if (currLine.startsWith("layout(location = ") && currLine.includes(" attrData")) {
|
||
|
OpenGLLocation = parseInt(currLine.substr(17));
|
||
|
if (OpenGLLocation == NaN) throw "Tried finding the attrData's OpenGL binding, but returned a NaN.";
|
||
|
newShaderLines.push(`ATTR_LAYOUT(${vulkanSet}, ${OpenGLLocation})${currLine.split(")")[1]}`);
|
||
|
}
|
||
|
else {
|
||
|
newShaderLines.push(currLine);
|
||
|
}
|
||
|
}
|
||
|
shaderLines = newShaderLines;
|
||
|
}
|
||
|
|
||
|
// Second (binding and ufBlock) pass
|
||
|
{
|
||
|
let ufBlockLineIndexes = {};
|
||
|
let consecutiveUfVariables = true;
|
||
|
let allUfLines = [];
|
||
|
for (preset in rulesPresets) {
|
||
|
let currPreset = rulesPresets[preset];
|
||
|
|
||
|
// Add properties to store parsing information.
|
||
|
currPreset.ufBlockVariables = [];
|
||
|
currPreset.shaderChanges = new Array(shaderLines.length);
|
||
|
|
||
|
// Fill shaders with presets
|
||
|
let presetChangedShader = [];
|
||
|
for (line in shaderLines) {
|
||
|
let currLine = shaderLines[line];
|
||
|
|
||
|
for (presetVar in currPreset.variables) {
|
||
|
let currPresetVar = currPreset.variables[presetVar];
|
||
|
let currPresetVarOccur = currLine.split(currPresetVar.key).length-1;
|
||
|
for (let i=0; i<currPresetVarOccur; i++) {
|
||
|
if (currPresetVar.isInt) currLine = currLine.replace(currPresetVar.key, currPresetVar.value.toFixed(0));
|
||
|
else currLine = currLine.replace(currPresetVar.key, currPresetVar.value.toFixed(3));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
presetChangedShader.push(currLine);
|
||
|
}
|
||
|
|
||
|
// Let glslangValidator pre-parse preprocessor directives
|
||
|
let preprocessedPresetLines = undefined;
|
||
|
try {
|
||
|
let preprocessOutput = child_process.spawnSync("glslangValidator.exe", ["--stdin"/*Pass shader via stdin instead of having to write a file*/, "-E"/*Only preprocessor pass*/, "-S "+(vulkanSet == 0 ? "vert" : "frag")/*Whether shader is a vertex or fragment shader*/], {encoding: "utf8", shell: true, input: presetChangedShader.join("\n")});
|
||
|
if (preprocessOutput.stderr.trim() != "") throw preprocessOutput.stderr; // Throw an error if glslangValidator logged a warning or an error, since those are passed via stderr.
|
||
|
preprocessedPresetLines = preprocessOutput.stdout.split("\n");
|
||
|
}
|
||
|
catch (err) {
|
||
|
console.error("An error occured while preprocessing this shader:\r\n", err);
|
||
|
fs.writeFileSync(`./errors/preprocess_${path.basename(path.join(process.cwd(), ...folderArray), path.extname(path.join(process.cwd(), ...folderArray)))}_${currPreset.name.replace(/\W/g, "")}${vulkanSet == 0 ? ".vert" : ".frag"}`, presetChangedShader.join("\n"), {encoding: "utf8"});
|
||
|
manualFixingShaders++;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// Create preset replacements
|
||
|
let textureBindingCounter = 0;
|
||
|
let bufferBindingCounter = 0;
|
||
|
let lastUfVariableLine = undefined;
|
||
|
let ufVariableCrossOffList = [...ufVariablesOrder];
|
||
|
let ufVariableShaderOrder = [];
|
||
|
|
||
|
for (let i=0; i<shaderLines.length; i++) {
|
||
|
let currLine = preprocessedPresetLines[i];
|
||
|
|
||
|
// Create TEXTURE_LAYOUT preset replacement
|
||
|
if (layoutTextureRegex.test(currLine)) {
|
||
|
currPreset.shaderChanges[i] = currLine.replace(layoutTextureRegex, `TEXTURE_LAYOUT($1, ${vulkanSet}, ${textureBindingCounter}) `);
|
||
|
textureBindingCounter++;
|
||
|
}
|
||
|
// Create UNIFORM_BUFFER_LAYOUT preset replacement
|
||
|
if (layoutBufferRegex.test(currLine)) {
|
||
|
currPreset.shaderChanges[i] = currLine.replace(layoutBufferRegex, `UNIFORM_BUFFER_LAYOUT($1, ${vulkanSet}, %BUFFER_BINDING_${bufferBindingCounter}%) `);
|
||
|
bufferBindingCounter++;
|
||
|
}
|
||
|
|
||
|
// Collect and replace uf variables with blank lines
|
||
|
if (uniformUfRegex.test(currLine)) {
|
||
|
if (currPreset.ufBlockVariables.length == 0) {
|
||
|
currPreset.shaderChanges[i] = "%FIRST_UF_VARIABLE_IN_PRESET%\r";
|
||
|
lastUfVariableLine = i;
|
||
|
}
|
||
|
else {
|
||
|
currPreset.shaderChanges[i] = `// ${currLine.match(uniformUfRegex)[3]} was moved to the ufBlock\r`;
|
||
|
if (lastUfVariableLine != i-1 && consecutiveUfVariables) {
|
||
|
lastUfVariableLine = i;
|
||
|
}
|
||
|
else {
|
||
|
consecutiveUfVariables = false;
|
||
|
}
|
||
|
}
|
||
|
currPreset.ufBlockVariables.push(currLine);
|
||
|
|
||
|
if (!ufBlockLineIndexes.hasOwnProperty(currLine.match(uniformUfRegex)[3])) {
|
||
|
ufBlockLineIndexes[currLine.match(uniformUfRegex)[3]] = [];
|
||
|
allUfLines.push(currLine);
|
||
|
}
|
||
|
ufBlockLineIndexes[currLine.match(uniformUfRegex)[3]].push(i);
|
||
|
let indexUfVariable = ufVariableCrossOffList.findIndex(ufVariableStart => currLine.match(uniformUfRegex)[3].startsWith(ufVariableStart));
|
||
|
ufVariableShaderOrder.push(currLine.match(uniformUfRegex)[3]);
|
||
|
if (indexUfVariable != -1) ufVariableCrossOffList.splice(0, indexUfVariable);
|
||
|
else {
|
||
|
shaderOutput.push(`This shader (with the current ${currPreset.name}) doesn't follow the uf_* variable order from Cemu itself, so it might've been messed with. \r\nOrder found:${ufVariableShaderOrder}.`);
|
||
|
}
|
||
|
}
|
||
|
currPreset.textureBindingCounter = textureBindingCounter;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Sort the order of uf_variables
|
||
|
let beforeOrder = [...allUfLines];
|
||
|
function getSortRank(unit) {
|
||
|
for (var i = 0; i < ufVariablesOrder.length; i++) {
|
||
|
if (unit.startsWith(ufVariablesOrder[i])) {
|
||
|
return i;
|
||
|
}
|
||
|
}
|
||
|
return ufVariablesOrder.length;
|
||
|
}
|
||
|
|
||
|
allUfLines.sort(function(a, b){
|
||
|
return getSortRank(a) - getSortRank(b);
|
||
|
});
|
||
|
|
||
|
if (beforeOrder.toString() != allUfLines.toString()) console.log(allUfLines);
|
||
|
|
||
|
// Log some extra stuff
|
||
|
if (!consecutiveUfVariables) {
|
||
|
//shaderOutput.push("The uf_* variables weren't found to be consecutive, which means that someone probably messed with them.\r");
|
||
|
// It seems that Cemu previously didn't insert all the uf_* variables consecutively.
|
||
|
// It's not really that big of a deal anyway to move them in one block anyways, and shouldn't cause any issues due to the early inserted backup method.
|
||
|
}
|
||
|
|
||
|
if (allUfLines.length == 0) {
|
||
|
shaderOutput.push("This shader doesn't seem to have any uf_* variables. This could mean that this shader was edited.\r");
|
||
|
}
|
||
|
|
||
|
// Check if unified ufBlock can be made
|
||
|
let differingUfBlock = false;
|
||
|
let ufBlockLineSize = undefined;
|
||
|
for (let [ufVariableName, lineIndexes] of Object.entries(ufBlockLineIndexes)) {
|
||
|
// Check if each ufBlock has the same amount of variables.
|
||
|
let sameAmount = true;
|
||
|
if (ufBlockLineSize == undefined) ufBlockLineSize = lineIndexes.length;
|
||
|
else if (sameAmount) sameAmount = (ufBlockLineSize == lineIndexes.length);
|
||
|
|
||
|
// Check if each uf variable is on the same lines.
|
||
|
let sameLineIndexes = true;
|
||
|
for (let i=0; i<lineIndexes.length; i++) {
|
||
|
if (lineIndexes[i] != lineIndexes[0]) sameLineIndexes = false;
|
||
|
}
|
||
|
|
||
|
if (!sameAmount || !sameLineIndexes) {
|
||
|
if (!sameAmount) shaderOutput.push("This shader doesn't have the same amount of uf_* variables for each preset.\r");
|
||
|
else if (!sameLineIndexes) shaderOutput.push("This shader doesn't have the same line indexes for the uf_* variables. Hence why it might've been edited.\r");
|
||
|
differingUfBlock = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Check if each preset has the same amount of texture bindings
|
||
|
let textureBindingsAmount = undefined;
|
||
|
sameTextureBindingsAmount = true;
|
||
|
for (preset in rulesPresets) {
|
||
|
if (textureBindingsAmount == undefined) textureBindingsAmount = rulesPresets[preset].textureBindingCounter;
|
||
|
if (rulesPresets[preset].textureBindingCounter != textureBindingsAmount) {
|
||
|
sameTextureBindingsAmount = false;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Cemu optimized some uf variables away at some point. So, it's commented in the ufBlock. But also disable the ufBlock creation when that was the only variable.
|
||
|
let optimizedUfVariablesLength = 0;
|
||
|
for (ufLine in allUfLines) {
|
||
|
if (allUfLines[ufLine].split(" ")[2].split(";")[0].split("[")[0] == "uf_windowSpaceToClipSpaceTransform") continue;
|
||
|
else optimizedUfVariablesLength++;
|
||
|
}
|
||
|
|
||
|
// Merge changes from each preset and insert placeholder text
|
||
|
let inplaceUfBlock = !differingUfBlock && sameTextureBindingsAmount;
|
||
|
|
||
|
if (!inplaceUfBlock && optimizedUfVariablesLength != 0) {
|
||
|
shaderOutput.push("Due to one or more things mentioned above, this shader might be broken.\r");
|
||
|
let newUfBlockLines = [];
|
||
|
newUfBlockLines.push("#ifdef VULKAN\r");
|
||
|
newUfBlockLines.push(`layout(set = ${vulkanSet}, binding = ${textureBindingsAmount}) uniform ufBlock\r`);
|
||
|
newUfBlockLines.push("{\r");
|
||
|
for (ufLine in allUfLines) {
|
||
|
if (allUfLines[ufLine].split(" ")[2].split(";")[0].split("[")[0] == "uf_windowSpaceToClipSpaceTransform") {
|
||
|
newUfBlockLines.push("// "+allUfLines[ufLine]+" // Cemu optimized this uf_variable away in Cemu 1.15.7\r");
|
||
|
}
|
||
|
else if (allUfLines[ufLine].split(" ")[2].split(";")[0].split("[")[0] == "uf_fragCoordScale") newUfBlockLines.push(allUfLines[ufLine].replace("vec2", "vec4"));
|
||
|
else newUfBlockLines.push(allUfLines[ufLine]);
|
||
|
}
|
||
|
newUfBlockLines.push("};\r");
|
||
|
newUfBlockLines.push("#else\r");
|
||
|
for (ufLine in allUfLines) {
|
||
|
if (allUfLines[ufLine].split(" ")[2].split(";")[0].split("[")[0] == "uf_windowSpaceToClipSpaceTransform") newUfBlockLines.push("// "+allUfLines[ufLine]+" // Cemu optimized this uf_variable away in Cemu 1.15.7\r");
|
||
|
else newUfBlockLines.push(allUfLines[ufLine]);
|
||
|
}
|
||
|
newUfBlockLines.push("#endif\r");
|
||
|
shaderLines[sharedUFBlockAlternativeLine] = newUfBlockLines.join("\n");
|
||
|
}
|
||
|
|
||
|
for (let i=0; i<shaderLines.length; i++) {
|
||
|
let currLineChanges = [];
|
||
|
for (preset in rulesPresets) {
|
||
|
if (rulesPresets[preset].shaderChanges[i] != undefined) {
|
||
|
// Replace placeholders
|
||
|
let bufferBindingMatches = rulesPresets[preset].shaderChanges[i].match(/%BUFFER_BINDING_(\d)+%/);
|
||
|
if (bufferBindingMatches != null) {
|
||
|
currLineChanges.push(rulesPresets[preset].shaderChanges[i].replace(`%BUFFER_BINDING_${bufferBindingMatches[1]}%`, parseInt(bufferBindingMatches[1])+(allUfLines.length!=0)+rulesPresets[preset].textureBindingCounter));
|
||
|
}
|
||
|
else if (rulesPresets[preset].shaderChanges[i] == "%FIRST_UF_VARIABLE_IN_PRESET%\r") {
|
||
|
if (inplaceUfBlock && optimizedUfVariablesLength != 0) {
|
||
|
let newUfBlockLines = [];
|
||
|
newUfBlockLines.push("#ifdef VULKAN\r");
|
||
|
newUfBlockLines.push(`layout(set = ${vulkanSet}, binding = ${rulesPresets[preset].textureBindingCounter}) uniform ufBlock\r`);
|
||
|
newUfBlockLines.push("{\r");
|
||
|
for (ufLine in allUfLines) {
|
||
|
if (allUfLines[ufLine].split(" ")[2].split(";")[0].split("[")[0] == "uf_fragCoordScale") newUfBlockLines.push(allUfLines[ufLine].replace("vec2", "vec4"));
|
||
|
else if (allUfLines[ufLine].split(" ")[2].split(";")[0].split("[")[0] == "uf_windowSpaceToClipSpaceTransform") newUfBlockLines.push("// "+allUfLines[ufLine].trim()+" // Cemu optimized this uf_variable away in Cemu 1.15.7\r");
|
||
|
else newUfBlockLines.push(allUfLines[ufLine]);
|
||
|
}
|
||
|
newUfBlockLines.push("};\r");
|
||
|
newUfBlockLines.push("#else\r");
|
||
|
for (ufLine in allUfLines) {
|
||
|
if (allUfLines[ufLine].split(" ")[2].split(";")[0].split("[")[0] == "uf_windowSpaceToClipSpaceTransform") newUfBlockLines.push("// "+allUfLines[ufLine].trim()+" // Cemu optimized this uf_variable away in Cemu 1.15.7\r");
|
||
|
else newUfBlockLines.push(allUfLines[ufLine]);
|
||
|
}
|
||
|
newUfBlockLines.push("#endif\r");
|
||
|
currLineChanges.push(newUfBlockLines.join("\n"));
|
||
|
}
|
||
|
else currLineChanges.push("\r");
|
||
|
}
|
||
|
else currLineChanges.push(rulesPresets[preset].shaderChanges[i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (currLineChanges.length == 0) continue;
|
||
|
else if (currLineChanges.length == 1) shaderLines[i] = currLineChanges[0];
|
||
|
else {
|
||
|
let conflictingLineChanges = [];
|
||
|
for (change in currLineChanges) {
|
||
|
if (!conflictingLineChanges.includes(currLineChanges[change])) conflictingLineChanges.push(currLineChanges[change]);
|
||
|
}
|
||
|
if (conflictingLineChanges.length > 1) {
|
||
|
shaderOutput.push(`Encountered a conflict between lines. Probably will end-up in a broken shader. Conflicts: ${conflictingLineChanges.toString()}\r`);
|
||
|
}
|
||
|
shaderLines[i] = conflictingLineChanges[0];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Insert shader output into shader output
|
||
|
if (verboseOutput) shaderLines.splice(shaderLines.findIndex(line => line === "%CONVERSION_OUTPUT%\r"), 1, ...shaderOutput);
|
||
|
if (shaderOutput.length != 0) console.error("Encountered a few warnings in this shader "+path.join(process.cwd(), ...folderArray)+":\r\n", shaderOutput);
|
||
|
|
||
|
shaderLines = shaderLines.join("\n").replace(/(UNIFORM_BUFFER_LAYOUT\(\d+,[ ]*\d+,[ ]*\d+\)[ ]*)\r*\n+(uniform[ ]+[a-zA-Z0-9_]+)/g, "$1$2").split("\n");
|
||
|
|
||
|
// Third (testing) pass
|
||
|
{
|
||
|
for (preset in rulesPresets) {
|
||
|
let currPreset = rulesPresets[preset];
|
||
|
|
||
|
// Replace values from this preset variable, if there are any.
|
||
|
let presetChangedShader = [];
|
||
|
for (line in shaderLines) {
|
||
|
let currLine = shaderLines[line];
|
||
|
|
||
|
for (presetVar in currPreset.variables) {
|
||
|
let currPresetVar = currPreset.variables[presetVar];
|
||
|
let currPresetVarOccur = currLine.split(currPresetVar.key).length-1;
|
||
|
for (let i=0; i<currPresetVarOccur; i++) {
|
||
|
if (currPresetVar.isInt) currLine = currLine.replace(currPresetVar.key, currPresetVar.value.toFixed(0));
|
||
|
else currLine = currLine.replace(currPresetVar.key, currPresetVar.value.toFixed(3));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
presetChangedShader.push(currLine);
|
||
|
}
|
||
|
|
||
|
// Compile final shader with OpenGL
|
||
|
try {
|
||
|
let preprocessOutput = child_process.spawnSync("glslangValidator.exe", ["--stdin"/*Pass shader via stdin instead of having to write a file*/, "-S "+(vulkanSet == 0 ? "vert" : "frag")/*Whether shader is a vertex or fragment shader*/], {encoding: "utf8", shell: true, input: presetChangedShader.join("\n")});
|
||
|
if (preprocessOutput.stderr.trim() != "") throw preprocessOutput.stderr; // Throw an error if glslangValidator logged a warning or an error, since those are passed via stderr.
|
||
|
if (preprocessOutput.output[1] != "stdin\r\n" && preprocessOutput.output[1].trim() != "") throw preprocessOutput.output[1];
|
||
|
}
|
||
|
catch (err) {
|
||
|
console.error("An error occured while compiling the V4 shader with OpenGL:\r\n", err);
|
||
|
fs.writeFileSync(`./errors/OpenGLTesting_${path.basename(path.join(process.cwd(), ...folderArray), path.extname(path.join(process.cwd(), ...folderArray)))}_${currPreset.name.replace(/\W/g, "")}${vulkanSet == 0 ? ".vert" : ".frag"}`, presetChangedShader.join("\n"), {encoding: "utf8"});
|
||
|
manualFixingShaders++;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// Compile final shader with Vulkan
|
||
|
try {
|
||
|
let preprocessOutput = child_process.spawnSync("glslangValidator.exe", ["--stdin"/*Pass shader via stdin instead of having to write a file*/, "--client vulkan100"/*Use Vulkan*/, "-S "+(vulkanSet == 0 ? "vert" : "frag")/*Whether shader is a vertex or fragment shader*/], {encoding: "utf8", shell: true, input: presetChangedShader.join("\n")});
|
||
|
if (preprocessOutput.stderr.trim() != "") throw preprocessOutput.stderr; // Throw an error if glslangValidator logged a warning or an error, since those are passed via stderr.
|
||
|
if (preprocessOutput.output[1] != "stdin\r\n" && preprocessOutput.output[1].trim() != "") throw preprocessOutput.output[1];
|
||
|
}
|
||
|
catch (err) {
|
||
|
console.error("An error occured while compiling the V4 shader with Vulkan:\r\n", err);
|
||
|
fs.writeFileSync(`./errors/VulkanTesting_${path.basename(path.join(process.cwd(), ...folderArray), path.extname(path.join(process.cwd(), ...folderArray)))}_${currPreset.name.replace(/\W/g, "")}${vulkanSet == 0 ? ".vert" : ".frag"}`, presetChangedShader.join("\n"), {encoding: "utf8"});
|
||
|
manualFixingShaders++;
|
||
|
continue;
|
||
|
}
|
||
|
process.stdout.clearLine();
|
||
|
process.stdout.write(`Testing presets: ${(((preset)/(rulesPresets.length-1))*100).toFixed().padStart(2, " ")}% has been compiled.\r`);
|
||
|
}
|
||
|
process.stdout.clearLine();
|
||
|
}
|
||
|
|
||
|
// Write to file
|
||
|
fs.writeFileSync(path.join(process.cwd(), ...folderArray), shaderLines.join("\n"));
|
||
|
}
|
||
|
|
||
|
function convertPack(convertFiles, folderArray) {
|
||
|
if (convertFiles.shaders.length == 0) {
|
||
|
//console.log("This graphic pack doesn't have any shaders so we can just copy all the files!");
|
||
|
}
|
||
|
|
||
|
let [prevUsedVersion, rulesPresets] = convertRules(convertFiles.rulesText, path.join(process.cwd(), "graphicPacks", ...folderArray, "rules.txt"));
|
||
|
|
||
|
for (otherFile in convertFiles.otherFiles) {
|
||
|
//console.debug("Copying unrelated file to the new directory "+srcFolder+convertFiles.otherFiles[otherFile]+"...");
|
||
|
fs.copyFileSync(path.join(process.cwd(), ...folderArray, convertFiles.otherFiles[otherFile]), path.join(process.cwd(), "graphicPacks", ...folderArray, convertFiles.otherFiles[otherFile]));
|
||
|
}
|
||
|
|
||
|
if (prevUsedVersion == 3) {
|
||
|
for (shader in convertFiles.shaders) {
|
||
|
convertShader(convertFiles.shaders[shader].shaderText, rulesPresets, ["graphicPacks", ...folderArray, convertFiles.shaders[shader].name], convertFiles.shaders[shader].vulkanSet);
|
||
|
}
|
||
|
}
|
||
|
else if (prevUsedVersion == 4 && convertFiles.shaders.length != 0) {
|
||
|
console.info("The shaders in this graphic pack are already ported, so just copy all the files...");
|
||
|
for (shader in convertFiles.shaders) {
|
||
|
fs.copyFileSync(path.join(process.cwd(), ...folderArray, convertFiles.shaders[shader].name), path.join(process.cwd(), "graphicPacks", ...folderArray, convertFiles.shaders[shader].name));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (prevUsedVersion == undefined) console.warn("Why does this rules.txt file have no version?");
|
||
|
}
|
||
|
|
||
|
function convertFolder(folderPathArray) {
|
||
|
let dirEntries = fs.readdirSync(path.join(process.cwd(), ...folderPathArray), {withFileTypes: true});
|
||
|
if (fs.existsSync(path.join(process.cwd(), "/graphicPacks/", ...folderPathArray))) fs.rmdirSync(path.join(process.cwd(), "/graphicPacks/", ...folderPathArray), {recursive: true});
|
||
|
fs.mkdirSync(path.join(process.cwd(), "/graphicPacks/", ...folderPathArray), {recursive: true});
|
||
|
|
||
|
for (entry in dirEntries) {
|
||
|
if (dirEntries[entry].isDirectory()) {
|
||
|
// Check for folder entries and see if there's a rules.txt file.
|
||
|
let packFiles = fs.readdirSync(path.join(process.cwd(), ...folderPathArray, dirEntries[entry].name), {withFileTypes: true});
|
||
|
|
||
|
if (filterFolderNames.length != 0) {
|
||
|
let filtered = true;
|
||
|
for (name in filterFolderNames) {
|
||
|
if (dirEntries[entry].name.toLowerCase().startsWith(filterFolderNames[name].toLowerCase())) filtered = false;
|
||
|
}
|
||
|
if (filtered) continue;
|
||
|
}
|
||
|
|
||
|
console.group("Converting "+path.join(process.cwd(), ...folderPathArray, dirEntries[entry].name)+" to "+path.join(process.cwd(), "/graphicPacks/", ...folderPathArray, dirEntries[entry].name)+"...");
|
||
|
|
||
|
let convertFiles = {rulesText: undefined, shaders: [], otherFiles: []};
|
||
|
for (file in packFiles) {
|
||
|
if (packFiles[file].isFile() && packFiles[file].name == "rules.txt") {
|
||
|
convertFiles.rulesText = fs.readFileSync(path.join(process.cwd(), ...folderPathArray, dirEntries[entry].name, "rules.txt"), {encoding: "utf8"});
|
||
|
}
|
||
|
else if (packFiles[file].isFile() && packFiles[file].name.length >= "0000000000000000_0000000000000000_xx.txt".length) {
|
||
|
if (/^[a-zA-Z0-9]{16}_[\w]{16}_[p|v]s/.test(packFiles[file].name)) {
|
||
|
convertFiles.shaders.push({name: packFiles[file].name, shaderText: fs.readFileSync(path.join(process.cwd(), ...folderPathArray, dirEntries[entry].name, packFiles[file].name), {encoding: "utf8"}), vulkanSet: ((packFiles[file].name.substr(34, 2) == "vs") ? 0 : 1)});
|
||
|
}
|
||
|
else {
|
||
|
convertFiles.otherFiles.push(packFiles[file].name);
|
||
|
//console.debug("Why does "+path+dirEntries[entry].name+"/ have "+packFiles[file].name+" as a disabled shader? Can this file be deleted?");
|
||
|
}
|
||
|
}
|
||
|
else if (packFiles[file].isFile() && (packFiles[file].name == "patches.txt" || packFiles[file].name == "output.glsl")) {
|
||
|
convertFiles.otherFiles.push(packFiles[file].name);
|
||
|
}
|
||
|
else if (packFiles[file].isDirectory()) {
|
||
|
//console.debug("Why does "+path+dirEntries[entry].name+"/ have a folder called "+packFiles[file].name+".");
|
||
|
fs.mkdirSync(path.join(process.cwd(), "/graphicPacks/", ...folderPathArray, dirEntries[entry].name), {recursive: true});
|
||
|
convertFolder([...folderPathArray, dirEntries[entry].name]);
|
||
|
break;
|
||
|
}
|
||
|
else if (packFiles[file].isFile()) {
|
||
|
//console.debug("Why does "+dirEntries[entry].name+"/ have a random file called "+packFiles[file].name+"? Is this one necessary?");
|
||
|
convertFiles.otherFiles.push(packFiles[file].name);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (convertFiles.rulesText == undefined) {
|
||
|
//console.warn("Why does "+dirEntries[entry].name+" not have a rules.txt file?");
|
||
|
console.groupEnd();
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
fs.mkdirSync(path.join(process.cwd(), "/graphicPacks/", ...folderPathArray, dirEntries[entry].name), {recursive: true});
|
||
|
convertPack(convertFiles, [...folderPathArray, dirEntries[entry].name], ["/graphicPacks/", ...folderPathArray, dirEntries[entry].name]);
|
||
|
console.groupEnd();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Configuration
|
||
|
const filterFolderNames = ["BreathOfTheWild"];
|
||
|
const verboseOutput = false;
|
||
|
|
||
|
|
||
|
if (fs.existsSync(path.join(process.cwd(), "/errors/"))) fs.rmdirSync(path.join(process.cwd(), "/errors/"), {recursive: true});
|
||
|
fs.mkdirSync(path.join(process.cwd(), "/errors/"));
|
||
|
|
||
|
console.group("Convert shaders from Filters...");
|
||
|
convertFolder(["Filters"]);
|
||
|
console.groupEnd();
|
||
|
|
||
|
console.group("Convert shaders from Enhancements...");
|
||
|
convertFolder(["Enhancements"]);
|
||
|
console.groupEnd();
|
||
|
|
||
|
console.group("Convert shaders from Resolutions...");
|
||
|
convertFolder(["Resolutions"]);
|
||
|
console.groupEnd();
|
||
|
|
||
|
console.group("Convert shaders from Mods...");
|
||
|
convertFolder(["Mods"]);
|
||
|
console.groupEnd();
|
||
|
|
||
|
console.group("Convert shaders from Workarounds...");
|
||
|
convertFolder(["Workarounds"]);
|
||
|
console.groupEnd();
|
||
|
|
||
|
if (fs.existsSync("./preprocessed-shader.temp")) fs.unlinkSync("./preprocessed-shader.temp");
|
||
|
if (fs.existsSync("./frag.spv")) fs.unlinkSync("./frag.spv");
|
||
|
if (fs.existsSync("./vert.spv")) fs.unlinkSync("./vert.spv");
|
||
|
|
||
|
console.log(manualFixingShaders+" shaders need to be fixed manually and "+manualCheckingShaders+" shaders need to be checked, see the output above.");
|