Merge pull request #1295 from ZappoMan/compressed_packets

Major improvements to Voxel Packet "packing" and compression
This commit is contained in:
Philip Rosedale 2013-11-29 10:06:36 -08:00
commit 14129cd86d
27 changed files with 1556 additions and 452 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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__) */

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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__) */

View file

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

View file

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

View file

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

View file

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

View file

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

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

View 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__) */

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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