Files
renderdoc/renderdoc/core/target_control.cpp
T
baldurk 934800793a Improve workflow for capture import/export
* If the export doesn't need buffers, we export directly from the loaded
  capture file instead of re-loading it.
* Add progress bars for the load step so it shows what's happening
  instead of looking stalled.
* Reduce compression rate on XML+ZIP buffers as it took too long trying
  to compress when exporting large captures.
2018-02-19 00:52:13 +00:00

802 lines
21 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015-2018 Baldur Karlsson
* Copyright (c) 2014 Crytek
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#include "android/android.h"
#include "api/replay/renderdoc_replay.h"
#include "core/core.h"
#include "jpeg-compressor/jpgd.h"
#include "os/os_specific.h"
#include "serialise/serialiser.h"
static const uint32_t TargetControlProtocolVersion = 2;
enum PacketType : uint32_t
{
ePacket_Noop = 1,
ePacket_Handshake,
ePacket_Busy,
ePacket_NewCapture,
ePacket_APIUse,
ePacket_TriggerCapture,
ePacket_CopyCapture,
ePacket_DeleteCapture,
ePacket_QueueCapture,
ePacket_NewChild,
ePacket_CaptureProgress,
};
DECLARE_REFLECTION_ENUM(PacketType);
template <>
std::string DoStringise(const PacketType &el)
{
BEGIN_ENUM_STRINGISE(PacketType);
{
STRINGISE_ENUM_NAMED(ePacket_Noop, "No-op");
STRINGISE_ENUM_NAMED(ePacket_Handshake, "Handshake");
STRINGISE_ENUM_NAMED(ePacket_Busy, "Busy");
STRINGISE_ENUM_NAMED(ePacket_NewCapture, "New Capture");
STRINGISE_ENUM_NAMED(ePacket_APIUse, "API Use");
STRINGISE_ENUM_NAMED(ePacket_TriggerCapture, "Trigger Capture");
STRINGISE_ENUM_NAMED(ePacket_CopyCapture, "Copy Capture");
STRINGISE_ENUM_NAMED(ePacket_DeleteCapture, "Delete Capture");
STRINGISE_ENUM_NAMED(ePacket_QueueCapture, "Queue Capture");
STRINGISE_ENUM_NAMED(ePacket_NewChild, "New Child");
}
END_ENUM_STRINGISE();
}
#define WRITE_DATA_SCOPE() WriteSerialiser &ser = writer;
#define READ_DATA_SCOPE() ReadSerialiser &ser = reader;
void RenderDoc::TargetControlClientThread(uint32_t version, Network::Socket *client)
{
Threading::KeepModuleAlive();
WriteSerialiser writer(new StreamWriter(client, Ownership::Nothing), Ownership::Stream);
ReadSerialiser reader(new StreamReader(client, Ownership::Nothing), Ownership::Stream);
writer.SetStreamingMode(true);
reader.SetStreamingMode(true);
std::string target = RenderDoc::Inst().GetCurrentTarget();
uint32_t mypid = Process::GetCurrentPID();
{
WRITE_DATA_SCOPE();
SCOPED_SERIALISE_CHUNK(ePacket_Handshake);
SERIALISE_ELEMENT(TargetControlProtocolVersion);
SERIALISE_ELEMENT(target);
SERIALISE_ELEMENT(mypid);
}
if(writer.IsErrored())
{
SAFE_DELETE(client);
{
SCOPED_LOCK(RenderDoc::Inst().m_SingleClientLock);
RenderDoc::Inst().m_SingleClientName = "";
}
Threading::ReleaseModuleExitThread();
return;
}
float captureProgress = -1.0f;
RenderDoc::Inst().SetProgressCallback<CaptureProgress>(
[&captureProgress](float p) { captureProgress = p; });
const int pingtime = 1000; // ping every 1000ms
const int ticktime = 10; // tick every 10ms
const int progresstime = 100; // update capture progress every 100ms
int curtime = 0;
std::vector<CaptureData> captures;
std::vector<pair<uint32_t, uint32_t> > children;
std::map<RDCDriver, bool> drivers;
float prevCaptureProgress = captureProgress;
while(client)
{
if(RenderDoc::Inst().m_ControlClientThreadShutdown || (client && !client->Connected()))
{
SAFE_DELETE(client);
break;
}
Threading::Sleep(ticktime);
curtime += ticktime;
std::map<RDCDriver, bool> curdrivers = RenderDoc::Inst().GetActiveDrivers();
std::vector<CaptureData> caps = RenderDoc::Inst().GetCaptures();
std::vector<pair<uint32_t, uint32_t> > childprocs = RenderDoc::Inst().GetChildProcesses();
if(curdrivers != drivers)
{
// find the first difference, either a new key or a key with a different value, and send it.
RDCDriver driver = RDCDriver::Unknown;
bool presenting = false;
// search for new drivers
for(auto it = curdrivers.begin(); it != curdrivers.end(); it++)
{
if(drivers.find(it->first) == drivers.end() || drivers[it->first] != it->second)
{
driver = it->first;
presenting = it->second;
break;
}
}
RDCASSERTNOTEQUAL(driver, RDCDriver::Unknown);
if(driver != RDCDriver::Unknown)
drivers[driver] = presenting;
bool supported =
RenderDoc::Inst().HasRemoteDriver(driver) || RenderDoc::Inst().HasReplayDriver(driver);
WRITE_DATA_SCOPE();
{
SCOPED_SERIALISE_CHUNK(ePacket_APIUse);
SERIALISE_ELEMENT(driver);
SERIALISE_ELEMENT(presenting);
SERIALISE_ELEMENT(supported);
}
}
else if(caps.size() != captures.size())
{
uint32_t idx = (uint32_t)captures.size();
captures.push_back(caps[idx]);
std::string path = FileIO::GetFullPathname(captures.back().path);
bytebuf buf;
ICaptureFile *file = RENDERDOC_OpenCaptureFile();
if(file->OpenFile(captures.back().path.c_str(), "rdc", NULL) == ReplayStatus::Succeeded)
{
buf = file->GetThumbnail(FileType::JPG, 0).data;
}
file->Shutdown();
WRITE_DATA_SCOPE();
{
SCOPED_SERIALISE_CHUNK(ePacket_NewCapture);
SERIALISE_ELEMENT(idx);
SERIALISE_ELEMENT(captures.back().timestamp);
SERIALISE_ELEMENT(path);
SERIALISE_ELEMENT(buf);
}
}
else if(childprocs.size() != children.size())
{
uint32_t idx = (uint32_t)children.size();
children.push_back(childprocs[idx]);
WRITE_DATA_SCOPE();
{
SCOPED_SERIALISE_CHUNK(ePacket_NewChild);
SERIALISE_ELEMENT(children.back().first);
SERIALISE_ELEMENT(children.back().second);
}
}
else if(prevCaptureProgress != captureProgress)
{
if(captureProgress == 1.0f || captureProgress == -1.0f)
captureProgress = -1.0f;
// send progress packets at reduced rate (not every tick), or if the progress is finished.
// we don't need to ping while we're sending capture progress, so we re-use curtime
if(captureProgress == -1.0f || curtime > progresstime)
{
curtime = 0;
prevCaptureProgress = captureProgress;
WRITE_DATA_SCOPE();
{
SCOPED_SERIALISE_CHUNK(ePacket_CaptureProgress);
SERIALISE_ELEMENT(captureProgress);
}
}
}
if(curtime > pingtime)
{
WRITE_DATA_SCOPE();
{
SCOPED_SERIALISE_CHUNK(ePacket_Noop);
}
curtime = 0;
}
if(writer.IsErrored())
{
SAFE_DELETE(client);
continue;
}
if(client->IsRecvDataWaiting() || !reader.GetReader()->AtEnd())
{
PacketType type = reader.ReadChunk<PacketType>();
if(type == ePacket_TriggerCapture)
{
uint32_t numFrames = 1;
READ_DATA_SCOPE();
SERIALISE_ELEMENT(numFrames);
RenderDoc::Inst().TriggerCapture(numFrames);
}
else if(type == ePacket_QueueCapture)
{
uint32_t frameNum = 0;
uint32_t numFrames = 1;
READ_DATA_SCOPE();
SERIALISE_ELEMENT(frameNum);
SERIALISE_ELEMENT(numFrames);
for(uint32_t f = 0; f < numFrames; f++)
RenderDoc::Inst().QueueCapture(frameNum + f);
}
else if(type == ePacket_DeleteCapture)
{
uint32_t id;
READ_DATA_SCOPE();
SERIALISE_ELEMENT(id);
// this means it will be deleted on shutdown
RenderDoc::Inst().MarkCaptureRetrieved(id);
}
else if(type == ePacket_CopyCapture)
{
caps = RenderDoc::Inst().GetCaptures();
uint32_t id;
{
READ_DATA_SCOPE();
SERIALISE_ELEMENT(id);
}
if(id < caps.size())
{
WRITE_DATA_SCOPE();
SCOPED_SERIALISE_CHUNK(ePacket_CopyCapture);
SERIALISE_ELEMENT(id);
std::string filename = caps[id].path;
StreamReader fileStream(FileIO::fopen(filename.c_str(), "rb"));
ser.SerialiseStream(filename, fileStream);
if(fileStream.IsErrored() || ser.IsErrored())
SAFE_DELETE(client);
else
RenderDoc::Inst().MarkCaptureRetrieved(id);
}
}
reader.EndChunk();
if(reader.IsErrored())
SAFE_DELETE(client);
}
}
RenderDoc::Inst().SetProgressCallback<CaptureProgress>(RENDERDOC_ProgressCallback());
// give up our connection
{
SCOPED_LOCK(RenderDoc::Inst().m_SingleClientLock);
RenderDoc::Inst().m_SingleClientName = "";
}
Threading::ReleaseModuleExitThread();
}
void RenderDoc::TargetControlServerThread(Network::Socket *sock)
{
Threading::KeepModuleAlive();
RenderDoc::Inst().m_SingleClientName = "";
Threading::ThreadHandle clientThread = 0;
RenderDoc::Inst().m_ControlClientThreadShutdown = false;
while(!RenderDoc::Inst().m_TargetControlThreadShutdown)
{
Network::Socket *client = sock->AcceptClient(false);
if(client == NULL)
{
if(!sock->Connected())
{
RDCERR("Error in accept - shutting down server");
SAFE_DELETE(sock);
Threading::ReleaseModuleExitThread();
return;
}
Threading::Sleep(5);
continue;
}
std::string existingClient;
std::string newClient;
uint32_t version;
bool kick = false;
// receive handshake from client and get its name
{
ReadSerialiser ser(new StreamReader(client, Ownership::Nothing), Ownership::Stream);
PacketType type = ser.ReadChunk<PacketType>();
if(type != ePacket_Handshake)
{
SAFE_DELETE(client);
continue;
}
SERIALISE_ELEMENT(version);
SERIALISE_ELEMENT(newClient);
SERIALISE_ELEMENT(kick);
ser.EndChunk();
if(newClient.empty())
{
SAFE_DELETE(client);
continue;
}
}
// see if we have a client
{
SCOPED_LOCK(RenderDoc::Inst().m_SingleClientLock);
existingClient = RenderDoc::Inst().m_SingleClientName;
}
if(!existingClient.empty() && kick)
{
// forcibly close communication thread which will kill the connection
RenderDoc::Inst().m_ControlClientThreadShutdown = true;
Threading::JoinThread(clientThread);
Threading::CloseThread(clientThread);
clientThread = 0;
RenderDoc::Inst().m_ControlClientThreadShutdown = false;
existingClient = "";
}
if(existingClient.empty())
{
SCOPED_LOCK(RenderDoc::Inst().m_SingleClientLock);
RenderDoc::Inst().m_SingleClientName = newClient;
}
// if we've claimed client status, spawn a thread to communicate
if(existingClient.empty() || kick)
{
clientThread =
Threading::CreateThread([version, client] { TargetControlClientThread(version, client); });
continue;
}
else
{
// if we've been asked to kick the existing connection off
// reject this connection and tell them who is busy
WriteSerialiser ser(new StreamWriter(client, Ownership::Nothing), Ownership::Stream);
ser.SetStreamingMode(true);
std::string target = RenderDoc::Inst().GetCurrentTarget();
{
SCOPED_SERIALISE_CHUNK(ePacket_Busy);
SERIALISE_ELEMENT(TargetControlProtocolVersion);
SERIALISE_ELEMENT(target);
SERIALISE_ELEMENT(RenderDoc::Inst().m_SingleClientName);
}
// don't care about errors, we're going to close the connection either way
SAFE_DELETE(client);
}
}
RenderDoc::Inst().m_ControlClientThreadShutdown = true;
// don't join, just close the thread, as we can't wait while in the middle of module unloading
Threading::CloseThread(clientThread);
clientThread = 0;
SAFE_DELETE(sock);
Threading::ReleaseModuleExitThread();
}
struct TargetControl : public ITargetControl
{
public:
TargetControl(Network::Socket *sock, std::string clientName, bool forceConnection)
: m_Socket(sock),
reader(new StreamReader(sock, Ownership::Nothing), Ownership::Stream),
writer(new StreamWriter(sock, Ownership::Nothing), Ownership::Stream)
{
std::vector<byte> payload;
writer.SetStreamingMode(true);
reader.SetStreamingMode(true);
m_PID = 0;
{
WRITE_DATA_SCOPE();
{
SCOPED_SERIALISE_CHUNK(ePacket_Handshake);
SERIALISE_ELEMENT(TargetControlProtocolVersion);
SERIALISE_ELEMENT(clientName);
SERIALISE_ELEMENT(forceConnection);
}
if(writer.IsErrored())
{
SAFE_DELETE(m_Socket);
return;
}
}
PacketType type = reader.ReadChunk<PacketType>();
if(reader.IsErrored())
{
SAFE_DELETE(m_Socket);
return;
}
if(type != ePacket_Handshake && type != ePacket_Busy)
{
RDCERR("Expected handshake packet, got %d", type);
SAFE_DELETE(m_Socket);
}
// failed handshaking
if(m_Socket == NULL)
return;
uint32_t version = TargetControlProtocolVersion;
{
READ_DATA_SCOPE();
SERIALISE_ELEMENT(version);
SERIALISE_ELEMENT(m_Target);
SERIALISE_ELEMENT(m_PID);
}
reader.EndChunk();
if(type == ePacket_Handshake)
{
RDCLOG("Got remote handshake: %s [%u]", m_Target.c_str(), m_PID);
}
else if(type == ePacket_Busy)
{
RDCLOG("Got remote busy signal: %s owned by %s", m_Target.c_str(), m_BusyClient.c_str());
}
}
virtual ~TargetControl() {}
bool Connected() { return m_Socket != NULL && m_Socket->Connected(); }
void Shutdown()
{
SAFE_DELETE(m_Socket);
delete this;
}
const char *GetTarget() { return m_Target.c_str(); }
const char *GetAPI() { return m_API.c_str(); }
uint32_t GetPID() { return m_PID; }
const char *GetBusyClient() { return m_BusyClient.c_str(); }
void TriggerCapture(uint32_t numFrames)
{
WRITE_DATA_SCOPE();
SCOPED_SERIALISE_CHUNK(ePacket_TriggerCapture);
SERIALISE_ELEMENT(numFrames);
if(ser.IsErrored())
SAFE_DELETE(m_Socket);
}
void QueueCapture(uint32_t frameNumber, uint32_t numFrames)
{
WRITE_DATA_SCOPE();
SCOPED_SERIALISE_CHUNK(ePacket_QueueCapture);
SERIALISE_ELEMENT(frameNumber);
SERIALISE_ELEMENT(numFrames);
if(ser.IsErrored())
SAFE_DELETE(m_Socket);
}
void CopyCapture(uint32_t remoteID, const char *localpath)
{
WRITE_DATA_SCOPE();
SCOPED_SERIALISE_CHUNK(ePacket_CopyCapture);
SERIALISE_ELEMENT(remoteID);
if(ser.IsErrored())
{
SAFE_DELETE(m_Socket);
return;
}
m_CaptureCopies[remoteID] = localpath;
}
void DeleteCapture(uint32_t remoteID)
{
WRITE_DATA_SCOPE();
SCOPED_SERIALISE_CHUNK(ePacket_DeleteCapture);
SERIALISE_ELEMENT(remoteID);
if(ser.IsErrored())
SAFE_DELETE(m_Socket);
}
TargetControlMessage ReceiveMessage()
{
TargetControlMessage msg;
if(m_Socket == NULL)
{
msg.type = TargetControlMessageType::Disconnected;
return msg;
}
if(!m_Socket->IsRecvDataWaiting() && reader.GetReader()->AtEnd())
{
if(!m_Socket->Connected())
{
SAFE_DELETE(m_Socket);
msg.type = TargetControlMessageType::Disconnected;
}
else
{
Threading::Sleep(2);
msg.type = TargetControlMessageType::Noop;
}
return msg;
}
PacketType type = reader.ReadChunk<PacketType>();
if(reader.IsErrored())
{
SAFE_DELETE(m_Socket);
msg.type = TargetControlMessageType::Disconnected;
return msg;
}
else if(type == ePacket_Noop)
{
msg.type = TargetControlMessageType::Noop;
reader.EndChunk();
return msg;
}
else if(type == ePacket_Busy)
{
READ_DATA_SCOPE();
SERIALISE_ELEMENT(msg.busy.clientName).Named("Client Name");
SAFE_DELETE(m_Socket);
RDCLOG("Got busy signal: '%s", msg.busy.clientName.c_str());
msg.type = TargetControlMessageType::Busy;
return msg;
}
else if(type == ePacket_NewChild)
{
msg.type = TargetControlMessageType::NewChild;
READ_DATA_SCOPE();
SERIALISE_ELEMENT(msg.newChild.processId).Named("PID");
SERIALISE_ELEMENT(msg.newChild.ident).Named("Child ident");
RDCLOG("Got a new child process: %u %u", msg.newChild.processId, msg.newChild.ident);
reader.EndChunk();
return msg;
}
else if(type == ePacket_CaptureProgress)
{
msg.type = TargetControlMessageType::CaptureProgress;
READ_DATA_SCOPE();
SERIALISE_ELEMENT(msg.capProgress).Named("Capture Progress");
reader.EndChunk();
return msg;
}
else if(type == ePacket_NewCapture)
{
msg.type = TargetControlMessageType::NewCapture;
bytebuf thumbnail;
{
READ_DATA_SCOPE();
SERIALISE_ELEMENT(msg.newCapture.captureId).Named("Capture ID");
SERIALISE_ELEMENT(msg.newCapture.timestamp).Named("timestamp");
SERIALISE_ELEMENT(msg.newCapture.path).Named("path");
SERIALISE_ELEMENT(thumbnail);
}
msg.newCapture.local = FileIO::exists(msg.newCapture.path.c_str());
RDCLOG("Got a new capture: %d (time %llu) %d byte thumbnail", msg.newCapture.captureId,
msg.newCapture.timestamp, thumbnail.count());
int w = 0;
int h = 0;
int comp = 3;
byte *thumbpixels = jpgd::decompress_jpeg_image_from_memory(
thumbnail.data(), thumbnail.count(), &w, &h, &comp, 3);
if(w > 0 && h > 0 && thumbpixels)
{
msg.newCapture.thumbWidth = w;
msg.newCapture.thumbHeight = h;
msg.newCapture.thumbnail.assign(thumbpixels, w * h * 3);
}
else
{
msg.newCapture.thumbWidth = 0;
msg.newCapture.thumbHeight = 0;
}
free(thumbpixels);
reader.EndChunk();
return msg;
}
else if(type == ePacket_APIUse)
{
msg.type = TargetControlMessageType::RegisterAPI;
RDCDriver driver = RDCDriver::Unknown;
bool presenting = false;
bool supported = false;
READ_DATA_SCOPE();
SERIALISE_ELEMENT(driver);
SERIALISE_ELEMENT(presenting);
SERIALISE_ELEMENT(supported);
msg.apiUse.name = ToStr(driver);
msg.apiUse.presenting = presenting;
msg.apiUse.supported = supported;
if(presenting)
m_API = ToStr(driver);
RDCLOG("Used API: %s (%s & %s)", msg.apiUse.name.c_str(),
presenting ? "Presenting" : "Not presenting",
supported ? "supported" : "not supported");
reader.EndChunk();
return msg;
}
else if(type == ePacket_CopyCapture)
{
msg.type = TargetControlMessageType::CaptureCopied;
READ_DATA_SCOPE();
SERIALISE_ELEMENT(msg.newCapture.captureId).Named("Capture ID");
msg.newCapture.path = m_CaptureCopies[msg.newCapture.captureId];
StreamWriter streamWriter(FileIO::fopen(msg.newCapture.path.c_str(), "wb"), Ownership::Stream);
ser.SerialiseStream(msg.newCapture.path.c_str(), streamWriter, NULL);
if(reader.IsErrored())
{
SAFE_DELETE(m_Socket);
msg.type = TargetControlMessageType::Disconnected;
return msg;
}
m_CaptureCopies.erase(msg.newCapture.captureId);
reader.EndChunk();
return msg;
}
else
{
RDCERR("Unexpected packed received: %d", type);
SAFE_DELETE(m_Socket);
msg.type = TargetControlMessageType::Disconnected;
return msg;
}
}
private:
Network::Socket *m_Socket;
WriteSerialiser writer;
ReadSerialiser reader;
std::string m_Target, m_API, m_BusyClient;
uint32_t m_PID;
std::map<uint32_t, std::string> m_CaptureCopies;
};
extern "C" RENDERDOC_API ITargetControl *RENDERDOC_CC RENDERDOC_CreateTargetControl(
const char *host, uint32_t ident, const char *clientName, bool forceConnection)
{
std::string s = "localhost";
if(host != NULL && host[0] != '\0')
s = host;
bool android = false;
if(host != NULL && Android::IsHostADB(host))
{
android = true;
s = "127.0.0.1";
// we don't need the index or device ID here, because the port is already the right one
// forwarded to the right device.
}
Network::Socket *sock = Network::CreateClientSocket(s.c_str(), ident & 0xffff, 750);
if(sock == NULL)
return NULL;
TargetControl *remote = new TargetControl(sock, clientName, forceConnection != 0);
if(remote->Connected())
return remote;
delete remote;
return NULL;
}