cemu_graphic_packs/docs/v4-converter/convert-packs.js
Crementif 24b9b1eb29
Update XCX packs for Vulkan (#413)
Xenoblade Chronicles X packs are now ported too! Which has, after BotW, the most shaders I think made by all of the efforts from getdls. All shaders were successfully verified and tested in-game (from what I could tell).
2020-01-01 20:10:18 +01:00

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 (let 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.");