CXX = g++ --std=c++11
LDXX = g++
-LIBS = sdl2 SDL2_image glew
+LIBS = sdl2 SDL2_image SDL2_ttf glew
PKGFLAGS := $(shell pkg-config --cflags $(LIBS))
PKGLIBS := $(shell pkg-config --libs $(LIBS))
usefull for development and later on world administration
-font rendering
-
- mostly for labelled blocks and some ui elements
-
networking
exchange of chunks and entities
Dependencies
============
- GLEW, GLM, SDL2, SDL2_image
+ GLEW, GLM, SDL2, SDL2_image, SDL2_ttf
CppUnit for tests
-archlinux: pacman -S glew glm sdl2 sdl2_image cppunit
+archlinux: pacman -S glew glm sdl2 sdl2_image sdl2_ttf cppunit
manual:
CppUnit http://sourceforge.net/projects/cppunit/
Application::Application(const Config &config)
: init_sdl()
, init_img()
+, init_ttf()
, init_gl(config.doublebuf, config.multisampling)
, window()
, ctx(window.CreateContext())
private:
InitSDL init_sdl;
InitIMG init_img;
+ InitTTF init_ttf;
InitGL init_gl;
Window window;
GLContext ctx;
#include <algorithm>
#include <SDL.h>
#include <SDL_image.h>
+#include <SDL_ttf.h>
#include <stdexcept>
#include <string>
#include <GL/glew.h>
}
+InitTTF::InitTTF() {
+ if (TTF_Init() != 0) {
+ sdl_error("TTF_Init()");
+ }
+}
+
+InitTTF::~InitTTF() {
+ TTF_Quit();
+}
+
+
InitGL::InitGL(bool double_buffer, int sample_size) {
if (SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3) != 0) {
sdl_error("SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3)");
glEnable(GL_CULL_FACE);
}
+void GLContext::EnableAlphaBlending() noexcept {
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+}
+
+void GLContext::DisableAlphaBlending() noexcept {
+ glDisable(GL_BLEND);
+}
+
void GLContext::Clear() noexcept {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
};
+class InitTTF {
+
+public:
+ InitTTF();
+ ~InitTTF();
+
+ InitTTF(const InitTTF &) = delete;
+ InitTTF &operator =(const InitTTF &) = delete;
+
+};
+
+
class InitGL {
public:
static void EnableVSync();
static void EnableDepthTest() noexcept;
static void EnableBackfaceCulling() noexcept;
+ static void EnableAlphaBlending() noexcept;
+ static void DisableAlphaBlending() noexcept;
static void Clear() noexcept;
static void ClearDepthBuffer() noexcept;
--- /dev/null
+#ifndef BLANK_GRAPHICS_BLENDEDSPRITE_HPP_
+#define BLANK_GRAPHICS_BLENDEDSPRITE_HPP_
+
+#include "Program.hpp"
+
+#include <GL/glew.h>
+#include <glm/glm.hpp>
+
+
+namespace blank {
+
+class Texture;
+
+class BlendedSprite {
+
+public:
+ BlendedSprite();
+
+ void Activate() noexcept;
+
+ void SetM(const glm::mat4 &m) noexcept;
+ void SetProjection(const glm::mat4 &p) noexcept;
+ void SetView(const glm::mat4 &v) noexcept;
+ void SetVP(const glm::mat4 &v, const glm::mat4 &p) noexcept;
+ void SetMVP(const glm::mat4 &m, const glm::mat4 &v, const glm::mat4 &p) noexcept;
+
+ void SetTexture(Texture &) noexcept;
+
+ const glm::mat4 &Projection() const noexcept { return projection; }
+ const glm::mat4 &View() const noexcept { return view; }
+ const glm::mat4 &GetVP() const noexcept { return vp; }
+
+private:
+ Program program;
+
+ glm::mat4 projection;
+ glm::mat4 view;
+ glm::mat4 vp;
+
+ GLuint mvp_handle;
+ GLuint sampler_handle;
+
+};
+
+}
+
+#endif
--- /dev/null
+#ifndef BLANK_GRAPHICS_FONT_HPP_
+#define BLANK_GRAPHICS_FONT_HPP_
+
+#include <SDL_ttf.h>
+#include <glm/glm.hpp>
+
+
+namespace blank {
+
+class Texture;
+
+class Font {
+
+public:
+ Font(const char *src, int size, long index = 0);
+ ~Font();
+
+ Font(Font &&) noexcept;
+ Font &operator =(Font &&) noexcept;
+
+ Font(const Font &) = delete;
+ Font &operator =(const Font &) = delete;
+
+public:
+ bool Kerning() const noexcept;
+ void Kerning(bool) noexcept;
+
+ int Height() const noexcept;
+ int Ascent() const noexcept;
+ int Descent() const noexcept;
+ int LineSkip() const noexcept;
+
+ bool HasGlyph(Uint16) const noexcept;
+
+ glm::tvec2<int> TextSize(const char *) const;
+
+ Texture Render(const char *, SDL_Color) const;
+
+private:
+ TTF_Font *handle;
+
+};
+
+}
+
+#endif
--- /dev/null
+#ifndef BLANK_GRAPHICS_FORMAT_HPP_
+#define BLANK_GRAPHICS_FORMAT_HPP_
+
+#include <SDL.h>
+#include <GL/glew.h>
+
+
+namespace blank {
+
+struct Format {
+
+ GLenum format;
+ GLenum type;
+ GLenum internal;
+
+ void ReadPixelFormat(const SDL_PixelFormat &);
+
+};
+
+}
+
+#endif
--- /dev/null
+#ifndef BLANK_GRAPHICS_TEXTURE_HPP_
+#define BLANK_GRAPHICS_TEXTURE_HPP_
+
+#include "Format.hpp"
+
+#include <GL/glew.h>
+
+struct SDL_Surface;
+
+
+namespace blank {
+
+class Texture {
+
+public:
+ Texture();
+ ~Texture();
+
+ Texture(Texture &&) noexcept;
+ Texture &operator =(Texture &&) noexcept;
+
+ Texture(const Texture &) = delete;
+ Texture &operator =(const Texture &) = delete;
+
+public:
+ GLsizei Width() const noexcept { return width; }
+ GLsizei Height() const noexcept { return height; }
+
+ void Bind() noexcept;
+
+ void Data(const SDL_Surface &, bool pad2 = true) noexcept;
+ void Data(GLsizei w, GLsizei h, const Format &, GLvoid *data) noexcept;
+
+ void FilterNearest() noexcept;
+ void FilterLinear() noexcept;
+ void FilterTrilinear() noexcept;
+
+ static void UnpackAlignment(GLint) noexcept;
+ static int UnpackAlignmentFromPitch(int) noexcept;
+ static void UnpackRowLength(GLint) noexcept;
+
+private:
+ GLuint handle;
+
+ GLsizei width, height;
+
+};
+
+}
+
+#endif
--- /dev/null
+#include "Font.hpp"
+#include "Format.hpp"
+#include "Texture.hpp"
+
+#include <algorithm>
+#include <cstring>
+#include <memory>
+#include <stdexcept>
+
+
+namespace blank {
+
+Font::Font(const char *src, int size, long index)
+: handle(TTF_OpenFontIndex(src, size, index)) {
+ if (!handle) {
+ throw std::runtime_error(TTF_GetError());
+ }
+}
+
+Font::~Font() {
+ if (handle) {
+ TTF_CloseFont(handle);
+ }
+}
+
+Font::Font(Font &&other) noexcept
+: handle(other.handle) {
+ other.handle = nullptr;
+}
+
+Font &Font::operator =(Font &&other) noexcept {
+ std::swap(handle, other.handle);
+ return *this;
+}
+
+
+bool Font::Kerning() const noexcept {
+ return TTF_GetFontKerning(handle);
+}
+
+void Font::Kerning(bool b) noexcept {
+ TTF_SetFontKerning(handle, b);
+}
+
+
+int Font::Height() const noexcept {
+ return TTF_FontHeight(handle);
+}
+
+int Font::Ascent() const noexcept {
+ return TTF_FontAscent(handle);
+}
+
+int Font::Descent() const noexcept {
+ return TTF_FontDescent(handle);
+}
+
+int Font::LineSkip() const noexcept {
+ return TTF_FontLineSkip(handle);
+}
+
+
+bool Font::HasGlyph(Uint16 c) const noexcept {
+ return TTF_GlyphIsProvided(handle, c);
+}
+
+
+glm::tvec2<int> Font::TextSize(const char *text) const {
+ glm::tvec2<int> size;
+ if (TTF_SizeUTF8(handle, text, &size.x, &size.y) != 0) {
+ throw std::runtime_error(TTF_GetError());
+ }
+ return size;
+}
+
+Texture Font::Render(const char *text, SDL_Color color) const {
+ SDL_Surface *srf = TTF_RenderUTF8_Blended(handle, text, color);
+ if (!srf) {
+ throw std::runtime_error(TTF_GetError());
+ }
+ Texture tex;
+ tex.Bind();
+ tex.Data(*srf, false);
+ tex.FilterLinear();
+ SDL_FreeSurface(srf);
+ return tex;
+}
+
+
+void Format::ReadPixelFormat(const SDL_PixelFormat &fmt) {
+ if (fmt.BytesPerPixel == 4) {
+ if (fmt.Amask == 0xFF) {
+ if (fmt.Rmask == 0xFF00) {
+ format = GL_BGRA;
+ } else {
+ format = GL_RGBA;
+ }
+ type = GL_UNSIGNED_INT_8_8_8_8;
+ } else {
+ if (fmt.Rmask == 0xFF) {
+ format = GL_RGBA;
+ } else {
+ format = GL_BGRA;
+ }
+ type = GL_UNSIGNED_INT_8_8_8_8_REV;
+ }
+ internal = GL_RGBA8;
+ } else {
+ if (fmt.Rmask == 0xFF) {
+ format = GL_RGB;
+ } else {
+ format = GL_BGR;
+ }
+ type = GL_UNSIGNED_BYTE;
+ internal = GL_RGB8;
+ }
+}
+
+
+Texture::Texture()
+: handle(0)
+, width(0)
+, height(0) {
+ glGenTextures(1, &handle);
+}
+
+Texture::~Texture() {
+ if (handle != 0) {
+ glDeleteTextures(1, &handle);
+ }
+}
+
+Texture::Texture(Texture &&other) noexcept
+: handle(other.handle) {
+ other.handle = 0;
+ width = other.width;
+ height = other.height;
+}
+
+Texture &Texture::operator =(Texture &&other) noexcept {
+ std::swap(handle, other.handle);
+ width = other.width;
+ height = other.height;
+ return *this;
+}
+
+
+void Texture::Bind() noexcept {
+ glBindTexture(GL_TEXTURE_2D, handle);
+}
+
+namespace {
+ bool ispow2(unsigned int i) {
+ // don't care about i == 0 here
+ return !(i & (i - 1));
+ }
+}
+
+void Texture::Data(const SDL_Surface &srf, bool pad2) noexcept {
+ Format format;
+ format.ReadPixelFormat(*srf.format);
+
+ if (!pad2 || (ispow2(srf.w) && ispow2(srf.h))) {
+ int align = UnpackAlignmentFromPitch(srf.pitch);
+
+ int pitch = (srf.w * srf.format->BytesPerPixel + align - 1) / align * align;
+ if (srf.pitch - pitch >= align) {
+ UnpackRowLength(srf.pitch / srf.format->BytesPerPixel);
+ } else {
+ UnpackRowLength(0);
+ }
+
+ Data(srf.w, srf.h, format, srf.pixels);
+
+ UnpackRowLength(0);
+ } else if (srf.w > (1 << 30) || srf.h > (1 << 30)) {
+#ifndef NDEBUG
+ throw std::runtime_error("texture too large");
+#endif
+ } else {
+ GLsizei width = 1, height = 1;
+ while (width < srf.w) {
+ width <<= 1;
+ }
+ while (height < srf.h) {
+ height <<= 1;
+ }
+ size_t pitch = width * srf.format->BytesPerPixel;
+ size_t size = pitch * height;
+ size_t row_pad = pitch - srf.pitch;
+ std::unique_ptr<unsigned char[]> data(new unsigned char[size]);
+ unsigned char *src = reinterpret_cast<unsigned char *>(srf.pixels);
+ unsigned char *dst = data.get();
+ for (int row = 0; row < srf.h; ++row) {
+ std::memcpy(dst, src, srf.pitch);
+ src += srf.pitch;
+ dst += srf.pitch;
+ std::memset(dst, 0, row_pad);
+ dst += row_pad;
+ }
+ std::memset(dst, 0, (height - srf.h) * pitch);
+ UnpackAlignmentFromPitch(pitch);
+ Data(width, height, format, data.get());
+ }
+
+ UnpackAlignment(4);
+}
+
+void Texture::Data(GLsizei w, GLsizei h, const Format &format, GLvoid *data) noexcept {
+ glTexImage2D(
+ GL_TEXTURE_2D,
+ 0, format.internal,
+ w, h,
+ 0,
+ format.format, format.type,
+ data
+ );
+ width = w;
+ height = h;
+}
+
+
+void Texture::FilterNearest() noexcept {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+}
+
+void Texture::FilterLinear() noexcept {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+}
+
+void Texture::FilterTrilinear() noexcept {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+ glGenerateMipmap(GL_TEXTURE_2D);
+}
+
+
+void Texture::UnpackAlignment(GLint i) noexcept {
+ glPixelStorei(GL_UNPACK_ALIGNMENT, i);
+}
+
+int Texture::UnpackAlignmentFromPitch(int pitch) noexcept {
+ int align = 8;
+ while (pitch % align) {
+ align >>= 1;
+ }
+ UnpackAlignment(align);
+ return align;
+}
+
+void Texture::UnpackRowLength(GLint i) noexcept {
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, i);
+}
+
+}
+#include "BlendedSprite.hpp"
#include "BlockLighting.hpp"
#include "DirectionalLighting.hpp"
#include "Program.hpp"
#include "Shader.hpp"
+#include "Texture.hpp"
#include "../app/init.hpp"
#include <algorithm>
void BlockLighting::Activate() noexcept {
GLContext::EnableDepthTest();
GLContext::EnableBackfaceCulling();
+ GLContext::DisableAlphaBlending();
program.Use();
}
SetM(m);
}
+
+BlendedSprite::BlendedSprite()
+: program()
+, vp(1.0f)
+, mvp_handle(0)
+, sampler_handle(0) {
+ program.LoadShader(
+ GL_VERTEX_SHADER,
+ "#version 330 core\n"
+ "layout(location = 0) in vec3 vtx_position;\n"
+ "layout(location = 1) in vec2 vtx_tex_uv;\n"
+ "uniform mat4 MVP;\n"
+ "out vec2 frag_tex_uv;\n"
+ "void main() {\n"
+ "gl_Position = MVP * vec4(vtx_position, 1);\n"
+ "frag_tex_uv = vtx_tex_uv;\n"
+ "}\n"
+ );
+ program.LoadShader(
+ GL_FRAGMENT_SHADER,
+ "#version 330 core\n"
+ "in vec2 frag_tex_uv;\n"
+ "uniform sampler2D tex_sampler;\n"
+ "out vec4 color;\n"
+ "void main() {\n"
+ "color = texture(tex_sampler, frag_tex_uv);\n"
+ "}\n"
+ );
+ program.Link();
+ if (!program.Linked()) {
+ program.Log(std::cerr);
+ throw std::runtime_error("link program");
+ }
+
+ mvp_handle = program.UniformLocation("MVP");
+ sampler_handle = program.UniformLocation("tex_sampler");
+}
+
+
+void BlendedSprite::Activate() noexcept {
+ GLContext::EnableAlphaBlending();
+ program.Use();
+}
+
+void BlendedSprite::SetM(const glm::mat4 &m) noexcept {
+ glm::mat4 mvp(vp * m);
+ glUniformMatrix4fv(mvp_handle, 1, GL_FALSE, &mvp[0][0]);
+}
+
+void BlendedSprite::SetProjection(const glm::mat4 &p) noexcept {
+ projection = p;
+ vp = p * view;
+}
+
+void BlendedSprite::SetView(const glm::mat4 &v) noexcept {
+ view = v;
+ vp = projection * v;
+}
+
+void BlendedSprite::SetVP(const glm::mat4 &v, const glm::mat4 &p) noexcept {
+ projection = p;
+ view = v;
+ vp = p * v;
+}
+
+void BlendedSprite::SetMVP(const glm::mat4 &m, const glm::mat4 &v, const glm::mat4 &p) noexcept {
+ SetVP(v, p);
+ SetM(m);
+}
+
+void BlendedSprite::SetTexture(Texture &tex) noexcept {
+ glActiveTexture(GL_TEXTURE0);
+ tex.Bind();
+ glUniform1i(sampler_handle, 0);
+}
+
}
--- /dev/null
+#ifndef BLANK_MODEL_SPRITEMODEL_HPP_
+#define BLANK_MODEL_SPRITEMODEL_HPP_
+
+#include <vector>
+#include <GL/glew.h>
+#include <glm/glm.hpp>
+
+
+namespace blank {
+
+class SpriteModel {
+
+public:
+ using Position = glm::vec3;
+ using TexCoord = glm::vec2;
+ using Index = unsigned short;
+
+ using Positions = std::vector<Position>;
+ using TexCoords = std::vector<TexCoord>;
+ using Indices = std::vector<Index>;
+
+public:
+ Positions vertices;
+ TexCoords coords;
+ Indices indices;
+
+public:
+ SpriteModel() noexcept;
+ ~SpriteModel() noexcept;
+
+ SpriteModel(const SpriteModel &) = delete;
+ SpriteModel &operator =(const SpriteModel &) = delete;
+
+ void Invalidate() noexcept { dirty = true; }
+
+ void Clear() noexcept;
+ void Reserve(int vtx_count, int idx_count);
+
+ void LoadRect(
+ float w, float h,
+ const glm::vec2 &pivot = glm::vec2(0.0f),
+ const glm::vec2 &tex_begin = glm::vec2(0.0f),
+ const glm::vec2 &tex_end = glm::vec2(1.0f, 1.0f)
+ );
+
+ void Draw() noexcept;
+
+private:
+ void Update() noexcept;
+
+private:
+ enum Attribute {
+ ATTRIB_VERTEX,
+ ATTRIB_TEXCOORD,
+ ATTRIB_INDEX,
+ ATTRIB_COUNT,
+ };
+
+ GLuint va;
+ GLuint handle[ATTRIB_COUNT];
+ bool dirty;
+
+};
+
+}
+
+#endif
#include "BlockModel.hpp"
#include "Model.hpp"
#include "OutlineModel.hpp"
+#include "SpriteModel.hpp"
#include <iostream>
);
}
+
+SpriteModel::SpriteModel() noexcept
+: vertices()
+, coords()
+, indices()
+, va(0)
+, handle{}
+, dirty(false) {
+ glGenVertexArrays(1, &va);
+ glGenBuffers(ATTRIB_COUNT, handle);
+}
+
+SpriteModel::~SpriteModel() noexcept {
+ glDeleteBuffers(ATTRIB_COUNT, handle);
+ glDeleteVertexArrays(1, &va);
+}
+
+
+void SpriteModel::Clear() noexcept {
+ vertices.clear();
+ coords.clear();
+ indices.clear();
+ Invalidate();
+}
+
+void SpriteModel::Reserve(int v, int i) {
+ vertices.reserve(v);
+ coords.reserve(v);
+ indices.reserve(i);
+}
+
+
+void SpriteModel::LoadRect(
+ float w, float h,
+ const glm::vec2 &pivot,
+ const glm::vec2 &tex_begin,
+ const glm::vec2 &tex_end
+) {
+ Clear();
+ Reserve(4, 6);
+
+ vertices.emplace_back( -pivot.x, -pivot.y, 0.0f);
+ vertices.emplace_back(w-pivot.x, -pivot.y, 0.0f);
+ vertices.emplace_back( -pivot.x, h-pivot.y, 0.0f);
+ vertices.emplace_back(w-pivot.x, h-pivot.y, 0.0f);
+
+ coords.emplace_back(tex_begin.x, tex_begin.y);
+ coords.emplace_back(tex_end.x, tex_begin.y);
+ coords.emplace_back(tex_begin.x, tex_end.y);
+ coords.emplace_back(tex_end.x, tex_end.y);
+
+ indices.assign({ 0, 2, 1, 1, 2, 3 });
+
+ Invalidate();
+}
+
+
+void SpriteModel::Update() noexcept {
+ glBindBuffer(GL_ARRAY_BUFFER, handle[ATTRIB_VERTEX]);
+ glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Position), vertices.data(), GL_STATIC_DRAW);
+ glEnableVertexAttribArray(ATTRIB_VERTEX);
+ glVertexAttribPointer(
+ ATTRIB_VERTEX, // location (for shader)
+ 3, // size
+ GL_FLOAT, // type
+ GL_FALSE, // normalized
+ 0, // stride
+ nullptr // offset
+ );
+
+#ifndef NDEBUG
+ if (coords.size() < vertices.size()) {
+ std::cerr << "SpriteModel: not enough coords!" << std::endl;
+ coords.resize(vertices.size(), { 1, 1 });
+ }
+#endif
+ glBindBuffer(GL_ARRAY_BUFFER, handle[ATTRIB_TEXCOORD]);
+ glBufferData(GL_ARRAY_BUFFER, coords.size() * sizeof(TexCoord), coords.data(), GL_STATIC_DRAW);
+ glEnableVertexAttribArray(ATTRIB_TEXCOORD);
+ glVertexAttribPointer(
+ ATTRIB_TEXCOORD, // location (for shader)
+ 2, // size
+ GL_FLOAT, // type
+ GL_FALSE, // normalized
+ 0, // stride
+ nullptr // offset
+ );
+
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, handle[ATTRIB_INDEX]);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(Index), indices.data(), GL_STATIC_DRAW);
+
+ dirty = false;
+}
+
+
+void SpriteModel::Draw() noexcept {
+ glBindVertexArray(va);
+
+ if (dirty) {
+ Update();
+ }
+
+ glDrawElements(
+ GL_TRIANGLES, // how
+ indices.size(), // count
+ GL_UNSIGNED_SHORT, // type
+ nullptr // offset
+ );
+}
+
}
void HUD::Render(DirectionalLighting &program) noexcept {
+ program.SetLightDirection({ 1.0f, 3.0f, 5.0f });
+ // disable distance fog
+ program.SetFogDensity(0.0f);
+ GLContext::ClearDepthBuffer();
+
+ program.SetVP(view, projection);
+
if (block_visible) {
- program.SetLightDirection({ 1.0f, 3.0f, 5.0f });
- // disable distance fog
- program.SetFogDensity(0.0f);
- GLContext::ClearDepthBuffer();
- program.SetMVP(block_transform, view, projection);
+ program.SetM(block_transform);
block.Draw();
- program.SetM(crosshair_transform);
- crosshair.Draw();
}
+
+ program.SetM(crosshair_transform);
+ crosshair.Draw();
}