mirror of
https://github.com/overte-org/overte.git
synced 2025-04-22 03:04:33 +02:00
Merge pull request #1295 from ZappoMan/compressed_packets
Major improvements to Voxel Packet "packing" and compression
This commit is contained in:
commit
14129cd86d
27 changed files with 1556 additions and 452 deletions
|
@ -686,9 +686,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
|||
break;
|
||||
|
||||
case Qt::Key_C:
|
||||
if (isShifted) {
|
||||
Menu::getInstance()->triggerOption(MenuOption::OcclusionCulling);
|
||||
} else if (_nudgeStarted) {
|
||||
if (_nudgeStarted) {
|
||||
_nudgeGuidePosition.y -= _mouseVoxel.s;
|
||||
} else {
|
||||
_myAvatar.setDriveKeys(DOWN, 1);
|
||||
|
@ -1526,7 +1524,6 @@ bool Application::sendVoxelsOperation(VoxelNode* node, void* extraData) {
|
|||
SendVoxelsOperationArgs* args = (SendVoxelsOperationArgs*)extraData;
|
||||
if (node->isColored()) {
|
||||
const unsigned char* nodeOctalCode = node->getOctalCode();
|
||||
|
||||
unsigned char* codeColorBuffer = NULL;
|
||||
int codeLength = 0;
|
||||
int bytesInCode = 0;
|
||||
|
@ -1547,10 +1544,9 @@ bool Application::sendVoxelsOperation(VoxelNode* node, void* extraData) {
|
|||
}
|
||||
|
||||
// copy the colors over
|
||||
codeColorBuffer[bytesInCode + RED_INDEX ] = node->getColor()[RED_INDEX ];
|
||||
codeColorBuffer[bytesInCode + RED_INDEX] = node->getColor()[RED_INDEX];
|
||||
codeColorBuffer[bytesInCode + GREEN_INDEX] = node->getColor()[GREEN_INDEX];
|
||||
codeColorBuffer[bytesInCode + BLUE_INDEX ] = node->getColor()[BLUE_INDEX ];
|
||||
|
||||
codeColorBuffer[bytesInCode + BLUE_INDEX] = node->getColor()[BLUE_INDEX];
|
||||
getInstance()->_voxelEditSender.queueVoxelEditMessage(PACKET_TYPE_SET_VOXEL_DESTRUCTIVE,
|
||||
codeColorBuffer, codeAndColorLength);
|
||||
|
||||
|
@ -1614,7 +1610,6 @@ void Application::pasteVoxelsToOctalCode(const unsigned char* octalCodeDestinati
|
|||
// the server as an set voxel message, this will also rebase the voxels to the new location
|
||||
SendVoxelsOperationArgs args;
|
||||
args.newBaseOctCode = octalCodeDestination;
|
||||
|
||||
_sharedVoxelSystem.getTree()->recurseTreeWithOperation(sendVoxelsOperation, &args);
|
||||
|
||||
if (_sharedVoxelSystem.getTree() != &_clipboard) {
|
||||
|
@ -1773,7 +1768,7 @@ void Application::init() {
|
|||
_voxels.setMaxVoxels(Menu::getInstance()->getMaxVoxels());
|
||||
_voxels.setUseVoxelShader(Menu::getInstance()->isOptionChecked(MenuOption::UseVoxelShader));
|
||||
_voxels.setVoxelsAsPoints(Menu::getInstance()->isOptionChecked(MenuOption::VoxelsAsPoints));
|
||||
_voxels.setDisableFastVoxelPipeline(Menu::getInstance()->isOptionChecked(MenuOption::DisableFastVoxelPipeline));
|
||||
_voxels.setDisableFastVoxelPipeline(false);
|
||||
_voxels.init();
|
||||
|
||||
|
||||
|
@ -2580,10 +2575,11 @@ void Application::queryVoxels() {
|
|||
bool wantExtraDebugging = Menu::getInstance()->isOptionChecked(MenuOption::ExtraDebugging);
|
||||
|
||||
// These will be the same for all servers, so we can set them up once and then reuse for each server we send to.
|
||||
_voxelQuery.setWantLowResMoving(Menu::getInstance()->isOptionChecked(MenuOption::LowRes));
|
||||
_voxelQuery.setWantColor(Menu::getInstance()->isOptionChecked(MenuOption::SendVoxelColors));
|
||||
_voxelQuery.setWantDelta(Menu::getInstance()->isOptionChecked(MenuOption::DeltaSending));
|
||||
_voxelQuery.setWantOcclusionCulling(Menu::getInstance()->isOptionChecked(MenuOption::OcclusionCulling));
|
||||
_voxelQuery.setWantLowResMoving(!Menu::getInstance()->isOptionChecked(MenuOption::DisableLowRes));
|
||||
_voxelQuery.setWantColor(!Menu::getInstance()->isOptionChecked(MenuOption::DisableColorVoxels));
|
||||
_voxelQuery.setWantDelta(!Menu::getInstance()->isOptionChecked(MenuOption::DisableDeltaSending));
|
||||
_voxelQuery.setWantOcclusionCulling(Menu::getInstance()->isOptionChecked(MenuOption::EnableOcclusionCulling));
|
||||
_voxelQuery.setWantCompression(Menu::getInstance()->isOptionChecked(MenuOption::EnableVoxelPacketCompression));
|
||||
|
||||
_voxelQuery.setCameraPosition(_viewFrustum.getPosition());
|
||||
_voxelQuery.setCameraOrientation(_viewFrustum.getOrientation());
|
||||
|
@ -2645,15 +2641,16 @@ void Application::queryVoxels() {
|
|||
int perServerPPS = 0;
|
||||
const int SMALL_BUDGET = 10;
|
||||
int perUnknownServer = SMALL_BUDGET;
|
||||
int totalPPS = Menu::getInstance()->getMaxVoxelPacketsPerSecond();
|
||||
|
||||
// determine PPS based on number of servers
|
||||
if (inViewServers >= 1) {
|
||||
// set our preferred PPS to be exactly evenly divided among all of the voxel servers... and allocate 1 PPS
|
||||
// for each unknown jurisdiction server
|
||||
perServerPPS = (DEFAULT_MAX_VOXEL_PPS / inViewServers) - (unknownJurisdictionServers * perUnknownServer);
|
||||
perServerPPS = (totalPPS / inViewServers) - (unknownJurisdictionServers * perUnknownServer);
|
||||
} else {
|
||||
if (unknownJurisdictionServers > 0) {
|
||||
perUnknownServer = (DEFAULT_MAX_VOXEL_PPS / unknownJurisdictionServers);
|
||||
perUnknownServer = (totalPPS / unknownJurisdictionServers);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3611,7 +3608,7 @@ void Application::displayStats() {
|
|||
// Voxel Rendering
|
||||
voxelStats.str("");
|
||||
voxelStats.precision(4);
|
||||
voxelStats << "Voxel Rendering Slots" <<
|
||||
voxelStats << "Voxel Rendering Slots " <<
|
||||
"Max: " << _voxels.getMaxVoxels() / 1000.f << "K " <<
|
||||
"Drawn: " << _voxels.getVoxelsWritten() / 1000.f << "K " <<
|
||||
"Abandoned: " << _voxels.getAbandonedVoxels() / 1000.f << "K ";
|
||||
|
@ -4321,6 +4318,24 @@ void Application::nodeKilled(Node* node) {
|
|||
}
|
||||
}
|
||||
|
||||
void Application::trackIncomingVoxelPacket(unsigned char* messageData, ssize_t messageLength,
|
||||
sockaddr senderAddress, bool wasStatsPacket) {
|
||||
|
||||
// Attempt to identify the sender from it's address.
|
||||
Node* voxelServer = NodeList::getInstance()->nodeWithAddress(&senderAddress);
|
||||
if (voxelServer) {
|
||||
QUuid nodeUUID = voxelServer->getUUID();
|
||||
|
||||
// now that we know the node ID, let's add these stats to the stats for that node...
|
||||
_voxelSceneStatsLock.lockForWrite();
|
||||
if (_voxelServerSceneStats.find(nodeUUID) != _voxelServerSceneStats.end()) {
|
||||
VoxelSceneStats& stats = _voxelServerSceneStats[nodeUUID];
|
||||
stats.trackIncomingVoxelPacket(messageData, messageLength, wasStatsPacket);
|
||||
}
|
||||
_voxelSceneStatsLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
int Application::parseVoxelStats(unsigned char* messageData, ssize_t messageLength, sockaddr senderAddress) {
|
||||
|
||||
// But, also identify the sender, and keep track of the contained jurisdiction root for this server
|
||||
|
@ -4399,13 +4414,26 @@ void* Application::networkReceive(void* args) {
|
|||
app->_audio.addReceivedAudioToBuffer(app->_incomingPacket, bytesReceived);
|
||||
break;
|
||||
case PACKET_TYPE_VOXEL_DATA:
|
||||
case PACKET_TYPE_VOXEL_DATA_MONOCHROME:
|
||||
case PACKET_TYPE_Z_COMMAND:
|
||||
case PACKET_TYPE_ERASE_VOXEL:
|
||||
case PACKET_TYPE_VOXEL_STATS:
|
||||
case PACKET_TYPE_ENVIRONMENT_DATA: {
|
||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||
"Application::networkReceive()... _voxelProcessor.queueReceivedPacket()");
|
||||
|
||||
bool wantExtraDebugging = Menu::getInstance()->isOptionChecked(MenuOption::ExtraDebugging);
|
||||
if (wantExtraDebugging && app->_incomingPacket[0] == PACKET_TYPE_VOXEL_DATA) {
|
||||
int numBytesPacketHeader = numBytesForPacketHeader(app->_incomingPacket);
|
||||
unsigned char* dataAt = app->_incomingPacket + numBytesPacketHeader;
|
||||
dataAt += sizeof(VOXEL_PACKET_FLAGS);
|
||||
VOXEL_PACKET_SEQUENCE sequence = (*(VOXEL_PACKET_SEQUENCE*)dataAt);
|
||||
dataAt += sizeof(VOXEL_PACKET_SEQUENCE);
|
||||
VOXEL_PACKET_SENT_TIME sentAt = (*(VOXEL_PACKET_SENT_TIME*)dataAt);
|
||||
dataAt += sizeof(VOXEL_PACKET_SENT_TIME);
|
||||
VOXEL_PACKET_SENT_TIME arrivedAt = usecTimestampNow();
|
||||
int flightTime = arrivedAt - sentAt;
|
||||
|
||||
printf("got PACKET_TYPE_VOXEL_DATA, sequence:%d flightTime:%d\n", sequence, flightTime);
|
||||
}
|
||||
|
||||
// add this packet to our list of voxel packets and process them on the voxel processing
|
||||
app->_voxelProcessor.queueReceivedPacket(senderAddress, app->_incomingPacket, bytesReceived);
|
||||
|
|
|
@ -465,6 +465,8 @@ private:
|
|||
PieMenu _pieMenu;
|
||||
|
||||
int parseVoxelStats(unsigned char* messageData, ssize_t messageLength, sockaddr senderAddress);
|
||||
void trackIncomingVoxelPacket(unsigned char* messageData, ssize_t messageLength,
|
||||
sockaddr senderAddress, bool wasStatsPacket);
|
||||
|
||||
NodeToJurisdictionMap _voxelServerJurisdictions;
|
||||
NodeToVoxelSceneStats _voxelServerSceneStats;
|
||||
|
|
|
@ -68,7 +68,8 @@ Menu::Menu() :
|
|||
_lodToolsDialog(NULL),
|
||||
_maxVoxels(DEFAULT_MAX_VOXELS_PER_SYSTEM),
|
||||
_voxelSizeScale(DEFAULT_VOXEL_SIZE_SCALE),
|
||||
_boundaryLevelAdjust(0)
|
||||
_boundaryLevelAdjust(0),
|
||||
_maxVoxelPacketsPerSecond(DEFAULT_MAX_VOXEL_PPS)
|
||||
{
|
||||
Application *appInstance = Application::getInstance();
|
||||
|
||||
|
@ -304,22 +305,14 @@ Menu::Menu() :
|
|||
addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::DontFadeOnVoxelServerChanges);
|
||||
addActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::LodTools, Qt::SHIFT | Qt::Key_L, this, SLOT(lodTools()));
|
||||
|
||||
QMenu* cullingOptionsMenu = voxelOptionsMenu->addMenu("Culling Options");
|
||||
addDisabledActionAndSeparator(cullingOptionsMenu, "Standard Settings");
|
||||
addCheckableActionToQMenuAndActionHash(cullingOptionsMenu, MenuOption::OldVoxelCullingMode, 0,
|
||||
false, this, SLOT(setOldVoxelCullingMode(bool)));
|
||||
|
||||
addCheckableActionToQMenuAndActionHash(cullingOptionsMenu, MenuOption::NewVoxelCullingMode, 0,
|
||||
false, this, SLOT(setNewVoxelCullingMode(bool)));
|
||||
|
||||
addDisabledActionAndSeparator(cullingOptionsMenu, "Individual Option Settings");
|
||||
addCheckableActionToQMenuAndActionHash(cullingOptionsMenu, MenuOption::DisableFastVoxelPipeline, 0,
|
||||
false, appInstance->getVoxels(), SLOT(setDisableFastVoxelPipeline(bool)));
|
||||
addCheckableActionToQMenuAndActionHash(cullingOptionsMenu, MenuOption::DisableHideOutOfView);
|
||||
addCheckableActionToQMenuAndActionHash(cullingOptionsMenu, MenuOption::RemoveOutOfView);
|
||||
addCheckableActionToQMenuAndActionHash(cullingOptionsMenu, MenuOption::UseFullFrustumInHide);
|
||||
addCheckableActionToQMenuAndActionHash(cullingOptionsMenu, MenuOption::DisableConstantCulling);
|
||||
|
||||
QMenu* voxelProtoOptionsMenu = voxelOptionsMenu->addMenu("Voxel Server Protocol Options");
|
||||
|
||||
addCheckableActionToQMenuAndActionHash(voxelProtoOptionsMenu, MenuOption::DisableColorVoxels);
|
||||
addCheckableActionToQMenuAndActionHash(voxelProtoOptionsMenu, MenuOption::DisableLowRes);
|
||||
addCheckableActionToQMenuAndActionHash(voxelProtoOptionsMenu, MenuOption::DisableDeltaSending);
|
||||
addCheckableActionToQMenuAndActionHash(voxelProtoOptionsMenu, MenuOption::EnableVoxelPacketCompression);
|
||||
addCheckableActionToQMenuAndActionHash(voxelProtoOptionsMenu, MenuOption::EnableOcclusionCulling);
|
||||
addCheckableActionToQMenuAndActionHash(voxelProtoOptionsMenu, MenuOption::DestructiveAddVoxel);
|
||||
|
||||
QMenu* avatarOptionsMenu = developerMenu->addMenu("Avatar Options");
|
||||
|
||||
|
@ -494,13 +487,6 @@ Menu::Menu() :
|
|||
addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::EchoServerAudio);
|
||||
addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::EchoLocalAudio);
|
||||
|
||||
QMenu* voxelProtoOptionsMenu = developerMenu->addMenu("Voxel Server Protocol Options");
|
||||
|
||||
addCheckableActionToQMenuAndActionHash(voxelProtoOptionsMenu, MenuOption::SendVoxelColors);
|
||||
addCheckableActionToQMenuAndActionHash(voxelProtoOptionsMenu, MenuOption::LowRes);
|
||||
addCheckableActionToQMenuAndActionHash(voxelProtoOptionsMenu, MenuOption::DeltaSending);
|
||||
addCheckableActionToQMenuAndActionHash(voxelProtoOptionsMenu, MenuOption::OcclusionCulling);
|
||||
addCheckableActionToQMenuAndActionHash(voxelProtoOptionsMenu, MenuOption::DestructiveAddVoxel);
|
||||
|
||||
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::ExtraDebugging);
|
||||
addActionToQMenuAndActionHash(developerMenu, MenuOption::PasteToVoxel,
|
||||
|
@ -531,6 +517,7 @@ void Menu::loadSettings(QSettings* settings) {
|
|||
_fieldOfView = loadSetting(settings, "fieldOfView", DEFAULT_FIELD_OF_VIEW_DEGREES);
|
||||
_faceshiftEyeDeflection = loadSetting(settings, "faceshiftEyeDeflection", DEFAULT_FACESHIFT_EYE_DEFLECTION);
|
||||
_maxVoxels = loadSetting(settings, "maxVoxels", DEFAULT_MAX_VOXELS_PER_SYSTEM);
|
||||
_maxVoxelPacketsPerSecond = loadSetting(settings, "maxVoxelsPPS", DEFAULT_MAX_VOXEL_PPS);
|
||||
_voxelSizeScale = loadSetting(settings, "voxelSizeScale", DEFAULT_VOXEL_SIZE_SCALE);
|
||||
_boundaryLevelAdjust = loadSetting(settings, "boundaryLevelAdjust", 0);
|
||||
|
||||
|
@ -560,6 +547,7 @@ void Menu::saveSettings(QSettings* settings) {
|
|||
settings->setValue("fieldOfView", _fieldOfView);
|
||||
settings->setValue("faceshiftEyeDeflection", _faceshiftEyeDeflection);
|
||||
settings->setValue("maxVoxels", _maxVoxels);
|
||||
settings->setValue("maxVoxelsPPS", _maxVoxelPacketsPerSecond);
|
||||
settings->setValue("voxelSizeScale", _voxelSizeScale);
|
||||
settings->setValue("boundaryLevelAdjust", _boundaryLevelAdjust);
|
||||
settings->beginGroup("View Frustum Offset Camera");
|
||||
|
@ -840,6 +828,16 @@ void Menu::editPreferences() {
|
|||
maxVoxels->setSingleStep(STEP_MAX_VOXELS);
|
||||
maxVoxels->setValue(_maxVoxels);
|
||||
form->addRow("Maximum Voxels:", maxVoxels);
|
||||
|
||||
QSpinBox* maxVoxelsPPS = new QSpinBox();
|
||||
const int MAX_MAX_VOXELS_PPS = 6000;
|
||||
const int MIN_MAX_VOXELS_PPS = 60;
|
||||
const int STEP_MAX_VOXELS_PPS = 10;
|
||||
maxVoxelsPPS->setMaximum(MAX_MAX_VOXELS_PPS);
|
||||
maxVoxelsPPS->setMinimum(MIN_MAX_VOXELS_PPS);
|
||||
maxVoxelsPPS->setSingleStep(STEP_MAX_VOXELS_PPS);
|
||||
maxVoxelsPPS->setValue(_maxVoxelPacketsPerSecond);
|
||||
form->addRow("Maximum Voxels Packets Per Second:", maxVoxelsPPS);
|
||||
|
||||
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
dialog.connect(buttons, SIGNAL(accepted()), SLOT(accept()));
|
||||
|
@ -879,6 +877,8 @@ void Menu::editPreferences() {
|
|||
|
||||
_maxVoxels = maxVoxels->value();
|
||||
applicationInstance->getVoxels()->setMaxVoxels(_maxVoxels);
|
||||
|
||||
_maxVoxelPacketsPerSecond = maxVoxelsPPS->value();
|
||||
|
||||
applicationInstance->getAvatar()->setLeanScale(leanScale->value());
|
||||
|
||||
|
@ -1146,30 +1146,3 @@ void Menu::updateFrustumRenderModeAction() {
|
|||
}
|
||||
}
|
||||
|
||||
void Menu::setOldVoxelCullingMode(bool oldMode) {
|
||||
setVoxelCullingMode(oldMode);
|
||||
}
|
||||
|
||||
void Menu::setNewVoxelCullingMode(bool newMode) {
|
||||
setVoxelCullingMode(!newMode);
|
||||
}
|
||||
|
||||
/// This will switch on or off several different individual settings options all at once based on choosing with Old or New
|
||||
/// voxel culling mode.
|
||||
void Menu::setVoxelCullingMode(bool oldMode) {
|
||||
const QString menus[] = { MenuOption::DisableFastVoxelPipeline, MenuOption::RemoveOutOfView, MenuOption::DisableHideOutOfView,
|
||||
MenuOption::UseFullFrustumInHide, MenuOption::DisableConstantCulling};
|
||||
bool oldModeValue[] = { true, true, true, true, true };
|
||||
bool newModeValue[] = { false, false, false, false, false };
|
||||
|
||||
for (int i = 0; i < sizeof(menus) / sizeof(menus[0]); i++) {
|
||||
bool desiredValue = oldMode ? oldModeValue[i] : newModeValue[i];
|
||||
if (isOptionChecked(menus[i]) != desiredValue) {
|
||||
triggerOption(menus[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// set the checkmarks accordingly...
|
||||
_actionHash.value(MenuOption::OldVoxelCullingMode)->setChecked(oldMode);
|
||||
_actionHash.value(MenuOption::NewVoxelCullingMode)->setChecked(!oldMode);
|
||||
}
|
||||
|
|
|
@ -68,6 +68,9 @@ public:
|
|||
void setBoundaryLevelAdjust(int boundaryLevelAdjust);
|
||||
int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; }
|
||||
|
||||
// User Tweakable PPS from Voxel Server
|
||||
int getMaxVoxelPacketsPerSecond() const { return _maxVoxelPacketsPerSecond; }
|
||||
|
||||
public slots:
|
||||
void bandwidthDetails();
|
||||
void voxelStatsDetails();
|
||||
|
@ -93,8 +96,6 @@ private slots:
|
|||
void chooseVoxelPaintColor();
|
||||
void runTests();
|
||||
void resetSwatchColors();
|
||||
void setOldVoxelCullingMode(bool oldMode);
|
||||
void setNewVoxelCullingMode(bool newMode);
|
||||
|
||||
private:
|
||||
static Menu* _instance;
|
||||
|
@ -124,7 +125,6 @@ private:
|
|||
const char* member = NULL);
|
||||
|
||||
void updateFrustumRenderModeAction();
|
||||
void setVoxelCullingMode(bool oldMode);
|
||||
|
||||
QHash<QString, QAction*> _actionHash;
|
||||
int _audioJitterBufferSamples; /// number of extra samples to wait before starting audio playback
|
||||
|
@ -140,6 +140,7 @@ private:
|
|||
float _voxelSizeScale;
|
||||
int _boundaryLevelAdjust;
|
||||
QAction* _useVoxelShader;
|
||||
int _maxVoxelPacketsPerSecond;
|
||||
};
|
||||
|
||||
namespace MenuOption {
|
||||
|
@ -161,13 +162,15 @@ namespace MenuOption {
|
|||
const QString DecreaseVoxelSize = "Decrease Voxel Size";
|
||||
const QString DeleteVoxels = "Delete";
|
||||
const QString DestructiveAddVoxel = "Create Voxel is Destructive";
|
||||
const QString DeltaSending = "Delta Sending";
|
||||
const QString DisableConstantCulling = "Disable Constant Culling";
|
||||
const QString DisableFastVoxelPipeline = "Disable Fast Voxel Pipeline";
|
||||
const QString DisableColorVoxels = "Disable Colored Voxels";
|
||||
const QString DisableDeltaSending = "Disable Delta Sending";
|
||||
const QString DisableLowRes = "Disable Lower Resolution While Moving";
|
||||
const QString DisplayFrustum = "Display Frustum";
|
||||
const QString DisplayLeapHands = "Display Leap Hands";
|
||||
const QString DontRenderVoxels = "Don't call _voxels.render()";
|
||||
const QString DontCallOpenGLForVoxels = "Don't call glDrawRangeElementsEXT() for Voxels";
|
||||
const QString EnableOcclusionCulling = "Enable Occlusion Culling";
|
||||
const QString EnableVoxelPacketCompression = "Enable Voxel Packet Compression";
|
||||
const QString EchoServerAudio = "Echo Server Audio";
|
||||
const QString EchoLocalAudio = "Echo Local Audio";
|
||||
const QString ExportVoxels = "Export Voxels";
|
||||
|
@ -191,7 +194,6 @@ namespace MenuOption {
|
|||
const QString GlowMode = "Cycle Glow Mode";
|
||||
const QString GoToDomain = "Go To Domain...";
|
||||
const QString GoToLocation = "Go To Location...";
|
||||
const QString DisableHideOutOfView = "Disable Hide Out of View Voxels";
|
||||
const QString GoToUser = "Go To User...";
|
||||
const QString ImportVoxels = "Import Voxels";
|
||||
const QString ImportVoxelsClipboard = "Import Voxels to Clipboard";
|
||||
|
@ -207,12 +209,10 @@ namespace MenuOption {
|
|||
const QString Login = "Login";
|
||||
const QString LookAtIndicator = "Look-at Indicator";
|
||||
const QString LookAtVectors = "Look-at Vectors";
|
||||
const QString LowRes = "Lower Resolution While Moving";
|
||||
const QString Mirror = "Mirror";
|
||||
const QString MoveWithLean = "Move with Lean";
|
||||
const QString NewVoxelCullingMode = "New Voxel Culling Mode";
|
||||
const QString NudgeVoxels = "Nudge";
|
||||
const QString OcclusionCulling = "Occlusion Culling";
|
||||
const QString OffAxisProjection = "Off-Axis Projection";
|
||||
const QString OldVoxelCullingMode = "Old Voxel Culling Mode";
|
||||
const QString TurnWithHead = "Turn using Head";
|
||||
|
@ -224,11 +224,9 @@ namespace MenuOption {
|
|||
const QString PipelineWarnings = "Show Render Pipeline Warnings";
|
||||
const QString Preferences = "Preferences...";
|
||||
const QString RandomizeVoxelColors = "Randomize Voxel TRUE Colors";
|
||||
const QString RemoveOutOfView = "Instead of Hide Remove Out of View Voxels";
|
||||
const QString ResetAvatarSize = "Reset Avatar Size";
|
||||
const QString ResetSwatchColors = "Reset Swatch Colors";
|
||||
const QString RunTimingTests = "Run Timing Tests";
|
||||
const QString SendVoxelColors = "Colored Voxels";
|
||||
const QString SettingsImport = "Import Settings";
|
||||
const QString Shadows = "Shadows";
|
||||
const QString SettingsExport = "Export Settings";
|
||||
|
@ -245,7 +243,6 @@ namespace MenuOption {
|
|||
const QString TreeStats = "Calculate Tree Stats";
|
||||
const QString TransmitterDrive = "Transmitter Drive";
|
||||
const QString Quit = "Quit";
|
||||
const QString UseFullFrustumInHide = "Use Full View Frustums when Culling";
|
||||
const QString UseVoxelShader = "Use Voxel Shader";
|
||||
const QString VoxelsAsPoints = "Draw Voxels as Points";
|
||||
const QString Voxels = "Voxels";
|
||||
|
|
|
@ -25,6 +25,8 @@ void VoxelPacketProcessor::processPacket(sockaddr& senderAddress, unsigned char*
|
|||
ssize_t messageLength = packetLength;
|
||||
|
||||
Application* app = Application::getInstance();
|
||||
bool wasStatsPacket = false;
|
||||
|
||||
|
||||
// check to see if the UI thread asked us to kill the voxel tree. since we're the only thread allowed to do that
|
||||
if (app->_wantToKillLocalVoxels) {
|
||||
|
@ -32,12 +34,13 @@ void VoxelPacketProcessor::processPacket(sockaddr& senderAddress, unsigned char*
|
|||
app->_wantToKillLocalVoxels = false;
|
||||
}
|
||||
|
||||
// note: PACKET_TYPE_VOXEL_STATS can have PACKET_TYPE_VOXEL_DATA or PACKET_TYPE_VOXEL_DATA_MONOCHROME
|
||||
// note: PACKET_TYPE_VOXEL_STATS can have PACKET_TYPE_VOXEL_DATA
|
||||
// immediately following them inside the same packet. So, we process the PACKET_TYPE_VOXEL_STATS first
|
||||
// then process any remaining bytes as if it was another packet
|
||||
if (packetData[0] == PACKET_TYPE_VOXEL_STATS) {
|
||||
|
||||
int statsMessageLength = app->parseVoxelStats(packetData, messageLength, senderAddress);
|
||||
wasStatsPacket = true;
|
||||
if (messageLength > statsMessageLength) {
|
||||
packetData += statsMessageLength;
|
||||
messageLength -= statsMessageLength;
|
||||
|
@ -45,11 +48,15 @@ void VoxelPacketProcessor::processPacket(sockaddr& senderAddress, unsigned char*
|
|||
return; // bail since piggyback data doesn't match our versioning
|
||||
}
|
||||
} else {
|
||||
// Note... stats packets don't have sequence numbers, so we don't want to send those to trackIncomingVoxelPacket()
|
||||
return; // bail since no piggyback data
|
||||
}
|
||||
} // fall through to piggyback message
|
||||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) {
|
||||
|
||||
app->trackIncomingVoxelPacket(packetData, messageLength, senderAddress, wasStatsPacket);
|
||||
|
||||
Node* voxelServer = NodeList::getInstance()->nodeWithAddress(&senderAddress);
|
||||
if (voxelServer && socketMatch(voxelServer->getActiveSocket(), &senderAddress)) {
|
||||
if (packetData[0] == PACKET_TYPE_ENVIRONMENT_DATA) {
|
||||
|
|
|
@ -595,50 +595,66 @@ int VoxelSystem::parseData(unsigned char* sourceBuffer, int numBytes) {
|
|||
|
||||
unsigned char command = *sourceBuffer;
|
||||
int numBytesPacketHeader = numBytesForPacketHeader(sourceBuffer);
|
||||
unsigned char* voxelData = sourceBuffer + numBytesPacketHeader;
|
||||
|
||||
switch(command) {
|
||||
case PACKET_TYPE_VOXEL_DATA: {
|
||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||
"readBitstreamToTree()");
|
||||
// ask the VoxelTree to read the bitstream into the tree
|
||||
ReadBitstreamToTreeParams args(WANT_COLOR, WANT_EXISTS_BITS, NULL, getDataSourceUUID());
|
||||
lockTree();
|
||||
_tree->readBitstreamToTree(voxelData, numBytes - numBytesPacketHeader, args);
|
||||
unlockTree();
|
||||
}
|
||||
break;
|
||||
case PACKET_TYPE_VOXEL_DATA_MONOCHROME: {
|
||||
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
|
||||
"readBitstreamToTree()");
|
||||
// ask the VoxelTree to read the MONOCHROME bitstream into the tree
|
||||
ReadBitstreamToTreeParams args(NO_COLOR, WANT_EXISTS_BITS, NULL, getDataSourceUUID());
|
||||
lockTree();
|
||||
_tree->readBitstreamToTree(voxelData, numBytes - numBytesPacketHeader, args);
|
||||
unlockTree();
|
||||
}
|
||||
break;
|
||||
case PACKET_TYPE_Z_COMMAND:
|
||||
|
||||
// the Z command is a special command that allows the sender to send high level semantic
|
||||
// requests, like erase all, or add sphere scene, different receivers may handle these
|
||||
// messages differently
|
||||
char* packetData = (char *)sourceBuffer;
|
||||
char* command = &packetData[numBytesPacketHeader]; // start of the command
|
||||
int commandLength = strlen(command); // commands are null terminated strings
|
||||
int totalLength = 1+commandLength+1;
|
||||
unsigned char* dataAt = sourceBuffer + numBytesPacketHeader;
|
||||
|
||||
qDebug("got Z message len(%d)= %s\n", numBytes, command);
|
||||
|
||||
while (totalLength <= numBytes) {
|
||||
if (0==strcmp(command,(char*)"erase all")) {
|
||||
qDebug("got Z message == erase all - NOT SUPPORTED ON INTERFACE\n");
|
||||
VOXEL_PACKET_FLAGS flags = (*(VOXEL_PACKET_FLAGS*)(dataAt));
|
||||
dataAt += sizeof(VOXEL_PACKET_FLAGS);
|
||||
VOXEL_PACKET_SEQUENCE sequence = (*(VOXEL_PACKET_SEQUENCE*)dataAt);
|
||||
dataAt += sizeof(VOXEL_PACKET_SEQUENCE);
|
||||
|
||||
VOXEL_PACKET_SENT_TIME sentAt = (*(VOXEL_PACKET_SENT_TIME*)dataAt);
|
||||
dataAt += sizeof(VOXEL_PACKET_SENT_TIME);
|
||||
|
||||
bool packetIsColored = oneAtBit(flags, PACKET_IS_COLOR_BIT);
|
||||
bool packetIsCompressed = oneAtBit(flags, PACKET_IS_COMPRESSED_BIT);
|
||||
|
||||
VOXEL_PACKET_SENT_TIME arrivedAt = usecTimestampNow();
|
||||
int flightTime = arrivedAt - sentAt;
|
||||
|
||||
VOXEL_PACKET_INTERNAL_SECTION_SIZE sectionLength = 0;
|
||||
int dataBytes = numBytes - VOXEL_PACKET_HEADER_SIZE;
|
||||
|
||||
int subsection = 1;
|
||||
while (dataBytes > 0) {
|
||||
if (packetIsCompressed) {
|
||||
if (dataBytes > sizeof(VOXEL_PACKET_INTERNAL_SECTION_SIZE)) {
|
||||
sectionLength = (*(VOXEL_PACKET_INTERNAL_SECTION_SIZE*)dataAt);
|
||||
dataAt += sizeof(VOXEL_PACKET_INTERNAL_SECTION_SIZE);
|
||||
dataBytes -= sizeof(VOXEL_PACKET_INTERNAL_SECTION_SIZE);
|
||||
} else {
|
||||
sectionLength = 0;
|
||||
dataBytes = 0; // stop looping something is wrong
|
||||
}
|
||||
} else {
|
||||
sectionLength = dataBytes;
|
||||
}
|
||||
if (0==strcmp(command,(char*)"add scene")) {
|
||||
qDebug("got Z message == add scene - NOT SUPPORTED ON INTERFACE\n");
|
||||
|
||||
if (sectionLength) {
|
||||
// ask the VoxelTree to read the bitstream into the tree
|
||||
ReadBitstreamToTreeParams args(packetIsColored ? WANT_COLOR : NO_COLOR, WANT_EXISTS_BITS, NULL, getDataSourceUUID());
|
||||
lockTree();
|
||||
VoxelPacketData packetData(packetIsCompressed);
|
||||
packetData.loadFinalizedContent(dataAt, sectionLength);
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::ExtraDebugging)) {
|
||||
qDebug("Got Packet color:%s compressed:%s sequence: %u flight:%d usec size:%d data:%d"
|
||||
" subsection:%d sectionLength:%d uncompressed:%d\n",
|
||||
debug::valueOf(packetIsColored), debug::valueOf(packetIsCompressed),
|
||||
sequence, flightTime, numBytes, dataBytes, subsection, sectionLength, packetData.getUncompressedSize());
|
||||
}
|
||||
_tree->readBitstreamToTree(packetData.getUncompressedData(), packetData.getUncompressedSize(), args);
|
||||
unlockTree();
|
||||
|
||||
dataBytes -= sectionLength;
|
||||
dataAt += sectionLength;
|
||||
}
|
||||
totalLength += commandLength+1;
|
||||
}
|
||||
subsection++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -762,7 +778,10 @@ void VoxelSystem::checkForCulling() {
|
|||
uint64_t start = usecTimestampNow();
|
||||
uint64_t sinceLastViewCulling = (start - _lastViewCulling) / 1000;
|
||||
|
||||
bool constantCulling = !Menu::getInstance()->isOptionChecked(MenuOption::DisableConstantCulling);
|
||||
// These items used to be menu options, we are not defaulting to and only supporting these modes.
|
||||
bool constantCulling = true;
|
||||
bool performHideOutOfViewLogic = true;
|
||||
bool performRemoveOutOfViewLogic = false;
|
||||
|
||||
// If the view frustum is no longer changing, but has changed, since last time, then remove nodes that are out of view
|
||||
if (constantCulling || (
|
||||
|
@ -773,7 +792,7 @@ void VoxelSystem::checkForCulling() {
|
|||
// When we call removeOutOfView() voxels, we don't actually remove the voxels from the VBOs, but we do remove
|
||||
// them from tree, this makes our tree caclulations faster, but doesn't require us to fully rebuild the VBOs (which
|
||||
// can be expensive).
|
||||
if (!Menu::getInstance()->isOptionChecked(MenuOption::DisableHideOutOfView)) {
|
||||
if (performHideOutOfViewLogic) {
|
||||
|
||||
// track how long its been since we were last moving. If we have recently moved then only use delta frustums, if
|
||||
// it's been a long time since we last moved, then go ahead and do a full frustum cull.
|
||||
|
@ -807,7 +826,7 @@ void VoxelSystem::checkForCulling() {
|
|||
_lastViewCullingElapsed = (endViewCulling - start) / 1000;
|
||||
}
|
||||
|
||||
} else if (Menu::getInstance()->isOptionChecked(MenuOption::RemoveOutOfView)) {
|
||||
} else if (performRemoveOutOfViewLogic) {
|
||||
_lastViewCulling = start;
|
||||
removeOutOfView();
|
||||
uint64_t endViewCulling = usecTimestampNow();
|
||||
|
@ -1931,7 +1950,7 @@ void VoxelSystem::hideOutOfView(bool forceFullFrustum) {
|
|||
// Both these problems are solved by intermittently calling this with forceFullFrustum set
|
||||
// to true. This will essentially clean up the improperly hidden or shown voxels.
|
||||
//
|
||||
bool wantDeltaFrustums = !forceFullFrustum && !Menu::getInstance()->isOptionChecked(MenuOption::UseFullFrustumInHide);
|
||||
bool wantDeltaFrustums = !forceFullFrustum;
|
||||
hideOutOfViewArgs args(this, this->_tree, _culledOnce, widenFrustum, wantDeltaFrustums);
|
||||
|
||||
const bool wantViewFrustumDebugging = false; // change to true for additional debugging
|
||||
|
|
|
@ -27,6 +27,7 @@ VoxelStatsDialog::VoxelStatsDialog(QWidget* parent, NodeToVoxelSceneStats* model
|
|||
|
||||
for (int i = 0; i < MAX_VOXEL_SERVERS; i++) {
|
||||
_voxelServerLables[i] = 0;
|
||||
_extraServerDetails[i] = LESS;
|
||||
}
|
||||
|
||||
for (int i = 0; i < MAX_STATS; i++) {
|
||||
|
@ -40,20 +41,13 @@ VoxelStatsDialog::VoxelStatsDialog(QWidget* parent, NodeToVoxelSceneStats* model
|
|||
this->QDialog::setLayout(_form);
|
||||
|
||||
// Setup stat items
|
||||
_serverVoxels = AddStatItem("Voxels on Servers", GREENISH);
|
||||
_localVoxels = AddStatItem("Local Voxels", YELLOWISH);
|
||||
_localVoxelsMemory = AddStatItem("Voxels Memory", GREYISH);
|
||||
_voxelsRendered = AddStatItem("Voxels Rendered", GREENISH);
|
||||
_sendingMode = AddStatItem("Sending Mode", YELLOWISH);
|
||||
|
||||
/** NOT YET READY
|
||||
VoxelSceneStats temp;
|
||||
for (int i = 0; i < VoxelSceneStats::ITEM_COUNT; i++) {
|
||||
VoxelSceneStats::Item item = (VoxelSceneStats::Item)(i);
|
||||
VoxelSceneStats::ItemInfo& itemInfo = temp.getItemInfo(item);
|
||||
AddStatItem(itemInfo.caption, itemInfo.colorRGBA);
|
||||
}
|
||||
**/
|
||||
_serverVoxels = AddStatItem("Voxels on Servers");
|
||||
_localVoxels = AddStatItem("Local Voxels");
|
||||
_localVoxelsMemory = AddStatItem("Voxels Memory");
|
||||
_voxelsRendered = AddStatItem("Voxels Rendered");
|
||||
_sendingMode = AddStatItem("Sending Mode");
|
||||
|
||||
layout()->setSizeConstraint(QLayout::SetFixedSize);
|
||||
}
|
||||
|
||||
void VoxelStatsDialog::RemoveStatItem(int item) {
|
||||
|
@ -66,12 +60,34 @@ void VoxelStatsDialog::RemoveStatItem(int item) {
|
|||
_labels[item] = NULL;
|
||||
}
|
||||
|
||||
void VoxelStatsDialog::moreless(const QString& link) {
|
||||
QStringList linkDetails = link.split("-");
|
||||
const int COMMAND_ITEM = 0;
|
||||
const int SERVER_NUMBER_ITEM = 1;
|
||||
QString serverNumberString = linkDetails[SERVER_NUMBER_ITEM];
|
||||
QString command = linkDetails[COMMAND_ITEM];
|
||||
int serverNumber = serverNumberString.toInt();
|
||||
|
||||
if (command == "more") {
|
||||
_extraServerDetails[serverNumber-1] = MORE;
|
||||
} else if (command == "most") {
|
||||
_extraServerDetails[serverNumber-1] = MOST;
|
||||
} else {
|
||||
_extraServerDetails[serverNumber-1] = LESS;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int VoxelStatsDialog::AddStatItem(const char* caption, unsigned colorRGBA) {
|
||||
char strBuf[64];
|
||||
const int STATS_LABEL_WIDTH = 900;
|
||||
const int STATS_LABEL_WIDTH = 600;
|
||||
|
||||
_statCount++; // increment our current stat count
|
||||
|
||||
|
||||
if (colorRGBA == 0) {
|
||||
static unsigned rotatingColors[] = { GREENISH, YELLOWISH, GREYISH };
|
||||
colorRGBA = rotatingColors[_statCount % (sizeof(rotatingColors)/sizeof(rotatingColors[0]))];
|
||||
}
|
||||
QLabel* label = _labels[_statCount] = new QLabel();
|
||||
|
||||
// Set foreground color to 62.5% brightness of the meter (otherwise will be hard to read on the bright background)
|
||||
|
@ -84,11 +100,9 @@ int VoxelStatsDialog::AddStatItem(const char* caption, unsigned colorRGBA) {
|
|||
rgb = ((rgb & colorpart1) >> 1) + ((rgb & colorpart2) >> 3);
|
||||
palette.setColor(QPalette::WindowText, QColor::fromRgb(rgb));
|
||||
label->setPalette(palette);
|
||||
|
||||
label->setFixedWidth(STATS_LABEL_WIDTH);
|
||||
|
||||
snprintf(strBuf, sizeof(strBuf), " %s:", caption);
|
||||
_form->addRow(strBuf, label);
|
||||
label->setFixedWidth(STATS_LABEL_WIDTH);
|
||||
|
||||
return _statCount;
|
||||
}
|
||||
|
@ -204,10 +218,11 @@ void VoxelStatsDialog::paintEvent(QPaintEvent* event) {
|
|||
showAllVoxelServers();
|
||||
|
||||
this->QDialog::paintEvent(event);
|
||||
//this->setFixedSize(this->width(), this->height());
|
||||
}
|
||||
|
||||
void VoxelStatsDialog::showAllVoxelServers() {
|
||||
QLocale locale(QLocale::English);
|
||||
|
||||
int serverNumber = 0;
|
||||
int serverCount = 0;
|
||||
NodeList* nodeList = NodeList::getInstance();
|
||||
|
@ -220,11 +235,16 @@ void VoxelStatsDialog::showAllVoxelServers() {
|
|||
if (serverCount > _voxelServerLabelsCount) {
|
||||
char label[128] = { 0 };
|
||||
sprintf(label, "Voxel Server %d",serverCount);
|
||||
_voxelServerLables[serverCount-1] = AddStatItem(label, GREENISH);
|
||||
int thisServerRow = _voxelServerLables[serverCount-1] = AddStatItem(label);
|
||||
_labels[thisServerRow]->setTextFormat(Qt::RichText);
|
||||
_labels[thisServerRow]->setTextInteractionFlags(Qt::TextBrowserInteraction);
|
||||
connect(_labels[thisServerRow], SIGNAL(linkActivated(const QString&)), this, SLOT(moreless(const QString&)));
|
||||
_voxelServerLabelsCount++;
|
||||
}
|
||||
|
||||
std::stringstream serverDetails("");
|
||||
std::stringstream extraDetails("");
|
||||
std::stringstream linkDetails("");
|
||||
|
||||
if (nodeList->getNodeActiveSocketOrPing(&(*node))) {
|
||||
serverDetails << "active ";
|
||||
|
@ -233,7 +253,6 @@ void VoxelStatsDialog::showAllVoxelServers() {
|
|||
}
|
||||
|
||||
QUuid nodeUUID = node->getUUID();
|
||||
serverDetails << " " << nodeUUID.toString().toLocal8Bit().constData() << " ";
|
||||
|
||||
NodeToJurisdictionMap& voxelServerJurisdictions = Application::getInstance()->getVoxelServerJurisdictions();
|
||||
|
||||
|
@ -265,8 +284,88 @@ void VoxelStatsDialog::showAllVoxelServers() {
|
|||
} // root code
|
||||
} // jurisdiction
|
||||
|
||||
// now lookup stats details for this server...
|
||||
if (_extraServerDetails[serverNumber-1] != LESS) {
|
||||
Application::getInstance()->lockVoxelSceneStats();
|
||||
NodeToVoxelSceneStats* sceneStats = Application::getInstance()->getVoxelSceneStats();
|
||||
if (sceneStats->find(nodeUUID) != sceneStats->end()) {
|
||||
VoxelSceneStats& stats = sceneStats->at(nodeUUID);
|
||||
|
||||
switch (_extraServerDetails[serverNumber-1]) {
|
||||
case MOST: {
|
||||
extraDetails << "<br/>" ;
|
||||
|
||||
const unsigned long USECS_PER_MSEC = 1000;
|
||||
float lastFullEncode = stats.getLastFullTotalEncodeTime() / USECS_PER_MSEC;
|
||||
float lastFullSend = stats.getLastFullElapsedTime() / USECS_PER_MSEC;
|
||||
|
||||
QString lastFullEncodeString = locale.toString(lastFullEncode);
|
||||
QString lastFullSendString = locale.toString(lastFullSend);
|
||||
|
||||
extraDetails << "<br/>" << "Last Full Scene... " <<
|
||||
"Encode Time: " << lastFullEncodeString.toLocal8Bit().constData() << " ms " <<
|
||||
"Send Time: " << lastFullSendString.toLocal8Bit().constData() << " ms ";
|
||||
|
||||
for (int i = 0; i < VoxelSceneStats::ITEM_COUNT; i++) {
|
||||
VoxelSceneStats::Item item = (VoxelSceneStats::Item)(i);
|
||||
VoxelSceneStats::ItemInfo& itemInfo = stats.getItemInfo(item);
|
||||
extraDetails << "<br/>" << itemInfo.caption << " " << stats.getItemValue(item);
|
||||
}
|
||||
} // fall through... since MOST has all of MORE
|
||||
case MORE: {
|
||||
QString totalString = locale.toString((uint)stats.getTotalVoxels());
|
||||
QString internalString = locale.toString((uint)stats.getTotalInternal());
|
||||
QString leavesString = locale.toString((uint)stats.getTotalLeaves());
|
||||
|
||||
serverDetails << "<br/>" << "Node UUID: " <<
|
||||
nodeUUID.toString().toLocal8Bit().constData() << " ";
|
||||
|
||||
serverDetails << "<br/>" << "Voxels: " <<
|
||||
totalString.toLocal8Bit().constData() << " total " <<
|
||||
internalString.toLocal8Bit().constData() << " internal " <<
|
||||
leavesString.toLocal8Bit().constData() << " leaves ";
|
||||
|
||||
QString incomingPacketsString = locale.toString((uint)stats.getIncomingPackets());
|
||||
QString incomingBytesString = locale.toString((uint)stats.getIncomingBytes());
|
||||
QString incomingWastedBytesString = locale.toString((uint)stats.getIncomingWastedBytes());
|
||||
QString incomingOutOfOrderString = locale.toString((uint)stats.getIncomingOutOfOrder());
|
||||
QString incomingLikelyLostString = locale.toString((uint)stats.getIncomingLikelyLost());
|
||||
QString incomingFlightTimeString = locale.toString(stats.getIncomingFlightTimeAverage());
|
||||
|
||||
serverDetails << "<br/>" << "Incoming Packets: " <<
|
||||
incomingPacketsString.toLocal8Bit().constData() <<
|
||||
" Out of Order: " << incomingOutOfOrderString.toLocal8Bit().constData() <<
|
||||
" Likely Lost: " << incomingLikelyLostString.toLocal8Bit().constData();
|
||||
|
||||
serverDetails << "<br/>" <<
|
||||
" Average Flight Time: " << incomingFlightTimeString.toLocal8Bit().constData() << " msecs";
|
||||
|
||||
serverDetails << "<br/>" << "Incoming" <<
|
||||
" Bytes: " << incomingBytesString.toLocal8Bit().constData() <<
|
||||
" Wasted Bytes: " << incomingWastedBytesString.toLocal8Bit().constData();
|
||||
|
||||
serverDetails << extraDetails.str();
|
||||
if (_extraServerDetails[serverNumber-1] == MORE) {
|
||||
linkDetails << " " << " [<a href='most-" << serverNumber << "'>most...</a>]";
|
||||
linkDetails << " " << " [<a href='less-" << serverNumber << "'>less...</a>]";
|
||||
} else {
|
||||
linkDetails << " " << " [<a href='more-" << serverNumber << "'>less...</a>]";
|
||||
linkDetails << " " << " [<a href='less-" << serverNumber << "'>least...</a>]";
|
||||
}
|
||||
|
||||
} break;
|
||||
case LESS: {
|
||||
// nothing
|
||||
} break;
|
||||
}
|
||||
}
|
||||
Application::getInstance()->unlockVoxelSceneStats();
|
||||
} else {
|
||||
linkDetails << " " << " [<a href='more-" << serverNumber << "'>more...</a>]";
|
||||
linkDetails << " " << " [<a href='most-" << serverNumber << "'>most...</a>]";
|
||||
}
|
||||
serverDetails << linkDetails.str();
|
||||
_labels[_voxelServerLables[serverCount - 1]]->setText(serverDetails.str().c_str());
|
||||
|
||||
} // is VOXEL_SERVER
|
||||
} // Node Loop
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
#define MAX_STATS 100
|
||||
#define MAX_VOXEL_SERVERS 50
|
||||
#define DEFAULT_COLOR 0
|
||||
|
||||
class VoxelStatsDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
@ -30,6 +31,7 @@ signals:
|
|||
|
||||
public slots:
|
||||
void reject();
|
||||
void moreless(const QString& link);
|
||||
|
||||
protected:
|
||||
// State <- data model held by BandwidthMeter
|
||||
|
@ -38,11 +40,14 @@ protected:
|
|||
// Emits a 'closed' signal when this dialog is closed.
|
||||
void closeEvent(QCloseEvent*);
|
||||
|
||||
int AddStatItem(const char* caption, unsigned colorRGBA);
|
||||
int AddStatItem(const char* caption, unsigned colorRGBA = DEFAULT_COLOR);
|
||||
void RemoveStatItem(int item);
|
||||
void showAllVoxelServers();
|
||||
|
||||
private:
|
||||
|
||||
typedef enum { LESS, MORE, MOST } details;
|
||||
|
||||
QFormLayout* _form;
|
||||
QLabel* _labels[MAX_STATS];
|
||||
NodeToVoxelSceneStats* _model;
|
||||
|
@ -55,6 +60,7 @@ private:
|
|||
int _voxelsRendered;
|
||||
int _voxelServerLables[MAX_VOXEL_SERVERS];
|
||||
int _voxelServerLabelsCount;
|
||||
details _extraServerDetails[MAX_VOXEL_SERVERS];
|
||||
};
|
||||
|
||||
#endif /* defined(__interface__VoxelStatsDialog__) */
|
||||
|
|
|
@ -37,12 +37,15 @@ PACKET_VERSION versionForPacketType(PACKET_TYPE type) {
|
|||
return 1;
|
||||
|
||||
case PACKET_TYPE_VOXEL_QUERY:
|
||||
return 1;
|
||||
return 2;
|
||||
|
||||
case PACKET_TYPE_SET_VOXEL:
|
||||
case PACKET_TYPE_SET_VOXEL_DESTRUCTIVE:
|
||||
case PACKET_TYPE_ERASE_VOXEL:
|
||||
return 1;
|
||||
|
||||
case PACKET_TYPE_VOXEL_DATA:
|
||||
return 1;
|
||||
|
||||
default:
|
||||
return 0;
|
||||
|
|
|
@ -20,7 +20,6 @@ const PACKET_TYPE PACKET_TYPE_PING = 'P';
|
|||
const PACKET_TYPE PACKET_TYPE_PING_REPLY = 'R';
|
||||
const PACKET_TYPE PACKET_TYPE_KILL_NODE = 'K';
|
||||
const PACKET_TYPE PACKET_TYPE_HEAD_DATA = 'H';
|
||||
const PACKET_TYPE PACKET_TYPE_Z_COMMAND = 'Z';
|
||||
const PACKET_TYPE PACKET_TYPE_INJECT_AUDIO = 'I';
|
||||
const PACKET_TYPE PACKET_TYPE_MIXED_AUDIO = 'A';
|
||||
const PACKET_TYPE PACKET_TYPE_MICROPHONE_AUDIO_NO_ECHO = 'M';
|
||||
|
@ -41,7 +40,6 @@ const PACKET_TYPE PACKET_TYPE_DATA_SERVER_SEND = 'u';
|
|||
const PACKET_TYPE PACKET_TYPE_DATA_SERVER_CONFIRM = 'c';
|
||||
const PACKET_TYPE PACKET_TYPE_VOXEL_QUERY = 'q';
|
||||
const PACKET_TYPE PACKET_TYPE_VOXEL_DATA = 'V';
|
||||
const PACKET_TYPE PACKET_TYPE_VOXEL_DATA_MONOCHROME = 'v';
|
||||
const PACKET_TYPE PACKET_TYPE_VOXEL_STATS = '#';
|
||||
const PACKET_TYPE PACKET_TYPE_VOXEL_JURISDICTION = 'J';
|
||||
const PACKET_TYPE PACKET_TYPE_VOXEL_JURISDICTION_REQUEST = 'j';
|
||||
|
|
|
@ -60,7 +60,7 @@ bool shouldDo(float desiredInterval, float deltaTime) {
|
|||
return randFloat() < deltaTime / desiredInterval;
|
||||
}
|
||||
|
||||
void outputBufferBits(unsigned char* buffer, int length, bool withNewLine) {
|
||||
void outputBufferBits(const unsigned char* buffer, int length, bool withNewLine) {
|
||||
for (int i = 0; i < length; i++) {
|
||||
outputBits(buffer[i], false);
|
||||
}
|
||||
|
@ -69,20 +69,20 @@ void outputBufferBits(unsigned char* buffer, int length, bool withNewLine) {
|
|||
}
|
||||
}
|
||||
|
||||
void outputBits(unsigned char byte, bool withNewLine) {
|
||||
void outputBits(unsigned char byte, bool withNewLine, bool usePrintf) {
|
||||
if (isalnum(byte)) {
|
||||
qDebug("[ %d (%c): ", byte, byte);
|
||||
usePrintf ? (void)printf("[ %d (%c): ", byte, byte) : qDebug("[ %d (%c): ", byte, byte);
|
||||
} else {
|
||||
qDebug("[ %d (0x%x): ", byte, byte);
|
||||
usePrintf ? (void)printf("[ %d (0x%x): ", byte, byte) : qDebug("[ %d (0x%x): ", byte, byte);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
qDebug("%d", byte >> (7 - i) & 1);
|
||||
usePrintf ? (void)printf("%d", byte >> (7 - i) & 1) : qDebug("%d", byte >> (7 - i) & 1);
|
||||
}
|
||||
qDebug(" ] ");
|
||||
usePrintf ? (void)printf(" ] ") : qDebug(" ] ");
|
||||
|
||||
if (withNewLine) {
|
||||
qDebug("\n");
|
||||
usePrintf ? (void)printf("\n") : qDebug("\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -52,8 +52,8 @@ bool randomBoolean();
|
|||
|
||||
bool shouldDo(float desiredInterval, float deltaTime);
|
||||
|
||||
void outputBufferBits(unsigned char* buffer, int length, bool withNewLine = true);
|
||||
void outputBits(unsigned char byte, bool withNewLine = true);
|
||||
void outputBufferBits(const unsigned char* buffer, int length, bool withNewLine = true);
|
||||
void outputBits(unsigned char byte, bool withNewLine = true, bool usePrintf = false);
|
||||
void printVoxelCode(unsigned char* voxelCode);
|
||||
int numberOfOnes(unsigned char byte);
|
||||
bool oneAtBit(unsigned char byte, int bitIndex);
|
||||
|
|
|
@ -16,25 +16,27 @@
|
|||
VoxelNodeData::VoxelNodeData(Node* owningNode) :
|
||||
VoxelQuery(owningNode),
|
||||
_viewSent(false),
|
||||
_voxelPacketAvailableBytes(MAX_VOXEL_PACKET_SIZE),
|
||||
_voxelPacketAvailableBytes(MAX_PACKET_SIZE),
|
||||
_maxSearchLevel(1),
|
||||
_maxLevelReachedInLastSearch(1),
|
||||
_lastTimeBagEmpty(0),
|
||||
_viewFrustumChanging(false),
|
||||
_viewFrustumJustStoppedChanging(true),
|
||||
_currentPacketIsColor(true),
|
||||
_currentPacketIsCompressed(false),
|
||||
_voxelSendThread(NULL),
|
||||
_lastClientBoundaryLevelAdjust(0),
|
||||
_lastClientVoxelSizeScale(DEFAULT_VOXEL_SIZE_SCALE),
|
||||
_lodChanged(false),
|
||||
_lodInitialized(false)
|
||||
{
|
||||
_voxelPacket = new unsigned char[MAX_VOXEL_PACKET_SIZE];
|
||||
_voxelPacket = new unsigned char[MAX_PACKET_SIZE];
|
||||
_voxelPacketAt = _voxelPacket;
|
||||
_lastVoxelPacket = new unsigned char[MAX_VOXEL_PACKET_SIZE];
|
||||
_lastVoxelPacket = new unsigned char[MAX_PACKET_SIZE];
|
||||
_lastVoxelPacketLength = 0;
|
||||
_duplicatePacketCount = 0;
|
||||
resetVoxelPacket();
|
||||
_sequenceNumber = 0;
|
||||
resetVoxelPacket(true); // don't bump sequence
|
||||
}
|
||||
|
||||
void VoxelNodeData::initializeVoxelSendThread(VoxelServer* voxelServer) {
|
||||
|
@ -45,8 +47,11 @@ void VoxelNodeData::initializeVoxelSendThread(VoxelServer* voxelServer) {
|
|||
}
|
||||
|
||||
bool VoxelNodeData::packetIsDuplicate() const {
|
||||
// since our packets now include header information, like sequence number, and createTime, we can't just do a memcmp
|
||||
// of the entire packet, we need to compare only the packet content...
|
||||
if (_lastVoxelPacketLength == getPacketLength()) {
|
||||
return memcmp(_lastVoxelPacket, _voxelPacket, getPacketLength()) == 0;
|
||||
return memcmp(_lastVoxelPacket + VOXEL_PACKET_HEADER_SIZE,
|
||||
_voxelPacket+VOXEL_PACKET_HEADER_SIZE , getPacketLength() - VOXEL_PACKET_HEADER_SIZE) == 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -71,7 +76,7 @@ bool VoxelNodeData::shouldSuppressDuplicatePacket() {
|
|||
|
||||
if (sinceFirstSuppressedPacket < MAX_TIME_BETWEEN_DUPLICATE_PACKETS) {
|
||||
// Finally, if we know we've sent at least one duplicate out, then suppress the rest...
|
||||
if (_duplicatePacketCount > 1) {
|
||||
if (_duplicatePacketCount >= 1) {
|
||||
shouldSuppress = true;
|
||||
}
|
||||
} else {
|
||||
|
@ -85,7 +90,7 @@ bool VoxelNodeData::shouldSuppressDuplicatePacket() {
|
|||
return shouldSuppress;
|
||||
}
|
||||
|
||||
void VoxelNodeData::resetVoxelPacket() {
|
||||
void VoxelNodeData::resetVoxelPacket(bool lastWasSurpressed) {
|
||||
// Whenever we call this, we will keep a copy of the last packet, so we can determine if the last packet has
|
||||
// changed since we last reset it. Since we know that no two packets can ever be identical without being the same
|
||||
// scene information, (e.g. the root node packet of a static scene), we can use this as a strategy for reducing
|
||||
|
@ -95,20 +100,60 @@ void VoxelNodeData::resetVoxelPacket() {
|
|||
|
||||
// If we're moving, and the client asked for low res, then we force monochrome, otherwise, use
|
||||
// the clients requested color state.
|
||||
_currentPacketIsColor = (LOW_RES_MONO && getWantLowResMoving() && _viewFrustumChanging) ? false : getWantColor();
|
||||
PACKET_TYPE voxelPacketType = _currentPacketIsColor ? PACKET_TYPE_VOXEL_DATA : PACKET_TYPE_VOXEL_DATA_MONOCHROME;
|
||||
_currentPacketIsColor = getWantColor();
|
||||
_currentPacketIsCompressed = getWantCompression();
|
||||
VOXEL_PACKET_FLAGS flags = 0;
|
||||
if (_currentPacketIsColor) {
|
||||
setAtBit(flags,PACKET_IS_COLOR_BIT);
|
||||
}
|
||||
if (_currentPacketIsCompressed) {
|
||||
setAtBit(flags,PACKET_IS_COMPRESSED_BIT);
|
||||
}
|
||||
|
||||
int numBytesPacketHeader = populateTypeAndVersion(_voxelPacket, voxelPacketType);
|
||||
_voxelPacketAvailableBytes = MAX_PACKET_SIZE;
|
||||
int numBytesPacketHeader = populateTypeAndVersion(_voxelPacket, PACKET_TYPE_VOXEL_DATA);
|
||||
_voxelPacketAt = _voxelPacket + numBytesPacketHeader;
|
||||
_voxelPacketAvailableBytes = MAX_VOXEL_PACKET_SIZE - numBytesPacketHeader;
|
||||
_voxelPacketAvailableBytes -= numBytesPacketHeader;
|
||||
|
||||
// pack in flags
|
||||
VOXEL_PACKET_FLAGS* flagsAt = (VOXEL_PACKET_FLAGS*)_voxelPacketAt;
|
||||
*flagsAt = flags;
|
||||
_voxelPacketAt += sizeof(VOXEL_PACKET_FLAGS);
|
||||
_voxelPacketAvailableBytes -= sizeof(VOXEL_PACKET_FLAGS);
|
||||
|
||||
// pack in sequence number
|
||||
VOXEL_PACKET_SEQUENCE* sequenceAt = (VOXEL_PACKET_SEQUENCE*)_voxelPacketAt;
|
||||
*sequenceAt = _sequenceNumber;
|
||||
_voxelPacketAt += sizeof(VOXEL_PACKET_SEQUENCE);
|
||||
_voxelPacketAvailableBytes -= sizeof(VOXEL_PACKET_SEQUENCE);
|
||||
if (!(lastWasSurpressed || _lastVoxelPacketLength == VOXEL_PACKET_HEADER_SIZE)) {
|
||||
_sequenceNumber++;
|
||||
}
|
||||
|
||||
// pack in timestamp
|
||||
VOXEL_PACKET_SENT_TIME now = usecTimestampNow();
|
||||
VOXEL_PACKET_SENT_TIME* timeAt = (VOXEL_PACKET_SENT_TIME*)_voxelPacketAt;
|
||||
*timeAt = now;
|
||||
_voxelPacketAt += sizeof(VOXEL_PACKET_SENT_TIME);
|
||||
_voxelPacketAvailableBytes -= sizeof(VOXEL_PACKET_SENT_TIME);
|
||||
|
||||
_voxelPacketWaiting = false;
|
||||
}
|
||||
|
||||
void VoxelNodeData::writeToPacket(unsigned char* buffer, int bytes) {
|
||||
memcpy(_voxelPacketAt, buffer, bytes);
|
||||
_voxelPacketAvailableBytes -= bytes;
|
||||
_voxelPacketAt += bytes;
|
||||
_voxelPacketWaiting = true;
|
||||
void VoxelNodeData::writeToPacket(const unsigned char* buffer, int bytes) {
|
||||
// compressed packets include lead bytes which contain compressed size, this allows packing of
|
||||
// multiple compressed portions together
|
||||
if (_currentPacketIsCompressed) {
|
||||
*(VOXEL_PACKET_INTERNAL_SECTION_SIZE*)_voxelPacketAt = bytes;
|
||||
_voxelPacketAt += sizeof(VOXEL_PACKET_INTERNAL_SECTION_SIZE);
|
||||
_voxelPacketAvailableBytes -= sizeof(VOXEL_PACKET_INTERNAL_SECTION_SIZE);
|
||||
}
|
||||
if (bytes <= _voxelPacketAvailableBytes) {
|
||||
memcpy(_voxelPacketAt, buffer, bytes);
|
||||
_voxelPacketAvailableBytes -= bytes;
|
||||
_voxelPacketAt += bytes;
|
||||
_voxelPacketWaiting = true;
|
||||
}
|
||||
}
|
||||
|
||||
VoxelNodeData::~VoxelNodeData() {
|
||||
|
@ -180,7 +225,6 @@ void VoxelNodeData::setViewSent(bool viewSent) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void VoxelNodeData::updateLastKnownViewFrustum() {
|
||||
bool frustumChanges = !_lastKnownViewFrustum.isVerySimilar(_currentViewFrustum);
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include <iostream>
|
||||
#include <NodeData.h>
|
||||
#include <VoxelPacketData.h>
|
||||
#include <VoxelQuery.h>
|
||||
|
||||
#include <CoverageMap.h>
|
||||
|
@ -26,48 +27,52 @@ public:
|
|||
VoxelNodeData(Node* owningNode);
|
||||
virtual ~VoxelNodeData();
|
||||
|
||||
void resetVoxelPacket(); // resets voxel packet to after "V" header
|
||||
void resetVoxelPacket(bool lastWasSurpressed = false); // resets voxel packet to after "V" header
|
||||
|
||||
void writeToPacket(unsigned char* buffer, int bytes); // writes to end of packet
|
||||
void writeToPacket(const unsigned char* buffer, int bytes); // writes to end of packet
|
||||
|
||||
const unsigned char* getPacket() const { return _voxelPacket; }
|
||||
int getPacketLength() const { return (MAX_VOXEL_PACKET_SIZE - _voxelPacketAvailableBytes); }
|
||||
int getPacketLength() const { return (MAX_PACKET_SIZE - _voxelPacketAvailableBytes); }
|
||||
bool isPacketWaiting() const { return _voxelPacketWaiting; }
|
||||
|
||||
bool packetIsDuplicate() const;
|
||||
bool shouldSuppressDuplicatePacket();
|
||||
|
||||
int getAvailable() const { return _voxelPacketAvailableBytes; }
|
||||
int getMaxSearchLevel() const { return _maxSearchLevel; };
|
||||
void resetMaxSearchLevel() { _maxSearchLevel = 1; };
|
||||
void incrementMaxSearchLevel() { _maxSearchLevel++; };
|
||||
int getMaxSearchLevel() const { return _maxSearchLevel; }
|
||||
void resetMaxSearchLevel() { _maxSearchLevel = 1; }
|
||||
void incrementMaxSearchLevel() { _maxSearchLevel++; }
|
||||
|
||||
int getMaxLevelReached() const { return _maxLevelReachedInLastSearch; };
|
||||
int getMaxLevelReached() const { return _maxLevelReachedInLastSearch; }
|
||||
void setMaxLevelReached(int maxLevelReached) { _maxLevelReachedInLastSearch = maxLevelReached; }
|
||||
|
||||
VoxelNodeBag nodeBag;
|
||||
CoverageMap map;
|
||||
|
||||
ViewFrustum& getCurrentViewFrustum() { return _currentViewFrustum; };
|
||||
ViewFrustum& getLastKnownViewFrustum() { return _lastKnownViewFrustum; };
|
||||
ViewFrustum& getCurrentViewFrustum() { return _currentViewFrustum; }
|
||||
ViewFrustum& getLastKnownViewFrustum() { return _lastKnownViewFrustum; }
|
||||
|
||||
// These are not classic setters because they are calculating and maintaining state
|
||||
// which is set asynchronously through the network receive
|
||||
bool updateCurrentViewFrustum();
|
||||
void updateLastKnownViewFrustum();
|
||||
|
||||
bool getViewSent() const { return _viewSent; };
|
||||
bool getViewSent() const { return _viewSent; }
|
||||
void setViewSent(bool viewSent);
|
||||
|
||||
bool getViewFrustumChanging() const { return _viewFrustumChanging; };
|
||||
bool getViewFrustumJustStoppedChanging() const { return _viewFrustumJustStoppedChanging; };
|
||||
bool getViewFrustumChanging() const { return _viewFrustumChanging; }
|
||||
bool getViewFrustumJustStoppedChanging() const { return _viewFrustumJustStoppedChanging; }
|
||||
|
||||
bool moveShouldDump() const;
|
||||
|
||||
uint64_t getLastTimeBagEmpty() const { return _lastTimeBagEmpty; };
|
||||
void setLastTimeBagEmpty(uint64_t lastTimeBagEmpty) { _lastTimeBagEmpty = lastTimeBagEmpty; };
|
||||
uint64_t getLastTimeBagEmpty() const { return _lastTimeBagEmpty; }
|
||||
void setLastTimeBagEmpty(uint64_t lastTimeBagEmpty) { _lastTimeBagEmpty = lastTimeBagEmpty; }
|
||||
|
||||
bool getCurrentPacketIsColor() const { return _currentPacketIsColor; };
|
||||
bool getCurrentPacketIsColor() const { return _currentPacketIsColor; }
|
||||
bool getCurrentPacketIsCompressed() const { return _currentPacketIsCompressed; }
|
||||
bool getCurrentPacketFormatMatches() {
|
||||
return (getCurrentPacketIsColor() == getWantColor() && getCurrentPacketIsCompressed() == getWantCompression());
|
||||
}
|
||||
|
||||
bool hasLodChanged() const { return _lodChanged; };
|
||||
|
||||
|
@ -101,6 +106,7 @@ private:
|
|||
bool _viewFrustumChanging;
|
||||
bool _viewFrustumJustStoppedChanging;
|
||||
bool _currentPacketIsColor;
|
||||
bool _currentPacketIsCompressed;
|
||||
|
||||
VoxelSendThread* _voxelSendThread;
|
||||
|
||||
|
@ -109,6 +115,8 @@ private:
|
|||
float _lastClientVoxelSizeScale;
|
||||
bool _lodChanged;
|
||||
bool _lodInitialized;
|
||||
|
||||
VOXEL_PACKET_SEQUENCE _sequenceNumber;
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__VoxelNodeData__) */
|
||||
|
|
|
@ -8,19 +8,29 @@
|
|||
// Threaded or non-threaded voxel packet sender
|
||||
//
|
||||
|
||||
#include <NodeList.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include <EnvironmentData.h>
|
||||
#include <NodeList.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include <PerfStat.h>
|
||||
#include <SharedUtil.h>
|
||||
|
||||
extern EnvironmentData environmentData[3];
|
||||
|
||||
|
||||
#include "VoxelSendThread.h"
|
||||
#include "VoxelServer.h"
|
||||
#include "VoxelServerConsts.h"
|
||||
|
||||
|
||||
uint64_t startSceneSleepTime = 0;
|
||||
uint64_t endSceneSleepTime = 0;
|
||||
|
||||
|
||||
VoxelSendThread::VoxelSendThread(const QUuid& nodeUUID, VoxelServer* myServer) :
|
||||
_nodeUUID(nodeUUID),
|
||||
_myServer(myServer) {
|
||||
_myServer(myServer),
|
||||
_packetData()
|
||||
{
|
||||
}
|
||||
|
||||
bool VoxelSendThread::process() {
|
||||
|
@ -44,7 +54,7 @@ bool VoxelSendThread::process() {
|
|||
// Sometimes the node data has not yet been linked, in which case we can't really do anything
|
||||
if (nodeData) {
|
||||
bool viewFrustumChanged = nodeData->updateCurrentViewFrustum();
|
||||
if (_myServer->wantsDebugVoxelSending()) {
|
||||
if (_myServer->wantsDebugVoxelSending() && _myServer->wantsVerboseDebug()) {
|
||||
printf("nodeData->updateCurrentViewFrustum() changed=%s\n", debug::valueOf(viewFrustumChanged));
|
||||
}
|
||||
packetsSent = deepestLevelVoxelDistributor(node, nodeData, viewFrustumChanged);
|
||||
|
@ -54,7 +64,7 @@ bool VoxelSendThread::process() {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
if (_myServer->wantsDebugVoxelSending()) {
|
||||
if (_myServer->wantsDebugVoxelSending() && _myServer->wantsVerboseDebug()) {
|
||||
qDebug("VoxelSendThread::process() waiting for isInitialLoadComplete()\n");
|
||||
}
|
||||
}
|
||||
|
@ -66,9 +76,10 @@ bool VoxelSendThread::process() {
|
|||
int usecToSleep = VOXEL_SEND_INTERVAL_USECS - elapsed;
|
||||
|
||||
if (usecToSleep > 0) {
|
||||
PerformanceWarning warn(false,"VoxelSendThread... usleep()",false,&_usleepTime,&_usleepCalls);
|
||||
usleep(usecToSleep);
|
||||
} else {
|
||||
if (_myServer->wantsDebugVoxelSending()) {
|
||||
if (_myServer->wantsDebugVoxelSending() && _myServer->wantsVerboseDebug()) {
|
||||
std::cout << "Last send took too much time, not sleeping!\n";
|
||||
}
|
||||
}
|
||||
|
@ -77,17 +88,34 @@ bool VoxelSendThread::process() {
|
|||
return isStillRunning(); // keep running till they terminate us
|
||||
}
|
||||
|
||||
uint64_t VoxelSendThread::_usleepTime = 0;
|
||||
uint64_t VoxelSendThread::_usleepCalls = 0;
|
||||
|
||||
uint64_t VoxelSendThread::_totalBytes = 0;
|
||||
uint64_t VoxelSendThread::_totalWastedBytes = 0;
|
||||
uint64_t VoxelSendThread::_totalPackets = 0;
|
||||
|
||||
int VoxelSendThread::handlePacketSend(Node* node, VoxelNodeData* nodeData, int& trueBytesSent, int& truePacketsSent) {
|
||||
bool debug = _myServer->wantsDebugVoxelSending();
|
||||
uint64_t now = usecTimestampNow();
|
||||
|
||||
bool packetSent = false; // did we send a packet?
|
||||
int packetsSent = 0;
|
||||
// Here's where we check to see if this packet is a duplicate of the last packet. If it is, we will silently
|
||||
// obscure the packet and not send it. This allows the callers and upper level logic to not need to know about
|
||||
// this rate control savings.
|
||||
if (nodeData->shouldSuppressDuplicatePacket()) {
|
||||
nodeData->resetVoxelPacket(); // we still need to reset it though!
|
||||
nodeData->resetVoxelPacket(true); // we still need to reset it though!
|
||||
return packetsSent; // without sending...
|
||||
}
|
||||
|
||||
const unsigned char* messageData = nodeData->getPacket();
|
||||
int numBytesPacketHeader = numBytesForPacketHeader(messageData);
|
||||
const unsigned char* dataAt = messageData + numBytesPacketHeader;
|
||||
dataAt += sizeof(VOXEL_PACKET_FLAGS);
|
||||
VOXEL_PACKET_SEQUENCE sequence = (*(VOXEL_PACKET_SEQUENCE*)dataAt);
|
||||
dataAt += sizeof(VOXEL_PACKET_SEQUENCE);
|
||||
|
||||
|
||||
// If we've got a stats message ready to send, then see if we can piggyback them together
|
||||
if (nodeData->stats.isReadyToSend()) {
|
||||
|
@ -102,30 +130,93 @@ int VoxelSendThread::handlePacketSend(Node* node, VoxelNodeData* nodeData, int&
|
|||
memcpy(statsMessage + statsMessageLength, nodeData->getPacket(), nodeData->getPacketLength());
|
||||
statsMessageLength += nodeData->getPacketLength();
|
||||
|
||||
// since a stats message is only included on end of scene, don't consider any of these bytes "wasted", since
|
||||
// there was nothing else to send.
|
||||
int thisWastedBytes = 0;
|
||||
_totalWastedBytes += thisWastedBytes;
|
||||
_totalBytes += nodeData->getPacketLength();
|
||||
_totalPackets++;
|
||||
if (debug) {
|
||||
qDebug("Adding stats to packet at %llu [%llu]: sequence: %d size:%d [%llu] wasted bytes:%d [%llu]\n",
|
||||
now,
|
||||
_totalPackets,
|
||||
sequence, nodeData->getPacketLength(), _totalBytes,
|
||||
thisWastedBytes, _totalWastedBytes);
|
||||
}
|
||||
|
||||
// actually send it
|
||||
NodeList::getInstance()->getNodeSocket()->send(node->getActiveSocket(), statsMessage, statsMessageLength);
|
||||
packetSent = true;
|
||||
} else {
|
||||
// not enough room in the packet, send two packets
|
||||
NodeList::getInstance()->getNodeSocket()->send(node->getActiveSocket(), statsMessage, statsMessageLength);
|
||||
|
||||
// since a stats message is only included on end of scene, don't consider any of these bytes "wasted", since
|
||||
// there was nothing else to send.
|
||||
int thisWastedBytes = 0;
|
||||
_totalWastedBytes += thisWastedBytes;
|
||||
_totalBytes += statsMessageLength;
|
||||
_totalPackets++;
|
||||
if (debug) {
|
||||
qDebug("Sending separate stats packet at %llu [%llu]: size:%d [%llu] wasted bytes:%d [%llu]\n",
|
||||
now,
|
||||
_totalPackets,
|
||||
statsMessageLength, _totalBytes,
|
||||
thisWastedBytes, _totalWastedBytes);
|
||||
}
|
||||
|
||||
trueBytesSent += statsMessageLength;
|
||||
truePacketsSent++;
|
||||
packetsSent++;
|
||||
|
||||
NodeList::getInstance()->getNodeSocket()->send(node->getActiveSocket(),
|
||||
nodeData->getPacket(), nodeData->getPacketLength());
|
||||
|
||||
packetSent = true;
|
||||
|
||||
thisWastedBytes = MAX_PACKET_SIZE - nodeData->getPacketLength();
|
||||
_totalWastedBytes += thisWastedBytes;
|
||||
_totalBytes += nodeData->getPacketLength();
|
||||
_totalPackets++;
|
||||
if (debug) {
|
||||
qDebug("Sending packet at %llu [%llu]: sequence: %d size:%d [%llu] wasted bytes:%d [%llu]\n",
|
||||
now,
|
||||
_totalPackets,
|
||||
sequence, nodeData->getPacketLength(), _totalBytes,
|
||||
thisWastedBytes, _totalWastedBytes);
|
||||
}
|
||||
}
|
||||
nodeData->stats.markAsSent();
|
||||
} else {
|
||||
// just send the voxel packet
|
||||
NodeList::getInstance()->getNodeSocket()->send(node->getActiveSocket(),
|
||||
nodeData->getPacket(), nodeData->getPacketLength());
|
||||
// If there's actually a packet waiting, then send it.
|
||||
if (nodeData->isPacketWaiting()) {
|
||||
// just send the voxel packet
|
||||
NodeList::getInstance()->getNodeSocket()->send(node->getActiveSocket(),
|
||||
nodeData->getPacket(), nodeData->getPacketLength());
|
||||
packetSent = true;
|
||||
|
||||
int thisWastedBytes = MAX_PACKET_SIZE - nodeData->getPacketLength();
|
||||
_totalWastedBytes += thisWastedBytes;
|
||||
_totalBytes += nodeData->getPacketLength();
|
||||
_totalPackets++;
|
||||
if (debug) {
|
||||
qDebug("Sending packet at %llu [%llu]: sequence:%d size:%d [%llu] wasted bytes:%d [%llu]\n",
|
||||
now,
|
||||
_totalPackets,
|
||||
sequence, nodeData->getPacketLength(), _totalBytes,
|
||||
thisWastedBytes, _totalWastedBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
// remember to track our stats
|
||||
nodeData->stats.packetSent(nodeData->getPacketLength());
|
||||
trueBytesSent += nodeData->getPacketLength();
|
||||
truePacketsSent++;
|
||||
packetsSent++;
|
||||
nodeData->resetVoxelPacket();
|
||||
if (packetSent) {
|
||||
nodeData->stats.packetSent(nodeData->getPacketLength());
|
||||
trueBytesSent += nodeData->getPacketLength();
|
||||
truePacketsSent++;
|
||||
packetsSent++;
|
||||
nodeData->resetVoxelPacket();
|
||||
}
|
||||
|
||||
return packetsSent;
|
||||
}
|
||||
|
||||
|
@ -137,7 +228,6 @@ int VoxelSendThread::deepestLevelVoxelDistributor(Node* node, VoxelNodeData* nod
|
|||
int packetsSentThisInterval = 0;
|
||||
bool somethingToSend = true; // assume we have something
|
||||
|
||||
|
||||
// FOR NOW... node tells us if it wants to receive only view frustum deltas
|
||||
bool wantDelta = viewFrustumChanged && nodeData->getWantDelta();
|
||||
|
||||
|
@ -145,37 +235,51 @@ int VoxelSendThread::deepestLevelVoxelDistributor(Node* node, VoxelNodeData* nod
|
|||
// If we're starting a fresh packet, then...
|
||||
// If we're moving, and the client asked for low res, then we force monochrome, otherwise, use
|
||||
// the clients requested color state.
|
||||
bool wantColor = LOW_RES_MONO && nodeData->getWantLowResMoving() && viewFrustumChanged ? false : nodeData->getWantColor();
|
||||
bool wantColor = nodeData->getWantColor();
|
||||
bool wantCompression = nodeData->getWantCompression();
|
||||
|
||||
// If we have a packet waiting, and our desired want color, doesn't match the current waiting packets color
|
||||
// then let's just send that waiting packet.
|
||||
if (wantColor != nodeData->getCurrentPacketIsColor()) {
|
||||
|
||||
if (!nodeData->getCurrentPacketFormatMatches()) {
|
||||
if (nodeData->isPacketWaiting()) {
|
||||
if (_myServer->wantsDebugVoxelSending()) {
|
||||
printf("wantColor=%s --- SENDING PARTIAL PACKET! nodeData->getCurrentPacketIsColor()=%s\n",
|
||||
debug::valueOf(wantColor), debug::valueOf(nodeData->getCurrentPacketIsColor()));
|
||||
if (_myServer->wantsDebugVoxelSending() && _myServer->wantsVerboseDebug()) {
|
||||
printf("wantColor=%s wantCompression=%s SENDING PARTIAL PACKET! currentPacketIsColor=%s currentPacketIsCompressed=%s\n",
|
||||
debug::valueOf(wantColor), debug::valueOf(wantCompression),
|
||||
debug::valueOf(nodeData->getCurrentPacketIsColor()),
|
||||
debug::valueOf(nodeData->getCurrentPacketIsCompressed()) );
|
||||
}
|
||||
|
||||
packetsSentThisInterval += handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent);
|
||||
} else {
|
||||
if (_myServer->wantsDebugVoxelSending()) {
|
||||
printf("wantColor=%s --- FIXING HEADER! nodeData->getCurrentPacketIsColor()=%s\n",
|
||||
debug::valueOf(wantColor), debug::valueOf(nodeData->getCurrentPacketIsColor()));
|
||||
if (_myServer->wantsDebugVoxelSending() && _myServer->wantsVerboseDebug()) {
|
||||
printf("wantColor=%s wantCompression=%s FIXING HEADER! currentPacketIsColor=%s currentPacketIsCompressed=%s\n",
|
||||
debug::valueOf(wantColor), debug::valueOf(wantCompression),
|
||||
debug::valueOf(nodeData->getCurrentPacketIsColor()),
|
||||
debug::valueOf(nodeData->getCurrentPacketIsCompressed()) );
|
||||
}
|
||||
nodeData->resetVoxelPacket();
|
||||
}
|
||||
int targetSize = MAX_VOXEL_PACKET_DATA_SIZE;
|
||||
if (wantCompression) {
|
||||
targetSize = nodeData->getAvailable() - sizeof(VOXEL_PACKET_INTERNAL_SECTION_SIZE);
|
||||
}
|
||||
if (_myServer->wantsDebugVoxelSending() && _myServer->wantsVerboseDebug()) {
|
||||
printf("line:%d _packetData.changeSettings() wantCompression=%s targetSize=%d\n",__LINE__,
|
||||
debug::valueOf(wantCompression), targetSize);
|
||||
}
|
||||
|
||||
_packetData.changeSettings(wantCompression, targetSize);
|
||||
}
|
||||
|
||||
if (_myServer->wantsDebugVoxelSending()) {
|
||||
printf("wantColor=%s getCurrentPacketIsColor()=%s, viewFrustumChanged=%s, getWantLowResMoving()=%s\n",
|
||||
debug::valueOf(wantColor), debug::valueOf(nodeData->getCurrentPacketIsColor()),
|
||||
debug::valueOf(viewFrustumChanged), debug::valueOf(nodeData->getWantLowResMoving()));
|
||||
if (_myServer->wantsDebugVoxelSending() && _myServer->wantsVerboseDebug()) {
|
||||
printf("wantColor/isColor=%s/%s wantCompression/isCompressed=%s/%s viewFrustumChanged=%s, getWantLowResMoving()=%s\n",
|
||||
debug::valueOf(wantColor), debug::valueOf(nodeData->getCurrentPacketIsColor()),
|
||||
debug::valueOf(wantCompression), debug::valueOf(nodeData->getCurrentPacketIsCompressed()),
|
||||
debug::valueOf(viewFrustumChanged), debug::valueOf(nodeData->getWantLowResMoving()));
|
||||
}
|
||||
|
||||
const ViewFrustum* lastViewFrustum = wantDelta ? &nodeData->getLastKnownViewFrustum() : NULL;
|
||||
|
||||
if (_myServer->wantsDebugVoxelSending()) {
|
||||
if (_myServer->wantsDebugVoxelSending() && _myServer->wantsVerboseDebug()) {
|
||||
printf("deepestLevelVoxelDistributor() viewFrustumChanged=%s, nodeBag.isEmpty=%s, viewSent=%s\n",
|
||||
debug::valueOf(viewFrustumChanged), debug::valueOf(nodeData->nodeBag.isEmpty()),
|
||||
debug::valueOf(nodeData->getViewSent())
|
||||
|
@ -186,7 +290,7 @@ int VoxelSendThread::deepestLevelVoxelDistributor(Node* node, VoxelNodeData* nod
|
|||
// the current view frustum for things to send.
|
||||
if (viewFrustumChanged || nodeData->nodeBag.isEmpty()) {
|
||||
uint64_t now = usecTimestampNow();
|
||||
if (_myServer->wantsDebugVoxelSending()) {
|
||||
if (_myServer->wantsDebugVoxelSending() && _myServer->wantsVerboseDebug()) {
|
||||
printf("(viewFrustumChanged=%s || nodeData->nodeBag.isEmpty() =%s)...\n",
|
||||
debug::valueOf(viewFrustumChanged), debug::valueOf(nodeData->nodeBag.isEmpty()));
|
||||
if (nodeData->getLastTimeBagEmpty() > 0) {
|
||||
|
@ -215,8 +319,20 @@ int VoxelSendThread::deepestLevelVoxelDistributor(Node* node, VoxelNodeData* nod
|
|||
uint64_t now = usecTimestampNow();
|
||||
nodeData->setLastTimeBagEmpty(now);
|
||||
}
|
||||
|
||||
|
||||
// track completed scenes and send out the stats packet accordingly
|
||||
nodeData->stats.sceneCompleted();
|
||||
::endSceneSleepTime = _usleepTime;
|
||||
unsigned long sleepTime = ::endSceneSleepTime - ::startSceneSleepTime;
|
||||
|
||||
unsigned long encodeTime = nodeData->stats.getTotalEncodeTime();
|
||||
unsigned long elapsedTime = nodeData->stats.getElapsedTime();
|
||||
packetsSentThisInterval += handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent);
|
||||
|
||||
if (_myServer->wantsDebugVoxelSending()) {
|
||||
qDebug("Scene completed at %llu encodeTime:%lu sleepTime:%lu elapsed:%lu Packets:%llu Bytes:%llu Wasted:%llu\n",
|
||||
usecTimestampNow(), encodeTime, sleepTime, elapsedTime, _totalPackets, _totalBytes, _totalWastedBytes);
|
||||
}
|
||||
|
||||
if (_myServer->wantDisplayVoxelStats()) {
|
||||
nodeData->stats.printDebugDetails();
|
||||
|
@ -230,7 +346,15 @@ int VoxelSendThread::deepestLevelVoxelDistributor(Node* node, VoxelNodeData* nod
|
|||
if (isFullScene) {
|
||||
nodeData->nodeBag.deleteAll();
|
||||
}
|
||||
nodeData->stats.sceneStarted(isFullScene, viewFrustumChanged, _myServer->getServerTree().rootNode, _myServer->getJurisdiction());
|
||||
|
||||
if (_myServer->wantsDebugVoxelSending()) {
|
||||
qDebug("Scene started at %llu Packets:%llu Bytes:%llu Wasted:%llu\n",
|
||||
usecTimestampNow(),_totalPackets,_totalBytes,_totalWastedBytes);
|
||||
}
|
||||
|
||||
::startSceneSleepTime = _usleepTime;
|
||||
nodeData->stats.sceneStarted(isFullScene, viewFrustumChanged,
|
||||
_myServer->getServerTree().rootNode, _myServer->getJurisdiction());
|
||||
|
||||
// This is the start of "resending" the scene.
|
||||
bool dontRestartSceneOnMove = false; // this is experimental
|
||||
|
@ -247,40 +371,29 @@ int VoxelSendThread::deepestLevelVoxelDistributor(Node* node, VoxelNodeData* nod
|
|||
if (!nodeData->nodeBag.isEmpty()) {
|
||||
int bytesWritten = 0;
|
||||
uint64_t start = usecTimestampNow();
|
||||
uint64_t startCompressTimeMsecs = VoxelPacketData::getCompressContentTime() / 1000;
|
||||
uint64_t startCompressCalls = VoxelPacketData::getCompressContentCalls();
|
||||
|
||||
bool shouldSendEnvironments = _myServer->wantSendEnvironments() && shouldDo(ENVIRONMENT_SEND_INTERVAL_USECS, VOXEL_SEND_INTERVAL_USECS);
|
||||
|
||||
int clientMaxPacketsPerInterval = std::max(1,(nodeData->getMaxVoxelPacketsPerSecond() / INTERVALS_PER_SECOND));
|
||||
int maxPacketsPerInterval = std::min(clientMaxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval());
|
||||
|
||||
if (_myServer->wantsDebugVoxelSending()) {
|
||||
if (_myServer->wantsDebugVoxelSending() && _myServer->wantsVerboseDebug()) {
|
||||
printf("truePacketsSent=%d packetsSentThisInterval=%d maxPacketsPerInterval=%d server PPI=%d nodePPS=%d nodePPI=%d\n",
|
||||
truePacketsSent, packetsSentThisInterval, maxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval(),
|
||||
nodeData->getMaxVoxelPacketsPerSecond(), clientMaxPacketsPerInterval);
|
||||
}
|
||||
|
||||
int extraPackingAttempts = 0;
|
||||
while (somethingToSend && packetsSentThisInterval < maxPacketsPerInterval - (shouldSendEnvironments ? 1 : 0)) {
|
||||
if (_myServer->wantsDebugVoxelSending()) {
|
||||
if (_myServer->wantsDebugVoxelSending() && _myServer->wantsVerboseDebug()) {
|
||||
printf("truePacketsSent=%d packetsSentThisInterval=%d maxPacketsPerInterval=%d server PPI=%d nodePPS=%d nodePPI=%d\n",
|
||||
truePacketsSent, packetsSentThisInterval, maxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval(),
|
||||
nodeData->getMaxVoxelPacketsPerSecond(), clientMaxPacketsPerInterval);
|
||||
}
|
||||
|
||||
|
||||
// Check to see if we're taking too long, and if so bail early...
|
||||
uint64_t now = usecTimestampNow();
|
||||
long elapsedUsec = (now - start);
|
||||
long elapsedUsecPerPacket = (truePacketsSent == 0) ? 0 : (elapsedUsec / truePacketsSent);
|
||||
long usecRemaining = (VOXEL_SEND_INTERVAL_USECS - elapsedUsec);
|
||||
|
||||
if (elapsedUsecPerPacket + SENDING_TIME_TO_SPARE > usecRemaining) {
|
||||
if (_myServer->wantsDebugVoxelSending()) {
|
||||
printf("packetLoop() usecRemaining=%ld bailing early took %ld usecs to generate %d bytes in %d packets (%ld usec avg), %d nodes still to send\n",
|
||||
usecRemaining, elapsedUsec, trueBytesSent, truePacketsSent, elapsedUsecPerPacket,
|
||||
nodeData->nodeBag.count());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
bool lastNodeDidntFit = false; // assume each node fits
|
||||
if (!nodeData->nodeBag.isEmpty()) {
|
||||
VoxelNode* subTree = nodeData->nodeBag.extract();
|
||||
bool wantOcclusionCulling = nodeData->getWantOcclusionCulling();
|
||||
|
@ -305,23 +418,94 @@ int VoxelSendThread::deepestLevelVoxelDistributor(Node* node, VoxelNodeData* nod
|
|||
|
||||
_myServer->getServerTree().lockForRead();
|
||||
nodeData->stats.encodeStarted();
|
||||
bytesWritten = _myServer->getServerTree().encodeTreeBitstream(subTree, _tempOutputBuffer, MAX_VOXEL_PACKET_SIZE - 1,
|
||||
nodeData->nodeBag, params);
|
||||
bytesWritten = _myServer->getServerTree().encodeTreeBitstream(subTree, &_packetData, nodeData->nodeBag, params);
|
||||
|
||||
// if we're trying to fill a full size packet, then we use this logic to determine if we have a DIDNT_FIT case.
|
||||
if (_packetData.getTargetSize() == MAX_VOXEL_PACKET_DATA_SIZE) {
|
||||
if (_packetData.hasContent() && bytesWritten == 0 &&
|
||||
params.stopReason == EncodeBitstreamParams::DIDNT_FIT) {
|
||||
lastNodeDidntFit = true;
|
||||
}
|
||||
} else {
|
||||
// in compressed mode and we are trying to pack more... and we don't care if the _packetData has
|
||||
// content or not... because in this case even if we were unable to pack any data, we want to drop
|
||||
// below to our sendNow logic, but we do want to track that we attempted to pack extra
|
||||
extraPackingAttempts++;
|
||||
if (bytesWritten == 0 && params.stopReason == EncodeBitstreamParams::DIDNT_FIT) {
|
||||
lastNodeDidntFit = true;
|
||||
}
|
||||
}
|
||||
|
||||
nodeData->stats.encodeStopped();
|
||||
_myServer->getServerTree().unlock();
|
||||
|
||||
if (nodeData->getAvailable() >= bytesWritten) {
|
||||
nodeData->writeToPacket(_tempOutputBuffer, bytesWritten);
|
||||
} else {
|
||||
packetsSentThisInterval += handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent);
|
||||
nodeData->writeToPacket(_tempOutputBuffer, bytesWritten);
|
||||
}
|
||||
} else {
|
||||
if (nodeData->isPacketWaiting()) {
|
||||
packetsSentThisInterval += handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent);
|
||||
// If the bag was empty then we didn't even attempt to encode, and so we know the bytesWritten were 0
|
||||
bytesWritten = 0;
|
||||
somethingToSend = false; // this will cause us to drop out of the loop...
|
||||
}
|
||||
|
||||
// If the last node didn't fit, but we're in compressed mode, then we actually want to see if we can fit a
|
||||
// little bit more in this packet. To do this we
|
||||
|
||||
// We only consider sending anything if there is something in the _packetData to send... But
|
||||
// if bytesWritten == 0 it means either the subTree couldn't fit or we had an empty bag... Both cases
|
||||
// mean we should send the previous packet contents and reset it.
|
||||
if (lastNodeDidntFit) {
|
||||
if (_packetData.hasContent()) {
|
||||
// if for some reason the finalized size is greater than our available size, then probably the "compressed"
|
||||
// form actually inflated beyond our padding, and in this case we will send the current packet, then
|
||||
// write to out new packet...
|
||||
int writtenSize = _packetData.getFinalizedSize()
|
||||
+ (nodeData->getCurrentPacketIsCompressed() ? sizeof(VOXEL_PACKET_INTERNAL_SECTION_SIZE) : 0);
|
||||
|
||||
|
||||
if (writtenSize > nodeData->getAvailable()) {
|
||||
if (_myServer->wantsDebugVoxelSending() && _myServer->wantsVerboseDebug()) {
|
||||
printf("writtenSize[%d] > available[%d] too big, sending packet as is.\n",
|
||||
writtenSize, nodeData->getAvailable());
|
||||
}
|
||||
packetsSentThisInterval += handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent);
|
||||
}
|
||||
|
||||
if (_myServer->wantsDebugVoxelSending() && _myServer->wantsVerboseDebug()) {
|
||||
printf("calling writeToPacket() available=%d compressedSize=%d uncompressedSize=%d target=%d\n",
|
||||
nodeData->getAvailable(), _packetData.getFinalizedSize(),
|
||||
_packetData.getUncompressedSize(), _packetData.getTargetSize());
|
||||
}
|
||||
nodeData->writeToPacket(_packetData.getFinalizedData(), _packetData.getFinalizedSize());
|
||||
extraPackingAttempts = 0;
|
||||
}
|
||||
//packetsSentThisInterval = _myServer->getPacketsPerClientPerInterval(); // done for now, no nodes left
|
||||
somethingToSend = false;
|
||||
|
||||
// If we're not running compressed, the we know we can just send now. Or if we're running compressed, but
|
||||
// the packet doesn't have enough space to bother attempting to pack more...
|
||||
bool sendNow = true;
|
||||
|
||||
if (nodeData->getCurrentPacketIsCompressed() &&
|
||||
nodeData->getAvailable() >= MINIMUM_ATTEMPT_MORE_PACKING &&
|
||||
extraPackingAttempts <= REASONABLE_NUMBER_OF_PACKING_ATTEMPTS) {
|
||||
sendNow = false; // try to pack more
|
||||
}
|
||||
|
||||
int targetSize = MAX_VOXEL_PACKET_DATA_SIZE;
|
||||
if (sendNow) {
|
||||
packetsSentThisInterval += handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent);
|
||||
if (wantCompression) {
|
||||
targetSize = nodeData->getAvailable() - sizeof(VOXEL_PACKET_INTERNAL_SECTION_SIZE);
|
||||
}
|
||||
} else {
|
||||
// If we're in compressed mode, then we want to see if we have room for more in this wire packet.
|
||||
// but we've finalized the _packetData, so we want to start a new section, we will do that by
|
||||
// resetting the packet settings with the max uncompressed size of our current available space
|
||||
// in the wire packet. We also include room for our section header, and a little bit of padding
|
||||
// to account for the fact that whenc compressing small amounts of data, we sometimes end up with
|
||||
// a larger compressed size then uncompressed size
|
||||
targetSize = nodeData->getAvailable() - sizeof(VOXEL_PACKET_INTERNAL_SECTION_SIZE) - COMPRESS_PADDING;
|
||||
}
|
||||
if (_myServer->wantsDebugVoxelSending() && _myServer->wantsVerboseDebug()) {
|
||||
printf("line:%d _packetData.changeSettings() wantCompression=%s targetSize=%d\n",__LINE__,
|
||||
debug::valueOf(nodeData->getWantCompression()), targetSize);
|
||||
}
|
||||
_packetData.changeSettings(nodeData->getWantCompression(), targetSize); // will do reset
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -343,18 +527,26 @@ int VoxelSendThread::deepestLevelVoxelDistributor(Node* node, VoxelNodeData* nod
|
|||
|
||||
uint64_t end = usecTimestampNow();
|
||||
int elapsedmsec = (end - start)/1000;
|
||||
|
||||
uint64_t endCompressCalls = VoxelPacketData::getCompressContentCalls();
|
||||
int elapsedCompressCalls = endCompressCalls - startCompressCalls;
|
||||
|
||||
uint64_t endCompressTimeMsecs = VoxelPacketData::getCompressContentTime() / 1000;
|
||||
int elapsedCompressTimeMsecs = endCompressTimeMsecs - startCompressTimeMsecs;
|
||||
|
||||
|
||||
if (elapsedmsec > 100) {
|
||||
if (elapsedmsec > 1000) {
|
||||
int elapsedsec = (end - start)/1000000;
|
||||
printf("WARNING! packetLoop() took %d seconds to generate %d bytes in %d packets %d nodes still to send\n",
|
||||
elapsedsec, trueBytesSent, truePacketsSent, nodeData->nodeBag.count());
|
||||
printf("WARNING! packetLoop() took %d seconds [%d milliseconds %d calls in compress] to generate %d bytes in %d packets %d nodes still to send\n",
|
||||
elapsedsec, elapsedCompressTimeMsecs, elapsedCompressCalls, trueBytesSent, truePacketsSent, nodeData->nodeBag.count());
|
||||
} else {
|
||||
printf("WARNING! packetLoop() took %d milliseconds to generate %d bytes in %d packets, %d nodes still to send\n",
|
||||
elapsedmsec, trueBytesSent, truePacketsSent, nodeData->nodeBag.count());
|
||||
printf("WARNING! packetLoop() took %d milliseconds [%d milliseconds %d calls in compress] to generate %d bytes in %d packets, %d nodes still to send\n",
|
||||
elapsedmsec, elapsedCompressTimeMsecs, elapsedCompressCalls, trueBytesSent, truePacketsSent, nodeData->nodeBag.count());
|
||||
}
|
||||
} else if (_myServer->wantsDebugVoxelSending()) {
|
||||
printf("packetLoop() took %d milliseconds to generate %d bytes in %d packets, %d nodes still to send\n",
|
||||
elapsedmsec, trueBytesSent, truePacketsSent, nodeData->nodeBag.count());
|
||||
} else if (_myServer->wantsDebugVoxelSending() && _myServer->wantsVerboseDebug()) {
|
||||
printf("packetLoop() took %d milliseconds [%d milliseconds %d calls in compress] to generate %d bytes in %d packets, %d nodes still to send\n",
|
||||
elapsedmsec, elapsedCompressTimeMsecs, elapsedCompressCalls, trueBytesSent, truePacketsSent, nodeData->nodeBag.count());
|
||||
}
|
||||
|
||||
// if after sending packets we've emptied our bag, then we want to remember that we've sent all
|
||||
|
@ -362,13 +554,13 @@ int VoxelSendThread::deepestLevelVoxelDistributor(Node* node, VoxelNodeData* nod
|
|||
if (nodeData->nodeBag.isEmpty()) {
|
||||
nodeData->updateLastKnownViewFrustum();
|
||||
nodeData->setViewSent(true);
|
||||
if (_myServer->wantsDebugVoxelSending()) {
|
||||
if (_myServer->wantsDebugVoxelSending() && _myServer->wantsVerboseDebug()) {
|
||||
nodeData->map.printStats();
|
||||
}
|
||||
nodeData->map.erase(); // It would be nice if we could save this, and only reset it when the view frustum changes
|
||||
}
|
||||
|
||||
if (_myServer->wantsDebugVoxelSending()) {
|
||||
if (_myServer->wantsDebugVoxelSending() && _myServer->wantsVerboseDebug()) {
|
||||
printf("truePacketsSent=%d packetsSentThisInterval=%d maxPacketsPerInterval=%d server PPI=%d nodePPS=%d nodePPI=%d\n",
|
||||
truePacketsSent, packetsSentThisInterval, maxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval(),
|
||||
nodeData->getMaxVoxelPacketsPerSecond(), clientMaxPacketsPerInterval);
|
||||
|
|
|
@ -22,6 +22,14 @@
|
|||
class VoxelSendThread : public virtual GenericThread {
|
||||
public:
|
||||
VoxelSendThread(const QUuid& nodeUUID, VoxelServer* myServer);
|
||||
|
||||
static uint64_t _totalBytes;
|
||||
static uint64_t _totalWastedBytes;
|
||||
static uint64_t _totalPackets;
|
||||
|
||||
static uint64_t _usleepTime;
|
||||
static uint64_t _usleepCalls;
|
||||
|
||||
protected:
|
||||
/// Implements generic processing behavior for this thread.
|
||||
virtual bool process();
|
||||
|
@ -33,7 +41,8 @@ private:
|
|||
int handlePacketSend(Node* node, VoxelNodeData* nodeData, int& trueBytesSent, int& truePacketsSent);
|
||||
int deepestLevelVoxelDistributor(Node* node, VoxelNodeData* nodeData, bool viewFrustumChanged);
|
||||
|
||||
unsigned char _tempOutputBuffer[MAX_VOXEL_PACKET_SIZE];
|
||||
unsigned char _tempOutputBuffer[MAX_VOXEL_PACKET_SIZE]; // used by environment sending code
|
||||
VoxelPacketData _packetData;
|
||||
};
|
||||
|
||||
#endif // __voxel_server__VoxelSendThread__
|
||||
|
|
|
@ -262,6 +262,36 @@ int VoxelServer::civetwebRequestHandler(struct mg_connection* connection) {
|
|||
mg_printf(connection, "%s", "\r\n");
|
||||
mg_printf(connection, "%s", "\r\n");
|
||||
|
||||
|
||||
// display outbound packet stats
|
||||
mg_printf(connection, "%s", "<b>Voxel Packet Statistics...</b>\r\n");
|
||||
uint64_t totalOutboundPackets = VoxelSendThread::_totalPackets;
|
||||
uint64_t totalOutboundBytes = VoxelSendThread::_totalBytes;
|
||||
uint64_t totalWastedBytes = VoxelSendThread::_totalWastedBytes;
|
||||
uint64_t totalBytesOfOctalCodes = VoxelPacketData::getTotalBytesOfOctalCodes();
|
||||
uint64_t totalBytesOfBitMasks = VoxelPacketData::getTotalBytesOfBitMasks();
|
||||
uint64_t totalBytesOfColor = VoxelPacketData::getTotalBytesOfColor();
|
||||
|
||||
const int COLUMN_WIDTH = 10;
|
||||
mg_printf(connection, " Total Outbound Packets: %s packets\r\n",
|
||||
locale.toString((uint)totalOutboundPackets).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
|
||||
mg_printf(connection, " Total Outbound Bytes: %s bytes\r\n",
|
||||
locale.toString((uint)totalOutboundBytes).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
|
||||
mg_printf(connection, " Total Wasted Bytes: %s bytes\r\n",
|
||||
locale.toString((uint)totalWastedBytes).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
|
||||
mg_printf(connection, " Total OctalCode Bytes: %s bytes (%5.2f%%)\r\n",
|
||||
locale.toString((uint)totalBytesOfOctalCodes).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData(),
|
||||
((float)totalBytesOfOctalCodes / (float)totalOutboundBytes) * AS_PERCENT);
|
||||
mg_printf(connection, " Total BitMasks Bytes: %s bytes (%5.2f%%)\r\n",
|
||||
locale.toString((uint)totalBytesOfBitMasks).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData(),
|
||||
((float)totalBytesOfBitMasks / (float)totalOutboundBytes) * AS_PERCENT);
|
||||
mg_printf(connection, " Total Color Bytes: %s bytes (%5.2f%%)\r\n",
|
||||
locale.toString((uint)totalBytesOfColor).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData(),
|
||||
((float)totalBytesOfColor / (float)totalOutboundBytes) * AS_PERCENT);
|
||||
|
||||
mg_printf(connection, "%s", "\r\n");
|
||||
mg_printf(connection, "%s", "\r\n");
|
||||
|
||||
// display inbound packet stats
|
||||
mg_printf(connection, "%s", "<b>Voxel Edit Statistics... <a href='/resetStats'>[RESET]</a></b>\r\n");
|
||||
uint64_t averageTransitTimePerPacket = theServer->_voxelServerPacketProcessor->getAverageTransitTimePerPacket();
|
||||
|
@ -274,7 +304,6 @@ int VoxelServer::civetwebRequestHandler(struct mg_connection* connection) {
|
|||
|
||||
float averageVoxelsPerPacket = totalPacketsProcessed == 0 ? 0 : totalVoxelsProcessed / totalPacketsProcessed;
|
||||
|
||||
const int COLUMN_WIDTH = 10;
|
||||
mg_printf(connection, " Total Inbound Packets: %s packets\r\n",
|
||||
locale.toString((uint)totalPacketsProcessed).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
|
||||
mg_printf(connection, " Total Inbound Voxels: %s voxels\r\n",
|
||||
|
@ -674,6 +703,11 @@ void VoxelServer::run() {
|
|||
int numBytesPacketHeader = numBytesForPacketHeader(packetData);
|
||||
|
||||
if (packetData[0] == PACKET_TYPE_VOXEL_QUERY) {
|
||||
bool debug = false;
|
||||
if (debug) {
|
||||
qDebug("Got PACKET_TYPE_VOXEL_QUERY at %llu.\n", usecTimestampNow());
|
||||
}
|
||||
|
||||
// If we got a PACKET_TYPE_VOXEL_QUERY, then we're talking to an NODE_TYPE_AVATAR, and we
|
||||
// need to make sure we have it in our nodeList.
|
||||
QUuid nodeUUID = QUuid::fromRfc4122(QByteArray((char*)packetData + numBytesPacketHeader,
|
||||
|
@ -700,8 +734,7 @@ void VoxelServer::run() {
|
|||
} else if (_voxelServerPacketProcessor &&
|
||||
(packetData[0] == PACKET_TYPE_SET_VOXEL
|
||||
|| packetData[0] == PACKET_TYPE_SET_VOXEL_DESTRUCTIVE
|
||||
|| packetData[0] == PACKET_TYPE_ERASE_VOXEL
|
||||
|| packetData[0] == PACKET_TYPE_Z_COMMAND)) {
|
||||
|| packetData[0] == PACKET_TYPE_ERASE_VOXEL)) {
|
||||
|
||||
|
||||
const char* messageName;
|
||||
|
@ -716,20 +749,6 @@ void VoxelServer::run() {
|
|||
messageName = "PACKET_TYPE_ERASE_VOXEL";
|
||||
break;
|
||||
}
|
||||
int numBytesPacketHeader = numBytesForPacketHeader(packetData);
|
||||
|
||||
if (packetData[0] != PACKET_TYPE_Z_COMMAND) {
|
||||
unsigned short int sequence = (*((unsigned short int*)(packetData + numBytesPacketHeader)));
|
||||
uint64_t sentAt = (*((uint64_t*)(packetData + numBytesPacketHeader + sizeof(sequence))));
|
||||
uint64_t arrivedAt = usecTimestampNow();
|
||||
uint64_t transitTime = arrivedAt - sentAt;
|
||||
if (wantShowAnimationDebug() || wantsDebugVoxelReceiving()) {
|
||||
printf("RECEIVE THREAD: got %s - command from client receivedBytes=%ld sequence=%d transitTime=%llu usecs\n",
|
||||
messageName,
|
||||
packetLength, sequence, transitTime);
|
||||
}
|
||||
}
|
||||
|
||||
_voxelServerPacketProcessor->queueReceivedPacket(senderAddress, packetData, packetLength);
|
||||
} else {
|
||||
// let processNodeData handle it.
|
||||
|
|
|
@ -169,35 +169,6 @@ void VoxelServerPacketProcessor::processPacket(sockaddr& senderAddress, unsigned
|
|||
_myServer->getServerTree().processRemoveVoxelBitstream((unsigned char*)packetData, packetLength);
|
||||
_myServer->getServerTree().unlock();
|
||||
|
||||
// Make sure our Node and NodeList knows we've heard from this node.
|
||||
Node* node = NodeList::getInstance()->nodeWithAddress(&senderAddress);
|
||||
if (node) {
|
||||
node->setLastHeardMicrostamp(usecTimestampNow());
|
||||
}
|
||||
} else if (packetData[0] == PACKET_TYPE_Z_COMMAND) {
|
||||
|
||||
// the Z command is a special command that allows the sender to send the voxel server high level semantic
|
||||
// requests, like erase all, or add sphere scene
|
||||
|
||||
char* command = (char*) &packetData[numBytesPacketHeader]; // start of the command
|
||||
int commandLength = strlen(command); // commands are null terminated strings
|
||||
int totalLength = numBytesPacketHeader + commandLength + 1; // 1 for null termination
|
||||
printf("got Z message len(%ld)= %s\n", packetLength, command);
|
||||
bool rebroadcast = true; // by default rebroadcast
|
||||
|
||||
while (totalLength <= packetLength) {
|
||||
if (strcmp(command, TEST_COMMAND) == 0) {
|
||||
printf("got Z message == a message, nothing to do, just report\n");
|
||||
}
|
||||
totalLength += commandLength + 1; // 1 for null termination
|
||||
}
|
||||
|
||||
if (rebroadcast) {
|
||||
// Now send this to the connected nodes so they can also process these messages
|
||||
printf("rebroadcasting Z message to connected nodes... nodeList.broadcastToNodes()\n");
|
||||
NodeList::getInstance()->broadcastToNodes(packetData, packetLength, &NODE_TYPE_AGENT, 1);
|
||||
}
|
||||
|
||||
// Make sure our Node and NodeList knows we've heard from this node.
|
||||
Node* node = NodeList::getInstance()->nodeWithAddress(&senderAddress);
|
||||
if (node) {
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <OctalCode.h>
|
||||
#include <NodeList.h>
|
||||
#include <PacketHeaders.h>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
// this is where the coordinate system is represented
|
||||
|
@ -32,9 +34,9 @@ const float DEFAULT_VOXEL_SIZE_SCALE = TREE_SCALE * 400.0f;
|
|||
const float MAX_LOD_SIZE_MULTIPLIER = 2000.0f;
|
||||
|
||||
const int NUMBER_OF_CHILDREN = 8;
|
||||
const int MAX_VOXEL_PACKET_SIZE = 1492;
|
||||
|
||||
const int MAX_TREE_SLICE_BYTES = 26;
|
||||
const int DEFAULT_MAX_VOXELS_PER_SYSTEM = 200000;
|
||||
const int DEFAULT_MAX_VOXELS_PER_SYSTEM = 500000;
|
||||
const int VERTICES_PER_VOXEL = 24; // 6 sides * 4 corners per side
|
||||
const int VERTEX_POINTS_PER_VOXEL = 3 * VERTICES_PER_VOXEL; // xyz for each VERTICE_PER_VOXEL
|
||||
const int INDICES_PER_VOXEL = 3 * 12; // 6 sides * 2 triangles per size * 3 vertices per triangle
|
||||
|
@ -62,4 +64,5 @@ const int DANGEROUSLY_DEEP_RECURSION = 200; // use this for something that needs
|
|||
|
||||
const int DEFAULT_MAX_VOXEL_PPS = 600; // the default maximum PPS we think a voxel server should send to a client
|
||||
|
||||
const bool VOXEL_PACKETS_COMPRESSED = false;
|
||||
#endif
|
325
libraries/voxels/src/VoxelPacketData.cpp
Normal file
325
libraries/voxels/src/VoxelPacketData.cpp
Normal file
|
@ -0,0 +1,325 @@
|
|||
//
|
||||
// VoxelPacketData.cpp
|
||||
// hifi
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 11/19/2013.
|
||||
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
|
||||
//
|
||||
|
||||
#include <PerfStat.h>
|
||||
#include "VoxelPacketData.h"
|
||||
|
||||
bool VoxelPacketData::_debug = false;
|
||||
uint64_t VoxelPacketData::_totalBytesOfOctalCodes = 0;
|
||||
uint64_t VoxelPacketData::_totalBytesOfBitMasks = 0;
|
||||
uint64_t VoxelPacketData::_totalBytesOfColor = 0;
|
||||
|
||||
|
||||
|
||||
VoxelPacketData::VoxelPacketData(bool enableCompression, int targetSize) {
|
||||
changeSettings(enableCompression, targetSize); // does reset...
|
||||
}
|
||||
|
||||
void VoxelPacketData::changeSettings(bool enableCompression, int targetSize) {
|
||||
_enableCompression = enableCompression;
|
||||
_targetSize = std::min(MAX_VOXEL_UNCOMRESSED_PACKET_SIZE, targetSize);
|
||||
reset();
|
||||
}
|
||||
|
||||
void VoxelPacketData::reset() {
|
||||
_bytesInUse = 0;
|
||||
_bytesAvailable = _targetSize;
|
||||
_subTreeAt = 0;
|
||||
_compressedBytes = 0;
|
||||
_bytesInUseLastCheck = 0;
|
||||
_dirty = false;
|
||||
|
||||
_bytesOfOctalCodes = 0;
|
||||
_bytesOfBitMasks = 0;
|
||||
_bytesOfColor = 0;
|
||||
_bytesOfOctalCodesCurrentSubTree = 0;
|
||||
}
|
||||
|
||||
VoxelPacketData::~VoxelPacketData() {
|
||||
}
|
||||
|
||||
bool VoxelPacketData::append(const unsigned char* data, int length) {
|
||||
bool success = false;
|
||||
|
||||
if (length <= _bytesAvailable) {
|
||||
memcpy(&_uncompressed[_bytesInUse], data, length);
|
||||
_bytesInUse += length;
|
||||
_bytesAvailable -= length;
|
||||
success = true;
|
||||
_dirty = true;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool VoxelPacketData::append(unsigned char byte) {
|
||||
bool success = false;
|
||||
if (_bytesAvailable > 0) {
|
||||
_uncompressed[_bytesInUse] = byte;
|
||||
_bytesInUse++;
|
||||
_bytesAvailable--;
|
||||
success = true;
|
||||
_dirty = true;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool VoxelPacketData::updatePriorBitMask(int offset, unsigned char bitmask) {
|
||||
bool success = false;
|
||||
if (offset >= 0 && offset < _bytesInUse) {
|
||||
_uncompressed[offset] = bitmask;
|
||||
success = true;
|
||||
_dirty = true;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool VoxelPacketData::updatePriorBytes(int offset, const unsigned char* replacementBytes, int length) {
|
||||
bool success = false;
|
||||
if (length >= 0 && offset >= 0 && ((offset + length) <= _bytesInUse)) {
|
||||
memcpy(&_uncompressed[offset], replacementBytes, length); // copy new content
|
||||
success = true;
|
||||
_dirty = true;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool VoxelPacketData::startSubTree(const unsigned char* octcode) {
|
||||
_bytesOfOctalCodesCurrentSubTree = _bytesOfOctalCodes;
|
||||
bool success = false;
|
||||
int possibleStartAt = _bytesInUse;
|
||||
int length = 0;
|
||||
if (octcode) {
|
||||
length = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(octcode));
|
||||
success = append(octcode, length); // handles checking compression
|
||||
} else {
|
||||
// NULL case, means root node, which is 0
|
||||
unsigned char byte = 0;
|
||||
length = 1;
|
||||
success = append(byte); // handles checking compression
|
||||
}
|
||||
if (success) {
|
||||
_subTreeAt = possibleStartAt;
|
||||
}
|
||||
if (success) {
|
||||
_bytesOfOctalCodes += length;
|
||||
_totalBytesOfOctalCodes += length;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
const unsigned char* VoxelPacketData::getFinalizedData() {
|
||||
if (!_enableCompression) {
|
||||
return &_uncompressed[0];
|
||||
}
|
||||
|
||||
if (_dirty) {
|
||||
if (_debug) {
|
||||
printf("getFinalizedData() _compressedBytes=%d _bytesInUse=%d\n",_compressedBytes, _bytesInUse);
|
||||
}
|
||||
compressContent();
|
||||
}
|
||||
return &_compressed[0];
|
||||
}
|
||||
|
||||
int VoxelPacketData::getFinalizedSize() {
|
||||
if (!_enableCompression) {
|
||||
return _bytesInUse;
|
||||
}
|
||||
|
||||
if (_dirty) {
|
||||
if (_debug) {
|
||||
printf("getFinalizedSize() _compressedBytes=%d _bytesInUse=%d\n",_compressedBytes, _bytesInUse);
|
||||
}
|
||||
compressContent();
|
||||
}
|
||||
|
||||
return _compressedBytes;
|
||||
}
|
||||
|
||||
|
||||
void VoxelPacketData::endSubTree() {
|
||||
_subTreeAt = _bytesInUse;
|
||||
}
|
||||
|
||||
void VoxelPacketData::discardSubTree() {
|
||||
int bytesInSubTree = _bytesInUse - _subTreeAt;
|
||||
_bytesInUse -= bytesInSubTree;
|
||||
_bytesAvailable += bytesInSubTree;
|
||||
_subTreeAt = _bytesInUse; // should be the same actually...
|
||||
_dirty = true;
|
||||
|
||||
// rewind to start of this subtree, other items rewound by endLevel()
|
||||
int reduceBytesOfOctalCodes = _bytesOfOctalCodes - _bytesOfOctalCodesCurrentSubTree;
|
||||
_bytesOfOctalCodes = _bytesOfOctalCodesCurrentSubTree;
|
||||
_totalBytesOfOctalCodes -= reduceBytesOfOctalCodes;
|
||||
}
|
||||
|
||||
LevelDetails VoxelPacketData::startLevel() {
|
||||
LevelDetails key(_bytesInUse, _bytesOfOctalCodes, _bytesOfBitMasks, _bytesOfColor);
|
||||
return key;
|
||||
}
|
||||
|
||||
void VoxelPacketData::discardLevel(LevelDetails key) {
|
||||
int bytesInLevel = _bytesInUse - key._startIndex;
|
||||
|
||||
// reset statistics...
|
||||
int reduceBytesOfOctalCodes = _bytesOfOctalCodes - key._bytesOfOctalCodes;
|
||||
int reduceBytesOfBitMasks = _bytesOfBitMasks - key._bytesOfBitmasks;
|
||||
int reduceBytesOfColor = _bytesOfColor - key._bytesOfColor;
|
||||
|
||||
_bytesOfOctalCodes = key._bytesOfOctalCodes;
|
||||
_bytesOfBitMasks = key._bytesOfBitmasks;
|
||||
_bytesOfColor = key._bytesOfColor;
|
||||
|
||||
_totalBytesOfOctalCodes -= reduceBytesOfOctalCodes;
|
||||
_totalBytesOfBitMasks -= reduceBytesOfBitMasks;
|
||||
_totalBytesOfColor -= reduceBytesOfColor;
|
||||
|
||||
if (_debug) {
|
||||
printf("discardLevel() BEFORE _dirty=%s bytesInLevel=%d _compressedBytes=%d _bytesInUse=%d\n",
|
||||
debug::valueOf(_dirty), bytesInLevel, _compressedBytes, _bytesInUse);
|
||||
}
|
||||
|
||||
_bytesInUse -= bytesInLevel;
|
||||
_bytesAvailable += bytesInLevel;
|
||||
_dirty = true;
|
||||
|
||||
if (_debug) {
|
||||
printf("discardLevel() AFTER _dirty=%s bytesInLevel=%d _compressedBytes=%d _bytesInUse=%d\n",
|
||||
debug::valueOf(_dirty), bytesInLevel, _compressedBytes, _bytesInUse);
|
||||
}
|
||||
}
|
||||
|
||||
bool VoxelPacketData::endLevel(LevelDetails key) {
|
||||
bool success = true;
|
||||
return success;
|
||||
}
|
||||
|
||||
bool VoxelPacketData::appendBitMask(unsigned char bitmask) {
|
||||
bool success = append(bitmask); // handles checking compression
|
||||
if (success) {
|
||||
_bytesOfBitMasks++;
|
||||
_totalBytesOfBitMasks++;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool VoxelPacketData::appendColor(const nodeColor& color) {
|
||||
// eventually we can make this use a dictionary...
|
||||
bool success = false;
|
||||
const int BYTES_PER_COLOR = 3;
|
||||
if (_bytesAvailable > BYTES_PER_COLOR) {
|
||||
// handles checking compression...
|
||||
if (append(color[RED_INDEX])) {
|
||||
if (append(color[GREEN_INDEX])) {
|
||||
if (append(color[BLUE_INDEX])) {
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (success) {
|
||||
_bytesOfColor += BYTES_PER_COLOR;
|
||||
_totalBytesOfColor += BYTES_PER_COLOR;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
uint64_t VoxelPacketData::_compressContentTime = 0;
|
||||
uint64_t VoxelPacketData::_compressContentCalls = 0;
|
||||
|
||||
bool VoxelPacketData::compressContent() {
|
||||
PerformanceWarning warn(false, "VoxelPacketData::compressContent()", false, &_compressContentTime, &_compressContentCalls);
|
||||
|
||||
// without compression, we always pass...
|
||||
if (!_enableCompression) {
|
||||
return true;
|
||||
}
|
||||
|
||||
_bytesInUseLastCheck = _bytesInUse;
|
||||
|
||||
bool success = false;
|
||||
const int MAX_COMPRESSION = 9;
|
||||
|
||||
// we only want to compress the data payload, not the message header
|
||||
const uchar* uncompressedData = &_uncompressed[0];
|
||||
int uncompressedSize = _bytesInUse;
|
||||
|
||||
QByteArray compressedData = qCompress(uncompressedData, uncompressedSize, MAX_COMPRESSION);
|
||||
|
||||
if (compressedData.size() < MAX_VOXEL_PACKET_DATA_SIZE) {
|
||||
_compressedBytes = compressedData.size();
|
||||
for (int i = 0; i < _compressedBytes; i++) {
|
||||
_compressed[i] = compressedData[i];
|
||||
}
|
||||
_dirty = false;
|
||||
success = true;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
|
||||
void VoxelPacketData::loadFinalizedContent(const unsigned char* data, int length) {
|
||||
reset();
|
||||
|
||||
if (data && length > 0) {
|
||||
|
||||
if (_enableCompression) {
|
||||
QByteArray compressedData;
|
||||
for (int i = 0; i < length; i++) {
|
||||
compressedData[i] = data[i];
|
||||
_compressed[i] = compressedData[i];
|
||||
}
|
||||
_compressedBytes = length;
|
||||
QByteArray uncompressedData = qUncompress(compressedData);
|
||||
if (uncompressedData.size() <= _bytesAvailable) {
|
||||
_bytesInUse = uncompressedData.size();
|
||||
_bytesAvailable -= uncompressedData.size();
|
||||
|
||||
for (int i = 0; i < _bytesInUse; i++) {
|
||||
_uncompressed[i] = uncompressedData[i];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < length; i++) {
|
||||
_uncompressed[i] = _compressed[i] = data[i];
|
||||
}
|
||||
_bytesInUse = _compressedBytes = length;
|
||||
}
|
||||
} else {
|
||||
if (_debug) {
|
||||
printf("VoxelPacketData::loadCompressedContent()... length = 0, nothing to do...\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VoxelPacketData::debugContent() {
|
||||
printf("VoxelPacketData::debugContent()... COMPRESSED DATA.... size=%d\n",_compressedBytes);
|
||||
int perline=0;
|
||||
for (int i = 0; i < _compressedBytes; i++) {
|
||||
printf("%.2x ",_compressed[i]);
|
||||
perline++;
|
||||
if (perline >= 30) {
|
||||
printf("\n");
|
||||
perline=0;
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
printf("VoxelPacketData::debugContent()... UNCOMPRESSED DATA.... size=%d\n",_bytesInUse);
|
||||
perline=0;
|
||||
for (int i = 0; i < _bytesInUse; i++) {
|
||||
printf("%.2x ",_uncompressed[i]);
|
||||
perline++;
|
||||
if (perline >= 30) {
|
||||
printf("\n");
|
||||
perline=0;
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
}
|
182
libraries/voxels/src/VoxelPacketData.h
Normal file
182
libraries/voxels/src/VoxelPacketData.h
Normal file
|
@ -0,0 +1,182 @@
|
|||
//
|
||||
// VoxelPacketData.h
|
||||
// hifi
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 11/19/2013
|
||||
//
|
||||
// TO DO:
|
||||
//
|
||||
// * add stats tracking for number of unique colors and consecutive identical colors.
|
||||
// (as research for color dictionaries and RLE)
|
||||
//
|
||||
// * further testing of compression to determine optimal configuration for performance and compression
|
||||
//
|
||||
// * improve semantics for "reshuffle" - current approach will work for now and with compression
|
||||
// but wouldn't work with RLE because the colors in the levels would get reordered and RLE would need
|
||||
// to be recalculated
|
||||
//
|
||||
|
||||
#ifndef __hifi__VoxelPacketData__
|
||||
#define __hifi__VoxelPacketData__
|
||||
|
||||
#include <SharedUtil.h>
|
||||
#include "VoxelConstants.h"
|
||||
#include "VoxelNode.h"
|
||||
|
||||
typedef unsigned char VOXEL_PACKET_FLAGS;
|
||||
typedef uint16_t VOXEL_PACKET_SEQUENCE;
|
||||
typedef uint64_t VOXEL_PACKET_SENT_TIME;
|
||||
typedef uint16_t VOXEL_PACKET_INTERNAL_SECTION_SIZE;
|
||||
const int MAX_VOXEL_PACKET_SIZE = MAX_PACKET_SIZE;
|
||||
const int VOXEL_PACKET_HEADER_SIZE = (sizeof(PACKET_TYPE) + sizeof(PACKET_VERSION) + sizeof(VOXEL_PACKET_FLAGS)
|
||||
+ sizeof(VOXEL_PACKET_SEQUENCE) + sizeof(VOXEL_PACKET_SENT_TIME));
|
||||
|
||||
const int MAX_VOXEL_PACKET_DATA_SIZE = MAX_PACKET_SIZE - VOXEL_PACKET_HEADER_SIZE;
|
||||
|
||||
const int MAX_VOXEL_UNCOMRESSED_PACKET_SIZE = MAX_VOXEL_PACKET_DATA_SIZE;
|
||||
|
||||
const int MINIMUM_ATTEMPT_MORE_PACKING = sizeof(VOXEL_PACKET_INTERNAL_SECTION_SIZE) + 40;
|
||||
const int COMPRESS_PADDING = 15;
|
||||
const int REASONABLE_NUMBER_OF_PACKING_ATTEMPTS = 5;
|
||||
|
||||
const int PACKET_IS_COLOR_BIT = 0;
|
||||
const int PACKET_IS_COMPRESSED_BIT = 1;
|
||||
|
||||
/// An opaque key used when starting, ending, and discarding encoding/packing levels of VoxelPacketData
|
||||
class LevelDetails {
|
||||
LevelDetails(int startIndex, int bytesOfOctalCodes, int bytesOfBitmasks, int bytesOfColor) :
|
||||
_startIndex(startIndex),
|
||||
_bytesOfOctalCodes(bytesOfOctalCodes),
|
||||
_bytesOfBitmasks(bytesOfBitmasks),
|
||||
_bytesOfColor(bytesOfColor) {
|
||||
}
|
||||
|
||||
friend class VoxelPacketData;
|
||||
|
||||
private:
|
||||
int _startIndex;
|
||||
int _bytesOfOctalCodes;
|
||||
int _bytesOfBitmasks;
|
||||
int _bytesOfColor;
|
||||
};
|
||||
|
||||
/// Handles packing of the data portion of PACKET_TYPE_VOXEL_DATA messages.
|
||||
class VoxelPacketData {
|
||||
public:
|
||||
VoxelPacketData(bool enableCompression = false, int maxFinalizedSize = MAX_VOXEL_PACKET_DATA_SIZE);
|
||||
~VoxelPacketData();
|
||||
|
||||
/// change compression and target size settings
|
||||
void changeSettings(bool enableCompression = false, int targetSize = MAX_VOXEL_PACKET_DATA_SIZE);
|
||||
|
||||
/// reset completely, all data is discarded
|
||||
void reset();
|
||||
|
||||
/// call to begin encoding a subtree starting at this point, this will append the octcode to the uncompressed stream
|
||||
/// at this point. May fail if new datastream is too long. In failure case the stream remains in it's previous state.
|
||||
bool startSubTree(const unsigned char* octcode = NULL);
|
||||
|
||||
// call to indicate that the current subtree is complete and changes should be committed.
|
||||
void endSubTree();
|
||||
|
||||
// call rollback the current subtree and restore the stream to the state prior to starting the subtree encoding
|
||||
void discardSubTree();
|
||||
|
||||
/// starts a level marker. returns an opaque key which can be used to discard the level
|
||||
LevelDetails startLevel();
|
||||
|
||||
/// discards all content back to a previous marker key
|
||||
void discardLevel(LevelDetails key);
|
||||
|
||||
/// ends a level, and performs any expensive finalization. may fail if finalization creates a stream which is too large
|
||||
/// if the finalization would fail, the packet will automatically discard the previous level.
|
||||
bool endLevel(LevelDetails key);
|
||||
|
||||
/// appends a bitmask to the end of the stream, may fail if new data stream is too long to fit in packet
|
||||
bool appendBitMask(unsigned char bitmask);
|
||||
|
||||
/// updates the value of a bitmask from a previously appended portion of the uncompressed stream, might fail if the new
|
||||
/// bitmask would cause packet to be less compressed, or if offset was out of range.
|
||||
bool updatePriorBitMask(int offset, unsigned char bitmask);
|
||||
|
||||
/// updates the uncompressed content of the stream starting at byte offset with replacementBytes for length.
|
||||
/// Might fail if the new bytes would cause packet to be less compressed, or if offset and length was out of range.
|
||||
bool updatePriorBytes(int offset, const unsigned char* replacementBytes, int length);
|
||||
|
||||
/// appends a color to the end of the stream, may fail if new data stream is too long to fit in packet
|
||||
bool appendColor(const nodeColor& color);
|
||||
|
||||
/// returns a byte offset from beginning of the uncompressed stream based on offset from end.
|
||||
/// Positive offsetFromEnd returns that many bytes before the end of uncompressed stream
|
||||
int getUncompressedByteOffset(int offsetFromEnd = 0) const { return _bytesInUse - offsetFromEnd; }
|
||||
|
||||
/// get access to the finalized data (it may be compressed or rewritten into optimal form)
|
||||
const unsigned char* getFinalizedData();
|
||||
/// get size of the finalized data (it may be compressed or rewritten into optimal form)
|
||||
int getFinalizedSize();
|
||||
|
||||
/// get pointer to the start of uncompressed stream buffer
|
||||
const unsigned char* getUncompressedData() { return &_uncompressed[0]; }
|
||||
/// the size of the packet in uncompressed form
|
||||
int getUncompressedSize() { return _bytesInUse; }
|
||||
|
||||
/// has some content been written to the packet
|
||||
bool hasContent() const { return (_bytesInUse > 0); }
|
||||
|
||||
/// load finalized content to allow access to decoded content for parsing
|
||||
void loadFinalizedContent(const unsigned char* data, int length);
|
||||
|
||||
/// returns whether or not zlib compression enabled on finalization
|
||||
bool isCompressed() const { return _enableCompression; }
|
||||
|
||||
/// returns the target uncompressed size
|
||||
int getTargetSize() const { return _targetSize; }
|
||||
|
||||
/// displays contents for debugging
|
||||
void debugContent();
|
||||
|
||||
static uint64_t getCompressContentTime() { return _compressContentTime; } /// total time spent compressing content
|
||||
static uint64_t getCompressContentCalls() { return _compressContentCalls; } /// total calls to compress content
|
||||
static uint64_t getTotalBytesOfOctalCodes() { return _totalBytesOfOctalCodes; } /// total bytes for octal codes
|
||||
static uint64_t getTotalBytesOfBitMasks() { return _totalBytesOfBitMasks; } /// total bytes of bitmasks
|
||||
static uint64_t getTotalBytesOfColor() { return _totalBytesOfColor; } /// total bytes of color
|
||||
|
||||
private:
|
||||
/// appends raw bytes, might fail if byte would cause packet to be too large
|
||||
bool append(const unsigned char* data, int length);
|
||||
|
||||
/// append a single byte, might fail if byte would cause packet to be too large
|
||||
bool append(unsigned char byte);
|
||||
|
||||
int _targetSize;
|
||||
bool _enableCompression;
|
||||
|
||||
unsigned char _uncompressed[MAX_VOXEL_UNCOMRESSED_PACKET_SIZE];
|
||||
int _bytesInUse;
|
||||
int _bytesAvailable;
|
||||
int _subTreeAt;
|
||||
|
||||
bool compressContent();
|
||||
|
||||
unsigned char _compressed[MAX_VOXEL_UNCOMRESSED_PACKET_SIZE];
|
||||
int _compressedBytes;
|
||||
int _bytesInUseLastCheck;
|
||||
bool _dirty;
|
||||
|
||||
// statistics...
|
||||
int _bytesOfOctalCodes;
|
||||
int _bytesOfBitMasks;
|
||||
int _bytesOfColor;
|
||||
int _bytesOfOctalCodesCurrentSubTree;
|
||||
|
||||
static bool _debug;
|
||||
|
||||
static uint64_t _compressContentTime;
|
||||
static uint64_t _compressContentCalls;
|
||||
|
||||
static uint64_t _totalBytesOfOctalCodes;
|
||||
static uint64_t _totalBytesOfBitMasks;
|
||||
static uint64_t _totalBytesOfColor;
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__VoxelPacketData__) */
|
|
@ -34,7 +34,8 @@ VoxelQuery::VoxelQuery(Node* owningNode) :
|
|||
_wantColor(true),
|
||||
_wantDelta(true),
|
||||
_wantLowResMoving(true),
|
||||
_wantOcclusionCulling(true),
|
||||
_wantOcclusionCulling(false), // disabled by default
|
||||
_wantCompression(false), // disabled by default
|
||||
_maxVoxelPPS(DEFAULT_MAX_VOXEL_PPS),
|
||||
_voxelSizeScale(DEFAULT_VOXEL_SIZE_SCALE)
|
||||
{
|
||||
|
@ -74,6 +75,7 @@ int VoxelQuery::getBroadcastData(unsigned char* destinationBuffer) {
|
|||
if (_wantColor) { setAtBit(bitItems, WANT_COLOR_AT_BIT); }
|
||||
if (_wantDelta) { setAtBit(bitItems, WANT_DELTA_AT_BIT); }
|
||||
if (_wantOcclusionCulling) { setAtBit(bitItems, WANT_OCCLUSION_CULLING_BIT); }
|
||||
if (_wantCompression) { setAtBit(bitItems, WANT_COMPRESSION); }
|
||||
|
||||
*destinationBuffer++ = bitItems;
|
||||
|
||||
|
@ -122,10 +124,11 @@ int VoxelQuery::parseData(unsigned char* sourceBuffer, int numBytes) {
|
|||
// voxel sending features...
|
||||
unsigned char bitItems = 0;
|
||||
bitItems = (unsigned char)*sourceBuffer++;
|
||||
_wantLowResMoving = oneAtBit(bitItems, WANT_LOW_RES_MOVING_BIT);
|
||||
_wantColor = oneAtBit(bitItems, WANT_COLOR_AT_BIT);
|
||||
_wantDelta = oneAtBit(bitItems, WANT_DELTA_AT_BIT);
|
||||
_wantLowResMoving = oneAtBit(bitItems, WANT_LOW_RES_MOVING_BIT);
|
||||
_wantColor = oneAtBit(bitItems, WANT_COLOR_AT_BIT);
|
||||
_wantDelta = oneAtBit(bitItems, WANT_DELTA_AT_BIT);
|
||||
_wantOcclusionCulling = oneAtBit(bitItems, WANT_OCCLUSION_CULLING_BIT);
|
||||
_wantCompression = oneAtBit(bitItems, WANT_COMPRESSION);
|
||||
|
||||
// desired Max Voxel PPS
|
||||
memcpy(&_maxVoxelPPS, sourceBuffer, sizeof(_maxVoxelPPS));
|
||||
|
|
|
@ -28,7 +28,8 @@
|
|||
const int WANT_LOW_RES_MOVING_BIT = 0;
|
||||
const int WANT_COLOR_AT_BIT = 1;
|
||||
const int WANT_DELTA_AT_BIT = 2;
|
||||
const int WANT_OCCLUSION_CULLING_BIT = 3; // 4th bit
|
||||
const int WANT_OCCLUSION_CULLING_BIT = 3;
|
||||
const int WANT_COMPRESSION = 4; // 5th bit
|
||||
|
||||
class VoxelQuery : public NodeData {
|
||||
Q_OBJECT
|
||||
|
@ -68,6 +69,7 @@ public:
|
|||
bool getWantDelta() const { return _wantDelta; }
|
||||
bool getWantLowResMoving() const { return _wantLowResMoving; }
|
||||
bool getWantOcclusionCulling() const { return _wantOcclusionCulling; }
|
||||
bool getWantCompression() const { return _wantCompression; }
|
||||
int getMaxVoxelPacketsPerSecond() const { return _maxVoxelPPS; }
|
||||
float getVoxelSizeScale() const { return _voxelSizeScale; }
|
||||
int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; }
|
||||
|
@ -77,6 +79,7 @@ public slots:
|
|||
void setWantColor(bool wantColor) { _wantColor = wantColor; }
|
||||
void setWantDelta(bool wantDelta) { _wantDelta = wantDelta; }
|
||||
void setWantOcclusionCulling(bool wantOcclusionCulling) { _wantOcclusionCulling = wantOcclusionCulling; }
|
||||
void setWantCompression(bool wantCompression) { _wantCompression = wantCompression; }
|
||||
void setMaxVoxelPacketsPerSecond(int maxVoxelPPS) { _maxVoxelPPS = maxVoxelPPS; }
|
||||
void setVoxelSizeScale(float voxelSizeScale) { _voxelSizeScale = voxelSizeScale; }
|
||||
void setBoundaryLevelAdjust(int boundaryLevelAdjust) { _boundaryLevelAdjust = boundaryLevelAdjust; }
|
||||
|
@ -98,6 +101,7 @@ protected:
|
|||
bool _wantDelta;
|
||||
bool _wantLowResMoving;
|
||||
bool _wantOcclusionCulling;
|
||||
bool _wantCompression;
|
||||
int _maxVoxelPPS;
|
||||
float _voxelSizeScale; /// used for LOD calculations
|
||||
int _boundaryLevelAdjust; /// used for LOD calculations
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <PacketHeaders.h>
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include "VoxelPacketData.h"
|
||||
#include "VoxelNode.h"
|
||||
#include "VoxelSceneStats.h"
|
||||
|
||||
|
@ -21,11 +22,21 @@ const int samples = 100;
|
|||
VoxelSceneStats::VoxelSceneStats() :
|
||||
_elapsedAverage(samples),
|
||||
_bitsPerVoxelAverage(samples),
|
||||
_incomingFlightTimeAverage(samples),
|
||||
_jurisdictionRoot(NULL)
|
||||
{
|
||||
reset();
|
||||
_isReadyToSend = false;
|
||||
_isStarted = false;
|
||||
_lastFullTotalEncodeTime = 0;
|
||||
_lastFullElapsed = 0;
|
||||
_incomingPacket = 0;
|
||||
_incomingBytes = 0;
|
||||
_incomingWastedBytes = 0;
|
||||
_incomingLastSequence = 0;
|
||||
_incomingOutOfOrder = 0;
|
||||
_incomingLikelyLost = 0;
|
||||
|
||||
}
|
||||
|
||||
// copy constructor
|
||||
|
@ -42,6 +53,9 @@ VoxelSceneStats& VoxelSceneStats::operator=(const VoxelSceneStats& other) {
|
|||
|
||||
void VoxelSceneStats::copyFromOther(const VoxelSceneStats& other) {
|
||||
_totalEncodeTime = other._totalEncodeTime;
|
||||
_elapsed = other._elapsed;
|
||||
_lastFullTotalEncodeTime = other._lastFullTotalEncodeTime;
|
||||
_lastFullElapsed = other._lastFullElapsed;
|
||||
_encodeStart = other._encodeStart;
|
||||
|
||||
_packets = other._packets;
|
||||
|
@ -116,6 +130,13 @@ void VoxelSceneStats::copyFromOther(const VoxelSceneStats& other) {
|
|||
_jurisdictionEndNodes.push_back(endNodeCodeCopy);
|
||||
}
|
||||
}
|
||||
|
||||
_incomingPacket = other._incomingPacket;
|
||||
_incomingBytes = other._incomingBytes;
|
||||
_incomingWastedBytes = other._incomingWastedBytes;
|
||||
_incomingLastSequence = other._incomingLastSequence;
|
||||
_incomingOutOfOrder = other._incomingOutOfOrder;
|
||||
_incomingLikelyLost = other._incomingLikelyLost;
|
||||
}
|
||||
|
||||
|
||||
|
@ -174,6 +195,11 @@ void VoxelSceneStats::sceneCompleted() {
|
|||
_end = usecTimestampNow();
|
||||
_elapsed = _end - _start;
|
||||
_elapsedAverage.updateAverage((float)_elapsed);
|
||||
|
||||
if (_isFullScene) {
|
||||
_lastFullElapsed = _elapsed;
|
||||
_lastFullTotalEncodeTime = _totalEncodeTime;
|
||||
}
|
||||
|
||||
_statsMessageLength = packIntoMessage(_statsMessage, sizeof(_statsMessage));
|
||||
_isReadyToSend = true;
|
||||
|
@ -465,8 +491,15 @@ int VoxelSceneStats::unpackFromMessage(unsigned char* sourceBuffer, int availabl
|
|||
sourceBuffer += sizeof(_elapsed);
|
||||
memcpy(&_totalEncodeTime, sourceBuffer, sizeof(_totalEncodeTime));
|
||||
sourceBuffer += sizeof(_totalEncodeTime);
|
||||
|
||||
memcpy(&_isFullScene, sourceBuffer, sizeof(_isFullScene));
|
||||
sourceBuffer += sizeof(_isFullScene);
|
||||
|
||||
if (_isFullScene) {
|
||||
_lastFullElapsed = _elapsed;
|
||||
_lastFullTotalEncodeTime = _totalEncodeTime;
|
||||
}
|
||||
|
||||
memcpy(&_isMoving, sourceBuffer, sizeof(_isMoving));
|
||||
sourceBuffer += sizeof(_isMoving);
|
||||
memcpy(&_packets, sourceBuffer, sizeof(_packets));
|
||||
|
@ -765,4 +798,45 @@ const char* VoxelSceneStats::getItemValue(Item item) {
|
|||
return _itemValueBuffer;
|
||||
}
|
||||
|
||||
void VoxelSceneStats::trackIncomingVoxelPacket(unsigned char* messageData, ssize_t messageLength, bool wasStatsPacket) {
|
||||
_incomingPacket++;
|
||||
_incomingBytes += messageLength;
|
||||
if (!wasStatsPacket) {
|
||||
_incomingWastedBytes += (MAX_PACKET_SIZE - messageLength);
|
||||
}
|
||||
|
||||
int numBytesPacketHeader = numBytesForPacketHeader(messageData);
|
||||
unsigned char* dataAt = messageData + numBytesPacketHeader;
|
||||
|
||||
//VOXEL_PACKET_FLAGS flags = (*(VOXEL_PACKET_FLAGS*)(dataAt));
|
||||
dataAt += sizeof(VOXEL_PACKET_FLAGS);
|
||||
VOXEL_PACKET_SEQUENCE sequence = (*(VOXEL_PACKET_SEQUENCE*)dataAt);
|
||||
dataAt += sizeof(VOXEL_PACKET_SEQUENCE);
|
||||
|
||||
VOXEL_PACKET_SENT_TIME sentAt = (*(VOXEL_PACKET_SENT_TIME*)dataAt);
|
||||
dataAt += sizeof(VOXEL_PACKET_SENT_TIME);
|
||||
|
||||
//bool packetIsColored = oneAtBit(flags, PACKET_IS_COLOR_BIT);
|
||||
//bool packetIsCompressed = oneAtBit(flags, PACKET_IS_COMPRESSED_BIT);
|
||||
|
||||
VOXEL_PACKET_SENT_TIME arrivedAt = usecTimestampNow();
|
||||
int flightTime = arrivedAt - sentAt;
|
||||
const int USECS_PER_MSEC = 1000;
|
||||
float flightTimeMsecs = flightTime / USECS_PER_MSEC;
|
||||
_incomingFlightTimeAverage.updateAverage(flightTimeMsecs);
|
||||
|
||||
|
||||
// detect out of order packets
|
||||
if (sequence < _incomingLastSequence) {
|
||||
_incomingOutOfOrder++;
|
||||
}
|
||||
|
||||
// detect likely lost packets
|
||||
VOXEL_PACKET_SEQUENCE expected = _incomingLastSequence+1;
|
||||
if (sequence > expected) {
|
||||
_incomingLikelyLost++;
|
||||
}
|
||||
|
||||
_incomingLastSequence = sequence;
|
||||
}
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ public:
|
|||
|
||||
/// Call when beginning the computation of a scene. Initializes internal structures
|
||||
void sceneStarted(bool fullScene, bool moving, VoxelNode* root, JurisdictionMap* jurisdictionMap);
|
||||
bool getIsSceneStarted() const { return _isStarted; }
|
||||
|
||||
/// Call when the computation of a scene is completed. Finalizes internal structures
|
||||
void sceneCompleted();
|
||||
|
@ -145,7 +146,22 @@ public:
|
|||
unsigned long getTotalVoxels() const { return _totalVoxels; }
|
||||
unsigned long getTotalInternal() const { return _totalInternal; }
|
||||
unsigned long getTotalLeaves() const { return _totalLeaves; }
|
||||
|
||||
unsigned long getTotalEncodeTime() const { return _totalEncodeTime; }
|
||||
unsigned long getElapsedTime() const { return _elapsed; }
|
||||
|
||||
unsigned long getLastFullTotalEncodeTime() const { return _lastFullTotalEncodeTime; }
|
||||
unsigned long getLastFullElapsedTime() const { return _lastFullElapsed; }
|
||||
|
||||
// Used in client implementations to track individual voxel packets
|
||||
void trackIncomingVoxelPacket(unsigned char* messageData, ssize_t messageLength, bool wasStatsPacket);
|
||||
|
||||
unsigned int getIncomingPackets() const { return _incomingPacket; }
|
||||
unsigned long getIncomingBytes() const { return _incomingBytes; }
|
||||
unsigned long getIncomingWastedBytes() const { return _incomingWastedBytes; }
|
||||
unsigned int getIncomingOutOfOrder() const { return _incomingOutOfOrder; }
|
||||
unsigned int getIncomingLikelyLost() const { return _incomingLikelyLost; }
|
||||
float getIncomingFlightTimeAverage() { return _incomingFlightTimeAverage.getAverage(); }
|
||||
|
||||
private:
|
||||
|
||||
void copyFromOther(const VoxelSceneStats& other);
|
||||
|
@ -155,15 +171,17 @@ private:
|
|||
int _statsMessageLength;
|
||||
|
||||
// scene timing data in usecs
|
||||
bool _isStarted;
|
||||
bool _isStarted;
|
||||
uint64_t _start;
|
||||
uint64_t _end;
|
||||
uint64_t _elapsed;
|
||||
uint64_t _lastFullElapsed;
|
||||
|
||||
SimpleMovingAverage _elapsedAverage;
|
||||
SimpleMovingAverage _bitsPerVoxelAverage;
|
||||
|
||||
uint64_t _totalEncodeTime;
|
||||
uint64_t _lastFullTotalEncodeTime;
|
||||
uint64_t _encodeStart;
|
||||
|
||||
// scene voxel related data
|
||||
|
@ -228,6 +246,15 @@ private:
|
|||
unsigned long _bytes;
|
||||
unsigned int _passes;
|
||||
|
||||
// incoming packets stats
|
||||
unsigned int _incomingPacket;
|
||||
unsigned long _incomingBytes;
|
||||
unsigned long _incomingWastedBytes;
|
||||
unsigned int _incomingLastSequence;
|
||||
unsigned int _incomingOutOfOrder;
|
||||
unsigned int _incomingLikelyLost;
|
||||
SimpleMovingAverage _incomingFlightTimeAverage;
|
||||
|
||||
// features related items
|
||||
bool _isMoving;
|
||||
bool _isFullScene;
|
||||
|
|
|
@ -170,7 +170,7 @@ VoxelNode* VoxelTree::nodeForOctalCode(VoxelNode* ancestorNode,
|
|||
}
|
||||
|
||||
// returns the node created!
|
||||
VoxelNode* VoxelTree::createMissingNode(VoxelNode* lastParentNode, unsigned char* codeToReach) {
|
||||
VoxelNode* VoxelTree::createMissingNode(VoxelNode* lastParentNode, const unsigned char* codeToReach) {
|
||||
int indexOfNewChild = branchIndexWithDescendant(lastParentNode->getOctalCode(), codeToReach);
|
||||
// If this parent node is a leaf, then you know the child path doesn't exist, so deal with
|
||||
// breaking up the leaf first, which will also create a child path
|
||||
|
@ -193,7 +193,7 @@ VoxelNode* VoxelTree::createMissingNode(VoxelNode* lastParentNode, unsigned char
|
|||
}
|
||||
}
|
||||
|
||||
int VoxelTree::readNodeData(VoxelNode* destinationNode, unsigned char* nodeData, int bytesLeftToRead,
|
||||
int VoxelTree::readNodeData(VoxelNode* destinationNode, const unsigned char* nodeData, int bytesLeftToRead,
|
||||
ReadBitstreamToTreeParams& args) {
|
||||
// give this destination node the child mask from the packet
|
||||
const unsigned char ALL_CHILDREN_ASSUMED_TO_EXIST = 0xFF;
|
||||
|
@ -294,10 +294,10 @@ int VoxelTree::readNodeData(VoxelNode* destinationNode, unsigned char* nodeData,
|
|||
return bytesRead;
|
||||
}
|
||||
|
||||
void VoxelTree::readBitstreamToTree(unsigned char * bitstream, unsigned long int bufferSizeBytes,
|
||||
void VoxelTree::readBitstreamToTree(const unsigned char * bitstream, unsigned long int bufferSizeBytes,
|
||||
ReadBitstreamToTreeParams& args) {
|
||||
int bytesRead = 0;
|
||||
unsigned char* bitstreamAt = bitstream;
|
||||
const unsigned char* bitstreamAt = bitstream;
|
||||
|
||||
// If destination node is not included, set it to root
|
||||
if (!args.destinationNode) {
|
||||
|
@ -328,6 +328,7 @@ void VoxelTree::readBitstreamToTree(unsigned char * bitstream, unsigned long int
|
|||
int octalCodeBytes = bytesRequiredForCodeLength(*bitstreamAt);
|
||||
int theseBytesRead = 0;
|
||||
theseBytesRead += octalCodeBytes;
|
||||
|
||||
theseBytesRead += readNodeData(bitstreamRootNode, bitstreamAt + octalCodeBytes,
|
||||
bufferSizeBytes - (bytesRead + octalCodeBytes), args);
|
||||
|
||||
|
@ -352,16 +353,16 @@ void VoxelTree::deleteVoxelAt(float x, float y, float z, float s) {
|
|||
|
||||
class DeleteVoxelCodeFromTreeArgs {
|
||||
public:
|
||||
bool collapseEmptyTrees;
|
||||
unsigned char* codeBuffer;
|
||||
int lengthOfCode;
|
||||
bool deleteLastChild;
|
||||
bool pathChanged;
|
||||
bool collapseEmptyTrees;
|
||||
const unsigned char* codeBuffer;
|
||||
int lengthOfCode;
|
||||
bool deleteLastChild;
|
||||
bool pathChanged;
|
||||
};
|
||||
|
||||
// Note: uses the codeColorBuffer format, but the color's are ignored, because
|
||||
// this only finds and deletes the node from the tree.
|
||||
void VoxelTree::deleteVoxelCodeFromTree(unsigned char* codeBuffer, bool collapseEmptyTrees) {
|
||||
void VoxelTree::deleteVoxelCodeFromTree(const unsigned char* codeBuffer, bool collapseEmptyTrees) {
|
||||
// recurse the tree while decoding the codeBuffer, once you find the node in question, recurse
|
||||
// back and implement color reaveraging, and marking of lastChanged
|
||||
DeleteVoxelCodeFromTreeArgs args;
|
||||
|
@ -490,13 +491,13 @@ void VoxelTree::eraseAllVoxels() {
|
|||
|
||||
class ReadCodeColorBufferToTreeArgs {
|
||||
public:
|
||||
unsigned char* codeColorBuffer;
|
||||
int lengthOfCode;
|
||||
bool destructive;
|
||||
bool pathChanged;
|
||||
const unsigned char* codeColorBuffer;
|
||||
int lengthOfCode;
|
||||
bool destructive;
|
||||
bool pathChanged;
|
||||
};
|
||||
|
||||
void VoxelTree::readCodeColorBufferToTree(unsigned char* codeColorBuffer, bool destructive) {
|
||||
void VoxelTree::readCodeColorBufferToTree(const unsigned char* codeColorBuffer, bool destructive) {
|
||||
ReadCodeColorBufferToTreeArgs args;
|
||||
args.codeColorBuffer = codeColorBuffer;
|
||||
args.lengthOfCode = numberOfThreeBitSectionsInCode(codeColorBuffer);
|
||||
|
@ -576,7 +577,7 @@ void VoxelTree::readCodeColorBufferToTreeRecursion(VoxelNode* node, void* extraD
|
|||
}
|
||||
}
|
||||
|
||||
void VoxelTree::processRemoveVoxelBitstream(unsigned char* bitstream, int bufferSizeBytes) {
|
||||
void VoxelTree::processRemoveVoxelBitstream(const unsigned char* bitstream, int bufferSizeBytes) {
|
||||
//unsigned short int itemNumber = (*((unsigned short int*)&bitstream[sizeof(PACKET_HEADER)]));
|
||||
|
||||
int numBytesPacketHeader = numBytesForPacketHeader(bitstream);
|
||||
|
@ -1020,8 +1021,9 @@ bool VoxelTree::findCapsulePenetration(const glm::vec3& start, const glm::vec3&
|
|||
return args.found;
|
||||
}
|
||||
|
||||
int VoxelTree::encodeTreeBitstream(VoxelNode* node, unsigned char* outputBuffer, int availableBytes, VoxelNodeBag& bag,
|
||||
EncodeBitstreamParams& params) {
|
||||
int VoxelTree::encodeTreeBitstream(VoxelNode* node,
|
||||
VoxelPacketData* packetData, VoxelNodeBag& bag,
|
||||
EncodeBitstreamParams& params) {
|
||||
|
||||
// How many bytes have we written so far at this level;
|
||||
int bytesWritten = 0;
|
||||
|
@ -1029,6 +1031,7 @@ int VoxelTree::encodeTreeBitstream(VoxelNode* node, unsigned char* outputBuffer,
|
|||
// you can't call this without a valid node
|
||||
if (!node) {
|
||||
qDebug("WARNING! encodeTreeBitstream() called with node=NULL\n");
|
||||
params.stopReason = EncodeBitstreamParams::NULL_NODE;
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
|
@ -1037,30 +1040,37 @@ int VoxelTree::encodeTreeBitstream(VoxelNode* node, unsigned char* outputBuffer,
|
|||
// If we're at a node that is out of view, then we can return, because no nodes below us will be in view!
|
||||
if (params.viewFrustum && !node->isInView(*params.viewFrustum)) {
|
||||
doneEncoding(node);
|
||||
params.stopReason = EncodeBitstreamParams::OUT_OF_VIEW;
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
|
||||
// write the octal code
|
||||
bool roomForOctalCode = false; // assume the worst
|
||||
int codeLength;
|
||||
if (params.chopLevels) {
|
||||
unsigned char* newCode = chopOctalCode(node->getOctalCode(), params.chopLevels);
|
||||
roomForOctalCode = packetData->startSubTree(newCode);
|
||||
|
||||
if (newCode) {
|
||||
codeLength = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(newCode));
|
||||
memcpy(outputBuffer, newCode, codeLength);
|
||||
delete newCode;
|
||||
delete newCode;
|
||||
} else {
|
||||
codeLength = 1; // chopped to root!
|
||||
*outputBuffer = 0; // root
|
||||
codeLength = 1;
|
||||
}
|
||||
} else {
|
||||
roomForOctalCode = packetData->startSubTree(node->getOctalCode());
|
||||
codeLength = bytesRequiredForCodeLength(numberOfThreeBitSectionsInCode(node->getOctalCode()));
|
||||
memcpy(outputBuffer, node->getOctalCode(), codeLength);
|
||||
}
|
||||
|
||||
outputBuffer += codeLength; // move the pointer
|
||||
// If the octalcode couldn't fit, then we can return, because no nodes below us will fit...
|
||||
if (!roomForOctalCode) {
|
||||
doneEncoding(node);
|
||||
bag.insert(node); // add the node back to the bag so it will eventually get included
|
||||
params.stopReason = EncodeBitstreamParams::DIDNT_FIT;
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
bytesWritten += codeLength; // keep track of byte count
|
||||
availableBytes -= codeLength; // keep track or remaining space
|
||||
|
||||
|
||||
int currentEncodeLevel = 0;
|
||||
|
||||
// record some stats, this is the one node that we won't record below in the recursion function, so we need to
|
||||
|
@ -1069,7 +1079,7 @@ int VoxelTree::encodeTreeBitstream(VoxelNode* node, unsigned char* outputBuffer,
|
|||
params.stats->traversed(node);
|
||||
}
|
||||
|
||||
int childBytesWritten = encodeTreeBitstreamRecursion(node, outputBuffer, availableBytes, bag, params, currentEncodeLevel);
|
||||
int childBytesWritten = encodeTreeBitstreamRecursion(node, packetData, bag, params, currentEncodeLevel);
|
||||
|
||||
// if childBytesWritten == 1 then something went wrong... that's not possible
|
||||
assert(childBytesWritten != 1);
|
||||
|
@ -1078,6 +1088,7 @@ int VoxelTree::encodeTreeBitstream(VoxelNode* node, unsigned char* outputBuffer,
|
|||
// couldn't be written... so reset them here... This isn't true for the non-color included case
|
||||
if (params.includeColor && childBytesWritten == 2) {
|
||||
childBytesWritten = 0;
|
||||
//params.stopReason = EncodeBitstreamParams::UNKNOWN; // possibly should be DIDNT_FIT...
|
||||
}
|
||||
|
||||
// if we wrote child bytes, then return our result of all bytes written
|
||||
|
@ -1086,21 +1097,30 @@ int VoxelTree::encodeTreeBitstream(VoxelNode* node, unsigned char* outputBuffer,
|
|||
} else {
|
||||
// otherwise... if we didn't write any child bytes, then pretend like we also didn't write our octal code
|
||||
bytesWritten = 0;
|
||||
//params.stopReason = EncodeBitstreamParams::DIDNT_FIT;
|
||||
}
|
||||
|
||||
if (bytesWritten == 0) {
|
||||
packetData->discardSubTree();
|
||||
} else {
|
||||
packetData->endSubTree();
|
||||
}
|
||||
|
||||
doneEncoding(node);
|
||||
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outputBuffer, int availableBytes, VoxelNodeBag& bag,
|
||||
int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node,
|
||||
VoxelPacketData* packetData, VoxelNodeBag& bag,
|
||||
EncodeBitstreamParams& params, int& currentEncodeLevel) const {
|
||||
|
||||
// How many bytes have we written so far at this level;
|
||||
int bytesAtThisLevel = 0;
|
||||
|
||||
// you can't call this without a valid node
|
||||
if (!node) {
|
||||
qDebug("WARNING! encodeTreeBitstreamRecursion() called with node=NULL\n");
|
||||
params.stopReason = EncodeBitstreamParams::NULL_NODE;
|
||||
return bytesAtThisLevel;
|
||||
}
|
||||
|
||||
|
@ -1111,6 +1131,7 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp
|
|||
|
||||
// If we've reached our max Search Level, then stop searching.
|
||||
if (currentEncodeLevel >= params.maxEncodeLevel) {
|
||||
params.stopReason = EncodeBitstreamParams::TOO_DEEP;
|
||||
return bytesAtThisLevel;
|
||||
}
|
||||
|
||||
|
@ -1119,6 +1140,7 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp
|
|||
// here's how it works... if we're currently above our root jurisdiction, then we proceed normally.
|
||||
// but once we're in our own jurisdiction, then we need to make sure we're not below it.
|
||||
if (JurisdictionMap::BELOW == params.jurisdictionMap->isMyJurisdiction(node->getOctalCode(), CHECK_NODE_ONLY)) {
|
||||
params.stopReason = EncodeBitstreamParams::OUT_OF_JURISDICTION;
|
||||
return bytesAtThisLevel;
|
||||
}
|
||||
}
|
||||
|
@ -1134,6 +1156,7 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp
|
|||
if (params.stats) {
|
||||
params.stats->skippedDistance(node);
|
||||
}
|
||||
params.stopReason = EncodeBitstreamParams::LOD_SKIP;
|
||||
return bytesAtThisLevel;
|
||||
}
|
||||
|
||||
|
@ -1144,6 +1167,7 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp
|
|||
if (params.stats) {
|
||||
params.stats->skippedOutOfView(node);
|
||||
}
|
||||
params.stopReason = EncodeBitstreamParams::OUT_OF_VIEW;
|
||||
return bytesAtThisLevel;
|
||||
}
|
||||
|
||||
|
@ -1183,6 +1207,7 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp
|
|||
if (params.stats) {
|
||||
params.stats->skippedWasInView(node);
|
||||
}
|
||||
params.stopReason = EncodeBitstreamParams::WAS_IN_VIEW;
|
||||
return bytesAtThisLevel;
|
||||
}
|
||||
|
||||
|
@ -1193,13 +1218,13 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp
|
|||
if (params.stats) {
|
||||
params.stats->skippedNoChange(node);
|
||||
}
|
||||
params.stopReason = EncodeBitstreamParams::NO_CHANGE;
|
||||
return bytesAtThisLevel;
|
||||
}
|
||||
|
||||
// If the user also asked for occlusion culling, check if this node is occluded, but only if it's not a leaf.
|
||||
// leaf occlusion is handled down below when we check child nodes
|
||||
if (params.wantOcclusionCulling && !node->isLeaf()) {
|
||||
//node->printDebugDetails("upper section, params.wantOcclusionCulling... node=");
|
||||
AABox voxelBox = node->getAABox();
|
||||
voxelBox.scale(TREE_SCALE);
|
||||
VoxelProjectedPolygon* voxelPolygon = new VoxelProjectedPolygon(params.viewFrustum->getProjectedPolygon(voxelBox));
|
||||
|
@ -1207,14 +1232,13 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp
|
|||
// In order to check occlusion culling, the shadow has to be "all in view" otherwise, we will ignore occlusion
|
||||
// culling and proceed as normal
|
||||
if (voxelPolygon->getAllInView()) {
|
||||
//node->printDebugDetails("upper section, voxelPolygon->getAllInView() node=");
|
||||
|
||||
CoverageMapStorageResult result = params.map->checkMap(voxelPolygon, false);
|
||||
delete voxelPolygon; // cleanup
|
||||
if (result == OCCLUDED) {
|
||||
if (params.stats) {
|
||||
params.stats->skippedOccluded(node);
|
||||
}
|
||||
params.stopReason = EncodeBitstreamParams::OCCLUDED;
|
||||
return bytesAtThisLevel;
|
||||
}
|
||||
} else {
|
||||
|
@ -1234,15 +1258,10 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp
|
|||
unsigned char childrenExistInTreeBits = 0;
|
||||
unsigned char childrenExistInPacketBits = 0;
|
||||
unsigned char childrenColoredBits = 0;
|
||||
|
||||
const int CHILD_COLOR_MASK_BYTES = sizeof(childrenColoredBits);
|
||||
const int BYTES_PER_COLOR = 3;
|
||||
const int CHILD_TREE_EXISTS_BYTES = sizeof(childrenExistInTreeBits) + sizeof(childrenExistInPacketBits);
|
||||
const int MAX_LEVEL_BYTES = CHILD_COLOR_MASK_BYTES + NUMBER_OF_CHILDREN * BYTES_PER_COLOR + CHILD_TREE_EXISTS_BYTES;
|
||||
|
||||
// Make our local buffer large enough to handle writing at this level in case we need to.
|
||||
unsigned char thisLevelBuffer[MAX_LEVEL_BYTES];
|
||||
unsigned char* writeToThisLevelBuffer = &thisLevelBuffer[0];
|
||||
LevelDetails thisLevelKey = packetData->startLevel();
|
||||
|
||||
int inViewCount = 0;
|
||||
int inViewNotLeafCount = 0;
|
||||
|
@ -1272,11 +1291,6 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp
|
|||
|
||||
if (params.wantOcclusionCulling) {
|
||||
if (childNode) {
|
||||
// chance to optimize, doesn't need to be actual distance!! Could be distance squared
|
||||
//float distanceSquared = childNode->distanceSquareToPoint(point);
|
||||
//qDebug("recurseNodeWithOperationDistanceSorted() CHECKING child[%d] point=%f,%f center=%f,%f distance=%f...\n", i, point.x, point.y, center.x, center.y, distance);
|
||||
//childNode->printDebugDetails("");
|
||||
|
||||
float distance = params.viewFrustum ? childNode->distanceToCamera(*params.viewFrustum) : 0;
|
||||
|
||||
currentCount = insertIntoSortedArrays((void*)childNode, distance, i,
|
||||
|
@ -1405,6 +1419,7 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp
|
|||
if (!childWasInView ||
|
||||
(params.deltaViewFrustum &&
|
||||
childNode->hasChangedSince(params.lastViewFrustumSent - CHANGE_FUDGE))){
|
||||
|
||||
childrenColoredBits += (1 << (7 - originalIndex));
|
||||
inViewWithColorCount++;
|
||||
} else {
|
||||
|
@ -1423,20 +1438,28 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp
|
|||
}
|
||||
}
|
||||
}
|
||||
*writeToThisLevelBuffer = childrenColoredBits;
|
||||
writeToThisLevelBuffer += sizeof(childrenColoredBits); // move the pointer
|
||||
bytesAtThisLevel += sizeof(childrenColoredBits); // keep track of byte count
|
||||
if (params.stats) {
|
||||
params.stats->colorBitsWritten();
|
||||
|
||||
bool continueThisLevel = true;
|
||||
continueThisLevel = packetData->appendBitMask(childrenColoredBits);
|
||||
|
||||
if (continueThisLevel) {
|
||||
bytesAtThisLevel += sizeof(childrenColoredBits); // keep track of byte count
|
||||
if (params.stats) {
|
||||
params.stats->colorBitsWritten();
|
||||
}
|
||||
}
|
||||
|
||||
// write the color data...
|
||||
if (params.includeColor) {
|
||||
if (continueThisLevel && params.includeColor) {
|
||||
for (int i = 0; i < NUMBER_OF_CHILDREN; i++) {
|
||||
if (oneAtBit(childrenColoredBits, i)) {
|
||||
VoxelNode* childNode = node->getChildAtIndex(i);
|
||||
memcpy(writeToThisLevelBuffer, &childNode->getColor(), BYTES_PER_COLOR);
|
||||
writeToThisLevelBuffer += BYTES_PER_COLOR; // move the pointer for color
|
||||
continueThisLevel = packetData->appendColor(childNode->getColor());
|
||||
|
||||
if (!continueThisLevel) {
|
||||
break; // no point in continuing
|
||||
}
|
||||
|
||||
bytesAtThisLevel += BYTES_PER_COLOR; // keep track of byte count for color
|
||||
|
||||
// don't need to check childNode here, because we can't get here with no childNode
|
||||
|
@ -1450,43 +1473,31 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp
|
|||
|
||||
// if the caller wants to include childExistsBits, then include them even if not in view, put them before the
|
||||
// childrenExistInPacketBits, so that the lower code can properly repair the packet exists bits
|
||||
if (params.includeExistsBits) {
|
||||
*writeToThisLevelBuffer = childrenExistInTreeBits;
|
||||
writeToThisLevelBuffer += sizeof(childrenExistInTreeBits); // move the pointer
|
||||
bytesAtThisLevel += sizeof(childrenExistInTreeBits); // keep track of byte count
|
||||
if (params.stats) {
|
||||
params.stats->existsBitsWritten();
|
||||
if (continueThisLevel && params.includeExistsBits) {
|
||||
continueThisLevel = packetData->appendBitMask(childrenExistInTreeBits);
|
||||
if (continueThisLevel) {
|
||||
bytesAtThisLevel += sizeof(childrenExistInTreeBits); // keep track of byte count
|
||||
if (params.stats) {
|
||||
params.stats->existsBitsWritten();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// write the child exist bits
|
||||
*writeToThisLevelBuffer = childrenExistInPacketBits;
|
||||
bytesAtThisLevel += sizeof(childrenExistInPacketBits); // keep track of byte count
|
||||
if (params.stats) {
|
||||
params.stats->existsInPacketBitsWritten();
|
||||
if (continueThisLevel) {
|
||||
continueThisLevel = packetData->appendBitMask(childrenExistInPacketBits);
|
||||
if (continueThisLevel) {
|
||||
bytesAtThisLevel += sizeof(childrenExistInPacketBits); // keep track of byte count
|
||||
if (params.stats) {
|
||||
params.stats->existsInPacketBitsWritten();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// We only need to keep digging, if there is at least one child that is inView, and not a leaf.
|
||||
keepDiggingDeeper = (inViewNotLeafCount > 0);
|
||||
|
||||
// If we have enough room to copy our local results into the buffer, then do so...
|
||||
if (availableBytes >= bytesAtThisLevel) {
|
||||
memcpy(outputBuffer, &thisLevelBuffer[0], bytesAtThisLevel);
|
||||
|
||||
outputBuffer += bytesAtThisLevel;
|
||||
availableBytes -= bytesAtThisLevel;
|
||||
} else {
|
||||
bag.insert(node);
|
||||
|
||||
// don't need to check node here, because we can't get here with no node
|
||||
if (params.stats) {
|
||||
params.stats->didntFit(node);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (keepDiggingDeeper) {
|
||||
if (continueThisLevel && keepDiggingDeeper) {
|
||||
// at this point, we need to iterate the children who are in view, even if not colored
|
||||
// and we need to determine if there's a deeper tree below them that we care about.
|
||||
//
|
||||
|
@ -1496,16 +1507,16 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp
|
|||
// write our childExistsBits as a place holder. Then let each potential tree have a go at it. If they
|
||||
// write something, we keep them in the bits, if they don't, we take them out.
|
||||
//
|
||||
// we know the last thing we wrote to the outputBuffer was our childrenExistInPacketBits. Let's remember where that was!
|
||||
unsigned char* childExistsPlaceHolder = outputBuffer-sizeof(childrenExistInPacketBits);
|
||||
// we know the last thing we wrote to the packet was our childrenExistInPacketBits. Let's remember where that was!
|
||||
int childExistsPlaceHolder = packetData->getUncompressedByteOffset(sizeof(childrenExistInPacketBits));
|
||||
|
||||
// we are also going to recurse these child trees in "distance" sorted order, but we need to pack them in the
|
||||
// final packet in standard order. So what we're going to do is keep track of how big each subtree was in bytes,
|
||||
// and then later reshuffle these sections of our output buffer back into normal order. This allows us to make
|
||||
// a single recursive pass in distance sorted order, but retain standard order in our encoded packet
|
||||
int recursiveSliceSizes[NUMBER_OF_CHILDREN];
|
||||
unsigned char* recursiveSliceStarts[NUMBER_OF_CHILDREN];
|
||||
unsigned char* firstRecursiveSlice = outputBuffer;
|
||||
const unsigned char* recursiveSliceStarts[NUMBER_OF_CHILDREN];
|
||||
int firstRecursiveSliceOffset = packetData->getUncompressedByteOffset();
|
||||
int allSlicesSize = 0;
|
||||
|
||||
// for each child node in Distance sorted order..., check to see if they exist, are colored, and in view, and if so
|
||||
|
@ -1518,10 +1529,22 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp
|
|||
|
||||
int thisLevel = currentEncodeLevel;
|
||||
// remember this for reshuffling
|
||||
recursiveSliceStarts[originalIndex] = outputBuffer;
|
||||
recursiveSliceStarts[originalIndex] = packetData->getUncompressedData() + packetData->getUncompressedSize();
|
||||
|
||||
int childTreeBytesOut = encodeTreeBitstreamRecursion(childNode, outputBuffer, availableBytes, bag,
|
||||
params, thisLevel);
|
||||
int childTreeBytesOut = 0;
|
||||
|
||||
// XXXBHG - Note, this seems like the correct logic here, if we included the color in this packet, then
|
||||
// the LOD logic determined that the child nodes would not be visible... and if so, we shouldn't recurse
|
||||
// them further. But... for some time now the code has included and recursed into these child nodes, which
|
||||
// would likely still send the child content, even though the client wouldn't render it. This change is
|
||||
// a major savings (~30%) and it seems to work correctly. But I want us to discuss as a group when we do
|
||||
// a voxel protocol review.
|
||||
//
|
||||
// This only applies in the view frustum case, in other cases, like file save and copy/past where
|
||||
// no viewFrustum was requested, we still want to recurse the child tree.
|
||||
if (!params.viewFrustum || !oneAtBit(childrenColoredBits, originalIndex)) {
|
||||
childTreeBytesOut = encodeTreeBitstreamRecursion(childNode, packetData, bag, params, thisLevel);
|
||||
}
|
||||
|
||||
// remember this for reshuffling
|
||||
recursiveSliceSizes[originalIndex] = childTreeBytesOut;
|
||||
|
@ -1555,21 +1578,24 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp
|
|||
//}
|
||||
|
||||
bytesAtThisLevel += childTreeBytesOut;
|
||||
availableBytes -= childTreeBytesOut;
|
||||
outputBuffer += childTreeBytesOut;
|
||||
|
||||
// If we had previously started writing, and if the child DIDN'T write any bytes,
|
||||
// then we want to remove their bit from the childExistsPlaceHolder bitmask
|
||||
if (childTreeBytesOut == 0) {
|
||||
// remove this child's bit...
|
||||
childrenExistInPacketBits -= (1 << (7 - originalIndex));
|
||||
// repair the child exists mask
|
||||
*childExistsPlaceHolder = childrenExistInPacketBits;
|
||||
|
||||
// repair the child exists mask
|
||||
continueThisLevel = packetData->updatePriorBitMask(childExistsPlaceHolder, childrenExistInPacketBits);
|
||||
|
||||
// If this is the last of the child exists bits, then we're actually be rolling out the entire tree
|
||||
if (params.stats && childrenExistInPacketBits == 0) {
|
||||
params.stats->childBitsRemoved(params.includeExistsBits, params.includeColor);
|
||||
}
|
||||
|
||||
if (!continueThisLevel) {
|
||||
break; // can't continue...
|
||||
}
|
||||
|
||||
// Note: no need to move the pointer, cause we already stored this
|
||||
} // end if (childTreeBytesOut == 0)
|
||||
|
@ -1577,8 +1603,8 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp
|
|||
} // end for
|
||||
|
||||
// reshuffle here...
|
||||
if (params.wantOcclusionCulling) {
|
||||
unsigned char tempReshuffleBuffer[MAX_VOXEL_PACKET_SIZE];
|
||||
if (continueThisLevel && params.wantOcclusionCulling) {
|
||||
unsigned char tempReshuffleBuffer[MAX_VOXEL_UNCOMRESSED_PACKET_SIZE];
|
||||
|
||||
unsigned char* tempBufferTo = &tempReshuffleBuffer[0]; // this is our temporary destination
|
||||
|
||||
|
@ -1588,7 +1614,7 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp
|
|||
for (int originalIndex = 0; originalIndex < NUMBER_OF_CHILDREN; originalIndex++) {
|
||||
if (oneAtBit(childrenExistInPacketBits, originalIndex)) {
|
||||
int thisSliceSize = recursiveSliceSizes[originalIndex];
|
||||
unsigned char* thisSliceStarts = recursiveSliceStarts[originalIndex];
|
||||
const unsigned char* thisSliceStarts = recursiveSliceStarts[originalIndex];
|
||||
|
||||
memcpy(tempBufferTo, thisSliceStarts, thisSliceSize);
|
||||
tempBufferTo += thisSliceSize;
|
||||
|
@ -1596,11 +1622,40 @@ int VoxelTree::encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outp
|
|||
}
|
||||
|
||||
// now that all slices are back in the correct order, copy them to the correct output buffer
|
||||
memcpy(firstRecursiveSlice, &tempReshuffleBuffer[0], allSlicesSize);
|
||||
continueThisLevel = packetData->updatePriorBytes(firstRecursiveSliceOffset, &tempReshuffleBuffer[0], allSlicesSize);
|
||||
}
|
||||
} // end keepDiggingDeeper
|
||||
|
||||
// At this point all our BitMasks are complete... so let's output them to see how they compare...
|
||||
/**
|
||||
printf("This Level's BitMasks: childInTree:");
|
||||
outputBits(childrenExistInTreeBits, false, true);
|
||||
printf(" childInPacket:");
|
||||
outputBits(childrenExistInPacketBits, false, true);
|
||||
printf(" childrenColored:");
|
||||
outputBits(childrenColoredBits, false, true);
|
||||
printf("\n");
|
||||
**/
|
||||
|
||||
// if we were unable to fit this level in our packet, then rewind and add it to the node bag for
|
||||
// sending later...
|
||||
if (continueThisLevel) {
|
||||
continueThisLevel = packetData->endLevel(thisLevelKey);
|
||||
} else {
|
||||
packetData->discardLevel(thisLevelKey);
|
||||
}
|
||||
|
||||
if (!continueThisLevel) {
|
||||
bag.insert(node);
|
||||
|
||||
// don't need to check node here, because we can't get here with no node
|
||||
if (params.stats) {
|
||||
params.stats->didntFit(node);
|
||||
}
|
||||
|
||||
|
||||
} // end keepDiggingDeeper
|
||||
params.stopReason = EncodeBitstreamParams::DIDNT_FIT;
|
||||
bytesAtThisLevel = 0; // didn't fit
|
||||
}
|
||||
|
||||
return bytesAtThisLevel;
|
||||
}
|
||||
|
@ -1806,18 +1861,36 @@ void VoxelTree::writeToSVOFile(const char* fileName, VoxelNode* node) {
|
|||
nodeBag.insert(rootNode);
|
||||
}
|
||||
|
||||
static unsigned char outputBuffer[MAX_VOXEL_PACKET_SIZE - 1]; // save on allocs by making this static
|
||||
static VoxelPacketData packetData;
|
||||
int bytesWritten = 0;
|
||||
bool lastPacketWritten = false;
|
||||
|
||||
while (!nodeBag.isEmpty()) {
|
||||
VoxelNode* subTree = nodeBag.extract();
|
||||
|
||||
|
||||
lockForRead(); // do tree locking down here so that we have shorter slices and less thread contention
|
||||
EncodeBitstreamParams params(INT_MAX, IGNORE_VIEW_FRUSTUM, WANT_COLOR, NO_EXISTS_BITS);
|
||||
bytesWritten = encodeTreeBitstream(subTree, &outputBuffer[0], MAX_VOXEL_PACKET_SIZE - 1, nodeBag, params);
|
||||
bytesWritten = encodeTreeBitstream(subTree, &packetData, nodeBag, params);
|
||||
unlock();
|
||||
file.write((const char*)&outputBuffer[0], bytesWritten);
|
||||
|
||||
// if bytesWritten == 0, then it means that the subTree couldn't fit, and so we should reset the packet
|
||||
// and reinsert the node in our bag and try again...
|
||||
if (bytesWritten == 0) {
|
||||
if (packetData.hasContent()) {
|
||||
file.write((const char*)packetData.getFinalizedData(), packetData.getFinalizedSize());
|
||||
lastPacketWritten = true;
|
||||
}
|
||||
packetData.reset(); // is there a better way to do this? could we fit more?
|
||||
nodeBag.insert(subTree);
|
||||
} else {
|
||||
lastPacketWritten = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!lastPacketWritten) {
|
||||
file.write((const char*)packetData.getFinalizedData(), packetData.getFinalizedSize());
|
||||
}
|
||||
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
|
@ -1841,19 +1914,18 @@ void VoxelTree::copySubTreeIntoNewTree(VoxelNode* startNode, VoxelTree* destinat
|
|||
chopLevels = numberOfThreeBitSectionsInCode(startNode->getOctalCode());
|
||||
}
|
||||
|
||||
static unsigned char outputBuffer[MAX_VOXEL_PACKET_SIZE - 1]; // save on allocs by making this static
|
||||
static VoxelPacketData packetData;
|
||||
int bytesWritten = 0;
|
||||
|
||||
while (!nodeBag.isEmpty()) {
|
||||
VoxelNode* subTree = nodeBag.extract();
|
||||
|
||||
packetData.reset(); // reset the packet between usage
|
||||
// ask our tree to write a bitsteam
|
||||
EncodeBitstreamParams params(INT_MAX, IGNORE_VIEW_FRUSTUM, WANT_COLOR, NO_EXISTS_BITS, chopLevels);
|
||||
bytesWritten = encodeTreeBitstream(subTree, &outputBuffer[0], MAX_VOXEL_PACKET_SIZE - 1, nodeBag, params);
|
||||
|
||||
bytesWritten = encodeTreeBitstream(subTree, &packetData, nodeBag, params);
|
||||
// ask destination tree to read the bitstream
|
||||
ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS);
|
||||
destinationTree->readBitstreamToTree(&outputBuffer[0], bytesWritten, args);
|
||||
destinationTree->readBitstreamToTree(packetData.getUncompressedData(), packetData.getUncompressedSize(), args);
|
||||
}
|
||||
|
||||
VoxelNode* destinationStartNode;
|
||||
|
@ -1870,20 +1942,22 @@ void VoxelTree::copyFromTreeIntoSubTree(VoxelTree* sourceTree, VoxelNode* destin
|
|||
// If we were given a specific node, start from there, otherwise start from root
|
||||
nodeBag.insert(sourceTree->rootNode);
|
||||
|
||||
static unsigned char outputBuffer[MAX_VOXEL_PACKET_SIZE - 1]; // save on allocs by making this static
|
||||
static VoxelPacketData packetData;
|
||||
int bytesWritten = 0;
|
||||
|
||||
while (!nodeBag.isEmpty()) {
|
||||
VoxelNode* subTree = nodeBag.extract();
|
||||
|
||||
packetData.reset(); // reset between usage
|
||||
|
||||
// ask our tree to write a bitsteam
|
||||
EncodeBitstreamParams params(INT_MAX, IGNORE_VIEW_FRUSTUM, WANT_COLOR, NO_EXISTS_BITS);
|
||||
bytesWritten = sourceTree->encodeTreeBitstream(subTree, &outputBuffer[0], MAX_VOXEL_PACKET_SIZE - 1, nodeBag, params);
|
||||
bytesWritten = sourceTree->encodeTreeBitstream(subTree, &packetData, nodeBag, params);
|
||||
|
||||
// ask destination tree to read the bitstream
|
||||
bool wantImportProgress = true;
|
||||
ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS, destinationNode, 0, wantImportProgress);
|
||||
readBitstreamToTree(&outputBuffer[0], bytesWritten, args);
|
||||
readBitstreamToTree(packetData.getUncompressedData(), packetData.getUncompressedSize(), args);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1911,26 +1985,26 @@ void VoxelTree::doneEncoding(VoxelNode* node) {
|
|||
emptyDeleteQueue();
|
||||
}
|
||||
|
||||
void VoxelTree::startDeleting(unsigned char* code) {
|
||||
void VoxelTree::startDeleting(const unsigned char* code) {
|
||||
pthread_mutex_lock(&_deleteSetLock);
|
||||
_codesBeingDeleted.insert(code);
|
||||
pthread_mutex_unlock(&_deleteSetLock);
|
||||
}
|
||||
|
||||
void VoxelTree::doneDeleting(unsigned char* code) {
|
||||
void VoxelTree::doneDeleting(const unsigned char* code) {
|
||||
pthread_mutex_lock(&_deleteSetLock);
|
||||
_codesBeingDeleted.erase(code);
|
||||
pthread_mutex_unlock(&_deleteSetLock);
|
||||
}
|
||||
|
||||
bool VoxelTree::isEncoding(unsigned char* codeBuffer) {
|
||||
bool VoxelTree::isEncoding(const unsigned char* codeBuffer) {
|
||||
pthread_mutex_lock(&_encodeSetLock);
|
||||
bool isEncoding = (_codesBeingEncoded.find(codeBuffer) != _codesBeingEncoded.end());
|
||||
pthread_mutex_unlock(&_encodeSetLock);
|
||||
return isEncoding;
|
||||
}
|
||||
|
||||
void VoxelTree::queueForLaterDelete(unsigned char* codeBuffer) {
|
||||
void VoxelTree::queueForLaterDelete(const unsigned char* codeBuffer) {
|
||||
pthread_mutex_lock(&_deletePendingSetLock);
|
||||
_codesPendingDelete.insert(codeBuffer);
|
||||
pthread_mutex_unlock(&_deletePendingSetLock);
|
||||
|
@ -1938,8 +2012,8 @@ void VoxelTree::queueForLaterDelete(unsigned char* codeBuffer) {
|
|||
|
||||
void VoxelTree::emptyDeleteQueue() {
|
||||
pthread_mutex_lock(&_deletePendingSetLock);
|
||||
for (std::set<unsigned char*>::iterator i = _codesPendingDelete.begin(); i != _codesPendingDelete.end(); ++i) {
|
||||
unsigned char* codeToDelete = *i;
|
||||
for (std::set<const unsigned char*>::iterator i = _codesPendingDelete.begin(); i != _codesPendingDelete.end(); ++i) {
|
||||
const unsigned char* codeToDelete = *i;
|
||||
_codesBeingDeleted.erase(codeToDelete);
|
||||
deleteVoxelCodeFromTree(codeToDelete, COLLAPSE_EMPTY_TREE);
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include "ViewFrustum.h"
|
||||
#include "VoxelNode.h"
|
||||
#include "VoxelNodeBag.h"
|
||||
#include "VoxelPacketData.h"
|
||||
#include "VoxelSceneStats.h"
|
||||
#include "VoxelEditPacketSender.h"
|
||||
|
||||
|
@ -65,6 +66,21 @@ public:
|
|||
CoverageMap* map;
|
||||
JurisdictionMap* jurisdictionMap;
|
||||
|
||||
// output hints from the encode process
|
||||
typedef enum {
|
||||
UNKNOWN,
|
||||
DIDNT_FIT,
|
||||
NULL_NODE,
|
||||
TOO_DEEP,
|
||||
OUT_OF_JURISDICTION,
|
||||
LOD_SKIP,
|
||||
OUT_OF_VIEW,
|
||||
WAS_IN_VIEW,
|
||||
NO_CHANGE,
|
||||
OCCLUDED
|
||||
} reason;
|
||||
reason stopReason;
|
||||
|
||||
EncodeBitstreamParams(
|
||||
int maxEncodeLevel = INT_MAX,
|
||||
const ViewFrustum* viewFrustum = IGNORE_VIEW_FRUSTUM,
|
||||
|
@ -96,8 +112,27 @@ public:
|
|||
forceSendScene(forceSendScene),
|
||||
stats(stats),
|
||||
map(map),
|
||||
jurisdictionMap(jurisdictionMap)
|
||||
jurisdictionMap(jurisdictionMap),
|
||||
stopReason(UNKNOWN)
|
||||
{}
|
||||
|
||||
void displayStopReason() {
|
||||
printf("StopReason: ");
|
||||
switch (stopReason) {
|
||||
default:
|
||||
case UNKNOWN: printf("UNKNOWN\n"); break;
|
||||
|
||||
case DIDNT_FIT: printf("DIDNT_FIT\n"); break;
|
||||
case NULL_NODE: printf("NULL_NODE\n"); break;
|
||||
case TOO_DEEP: printf("TOO_DEEP\n"); break;
|
||||
case OUT_OF_JURISDICTION: printf("OUT_OF_JURISDICTION\n"); break;
|
||||
case LOD_SKIP: printf("LOD_SKIP\n"); break;
|
||||
case OUT_OF_VIEW: printf("OUT_OF_VIEW\n"); break;
|
||||
case WAS_IN_VIEW: printf("WAS_IN_VIEW\n"); break;
|
||||
case NO_CHANGE: printf("NO_CHANGE\n"); break;
|
||||
case OCCLUDED: printf("OCCLUDED\n"); break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class ReadBitstreamToTreeParams {
|
||||
|
@ -143,10 +178,10 @@ public:
|
|||
|
||||
void eraseAllVoxels();
|
||||
|
||||
void processRemoveVoxelBitstream(unsigned char* bitstream, int bufferSizeBytes);
|
||||
void readBitstreamToTree(unsigned char* bitstream, unsigned long int bufferSizeBytes, ReadBitstreamToTreeParams& args);
|
||||
void readCodeColorBufferToTree(unsigned char* codeColorBuffer, bool destructive = false);
|
||||
void deleteVoxelCodeFromTree(unsigned char* codeBuffer, bool collapseEmptyTrees = DONT_COLLAPSE);
|
||||
void processRemoveVoxelBitstream(const unsigned char* bitstream, int bufferSizeBytes);
|
||||
void readBitstreamToTree(const unsigned char* bitstream, unsigned long int bufferSizeBytes, ReadBitstreamToTreeParams& args);
|
||||
void readCodeColorBufferToTree(const unsigned char* codeColorBuffer, bool destructive = false);
|
||||
void deleteVoxelCodeFromTree(const unsigned char* codeBuffer, bool collapseEmptyTrees = DONT_COLLAPSE);
|
||||
void printTreeForDebugging(VoxelNode* startNode);
|
||||
void reaverageVoxelColors(VoxelNode* startNode);
|
||||
|
||||
|
@ -163,7 +198,7 @@ public:
|
|||
void recurseTreeWithOperationDistanceSorted(RecurseVoxelTreeOperation operation,
|
||||
const glm::vec3& point, void* extraData=NULL);
|
||||
|
||||
int encodeTreeBitstream(VoxelNode* node, unsigned char* outputBuffer, int availableBytes, VoxelNodeBag& bag,
|
||||
int encodeTreeBitstream(VoxelNode* node, VoxelPacketData* packetData, VoxelNodeBag& bag,
|
||||
EncodeBitstreamParams& params) ;
|
||||
|
||||
bool isDirty() const { return _isDirty; }
|
||||
|
@ -221,14 +256,16 @@ private:
|
|||
void deleteVoxelCodeFromTreeRecursion(VoxelNode* node, void* extraData);
|
||||
void readCodeColorBufferToTreeRecursion(VoxelNode* node, void* extraData);
|
||||
|
||||
int encodeTreeBitstreamRecursion(VoxelNode* node, unsigned char* outputBuffer, int availableBytes, VoxelNodeBag& bag,
|
||||
int encodeTreeBitstreamRecursion(VoxelNode* node,
|
||||
VoxelPacketData* packetData, VoxelNodeBag& bag,
|
||||
EncodeBitstreamParams& params, int& currentEncodeLevel) const;
|
||||
|
||||
static bool countVoxelsOperation(VoxelNode* node, void* extraData);
|
||||
|
||||
VoxelNode* nodeForOctalCode(VoxelNode* ancestorNode, const unsigned char* needleCode, VoxelNode** parentOfFoundNode) const;
|
||||
VoxelNode* createMissingNode(VoxelNode* lastParentNode, unsigned char* deepestCodeToCreate);
|
||||
int readNodeData(VoxelNode *destinationNode, unsigned char* nodeData, int bufferSizeBytes, ReadBitstreamToTreeParams& args);
|
||||
VoxelNode* createMissingNode(VoxelNode* lastParentNode, const unsigned char* codeToReach);
|
||||
int readNodeData(VoxelNode *destinationNode, const unsigned char* nodeData,
|
||||
int bufferSizeBytes, ReadBitstreamToTreeParams& args);
|
||||
|
||||
bool _isDirty;
|
||||
unsigned long int _nodesChangedFromBitstream;
|
||||
|
@ -246,26 +283,26 @@ private:
|
|||
/// Called to indicate that a VoxelNode is done being encoded.
|
||||
void doneEncoding(VoxelNode* node);
|
||||
/// Is the Octal Code currently being deleted?
|
||||
bool isEncoding(unsigned char* codeBuffer);
|
||||
bool isEncoding(const unsigned char* codeBuffer);
|
||||
|
||||
/// Octal Codes of any subtrees currently being deleted. While any of these codes is being deleted, ancestors and
|
||||
/// descendants of them can not be encoded.
|
||||
std::set<unsigned char*> _codesBeingDeleted;
|
||||
std::set<const unsigned char*> _codesBeingDeleted;
|
||||
/// mutex lock to protect the deleting set
|
||||
pthread_mutex_t _deleteSetLock;
|
||||
|
||||
/// Called to indicate that an octal code is in the process of being deleted.
|
||||
void startDeleting(unsigned char* code);
|
||||
void startDeleting(const unsigned char* code);
|
||||
/// Called to indicate that an octal code is done being deleted.
|
||||
void doneDeleting(unsigned char* code);
|
||||
void doneDeleting(const unsigned char* code);
|
||||
/// Octal Codes that were attempted to be deleted but couldn't be because they were actively being encoded, and were
|
||||
/// instead queued for later delete
|
||||
std::set<unsigned char*> _codesPendingDelete;
|
||||
std::set<const unsigned char*> _codesPendingDelete;
|
||||
/// mutex lock to protect the deleting set
|
||||
pthread_mutex_t _deletePendingSetLock;
|
||||
|
||||
/// Adds an Octal Code to the set of codes that needs to be deleted
|
||||
void queueForLaterDelete(unsigned char* codeBuffer);
|
||||
void queueForLaterDelete(const unsigned char* codeBuffer);
|
||||
/// flushes out any Octal Codes that had to be queued
|
||||
void emptyDeleteQueue();
|
||||
|
||||
|
|
Loading…
Reference in a new issue