279 lines
10 KiB
C++
279 lines
10 KiB
C++
//
|
|
// SuperTuxKart - a fun racing game with go-kart
|
|
// Copyright (C) 2020 SuperTuxKart-Team
|
|
//
|
|
// 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 3
|
|
// of the License, 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 this program; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
#ifndef SERVER_ONLY
|
|
|
|
#include "font/font_drawer.hpp"
|
|
#include "graphics/2dutils.hpp"
|
|
#include "graphics/central_settings.hpp"
|
|
#include "graphics/irr_driver.hpp"
|
|
#include "graphics/glwrap.hpp"
|
|
#include "graphics/texture_shader.hpp"
|
|
|
|
#include <S3DVertex.h>
|
|
#include <irrArray.h>
|
|
#include <map>
|
|
#include <memory>
|
|
|
|
// ============================================================================
|
|
class FontDrawerShader : public TextureShader<FontDrawerShader, 1, core::vector2df>
|
|
{
|
|
public:
|
|
GLuint m_vao, m_vbo, m_ibo;
|
|
FontDrawerShader()
|
|
{
|
|
loadProgram(OBJECT, GL_VERTEX_SHADER, "primitive2dlist.vert",
|
|
GL_FRAGMENT_SHADER, "colortexturedquad.frag");
|
|
assignUniforms("fullscreen");
|
|
assignSamplerNames(0, "tex", ST_BILINEAR_FILTERED);
|
|
glGenVertexArrays(1, &m_vao);
|
|
glGenBuffers(1, &m_vbo);
|
|
glGenBuffers(1, &m_ibo);
|
|
} // FontDrawerShader
|
|
~FontDrawerShader()
|
|
{
|
|
glDeleteVertexArrays(1, &m_vao);
|
|
glDeleteBuffers(1, &m_vbo);
|
|
glDeleteBuffers(1, &m_ibo);
|
|
}
|
|
}; //FontDrawerShader
|
|
|
|
// ============================================================================
|
|
std::unique_ptr<core::rect<s32> > g_clip;
|
|
// ============================================================================
|
|
std::map<video::ITexture*, std::vector<uint8_t> > g_glyphs;
|
|
// ============================================================================
|
|
bool g_batching = false;
|
|
// ----------------------------------------------------------------------------
|
|
void FontDrawer::startBatching()
|
|
{
|
|
g_batching = true;
|
|
} // startBatching
|
|
|
|
// ----------------------------------------------------------------------------
|
|
bool FontDrawer::isBatching()
|
|
{
|
|
return g_batching;
|
|
} // isBatching
|
|
|
|
// ----------------------------------------------------------------------------
|
|
void FontDrawer::endBatching()
|
|
{
|
|
g_batching = false;
|
|
draw();
|
|
} // endBatching
|
|
|
|
// ----------------------------------------------------------------------------
|
|
void FontDrawer::addGlyph(video::ITexture* texture,
|
|
const core::rect<float>& dest_rect,
|
|
const core::rect<s32>& source_rect,
|
|
const core::rect<s32>* clip_rect,
|
|
const video::SColor* color)
|
|
{
|
|
if (clip_rect)
|
|
g_clip.reset(new core::rect<s32>(*clip_rect));
|
|
else
|
|
g_clip.reset();
|
|
const float tex_h = (float)texture->getSize().Height;
|
|
const float tex_w = (float)texture->getSize().Width;
|
|
|
|
if (g_glyphs.find(texture) == g_glyphs.end())
|
|
texture->grab();
|
|
|
|
auto& glyph_data = g_glyphs[texture];
|
|
size_t stride = sizeof(video::S3DVertex);
|
|
if (CVS->isGLSL())
|
|
stride -= 16;
|
|
size_t old_size = glyph_data.size();
|
|
glyph_data.resize(old_size + stride * 4);
|
|
uint8_t* glyph_ptr = &glyph_data[old_size];
|
|
|
|
auto copy_glyph = [stride](const video::S3DVertex& v, uint8_t* glyph_ptr)
|
|
{
|
|
if (CVS->isGLSL())
|
|
{
|
|
size_t pos_size = sizeof(float) * 2;
|
|
memcpy(glyph_ptr, &v.Pos.X, pos_size);
|
|
memcpy(glyph_ptr + pos_size, &v.Color, stride - pos_size);
|
|
}
|
|
else
|
|
memcpy(glyph_ptr, &v.Pos.X, stride);
|
|
};
|
|
|
|
video::S3DVertex glyph = video::S3DVertex(
|
|
dest_rect.UpperLeftCorner.X, dest_rect.LowerRightCorner.Y, 0, 0, 0, 0,
|
|
color[1], source_rect.UpperLeftCorner.X / tex_w,
|
|
source_rect.LowerRightCorner.Y / tex_h);
|
|
copy_glyph(glyph, glyph_ptr);
|
|
glyph = video::S3DVertex(
|
|
dest_rect.UpperLeftCorner.X, dest_rect.UpperLeftCorner.Y, 0, 0, 0, 0,
|
|
color[0], source_rect.UpperLeftCorner.X / tex_w,
|
|
source_rect.UpperLeftCorner.Y / tex_h);
|
|
copy_glyph(glyph, glyph_ptr + stride);
|
|
glyph = video::S3DVertex(
|
|
dest_rect.LowerRightCorner.X, dest_rect.UpperLeftCorner.Y, 0, 0, 0, 0,
|
|
color[2], source_rect.LowerRightCorner.X / tex_w,
|
|
source_rect.UpperLeftCorner.Y / tex_h);
|
|
copy_glyph(glyph, glyph_ptr + stride * 2);
|
|
glyph = video::S3DVertex(
|
|
dest_rect.LowerRightCorner.X, dest_rect.LowerRightCorner.Y, 0, 0, 0, 0,
|
|
color[3], source_rect.LowerRightCorner.X / tex_w,
|
|
source_rect.LowerRightCorner.Y / tex_h);
|
|
copy_glyph(glyph, glyph_ptr + stride * 3);
|
|
} // addGlyph
|
|
|
|
// ----------------------------------------------------------------------------
|
|
void FontDrawer::draw()
|
|
{
|
|
if (g_batching || g_glyphs.empty())
|
|
return;
|
|
|
|
if (g_clip && !g_clip->isValid())
|
|
{
|
|
for (auto it = g_glyphs.begin(); it != g_glyphs.end();)
|
|
{
|
|
it->first->drop();
|
|
it = g_glyphs.erase(it);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (CVS->isGLSL())
|
|
{
|
|
FontDrawerShader::getInstance()->use();
|
|
FontDrawerShader::getInstance()->setUniforms(
|
|
core::vector2df(float(irr_driver->getActualScreenSize().Width),
|
|
float(irr_driver->getActualScreenSize().Height)));
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
}
|
|
|
|
if (g_clip)
|
|
{
|
|
const core::dimension2d<u32>& render_target_size =
|
|
irr_driver->getActualScreenSize();
|
|
s32 y = (s32)render_target_size.Height - g_clip->LowerRightCorner.Y;
|
|
core::rect<s32> r(g_clip->UpperLeftCorner.X, y,
|
|
g_clip->UpperLeftCorner.X + g_clip->getWidth(),
|
|
y + g_clip->getHeight());
|
|
irr_driver->getVideoDriver()->enableScissorTest(r);
|
|
}
|
|
else
|
|
irr_driver->getVideoDriver()->disableScissorTest();
|
|
|
|
for (auto& glyph : g_glyphs)
|
|
{
|
|
std::vector<uint16_t> indices;
|
|
std::vector<uint32_t> indices_32;
|
|
u32 idx = 0;
|
|
size_t stride = sizeof(video::S3DVertex);
|
|
if (CVS->isGLSL())
|
|
stride -= 16;
|
|
for (unsigned i = 0; i < glyph.second.size() / stride; i += 4)
|
|
{
|
|
if (idx >= 65536)
|
|
{
|
|
if (indices_32.empty())
|
|
{
|
|
for (auto& index : indices)
|
|
indices_32.push_back(index);
|
|
}
|
|
indices_32.push_back(idx++);
|
|
indices_32.push_back(idx++);
|
|
indices_32.push_back(idx++);
|
|
indices_32.push_back(indices_32[indices_32.size() - 3]);
|
|
indices_32.push_back(indices_32[indices_32.size() - 2]);
|
|
indices_32.push_back(idx++);
|
|
}
|
|
else
|
|
{
|
|
indices.push_back(idx++);
|
|
indices.push_back(idx++);
|
|
indices.push_back(idx++);
|
|
indices.push_back(indices[indices.size() - 3]);
|
|
indices.push_back(indices[indices.size() - 2]);
|
|
indices.push_back(idx++);
|
|
}
|
|
}
|
|
void* idx_data = indices_32.empty() ?
|
|
(void*)indices.data() : (void*)indices_32.data();
|
|
size_t idx_count = indices_32.empty() ? indices.size() : indices_32.size();
|
|
size_t idx_size = indices_32.empty() ?
|
|
idx_count * sizeof(u16) : idx_count * sizeof(u32);
|
|
if (CVS->isGLSL())
|
|
{
|
|
// Unfortunately we need to re-define the vao each time otherwise
|
|
// if texture is changed no glyph is drawn
|
|
glBindVertexArray(FontDrawerShader::getInstance()->m_vao);
|
|
glBindBuffer(GL_ARRAY_BUFFER,
|
|
FontDrawerShader::getInstance()->m_vbo);
|
|
glBufferData(GL_ARRAY_BUFFER,
|
|
glyph.second.size(), glyph.second.data(), GL_STREAM_DRAW);
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,
|
|
FontDrawerShader::getInstance()->m_ibo);
|
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_size, idx_data,
|
|
GL_STREAM_DRAW);
|
|
glEnableVertexAttribArray(0);
|
|
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, stride, 0);
|
|
glDisableVertexAttribArray(1);
|
|
glEnableVertexAttribArray(2);
|
|
glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, (GLvoid*)8);
|
|
glEnableVertexAttribArray(3);
|
|
glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, stride, (GLvoid*)12);
|
|
FontDrawerShader::getInstance()->setTextureUnits(
|
|
glyph.first->getTextureHandler());
|
|
glDrawElements(GL_TRIANGLES, idx_count,
|
|
indices_32.empty() ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, 0);
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
glBindVertexArray(0);
|
|
}
|
|
else
|
|
{
|
|
video::SMaterial m;
|
|
m.setTexture(0, glyph.first);
|
|
m.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
|
|
irr_driver->getVideoDriver()->setMaterial(m);
|
|
irr_driver->getVideoDriver()->draw2DVertexPrimitiveList(
|
|
glyph.second.data(), glyph.second.size() / stride,
|
|
idx_data, idx_count / 3, video::EVT_STANDARD,
|
|
scene::EPT_TRIANGLES,
|
|
indices_32.empty() ? video::EIT_16BIT : video::EIT_32BIT);
|
|
}
|
|
}
|
|
|
|
if (g_clip)
|
|
{
|
|
irr_driver->getVideoDriver()->disableScissorTest();
|
|
g_clip.reset();
|
|
}
|
|
|
|
if (CVS->isGLSL())
|
|
{
|
|
glUseProgram(0);
|
|
}
|
|
|
|
for (auto it = g_glyphs.begin(); it != g_glyphs.end();)
|
|
{
|
|
it->first->drop();
|
|
it = g_glyphs.erase(it);
|
|
}
|
|
} // draw
|
|
|
|
#endif
|