diff --git a/.gitmodules b/.gitmodules index 06844f2..c8c0357 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "modules/glamour"] path = modules/glamour url = git://github.com/Dav1dde/glamour +[submodule "modules/dft"] + path = modules/dft + url = ../../dft.git diff --git a/Rsconscript b/Rsconscript index fe93e37..72afcdc 100644 --- a/Rsconscript +++ b/Rsconscript @@ -1,5 +1,6 @@ configure do check_d_compiler + check_cfg package: "freetype2" end build do @@ -12,8 +13,9 @@ build do "modules/DerelictGL3/source", "modules/gl3n/gl3n", "modules/glamour/glamour", + "modules/dft/src", ].each do |dir| - sources += glob("#{dir}/**/*.d") + sources += glob("#{dir}/**/*.{d,c}") end env["D_IMPORT_PATH"] += [ "modules/DerelictUtil/source", @@ -21,6 +23,7 @@ build do "modules/DerelictGL3/source", "modules/gl3n", "modules/glamour", + "modules/dft/src", ] env["DFLAGS"] += ["-fversion=Derelict3", "-fversion=gl3n", "-fversion=SDLImage2"] env["LDFLAGS"] += ["-static-libgcc"] diff --git a/assets/FreeSans.ttf b/assets/FreeSans.ttf new file mode 100644 index 0000000..6b7543d Binary files /dev/null and b/assets/FreeSans.ttf differ diff --git a/modules/dft b/modules/dft new file mode 160000 index 0000000..4a0727b --- /dev/null +++ b/modules/dft @@ -0,0 +1 @@ +Subproject commit 4a0727b535bb3164e48c6bb57194f737dfeb0aab diff --git a/src/app.d b/src/app.d index 61b59a3..2af2803 100644 --- a/src/app.d +++ b/src/app.d @@ -1,24 +1,142 @@ import std.stdio; +import core.stdc.string; import derelict.sdl2.sdl; import derelict.opengl3.gl3; import glamour.vao; import glamour.shader; import glamour.vbo; +import glamour.texture; import gl3n.linalg; +static import dft; enum int WIDTH = 800; enum int HEIGHT = 600; +Shader spinny_program; GLint position_idx; GLint color_idx; GLint view_idx; +Shader font_program; +GLint font_coords_idx; +GLint font_viewport_size_idx; +GLint font_position_idx; +GLint font_texture_idx; +GLint font_color_idx; mat4 view_matrix; +private uint next_power_of_2(uint n) +{ + n--; + n |= n >> 1u; + n |= n >> 2u; + n |= n >> 4u; + n |= n >> 8u; + n |= n >> 16u; + n++; + return n; +} + +class Glyph +{ + Texture2D m_texture; + VAO m_vao; + Buffer m_vbo; + int m_advance; + + this(dft.Font font, uint char_code, int outline_size) + { + dft.Font.Glyph dft_glyph = font.get_glyph(char_code, outline_size); + m_advance = dft_glyph.advance; + + m_texture = new Texture2D(); + uint rwidth = next_power_of_2(dft_glyph.width); + uint rheight = next_power_of_2(dft_glyph.height); + ubyte[] texture_data = new ubyte[rwidth * rheight]; + for (uint row = 0u; row < dft_glyph.height; row++) + { + memcpy(&texture_data[row * rwidth], + &dft_glyph.bitmap[(dft_glyph.height - row - 1) * dft_glyph.width], + dft_glyph.width); + } + m_texture.bind(); + m_texture.set_parameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + m_texture.set_parameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + m_texture.set_parameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); + m_texture.set_parameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR); + m_texture.set_data(texture_data, GL_ALPHA, rwidth, rheight, GL_ALPHA, GL_UNSIGNED_BYTE, false); + + m_vao = new VAO(); + m_vao.bind(); + + int left = dft_glyph.left; + int top = dft_glyph.top; + float s_max = dft_glyph.width / cast(float)rwidth; + float t_max = dft_glyph.height / cast(float)rheight; + GLfloat[] data = [ + cast(GLfloat)left, cast(GLfloat)(top - dft_glyph.height), 0.0, 0.0, + cast(GLfloat)(left + dft_glyph.width), cast(GLfloat)(top - dft_glyph.height), s_max, 0.0, + cast(GLfloat)left, cast(GLfloat)top, 0.0, t_max, + cast(GLfloat)(left + dft_glyph.width), cast(GLfloat)top, s_max, t_max, + ]; + m_vbo = new Buffer(data); + m_vbo.bind(); + + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, null); + } + + void render() + { + m_vao.bind(); + m_texture.bind(); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + } + + @property int advance() + { + return m_advance; + } +} + +class OutlinedGlyph +{ + Glyph m_outline_glyph; + Glyph m_inner_glyph; + + this(dft.Font font, uint char_code, int outline_size) + { + m_outline_glyph = new Glyph(font, char_code, outline_size); + m_inner_glyph = new Glyph(font, char_code, 0); + } + + void render() + { + glUniform4f(font_color_idx, 0.0, 0.0, 0.0, 1.0); + m_outline_glyph.render(); + glUniform4f(font_color_idx, 1.0, 1.0, 1.0, 1.0); + m_inner_glyph.render(); + } + + @property int advance() + { + return m_inner_glyph.advance; + } +} + +dft.Font font; +VAO spinny_vao; + void init() { + glActiveTexture(GL_TEXTURE0); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glClearColor (1.0, 0.7, 0.0, 0.0); glViewport(0, 0, WIDTH, HEIGHT); immutable string shader_src = ` +#version 130 + vertex: uniform mat4 view; in vec2 position; @@ -36,11 +154,12 @@ fragment: gl_FragColor = vec4(color_i, 1.0); } `; - VAO vao = new VAO(); - Shader program = new Shader("program", shader_src); - program.bind(); - position_idx = program.get_attrib_location("position"); - color_idx = program.get_attrib_location("color"); + spinny_vao = new VAO(); + spinny_vao.bind(); + spinny_program = new Shader("spinny_program", shader_src); + spinny_program.bind(); + position_idx = spinny_program.get_attrib_location("position"); + color_idx = spinny_program.get_attrib_location("color"); float[] vertices = [0.4, 0.4, -0.4, 0.4, -0.4, -0.4, 0.4, -0.4]; float[] colors = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0]; ushort[] indices = [0, 1, 2, 3]; @@ -54,19 +173,102 @@ fragment: glVertexAttribPointer(color_idx, 3, GL_FLOAT, GL_FALSE, 0, null); ElementBuffer ibo = new ElementBuffer(indices); ibo.bind(); - view_idx = program.get_uniform_location("view"); + view_idx = spinny_program.get_uniform_location("view"); + + font = new dft.Font("assets/FreeSans.ttf", 60); + immutable string font_shader_src = ` +#version 130 + +vertex: + /* Viewport width and height */ + uniform ivec2 viewport_size; + /* Position of lower left corner of glyph */ + uniform ivec2 position; + + /* Vertex coordinates: x, y, s, t */ + in vec4 coords; + + /* Output texture coordinate: s, t */ + out vec2 texture_coord_v; + + /** + * Map coordinates such that: + * (0 .. viewport_size.[xy]) => (-1.0 .. 1.0) + */ + vec2 map_to_screen(vec2 position) + { + return 2.0 * position / viewport_size - 1.0; + } + + void main(void) + { + gl_Position = vec4(map_to_screen(vec2(position) + coords.xy), 0, 1); + texture_coord_v = coords.zw; + } + +fragment: + /* Texture coordinate: s, t */ + in vec2 texture_coord_v; + + /* Texture unit */ + uniform sampler2D texture; + uniform vec4 color; + + out vec4 frag_color; + + void main(void) + { + frag_color = vec4(1, 1, 1, texture2D(texture, texture_coord_v).a) * color; + } + `; + font_program = new Shader("fontshader", font_shader_src); + font_program.bind(); + font_coords_idx = font_program.get_attrib_location("coords"); + font_viewport_size_idx = font_program.get_uniform_location("viewport_size"); + font_position_idx = font_program.get_uniform_location("position"); + font_texture_idx = font_program.get_uniform_location("texture"); + font_color_idx = font_program.get_uniform_location("color"); + glUniform2i(font_viewport_size_idx, WIDTH, HEIGHT); + glUniform1i(font_texture_idx, 0); +} + +OutlinedGlyph[uint] outlined_glyphs; + +OutlinedGlyph get_glyph(uint char_code) +{ + if (char_code in outlined_glyphs) + { + return outlined_glyphs[char_code]; + } + return outlined_glyphs[char_code] = new OutlinedGlyph(font, char_code, 80); +} + +void draw_string(int x, int y, string s) +{ + foreach (c; s) + { + glUniform2i(font_position_idx, x, y); + OutlinedGlyph outlined_glyph = get_glyph(c); + outlined_glyph.render(); + x += outlined_glyph.advance; + } } void display(SDL_Window * window) { glClear(GL_COLOR_BUFFER_BIT); + spinny_program.bind(); + spinny_vao.bind(); view_matrix.make_identity(); view_matrix.rotatez(SDL_GetTicks() / 500.0); view_matrix.scale(HEIGHT / cast(float)WIDTH, 1.0, 1.0); glUniformMatrix4fv(view_idx, 1, GL_TRUE, view_matrix.value_ptr); glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_SHORT, null); + font_program.bind(); + draw_string(40, 40, "Hello world!"); + SDL_GL_SwapWindow(window); }