diff --git a/renderdoc/api/replay/renderdoc_replay.h b/renderdoc/api/replay/renderdoc_replay.h index eec728df9..19233a4a5 100644 --- a/renderdoc/api/replay/renderdoc_replay.h +++ b/renderdoc/api/replay/renderdoc_replay.h @@ -488,6 +488,7 @@ DECLARE_REFLECTION_STRUCT(ResourceId); // here and document them immediately below. They can be linked to from anywhere by name. typedef std::function RENDERDOC_KillCallback; typedef std::function RENDERDOC_ProgressCallback; +typedef std::function &)> RENDERDOC_PreviewWindowCallback; DOCUMENT(R"(A stateful output handle that contains the current configuration for one particular view of the capture. This allows multiple outputs to run independently without interfering with each @@ -515,6 +516,19 @@ The different types are enumerated in :class:`ReplayOutputType`. :param float progress: The latest progress amount. +.. function:: PreviewWindowCallback() + + Not an actual member function - the signature for any ``PreviewWindowCallback`` callbacks. + + Called when a preview window could optionally be opened to display some information. It will be + called repeatedly with :paramref:`active` set to ``True`` to allow any platform-specific message + pumping. + + :param bool active: ``True`` if a preview window is active/opened, ``False`` if it has closed. + :return: The windowing data for a preview window, or empty/default values if no window should be + created. + :rtype: WindowingData + .. data:: NoResult No result was found in e.g. :meth:`PickVertex`. @@ -1858,9 +1872,12 @@ This function will block until a remote connection tells the server to shut down :param int port: The port to listen on, or the default port if 0. :param KillCallback killReplay: A callback that returns a ``bool`` indicating if the server should be shut down or not. +:param PreviewWindowCallback previewWindow: A callback that returns information for a preview window + when the server wants to display some preview of the ongoing replay. )"); extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_BecomeRemoteServer( - const char *listenhost, uint32_t port, RENDERDOC_KillCallback killReplay); + const char *listenhost, uint32_t port, RENDERDOC_KillCallback killReplay, + RENDERDOC_PreviewWindowCallback previewWindow); ////////////////////////////////////////////////////////////////////////// // Injection/execution capture functions. diff --git a/renderdoc/core/core.h b/renderdoc/core/core.h index 4ba37eb4f..def14cc20 100644 --- a/renderdoc/core/core.h +++ b/renderdoc/core/core.h @@ -387,7 +387,8 @@ public: bool IsReplayApp() const { return m_Replay; } const string &GetConfigSetting(string name) { return m_ConfigSettings[name]; } void SetConfigSetting(string name, string value) { m_ConfigSettings[name] = value; } - void BecomeRemoteServer(const char *listenhost, uint16_t port, RENDERDOC_KillCallback killReplay); + void BecomeRemoteServer(const char *listenhost, uint16_t port, RENDERDOC_KillCallback killReplay, + RENDERDOC_PreviewWindowCallback previewWindow); void SetCaptureOptions(const CaptureOptions &opts); const CaptureOptions &GetCaptureOptions() const { return m_Options; } diff --git a/renderdoc/core/remote_server.cpp b/renderdoc/core/remote_server.cpp index cfc1ca93b..da5044de7 100644 --- a/renderdoc/core/remote_server.cpp +++ b/renderdoc/core/remote_server.cpp @@ -146,7 +146,8 @@ static void InactiveRemoteClientThread(ClientThread *threadData) } } -static void ActiveRemoteClientThread(ClientThread *threadData) +static void ActiveRemoteClientThread(ClientThread *threadData, + RENDERDOC_PreviewWindowCallback previewWindow) { Network::Socket *&client = threadData->socket; @@ -197,7 +198,8 @@ static void ActiveRemoteClientThread(ClientThread *threadData) } std::vector tempFiles; - IRemoteDriver *driver = NULL; + IRemoteDriver *remoteDriver = NULL; + IReplayDriver *replayDriver = NULL; ReplayProxy *proxy = NULL; RDCFile *rdc = NULL; Callstack::StackResolver *resolver = NULL; @@ -229,6 +231,9 @@ static void ActiveRemoteClientThread(ClientThread *threadData) { reader.EndChunk(); + if(proxy) + proxy->RefreshPreviewWindow(); + WRITE_DATA_SCOPE(); SCOPED_SERIALISE_CHUNK(eRemoteServer_Ping); } @@ -378,7 +383,7 @@ static void ActiveRemoteClientThread(ClientThread *threadData) reader.EndChunk(); - RDCASSERT(driver == NULL && proxy == NULL && rdc == NULL); + RDCASSERT(remoteDriver == NULL && proxy == NULL && rdc == NULL); ReplayStatus status = ReplayStatus::InternalError; rdc = new RDCFile(); @@ -420,22 +425,32 @@ static void ActiveRemoteClientThread(ClientThread *threadData) } }); - status = RenderDoc::Inst().CreateRemoteDriver(rdc, &driver); + // if we have a replay driver, try to create it so we can display a local preview e.g. + if(RenderDoc::Inst().HasReplayDriver(rdc->GetDriver())) + { + status = RenderDoc::Inst().CreateReplayDriver(rdc, &replayDriver); + if(replayDriver) + remoteDriver = replayDriver; + } + else + { + status = RenderDoc::Inst().CreateRemoteDriver(rdc, &remoteDriver); + } - if(status != ReplayStatus::Succeeded || driver == NULL) + if(status != ReplayStatus::Succeeded || remoteDriver == NULL) { RDCERR("Failed to create remote driver for driver '%s'", rdc->GetDriverName().c_str()); } else { - status = driver->ReadLogInitialisation(rdc, false); + status = remoteDriver->ReadLogInitialisation(rdc, false); if(status != ReplayStatus::Succeeded) { RDCERR("Failed to initialise remote driver."); - driver->Shutdown(); - driver = NULL; + remoteDriver->Shutdown(); + remoteDriver = NULL; } else { @@ -445,7 +460,7 @@ static void ActiveRemoteClientThread(ClientThread *threadData) Threading::JoinThread(ticker); Threading::CloseThread(ticker); - proxy = new ReplayProxy(reader, writer, driver); + proxy = new ReplayProxy(reader, writer, remoteDriver, replayDriver, previewWindow); } } } @@ -692,11 +707,13 @@ static void ActiveRemoteClientThread(ClientThread *threadData) { reader.EndChunk(); - if(driver) - driver->Shutdown(); - driver = NULL; - SAFE_DELETE(proxy); + + if(remoteDriver) + remoteDriver->Shutdown(); + remoteDriver = NULL; + replayDriver = NULL; + SAFE_DELETE(rdc); SAFE_DELETE(resolver); } @@ -746,9 +763,12 @@ static void ActiveRemoteClientThread(ClientThread *threadData) } } - if(driver) - driver->Shutdown(); SAFE_DELETE(proxy); + + if(remoteDriver) + remoteDriver->Shutdown(); + remoteDriver = NULL; + replayDriver = NULL; SAFE_DELETE(rdc); SAFE_DELETE(resolver); @@ -766,7 +786,8 @@ static void ActiveRemoteClientThread(ClientThread *threadData) } void RenderDoc::BecomeRemoteServer(const char *listenhost, uint16_t port, - RENDERDOC_KillCallback killReplay) + RENDERDOC_KillCallback killReplay, + RENDERDOC_PreviewWindowCallback previewWindow) { Network::Socket *sock = Network::CreateServerSocket(listenhost, port, 1); @@ -934,8 +955,9 @@ void RenderDoc::BecomeRemoteServer(const char *listenhost, uint16_t port, activeClientData->socket = client; activeClientData->allowExecution = allowExecution; - activeClientData->thread = Threading::CreateThread( - [activeClientData]() { ActiveRemoteClientThread(activeClientData); }); + activeClientData->thread = Threading::CreateThread([activeClientData, previewWindow]() { + ActiveRemoteClientThread(activeClientData, previewWindow); + }); RDCLOG("Making active connection"); } diff --git a/renderdoc/core/replay_proxy.cpp b/renderdoc/core/replay_proxy.cpp index 001f93838..cdc4d0977 100644 --- a/renderdoc/core/replay_proxy.cpp +++ b/renderdoc/core/replay_proxy.cpp @@ -68,6 +68,8 @@ ReplayProxy::~ReplayProxy() { + ShutdownPreviewWindow(); + if(m_Proxy) m_Proxy->Shutdown(); m_Proxy = NULL; @@ -1295,6 +1297,9 @@ void ReplayProxy::Proxied_ReplayLog(ParamSerialiser ¶mser, ReturnSerialiser if(paramser.IsReading() && !paramser.IsErrored() && !m_IsErrored) m_Remote->ReplayLog(endEventID, replayType); + if(m_RemoteServer) + m_PreviewEvent = endEventID; + if(retser.IsReading()) { m_TextureProxyCache.clear(); @@ -1834,6 +1839,141 @@ void ReplayProxy::EnsureBufCached(ResourceId bufid) } } +const DrawcallDescription *ReplayProxy::FindDraw(const rdcarray &drawcallList, + uint32_t eventId) +{ + for(const DrawcallDescription &d : drawcallList) + { + if(!d.children.empty()) + { + const DrawcallDescription *draw = FindDraw(d.children, eventId); + if(draw != NULL) + return draw; + } + + if(d.eventId == eventId) + return &d; + } + + return NULL; +} + +void ReplayProxy::InitPreviewWindow() +{ + if(m_Replay && m_PreviewWindow) + { + WindowingData data = m_PreviewWindow(true, m_Replay->GetSupportedWindowSystems()); + if(data.system != WindowingSystem::Unknown) + m_PreviewOutput = m_Replay->MakeOutputWindow(data, false); + + m_FrameRecord = m_Replay->GetFrameRecord(); + } +} + +void ReplayProxy::ShutdownPreviewWindow() +{ + if(m_Replay && m_PreviewOutput) + { + m_Replay->DestroyOutputWindow(m_PreviewOutput); + m_PreviewWindow(false, {}); + } +} + +void ReplayProxy::RefreshPreviewWindow() +{ + if(m_Replay && m_PreviewOutput) + { + m_Replay->BindOutputWindow(m_PreviewOutput, false); + m_Replay->ClearOutputWindowColor(m_PreviewOutput, FloatVector(0.0f, 0.0f, 0.0f, 1.0f)); + + int32_t winWidth = 1; + int32_t winHeight = 1; + m_Replay->GetOutputWindowDimensions(m_PreviewOutput, winWidth, winHeight); + + m_Replay->RenderCheckerboard(); + + const DrawcallDescription *curDraw = FindDraw(m_FrameRecord.drawcallList, m_PreviewEvent); + + if(curDraw) + { + TextureDisplay cfg = {}; + + cfg.red = cfg.green = cfg.blue = true; + cfg.alpha = false; + + for(ResourceId id : curDraw->outputs) + { + if(id != ResourceId()) + { + cfg.resourceId = id; + break; + } + } + + // if we didn't get a colour target, try the depth target + if(cfg.resourceId == ResourceId() && curDraw->depthOut != ResourceId()) + { + cfg.resourceId = curDraw->depthOut; + // red only for depth textures + cfg.green = cfg.blue = false; + } + + // if we didn't get any target, use the copy destination + if(cfg.resourceId == ResourceId()) + cfg.resourceId = curDraw->copyDestination; + + // if we did get a texture, get the live ID for it + if(cfg.resourceId != ResourceId()) + cfg.resourceId = m_Replay->GetLiveID(cfg.resourceId); + + if(cfg.resourceId != ResourceId()) + { + TextureDescription texInfo = m_Replay->GetTexture(cfg.resourceId); + + cfg.typeHint = CompType::Typeless; + cfg.rangeMin = 0.0f; + cfg.rangeMax = 1.0f; + cfg.flipY = false; + cfg.hdrMultiplier = -1.0f; + cfg.linearDisplayAsGamma = true; + cfg.customShaderId = ResourceId(); + cfg.mip = 0; + cfg.sliceFace = 0; + cfg.sampleIdx = 0; + cfg.rawOutput = false; + cfg.backgroundColor = FloatVector(0, 0, 0, 0); + cfg.overlay = DebugOverlay::NoOverlay; + cfg.xOffset = 0.0f; + cfg.yOffset = 0.0f; + + float xScale = float(winWidth) / float(texInfo.width); + float yScale = float(winHeight) / float(texInfo.height); + + if(xScale > yScale) + { + // use the y scale, calculate the x offset to centre horizontally + cfg.scale = yScale; + + cfg.xOffset = (float(winWidth) - float(texInfo.width) * cfg.scale) / 2.0f; + } + else + { + // use the x scale, calculate the y offset to centre vertically + cfg.scale = xScale; + + cfg.yOffset = (float(winHeight) - float(texInfo.height) * cfg.scale) / 2.0f; + } + + m_Replay->RenderTexture(cfg); + } + } + + m_Replay->FlipOutputWindow(m_PreviewOutput); + + m_PreviewWindow(true, m_Replay->GetSupportedWindowSystems()); + } +} + bool ReplayProxy::Tick(int type) { if(!m_RemoteServer) @@ -1927,6 +2067,8 @@ bool ReplayProxy::Tick(int type) default: RDCERR("Unexpected command %u", type); return false; } + RefreshPreviewWindow(); + if(m_Writer.IsErrored() || m_Reader.IsErrored() || m_IsErrored) return false; diff --git a/renderdoc/core/replay_proxy.h b/renderdoc/core/replay_proxy.h index 826511ea0..847a2aa7e 100644 --- a/renderdoc/core/replay_proxy.h +++ b/renderdoc/core/replay_proxy.h @@ -111,20 +111,38 @@ class ReplayProxy : public IReplayDriver { public: ReplayProxy(ReadSerialiser &reader, WriteSerialiser &writer, IReplayDriver *proxy) - : m_Reader(reader), m_Writer(writer), m_Proxy(proxy), m_Remote(NULL), m_RemoteServer(false) + : m_Reader(reader), + m_Writer(writer), + m_Proxy(proxy), + m_Remote(NULL), + m_Replay(NULL), + m_RemoteServer(false) { GetAPIProperties(); FetchStructuredFile(); } - ReplayProxy(ReadSerialiser &reader, WriteSerialiser &writer, IRemoteDriver *remote) - : m_Reader(reader), m_Writer(writer), m_Proxy(NULL), m_Remote(remote), m_RemoteServer(true) + ReplayProxy(ReadSerialiser &reader, WriteSerialiser &writer, IRemoteDriver *remoteDriver, + IReplayDriver *replayDriver, RENDERDOC_PreviewWindowCallback previewWindow) + : m_Reader(reader), + m_Writer(writer), + m_Proxy(NULL), + m_Remote(remoteDriver), + m_Replay(replayDriver), + m_PreviewWindow(previewWindow), + m_RemoteServer(true) { RDCEraseEl(m_APIProps); + + if(m_Replay) + InitPreviewWindow(); } virtual ~ReplayProxy(); + void InitPreviewWindow(); + void ShutdownPreviewWindow(); + bool IsRemoteProxy() { return !m_RemoteServer; } void Shutdown() { delete this; } ReplayStatus ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers) @@ -394,6 +412,8 @@ public: return ResourceId(); } + void RefreshPreviewWindow(); + bool Tick(int type); const D3D11Pipe::State &GetD3D11PipelineState() { return m_D3D11PipelineState; } @@ -521,6 +541,9 @@ private: void EnsureBufCached(ResourceId bufid); IMPLEMENT_FUNCTION_PROXIED(bool, NeedRemapForFetch, const ResourceFormat &format); + const DrawcallDescription *FindDraw(const rdcarray &drawcallList, + uint32_t eventId); + struct TextureCacheEntry { ResourceId replayid; @@ -587,14 +610,35 @@ private: std::map m_ShaderReflectionCache; + // reader from the other side of the host <-> remote connection ReadSerialiser &m_Reader; + // writer to the other side of the host <-> remote connection WriteSerialiser &m_Writer; + + // the local proxy replay driver when on the host side, NULL on the remote server IReplayDriver *m_Proxy; + // the remote driver on the remote server, NULL on the host side IRemoteDriver *m_Remote; + // an *optional* replay driver on the remote server, could be NULL if not supported by the system + // on the remote server. Always NULL on the host side. + // This allows us to do some extra things on the remote server such as displaying a preview window + IReplayDriver *m_Replay; + + // true if we're the remote server, false if we're the host bool m_RemoteServer; + // The callback (if provided) that handles creating and ticking a preview window on the remote + // host. + RENDERDOC_PreviewWindowCallback m_PreviewWindow; + // the ID of the output window to use for previewing on the remote host. Only valid/useful if + // m_Replay is set + uint64_t m_PreviewOutput = 0; + + uint32_t m_PreviewEvent = 0; + bool m_IsErrored = false; + FrameRecord m_FrameRecord; APIProperties m_APIProps; SDFile m_StructuredFile; diff --git a/renderdoc/replay/entry_points.cpp b/renderdoc/replay/entry_points.cpp index ba3b0aaf7..e3f0f19eb 100644 --- a/renderdoc/replay/entry_points.cpp +++ b/renderdoc/replay/entry_points.cpp @@ -401,7 +401,8 @@ extern "C" RENDERDOC_API uint32_t RENDERDOC_CC RENDERDOC_GetDefaultRemoteServerP } extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_BecomeRemoteServer( - const char *listenhost, uint32_t port, RENDERDOC_KillCallback killReplay) + const char *listenhost, uint32_t port, RENDERDOC_KillCallback killReplay, + RENDERDOC_PreviewWindowCallback previewWindow) { if(listenhost == NULL || listenhost[0] == 0) listenhost = "0.0.0.0"; @@ -410,10 +411,17 @@ extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_BecomeRemoteServer( if(!killReplay) killReplay = []() { return false; }; + // ditto for preview windows + if(!previewWindow) + previewWindow = [](bool, const rdcarray &) { + WindowingData ret = {WindowingSystem::Unknown}; + return ret; + }; + if(port == 0) port = RENDERDOC_GetDefaultRemoteServerPort(); - RenderDoc::Inst().BecomeRemoteServer(listenhost, (uint16_t)port, killReplay); + RenderDoc::Inst().BecomeRemoteServer(listenhost, (uint16_t)port, killReplay, previewWindow); } extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_StartSelfHostCapture(const char *dllname) diff --git a/renderdoccmd/renderdoccmd.cpp b/renderdoccmd/renderdoccmd.cpp index b82e229f7..efb83e9f3 100644 --- a/renderdoccmd/renderdoccmd.cpp +++ b/renderdoccmd/renderdoccmd.cpp @@ -474,6 +474,7 @@ struct RemoteServerCommand : public Command "host", 'h', "The interface to listen on. By default listens on all interfaces", false, ""); parser.add("port", 'p', "The port to listen on.", false, RENDERDOC_GetDefaultRemoteServerPort()); + parser.add("preview", 'v', "Display a preview window when a replay is active."); } virtual const char *Description() { @@ -499,8 +500,19 @@ struct RemoteServerCommand : public Command usingKillSignal = true; + // by default have a do-nothing callback that creates no windows + RENDERDOC_PreviewWindowCallback previewWindow; + + // if the user asked for a preview, then call to the platform-specific preview function + if(parser.exist("preview")) + previewWindow = &DisplayRemoteServerPreview; + + // OR if the platform-specific preview function always has a window, then return it anyway. + if(DisplayRemoteServerPreview(false, {}).system != WindowingSystem::Unknown) + previewWindow = &DisplayRemoteServerPreview; + RENDERDOC_BecomeRemoteServer(host.empty() ? NULL : host.c_str(), port, - []() { return killSignal; }); + []() { return killSignal; }, previewWindow); std::cerr << std::endl << "Cleaning up from replay hosting." << std::endl; diff --git a/renderdoccmd/renderdoccmd.h b/renderdoccmd/renderdoccmd.h index 761a89eef..ddf13e0e4 100644 --- a/renderdoccmd/renderdoccmd.h +++ b/renderdoccmd/renderdoccmd.h @@ -57,4 +57,5 @@ void readCapOpts(const std::string &str, CaptureOptions *opts); // these must be defined in platform .cpps void DisplayRendererPreview(IReplayController *renderer, TextureDisplay &displayCfg, uint32_t width, uint32_t height); +WindowingData DisplayRemoteServerPreview(bool active, const rdcarray &systems); void Daemonise(); diff --git a/renderdoccmd/renderdoccmd_android.cpp b/renderdoccmd/renderdoccmd_android.cpp index 28c4851a0..7aa18bdfc 100644 --- a/renderdoccmd/renderdoccmd_android.cpp +++ b/renderdoccmd/renderdoccmd_android.cpp @@ -57,31 +57,6 @@ void Daemonise() { } -void DisplayRendererPreview(IReplayController *renderer, TextureDisplay &displayCfg, uint32_t width, - uint32_t height) -{ - ANativeWindow *connectionScreenWindow = android_state->window; - - pthread_mutex_lock(&m_DrawLock.lock); - - IReplayOutput *out = renderer->CreateOutput(CreateAndroidWindowingData(connectionScreenWindow), - ReplayOutputType::Texture); - - out->SetTextureDisplay(displayCfg); - - for(int i = 0; i < 100; i++) - { - renderer->SetFrameEvent(10000000, true); - - ANDROID_LOG("Frame %i", i); - out->Display(); - - usleep(100000); - } - - pthread_mutex_unlock(&m_DrawLock.lock); -} - void DisplayGenericSplash() { // if something else is drawing and holding the lock, then bail @@ -269,6 +244,57 @@ void main() pthread_mutex_unlock(&m_DrawLock.lock); } +WindowingData DisplayRemoteServerPreview(bool active, const rdcarray &systems) +{ + static bool wasActive = false; + + // detect when the preview starts or stops + if(wasActive != active) + { + wasActive = active; + + // if we're opening it, aquire the draw lock, otherwise release it. + if(active) + { + pthread_mutex_lock(&m_DrawLock.lock); + } + else + { + pthread_mutex_unlock(&m_DrawLock.lock); + + // when we release it, re-draw the splash + DisplayGenericSplash(); + } + } + + return CreateAndroidWindowingData(android_state->window); +} + +void DisplayRendererPreview(IReplayController *renderer, TextureDisplay &displayCfg, uint32_t width, + uint32_t height) +{ + ANativeWindow *connectionScreenWindow = android_state->window; + + pthread_mutex_lock(&m_DrawLock.lock); + + IReplayOutput *out = renderer->CreateOutput(CreateAndroidWindowingData(connectionScreenWindow), + ReplayOutputType::Texture); + + out->SetTextureDisplay(displayCfg); + + for(int i = 0; i < 100; i++) + { + renderer->SetFrameEvent(10000000, true); + + ANDROID_LOG("Frame %i", i); + out->Display(); + + usleep(100000); + } + + pthread_mutex_unlock(&m_DrawLock.lock); +} + // Returns the renderdoccmd arguments passed via am start // Examples: am start ... -e renderdoccmd "remoteserver" // -e renderdoccmd "replay /sdcard/capture.rdc" diff --git a/renderdoccmd/renderdoccmd_apple.cpp b/renderdoccmd/renderdoccmd_apple.cpp index 3e5e04781..56a390fd3 100644 --- a/renderdoccmd/renderdoccmd_apple.cpp +++ b/renderdoccmd/renderdoccmd_apple.cpp @@ -34,6 +34,12 @@ void Daemonise() { } +WindowingData DisplayRemoteServerPreview(bool active, const rdcarray &systems) +{ + WindowingData ret = {WindowingSystem::Unknown}; + return ret; +} + void DisplayRendererPreview(IReplayController *renderer, TextureDisplay &displayCfg, uint32_t width, uint32_t height) { diff --git a/renderdoccmd/renderdoccmd_linux.cpp b/renderdoccmd/renderdoccmd_linux.cpp index d89a83245..eeed4a05b 100644 --- a/renderdoccmd/renderdoccmd_linux.cpp +++ b/renderdoccmd/renderdoccmd_linux.cpp @@ -202,6 +202,120 @@ void VerifyVulkanLayer(const GlobalEnvironment &env, int argc, char *argv[]) static Display *display = NULL; +WindowingData DisplayRemoteServerPreview(bool active, const rdcarray &systems) +{ + static WindowingData remoteServerPreview = {WindowingSystem::Unknown}; + +// we only have the preview implemented for platforms that have xlib & xcb. It's unlikely +// a meaningful platform exists with only one, and at the time of writing no other windowing +// systems are supported on linux for the replay +#if defined(RENDERDOC_WINDOWING_XLIB) && defined(RENDERDOC_WINDOWING_XCB) + if(active) + { + if(remoteServerPreview.system == WindowingSystem::Unknown) + { + // if we're first initialising, create the window + if(display == NULL) + return remoteServerPreview; + + int scr = DefaultScreen(display); + + xcb_connection_t *connection = XGetXCBConnection(display); + + if(connection == NULL) + { + std::cerr << "Couldn't get XCB connection from Xlib Display" << std::endl; + return remoteServerPreview; + } + + XSetEventQueueOwner(display, XCBOwnsEventQueue); + + const xcb_setup_t *setup = xcb_get_setup(connection); + xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup); + while(scr-- > 0) + xcb_screen_next(&iter); + + xcb_screen_t *screen = iter.data; + + uint32_t value_mask, value_list[32]; + + xcb_window_t window = xcb_generate_id(connection); + + value_mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK; + value_list[0] = screen->black_pixel; + value_list[1] = + XCB_EVENT_MASK_KEY_RELEASE | XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY; + + xcb_create_window(connection, XCB_COPY_FROM_PARENT, window, screen->root, 0, 0, 1280, 720, 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual, value_mask, value_list); + + /* Magic code that will send notification when window is destroyed */ + xcb_intern_atom_cookie_t cookie = xcb_intern_atom(connection, 1, 12, "WM_PROTOCOLS"); + xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(connection, cookie, 0); + + xcb_intern_atom_cookie_t cookie2 = xcb_intern_atom(connection, 0, 16, "WM_DELETE_WINDOW"); + xcb_intern_atom_reply_t *atom_wm_delete_window = xcb_intern_atom_reply(connection, cookie2, 0); + + xcb_change_property(connection, XCB_PROP_MODE_REPLACE, window, XCB_ATOM_WM_NAME, + XCB_ATOM_STRING, 8, sizeof("Remote Server Preview") - 1, + "Remote Server Preview"); + + xcb_change_property(connection, XCB_PROP_MODE_REPLACE, window, (*reply).atom, 4, 32, 1, + &(*atom_wm_delete_window).atom); + free(reply); + + xcb_map_window(connection, window); + + bool xcb = false, xlib = false; + + for(size_t i = 0; i < systems.size(); i++) + { + if(systems[i] == WindowingSystem::Xlib) + xlib = true; + if(systems[i] == WindowingSystem::XCB) + xcb = true; + } + + // prefer xcb + if(xcb) + remoteServerPreview = CreateXCBWindowingData(connection, window); + else if(xlib) + remoteServerPreview = CreateXlibWindowingData(display, (Drawable)window); + + xcb_flush(connection); + } + else + { + // otherwise, we can pump messages here, but we don't actually care to process any. Just clear + // the queue + xcb_generic_event_t *event = NULL; + + xcb_connection_t *connection = remoteServerPreview.xcb.connection; + + if(remoteServerPreview.system == WindowingSystem::Xlib) + connection = XGetXCBConnection(remoteServerPreview.xlib.display); + + if(connection) + { + do + { + event = xcb_poll_for_event(connection); + if(event) + free(event); + } while(event); + } + } + } + else + { + // reset the windowing data to 'no window' + remoteServerPreview = {WindowingSystem::Unknown}; + } +#endif + + return remoteServerPreview; +} + void DisplayRendererPreview(IReplayController *renderer, TextureDisplay &displayCfg, uint32_t width, uint32_t height) { diff --git a/renderdoccmd/renderdoccmd_win32.cpp b/renderdoccmd/renderdoccmd_win32.cpp index e354bc68b..e9de122ac 100644 --- a/renderdoccmd/renderdoccmd_win32.cpp +++ b/renderdoccmd/renderdoccmd_win32.cpp @@ -128,6 +128,61 @@ void Daemonise() // nothing really to do, windows version of renderdoccmd is already 'detached' } +WindowingData DisplayRemoteServerPreview(bool active, const rdcarray &systems) +{ + static WindowingData remoteServerPreview = {WindowingSystem::Unknown}; + + if(active) + { + if(remoteServerPreview.system == WindowingSystem::Unknown) + { + // if we're first initialising, create the window + + RECT wr = {0, 0, (LONG)1280, (LONG)720}; + AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, FALSE); + + HWND wnd = + CreateWindowEx(WS_EX_CLIENTEDGE, L"renderdoccmd", L"Remote Server Preview", + WS_OVERLAPPED | WS_CAPTION | WS_MINIMIZEBOX, CW_USEDEFAULT, CW_USEDEFAULT, + wr.right - wr.left, wr.bottom - wr.top, NULL, NULL, hInstance, NULL); + + if(wnd == NULL) + return remoteServerPreview; + + ShowWindow(wnd, SW_SHOW); + UpdateWindow(wnd); + + remoteServerPreview.system = WindowingSystem::Win32; + remoteServerPreview.win32.window = wnd; + } + else + { + // otherwise, pump messages + MSG msg; + ZeroMemory(&msg, sizeof(msg)); + + // Check to see if any messages are waiting in the queue + while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + { + // Translate the message and dispatch it to WindowProc() + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + } + else + { + // if we had a previous window, destroy it. + if(remoteServerPreview.win32.window != NULL) + DestroyWindow(remoteServerPreview.win32.window); + + // reset the windowing data to 'no window' + remoteServerPreview = {WindowingSystem::Unknown}; + } + + return remoteServerPreview; +} + void DisplayRendererPreview(IReplayController *renderer, TextureDisplay &displayCfg, uint32_t width, uint32_t height) {