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