mirror of
https://github.com/lubosz/overte.git
synced 2025-04-13 00:43:06 +02:00
Improve the TextRenderer::draw(), first step
- Introduce a managed Buffer for data to be used on the GPU. THis is the first type of resource (second will be texture) that we plan to use in the graphics engine in the long term. this is an api agnostic replacement to QGLBuggerObject It's in the new file gpu/Resource.h(.cpp) - Add two gpu::Buffers in the TextRenderer that collect all the glyph vertices (coords + texcoords + color) during the for loop on the string characters of the TextRenderer::draw(). Right now the text glyphs are then drawn in one draw call (and not one per character) at the end of the function. THe step 2 plan is to keep on collecting all the glyphs from all the TextRenderer::Draw() issued during one frame and to draw all of them in a single drawcall. We decided to split the task in 2 so it's easier to review.
This commit is contained in:
parent
dda7c6699e
commit
9f0936de53
8 changed files with 721 additions and 624 deletions
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
219
interface/src/gpu/Resource.cpp
Normal file
219
interface/src/gpu/Resource.cpp
Normal file
|
@ -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 <QDebug>
|
||||
|
||||
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();
|
||||
//}
|
||||
}
|
||||
|
||||
};
|
||||
};
|
164
interface/src/gpu/Resource.h
Normal file
164
interface/src/gpu/Resource.h
Normal file
|
@ -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 <assert.h>
|
||||
#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
|
|
@ -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,
|
||||
|
|
|
@ -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 <QApplication>
|
||||
#include <QDesktopWidget>
|
||||
#include <QFont>
|
||||
#include <QPaintEngine>
|
||||
#include <QtDebug>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QWindow>
|
||||
|
||||
#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::Properties, TextRenderer*> 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 <QApplication>
|
||||
#include <QDesktopWidget>
|
||||
#include <QFont>
|
||||
#include <QPaintEngine>
|
||||
#include <QtDebug>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QWindow>
|
||||
|
||||
#include "InterfaceConfig.h"
|
||||
#include "TextRenderer.h"
|
||||
|
||||
#include "glm/glm.hpp"
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
|
||||
// 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();
|
||||
//}
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
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::Properties, TextRenderer*> TextRenderer::_instances;
|
||||
|
||||
Glyph::Glyph(int textureID, const QPoint& location, const QRect& bounds, int width) :
|
||||
_textureID(textureID), _location(location), _bounds(bounds), _width(width) {
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
#include <QImage>
|
||||
#include <QVector>
|
||||
|
||||
#include <assert.h>
|
||||
#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<Properties, TextRenderer*> _instances;
|
||||
|
|
Loading…
Reference in a new issue