From 7802ad1211ec41692b70e02b8917b921ca47b9c1 Mon Sep 17 00:00:00 2001 From: falsycat Date: Wed, 21 Apr 2021 00:00:00 +0000 Subject: [PATCH] Merges thirdparty modules into this repo. --- .gitmodules | 9 - thirdparty/dast | 1 - thirdparty/dast/.gitignore | 4 + thirdparty/dast/dub.json | 6 + thirdparty/dast/src/dast/parse/exception.d | 16 ++ thirdparty/dast/src/dast/parse/itemset.d | 219 ++++++++++++++++++ thirdparty/dast/src/dast/parse/package.d | 201 ++++++++++++++++ thirdparty/dast/src/dast/parse/rule.d | 172 ++++++++++++++ thirdparty/dast/src/dast/parse/ruleset.d | 96 ++++++++ thirdparty/dast/src/dast/parse/table.d | 80 +++++++ thirdparty/dast/src/dast/tokenize/data.d | 44 ++++ thirdparty/dast/src/dast/tokenize/exception.d | 31 +++ thirdparty/dast/src/dast/tokenize/match.d | 142 ++++++++++++ thirdparty/dast/src/dast/tokenize/package.d | 122 ++++++++++ thirdparty/dast/src/dast/util/range.d | 19 ++ thirdparty/dast/test/all.sh | 15 ++ thirdparty/dast/test/math.d | 91 ++++++++ thirdparty/ft4d | 1 - thirdparty/ft4d/.gitignore | 5 + thirdparty/ft4d/LICENSE | 9 + thirdparty/ft4d/dub.json | 14 ++ thirdparty/ft4d/src/ft4d/Face.d | 100 ++++++++ thirdparty/ft4d/src/ft4d/ft.d | 40 ++++ thirdparty/ft4d/src/ft4d/package.d | 7 + thirdparty/ft4d/test.d | 32 +++ thirdparty/gl4d | 1 - thirdparty/gl4d/.gitignore | 8 + thirdparty/gl4d/dub.json | 13 ++ thirdparty/gl4d/src/gl4d/Buffer.d | 164 +++++++++++++ thirdparty/gl4d/src/gl4d/Framebuffer.d | 95 ++++++++ thirdparty/gl4d/src/gl4d/GLObject.d | 56 +++++ thirdparty/gl4d/src/gl4d/Program.d | 204 ++++++++++++++++ thirdparty/gl4d/src/gl4d/Renderbuffer.d | 46 ++++ thirdparty/gl4d/src/gl4d/Sampler.d | 57 +++++ thirdparty/gl4d/src/gl4d/Shader.d | 99 ++++++++ thirdparty/gl4d/src/gl4d/Texture.d | 146 ++++++++++++ thirdparty/gl4d/src/gl4d/VertexArray.d | 84 +++++++ thirdparty/gl4d/src/gl4d/gl.d | 65 ++++++ thirdparty/gl4d/src/gl4d/math.d | 6 + thirdparty/gl4d/src/gl4d/package.d | 17 ++ .../gl4d/src/gl4d/util/ModelMatrixFactory.d | 50 ++++ .../src/gl4d/util/ProjectionMatrixFactory.d | 22 ++ .../gl4d/src/gl4d/util/ViewMatrixFactory.d | 20 ++ thirdparty/gl4d/src/gl4d/util/package.d | 8 + thirdparty/gl4d/test.d | 201 ++++++++++++++++ 45 files changed, 2826 insertions(+), 12 deletions(-) delete mode 100644 .gitmodules delete mode 160000 thirdparty/dast create mode 100644 thirdparty/dast/.gitignore create mode 100644 thirdparty/dast/dub.json create mode 100644 thirdparty/dast/src/dast/parse/exception.d create mode 100644 thirdparty/dast/src/dast/parse/itemset.d create mode 100644 thirdparty/dast/src/dast/parse/package.d create mode 100644 thirdparty/dast/src/dast/parse/rule.d create mode 100644 thirdparty/dast/src/dast/parse/ruleset.d create mode 100644 thirdparty/dast/src/dast/parse/table.d create mode 100644 thirdparty/dast/src/dast/tokenize/data.d create mode 100644 thirdparty/dast/src/dast/tokenize/exception.d create mode 100644 thirdparty/dast/src/dast/tokenize/match.d create mode 100644 thirdparty/dast/src/dast/tokenize/package.d create mode 100644 thirdparty/dast/src/dast/util/range.d create mode 100644 thirdparty/dast/test/all.sh create mode 100644 thirdparty/dast/test/math.d delete mode 160000 thirdparty/ft4d create mode 100644 thirdparty/ft4d/.gitignore create mode 100644 thirdparty/ft4d/LICENSE create mode 100644 thirdparty/ft4d/dub.json create mode 100644 thirdparty/ft4d/src/ft4d/Face.d create mode 100644 thirdparty/ft4d/src/ft4d/ft.d create mode 100644 thirdparty/ft4d/src/ft4d/package.d create mode 100644 thirdparty/ft4d/test.d delete mode 160000 thirdparty/gl4d create mode 100644 thirdparty/gl4d/.gitignore create mode 100644 thirdparty/gl4d/dub.json create mode 100644 thirdparty/gl4d/src/gl4d/Buffer.d create mode 100644 thirdparty/gl4d/src/gl4d/Framebuffer.d create mode 100644 thirdparty/gl4d/src/gl4d/GLObject.d create mode 100644 thirdparty/gl4d/src/gl4d/Program.d create mode 100644 thirdparty/gl4d/src/gl4d/Renderbuffer.d create mode 100644 thirdparty/gl4d/src/gl4d/Sampler.d create mode 100644 thirdparty/gl4d/src/gl4d/Shader.d create mode 100644 thirdparty/gl4d/src/gl4d/Texture.d create mode 100644 thirdparty/gl4d/src/gl4d/VertexArray.d create mode 100644 thirdparty/gl4d/src/gl4d/gl.d create mode 100644 thirdparty/gl4d/src/gl4d/math.d create mode 100644 thirdparty/gl4d/src/gl4d/package.d create mode 100644 thirdparty/gl4d/src/gl4d/util/ModelMatrixFactory.d create mode 100644 thirdparty/gl4d/src/gl4d/util/ProjectionMatrixFactory.d create mode 100644 thirdparty/gl4d/src/gl4d/util/ViewMatrixFactory.d create mode 100644 thirdparty/gl4d/src/gl4d/util/package.d create mode 100644 thirdparty/gl4d/test.d diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 09d0611..0000000 --- a/.gitmodules +++ /dev/null @@ -1,9 +0,0 @@ -[submodule "thirdparty/dast"] - path = thirdparty/dast - url = https://gogs.enigmatical.work/kajironagi/dast.git -[submodule "thirdparty/gl4d"] - path = thirdparty/gl4d - url = https://gogs.enigmatical.work/kajironagi/gl4d.git -[submodule "thirdparty/ft4d"] - path = thirdparty/ft4d - url = https://gogs.enigmatical.work/KajiroNagi/ft4d.git diff --git a/thirdparty/dast b/thirdparty/dast deleted file mode 160000 index d99fcb9..0000000 --- a/thirdparty/dast +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d99fcb9bf502683ab4e5f6fb19a64a3bda98c83e diff --git a/thirdparty/dast/.gitignore b/thirdparty/dast/.gitignore new file mode 100644 index 0000000..e653e81 --- /dev/null +++ b/thirdparty/dast/.gitignore @@ -0,0 +1,4 @@ +/.bin +/.dub + +*.swp diff --git a/thirdparty/dast/dub.json b/thirdparty/dast/dub.json new file mode 100644 index 0000000..f589de9 --- /dev/null +++ b/thirdparty/dast/dub.json @@ -0,0 +1,6 @@ +{ + "name": "dast", + + "targetType": "library", + "targetPath": ".bin" +} diff --git a/thirdparty/dast/src/dast/parse/exception.d b/thirdparty/dast/src/dast/parse/exception.d new file mode 100644 index 0000000..8c8674a --- /dev/null +++ b/thirdparty/dast/src/dast/parse/exception.d @@ -0,0 +1,16 @@ +/// License: MIT +module dast.parse.exception; + +import dast.tokenize; + +/// +class ParseException(Token) : Exception if (IsToken!Token) { + public: + /// + this(string msg, Token token, string file = __FILE__, size_t line = __LINE__) { + super(msg, file, line); + this.token = token; + } + + Token token; +} diff --git a/thirdparty/dast/src/dast/parse/itemset.d b/thirdparty/dast/src/dast/parse/itemset.d new file mode 100644 index 0000000..41464b1 --- /dev/null +++ b/thirdparty/dast/src/dast/parse/itemset.d @@ -0,0 +1,219 @@ +/// License: MIT +module dast.parse.itemset; + +import std.algorithm, + std.array, + std.conv, + std.range, + std.range.primitives, + std.traits, + std.typecons; + +import dast.parse.rule, + dast.parse.ruleset, + dast.util.range; + +/// +unittest { + import std; + + enum TokenType { + Ident, + End, + } + alias Token = dast.tokenize.Token!(TokenType, string); + + struct RuleSet { + public: + @ParseRule: + int ParseWhole(string, @(TokenType.End) Token) { + assert(false); + } + string ParseString(@(TokenType.Ident) Token) { + assert(false); + } + } + + enum rules = CreateRulesFromRuleSet!(RuleSet, TokenType)(); + enum itemset = rules.CreateItemSetFromRules(typeid(int)); +} + +/// +struct RuleWithCursor(T) if (IsRule!T) { + public: + alias entity this; + + /// + bool opEquals(in RuleWithCursor other) const { + return + entity == other.entity && + cursor == other.cursor && + follows.equal(other.follows); + } + + /// + @property RuleWithCursor advanced() const in (canAdvance) { + return RuleWithCursor(entity, cursor+1, follows); + } + + /// + @property Term!(T.TokenType) prev() const in (cursor > 0) { + return cast(Term!(T.TokenType)) entity.rhs[cursor-1]; + } + /// + @property Term!(T.TokenType) next() const in (canAdvance) { + return cast(Term!(T.TokenType)) entity.rhs[cursor]; + } + + /// + @property bool canAdvance() const { + return cursor < entity.rhs.length; + } + + /// + const T entity; + + /// + const size_t cursor; + + /// + const T.TokenType[] follows; +} + +/// +struct ItemSet(T) if (IsRule!T) { + public: + /// + TypeInfo whole; + + /// + const Term!(T.TokenType)[] terms; + /// + const size_t[size_t][] automaton; + /// + const RuleWithCursor!T[][] states; +} + +/// +ItemSet!(ElementType!R) CreateItemSetFromRules(R)(R rules, TypeInfo whole) + if (isInputRange!R && IsRule!(ElementType!R)) +in { + assert(rules.count!(x => x.lhs is whole) == 1); +} +do { + alias T = ElementType!R; + + Term!(T.TokenType)[] terms; + size_t FindOrRegisterTermIndices(typeof(terms[0]) term) { + auto index = terms.countUntil!(x => x == term); + if (index < 0) { + index = terms.length.to!(typeof(index)); + terms ~= term; + } + return index.to!size_t; + } + + RuleWithCursor!T[] Resolve( + RuleWithCursor!T[] items, TypeInfo[] prev_resolved_types = [null]) { + auto resolved_types = prev_resolved_types.appender; + + T[] new_items; + foreach (item; items) { + const type = + !item.canAdvance || item.next.isTerminal? null: item.next.nonTerminalType; + if (resolved_types[].canFind!"a is b"(type)) continue; + + rules. + filter!(x => x.lhs is type). + each !(x => new_items ~= x); + resolved_types ~= type; + } + + auto result = items ~ new_items. + map!(x => RuleWithCursor!T(x)). + array; + return new_items.length > 0? + Resolve(result, resolved_types[]): result; + } + RuleWithCursor!T[][size_t] Advance(in RuleWithCursor!T[] items) { + RuleWithCursor!T[][size_t] new_items; + + foreach (item; items.filter!"a.canAdvance".map!"a.advanced") { + auto term = item.prev; + const index = FindOrRegisterTermIndices(term); + if (index !in new_items) { + new_items[index] = []; + } + new_items[index] ~= item; + } + foreach (ref state; new_items) { + state = Resolve(state).AttachFollowSet(); + } + return new_items; + } + + size_t[size_t][] automaton; + RuleWithCursor!T[][] states; + + const first_rule = rules.find!"a.lhs is b"(whole)[0]; + + size_t current_state; + states = [Resolve([RuleWithCursor!T(first_rule)]).AttachFollowSet()]; + + while (true) { + automaton.length = current_state+1; + + const advanced_states = Advance(states[current_state]); + foreach (termi, state; advanced_states) { + auto index = states.countUntil!(x => x.equal(state)); + if (index < 0) { + index = states.length.to!(typeof(index)); + states ~= state.dup; + } + automaton[current_state][termi] = index.to!size_t; + } + if (++current_state >= states.length) break; + } + return ItemSet!T(whole, terms, automaton, states); +} + +private RuleWithCursor!T[] AttachFollowSet(T)(RuleWithCursor!T[] rules) { + alias TokenType = T.TokenType; + + TokenType[] CreateFollowSet(TypeInfo type, TypeInfo[] skip_types = []) { + if (skip_types.canFind!"a is b"(type)) return []; + + auto result = appender!(TokenType[]); + skip_types ~= type; + + foreach (const ref rule; rules) { + if (rule.lhs is type) { + result ~= rule.follows; + } + if (!rule.canAdvance || rule.next != type) continue; + + if (rule.cursor+1 < rule.rhs.length) { + const follow_term = rule.rhs[rule.cursor+1]; + if (follow_term.isTerminal) { + result ~= follow_term.terminalType; + } else { + result ~= rules.CreateFirstSet!TokenType(follow_term.nonTerminalType); + } + } else { + result ~= CreateFollowSet(rule.lhs, skip_types); + } + } + return result[]; + } + + TokenType[][TypeInfo] caches; + + RuleWithCursor!T[] result; + foreach (const ref rule; rules) { + if (rule.lhs !in caches) { + caches[rule.lhs] = CreateFollowSet(rule.lhs).DropDuplicated; + } + result ~= RuleWithCursor!T(rule.entity, rule.cursor, caches[rule.lhs]); + } + return result; +} diff --git a/thirdparty/dast/src/dast/parse/package.d b/thirdparty/dast/src/dast/parse/package.d new file mode 100644 index 0000000..7fd1b96 --- /dev/null +++ b/thirdparty/dast/src/dast/parse/package.d @@ -0,0 +1,201 @@ +/// License: MIT +module dast.parse; + +import std.algorithm, + std.conv, + std.range.primitives, + std.traits, + std.variant; + +import dast.tokenize; + +import dast.parse.itemset, + dast.parse.rule, + dast.parse.ruleset, + dast.parse.table; + +public { + import dast.parse.exception, + dast.parse.ruleset; +} + +/// +unittest { + import std; + + enum TokenType { + Unknown, + + @TextCompleteMatcher!"," Comma, + + @TextAllMatcher!isDigit Number, + @TextAllMatcher!isWhite Whitespace, + + End, + } + alias Token = dast.tokenize.Token!(TokenType, string); + + struct Whole { + int[] numbers; + } + class RuleSet { + public: + @ParseRule: + Whole ParseWhole(int[] nums, @(TokenType.End) Token) { + return Whole(nums); + } + int[] ParseFirstNumber(@(TokenType.Number) Token num) { + return [num.text.to!int]; + } + int[] ParseNextNumber(int[] nums, @(TokenType.Comma) Token, @(TokenType.Number) Token num) { + return nums ~ num.text.to!int; + } + } + + auto ruleset = new RuleSet; + + int[] ParseNumbers(string src) { + return src. + Tokenize!TokenType. + filter!(x => x.type != TokenType.Whitespace). + chain([Token("", TokenType.End)]). + Parse!Whole(ruleset).numbers; + } + assert(ParseNumbers("0, 1, 2, 3").equal([0, 1, 2, 3])); + + assertThrown!(ParseException!Token)(ParseNumbers("0 1 2 3")); +} + +/// +Whole Parse(Whole, Tokenizer, RuleSet)(Tokenizer tokenizer, RuleSet ruleset) + if (isInputRange!Tokenizer && IsToken!(ElementType!Tokenizer)) { + alias Token = ElementType!Tokenizer; + alias Exception = ParseException!Token; + + enum rules = CreateRulesFromRuleSet!(RuleSet, Token.TokenType); + static assert(rules.length > 0); + + enum itemset = rules.CreateItemSetFromRules(typeid(Whole)); + enum table = itemset.CreateTableFromItemSet(); + enum statelen = table.shift.length; + + enum stackitem_size = itemset.terms. + filter!"!a.isTerminal". + map !"a.nonTerminalType.tsize". + maxElement. + max(Token.sizeof); + + alias StackItem = VariantN!(stackitem_size); + + StackItem[] stack; + size_t[] status = [0]; + + size_t Reduce(Token, string name)() { + alias Func = mixin("RuleSet."~name); + alias Params = Parameters!Func; + + Params params; + static foreach (parami; 0..Params.length) { + static if (is(Params[$-parami-1] == Token)) { + params[$-parami-1] = stack[$-parami-1].get!Token; + } else { + params[$-parami-1] = stack[$-parami-1].get!(Params[$-parami-1]); + } + } + stack = stack[0..$-Params.length]; + stack ~= StackItem(mixin("ruleset."~name~"(params)")); + return Params.length; + } + + void GoStateAfterReduce() { + auto stack_top_type = stack[$-1].type; + const current_status = status[$-1]; + + TypeInfo temp; + static foreach (statei; 0..statelen) { + if (statei == current_status) { + static foreach (type, number; table.go[statei]) { + if (type is stack_top_type) { + status ~= table.go[statei][stack_top_type]; + return; + } + } + } + } + assert(false); // broken go table + } + + Token prev_token; + while (status[$-1] != table.accept_state) { + if (tokenizer.empty) { + throw new Exception("all tokens are consumed without acception", prev_token); + } + const token = tokenizer.front; + scope(exit) prev_token = token; + +MAIN_SWITCH: switch (status[$-1]) { + static foreach (statei; 0..statelen) { + case statei: + static foreach (token_type, reduce; table.reduce[statei]) { + if (token_type == token.type) { + const pop = Reduce!(Token, reduce); + status = status[0..$-pop]; + GoStateAfterReduce(); + break MAIN_SWITCH; + } + } + static foreach (token_type, shift; table.shift[statei]) { + if (token_type == token.type) { + stack ~= StackItem(cast(Unqual!Token) token); + status ~= shift; + tokenizer.popFront(); + break MAIN_SWITCH; + } + } + throw new Exception("unexpected token", token); + } + default: assert(false); + } + } + + Reduce!(Token, itemset.states[0][0].name); + return stack[0].get!Whole; +} + +debug void PrintItemSet(TokenType, RuleSet, Whole)() + if (is(TokenType == enum)) { + import std; + + enum itemset = + CreateRulesFromRuleSet!(RuleSet, TokenType). + CreateItemSetFromRules(typeid(Whole)); + + void PrintTerm(T2)(T2 term) { + if (term.isTerminal) { + term.terminalType.write; + } else { + term.nonTerminalType.write; + } + } + + "[states]".writeln; + foreach (statei, state; itemset.states) { + " (%d) ".writef(statei); + foreach (termi, go; itemset.automaton[statei]) { + PrintTerm(itemset.terms[termi]); + ":%d, ".writef(go); + } + writeln; + + foreach (rule; state) { + " %s => ".writef(rule.lhs); + foreach (termi, term; rule.rhs) { + if (termi == rule.cursor) "/ ".write; + PrintTerm(term); + " ".write; + } + if (rule.rhs.length == rule.cursor) "/ ".write; + rule.follows.writeln; + } + } +} diff --git a/thirdparty/dast/src/dast/parse/rule.d b/thirdparty/dast/src/dast/parse/rule.d new file mode 100644 index 0000000..407233e --- /dev/null +++ b/thirdparty/dast/src/dast/parse/rule.d @@ -0,0 +1,172 @@ +/// License: MIT +module dast.parse.rule; + +import std.algorithm, + std.array, + std.traits; + +import dast.tokenize; + +/// +unittest { + enum TokenType { + Number, + } + alias Token = dast.tokenize.Token!(TokenType, string); + + struct RuleSet { + public: + int ParseWhole(@(TokenType.Number) Token, string v) { + assert(false); + } + } + enum rule_ctfe = Rule!TokenType.CreateFromNonOverloadedMethod!(RuleSet, "ParseWhole"); + + static assert(rule_ctfe.lhs is typeid(int)); + + static assert(rule_ctfe.rhs.length == 2); + static assert(rule_ctfe.rhs[0] == TokenType.Number); + static assert(rule_ctfe.rhs[1] == typeid(string)); +} + +/// +template IsRule(T) { + static if (__traits(compiles, T.TokenType)) { + enum IsRule = is(T : Rule!(T.TokenType)); + } else { + enum IsRule = false; + } +} + +/// +struct NamedRule(TokenType) if (is(TokenType == enum)) { + public: + alias entity this; + + /// + const string name; + /// + const Rule!TokenType entity; +} + +/// +struct Rule(TokenType_) if (is(TokenType_ == enum)) { + public: + /// + alias TokenType = TokenType_; + + /// + static Rule CreateFromNonOverloadedMethod(T, string name)() + if (__traits(hasMember, T, name)) { + alias method = mixin("T." ~ name); + alias Params = Parameters!method; + + auto rhs = appender!(Term!TokenType[])(); + static foreach (parami; 0..Params.length) { + static if (IsToken!(Params[parami])) {{ + static foreach (attr; __traits(getAttributes, Params[parami..parami+1])) { + static if (is(typeof(attr) == TokenType)) { + static assert(!__traits(compiles, found_attr)); + enum found_attr = true; + rhs ~= Term!TokenType(attr); + } + } + static assert(__traits(compiles, found_attr)); + }} else { + rhs ~= Term!TokenType(TypeInfoWithSize.CreateFromType!(Params[parami])); + } + } + return Rule(typeid(ReturnType!method), rhs.array); + } + + /// + bool opEquals(Rule other) const { + return lhs is other.lhs && rhs_.equal(other.rhs_); + } + + /// + @property TypeInfo lhs() const { + return cast(TypeInfo) lhs_; + } + /// + @property const(Term!TokenType[]) rhs() const { + return rhs_; + } + + private: + const TypeInfo lhs_; + invariant(lhs_ !is null); + + const Term!TokenType[] rhs_; + invariant(rhs_.length > 0); +} + +/// +struct Term(TokenType) if (is(TokenType == enum)) { + public: + @disable this(); + + /// + this(TypeInfoWithSize type) { + data_.type_info = type; + terminal_ = false; + } + /// + this(TokenType type) { + data_.token_type = type; + terminal_ = true; + } + + /// + bool opEquals(in Term rhs) const { + return isTerminal? + rhs.isTerminal && terminalType == rhs.terminalType: + !rhs.isTerminal && nonTerminalType is rhs.nonTerminalType; + } + /// + bool opEquals(in TypeInfo type) const { + return !isTerminal && nonTerminalType is type; + } + /// + bool opEquals(TokenType type) const { + return isTerminal && terminalType == type; + } + + /// + @property bool isTerminal() const { + return terminal_; + } + + /// + @property TokenType terminalType() const in (isTerminal) { + return data_.token_type; + } + /// + @property TypeInfoWithSize nonTerminalType() const in (!isTerminal) { + return cast(TypeInfoWithSize) data_.type_info; + } + + private: + union Data { + TypeInfoWithSize type_info; + TokenType token_type; + } + Data data_; + bool terminal_; +} + +/// +struct TypeInfoWithSize { + public: + alias entity this; + + /// + static TypeInfoWithSize CreateFromType(T)() { + return TypeInfoWithSize(T.sizeof, typeid(T)); + } + + /// + const size_t tsize; + /// + TypeInfo entity; +} diff --git a/thirdparty/dast/src/dast/parse/ruleset.d b/thirdparty/dast/src/dast/parse/ruleset.d new file mode 100644 index 0000000..36bb5be --- /dev/null +++ b/thirdparty/dast/src/dast/parse/ruleset.d @@ -0,0 +1,96 @@ +/// License: MIT +module dast.parse.ruleset; + +import std.algorithm, + std.array, + std.meta, + std.range.primitives, + std.traits; + +import dast.tokenize; + +import dast.parse.rule; + +/// +unittest { + import std; + import dast.util.range; + + enum TokenType { + Number, + Ident, + End, + } + alias Token = dast.tokenize.Token!(TokenType, string); + + struct RuleSet { + public: + @ParseRule: + int ParseWhole(string, @(TokenType.End) Token) { + assert(false); + } + string ParseStr(@(TokenType.Ident) Token) { + assert(false); + } + string ParseStrFromFloat(float) { + assert(false); + } + float ParseFloat(@(TokenType.Number) Token) { + assert(false); + } + float ParseFloatFromStr(@(TokenType.Ident) Token, string, @(TokenType.Ident) Token) { + assert(false); + } + } + + enum rules = CreateRulesFromRuleSet!(RuleSet, TokenType)(); + static assert(rules.length == 5); + + enum firsts = rules.CreateFirstSet!TokenType(typeid(int)).DropDuplicated; + static assert(firsts.equal([TokenType.Ident, TokenType.Number])); +} + +/// A struct which is attributed to rule methods. +struct ParseRule {} + +/// Creates rules from RuleSet's methods attributed by ParseRule. +NamedRule!TokenType[] CreateRulesFromRuleSet(RuleSet, TokenType)() { + alias members = __traits(allMembers, RuleSet); + + NamedRule!TokenType[] result; // Appender causes CTFE errors :( + + static foreach (name; members) {{ + enum method = "RuleSet." ~ name; + static if (__traits(compiles, mixin(method))) { + alias attrs = __traits(getAttributes, mixin(method)); + static if (staticIndexOf!(ParseRule, attrs) >= 0) { + static assert(__traits(getOverloads, RuleSet, name).length == 1); + auto rule = Rule!TokenType. + CreateFromNonOverloadedMethod!(RuleSet, name); + result ~= NamedRule!TokenType(name, rule); + } + } + }} + return result; +} + +/// Creates a first set of the target. +/// +/// Items in the result can be duplicated. +TokenType[] CreateFirstSet(TokenType, R)(R rules, TypeInfo target, TypeInfo[] skip_types = []) + if (isInputRange!R && is(Unqual!(ElementType!R) : Rule!TokenType)) { + auto result = appender!(TokenType[]); + skip_types ~= target; + + foreach (const ref rule; rules.filter!(x => x.lhs is target)) { + const first = rule.rhs[0]; + if (first.isTerminal) { + result ~= first.terminalType; + } else if (!skip_types.canFind!"a is b"(first.nonTerminalType)) { + result ~= rules. + CreateFirstSet!(TokenType)(first.nonTerminalType, skip_types); + skip_types ~= first.nonTerminalType; + } + } + return result.array; +} diff --git a/thirdparty/dast/src/dast/parse/table.d b/thirdparty/dast/src/dast/parse/table.d new file mode 100644 index 0000000..938d525 --- /dev/null +++ b/thirdparty/dast/src/dast/parse/table.d @@ -0,0 +1,80 @@ +/// License: MIT +module dast.parse.table; + +import std.algorithm; + +import dast.parse.itemset, + dast.parse.rule, + dast.parse.ruleset; + +/// +unittest { + import std; + + enum TokenType { + Ident, + End, + } + alias Token = dast.tokenize.Token!(TokenType, string); + + struct RuleSet { + public: + @ParseRule: + int ParseWhole(string, @(TokenType.End) Token) { + assert(false); + } + string ParseString(@(TokenType.Ident) Token) { + assert(false); + } + } + + enum rules = CreateRulesFromRuleSet!(RuleSet, TokenType)(); + enum itemset = rules.CreateItemSetFromRules(typeid(int)); + enum table = itemset.CreateTableFromItemSet(); +} + +/// +struct Table(TokenType) if (is(TokenType == enum)) { + public: + /// + const size_t[TokenType][] shift; + /// + const string[TokenType][] reduce; + /// + const size_t[TypeInfo][] go; + + /// + const size_t accept_state; +} + +/// +Table!(T.TokenType) CreateTableFromItemSet(T)(in ItemSet!T itemset) { + size_t[T.TokenType][] shift; + string[T.TokenType][] reduce; + size_t[TypeInfo][] go; + + size_t accept_state; + + shift.length = itemset.automaton.length; + reduce.length = itemset.automaton.length; + go.length = itemset.automaton.length; + + foreach (state, numbers; itemset.automaton) { + foreach (k, v; numbers) { + const term = itemset.terms[k]; + if (term.isTerminal) { + shift[state][term.terminalType] = v; + } else { + go[state][term.nonTerminalType] = v; + } + } + } + + foreach (state, rules; itemset.states) { + foreach (rule; rules.filter!"!a.canAdvance") { + if (rule.lhs is itemset.whole) accept_state = state; + rule.follows.each!(x => reduce[state][x] = rule.name); + } + } + return Table!(T.TokenType)(shift, reduce, go, accept_state); +} diff --git a/thirdparty/dast/src/dast/tokenize/data.d b/thirdparty/dast/src/dast/tokenize/data.d new file mode 100644 index 0000000..4fe4ade --- /dev/null +++ b/thirdparty/dast/src/dast/tokenize/data.d @@ -0,0 +1,44 @@ +/// License: MIT +module dast.tokenize.data; + +import std.traits; + +/// +template IsToken(T) { + private enum hasRequiredMembers = + __traits(hasMember, T, "text") && + __traits(hasMember, T, "type") && + __traits(hasMember, T, "pos"); + + static if (hasRequiredMembers) { + enum IsToken = + isSomeString!(typeof(T.text)) && + is(typeof(T.type) == enum) && + is(typeof(T.pos) == TokenPos); + } else { + enum IsToken = false; + } +} + +/// +enum IsToken(T, S) = IsToken!T && is(typeof(T.text) == S); + +/// +struct Token(TokenType_, S) + if (is(TokenType_ == enum) && isSomeString!S) { + alias TokenType = TokenType_; + + S text; + TokenType type; + + TokenPos pos; +} + +/// +struct TokenPos { + size_t stline; + size_t edline; + + size_t stchar; + size_t edchar; +} diff --git a/thirdparty/dast/src/dast/tokenize/exception.d b/thirdparty/dast/src/dast/tokenize/exception.d new file mode 100644 index 0000000..dce0a78 --- /dev/null +++ b/thirdparty/dast/src/dast/tokenize/exception.d @@ -0,0 +1,31 @@ +/// License: MIT +module dast.tokenize.exception; + +import std.format; + +/// +class TokenizeException : Exception { + public: + /// + this(string msg, + size_t srcline, + size_t srchar, + string src = "", + string file = __FILE__, + size_t line = __LINE__) { + if (src == "") { + msg ~= " at (%d, %d)".format(srcline, srcchar); + } else { + msg ~= " at token '%s' (%d, %d)".format(src, srcline, srcchar); + } + super(msg, file, line); + + this.srcline = srcline; + this.srcchar = srcchar; + } + + /// + const size_t srcline; + /// + const size_t srcchar; +} diff --git a/thirdparty/dast/src/dast/tokenize/match.d b/thirdparty/dast/src/dast/tokenize/match.d new file mode 100644 index 0000000..0a6f810 --- /dev/null +++ b/thirdparty/dast/src/dast/tokenize/match.d @@ -0,0 +1,142 @@ +/// License: MIT +module dast.tokenize.match; + +import std.algorithm, + std.ascii, + std.conv, + std.traits; + +/// +unittest { + import std; + + enum TokenType { + @TextAllMatcher!isDigit Number, + @TextCompleteMatcher!"12" OneTwo, + + @TextFuncMatcher!((string text) { + return text.length >= 3? 3LU: 0LU; + }) ThreeLetters, + + @TextAllMatcher!isUpper + @TextAllMatcher!isLower Ident, + } + + with (TokenType) { + assert(MatchTextAsTokenType!Number("0123") == 4); + assert(MatchTextAsTokenType!Number("01a23") == 2); + assert(MatchTextAsTokenType!Number("a0123") == 0); + + assert(MatchTextAsTokenType!OneTwo("12") == 2); + assert(MatchTextAsTokenType!OneTwo("12a") == 2); + assert(MatchTextAsTokenType!OneTwo("1") == 0); + + assert(MatchTextAsTokenType!ThreeLetters("abc") == 3); + assert(MatchTextAsTokenType!ThreeLetters("abcd") == 3); + assert(MatchTextAsTokenType!ThreeLetters("ab") == 0); + + assert(MatchTextAsTokenType!Ident("abC") == 2); + assert(MatchTextAsTokenType!Ident("AB0") == 2); + assert(MatchTextAsTokenType!Ident("0Ab") == 0); + + { + const result = FindMatchedTokenTypes!TokenType("012"); + assert(result.types.equal([Number, ThreeLetters])); + assert(result.length == 3); + } + { + const result = FindMatchedTokenTypes!TokenType("12"); + assert(result.types.equal([Number, OneTwo])); + assert(result.length == 2); + } + } +} + +/// Checks if the matcher is a text matcher for S. +enum IsTextMatcher(alias matcher, S) = + __traits(compiles, (S str) => matcher.Match(str)) && + is(ReturnType!((S str) => matcher.Match(str)) == size_t); +/// ditto +enum IsTextMatcher(alias matcher) = + IsTextMatcher!(matcher, string) || + IsTextMatcher!(matcher, wstring) || + IsTextMatcher!(matcher, dstring); + +/// +struct TextAllMatcher(alias func) { + public: + /// + static size_t Match(S)(S text) if (isSomeString!S) { + const index = text.countUntil!(x => !func(x)); + return index >= 0? index.to!size_t: text.length; + } + static assert(IsTextMatcher!TextAllMatcher); +} + +/// +struct TextCompleteMatcher(alias str) if (isSomeString!(typeof(str))) { + public: + /// + static size_t Match(typeof(str) text) { + if (text.length < str.length || text[0..str.length] != str) { + return 0; + } + return str.length; + } + static assert(IsTextMatcher!TextCompleteMatcher); +} + +/// +struct TextFuncMatcher(alias func) + if ((is(typeof((string x) => func(x))) || + is(typeof((wstring x) => func(x))) || + is(typeof((dstring x) => func(x)))) && + is(ReturnType!func == size_t)) { + public: + /// + static size_t Match(S)(S text) + if (isSomeString!S && __traits(compiles, func(text))) { + return func(text); + } + static assert(IsTextMatcher!(TextFuncMatcher, string)); +} + +/// Finds matched token types from TokenType enum. +auto FindMatchedTokenTypes(TokenType, S)(S src) + if (is(TokenType == enum) && isSomeString!S) { + struct Result { + TokenType[] types; + size_t length; + } + Result result; + + size_t length = void; + static foreach (type; EnumMembers!TokenType) { + length = MatchTextAsTokenType!type(src); + if (length == 0) { + + } else if (length > result.length) { + result.types = [type]; + result.length = length; + + } else if (length == result.length) { + result.types ~= type; + } + } + return result; +} + +/// Checks if the src can be a token with the type. +size_t MatchTextAsTokenType(alias type, S)(S src) + if (is(typeof(type) == enum) && isSomeString!S) { + alias TokenType = typeof(type); + enum typestr = "TokenType." ~ type.to!string; + + size_t result; + static foreach (attr; __traits(getAttributes, mixin(typestr))) { + static if (IsTextMatcher!(attr, S)) { + result = result.max(attr.Match(src)); + } + } + return result; +} diff --git a/thirdparty/dast/src/dast/tokenize/package.d b/thirdparty/dast/src/dast/tokenize/package.d new file mode 100644 index 0000000..68ae978 --- /dev/null +++ b/thirdparty/dast/src/dast/tokenize/package.d @@ -0,0 +1,122 @@ +/// License: MIT +module dast.tokenize; + +import std.format, + std.range, + std.traits, + std.typecons; + +public { + import dast.tokenize.data, + dast.tokenize.exception, + dast.tokenize.match; +} + +/// +unittest { + import std; + + enum TokenType { + @TextAllMatcher!isDigit Number, + @TextAllMatcher!isAlpha Ident, + @TextAllMatcher!isWhite Whitespace, + + @TextCompleteMatcher!"0" Zero, + @TextCompleteMatcher!"0a" ZeroAndA, + } + + with (TokenType) { + auto tokens = "1 abc 2".Tokenize!(TokenType, string)(); + assert(tokens. + map!"a.type". + equal([Number, Whitespace, Ident, Whitespace, Number])); + assert(tokens. + filter!(x => x.type != Whitespace). + map!"a.text". + equal(["1", "abc", "2"])); + } + with (TokenType) { + auto tokens = "0a 1 2".Tokenize!(TokenType, string)(); + assert(tokens. + map!"a.type". + equal([ZeroAndA, Whitespace, Number, Whitespace, Number])); + assert(tokens. + filter!(x => x.type != Whitespace). + map!"a.text". + equal(["0a", "1", "2"])); + } + assertThrown!TokenizeException("0".Tokenize!(TokenType, string)); +} + +/// Tokenizes the src into the TokenType. +/// Returns: an input range of Token!(TokenType, S) as a voldemorte type +auto Tokenize(TokenType, S)(S src) + if (is(TokenType == enum) && isSomeString!S) { + return Tokenizer!(TokenType, S)(src).drop(1); +} + +private struct Tokenizer(TokenType_, S) + if (is(TokenType_ == enum) && isSomeString!S) { + public: + alias TokenType = TokenType_; + alias Token = dast.tokenize.data.Token!(TokenType, S); + + void PopFront() in (!empty) { + if (cursor_ < src.length) { + TokenizeNext(); + } else { + end_ = true; + } + } + alias popFront = PopFront; + + @property Token front() const in (!empty) { + return last_tokenized_; + } + @property bool empty() const { + return end_; + } + + const S src; + + private: + void TokenizeNext() in (!empty) { + last_tokenized_.pos.stline = line_; + last_tokenized_.pos.stchar = char_; + + const match = FindMatchedTokenTypes!TokenType(src[cursor_..$]); + if (match.types.length == 0 || match.length == 0) { + throw new TokenizeException( + "found the uncategorizable token", + line_, char_, src[cursor_..cursor_+1]); + } + if (match.types.length > 1) { + throw new TokenizeException( + "found the token which can be categorizable to multiple types %s". + format(match.types), + line_, char_, src[cursor_..cursor_+1]); + } + + last_tokenized_.text = src[cursor_..cursor_+match.length]; + last_tokenized_.type = match.types[0]; + + foreach (c; last_tokenized_.text) { + ++char_; + if (c == '\n') { + ++line_; + char_ = 0; + } + } + cursor_ += match.length; + + last_tokenized_.pos.edline = line_; + last_tokenized_.pos.edchar = char_; + } + + Token last_tokenized_; + + bool end_; + + size_t cursor_; + size_t line_, char_; +} diff --git a/thirdparty/dast/src/dast/util/range.d b/thirdparty/dast/src/dast/util/range.d new file mode 100644 index 0000000..ea1cbe3 --- /dev/null +++ b/thirdparty/dast/src/dast/util/range.d @@ -0,0 +1,19 @@ +/// License: MIT +module dast.util.range; + +import std.algorithm, + std.array, + std.range.primitives; + +/// Returns: an input range which has unique items +auto DropDuplicated(R)(R src) if (isInputRange!R) { + auto dest = appender!(ElementType!R[]); + dest.reserve(src.length); + + foreach (item; src) if (!dest[].canFind(item)) dest ~= item; + return dest[]; +} +/// +unittest { + static assert([0, 1, 0, 1].DropDuplicated.equal([0, 1])); +} diff --git a/thirdparty/dast/test/all.sh b/thirdparty/dast/test/all.sh new file mode 100644 index 0000000..ebc8488 --- /dev/null +++ b/thirdparty/dast/test/all.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +if [ ! -d ".git" ]; then + echo "plz run at root of this repository" + exit 1 +fi + +function test() { + result=`$1 "$2"` + if [ "$result" != "$3" ]; then + echo "\`$1 \"$2\"\` != \`$3\`" + fi +} + +test ./test/math.d "1+2-3*4/5" "1" diff --git a/thirdparty/dast/test/math.d b/thirdparty/dast/test/math.d new file mode 100644 index 0000000..089b816 --- /dev/null +++ b/thirdparty/dast/test/math.d @@ -0,0 +1,91 @@ +#!/usr/bin/env dub +/+ dub.json: +{ + "name": "math", + "dependencies": { + "dast": {"path": "../"} + } +} +/ + +import std; +import dast.parse, + dast.tokenize; + +enum TokenType { + @TextAllMatcher!isDigit Number, + + @TextCompleteMatcher!"+" Add, + @TextCompleteMatcher!"-" Sub, + @TextCompleteMatcher!"*" Mul, + @TextCompleteMatcher!"/" Div, + + @TextCompleteMatcher!"(" OpenParen, + @TextCompleteMatcher!")" CloseParen, + + End, +} +alias Token = dast.tokenize.Token!(TokenType, string); + +struct Whole { + int result; +} +struct TermList { + int value; +} +struct Term { + int value; +} + +class RuleSet { + public: + @ParseRule: + static Whole ParseWhole(TermList terms, @(TokenType.End) Token) { + return Whole(terms.value); + } + + static TermList ParseTermListFromAddedNextTerm( + TermList lterms, @(TokenType.Add) Token, Term term) { + return TermList(lterms.value + term.value); + } + static TermList ParseTermListFromSubtractedNextTerm( + TermList lterms, @(TokenType.Sub) Token, Term term) { + return TermList(lterms.value - term.value); + } + static TermList ParseTermListFirstItem(Term term) { + return TermList(term.value); + } + + static Term ParseTermFromFirstNumber(@(TokenType.Number) Token num) { + return Term(num.text.to!int); + } + + static Term ParseTermFromTermList( + @(TokenType.OpenParen) Token, TermList terms, @(TokenType.CloseParen) Token) { + return Term(terms.value); + } + static Term ParseMultipledTerm( + Term lterm, @(TokenType.Mul) Token, @(TokenType.Number) Token num) { + return Term(lterm.value * num.text.to!int); + } + static Term ParseDividedTerm( + Term lterm, @(TokenType.Div) Token, @(TokenType.Number) Token num) { + return Term(lterm.value / num.text.to!int); + } +} + +void main(string[] args) { + assert(args.length == 2); + + // PrintItemSet!(TokenType, RuleSet, Whole); + + try { + args[1]. + Tokenize!TokenType. + chain([Token("", TokenType.End)]). + Parse!Whole(cast(RuleSet) null). + result.writeln; + } catch (ParseException!Token e) { + "%s at token '%s' [%s] at (%d, %d)". + writefln(e.msg, e.token.text, e.token.type, e.token.pos.stline, e.token.pos.stchar); + } +} diff --git a/thirdparty/ft4d b/thirdparty/ft4d deleted file mode 160000 index 791af0d..0000000 --- a/thirdparty/ft4d +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 791af0dfe67ad25b41aae170568b89303ed853e9 diff --git a/thirdparty/ft4d/.gitignore b/thirdparty/ft4d/.gitignore new file mode 100644 index 0000000..8f16998 --- /dev/null +++ b/thirdparty/ft4d/.gitignore @@ -0,0 +1,5 @@ +/.bin +/.dub +/dub.selections.json + +*.swp diff --git a/thirdparty/ft4d/LICENSE b/thirdparty/ft4d/LICENSE new file mode 100644 index 0000000..d93466b --- /dev/null +++ b/thirdparty/ft4d/LICENSE @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) 2019 KajiroNagi + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/thirdparty/ft4d/dub.json b/thirdparty/ft4d/dub.json new file mode 100644 index 0000000..200d028 --- /dev/null +++ b/thirdparty/ft4d/dub.json @@ -0,0 +1,14 @@ +{ + "name": "ft4d", + "license": "Boost", + + "targetType": "library", + "targetPath": ".bin", + + "dependencies": { + "bindbc-freetype": "~>0.7.0" + }, + + "versions": ["BindFT_Static"], + "libs": ["freetype"] +} diff --git a/thirdparty/ft4d/src/ft4d/Face.d b/thirdparty/ft4d/src/ft4d/Face.d new file mode 100644 index 0000000..46ed58c --- /dev/null +++ b/thirdparty/ft4d/src/ft4d/Face.d @@ -0,0 +1,100 @@ +// License: MIT +module ft4d.Face; + +import std.conv, + std.exception, + std.string, + std.typecons; + +import ft4d.ft; + +/// RefCounted version of Face. +alias FaceRef = RefCounted!Face; + +/// A wrapper type for freetype Face object. +/// +/// Usually this is wrapped by RefCounted. +/// When it's default, empty() property returns true. +struct Face { + public: + @disable this(this); + + alias native this; + + /// Creates new face from the path. + static FaceRef CreateFromPath(string path, int index = 0) { + FT_Face f; + FT_New_Face(ft.lib, path.toStringz, index.to!FT_Long, &f). + EnforceFT(); + return FaceRef(f); + } + /// Creates new face from the buffer. + static FaceRef CreateFromBuffer(in ubyte[] buf, int index = 0) { + FT_Face f; + FT_New_Memory_Face(ft.lib, buf.ptr, buf.length.to!FT_Long, index.to!FT_Long, &f). + EnforceFT(); + return FaceRef(f); + } + + ~this() { + if (!empty) FT_Done_Face(native_).EnforceFT(); + } + + /// A move operator. RHS will be empty. + ref Face opAssign(ref Face rhs) { + if (&rhs != &this) { + native_ = rhs.native_; + rhs.native_ = null; + } + return this; + } + + /// + @property bool empty() const { + return !native_; + } + /// You should not modify the pointer directly. + @property inout(FT_Face) native() inout in (!empty) { + return native_; + } + + private: + FT_Face native_; +} + +/// A set of parameters for loading glyphs. +struct GlyphLoader { + public: + /// + int pxWidth, pxHeight; + /// + dchar character; + + /// + FT_Int32 flags = FT_LOAD_DEFAULT; + + /// Loads a glyph with the parameters this has. + bool Load(ref FaceRef face) const + in { + assert(!face.empty); + assert(pxWidth+pxHeight > 0 && pxWidth >= 0 && pxHeight >= 0); + } + do { + const i = FT_Get_Char_Index(face, character.to!FT_ULong); + if (i == 0) return false; + + FT_Set_Pixel_Sizes(face, pxWidth.to!FT_UInt, pxHeight.to!FT_UInt).EnforceFT(); + + FT_Load_Glyph(face, i, flags).EnforceFT(); + return true; + } +} + +/// Throws an exception if the face doesn't have a rendered bitmap in its glyph slot. +/// +/// Returns: the rendered bitmap in the glyph slot +ref const(FT_Bitmap) EnforceGlyphBitmap(in ref FaceRef face) in (!face.empty) { + (face.glyph.format == FT_GLYPH_FORMAT_BITMAP). + enforce("the glyph doesn't have bitmap"); + return face.glyph.bitmap; +} diff --git a/thirdparty/ft4d/src/ft4d/ft.d b/thirdparty/ft4d/src/ft4d/ft.d new file mode 100644 index 0000000..11b038d --- /dev/null +++ b/thirdparty/ft4d/src/ft4d/ft.d @@ -0,0 +1,40 @@ +// License: MIT +module ft4d.ft; + +import std.exception; + +public import bindbc.freetype; + +/// This class is just for separating ft functions from global namespace. +abstract class ft { + public: + /// Initializes freetype library. + static void Initialize() { + if (lib_) return; + FT_Init_FreeType(&lib_).EnforceFT(); + } + /// Disposes all resources allocated by freetype library. + static void Dispose() { + if (!lib_) return; + FT_Done_FreeType(lib_).EnforceFT(); + } + /// Checks if freetype library has already been initialized + static @property bool IsInitialized() { + return !!lib_; + } + + /// Returns: a pointer to an initialized library + static @property FT_Library lib() in (IsInitialized) { + return lib_; + } + + private: + static FT_Library lib_; +} + +/// Checks the result value of freetype functions and throws an exception if needed. +void EnforceFT(FT_Error i) { + // TODO: throw with descriptions + + (i == FT_Err_Ok).enforce("unknown error"); +} diff --git a/thirdparty/ft4d/src/ft4d/package.d b/thirdparty/ft4d/src/ft4d/package.d new file mode 100644 index 0000000..566ff03 --- /dev/null +++ b/thirdparty/ft4d/src/ft4d/package.d @@ -0,0 +1,7 @@ +// License: MIT +module ft4d; + +public { + import ft4d.ft, + ft4d.Face; +} diff --git a/thirdparty/ft4d/test.d b/thirdparty/ft4d/test.d new file mode 100644 index 0000000..a819740 --- /dev/null +++ b/thirdparty/ft4d/test.d @@ -0,0 +1,32 @@ +#!/usr/bin/env dub + +/+ dub.json: +{ + "name": "test", + + "dependencies": { + "ft4d": {"path": "."} + } +} ++/ + +import std; +import ft4d; + +void main() { + ft.Initialize(); + assert(ft.IsInitialized); + scope(exit) ft.Dispose(); + + auto face = Face.CreateFromPath("/usr/share/fonts/TTF/Ricty-Regular.ttf"); + + GlyphLoader loader; + loader.pxWidth = 16; + loader.pxHeight = 0; + loader.flags = FT_LOAD_DEFAULT | FT_LOAD_RENDER; + + loader.character = 'a'; + loader.Load(face).enforce(); + + face.EnforceGlyphBitmap().writeln; +} diff --git a/thirdparty/gl4d b/thirdparty/gl4d deleted file mode 160000 index 33883a6..0000000 --- a/thirdparty/gl4d +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 33883a68a9179cdfca72da9d0f60a88e1f385715 diff --git a/thirdparty/gl4d/.gitignore b/thirdparty/gl4d/.gitignore new file mode 100644 index 0000000..70e0143 --- /dev/null +++ b/thirdparty/gl4d/.gitignore @@ -0,0 +1,8 @@ +/.bin +/.dub +/dub.selections.json + +/docs +/docs.json + +*.swp diff --git a/thirdparty/gl4d/dub.json b/thirdparty/gl4d/dub.json new file mode 100644 index 0000000..905dba4 --- /dev/null +++ b/thirdparty/gl4d/dub.json @@ -0,0 +1,13 @@ +{ + "name": "gl4d", + "license": "MIT", + + "targetType": "library", + "targetPath": ".bin", + + "dependencies": { + "bindbc-opengl": "~>0.9.0", + "gl3n": "~>1.3.1" + }, + "versions": ["GL_33"] +} diff --git a/thirdparty/gl4d/src/gl4d/Buffer.d b/thirdparty/gl4d/src/gl4d/Buffer.d new file mode 100644 index 0000000..4014084 --- /dev/null +++ b/thirdparty/gl4d/src/gl4d/Buffer.d @@ -0,0 +1,164 @@ +/// License: MIT +module gl4d.Buffer; + +import std.conv, + std.typecons; + +import gl4d.gl, + gl4d.math, + gl4d.GLObject; + +/// +alias ArrayBuffer = Buffer!GL_ARRAY_BUFFER; +/// RefCounted version of ArrayBuffer. +alias ArrayBufferRef = BufferRef!GL_ARRAY_BUFFER; +/// +alias ArrayBufferAllocator = BufferAllocator!GL_ARRAY_BUFFER; +/// +alias ArrayBufferOverwriter = BufferOverwriter!GL_ARRAY_BUFFER; + +/// +alias ElementArrayBuffer = Buffer!GL_ELEMENT_ARRAY_BUFFER; +/// RefCounted version of ElementArrayBuffer. +alias ElementArrayBufferRef = BufferRef!GL_ELEMENT_ARRAY_BUFFER; +/// +alias ElementArrayBufferAllocator = BufferAllocator!GL_ELEMENT_ARRAY_BUFFER; +/// +alias ElementArrayBufferOverwriter = BufferOverwriter!GL_ELEMENT_ARRAY_BUFFER; + +/// +alias UniformBuffer = Buffer!GL_UNIFORM_BUFFER; +/// RefCounted version of UniformBuffer. +alias UniformBufferRef = BufferRef!GL_UNIFORM_BUFFER; +/// +alias UniformBufferAllocator = BufferAllocator!GL_UNIFORM_BUFFER; +/// +alias UniformBufferOverwriter = BufferOverwriter!GL_UNIFORM_BUFFER; + +/// RefCounted version of Buffer. +template BufferRef(GLenum target) { + alias BufferRef = RefCounted!(Buffer!target); +} + +/// A wrapper type for OpenGL buffer. +/// +/// Usually this is wrapped by RefCounted. +/// When it's in default, empty() property returns true and id() property is invalid. +struct Buffer(GLenum target_) { + mixin GLObject!( + (x, y) => gl.GenBuffers(x, y), + (x) => gl.BindBuffer(target_, x), + (x) => gl.DeleteTextures(1, x) + ); + + public: + /// + enum target = target_; + + /// Binds this buffer to be written by transform feedbacks. + static if (target_ == GL_ARRAY_BUFFER) + void BindForTransformFeedback(int index) { + assert(!empty); + gl.BindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, index.to!GLuint, id); + } + + /// Binds this buffer to the uniform block. + static if (target_ == GL_UNIFORM_BUFFER) + void BindForUniformBlock(int index) { + assert(!empty); + gl.BindBufferBase(GL_UNIFORM_BUFFER, index.to!GLuint, id); + } +} + +/// An allocator for buffers. +struct BufferAllocator(GLenum target) { + public: + /// + size_t size; + /// + const(void)* data; + /// + GLenum usage = GL_STATIC_DRAW; + + /// Allocates the buffer with parameters this has. + /// + /// Binds the buffer automatically. + void Allocate(ref BufferRef!target buffer) + in { + assert(!buffer.empty); + assert(size > 0); + } + do { + buffer.Bind(); + gl.BufferData(target, size.to!GLsizeiptr, data, usage); + } +} + +/// An overwriter for buffers. +struct BufferOverwriter(GLenum target) { + public: + /// + size_t offset; + /// + size_t size; + /// + const(void)* data; + + /// Overwrites the buffer with parameters this has. + /// + /// The buffer will be bound automatically. + void Overwrite(ref BufferRef!target buffer) + in { + assert(!buffer.empty); + + assert(offset >= 0); + assert(size > 0); + } + do { + buffer.Bind(); + gl.BufferSubData(target, offset.to!GLintptr, size.to!GLsizeiptr, data); + } +} + +/// Takes the buffer's data pointer. +/// +/// T must be a BufferRef type. +/// +/// The buffer will be bound automatically. +/// +/// Escaping its scope, the pointer will be disabled automatically. +/// Returns: a voldemorte type which can behave as same as PtrT* +auto MapToRead(PtrT = void, T)(ref T buf) { + return buf.Map!(PtrT, T.target, GL_READ_ONLY); +} +/// ditto +auto MapToWrite(PtrT = void, T)(ref T buf) { + return buf.Map!(PtrT, T.target, GL_WRITE_ONLY); +} +/// ditto +auto MapToReadWrite(PtrT = void, T)(ref T buf) { + return buf.Map!(PtrT, T.target, GL_READ_WRITE); +} +private auto Map(PtrT, GLenum target, GLenum usage)(ref BufferRef!target buf) { + assert(!buf.empty); + + buf.Bind(); + auto ptr = gl.MapBuffer(target, usage); + + struct Mapper { + public: + @disable this(this); + + ~this() { + gl.UnmapBuffer(target); + } + + static if (usage == GL_READ_ONLY) { + @property const(PtrT*) entity() const return { return cast(PtrT*) ptr; } + } else { + @property inout(PtrT*) entity() inout return { return cast(PtrT*) ptr; } + } + alias entity this; + } + return Mapper(); +} diff --git a/thirdparty/gl4d/src/gl4d/Framebuffer.d b/thirdparty/gl4d/src/gl4d/Framebuffer.d new file mode 100644 index 0000000..143534a --- /dev/null +++ b/thirdparty/gl4d/src/gl4d/Framebuffer.d @@ -0,0 +1,95 @@ +/// License: MIT +module gl4d.Framebuffer; + +import std.conv, + std.exception, + std.typecons, + std.variant; + +import gl4d.gl, + gl4d.math, + gl4d.GLObject, + gl4d.Renderbuffer, + gl4d.Texture; + +/// RefCounted version of OpenGL framebuffer. +alias FramebufferRef = RefCounted!Framebuffer; + +/// A variant type of types which can be framebuffers' attachments. +alias FramebufferAttachment = Algebraic!( + Texture2DRef, + TextureRectRef, + RenderbufferRef + ); + +/// A wrapper type for OpenGL framebuffer. +/// +/// Usually this is wrapped by RefCounted. +/// When it's in default, empty() property returns true and id() property is invalid. +struct Framebuffer { + mixin GLObject!( + (x, y) => gl.GenFramebuffers(x, y), + (x) => gl.BindFramebuffer(GL_FRAMEBUFFER, x), + (x) => gl.DeleteFramebuffers(1, x) + ); + + public: + ~this() { + // Forces unrefering all buffers. + foreach (key; attachments_.keys) { + attachments_[key] = FramebufferAttachment.init; + } + } + + /// Binds this framebuffer to be read. + void BindToRead() { + gl.BindFramebuffer(GL_READ_FRAMEBUFFER, id); + } + /// Binds this framebuffer to be written. + void BindToWrite() { + gl.BindFramebuffer(GL_DRAW_FRAMEBUFFER, id); + } + + /// Validates this framebuffer. + /// + /// This framebuffer must be bound to be written. + void Validate() { + const status = gl.CheckFramebufferStatus(GL_FRAMEBUFFER); + (status == GL_FRAMEBUFFER_COMPLETE). + enforce("The framebuffer validation failed."); + } + + private: + FramebufferAttachment[GLenum] attachments_; +} + +/// Attaches the buffer as the attachment to the framebuffer. +/// +/// The framebuffer must be bound to be written. +@property void attachment(GLenum type)( + ref FramebufferRef fb, ref RenderbufferRef buf) { + assert(!fb.empty); + assert(!buf.empty); + + fb.attachments_[type] = buf; + gl.FramebufferRenderbuffer(GL_FRAMEBUFFER, type, GL_RENDERBUFFER, buf.id); +} +/// ditto +@property void attachment(GLenum type, int miplvl = 0, GLenum target)( + ref FramebufferRef fb, ref TextureRef!target buf) + if (target.IsSupported2DTextureTarget) { + assert(!fb.empty); + assert(!buf.empty); + + fb.attachments_[type] = buf; + gl.FramebufferTexture2D(GL_FRAMEBUFFER, + type, target, buf.id, miplvl.to!GLint); +} + +/// Changes color attachments' order. +/// +/// The framebuffer must be bound to be written. +@property void attachmentOrder(ref FramebufferRef fb, GLenum[] attachments) { + assert(!fb.empty); + gl.DrawBuffers(attachments.length.to!GLsizei, attachments.ptr); +} diff --git a/thirdparty/gl4d/src/gl4d/GLObject.d b/thirdparty/gl4d/src/gl4d/GLObject.d new file mode 100644 index 0000000..204fc3e --- /dev/null +++ b/thirdparty/gl4d/src/gl4d/GLObject.d @@ -0,0 +1,56 @@ +/// License: MIT +module gl4d.GLObject; + +import gl4d.gl; + +/// A template for OpenGL objects' wrapper types. +mixin template GLObject(alias generator, alias binder, alias deleter) { + import std.algorithm, + std.array, + std.typecons; + + public: + @disable this(this); + + /// Creates a single object. + static RefCounted!This Create() { + GLuint id; + generator(1, &id); + return RefCounted!This(id); + } + /// Creates multiple objects. + static RefCounted!This[] Create(int count) { + assert(count > 0); + auto id = new GLuint[count]; + generator(count, id.ptr); + return id.map!(i => RefCounted!This(i)).array; + } + + ~this() { + if (!empty) deleter(&id); + } + + static if (is(typeof(() => binder(0)))) { + /// Binds this object. + void Bind() { + assert(!empty); + binder(id); + } + /// Unbinds this object + void Unbind() { + assert(!empty); + binder(0); + } + } + + /// + @property bool empty() const { + return id == 0; + } + + /// + const GLuint id; + + private: + alias This = typeof(this); +} diff --git a/thirdparty/gl4d/src/gl4d/Program.d b/thirdparty/gl4d/src/gl4d/Program.d new file mode 100644 index 0000000..3d937c7 --- /dev/null +++ b/thirdparty/gl4d/src/gl4d/Program.d @@ -0,0 +1,204 @@ +/// License: MIT +module gl4d.Program; + +import std.algorithm, + std.array, + std.conv, + std.exception, + std.string, + std.typecons; + +import gl4d.gl, + gl4d.math, + gl4d.Shader; + +/// Whether geometry shaders are available. +enum IsGeometryShaderAvailable = (glSupport >= GLSupport.gl40); + +/// RefCounted version of OpenGL program. +alias ProgramRef = RefCounted!Program; + +/// A wrapper type for OpenGL program. +/// +/// Usually this is wrapped by RefCounted. +/// When it's in default, empty() property returns true and id() property is invalid. +struct Program { + public: + @disable this(this); + + /// + this(GLuint id) { + this.id = id; + + assert(!empty); + (Get!GL_LINK_STATUS == GL_TRUE).enforce(log); + } + ~this() { + if (!empty) gl.DeleteProgram(id); + } + + /// Makes this program current. + void Use() { + assert(!empty); + gl.UseProgram(id); + } + /// Makes this program incurrent. + void Unuse() { + assert(!empty); + gl.UseProgram(0); + } + + /// Validates this program. + /// + /// If it's failed, throws an exception with log. + void Validate() const { + gl.ValidateProgram(id); + (Get!GL_VALIDATE_STATUS == GL_TRUE).enforce(log); + } + + /// This may takes too long time. + @property string log() const { + assert(!empty); + + const len = Get!GL_INFO_LOG_LENGTH; + + auto msg = new char[len]; + gl.GetProgramInfoLog(id, len, null, msg.ptr); + return msg.to!string; + } + + /// + @property bool empty() const { + return id == 0; + } + + /// + const GLuint id; + + private: + // Be carefully, this may take too long time. + GLint Get(GLenum param)() const { + GLint temp = void; + gl.GetProgramiv(id, param, &temp); + return temp; + } + + VertexShaderRef vertex_; + + static if (IsGeometryShaderAvailable) + GeometryShaderRef geometry_; + + FragmentShaderRef fragment_; +} + +/// A linker for OpenGL program. +struct ProgramLinker { + public: + /// + VertexShaderRef vertex; + + static if (IsGeometryShaderAvailable) { + /// + GeometryShaderRef geometry; + /// + int geometryOutputVertices = 1024; + /// + GLenum geometryInputType = GL_POINTS; + /// + GLenum geometryOutputType = GL_POINTS; + } + + /// + FragmentShaderRef fragment; + + /// + string[] feedbackVaryings; + /// + bool feedbackInterleaved; + + /// Creates new program with parameters this has. + ProgramRef Link() + in { + assert(!vertex.empty); + assert(!fragment.empty); + } + do { + const id = gl.CreateProgram(); + + gl.AttachShader(id, vertex.id); + scope(exit) vertex = vertex.init; + + gl.AttachShader(id, fragment.id); + scope(exit) fragment = fragment.init; + + static if (IsGeometryShaderAvailable) if (!geometry.empty) { + gl.AttachShader(id, geometry.id); + scope(exit) geometry = geometry.init; + + static if (glSupport >= GLSupport.gl40) { + gl.ProgramParameteri(id, GL_GEOMETRY_VERTICES_OUT, geometryOutputVertices); + + gl.ProgramParameteri(id, GL_GEOMETRY_INPUT_TYPE, geometryInputType); + gl.ProgramParameteri(id, GL_GEOMETRY_OUTPUT_TYPE, geometryOutputType); + } + } + + if (feedbackVaryings.length > 0) { + const varys = feedbackVaryings.map!toStringz.array; + gl.TransformFeedbackVaryings(id, + feedbackVaryings.length.to!GLsizei, cast(char**) varys.ptr, + feedbackInterleaved? GL_INTERLEAVED_ATTRIBS: GL_SEPARATE_ATTRIBS); + } + + gl.LinkProgram(id); + return ProgramRef(id); + } +} + +/// Resets uniform values with the data. (The program must be current.) +/// +/// The program must be current. +@property void uniform(int loc, T)(ref ProgramRef program, T data) { + assert(!program.empty); + + static if (is(T == int)) { + gl.Uniform1i(loc, data); + } else static if (is(T == vec2i)) { + gl.Uniform2i(loc, data.x, data.y); + } else static if (is(T == vec3i)) { + gl.Uniform3i(loc, data.x, data.y, data.z); + } else static if (is(T == vec4i)) { + gl.Uniform4i(loc, data.x, data.y, data.z, data.w); + } else static if (is(T == float)) { + gl.Uniform1f(loc, data); + } else static if (is(T == vec2)) { + gl.Uniform2f(loc, data.x, data.y); + } else static if (is(T == vec3)) { + gl.Uniform3f(loc, data.x, data.y, data.z); + } else static if (is(T == vec4)) { + gl.Uniform4f(loc, data.x, data.y, data.z, data.w); + } else static if (is(T == mat2)) { + gl.UniformMatrix2fv(loc, 1, true, &data[0][0]); + } else static if (is(T == mat3)) { + gl.UniformMatrix3fv(loc, 1, true, &data[0][0]); + } else static if (is(T == mat4)) { + gl.UniformMatrix4fv(loc, 1, true, &data[0][0]); + } else { + static assert(false); + } +} +// Tests for the above template. +static assert(is(typeof((ref ProgramRef program) => program.uniform!0 = 0))); +static assert(is(typeof((ref ProgramRef program) => program.uniform!1 = vec4()))); +static assert(is(typeof((ref ProgramRef program) => program.uniform!2 = mat4()))); + +/// Numbers uniform blocks of the names. +void NumberUniformBlocks(string[int] names)(ref ProgramRef program) { + assert(!program.empty); + + GLuint ubi = void; + static foreach (i, name; names) { + ubi = gl.GetUniformBlockIndex(program.id, name.toStringz); + gl.UniformBlockBinding(program.id, ubi, i.to!GLuint); + } +} diff --git a/thirdparty/gl4d/src/gl4d/Renderbuffer.d b/thirdparty/gl4d/src/gl4d/Renderbuffer.d new file mode 100644 index 0000000..a854a32 --- /dev/null +++ b/thirdparty/gl4d/src/gl4d/Renderbuffer.d @@ -0,0 +1,46 @@ +/// License: MIT +module gl4d.Renderbuffer; + +import std.conv, + std.typecons; + +import gl4d.gl, + gl4d.math, + gl4d.GLObject; + +/// RefCounted version of OpenGL renderbuffer. +alias RenderbufferRef = RefCounted!Renderbuffer; + +/// A wrapper type for OpenGL renderbuffer. +/// +/// Usually this is wrapped by RefCounted. +/// When it's in default, empty() property returns true and id() property is invalid. +struct Renderbuffer { + mixin GLObject!( + (x, y) => gl.GenRenderbuffers(x, y), + (x) => gl.BindRenderbuffer(GL_RENDERBUFFER, x), + (x) => gl.DeleteRenderbuffers(1, x) + ); +} + +/// An allocator for OpenGL renderbuffers. +struct RenderbufferAllocator { + public: + /// + GLenum format; + /// + vec2i size; + + /// Allocates the renderbuffer's storage with parameters this has. + /// + /// The renderbuffer will be bound automatically. + void Allocate(ref RenderbufferRef rb) + in { + assert(!rb.empty); + } + do { + rb.Bind(); + gl.RenderbufferStorage(GL_RENDERBUFFER, + format, size.x.to!GLsizei, size.y.to!GLsizei); + } +} diff --git a/thirdparty/gl4d/src/gl4d/Sampler.d b/thirdparty/gl4d/src/gl4d/Sampler.d new file mode 100644 index 0000000..2e888a5 --- /dev/null +++ b/thirdparty/gl4d/src/gl4d/Sampler.d @@ -0,0 +1,57 @@ +/// License: MIT +module gl4d.Sampler; + +import std.conv, + std.typecons; + +import gl4d.gl, + gl4d.GLObject; + +/// RefCounted version of Sampler. +alias SamplerRef = RefCounted!Sampler; + +/// A wrapper type for OpenGL sampler. +struct Sampler { + mixin GLObject!( + (x, y) => gl.GenSamplers(x, y), + void, + (x) => gl.DeleteSamplers(1, x) + ); + + public: + /// Binds to the texture unit index. + void Bind(int index) { + assert(!empty); + gl.BindSampler(index.to!GLuint, id); + } +} + +/// A configurer for OpenGL sampler. +struct SamplerConfigurer { + public: + /// + GLenum wrapS = GL_CLAMP_TO_EDGE; + /// + GLenum wrapT = GL_CLAMP_TO_EDGE; + /// + GLenum wrapR = GL_CLAMP_TO_EDGE; + + /// + GLenum filterMin = GL_NEAREST; + /// + GLenum filterMag = GL_NEAREST; + + /// Configures the sampler with parameters this has. + void Configure(ref SamplerRef sampler) + in { + assert(!sampler.empty); + } + do { + gl.SamplerParameteri(sampler.id, GL_TEXTURE_WRAP_S, wrapS); + gl.SamplerParameteri(sampler.id, GL_TEXTURE_WRAP_T, wrapT); + gl.SamplerParameteri(sampler.id, GL_TEXTURE_WRAP_R, wrapR); + + gl.SamplerParameteri(sampler.id, GL_TEXTURE_MIN_FILTER, filterMin); + gl.SamplerParameteri(sampler.id, GL_TEXTURE_MAG_FILTER, filterMag); + } +} diff --git a/thirdparty/gl4d/src/gl4d/Shader.d b/thirdparty/gl4d/src/gl4d/Shader.d new file mode 100644 index 0000000..a24ac4e --- /dev/null +++ b/thirdparty/gl4d/src/gl4d/Shader.d @@ -0,0 +1,99 @@ +/// License: MIT +module gl4d.Shader; + +import std.conv, + std.exception, + std.string, + std.typecons; + +import gl4d.gl; + +/// A wrapper type for OpenGL vertex shader. +/// +/// Usually this is wrapped by RefCounted. +/// When it's in default, empty() property returns true and id() property is invalid. +struct VertexShader { + mixin Shader!GL_VERTEX_SHADER; +} +/// RefCounted version of VertexShader. +alias VertexShaderRef = RefCounted!VertexShader; + +/// A wrapper type for OpenGL geometry shader. +/// +/// Usually this is wrapped by RefCounted. +/// When it's in default, empty() property returns true and id() property is invalid. +struct GeometryShader { + mixin Shader!GL_GEOMETRY_SHADER; +} +/// RefCounted version of GeometryShader. +alias GeometryShaderRef = RefCounted!GeometryShader; + +/// A wrapper type for OpenGL fragment shader. +/// +/// Usually this is wrapped by RefCounted. +/// When it's in default, empty() property returns true and id() property is invalid. +struct FragmentShader { + mixin Shader!GL_FRAGMENT_SHADER; +} +/// RefCounted version of FragmentShader. +alias FragmentShaderRef = RefCounted!FragmentShader; + +/// A body of shader structures which cannot be refered from other modules. +private mixin template Shader(GLenum type) { + public: + @disable this(this); + + /// Creates new shader from the source with the type. + static RefCounted!This Compile(string src) { + const id = gl.CreateShader(type); + + const srcptr = src.toStringz; + gl.ShaderSource(id, 1, &srcptr, null); + gl.CompileShader(id); + + return RefCounted!This(id); + } + + /// + this(GLuint id) { + this.id = id; + + assert(!empty); + (Get!GL_COMPILE_STATUS == GL_TRUE).enforce(log); + } + ~this() { + if (!empty) gl.DeleteShader(id); + } + + /// This may takes too long time. + @property string log() const { + assert(!empty); + + const len = Get!GL_INFO_LOG_LENGTH; + if (len == 0) return null; + + auto msg = new char[len]; + gl.GetShaderInfoLog(id, len, null, msg.ptr); + return msg.to!string; + } + + /// + @property bool empty() const { + return id == 0; + } + + /// + const GLuint id; + + private: + alias This = typeof(this); + + // Be carefully, this may take too long time. + GLint Get(GLenum param)() const { + assert(!empty); + + GLint temp = void; + gl.GetShaderiv(id, param, &temp); + return temp; + } +} diff --git a/thirdparty/gl4d/src/gl4d/Texture.d b/thirdparty/gl4d/src/gl4d/Texture.d new file mode 100644 index 0000000..0d0d363 --- /dev/null +++ b/thirdparty/gl4d/src/gl4d/Texture.d @@ -0,0 +1,146 @@ +/// License: MIT +module gl4d.Texture; + +import std.conv, + std.typecons; + +import gl4d.gl, + gl4d.math, + gl4d.GLObject; + +/// +alias Texture2D = Texture!GL_TEXTURE_2D; +/// RefCounted version of Texture2D. +alias Texture2DRef = TextureRef!GL_TEXTURE_2D; +/// +alias Texture2DAllocator = TextureAllocator!GL_TEXTURE_2D; +/// +alias Texture2DOverwriter = TextureOverwriter!GL_TEXTURE_2D; + +/// +alias TextureRect = Texture!GL_TEXTURE_RECTANGLE; +/// RefCounted version of TextureRect. +alias TextureRectRef = TextureRef!GL_TEXTURE_RECTANGLE; +/// +alias TextureRectAllocator = TextureAllocator!GL_TEXTURE_RECTANGLE; +/// +alias TextureRectOverwriter = TextureOverwriter!GL_TEXTURE_RECTANGLE; + +/// RefCounted version of Texture. +template TextureRef(GLenum target) { + alias TextureRef = RefCounted!(Texture!target); +} + +/// A wrapper type for OpenGL texture. +/// +/// Usually this is wrapped by RefCounted. +/// When it's in default, empty() property returns true and id() property is invalid. +struct Texture(GLenum target_) { + mixin GLObject!( + (x, y) => gl.GenTextures(x, y), + (x) => gl.BindTexture(target_, x), + (x) => gl.DeleteTextures(1, x) + ); + public: + /// + enum target = target_; + + /// Binds this texture to the texture unit. + /// + /// This texture will be bound. + void BindToUnit(GLenum unit) { + assert(!empty); + gl.ActiveTexture(unit); + Bind(); + } + + /// Generates mipmaps of this texture. + /// + /// This texture must be bound. + void GenerateMipmap() { + assert(!empty); + gl.GenerateMipmap(target_); + } +} + +/// An allocator for 2D textures. +struct TextureAllocator(GLenum target) + if (target.IsSupported2DTextureTarget()) { + public: + /// + int level; + /// + GLint internalFormat; + /// + vec2i size; + /// + GLint border; + /// + GLenum format; + /// + GLenum type; + /// + const(void)* data; + + /// Allocates the texture with parameters this has. + /// + /// The texture will be bound. + void Allocate(ref TextureRef!target texture) + in { + assert(!texture.empty); + + assert(level >= 0); + assert(size.x > 0 && size.y > 0); + assert(border == 0); + } + do { + texture.Bind(); + gl.TexImage2D(target, level.to!GLint, internalFormat, + size.x.to!GLsizei, size.y.to!GLsizei, border, format, type, data); + } +} + +/// An overwriter for 2D textures. +struct TextureOverwriter(GLenum target) + if (target.IsSupported2DTextureTarget()) { + public: + /// + int level; + /// + vec2i offset; + /// + vec2i size; + /// + GLenum format; + /// + GLenum type; + /// + const(void)* data; + + /// Overwrites the texture with parameters this has. + /// + /// The texture will be bound. + void Overwrite(ref TextureRef!target texture) + in { + assert(!texture.empty); + + assert(level >= 0); + assert(offset.x >= 0 && offset.y >= 0); + assert(size.x > 0 && size.y > 0); + + } + do { + texture.Bind(); + gl.TexImage2D(target, level.to!GLint, + offset.x.to!GLint, offset.y.to!GLint, + size.x.to!GLsizei, size.y.to!GLsizei, + format, type, data); + } +} + +/// Returns: whether the target is supported 2d texture +@property bool IsSupported2DTextureTarget(GLenum target) { + return + target == GL_TEXTURE_2D || + target == GL_TEXTURE_RECTANGLE; +} diff --git a/thirdparty/gl4d/src/gl4d/VertexArray.d b/thirdparty/gl4d/src/gl4d/VertexArray.d new file mode 100644 index 0000000..4ee5a34 --- /dev/null +++ b/thirdparty/gl4d/src/gl4d/VertexArray.d @@ -0,0 +1,84 @@ +/// License: MIT +module gl4d.VertexArray; + +import std.conv, + std.typecons; + +import gl4d.gl, + gl4d.math, + gl4d.Buffer, + gl4d.GLObject; + +/// RefCounted version of VertexArray. +alias VertexArrayRef = RefCounted!VertexArray; + +/// A wrapper type for OpenGL vertex array. +/// +/// Usually this is wrapped by RefCounted. +/// When it's in default, empty() property returns true and id() property is invalid. +struct VertexArray { + mixin GLObject!( + (x, y) => gl.GenVertexArrays(x, y), + (x) => gl.BindVertexArray(x), + (x) => gl.DeleteVertexArrays(1, x) + ); + + public: + ~this() { + // Forces unrefering all buffers. + foreach (key; attachments_.keys) { + attachments_[key] = ArrayBufferRef.init; + } + } + + private: + ArrayBufferRef[int] attachments_; +} + +/// An attacher between array buffers and vertex arrays. +struct VertexArrayAttacher { + public: + /// + int index; + /// + GLenum type; + /// + int dimension; + /// + bool normalized; + /// + int stride; + /// + int offset; + /// + int divisor; + + /// Attaches the buffer to the vertex array with parameters this has. + /// (The vertex array must be bound.) + void Attach(ref VertexArrayRef va, ref ArrayBufferRef buf) + in { + assert(!va.empty); + assert(!buf.empty); + + assert(index >= 0); + assert(0 < dimension && dimension <= 4); + assert(stride >= 0); + assert(offset >= 0); + assert(divisor >= 0); + } + do { + va.attachments_[index] = buf; + + buf.Bind(); + + const i = index.to!GLuint; + gl.EnableVertexAttribArray(i); + + gl.VertexAttribPointer( + i, dimension.to!GLint, + type, normalized, stride.to!GLsizei, + cast(GLvoid*) offset.to!ptrdiff_t); + + gl.VertexAttribDivisor(i, divisor.to!GLuint); + } +} diff --git a/thirdparty/gl4d/src/gl4d/gl.d b/thirdparty/gl4d/src/gl4d/gl.d new file mode 100644 index 0000000..5c13719 --- /dev/null +++ b/thirdparty/gl4d/src/gl4d/gl.d @@ -0,0 +1,65 @@ +/// License: MIT +module gl4d.gl; + +import std.conv, + std.exception, + std.format; + +public import bindbc.opengl; + +/// This class is just for separating gl functions from the global namespace. +abstract class gl { + public: + /// This library requires this version. + enum RequiredVersion = GLSupport.gl33; + + /// Applies current OpenGL context for gl4d features. + /// + /// If the context version is not equal to RequiredVersion, + /// an exception will be thrown. + static void ApplyContext() { + const loaded = loadOpenGL(); + (RequiredVersion == loaded). + enforce("Loading OpenGL failed with GLSupport %s. (expected %s)". + format(loaded, RequiredVersion)); + } + + /// Calls OpenGL function with error handling. + static auto opDispatch(string func, + string file = __FILE__, size_t line = __LINE__, Args...)(Args args) { + scope (exit) { + auto err = glGetError(); + (err == GL_NO_ERROR). + enforce(GetErrorString(err), file, line); + } + return mixin("gl"~func~"(args)"); + } + + private: + static string GetErrorString(GLenum err) { + switch (err) { + case GL_NO_ERROR: + return "GL_NO_ERROR "~ + "(No error has been recorded."~ + " The value of this symbolic constant is guaranteed to be 0.)"; + case GL_INVALID_ENUM: + return "GL_INVALID_ENUM "~ + "(An unacceptable value is specified for an enumerated argument."~ + " The offending command is ignored and has no other side effect than to set the error flag.)"; + case GL_INVALID_VALUE: + return "GL_INVALID_VALUE "~ + "(A numeric argument is out of range. "~ + "The offending command is ignored and has no other side effect than to set the error flag.)"; + case GL_INVALID_OPERATION: + return "GL_INVALID_OPERATION "~ + "(The specified operation is not allowed in the current state."~ + " The offending command is ignored and has no other side effect than to set the error flag.)"; + case GL_OUT_OF_MEMORY: + return "GL_OUT_OF_MEMORY "~ + "(There is not enough memory left to execute the command."~ + " The state of the GL is undefined, except for the state of the error flags, after this error is recorded."; + default: + return err.to!string; + } + } +} diff --git a/thirdparty/gl4d/src/gl4d/math.d b/thirdparty/gl4d/src/gl4d/math.d new file mode 100644 index 0000000..4bcdf19 --- /dev/null +++ b/thirdparty/gl4d/src/gl4d/math.d @@ -0,0 +1,6 @@ +/// License: MIT +module gl4d.math; + +public { + import gl3n.linalg; +} diff --git a/thirdparty/gl4d/src/gl4d/package.d b/thirdparty/gl4d/src/gl4d/package.d new file mode 100644 index 0000000..d153504 --- /dev/null +++ b/thirdparty/gl4d/src/gl4d/package.d @@ -0,0 +1,17 @@ +/// License: MIT +module gl4d; + +public { + import gl4d.gl, + gl4d.math, + gl4d.util; + + import gl4d.Buffer, + gl4d.Framebuffer, + gl4d.Program, + gl4d.Renderbuffer, + gl4d.Sampler, + gl4d.Shader, + gl4d.Texture, + gl4d.VertexArray; +} diff --git a/thirdparty/gl4d/src/gl4d/util/ModelMatrixFactory.d b/thirdparty/gl4d/src/gl4d/util/ModelMatrixFactory.d new file mode 100644 index 0000000..5c69f42 --- /dev/null +++ b/thirdparty/gl4d/src/gl4d/util/ModelMatrixFactory.d @@ -0,0 +1,50 @@ +/// License: MIT +module gl4d.util.ModelMatrixFactory; + +import gl3n.linalg; + +/// +struct ModelMatrixFactory(size_t dim) if (dim == 3 || dim == 4) { + public: + /// + alias mat = Matrix!(float, dim, dim); + + /// + mat Create() const { + auto m = mat.identity; + static if (dim == 3) { + m.scale(scale.x, scale.y, 1); + m.rotatex(rotation.x); + m.rotatey(rotation.y); + m.rotatez(rotation.z); + m.translate(translation.x, translation.y, 1); + + } else static if (dim == 4) { + m.scale(scale.x, scale.y, scale.z); + m.rotatex(rotation.x); + m.rotatey(rotation.y); + m.rotatez(rotation.z); + m.translate(translation.x, translation.y, translation.z); + } + return m; + } + + static if (dim == 3) { + /// + vec2 scale = vec2(1, 1); + /// + vec3 rotation = vec3(0, 0, 0); + /// + vec2 translation = vec2(0, 0); + + } else static if (dim == 4) { + /// + vec3 scale = vec3(1, 1, 1); + /// + vec3 rotation = vec3(0, 0, 0); + /// + vec3 translation = vec3(0, 0, 0); + } +} +static assert(__traits(compiles, ModelMatrixFactory!3)); +static assert(__traits(compiles, ModelMatrixFactory!4)); diff --git a/thirdparty/gl4d/src/gl4d/util/ProjectionMatrixFactory.d b/thirdparty/gl4d/src/gl4d/util/ProjectionMatrixFactory.d new file mode 100644 index 0000000..33e96dc --- /dev/null +++ b/thirdparty/gl4d/src/gl4d/util/ProjectionMatrixFactory.d @@ -0,0 +1,22 @@ +/// License: MIT +module gl4d.util.ProjectionMatrixFactory; + +import gl3n.linalg; + +/// +struct ProjectionMatrixFactory { + public: + /// + mat4 Create() const { + return mat4.perspective(aspect, 1, fov, near, far); + } + + /// + float aspect = 1; + /// + float fov = 60; + /// + float far = 100; + /// + float near = 0.1; +} diff --git a/thirdparty/gl4d/src/gl4d/util/ViewMatrixFactory.d b/thirdparty/gl4d/src/gl4d/util/ViewMatrixFactory.d new file mode 100644 index 0000000..727dd73 --- /dev/null +++ b/thirdparty/gl4d/src/gl4d/util/ViewMatrixFactory.d @@ -0,0 +1,20 @@ +/// License: MIT +module gl4d.util.ViewMatrixFactory; + +import gl3n.linalg; + +/// +struct ViewMatrixFactory { + public: + /// + mat4 Create() const { + return mat4.look_at(pos, target, up); + } + + /// + vec3 pos = vec3(0, -1, 0); + /// + vec3 target = vec3(0, 0, 0); + /// + vec3 up = vec3(0, 1, 0); +} diff --git a/thirdparty/gl4d/src/gl4d/util/package.d b/thirdparty/gl4d/src/gl4d/util/package.d new file mode 100644 index 0000000..7ae6a34 --- /dev/null +++ b/thirdparty/gl4d/src/gl4d/util/package.d @@ -0,0 +1,8 @@ +/// License: MIT +module gl4d.util; + +public { + import gl4d.util.ModelMatrixFactory, + gl4d.util.ProjectionMatrixFactory, + gl4d.util.ViewMatrixFactory; +} diff --git a/thirdparty/gl4d/test.d b/thirdparty/gl4d/test.d new file mode 100644 index 0000000..17ee233 --- /dev/null +++ b/thirdparty/gl4d/test.d @@ -0,0 +1,201 @@ +#!/usr/bin/env dub + +/+ dub.json: +{ + "name": "test", + + "dependencies": { + "bindbc-sdl": "~>0.11.0", + "gl4d": {"path": "."} + }, + "versions": ["SDL_209"] +} ++/ + +import std; +import bindbc.sdl; +import gl4d; + +enum ShaderHeader = "#version 330 core +#extension GL_ARB_explicit_uniform_location : enable"; + +enum VertexShaderSource = ShaderHeader~q{ + layout(location=0) in vec4 pos; + + layout(std140) uniform uniformblock { + float value; + } ub; + + out vec2 uv; + out float feedback_value; + + void main() { + uv = pos.xy; + gl_Position = pos * ub.value; + feedback_value = length(pos.xy); + } +}; +enum FragmentShaderSource = ShaderHeader~q{ + in vec2 uv; + + layout(std140) uniform uniformblock { + float value; + } ub; + + layout(location = 1) uniform sampler2D tex; + + out vec4 color; + + void main() { + color = texture(tex, uv); + } +}; + +void Test() { + auto tex = Texture2D.Create(); + { + auto data = new ubyte[16*16]; + data[] = ubyte.max; + + Texture2DAllocator allocator; + allocator.internalFormat = GL_RGBA8; + allocator.size = vec2i(16, 16); + allocator.format = GL_RED; + allocator.type = GL_UNSIGNED_BYTE; + allocator.data = data.ptr; + allocator.Allocate(tex); + } + + auto sampler = Sampler.Create(); + { + SamplerConfigurer configurer; + configurer.filterMin = GL_NEAREST; + configurer.filterMag = GL_NEAREST; + configurer.Configure(sampler); + } + + auto buf = ArrayBuffer.Create(); + { + ArrayBufferAllocator allocator; + allocator.size = float.sizeof*4*3; + allocator.data = null; + allocator.usage = GL_STATIC_DRAW; + allocator.Allocate(buf); + + auto ptr = buf.MapToWrite!float(); + ptr[0] = 0; + ptr[1] = 0; + ptr[2] = 0; + ptr[3] = 1; + + ptr[4] = 0.5; + ptr[5] = 0.5; + ptr[6] = 0; + ptr[7] = 1; + + ptr[8] = 0.5; + ptr[9] = 0; + ptr[10] = 0; + ptr[11] = 1; + } + + auto buf_tf = ArrayBuffer.Create(); + { + ArrayBufferAllocator allocator; + allocator.size = float.sizeof * 3; + allocator.data = null; + allocator.usage = GL_STREAM_COPY; + allocator.Allocate(buf_tf); + } + + auto ub = UniformBuffer.Create(); + { + const data = 1.5f; + UniformBufferAllocator allocator; + allocator.size = float.sizeof; + allocator.data = &data; + allocator.usage = GL_STATIC_DRAW; + allocator.Allocate(ub); + } + + ProgramRef program; + { + ProgramLinker linker; + linker.vertex = VertexShader.Compile(VertexShaderSource); + linker.fragment = FragmentShader.Compile(FragmentShaderSource); + linker.feedbackVaryings = ["feedback_value"]; + linker.feedbackInterleaved = true; + program = linker.Link(); + program.NumberUniformBlocks!(["uniformblock"]); + + program.Use(); + program.uniform!1 = 0; + } + + auto va = VertexArray.Create(); + { + va.Bind(); + VertexArrayAttacher attacher; + attacher.index = 0; + attacher.type = GL_FLOAT; + attacher.dimension = 4; + attacher.Attach(va, buf); + } + + auto rb = Renderbuffer.Create(); + { + RenderbufferAllocator allocator; + allocator.format = GL_RGB8; + allocator.size = vec2i(32, 32); + allocator.Allocate(rb); + } + + auto fb = Framebuffer.Create(); + { + fb.Bind(); + fb.attachment!GL_COLOR_ATTACHMENT0 = rb; + fb.attachmentOrder = [GL_COLOR_ATTACHMENT0]; + fb.Validate(); + fb.Unbind(); + } + + buf_tf.BindForTransformFeedback(0); + ub.BindForUniformBlock(0); + tex.BindToUnit(GL_TEXTURE0); + sampler.Bind(0); + + gl.BeginTransformFeedback(GL_POINTS); + gl.PointSize(5); + gl.DrawArrays(GL_POINTS, 0, 3); + gl.EndTransformFeedback(); + + { + auto ptr = buf_tf.MapToRead!float(); + static foreach (i; 0..3) ptr[i].writeln; + } +} + +void main() { + (loadSDL() == sdlSupport). + enforce("SDL library loading failed."); + + SDL_Init(SDL_INIT_VIDEO); + scope(exit) SDL_Quit(); + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + + auto win = SDL_CreateWindow("gl4d testing", 0, 0, 100, 100, SDL_WINDOW_OPENGL). + enforce("Failed creating OpenGL window."); + scope(exit) SDL_DestroyWindow(win); + + auto context = SDL_GL_CreateContext(win); + SDL_GL_MakeCurrent(win, context); + gl.ApplyContext(); + + Test(); + + SDL_GL_SwapWindow(win); + SDL_Delay(3000); +}