diff --git a/rwengine/include/engine/GameData.hpp b/rwengine/include/engine/GameData.hpp index 92b1db7b..0ce1939b 100644 --- a/rwengine/include/engine/GameData.hpp +++ b/rwengine/include/engine/GameData.hpp @@ -28,6 +28,7 @@ class SCMFile; * @brief Stores simple data about Textures such as transparency flags. * * @todo Covert usage to TextureHandles or something for streaming. + * @todo Move out of GameData.hpp and into TextureInfo.hpp */ struct TextureInfo { diff --git a/rwengine/include/render/GameRenderer.hpp b/rwengine/include/render/GameRenderer.hpp index bb241a9f..edb3562d 100644 --- a/rwengine/include/render/GameRenderer.hpp +++ b/rwengine/include/render/GameRenderer.hpp @@ -10,6 +10,7 @@ #include #include "MapRenderer.hpp" +#include "TextRenderer.hpp" class Model; class ModelFrame; @@ -244,6 +245,7 @@ public: } MapRenderer map; + TextRenderer text; }; #endif diff --git a/rwengine/include/render/OpenGLRenderer.hpp b/rwengine/include/render/OpenGLRenderer.hpp index 056e0251..5579f8b9 100644 --- a/rwengine/include/render/OpenGLRenderer.hpp +++ b/rwengine/include/render/OpenGLRenderer.hpp @@ -77,6 +77,7 @@ public: /// @todo dont use GLint in the interface. virtual void setProgramBlockBinding(ShaderProgram* p, const std::string& name, GLint point) = 0; virtual void setUniformTexture(ShaderProgram*p, const std::string& name, GLint tex) = 0; + virtual void setUniform(ShaderProgram*p, const std::string& name, const glm::mat4& m) = 0; virtual void setUniform(ShaderProgram*p, const std::string& name, const glm::vec4& v) = 0; virtual void setUniform(ShaderProgram*p, const std::string& name, const glm::vec3& v) = 0; virtual void setUniform(ShaderProgram*p, const std::string& name, const glm::vec2& v) = 0; @@ -89,7 +90,15 @@ public: virtual void draw(const glm::mat4& model, DrawBuffer* draw, const DrawParameters& p) = 0; virtual void drawArrays(const glm::mat4& model, DrawBuffer* draw, const DrawParameters& p) = 0; + void setViewport(const glm::ivec2& vp) { viewport = vp; } + const glm::ivec2& getViewport() const { return viewport; } + + glm::mat4 get2DProjection() const; + virtual void invalidate() = 0; + +private: + glm::ivec2 viewport; }; class OpenGLRenderer : public Renderer @@ -127,6 +136,7 @@ public: ShaderProgram* createShader(const std::string &vert, const std::string &frag); void setProgramBlockBinding(ShaderProgram* p, const std::string &name, GLint point); void setUniformTexture(ShaderProgram* p, const std::string &name, GLint tex); + void setUniform(ShaderProgram* p, const std::string& name, const glm::mat4& m); void setUniform(ShaderProgram* p, const std::string& name, const glm::vec4& m); void setUniform(ShaderProgram* p, const std::string& name, const glm::vec3& m); void setUniform(ShaderProgram* p, const std::string& name, const glm::vec2& m); diff --git a/rwengine/include/render/TextRenderer.hpp b/rwengine/include/render/TextRenderer.hpp new file mode 100644 index 00000000..5cb7140e --- /dev/null +++ b/rwengine/include/render/TextRenderer.hpp @@ -0,0 +1,53 @@ +#pragma once +#include +#include "OpenGLRenderer.hpp" + +#define GAME_FONTS 3 + +class GameWorld; +class GameRenderer; +/** + * @brief Handles rendering of bitmap font textures. + * + * In future, strings textures might be cached to improve performance, but + * for now, we just render each glyph on it's own quad + */ +class TextRenderer +{ +public: + + /** + * @todo Can this be merged with the gamestate text entries? + */ + struct TextInfo + { + /// Font index @see TextRenderer::setFontTexture + int font; + /// Message to be displayed (including markup) + std::string text; + /// On screen position + glm::vec2 screenPosition; + /// font size + float size; + /// Base colour + glm::vec3 baseColour; + + TextInfo(); + }; + + TextRenderer(GameWorld* engine, GameRenderer* renderer); + ~TextRenderer(); + + void setFontTexture( int index, const std::string& font ); + + void renderText( const TextInfo& ti ); + +private: + std::string fonts[GAME_FONTS]; + GameWorld* engine; + GameRenderer* renderer; + Renderer::ShaderProgram* textShader; + + GeometryBuffer gb; + DrawBuffer db; +}; \ No newline at end of file diff --git a/rwengine/src/engine/GameData.cpp b/rwengine/src/engine/GameData.cpp index 1e3722c8..02a099c1 100644 --- a/rwengine/src/engine/GameData.cpp +++ b/rwengine/src/engine/GameData.cpp @@ -107,6 +107,7 @@ void GameData::load() _knownFiles.insert({"hud.txd", {false, datpath+"/models/hud.txd"}}); _knownFiles.insert({"english.gxt", {false, datpath+"/TEXT/english.gxt"}}); _knownFiles.insert({"ped.ifp", {false, datpath+"/anim/ped.ifp"}}); + _knownFiles.insert({"fonts.txd", {false, datpath+"/models/fonts.txd"}}); _knownFiles.insert({"news.txd", {false, datpath+"/txd/NEWS.TXD"}}); _knownFiles.insert({"splash1.txd", {false, datpath+"/txd/SPLASH1.TXD"}}); @@ -118,6 +119,7 @@ void GameData::load() loadDFF("arrow.dff"); loadTXD("particle.txd"); loadTXD("hud.txd"); + loadTXD("fonts.txd"); loadCarcols(datpath+"/data/carcols.dat"); loadWeather(datpath+"/data/timecyc.dat"); diff --git a/rwengine/src/render/GameRenderer.cpp b/rwengine/src/render/GameRenderer.cpp index 11d4eeed..67c9c33c 100644 --- a/rwengine/src/render/GameRenderer.cpp +++ b/rwengine/src/render/GameRenderer.cpp @@ -80,7 +80,7 @@ DrawBuffer ssRectDraw; GameRenderer::GameRenderer(GameWorld* engine) : engine(engine), renderer(new OpenGLRenderer), _renderAlpha(0.f), - map(engine, renderer) + map(engine, renderer), text(engine, this) { engine->logInfo("[DRAW] " + renderer->getIDString()); diff --git a/rwengine/src/render/OpenGLRenderer.cpp b/rwengine/src/render/OpenGLRenderer.cpp index 01962c5e..90a29ed8 100644 --- a/rwengine/src/render/OpenGLRenderer.cpp +++ b/rwengine/src/render/OpenGLRenderer.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -79,6 +80,22 @@ GLuint compileProgram(const char* vertex, const char* fragment) return prog; } +glm::mat4 Renderer::get2DProjection() const +{ + glm::vec2 aspect(1.f, 1.f); + if( viewport.x > viewport.y ) + { + // Widescreen + aspect.x = viewport.x / (float) viewport.y; + } + else + { + // Tall-o-vision + aspect.y = viewport.y / (float)viewport.x; + } + return glm::ortho(0.f, 800.f * aspect.x, 600.f * aspect.y, 0.f, -1.f, 1.f); +} + void OpenGLRenderer::useDrawBuffer(DrawBuffer* dbuff) { if( dbuff != currentDbuff ) @@ -147,6 +164,13 @@ void OpenGLRenderer::setUniformTexture(Renderer::ShaderProgram* p, const std::st glUniform1i(currentProgram->getUniformLocation(name), tex); } +void OpenGLRenderer::setUniform(Renderer::ShaderProgram* p, const std::string& name, const glm::mat4& m) +{ + useProgram(p); + + glUniformMatrix4fv(currentProgram->getUniformLocation(name.c_str()), 1, GL_FALSE, glm::value_ptr(m)); +} + void OpenGLRenderer::setUniform(Renderer::ShaderProgram* p, const std::string& name, const glm::vec4& m) { useProgram(p); diff --git a/rwengine/src/render/TextRenderer.cpp b/rwengine/src/render/TextRenderer.cpp new file mode 100644 index 00000000..8bb6335b --- /dev/null +++ b/rwengine/src/render/TextRenderer.cpp @@ -0,0 +1,178 @@ +#include "render/TextRenderer.hpp" +#include +#include + +const char* TextVertexShader = R"( +#version 130 +#extension GL_ARB_explicit_attrib_location : enable +#extension GL_ARB_uniform_buffer_object : enable + +layout(location = 0) in vec2 position; +layout(location = 3) in vec2 texcoord; +layout(location = 2) in vec3 colour; +out vec2 TexCoord; +out vec3 Colour; + +uniform mat4 proj; + +void main() +{ + gl_Position = proj * vec4(position, 0.0, 1.0); + TexCoord = texcoord; + Colour = colour; +})"; + +const char* TextFragmentShader = R"( +#version 130 +in vec2 TexCoord; +in vec3 Colour; +uniform vec4 colour; +uniform sampler2D fontTexture; +out vec4 outColour; + +void main() +{ + float a = texture(fontTexture, TexCoord).a; + outColour = vec4(Colour, a); +})"; + +struct TextVertex +{ + glm::vec2 position; + glm::vec2 texcoord; + glm::vec3 colour; + + static const AttributeList vertex_attributes() { + return { + {ATRS_Position, 2, sizeof(TextVertex), 0ul}, + {ATRS_TexCoord, 2, sizeof(TextVertex), 0ul + sizeof(glm::vec2)}, + {ATRS_Colour, 3, sizeof(TextVertex), 0ul + sizeof(glm::vec2) * 2}, + }; + } +}; + + +TextRenderer::TextInfo::TextInfo() +: font(0), size(1.f), baseColour({1.f, 1.f, 1.f}) +{ + +} + + +TextRenderer::TextRenderer(GameWorld* engine, GameRenderer* renderer) +: fonts({}), engine(engine), renderer(renderer) +{ + textShader = renderer->getRenderer()->createShader( + TextVertexShader, TextFragmentShader ); +} + +TextRenderer::~TextRenderer() +{ + +} + +void TextRenderer::setFontTexture(int index, const std::string& texture) +{ + if( index < GAME_FONTS ) + { + fonts[index] = texture; + } +} + +/// @todo This is very rough +int charToIndex(char g) +{ + if( g >= '0' && g <= '9' ) + { + return 16 + (g - '0'); + } + else if( g >= 'A' && g <= 'Z' ) + { + return 33 + (g - 'A'); + } + else if( g >= 'a' && g <= 'z' ) + { + return 65 + (g - 'a'); + } + switch(g) + { + default: return 0; + case '!': return 1; + case '"': return 2; + case '#': return 3; + case '$': return 4; + case '%': return 5; + case '&': return 6; + case '\'': return 7; + case '(': return 8; + case ')': return 9; + case '*': return 10; + case '+': return 11; + case ',': return 12; + case '-': return 13; + case '.': return 14; + case '/': return 15; + } +} + +glm::vec4 indexToCoord(int font, int index) +{ + int x = index % 16; + int y = index / 16; + glm::vec2 gsize( 1.f / 16.f, 1.f / ((font == 0) ? 16.f : 13.f) ); + return glm::vec4( x, y, x + 1, y + 1 ) * + glm::vec4( gsize, gsize ); // + glm::vec4( 0.0001f, 0.0001f,-0.0001f,-0.0001f); +} + +void TextRenderer::renderText(const TextRenderer::TextInfo& ti) +{ + renderer->getRenderer()->useProgram(textShader); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glActiveTexture(GL_TEXTURE0); + + renderer->getRenderer()->setUniform(textShader, "proj", renderer->getRenderer()->get2DProjection()); + renderer->getRenderer()->setUniformTexture(textShader, "fontTexture", 0); + + glm::vec2 coord = ti.screenPosition; + + Renderer::DrawParameters dp; + dp.start = 0; + dp.count = gb.getCount(); + dp.texture = engine->gameData.textures[{fonts[ti.font], ""}].texName; + glm::vec2 ss( ti.size ); + + glm::vec3 colour = ti.baseColour; + + /// @todo make this less wastefull + for( const char& c : ti.text ) + { + // Handle special chars. + if( c == '\n' ) + { + coord.x = ti.screenPosition.x; + coord.y += ss.y; + continue; + } + + int glyph = charToIndex(c); + auto tex = indexToCoord(ti.font, glyph); + + glm::vec2 p = coord; + coord.x += ss.x; + + std::vector geo = { + { { p.x, p.y + ss.y }, {tex.x, tex.w}, colour }, + { { p.x + ss.x, p.y + ss.y }, {tex.z, tex.w}, colour }, + { { p.x, p.y }, {tex.x, tex.y}, colour }, + { { p.x + ss.x, p.y }, {tex.z, tex.y}, colour }, + }; + + gb.uploadVertices(geo); + db.addGeometry(&gb); + db.setFaceType(GL_TRIANGLE_STRIP); + + renderer->getRenderer()->drawArrays(glm::mat4(), &db, dp); + } +} diff --git a/rwgame/MenuSystem.hpp b/rwgame/MenuSystem.hpp index 533dab33..bfaad88d 100644 --- a/rwgame/MenuSystem.hpp +++ b/rwgame/MenuSystem.hpp @@ -2,17 +2,16 @@ #define _GAME_MENUSYSTEM_HPP_ #include #include -#include -#include #include +#include #include class Menu { - sf::Font font; + int font; public: - Menu(const sf::Font& font) + Menu(int font) : font(font), activeEntry(-1) {} struct MenuEntry @@ -20,19 +19,26 @@ public: std::string name; float _size; - MenuEntry(const std::string& n, float size = 38.f) : name(n), _size(size) {} + MenuEntry(const std::string& n, float size = 30.f) : name(n), _size(size) {} float getHeight() { return _size; } - virtual void draw(const sf::Font& font, sf::RenderWindow& window, glm::vec2& basis) + virtual void draw(int font, bool active, GameRenderer* r, glm::vec2& basis) { - sf::Text t; - t.setFont(font); - t.setPosition(basis.x + 6, basis.y + 2); - t.setString(name); - auto cSize = getHeight() - 10.f; - t.setCharacterSize(cSize); - window.draw(t); + TextRenderer::TextInfo ti; + ti.font = font; + ti.screenPosition = basis; + ti.text = name; + ti.size = getHeight(); + if( ! active ) + { + ti.baseColour = glm::vec3(1.f, 1.f, 1.f); + } + else + { + ti.baseColour = glm::vec3(1.f, 1.f, 0.f); + } + r->text.renderText(ti); basis.y += getHeight(); } @@ -49,7 +55,7 @@ public: void activate(float clickX, float clickY) { callback(); } }; - static std::shared_ptr lambda(const std::string& n, std::function callback, float size = 38.f) + static std::shared_ptr lambda(const std::string& n, std::function callback, float size = 30.f) { return std::shared_ptr(new Entry(n, callback, size)); } @@ -68,20 +74,19 @@ public: entries.push_back(entry); } - void draw(sf::RenderWindow& window) + void draw(GameRenderer* r) { glm::vec2 basis(offset); for(size_t i = 0; i < entries.size(); ++i) { - if(activeEntry >= 0 && i == (unsigned) activeEntry) { - sf::RectangleShape rs(sf::Vector2f(250.f, entries[i]->getHeight())); - rs.setPosition(basis.x, basis.y); - rs.setFillColor(sf::Color::Cyan); - window.draw(rs); + bool active = false; + if(activeEntry >= 0 && i == (unsigned) activeEntry) + { + active = true; } - entries[i]->draw(font, window, basis); + entries[i]->draw(font, active, r, basis); } } diff --git a/rwgame/RWGame.cpp b/rwgame/RWGame.cpp index a20084cc..416de486 100644 --- a/rwgame/RWGame.cpp +++ b/rwgame/RWGame.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include