/* * Copyright (C) 2014-2016 PCSX2 Dev Team * Copyright (C) 2016-2016 Jason Brown * * This Program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This Program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Make; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA USA. * http://www.gnu.org/copyleft/gpl.html * */ #include "stdafx.h" #include "GSdx.h" #include "GSOsdManager.h" #ifdef _WIN32 #include "resource.h" #endif void GSOsdManager::LoadFont() { FT_Error error = FT_New_Face(m_library, theApp.GetConfigS("osd_fontname").c_str(), 0, &m_face); if (error) { FT_Error error_load_res = 1; if(theApp.LoadResource(IDR_FONT_ROBOTO, resource_data_buffer)) error_load_res = FT_New_Memory_Face(m_library, (const FT_Byte*)resource_data_buffer.data(), resource_data_buffer.size(), 0, &m_face); if (error_load_res) { m_face = NULL; fprintf(stderr, "Failed to init freetype face from external and internal resource\n"); if(error == FT_Err_Unknown_File_Format) fprintf(stderr, "\tFreetype unknown file format for external file\n"); return; } } LoadSize(); } void GSOsdManager::LoadSize() { if (!m_face) return; FT_Error error = FT_Set_Pixel_Sizes(m_face, 0, m_size);; if (error) { fprintf(stderr, "Failed to init the face size\n"); return; } /* This is not exact, I'm sure there's some convoluted way to determine these * from FreeType but they don't make it easy. */ m_atlas_w = m_size * 96; // random guess m_atlas_h = m_size + 10; // another random guess } GSOsdManager::GSOsdManager() : m_atlas_h(0) , m_atlas_w(0) , m_max_width(0) , m_onscreen_messages(0) , m_texture_dirty(true) { m_monitor_enabled = theApp.GetConfigB("osd_monitor_enabled"); m_log_enabled = theApp.GetConfigB("osd_log_enabled"); m_size = std::max(1, std::min(theApp.GetConfigI("osd_fontsize"), 100)); m_opacity = std::max(0, std::min(theApp.GetConfigI("osd_color_opacity"), 100)); m_log_timeout = std::max(2, std::min(theApp.GetConfigI("osd_log_timeout"), 10)); m_max_onscreen_messages = std::max(1, std::min(theApp.GetConfigI("osd_max_log_messages"), 20)); int r = std::max(0, std::min(theApp.GetConfigI("osd_color_r"), 255)); int g = std::max(0, std::min(theApp.GetConfigI("osd_color_g"), 255)); int b = std::max(0, std::min(theApp.GetConfigI("osd_color_b"), 255)); m_color = r | (g << 8) | (b << 16) | (255 << 24); if (FT_Init_FreeType(&m_library)) { m_face = NULL; fprintf(stderr, "Failed to init the freetype library\n"); return; } LoadFont(); /* The space character's width is used in GeneratePrimitives() */ AddGlyph(' '); } GSOsdManager::~GSOsdManager() { FT_Done_FreeType(m_library); } GSVector2i GSOsdManager::get_texture_font_size() { return GSVector2i(m_atlas_w, m_atlas_h); } void GSOsdManager::upload_texture_atlas(GSTexture* t) { if (!m_face) return; if (m_char_info.size() > 96) // we only reserved space for this many glyphs fprintf(stderr, "More than 96 glyphs needed for OSD"); // This can be sped up a bit by only uploading new glyphs int x = 0; for(auto &pair : m_char_info) { if(FT_Load_Char(m_face, pair.first, FT_LOAD_RENDER)) { fprintf(stderr, "failed to load char U%d\n", (int)pair.first); continue; } // Size of char pair.second.ax = m_face->glyph->advance.x >> 6; pair.second.ay = m_face->glyph->advance.y >> 6; pair.second.bw = m_face->glyph->bitmap.width; pair.second.bh = m_face->glyph->bitmap.rows; pair.second.bl = m_face->glyph->bitmap_left; pair.second.bt = m_face->glyph->bitmap_top; GSVector4i r(x, 0, x+pair.second.bw, pair.second.bh); if (r.width()) t->Update(r, m_face->glyph->bitmap.buffer, m_face->glyph->bitmap.pitch); if (r.width() > m_max_width) m_max_width = r.width(); pair.second.tx = (float)x / m_atlas_w; pair.second.ty = (float)pair.second.bh / m_atlas_h; pair.second.tw = (float)pair.second.bw / m_atlas_w; x += pair.second.bw; } m_texture_dirty = false; } #if __GNUC__ < 5 || ( __GNUC__ == 5 && __GNUC_MINOR__ < 4 ) /* This is dumb in that it doesn't check for malformed UTF8. This function * is not expected to operate on user input, but only on compiled in strings */ void dumb_utf8_to_utf32(const char *utf8, char32_t *utf32, unsigned size) { while(*utf8 && --size) { if((*utf8 & 0xF1) == 0xF0) { *utf32++ = (utf8[0] & 0x07) << 18 | (utf8[1] & 0x3F) << 12 | (utf8[2] & 0x3F) << 6 | utf8[3] & 0x3F; utf8 += 4; } else if((*utf8 & 0xF0) == 0xE0) { *utf32++ = (utf8[0] & 0x0F) << 12 | (utf8[1] & 0x3F) << 6 | utf8[2] & 0x3F; utf8 += 3; } else if((*utf8 & 0xE0) == 0xC0) { *utf32++ = (utf8[0] & 0x1F) << 6 | utf8[1] & 0x3F; utf8 += 2; } else if((*utf8 & 0x80) == 0x00) { *utf32++ = utf8[0] & 0x7F; utf8 += 1; } } if(size) *utf32 = *utf8; // Copy NUL char } #endif void GSOsdManager::AddGlyph(char32_t codepoint) { if (!m_face) return; if(m_char_info.count(codepoint) == 0) { m_texture_dirty = true; m_char_info[codepoint]; // add it if(FT_HAS_KERNING(m_face)) { FT_UInt new_glyph = FT_Get_Char_Index(m_face, codepoint); for(auto pair : m_char_info) { FT_Vector delta; FT_UInt glyph_index = FT_Get_Char_Index(m_face, pair.first); FT_Get_Kerning(m_face, glyph_index, new_glyph, FT_KERNING_DEFAULT, &delta); m_kern_info[std::make_pair(pair.first, codepoint)] = delta.x >> 6; } } } } void GSOsdManager::Log(const char *utf8) { if(!m_log_enabled) return; #if __GNUC__ < 5 || ( __GNUC__ == 5 && __GNUC_MINOR__ < 4 ) char32_t buffer[256]; dumb_utf8_to_utf32(utf8, buffer, countof(buffer)); for(char32_t* c = buffer; *c; ++c) AddGlyph(*c); #else #if _MSC_VER == 1900 std::wstring_convert, unsigned int> conv; #else std::wstring_convert, char32_t> conv; #endif std::u32string buffer = conv.from_bytes(utf8); for(auto const &c : buffer) AddGlyph(c); #endif m_onscreen_messages++; m_log.push_back(log_info{buffer, std::chrono::system_clock::time_point()}); } void GSOsdManager::Monitor(const char *key, const char *value) { if(!m_monitor_enabled) return; if(value && *value) { #if __GNUC__ < 5 || ( __GNUC__ == 5 && __GNUC_MINOR__ < 4 ) char32_t buffer[256], vbuffer[256]; dumb_utf8_to_utf32(key, buffer, countof(buffer)); dumb_utf8_to_utf32(value, vbuffer, countof(vbuffer)); for(char32_t* c = buffer; *c; ++c) AddGlyph(*c); for(char32_t* c = vbuffer; *c; ++c) AddGlyph(*c); #else #if _MSC_VER == 1900 std::wstring_convert, unsigned int> conv; #else std::wstring_convert, char32_t> conv; #endif std::u32string buffer = conv.from_bytes(key); std::u32string vbuffer = conv.from_bytes(value); for(auto const &c : buffer) AddGlyph(c); for(auto const &c : vbuffer) AddGlyph(c); #endif m_monitor[buffer] = vbuffer; } else { #if __GNUC__ < 5 || ( __GNUC__ == 5 && __GNUC_MINOR__ < 4 ) char32_t buffer[256]; dumb_utf8_to_utf32(key, buffer, countof(buffer)); #else #if _MSC_VER == 1900 std::wstring_convert, unsigned int> conv; #else std::wstring_convert, char32_t> conv; #endif std::u32string buffer = conv.from_bytes(key); #endif m_monitor.erase(buffer); } } void GSOsdManager::RenderGlyph(GSVertexPT1* dst, const glyph_info g, float x, float y, uint32 color) { float x2 = x + g.bl * (2.0f/m_real_size.x); float y2 = -y - g.bt * (2.0f/m_real_size.y); float w = g.bw * (2.0f/m_real_size.x); float h = g.bh * (2.0f/m_real_size.y); dst->p = GSVector4(x2 , -y2 , 0.0f, 1.0f); dst->t = GSVector2(g.tx , 0.0f); dst->c = color; ++dst; dst->p = GSVector4(x2 + w, -y2 , 0.0f, 1.0f); dst->t = GSVector2(g.tx + g.tw, 0.0f); dst->c = color; ++dst; dst->p = GSVector4(x2 , -y2 - h, 0.0f, 1.0f); dst->t = GSVector2(g.tx , g.ty); dst->c = color; ++dst; dst->p = GSVector4(x2 + w, -y2 , 0.0f, 1.0f); dst->t = GSVector2(g.tx + g.tw, 0.0f); dst->c = color; ++dst; dst->p = GSVector4(x2 , -y2 - h, 0.0f, 1.0f); dst->t = GSVector2(g.tx , g.ty); dst->c = color; ++dst; dst->p = GSVector4(x2 + w, -y2 - h, 0.0f, 1.0f); dst->t = GSVector2(g.tx + g.tw, g.ty); dst->c = color; ++dst; } void GSOsdManager::RenderString(GSVertexPT1* dst, const std::u32string msg, float x, float y, uint32 color) { char32_t p = 0; for(const auto & c : msg) { if(p) { x += m_kern_info[std::make_pair(p, c)] * (2.0f/m_real_size.x); } RenderGlyph(dst, m_char_info[c], x, y, color); /* Advance the cursor to the start of the next character */ x += m_char_info[c].ax * (2.0f/m_real_size.x); y += m_char_info[c].ay * (2.0f/m_real_size.y); dst += 6; p = c; } } size_t GSOsdManager::Size() { size_t sum = 0; if(m_log_enabled) { float offset = 0; for(auto it = m_log.begin(); it != m_log.end(); ++it) { float y = 1 - ((m_size+2)*(it-m_log.begin()+1)) * (2.0f/m_real_size.y); if(y + offset < -1) break; std::chrono::duration elapsed; if(it->OnScreen.time_since_epoch().count() == 0) { elapsed = std::chrono::seconds(0); } else { elapsed = std::chrono::system_clock::now() - it->OnScreen; if(elapsed > std::chrono::seconds(m_log_timeout) || m_onscreen_messages > m_max_onscreen_messages) { continue; } } float ratio = (elapsed - std::chrono::seconds(m_log_timeout/2)).count() / std::chrono::seconds(m_log_timeout/2).count(); ratio = ratio > 1.0f ? 1.0f : ratio < 0.0f ? 0.0f : ratio; y += offset += ((m_size+2) * (2.0f/m_real_size.y)) * ratio; sum += it->msg.size(); } } if(m_monitor_enabled) { for(const auto &pair : m_monitor) { sum += pair.first.size(); sum += pair.second.size(); } } return sum * 6; } float GSOsdManager::StringSize(const std::u32string msg) { char32_t p = 0; float x = 0.0; for(auto c : msg) { if(p) { x += m_kern_info[std::make_pair(p, c)] * (2.0f/m_real_size.x); } /* Advance the cursor to the start of the next character */ x += m_char_info[c].ax * (2.0f/m_real_size.x); p = c; } return x; } size_t GSOsdManager::GeneratePrimitives(GSVertexPT1* dst, size_t count) { size_t drawn = 0; float opacity = m_opacity * 0.01f; if(m_log_enabled) { float offset = 0; for(auto it = m_log.begin(); it != m_log.end();) { float x = -1 + 8 * (2.0f/m_real_size.x); float y = 1 - ((m_size+2)*(it-m_log.begin()+1)) * (2.0f/m_real_size.y); if(y + offset < -1) break; if(it->OnScreen.time_since_epoch().count() == 0) it->OnScreen = std::chrono::system_clock::now(); std::chrono::duration elapsed = std::chrono::system_clock::now() - it->OnScreen; if(elapsed > std::chrono::seconds(m_log_timeout) || m_onscreen_messages > m_max_onscreen_messages) { m_onscreen_messages--; it = m_log.erase(it); continue; } if(it->msg.size() * 6 > count - drawn) break; float ratio = (elapsed - std::chrono::seconds(m_log_timeout/2)).count() / std::chrono::seconds(m_log_timeout/2).count(); ratio = ratio > 1.0f ? 1.0f : ratio < 0.0f ? 0.0f : ratio; y += offset += ((m_size+2) * (2.0f/m_real_size.y)) * ratio; uint32 color = m_color; ((uint8 *)&color)[3] = (uint8)(((uint8 *)&color)[3] * (1.0f - ratio) * opacity); RenderString(dst, it->msg, x, y, color); dst += it->msg.size() * 6; drawn += it->msg.size() * 6; ++it; } } if(m_monitor_enabled) { // pair.first is the key and second is the value and color // Since the monitor is right justified, but we render from left to right // we need to find the longest string float first_max = 0.0, second_max = 0.0; for(const auto &pair : m_monitor) { float first_len = StringSize(pair.first); float second_len = StringSize(pair.second); first_max = first_max < first_len ? first_len : first_max; second_max = second_max < second_len ? second_len : second_max; } size_t line = 1; for(const auto &pair : m_monitor) { if((pair.first.size() + pair.second.size()) * 6 > count - drawn) break; // Calculate where to start rendering from by taking the right most position 1.0 // and subtracting (going left) 8 scaled pixels for a margin, then subtracting // the size of the longest key and subtracting a scaled space and finally // subtracting the longest value float x = 1.0f - 8 * (2.0f/m_real_size.x) - first_max - m_char_info[' '].ax * (2.0f/m_real_size.x) - second_max; float y = -1.0f + ((m_size+2)*(2.0f/m_real_size.y)) * line++; uint32 color = m_color; ((uint8 *)&color)[3] = (uint8)(((uint8 *)&color)[3] * opacity); // Render the key RenderString(dst, pair.first, x, y, color); dst += pair.first.size() * 6; drawn += pair.first.size() * 6; // Calculate the position for the value x = 1.0f - 8 * (2.0f/m_real_size.x) - second_max; // Render the value RenderString(dst, pair.second, x, y, color); dst += pair.second.size() * 6; drawn += pair.second.size() * 6; } } return drawn; }