rpcs3/rpcs3/Emu/RSX/GL/GLTextOut.h

214 lines
4.9 KiB
C++

#pragma once
#include "util/types.hpp"
#include "GLHelpers.h"
#include "glutils/vao.hpp"
#include "../Common/TextGlyphs.h"
#include <string>
#include <vector>
#include <unordered_map>
namespace gl
{
class text_writer
{
private:
gl::glsl::program m_program;
gl::glsl::shader m_vs;
gl::glsl::shader m_fs;
gl::vao m_vao;
gl::buffer m_text_buffer;
std::unordered_map<u8, std::pair<u32, u32>> m_offsets;
bool initialized = false;
bool enabled = false;
f32 m_scale = 1.0f;
void init_program()
{
std::string vs =
{
"#version 420\n"
"#extension GL_ARB_shader_draw_parameters: enable\n"
"layout(location=0) in vec2 pos;\n"
"uniform vec2 offsets[255];\n"
"uniform vec2 scale;\n"
"\n"
"void main()\n"
"{\n"
" vec2 offset = offsets[gl_DrawIDARB];\n"
" gl_Position = vec4(pos, 0., 1.);\n"
" gl_Position.xy = gl_Position.xy * scale + offset;\n"
"}\n"
};
std::string fs =
{
"#version 420\n"
"layout(location=0) out vec4 col0;\n"
"uniform vec4 draw_color;\n"
"\n"
"void main()\n"
"{\n"
" col0 = draw_color;\n"
"}\n"
};
m_fs.create(::glsl::program_domain::glsl_fragment_program, fs);
m_fs.compile();
m_vs.create(::glsl::program_domain::glsl_vertex_program, vs);
m_vs.compile();
m_program.create();
m_program.attach(m_vs);
m_program.attach(m_fs);
m_program.link();
}
void load_program(gl::command_context& cmd, float scale_x, float scale_y, float *offsets, usz nb_offsets, color4f color)
{
float scale[] = { scale_x, scale_y };
cmd->use_program(m_program.id());
m_program.uniforms["draw_color"] = color;
glProgramUniform2fv(m_program.id(), m_program.uniforms["offsets"].location(), static_cast<GLsizei>(nb_offsets), offsets);
glProgramUniform2fv(m_program.id(), m_program.uniforms["scale"].location(), 1, scale);
}
public:
text_writer() = default;
~text_writer() = default;
void init()
{
GlyphManager glyph_source;
auto points = glyph_source.generate_point_map();
const usz buffer_size = points.size() * sizeof(GlyphManager::glyph_point);
m_text_buffer.create(gl::buffer::target::array, buffer_size, points.data(), gl::buffer::memory_type::host_visible);
m_offsets = glyph_source.get_glyph_offsets();
//Init VAO
int old_vao;
glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &old_vao);
m_vao.create();
m_vao.bind();
//This is saved as part of the bound VAO's state
m_vao.array_buffer = m_text_buffer;
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, false, sizeof(GlyphManager::glyph_point), 0);
glBindVertexArray(old_vao);
init_program();
initialized = true;
}
void set_enabled(bool state)
{
enabled = state;
}
bool is_enabled()
{
return enabled;
}
void print_text(gl::command_context& cmd, int x, int y, int target_w, int target_h, const std::string &text, color4f color = { 0.3f, 1.f, 0.3f, 1.f })
{
if (!enabled) return;
ensure(initialized);
std::vector<GLint> offsets;
std::vector<GLsizei> counts;
std::vector<float> shader_offsets;
char *s = const_cast<char *>(text.c_str());
//Y is in raster coordinates: convert to bottom-left origin
y = (static_cast<int>(target_h / m_scale) - y - 16);
//Compress [0, w] and [0, h] into range [-1, 1]
float scale_x = m_scale * 2.f / target_w;
float scale_y = m_scale * 2.f / target_h;
float base_offset = 0.f;
shader_offsets.reserve(text.length() * 2);
while (u8 offset = static_cast<u8>(*s))
{
bool to_draw = false; //Can be false for space or unsupported characters
auto o = m_offsets.find(offset);
if (o != m_offsets.end())
{
if (o->second.second > 0)
{
to_draw = true;
offsets.push_back(o->second.first);
counts.push_back(o->second.second);
}
}
if (to_draw)
{
//Generate a scale_offset pair for this entry
float offset_x = scale_x * (x + base_offset);
offset_x -= 1.f;
float offset_y = scale_y * y;
offset_y -= 1.f;
shader_offsets.push_back(offset_x);
shader_offsets.push_back(offset_y);
}
base_offset += 9.f;
s++;
}
//TODO: Add drop shadow if deemed necessary for visibility
int old_vao;
glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &old_vao);
load_program(cmd, scale_x, scale_y, shader_offsets.data(), counts.size(), color);
cmd->clip_planes(GL_NONE);
m_vao.bind();
glMultiDrawArrays(GL_POINTS, offsets.data(), counts.data(), static_cast<GLsizei>(counts.size()));
glBindVertexArray(old_vao);
}
void close()
{
if (initialized)
{
m_text_buffer.remove();
m_vao.remove();
m_program.remove();
m_fs.remove();
m_vs.remove();
initialized = false;
}
}
void set_scale(double scale)
{
// Restrict scale to 2. The dots are gonna be too sparse otherwise.
m_scale = std::min(static_cast<f32>(scale), 2.0f);
}
};
}