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(/^(?\$[a-zA-Z_]+)(?:(?:int))?[ ]*=[ ]*(?\d+\.\d+|\d+|0x[0-9a-fA-F]+|(?[+\-*\/\(\)\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 { 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 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 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= "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.");