resolve conflicts on merge with upstream master
|
@ -94,6 +94,7 @@ var modelURLs = [
|
|||
var mode = 0;
|
||||
var isActive = false;
|
||||
|
||||
var placingEntityID = null;
|
||||
|
||||
var toolBar = (function () {
|
||||
var that = {},
|
||||
|
@ -363,7 +364,7 @@ var toolBar = (function () {
|
|||
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE));
|
||||
|
||||
if (position.x > 0 && position.y > 0 && position.z > 0) {
|
||||
Entities.addEntity({
|
||||
placingEntityID = Entities.addEntity({
|
||||
type: "Box",
|
||||
position: grid.snapToSurface(grid.snapToGrid(position, false, DEFAULT_DIMENSIONS), DEFAULT_DIMENSIONS),
|
||||
dimensions: DEFAULT_DIMENSIONS,
|
||||
|
@ -380,7 +381,7 @@ var toolBar = (function () {
|
|||
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE));
|
||||
|
||||
if (position.x > 0 && position.y > 0 && position.z > 0) {
|
||||
Entities.addEntity({
|
||||
placingEntityID = Entities.addEntity({
|
||||
type: "Sphere",
|
||||
position: grid.snapToSurface(grid.snapToGrid(position, false, DEFAULT_DIMENSIONS), DEFAULT_DIMENSIONS),
|
||||
dimensions: DEFAULT_DIMENSIONS,
|
||||
|
@ -396,7 +397,7 @@ var toolBar = (function () {
|
|||
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE));
|
||||
|
||||
if (position.x > 0 && position.y > 0 && position.z > 0) {
|
||||
Entities.addEntity({
|
||||
placingEntityID = Entities.addEntity({
|
||||
type: "Light",
|
||||
position: grid.snapToSurface(grid.snapToGrid(position, false, DEFAULT_DIMENSIONS), DEFAULT_DIMENSIONS),
|
||||
dimensions: DEFAULT_DIMENSIONS,
|
||||
|
@ -421,7 +422,7 @@ var toolBar = (function () {
|
|||
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE));
|
||||
|
||||
if (position.x > 0 && position.y > 0 && position.z > 0) {
|
||||
Entities.addEntity({
|
||||
placingEntityID = Entities.addEntity({
|
||||
type: "Text",
|
||||
position: grid.snapToSurface(grid.snapToGrid(position, false, DEFAULT_DIMENSIONS), DEFAULT_DIMENSIONS),
|
||||
dimensions: { x: 0.5, y: 0.3, z: 0.01 },
|
||||
|
@ -533,8 +534,22 @@ var mouseCapturedByTool = false;
|
|||
var lastMousePosition = null;
|
||||
var idleMouseTimerId = null;
|
||||
var IDLE_MOUSE_TIMEOUT = 200;
|
||||
var DEFAULT_ENTITY_DRAG_DROP_DISTANCE = 2.0;
|
||||
|
||||
function mouseMoveEvent(event) {
|
||||
if (placingEntityID) {
|
||||
if (!placingEntityID.isKnownID) {
|
||||
placingEntityID = Entities.identifyEntity(placingEntityID);
|
||||
}
|
||||
var pickRay = Camera.computePickRay(event.x, event.y);
|
||||
var distance = cameraManager.enabled ? cameraManager.zoomDistance : DEFAULT_ENTITY_DRAG_DROP_DISTANCE;
|
||||
var offset = Vec3.multiply(distance, pickRay.direction);
|
||||
var position = Vec3.sum(Camera.position, offset);
|
||||
Entities.editEntity(placingEntityID, {
|
||||
position: position,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!isActive) {
|
||||
return;
|
||||
}
|
||||
|
@ -590,6 +605,10 @@ function highlightEntityUnderCursor(position, accurateRay) {
|
|||
|
||||
|
||||
function mouseReleaseEvent(event) {
|
||||
if (placingEntityID) {
|
||||
selectionManager.setSelections([placingEntityID]);
|
||||
placingEntityID = null;
|
||||
}
|
||||
if (isActive && selectionManager.hasSelection()) {
|
||||
tooltip.show(false);
|
||||
}
|
||||
|
|
|
@ -119,6 +119,7 @@
|
|||
var elCollisionsWillMove = document.getElementById("property-collisions-will-move");
|
||||
var elLifetime = document.getElementById("property-lifetime");
|
||||
var elScriptURL = document.getElementById("property-script-url");
|
||||
var elUserData = document.getElementById("property-user-data");
|
||||
|
||||
var elBoxSections = document.querySelectorAll(".box-section");
|
||||
var elBoxColorRed = document.getElementById("property-box-red");
|
||||
|
@ -224,6 +225,7 @@
|
|||
elCollisionsWillMove.checked = properties.collisionsWillMove;
|
||||
elLifetime.value = properties.lifetime;
|
||||
elScriptURL.value = properties.script;
|
||||
elUserData.value = properties.userData;
|
||||
|
||||
if (properties.type != "Box") {
|
||||
for (var i = 0; i < elBoxSections.length; i++) {
|
||||
|
@ -361,6 +363,7 @@
|
|||
elCollisionsWillMove.addEventListener('change', createEmitCheckedPropertyUpdateFunction('collisionsWillMove'));
|
||||
elLifetime.addEventListener('change', createEmitNumberPropertyUpdateFunction('lifetime'));
|
||||
elScriptURL.addEventListener('change', createEmitTextPropertyUpdateFunction('script'));
|
||||
elUserData.addEventListener('change', createEmitTextPropertyUpdateFunction('userData'));
|
||||
|
||||
var boxColorChangeFunction = createEmitColorPropertyUpdateFunction(
|
||||
'color', elBoxColorRed, elBoxColorGreen, elBoxColorBlue);
|
||||
|
@ -593,6 +596,13 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="property">
|
||||
<div class="label">User Data</div>
|
||||
<div class="value">
|
||||
<textarea id="property-user-data"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="box-section property">
|
||||
<div class="label">Color</div>
|
||||
|
@ -638,7 +648,7 @@
|
|||
<div class="model-section property">
|
||||
<div class="label">Animation Settings</div>
|
||||
<div class="value">
|
||||
<textarea id="property-model-animation-settings" value='asdfasdf'></textarea>
|
||||
<textarea id="property-model-animation-settings"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="model-section property">
|
||||
|
|
BIN
examples/images/text/NewTextRenderer.png
Normal file
After Width: | Height: | Size: 3.6 MiB |
BIN
examples/images/text/OldTextRenderer.png
Normal file
After Width: | Height: | Size: 3.5 MiB |
BIN
examples/images/text/TextTestNew.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
examples/images/text/TextTestOld.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
examples/images/text/new_1.png
Normal file
After Width: | Height: | Size: 250 KiB |
BIN
examples/images/text/new_2.png
Normal file
After Width: | Height: | Size: 146 KiB |
BIN
examples/images/text/new_3.png
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
examples/images/text/old_1.png
Normal file
After Width: | Height: | Size: 135 KiB |
BIN
examples/images/text/old_2.png
Normal file
After Width: | Height: | Size: 146 KiB |
BIN
examples/images/text/old_3.png
Normal file
After Width: | Height: | Size: 42 KiB |
|
@ -80,14 +80,6 @@ Controller.mousePressEvent.connect(mousePressEvent);
|
|||
Controller.mouseMoveEvent.connect(mouseMoveEvent);
|
||||
Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
|
||||
|
||||
// disable the standard application for mouse events
|
||||
Controller.captureMouseEvents();
|
||||
|
||||
function scriptEnding() {
|
||||
// re-enabled the standard application for mouse events
|
||||
Controller.releaseMouseEvents();
|
||||
}
|
||||
|
||||
MyAvatar.bodyYaw = 0;
|
||||
MyAvatar.bodyPitch = 0;
|
||||
MyAvatar.bodyRoll = 0;
|
||||
|
|
|
@ -73,6 +73,7 @@
|
|||
#include <PhysicsEngine.h>
|
||||
#include <ProgramObject.h>
|
||||
#include <ResourceCache.h>
|
||||
#include <ScriptCache.h>
|
||||
#include <SettingHandle.h>
|
||||
#include <SoundCache.h>
|
||||
#include <TextRenderer.h>
|
||||
|
@ -187,6 +188,7 @@ bool setupEssentials(int& argc, char** argv) {
|
|||
auto jsConsole = DependencyManager::set<StandAloneJSConsole>();
|
||||
auto dialogsManager = DependencyManager::set<DialogsManager>();
|
||||
auto bandwidthRecorder = DependencyManager::set<BandwidthRecorder>();
|
||||
auto resouceCacheSharedItems = DependencyManager::set<ResouceCacheSharedItems>();
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
||||
auto speechRecognizer = DependencyManager::set<SpeechRecognizer>();
|
||||
#endif
|
||||
|
@ -227,6 +229,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
|||
_touchAvgX(0.0f),
|
||||
_touchAvgY(0.0f),
|
||||
_isTouchPressed(false),
|
||||
_cursorVisible(true),
|
||||
_mousePressed(false),
|
||||
_enableProcessOctreeThread(true),
|
||||
_octreeProcessor(),
|
||||
|
@ -514,6 +517,11 @@ Application::~Application() {
|
|||
_myAvatar = NULL;
|
||||
|
||||
DependencyManager::destroy<GLCanvas>();
|
||||
DependencyManager::destroy<AnimationCache>();
|
||||
DependencyManager::destroy<TextureCache>();
|
||||
DependencyManager::destroy<GeometryCache>();
|
||||
DependencyManager::destroy<ScriptCache>();
|
||||
DependencyManager::destroy<SoundCache>();
|
||||
}
|
||||
|
||||
void Application::initializeGL() {
|
||||
|
@ -1473,6 +1481,8 @@ void Application::setEnableVRMode(bool enableVRMode) {
|
|||
|
||||
auto glCanvas = DependencyManager::get<GLCanvas>();
|
||||
resizeGL(glCanvas->getDeviceWidth(), glCanvas->getDeviceHeight());
|
||||
|
||||
updateCursorVisibility();
|
||||
}
|
||||
|
||||
void Application::setLowVelocityFilter(bool lowVelocityFilter) {
|
||||
|
@ -1943,35 +1953,22 @@ void Application::updateCursor(float deltaTime) {
|
|||
|
||||
static QPoint lastMousePos = QPoint();
|
||||
_lastMouseMove = (lastMousePos == QCursor::pos()) ? _lastMouseMove : usecTimestampNow();
|
||||
bool hideMouse = false;
|
||||
bool underMouse = QGuiApplication::topLevelAt(QCursor::pos()) ==
|
||||
Application::getInstance()->getWindow()->windowHandle();
|
||||
|
||||
static const int HIDE_CURSOR_TIMEOUT = 3 * USECS_PER_SECOND; // 3 second
|
||||
int elapsed = usecTimestampNow() - _lastMouseMove;
|
||||
if ((elapsed > HIDE_CURSOR_TIMEOUT) ||
|
||||
(OculusManager::isConnected() && Menu::getInstance()->isOptionChecked(MenuOption::EnableVRMode))) {
|
||||
hideMouse = underMouse;
|
||||
}
|
||||
|
||||
setCursorVisible(!hideMouse);
|
||||
lastMousePos = QCursor::pos();
|
||||
}
|
||||
|
||||
void Application::setCursorVisible(bool visible) {
|
||||
if (visible) {
|
||||
if (overrideCursor() != NULL) {
|
||||
restoreOverrideCursor();
|
||||
}
|
||||
void Application::updateCursorVisibility() {
|
||||
if (!_cursorVisible || Menu::getInstance()->isOptionChecked(MenuOption::EnableVRMode)) {
|
||||
DependencyManager::get<GLCanvas>()->setCursor(Qt::BlankCursor);
|
||||
} else {
|
||||
if (overrideCursor() != NULL) {
|
||||
changeOverrideCursor(Qt::BlankCursor);
|
||||
} else {
|
||||
setOverrideCursor(Qt::BlankCursor);
|
||||
}
|
||||
DependencyManager::get<GLCanvas>()->unsetCursor();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::setCursorVisible(bool visible) {
|
||||
_cursorVisible = visible;
|
||||
updateCursorVisibility();
|
||||
}
|
||||
|
||||
void Application::update(float deltaTime) {
|
||||
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
||||
PerformanceWarning warn(showWarnings, "Application::update()");
|
||||
|
|
|
@ -182,8 +182,7 @@ public:
|
|||
EntityTreeRenderer* getEntityClipboardRenderer() { return &_entityClipboardRenderer; }
|
||||
|
||||
bool isMousePressed() const { return _mousePressed; }
|
||||
bool isMouseHidden() const { return DependencyManager::get<GLCanvas>()->cursor().shape() == Qt::BlankCursor; }
|
||||
void setCursorVisible(bool visible);
|
||||
bool isMouseHidden() const { return !_cursorVisible; }
|
||||
const glm::vec3& getMouseRayOrigin() const { return _mouseRayOrigin; }
|
||||
const glm::vec3& getMouseRayDirection() const { return _mouseRayDirection; }
|
||||
bool mouseOnScreen() const;
|
||||
|
@ -391,11 +390,15 @@ private slots:
|
|||
|
||||
void audioMuteToggled();
|
||||
|
||||
void setCursorVisible(bool visible);
|
||||
|
||||
private:
|
||||
void resetCamerasOnResizeGL(Camera& camera, int width, int height);
|
||||
void updateProjectionMatrix();
|
||||
void updateProjectionMatrix(Camera& camera, bool updateViewFrustum = true);
|
||||
|
||||
void updateCursorVisibility();
|
||||
|
||||
void sendPingPackets();
|
||||
|
||||
void initDisplay();
|
||||
|
@ -505,6 +508,7 @@ private:
|
|||
|
||||
Environment _environment;
|
||||
|
||||
bool _cursorVisible;
|
||||
int _mouseDragStartedX;
|
||||
int _mouseDragStartedY;
|
||||
quint64 _lastMouseMove;
|
||||
|
|
|
@ -96,7 +96,7 @@ static TextRenderer* textRenderer(int mono) {
|
|||
}
|
||||
|
||||
int widthText(float scale, int mono, char const* string) {
|
||||
return textRenderer(mono)->computeWidth(string) * (scale / 0.10);
|
||||
return textRenderer(mono)->computeExtent(string).x; // computeWidth(string) * (scale / 0.10);
|
||||
}
|
||||
|
||||
void drawText(int x, int y, float scale, float radians, int mono,
|
||||
|
|
|
@ -680,9 +680,7 @@ void Avatar::renderDisplayName() {
|
|||
glRotatef(glm::degrees(angle), 0.0f, 1.0f, 0.0f);
|
||||
|
||||
float scaleFactor = calculateDisplayNameScaleFactor(textPosition, inHMD);
|
||||
glScalef(scaleFactor, scaleFactor, 1.0);
|
||||
|
||||
glScalef(1.0f, -1.0f, 1.0f); // TextRenderer::draw paints the text upside down in y axis
|
||||
glScalef(scaleFactor, -scaleFactor, scaleFactor); // TextRenderer::draw paints the text upside down in y axis
|
||||
|
||||
int text_x = -_displayNameBoundingRect.width() / 2;
|
||||
int text_y = -_displayNameBoundingRect.height() / 2;
|
||||
|
@ -915,7 +913,9 @@ void Avatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {
|
|||
|
||||
void Avatar::setDisplayName(const QString& displayName) {
|
||||
AvatarData::setDisplayName(displayName);
|
||||
_displayNameBoundingRect = textRenderer(DISPLAYNAME)->metrics().tightBoundingRect(displayName);
|
||||
// FIXME is this a sufficient replacement for tightBoundingRect?
|
||||
glm::vec2 extent = textRenderer(DISPLAYNAME)->computeExtent(displayName);
|
||||
_displayNameBoundingRect = QRect(QPoint(0, 0), QPoint((int)extent.x, (int)extent.y));
|
||||
}
|
||||
|
||||
void Avatar::setBillboard(const QByteArray& billboard) {
|
||||
|
|
|
@ -37,7 +37,7 @@ public:
|
|||
|
||||
// Semantic and Index types to retreive the JointTrackers of this MotionTracker
|
||||
typedef std::string Semantic;
|
||||
typedef int Index;
|
||||
typedef uint32_t Index;
|
||||
static const Index INVALID_SEMANTIC = -1;
|
||||
static const Index INVALID_PARENT = -2;
|
||||
|
||||
|
|
|
@ -217,7 +217,7 @@ void PrioVR::renderCalibrationCountdown() {
|
|||
false, TextRenderer::OUTLINE_EFFECT, 2);
|
||||
QByteArray text = "Assume T-Pose in " + QByteArray::number(secondsRemaining) + "...";
|
||||
auto glCanvas = DependencyManager::get<GLCanvas>();
|
||||
textRenderer->draw((glCanvas->width() - textRenderer->computeWidth(text.constData())) / 2,
|
||||
textRenderer->draw((glCanvas->width() - textRenderer->computeExtent(text.constData()).x) / 2,
|
||||
glCanvas->height() / 2,
|
||||
text, glm::vec4(1,1,1,1));
|
||||
#endif
|
||||
|
|
|
@ -38,7 +38,7 @@ public slots:
|
|||
private:
|
||||
|
||||
int _deviceTrackerId;
|
||||
int _subTrackerId;
|
||||
uint _subTrackerId;
|
||||
|
||||
// cache for the spatial
|
||||
SpatialEvent _eventCache;
|
||||
|
|
|
@ -50,7 +50,12 @@ void WindowScriptingInterface::setFocus() {
|
|||
}
|
||||
|
||||
void WindowScriptingInterface::setCursorVisible(bool visible) {
|
||||
Application::getInstance()->setCursorVisible(visible);
|
||||
QMetaObject::invokeMethod(Application::getInstance(), "setCursorVisible", Qt::BlockingQueuedConnection,
|
||||
Q_ARG(bool, visible));
|
||||
}
|
||||
|
||||
bool WindowScriptingInterface::isCursorVisible() const {
|
||||
return !Application::getInstance()->isMouseHidden();
|
||||
}
|
||||
|
||||
void WindowScriptingInterface::setCursorPosition(int x, int y) {
|
||||
|
|
|
@ -27,12 +27,14 @@ class WindowScriptingInterface : public QObject {
|
|||
Q_PROPERTY(int innerHeight READ getInnerHeight)
|
||||
Q_PROPERTY(int x READ getX)
|
||||
Q_PROPERTY(int y READ getY)
|
||||
Q_PROPERTY(bool cursorVisible READ isCursorVisible WRITE setCursorVisible)
|
||||
public:
|
||||
static WindowScriptingInterface* getInstance();
|
||||
int getInnerWidth();
|
||||
int getInnerHeight();
|
||||
int getX();
|
||||
int getY();
|
||||
bool isCursorVisible() const;
|
||||
|
||||
public slots:
|
||||
QScriptValue getCursorPositionX();
|
||||
|
|
|
@ -330,8 +330,9 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) {
|
|||
_overlays.buildVBO(_textureFov, _textureAspectRatio, 80, 80);
|
||||
}
|
||||
_overlays.render();
|
||||
renderPointersOculus(myAvatar->getDefaultEyePosition());
|
||||
|
||||
if (!Application::getInstance()->isMouseHidden()) {
|
||||
renderPointersOculus(myAvatar->getDefaultEyePosition());
|
||||
}
|
||||
glDepthMask(GL_TRUE);
|
||||
_overlays.releaseTexture();
|
||||
glDisable(GL_TEXTURE_2D);
|
||||
|
@ -537,7 +538,7 @@ void ApplicationOverlay::renderPointers() {
|
|||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, _crosshairTexture);
|
||||
|
||||
if (OculusManager::isConnected() && !qApp->getLastMouseMoveWasSimulated()) {
|
||||
if (OculusManager::isConnected() && !qApp->getLastMouseMoveWasSimulated() && !qApp->isMouseHidden()) {
|
||||
//If we are in oculus, render reticle later
|
||||
if (_lastMouseMove == 0) {
|
||||
_lastMouseMove = usecTimestampNow();
|
||||
|
@ -922,14 +923,13 @@ void ApplicationOverlay::renderStatsAndLogs() {
|
|||
|
||||
// Show on-screen msec timer
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::FrameTimer)) {
|
||||
char frameTimer[10];
|
||||
quint64 mSecsNow = floor(usecTimestampNow() / 1000.0 + 0.5);
|
||||
sprintf(frameTimer, "%d\n", (int)(mSecsNow % 1000));
|
||||
QString frameTimer = QString("%1\n").arg((int)(mSecsNow % 1000));
|
||||
int timerBottom =
|
||||
(Menu::getInstance()->isOptionChecked(MenuOption::Stats))
|
||||
? 80 : 20;
|
||||
drawText(glCanvas->width() - 100, glCanvas->height() - timerBottom,
|
||||
0.30f, 0.0f, 0, frameTimer, WHITE_TEXT);
|
||||
0.30f, 0.0f, 0, frameTimer.toUtf8().constData(), WHITE_TEXT);
|
||||
}
|
||||
nodeBoundsDisplay.drawOverlay();
|
||||
}
|
||||
|
|
|
@ -236,9 +236,8 @@ void OctreeStatsDialog::showOctreeServersOfType(int& serverCount, NodeType_t ser
|
|||
serverCount++;
|
||||
|
||||
if (serverCount > _octreeServerLabelsCount) {
|
||||
char label[128] = { 0 };
|
||||
sprintf(label, "%s Server %d", serverTypeName, serverCount);
|
||||
int thisServerRow = _octreeServerLables[serverCount-1] = AddStatItem(label);
|
||||
QString label = QString("%1 Server %2").arg(serverTypeName).arg(serverCount);
|
||||
int thisServerRow = _octreeServerLables[serverCount-1] = AddStatItem(label.toUtf8().constData());
|
||||
_labels[thisServerRow]->setTextFormat(Qt::RichText);
|
||||
_labels[thisServerRow]->setTextInteractionFlags(Qt::TextBrowserInteraction);
|
||||
connect(_labels[thisServerRow], SIGNAL(linkActivated(const QString&)), this, SLOT(moreless(const QString&)));
|
||||
|
|
|
@ -257,25 +257,20 @@ void Stats::display(
|
|||
|
||||
int columnOneHorizontalOffset = horizontalOffset;
|
||||
|
||||
char serverNodes[30];
|
||||
sprintf(serverNodes, "Servers: %d", totalServers);
|
||||
char avatarNodes[30];
|
||||
sprintf(avatarNodes, "Avatars: %d", totalAvatars);
|
||||
char framesPerSecond[30];
|
||||
sprintf(framesPerSecond, "Framerate: %3.0f FPS", fps);
|
||||
QString serverNodes = QString("Servers: %1").arg(totalServers);
|
||||
QString avatarNodes = QString("Avatars: %1").arg(totalAvatars);
|
||||
QString framesPerSecond = QString("Framerate: %1 FPS").arg(fps, 3, 'f', 0);
|
||||
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, serverNodes, color);
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, serverNodes.toUtf8().constData(), color);
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, avatarNodes, color);
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, avatarNodes.toUtf8().constData(), color);
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, framesPerSecond, color);
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, framesPerSecond.toUtf8().constData(), color);
|
||||
|
||||
// TODO: the display of these timing details should all be moved to JavaScript
|
||||
if (_expanded && Menu::getInstance()->isOptionChecked(MenuOption::DisplayTimingDetails)) {
|
||||
// Timing details...
|
||||
const int TIMER_OUTPUT_LINE_LENGTH = 1000;
|
||||
char perfLine[TIMER_OUTPUT_LINE_LENGTH];
|
||||
verticalOffset += STATS_PELS_PER_LINE * 4; // skip 4 lines to be under the other columns
|
||||
drawText(columnOneHorizontalOffset, verticalOffset, scale, rotation, font,
|
||||
"-------------------------------------------------------- Function "
|
||||
|
@ -302,12 +297,13 @@ void Stats::display(
|
|||
QString functionName = j.value();
|
||||
const PerformanceTimerRecord& record = allRecords.value(functionName);
|
||||
|
||||
sprintf(perfLine, "%120s: %8.4f [%6llu]", qPrintable(functionName),
|
||||
(float)record.getMovingAverage() / (float)USECS_PER_MSEC,
|
||||
record.getCount());
|
||||
|
||||
QString perfLine = QString("%1: %2 [%3]").
|
||||
arg(QString(qPrintable(functionName)), 120).
|
||||
arg((float)record.getMovingAverage() / (float)USECS_PER_MSEC, 8, 'f', 3).
|
||||
arg(record.getCount(), 6);
|
||||
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(columnOneHorizontalOffset, verticalOffset, scale, rotation, font, perfLine, color);
|
||||
drawText(columnOneHorizontalOffset, verticalOffset, scale, rotation, font, perfLine.toUtf8().constData(), color);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -321,17 +317,15 @@ void Stats::display(
|
|||
}
|
||||
horizontalOffset += 5;
|
||||
|
||||
char packetsPerSecondString[30];
|
||||
sprintf(packetsPerSecondString, "Packets In/Out: %d/%d", inPacketsPerSecond, outPacketsPerSecond);
|
||||
char averageMegabitsPerSecond[30];
|
||||
sprintf(averageMegabitsPerSecond, "Mbps In/Out: %3.2f/%3.2f",
|
||||
(float)inKbitsPerSecond * 1.0f / 1000.0f,
|
||||
(float)outKbitsPerSecond * 1.0f / 1000.0f);
|
||||
QString packetsPerSecondString = QString("Packets In/Out: %1/%2").arg(inPacketsPerSecond).arg(outPacketsPerSecond);
|
||||
QString averageMegabitsPerSecond = QString("Mbps In/Out: %1/%2").
|
||||
arg((float)inKbitsPerSecond * 1.0f / 1000.0f).
|
||||
arg((float)outKbitsPerSecond * 1.0f / 1000.0f);
|
||||
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, packetsPerSecondString, color);
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, packetsPerSecondString.toUtf8().constData(), color);
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, averageMegabitsPerSecond, color);
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, averageMegabitsPerSecond.toUtf8().constData(), color);
|
||||
|
||||
|
||||
|
||||
|
@ -376,44 +370,44 @@ void Stats::display(
|
|||
horizontalOffset += 5;
|
||||
|
||||
|
||||
char audioPing[30];
|
||||
QString audioPing;
|
||||
if (pingAudio >= 0) {
|
||||
sprintf(audioPing, "Audio ping: %d", pingAudio);
|
||||
audioPing = QString("Audio ping: %1").arg(pingAudio);
|
||||
} else {
|
||||
sprintf(audioPing, "Audio ping: --");
|
||||
audioPing = QString("Audio ping: --");
|
||||
}
|
||||
|
||||
char avatarPing[30];
|
||||
QString avatarPing;
|
||||
if (pingAvatar >= 0) {
|
||||
sprintf(avatarPing, "Avatar ping: %d", pingAvatar);
|
||||
avatarPing = QString("Avatar ping: %1").arg(pingAvatar);
|
||||
} else {
|
||||
sprintf(avatarPing, "Avatar ping: --");
|
||||
avatarPing = QString("Avatar ping: --");
|
||||
}
|
||||
|
||||
char voxelAvgPing[30];
|
||||
QString voxelAvgPing;
|
||||
if (pingVoxel >= 0) {
|
||||
sprintf(voxelAvgPing, "Entities avg ping: %d", pingVoxel);
|
||||
voxelAvgPing = QString("Entities avg ping: %1").arg(pingVoxel);
|
||||
} else {
|
||||
sprintf(voxelAvgPing, "Entities avg ping: --");
|
||||
voxelAvgPing = QString("Entities avg ping: --");
|
||||
}
|
||||
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, audioPing, color);
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, audioPing.toUtf8().constData(), color);
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, avatarPing, color);
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, avatarPing.toUtf8().constData(), color);
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, voxelAvgPing, color);
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, voxelAvgPing.toUtf8().constData(), color);
|
||||
|
||||
if (_expanded) {
|
||||
char voxelMaxPing[30];
|
||||
QString voxelMaxPing;
|
||||
if (pingVoxel >= 0) { // Average is only meaningful if pingVoxel is valid.
|
||||
sprintf(voxelMaxPing, "Voxel max ping: %d", pingOctreeMax);
|
||||
voxelMaxPing = QString("Voxel max ping: %1").arg(pingOctreeMax);
|
||||
} else {
|
||||
sprintf(voxelMaxPing, "Voxel max ping: --");
|
||||
voxelMaxPing = QString("Voxel max ping: --");
|
||||
}
|
||||
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, voxelMaxPing, color);
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, voxelMaxPing.toUtf8().constData(), color);
|
||||
}
|
||||
|
||||
verticalOffset = 0;
|
||||
|
@ -430,35 +424,35 @@ void Stats::display(
|
|||
}
|
||||
horizontalOffset += 5;
|
||||
|
||||
char avatarPosition[200];
|
||||
sprintf(avatarPosition, "Position: %.1f, %.1f, %.1f", avatarPos.x, avatarPos.y, avatarPos.z);
|
||||
char avatarVelocity[30];
|
||||
sprintf(avatarVelocity, "Velocity: %.1f", glm::length(myAvatar->getVelocity()));
|
||||
char avatarBodyYaw[30];
|
||||
sprintf(avatarBodyYaw, "Yaw: %.1f", myAvatar->getBodyYaw());
|
||||
char avatarMixerStats[200];
|
||||
QString avatarPosition = QString("Position: %1, %2, %3").
|
||||
arg(avatarPos.x, -1, 'f', 1).
|
||||
arg(avatarPos.y, -1, 'f', 1).
|
||||
arg(avatarPos.z, -1, 'f', 1);
|
||||
QString avatarVelocity = QString("Velocity: %1").arg(glm::length(myAvatar->getVelocity()), -1, 'f', 1);
|
||||
QString avatarBodyYaw = QString("Yaw: %1").arg(myAvatar->getBodyYaw(), -1, 'f', 1);
|
||||
QString avatarMixerStats;
|
||||
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, avatarPosition, color);
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, avatarPosition.toUtf8().constData(), color);
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, avatarVelocity, color);
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, avatarVelocity.toUtf8().constData(), color);
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, avatarBodyYaw, color);
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, avatarBodyYaw.toUtf8().constData(), color);
|
||||
|
||||
if (_expanded) {
|
||||
SharedNodePointer avatarMixer = DependencyManager::get<NodeList>()->soloNodeOfType(NodeType::AvatarMixer);
|
||||
if (avatarMixer) {
|
||||
sprintf(avatarMixerStats, "Avatar Mixer: %.f kbps, %.f pps",
|
||||
roundf(bandwidthRecorder->getAverageInputKilobitsPerSecond(NodeType::AudioMixer) +
|
||||
bandwidthRecorder->getAverageOutputKilobitsPerSecond(NodeType::AudioMixer)),
|
||||
roundf(bandwidthRecorder->getAverageInputPacketsPerSecond(NodeType::AudioMixer) +
|
||||
avatarMixerStats = QString("Avatar Mixer: %1 kbps, %2 pps").
|
||||
arg(roundf(bandwidthRecorder->getAverageInputKilobitsPerSecond(NodeType::AudioMixer) +
|
||||
bandwidthRecorder->getAverageOutputKilobitsPerSecond(NodeType::AudioMixer))).
|
||||
arg(roundf(bandwidthRecorder->getAverageInputPacketsPerSecond(NodeType::AudioMixer) +
|
||||
bandwidthRecorder->getAverageOutputPacketsPerSecond(NodeType::AudioMixer)));
|
||||
} else {
|
||||
sprintf(avatarMixerStats, "No Avatar Mixer");
|
||||
avatarMixerStats = QString("No Avatar Mixer");
|
||||
}
|
||||
|
||||
verticalOffset += STATS_PELS_PER_LINE;
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, avatarMixerStats, color);
|
||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, avatarMixerStats.toUtf8().constData(), color);
|
||||
|
||||
stringstream downloads;
|
||||
downloads << "Downloads: ";
|
||||
|
|
|
@ -108,7 +108,7 @@ void Text3DOverlay::render(RenderArgs* args) {
|
|||
|
||||
// Same font properties as textSize()
|
||||
TextRenderer* textRenderer = TextRenderer::getInstance(SANS_FONT_FAMILY, FIXED_FONT_POINT_SIZE);
|
||||
float maxHeight = (float)textRenderer->calculateHeight("Xy") * LINE_SCALE_RATIO;
|
||||
float maxHeight = (float)textRenderer->computeExtent("Xy").y * LINE_SCALE_RATIO;
|
||||
|
||||
float scaleFactor = (maxHeight / FIXED_FONT_SCALING_RATIO) * _lineHeight;
|
||||
|
||||
|
@ -124,13 +124,8 @@ void Text3DOverlay::render(RenderArgs* args) {
|
|||
enableClipPlane(GL_CLIP_PLANE2, 0.0f, -1.0f, 0.0f, clipMinimum.y + clipDimensions.y);
|
||||
enableClipPlane(GL_CLIP_PLANE3, 0.0f, 1.0f, 0.0f, -clipMinimum.y);
|
||||
|
||||
glm::vec4 textColor = {_color.red / MAX_COLOR, _color.green / MAX_COLOR, _color.blue / MAX_COLOR, getAlpha() };
|
||||
QStringList lines = _text.split("\n");
|
||||
int lineOffset = maxHeight;
|
||||
foreach(QString thisLine, lines) {
|
||||
textRenderer->draw(0, lineOffset, qPrintable(thisLine), textColor);
|
||||
lineOffset += maxHeight;
|
||||
}
|
||||
glm::vec4 textColor = { _color.red / MAX_COLOR, _color.green / MAX_COLOR, _color.blue / MAX_COLOR, getAlpha() };
|
||||
textRenderer->draw(0, 0, _text, textColor);
|
||||
|
||||
glDisable(GL_CLIP_PLANE0);
|
||||
glDisable(GL_CLIP_PLANE1);
|
||||
|
|
|
@ -92,17 +92,7 @@ void TextOverlay::render(RenderArgs* args) {
|
|||
|
||||
float alpha = getAlpha();
|
||||
glm::vec4 textColor = {_color.red / MAX_COLOR, _color.green / MAX_COLOR, _color.blue / MAX_COLOR, alpha };
|
||||
QStringList lines = _text.split("\n");
|
||||
int lineOffset = 0;
|
||||
foreach(QString thisLine, lines) {
|
||||
if (lineOffset == 0) {
|
||||
lineOffset = textRenderer->calculateHeight(qPrintable(thisLine));
|
||||
}
|
||||
lineOffset += textRenderer->draw(x, y + lineOffset, qPrintable(thisLine), textColor);
|
||||
|
||||
const int lineGap = 2;
|
||||
lineOffset += lineGap;
|
||||
}
|
||||
textRenderer->draw(x, y, _text, textColor);
|
||||
}
|
||||
|
||||
void TextOverlay::setProperties(const QScriptValue& properties) {
|
||||
|
|
|
@ -52,7 +52,7 @@ public:
|
|||
AudioFilterBank() :
|
||||
_sampleRate(0.0f),
|
||||
_frameCount(0) {
|
||||
for (int i = 0; i < _channelCount; ++i) {
|
||||
for (uint32_t i = 0; i < _channelCount; ++i) {
|
||||
_buffer[ i ] = NULL;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include <TextRenderer.h>
|
||||
|
||||
#include "RenderableTextEntityItem.h"
|
||||
#include "GLMHelpers.h"
|
||||
|
||||
const int FIXED_FONT_POINT_SIZE = 40;
|
||||
const float LINE_SCALE_RATIO = 1.2f;
|
||||
|
@ -46,51 +47,22 @@ void RenderableTextEntityItem::render(RenderArgs* args) {
|
|||
glm::vec3 axis = glm::axis(rotation);
|
||||
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
|
||||
|
||||
const float MAX_COLOR = 255.0f;
|
||||
xColor backgroundColor = getBackgroundColorX();
|
||||
float alpha = 1.0f; //getBackgroundAlpha();
|
||||
glm::vec4 color(backgroundColor.red / MAX_COLOR, backgroundColor.green / MAX_COLOR, backgroundColor.blue / MAX_COLOR, alpha);
|
||||
|
||||
const float SLIGHTLY_BEHIND = -0.005f;
|
||||
static const float SLIGHTLY_BEHIND = -0.005f;
|
||||
|
||||
glm::vec3 topLeft(-halfDimensions.x, -halfDimensions.y, SLIGHTLY_BEHIND);
|
||||
glm::vec3 bottomRight(halfDimensions.x, halfDimensions.y, SLIGHTLY_BEHIND);
|
||||
DependencyManager::get<GeometryCache>()->renderQuad(topLeft, bottomRight, color);
|
||||
|
||||
const int FIXED_FONT_SCALING_RATIO = FIXED_FONT_POINT_SIZE * 40.0f; // this is a ratio determined through experimentation
|
||||
|
||||
// Same font properties as textSize()
|
||||
TextRenderer* textRenderer = TextRenderer::getInstance(SANS_FONT_FAMILY, FIXED_FONT_POINT_SIZE);
|
||||
float maxHeight = (float)textRenderer->calculateHeight("Xy") * LINE_SCALE_RATIO;
|
||||
|
||||
float scaleFactor = (maxHeight / FIXED_FONT_SCALING_RATIO) * _lineHeight;
|
||||
DependencyManager::get<GeometryCache>()->renderQuad(topLeft, bottomRight, glm::vec4(toGlm(getBackgroundColorX()), alpha));
|
||||
|
||||
TextRenderer* textRenderer = TextRenderer::getInstance(SANS_FONT_FAMILY, FIXED_FONT_POINT_SIZE / 2.0f);
|
||||
|
||||
glTranslatef(-(halfDimensions.x - leftMargin), halfDimensions.y - topMargin, 0.0f);
|
||||
|
||||
glm::vec2 clipMinimum(0.0f, 0.0f);
|
||||
glm::vec2 clipDimensions((dimensions.x - (leftMargin + rightMargin)) / scaleFactor,
|
||||
(dimensions.y - (topMargin + bottomMargin)) / scaleFactor);
|
||||
|
||||
glScalef(scaleFactor, -scaleFactor, 1.0);
|
||||
enableClipPlane(GL_CLIP_PLANE0, -1.0f, 0.0f, 0.0f, clipMinimum.x + clipDimensions.x);
|
||||
enableClipPlane(GL_CLIP_PLANE1, 1.0f, 0.0f, 0.0f, -clipMinimum.x);
|
||||
enableClipPlane(GL_CLIP_PLANE2, 0.0f, -1.0f, 0.0f, clipMinimum.y + clipDimensions.y);
|
||||
enableClipPlane(GL_CLIP_PLANE3, 0.0f, 1.0f, 0.0f, -clipMinimum.y);
|
||||
|
||||
xColor textColor = getTextColorX();
|
||||
glm::vec4 textColorV4(textColor.red / MAX_COLOR, textColor.green / MAX_COLOR, textColor.blue / MAX_COLOR, 1.0f);
|
||||
QStringList lines = _text.split("\n");
|
||||
int lineOffset = maxHeight;
|
||||
foreach(QString thisLine, lines) {
|
||||
textRenderer->draw(0, lineOffset, qPrintable(thisLine), textColorV4);
|
||||
lineOffset += maxHeight;
|
||||
}
|
||||
|
||||
glDisable(GL_CLIP_PLANE0);
|
||||
glDisable(GL_CLIP_PLANE1);
|
||||
glDisable(GL_CLIP_PLANE2);
|
||||
glDisable(GL_CLIP_PLANE3);
|
||||
|
||||
glm::vec4 textColor(toGlm(getTextColorX()), alpha);
|
||||
// this is a ratio determined through experimentation
|
||||
const float scaleFactor = 0.08f * _lineHeight;
|
||||
glScalef(scaleFactor, -scaleFactor, scaleFactor);
|
||||
glm::vec2 bounds(dimensions.x / scaleFactor, dimensions.y / scaleFactor);
|
||||
textRenderer->draw(0, 0, _text, textColor, bounds);
|
||||
}
|
||||
glPopMatrix();
|
||||
}
|
||||
|
|
|
@ -548,7 +548,10 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
|||
// is sending us data with a known "last simulated" time. That time is likely in the past, and therefore
|
||||
// this "new" data is actually slightly out of date. We calculate the time we need to skip forward and
|
||||
// use our simulation helper routine to get a best estimate of where the entity should be.
|
||||
float skipTimeForward = (float)(now - _lastSimulated) / (float)(USECS_PER_SECOND);
|
||||
const float MIN_TIME_SKIP = 0.0f;
|
||||
const float MAX_TIME_SKIP = 1.0f; // in seconds
|
||||
float skipTimeForward = glm::clamp((float)(now - _lastSimulated) / (float)(USECS_PER_SECOND),
|
||||
MIN_TIME_SKIP, MAX_TIME_SKIP);
|
||||
if (skipTimeForward > 0.0f) {
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "skipTimeForward:" << skipTimeForward;
|
||||
|
|
|
@ -175,7 +175,7 @@ Texture::Size Texture::resize(Type type, const Element& texelFormat, uint16 widt
|
|||
|
||||
// Evaluate the new size with the new format
|
||||
const int DIM_SIZE[] = {1, 1, 1, 6};
|
||||
int size = DIM_SIZE[_type] *_width * _height * _depth * _numSamples * texelFormat.getSize();
|
||||
uint32_t size = DIM_SIZE[_type] *_width * _height * _depth * _numSamples * texelFormat.getSize();
|
||||
|
||||
// If size change then we need to reset
|
||||
if (changed || (size != getSize())) {
|
||||
|
|
|
@ -112,27 +112,30 @@ void ResourceCache::reserveUnusedResource(qint64 resourceSize) {
|
|||
}
|
||||
|
||||
void ResourceCache::attemptRequest(Resource* resource) {
|
||||
auto sharedItems = DependencyManager::get<ResouceCacheSharedItems>();
|
||||
if (_requestLimit <= 0) {
|
||||
// wait until a slot becomes available
|
||||
_pendingRequests.append(resource);
|
||||
sharedItems->_pendingRequests.append(resource);
|
||||
return;
|
||||
}
|
||||
_requestLimit--;
|
||||
_loadingRequests.append(resource);
|
||||
sharedItems->_loadingRequests.append(resource);
|
||||
resource->makeRequest();
|
||||
}
|
||||
|
||||
void ResourceCache::requestCompleted(Resource* resource) {
|
||||
_loadingRequests.removeOne(resource);
|
||||
|
||||
auto sharedItems = DependencyManager::get<ResouceCacheSharedItems>();
|
||||
sharedItems->_loadingRequests.removeOne(resource);
|
||||
_requestLimit++;
|
||||
|
||||
// look for the highest priority pending request
|
||||
int highestIndex = -1;
|
||||
float highestPriority = -FLT_MAX;
|
||||
for (int i = 0; i < _pendingRequests.size(); ) {
|
||||
Resource* resource = _pendingRequests.at(i).data();
|
||||
for (int i = 0; i < sharedItems->_pendingRequests.size(); ) {
|
||||
Resource* resource = sharedItems->_pendingRequests.at(i).data();
|
||||
if (!resource) {
|
||||
_pendingRequests.removeAt(i);
|
||||
sharedItems->_pendingRequests.removeAt(i);
|
||||
continue;
|
||||
}
|
||||
float priority = resource->getLoadPriority();
|
||||
|
@ -143,16 +146,13 @@ void ResourceCache::requestCompleted(Resource* resource) {
|
|||
i++;
|
||||
}
|
||||
if (highestIndex >= 0) {
|
||||
attemptRequest(_pendingRequests.takeAt(highestIndex));
|
||||
attemptRequest(sharedItems->_pendingRequests.takeAt(highestIndex));
|
||||
}
|
||||
}
|
||||
|
||||
const int DEFAULT_REQUEST_LIMIT = 10;
|
||||
int ResourceCache::_requestLimit = DEFAULT_REQUEST_LIMIT;
|
||||
|
||||
QList<QPointer<Resource> > ResourceCache::_pendingRequests;
|
||||
QList<Resource*> ResourceCache::_loadingRequests;
|
||||
|
||||
Resource::Resource(const QUrl& url, bool delayLoad) :
|
||||
_url(url),
|
||||
_request(url) {
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
#include <QUrl>
|
||||
#include <QWeakPointer>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
|
||||
class QNetworkReply;
|
||||
class QTimer;
|
||||
|
||||
|
@ -40,6 +42,21 @@ static const qint64 DEFAULT_UNUSED_MAX_SIZE = 1024 * BYTES_PER_MEGABYTES;
|
|||
static const qint64 MIN_UNUSED_MAX_SIZE = 0;
|
||||
static const qint64 MAX_UNUSED_MAX_SIZE = 10 * BYTES_PER_GIGABYTES;
|
||||
|
||||
// We need to make sure that these items are available for all instances of
|
||||
// ResourceCache derived classes. Since we can't count on the ordering of
|
||||
// static members destruction, we need to use this Dependency manager implemented
|
||||
// object instead
|
||||
class ResouceCacheSharedItems : public Dependency {
|
||||
SINGLETON_DEPENDENCY
|
||||
public:
|
||||
QList<QPointer<Resource> > _pendingRequests;
|
||||
QList<Resource*> _loadingRequests;
|
||||
private:
|
||||
ResouceCacheSharedItems() { }
|
||||
virtual ~ResouceCacheSharedItems() { }
|
||||
};
|
||||
|
||||
|
||||
/// Base class for resource caches.
|
||||
class ResourceCache : public QObject {
|
||||
Q_OBJECT
|
||||
|
@ -51,9 +68,11 @@ public:
|
|||
void setUnusedResourceCacheSize(qint64 unusedResourcesMaxSize);
|
||||
qint64 getUnusedResourceCacheSize() const { return _unusedResourcesMaxSize; }
|
||||
|
||||
static const QList<Resource*>& getLoadingRequests() { return _loadingRequests; }
|
||||
static const QList<Resource*>& getLoadingRequests()
|
||||
{ return DependencyManager::get<ResouceCacheSharedItems>()->_loadingRequests; }
|
||||
|
||||
static int getPendingRequestCount() { return _pendingRequests.size(); }
|
||||
static int getPendingRequestCount()
|
||||
{ return DependencyManager::get<ResouceCacheSharedItems>()->_pendingRequests.size(); }
|
||||
|
||||
ResourceCache(QObject* parent = NULL);
|
||||
virtual ~ResourceCache();
|
||||
|
@ -90,8 +109,6 @@ private:
|
|||
int _lastLRUKey = 0;
|
||||
|
||||
static int _requestLimit;
|
||||
static QList<QPointer<Resource> > _pendingRequests;
|
||||
static QList<Resource*> _loadingRequests;
|
||||
};
|
||||
|
||||
/// Base class for resources.
|
||||
|
|
|
@ -86,7 +86,7 @@ void PhysicsEngine::removeEntityInternal(EntityItem* entity) {
|
|||
if (physicsInfo) {
|
||||
EntityMotionState* motionState = static_cast<EntityMotionState*>(physicsInfo);
|
||||
if (motionState->getRigidBody()) {
|
||||
removeObject(motionState);
|
||||
removeObjectFromBullet(motionState);
|
||||
} else {
|
||||
// only need to hunt in this list when there is no RigidBody
|
||||
_nonPhysicalKinematicObjects.remove(motionState);
|
||||
|
@ -130,7 +130,7 @@ void PhysicsEngine::clearEntitiesInternal() {
|
|||
// For now we assume this would only be called on shutdown in which case we can just let the memory get lost.
|
||||
QSet<EntityMotionState*>::const_iterator stateItr = _entityMotionStates.begin();
|
||||
for (stateItr = _entityMotionStates.begin(); stateItr != _entityMotionStates.end(); ++stateItr) {
|
||||
removeObject(*stateItr);
|
||||
removeObjectFromBullet(*stateItr);
|
||||
delete (*stateItr);
|
||||
}
|
||||
_entityMotionStates.clear();
|
||||
|
@ -211,6 +211,9 @@ void PhysicsEngine::relayIncomingChangesToSimulation() {
|
|||
if (removeMotionState) {
|
||||
// if we get here then there is no need to keep this motionState around (no physics or kinematics)
|
||||
_outgoingPackets.remove(motionState);
|
||||
if (motionState->getType() == MOTION_STATE_TYPE_ENTITY) {
|
||||
_entityMotionStates.remove(static_cast<EntityMotionState*>(motionState));
|
||||
}
|
||||
// NOTE: motionState will clean up its own backpointers in the Object
|
||||
delete motionState;
|
||||
continue;
|
||||
|
@ -455,7 +458,7 @@ void PhysicsEngine::addObject(const ShapeInfo& shapeInfo, btCollisionShape* shap
|
|||
_dynamicsWorld->addRigidBody(body);
|
||||
}
|
||||
|
||||
void PhysicsEngine::removeObject(ObjectMotionState* motionState) {
|
||||
void PhysicsEngine::removeObjectFromBullet(ObjectMotionState* motionState) {
|
||||
assert(motionState);
|
||||
btRigidBody* body = motionState->getRigidBody();
|
||||
if (body) {
|
||||
|
@ -464,8 +467,9 @@ void PhysicsEngine::removeObject(ObjectMotionState* motionState) {
|
|||
ShapeInfoUtil::collectInfoFromShape(shape, shapeInfo);
|
||||
_dynamicsWorld->removeRigidBody(body);
|
||||
_shapeManager.releaseShape(shapeInfo);
|
||||
delete body;
|
||||
// NOTE: setRigidBody() modifies body->m_userPointer so we should clear the MotionState's body BEFORE deleting it.
|
||||
motionState->setRigidBody(NULL);
|
||||
delete body;
|
||||
motionState->setKinematic(false, _numSubsteps);
|
||||
|
||||
removeContacts(motionState);
|
||||
|
@ -492,8 +496,9 @@ bool PhysicsEngine::updateObjectHard(btRigidBody* body, ObjectMotionState* motio
|
|||
// FAIL! we are unable to support these changes!
|
||||
_shapeManager.releaseShape(oldShape);
|
||||
|
||||
delete body;
|
||||
// NOTE: setRigidBody() modifies body->m_userPointer so we should clear the MotionState's body BEFORE deleting it.
|
||||
motionState->setRigidBody(NULL);
|
||||
delete body;
|
||||
motionState->setKinematic(false, _numSubsteps);
|
||||
removeContacts(motionState);
|
||||
return false;
|
||||
|
|
|
@ -79,13 +79,13 @@ public:
|
|||
/// \return true if Object added
|
||||
void addObject(const ShapeInfo& shapeInfo, btCollisionShape* shape, ObjectMotionState* motionState);
|
||||
|
||||
/// \param motionState pointer to Object's MotionState
|
||||
void removeObject(ObjectMotionState* motionState);
|
||||
|
||||
/// process queue of changed from external sources
|
||||
void relayIncomingChangesToSimulation();
|
||||
|
||||
private:
|
||||
/// \param motionState pointer to Object's MotionState
|
||||
void removeObjectFromBullet(ObjectMotionState* motionState);
|
||||
|
||||
void removeContacts(ObjectMotionState* motionState);
|
||||
|
||||
// return 'true' of update was successful
|
||||
|
|
31760
libraries/render-utils/src/FontCourierPrime.h
Normal file
16218
libraries/render-utils/src/FontInconsolataMedium.h
Normal file
36290
libraries/render-utils/src/FontRoboto.h
Normal file
10039
libraries/render-utils/src/FontTimeless.h
Normal file
1
libraries/render-utils/src/MatrixStack.cpp
Normal file
|
@ -0,0 +1 @@
|
|||
#include "MatrixStack.h"
|
204
libraries/render-utils/src/MatrixStack.h
Normal file
|
@ -0,0 +1,204 @@
|
|||
/************************************************************************************
|
||||
|
||||
Authors : Bradley Austin Davis <bdavis@saintandreas.org>
|
||||
Copyright : Copyright Brad Davis. All Rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
************************************************************************************/
|
||||
|
||||
#pragma once
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <glm/gtc/noise.hpp>
|
||||
#include <glm/gtc/epsilon.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <stack>
|
||||
|
||||
#include <gpu/GPUConfig.h>
|
||||
|
||||
|
||||
|
||||
class MatrixStack : public std::stack<glm::mat4> {
|
||||
|
||||
public:
|
||||
|
||||
MatrixStack() {
|
||||
push(glm::mat4());
|
||||
}
|
||||
|
||||
explicit MatrixStack(const MatrixStack & other) {
|
||||
*((std::stack<glm::mat4>*)this) = *((std::stack<glm::mat4>*)&other);
|
||||
}
|
||||
|
||||
operator const glm::mat4 & () const {
|
||||
return top();
|
||||
}
|
||||
|
||||
MatrixStack & pop() {
|
||||
std::stack<glm::mat4>::pop();
|
||||
assert(!empty());
|
||||
return *this;
|
||||
}
|
||||
|
||||
MatrixStack & push() {
|
||||
emplace(top());
|
||||
return *this;
|
||||
}
|
||||
|
||||
MatrixStack & identity() {
|
||||
top() = glm::mat4();
|
||||
return *this;
|
||||
}
|
||||
|
||||
MatrixStack & push(const glm::mat4 & mat) {
|
||||
std::stack<glm::mat4>::push(mat);
|
||||
return *this;
|
||||
}
|
||||
|
||||
MatrixStack & rotate(const glm::mat3 & rotation) {
|
||||
return postMultiply(glm::mat4(rotation));
|
||||
}
|
||||
|
||||
MatrixStack & rotate(const glm::quat & rotation) {
|
||||
return postMultiply(glm::mat4_cast(rotation));
|
||||
}
|
||||
|
||||
MatrixStack & rotate(float theta, const glm::vec3 & axis) {
|
||||
return postMultiply(glm::rotate(glm::mat4(), theta, axis));
|
||||
}
|
||||
|
||||
MatrixStack & translate(float translation) {
|
||||
return translate(glm::vec3(translation, 0, 0));
|
||||
}
|
||||
|
||||
MatrixStack & translate(const glm::vec2 & translation) {
|
||||
return translate(glm::vec3(translation, 0));
|
||||
}
|
||||
|
||||
MatrixStack & translate(const glm::vec3 & translation) {
|
||||
return postMultiply(glm::translate(glm::mat4(), translation));
|
||||
}
|
||||
|
||||
MatrixStack & preTranslate(const glm::vec3 & translation) {
|
||||
return preMultiply(glm::translate(glm::mat4(), translation));
|
||||
}
|
||||
|
||||
|
||||
MatrixStack & scale(float factor) {
|
||||
return scale(glm::vec3(factor));
|
||||
}
|
||||
|
||||
MatrixStack & scale(const glm::vec3 & scale) {
|
||||
return postMultiply(glm::scale(glm::mat4(), scale));
|
||||
}
|
||||
|
||||
MatrixStack & transform(const glm::mat4 & xfm) {
|
||||
return postMultiply(xfm);
|
||||
}
|
||||
|
||||
MatrixStack & preMultiply(const glm::mat4 & xfm) {
|
||||
top() = xfm * top();
|
||||
return *this;
|
||||
}
|
||||
|
||||
MatrixStack & postMultiply(const glm::mat4 & xfm) {
|
||||
top() *= xfm;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Remove the rotation component of a matrix. useful for billboarding
|
||||
MatrixStack & unrotate() {
|
||||
glm::quat inverse = glm::inverse(glm::quat_cast(top()));
|
||||
top() = top() * glm::mat4_cast(inverse);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Remove the translation component of a matrix. useful for skyboxing
|
||||
MatrixStack & untranslate() {
|
||||
top()[3] = glm::vec4(0, 0, 0, 1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename Function>
|
||||
void withPush(Function f) {
|
||||
size_t startingDepth = size();
|
||||
push();
|
||||
f();
|
||||
pop();
|
||||
assert(startingDepth = size());
|
||||
}
|
||||
|
||||
template <typename Function>
|
||||
void withIdentity(Function f) {
|
||||
withPush([&] {
|
||||
identity();
|
||||
f();
|
||||
});
|
||||
}
|
||||
|
||||
static MatrixStack & projection() {
|
||||
static MatrixStack projection;
|
||||
return projection;
|
||||
}
|
||||
|
||||
static MatrixStack & modelview() {
|
||||
static MatrixStack modelview;
|
||||
return modelview;
|
||||
}
|
||||
|
||||
template <typename Function>
|
||||
static void withPushAll(Function f) {
|
||||
withPush(projection(), modelview(), f);
|
||||
}
|
||||
|
||||
template <typename Function>
|
||||
static void withIdentityAll(Function f) {
|
||||
withPush(projection(), modelview(), [=] {
|
||||
projection().identity();
|
||||
modelview().identity();
|
||||
f();
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Function>
|
||||
static void withPush(MatrixStack & stack, Function f) {
|
||||
stack.withPush(f);
|
||||
}
|
||||
|
||||
template <typename Function>
|
||||
static void withPush(MatrixStack & stack1, MatrixStack & stack2, Function f) {
|
||||
stack1.withPush([&]{
|
||||
stack2.withPush(f);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Function>
|
||||
static void withGlMatrices(Function f) {
|
||||
// Push the current stack, and then copy the values out of OpenGL
|
||||
withPushAll([&] {
|
||||
// Fetch the current matrices out of GL stack
|
||||
// FIXME, eliminate the usage of deprecated GL
|
||||
MatrixStack & mv = MatrixStack::modelview();
|
||||
MatrixStack & pr = MatrixStack::projection();
|
||||
glm::mat4 & mvm = mv.top();
|
||||
glGetFloatv(GL_MODELVIEW_MATRIX, &(mvm[0][0]));
|
||||
|
||||
glm::mat4 & prm = pr.top();
|
||||
glGetFloatv(GL_PROJECTION_MATRIX, &(prm[0][0]));
|
||||
f();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -9,319 +9,517 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <gpu/GPUConfig.h>
|
||||
|
||||
#include <gpu/GPUConfig.h>
|
||||
#include <QApplication>
|
||||
#include <QDesktopWidget>
|
||||
#include <QFont>
|
||||
#include <QPaintEngine>
|
||||
#include <QtDebug>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QWindow>
|
||||
#include <QBuffer>
|
||||
#include <QFile>
|
||||
|
||||
#include "TextRenderer.h"
|
||||
// FIXME, decouple from the GL headers
|
||||
#include <QOpenGLShaderProgram>
|
||||
#include <QOpenGLTexture>
|
||||
#include <QOpenGLVertexArrayObject>
|
||||
#include <QOpenGLBuffer>
|
||||
|
||||
#include "glm/glm.hpp"
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
#include "gpu/GLBackend.h"
|
||||
#include "gpu/Stream.h"
|
||||
|
||||
#include "GLMHelpers.h"
|
||||
#include "FontInconsolataMedium.h"
|
||||
#include "FontRoboto.h"
|
||||
#include "FontTimeless.h"
|
||||
#include "FontCourierPrime.h"
|
||||
#include "MatrixStack.h"
|
||||
#include "TextRenderer.h"
|
||||
|
||||
// the width/height of the cached glyph textures
|
||||
const int IMAGE_SIZE = 512;
|
||||
#include "sdf_text_vert.h"
|
||||
#include "sdf_text_frag.h"
|
||||
|
||||
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));
|
||||
// Helper functions for reading binary data from an IO device
|
||||
template<class T>
|
||||
void readStream(QIODevice & in, T & t) {
|
||||
in.read((char*) &t, sizeof(t));
|
||||
}
|
||||
|
||||
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;
|
||||
template<typename T, size_t N>
|
||||
void readStream(QIODevice & in, T (&t)[N]) {
|
||||
in.read((char*) t, N);
|
||||
}
|
||||
|
||||
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);
|
||||
template<class T, size_t N>
|
||||
void fillBuffer(QBuffer & buffer, T (&t)[N]) {
|
||||
buffer.setData((const char*) t, N);
|
||||
}
|
||||
|
||||
// FIXME support the shadow effect, or remove it from the API
|
||||
// FIXME figure out how to improve the anti-aliasing on the
|
||||
// interior of the outline fonts
|
||||
|
||||
// stores the font metrics for a single character
|
||||
struct Glyph {
|
||||
QChar c;
|
||||
glm::vec2 texOffset;
|
||||
glm::vec2 texSize;
|
||||
glm::vec2 size;
|
||||
glm::vec2 offset;
|
||||
float d; // xadvance - adjusts character positioning
|
||||
size_t indexOffset;
|
||||
|
||||
QRectF bounds() const;
|
||||
QRectF textureBounds(const glm::vec2 & textureSize) const;
|
||||
|
||||
void read(QIODevice & in);
|
||||
};
|
||||
|
||||
void Glyph::read(QIODevice & in) {
|
||||
uint16_t charcode;
|
||||
readStream(in, charcode);
|
||||
c = charcode;
|
||||
readStream(in, texOffset);
|
||||
readStream(in, size);
|
||||
readStream(in, offset);
|
||||
readStream(in, d);
|
||||
texSize = size;
|
||||
}
|
||||
|
||||
const float DEFAULT_POINT_SIZE = 12;
|
||||
|
||||
class Font {
|
||||
public:
|
||||
using TexturePtr = QSharedPointer < QOpenGLTexture >;
|
||||
using VertexArrayPtr = QSharedPointer< QOpenGLVertexArrayObject >;
|
||||
using ProgramPtr = QSharedPointer < QOpenGLShaderProgram >;
|
||||
using BufferPtr = QSharedPointer < QOpenGLBuffer >;
|
||||
|
||||
// maps characters to cached glyph info
|
||||
// HACK... the operator[] const for QHash returns a
|
||||
// copy of the value, not a const value reference, so
|
||||
// we declare the hash as mutable in order to avoid such
|
||||
// copies
|
||||
mutable QHash<QChar, Glyph> _glyphs;
|
||||
|
||||
// the id of the glyph texture to which we're currently writing
|
||||
GLuint _currentTextureID;
|
||||
|
||||
int _pointSize;
|
||||
|
||||
// the height of the current row of characters
|
||||
int _rowHeight;
|
||||
|
||||
QString _family;
|
||||
float _fontSize { 0 };
|
||||
float _leading { 0 };
|
||||
float _ascent { 0 };
|
||||
float _descent { 0 };
|
||||
float _spaceWidth { 0 };
|
||||
|
||||
BufferPtr _vertices;
|
||||
BufferPtr _indices;
|
||||
TexturePtr _texture;
|
||||
VertexArrayPtr _vao;
|
||||
QImage _image;
|
||||
ProgramPtr _program;
|
||||
|
||||
const Glyph & getGlyph(const QChar & c) const;
|
||||
void read(QIODevice & path);
|
||||
// Initialize the OpenGL structures
|
||||
void setupGL();
|
||||
|
||||
glm::vec2 computeExtent(const QString & str) const;
|
||||
|
||||
glm::vec2 computeTokenExtent(const QString & str) const;
|
||||
|
||||
glm::vec2 drawString(float x, float y, const QString & str,
|
||||
const glm::vec4& color, TextRenderer::EffectType effectType,
|
||||
const glm::vec2& bound) const;
|
||||
|
||||
private:
|
||||
QStringList tokenizeForWrapping(const QString & str) const;
|
||||
};
|
||||
|
||||
static QHash<QString, Font *> LOADED_FONTS;
|
||||
|
||||
Font * loadFont(QIODevice & buffer) {
|
||||
Font * result = new Font();
|
||||
result->read(buffer);
|
||||
return result;
|
||||
}
|
||||
|
||||
template<class T, size_t N>
|
||||
Font * loadFont(T (&t)[N]) {
|
||||
QBuffer buffer;
|
||||
buffer.setData((const char*) t, N);
|
||||
buffer.open(QBuffer::ReadOnly);
|
||||
return loadFont(buffer);
|
||||
}
|
||||
|
||||
Font * loadFont(const QString & family) {
|
||||
if (!LOADED_FONTS.contains(family)) {
|
||||
if (family == MONO_FONT_FAMILY) {
|
||||
|
||||
LOADED_FONTS[family] = loadFont(SDFF_COURIER_PRIME);
|
||||
} else if (family == INCONSOLATA_FONT_FAMILY) {
|
||||
LOADED_FONTS[family] = loadFont(SDFF_INCONSOLATA_MEDIUM);
|
||||
} else if (family == SANS_FONT_FAMILY) {
|
||||
LOADED_FONTS[family] = loadFont(SDFF_ROBOTO);
|
||||
} else {
|
||||
if (!LOADED_FONTS.contains(SERIF_FONT_FAMILY)) {
|
||||
LOADED_FONTS[SERIF_FONT_FAMILY] = loadFont(SDFF_TIMELESS);
|
||||
}
|
||||
LOADED_FONTS[family] = LOADED_FONTS[SERIF_FONT_FAMILY];
|
||||
}
|
||||
}
|
||||
return LOADED_FONTS[family];
|
||||
}
|
||||
|
||||
// NERD RAGE: why doesn't QHash have a 'const T & operator[] const' member
|
||||
const Glyph & Font::getGlyph(const QChar & c) const {
|
||||
if (!_glyphs.contains(c)) {
|
||||
return _glyphs[QChar('?')];
|
||||
}
|
||||
return _glyphs[c];
|
||||
}
|
||||
|
||||
void Font::read(QIODevice & in) {
|
||||
uint8_t header[4];
|
||||
readStream(in, header);
|
||||
if (memcmp(header, "SDFF", 4)) {
|
||||
qFatal("Bad SDFF file");
|
||||
}
|
||||
|
||||
uint16_t version;
|
||||
readStream(in, version);
|
||||
|
||||
// read font name
|
||||
_family = "";
|
||||
if (version > 0x0001) {
|
||||
char c;
|
||||
readStream(in, c);
|
||||
while (c) {
|
||||
_family += c;
|
||||
readStream(in, c);
|
||||
}
|
||||
}
|
||||
|
||||
// read font data
|
||||
readStream(in, _leading);
|
||||
readStream(in, _ascent);
|
||||
readStream(in, _descent);
|
||||
readStream(in, _spaceWidth);
|
||||
_fontSize = _ascent + _descent;
|
||||
_rowHeight = _fontSize + _descent;
|
||||
|
||||
// Read character count
|
||||
uint16_t count;
|
||||
readStream(in, count);
|
||||
// read metrics data for each character
|
||||
QVector<Glyph> glyphs(count);
|
||||
// std::for_each instead of Qt foreach because we need non-const references
|
||||
std::for_each(glyphs.begin(), glyphs.end(), [&](Glyph & g) {
|
||||
g.read(in);
|
||||
});
|
||||
|
||||
// read image data
|
||||
if (!_image.loadFromData(in.readAll(), "PNG")) {
|
||||
qFatal("Failed to read SDFF image");
|
||||
}
|
||||
|
||||
_glyphs.clear();
|
||||
foreach(Glyph g, glyphs) {
|
||||
// Adjust the pixel texture coordinates into UV coordinates,
|
||||
glm::vec2 imageSize = toGlm(_image.size());
|
||||
g.texSize /= imageSize;
|
||||
g.texOffset /= imageSize;
|
||||
// store in the character to glyph hash
|
||||
_glyphs[g.c] = g;
|
||||
};
|
||||
|
||||
setupGL();
|
||||
}
|
||||
|
||||
struct TextureVertex {
|
||||
glm::vec2 pos;
|
||||
glm::vec2 tex;
|
||||
TextureVertex() {
|
||||
}
|
||||
TextureVertex(const glm::vec2 & pos, const glm::vec2 & tex) :
|
||||
pos(pos), tex(tex) {
|
||||
}
|
||||
TextureVertex(const QPointF & pos, const QPointF & tex) :
|
||||
pos(pos.x(), pos.y()), tex(tex.x(), tex.y()) {
|
||||
}
|
||||
};
|
||||
|
||||
struct QuadBuilder {
|
||||
TextureVertex vertices[4];
|
||||
QuadBuilder(const QRectF & r, const QRectF & tr) {
|
||||
vertices[0] = TextureVertex(r.bottomLeft(), tr.topLeft());
|
||||
vertices[1] = TextureVertex(r.bottomRight(), tr.topRight());
|
||||
vertices[2] = TextureVertex(r.topRight(), tr.bottomRight());
|
||||
vertices[3] = TextureVertex(r.topLeft(), tr.bottomLeft());
|
||||
}
|
||||
};
|
||||
|
||||
QRectF Glyph::bounds() const {
|
||||
return glmToRect(offset, size);
|
||||
}
|
||||
|
||||
QRectF Glyph::textureBounds(const glm::vec2 & textureSize) const {
|
||||
return glmToRect(texOffset, texSize);
|
||||
}
|
||||
|
||||
void Font::setupGL() {
|
||||
_texture = TexturePtr(
|
||||
new QOpenGLTexture(_image, QOpenGLTexture::GenerateMipMaps));
|
||||
_program = ProgramPtr(new QOpenGLShaderProgram());
|
||||
if (!_program->create()) {
|
||||
qFatal("Could not create text shader");
|
||||
}
|
||||
if (!_program->addShaderFromSourceCode(QOpenGLShader::Vertex, sdf_text_vert) || //
|
||||
!_program->addShaderFromSourceCode(QOpenGLShader::Fragment, sdf_text_frag) || //
|
||||
!_program->link()) {
|
||||
qFatal("%s", _program->log().toLocal8Bit().constData());
|
||||
}
|
||||
|
||||
std::vector<TextureVertex> vertexData;
|
||||
std::vector<GLuint> indexData;
|
||||
vertexData.reserve(_glyphs.size() * 4);
|
||||
std::for_each(_glyphs.begin(), _glyphs.end(), [&](Glyph & m) {
|
||||
GLuint index = (GLuint)vertexData.size();
|
||||
|
||||
QRectF bounds = m.bounds();
|
||||
QRectF texBounds = m.textureBounds(toGlm(_image.size()));
|
||||
QuadBuilder qb(bounds, texBounds);
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
vertexData.push_back(qb.vertices[i]);
|
||||
}
|
||||
|
||||
m.indexOffset = indexData.size() * sizeof(GLuint);
|
||||
// FIXME use triangle strips + primitive restart index
|
||||
indexData.push_back(index + 0);
|
||||
indexData.push_back(index + 1);
|
||||
indexData.push_back(index + 2);
|
||||
indexData.push_back(index + 0);
|
||||
indexData.push_back(index + 2);
|
||||
indexData.push_back(index + 3);
|
||||
});
|
||||
|
||||
_vao = VertexArrayPtr(new QOpenGLVertexArrayObject());
|
||||
_vao->create();
|
||||
_vao->bind();
|
||||
|
||||
_vertices = BufferPtr(new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer));
|
||||
_vertices->create();
|
||||
_vertices->bind();
|
||||
_vertices->allocate(&vertexData[0],
|
||||
sizeof(TextureVertex) * vertexData.size());
|
||||
_indices = BufferPtr(new QOpenGLBuffer(QOpenGLBuffer::IndexBuffer));
|
||||
_indices->create();
|
||||
_indices->bind();
|
||||
_indices->allocate(&indexData[0], sizeof(GLuint) * indexData.size());
|
||||
|
||||
GLsizei stride = (GLsizei) sizeof(TextureVertex);
|
||||
void* offset = (void*) offsetof(TextureVertex, tex);
|
||||
int posLoc = _program->attributeLocation("Position");
|
||||
int texLoc = _program->attributeLocation("TexCoord");
|
||||
glEnableVertexAttribArray(posLoc);
|
||||
glVertexAttribPointer(posLoc, 3, GL_FLOAT, false, stride, nullptr);
|
||||
glEnableVertexAttribArray(texLoc);
|
||||
glVertexAttribPointer(texLoc, 2, GL_FLOAT, false, stride, offset);
|
||||
_vao->release();
|
||||
}
|
||||
|
||||
// FIXME there has to be a cleaner way of doing this
|
||||
QStringList Font::tokenizeForWrapping(const QString & str) const {
|
||||
QStringList result;
|
||||
foreach(const QString & token1, str.split(" ", QString::SkipEmptyParts)) {
|
||||
bool lineFeed = false;
|
||||
foreach(const QString & token2, token1.split("\n")) {
|
||||
if (lineFeed) {
|
||||
result << "\n";
|
||||
}
|
||||
if (token2.size()) {
|
||||
result << token2;
|
||||
}
|
||||
lineFeed = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
glm::vec2 Font::computeTokenExtent(const QString & token) const {
|
||||
glm::vec2 advance(0, _rowHeight - _descent);
|
||||
foreach(QChar c, token) {
|
||||
assert(c != ' ' && c != '\n');
|
||||
const Glyph & m = getGlyph(c);
|
||||
advance.x += m.d;
|
||||
}
|
||||
return advance;
|
||||
}
|
||||
|
||||
|
||||
glm::vec2 Font::computeExtent(const QString & str) const {
|
||||
glm::vec2 extent(0, _rowHeight - _descent);
|
||||
// FIXME, come up with a better method of splitting text
|
||||
// that will allow wrapping but will preserve things like
|
||||
// tabs or consecutive spaces
|
||||
bool firstTokenOnLine = true;
|
||||
float lineWidth = 0.0f;
|
||||
QStringList tokens = tokenizeForWrapping(str);
|
||||
foreach(const QString & token, tokens) {
|
||||
if (token == "\n") {
|
||||
extent.x = std::max(lineWidth, extent.x);
|
||||
lineWidth = 0.0f;
|
||||
extent.y += _rowHeight;
|
||||
firstTokenOnLine = true;
|
||||
continue;
|
||||
}
|
||||
if (!firstTokenOnLine) {
|
||||
lineWidth += _spaceWidth;
|
||||
}
|
||||
lineWidth += computeTokenExtent(token).x;
|
||||
firstTokenOnLine = false;
|
||||
}
|
||||
extent.x = std::max(lineWidth, extent.x);
|
||||
return extent;
|
||||
}
|
||||
|
||||
// FIXME support the maxWidth parameter and allow the text to automatically wrap
|
||||
// even without explicit line feeds.
|
||||
glm::vec2 Font::drawString(float x, float y, const QString & str,
|
||||
const glm::vec4& color, TextRenderer::EffectType effectType,
|
||||
const glm::vec2& bounds) const {
|
||||
|
||||
// Stores how far we've moved from the start of the string, in DTP units
|
||||
glm::vec2 advance(0, -_rowHeight - _descent);
|
||||
|
||||
_program->bind();
|
||||
_program->setUniformValue("Color", color.r, color.g, color.b, color.a);
|
||||
_program->setUniformValue("Projection",
|
||||
fromGlm(MatrixStack::projection().top()));
|
||||
if (effectType == TextRenderer::OUTLINE_EFFECT) {
|
||||
_program->setUniformValue("Outline", true);
|
||||
}
|
||||
// Needed?
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
_texture->bind();
|
||||
_vao->bind();
|
||||
|
||||
MatrixStack & mv = MatrixStack::modelview();
|
||||
// scale the modelview into font units
|
||||
mv.translate(glm::vec3(0, _ascent, 0));
|
||||
foreach(const QString & token, tokenizeForWrapping(str)) {
|
||||
if (token == "\n") {
|
||||
advance.x = 0.0f;
|
||||
advance.y -= _rowHeight;
|
||||
// If we've wrapped right out of the bounds, then we're
|
||||
// done with rendering the tokens
|
||||
if (bounds.y > 0 && abs(advance.y) > bounds.y) {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
glm::vec2 tokenExtent = computeTokenExtent(token);
|
||||
if (bounds.x > 0 && advance.x > 0) {
|
||||
// We check if we'll be out of bounds
|
||||
if (advance.x + tokenExtent.x >= bounds.x) {
|
||||
// We're out of bounds, so wrap to the next line
|
||||
advance.x = 0.0f;
|
||||
advance.y -= _rowHeight;
|
||||
// If we've wrapped right out of the bounds, then we're
|
||||
// done with rendering the tokens
|
||||
if (bounds.y > 0 && abs(advance.y) > bounds.y) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach(const QChar & c, token) {
|
||||
// get metrics for this character to speed up measurements
|
||||
const Glyph & m = getGlyph(c);
|
||||
// We create an offset vec2 to hold the local offset of this character
|
||||
// This includes compensating for the inverted Y axis of the font
|
||||
// coordinates
|
||||
glm::vec2 offset(advance);
|
||||
offset.y -= m.size.y;
|
||||
// Bind the new position
|
||||
mv.withPush([&] {
|
||||
mv.translate(offset);
|
||||
// FIXME find a better (and GL ES 3.1 compatible) way of rendering the text
|
||||
// that doesn't involve a single GL call per character.
|
||||
// Most likely an 'indirect' call or an 'instanced' call.
|
||||
_program->setUniformValue("ModelView", fromGlm(mv.top()));
|
||||
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (void*)(m.indexOffset));
|
||||
});
|
||||
advance.x += m.d;
|
||||
}
|
||||
advance.x += _spaceWidth;
|
||||
}
|
||||
|
||||
_vao->release();
|
||||
_program->release();
|
||||
// FIXME, needed?
|
||||
// glDisable(GL_TEXTURE_2D);
|
||||
return advance;
|
||||
}
|
||||
|
||||
TextRenderer* TextRenderer::getInstance(const char* family, float pointSize,
|
||||
int weight, bool italic, EffectType effect, int effectThickness,
|
||||
const QColor& color) {
|
||||
if (pointSize < 0) {
|
||||
pointSize = DEFAULT_POINT_SIZE;
|
||||
}
|
||||
return new TextRenderer(family, pointSize, weight, italic, effect,
|
||||
effectThickness, color);
|
||||
}
|
||||
|
||||
TextRenderer::TextRenderer(const char* family, float pointSize, int weight,
|
||||
bool italic, EffectType effect, int effectThickness,
|
||||
const QColor& color) :
|
||||
_effectType(effect), _effectThickness(effectThickness), _pointSize(pointSize), _color(color), _font(loadFont(family)) {
|
||||
if (1 != _effectThickness) {
|
||||
qWarning() << "Effect thickness not current supported";
|
||||
}
|
||||
if (NO_EFFECT != _effectType && OUTLINE_EFFECT != _effectType) {
|
||||
qWarning() << "Effect thickness not current supported";
|
||||
}
|
||||
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;
|
||||
glm::vec2 TextRenderer::computeExtent(const QString & str) const {
|
||||
float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f;
|
||||
return _font->computeExtent(str) * scale;
|
||||
}
|
||||
|
||||
int TextRenderer::draw(int x, int y, const char* str, const glm::vec4& color) {
|
||||
int compactColor = ((int(color.x * 255.0f) & 0xFF)) |
|
||||
((int(color.y * 255.0f) & 0xFF) << 8) |
|
||||
((int(color.z * 255.0f) & 0xFF) << 16) |
|
||||
((int(color.w * 255.0f) & 0xFF) << 24);
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
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();
|
||||
float TextRenderer::draw(float x, float y, const QString & str,
|
||||
const glm::vec4& color, const glm::vec2 & bounds) {
|
||||
glm::vec4 actualColor(color);
|
||||
if (actualColor.r < 0) {
|
||||
actualColor = toGlm(_color);
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
return maxHeight;
|
||||
float scale = (_pointSize / DEFAULT_POINT_SIZE) * 0.25f;
|
||||
glm::vec2 result;
|
||||
MatrixStack::withGlMatrices([&] {
|
||||
MatrixStack & mv = MatrixStack::modelview();
|
||||
// scale the modelview into font units
|
||||
// FIXME migrate the constant scale factor into the geometry of the
|
||||
// fonts so we don't have to flip the Y axis here and don't have to
|
||||
// scale at all.
|
||||
mv.translate(glm::vec2(x, y)).scale(glm::vec3(scale, -scale, scale));
|
||||
// The font does all the OpenGL work
|
||||
result = _font->drawString(x, y, str, actualColor, _effectType, bounds / scale);
|
||||
});
|
||||
return result.x;
|
||||
}
|
||||
|
||||
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(new gpu::Buffer()),
|
||||
_glyphsColorBuffer(new gpu::Buffer()),
|
||||
_glyphsStreamFormat(new gpu::Stream::Format()),
|
||||
_glyphsStream(new gpu::BufferStream()),
|
||||
_numGlyphsBatched(0)
|
||||
{
|
||||
_glyphsStreamFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::XYZ), 0);
|
||||
const int NUM_POS_COORDS = 2;
|
||||
const int VERTEX_TEXCOORD_OFFSET = NUM_POS_COORDS * sizeof(float);
|
||||
_glyphsStreamFormat->setAttribute(gpu::Stream::TEXCOORD, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), VERTEX_TEXCOORD_OFFSET);
|
||||
|
||||
_glyphsStreamFormat->setAttribute(gpu::Stream::COLOR, 1, gpu::Element(gpu::VEC4, gpu::UINT8, gpu::RGBA));
|
||||
|
||||
_glyphsStream->addBuffer(_glyphsBuffer, 0, _glyphsStreamFormat->getChannels().at(0)._stride);
|
||||
_glyphsStream->addBuffer(_glyphsColorBuffer, 0, _glyphsStreamFormat->getChannels().at(1)._stride);
|
||||
|
||||
_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();
|
||||
*/
|
||||
|
||||
gpu::Batch batch;
|
||||
|
||||
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);
|
||||
|
||||
batch.setInputFormat(_glyphsStreamFormat);
|
||||
batch.setInputStream(0, *_glyphsStream);
|
||||
batch.draw(gpu::QUADS, _numGlyphsBatched * 4, 0);
|
||||
|
||||
gpu::GLBackend::renderBatch(batch);
|
||||
|
||||
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) {
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
#include <gpu/GPUConfig.h>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <unordered_map>
|
||||
#include <QColor>
|
||||
#include <QFont>
|
||||
#include <QFontMetrics>
|
||||
|
@ -25,14 +25,15 @@
|
|||
#include <gpu/Resource.h>
|
||||
#include <gpu/Stream.h>
|
||||
|
||||
|
||||
|
||||
// a special "character" that renders as a solid block
|
||||
const char SOLID_BLOCK_CHAR = 127;
|
||||
|
||||
// the standard sans serif font family
|
||||
#define SANS_FONT_FAMILY "Helvetica"
|
||||
|
||||
// the standard sans serif font family
|
||||
#define SERIF_FONT_FAMILY "Timeless"
|
||||
|
||||
// the standard mono font family
|
||||
#define MONO_FONT_FAMILY "Courier"
|
||||
|
||||
|
@ -45,110 +46,44 @@ const char SOLID_BLOCK_CHAR = 127;
|
|||
#define INCONSOLATA_FONT_WEIGHT QFont::Bold
|
||||
#endif
|
||||
|
||||
class Glyph;
|
||||
class Font;
|
||||
|
||||
// TextRenderer is actually a fairly thin wrapper around a Font class
|
||||
// defined in the cpp file.
|
||||
class TextRenderer {
|
||||
public:
|
||||
|
||||
enum EffectType { NO_EFFECT, SHADOW_EFFECT, OUTLINE_EFFECT };
|
||||
|
||||
class Properties {
|
||||
public:
|
||||
QFont font;
|
||||
EffectType effect;
|
||||
int effectThickness;
|
||||
QColor color;
|
||||
};
|
||||
|
||||
static TextRenderer* getInstance(const char* family, int pointSize = -1, int weight = -1, bool italic = false,
|
||||
static TextRenderer* getInstance(const char* family, float pointSize = -1, int weight = -1, bool italic = false,
|
||||
EffectType effect = NO_EFFECT, int effectThickness = 1, const QColor& color = QColor(255, 255, 255));
|
||||
|
||||
~TextRenderer();
|
||||
|
||||
const QFontMetrics& metrics() const { return _metrics; }
|
||||
glm::vec2 computeExtent(const QString & str) const;
|
||||
|
||||
// returns the height of the tallest character
|
||||
int calculateHeight(const char* str);
|
||||
float draw(
|
||||
float x, float y,
|
||||
const QString & str,
|
||||
const glm::vec4& color = glm::vec4(-1.0f),
|
||||
const glm::vec2& bounds = glm::vec2(-1.0f));
|
||||
|
||||
// also returns the height of the tallest character
|
||||
int draw(int x, int y, const char* str, const glm::vec4& color);
|
||||
|
||||
int computeWidth(char ch);
|
||||
int computeWidth(const char* str);
|
||||
|
||||
void drawBatch();
|
||||
void clearBatch();
|
||||
private:
|
||||
|
||||
TextRenderer(const Properties& properties);
|
||||
|
||||
const Glyph& getGlyph(char c);
|
||||
|
||||
// the font to render
|
||||
QFont _font;
|
||||
|
||||
// the font metrics
|
||||
QFontMetrics _metrics;
|
||||
TextRenderer(const char* family, float pointSize = -1, int weight = -1, bool italic = false,
|
||||
EffectType effect = NO_EFFECT, int effectThickness = 1, const QColor& color = QColor(255, 255, 255));
|
||||
|
||||
// the type of effect to apply
|
||||
EffectType _effectType;
|
||||
const EffectType _effectType;
|
||||
|
||||
// the thickness of the effect
|
||||
int _effectThickness;
|
||||
|
||||
// maps characters to cached glyph info
|
||||
QHash<char, Glyph> _glyphs;
|
||||
|
||||
// the id of the glyph texture to which we're currently writing
|
||||
GLuint _currentTextureID;
|
||||
|
||||
// the position within the current glyph texture
|
||||
int _x, _y;
|
||||
|
||||
// the height of the current row of characters
|
||||
int _rowHeight;
|
||||
|
||||
// the list of all texture ids for which we're responsible
|
||||
QVector<GLuint> _allTextureIDs;
|
||||
|
||||
const int _effectThickness;
|
||||
|
||||
const float _pointSize;
|
||||
|
||||
// text color
|
||||
QColor _color;
|
||||
|
||||
// Graphics Buffer containing the current accumulated glyphs to render
|
||||
gpu::BufferPointer _glyphsBuffer;
|
||||
gpu::BufferPointer _glyphsColorBuffer;
|
||||
gpu::Stream::FormatPointer _glyphsStreamFormat;
|
||||
gpu::BufferStreamPointer _glyphsStream;
|
||||
int _numGlyphsBatched;
|
||||
const QColor _color;
|
||||
|
||||
static QHash<Properties, TextRenderer*> _instances;
|
||||
Font * _font;
|
||||
};
|
||||
|
||||
class Glyph {
|
||||
public:
|
||||
|
||||
Glyph(int textureID = 0, const QPoint& location = QPoint(), const QRect& bounds = QRect(), int width = 0);
|
||||
|
||||
GLuint textureID() const { return _textureID; }
|
||||
const QPoint& location () const { return _location; }
|
||||
const QRect& bounds() const { return _bounds; }
|
||||
int width () const { return _width; }
|
||||
|
||||
bool isValid() { return _width != 0; }
|
||||
|
||||
private:
|
||||
|
||||
// the id of the OpenGL texture containing the glyph
|
||||
GLuint _textureID;
|
||||
|
||||
// the location of the character within the texture
|
||||
QPoint _location;
|
||||
|
||||
// the bounds of the character
|
||||
QRect _bounds;
|
||||
|
||||
// the width of the character (distance to next, as opposed to bounds width)
|
||||
int _width;
|
||||
};
|
||||
|
||||
#endif // hifi_TextRenderer_h
|
||||
|
|
49
libraries/render-utils/src/sdf_text.slf
Normal file
|
@ -0,0 +1,49 @@
|
|||
<@include Config.slh@>
|
||||
<$VERSION_HEADER$>
|
||||
// Generated on <$_SCRIBE_DATE$>
|
||||
// sdf_text.frag
|
||||
// fragment shader
|
||||
//
|
||||
// Created by Bradley Austin Davis on 2015-02-04
|
||||
// Based on fragment shader code from
|
||||
// https://github.com/paulhoux/Cinder-Samples/blob/master/TextRendering/include/text/Text.cpp
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
uniform sampler2D Font;
|
||||
uniform vec4 Color;
|
||||
uniform bool Outline;
|
||||
|
||||
varying vec2 vTexCoord;
|
||||
|
||||
const float gamma = 2.6;
|
||||
const float smoothing = 100.0;
|
||||
const float interiorCutoff = 0.8;
|
||||
const float outlineExpansion = 0.2;
|
||||
|
||||
void main() {
|
||||
// retrieve signed distance
|
||||
float sdf = texture2D(Font, vTexCoord).r;
|
||||
if (Outline) {
|
||||
if (sdf > interiorCutoff) {
|
||||
sdf = 1.0 - sdf;
|
||||
} else {
|
||||
sdf += outlineExpansion;
|
||||
}
|
||||
}
|
||||
// perform adaptive anti-aliasing of the edges
|
||||
// The larger we're rendering, the less anti-aliasing we need
|
||||
float s = smoothing * length(fwidth(vTexCoord));
|
||||
float w = clamp( s, 0.0, 0.5);
|
||||
float a = smoothstep(0.5 - w, 0.5 + w, sdf);
|
||||
|
||||
// gamma correction for linear attenuation
|
||||
a = pow(a, 1.0 / gamma);
|
||||
|
||||
if (a < 0.01) {
|
||||
discard;
|
||||
}
|
||||
|
||||
// final color
|
||||
gl_FragColor = vec4(Color.rgb, a);
|
||||
}
|
24
libraries/render-utils/src/sdf_text.slv
Normal file
|
@ -0,0 +1,24 @@
|
|||
<@include Config.slh@>
|
||||
<$VERSION_HEADER$>
|
||||
// Generated on <$_SCRIBE_DATE$>
|
||||
// sdf_text.vert
|
||||
// vertex shader
|
||||
//
|
||||
// Created by Brad Davis on 10/14/13.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
uniform mat4 Projection;
|
||||
uniform mat4 ModelView;
|
||||
|
||||
attribute vec3 Position;
|
||||
attribute vec2 TexCoord;
|
||||
|
||||
varying vec2 vTexCoord;
|
||||
|
||||
void main() {
|
||||
vTexCoord = TexCoord;
|
||||
gl_Position = Projection * ModelView * vec4(Position, 1.0);
|
||||
}
|
|
@ -2,7 +2,7 @@ set(TARGET_NAME shared)
|
|||
|
||||
# use setup_hifi_library macro to setup our project and link appropriate Qt modules
|
||||
# TODO: there isn't really a good reason to have Script linked here - let's get what is requiring it out (RegisteredMetaTypes.cpp)
|
||||
setup_hifi_library(Network Script Widgets)
|
||||
setup_hifi_library(Gui Network Script Widgets)
|
||||
|
||||
# call macro to include our dependency includes and bubble them up via a property on our target
|
||||
include_dependency_includes()
|
|
@ -302,4 +302,36 @@ bool isSimilarPosition(const glm::vec3& positionA, const glm::vec3& positionB, f
|
|||
// Compute the distance between the two points
|
||||
float positionDistance = glm::distance(positionA, positionB);
|
||||
return (positionDistance <= similarEnough);
|
||||
}
|
||||
}
|
||||
|
||||
glm::uvec2 toGlm(const QSize & size) {
|
||||
return glm::uvec2(size.width(), size.height());
|
||||
}
|
||||
|
||||
glm::ivec2 toGlm(const QPoint & pt) {
|
||||
return glm::ivec2(pt.x(), pt.y());
|
||||
}
|
||||
|
||||
glm::vec2 toGlm(const QPointF & pt) {
|
||||
return glm::vec2(pt.x(), pt.y());
|
||||
}
|
||||
|
||||
glm::vec3 toGlm(const xColor & color) {
|
||||
static const float MAX_COLOR = 255.0f;
|
||||
return std::move(glm::vec3(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR));
|
||||
}
|
||||
|
||||
glm::vec4 toGlm(const QColor & color) {
|
||||
return glm::vec4(color.redF(), color.greenF(), color.blueF(), color.alphaF());
|
||||
}
|
||||
|
||||
QMatrix4x4 fromGlm(const glm::mat4 & m) {
|
||||
return QMatrix4x4(&m[0][0]).transposed();
|
||||
}
|
||||
|
||||
QRectF glmToRect(const glm::vec2 & pos, const glm::vec2 & size) {
|
||||
QRectF result(pos.x, pos.y, size.x, size.y);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtGui/QMatrix4x4>
|
||||
#include <QtGui/QColor>
|
||||
|
||||
#include "SharedUtil.h"
|
||||
|
||||
|
@ -91,5 +93,14 @@ bool isSimilarOrientation(const glm::quat& orientionA, const glm::quat& orientio
|
|||
const float POSITION_SIMILAR_ENOUGH = 0.1f; // 0.1 meter
|
||||
bool isSimilarPosition(const glm::vec3& positionA, const glm::vec3& positionB, float similarEnough = POSITION_SIMILAR_ENOUGH);
|
||||
|
||||
glm::uvec2 toGlm(const QSize & size);
|
||||
glm::ivec2 toGlm(const QPoint & pt);
|
||||
glm::vec2 toGlm(const QPointF & pt);
|
||||
glm::vec3 toGlm(const xColor & color);
|
||||
glm::vec4 toGlm(const QColor & color);
|
||||
|
||||
#endif // hifi_GLMHelpers_h
|
||||
QMatrix4x4 fromGlm(const glm::mat4 & m);
|
||||
|
||||
QRectF glmToRect(const glm::vec2 & pos, const glm::vec2 & size);
|
||||
|
||||
#endif // hifi_GLMHelpers_h
|
||||
|
|
19
tests/render-utils/CMakeLists.txt
Normal file
|
@ -0,0 +1,19 @@
|
|||
set(TARGET_NAME render-utils-tests)
|
||||
|
||||
setup_hifi_project(Quick Gui OpenGL)
|
||||
|
||||
include_glm()
|
||||
|
||||
#include_oglplus()
|
||||
|
||||
# link in the shared libraries
|
||||
link_hifi_libraries(render-utils gpu shared)
|
||||
|
||||
if (WIN32)
|
||||
# we're using static GLEW, so define GLEW_STATIC
|
||||
add_definitions(-DGLEW_STATIC)
|
||||
endif ()
|
||||
|
||||
#link_libraries(animation fbx shared gpu)
|
||||
include_dependency_includes()
|
||||
|
225
tests/render-utils/src/main.cpp
Normal file
|
@ -0,0 +1,225 @@
|
|||
//
|
||||
// main.cpp
|
||||
// tests/render-utils/src
|
||||
//
|
||||
// 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 "TextRenderer.h"
|
||||
#include "MatrixStack.h"
|
||||
|
||||
#include <QWindow>
|
||||
#include <QFile>
|
||||
#include <QTime>
|
||||
#include <QImage>
|
||||
#include <QTimer>
|
||||
#include <QElapsedTimer>
|
||||
#include <QOpenGLContext>
|
||||
#include <QOpenGLBuffer>
|
||||
#include <QOpenGLShaderProgram>
|
||||
#include <QResizeEvent>
|
||||
#include <QOpenGLTexture>
|
||||
#include <QOpenGLVertexArrayObject>
|
||||
#include <QApplication>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <glm/glm.hpp>
|
||||
#include <PathUtils.h>
|
||||
|
||||
class RateCounter {
|
||||
std::vector<float> times;
|
||||
QElapsedTimer timer;
|
||||
public:
|
||||
RateCounter() {
|
||||
timer.start();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
times.clear();
|
||||
}
|
||||
|
||||
unsigned int count() const {
|
||||
return times.size() - 1;
|
||||
}
|
||||
|
||||
float elapsed() const {
|
||||
if (times.size() < 1) {
|
||||
return 0.0f;
|
||||
}
|
||||
float elapsed = *times.rbegin() - *times.begin();
|
||||
return elapsed;
|
||||
}
|
||||
|
||||
void increment() {
|
||||
times.push_back(timer.elapsed() / 1000.0f);
|
||||
}
|
||||
|
||||
float rate() const {
|
||||
if (elapsed() == 0.0f) {
|
||||
return NAN;
|
||||
}
|
||||
return (float) count() / elapsed();
|
||||
}
|
||||
};
|
||||
|
||||
// Create a simple OpenGL window that renders text in various ways
|
||||
class QTestWindow: public QWindow {
|
||||
Q_OBJECT
|
||||
QOpenGLContext * _context;
|
||||
QSize _size;
|
||||
TextRenderer* _textRenderer[4];
|
||||
RateCounter fps;
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent * ev) override {
|
||||
QWindow::resizeEvent(ev);
|
||||
_size = ev->size();
|
||||
resizeGl();
|
||||
}
|
||||
|
||||
void resizeGl() {
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
glOrtho(0, _size.width(), _size.height(), 0, 1, -1);
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glViewport(0, 0, _size.width(), _size.height());
|
||||
}
|
||||
|
||||
public:
|
||||
QTestWindow();
|
||||
virtual ~QTestWindow() {
|
||||
|
||||
}
|
||||
void makeCurrent() {
|
||||
_context->makeCurrent(this);
|
||||
}
|
||||
|
||||
void draw();
|
||||
};
|
||||
|
||||
#ifndef SERIF_FONT_FAMILY
|
||||
#define SERIF_FONT_FAMILY "Times New Roman"
|
||||
#endif
|
||||
|
||||
QTestWindow::QTestWindow() {
|
||||
setSurfaceType(QSurface::OpenGLSurface);
|
||||
|
||||
QSurfaceFormat format;
|
||||
// Qt Quick may need a depth and stencil buffer. Always make sure these are available.
|
||||
format.setDepthBufferSize(16);
|
||||
format.setStencilBufferSize(8);
|
||||
format.setVersion(3, 2);
|
||||
format.setProfile(
|
||||
QSurfaceFormat::OpenGLContextProfile::CompatibilityProfile);
|
||||
setFormat(format);
|
||||
|
||||
_context = new QOpenGLContext;
|
||||
_context->setFormat(format);
|
||||
_context->create();
|
||||
|
||||
show();
|
||||
makeCurrent();
|
||||
qDebug() << (const char*) glGetString(GL_VERSION);
|
||||
|
||||
#ifdef WIN32
|
||||
glewExperimental = true;
|
||||
GLenum err = glewInit();
|
||||
if (GLEW_OK != err) {
|
||||
/* Problem: glewInit failed, something is seriously wrong. */
|
||||
const GLubyte * errStr = glewGetErrorString(err);
|
||||
qDebug("Error: %s\n", errStr);
|
||||
}
|
||||
qDebug("Status: Using GLEW %s\n", glewGetString(GLEW_VERSION));
|
||||
|
||||
if (wglewGetExtension("WGL_EXT_swap_control")) {
|
||||
int swapInterval = wglGetSwapIntervalEXT();
|
||||
qDebug("V-Sync is %s\n", (swapInterval > 0 ? "ON" : "OFF"));
|
||||
}
|
||||
glGetError();
|
||||
#endif
|
||||
|
||||
setFramePosition(QPoint(100, -900));
|
||||
resize(QSize(800, 600));
|
||||
_size = QSize(800, 600);
|
||||
|
||||
_textRenderer[0] = TextRenderer::getInstance(SANS_FONT_FAMILY, 12, false);
|
||||
_textRenderer[1] = TextRenderer::getInstance(SERIF_FONT_FAMILY, 12, false,
|
||||
TextRenderer::SHADOW_EFFECT);
|
||||
_textRenderer[2] = TextRenderer::getInstance(MONO_FONT_FAMILY, 48, -1,
|
||||
false, TextRenderer::OUTLINE_EFFECT);
|
||||
_textRenderer[3] = TextRenderer::getInstance(INCONSOLATA_FONT_FAMILY, 24);
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glClearColor(0.2f, 0.2f, 0.2f, 1);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
resizeGl();
|
||||
}
|
||||
|
||||
static const wchar_t * EXAMPLE_TEXT = L"Hello";
|
||||
//static const wchar_t * EXAMPLE_TEXT = L"\xC1y Hello 1.0\ny\xC1 line 2\n\xC1y";
|
||||
static const glm::uvec2 QUAD_OFFSET(10, 10);
|
||||
|
||||
static const glm::vec3 COLORS[4] = { { 1.0, 1.0, 1.0 }, { 0.5, 1.0, 0.5 }, {
|
||||
1.0, 0.5, 0.5 }, { 0.5, 0.5, 1.0 } };
|
||||
|
||||
void QTestWindow::draw() {
|
||||
makeCurrent();
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
const glm::uvec2 size = glm::uvec2(_size.width() / 2, _size.height() / 2);
|
||||
const glm::uvec2 offsets[4] = { { QUAD_OFFSET.x, QUAD_OFFSET.y }, { size.x
|
||||
+ QUAD_OFFSET.x, QUAD_OFFSET.y }, { size.x + QUAD_OFFSET.x, size.y
|
||||
+ QUAD_OFFSET.y }, { QUAD_OFFSET.x, size.y + QUAD_OFFSET.y }, };
|
||||
|
||||
QString str = QString::fromWCharArray(EXAMPLE_TEXT);
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
glm::vec2 bounds = _textRenderer[i]->computeExtent(str);
|
||||
glPushMatrix();
|
||||
{
|
||||
glTranslatef(offsets[i].x, offsets[i].y, 0);
|
||||
glColor3f(0, 0, 0);
|
||||
glBegin(GL_QUADS);
|
||||
{
|
||||
glVertex2f(0, 0);
|
||||
glVertex2f(0, bounds.y);
|
||||
glVertex2f(bounds.x, bounds.y);
|
||||
glVertex2f(bounds.x, 0);
|
||||
}
|
||||
glEnd();
|
||||
}
|
||||
glPopMatrix();
|
||||
const int testCount = 100;
|
||||
for (int j = 0; j < testCount; ++j) {
|
||||
// Draw backgrounds around where the text will appear
|
||||
// Draw the text itself
|
||||
_textRenderer[i]->draw(offsets[i].x, offsets[i].y, str.toLocal8Bit().constData(),
|
||||
glm::vec4(COLORS[i], 1.0f));
|
||||
}
|
||||
}
|
||||
_context->swapBuffers(this);
|
||||
glFinish();
|
||||
fps.increment();
|
||||
if (fps.elapsed() >= 2.0f) {
|
||||
qDebug() << "FPS: " << fps.rate();
|
||||
fps.reset();
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
QApplication app(argc, argv);
|
||||
QTestWindow window;
|
||||
QTimer timer;
|
||||
timer.setInterval(1);
|
||||
app.connect(&timer, &QTimer::timeout, &app, [&] {
|
||||
window.draw();
|
||||
});
|
||||
timer.start();
|
||||
app.exec();
|
||||
return 0;
|
||||
}
|
||||
|
||||
#include "main.moc"
|