diff --git a/LinearLayout.cc b/LinearLayout.cc new file mode 100644 index 0000000..fb2eb6d --- /dev/null +++ b/LinearLayout.cc @@ -0,0 +1,77 @@ + +#include "LinearLayout.hh" + +LinearLayout::LinearLayout(int direction, int width, int height, std::string name) + : View(width, height, std::move(name)), direction_(direction) +{ +} + +LinearLayout::~LinearLayout() +{ +} + +void LinearLayout::setChildPadding(int pad) +{ + if (pad != padChildren_) { + padChildren_ = pad; + + layout(); + } +} + +void LinearLayout::layout() +{ + // TODO: Respect parent padding + if ((width_ == MATCH_PARENT) && parent_) + realWidth_ = parent_->getRealWidth(); + + if ((height_ == MATCH_PARENT) && parent_) + realHeight_ = parent_->getRealHeight(); + + if (direction_ == DIR_VERTICAL) { + int contentWidth = 0; + + if (width_ == WRAP_CONTENT) { + // Determine content width + for(auto& ent : children_) { + auto& child = *(std::get<1>(ent)); + assert(child.getHeight() != MATCH_PARENT); + if (child.getWidth() == MATCH_PARENT) + continue; + if (child.getWidth() == WRAP_CONTENT) + _layout(child); + contentWidth = std::max(contentWidth, child.getRealWidth()); + } + assert(contentWidth > 0); + realWidth_ = contentWidth + padLeft_ + padRight_; + printf("Inner width: %d, outer width: %d\n", contentWidth, realWidth_); + } + + int currentY = padTop_; + + // Layout children + for(auto& ent : children_) { + auto& child = *(std::get<1>(ent)); + if (child.getWidth() == MATCH_PARENT) + _layout(child); + + SDL_Rect cr; + cr.x = padLeft_; + cr.y = currentY; + if ((realWidth_ - padLeft_ - padRight_) > child.getRealWidth()) + cr.w = child.getRealWidth(); + else + cr.w = realWidth_ - padLeft_ - padRight_; + cr.h = child.getRealHeight(); + + std::get<2>(ent) = cr; + + currentY += child.getRealHeight() + padChildren_; + } + + if (height_ == WRAP_CONTENT) + realHeight_ = currentY - padChildren_ + padBottom_; + } + + invalidateGL(); +} diff --git a/LinearLayout.hh b/LinearLayout.hh new file mode 100644 index 0000000..3a3acfd --- /dev/null +++ b/LinearLayout.hh @@ -0,0 +1,27 @@ +#ifndef __OPENGLPLAYGROUND_LINEARLAYOUT_HH__ +#define __OPENGLPLAYGROUND_LINEARLAYOUT_HH__ + +#include "View.hh" + +static const int DIR_VERTICAL = 0; +static const int DIR_HORIZONTAL = 0; + +class LinearLayout : public View { +public: + LinearLayout(int direction = DIR_VERTICAL, int width = WRAP_CONTENT, int height = WRAP_CONTENT, std::string name = ""); + + LinearLayout(LinearLayout const& copy) = delete; + LinearLayout& operator=(LinearLayout const& copy) = delete; + + ~LinearLayout(); + + void setChildPadding(int pad); + +protected: + void layout() override; + + int direction_; + int padChildren_; +}; + +#endif diff --git a/Makefile b/Makefile index 95e6661..af70b99 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ CXX=g++ -CXXOPTS=-O2 -ggdb -Wall -Wextra -pedantic -Wno-unused-function -Wno-unused-parameter -Wno-sign-compare -std=c++14 -flto +CXXOPTS=-Og -ggdb -Wall -Wextra -pedantic -Wno-unused-function -Wno-unused-parameter -Wno-sign-compare -std=c++14 -flto LDOPTS= LIBS=-lglbinding -lSDL2 -lSDL2_image -lobj -lSDL2_ttf -lprotobuf -CXXSRCS=main.cc objectParser.cc shaders.cc Object.cc VBOManager.cc texture.cc font.cc Overlay.cc TextWidget.cc +CXXSRCS=main.cc objectParser.cc shaders.cc Object.cc VBOManager.cc texture.cc font.cc Overlay.cc TextWidget.cc Widget.cc View.cc LinearLayout.cc BINIFY_SRCS=binifyObj.cc objectParser.cc object.pb.cc OBJS=$(addprefix objs/,$(CXXSRCS:.cc=.o)) BINIFY_OBJS=$(addprefix objs/,$(BINIFY_SRCS:.cc=.o)) diff --git a/Object.cc b/Object.cc index 0b0ec91..91aec84 100644 --- a/Object.cc +++ b/Object.cc @@ -12,16 +12,15 @@ using namespace gl; -Object::Object(VBOManager& vboManager, std::vector const& vas, - std::vector indices, Program& prog) - : _vboManager(vboManager), _prog(prog), _indices(std::move(indices)), _vaID(0) +Object::Object(std::vector const& vas, + std::vector indices) + : _indices(std::move(indices)), _vaID(0) { construct(vas); } -Object::Object(VBOManager& vboManager, std::string const& filename, - Program& prog) - : _vboManager(vboManager), _prog(prog), _vaID(0) +Object::Object(std::string const& filename) + : _vaID(0) { auto tmp = readObject(filename); _indices = std::get<1>(tmp); @@ -33,22 +32,17 @@ Object::~Object() glDeleteVertexArrays(1, &_vaID); } -void Object::draw(glm::mat4 const& modelview, Program *override) const +void Object::draw() const { glBindVertexArray(_vaID); - if (override) - override->use(); - else - _prog.use(); - glUniformMatrix4fv(_prog.getUniformLocation("model_matrix"), 1, GL_FALSE, - glm::value_ptr(modelview)); + glDrawElements(GL_TRIANGLES, _indices.size(), GL_UNSIGNED_SHORT, _indices.data()); } void Object::construct(std::vector const& vas) { - _vbo = _vboManager.alloc(sizeof(objVertexAttribs)*vas.size()); + _vbo = VBOManager::getInstance().alloc(sizeof(objVertexAttribs)*vas.size()); glBindBuffer(GL_ARRAY_BUFFER, _vbo.getVBOId()); glBufferSubData(GL_ARRAY_BUFFER, _vbo.getOfs(), sizeof(objVertexAttribs)*vas.size(), (void*)vas.data()); @@ -56,22 +50,17 @@ void Object::construct(std::vector const& vas) glGenVertexArrays(1, &_vaID); glBindVertexArray(_vaID); - GLint al; - if((al = _prog.getAttribLocation("vertex")) != -1) { - glEnableVertexAttribArray(al); - glVertexAttribPointer(al, 3, GL_FLOAT, GL_FALSE, sizeof(objVertexAttribs), - (void*)(_vbo.getOfs()+offsetof(objVertexAttribs, vertex))); - } - if((al = _prog.getAttribLocation("vertexTC")) != -1) { - glEnableVertexAttribArray(al); - glVertexAttribPointer(al, 2, GL_UNSIGNED_SHORT, GL_TRUE, - sizeof(objVertexAttribs), - (void*)(_vbo.getOfs()+offsetof(objVertexAttribs, texCoords))); - } - if((al = _prog.getAttribLocation("vertexNorm")) != -1) { - glEnableVertexAttribArray(al); - glVertexAttribPointer(al, 4, GL_INT_2_10_10_10_REV, GL_TRUE, - sizeof(objVertexAttribs), - (void*)(_vbo.getOfs()+offsetof(objVertexAttribs, normal))); - } + glEnableVertexAttribArray(ATTRIB_VERTEX_POS); + glVertexAttribPointer(ATTRIB_VERTEX_POS, 3, GL_FLOAT, GL_FALSE, sizeof(objVertexAttribs), + (void*)(_vbo.getOfs()+offsetof(objVertexAttribs, vertex))); + + glEnableVertexAttribArray(ATTRIB_VERTEX_TC); + glVertexAttribPointer(ATTRIB_VERTEX_TC, 2, GL_UNSIGNED_SHORT, GL_TRUE, + sizeof(objVertexAttribs), + (void*)(_vbo.getOfs()+offsetof(objVertexAttribs, texCoords))); + + glEnableVertexAttribArray(ATTRIB_VERTEX_NORM); + glVertexAttribPointer(ATTRIB_VERTEX_NORM, 4, GL_INT_2_10_10_10_REV, GL_TRUE, + sizeof(objVertexAttribs), + (void*)(_vbo.getOfs()+offsetof(objVertexAttribs, normal))); } diff --git a/Object.hh b/Object.hh index c5541d5..fbfa896 100644 --- a/Object.hh +++ b/Object.hh @@ -7,14 +7,12 @@ #include "objectParser.hh" #include "VBOManager.hh" -class Program; - class Object { public: - Object(VBOManager& vboManager, std::vector const& vas, - std::vector indices, Program& prog); + Object(std::vector const& vas, + std::vector indice); - Object(VBOManager& vboManager, std::string const& filename, Program& prog); + Object(std::string const& filename); Object(Object const& copy) = delete; @@ -22,14 +20,12 @@ public: Object& operator=(Object const& copy) = delete; - void draw(glm::mat4 const& modelview, Program *override = nullptr) const; + void draw() const; private: void construct(std::vector const& vas); - VBOManager& _vboManager; VBOManager::VBOAlloc _vbo; - Program& _prog; std::vector _indices; gl::GLuint _vaID; }; diff --git a/Overlay.cc b/Overlay.cc index 0ae2bb9..3624e76 100644 --- a/Overlay.cc +++ b/Overlay.cc @@ -6,10 +6,9 @@ using namespace gl; -Overlay::Overlay(VBOManager& vboManager, std::vector const& vas, - Program& prog) - : vboManager_(vboManager), vbo_(vboManager_.alloc(sizeof(ovlVertexAttribs)*vas.size())), - prog_(prog), vaID_(0), vertices_(vas.size()) +Overlay::Overlay(std::vector const& vas) + : vbo_(VBOManager::getInstance().alloc(sizeof(ovlVertexAttribs)*vas.size())), + vaID_(0), vertices_(vas.size()) { glBindBuffer(GL_ARRAY_BUFFER, vbo_.getVBOId()); glBufferSubData(GL_ARRAY_BUFFER, vbo_.getOfs(), @@ -19,18 +18,16 @@ Overlay::Overlay(VBOManager& vboManager, std::vector const& va glGenVertexArrays(1, &vaID_); try { glBindVertexArray(vaID_); - GLint al; - if((al = prog_.getAttribLocation("vertex")) != -1) { - glEnableVertexAttribArray(al); - glVertexAttribPointer(al, 2, GL_SHORT, GL_TRUE, sizeof(ovlVertexAttribs), - (void*)(vbo_.getOfs()+offsetof(ovlVertexAttribs, vertex))); - } - if((al = prog_.getAttribLocation("vertexTC")) != -1) { - glEnableVertexAttribArray(al); - glVertexAttribPointer(al, 2, GL_UNSIGNED_SHORT, GL_TRUE, - sizeof(ovlVertexAttribs), - (void*)(vbo_.getOfs()+offsetof(ovlVertexAttribs, texCoords))); - } + + glEnableVertexAttribArray(ATTRIB_VERTEX_POS); + glVertexAttribPointer(ATTRIB_VERTEX_POS, 2, GL_SHORT, GL_FALSE, + sizeof(ovlVertexAttribs), + (void*)(vbo_.getOfs()+offsetof(ovlVertexAttribs, vertex))); + + glEnableVertexAttribArray(ATTRIB_VERTEX_TC); + glVertexAttribPointer(ATTRIB_VERTEX_TC, 2, GL_UNSIGNED_SHORT, GL_TRUE, + sizeof(ovlVertexAttribs), + (void*)(vbo_.getOfs()+offsetof(ovlVertexAttribs, texCoords))); } catch(...) { glDeleteVertexArrays(1, &vaID_); throw; @@ -42,12 +39,9 @@ Overlay::~Overlay() glDeleteVertexArrays(1, &vaID_); } -void Overlay::draw(Program *override) const +void Overlay::draw() const { glBindVertexArray(vaID_); - if (override) - override->use(); - else - prog_.use(); + glDrawArrays(GL_TRIANGLES, 0, vertices_); } diff --git a/Overlay.hh b/Overlay.hh index bd43f8b..e484cad 100644 --- a/Overlay.hh +++ b/Overlay.hh @@ -6,29 +6,24 @@ #include "VBOManager.hh" -class Program; - struct ovlVertexAttribs { - uint16_t vertex[2]; - uint16_t texCoords[2]; -} __attribute__((__packed__)); + int16_t vertex[2]; + uint16_t texCoords[2]; + } __attribute__((__packed__)); class Overlay { public: - Overlay(VBOManager& vboManager, std::vector const& vas, - Program& prog); + Overlay(std::vector const& vas); Overlay(Overlay const& copy) = delete; Overlay& operator=(Overlay const& copy) = delete; ~Overlay(); - void draw(Program *override = nullptr) const; + void draw() const; private: - VBOManager& vboManager_; VBOManager::VBOAlloc vbo_; - Program& prog_; gl::GLuint vaID_; size_t vertices_; }; diff --git a/TextWidget.cc b/TextWidget.cc new file mode 100644 index 0000000..f102a08 --- /dev/null +++ b/TextWidget.cc @@ -0,0 +1,84 @@ +#include + +#include "font.hh" +#include "TextWidget.hh" + +TextWidget::TextWidget(Font& font, std::string text, int width, int height) + : Widget(width, height), text_(std::move(text)), font_(font), + textSurf_(nullptr) +{ + layout(); +} + +TextWidget::~TextWidget() +{ +} + +void TextWidget::setText(std::string text) +{ + if (text != text_) { + text_ = std::move(text); + invalidateGL(); + layout(); + } +} + +void TextWidget::setForegroundColor(SDL_Color fg) +{ + Widget::setForegroundColor(fg); + + // Trigger text rerender + layout(); +} + +void TextWidget::layout() +{ + // Determine text size + int width, height; + if(TTF_SizeUTF8(font_.getFont(), text_.c_str(), &width, &height) != 0) + throw TTFException{}; + + if ((width > (width_ - padLeft_ - padRight_)) && (width_ != WRAP_CONTENT)) { + textSurf_.reset(TTF_RenderUTF8_Blended_Wrapped(font_.getFont(), + text_.c_str(), + fg_, width_- padLeft_ - padRight_)); + if (!textSurf_) + throw TTFException{}; + + if (height_ == WRAP_CONTENT) + // TODO: Get proper tight bounding box for multiline text + if (realHeight_ != (textSurf_->h - // TTF_FontLineSkip(font_.getFont()) + + padTop_ + padBottom_)) { + realHeight_ = textSurf_->h - // TTF_FontLineSkip(font_.getFont()) + + padTop_ + padBottom_; + invalidateGL(); + } + } else { + textSurf_.reset(TTF_RenderUTF8_Blended(font_.getFont(), text_.c_str(), fg_)); + + if (!textSurf_) + throw TTFException{}; + + if (width_ == WRAP_CONTENT) + if (realWidth_ != (textSurf_->w + padLeft_ + padRight_)) { + realWidth_ = textSurf_->w + padLeft_ + padRight_; + invalidateGL(); + } + if (height_ == WRAP_CONTENT) + if (realHeight_ != (textSurf_->h + padTop_ + padBottom_)) { + realHeight_ = textSurf_->h + padTop_ + padBottom_; + invalidateGL(); + } + } + +} + +void TextWidget::render(SDL_Surface *dst, SDL_Rect *dstRect) const +{ + Widget::render(dst, dstRect); + + SDL_Rect rect = calcContentRect(dst, dstRect, textSurf_->clip_rect); + + if (SDL_BlitSurface(textSurf_.get(), nullptr, dst, &rect) != 0) + throw SDLException{}; +} diff --git a/TextWidget.hh b/TextWidget.hh new file mode 100644 index 0000000..26306de --- /dev/null +++ b/TextWidget.hh @@ -0,0 +1,37 @@ +#ifndef __OPENGLPLAYGROUND_TEXTWIDGET_HH__ +#define __OPENGLPLAYGROUND_TEXTWIDGET_HH__ + +#include + +#include "common.hh" +#include "Widget.hh" + +class Font; + +class TextWidget final : public Widget { +public: + TextWidget(Font& font, std::string text = "", + int width = WRAP_CONTENT, int height = WRAP_CONTENT); + + TextWidget(TextWidget const& copy) = delete; + TextWidget& operator=(TextWidget const& copy) = delete; + + ~TextWidget(); + + void setText(std::string text); + std::string const& getText() const { return text_; } + + void setForegroundColor(SDL_Color fg) override; + + void render(SDL_Surface *dst, SDL_Rect *dstRect) const override; + +protected: + void layout() override; + +private: + std::string text_; + Font& font_; + SDLSurfaceUPtr textSurf_; +}; + +#endif diff --git a/VBOManager.cc b/VBOManager.cc index 64f161e..b0e31f8 100644 --- a/VBOManager.cc +++ b/VBOManager.cc @@ -1,10 +1,26 @@ #include #include +#include #include "VBOManager.hh" +#include "common.hh" using namespace gl; +std::unique_ptr VBOManager::instance{nullptr}; +static std::once_flag instance_flag; + +VBOManager& VBOManager::getInstance() +{ + std::call_once(instance_flag, init); + return *instance; +} + +void VBOManager::init() +{ + instance.reset(new VBOManager{}); +} + VBOManager::VBOManager() { } diff --git a/VBOManager.hh b/VBOManager.hh index 6a52703..6d35bab 100644 --- a/VBOManager.hh +++ b/VBOManager.hh @@ -2,20 +2,26 @@ #define __OPENGLPLAYGROUND_VBOMANAGER_HH__ #include -#include -#include #include +#include #include #include class VBOManager { public: - VBOManager(); - + static VBOManager& getInstance(); + ~VBOManager(); + VBOManager(VBOManager const& copy) = delete; + VBOManager& operator=(VBOManager const& copy) = delete; + private: + VBOManager(); + + static void init(); + static std::unique_ptr instance; class VBO; @@ -53,6 +59,10 @@ public: assert(_vbo); return _vbo->getID(); } + + explicit operator bool() const noexcept { + return (_vbo != nullptr); + } private: VBOAlloc(VBO &vbo, size_t ofs, size_t size); diff --git a/View.cc b/View.cc new file mode 100644 index 0000000..be6c0f4 --- /dev/null +++ b/View.cc @@ -0,0 +1,109 @@ +#include "View.hh" + +View::View(int width, int height, std::string name) + : Widget(width, height, std::move(name)) +{ +} + +View::~View() +{ +} + +void View::addChild(std::unique_ptr child) +{ + _setParent(*child); + children_.push_back(make_tuple(child->getName(), std::move(child), SDL_Rect{0, 0, 0, 0})); + + layout(); +} + +Widget& View::getChildByName(std::string name) +{ + if (name == "") + throw ChildNotFoundException{}; + + for(auto& ent : children_) { + if (std::get<0>(ent) == name) + return *std::get<1>(ent); + } + + throw ChildNotFoundException{}; +} + +Widget const& View::getChildByName(std::string name) const +{ + if (name == "") + throw ChildNotFoundException{}; + + for(auto& ent : children_) { + if (std::get<0>(ent) == name) + return *std::get<1>(ent); + } + + throw ChildNotFoundException{}; +} + +std::unique_ptr View::removeChild(std::string name) +{ + if (name == "") + throw ChildNotFoundException{}; + + for(auto it = children_.begin(); it != children_.end(); ++it) { + if (std::get<0>(*it) == name) { + auto child = std::move(std::get<1>(*it)); + children_.erase(it); + _clearParent(*child); + _layout(*child); + + layout(); + + return child; + } + } + + throw ChildNotFoundException{}; +} + +std::unique_ptr View::removeChild(Widget& child) +{ + for(auto it = children_.begin(); it != children_.end(); ++it) { + if (std::get<1>(*it).get() == &child) { + auto child = std::move(std::get<1>(*it)); + children_.erase(it); + + _clearParent(*child); + _layout(*child); + + layout(); + + return child; + } + } + + throw ChildNotFoundException{}; +} + + +void View::render(SDL_Surface *dst, SDL_Rect *dstRect) const +{ + Widget::render(dst, dstRect); + + SDL_Rect rect; + if (dstRect) + memcpy(&rect, dstRect, sizeof(SDL_Rect)); + else { + rect.x = 0; + rect.y = 0; + rect.w = dst->w; + rect.h = dst->h; + } + + for (auto& ent : children_) { + SDL_Rect childDst = std::get<2>(ent), childDstCliped; + childDst.x += rect.x; + childDst.y += rect.y; + SDL_IntersectRect(&rect, &childDst, &childDstCliped); + + std::get<1>(ent)->render(dst, &childDst); + } +} diff --git a/View.hh b/View.hh new file mode 100644 index 0000000..a34bff6 --- /dev/null +++ b/View.hh @@ -0,0 +1,51 @@ +#ifndef __OPENGLPLAYGROUND_VIEW_HH__ +#define __OPENGLPLAYGROUND_VIEW_HH__ + +#include +#include +#include +#include + +#include "Widget.hh" +#include "common.hh" + +class ChildNotFoundException : public Exception { +public: + ChildNotFoundException() : Exception() { + } + + std::string toString() const override { + return "ChildNotFoundException"s; + } +}; + + +/* Base class for all Widgets containing user-specified other Widgets + (i.e. Views/Layouts) */ +class View : public Widget { +public: + View(int width = WRAP_CONTENT, int height = WRAP_CONTENT, std::string name = ""); + + View(View const& copy) = delete; + View& operator=(View const& copy) = delete; + + ~View(); + + void addChild(std::unique_ptr child); + + Widget& getChildByName(std::string name); + + Widget const& getChildByName(std::string name) const; + + std::unique_ptr removeChild(std::string name); + std::unique_ptr removeChild(Widget& child); + + void render(SDL_Surface *dst, SDL_Rect *dstRect) const override; + +protected: + SDL_Rect childrenBB_; + + std::vector, SDL_Rect > > children_; +}; + +#endif diff --git a/Widget.cc b/Widget.cc new file mode 100644 index 0000000..49a9ba7 --- /dev/null +++ b/Widget.cc @@ -0,0 +1,274 @@ +#include + +#define GLM_FORCE_RADIANS +#include + +#include "Widget.hh" +#include "texture.hh" +#include "shaders.hh" + +Widget::Widget(int width, int height, std::string name) + : name_(std::move(name)), width_(width), height_(height), + background_(nullptr), + bg_(SDL_Color{0, 0, 0, 0}), fg_(SDL_Color{255, 255, 255, 255}), + padLeft_(0), padTop_(0), padRight_(0), padBottom_(0), + alignHoriz_(ALIGN_LEFT), alignVert_(ALIGN_TOP), + renderTop_(0), renderLeft_(0), + renderTexValid_(false), renderAttribsValid_(false), + renderTex_(nullptr), vaID_(0) +{ + if (width_ == WRAP_CONTENT) + realWidth_ = 0; + else + realWidth_ = width_; + + if (height_ == WRAP_CONTENT) + realHeight_ = 0; + else + realHeight_ = height_; +} + +Widget::~Widget() +{ + glDeleteVertexArrays(1, &vaID_); +} + +void Widget::setSize(int width, int height) +{ + if ((width_ != width) || (height_ != height)) { + width_ = width; + height_ = height; + + if (width_ != WRAP_CONTENT) + if (realWidth_ != width_) { + realWidth_ = width_; + } + + if (height_ != WRAP_CONTENT) + if (realHeight_ != height_) { + realHeight_ = height_; + } + + layout(); + } +} + +void Widget::setBackground(SDL_Surface* surf) +{ + if (background_ != surf) { + background_ = surf; + invalidateGL(); + } +} + +void Widget::setBackgroundColor(SDL_Color bg) +{ + if (bg_ != bg) { + bg_ = bg; + invalidateGL(); + } +} + +void Widget::setForegroundColor(SDL_Color fg) +{ + if (fg_ != fg) { + fg_ = fg; + invalidateGL(); + } +} + +void Widget::setPadding(int left, int top, int right, int bottom) +{ + if ((padLeft_ != left) || + (padTop_ != top) || + (padRight_ != right) || + (padBottom_ != bottom)) { + padLeft_ = left; + padTop_ = top; + padRight_ = right; + padBottom_ = bottom; + + layout(); + } +} + +void Widget::setAlignment(int horiz, int vert) { + if ((alignHoriz_ != horiz) || + (alignVert_ != vert)) { + alignHoriz_ = horiz; + alignVert_ = vert; + + invalidateGL(); + } +} + +void Widget::setParent(Widget *parent) +{ + parent_ = parent; +} + +void Widget::setGLRenderPos(int left, int top) +{ + if ((top != renderTop_) || + (left != renderLeft_)) { + renderAttribsValid_ = false; + renderTop_ = top; + renderLeft_ = left; + } +} + +void Widget::render(SDL_Surface *dst, SDL_Rect *dstRect) const +{ + if (background_) { + if (SDL_BlitScaled(background_, nullptr, dst, dstRect) != 0) + throw SDLException{}; + } else { + // Skip completely transparent background + if (bg_.a == 0) + return; + + // Simply write completely intransparent bg + if (bg_.a == 255) { + SDLSurfaceScopedLock lock{dst}; + if (SDL_FillRect(dst, dstRect, + SDL_MapRGBA(dst->format, bg_.r, bg_.g, bg_.b, bg_.a)) != 0) + throw SDLException{}; + + return; + } + + // Do proper blending blit for all other bgs + int w = dstRect?dstRect->w:dst->w; + int h = dstRect?dstRect->h:dst->h; + + uint32_t rmask, gmask, bmask, amask; + int bpp; + if (!SDL_PixelFormatEnumToMasks(dst->format->format, + &bpp, &rmask, &gmask, &bmask, &amask)) + throw SDLException{}; + + SDLSurfaceUPtr tmp{SDL_CreateRGBSurface(0, w, h, bpp, + rmask, gmask, bmask, amask)}; + if (!tmp) + throw SDLException{}; + + { + SDLSurfaceScopedLock lock{tmp}; + if (SDL_FillRect(tmp.get(), nullptr, + SDL_MapRGBA(tmp->format, bg_.r, bg_.g, bg_.b, bg_.a)) != 0) + throw SDLException{}; + } + + if (SDL_BlitSurface(tmp.get(), nullptr, dst, dstRect) != 0) + throw SDLException{}; + } +} + +SDL_Rect Widget::calcContentRect(SDL_Surface *dst, SDL_Rect *dstRect, SDL_Rect contentRect) const +{ + SDL_Rect rect; + + if (dstRect) { + rect.x = dstRect->x + padLeft_; + rect.y = dstRect->y + padTop_; + rect.w = dstRect->w - (padLeft_ + padRight_); + rect.h = dstRect->h - (padTop_ + padBottom_); + } else { + rect.x = padLeft_; + rect.y = padTop_; + rect.w = dst->w - padLeft_ - padRight_; + rect.h = dst->h - padTop_ - padBottom_; + } + + if (contentRect.w < rect.w) { + auto space = rect.w - contentRect.w; + if (alignHoriz_ == ALIGN_RIGHT) { + rect.x += space; + rect.w -= space; + } else if (alignHoriz_ == ALIGN_CENTER) { + rect.x += space/2; + rect.w -= space/2; + } + } + + if (contentRect.h < rect.h) { + auto space = rect.h - contentRect.h; + if (alignHoriz_ == ALIGN_BOTTOM) { + rect.y += space; + rect.h -= space; + } else if (alignHoriz_ == ALIGN_CENTER) { + rect.y += space/2; + rect.h -= space/2; + } + } + + return rect; +} + +void Widget::renderToGL() const +{ + const size_t NUM_TRIANGLES = 6; + + if (!renderTexValid_) { + uint32_t rmask, gmask, bmask, amask; + int bpp; + if (!SDL_PixelFormatEnumToMasks(SDL_PIXELFORMAT_ABGR8888, + &bpp, &rmask, &gmask, &bmask, &amask)) + throw SDLException{}; + + SDLSurfaceUPtr tmp(SDL_CreateRGBSurface(0, realWidth_, realHeight_, bpp, + rmask, gmask, bmask, amask)); + render(tmp.get(), nullptr); + + if (!renderTex_ || (renderTex_->getWidth() != realWidth_) || + (renderTex_->getHeight() != realHeight_)) { + + renderTex_ = make_unique(tmp.get()); + + // Texture resize invalidates vertex attributes + renderAttribsValid_ = false; + } else { + renderTex_->copyFromSurface(tmp.get()); + } + renderTexValid_ = true; + } else + renderTex_->bind(); + + if (!renderAttribsValid_) { + int t = renderTop_, l = renderLeft_, b = renderTop_+realHeight_, + r = renderLeft_+realWidth_; + std::vector vertexAttribs{ + {{l, t}, {0, 0}}, + {{r, b}, {65535u, 65535u}}, + {{r, t}, {65535u, 0}}, + {{l, t}, {0, 0}}, + {{l, b}, {0, 65535u}}, + {{r, b}, {65535u, 65535u}}}; + + if (!vbo_) { + vbo_ = VBOManager::getInstance().alloc(sizeof(VertexAttribs)*NUM_TRIANGLES); + glBindBuffer(GL_ARRAY_BUFFER, vbo_.getVBOId()); + glGenVertexArrays(1, &vaID_); + glBindVertexArray(vaID_); + + glEnableVertexAttribArray(ATTRIB_VERTEX_POS); + glVertexAttribPointer(ATTRIB_VERTEX_POS, 2, GL_SHORT, GL_FALSE, sizeof(VertexAttribs), + (void*)(vbo_.getOfs()+offsetof(VertexAttribs, vertex))); + + glEnableVertexAttribArray(ATTRIB_VERTEX_TC); + glVertexAttribPointer(ATTRIB_VERTEX_TC, 2, GL_UNSIGNED_SHORT, GL_TRUE, + sizeof(VertexAttribs), + (void*)(vbo_.getOfs()+offsetof(VertexAttribs, texCoords))); + } else { + glBindBuffer(GL_ARRAY_BUFFER, vbo_.getVBOId()); + glBindVertexArray(vaID_); + } + glBufferSubData(GL_ARRAY_BUFFER, vbo_.getOfs(), + sizeof(VertexAttribs)*NUM_TRIANGLES, + static_cast(vertexAttribs.data())); + renderAttribsValid_ = true; + } else + glBindVertexArray(vaID_); + + glDrawArrays(GL_TRIANGLES, 0, NUM_TRIANGLES); +} diff --git a/Widget.hh b/Widget.hh new file mode 100644 index 0000000..4f03bee --- /dev/null +++ b/Widget.hh @@ -0,0 +1,114 @@ +#ifndef __OPENGLPLAYGROUND_WIDGET_HH__ +#define __OPENGLPLAYGROUND_WIDGET_HH__ + +#include +#include +#include + +#include "common.hh" +#include "VBOManager.hh" + +/* Size the widget to wrap the content size */ +static const int WRAP_CONTENT = -1; + +/* Size the widget to match the parent. If there is no parent, behaves like + WRAP_CONTENT */ +static const int MATCH_PARENT = -2; + + +static const int ALIGN_TOP = 0; +static const int ALIGN_BOTTOM = 1; +static const int ALIGN_CENTER = 2; +static const int ALIGN_LEFT = 0; +static const int ALIGN_RIGHT = 1; + +class Texture2D; + +class Widget { +public: + Widget(int width = WRAP_CONTENT, int height = WRAP_CONTENT, std::string name = ""); + + Widget(Widget const& copy) = delete; + Widget& operator=(Widget const& copy) = delete; + + virtual ~Widget(); + + std::string const& getName() const { return name_; } + + Widget* getParent() { return parent_; } + + int getWidth() const { return width_; } + int getHeight() const { return height_; } + int getRealWidth() const { return realWidth_; } + int getRealHeight() const { return realHeight_; } + + virtual void setSize(int width, int height); + + /* Set background image. The image is scaled to the widgets actual size*/ + void setBackground(SDL_Surface* surf); + /* Set background color. Only takes effect when the background image is NULL */ + void setBackgroundColor(SDL_Color bg); + /* Set foreground color. Effect depends on widget type */ + virtual void setForegroundColor(SDL_Color fg); + + /* Set the padding between the widget position and the content + Exact effect depends on widget type. In general, when a widget is rendered + the background is applied to the entire dstRect, and the content is + rendered with padding*/ + void setPadding(int left, int top, int right, int bottom); + + virtual void setAlignment(int horiz, int vert); + + void setGLRenderPos(int left, int top); + + virtual void render(SDL_Surface *dst, SDL_Rect *dstRect) const; + void renderToGL() const; + +protected: + /* Update realWidth_ and realHeight_ where necessary + (width_ or height_ == WRAP_CONTENT) */ + virtual void layout() = 0; + + /* Inform the renderToGL() code that any cached presentations are now invalid */ + void invalidateGL() { renderTexValid_ = false; } + + /* Helper to calculate the SDL_Rect with which to render the content, + respecting alignment and padding */ + SDL_Rect calcContentRect(SDL_Surface *dst, SDL_Rect *dstRect, SDL_Rect contentRect) const; + + void setParent(Widget *parent); + + /* Helpers to allow Views to access protected methods of other Widgets */ + void _setParent(Widget& child) { child.setParent(this); } + void _clearParent(Widget& child) { child.setParent(nullptr); } + void _layout(Widget& child) { child.layout(); } + + std::string name_; + + int width_, height_; + int realWidth_, realHeight_; + + SDL_Surface *background_; + SDL_Color bg_, fg_; + + int padLeft_, padTop_, padRight_, padBottom_; + + int alignHoriz_, alignVert_; + + Widget *parent_; + +private: + struct VertexAttribs { + int16_t vertex[2]; + uint16_t texCoords[2]; + } __attribute__((__packed__)); + + int renderTop_, renderLeft_; + + mutable bool renderTexValid_, renderAttribsValid_; + mutable std::unique_ptr renderTex_; + mutable VBOManager::VBOAlloc vbo_; + mutable gl::GLuint vaID_; +}; + +#endif diff --git a/common.hh b/common.hh index d75db5e..d3f0141 100644 --- a/common.hh +++ b/common.hh @@ -137,6 +137,9 @@ enum class VAFormats { VertexNormalTexcoord }; + +// Some helpers to C++11-ify SDL + struct SDLSurfaceDeleter { void operator()(SDL_Surface* ptr) const { @@ -146,4 +149,47 @@ struct SDLSurfaceDeleter { using SDLSurfaceUPtr = std::unique_ptr; +class SDLSurfaceScopedLock { +public: + SDLSurfaceScopedLock(SDL_Surface *surf) : surf_(surf) { + if (SDL_MUSTLOCK(surf_)) + if (SDL_LockSurface(surf_) != 0) + throw SDLException{}; + } + + SDLSurfaceScopedLock(SDLSurfaceUPtr& surf) : surf_(surf.get()) { + if (SDL_MUSTLOCK(surf_)) + if (SDL_LockSurface(surf_) != 0) + throw SDLException{}; + } + + SDLSurfaceScopedLock(SDLSurfaceScopedLock const& copy) = delete; + SDLSurfaceScopedLock& operator=(SDLSurfaceScopedLock const& copy) = delete; + + ~SDLSurfaceScopedLock() { + if (surf_ && SDL_MUSTLOCK(surf_)) + SDL_UnlockSurface(surf_); + } + + void unlock() { + if (surf_ && SDL_MUSTLOCK(surf_)) + SDL_UnlockSurface(surf_); + surf_ = nullptr; + } + +private: + SDL_Surface *surf_; +}; + +static bool operator==(SDL_Color const& a, SDL_Color const& b) { + return ((a.r == b.r) && (a.g == b.g) && (a.b == b.b) && (a.a == b.a)); +} + +static bool operator!=(SDL_Color const& a, SDL_Color const& b) { + return ((a.r != b.r) || (a.g != b.g) || (a.b != b.b) || (a.a != b.a)); +} + +// Compatibility with C++11 where make_unique was not in std:: yet +using std::make_unique; + #endif diff --git a/main.cc b/main.cc index b15eb1c..0921a29 100644 --- a/main.cc +++ b/main.cc @@ -25,6 +25,8 @@ #include "Object.hh" #include "font.hh" #include "Overlay.hh" +#include "TextWidget.hh" +#include "LinearLayout.hh" using namespace gl; @@ -80,6 +82,8 @@ int main(int argc, char *argv[]) if (!window) { std::printf("Could not create window: %s\n", SDL_GetError()); + TTF_Quit(); + IMG_Quit(); SDL_Quit(); return 1; } @@ -88,6 +92,8 @@ int main(int argc, char *argv[]) if (!glcontext) { std::printf("Could not create GL context: %s\n", SDL_GetError()); SDL_DestroyWindow(window); + TTF_Quit(); + IMG_Quit(); SDL_Quit(); return 1; } @@ -135,9 +141,7 @@ int main(int argc, char *argv[]) // sf::Clock clock; // sf::Time last = clock.getElapsedTime(); auto last = SDL_GetTicks(); - - VBOManager vboManager; - + Texture2D cubeTex("textures/Wood_Box_Texture.jpg"); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, static_cast(GL_LINEAR_MIPMAP_LINEAR)); if (SDL_GL_ExtensionSupported("GL_EXT_texture_filter_anisotropic")) @@ -164,7 +168,8 @@ int main(int argc, char *argv[]) // const glm::vec3 lightColor[lights] = {glm::vec3(1.0, 0.9, 0.8), // glm::vec3(0.0, 1.0, 0.0)}; // const float lightIntensity[lights] = {75.0f, 25.0f}; - + + glm::mat4 ovlProj = glm::ortho(0.0f, static_cast(width), static_cast(height), 0.0f); Framebuffer shadowFB; @@ -195,31 +200,46 @@ int main(int argc, char *argv[]) glm::value_ptr(shadowProj)); ovlProg.use(); + glUniformMatrix4fv(ovlProg.getUniformLocation("projection_matrix"), 1, GL_FALSE, + glm::value_ptr(ovlProj)); glUniform1i(ovlProg.getUniformLocation("texBase"), 0); - Object box(vboManager, "objects/woodbox.obj", prog); - Object pyramid(vboManager, "objects/pyramid.obj", prog); - Object plane(vboManager, "objects/plane.obj", prog); - - float px20_width = 2.0f*(60.0f/width); - float px20_height = 2.0f*(20.0f/height); - - std::vector ovlAttribs{ - {{glm::packSnorm1x16(1.0-px20_width), glm::packSnorm1x16(1.0)}, {0, 0}}, - {{glm::packSnorm1x16(1.0), glm::packSnorm1x16(1.0-px20_height)}, {65535u, 65535u}}, - {{glm::packSnorm1x16(1.0), glm::packSnorm1x16(1.0)}, {65535u, 0}}, - {{glm::packSnorm1x16(1.0-px20_width), glm::packSnorm1x16(1.0)}, {0, 0}}, - {{glm::packSnorm1x16(1.0-px20_width), glm::packSnorm1x16(1.0-px20_height)}, {0, 65535u}}, - {{glm::packSnorm1x16(1.0), glm::packSnorm1x16(1.0-px20_height)}, {65535u, 65535u}}}; + Object box("objects/woodbox.obj"); + Object pyramid("objects/pyramid.obj"); + Object plane("objects/plane.obj"); - Overlay ovl{vboManager, ovlAttribs, ovlProg}; - glClearColor(0.0f, 0.0f, 0.0f, 0.0f); bool close = false; - Texture2D fpsTex{64, 64, true}; unsigned fpsTime = 0, fpsCount = 0; + TextWidget fpsText{font, "0 FPS"}; + fpsText.setBackgroundColor(SDL_Color{0, 255, 0, 128}); + fpsText.setPadding(8, 8, 8, 8); + + auto laber = make_unique(font, "Dies ist ein längerer\nText, der umgebrochen werden sollte. Bla laber schwafl, laber bullshit bingo", + 200, WRAP_CONTENT); + laber->setBackgroundColor(SDL_Color{255, 0, 0, 128}); + laber->setPadding(4, 0, 4, 0); + + SDLSurfaceUPtr buttonImg(IMG_Load("textures/button_100x30.png")); + + if (!buttonImg) + throw SDLException(); + + auto button = make_unique(font, "Do stuff!", 100, 30); + button->setPadding(6, 6, 6, 6); + button->setBackground(buttonImg.get()); + button->setForegroundColor(SDL_Color{0, 0, 0, 255}); + button->setAlignment(ALIGN_CENTER, ALIGN_CENTER); + + auto layout = make_unique(); + layout->setBackgroundColor(SDL_Color{0, 255, 255, 128}); + layout->addChild(std::move(laber)); + layout->addChild(std::move(button)); + layout->setChildPadding(4); + layout->setPadding(4, 4, 4, 4); + layout->setGLRenderPos(200, 200); while (!close) { SDL_Event event; @@ -279,10 +299,18 @@ int main(int argc, char *argv[]) glm::mat4 model = glm::translate(glm::vec3(0.5f, 0.5f, -0.5f)) * glm::rotate(SDL_GetTicks()*0.001f, glm::vec3(1.0f, 0.0f, 0.0f)) * glm::translate(glm::vec3(-0.5f, -0.5f, 0.5f)); - box.draw(model, &shadowProg); - plane.draw(glm::translate(glm::vec3(2.0f, -2.5f, 0.0f))* - glm::rotate(0.35f, glm::vec3(0.0f, 1.0f, 0.0f)), &shadowProg); - pyramid.draw(glm::translate(glm::vec3(-2.0f, 0.0f, 0.0f)), &shadowProg); + glUniformMatrix4fv(shadowProg.getUniformLocation("model_matrix"), 1, GL_FALSE, + glm::value_ptr(model)); + box.draw(); + model = glm::translate(glm::vec3(2.0f, -2.5f, 0.0f)) * + glm::rotate(0.35f, glm::vec3(0.0f, 1.0f, 0.0f)); + glUniformMatrix4fv(shadowProg.getUniformLocation("model_matrix"), 1, GL_FALSE, + glm::value_ptr(model)); + plane.draw(); + model = glm::translate(glm::vec3(-2.0f, 0.0f, 0.0f)); + glUniformMatrix4fv(shadowProg.getUniformLocation("model_matrix"), 1, GL_FALSE, + glm::value_ptr(model)); + pyramid.draw(); } } @@ -307,24 +335,37 @@ int main(int argc, char *argv[]) glm::rotate(SDL_GetTicks()*0.001f, glm::vec3(1.0f, 0.0f, 0.0f)) * glm::translate(glm::vec3(-0.5f, -0.5f, 0.5f)); cubeTex.bind(); - + prog.use(); + + glUniformMatrix4fv(prog.getUniformLocation("model_matrix"), 1, GL_FALSE, + glm::value_ptr(model)); + // Shadow maps must be rendered before real drawing can begin glClientWaitSync(fence, SyncObjectMask(), 0xffffffffu); glDeleteSync(fence); - box.draw(model); + + box.draw(); + whiteTex.bind(); - plane.draw(glm::translate(glm::vec3(2.0f, -2.5f, 0.0f))* - glm::rotate(0.35f, glm::vec3(0.0f, 1.0f, 0.0f))); + model = glm::translate(glm::vec3(2.0f, -2.5f, 0.0f)) * + glm::rotate(0.35f, glm::vec3(0.0f, 1.0f, 0.0f)); + glUniformMatrix4fv(prog.getUniformLocation("model_matrix"), 1, GL_FALSE, + glm::value_ptr(model)); + plane.draw(); + redTex.bind(); - pyramid.draw(glm::translate(glm::vec3(-2.0f, 0.0f, 0.0f))); + model = glm::translate(glm::vec3(-2.0f, 0.0f, 0.0f)); + glUniformMatrix4fv(prog.getUniformLocation("model_matrix"), 1, GL_FALSE, + glm::value_ptr(model)); + pyramid.draw(); auto now = SDL_GetTicks(); auto elapsed = now - last; last = now; if (fpsTime+elapsed > 1000) { - const std::string fpsText{std::to_string(fpsCount + 1) + " FPS"s}; - fpsTex = font.render(fpsText); + fpsText.setText(std::to_string(fpsCount + 1) + " FPS"); + fpsCount = 0; fpsTime = 0; } else { @@ -335,9 +376,10 @@ int main(int argc, char *argv[]) glDisable(GL_DEPTH_TEST); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - fpsTex.bind(); - - ovl.draw(); + ovlProg.use(); + + fpsText.renderToGL(); + layout->renderToGL(); glDisable(GL_BLEND); SDL_GL_SwapWindow(window); @@ -352,6 +394,7 @@ int main(int argc, char *argv[]) SDL_DestroyWindow(window); // Clean up + TTF_Quit(); IMG_Quit(); SDL_Quit(); diff --git a/shaders.cc b/shaders.cc index 8f32c40..b9fccf6 100644 --- a/shaders.cc +++ b/shaders.cc @@ -121,14 +121,14 @@ GLint Program::getUniformLocation(std::string const& name) const return ret; } -GLint Program::getAttribLocation(std::string const& name) const -{ - auto search = _attribLocCache.find(name); - if (search != _attribLocCache.end()) - return search->second; +// GLint Program::getAttribLocation(std::string const& name) const +// { +// auto search = _attribLocCache.find(name); +// if (search != _attribLocCache.end()) +// return search->second; - GLint ret = glGetAttribLocation(_progID, name.c_str()); - if (ret != -1) - _attribLocCache.emplace(name, ret); - return ret; -} +// GLint ret = glGetAttribLocation(_progID, name.c_str()); +// if (ret != -1) +// _attribLocCache.emplace(name, ret); +// return ret; +// } diff --git a/shaders.hh b/shaders.hh index ef6a415..8ed9fa5 100644 --- a/shaders.hh +++ b/shaders.hh @@ -8,6 +8,10 @@ #include "common.hh" +static const gl::GLint ATTRIB_VERTEX_POS = 0; +static const gl::GLint ATTRIB_VERTEX_TC = 1; +static const gl::GLint ATTRIB_VERTEX_NORM = 2; +static const gl::GLint ATTRIB_VERTEX_COLOR = 3; class ShaderException : public GLException { public: @@ -78,7 +82,7 @@ public: gl::GLint getUniformLocation(std::string const& name) const; - gl::GLint getAttribLocation(std::string const& name) const; + // gl::GLint getAttribLocation(std::string const& name) const; private: GeometryShader* _geom; diff --git a/shaders/color.vs b/shaders/color.vs index f34a49e..50b957d 100644 --- a/shaders/color.vs +++ b/shaders/color.vs @@ -3,8 +3,8 @@ uniform mat4 projection_matrix; uniform mat4 modelview_matrix; -in vec3 vertex; -in vec3 vertecColor; +layout(location = 0) in vec3 vertex; +layout(location = 3) in vec3 vertecColor; out vec3 fragColor; diff --git a/shaders/overlay.vs b/shaders/overlay.vs index 85c6936..d53b36c 100644 --- a/shaders/overlay.vs +++ b/shaders/overlay.vs @@ -1,13 +1,15 @@ #version 330 core #extension GL_ARB_shading_language_420pack : enable +uniform mat4 projection_matrix; + layout(location = 0) in vec2 vertex; layout(location = 1) in vec2 vertexTC; out vec2 fragTC; void main(void) { - vec4 pos = vec4(vertex, 0.0, 1.0); + vec4 pos = projection_matrix * vec4(vertex, 0.0, 1.0); gl_Position = pos; fragTC = vertexTC; } diff --git a/texture.cc b/texture.cc index 2cd75c4..f09b96c 100644 --- a/texture.cc +++ b/texture.cc @@ -101,10 +101,10 @@ void TextureCubeMap::bind() const glBindTexture(GL_TEXTURE_CUBE_MAP, _texID); } -Texture2D::Texture2D() - : texID_(0), width_(0), height_(0), alpha_(false) -{ -} +// Texture2D::Texture2D() +// : texID_(0), width_(0), height_(0), alpha_(false) +// { +// } Texture2D::Texture2D(unsigned width, unsigned height, bool alpha) : texID_(0), width_(width), height_(height), alpha_(alpha) @@ -202,14 +202,15 @@ void Texture2D::copyFromSurface(SDL_Surface *src) surf = src; } - if (SDL_MUSTLOCK(surf)) - SDL_LockSurface(surf); - + SDLSurfaceScopedLock lock{surf}; bind(); if(surf->format->Amask == 0) glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, surf->w, surf->h, GL_RGB, GL_UNSIGNED_BYTE, surf->pixels); else glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, surf->w, surf->h, GL_RGBA, GL_UNSIGNED_BYTE, surf->pixels); + + lock.unlock(); + glGenerateMipmap(GL_TEXTURE_2D); } diff --git a/texture.hh b/texture.hh index 880ff4e..59ed264 100644 --- a/texture.hh +++ b/texture.hh @@ -61,7 +61,7 @@ private: class Texture2D { public: - Texture2D(); + // Texture2D(); Texture2D(unsigned width, unsigned height, bool alpha = false); Texture2D(std::string const& file); Texture2D(SDL_Surface *surface); diff --git a/textures/button_100x30.png b/textures/button_100x30.png new file mode 100644 index 0000000..b423bca Binary files /dev/null and b/textures/button_100x30.png differ