mirror of
https://github.com/PCSX2/pcsx2.git
synced 2026-09-24 05:25:50 +02:00
This pull request is for the pending reorganization of the folder structure on GSdx, making it better organized and easier to work with. Also remove unused GSTextureFX.cpp file.
448 lines
14 KiB
C++
448 lines
14 KiB
C++
/*
|
|
* 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"
|
|
|
|
void GSOsdManager::LoadFont() {
|
|
FT_Error error = FT_New_Face(m_library, theApp.GetConfigS("osd_fontname").c_str(), 0, &m_face);
|
|
if (error) {
|
|
m_face = NULL;
|
|
fprintf(stderr, "Failed to init the freetype face\n");
|
|
if(error == FT_Err_Unknown_File_Format)
|
|
fprintf(stderr, "\tFreetype unknown file format\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; // 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_log_enabled = theApp.GetConfigB("osd_log_enabled");
|
|
m_log_speed = std::max(2, std::min(theApp.GetConfigI("osd_log_speed"), 10));
|
|
m_monitor_enabled = theApp.GetConfigB("osd_monitor_enabled");
|
|
m_indicator_enabled = theApp.GetConfigB("osd_indicator_enabled");
|
|
m_osd_transparency = std::max(0, std::min(theApp.GetConfigI("osd_transparency"), 100));
|
|
m_max_onscreen_messages = theApp.GetConfigI("osd_max_log_messages");
|
|
m_size = theApp.GetConfigI("osd_fontsize");
|
|
|
|
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, uint32 color) {
|
|
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<std::codecvt_utf8<unsigned int>, unsigned int> conv;
|
|
#else
|
|
std::wstring_convert<std::codecvt_utf8<char32_t>, 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{color, buffer, std::chrono::system_clock::time_point()});
|
|
|
|
}
|
|
|
|
void GSOsdManager::Monitor(const char *key, const char *value, uint32 color) {
|
|
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<std::codecvt_utf8<unsigned int>, unsigned int> conv;
|
|
#else
|
|
std::wstring_convert<std::codecvt_utf8<char32_t>, 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] = std::make_pair(vbuffer, color);
|
|
} 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<std::codecvt_utf8<unsigned int>, unsigned int> conv;
|
|
#else
|
|
std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> conv;
|
|
#endif
|
|
std::u32string buffer = conv.from_bytes(key);
|
|
#endif
|
|
m_monitor.erase(buffer);
|
|
}
|
|
}
|
|
|
|
void GSOsdManager::Indicate(const std::string key, bool on) {
|
|
if(!m_indicator_enabled)
|
|
return;
|
|
|
|
m_indicator[key].on = on;
|
|
}
|
|
|
|
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, 0.0f);
|
|
dst->t = GSVector2(g.tx , 0.0f);
|
|
dst->c = color;
|
|
++dst;
|
|
dst->p = GSVector4(x2 + w, -y2 , 0.0f, 0.0f);
|
|
dst->t = GSVector2(g.tx + g.tw, 0.0f);
|
|
dst->c = color;
|
|
++dst;
|
|
dst->p = GSVector4(x2 , -y2 - h, 0.0f, 0.0f);
|
|
dst->t = GSVector2(g.tx , g.ty);
|
|
dst->c = color;
|
|
++dst;
|
|
dst->p = GSVector4(x2 + w, -y2 , 0.0f, 0.0f);
|
|
dst->t = GSVector2(g.tx + g.tw, 0.0f);
|
|
dst->c = color;
|
|
++dst;
|
|
dst->p = GSVector4(x2 , -y2 - h, 0.0f, 0.0f);
|
|
dst->t = GSVector2(g.tx , g.ty);
|
|
dst->c = color;
|
|
++dst;
|
|
dst->p = GSVector4(x2 + w, -y2 - h, 0.0f, 0.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<float> 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_speed) || m_onscreen_messages > m_max_onscreen_messages) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
float ratio = (elapsed - std::chrono::seconds(m_log_speed/2)).count() / std::chrono::seconds(m_log_speed/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.first.size();
|
|
}
|
|
}
|
|
|
|
if(m_indicator_enabled) {
|
|
sum += m_indicator.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 transparency = 1.0f - m_osd_transparency / 100.0f;
|
|
|
|
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<float> elapsed = std::chrono::system_clock::now() - it->OnScreen;
|
|
if(elapsed > std::chrono::seconds(m_log_speed) || 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_speed/2)).count() / std::chrono::seconds(m_log_speed/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 = it->color;
|
|
((uint8 *)&color)[3] = (uint8)(((uint8 *)&color)[3] * (1.0f - ratio) * transparency);
|
|
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);
|
|
|
|
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.first.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 = pair.second.second;
|
|
((uint8 *)&color)[3] = (uint8)(((uint8 *)&color)[3] * transparency);
|
|
|
|
// 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.first, x, y, color);
|
|
dst += pair.second.first.size() * 6;
|
|
drawn += pair.second.first.size() * 6;
|
|
}
|
|
}
|
|
|
|
if(m_indicator_enabled) {
|
|
for(const auto pair : m_indicator) {
|
|
float alpha = 1.0;
|
|
if(!pair.second.on) {
|
|
alpha = 0.25;
|
|
}
|
|
uint32 color = pair.second.color;
|
|
((uint8 *)&color)[3] = (uint8)(((uint8 *)&color)[3] * alpha * transparency);
|
|
RenderGlyph(dst, pair.second.glyph, pair.second.x, pair.second.y, color);
|
|
dst += 6;
|
|
drawn += 6;
|
|
}
|
|
}
|
|
|
|
return drawn;
|
|
}
|
|
|