UNPKG

rollup-plugin-glsl-optimize

Version:

Import GLSL source files as strings. Pre-processed, validated and optimized with Khronos Group SPIRV-Tools. Supports glslify.

118 lines (103 loc) 3.9 kB
import {createFilter} from '@rollup/pluginutils'; import {glslProcessSource} from './lib/glslProcess.js'; import {glslifyInit, glslifyProcessSource} from './lib/glslify.js'; import * as fsSync from 'fs'; /** * @typedef {import('./lib/glslProcess').GLSLStageName} GLSLStageName * @typedef {{[P in GLSLStageName]: string[]}} GLSLStageDefs */ /** @type {GLSLStageDefs} */ const stageDefs = { 'vert': ['.vs', '.vert', '.vs.glsl', '.vert.glsl'], 'frag': ['.fs', '.frag', '.fs.glsl', '.frag.glsl'], // The following are untested: 'geom': ['.geom', '.geom.glsl'], 'comp': ['.comp', '.comp.glsl'], 'tesc': ['.tesc', '.tesc.glsl'], 'tese': ['.tese', '.tese.glsl'], }; const extsIncludeDefault = [...Object.values(stageDefs).flatMap( (exts) => exts.map((ext) => `**/*${ext}`)), '**/*.glsl', // Additionally include all *.glsl by default so we throw an error // if the user includes a file extension without a stage ]; /** @type {[GLSLStageName, RegExp][]} */ const stageRegexes = ( /** @type {[GLSLStageName, string[]][]} */(Object.entries(stageDefs)) .map(([st, exts]) => [st, new RegExp(`(?:${exts.map((ext) => ext.replace('.', '\\.')).join('|')})$`, 'i'), ])); function generateCode(source) { return `export default ${JSON.stringify(source)}; // eslint-disable-line`; } /** * @typedef {Array<string | RegExp> | string | RegExp | null} PathFilter * @typedef {Object} GLSLPluginGlobalOptions * @property {PathFilter} include * File extensions within rollup to include. * @property {PathFilter} exclude * File extensions within rollup to exclude. * @property {boolean} glslify * Process sources using glslify prior to all preprocessing, validation and optimization. * @property {Partial<import('./lib/glslify.js').GlslifyOptions>} glslifyOptions * When glslify enabled, pass these additional options to glslify.compile() * @typedef {GLSLPluginGlobalOptions & Partial<import('./lib/glslProcess.js').GLSLToolOptions>} GLSLPluginOptions */ /** * @param {Partial<GLSLPluginOptions>} userOptions * @return {import('rollup').Plugin} */ export default function glslOptimize(userOptions = {}) { /** @type {GLSLPluginOptions} */ const pluginOptions = { include: extsIncludeDefault, exclude: [], glslify: false, glslifyOptions: {}, ...userOptions, }; const filter = createFilter(pluginOptions.include, pluginOptions.exclude); return { name: 'glsl-optimize', async options(options) { if (pluginOptions.glslify) { // Try to dynamically load glslify if installed await glslifyInit(); } return options; }, /* We use a load hook instead of transform because we want sourcemaps to reflect the optimized shader source. */ async load(id) { if (!id || !filter(id) || !fsSync.existsSync(id)) return; let source; try { source = fsSync.readFileSync(id, {encoding: 'utf8'}); } catch (err) { this.warn(`Failed to load file '${id}' : ${err.message}`); return; } /** @type {GLSLStageName} */ const stage = stageRegexes.find(([, regex]) => id.match(regex))?.[0]; if (!stage) { this.error({message: `File '${id}' : extension did not match a shader stage.`}); } if (pluginOptions.glslify) { try { source = await glslifyProcessSource(id, source, pluginOptions.glslifyOptions, (message) => this.error({message})); } catch (err) { this.error({message: `Error processing GLSL source with glslify:\n${err.message}`}); } } try { const result = await glslProcessSource(id, source, stage, pluginOptions); result.code = generateCode(result.code); return result; } catch (err) { this.error({message: `Error processing GLSL source:\n${err.message}`}); } }, }; }