diff --git a/renderdoc/api/replay/renderdoc_replay.h b/renderdoc/api/replay/renderdoc_replay.h index a5be48db8..70fe76bdd 100644 --- a/renderdoc/api/replay/renderdoc_replay.h +++ b/renderdoc/api/replay/renderdoc_replay.h @@ -165,6 +165,8 @@ struct IReplayRenderer virtual void Shutdown() = 0; virtual void ShutdownOutput(ReplayOutput *output) = 0; + virtual void FileChanged() = 0; + virtual bool HasCallstacks() = 0; virtual bool InitResolver() = 0; @@ -230,6 +232,8 @@ extern "C" RENDERDOC_API ReplayOutput* RENDERDOC_CC ReplayRenderer_CreateOutput( extern "C" RENDERDOC_API void RENDERDOC_CC ReplayRenderer_Shutdown(ReplayRenderer *rend); extern "C" RENDERDOC_API void RENDERDOC_CC ReplayRenderer_ShutdownOutput(ReplayRenderer *rend, ReplayOutput *output); +extern "C" RENDERDOC_API void RENDERDOC_CC ReplayRenderer_FileChanged(ReplayRenderer *rend); + extern "C" RENDERDOC_API bool32 RENDERDOC_CC ReplayRenderer_HasCallstacks(ReplayRenderer *rend); extern "C" RENDERDOC_API bool32 RENDERDOC_CC ReplayRenderer_InitResolver(ReplayRenderer *rend); diff --git a/renderdoc/core/image_viewer.cpp b/renderdoc/core/image_viewer.cpp index 41af204db..f53f29380 100644 --- a/renderdoc/core/image_viewer.cpp +++ b/renderdoc/core/image_viewer.cpp @@ -33,8 +33,10 @@ class ImageViewer : public IReplayDriver { public: - ImageViewer(IReplayDriver *proxy, const char *filename, ResourceId texID) + ImageViewer(IReplayDriver *proxy, const char *filename) : m_Proxy(proxy) + , m_Filename(filename) + , m_TextureID() { if(m_Proxy == NULL) RDCERR("Unexpectedly NULL proxy at creation of ImageViewer"); @@ -54,11 +56,13 @@ class ImageViewer : public IReplayDriver d.name = filename; record.drawcallList.push_back(d); - - create_array_uninit(m_PipelineState.m_OM.RenderTargets, 1); - m_PipelineState.m_OM.RenderTargets[0].Resource = texID; m_FrameRecord.push_back(record); + + RefreshFile(); + + create_array_uninit(m_PipelineState.m_OM.RenderTargets, 1); + m_PipelineState.m_OM.RenderTargets[0].Resource = m_TextureID; } virtual ~ImageViewer() @@ -83,22 +87,22 @@ class ImageViewer : public IReplayDriver void RenderCheckerboard(Vec3f light, Vec3f dark) { m_Proxy->RenderCheckerboard(light, dark); } void RenderHighlightBox(float w, float h, float scale) { m_Proxy->RenderHighlightBox(w, h, scale); } bool GetMinMax(ResourceId texid, uint32_t sliceFace, uint32_t mip, uint32_t sample, float *minval, float *maxval) - { return m_Proxy->GetMinMax(texid, sliceFace, mip, sample, minval, maxval); } + { return m_Proxy->GetMinMax(m_TextureID, sliceFace, mip, sample, minval, maxval); } bool GetHistogram(ResourceId texid, uint32_t sliceFace, uint32_t mip, uint32_t sample, float minval, float maxval, bool channels[4], vector &histogram) - { return m_Proxy->GetHistogram(texid, sliceFace, mip, sample, minval, maxval, channels, histogram); } - bool RenderTexture(TextureDisplay cfg) { return m_Proxy->RenderTexture(cfg); } + { return m_Proxy->GetHistogram(m_TextureID, sliceFace, mip, sample, minval, maxval, channels, histogram); } + bool RenderTexture(TextureDisplay cfg) { cfg.texid = m_TextureID; return m_Proxy->RenderTexture(cfg); } void PickPixel(ResourceId texture, uint32_t x, uint32_t y, uint32_t sliceFace, uint32_t mip, uint32_t sample, float pixel[4]) - { m_Proxy->PickPixel(texture, x, y, sliceFace, mip, sample, pixel); } + { m_Proxy->PickPixel(m_TextureID, x, y, sliceFace, mip, sample, pixel); } uint32_t PickVertex(uint32_t frameID, uint32_t eventID, MeshDisplay cfg, uint32_t x, uint32_t y) { return m_Proxy->PickVertex(frameID, eventID, cfg, x, y); } void BuildCustomShader(string source, string entry, const uint32_t compileFlags, ShaderStageType type, ResourceId *id, string *errors) { m_Proxy->BuildCustomShader(source, entry, compileFlags, type, id, errors); } void FreeCustomShader(ResourceId id) { m_Proxy->FreeTargetResource(id); } - ResourceId ApplyCustomShader(ResourceId shader, ResourceId texid, uint32_t mip) { return m_Proxy->ApplyCustomShader(shader, texid, mip); } + ResourceId ApplyCustomShader(ResourceId shader, ResourceId texid, uint32_t mip) { return m_Proxy->ApplyCustomShader(shader, m_TextureID, mip); } vector GetTextures() { return m_Proxy->GetTextures(); } - FetchTexture GetTexture(ResourceId id) { return m_Proxy->GetTexture(id); } + FetchTexture GetTexture(ResourceId id) { return m_Proxy->GetTexture(m_TextureID); } byte *GetTextureData(ResourceId tex, uint32_t arrayIdx, uint32_t mip, bool resolve, bool forceRGBA8unorm, float blackPoint, float whitePoint, size_t &dataSize) - { return m_Proxy->GetTextureData(tex, arrayIdx, mip, resolve, forceRGBA8unorm, blackPoint, whitePoint, dataSize); } + { return m_Proxy->GetTextureData(m_TextureID, arrayIdx, mip, resolve, forceRGBA8unorm, blackPoint, whitePoint, dataSize); } // handle a couple of operations ourselves to return a simple fake log APIProperties GetAPIProperties() { return m_Props; } @@ -166,11 +170,21 @@ class ImageViewer : public IReplayDriver RDCERR("Calling proxy-render functions on an image viewer"); } + void FileChanged() + { + RefreshFile(); + } + private: + void RefreshFile(); + APIProperties m_Props; vector m_FrameRecord; D3D11PipelineState m_PipelineState; IReplayDriver *m_Proxy; + string m_Filename; + ResourceId m_TextureID; + FetchTexture m_TexDetails; }; ReplayCreateStatus IMG_CreateReplayDevice(const char *logfile, IReplayDriver **driver) @@ -179,7 +193,112 @@ ReplayCreateStatus IMG_CreateReplayDevice(const char *logfile, IReplayDriver **d if(!f) return eReplayCreate_FileIOFailed; + + // make sure the file is a type we recognise before going further + if(is_exr_file(f)) + { + FileIO::fseek64(f, 0, SEEK_SET); + + const char *err = NULL; + + float *data = NULL; + int dummy; + + int ret = LoadEXRFP(&data, &dummy, &dummy, f, &err); + + if(data) free(data); + + // could be an unsupported form of EXR, like deep image or other + if(ret != 0) + { + FileIO::fclose(f); + + RDCERR("EXR file detected, but couldn't load with LoadEXR %d: '%s'", ret, err); + return eReplayCreate_APIUnsupported; + } + } + else if(stbi_is_hdr_from_file(f)) + { + FileIO::fseek64(f, 0, SEEK_SET); + + int ignore = 0; + float *data = stbi_loadf_from_file(f, &ignore, &ignore, &ignore, 4); + + if(!data) + { + FileIO::fclose(f); + RDCERR("HDR file recognised, but couldn't load with stbi_loadf_from_file"); + return eReplayCreate_FileCorrupted; + } + + free(data); + } + else if(is_dds_file(f)) + { + FileIO::fseek64(f, 0, SEEK_SET); + dds_data read_data = load_dds_from_file(f); + + if(read_data.subdata == NULL) + { + FileIO::fclose(f); + RDCERR("DDS file recognised, but couldn't load"); + return eReplayCreate_FileCorrupted; + } + + for(int i=0; i < read_data.slices*read_data.mips; i++) + delete[] read_data.subdata[i]; + + delete[] read_data.subdata; + delete[] read_data.subsizes; + } + else + { + int width = 0, height = 0; + int ignore = 0; + int ret = stbi_info_from_file(f, &width, &height, &ignore); + + // just in case (we shouldn't have come in here if this weren't true), make sure + // the format is supported + if(ret == 0 || + width == 0 || width == ~0U || + height == 0 || height == ~0U) + { + FileIO::fclose(f); + return eReplayCreate_APIUnsupported; + } + + byte *data = stbi_load_from_file(f, &ignore, &ignore, &ignore, 4); + + if(!data) + { + FileIO::fclose(f); + RDCERR("File recognised, but couldn't load with stbi_load_from_file"); + return eReplayCreate_FileCorrupted; + } + free(data); + } + + FileIO::fclose(f); + + IReplayDriver *proxy = NULL; + auto status = RenderDoc::Inst().CreateReplayDriver(RDC_Unknown, NULL, &proxy); + + if(status != eReplayCreate_Success || !proxy) + { + if(proxy) proxy->Shutdown(); + return status; + } + + *driver = new ImageViewer(proxy, logfile); + + return eReplayCreate_Success; +} + +void ImageViewer::RefreshFile() +{ + FILE *f = FileIO::fopen(m_Filename.c_str(), "rb"); + FetchTexture texDetails; ResourceFormat rgba8_unorm; @@ -195,16 +314,19 @@ ReplayCreateStatus IMG_CreateReplayDevice(const char *logfile, IReplayDriver **d texDetails.creationFlags = eTextureCreate_SwapBuffer|eTextureCreate_RTV; texDetails.cubemap = false; texDetails.customName = true; - texDetails.name = logfile; - texDetails.ID = ResourceId(); + texDetails.name = m_Filename; + texDetails.ID = m_TextureID; texDetails.byteSize = 0; texDetails.msQual = 0; texDetails.msSamp = 1; + texDetails.format = rgba8_unorm; - // reasonable defaults for everything but dds + // reasonable defaults texDetails.numSubresources = 1; texDetails.dimension = 2; texDetails.arraysize = 1; + texDetails.width = 1; + texDetails.height = 1; texDetails.depth = 1; texDetails.mips = 1; @@ -229,7 +351,8 @@ ReplayCreateStatus IMG_CreateReplayDevice(const char *logfile, IReplayDriver **d { if(data) free(data); RDCERR("EXR file detected, but couldn't load with LoadEXR %d: '%s'", ret, err); - return eReplayCreate_APIUnsupported; + FileIO::fclose(f); + return; } } else if(stbi_is_hdr_from_file(f)) @@ -257,7 +380,8 @@ ReplayCreateStatus IMG_CreateReplayDevice(const char *logfile, IReplayDriver **d texDetails.width == 0 || texDetails.width == ~0U || texDetails.height == 0 || texDetails.height == ~0U) { - return eReplayCreate_APIUnsupported; + FileIO::fclose(f); + return; } texDetails.format = rgba8_unorm; @@ -270,36 +394,21 @@ ReplayCreateStatus IMG_CreateReplayDevice(const char *logfile, IReplayDriver **d // file was corrupted and we failed to load it if(!dds && data == NULL) { - return eReplayCreate_FileCorrupted; - } - - IReplayDriver *proxy = NULL; - auto status = RenderDoc::Inst().CreateReplayDriver(RDC_Unknown, NULL, &proxy); - - if(status != eReplayCreate_Success || !proxy) - { - if(proxy) proxy->Shutdown(); - return status; + FileIO::fclose(f); + return; } - ResourceId id; + dds_data read_data; - if(!dds) - { - id = proxy->CreateProxyTexture(texDetails); - - proxy->SetProxyTextureData(id, 0, 0, data, datasize); - free(data); - } - else + if(dds) { FileIO::fseek64(f, 0, SEEK_SET); dds_data read_data = load_dds_from_file(f); if(read_data.subdata == NULL) { - proxy->Shutdown(); - return eReplayCreate_FileCorrupted; + FileIO::fclose(f); + return; } texDetails.cubemap = read_data.cubemap; @@ -313,12 +422,42 @@ ReplayCreateStatus IMG_CreateReplayDevice(const char *logfile, IReplayDriver **d texDetails.dimension = 1; if(texDetails.width > 1) texDetails.dimension = 2; if(texDetails.depth > 1) texDetails.dimension = 3; + } - id = proxy->CreateProxyTexture(texDetails); + // recreate proxy texture if necessary. + // we rewrite the texture IDs so that the + // outside world doesn't need to know about this + // (we only ever have one texture in the image + // viewer so we can just set all texture IDs + // used to that). + if(m_TextureID != ResourceId()) + { + if(m_TexDetails.width != texDetails.width || + m_TexDetails.height != texDetails.height || + m_TexDetails.depth != texDetails.depth || + m_TexDetails.cubemap != texDetails.cubemap || + m_TexDetails.mips != texDetails.mips || + m_TexDetails.arraysize != texDetails.arraysize || + m_TexDetails.width != texDetails.width || + m_TexDetails.format != texDetails.format) + { + m_TextureID = ResourceId(); + } + } + if(m_TextureID == ResourceId()) + m_TextureID = m_Proxy->CreateProxyTexture(texDetails); + + if(!dds) + { + m_Proxy->SetProxyTextureData(m_TextureID, 0, 0, data, datasize); + free(data); + } + else + { for(uint32_t i=0; i < texDetails.numSubresources; i++) { - proxy->SetProxyTextureData(id, i/texDetails.mips, i%texDetails.mips, read_data.subdata[i], (size_t)read_data.subsizes[i]); + m_Proxy->SetProxyTextureData(m_TextureID, i/texDetails.mips, i%texDetails.mips, read_data.subdata[i], (size_t)read_data.subsizes[i]); delete[] read_data.subdata[i]; } @@ -327,11 +466,7 @@ ReplayCreateStatus IMG_CreateReplayDevice(const char *logfile, IReplayDriver **d delete[] read_data.subsizes; } - *driver = new ImageViewer(proxy, logfile, id); - FileIO::fclose(f); - - return eReplayCreate_Success; } static DriverRegistration IMGDriverRegistration(RDC_Image, "Image", &IMG_CreateReplayDevice); diff --git a/renderdoc/core/replay_proxy.h b/renderdoc/core/replay_proxy.h index 9febf997f..cb2c76c84 100644 --- a/renderdoc/core/replay_proxy.h +++ b/renderdoc/core/replay_proxy.h @@ -373,6 +373,8 @@ class ProxySerialiser : public IReplayDriver, Callstack::StackResolver void ReplaceResource(ResourceId from, ResourceId to); void RemoveReplacement(ResourceId id); + void FileChanged() {} + // will never be used ResourceId CreateProxyTexture(FetchTexture templateTex) { diff --git a/renderdoc/driver/d3d11/d3d11_replay.cpp b/renderdoc/driver/d3d11/d3d11_replay.cpp index 94eccb209..43e6931ce 100644 --- a/renderdoc/driver/d3d11/d3d11_replay.cpp +++ b/renderdoc/driver/d3d11/d3d11_replay.cpp @@ -45,6 +45,10 @@ D3D11Replay::D3D11Replay() void D3D11Replay::Shutdown() { + for(size_t i=0; i < m_ProxyResources.size(); i++) + m_ProxyResources[i]->Release(); + m_ProxyResources.clear(); + m_pDevice->Release(); D3D11DebugManager::PostDeviceShutdownCounters(); @@ -1547,6 +1551,8 @@ ResourceId D3D11Replay::CreateProxyTexture(FetchTexture templateTex) SetDebugName(resource, templateTex.name.elems); } + m_ProxyResources.push_back(resource); + return ret; } @@ -1694,6 +1700,8 @@ ResourceId D3D11Replay::CreateProxyBuffer(FetchBuffer templateBuf) SetDebugName(resource, templateBuf.name.elems); } + m_ProxyResources.push_back(resource); + return ret; } diff --git a/renderdoc/driver/d3d11/d3d11_replay.h b/renderdoc/driver/d3d11/d3d11_replay.h index e112b5437..3ae3ccc36 100644 --- a/renderdoc/driver/d3d11/d3d11_replay.h +++ b/renderdoc/driver/d3d11/d3d11_replay.h @@ -129,6 +129,8 @@ class D3D11Replay : public IReplayDriver ResourceId ApplyCustomShader(ResourceId shader, ResourceId texid, uint32_t mip); bool IsRenderOutput(ResourceId id); + + void FileChanged() {} void InitCallstackResolver(); bool HasCallstacks(); @@ -139,6 +141,8 @@ class D3D11Replay : public IReplayDriver bool m_WARP; bool m_Proxy; + vector m_ProxyResources; + WrappedID3D11Device *m_pDevice; D3D11PipelineState m_CurPipelineState; diff --git a/renderdoc/driver/gl/gl_replay.h b/renderdoc/driver/gl/gl_replay.h index 7d207f90b..e585c8f27 100644 --- a/renderdoc/driver/gl/gl_replay.h +++ b/renderdoc/driver/gl/gl_replay.h @@ -177,6 +177,8 @@ class GLReplay : public IReplayDriver void SetProxyBufferData(ResourceId bufid, byte *data, size_t dataSize); bool IsRenderOutput(ResourceId id); + + void FileChanged() {} void InitCallstackResolver(); bool HasCallstacks(); diff --git a/renderdoc/replay/replay_driver.h b/renderdoc/replay/replay_driver.h index 858f857f2..2a8d9de96 100644 --- a/renderdoc/replay/replay_driver.h +++ b/renderdoc/replay/replay_driver.h @@ -105,6 +105,8 @@ class IRemoteDriver virtual ResourceId RenderOverlay(ResourceId texid, TextureDisplayOverlay overlay, uint32_t frameID, uint32_t eventID, const vector &passEvents) = 0; virtual bool IsRenderOutput(ResourceId id) = 0; + + virtual void FileChanged() = 0; virtual void InitCallstackResolver() = 0; virtual bool HasCallstacks() = 0; diff --git a/renderdoc/replay/replay_renderer.cpp b/renderdoc/replay/replay_renderer.cpp index 87f2cd912..1c7d78855 100644 --- a/renderdoc/replay/replay_renderer.cpp +++ b/renderdoc/replay/replay_renderer.cpp @@ -1475,6 +1475,11 @@ FetchDrawcall *ReplayRenderer::SetupDrawcallPointers(FetchFrameInfo frame, rdcty return ret; } +void ReplayRenderer::FileChanged() +{ + m_pDevice->FileChanged(); +} + bool ReplayRenderer::HasCallstacks() { return m_pDevice->HasCallstacks(); @@ -1523,6 +1528,9 @@ extern "C" RENDERDOC_API void RENDERDOC_CC ReplayRenderer_Shutdown(ReplayRendere extern "C" RENDERDOC_API void RENDERDOC_CC ReplayRenderer_ShutdownOutput(ReplayRenderer *rend, ReplayOutput *output) { rend->ShutdownOutput(output); } +extern "C" RENDERDOC_API void RENDERDOC_CC ReplayRenderer_FileChanged(ReplayRenderer *rend) +{ rend->FileChanged(); } + extern "C" RENDERDOC_API bool32 RENDERDOC_CC ReplayRenderer_HasCallstacks(ReplayRenderer *rend) { return rend->HasCallstacks(); } extern "C" RENDERDOC_API bool32 RENDERDOC_CC ReplayRenderer_InitResolver(ReplayRenderer *rend) diff --git a/renderdoc/replay/replay_renderer.h b/renderdoc/replay/replay_renderer.h index 058919d1c..6f88f3b6e 100644 --- a/renderdoc/replay/replay_renderer.h +++ b/renderdoc/replay/replay_renderer.h @@ -133,7 +133,9 @@ struct ReplayRenderer : public IReplayRenderer ReplayCreateStatus CreateDevice(const char *logfile); ReplayCreateStatus SetDevice(IReplayDriver *device); - + + void FileChanged(); + bool HasCallstacks(); bool InitResolver(); diff --git a/renderdocui/Code/Core.cs b/renderdocui/Code/Core.cs index d746bb785..ee054f0b7 100644 --- a/renderdocui/Code/Core.cs +++ b/renderdocui/Code/Core.cs @@ -58,6 +58,8 @@ namespace renderdocui.Code private bool m_LogLoaded = false; + private FileSystemWatcher m_LogWatcher = null; + private string m_LogFile = ""; private UInt32 m_FrameID = 0; @@ -529,6 +531,13 @@ namespace renderdocui.Code m_LogLoaded = true; progressThread = false; + m_LogWatcher = new FileSystemWatcher(Path.GetDirectoryName(m_LogFile), Path.GetFileName(m_LogFile)); + m_LogWatcher.EnableRaisingEvents = true; + m_LogWatcher.NotifyFilter = NotifyFilters.Size | NotifyFilters.FileName | NotifyFilters.LastAccess | NotifyFilters.LastWrite; + m_LogWatcher.Created += new FileSystemEventHandler(OnLogfileChanged); + m_LogWatcher.Changed += new FileSystemEventHandler(OnLogfileChanged); + m_LogWatcher.SynchronizingObject = m_MainWindow; // callbacks on UI thread please + List logviewers = new List(); logviewers.AddRange(m_LogViewers); @@ -566,6 +575,17 @@ namespace renderdocui.Code p.LogfileProgress(1.0f); } + void OnLogfileChanged(object sender, FileSystemEventArgs e) + { + m_Renderer.Invoke((ReplayRenderer r) => + { + r.FileChanged(); + r.SetFrameEvent(m_FrameID, m_EventID > 0 ? m_EventID-1 : 1); + }); + + SetEventID(null, CurFrame, CurEvent); + } + public void CloseLogfile() { if (!m_LogLoaded) return; @@ -590,6 +610,10 @@ namespace renderdocui.Code m_LogLoaded = false; + if (m_LogWatcher != null) + m_LogWatcher.EnableRaisingEvents = false; + m_LogWatcher = null; + foreach (var logviewer in m_LogViewers) { Control c = (Control)logviewer; diff --git a/renderdocui/Interop/ReplayRenderer.cs b/renderdocui/Interop/ReplayRenderer.cs index d1c62d844..0b47ddd27 100644 --- a/renderdocui/Interop/ReplayRenderer.cs +++ b/renderdocui/Interop/ReplayRenderer.cs @@ -185,6 +185,9 @@ namespace renderdoc [DllImport("renderdoc.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] private static extern void ReplayRenderer_ShutdownOutput(IntPtr real, IntPtr replayOutput); + [DllImport("renderdoc.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] + private static extern void ReplayRenderer_FileChanged(IntPtr real); + [DllImport("renderdoc.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] private static extern bool ReplayRenderer_HasCallstacks(IntPtr real); [DllImport("renderdoc.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)] @@ -301,6 +304,9 @@ namespace renderdoc return new ReplayOutput(ret); } + public void FileChanged() + { ReplayRenderer_FileChanged(m_Real); } + public bool HasCallstacks() { return ReplayRenderer_HasCallstacks(m_Real); }