Task 241: .FRAG File Format

Task 241: .FRAG File Format

.FRAG File Format Specifications

The .FRAG file format is a plain text file containing source code written in the OpenGL Shading Language (GLSL) for fragment shaders, which are used in graphics APIs like OpenGL and Vulkan to process pixel fragments during rendering. Fragment shaders define how colors and other attributes are computed for each pixel in a rendered image, based on inputs from the vertex shader stage. The format follows the GLSL specification published by the Khronos Group, with the current version being GLSL 4.60 (as of the latest updates). The language syntax is C-like, supporting directives, declarations, and functions, and the file is typically encoded in UTF-8 with Unix-style line endings (LF). There is no binary header or footer; the structure is purely textual and human-readable.

1. List of All Properties Intrinsic to the File Format

The .FRAG format has no rigid binary structure (unlike container formats), so its intrinsic properties are derived from GLSL syntax and semantics. These are the key structural elements that define the file's content and behavior in a graphics pipeline:

  • GLSL Version Directive: Specifies the GLSL version (e.g., #version 330 core), determining supported features and built-in functions.
  • Extension Directives: Optional #extension lines enabling experimental or optional features (e.g., #extension GL_OES_standard_derivatives : enable).
  • Precision Qualifiers: Declarations like precision highp float;, setting default precision for floating-point calculations (lowp, mediump, highp) to optimize for hardware.
  • Uniform Declarations: Global inputs from the application (e.g., uniform vec3 lightColor;), constant across the shader invocation.
  • Input Variables (in): Varyings received from the vertex shader (e.g., in vec2 texCoord;), interpolated per fragment.
  • Output Variables (out): Results written to the framebuffer (e.g., out vec4 FragColor;), including color, depth, or stencil values.
  • Built-in Variables: Implicit globals like gl_FragCoord (fragment position), gl_FragDepth (depth output), or gl_FrontFacing (face orientation).
  • Function Definitions: Custom functions (e.g., vec4 computeColor(vec2 uv) { ... }), supporting modularity.
  • Main Function: The entry point void main() { ... }, containing the core logic for fragment processing.
  • Encoding and Line Endings: UTF-8 text encoding; flexible line endings (LF preferred for cross-platform compatibility).
  • MIME Type: text/plain or application/x-glsl.
  • File Size Constraints: No formal limits, but practical limits based on GPU memory (typically <64KB for mobile).

These properties ensure compatibility with shader compilers in graphics APIs.

3. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .FRAG Property Dump

Embed this HTML snippet in a Ghost blog post (via the HTML card or custom HTML block). It creates a drag-and-drop zone that reads a dropped .FRAG file, parses it using regex for the listed properties, and dumps them to a <pre> block below. Uses browser File API for reading; no server needed.

Drag and drop a .FRAG file here to analyze its properties.

4. Python Class for .FRAG Handling

This class opens a .FRAG file, reads/decodes it as UTF-8 text, parses the properties using regex, prints them to console, and supports writing the content back to a file.

import re
import sys

class FragHandler:
    def __init__(self, filepath=None):
        self.filepath = filepath
        self.content = None
        self.lines = []

    def read(self):
        if not self.filepath:
            raise ValueError("Filepath required for reading.")
        with open(self.filepath, 'r', encoding='utf-8') as f:
            self.content = f.read()
            self.lines = self.content.split('\n')
        print("File read successfully.")

    def extract_version(self):
        match = re.search(r'#version\s+(\d+(?:\.\d+)?)\s*(core|compatibility|es)?', self.content, re.I)
        return match.group(1) + (f" {match.group(2)}" if match.group(2) else '') if match else 'Not specified'

    def extract_extensions(self):
        matches = re.findall(r'#extension\s+([^:]+):?\s*(enable|require|warn|disable)?', self.content, re.I)
        return '\n'.join([f"#extension {m[0]} {m[1]}" for m in matches]) if matches else 'None'

    def extract_precision(self):
        matches = [line.strip() for line in self.lines if re.match(r'^precision\s+(lowp|mediump|highp)\s+(float|int);$', line.strip(), re.I)]
        return '\n'.join(matches) if matches else 'Default (highp float)'

    def extract_uniforms(self):
        matches = [line.strip() for line in self.lines if re.match(r'^uniform\s+\w+\s+\w+;', line.strip(), re.I)]
        return '\n'.join(matches) if matches else 'None'

    def extract_inputs(self):
        matches = [line.strip() for line in self.lines if re.match(r'^in\s+\w+\s+\w+;', line.strip(), re.I)]
        return '\n'.join(matches) if matches else 'None'

    def extract_outputs(self):
        matches = [line.strip() for line in self.lines if re.match(r'^out\s+\w+\s+\w+;', line.strip(), re.I)]
        return '\n'.join(matches) if matches else 'None'

    def extract_builtins(self):
        builtins = ['gl_FragCoord', 'gl_FragDepth', 'gl_FrontFacing', 'gl_PointCoord']
        used = [b for b in builtins if b in self.content]
        return ', '.join(used) if used else 'None'

    def extract_functions(self):
        matches = re.findall(r'(\w+)\s+(\w+)\s*\([^)]*\)\s*\{[^}]*\}', self.content)
        funcs = [f"{m[0]} {m[1]}({...})" for m in matches[:5]]
        return '\n'.join(funcs) + ('\n... (truncated)' if len(matches) > 5 else '') if matches else 'None'

    def extract_main(self):
        match = re.search(r'void\s+main\s*\([^)]*\)\s*\{([^}]*)\}', self.content, re.S)
        return match.group(1).strip() if match else 'Not found'

    def print_properties(self):
        if not self.content:
            self.read()
        print("=== .FRAG Properties ===")
        print(f"GLSL Version: {self.extract_version()}")
        print(f"Extensions: {self.extract_extensions()}")
        print(f"Precision Qualifiers: {self.extract_precision()}")
        print(f"Uniforms: {self.extract_uniforms()}")
        print(f"Input Variables (in): {self.extract_inputs()}")
        print(f"Output Variables (out): {self.extract_outputs()}")
        print(f"Built-in Variables: {self.extract_builtins()}")
        print(f"Custom Functions: {self.extract_functions()}")
        print(f"Main Function Body: {self.extract_main()}")
        print("Encoding/Line Endings: UTF-8 / LF (assumed)")
        print("MIME Type: text/plain")
        print("File Size Constraints: None formal")
        print("======================")

    def write(self, output_path):
        if not self.content:
            raise ValueError("Content not loaded. Call read() first.")
        with open(output_path, 'w', encoding='utf-8') as f:
            f.write(self.content)
        print(f"File written to {output_path}.")

# Example usage
if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Usage: python frag_handler.py <path_to.frag>")
        sys.exit(1)
    handler = FragHandler(sys.argv[1])
    handler.read()
    handler.print_properties()
    handler.write("output.frag")  # Writes a copy

5. Java Class for .FRAG Handling

This class uses java.nio.file for I/O and java.util.regex for parsing. Compile with javac FragHandler.java and run with java FragHandler <path_to.frag>.

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class FragHandler {
    private String filepath;
    private String content;
    private List<String> lines;

    public FragHandler(String filepath) {
        this.filepath = filepath;
    }

    public void read() throws IOException {
        Path path = Paths.get(filepath);
        content = Files.readString(path, StandardCharsets.UTF_8);
        lines = Files.readAllLines(path, StandardCharsets.UTF_8);
        System.out.println("File read successfully.");
    }

    private String extractVersion() {
        Pattern p = Pattern.compile("#version\\s+(\\d+(?:\\.\\d+)?)\\s*(core|compatibility|es)?", Pattern.CASE_INSENSITIVE);
        Matcher m = p.matcher(content);
        if (m.find()) {
            return m.group(1) + (m.group(2) != null ? " " + m.group(2) : "");
        }
        return "Not specified";
    }

    private String extractExtensions() {
        Pattern p = Pattern.compile("#extension\\s+([^:]+):?\\s*(enable|require|warn|disable)?", Pattern.CASE_INSENSITIVE);
        Matcher m = p.matcher(content);
        StringBuilder sb = new StringBuilder();
        while (m.find()) {
            sb.append("#extension ").append(m.group(1)).append(" ").append(m.group(2)).append("\n");
        }
        return sb.length() > 0 ? sb.toString().trim() : "None";
    }

    private String extractPrecision() {
        Pattern p = Pattern.compile("^precision\\s+(lowp|mediump|highp)\\s+(float|int);$", Pattern.CASE_INSENSITIVE);
        List<String> matches = new ArrayList<>();
        for (String line : lines) {
            Matcher mm = p.matcher(line.trim());
            if (mm.find()) {
                matches.add(line.trim());
            }
        }
        return matches.isEmpty() ? "Default (highp float)" : String.join("\n", matches);
    }

    private String extractUniforms() {
        Pattern p = Pattern.compile("^uniform\\s+\\w+\\s+\\w+;", Pattern.CASE_INSENSITIVE);
        List<String> matches = new ArrayList<>();
        for (String line : lines) {
            Matcher mm = p.matcher(line.trim());
            if (mm.find()) {
                matches.add(line.trim());
            }
        }
        return matches.isEmpty() ? "None" : String.join("\n", matches);
    }

    private String extractInputs() {
        Pattern p = Pattern.compile("^in\\s+\\w+\\s+\\w+;", Pattern.CASE_INSENSITIVE);
        List<String> matches = new ArrayList<>();
        for (String line : lines) {
            Matcher mm = p.matcher(line.trim());
            if (mm.find()) {
                matches.add(line.trim());
            }
        }
        return matches.isEmpty() ? "None" : String.join("\n", matches);
    }

    private String extractOutputs() {
        Pattern p = Pattern.compile("^out\\s+\\w+\\s+\\w+;", Pattern.CASE_INSENSITIVE);
        List<String> matches = new ArrayList<>();
        for (String line : lines) {
            Matcher mm = p.matcher(line.trim());
            if (mm.find()) {
                matches.add(line.trim());
            }
        }
        return matches.isEmpty() ? "None" : String.join("\n", matches);
    }

    private String extractBuiltins() {
        String[] builtins = {"gl_FragCoord", "gl_FragDepth", "gl_FrontFacing", "gl_PointCoord"};
        List<String> used = new ArrayList<>();
        for (String b : builtins) {
            if (content.contains(b)) {
                used.add(b);
            }
        }
        return used.isEmpty() ? "None" : String.join(", ", used);
    }

    private String extractFunctions() {
        Pattern p = Pattern.compile("(\\w+)\\s+(\\w+)\\s*\\([^)]*\\)\\s*\\{[^}]*\\}");
        Matcher m = p.matcher(content);
        List<String> funcs = new ArrayList<>();
        while (m.find() && funcs.size() < 5) {
            funcs.add(m.group(1) + " " + m.group(2) + "(...)");
        }
        String res = String.join("\n", funcs);
        return m.find() ? res + "\n... (truncated)" : (funcs.isEmpty() ? "None" : res);
    }

    private String extractMain() {
        Pattern p = Pattern.compile("void\\s+main\\s*\\([^)]*\\)\\s*\\{([^}]*)}", Pattern.DOTALL | Pattern.CASE_INSENSITIVE);
        Matcher m = p.matcher(content);
        return m.find() ? m.group(1).trim() : "Not found";
    }

    public void printProperties() throws IOException {
        if (content == null) {
            read();
        }
        System.out.println("=== .FRAG Properties ===");
        System.out.println("GLSL Version: " + extractVersion());
        System.out.println("Extensions: " + extractExtensions());
        System.out.println("Precision Qualifiers: " + extractPrecision());
        System.out.println("Uniforms: " + extractUniforms());
        System.out.println("Input Variables (in): " + extractInputs());
        System.out.println("Output Variables (out): " + extractOutputs());
        System.out.println("Built-in Variables: " + extractBuiltins());
        System.out.println("Custom Functions: " + extractFunctions());
        System.out.println("Main Function Body: " + extractMain());
        System.out.println("Encoding/Line Endings: UTF-8 / LF (assumed)");
        System.out.println("MIME Type: text/plain");
        System.out.println("File Size Constraints: None formal");
        System.out.println("======================");
    }

    public void write(String outputPath) throws IOException {
        if (content == null) {
            throw new IllegalStateException("Content not loaded. Call read() first.");
        }
        Path outPath = Paths.get(outputPath);
        Files.writeString(outPath, content, StandardCharsets.UTF_8);
        System.out.println("File written to " + outputPath + ".");
    }

    public static void main(String[] args) {
        if (args.length < 1) {
            System.out.println("Usage: java FragHandler <path_to.frag>");
            System.exit(1);
        }
        try {
            FragHandler handler = new FragHandler(args[0]);
            handler.printProperties();
            handler.write("output.frag");  // Writes a copy
        } catch (IOException e) {
            System.err.println("Error: " + e.getMessage());
        }
    }
}

6. JavaScript Class for .FRAG Handling

This Node.js-compatible class (uses fs module) reads/decodes a .FRAG file, parses properties with regex, prints to console, and writes back. Run with node fragHandler.js <path_to.frag> (requires Node.js).

const fs = require('fs');
const path = require('path');

class FragHandler {
  constructor(filepath) {
    this.filepath = filepath;
    this.content = null;
    this.lines = [];
  }

  read() {
    if (!this.filepath) throw new Error('Filepath required for reading.');
    this.content = fs.readFileSync(this.filepath, 'utf8');
    this.lines = this.content.split('\n');
    console.log('File read successfully.');
  }

  extractVersion() {
    const match = this.content.match(/#version\s+(\d+(?:\.\d+)?)\s*(core|compatibility|es)?/i);
    return match ? `${match[1]}${match[2] ? ` ${match[2]}` : ''}` : 'Not specified';
  }

  extractExtensions() {
    const matches = this.content.match(/#extension\s+([^:]+):?\s*(enable|require|warn|disable)?/gi) || [];
    return matches.length ? matches.join('\n') : 'None';
  }

  extractPrecision() {
    const matches = this.lines.filter(line => /^precision\s+(lowp|mediump|highp)\s+(float|int);$/i.test(line.trim()));
    return matches.length ? matches.join('\n') : 'Default (highp float)';
  }

  extractUniforms() {
    const matches = this.lines.filter(line => /^uniform\s+\w+\s+\w+;$/i.test(line.trim()));
    return matches.length ? matches.join('\n') : 'None';
  }

  extractInputs() {
    const matches = this.lines.filter(line => /^in\s+\w+\s+\w+;$/i.test(line.trim()));
    return matches.length ? matches.join('\n') : 'None';
  }

  extractOutputs() {
    const matches = this.lines.filter(line => /^out\s+\w+\s+\w+;$/i.test(line.trim()));
    return matches.length ? matches.join('\n') : 'None';
  }

  extractBuiltins() {
    const builtins = ['gl_FragCoord', 'gl_FragDepth', 'gl_FrontFacing', 'gl_PointCoord'];
    const used = builtins.filter(b => this.content.includes(b));
    return used.length ? used.join(', ') : 'None';
  }

  extractFunctions() {
    const matches = (this.content.match(/(\w+)\s+(\w+)\s*\([^)]*\)\s*\{[^}]*\}/g) || []).slice(0, 5);
    return matches.length ? matches.join('\n') + (matches.length === 5 ? '\n... (truncated)' : '') : 'None';
  }

  extractMain() {
    const match = this.content.match(/void\s+main\s*\([^)]*\)\s*\{([^}]*)\}/s);
    return match ? match[1].trim() : 'Not found';
  }

  printProperties() {
    if (!this.content) this.read();
    console.log('=== .FRAG Properties ===');
    console.log(`GLSL Version: ${this.extractVersion()}`);
    console.log(`Extensions: ${this.extractExtensions()}`);
    console.log(`Precision Qualifiers: ${this.extractPrecision()}`);
    console.log(`Uniforms: ${this.extractUniforms()}`);
    console.log(`Input Variables (in): ${this.extractInputs()}`);
    console.log(`Output Variables (out): ${this.extractOutputs()}`);
    console.log(`Built-in Variables: ${this.extractBuiltins()}`);
    console.log(`Custom Functions: ${this.extractFunctions()}`);
    console.log(`Main Function Body: ${this.extractMain()}`);
    console.log('Encoding/Line Endings: UTF-8 / LF (assumed)');
    console.log('MIME Type: text/plain');
    console.log('File Size Constraints: None formal');
    console.log('======================');
  }

  write(outputPath) {
    if (!this.content) throw new Error('Content not loaded. Call read() first.');
    fs.writeFileSync(outputPath, this.content, 'utf8');
    console.log(`File written to ${outputPath}.`);
  }
}

// Example usage
const args = process.argv.slice(2);
if (args.length < 1) {
  console.log('Usage: node fragHandler.js <path_to.frag>');
  process.exit(1);
}
const handler = new FragHandler(args[0]);
handler.read();
handler.printProperties();
handler.write('output.frag');  // Writes a copy

7. C Class for .FRAG Handling

This is a C++ class (using <fstream>, <regex>, <vector> for C++11+ compatibility; compile with g++ -std=c++11 frag_handler.cpp -o frag_handler and run ./frag_handler <path_to.frag>). It reads/decodes as UTF-8 (assumed), parses with regex, prints to stdout, and writes back.

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <regex>

class FragHandler {
private:
    std::string filepath;
    std::string content;
    std::vector<std::string> lines;

public:
    FragHandler(const std::string& fp) : filepath(fp) {}

    void read() {
        std::ifstream file(filepath, std::ios::in);
        if (!file.is_open()) {
            throw std::runtime_error("Cannot open file.");
        }
        std::stringstream buffer;
        buffer << file.rdbuf();
        content = buffer.str();
        std::string line;
        while (std::getline(file, line)) {
            lines.push_back(line);
        }
        file.close();
        std::cout << "File read successfully." << std::endl;
    }

    std::string extractVersion() {
        std::regex re(R"(#version\s+(\d+(?:\.\d+)?)\s*(core|compatibility|es)?)", std::regex::icase);
        std::smatch match;
        if (std::regex_search(content, match, re)) {
            return match[1].str() + (match[2].matched ? " " + match[2].str() : "");
        }
        return "Not specified";
    }

    std::string extractExtensions() {
        std::regex re(R"(#extension\s+([^:]+):?\s*(enable|require|warn|disable)?)", std::regex::icase);
        std::sregex_iterator iter(content.begin(), content.end(), re);
        std::sregex_iterator end;
        std::stringstream ss;
        for (; iter != end; ++iter) {
            ss << "#extension " << iter->str(1) << " " << (iter->str(2).empty() ? "" : iter->str(2)) << "\n";
        }
        std::string res = ss.str();
        return res.empty() ? "None" : res;
    }

    std::string extractPrecision() {
        std::regex re(R"(^precision\s+(lowp|mediump|highp)\s+(float|int);$)", std::regex::icase);
        std::vector<std::string> matches;
        for (const auto& line : lines) {
            std::smatch m;
            if (std::regex_match(line, m, re)) {
                matches.push_back(line);
            }
        }
        if (matches.empty()) return "Default (highp float)";
        std::stringstream ss;
        for (const auto& m : matches) ss << m << "\n";
        return ss.str();
    }

    std::string extractUniforms() {
        std::regex re(R"(^uniform\s+\w+\s+\w+;)", std::regex::icase);
        std::vector<std::string> matches;
        for (const auto& line : lines) {
            std::smatch m;
            if (std::regex_search(line, m, re)) {
                matches.push_back(line);
            }
        }
        if (matches.empty()) return "None";
        std::stringstream ss;
        for (const auto& m : matches) ss << m << "\n";
        return ss.str();
    }

    std::string extractInputs() {
        std::regex re(R"(^in\s+\w+\s+\w+;)", std::regex::icase);
        std::vector<std::string> matches;
        for (const auto& line : lines) {
            std::smatch m;
            if (std::regex_search(line, m, re)) {
                matches.push_back(line);
            }
        }
        if (matches.empty()) return "None";
        std::stringstream ss;
        for (const auto& m : matches) ss << m << "\n";
        return ss.str();
    }

    std::string extractOutputs() {
        std::regex re(R"(^out\s+\w+\s+\w+;)", std::regex::icase);
        std::vector<std::string> matches;
        for (const auto& line : lines) {
            std::smatch m;
            if (std::regex_search(line, m, re)) {
                matches.push_back(line);
            }
        }
        if (matches.empty()) return "None";
        std::stringstream ss;
        for (const auto& m : matches) ss << m << "\n";
        return ss.str();
    }

    std::string extractBuiltins() {
        std::vector<std::string> builtins = {"gl_FragCoord", "gl_FragDepth", "gl_FrontFacing", "gl_PointCoord"};
        std::vector<std::string> used;
        for (const auto& b : builtins) {
            if (content.find(b) != std::string::npos) {
                used.push_back(b);
            }
        }
        if (used.empty()) return "None";
        std::stringstream ss;
        for (size_t i = 0; i < used.size(); ++i) {
            ss << used[i];
            if (i < used.size() - 1) ss << ", ";
        }
        return ss.str();
    }

    std::string extractFunctions() {
        std::regex re(R"(\w+\s+\w+\s*\([^)]*\)\s*\{[^}]*\})");
        std::sregex_iterator iter(content.begin(), content.end(), re);
        std::sregex_iterator end;
        std::vector<std::string> funcs;
        for (; iter != end && funcs.size() < 5; ++iter) {
            funcs.push_back(iter->str().substr(0, 30) + "(...)");  // Truncate for display
        }
        if (funcs.empty()) return "None";
        std::stringstream ss;
        for (const auto& f : funcs) ss << f << "\n";
        if (std::distance(std::sregex_iterator(content.begin(), content.end(), re), end) > 5) ss << "... (truncated)";
        return ss.str();
    }

    std::string extractMain() {
        std::regex re(R"(void\s+main\s*\([^)]*\)\s*\{([^}]*)\})", std::regex::icase);
        std::smatch match;
        if (std::regex_search(content, match, re)) {
            return match[1].str();
        }
        return "Not found";
    }

    void printProperties() {
        if (content.empty()) read();
        std::cout << "=== .FRAG Properties ===" << std::endl;
        std::cout << "GLSL Version: " << extractVersion() << std::endl;
        std::cout << "Extensions: " << extractExtensions() << std::endl;
        std::cout << "Precision Qualifiers: " << extractPrecision() << std::endl;
        std::cout << "Uniforms: " << extractUniforms() << std::endl;
        std::cout << "Input Variables (in): " << extractInputs() << std::endl;
        std::cout << "Output Variables (out): " << extractOutputs() << std::endl;
        std::cout << "Built-in Variables: " << extractBuiltins() << std::endl;
        std::cout << "Custom Functions: " << extractFunctions() << std::endl;
        std::cout << "Main Function Body: " << extractMain() << std::endl;
        std::cout << "Encoding/Line Endings: UTF-8 / LF (assumed)" << std::endl;
        std::cout << "MIME Type: text/plain" << std::endl;
        std::cout << "File Size Constraints: None formal" << std::endl;
        std::cout << "======================" << std::endl;
    }

    void write(const std::string& outputPath) {
        if (content.empty()) throw std::runtime_error("Content not loaded. Call read() first.");
        std::ofstream file(outputPath);
        if (!file.is_open()) {
            throw std::runtime_error("Cannot write file.");
        }
        file << content;
        file.close();
        std::cout << "File written to " << outputPath << "." << std::endl;
    }
};

int main(int argc, char* argv[]) {
    if (argc < 2) {
        std::cout << "Usage: ./frag_handler <path_to.frag>" << std::endl;
        return 1;
    }
    try {
        FragHandler handler(argv[1]);
        handler.read();
        handler.printProperties();
        handler.write("output.frag");  // Writes a copy
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        return 1;
    }
    return 0;
}