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
 |