diff --git a/.travis.yml b/.travis.yml index fc01f58..3781d37 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,10 @@ language: d d: - dmd +before_install: + - sudo apt-get -qq update + - sudo apt-get install -y libsdl2-2.0 + - sudo apt-get install libsdl2-image-dev + script: - - dub test -b unittest-cov --compiler=${DC} -after_success: - - bash <(curl -s https://codecov.io/bash) \ No newline at end of file + - dub test \ No newline at end of file diff --git a/documentation/daque/graphics.aux b/documentation/daque/graphics.aux new file mode 100644 index 0000000..f23e546 --- /dev/null +++ b/documentation/daque/graphics.aux @@ -0,0 +1 @@ +\relax diff --git a/documentation/daque/graphics.log b/documentation/daque/graphics.log new file mode 100644 index 0000000..b9c061a --- /dev/null +++ b/documentation/daque/graphics.log @@ -0,0 +1,59 @@ +This is pdfTeX, Version 3.14159265-2.6-1.40.19 (TeX Live 2018/Arch Linux) (preloaded format=pdflatex 2018.6.16) 8 SEP 2018 12:51 +entering extended mode + restricted \write18 enabled. + %&-line parsing enabled. +**graphics.tex +(./graphics.tex +LaTeX2e <2018-04-01> patch level 2 +Babel <3.18> and hyphenation patterns for 84 language(s) loaded. +(/usr/share/texmf-dist/tex/latex/base/article.cls +Document Class: article 2014/09/29 v1.4h Standard LaTeX document class +(/usr/share/texmf-dist/tex/latex/base/size10.clo +File: size10.clo 2014/09/29 v1.4h Standard LaTeX file (size option) +) +\c@part=\count80 +\c@section=\count81 +\c@subsection=\count82 +\c@subsubsection=\count83 +\c@paragraph=\count84 +\c@subparagraph=\count85 +\c@figure=\count86 +\c@table=\count87 +\abovecaptionskip=\skip41 +\belowcaptionskip=\skip42 +\bibindent=\dimen102 +) (./graphics.aux) +\openout1 = `graphics.aux'. + +LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 3. +LaTeX Font Info: ... okay on input line 3. +LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 3. +LaTeX Font Info: ... okay on input line 3. +LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 3. +LaTeX Font Info: ... okay on input line 3. +LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 3. +LaTeX Font Info: ... okay on input line 3. +LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 3. +LaTeX Font Info: ... okay on input line 3. +LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 3. +LaTeX Font Info: ... okay on input line 3. + [1 + +{/var/lib/texmf/fonts/map/pdftex/updmap/pdftex.map}] (./graphics.aux) ) +Here is how much of TeX's memory you used: + 201 strings out of 492649 + 2136 string characters out of 6135778 + 58838 words of memory out of 5000000 + 4170 multiletter control sequences out of 15000+600000 + 3640 words of font info for 14 fonts, out of 8000000 for 9000 + 1141 hyphenation exceptions out of 8191 + 23i,4n,17p,116b,107s stack positions out of 5000i,500n,10000p,200000b,80000s + +Output written on graphics.pdf (1 page, 13466 bytes). +PDF statistics: + 12 PDF objects out of 1000 (max. 8388607) + 7 compressed objects within 1 object stream + 0 named destinations out of 1000 (max. 500000) + 1 words of extra memory for PDF output out of 10000 (max. 10000000) + diff --git a/documentation/daque/graphics.pdf b/documentation/daque/graphics.pdf new file mode 100644 index 0000000..5b4f439 Binary files /dev/null and b/documentation/daque/graphics.pdf differ diff --git a/documentation/daque/graphics.tex b/documentation/daque/graphics.tex new file mode 100644 index 0000000..5f5ceff --- /dev/null +++ b/documentation/daque/graphics.tex @@ -0,0 +1,7 @@ +\documentclass{article} + +\begin{document} +Documento de ejemplo. +Con un cambio +\end{document} + diff --git a/libdaque-test-library b/libdaque-test-library deleted file mode 100755 index 9990bc9..0000000 Binary files a/libdaque-test-library and /dev/null differ diff --git a/source/daque/graphics/attributeformat.d b/source/daque/graphics/attributeformat.d new file mode 100644 index 0000000..c9888b8 --- /dev/null +++ b/source/daque/graphics/attributeformat.d @@ -0,0 +1,51 @@ +module daque.graphics.attributeformat; + +import std.conv; + +import derelict.opengl; + +/++ +Data needed to represent a particular attribute for a Vertex. ++/ +struct AttributeFormat +{ + /// OpenGL identifies each attribute by an @index + GLuint index; + /// No. of components of this attribute + GLint size; + /// Data type of the components of this attribute + GLenum type; + /// Does it need to be _normalized_(Clipped to a range of 0.0 - 1.0)? + GLboolean normalized; + /// Space between each appearance of this attribute in an array of Vertices, equivalently, the + /// size of each Vertex + GLsizei stride; + /// Offset to first appearance of this attribute in an array of Vertices, equivalently, the + /// offset of this member in the Vertex structure + const GLvoid* pointer; + + string toString() + { + string str = "AttributeFormat(index: " + ~ to!string(index) ~ + ", size: " ~ to!string(size) ~ ")"; + return str; + } +} + +/++ +Given the Buffer and the VertexArray currently bound to the OpenGL context, this function provides +format info about the attribute format.index of the vertices in the VertexArray. + +This associates the Buffer to the VertexArray. + +Params: +format = attribute format to be given to the VertexArray currently bound + +/ +void setup(AttributeFormat format) +{ + glEnableVertexAttribArray(format.index); + + glVertexAttribPointer(format.index, format.size, format.type, + format.normalized, format.stride, format.pointer); +} diff --git a/source/daque/graphics/color.d b/source/daque/graphics/color.d index 5540b44..fb2f0a2 100644 --- a/source/daque/graphics/color.d +++ b/source/daque/graphics/color.d @@ -6,77 +6,77 @@ module daque.graphics.color; struct Color { public: - /++ + /++ RGBA components of the color +/ - ubyte[4] component; + ubyte[4] component; - /++ + /++ Gets a reference to the Red component of the color +/ - ref ubyte r() - { - return component[0]; - } + ref ubyte r() + { + return component[0]; + } - /++ + /++ Gets a reference to the Green component of the color +/ - ref ubyte g() - { - return component[1]; - } + ref ubyte g() + { + return component[1]; + } - /++ + /++ Gets a reference to the Blue component of the color +/ - ref ubyte b() - { - return component[2]; - } + ref ubyte b() + { + return component[2]; + } - /++ + /++ Gets a reference to the Alpha component of the color +/ - ref ubyte a() - { - return component[3]; - } + ref ubyte a() + { + return component[3]; + } - /++ + /++ Constructs a new Color from the given red (r), green (g), blue (b) and alpha (a) components +/ - this(ubyte r, ubyte g, ubyte b, ubyte a) - { - component[] = [r, g, b, a]; - } + this(ubyte r, ubyte g, ubyte b, ubyte a) + { + component[] = [r, g, b, a]; + } - /++ + /++ Constructs a color from the encoded 32 bit unsigned integer color +/ - this(uint color) - { - for (uint i; i < component.length; i++) - { - component[i] = color % 0x100; - color >>= 8; - } - } + this(uint color) + { + for (uint i; i < component.length; i++) + { + component[i] = color % 0x100; + color >>= 8; + } + } - /++ + /++ Encodes the color into a 32 bit unsigned integer +/ - uint toInt() - { - uint color = 0u; + uint toInt() + { + uint color = 0u; - for (int i = cast(int) component.length - 1; i >= 0; i--) - { - color += component[i]; - if (i - 1 >= 0) - color <<= 8; - } + for (int i = cast(int) component.length - 1; i >= 0; i--) + { + color += component[i]; + if (i - 1 >= 0) + color <<= 8; + } - return color; - } + return color; + } } diff --git a/source/daque/graphics/glsl.d b/source/daque/graphics/glsl.d new file mode 100644 index 0000000..ab224e8 --- /dev/null +++ b/source/daque/graphics/glsl.d @@ -0,0 +1,770 @@ +module daque.graphics.glsl; + +import std.format; +import std.conv; +import std.algorithm.searching; +import std.range; +import std.string; + +import daque.graphics.attributeformat; + +import derelict.opengl; + +/++ +Glsl Module: + - Purpose: + +Structs defined in this: + ScalarType: + Can be one of the following: + - Bool, Int, Uint, Float or Double + + VectorType: + Is a vector of 2, 3 or 4 ScalarType's + + MatrixType: + A Matrix of mxn floats + + GlslBasicType: + Can be one of the following: + - ScalarType, VectorType or MatrixType + + GlslArrayOrGlslBasicType: + Can be an array or not of GlslBasicType values + + Declaration; + Uniform(Declaration declaration); + LayoutInput; + ProgramDescriptor; + ShaderDescriptor; + Program(ProgramDescriptor Program_Descriptor); + VertexType(LayoutInput[] Layout_Inputs); ++/ + +struct ScalarType +{ + enum Type + { + Bool, + Int, + Uint, + Float, + Double + } + Type type; + + string To_Dlang_String() + { + final switch(type) + { + case Type.Bool: + return "bool"; + case Type.Int: + return "int"; + case Type.Uint: + return "uint"; + case Type.Float: + return "float"; + case Type.Double: + return "double"; + } + } + + GLenum Get_Gl_Type() + { + final switch(type) + { + case Type.Bool: + assert(0, "UNSUPPORTED"); + case Type.Int: + return GL_INT; + case Type.Uint: + return GL_UNSIGNED_INT; + case Type.Float: + return GL_FLOAT; + case Type.Double: + return GL_DOUBLE; + } + } + + static ScalarType From_String(string str, out bool success) + { + ScalarType scalar_type; + switch(str) + { + case "bool": + scalar_type.type = Type.Bool; + success = true; + break; + case "int": + scalar_type.type = Type.Int; + success = true; + break; + case "uint": + scalar_type.type = Type.Uint; + success = true; + break; + case "float": + scalar_type.type = Type.Float; + success = true; + break; + case "double": + scalar_type.type = Type.Double; + success = true; + break; + default: + success = false; + } + return scalar_type; + } + + static ScalarType From_Char(char c, out bool success) + { + ScalarType scalar_type; + switch(c) + { + case 'b': + scalar_type.type = Type.Bool; + success = true; + break; + case 'i': + scalar_type.type = Type.Int; + success = true; + break; + case 'u': + scalar_type.type = Type.Uint; + success = true; + break; + case 'f': + scalar_type.type = Type.Float; + success = true; + break; + case 'd': + scalar_type.type = Type.Double; + success = true; + break; + default: + success = false; + } + return scalar_type; + } + + char To_Char() + { + final switch(type) + { + case Type.Bool: + return 'b'; + case Type.Int: + return 'i'; + case Type.Uint: + return 'u'; + case Type.Float: + return 'f'; + case Type.Double: + return 'd'; + } + } + + /++ + Gets a "little string" representation of the scalar type represented by this. + + The little string representation depends on the type. + LittleString(Bool) => 1b + LittleString(Int) => 1i + LittleString(Uint) => 1ui + LittleString(Float) => 1f + LittleString(Double) => 1d + +/ + string Get_LittleString_Representation() + { + final switch(type) + { + case Type.Bool: + return "1b"; + case Type.Int: + return "1i"; + case Type.Uint: + return "1ui"; + case Type.Float: + return "1f"; + case Type.Double: + return "1d"; + } + } + + string To_Glsl_String() + { + final switch(type) + { + case Type.Bool: + return "bool"; + case Type.Int: + return "int"; + case Type.Uint: + return "uint"; + case Type.Float: + return "float"; + case Type.Double: + return "double"; + } + } +} + +struct VectorType +{ + ScalarType scalar_type; + uint components = 2; + + string To_Dlang_String() + { + return scalar_type.To_Dlang_String() ~ "[" ~ to!string(components) ~ "]"; + } + + string To_Glsl_String() + { + if(scalar_type.type == ScalarType.Type.Float) + { + return "vec" ~ to!string(components); + } + else + { + return scalar_type.To_Char() ~ "vec" ~ to!string(components); + } + } + + static VectorType From_String(string str, out bool success) + { + success = true; + + char start_char = str[0]; + if (start_char != 'v') + str = str[1 .. $]; + else + start_char = 'f'; + + VectorType vector_type; + vector_type.scalar_type = ScalarType.From_Char(start_char, success); + uint valid_reads; + valid_reads = str.formattedRead!"vec%s"(vector_type.components); + if(valid_reads != 1) + success = false; + + return vector_type; + } + + string Get_LittleString_Representation() + { + return to!string(components) ~ scalar_type.To_Char(); + } +} + +struct MatrixType +{ + uint columns, rows; + + string To_Dlang_String() + { + return "Matrix!(float, " ~ to!string(rows) ~ ", " ~ to!string(columns) ~ ")"; + } + + string To_Glsl_String() + { + return "mat" ~ to!string(rows) ~ "x" ~ to!string(columns); + } + + static MatrixType From_String(string str, out bool success) + { + MatrixType matrix_type; + + if(str[0 .. 3] != "mat") + { + success = false; + return matrix_type; + } + + str = str[3 .. $]; + + if(!(str.length == 3 || str.length == 1)) + { + success = false; + return matrix_type; + } + + auto split = findSplit(str, "x"); + string first = split[0]; + string second = split[1]; + + if(first.empty) + { + success = false; + return matrix_type; + } + if (second.empty) + { + matrix_type.columns = matrix_type.rows = to!uint(first); + } + else + { + matrix_type.rows = to!uint(first); + matrix_type.columns = to!uint(split[2]); + } + + success = true; + return matrix_type; + } +} + +struct GlslBasicType +{ + enum TypeSelection + { + Scalar, Vector, Matrix + } + TypeSelection selection; + + union Type + { + ScalarType scalar_type; + VectorType vector_type; + MatrixType matrix_type; + } + Type type; + + string To_Glsl_String() + { + final switch(selection) + { + case TypeSelection.Scalar: + return type.scalar_type.To_Glsl_String(); + case TypeSelection.Vector: + return type.vector_type.To_Glsl_String(); + case TypeSelection.Matrix: + return type.matrix_type.To_Glsl_String(); + } + } + + string Get_LittleString_Representation() + { + final switch(selection) + { + case TypeSelection.Scalar: + return type.scalar_type.Get_LittleString_Representation(); + case TypeSelection.Vector: + return type.vector_type.Get_LittleString_Representation(); + case TypeSelection.Matrix: + assert(0, "MATRIX DOESN'T HAVE LITTLESTRING REPRESENTATION"); + } + } + + uint Get_No_Components() + { + final switch(selection) + { + case TypeSelection.Scalar: + return 1; + case TypeSelection.Vector: + return type.vector_type.components; + case TypeSelection.Matrix: + assert(0, "MATRIX DOESNT HAVE NO COMPONENTS"); + } + } + + GLenum Get_Gl_Type() + { + final switch(selection) + { + case TypeSelection.Scalar: + return type.scalar_type.Get_Gl_Type(); + case TypeSelection.Vector: + return type.vector_type.scalar_type.Get_Gl_Type(); + case TypeSelection.Matrix: + assert(0, "MATRIX DOESNT HAVE GL TYPE"); + } + } + + string To_Dlang_String() + { + final switch(selection) + { + case TypeSelection.Scalar: + return type.scalar_type.To_Dlang_String(); + case TypeSelection.Vector: + return type.vector_type.To_Dlang_String(); + case TypeSelection.Matrix: + return type.matrix_type.To_Dlang_String(); + } + } + + static GlslBasicType From_String(string str, out bool success) + { + GlslBasicType glsl_basic_type; + bool read_success; + { + glsl_basic_type.selection = TypeSelection.Scalar; + glsl_basic_type.type.scalar_type = ScalarType.From_String(str, read_success); + if(read_success) + { + success = true; + return glsl_basic_type; + } + } + { + glsl_basic_type.selection = TypeSelection.Vector; + glsl_basic_type.type.vector_type = VectorType.From_String(str, read_success); + if(read_success) + { + success = true; + return glsl_basic_type; + } + } + { + glsl_basic_type.selection = TypeSelection.Matrix; + glsl_basic_type.type.matrix_type = MatrixType.From_String(str, read_success); + if(read_success) + { + success = true; + return glsl_basic_type; + } + } + + success = false; + return glsl_basic_type; + } + + static GlslBasicType From_String_Safe(string str) + { + bool success; + GlslBasicType glsl_basic_type = From_String(str, success); + assert(success); + return glsl_basic_type; + } +} + +struct GlslArrayOrGlslBasicType +{ + GlslBasicType glsl_basic_type; + uint no_elements; + bool is_array; + + string To_Dlang_String() + { + if (is_array) + { + return glsl_basic_type.To_Dlang_String() ~ "[" ~ to!string(no_elements) ~ "]"; + } + else + { + return glsl_basic_type.To_Dlang_String(); + } + } + + static GlslArrayOrGlslBasicType From_String(string str, out bool success) + { + GlslArrayOrGlslBasicType array_or_glsl_basic_type; + success = true; + auto split = findSplitBefore(str, "["); + string first = split[0]; + string second = split[1]; + + if(!second.empty) + { + array_or_glsl_basic_type.glsl_basic_type = GlslBasicType.From_String(first, success); + second.formattedRead!"[%s]"(array_or_glsl_basic_type.no_elements); + array_or_glsl_basic_type.is_array = true; + return array_or_glsl_basic_type; + } + else if(!first.empty) + { + array_or_glsl_basic_type.glsl_basic_type = GlslBasicType.From_String(first, success); + array_or_glsl_basic_type.no_elements = 1; + array_or_glsl_basic_type.is_array = false; + return array_or_glsl_basic_type; + } + else + { + success = false; + return array_or_glsl_basic_type; + } + } + + static GlslArrayOrGlslBasicType From_String_Safe(string str) + { + bool success; + GlslArrayOrGlslBasicType array_or_glsl_basic_type = From_String(str, success); + assert(success); + return array_or_glsl_basic_type; + } +} + +struct Declaration +{ + GlslArrayOrGlslBasicType type; + string identifier; + + // EXPECTED BUG: IT WILL NOT WORK FOR ARRAYS + static Declaration From_String(string str) + { + auto split = split(str, " "); + Declaration declaration; + assert(split.length == 2); + declaration.type = GlslArrayOrGlslBasicType.From_String_Safe(split[0]); + declaration.identifier = split[1]; + return declaration; + } + + string To_Glsl_String() + { + if(type.is_array) + return type.glsl_basic_type.To_Glsl_String() ~ " " ~ identifier ~ "[" ~ to!string(type.no_elements) ~ "]"; + else + return type.glsl_basic_type.To_Glsl_String() ~ " " ~ identifier; + } + + string To_Dlang_String() + { + return type.To_Dlang_String() ~ " " ~ identifier; + } +} + +struct Uniform(Declaration declaration) +{ + mixin("alias Type = " ~ declaration.type.To_Dlang_String() ~ ";"); + int location; + int program; + + void Set_Program(int program) + { + this.program = program; + this.location = glGetUniformLocation(program, toStringz(declaration.identifier)); + } + + void Set(Type value) + { + static if (declaration.type.glsl_basic_type.selection == GlslBasicType.TypeSelection.Scalar && !declaration.type.is_array) + { + enum string Type_Little_String = declaration.type.glsl_basic_type.Get_LittleString_Representation(); + mixin("alias ProgramUniform = glProgramUniform" ~ Type_Little_String ~ "v;"); + ProgramUniform(program, location, 1, &value); + } + else static if (declaration.type.glsl_basic_type.selection == GlslBasicType.TypeSelection.Matrix) + { + // TODO: Support Matrix Uniform setting + static assert(0, "MATRIX UNIFORM NOT SUPPORTED"); + } + else + { + enum string Type_Little_String = declaration.type.glsl_basic_type.Get_LittleString_Representation(); + mixin("alias ProgramUniform = glProgramUniform" ~ Type_Little_String ~ "v;"); + ProgramUniform(program, location, declaration.type.no_elements, value.ptr); + } + } + + void opAssign(Type value) + { + this.Set(value); + } +} + +struct LayoutInput +{ + int location = -1; + Declaration declaration; + enum Normalized : bool + { + NORMALIZE = true, DONT_NORMALIZE = false + } + Normalized normalized; + + string To_Glsl_String() + { + assert(location >= 0); + return "layout (location = " ~ to!string(location) ~ ") in " ~ declaration.To_Glsl_String(); + } +} + +struct ProgramDescriptor +{ + ShaderDescriptor vertex_shader; + ShaderDescriptor fragment_shader; +} + +struct ShaderDescriptor +{ + string glsl_version; + LayoutInput[] layout_inputs; + Declaration[] inputs; + Declaration[] uniforms; + Declaration[] outputs; + string source; + + string Get_Full_Source() + { + string full_source; + + full_source ~= glsl_version; + full_source ~= '\n'; + + foreach(LayoutInput layout_input; layout_inputs) + { + full_source ~= layout_input.To_Glsl_String() ~ ";"; + full_source ~= '\n'; + } + + foreach(Declaration input; inputs) + { + full_source ~= "in " ~ input.To_Glsl_String() ~ ";"; + full_source ~= '\n'; + } + + foreach(Declaration uniform; uniforms) + { + full_source ~= "uniform " ~ uniform.To_Glsl_String() ~ ";"; + full_source ~= '\n'; + } + + foreach(Declaration output; outputs) + { + full_source ~= "out " ~ output.To_Glsl_String() ~ ";"; + full_source ~= '\n'; + } + + full_source ~= source; + + return full_source; + } +} + +import daque.graphics.opengl; + +struct Program(ProgramDescriptor PROGRAM_DESCRIPTOR) +{ + int vertex_shader, fragment_shader; + int program_id; + + alias Vertex = VertexType!(PROGRAM_DESCRIPTOR.vertex_shader.layout_inputs); + + static foreach(Declaration UNIFORM_DECLARATION; PROGRAM_DESCRIPTOR.vertex_shader.uniforms) + { + mixin(q{Uniform!(UNIFORM_DECLARATION) } ~ UNIFORM_DECLARATION.identifier ~ ";"); + } + + void init() + { + program_id = glCreateProgram(); + + vertex_shader = Compile_Shader(GL_VERTEX_SHADER, + PROGRAM_DESCRIPTOR.vertex_shader.Get_Full_Source()); + + fragment_shader = Compile_Shader(GL_FRAGMENT_SHADER, + PROGRAM_DESCRIPTOR.fragment_shader.Get_Full_Source()); + + Attach_Shaders(program_id, [vertex_shader, fragment_shader]); + + Link_Program(program_id); + } + + +} + +enum ProgramDescriptor TESTING_PROGRAM_DESCRIPTOR = +{ + vertex_shader: { + glsl_version: "#version 330 core", + layout_inputs: [ + {0, Declaration.From_String("vec3 position"), normalized: LayoutInput.Normalized.DONT_NORMALIZE}, + {1, Declaration.From_String("vec4 color"), normalized: LayoutInput.Normalized.NORMALIZE} + ], + uniforms: [ + Declaration.From_String("float z_near"), + Declaration.From_String("float z_far"), + Declaration.From_String("float alpha"), + Declaration.From_String("float xy_ratio"), + Declaration.From_String("vec3 translation") + ], + outputs: [ + Declaration.From_String("vec4 v_color") + ], + source: q{ + void main() + { + vec3 rotated = position; + vec3 translated = rotated + translation; + gl_Position = vec4(position, 1); + } + } + }, + fragment_shader: { + glsl_version: "#version 330 core", + outputs: [ + Declaration.From_String("vec4 final_color") + ], + inputs: [ + Declaration.From_String("vec4 v_color") + ], + source: q{ + void main() + { + final_color = v_color; + } + } + } +}; + +template VertexType(LayoutInput[] LAYOUT_INPUTS) +{ + struct VertexType + { + static foreach(LayoutInput LAYOUT_INPUT; LAYOUT_INPUTS) + { + mixin(LAYOUT_INPUT.declaration.To_Dlang_String() ~ ";"); + } + + static AttributeFormat[] INPUT_FORMATS; + + static this() + { + static foreach(LayoutInput LAYOUT_INPUT; LAYOUT_INPUTS) + { + mixin( + r" + { + AttributeFormat format = { + index: LAYOUT_INPUT.location, + size: LAYOUT_INPUT.declaration.type.glsl_basic_type.Get_No_Components(), + type: LAYOUT_INPUT.declaration.type.glsl_basic_type.Get_Gl_Type(), + normalized: LAYOUT_INPUT.normalized? GL_TRUE: GL_FALSE, + stride: this.sizeof, + pointer: cast(void*) this." ~ LAYOUT_INPUT.declaration.identifier ~ r".offsetof + }; + INPUT_FORMATS ~= format; + } + "); + } + } + } +} + +void Print_Value(alias T)() +{ + import std.stdio; + writeln(__traits(identifier, T), " = ", T); +} + +unittest +{ + import daque.graphics.sdl; + import derelict.sdl2.sdl; + import std.stdio; + + Window window = new Window("something", 800, 600); + Program!TESTING_PROGRAM_DESCRIPTOR testing_program; + testing_program.init; + testing_program.z_near = 0.13f; + window.close; + + testing_program.Vertex v; + v.position[] = [2, 3, 4]; + v.color[] = [123, 21, 31, 3]; +} + diff --git a/source/daque/graphics/image.d b/source/daque/graphics/image.d index e057d98..9af180e 100644 --- a/source/daque/graphics/image.d +++ b/source/daque/graphics/image.d @@ -8,7 +8,7 @@ import daque.math.linear; class Image { public: - /++ + /++ Creates a new empty image with the specified width and height and optional fill params: @@ -16,68 +16,68 @@ public: height = height of the new image fill = color to fill the new image +/ - this(uint width, uint height, uint fill = 0xffffffff) - { - m_width = width; - m_height = height; + this(uint width, uint height, uint fill = 0xffffffff) + { + m_width = width; + m_height = height; - m_pixel.length = m_width; - for (uint i; i < m_width; i++) - { - m_pixel[i].length = m_height; + m_pixel.length = m_width; + for (uint i; i < m_width; i++) + { + m_pixel[i].length = m_height; - for (uint j; j < m_height; j++) - { - m_pixel[i][j] = fill; - } - } - } + for (uint j; j < m_height; j++) + { + m_pixel[i][j] = fill; + } + } + } - /++ + /++ Gets a reference to the pixel located at position (x, y) params: x = x position of pixel from left to right y = y position of pixel from top to bottom +/ - ref uint opIndex(uint x, uint y) - { - return m_pixel[x][y]; - } + ref uint opIndex(uint x, uint y) + { + return m_pixel[x][y]; + } - /++ + /++ Gets a linear representation of the image according to the matrixOrder rule that is, if matrixOrder is RowMajor, then the image is given row by row if matrixOrder is ColumnMajor, then the image is given column by column +/ - uint[] linearize(MatrixOrder matrixOrder)() - { - uint[] linearization; + uint[] linearize(MatrixOrder matrixOrder)() + { + uint[] linearization; - static if (matrixOrder == MatrixOrder.RowMajor) - { - for (uint y; y < m_height; y++) - { - for (uint x; x < m_width; x++) - { - linearization ~= this[x, y]; - } - } - } - static if (matrixOrder == MatrixOrder.ColumnMajor) - { - for (uint x; x < m_width; x++) - { - for (uint y; y < m_height; y++) - { - linearization ~= this[x, y]; - } - } - } - return linearization; - } + static if (matrixOrder == MatrixOrder.RowMajor) + { + for (uint y; y < m_height; y++) + { + for (uint x; x < m_width; x++) + { + linearization ~= this[x, y]; + } + } + } + static if (matrixOrder == MatrixOrder.ColumnMajor) + { + for (uint x; x < m_width; x++) + { + for (uint y; y < m_height; y++) + { + linearization ~= this[x, y]; + } + } + } + return linearization; + } private: - uint[][] m_pixel; - const uint m_width, m_height; + uint[][] m_pixel; + const uint m_width, m_height; } diff --git a/source/daque/graphics/opengl.d b/source/daque/graphics/opengl.d index b023909..6f1b890 100644 --- a/source/daque/graphics/opengl.d +++ b/source/daque/graphics/opengl.d @@ -15,251 +15,138 @@ import derelict.sdl2.sdl; import derelict.sdl2.image; import daque.math.geometry; +import daque.math.linear; + +import daque.graphics.attributeformat; -/// Renders an Array of vertices already on GPU memory void render(GpuArray vertices) { - vertices.bind(); - glDrawArrays(GL_TRIANGLES, 0, cast(int) vertices.size()); + vertices.bind(); + glDrawArrays(GL_TRIANGLES, 0, cast(int) vertices.size()); } -/++ -Initializes required libraries for graphics rendering. - -Initializes SDL2 and OpenGL libraries. - +/ static this() { - DerelictGL3.load(); + DerelictGL3.load(); } - -/++ - +/ static ~this() { } -/++ -Represents a 'shader' OpenGL object. -A shader is *part* of a program ought to be executed by the GPU. -This class serves as a way to compile and use those program parts. +import std.typecons; -The "whole" Program is another OpenGL object constructed by assembling -many Shader s. -+/ -class Shader +alias CompileOutput = Tuple!(bool, "success", string, "log", GLuint, "id"); +CompileOutput Try_Compile_Shader(GLenum type, string source) { -public: - /// Types of shaders there can be - enum Type - { - Vertex, - Fragment - } - /++ - Get the type of the shader - Returns: type of the shader - +/ - @property Type type() - { - return m_type; - } + GLuint shader_name = glCreateShader(type); + const char* source_code_z_terminated = toStringz(source); + glShaderSource(shader_name, 1, &source_code_z_terminated, null); + glCompileShader(shader_name); + + GLint compilation_success = 0; + glGetShaderiv(shader_name, GL_COMPILE_STATUS, &compilation_success); + + if (compilation_success == GL_FALSE) + { + GLint log_size = 0; + GLchar[] error_log; + glGetShaderiv(shader_name, GL_INFO_LOG_LENGTH, &log_size); + error_log.length = log_size; + glGetShaderInfoLog(shader_name, log_size, &log_size, error_log.ptr); + string info = cast(string) fromStringz(error_log.ptr); + glDeleteShader(shader_name); + assert(info.length > 0, "COMPILATION FAILED BUT WE COULDN'T GET ANY INFO ON HOW IT FAILED"); + return CompileOutput(false, info, 0); + } + + return CompileOutput(true, "", shader_name); +} - /++ - Constructs a new shader of the specified type, using as source code the file pointed to by - sourcePath +GLuint Compile_Shader(GLenum type, string source) +{ + auto compilation = Try_Compile_Shader(type, source); + assert(compilation.success, "SHADER COMPILATION FAILED: " ~ compilation.log); + return compilation.id; +} - Params: - type = Type of the shader to be constructed - sourcePath = String representing a path to a file containing the - source code which will be used as source for the constructed shader - +/ - this(Shader.Type type, string sourcePath) - { - m_type = type; - m_shaderGlName = cast(immutable(GLuint)) compileShader(type, sourcePath); - } +void Attach_Shaders(GLuint program, GLuint[] shaders) +{ + shaders.each!(s => glAttachShader(program, s)); +} - ~this() - { - glDeleteShader(m_shaderGlName); - } +bool Try_Link_Program(GLuint program) +{ + glLinkProgram(program); -private: - immutable(GLuint) m_shaderGlName; - immutable(Type) m_type; - /++ - Compiles a shader of the specified type, using as source code the file pointed to by sourcePath - and returns the name of the opengl object representing the compiled shader. + GLint is_linked = 0; + glGetProgramiv(program, GL_LINK_STATUS, cast(int*)&is_linked); - Params: - type = Type of shader to be compiled - sourcePath = Path to the shader's source code + if (is_linked == GL_FALSE) + return false; + else + return true; +} - Returns: - Opengl Name of the compiled shader - +/ - static GLuint compileShader(immutable Type type, string sourcePath) - { - // Create and compile - GLuint shaderName = glCreateShader(typeToGlenum(type)); - const char* sourceCodeZ = toStringz(readText(sourcePath)); - glShaderSource(shaderName, 1, &sourceCodeZ, null); - glCompileShader(shaderName); - - // Error checking - GLint compilationSuccess = 0; - glGetShaderiv(shaderName, GL_COMPILE_STATUS, &compilationSuccess); - // Error case - if (compilationSuccess == GL_FALSE) - { - GLint logSize = 0; - GLchar[] errorLog; - - glGetShaderiv(shaderName, GL_INFO_LOG_LENGTH, &logSize); - errorLog.length = logSize; - glGetShaderInfoLog(shaderName, logSize, &logSize, &errorLog[0]); - string info = cast(string) fromStringz(&errorLog[0]); - - import std.stdio : writeln; - writeln("COMPILATION ERROR: ", info); - - glDeleteShader(shaderName); - shaderName = 0; - } - else // Success case - { - } - - return shaderName; - } - - /++ - Maps Shader.Type to equivalent OpenGL GLenum. +void Link_Program(GLuint program) +{ + bool success = Try_Link_Program(program); + assert(success, "LINKING ERROR"); +} - Params: - type = type to be mapped to GLenum - Returns: - GLenum equivalent of type - +/ - static pure GLenum typeToGlenum(Shader.Type type) - { - final switch (type) - { - case Shader.Type.Vertex: - return GL_VERTEX_SHADER; - case Shader.Type.Fragment: - return GL_FRAGMENT_SHADER; - } - } +int GetUniformLocation(GLuint program, string name) +{ + return glGetUniformLocation(program, name.toStringz()); } -/++ -Represents and handles a Program Opengl Object. -A Program is a group of Opengl Shaders which will be linked together. -A Program is a program to be executed by the GPU to each of the Vertices of a model. -+/ -class Program +template strToType(string typeString) { -public: - /++ - Creates a new empty program - +/ - this() - { - m_programGlName = glCreateProgram(); - } - /++ - Creates a program with the specified shaders already attached - +/ - this(Shader[] shaders) - { - this(); - shaders.each!(s => this.attach(s)); - } - - ~this() - { - glDeleteProgram(m_programGlName); - } - /++ - Attaches the shader to this program + static if (typeString == "i") + alias strToType = int; + else static if (typeString == "f") + alias strToType = float; + else + static assert(0, "unrecognized string type " ~ typeString); +} - Params: - shader = Shader to be attached - +/ - void attach(Shader shader) - { - glAttachShader(m_programGlName, shader.m_shaderGlName); - } - /++ - Links the currently attached shaders - +/ - void link() - { - glLinkProgram(m_programGlName); - - GLint isLinked = 0; - glGetProgramiv(m_programGlName, GL_LINK_STATUS, cast(int*)&isLinked); - if (isLinked == GL_FALSE) - { - import std.stdio: writeln; - writeln("LINKING ERROR"); - } - } - - /++ - Binds the program to the current opengl context so that it is used to process new render - commands - +/ - void use() - { - glUseProgram(m_programGlName); - } - - - int getUniformLocation(string name) - { - return glGetUniformLocation(m_programGlName, name.toStringz()); - } - - template strToType(string typeString) - { - static if(typeString == "i") - alias strToType = int; - else static if(typeString == "f") - alias strToType = float; - else - static assert(0, "unrecognized string type " ~ typeString); - } - - import std.conv; - /++ - Sets a integer uniform variable inside the program +import std.conv; +/++ + Sets a integer uniform variable inside the program - Params: - uniformName = name of the single-valued uniform integer to be changed - val = new value to be assigned - +/ - void setUniform(uint count, string typeString)(int location, void[] data) - { - this.use(); - mixin("alias glUniform = " ~ "glUniform" ~ to!string(count) ~ typeString ~ "v;"); - glUniform(location, cast(int)(data.length / (strToType!typeString.sizeof * count)), cast(strToType!typeString*)data.ptr); - } + Params: + uniformName = name of the single-valued uniform integer to be changed + val = new value to be assigned + +/ - // TODO: setUniformMatrix method +template matrixDimensionString(uint Rows, uint Columns) +{ + static if (Rows == Columns) + { + enum matrixDimensionString = to!string(Rows); + } + else + { + enum matrixDimensionString = to!string(Rows) ~ "x" ~ to!string(Columns); + } +} - void getUniform(string typeString)(int location, strToType!typeString* output) - { - mixin("alias glGetUniform = glGetUniform" ~ typeString ~ "v;"); - glGetUniform(m_programGlName, location, output); - } +// TODO: TEST +void SetUniformMatrix(uint Rows, uint Columns, RealType)(GLuint program, int location, + Matrix!(RealType, Rows, Columns) matrix) +{ + mixin("alias UniformMatrix = glUniformMatrix" + ~ matrixDimensionString!(Rows, Columns) ~ "fv;"); -private: - // associated Opengl Object Program's name - immutable(GLuint) m_programGlName; + RealType[] linearization = matrix.linearize!(MatrixOrder.ColumnMajor)(); + assert(linearization.length == Rows * Columns); + + glUseProgram(program); + UniformMatrix(location, 1, GL_FALSE, cast(const GLfloat*) linearization.ptr); +} +void GetUniform(string typeString)(int location, strToType!typeString* output) +{ + mixin("alias glGetUniform = glGetUniform" ~ typeString ~ "v;"); + glGetUniform(m_programGlName, location, output); } /++ @@ -272,103 +159,66 @@ This class eases/abstracts the interaction with this kind of opengl objects. class Buffer { public: - /++ + /++ Constructs a new and empty buffer +/ - this() - { - m_name = Buffer.gen(); - } + this() + { + m_name = Buffer.gen(); + } - ~this() - { - del(m_name); - } + ~this() + { + del(m_name); + } - /++ + /++ Generates a new opengl buffer and returns it's name Returns: name of the newly created opengl buffer +/ - static GLuint gen() - { - GLuint buffer; - glGenBuffers(1, &buffer); - return buffer; - } - - /++ + static GLuint gen() + { + GLuint buffer; + glGenBuffers(1, &buffer); + return buffer; + } + + /++ Deletes a opengl buffer given it's name Params : buffer = opengl name of the opengl buffer +/ - static void del(GLuint buffer) - { - glDeleteBuffers(1, &buffer); - } + static void del(GLuint buffer) + { + glDeleteBuffers(1, &buffer); + } - /++ + /++ Sends the unformatted data of the specified size to the buffer Params: data = Pointer to the data to be sent size = Size in bytes of the data to be sent +/ - void bufferData(void* data, size_t size) - { - bind(); - glBufferData(GL_ARRAY_BUFFER, size, data, GL_DYNAMIC_DRAW); - } + void bufferData(void* data, size_t size) + { + bind(); + glBufferData(GL_ARRAY_BUFFER, size, data, GL_DYNAMIC_DRAW); + } - /++ + /++ Binds the buffer to the current opengl context +/ - void bind() - { - glBindBuffer(GL_ARRAY_BUFFER, m_name); - } + void bind() + { + glBindBuffer(GL_ARRAY_BUFFER, m_name); + } private: - // opengl name of the buffer managed by @this - immutable(GLuint) m_name; -} - -/++ -Data needed to represent a particular attribute for a Vertex. -+/ -struct AttributeFormat -{ - /// OpenGL identifies each attribute by an @index - GLuint index; - /// No. of components of this attribute - GLint size; - /// Data type of the components of this attribute - GLenum type; - /// Does it need to be _normalized_(Clipped to a range of 0.0 - 1.0)? - GLboolean normalized; - /// Space between each appearance of this attribute in an array of Vertices, equivalently, the - /// size of each Vertex - GLsizei stride; - /// Offset to first appearance of this attribute in an array of Vertices, equivalently, the - /// offset of this member in the Vertex structure - const GLvoid* pointer; + // opengl name of the buffer managed by @this + immutable(GLuint) m_name; } -/++ -Given the Buffer and the VertexArray currently bound to the OpenGL context, this function provides -format info about the attribute format.index of the vertices in the VertexArray. - -This associates the Buffer to the VertexArray. - -Params: -format = attribute format to be given to the VertexArray currently bound - +/ -void setup(AttributeFormat format) -{ - glEnableVertexAttribArray(format.index); - - glVertexAttribPointer(format.index, format.size, format.type, - format.normalized, format.stride, format.pointer); -} /++ Represents an opengl Vertex Array Object (VAO). @@ -377,56 +227,56 @@ A VAO relates Opengl Buffers and Vertex Formats. class VertexArray { private: - // opengl name of the VAO managed by @this - immutable(GLuint) m_name; + // opengl name of the VAO managed by @this + immutable(GLuint) m_name; public: - /++ + /++ Generates and empty VAO and saves it's name +/ - this() - { - m_name = genVertexArray(); - } - - static GLuint genVertexArray() - { - GLuint name; - glGenVertexArrays(1, &name); - return name; - } - /++ + this() + { + m_name = genVertexArray(); + } + + static GLuint genVertexArray() + { + GLuint name; + glGenVertexArrays(1, &name); + return name; + } + /++ Deallocates the VAO +/ - ~this() - { - deleteVertexArray(m_name); - } - - static void deleteVertexArray(GLuint vertexArrayName) - { - glDeleteVertexArrays(1, &vertexArrayName); - } - /++ + ~this() + { + deleteVertexArray(m_name); + } + + static void deleteVertexArray(GLuint vertexArrayName) + { + glDeleteVertexArrays(1, &vertexArrayName); + } + /++ Associates this VertexArray with the buffer and the format given by the type VertexType. Inputs: buffer = Buffer to associae with this VertexArray and this format +/ - void use(Buffer buffer, AttributeFormat[] formats) - { - bind(); - buffer.bind(); - formats.each!setup; - } - - /++ + void use(Buffer buffer, AttributeFormat[] formats) + { + bind(); + buffer.bind(); + formats.each!setup; + } + + /++ Binds this VertexArray to the opengl context +/ - void bind() - { - glBindVertexArray(m_name); - } + void bind() + { + glBindVertexArray(m_name); + } } /++ @@ -437,37 +287,37 @@ DataType = Type of the data to be stored class GpuArray { private: - Buffer m_buffer; - VertexArray m_vao; - uint m_size; + Buffer m_buffer; + VertexArray m_vao; + uint m_size; public: - /++ + /++ Creates and fills a new gpu array Params: data = data to be initialy filled with +/ - this(void[] data, uint noElements, AttributeFormat[] attributeFormats) - { - m_buffer = new Buffer(); - m_vao = new VertexArray(); - m_size = noElements; - - m_buffer.bufferData(data.ptr, data.length); - m_vao.use(m_buffer, attributeFormats); - } - - /// Binds the associated VertexArray to the opengl context - void bind() - { - m_vao.bind(); - } - - uint size() - { - return m_size; - } + this(void[] data, uint noElements, AttributeFormat[] attributeFormats) + { + m_buffer = new Buffer(); + m_vao = new VertexArray(); + m_size = noElements; + + m_buffer.bufferData(data.ptr, data.length); + m_vao.use(m_buffer, attributeFormats); + } + + /// Binds the associated VertexArray to the opengl context + void bind() + { + m_vao.bind(); + } + + uint size() + { + return m_size; + } } /++ @@ -476,44 +326,44 @@ Represents a 2D opengl texture class Texture { private: - immutable(GLuint) m_name; - immutable(GLenum) m_type; + immutable(GLuint) m_name; + immutable(GLenum) m_type; - immutable(SDL_Surface*) m_surface; - immutable(uint) m_width, m_height; + immutable(SDL_Surface*) m_surface; + immutable(uint) m_width, m_height; public: - /++ + /++ Constructs a Texture from an image file got from imagePath Params: imagePath = path to the image to be used as a source to construct the texture +/ - this(string imagePath) - { - m_type = GL_TEXTURE_2D; - m_surface = cast(immutable(SDL_Surface*)) IMG_Load(imagePath.toStringz()); - m_width = m_surface.w; - m_height = m_surface.h; - - if (!m_surface) // error reading surface - { - return; - } - else if (m_surface.format.format != SDL_PIXELFORMAT_RGBA32) // unsupported pixel format - { - return; - } - - m_name = Texture.gen(); - this.bind(); - this.setParameter!"i"(GL_TEXTURE_MIN_FILTER, GL_NEAREST); - this.setParameter!"i"(GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_surface.w, m_surface.h, 0, - GL_RGBA, GL_UNSIGNED_BYTE, m_surface.pixels); - } - - /++ + this(string imagePath) + { + m_type = GL_TEXTURE_2D; + m_surface = cast(immutable(SDL_Surface*)) IMG_Load(imagePath.toStringz()); + m_width = m_surface.w; + m_height = m_surface.h; + + if (!m_surface) // error reading surface + { + return; + } + else if (m_surface.format.format != SDL_PIXELFORMAT_RGBA32) // unsupported pixel format + { + return; + } + + m_name = Texture.gen(); + this.bind(); + this.setParameter!"i"(GL_TEXTURE_MIN_FILTER, GL_NEAREST); + this.setParameter!"i"(GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_surface.w, m_surface.h, 0, + GL_RGBA, GL_UNSIGNED_BYTE, m_surface.pixels); + } + + /++ Constructs an empty Texture with the specified width and height, and fills it with color clearColor @@ -522,77 +372,77 @@ public: height = height of the texture to be constructed clearColor = color to be filled with +/ - this(uint width, uint height, uint clearColor = 0xffffffff) - { - m_width = width; - m_height = height; - - m_name = Texture.gen(); - m_type = GL_TEXTURE_2D; - m_surface = null; - - this.bind(); - this.setParameter!"i"(GL_TEXTURE_MIN_FILTER, GL_NEAREST); - this.setParameter!"i"(GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexImage2D(m_type, 0, GL_RGBA, m_width, m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, null); - } - - ~this() - { - Texture.del(m_name); - } - - /++ + this(uint width, uint height, uint clearColor = 0xffffffff) + { + m_width = width; + m_height = height; + + m_name = Texture.gen(); + m_type = GL_TEXTURE_2D; + m_surface = null; + + this.bind(); + this.setParameter!"i"(GL_TEXTURE_MIN_FILTER, GL_NEAREST); + this.setParameter!"i"(GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexImage2D(m_type, 0, GL_RGBA, m_width, m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, null); + } + + ~this() + { + Texture.del(m_name); + } + + /++ Fills the texture with color clearColor +/ - void clear(uint clearColor) - { - //t glClearTexImage(m_name, 0, GL_RGBA, GL_UNSIGNED_BYTE, &clearColor); - } - - private template GLType(string name) - { - static if (name == "f") - { - alias GLType = GLfloat; - } - else static if (name == "i") - { - alias GLType = GLint; - } - } - /// Sets an internal opengl parameter for the texture - void setParameter(string typename)(GLenum parameterName, GLType!typename value) - { - this.bind(); - - mixin("alias glTexParameter = glTexParameter" ~ typename ~ ";"); - glTexParameter(m_type, parameterName, value); - } - - /// Returns the width of the texture - uint width() - { - return m_width; - } - /// Returns the height of the texture - uint height() - { - return m_height; - } - - /// Binds the texture to the opengl context - void bind() - { - glBindTexture(m_type, m_name); - } - - /// Returns the opengl index of the texture ( aka: it's name ) - GLuint name() - { - return m_name; - } - /++ + void clear(uint clearColor) + { + //t glClearTexImage(m_name, 0, GL_RGBA, GL_UNSIGNED_BYTE, &clearColor); + } + + private template GLType(string name) + { + static if (name == "f") + { + alias GLType = GLfloat; + } + else static if (name == "i") + { + alias GLType = GLint; + } + } + /// Sets an internal opengl parameter for the texture + void setParameter(string typename)(GLenum parameterName, GLType!typename value) + { + this.bind(); + + mixin("alias glTexParameter = glTexParameter" ~ typename ~ ";"); + glTexParameter(m_type, parameterName, value); + } + + /// Returns the width of the texture + uint width() + { + return m_width; + } + /// Returns the height of the texture + uint height() + { + return m_height; + } + + /// Binds the texture to the opengl context + void bind() + { + glBindTexture(m_type, m_name); + } + + /// Returns the opengl index of the texture ( aka: it's name ) + GLuint name() + { + return m_name; + } + /++ Sets the pixels of a specified rectangular region Params: @@ -607,40 +457,40 @@ public: data = data in row major order of the pixels to be set +/ - void updateRegion(uint offsetx, uint offsety, uint width, uint height, uint[] data) - in - { - assert(data.length >= width * height); - } - out - { - } - do - { - this.bind(); - glTexSubImage2D(m_type, 0, offsetx, offsety, width, height, GL_RGBA, - GL_UNSIGNED_BYTE, data.ptr); - } - - /++ + void updateRegion(uint offsetx, uint offsety, uint width, uint height, uint[] data) + in + { + assert(data.length >= width * height); + } + out + { + } + do + { + this.bind(); + glTexSubImage2D(m_type, 0, offsetx, offsety, width, height, GL_RGBA, + GL_UNSIGNED_BYTE, data.ptr); + } + + /++ Generates a new opengl texture and returns it's name Returns: the name of the newly created texture +/ - static GLuint gen() - { - GLuint name; - glGenTextures(1, &name); - return name; - } - - /++ + static GLuint gen() + { + GLuint name; + glGenTextures(1, &name); + return name; + } + + /++ Deletes an opengl texture using it's name +/ - static void del(GLuint texture) - { - glDeleteTextures(1, &texture); - } + static void del(GLuint texture) + { + glDeleteTextures(1, &texture); + } } /++ @@ -652,6 +502,6 @@ texture = texture to be assigned +/ void setTextureUnit(int textureUnit, Texture texture) { - glActiveTexture(GL_TEXTURE0 + textureUnit); - glBindTexture(texture.m_type, texture.m_name); + glActiveTexture(GL_TEXTURE0 + textureUnit); + glBindTexture(texture.m_type, texture.m_name); } diff --git a/source/daque/graphics/sdl.d b/source/daque/graphics/sdl.d index 969136a..95779e7 100644 --- a/source/daque/graphics/sdl.d +++ b/source/daque/graphics/sdl.d @@ -10,20 +10,20 @@ import derelict.opengl; static this() { - DerelictSDL2.load(SharedLibVersion(2, 0, 2)); - DerelictSDL2Image.load(); + DerelictSDL2.load(SharedLibVersion(2, 0, 2)); + DerelictSDL2Image.load(); - if (SDL_Init(SDL_INIT_EVERYTHING) < 0) // error initializing SDL2 - { - exit(-1); - } + if (SDL_Init(SDL_INIT_EVERYTHING) < 0) // error initializing SDL2 + { + exit(-1); + } } /// Canvas for drawing class Window { public: - /++ + /++ Constructs a new window with the specified dimensions and name. Params: @@ -31,60 +31,58 @@ public: width = width of the window to be constructed height = height of the window to be constructed +/ - this(string name, uint width, uint height) - { - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + this(string name, uint width, uint height) + { + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); - m_window = cast(immutable(SDL_Window*)) SDL_CreateWindow(name.toStringz(), SDL_WINDOWPOS_CENTERED, - SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL); - m_isOpen = m_window != null; + m_window = cast(immutable(SDL_Window*)) SDL_CreateWindow(name.toStringz(), SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL); + m_isOpen = m_window != null; - m_glContext = SDL_GL_CreateContext(getWindow); + m_glContext = SDL_GL_CreateContext(getWindow); - DerelictGL3.reload(); - } - /// Closes the window - ~this() - { - this.close(); - } - /// Closes the window if it is open, deallocating it's resources. - void close() - { - if (isOpen()) - SDL_DestroyWindow(getWindow()); - m_isOpen = false; - } - /// Tells wether the window is currently open - bool isOpen() - { - return m_isOpen; - } + DerelictGL3.reload(); + } + /// Closes the window + ~this() + { + this.close(); + } + /// Closes the window if it is open, deallocating it's resources. + void close() + { + if (isOpen()) + SDL_DestroyWindow(getWindow()); + m_isOpen = false; + } + /// Tells wether the window is currently open + bool isOpen() + { + return m_isOpen; + } - /// Clear window buffer contents - void clear() - { - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - } - /// Prints current buffer contents into screen - void print() - { - SDL_GL_SwapWindow(getWindow); - } + /// Clear window buffer contents + void clear() + { + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + } + /// Prints current buffer contents into screen + void print() + { + SDL_GL_SwapWindow(getWindow); + } private: - /// reference to SDL window representation - immutable(SDL_Window*) m_window; - bool m_isOpen; - /// get non immutable reference to the window - SDL_Window* getWindow() - { - return cast(SDL_Window*) m_window; - } - /// SDL-GL context handle - SDL_GLContext m_glContext; + /// reference to SDL window representation + immutable(SDL_Window*) m_window; + bool m_isOpen; + /// get non immutable reference to the window + SDL_Window* getWindow() + { + return cast(SDL_Window*) m_window; + } + /// SDL-GL context handle + SDL_GLContext m_glContext; } - - diff --git a/source/daque/math/geometry.d b/source/daque/math/geometry.d index 1ee8057..ace29c1 100644 --- a/source/daque/math/geometry.d +++ b/source/daque/math/geometry.d @@ -1,114 +1,147 @@ +/++ +Authors: + Miguel Ángel (quevangel), quevangel@protonmail.com + David Omar Flores Chávez (davidomarf), davidomarfch@gmail.com ++/ module daque.math.geometry; import std.math; +import std.traits; -void geometryTest() +private bool isNan(R)(R[] v) if (isNumeric!R) { - import std.stdio; - writeln("daque geometry correctly linked"); + static if (!isFloatingPoint!R) + return false; + else + { + foreach (e; v) + if (isNaN(e)) + return true; + return false; + } } /++ - Mathematical dot product + Mathematical dot product +/ -R dot(R)(R[] v, R[] w) +R dot(R)(R[] v, R[] w) if (isNumeric!R) in { - assert(v.length == w.length, "Dot product can only be applied between to vectors of the same dimension"); + assert(!isNan(v) && !isNan(w), "Dot product argument was NaN"); + assert(v.length == w.length, + "Dot product can only be applied between to vectors of the same dimension"); + assert(v.length > 0, "Dot product can't be performed on empty vectors"); } -out +do { + import std.array; + import std.algorithm; + + Unqual!R product_sum = 0; + for (uint i; i < v.length /* = w.length */ ; i++) + product_sum += v[i] * w[i]; + + return product_sum; } -do -{ - import std.array; - import std.algorithm; - R[] products; - products.length = v.length; - products[] = v[] * w[]; - return products.array.sum; +/// +unittest +{ + assert(dot([1, 0], [0, 1]) == 0); + assert(dot([1, 0], [1, 0]) == 1); + assert(dot([1, 2, 3], [1, 2, 3]) == 14); } /++ Mathematical distance between to points +/ -real distance(R)(R[] v, R[] w) +real distance(R)(R[] v, R[] w) if (isNumeric!R) in { - assert(v.length == w.length, "distance can only be calculated between same-dimensional vectors"); + assert(!isNan(v) && !isNan(w), "some distance argument was nan"); + assert(v.length == w.length, "distance can only be calculated between same-dimensional vectors"); } out { } do { - R[] diff; - diff.length = v.length; - diff[] = w[] - v[]; - return magnitude(diff); + Unqual!R[] diff; + diff.length = v.length; + diff[] = w[] - v[]; + return magnitude(diff); } /++ - Mathematical cross product + Mathematical cross product +/ -R[] cross(R)(R[] v, R[] w) +R[] cross(R)(R[] v, R[] w) if (isNumeric!R) in { + assert(!isNan(v) && !isNan(w), "some cross argument was nan"); assert(v.length == 3 && w.length == 3, "Cross product can only be applied between 3d vectors"); } out (result) { - R[] resultDup = result.dup; - assert(approxEqual(dot(resultDup, v), 0.0) && approxEqual(dot(resultDup, w), 0.0), "Cross product was not orthogonal to it's operands"); } do { - R[] result; + Unqual!R[] result; result.length = 3; - for(uint i; i < 3; i++) + for (uint i; i < 3; i++) { - int j = (i + 1) % 3; + int j = (i + 1) % 3; int k = (j + 1) % 3; result[i] = v[j] * w[k] - v[k] * w[j]; } return result; } -R magnitudeSquared(R)(R[] v) -out(result) +R magnitudeSquared(R)(R[] v) if (isNumeric!R) +out (result) { - assert(result >= 0); + import std.conv; + + assert(!isNan(v), "magnitudeSquared argument was nan"); + assert(result >= 0, "magnitudeSquared " ~ to!string(v) ~ " = " ~ to!string(result) ~ " < 0"); } do { - return dot(v, v); + return dot(v, v); } -real magnitude(R)(R[] v) +real magnitude(R)(R[] v) if (isNumeric!R) { - return sqrt(cast(real)magnitudeSquared(v)); + return sqrt(cast(real) magnitudeSquared(v)); } -R[] normalize(R)(R[] v) -in +/// +unittest { - assert(magnitudeSquared(v) != 0, "Cannot normalize a zero vector"); + import daque.utils.test; + + Tester test = new Tester("Magnitude"); + + test.approx!(magnitude!double)([0.0], 0.0); + test.approx!(magnitude!double)([1.0, 1.0], sqrt(2.0)); + test.approx!(magnitude!double)([1.0, 3.0], 0.0); } -out(result) + +R[] normalize(R)(R[] v) if (isNumeric!R) +in { - assert(approxEqual(magnitudeSquared(result.dup), 1), "Normalized vector wasn't unitary"); + assert(!isNan(v), "normalize argument was nan"); + assert(magnitudeSquared(v) != 0, "Cannot normalize a zero vector"); } -do +out (result) { - R[] normalized; - normalized.length = v.length; - normalized[] = v[] / magnitude(v); - return normalized; + assert(approxEqual(magnitudeSquared(result.dup), 1), "Normalized vector wasn't unitary"); } - -unittest +do { - assert(distance([0.0, 0.0], [0.5, 0.5]) != 0.0); + R[] normalized; + normalized.length = v.length; + normalized[] = v[] / magnitude(v); + return normalized; } diff --git a/source/daque/math/linear.d b/source/daque/math/linear.d index a9ce841..6828032 100644 --- a/source/daque/math/linear.d +++ b/source/daque/math/linear.d @@ -2,69 +2,103 @@ module daque.math.linear; enum MatrixOrder { - RowMajor, ColumnMajor + RowMajor, + ColumnMajor } struct Matrix(RealType, uint Rows, uint Columns, MatrixOrder Order = MatrixOrder.ColumnMajor) { - private RealType[Rows * Columns] m_element; - - ref RealType opIndex(uint i, uint j) - { - static if(Order == MatrixOrder.RowMajor) - { - return m_element[j * Rows + i]; - } - else - { - return m_element[i * Columns + j]; - } - } + private RealType[Rows * Columns] m_element; - RealType[] linearize(MatrixOrder order = Order)() - { - RealType[] linearization; + ref RealType opIndex(uint i, uint j) + { + static if (Order == MatrixOrder.RowMajor) + { + return m_element[j * Rows + i]; + } + else + { + return m_element[i * Columns + j]; + } + } - static if(order == Order) - { - linearization = m_element; - } - else - { - static if(order == MatrixOrder.RowMajor) - { - for(uint i; i < Rows; i++) - for(uint j; j < Columns; j++) - linearization ~= this[i, j]; - } - else - { - for(uint j; j < Columns; j++) - for(uint i; i < Rows; i++) - linearization ~= this[i, j]; - } - } + RealType[] linearize(MatrixOrder order = Order)() + { + RealType[] linearization; - return linearization; - } + static if (order == Order) + { + linearization = m_element; + } + else + { + static if (order == MatrixOrder.RowMajor) + { + for (uint i; i < Rows; i++) + for (uint j; j < Columns; j++) + linearization ~= this[i, j]; + } + else + { + for (uint j; j < Columns; j++) + for (uint i; i < Rows; i++) + linearization ~= this[i, j]; + } + } - static Matrix!(RealType, Rows, Columns, Order) Identity() - { - Matrix!(RealType, Rows, Columns, Order) identity; - for(uint i; i < Rows; i++) - for(uint j; j < Columns; j++) - identity[i, j] = i == j; - return identity; - } + return linearization; + } + + static Matrix!(RealType, Rows, Columns, Order) Identity() + { + Matrix!(RealType, Rows, Columns, Order) identity; + for (uint i; i < Rows; i++) + for (uint j; j < Columns; j++) + identity[i, j] = i == j; + return identity; + } + + RealType[Rows] column(uint j) + { + RealType[Rows] col; + + for (uint i; i < Rows; i++) + { + col[i] = this[i, j]; + } + + return col; + } + + RealType[Rows] applyOn(RealType[] v) + in + { + assert(v.length == Columns); + } + out (result) + { + assert(result.length == Rows); + } + do + { + RealType[Rows] w; + w[] = 0; + + for (uint e; e < Columns; e++) + w[] += v[e] * this.column(e)[]; + + return w; + } } unittest { - import std.algorithm; - import std.range; - import std.stdio; - auto identityColumn = Matrix!(real, 3, 3, MatrixOrder.ColumnMajor).Identity(); - auto identityRow = Matrix!(real, 3, 3, MatrixOrder.RowMajor).Identity(); - writeln(identityColumn.linearize!(MatrixOrder.RowMajor)()); - writeln(identityRow); + import std.algorithm; + import std.range; + import std.stdio; + + auto identityColumn = Matrix!(real, 3, 3, MatrixOrder.ColumnMajor).Identity(); + auto identityRow = Matrix!(real, 3, 3, MatrixOrder.RowMajor).Identity(); + writeln(identityColumn.linearize!(MatrixOrder.RowMajor)()); + writeln(identityRow); } diff --git a/source/daque/math/quaternion.d b/source/daque/math/quaternion.d index f879d26..9fb02e0 100644 --- a/source/daque/math/quaternion.d +++ b/source/daque/math/quaternion.d @@ -7,89 +7,141 @@ import daque.math.linear; struct Quaternion(R) { - public R scalar; - public R[3] vector; - - public this(R[4] components) - { - scalar = components[0]; - vector[] = components[1 .. 4]; - } - - public this(R scalar, R[3] vector) - { - this.scalar = scalar; - this.vector = vector; - } - - static public Quaternion!R getRotation(R[3] axis, R amount) - { - Quaternion quaternion = Quaternion([0, 0, 0, 0]); - quaternion.scalar = sin(amount / 2.0f); - quaternion.vector[] = cos(amount / 2.0f) * axis[]; - return quaternion; - } - - Matrix!(R, 3, 3) getRotationMatrix() - { - auto m = Matrix!(R, 3, 3).Identity(); - - for(uint j; j < 3; j++) - { - Quaternion columnQuaternion = Quaternion([0, 0, 0, 0]); - columnQuaternion.scalar = 0; - for(uint i; i < 3; i++) - columnQuaternion.vector[i] = m[i, j]; - - columnQuaternion = columnQuaternion * this; - for(uint i; i < 3; i++) - m[i, j] = columnQuaternion.vector[i]; - } - - return m; - } - - public Quaternion opBinary(string op)(Quaternion rhs) - { - Quaternion result; - static if (op == "+") - { - result.scalar = this.scalar + rhs.scalar; - result.vector[] = this.vector[] + rhs.vector[]; - } - else static if (op == "*") - { - result.scalar = this.scalar * rhs.scalar - dot(this.vector, rhs.vector); - result.vector[] = this.scalar * rhs.vector[] + this.vector[] * rhs.scalar + cross(this.vector, rhs.vector)[]; - } - else - { - static assert(0, "Quaternion: Unsupported operation: " ~ op); - } - return result; - } - - public Quaternion conjugate() - { - Quaternion conjugateQuaternion; - conjugateQuaternion.scalar = this.scalar; - conjugateQuaternion.vector[] = -1 * this.vector[]; - return conjugateQuaternion; - } - - public Quaternion inverse() - { - Quaternion invSquareAbs = Quaternion(1.0 / squareAbs(), [0, 0, 0]); - return conjugate() * invSquareAbs; - } - - public real abs() - { - return sqrt(squareAbs); - } - - public real squareAbs() - { - return (this * this.conjugate).scalar; - } + public R scalar; + public R[3] vector; + + public this(R[4] components) + { + scalar = components[0]; + vector[] = components[1 .. 4]; + } + + public this(R scalar, R[3] vector) + { + this.scalar = scalar; + this.vector = vector; + } + + public this(R scalar) + { + this.scalar = scalar; + this.vector[] = 0; + } + + public this(R[3] vector) + { + this.scalar = 0; + this.vector[] = vector[]; + } + + public bool isRotation() + { + return approxEqual(squareAbs(), 1.0f); + } + + static public Quaternion!R getRotation(R[3] axis, R amount) + { + R[] unitAxis = normalize(axis); + Quaternion quaternion = Quaternion([0, 0, 0, 0]); + quaternion.scalar = cos(amount / 2.0f); + quaternion.vector[] = sin(amount / 2.0f) * unitAxis[]; + return quaternion; + } + + Matrix!(R, 3, 3) getRotationMatrix() + { + auto m = Matrix!(R, 3, 3).Identity(); + + for (uint j; j < 3; j++) + { + Quaternion columnQuaternion = Quaternion([0, 0, 0, 0]); + columnQuaternion.scalar = 0; + for (uint i; i < 3; i++) + columnQuaternion.vector[i] = m[i, j]; + + columnQuaternion = columnQuaternion * this; + for (uint i; i < 3; i++) + m[i, j] = columnQuaternion.vector[i]; + } + + return m; + } + + public Quaternion opBinary(string op)(const Quaternion rhs) const + { + Quaternion result; + static if (op == "+") + { + result.scalar = this.scalar + rhs.scalar; + result.vector[] = this.vector[] + rhs.vector[]; + } + else static if (op == "*") + { + result.scalar = this.scalar * rhs.scalar - dot(this.vector, rhs.vector); + result.vector[] = this.scalar * rhs.vector[] + this.vector[] * rhs.scalar + cross(this.vector, + rhs.vector)[]; + } + else static if (op == "/") + { + return this * rhs.inverse(); + } + else + { + static assert(0, "Quaternion: Unsupported operation: " ~ op); + } + return result; + } + + public Quaternion conjugate() + { + Quaternion conjugateQuaternion; + conjugateQuaternion.scalar = this.scalar; + conjugateQuaternion.vector[] = -1 * this.vector[]; + return conjugateQuaternion; + } + + public Quaternion conjugate(Quaternion q) + { + return this * q * this.inverse(); + } + + public Matrix!(R, 3, 3) rotationMatrix() + { + Matrix!(R, 3, 3) matrix; + + for (uint column; column < 3; column++) + { + Quaternion quaternionColumn = Quaternion(0); + quaternionColumn.vector[column] = 1; + quaternionColumn = this.conjugate(quaternionColumn); + for (uint i; i < 3; i++) + matrix[i, column] = quaternionColumn.vector[i]; + } + + return matrix; + } + + public Quaternion inverse() + { + Quaternion invSquareAbs = Quaternion(1.0 / squareAbs(), [0, 0, 0]); + return conjugate() * invSquareAbs; + } + + public real abs() + { + return sqrt(squareAbs); + } + + public real squareAbs() + { + return (this * this.conjugate).scalar; + } +} + +unittest +{ + Quaternion!float t = Quaternion!float(3); + assert(t.scalar == 3); + assert(t.vector[] == [0, 0, 0]); + assert(1 == 1); } diff --git a/source/daque/utils/terminal.d b/source/daque/utils/terminal.d new file mode 100644 index 0000000..a402e1b --- /dev/null +++ b/source/daque/utils/terminal.d @@ -0,0 +1,36 @@ +/++ +Authors: + David Omar Flores Chávez (davidomarf), davidomarf@gmail.com + Miguel Ángel (quevangel), quevangel@protonmail.com ++/ + +module daque.utils.terminal; + +import std.traits; + +/++ + Maps a color with its ANSI escape code + + Printed before any text, will color that text until + another color is specified. ++/ +enum TerminalColor : string +{ + Black = "\033[0;30m", + D_Gray = "\033[1;30m", + Red = "\033[0;31m", + L_Red = "\033[1;31m", + Green = "\033[0;32m", + L_Green = "\033[1;32m", + Brown = "\033[0;33m", + Yellow = "\033[1;33m", + Blue = "\033[0;34m", + L_Blue = "\033[1;34m", + Purple = "\033[0;35m", + L_Purple = "\033[1;35m", + Cyan = "\033[0;36m", + L_Cyan = "\033[1;36m", + L_Gray = "\033[0;37m", + White = "\033[1;37m", + NoColor = "\033[0m" +} diff --git a/source/daque/utils/testlib.d b/source/daque/utils/testlib.d new file mode 100644 index 0000000..7f03dce --- /dev/null +++ b/source/daque/utils/testlib.d @@ -0,0 +1,64 @@ +/++ +Authors: + David Omar Flores Chávez (davidomarf), davidomarf@gmail.com + Miguel Ángel (quevangel), quevangel@protonmail.com ++/ + +module daque.utils.test; + +import std.stdio; +import std.math; +import std.traits; + +import daque.utils.terminal; + +/++ + Tests the validity of a function @functionName and reports its + results to the console. + + Every unittest in the source—code will create a Tester. ++/ +class Tester +{ +public: + /++ + Will only print the name of the function being tested. + +/ + this(string functionName) + { + writeln("\n--- ", functionName, " test ---"); + } + + ~this() + { + + } + + /++ + Compares two numeric values that are roughly equal, up to a + default tolerance 1e-5. + + It uses std.math.approxEqual() + +/ + void approx(alias func, ReturnType, InputTypes...)(InputTypes inputs, ReturnType expected) + { + + // Compute the actual returned value + ReturnType result = func(inputs); + + // Compare it against the known expected value and print + // a successful or failure message accordingly. + if (approxEqual(result, expected)) + { + writeln(cast(string) TerminalColor.L_Green, __traits(identifier, + func), "(", inputs, ") = ", expected); + } + else + { + writeln(cast(string) TerminalColor.Red, __traits(identifier, func), + "(", inputs, ") = ", result, " != ", expected); + } + // Set non-colored text again + write(cast(string) TerminalColor.NoColor); + } +}