forked from LeenkxTeam/LNXSDK
		
	
		
			
	
	
		
			459 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			459 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | "use strict"; | ||
|  | Object.defineProperty(exports, "__esModule", { value: true }); | ||
|  | exports.ShaderCompiler = exports.CompiledShader = void 0; | ||
|  | const child_process = require("child_process"); | ||
|  | const fs = require("fs-extra"); | ||
|  | const os = require("os"); | ||
|  | const path = require("path"); | ||
|  | const chokidar = require("chokidar"); | ||
|  | const Throttle = require("promise-parallel-throttle"); | ||
|  | const GraphicsApi_1 = require("./GraphicsApi"); | ||
|  | const Platform_1 = require("./Platform"); | ||
|  | const AssetConverter_1 = require("./AssetConverter"); | ||
|  | const log = require("./log"); | ||
|  | class CompiledShader { | ||
|  |     constructor() { | ||
|  |         this.files = []; | ||
|  |         this.inputs = []; | ||
|  |         this.outputs = []; | ||
|  |         this.uniforms = []; | ||
|  |         this.types = []; | ||
|  |         this.noembed = false; | ||
|  |     } | ||
|  | } | ||
|  | exports.CompiledShader = CompiledShader; | ||
|  | class ShaderCompiler { | ||
|  |     constructor(exporter, platform, compiler, to, temp, builddir, options, shaderMatchers) { | ||
|  |         this.exporter = exporter; | ||
|  |         if (platform.endsWith('-native')) | ||
|  |             platform = platform.substr(0, platform.length - '-native'.length); | ||
|  |         if (platform.endsWith('-hl')) | ||
|  |             platform = platform.substr(0, platform.length - '-hl'.length); | ||
|  |         this.platform = platform; | ||
|  |         this.compiler = compiler; | ||
|  |         this.type = ShaderCompiler.findType(platform, options); | ||
|  |         this.options = options; | ||
|  |         this.to = to; | ||
|  |         this.temp = temp; | ||
|  |         this.builddir = builddir; | ||
|  |         this.shaderMatchers = shaderMatchers; | ||
|  |     } | ||
|  |     close() { | ||
|  |         if (this.watcher) | ||
|  |             this.watcher.close(); | ||
|  |     } | ||
|  |     static findType(platform, options) { | ||
|  |         switch (platform) { | ||
|  |             case Platform_1.Platform.Empty: | ||
|  |             case Platform_1.Platform.Node: | ||
|  |                 return 'glsl'; | ||
|  |             case Platform_1.Platform.Flash: | ||
|  |                 return 'agal'; | ||
|  |             case Platform_1.Platform.Android: | ||
|  |                 if (options.graphics === GraphicsApi_1.GraphicsApi.Vulkan || options.graphics === GraphicsApi_1.GraphicsApi.Default) { | ||
|  |                     return 'spirv'; | ||
|  |                 } | ||
|  |                 else if (options.graphics === GraphicsApi_1.GraphicsApi.OpenGL) { | ||
|  |                     return 'essl'; | ||
|  |                 } | ||
|  |                 else { | ||
|  |                     throw new Error('Unsupported shader language.'); | ||
|  |                 } | ||
|  |             case Platform_1.Platform.HTML5: | ||
|  |             case Platform_1.Platform.DebugHTML5: | ||
|  |             case Platform_1.Platform.HTML5Worker: | ||
|  |             case Platform_1.Platform.Pi: | ||
|  |                 return 'essl'; | ||
|  |             case Platform_1.Platform.tvOS: | ||
|  |             case Platform_1.Platform.iOS: | ||
|  |                 if (options.graphics === GraphicsApi_1.GraphicsApi.Metal || options.graphics === GraphicsApi_1.GraphicsApi.Default) { | ||
|  |                     return 'metal'; | ||
|  |                 } | ||
|  |                 else if (options.graphics === GraphicsApi_1.GraphicsApi.OpenGL) { | ||
|  |                     return 'essl'; | ||
|  |                 } | ||
|  |                 else { | ||
|  |                     throw new Error('Unsupported shader language.'); | ||
|  |                 } | ||
|  |             case Platform_1.Platform.Windows: | ||
|  |                 if (options.graphics === GraphicsApi_1.GraphicsApi.Vulkan) { | ||
|  |                     return 'spirv'; | ||
|  |                 } | ||
|  |                 else if (options.graphics === GraphicsApi_1.GraphicsApi.OpenGL) { | ||
|  |                     return 'glsl'; | ||
|  |                 } | ||
|  |                 else if (options.graphics === GraphicsApi_1.GraphicsApi.Direct3D11 || options.graphics === GraphicsApi_1.GraphicsApi.Direct3D12 || options.graphics === GraphicsApi_1.GraphicsApi.Default) { | ||
|  |                     return 'd3d11'; | ||
|  |                 } | ||
|  |                 else if (options.graphics === GraphicsApi_1.GraphicsApi.Direct3D9) { | ||
|  |                     return 'd3d9'; | ||
|  |                 } | ||
|  |                 else { | ||
|  |                     throw new Error('Unsupported shader language.'); | ||
|  |                 } | ||
|  |             case Platform_1.Platform.WindowsApp: | ||
|  |                 return 'd3d11'; | ||
|  |             case Platform_1.Platform.Xbox360: | ||
|  |             case Platform_1.Platform.PlayStation3: | ||
|  |                 return 'd3d9'; | ||
|  |             case Platform_1.Platform.Linux: | ||
|  |                 if (options.graphics === GraphicsApi_1.GraphicsApi.Vulkan || options.graphics === GraphicsApi_1.GraphicsApi.Default) { | ||
|  |                     return 'spirv'; | ||
|  |                 } | ||
|  |                 else if (options.graphics === GraphicsApi_1.GraphicsApi.OpenGL) { | ||
|  |                     return 'glsl'; | ||
|  |                 } | ||
|  |                 else { | ||
|  |                     throw new Error('Unsupported shader language.'); | ||
|  |                 } | ||
|  |             case Platform_1.Platform.OSX: | ||
|  |                 if (options.graphics === GraphicsApi_1.GraphicsApi.Metal || options.graphics === GraphicsApi_1.GraphicsApi.Default) { | ||
|  |                     return 'metal'; | ||
|  |                 } | ||
|  |                 else if (options.graphics === GraphicsApi_1.GraphicsApi.OpenGL) { | ||
|  |                     return 'glsl'; | ||
|  |                 } | ||
|  |                 else { | ||
|  |                     throw new Error('Unsupported shader language.'); | ||
|  |                 } | ||
|  |             case Platform_1.Platform.Krom: | ||
|  |                 if (options.graphics === GraphicsApi_1.GraphicsApi.Default) { | ||
|  |                     if (process.platform === 'win32') { | ||
|  |                         return 'd3d11'; | ||
|  |                     } | ||
|  |                     else if (process.platform === 'darwin') { | ||
|  |                         return 'metal'; | ||
|  |                     } | ||
|  |                     else { | ||
|  |                         return 'glsl'; | ||
|  |                     } | ||
|  |                 } | ||
|  |                 else if (options.graphics === GraphicsApi_1.GraphicsApi.Vulkan) { | ||
|  |                     return 'spirv'; | ||
|  |                 } | ||
|  |                 else if (options.graphics === GraphicsApi_1.GraphicsApi.Metal) { | ||
|  |                     return 'metal'; | ||
|  |                 } | ||
|  |                 else if (options.graphics === GraphicsApi_1.GraphicsApi.OpenGL) { | ||
|  |                     return 'glsl'; | ||
|  |                 } | ||
|  |                 else if (options.graphics === GraphicsApi_1.GraphicsApi.Direct3D11 || options.graphics === GraphicsApi_1.GraphicsApi.Direct3D12) { | ||
|  |                     return 'd3d11'; | ||
|  |                 } | ||
|  |                 else if (options.graphics === GraphicsApi_1.GraphicsApi.Direct3D9) { | ||
|  |                     return 'd3d9'; | ||
|  |                 } | ||
|  |                 else { | ||
|  |                     throw new Error('Unsupported shader language.'); | ||
|  |                 } | ||
|  |             case Platform_1.Platform.FreeBSD: | ||
|  |                 return 'glsl'; | ||
|  |             default: | ||
|  |                 return platform; | ||
|  |         } | ||
|  |     } | ||
|  |     watch(watch, match, options, recompileAll) { | ||
|  |         return new Promise((resolve, reject) => { | ||
|  |             let shaders = []; | ||
|  |             let ready = false; | ||
|  |             this.watcher = chokidar.watch(match, { ignored: /[\/\\]\.(git|DS_Store)/, persistent: watch }); | ||
|  |             this.watcher.on('add', (filepath) => { | ||
|  |                 let file = path.parse(filepath); | ||
|  |                 if (ready) { | ||
|  |                     switch (file.ext) { | ||
|  |                         case '.glsl': | ||
|  |                             if (!file.name.endsWith('.inc') && this.isSupported(file.name)) { | ||
|  |                                 log.info('Compiling ' + file.name); | ||
|  |                                 this.compileShader(filepath, options, recompileAll); | ||
|  |                             } | ||
|  |                             break; | ||
|  |                     } | ||
|  |                 } | ||
|  |                 else { | ||
|  |                     switch (file.ext) { | ||
|  |                         case '.glsl': | ||
|  |                             if (!file.name.endsWith('.inc')) { | ||
|  |                                 shaders.push(filepath); | ||
|  |                             } | ||
|  |                             break; | ||
|  |                     } | ||
|  |                 } | ||
|  |             }); | ||
|  |             if (watch) { | ||
|  |                 this.watcher.on('change', (filepath) => { | ||
|  |                     let file = path.parse(filepath); | ||
|  |                     switch (file.ext) { | ||
|  |                         case '.glsl': | ||
|  |                             if (!file.name.endsWith('.inc') && this.isSupported(file.name)) { | ||
|  |                                 log.info('Recompiling ' + file.name); | ||
|  |                                 this.compileShader(filepath, options, recompileAll); | ||
|  |                             } | ||
|  |                             break; | ||
|  |                     } | ||
|  |                 }); | ||
|  |             } | ||
|  |             this.watcher.on('unlink', (file) => { | ||
|  |             }); | ||
|  |             this.watcher.on('ready', async () => { | ||
|  |                 ready = true; | ||
|  |                 let compiledShaders = []; | ||
|  |                 const self = this; | ||
|  |                 async function compile(shader, index) { | ||
|  |                     let parsed = path.parse(shader); | ||
|  |                     if (self.isSupported(shader)) { | ||
|  |                         log.info('Compiling shader ' + (index + 1) + ' of ' + shaders.length + ' (' + parsed.base + ').'); | ||
|  |                         let compiledShader = null; | ||
|  |                         try { | ||
|  |                             compiledShader = await self.compileShader(shader, options, recompileAll); | ||
|  |                         } | ||
|  |                         catch (error) { | ||
|  |                             log.error('Compiling shader ' + (index + 1) + ' of ' + shaders.length + ' (' + parsed.base + ') failed:'); | ||
|  |                             log.error(error); | ||
|  |                             return Promise.reject(error); | ||
|  |                         } | ||
|  |                         if (compiledShader === null) { | ||
|  |                             compiledShader = new CompiledShader(); | ||
|  |                             compiledShader.noembed = options.noembed; | ||
|  |                             // mark variables as invalid, so they are loaded from previous compilation
 | ||
|  |                             compiledShader.files = null; | ||
|  |                             compiledShader.inputs = null; | ||
|  |                             compiledShader.outputs = null; | ||
|  |                             compiledShader.uniforms = null; | ||
|  |                             compiledShader.types = null; | ||
|  |                         } | ||
|  |                         if (compiledShader.files != null && compiledShader.files.length === 0) { | ||
|  |                             // TODO: Remove when krafix has been recompiled everywhere
 | ||
|  |                             compiledShader.files.push(parsed.name + '.' + self.type); | ||
|  |                         } | ||
|  |                         compiledShader.name = AssetConverter_1.AssetConverter.createExportInfo(parsed, false, options, self.exporter.options.from).name; | ||
|  |                         compiledShaders.push(compiledShader); | ||
|  |                     } | ||
|  |                     else { | ||
|  |                         log.info('Skipping shader ' + (index + 1) + ' of ' + shaders.length + ' (' + parsed.base + ').'); | ||
|  |                     } | ||
|  |                     ++index; | ||
|  |                     return Promise.resolve(); | ||
|  |                 } | ||
|  |                 if (this.options.parallelAssetConversion !== 0) { | ||
|  |                     let todo = shaders.map((shader, index) => { | ||
|  |                         return async () => { | ||
|  |                             await compile(shader, index); | ||
|  |                         }; | ||
|  |                     }); | ||
|  |                     let processes = this.options.parallelAssetConversion === -1 | ||
|  |                         ? require('os').cpus().length - 1 | ||
|  |                         : this.options.parallelAssetConversion; | ||
|  |                     await Throttle.all(todo, { | ||
|  |                         maxInProgress: processes, | ||
|  |                     }); | ||
|  |                 } | ||
|  |                 else { | ||
|  |                     let index = 0; | ||
|  |                     for (let shader of shaders) { | ||
|  |                         try { | ||
|  |                             await compile(shader, index); | ||
|  |                         } | ||
|  |                         catch (err) { | ||
|  |                             reject(); | ||
|  |                             return; | ||
|  |                         } | ||
|  |                         index += 1; | ||
|  |                     } | ||
|  |                 } | ||
|  |                 resolve(compiledShaders); | ||
|  |                 return; | ||
|  |             }); | ||
|  |         }); | ||
|  |     } | ||
|  |     async run(watch, recompileAll) { | ||
|  |         let shaders = []; | ||
|  |         for (let matcher of this.shaderMatchers) { | ||
|  |             shaders = shaders.concat(await this.watch(watch, matcher.match, matcher.options, recompileAll)); | ||
|  |         } | ||
|  |         return shaders; | ||
|  |     } | ||
|  |     isSupported(file) { | ||
|  |         if (file.endsWith('.frag.glsl') || file.endsWith('.vert.glsl')) { | ||
|  |             return true; | ||
|  |         } | ||
|  |         return this.type !== 'essl' && this.type !== 'agal'; | ||
|  |     } | ||
|  |     compileShader(file, options, recompile) { | ||
|  |         return new Promise((resolve, reject) => { | ||
|  |             if (!this.compiler) | ||
|  |                 reject('No shader compiler found.'); | ||
|  |             if (this.type === 'none') { | ||
|  |                 resolve(new CompiledShader()); | ||
|  |                 return; | ||
|  |             } | ||
|  |             let fileinfo = path.parse(file); | ||
|  |             let from = file; | ||
|  |             let to = path.join(this.to, fileinfo.name + '.' + this.type); | ||
|  |             let temp = to + '.temp'; | ||
|  |             fs.stat(from, (fromErr, fromStats) => { | ||
|  |                 fs.stat(to, (toErr, toStats) => { | ||
|  |                     if (options.noprocessing) { | ||
|  |                         if (!toStats || toStats.mtime.getTime() < fromStats.mtime.getTime()) { | ||
|  |                             fs.copySync(from, to, { overwrite: true }); | ||
|  |                         } | ||
|  |                         let compiledShader = new CompiledShader(); | ||
|  |                         compiledShader.noembed = options.noembed; | ||
|  |                         resolve(compiledShader); | ||
|  |                         return; | ||
|  |                     } | ||
|  |                     fs.stat(this.compiler, (compErr, compStats) => { | ||
|  |                         if (!recompile && (fromErr || (!toErr && toStats.mtime.getTime() > fromStats.mtime.getTime() && toStats.mtime.getTime() > compStats.mtime.getTime()))) { | ||
|  |                             if (fromErr) | ||
|  |                                 log.error('Shader compiler error: ' + fromErr); | ||
|  |                             resolve(null); | ||
|  |                         } | ||
|  |                         else { | ||
|  |                             if (this.type === 'metal' && this.platform !== Platform_1.Platform.Krom) { | ||
|  |                                 fs.ensureDirSync(path.join(this.builddir, 'Sources')); | ||
|  |                                 let funcname = fileinfo.name; | ||
|  |                                 funcname = funcname.replace(/-/g, '_'); | ||
|  |                                 funcname = funcname.replace(/\./g, '_'); | ||
|  |                                 funcname += '_main'; | ||
|  |                                 fs.writeFileSync(to, '>' + funcname, 'utf8'); | ||
|  |                                 to = path.join(this.builddir, 'Sources', fileinfo.name + '.' + this.type); | ||
|  |                                 temp = to; | ||
|  |                             } | ||
|  |                             let parameters = [this.type === 'hlsl' ? 'd3d9' : this.type, from, temp, this.temp, this.platform]; | ||
|  |                             if (this.options.shaderversion) { | ||
|  |                                 parameters.push('--version'); | ||
|  |                                 parameters.push(this.options.shaderversion); | ||
|  |                             } | ||
|  |                             else if (this.platform === Platform_1.Platform.Krom && os.platform() === 'linux') { | ||
|  |                                 parameters.push('--version'); | ||
|  |                                 parameters.push('110'); | ||
|  |                             } | ||
|  |                             if (this.options.glsl2) { | ||
|  |                                 parameters.push('--glsl2'); | ||
|  |                             } | ||
|  |                             if (this.options.debug) { | ||
|  |                                 parameters.push('--debug'); | ||
|  |                             } | ||
|  |                             if (options.defines) { | ||
|  |                                 for (let define of options.defines) { | ||
|  |                                     parameters.push('-D' + define); | ||
|  |                                 } | ||
|  |                             } | ||
|  |                             if (this.platform === Platform_1.Platform.HTML5 || this.platform === Platform_1.Platform.HTML5Worker || this.platform === Platform_1.Platform.Android) { | ||
|  |                                 parameters.push('--relax'); | ||
|  |                             } | ||
|  |                             parameters[1] = path.resolve(parameters[1]); | ||
|  |                             parameters[2] = path.resolve(parameters[2]); | ||
|  |                             parameters[3] = path.resolve(parameters[3]); | ||
|  |                             let child = child_process.spawn(this.compiler, parameters); | ||
|  |                             let errorLine = ''; | ||
|  |                             let newErrorLine = true; | ||
|  |                             let errorData = false; | ||
|  |                             let compiledShader = new CompiledShader(); | ||
|  |                             compiledShader.noembed = options.noembed; | ||
|  |                             function parseData(data) { | ||
|  |                                 data = data.replace(':\\', '#\\'); // Filter out absolute paths on Windows
 | ||
|  |                                 let parts = data.split(':'); | ||
|  |                                 if (parts.length >= 3) { | ||
|  |                                     if (parts[0] === 'uniform') { | ||
|  |                                         compiledShader.uniforms.push({ name: parts[1], type: parts[2] }); | ||
|  |                                     } | ||
|  |                                     else if (parts[0] === 'input') { | ||
|  |                                         compiledShader.inputs.push({ name: parts[1], type: parts[2] }); | ||
|  |                                     } | ||
|  |                                     else if (parts[0] === 'output') { | ||
|  |                                         compiledShader.outputs.push({ name: parts[1], type: parts[2] }); | ||
|  |                                     } | ||
|  |                                     else if (parts[0] === 'type') { | ||
|  |                                         let type = data.substring(data.indexOf(':') + 1); | ||
|  |                                         let name = type.substring(0, type.indexOf(':')); | ||
|  |                                         let typedata = type.substring(type.indexOf(':') + 2); | ||
|  |                                         typedata = typedata.substr(0, typedata.length - 1); | ||
|  |                                         let members = typedata.split(','); | ||
|  |                                         let memberdecls = []; | ||
|  |                                         for (let member of members) { | ||
|  |                                             let memberparts = member.split(':'); | ||
|  |                                             memberdecls.push({ type: memberparts[1], name: memberparts[0] }); | ||
|  |                                         } | ||
|  |                                         compiledShader.types.push({ name: name, members: memberdecls }); | ||
|  |                                     } | ||
|  |                                 } | ||
|  |                                 else if (parts.length >= 2) { | ||
|  |                                     if (parts[0] === 'file') { | ||
|  |                                         const parsed = path.parse(parts[1].replace('#\\', ':\\')); | ||
|  |                                         let name = parsed.name; | ||
|  |                                         if (parsed.ext !== '.temp') | ||
|  |                                             name += parsed.ext; | ||
|  |                                         compiledShader.files.push(name); | ||
|  |                                     } | ||
|  |                                 } | ||
|  |                             } | ||
|  |                             let stdOutString = ''; | ||
|  |                             child.stdout.on('data', (data) => { | ||
|  |                                 stdOutString += data.toString(); | ||
|  |                             }); | ||
|  |                             child.stderr.on('data', (data) => { | ||
|  |                                 let str = data.toString(); | ||
|  |                                 for (let char of str) { | ||
|  |                                     if (char === '\n') { | ||
|  |                                         if (errorData) { | ||
|  |                                             parseData(errorLine.trim()); | ||
|  |                                         } | ||
|  |                                         else { | ||
|  |                                             log.error(errorLine.trim()); | ||
|  |                                         } | ||
|  |                                         errorLine = ''; | ||
|  |                                         newErrorLine = true; | ||
|  |                                         errorData = false; | ||
|  |                                     } | ||
|  |                                     else if (newErrorLine && char === '#') { | ||
|  |                                         errorData = true; | ||
|  |                                         newErrorLine = false; | ||
|  |                                     } | ||
|  |                                     else { | ||
|  |                                         errorLine += char; | ||
|  |                                         newErrorLine = false; | ||
|  |                                     } | ||
|  |                                 } | ||
|  |                             }); | ||
|  |                             child.on('close', (code) => { | ||
|  |                                 if (stdOutString) { | ||
|  |                                     if (code === 0) { | ||
|  |                                         log.info(stdOutString); | ||
|  |                                     } | ||
|  |                                     else { | ||
|  |                                         log.error(stdOutString); | ||
|  |                                     } | ||
|  |                                 } | ||
|  |                                 if (errorLine.trim().length > 0) { | ||
|  |                                     if (errorData) { | ||
|  |                                         parseData(errorLine.trim()); | ||
|  |                                     } | ||
|  |                                     else { | ||
|  |                                         log.error(errorLine.trim()); | ||
|  |                                     } | ||
|  |                                 } | ||
|  |                                 if (code === 0) { | ||
|  |                                     if (this.type !== 'metal' || this.platform === Platform_1.Platform.Krom) { | ||
|  |                                         if (compiledShader.files === null || compiledShader.files.length === 0) { | ||
|  |                                             fs.renameSync(temp, to); | ||
|  |                                         } | ||
|  |                                         for (let file of compiledShader.files) { | ||
|  |                                             fs.renameSync(path.join(this.to, file + '.temp'), path.join(this.to, file)); | ||
|  |                                         } | ||
|  |                                     } | ||
|  |                                     resolve(compiledShader); | ||
|  |                                 } | ||
|  |                                 else { | ||
|  |                                     process.exitCode = 1; | ||
|  |                                     reject('Shader compiler error.'); | ||
|  |                                 } | ||
|  |                             }); | ||
|  |                         } | ||
|  |                     }); | ||
|  |                 }); | ||
|  |             }); | ||
|  |         }); | ||
|  |     } | ||
|  | } | ||
|  | exports.ShaderCompiler = ShaderCompiler; | ||
|  | //# sourceMappingURL=ShaderCompiler.js.map
 |