1 // Copyright 2015-2017 the openage authors. See copying.md for legal info.
2
3 #include "glyph_atlas.h"
4
5 #include <functional>
6 #include <typeindex>
7 #include <cstring>
8 #include <epoxy/gl.h>
9
10 #include "../../error/error.h"
11 #include "../../util/hash.h"
12
13 namespace openage {
14 namespace renderer {
15
Shelf(int y_position,int width,int height)16 GlyphAtlas::Shelf::Shelf(int y_position, int width, int height)
17 :
18 y_position{y_position},
19 width{width},
20 height{height},
21 remaining_width{width} {
22 // Empty
23 }
24
check_fits(int required_width,int required_height)25 bool GlyphAtlas::Shelf::check_fits(int required_width, int required_height) {
26 if (required_width > this->remaining_width || required_height > this->height) {
27 return false;
28 }
29 return true;
30 }
31
reserve(int required_width,int)32 int GlyphAtlas::Shelf::reserve(int required_width, int/* required_height*/) {
33 int x_position = this->width - this->remaining_width;
34 this->remaining_width -= required_width;
35 return x_position;
36 }
37
GlyphAtlas(int width,int height)38 GlyphAtlas::GlyphAtlas(int width, int height)
39 :
40 width{width},
41 height{height},
42 is_dirty{false},
43 dirty_area{width, height, 0, 0},
44 texture_id{0} {
45
46 this->buffer = std::unique_ptr<unsigned char>(new unsigned char[this->width * this->height]);
47 memset(this->buffer.get(), 0, this->width * this->height);
48
49 glGenTextures(1, &this->texture_id);
50 glBindTexture(GL_TEXTURE_2D, this->texture_id);
51 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
52 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
53 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
54 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
55
56 glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, this->width, this->height, 0, GL_RED, GL_UNSIGNED_BYTE, this->buffer.get());
57 }
58
~GlyphAtlas()59 GlyphAtlas::~GlyphAtlas() {
60 if (this->texture_id) {
61 glDeleteTextures(1, &this->texture_id);
62 }
63 }
64
bind(int unit)65 void GlyphAtlas::bind(int unit) {
66 glActiveTexture(GL_TEXTURE0 + unit);
67 glBindTexture(GL_TEXTURE_2D, this->texture_id);
68
69 if (this->is_dirty) {
70 // We update the entire width of the atlas because of our buffer memory alignment
71 glTexSubImage2D(GL_TEXTURE_2D, 0,
72 0, this->dirty_area.y1, this->width, this->dirty_area.y2 - this->dirty_area.y1,
73 GL_RED, GL_UNSIGNED_BYTE, this->buffer.get() + this->width * this->dirty_area.y1);
74
75 this->is_dirty = false;
76 this->dirty_area = {this->width, this->height, 0, 0};
77 }
78 }
79
get(Font * font,codepoint_t codepoint)80 GlyphAtlas::Entry GlyphAtlas::get(Font *font, codepoint_t codepoint) {
81 size_t key = this->get_cache_key(font, codepoint);
82 auto it = this->glyphs.find(key);
83 if (it != this->glyphs.end()) {
84 return it->second;
85 }
86
87 Glyph glyph;
88 std::unique_ptr<unsigned char[]> image = font->load_glyph(codepoint, glyph);
89 return this->set(key, glyph, image.get());
90 }
91
get_cache_key(Font * font,codepoint_t codepoint) const92 size_t GlyphAtlas::get_cache_key(Font *font, codepoint_t codepoint) const {
93 size_t hash = std::hash<std::type_index>()(std::type_index(typeid(openage::renderer::GlyphAtlas)));
94 hash = openage::util::hash_combine(hash, std::hash<font_description>()(font->description));
95 hash = openage::util::hash_combine(hash, std::hash<codepoint_t>()(codepoint));
96 return hash;
97 }
98
set(size_t key,const Glyph & glyph,const unsigned char * image)99 GlyphAtlas::Entry GlyphAtlas::set(size_t key, const Glyph &glyph, const unsigned char *image) {
100 // Give the glyphs a 1px padding in the atlas
101 int required_width = glyph.width + 1;
102 int required_height = glyph.height + 1;
103
104 if (this->shelves.empty()) {
105 this->shelves.emplace_back(0, this->width, required_height);
106 }
107
108 for (auto &shelf : this->shelves) {
109 if (shelf.check_fits(required_width, required_height)) {
110 int x_pos = shelf.reserve(required_width, required_height);
111 int y_pos = shelf.y_position;
112
113 // Create a new entry and insert it
114 GlyphAtlas::Entry entry;
115 entry.glyph = glyph;
116 entry.u0 = static_cast<float>(x_pos)/this->width;
117 entry.v0 = static_cast<float>(y_pos)/this->height;
118 entry.u1 = static_cast<float>(x_pos + glyph.width)/this->width;
119 entry.v1 = static_cast<float>(y_pos + glyph.height)/this->height;
120
121 for (unsigned int i = 0; i < glyph.height; i++) {
122 memcpy(
123 this->buffer.get() + ((y_pos + i) * this->width + x_pos),
124 image + (i * glyph.width),
125 glyph.width * sizeof(unsigned char)
126 );
127 }
128 this->update_dirty_area(x_pos, y_pos, glyph.width, glyph.height);
129
130 this->glyphs.emplace(key, entry);
131 return entry;
132 }
133 }
134
135 GlyphAtlas::Shelf last_shelf = this->shelves.back();
136 if (this->height > (last_shelf.y_position + last_shelf.height + required_height)) {
137 this->shelves.emplace_back(last_shelf.y_position + last_shelf.height, this->width, required_height);
138
139 return this->set(key, glyph, image);
140 }
141
142 // TODO: figure out a clean way to resize the atlas
143 throw Error(MSG(err) << "Not enough space in the atlas");
144 }
145
update_dirty_area(int x,int y,int width,int height)146 void GlyphAtlas::update_dirty_area(int x, int y, int width, int height) {
147 this->is_dirty = true;
148 this->dirty_area = {
149 std::min(this->dirty_area.x1, x),
150 std::min(this->dirty_area.y1, y),
151 std::max(this->dirty_area.x2, x + width),
152 std::max(this->dirty_area.y2, y + height)};
153 }
154
155 }} // openage::renderer
156