diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 19f8ef6d5f..1202b36a9f 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -41,7 +41,7 @@ configure_file(InterfaceVersion.h.in "${PROJECT_BINARY_DIR}/includes/InterfaceVe # grab the implementation and header files from src dirs file(GLOB INTERFACE_SRCS src/*.cpp src/*.h) -foreach(SUBDIR avatar devices renderer ui starfield location scripting voxels particles entities) +foreach(SUBDIR avatar devices renderer ui starfield location scripting voxels particles entities gpu) file(GLOB_RECURSE SUBDIR_SRCS src/${SUBDIR}/*.cpp src/${SUBDIR}/*.h) set(INTERFACE_SRCS ${INTERFACE_SRCS} "${SUBDIR_SRCS}") endforeach(SUBDIR) diff --git a/interface/src/Util.cpp b/interface/src/Util.cpp index 5399af8e75..9d7f5518d0 100644 --- a/interface/src/Util.cpp +++ b/interface/src/Util.cpp @@ -197,11 +197,6 @@ static TextRenderer* textRenderer(int mono) { } } -void renderTextRenderer(int mono) { - textRenderer(mono)->executeDrawBatch(); - textRenderer(mono)->clearDrawBatch(); -} - int widthText(float scale, int mono, char const* string) { return textRenderer(mono)->computeWidth(string) * (scale / 0.10); } diff --git a/interface/src/Util.h b/interface/src/Util.h index acdbdb6f75..02cfd99f9a 100644 --- a/interface/src/Util.h +++ b/interface/src/Util.h @@ -31,8 +31,6 @@ float widthChar(float scale, int mono, char ch); void drawText(int x, int y, float scale, float radians, int mono, char const* string, const float* color); -void renderTextRenderer(int mono); - void drawvec3(int x, int y, float scale, float radians, float thick, int mono, glm::vec3 vec, float r=1.0, float g=1.0, float b=1.0); diff --git a/interface/src/gpu/Resource.cpp b/interface/src/gpu/Resource.cpp new file mode 100644 index 0000000000..890039e429 --- /dev/null +++ b/interface/src/gpu/Resource.cpp @@ -0,0 +1,219 @@ +// +// Resource.cpp +// interface/src/gpu +// +// Created by Sam Gateau on 10/8/2014. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "Resource.h" + +#include + +using namespace gpu; + +Resource::Size Resource::Sysmem::allocateMemory(Byte** dataAllocated, Size size) { + if ( !dataAllocated ) { + qWarning() << "Buffer::Sysmem::allocateMemory() : Must have a valid dataAllocated pointer."; + return NOT_ALLOCATED; + } + + // Try to allocate if needed + Size newSize = 0; + if (size > 0) { + // Try allocating as much as the required size + one block of memory + newSize = size; + (*dataAllocated) = new Byte[newSize]; + // Failed? + if (!(*dataAllocated)) { + qWarning() << "Buffer::Sysmem::allocate() : Can't allocate a system memory buffer of " << newSize << "bytes. Fails to create the buffer Sysmem."; + return NOT_ALLOCATED; + } + } + + // Return what's actually allocated + return newSize; +} + +void Resource::Sysmem::deallocateMemory(Byte* dataAllocated, Size size) { + if (dataAllocated) { + delete[] dataAllocated; + } +} + +Resource::Sysmem::Sysmem() : + _data(NULL), + _size(0), + _stamp(0) +{ +} + +Resource::Sysmem::Sysmem(Size size, const Byte* bytes) : + _data(NULL), + _size(0), + _stamp(0) +{ + if (size > 0) { + _size = allocateMemory(&_data, size); + if (_size >= size) { + if (bytes) { + memcpy(_data, bytes, size); + } + } + } +} + +Resource::Sysmem::~Sysmem() { + deallocateMemory( _data, _size ); + _data = NULL; + _size = 0; +} + +Resource::Size Resource::Sysmem::allocate(Size size) { + if (size != _size) { + Byte* newData = 0; + Size newSize = 0; + if (size > 0) { + Size allocated = allocateMemory(&newData, size); + if (allocated == NOT_ALLOCATED) { + // early exit because allocation failed + return 0; + } + newSize = allocated; + } + // Allocation was successful, can delete previous data + deallocateMemory(_data, _size); + _data = newData; + _size = newSize; + _stamp++; + } + return _size; +} + +Resource::Size Resource::Sysmem::resize(Size size) { + if (size != _size) { + Byte* newData = 0; + Size newSize = 0; + if (size > 0) { + Size allocated = allocateMemory(&newData, size); + if (allocated == NOT_ALLOCATED) { + // early exit because allocation failed + return _size; + } + newSize = allocated; + // Restore back data from old buffer in the new one + if (_data) { + Size copySize = ((newSize < _size)? newSize: _size); + memcpy( newData, _data, copySize); + } + } + // Reallocation was successful, can delete previous data + deallocateMemory(_data, _size); + _data = newData; + _size = newSize; + _stamp++; + } + return _size; +} + +Resource::Size Resource::Sysmem::setData( Size size, const Byte* bytes ) { + if (allocate(size) == size) { + if (bytes) { + memcpy( _data, bytes, _size ); + _stamp++; + } + } + return _size; +} + +Resource::Size Resource::Sysmem::setSubData( Size offset, Size size, const Byte* bytes) { + if (((offset + size) <= getSize()) && bytes) { + memcpy( _data + offset, bytes, size ); + _stamp++; + return size; + } + return 0; +} + +Resource::Size Resource::Sysmem::append(Size size, const Byte* bytes) { + if (size > 0) { + Size oldSize = getSize(); + Size totalSize = oldSize + size; + if (resize(totalSize) == totalSize) { + return setSubData(oldSize, size, bytes); + } + } + return 0; +} + +Buffer::Buffer() : + Resource(), + _sysmem(NULL), + _gpuObject(NULL) { + _sysmem = new Sysmem(); +} + +Buffer::~Buffer() { + if (_sysmem) { + delete _sysmem; + _sysmem = 0; + } + if (_gpuObject) { + delete _gpuObject; + _gpuObject = 0; + } +} + +Buffer::Size Buffer::resize(Size size) { + return editSysmem().resize(size); +} + +Buffer::Size Buffer::setData(Size size, const Byte* data) { + return editSysmem().setData(size, data); +} + +Buffer::Size Buffer::setSubData(Size offset, Size size, const Byte* data) { + return editSysmem().setSubData( offset, size, data); +} + +Buffer::Size Buffer::append(Size size, const Byte* data) { + return editSysmem().append( size, data); +} + +namespace gpu { +namespace backend { + +BufferObject::~BufferObject() { + if (_buffer!=0) { + glDeleteBuffers(1, &_buffer); + } +} + +void syncGPUObject(const Buffer& buffer) { + BufferObject* object = buffer.getGPUObject(); + + if (object && (object->_stamp == buffer.getSysmem().getStamp())) { + return; + } + + // need to have a gpu object? + if (!object) { + object = new BufferObject(); + glGenBuffers(1, &object->_buffer); + buffer.setGPUObject(object); + } + + // Now let's update the content of the bo with the sysmem version + //if (object->_size < buffer.getSize()) { + glBindBuffer(GL_ARRAY_BUFFER, object->_buffer); + glBufferData(GL_ARRAY_BUFFER, buffer.getSysmem().getSize(), buffer.getSysmem().readData(), GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + object->_stamp = buffer.getSysmem().getStamp(); + object->_size = buffer.getSysmem().getSize(); + //} +} + +}; +}; \ No newline at end of file diff --git a/interface/src/gpu/Resource.h b/interface/src/gpu/Resource.h new file mode 100644 index 0000000000..32e8e454e8 --- /dev/null +++ b/interface/src/gpu/Resource.h @@ -0,0 +1,164 @@ +// +// Resource.h +// interface/src/gpu +// +// Created by Sam Gateau on 10/8/2014. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_gpu_Resource_h +#define hifi_gpu_Resource_h + +#include +#include "interfaceconfig.h" + +namespace gpu { + +class Buffer; +typedef int Stamp; + +// TODO: move the backend namespace into dedicated files, for now we keep it close to the gpu objects definition for convenience +namespace backend { + + class BufferObject { + public: + Stamp _stamp; + GLuint _buffer; + GLuint _size; + + BufferObject() : + _stamp(0), + _buffer(0), + _size(0) + {} + + ~BufferObject(); + }; + void syncGPUObject(const Buffer& buffer); +}; + +class Resource { +public: + typedef unsigned char Byte; + typedef unsigned int Size; + + static const Size NOT_ALLOCATED = -1; + + // The size in bytes of data stored in the resource + virtual Size getSize() const = 0; + +protected: + + Resource() {} + virtual ~Resource() {} + + // Sysmem is the underneath cache for the data in ram of a resource. + class Sysmem { + public: + + Sysmem(); + Sysmem(Size size , const Byte* bytes); + ~Sysmem(); + + Size getSize() const { return _size; } + + // Allocate the byte array + // \param pSize The nb of bytes to allocate, if already exist, content is lost. + // \return The nb of bytes allocated, nothing if allready the appropriate size. + Size allocate(Size pSize); + + // Resize the byte array + // Keep previous data [0 to min(pSize, mSize)] + Size resize(Size pSize); + + // Assign data bytes and size (allocate for size, then copy bytes if exists) + Size setData( Size size, const Byte* bytes ); + + // Update Sub data, + // doesn't allocate and only copy size * bytes at the offset location + // only if all fits in the existing allocated buffer + Size setSubData( Size offset, Size size, const Byte* bytes); + + // Append new data at the end of the current buffer + // do a resize( size + getSIze) and copy the new data + // \return the number of bytes copied + Size append(Size size, const Byte* data); + + // Access the byte array. + // The edit version allow to map data. + inline const Byte* readData() const { return _data; } + inline Byte* editData() { _stamp++; return _data; } + + template< typename T > + const T* read() const { return reinterpret_cast< T* > ( _data ); } + template< typename T > + T* edit() const { _stamp++; return reinterpret_cast< T* > ( _data ); } + + // Access the current version of the sysmem, used to compare if copies are in sync + inline Stamp getStamp() const { return _stamp; } + + static Size allocateMemory(Byte** memAllocated, Size size); + static void deallocateMemory(Byte* memDeallocated, Size size); + + private: + Sysmem(const Sysmem& sysmem) {} + Sysmem &operator=(const Sysmem &other) {return *this;} + + Stamp _stamp; + Size _size; + Byte* _data; + }; + +}; + +class Buffer : public Resource { +public: + + Buffer(); + Buffer(const Buffer& buf ); + ~Buffer(); + + // The size in bytes of data stored in the buffer + inline Size getSize() const { return getSysmem().getSize(); } + inline const Byte* getData() const { return getSysmem().readData(); } + + // Resize the buffer + // Keep previous data [0 to min(pSize, mSize)] + Size resize(Size pSize); + + // Assign data bytes and size (allocate for size, then copy bytes if exists) + Size setData(Size size, const Byte* data); + + // Assign data bytes and size (allocate for size, then copy bytes if exists) + Size setSubData(Size offset, Size size, const Byte* data); + + // Append new data at the end of the current buffer + // do a resize( size + getSize) and copy the new data + // \return the number of bytes copied + Size append(Size size, const Byte* data); + + // this is a temporary hack so the current rendering code can access the underneath gl Buffer Object + // TODO: remove asap, when the backend is doing more of the gl features + inline GLuint getGLBufferObject() const { backend::syncGPUObject(*this); return getGPUObject()->_buffer; } + +protected: + + Sysmem* _sysmem; + + typedef backend::BufferObject GPUObject; + mutable backend::BufferObject* _gpuObject; + + inline const Sysmem& getSysmem() const { assert(_sysmem); return (*_sysmem); } + inline Sysmem& editSysmem() { assert(_sysmem); return (*_sysmem); } + + inline GPUObject* getGPUObject() const { return _gpuObject; } + inline void setGPUObject(GPUObject* gpuObject) const { _gpuObject = gpuObject; } + + friend void backend::syncGPUObject(const Buffer& buffer); +}; + +}; + +#endif \ No newline at end of file diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 6b7d499bf9..dd9ac67837 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -690,7 +690,6 @@ void Stats::display( drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, reflectionsStatus, color); } - } void Stats::setMetavoxelStats(int internal, int leaves, int sendProgress, diff --git a/interface/src/ui/TextRenderer.cpp b/interface/src/ui/TextRenderer.cpp index 5f00d84b90..d8d1b17da7 100644 --- a/interface/src/ui/TextRenderer.cpp +++ b/interface/src/ui/TextRenderer.cpp @@ -1,490 +1,343 @@ -// -// TextRenderer.cpp -// interface/src/ui -// -// Created by Andrzej Kapolka on 4/24/13. -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "InterfaceConfig.h" -#include "TextRenderer.h" - -// the width/height of the cached glyph textures -const int IMAGE_SIZE = 256; - -static uint qHash(const TextRenderer::Properties& key, uint seed = 0) { - // can be switched to qHash(key.font, seed) when we require Qt 5.3+ - return qHash(key.font.family(), qHash(key.font.pointSize(), seed)); -} - -static bool operator==(const TextRenderer::Properties& p1, const TextRenderer::Properties& p2) { - return p1.font == p2.font && p1.effect == p2.effect && p1.effectThickness == p2.effectThickness && p1.color == p2.color; -} - -TextRenderer* TextRenderer::getInstance(const char* family, int pointSize, int weight, bool italic, - EffectType effect, int effectThickness, const QColor& color) { - Properties properties = { QFont(family, pointSize, weight, italic), effect, effectThickness, color }; - TextRenderer*& instance = _instances[properties]; - if (!instance) { - instance = new TextRenderer(properties); - } - return instance; -} - -TextRenderer::~TextRenderer() { - glDeleteTextures(_allTextureIDs.size(), _allTextureIDs.constData()); -} - -int TextRenderer::calculateHeight(const char* str) { - int maxHeight = 0; - for (const char* ch = str; *ch != 0; ch++) { - const Glyph& glyph = getGlyph(*ch); - if (glyph.textureID() == 0) { - continue; - } - - if (glyph.bounds().height() > maxHeight) { - maxHeight = glyph.bounds().height(); - } - } - return maxHeight; -} - -int TextRenderer::draw(int x, int y, const char* str) { - glEnable(GL_TEXTURE_2D); - - int maxHeight = 0; - for (const char* ch = str; *ch != 0; ch++) { - const Glyph& glyph = getGlyph(*ch); - if (glyph.textureID() == 0) { - x += glyph.width(); - continue; - } - - if (glyph.bounds().height() > maxHeight) { - maxHeight = glyph.bounds().height(); - } - - glBindTexture(GL_TEXTURE_2D, glyph.textureID()); - - int left = x + glyph.bounds().x(); - int right = x + glyph.bounds().x() + glyph.bounds().width(); - int bottom = y + glyph.bounds().y(); - int top = y + glyph.bounds().y() + glyph.bounds().height(); - - float scale = QApplication::desktop()->windowHandle()->devicePixelRatio() / IMAGE_SIZE; - float ls = glyph.location().x() * scale; - float rs = (glyph.location().x() + glyph.bounds().width()) * scale; - float bt = glyph.location().y() * scale; - float tt = (glyph.location().y() + glyph.bounds().height()) * scale; - - glBegin(GL_QUADS); - glTexCoord2f(ls, bt); - glVertex2f(left, bottom); - glTexCoord2f(rs, bt); - glVertex2f(right, bottom); - glTexCoord2f(rs, tt); - glVertex2f(right, top); - glTexCoord2f(ls, tt); - glVertex2f(left, top); - glEnd(); -/* - const int NUM_COORDS_PER_GLYPH = 16; - float vertexBuffer[NUM_COORDS_PER_GLYPH] = { ls, bt, left, bottom, rs, bt, right, bottom, rs, tt, right, top, ls, tt, left, top }; - gpu::Buffer::Size offset = sizeof(vertexBuffer)*_numGlyphsBatched; - if ((offset + sizeof(vertexBuffer)) > _glyphsBuffer.getSize()) { - _glyphsBuffer.append(sizeof(vertexBuffer), (gpu::Buffer::Byte*) vertexBuffer); - } else { - _glyphsBuffer.setSubData(offset, sizeof(vertexBuffer), (gpu::Buffer::Byte*) vertexBuffer); - } - _numGlyphsBatched++; -*/ - x += glyph.width(); - } - glBindTexture(GL_TEXTURE_2D, 0); - glDisable(GL_TEXTURE_2D); - - // executeDrawBatch(); - // clearDrawBatch(); - - return maxHeight; -} - -int TextRenderer::computeWidth(char ch) -{ - return getGlyph(ch).width(); -} - -int TextRenderer::computeWidth(const char* str) -{ - int width = 0; - for (const char* ch = str; *ch != 0; ch++) { - width += computeWidth(*ch); - } - return width; -} - -TextRenderer::TextRenderer(const Properties& properties) : - _font(properties.font), - _metrics(_font), - _effectType(properties.effect), - _effectThickness(properties.effectThickness), - _x(IMAGE_SIZE), - _y(IMAGE_SIZE), - _rowHeight(0), - _color(properties.color), - _glyphsBuffer(), - _numGlyphsBatched(0) -{ - _font.setKerning(false); -} - -const Glyph& TextRenderer::getGlyph(char c) { - Glyph& glyph = _glyphs[c]; - if (glyph.isValid()) { - return glyph; - } - // we use 'J' as a representative size for the solid block character - QChar ch = (c == SOLID_BLOCK_CHAR) ? QChar('J') : QChar(c); - QRect baseBounds = _metrics.boundingRect(ch); - if (baseBounds.isEmpty()) { - glyph = Glyph(0, QPoint(), QRect(), _metrics.width(ch)); - return glyph; - } - // grow the bounds to account for effect, if any - if (_effectType == SHADOW_EFFECT) { - baseBounds.adjust(-_effectThickness, 0, 0, _effectThickness); - - } else if (_effectType == OUTLINE_EFFECT) { - baseBounds.adjust(-_effectThickness, -_effectThickness, _effectThickness, _effectThickness); - } - - // grow the bounds to account for antialiasing - baseBounds.adjust(-1, -1, 1, 1); - - // adjust bounds for device pixel scaling - float ratio = QApplication::desktop()->windowHandle()->devicePixelRatio(); - QRect bounds(baseBounds.x() * ratio, baseBounds.y() * ratio, baseBounds.width() * ratio, baseBounds.height() * ratio); - - if (_x + bounds.width() > IMAGE_SIZE) { - // we can't fit it on the current row; move to next - _y += _rowHeight; - _x = _rowHeight = 0; - } - if (_y + bounds.height() > IMAGE_SIZE) { - // can't fit it on current texture; make a new one - glGenTextures(1, &_currentTextureID); - _x = _y = _rowHeight = 0; - - glBindTexture(GL_TEXTURE_2D, _currentTextureID); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, IMAGE_SIZE, IMAGE_SIZE, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - _allTextureIDs.append(_currentTextureID); - - } else { - glBindTexture(GL_TEXTURE_2D, _currentTextureID); - } - // render the glyph into an image and copy it into the texture - QImage image(bounds.width(), bounds.height(), QImage::Format_ARGB32); - if (c == SOLID_BLOCK_CHAR) { - image.fill(_color); - - } else { - image.fill(0); - QPainter painter(&image); - QFont font = _font; - if (ratio == 1.0f) { - painter.setFont(_font); - } else { - QFont enlargedFont = _font; - enlargedFont.setPointSize(_font.pointSize() * ratio); - painter.setFont(enlargedFont); - } - if (_effectType == SHADOW_EFFECT) { - for (int i = 0; i < _effectThickness * ratio; i++) { - painter.drawText(-bounds.x() - 1 - i, -bounds.y() + 1 + i, ch); - } - } else if (_effectType == OUTLINE_EFFECT) { - QPainterPath path; - QFont font = _font; - font.setStyleStrategy(QFont::ForceOutline); - path.addText(-bounds.x() - 0.5, -bounds.y() + 0.5, font, ch); - QPen pen; - pen.setWidth(_effectThickness * ratio); - pen.setJoinStyle(Qt::RoundJoin); - pen.setCapStyle(Qt::RoundCap); - painter.setPen(pen); - painter.setRenderHint(QPainter::Antialiasing); - painter.drawPath(path); - } - painter.setPen(_color); - painter.drawText(-bounds.x(), -bounds.y(), ch); - } - glTexSubImage2D(GL_TEXTURE_2D, 0, _x, _y, bounds.width(), bounds.height(), GL_RGBA, GL_UNSIGNED_BYTE, image.constBits()); - - glyph = Glyph(_currentTextureID, QPoint(_x / ratio, _y / ratio), baseBounds, _metrics.width(ch)); - _x += bounds.width(); - _rowHeight = qMax(_rowHeight, bounds.height()); - - glBindTexture(GL_TEXTURE_2D, 0); - return glyph; -} - -void TextRenderer::executeDrawBatch() { - if (_numGlyphsBatched<=0) { - return; - } - - glEnable(GL_TEXTURE_2D); - - GLuint textureID = 0; - glBindTexture(GL_TEXTURE_2D, textureID); - - gpu::backend::syncGPUObject(_glyphsBuffer); - GLuint vbo = _glyphsBuffer.getGLBufferObject(); - - glBindBuffer(GL_ARRAY_BUFFER, vbo); - - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - - const int NUM_POS_COORDS = 2; - const int NUM_TEX_COORDS = 2; - const int VERTEX_STRIDE = (NUM_POS_COORDS + NUM_TEX_COORDS) * sizeof(float); - const int VERTEX_TEXCOORD_OFFSET = NUM_POS_COORDS * sizeof(float); - glVertexPointer(2, GL_FLOAT, VERTEX_STRIDE, 0); - glTexCoordPointer(2, GL_FLOAT, VERTEX_STRIDE, (GLvoid*) VERTEX_TEXCOORD_OFFSET ); - - glDrawArrays(GL_QUADS, 0, _numGlyphsBatched * 4); - - glDisableClientState(GL_VERTEX_ARRAY); - glDisableClientState(GL_TEXTURE_COORD_ARRAY); - - glBindBuffer(GL_ARRAY_BUFFER, 0); - - glBindTexture(GL_TEXTURE_2D, 0); - glDisable(GL_TEXTURE_2D); - -} - -void TextRenderer::clearDrawBatch() { - _numGlyphsBatched = 0; -} - -QHash TextRenderer::_instances; - -Glyph::Glyph(int textureID, const QPoint& location, const QRect& bounds, int width) : - _textureID(textureID), _location(location), _bounds(bounds), _width(width) { -} - -using namespace gpu; - -Buffer::Size Buffer::Sysmem::allocateMemory(Byte** dataAllocated, Size size) { - if ( !dataAllocated ) { - qWarning() << "Buffer::Sysmem::allocateMemory() : Must have a valid dataAllocated pointer."; - return NOT_ALLOCATED; - } - - // Try to allocate if needed - Size newSize = 0; - if (size > 0) { - // Try allocating as much as the required size + one block of memory - newSize = size; - (*dataAllocated) = new Byte[newSize]; - // Failed? - if (!(*dataAllocated)) { - qWarning() << "Buffer::Sysmem::allocate() : Can't allocate a system memory buffer of " << newSize << "bytes. Fails to create the buffer Sysmem."; - return NOT_ALLOCATED; - } - } - - // Return what's actually allocated - return newSize; -} - -void Buffer::Sysmem::deallocateMemory(Byte* dataAllocated, Size size) { - if (dataAllocated) { - delete[] dataAllocated; - } -} - -Buffer::Sysmem::Sysmem() : - _data(NULL), - _size(0), - _stamp(0) -{ -} - -Buffer::Sysmem::Sysmem(Size size, const Byte* bytes) : - _data(NULL), - _size(0), - _stamp(0) -{ - if (size > 0) { - _size = allocateMemory(&_data, size); - if (_size >= size) { - if (bytes) { - memcpy(_data, bytes, size); - } - } - } -} - -Buffer::Sysmem::~Sysmem() { - deallocateMemory( _data, _size ); - _data = NULL; - _size = 0; +// +// TextRenderer.cpp +// interface/src/ui +// +// Created by Andrzej Kapolka on 4/24/13. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "InterfaceConfig.h" +#include "TextRenderer.h" + +#include "glm/glm.hpp" +#include + + +// the width/height of the cached glyph textures +const int IMAGE_SIZE = 256; + +static uint qHash(const TextRenderer::Properties& key, uint seed = 0) { + // can be switched to qHash(key.font, seed) when we require Qt 5.3+ + return qHash(key.font.family(), qHash(key.font.pointSize(), seed)); } -Buffer::Size Buffer::Sysmem::allocate(Size size) { - if (size != _size) { - Byte* newData = 0; - Size newSize = 0; - if (size > 0) { - Size allocated = allocateMemory(&newData, size); - if (allocated == NOT_ALLOCATED) { - // early exit because allocation failed - return 0; - } - newSize = allocated; - } - // Allocation was successful, can delete previous data - deallocateMemory(_data, _size); - _data = newData; - _size = newSize; - _stamp++; - } - return _size; +static bool operator==(const TextRenderer::Properties& p1, const TextRenderer::Properties& p2) { + return p1.font == p2.font && p1.effect == p2.effect && p1.effectThickness == p2.effectThickness && p1.color == p2.color; } -Buffer::Size Buffer::Sysmem::resize(Size size) { - if (size != _size) { - Byte* newData = 0; - Size newSize = 0; - if (size > 0) { - Size allocated = allocateMemory(&newData, size); - if (allocated == NOT_ALLOCATED) { - // early exit because allocation failed - return _size; - } - newSize = allocated; - // Restore back data from old buffer in the new one - if (_data) { - Size copySize = ((newSize < _size)? newSize: _size); - memcpy( newData, _data, copySize); - } - } - // Reallocation was successful, can delete previous data - deallocateMemory(_data, _size); - _data = newData; - _size = newSize; - _stamp++; +TextRenderer* TextRenderer::getInstance(const char* family, int pointSize, int weight, bool italic, + EffectType effect, int effectThickness, const QColor& color) { + Properties properties = { QFont(family, pointSize, weight, italic), effect, effectThickness, color }; + TextRenderer*& instance = _instances[properties]; + if (!instance) { + instance = new TextRenderer(properties); } - return _size; + return instance; } -Buffer::Size Buffer::Sysmem::setData( Size size, const Byte* bytes ) { - if (allocate(size) == size) { - if (bytes) { - memcpy( _data, bytes, _size ); - _stamp++; - } - } - return _size; +TextRenderer::~TextRenderer() { + glDeleteTextures(_allTextureIDs.size(), _allTextureIDs.constData()); } -Buffer::Size Buffer::Sysmem::setSubData( Size offset, Size size, const Byte* bytes) { - if (((offset + size) <= getSize()) && bytes) { - memcpy( _data + offset, bytes, size ); - _stamp++; - return size; +int TextRenderer::calculateHeight(const char* str) { + int maxHeight = 0; + for (const char* ch = str; *ch != 0; ch++) { + const Glyph& glyph = getGlyph(*ch); + if (glyph.textureID() == 0) { + continue; + } + + if (glyph.bounds().height() > maxHeight) { + maxHeight = glyph.bounds().height(); + } } - return 0; -} - -Buffer::Size Buffer::Sysmem::append(Size size, const Byte* bytes) { - if (size > 0) { - Size oldSize = getSize(); - Size totalSize = oldSize + size; - if (resize(totalSize) == totalSize) { - return setSubData(oldSize, size, bytes); - } - } - return 0; -} - -Buffer::Buffer() : - _sysmem(NULL), - _gpuObject(NULL) { - _sysmem = new Sysmem(); -} - -Buffer::~Buffer() { - if (_sysmem) { - delete _sysmem; - _sysmem = 0; - } - if (_gpuObject) { - delete _gpuObject; - _gpuObject = 0; - } -} - -Buffer::Size Buffer::resize(Size size) { - return editSysmem().resize(size); -} - -Buffer::Size Buffer::setData(Size size, const Byte* data) { - return editSysmem().setData(size, data); -} - -Buffer::Size Buffer::setSubData(Size offset, Size size, const Byte* data) { - return editSysmem().setSubData( offset, size, data); -} - -Buffer::Size Buffer::append(Size size, const Byte* data) { - return editSysmem().append( size, data); -} - -namespace gpu { -namespace backend { - -void syncGPUObject(const Buffer& buffer) { - BufferObject* object = buffer.getGPUObject(); - - if (object && (object->_stamp == buffer.getSysmem().getStamp())) { - return; - } - - // need to have a gpu object? - if (!object) { - object = new BufferObject(); - glGenBuffers(1, &object->_buffer); - buffer.setGPUObject(object); - } - - // Now let's update the content of the bo with the sysmem version - //if (object->_size < buffer.getSize()) { - glBindBuffer(GL_COPY_WRITE_BUFFER, object->_buffer); - glBufferData(GL_COPY_WRITE_BUFFER, buffer.getSysmem().getSize(), buffer.getSysmem().read(), GL_STATIC_DRAW); - glBindBuffer(GL_COPY_WRITE_BUFFER, 0); - object->_stamp = buffer.getSysmem().getStamp(); - object->_size = buffer.getSysmem().getSize(); - //} -} - -}; -}; \ No newline at end of file + return maxHeight; +} + +int TextRenderer::draw(int x, int y, const char* str) { + // Grab the current color + float currentColor[4]; + glGetFloatv(GL_CURRENT_COLOR, currentColor); + int compactColor = ((int( currentColor[0] * 255.f) & 0xFF)) | + ((int( currentColor[1] * 255.f) & 0xFF) << 8) | + ((int( currentColor[2] * 255.f) & 0xFF) << 16) | + ((int( currentColor[3] * 255.f) & 0xFF) << 24); + + //glEnable(GL_TEXTURE_2D); + + int maxHeight = 0; + for (const char* ch = str; *ch != 0; ch++) { + const Glyph& glyph = getGlyph(*ch); + if (glyph.textureID() == 0) { + x += glyph.width(); + continue; + } + + if (glyph.bounds().height() > maxHeight) { + maxHeight = glyph.bounds().height(); + } + //glBindTexture(GL_TEXTURE_2D, glyph.textureID()); + + int left = x + glyph.bounds().x(); + int right = x + glyph.bounds().x() + glyph.bounds().width(); + int bottom = y + glyph.bounds().y(); + int top = y + glyph.bounds().y() + glyph.bounds().height(); + + glm::vec2 leftBottom = glm::vec2(float(left), float(bottom)); + glm::vec2 rightTop = glm::vec2(float(right), float(top)); + + float scale = QApplication::desktop()->windowHandle()->devicePixelRatio() / IMAGE_SIZE; + float ls = glyph.location().x() * scale; + float rs = (glyph.location().x() + glyph.bounds().width()) * scale; + float bt = glyph.location().y() * scale; + float tt = (glyph.location().y() + glyph.bounds().height()) * scale; +/* + glBegin(GL_QUADS); + glTexCoord2f(ls, bt); + glVertex2f(left, bottom); + glTexCoord2f(rs, bt); + glVertex2f(right, bottom); + glTexCoord2f(rs, tt); + glVertex2f(right, top); + glTexCoord2f(ls, tt); + glVertex2f(left, top); + glEnd(); +*/ + + const int NUM_COORDS_SCALARS_PER_GLYPH = 16; + float vertexBuffer[NUM_COORDS_SCALARS_PER_GLYPH] = { leftBottom.x, leftBottom.y, ls, bt, + rightTop.x, leftBottom.y, rs, bt, + rightTop.x, rightTop.y, rs, tt, + leftBottom.x, rightTop.y, ls, tt, }; + + const int NUM_COLOR_SCALARS_PER_GLYPH = 4; + unsigned int colorBuffer[NUM_COLOR_SCALARS_PER_GLYPH] = { compactColor, compactColor, compactColor, compactColor }; + + gpu::Buffer::Size offset = sizeof(vertexBuffer)*_numGlyphsBatched; + gpu::Buffer::Size colorOffset = sizeof(colorBuffer)*_numGlyphsBatched; + if ((offset + sizeof(vertexBuffer)) > _glyphsBuffer.getSize()) { + _glyphsBuffer.append(sizeof(vertexBuffer), (gpu::Buffer::Byte*) vertexBuffer); + _glyphsColorBuffer.append(sizeof(colorBuffer), (gpu::Buffer::Byte*) colorBuffer); + } else { + _glyphsBuffer.setSubData(offset, sizeof(vertexBuffer), (gpu::Buffer::Byte*) vertexBuffer); + _glyphsColorBuffer.setSubData(colorOffset, sizeof(colorBuffer), (gpu::Buffer::Byte*) colorBuffer); + } + _numGlyphsBatched++; + + x += glyph.width(); + } + + // TODO: remove these calls once we move to a full batched rendering of the text, for now, one draw call per draw() function call + drawBatch(); + clearBatch(); + + // glBindTexture(GL_TEXTURE_2D, 0); + // glDisable(GL_TEXTURE_2D); + + return maxHeight; +} + +int TextRenderer::computeWidth(char ch) +{ + return getGlyph(ch).width(); +} + +int TextRenderer::computeWidth(const char* str) +{ + int width = 0; + for (const char* ch = str; *ch != 0; ch++) { + width += computeWidth(*ch); + } + return width; +} + +TextRenderer::TextRenderer(const Properties& properties) : + _font(properties.font), + _metrics(_font), + _effectType(properties.effect), + _effectThickness(properties.effectThickness), + _x(IMAGE_SIZE), + _y(IMAGE_SIZE), + _rowHeight(0), + _color(properties.color), + _glyphsBuffer(), + _numGlyphsBatched(0) +{ + _font.setKerning(false); +} + +const Glyph& TextRenderer::getGlyph(char c) { + Glyph& glyph = _glyphs[c]; + if (glyph.isValid()) { + return glyph; + } + // we use 'J' as a representative size for the solid block character + QChar ch = (c == SOLID_BLOCK_CHAR) ? QChar('J') : QChar(c); + QRect baseBounds = _metrics.boundingRect(ch); + if (baseBounds.isEmpty()) { + glyph = Glyph(0, QPoint(), QRect(), _metrics.width(ch)); + return glyph; + } + // grow the bounds to account for effect, if any + if (_effectType == SHADOW_EFFECT) { + baseBounds.adjust(-_effectThickness, 0, 0, _effectThickness); + + } else if (_effectType == OUTLINE_EFFECT) { + baseBounds.adjust(-_effectThickness, -_effectThickness, _effectThickness, _effectThickness); + } + + // grow the bounds to account for antialiasing + baseBounds.adjust(-1, -1, 1, 1); + + // adjust bounds for device pixel scaling + float ratio = QApplication::desktop()->windowHandle()->devicePixelRatio(); + QRect bounds(baseBounds.x() * ratio, baseBounds.y() * ratio, baseBounds.width() * ratio, baseBounds.height() * ratio); + + if (_x + bounds.width() > IMAGE_SIZE) { + // we can't fit it on the current row; move to next + _y += _rowHeight; + _x = _rowHeight = 0; + } + if (_y + bounds.height() > IMAGE_SIZE) { + // can't fit it on current texture; make a new one + glGenTextures(1, &_currentTextureID); + _x = _y = _rowHeight = 0; + + glBindTexture(GL_TEXTURE_2D, _currentTextureID); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, IMAGE_SIZE, IMAGE_SIZE, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + _allTextureIDs.append(_currentTextureID); + + } else { + glBindTexture(GL_TEXTURE_2D, _currentTextureID); + } + // render the glyph into an image and copy it into the texture + QImage image(bounds.width(), bounds.height(), QImage::Format_ARGB32); + if (c == SOLID_BLOCK_CHAR) { + image.fill(_color); + + } else { + image.fill(0); + QPainter painter(&image); + QFont font = _font; + if (ratio == 1.0f) { + painter.setFont(_font); + } else { + QFont enlargedFont = _font; + enlargedFont.setPointSize(_font.pointSize() * ratio); + painter.setFont(enlargedFont); + } + if (_effectType == SHADOW_EFFECT) { + for (int i = 0; i < _effectThickness * ratio; i++) { + painter.drawText(-bounds.x() - 1 - i, -bounds.y() + 1 + i, ch); + } + } else if (_effectType == OUTLINE_EFFECT) { + QPainterPath path; + QFont font = _font; + font.setStyleStrategy(QFont::ForceOutline); + path.addText(-bounds.x() - 0.5, -bounds.y() + 0.5, font, ch); + QPen pen; + pen.setWidth(_effectThickness * ratio); + pen.setJoinStyle(Qt::RoundJoin); + pen.setCapStyle(Qt::RoundCap); + painter.setPen(pen); + painter.setRenderHint(QPainter::Antialiasing); + painter.drawPath(path); + } + painter.setPen(_color); + painter.drawText(-bounds.x(), -bounds.y(), ch); + } + glTexSubImage2D(GL_TEXTURE_2D, 0, _x, _y, bounds.width(), bounds.height(), GL_RGBA, GL_UNSIGNED_BYTE, image.constBits()); + + glyph = Glyph(_currentTextureID, QPoint(_x / ratio, _y / ratio), baseBounds, _metrics.width(ch)); + _x += bounds.width(); + _rowHeight = qMax(_rowHeight, bounds.height()); + + glBindTexture(GL_TEXTURE_2D, 0); + return glyph; +} + +void TextRenderer::drawBatch() { + if (_numGlyphsBatched<=0) { + return; + } + + // TODO: Right now the drawBatch is called while calling the draw() function but in the future we'll need to apply the correct transform stack + /* + GLint matrixMode; + glGetIntegerv(GL_MATRIX_MODE, &matrixMode); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + */ + + glEnable(GL_TEXTURE_2D); + // TODO: Apply the correct font atlas texture, for now only one texture per TextRenderer so it should be good + glBindTexture(GL_TEXTURE_2D, _currentTextureID); + + GLuint vbo = _glyphsBuffer.getGLBufferObject(); + GLuint colorvbo = _glyphsColorBuffer.getGLBufferObject(); + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + + const int NUM_POS_COORDS = 2; + const int NUM_TEX_COORDS = 2; + const int VERTEX_STRIDE = (NUM_POS_COORDS + NUM_TEX_COORDS) * sizeof(float); + const int VERTEX_TEXCOORD_OFFSET = NUM_POS_COORDS * sizeof(float); + + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glVertexPointer(2, GL_FLOAT, VERTEX_STRIDE, 0); + glTexCoordPointer(2, GL_FLOAT, VERTEX_STRIDE, (GLvoid*) VERTEX_TEXCOORD_OFFSET ); + + glBindBuffer(GL_ARRAY_BUFFER, colorvbo); + glColorPointer(4, GL_UNSIGNED_BYTE, 0, (GLvoid*) 0 ); + + glDrawArrays(GL_QUADS, 0, _numGlyphsBatched * 4); + + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + + glBindTexture(GL_TEXTURE_2D, 0); + glDisable(GL_TEXTURE_2D); + + // TODO: Right now the drawBatch is called while calling the draw() function but in the future we'll need to apply the correct transform stack + /* + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + glMatrixMode(matrixMode); + */ +} + +void TextRenderer::clearBatch() { + _numGlyphsBatched = 0; +} + +QHash TextRenderer::_instances; + +Glyph::Glyph(int textureID, const QPoint& location, const QRect& bounds, int width) : + _textureID(textureID), _location(location), _bounds(bounds), _width(width) { +} diff --git a/interface/src/ui/TextRenderer.h b/interface/src/ui/TextRenderer.h index ec1c9a088e..70283eedd8 100644 --- a/interface/src/ui/TextRenderer.h +++ b/interface/src/ui/TextRenderer.h @@ -19,7 +19,7 @@ #include #include -#include +#include "gpu/Resource.h" #include "InterfaceConfig.h" @@ -37,138 +37,6 @@ const char SOLID_BLOCK_CHAR = 127; class Glyph; -namespace gpu { -class Buffer; -typedef int Stamp; - -namespace backend { - - class BufferObject { - public: - Stamp _stamp; - GLuint _buffer; - GLuint _size; - - BufferObject() : - _stamp(0), - _buffer(0), - _size(0) - {} - }; - void syncGPUObject(const Buffer& buffer); -}; - -class Buffer { -public: - - typedef unsigned char Byte; - typedef unsigned int Size; - - static const Size MIN_ALLOCATION_BLOCK_SIZE = 256; - static const Size NOT_ALLOCATED = -1; - - Buffer(); - Buffer(const Buffer& buf ); - ~Buffer(); - - // The size in bytes of data stored in the buffer - inline Size getSize() const { return getSysmem().getSize(); } - inline const Byte* getData() const { return getSysmem().read(); } - - // Resize the buffer - // Keep previous data [0 to min(pSize, mSize)] - Size resize(Size pSize); - - // Assign data bytes and size (allocate for size, then copy bytes if exists) - Size setData(Size size, const Byte* data); - - // Assign data bytes and size (allocate for size, then copy bytes if exists) - Size setSubData(Size offset, Size size, const Byte* data); - - // Append new data at the end of the current buffer - // do a resize( size + getSIze) and copy the new data - // \return the number of bytes copied - Size append(Size size, const Byte* data); - - // this is a temporary hack so the current rendering code can access the underneath gl Buffer Object - // TODO: remove asap, when the backend is doing more of the gl features - inline GLuint getGLBufferObject() const { backend::syncGPUObject(*this); return getGPUObject()->_buffer; } - -protected: - - // Sysmem is the underneath cache for the data in ram. - class Sysmem { - public: - - Sysmem(); - Sysmem(Size size , const Byte* bytes); - ~Sysmem(); - - Size getSize() const { return _size; } - - // Allocate the byte array - // \param pSize The nb of bytes to allocate, if already exist, content is lost. - // \return The nb of bytes allocated, nothing if allready the appropriate size. - Size allocate(Size pSize); - - // Resize the byte array - // Keep previous data [0 to min(pSize, mSize)] - Size resize(Size pSize); - - // Assign data bytes and size (allocate for size, then copy bytes if exists) - Size setData( Size size, const Byte* bytes ); - - // Update Sub data, - // doesn't allocate and only copy size * bytes at the offset location - // only if all fits in the existing allocated buffer - Size setSubData( Size offset, Size size, const Byte* bytes); - - // Append new data at the end of the current buffer - // do a resize( size + getSIze) and copy the new data - // \return the number of bytes copied - Size append(Size size, const Byte* data); - - // Access the byte array. - // The edit version allow to map data. - inline const Byte* read() const { return _data; } - inline Byte* edit() { _stamp++; return _data; } - - template< typename T > - const T* read() const { return reinterpret_cast< T* > ( _data ); } - template< typename T > - T* edit() const { _stamp++; return reinterpret_cast< T* > ( _data ); } - - // Access the current version of the sysmem, used to compare if copies are in sync - inline Stamp getStamp() const { return _stamp; } - - static Size allocateMemory(Byte** memAllocated, Size size); - static void deallocateMemory(Byte* memDeallocated, Size size); - - private: - Sysmem(const Sysmem& sysmem) {} - Sysmem &operator=(const Sysmem &other) {return *this;} - - Byte* _data; - Size _size; - Stamp _stamp; - }; - - Sysmem* _sysmem; - - typedef backend::BufferObject GPUObject; - mutable backend::BufferObject* _gpuObject; - - inline const Sysmem& getSysmem() const { assert(_sysmem); return (*_sysmem); } - inline Sysmem& editSysmem() { assert(_sysmem); return (*_sysmem); } - - inline GPUObject* getGPUObject() const { return _gpuObject; } - inline void setGPUObject(GPUObject* gpuObject) const { _gpuObject = gpuObject; } - - friend void backend::syncGPUObject(const Buffer& buffer); -}; - -}; - class TextRenderer { public: @@ -198,8 +66,8 @@ public: int computeWidth(char ch); int computeWidth(const char* str); - void executeDrawBatch(); - void clearDrawBatch(); + void drawBatch(); + void clearBatch(); private: TextRenderer(const Properties& properties); @@ -238,6 +106,7 @@ private: // Graphics Buffer containing the current accumulated glyphs to render gpu::Buffer _glyphsBuffer; + gpu::Buffer _glyphsColorBuffer; int _numGlyphsBatched; static QHash _instances;