resolve conflicts on merge with upstream master

This commit is contained in:
Stephen Birarda 2015-02-06 09:49:46 -08:00
commit 219e78425b
50 changed files with 95580 additions and 571 deletions

View file

@ -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);
}

View file

@ -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">

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View file

@ -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;

View file

@ -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()");

View file

@ -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;

View file

@ -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,

View file

@ -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) {

View file

@ -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;

View file

@ -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

View file

@ -38,7 +38,7 @@ public slots:
private:
int _deviceTrackerId;
int _subTrackerId;
uint _subTrackerId;
// cache for the spatial
SpatialEvent _eventCache;

View file

@ -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) {

View file

@ -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();

View file

@ -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();
}

View file

@ -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&)));

View file

@ -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: ";

View file

@ -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);

View file

@ -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) {

View file

@ -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;
}
}

View file

@ -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();
}

View file

@ -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;

View file

@ -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())) {

View file

@ -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) {

View file

@ -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.

View file

@ -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;

View file

@ -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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1 @@
#include "MatrixStack.h"

View 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();
});
}
};

View file

@ -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) {
}

View file

@ -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

View 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);
}

View 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);
}

View file

@ -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()

View file

@ -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;
}

View file

@ -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

View 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()

View 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"