diff --git a/qrenderdoc/Code/CaptureContext.cpp b/qrenderdoc/Code/CaptureContext.cpp index ec35655db..449300b1e 100644 --- a/qrenderdoc/Code/CaptureContext.cpp +++ b/qrenderdoc/Code/CaptureContext.cpp @@ -44,6 +44,7 @@ #include "Windows/ConstantBufferPreviewer.h" #include "Windows/DebugMessageView.h" #include "Windows/Dialogs/CaptureDialog.h" +#include "Windows/Dialogs/CrashDialog.h" #include "Windows/Dialogs/LiveCapture.h" #include "Windows/Dialogs/SettingsDialog.h" #include "Windows/EventBrowser.h" @@ -98,6 +99,98 @@ CaptureContext::CaptureContext(PersistantConfig &cfg) : m_Config(cfg) m_MainWindow->LoadInitialLayout(); + m_Replay.SetFatalErrorCallback([this]() { + GUIInvoke::call(m_MainWindow, [this]() { + RefreshUIStatus({}, true, true); + + ReplayStatus err = GetFatalError(); + + QString title; + QString text; + + if(err == ReplayStatus::RemoteServerConnectionLost) + { + QString serverName = Replay().CurrentRemote().Name(); + + title = tr("Connection lost to %1").arg(serverName); + text = + tr("Connection to %1 was lost while analysing capture.\n" + "This is most commonly caused by a crash, but that can't be verified and it may be " + "a connection issue.") + .arg(serverName); + } + else if(err == ReplayStatus::ReplayDeviceLost) + { + title = tr("Device Lost error"); + text = + tr("The capture analysis encountered a device lost error. This may be due to an " + "application bug, a RenderDoc bug, or insufficient resources on the system to " + "analyse the capture.\n\n" + "It is recommended that you run your application with API validation enabled, as " + "API use errors can cause this kind of problem."); + } + else if(err == ReplayStatus::ReplayOutOfMemory) + { + title = tr("Out of GPU memory"); + text = tr( + "While analysing the capture it ran out of GPU memory. This is most likely caused by " + "insufficient resources on the system to analyse the capture, which is possible if " + "there is memory pressure from other applications or if the capture is heavyweight."); + } + else + { + title = tr("Unrecoverable error"); + text = tr("While analysing the capture we encountered an unrecoverable error:\n\n%1") + .arg(QString(ToStr(err))); + } + + text += + tr("\n\n" + "This error is not recoverable and the analysis cannot continue. The UI will be " + "non-functional until the capture is closed and reloaded.\n\n"); + + bool add_report = false; + + if(CrashDialog::HasCaptureReady(m_Config)) + { + text += tr( + "If you think this may be a RenderDoc bug please click the button below to report it, " + "but note that this will require you to upload the capture for reproduction as " + "otherwise it is impossible to tell what the problem may be."); + + add_report = true; + } + else + { + text += tr("The capture must be saved locally if you want to report this as a bug. "); + + if(Replay().CurrentRemote().IsConnected()) + { + text += + tr("Before closing the capture you can save it to disk and manually report a bug. " + "Please include the capture, or else it will be impossible to tell what the " + "problem may be."); + } + else + { + text += tr("You will need to reconnect to the remote server to save the capture."); + } + } + + QMessageBox mb(QMessageBox::Critical, title, text, QMessageBox::Ok, m_MainWindow); + mb.setDefaultButton(QMessageBox::NoButton); + QPushButton *report = NULL; + if(add_report) + report = mb.addButton(tr("Report bug"), QMessageBox::AcceptRole); + RDDialog::show(&mb); + + if(mb.clickedButton() == (QAbstractButton *)report) + { + m_MainWindow->sendErrorReport(true); + } + }); + }); + { QDir dir(ConfigFilePath("extensions")); @@ -712,10 +805,11 @@ void CaptureContext::LoadCapture(const rdcstr &captureFile, const ReplayOptions m_LoadInProgress = true; if(local) - { m_Config.CrashReport_LastOpenedCapture = origFilename; - m_Config.Save(); - } + else + m_Config.CrashReport_LastOpenedCapture = QString(); + + m_Config.Save(); bool newCapture = (!temporary && !Config().RecentCaptureFiles.contains(origFilename)); diff --git a/qrenderdoc/Code/CaptureContext.h b/qrenderdoc/Code/CaptureContext.h index 6aa79b2c5..731396e16 100644 --- a/qrenderdoc/Code/CaptureContext.h +++ b/qrenderdoc/Code/CaptureContext.h @@ -148,6 +148,7 @@ public: bool IsCaptureLocal() override { return m_CaptureLocal; } bool IsCaptureTemporary() override { return m_CaptureTemporary; } bool IsCaptureLoading() override { return m_LoadInProgress; } + ReplayStatus GetFatalError() override { return m_Replay.GetFatalError(); } rdcstr GetCaptureFilename() override { return m_CaptureFile; } CaptureModifications GetCaptureModifications() override { return m_CaptureMods; } const FrameDescription &FrameInfo() override { return m_FrameInfo; } diff --git a/qrenderdoc/Code/Interface/QRDInterface.h b/qrenderdoc/Code/Interface/QRDInterface.h index a5aaaa22d..566651fbf 100644 --- a/qrenderdoc/Code/Interface/QRDInterface.h +++ b/qrenderdoc/Code/Interface/QRDInterface.h @@ -1857,6 +1857,13 @@ temporary and treated like any other capture. )"); virtual bool IsCaptureLoading() = 0; + DOCUMENT(R"(If a capture is loaded, return the current fatal error status. + +:return: If a capture is currently loaded, return the fatal error status. +:rtype: ReplayStatus +)"); + virtual ReplayStatus GetFatalError() = 0; + DOCUMENT(R"(Retrieve the filename for the currently loaded capture. :return: The filename of the current capture. diff --git a/qrenderdoc/Code/ReplayManager.cpp b/qrenderdoc/Code/ReplayManager.cpp index dab297723..2a313c8a1 100644 --- a/qrenderdoc/Code/ReplayManager.cpp +++ b/qrenderdoc/Code/ReplayManager.cpp @@ -48,6 +48,8 @@ void ReplayManager::OpenCapture(const QString &capturefile, const ReplayOptions if(m_Running) return; + m_FatalError = ReplayStatus::Succeeded; + // TODO maybe we could expose this choice to the user? int proxyRenderer = -1; @@ -296,6 +298,7 @@ void ReplayManager::CancelReplayLoop() void ReplayManager::CloseThread() { m_Running = false; + m_FatalError = ReplayStatus::Succeeded; m_RenderCondition.wakeAll(); @@ -492,6 +495,13 @@ void ReplayManager::run(int proxyRenderer, const QString &capturefile, const Rep cmd->method(m_Renderer); + ReplayStatus err = m_Renderer->GetFatalErrorStatus(); + if(m_FatalError == ReplayStatus::Succeeded && err != ReplayStatus::Succeeded) + { + m_FatalError = err; + m_FatalErrorCallback(); + } + { QMutexLocker lock(&m_TimerLock); m_CommandTimer.invalidate(); diff --git a/qrenderdoc/Code/ReplayManager.h b/qrenderdoc/Code/ReplayManager.h index 660aee10d..707ed04fb 100644 --- a/qrenderdoc/Code/ReplayManager.h +++ b/qrenderdoc/Code/ReplayManager.h @@ -54,6 +54,8 @@ public: bool IsRunning(); ReplayStatus GetCreateStatus() { return m_CreateStatus; } + void SetFatalErrorCallback(std::function cb) { m_FatalErrorCallback = cb; } + ReplayStatus GetFatalError() { return m_FatalError; } float GetCurrentProcessingTime(); QString GetCurrentProcessingTag(); // this tagged version is for cases when we might send a request - e.g. to pick a vertex or pixel @@ -136,6 +138,8 @@ private: IRemoteServer *m_Remote = NULL; IRemoteServer *m_OrphanedRemote = NULL; + std::function m_FatalErrorCallback; + ReplayStatus m_FatalError = ReplayStatus::Succeeded; volatile bool m_Running; LambdaThread *m_Thread; ReplayStatus m_CreateStatus = ReplayStatus::Succeeded; diff --git a/qrenderdoc/Widgets/CustomPaintWidget.cpp b/qrenderdoc/Widgets/CustomPaintWidget.cpp index 538c88e1a..397ba9c18 100644 --- a/qrenderdoc/Widgets/CustomPaintWidget.cpp +++ b/qrenderdoc/Widgets/CustomPaintWidget.cpp @@ -96,7 +96,12 @@ void CustomPaintWidget::OnSelectedEventChanged(uint32_t eventId) void CustomPaintWidget::OnEventChanged(uint32_t eventId) { - // nothing, we only care about capture loaded/closed events + // if we've encountered a fatal error recreate the widget and take over painting + if(m_Rendering && m_Ctx && m_Ctx->GetFatalError() != ReplayStatus::Succeeded) + { + RecreateInternalWidget(); + update(); + } } void CustomPaintWidget::update() @@ -130,8 +135,9 @@ void CustomPaintWidget::RecreateInternalWidget() return; } - // if no capture is loaded, we're not rendering anymore. - m_Rendering = m_Rendering && m_Ctx && m_Ctx->IsCaptureLoaded(); + // if no capture is loaded, or we've encountered a fatal error, we're not rendering anymore. + m_Rendering = m_Rendering && m_Ctx && m_Ctx->IsCaptureLoaded() && + m_Ctx->GetFatalError() == ReplayStatus::Succeeded; // we need to recreate the widget if it's not matching out rendering state. if(m_Internal == NULL || m_Rendering != m_Internal->IsRendering()) diff --git a/qrenderdoc/Windows/Dialogs/CrashDialog.cpp b/qrenderdoc/Windows/Dialogs/CrashDialog.cpp index f89503c82..0df30dc03 100644 --- a/qrenderdoc/Windows/Dialogs/CrashDialog.cpp +++ b/qrenderdoc/Windows/Dialogs/CrashDialog.cpp @@ -39,6 +39,8 @@ #include "Code/QRDUtils.h" #include "ui_CrashDialog.h" +const qint64 MaxUploadSize = 2250LL * 1024LL * 1024LL; + CrashDialog::CrashDialog(PersistantConfig &cfg, QVariantMap crashReportJSON, QWidget *parent) : QDialog(parent), ui(new Ui::CrashDialog), m_Config(cfg) { @@ -50,11 +52,13 @@ CrashDialog::CrashDialog(PersistantConfig &cfg, QVariantMap crashReportJSON, QWi m_ReportMetadata = crashReportJSON; const bool replayCrash = crashReportJSON[lit("replaycrash")].toUInt() != 0; + const bool forceCapture = crashReportJSON[lit("forcecapture")].toUInt() != 0; const bool manualReport = crashReportJSON.contains(lit("manual")) && crashReportJSON[lit("manual")].toUInt() != 0; // remove metadata we don't send directly m_ReportMetadata.remove(lit("report")); + m_ReportMetadata.remove(lit("forcecapture")); m_ReportMetadata.remove(lit("replaycrash")); setStage(ReportStage::FillingDetails); @@ -98,7 +102,7 @@ CrashDialog::CrashDialog(PersistantConfig &cfg, QVariantMap crashReportJSON, QWi cap->Shutdown(); - if(capInfo.size() > 2250LL * 1024LL * 1024LL) + if(capInfo.size() > MaxUploadSize) { // capture is too large to upload :( ui->captureFilename->setText( @@ -108,6 +112,13 @@ CrashDialog::CrashDialog(PersistantConfig &cfg, QVariantMap crashReportJSON, QWi ui->capturePreviewFrame->hide(); } + + if(forceCapture) + { + ui->captureUpload->setChecked(true); + ui->captureUpload->setEnabled(false); + ui->captureUpload->setText(ui->captureUpload->text() + tr(" (required)")); + } } else { @@ -238,6 +249,13 @@ CrashDialog::~CrashDialog() delete ui; } +bool CrashDialog::HasCaptureReady(PersistantConfig &cfg) +{ + QFileInfo capInfo(cfg.CrashReport_LastOpenedCapture); + + return capInfo.exists() && capInfo.size() <= MaxUploadSize; +} + void CrashDialog::showEvent(QShowEvent *) { adjustSize(); @@ -299,7 +317,8 @@ void CrashDialog::on_send_clicked() if(result != QMessageBox::Yes) { // uncheck and return back so they can confirm - ui->captureUpload->setChecked(false); + if(ui->captureUpload->isEnabled()) + ui->captureUpload->setChecked(false); return; } } diff --git a/qrenderdoc/Windows/Dialogs/CrashDialog.h b/qrenderdoc/Windows/Dialogs/CrashDialog.h index d5c32d5b3..ef1edd8e0 100644 --- a/qrenderdoc/Windows/Dialogs/CrashDialog.h +++ b/qrenderdoc/Windows/Dialogs/CrashDialog.h @@ -46,6 +46,8 @@ public: explicit CrashDialog(PersistantConfig &cfg, QVariantMap crashReportJSON, QWidget *parent = 0); ~CrashDialog(); + static bool HasCaptureReady(PersistantConfig &cfg); + private slots: // automatic slots void on_send_clicked(); diff --git a/qrenderdoc/Windows/MainWindow.cpp b/qrenderdoc/Windows/MainWindow.cpp index 674ca1065..25d3d07ee 100644 --- a/qrenderdoc/Windows/MainWindow.cpp +++ b/qrenderdoc/Windows/MainWindow.cpp @@ -1828,16 +1828,6 @@ void MainWindow::messageCheck() } GUIInvoke::call(this, [this, disconnected, msgs] { - // if we just got disconnected while replaying a capture, alert the user. - if(disconnected) - { - RDDialog::critical(this, tr("Remote server disconnected"), - tr("Remote server disconnected during replaying of this capture.\n" - "The replay will now be non-functional. To restore you will have " - "to close the capture, allow " - "RenderDoc to reconnect and load the capture again")); - } - if(m_Ctx.Replay().CurrentRemote().IsValid() && !m_Ctx.Replay().CurrentRemote().IsServerRunning()) contextChooser->setIcon(Icons::cross()); @@ -2841,6 +2831,11 @@ void MainWindow::on_action_Resource_Inspector_triggered() } void MainWindow::on_action_Send_Error_Report_triggered() +{ + sendErrorReport(false); +} + +void MainWindow::sendErrorReport(bool forceCaptureInclusion) { rdcstr report; RENDERDOC_CreateBugReport(RENDERDOC_GetLogFile(), "", report); @@ -2851,6 +2846,7 @@ void MainWindow::on_action_Send_Error_Report_triggered() json[lit("gitcommit")] = QString::fromLatin1(RENDERDOC_GetCommitHash()); json[lit("replaycrash")] = 1; json[lit("manual")] = 1; + json[lit("forcecapture")] = forceCaptureInclusion ? 1 : 0; json[lit("report")] = (QString)report; CrashDialog crash(m_Ctx.Config(), json, this); diff --git a/qrenderdoc/Windows/MainWindow.h b/qrenderdoc/Windows/MainWindow.h index 7d29c5e2b..5edf94983 100644 --- a/qrenderdoc/Windows/MainWindow.h +++ b/qrenderdoc/Windows/MainWindow.h @@ -94,6 +94,8 @@ public: void LoadInitialLayout(); + void sendErrorReport(bool forceCaptureInclusion); + void setProgress(float val); void setRemoteHost(int hostIdx); void takeCaptureOwnership() { m_OwnTempCapture = true; } diff --git a/qrenderdoc/Windows/PythonShell.cpp b/qrenderdoc/Windows/PythonShell.cpp index 3478d0276..f0c8a33bb 100644 --- a/qrenderdoc/Windows/PythonShell.cpp +++ b/qrenderdoc/Windows/PythonShell.cpp @@ -414,6 +414,7 @@ struct CaptureContextInvoker : ObjectForwarder virtual bool IsCaptureLocal() override { return m_Obj.IsCaptureLocal(); } virtual bool IsCaptureTemporary() override { return m_Obj.IsCaptureTemporary(); } virtual bool IsCaptureLoading() override { return m_Obj.IsCaptureLoading(); } + virtual ReplayStatus GetFatalError() override { return m_Obj.GetFatalError(); } virtual rdcstr GetCaptureFilename() override { return m_Obj.GetCaptureFilename(); } virtual CaptureModifications GetCaptureModifications() override { diff --git a/renderdoc/CMakeLists.txt b/renderdoc/CMakeLists.txt index 0427ece07..e8d218d6a 100644 --- a/renderdoc/CMakeLists.txt +++ b/renderdoc/CMakeLists.txt @@ -175,6 +175,8 @@ set(sources replay/app_api.cpp replay/basic_types_tests.cpp replay/capture_options.cpp + replay/dummy_driver.cpp + replay/dummy_driver.h replay/renderdoc_serialise.inl replay/capture_file.cpp replay/entry_points.cpp diff --git a/renderdoc/api/replay/renderdoc_replay.h b/renderdoc/api/replay/renderdoc_replay.h index 1ee6fe299..133fa823f 100644 --- a/renderdoc/api/replay/renderdoc_replay.h +++ b/renderdoc/api/replay/renderdoc_replay.h @@ -762,6 +762,22 @@ newly generated messages will be returned after that. )"); virtual rdcarray GetDebugMessages() = 0; + DOCUMENT(R"(Poll for the current status of the replay. + +This function can be used to monitor to see if a fatal error has been encountered and react +appropriately, such as by displaying a message to the user. The replay controller interface should +remain stable and return null/empty data for the most part, but it's recommended for maximum +stability to stop using the controller when a fatal error is encountered. + +If there has been no error, this will return :data:`ReplayStatus.Succeeded`. If there has been an +error this will return the error code every time, it will not be 'consumed' so it's safe to have +multiple things checking it. + +:return: The current fatal error status. +:rtype: ReplayStatus +)"); + virtual ReplayStatus GetFatalErrorStatus() = 0; + DOCUMENT(R"(Retrieve a list of entry points for a shader. If the given ID doesn't specify a shader, an empty list will be return. On some APIs, the list will diff --git a/renderdoc/api/replay/renderdoc_tostr.inl b/renderdoc/api/replay/renderdoc_tostr.inl index c03dc589a..548969361 100644 --- a/renderdoc/api/replay/renderdoc_tostr.inl +++ b/renderdoc/api/replay/renderdoc_tostr.inl @@ -91,6 +91,9 @@ rdcstr DoStringise(const ReplayStatus &el) "Failed to install Android remote server for unknown reasons"); STRINGISE_ENUM_CLASS_NAMED(AndroidAPKVerifyFailed, "Failed to verify installed Android remote server"); + STRINGISE_ENUM_CLASS_NAMED(RemoteServerConnectionLost, "Connection lost to remote server"); + STRINGISE_ENUM_CLASS_NAMED(ReplayOutOfMemory, "Encountered GPU out of memory error"); + STRINGISE_ENUM_CLASS_NAMED(ReplayDeviceLost, "Encountered a GPU device lost error"); } END_ENUM_STRINGISE(); } diff --git a/renderdoc/api/replay/replay_enums.h b/renderdoc/api/replay/replay_enums.h index 2dc5c8991..f8b08574b 100644 --- a/renderdoc/api/replay/replay_enums.h +++ b/renderdoc/api/replay/replay_enums.h @@ -3656,6 +3656,18 @@ a remote server. .. data:: AndroidAPKVerifyFailed Failed to install Android remote server. + +.. data:: RemoteServerConnectionLost + + While replaying on a remote server, the connection was lost. + +.. data:: ReplayOutOfMemory + + While replaying, a GPU out of memory error was encountered. + +.. data:: ReplayDeviceLost + + While replaying a device lost fatal error was encountered. )"); enum class ReplayStatus : uint32_t { @@ -3684,6 +3696,9 @@ enum class ReplayStatus : uint32_t AndroidAPKFolderNotFound, AndroidAPKInstallFailed, AndroidAPKVerifyFailed, + RemoteServerConnectionLost, + ReplayOutOfMemory, + ReplayDeviceLost, }; DECLARE_REFLECTION_ENUM(ReplayStatus); diff --git a/renderdoc/core/image_viewer.cpp b/renderdoc/core/image_viewer.cpp index 0992eefa7..7e58a89f8 100644 --- a/renderdoc/core/image_viewer.cpp +++ b/renderdoc/core/image_viewer.cpp @@ -25,6 +25,7 @@ #include "common/dds_readwrite.h" #include "core/core.h" #include "maths/formatpacking.h" +#include "replay/dummy_driver.h" #include "replay/replay_driver.h" #include "serialise/rdcfile.h" #include "stb/stb_image.h" @@ -72,12 +73,27 @@ public: virtual ~ImageViewer() { - m_Proxy->Shutdown(); - m_Proxy = NULL; SAFE_DELETE(m_File); + if(m_Proxy) + { + m_Proxy->Shutdown(); + m_Proxy = NULL; + } } bool IsRemoteProxy() { return true; } + ReplayStatus FatalErrorCheck() + { + // check for errors on the underlying proxy driver + return m_Proxy->FatalErrorCheck(); + } + IReplayDriver *MakeDummyDriver() + { + IReplayDriver *ret = new DummyDriver(this, {}); + // lose our structured file reference + m_File = NULL; + return ret; + } void Shutdown() { delete this; } // pass through necessary operations to proxy rdcarray GetSupportedWindowSystems() diff --git a/renderdoc/core/replay_proxy.cpp b/renderdoc/core/replay_proxy.cpp index 1fe682102..c480ea7b4 100644 --- a/renderdoc/core/replay_proxy.cpp +++ b/renderdoc/core/replay_proxy.cpp @@ -26,6 +26,7 @@ #include "replay_proxy.h" #include #include "lz4/lz4.h" +#include "replay/dummy_driver.h" #include "serialise/lz4io.h" template <> @@ -128,25 +129,37 @@ rdcstr DoStringise(const ReplayProxyPacket &el) // begin serialising a return value. We begin a chunk here in either the writing or reading case // since this chunk is used purely to send/receive the return value and is fully handled within the // function. -#define SERIALISE_RETURN(retval) \ - { \ - ReturnSerialiser &ser = retser; \ - PACKET_HEADER(packet); \ - SERIALISE_ELEMENT(retval); \ - GET_SERIALISER.Serialise("packet"_lit, packet); \ - ser.EndChunk(); \ - CheckError(packet, expectedPacket); \ +#define SERIALISE_RETURN(retval) \ + { \ + ReplayStatus fatalStatus = ReplayStatus::Succeeded; \ + if(m_RemoteServer) \ + fatalStatus = m_Remote->FatalErrorCheck(); \ + ReturnSerialiser &ser = retser; \ + PACKET_HEADER(packet); \ + SERIALISE_ELEMENT(retval); \ + GET_SERIALISER.Serialise("fatalStatus"_lit, fatalStatus); \ + GET_SERIALISER.Serialise("packet"_lit, packet); \ + ser.EndChunk(); \ + if(fatalStatus != ReplayStatus::Succeeded && m_FatalError == ReplayStatus::Succeeded) \ + m_FatalError = fatalStatus; \ + CheckError(packet, expectedPacket); \ } // similar to the above, but for void functions that don't return anything. We still want to check // that both sides of the communication are on the same page. -#define SERIALISE_RETURN_VOID() \ - { \ - ReturnSerialiser &ser = retser; \ - PACKET_HEADER(packet); \ - SERIALISE_ELEMENT(packet); \ - ser.EndChunk(); \ - CheckError(packet, expectedPacket); \ +#define SERIALISE_RETURN_VOID() \ + { \ + ReplayStatus fatalStatus = ReplayStatus::Succeeded; \ + if(m_RemoteServer) \ + fatalStatus = m_Remote->FatalErrorCheck(); \ + ReturnSerialiser &ser = retser; \ + PACKET_HEADER(packet); \ + SERIALISE_ELEMENT(packet); \ + GET_SERIALISER.Serialise("fatalStatus"_lit, fatalStatus); \ + ser.EndChunk(); \ + if(fatalStatus != ReplayStatus::Succeeded && m_FatalError == ReplayStatus::Succeeded) \ + m_FatalError = fatalStatus; \ + CheckError(packet, expectedPacket); \ } // defines the area where we're executing on the remote host. To avoid timeouts, the remote side @@ -2766,8 +2779,49 @@ void ReplayProxy::RemoteExecutionThreadEntry() } } +ReplayStatus ReplayProxy::FatalErrorCheck() +{ + // this isn't proxied since it's called at relatively high frequency. Whenever we proxy a + // function, we also return the the remote side's status + if(m_IsErrored) + { + // if we're error'd due to a network issue (i.e. the other side crashed and disconnected) we + // won't have a status, set a generic one + if(m_FatalError == ReplayStatus::Succeeded) + m_FatalError = ReplayStatus::RemoteServerConnectionLost; + + return m_FatalError; + } + + return ReplayStatus::Succeeded; +} + +IReplayDriver *ReplayProxy::MakeDummyDriver() +{ + // gather up the shaders we've allocated to pass to the dummy driver + rdcarray shaders; + for(auto it = m_ShaderReflectionCache.begin(); it != m_ShaderReflectionCache.end(); ++it) + shaders.push_back(it->second); + m_ShaderReflectionCache.clear(); + + IReplayDriver *dummy = new DummyDriver(this, shaders); + + // the dummy driver now owns the file, remove our reference + m_StructuredFile = NULL; + + return dummy; +} + bool ReplayProxy::CheckError(ReplayProxyPacket receivedPacket, ReplayProxyPacket expectedPacket) { + if(m_FatalError != ReplayStatus::Succeeded) + { + RDCERR("Fatal error detected while processing %s: %s", ToStr(expectedPacket).c_str(), + ToStr(m_FatalError).c_str()); + m_IsErrored = true; + return true; + } + if(m_Writer.IsErrored() || m_Reader.IsErrored() || m_IsErrored) { RDCERR("Error during processing of %s", ToStr(expectedPacket).c_str()); diff --git a/renderdoc/core/replay_proxy.h b/renderdoc/core/replay_proxy.h index 21af39464..bfe6869fc 100644 --- a/renderdoc/core/replay_proxy.h +++ b/renderdoc/core/replay_proxy.h @@ -103,6 +103,8 @@ enum ReplayProxyPacket eReplayProxy_ContinueDebug, eReplayProxy_FreeDebugger, + + eReplayProxy_FatalErrorCheck, }; DECLARE_REFLECTION_ENUM(ReplayProxyPacket); @@ -140,6 +142,8 @@ public: void RemoteExecutionThreadEntry(); bool IsRemoteProxy() { return !m_RemoteServer; } + ReplayStatus FatalErrorCheck(); + IReplayDriver *MakeDummyDriver(); void Shutdown() { delete this; } ReplayStatus ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers) { @@ -694,6 +698,7 @@ private: Threading::ThreadHandle m_RemoteExecutionThread = 0; bool m_IsErrored = false; + ReplayStatus m_FatalError = ReplayStatus::Succeeded; FrameRecord m_FrameRecord; APIProperties m_APIProps; diff --git a/renderdoc/driver/d3d11/d3d11_device.h b/renderdoc/driver/d3d11/d3d11_device.h index 92e28449d..26afce5a9 100644 --- a/renderdoc/driver/d3d11/d3d11_device.h +++ b/renderdoc/driver/d3d11/d3d11_device.h @@ -601,6 +601,7 @@ private: SDFile *m_StoredStructuredData; rdcarray m_DebugMessages; + ReplayStatus m_FatalError = ReplayStatus::Succeeded; rdcarray m_CapturedFrames; rdcarray m_Actions; @@ -660,10 +661,12 @@ public: void UnlockForChunkRemoval(); SDFile *GetStructuredFile() { return m_StructuredFile; } + void DetachStructuredFile() { m_StoredStructuredData = m_StructuredFile = NULL; } uint64_t GetTimeBase() { return m_TimeBase; } double GetTimeFrequency() { return m_TimeFrequency; } void FirstFrame(IDXGISwapper *swapper); + ReplayStatus FatalErrorCheck() { return m_FatalError; } rdcarray GetDebugMessages(); void AddDebugMessage(DebugMessage msg); void AddDebugMessage(MessageCategory c, MessageSeverity sv, MessageSource src, rdcstr d); diff --git a/renderdoc/driver/d3d11/d3d11_replay.cpp b/renderdoc/driver/d3d11/d3d11_replay.cpp index e6a525a28..cfb84ed56 100644 --- a/renderdoc/driver/d3d11/d3d11_replay.cpp +++ b/renderdoc/driver/d3d11/d3d11_replay.cpp @@ -32,6 +32,7 @@ #include "maths/camera.h" #include "maths/formatpacking.h" #include "maths/matrix.h" +#include "replay/dummy_driver.h" #include "serialise/rdcfile.h" #include "strings/string_utils.h" #include "d3d11_context.h" @@ -77,10 +78,31 @@ void D3D11Replay::Shutdown() m_ProxyResources[i]->Release(); m_ProxyResources.clear(); + m_RealState.state.Clear(); + // explicitly delete the device, as all the replay resources created will be keeping refs on it delete m_pDevice; } +ReplayStatus D3D11Replay::FatalErrorCheck() +{ + return m_pDevice->FatalErrorCheck(); +} + +IReplayDriver *D3D11Replay::MakeDummyDriver() +{ + // gather up the shaders we've allocated to pass to the dummy driver + rdcarray shaders; + WrappedID3D11Shader::GetReflections(shaders); + + IReplayDriver *dummy = new DummyDriver(this, shaders); + + // the dummy driver now owns the file, remove our reference + m_pDevice->DetachStructuredFile(); + + return dummy; +} + void D3D11Replay::CreateResources(IDXGIFactory *factory) { bool wrapped = diff --git a/renderdoc/driver/d3d11/d3d11_replay.h b/renderdoc/driver/d3d11/d3d11_replay.h index 669337b13..60bfcb9d0 100644 --- a/renderdoc/driver/d3d11/d3d11_replay.h +++ b/renderdoc/driver/d3d11/d3d11_replay.h @@ -109,6 +109,9 @@ public: bool IsRemoteProxy() { return m_Proxy; } void Shutdown(); + ReplayStatus FatalErrorCheck(); + IReplayDriver *MakeDummyDriver(); + void CreateResources(IDXGIFactory *factory); void DestroyResources(); diff --git a/renderdoc/driver/d3d11/d3d11_resources.h b/renderdoc/driver/d3d11/d3d11_resources.h index 3cae51067..4e692699d 100644 --- a/renderdoc/driver/d3d11/d3d11_resources.h +++ b/renderdoc/driver/d3d11/d3d11_resources.h @@ -1041,6 +1041,8 @@ public: DXBC::DXBCContainer *m_DXBCFile; ShaderReflection *m_Details; ShaderBindpointMapping m_Mapping; + + friend class WrappedShader; }; static std::map m_ShaderList; @@ -1094,6 +1096,17 @@ public: return m_ShaderList[m_ID]->GetMapping(); } + static void GetReflections(rdcarray &refls) + { + SCOPED_LOCK(m_ShaderListLock); + refls.clear(); + for(auto it = m_ShaderList.begin(); it != m_ShaderList.end(); ++it) + { + refls.push_back(it->second->m_Details); + it->second->m_Details = NULL; + } + } + private: ResourceId m_ID; }; diff --git a/renderdoc/driver/d3d12/d3d12_device.h b/renderdoc/driver/d3d12/d3d12_device.h index 3e580a03c..9209fca14 100644 --- a/renderdoc/driver/d3d12/d3d12_device.h +++ b/renderdoc/driver/d3d12/d3d12_device.h @@ -667,6 +667,7 @@ private: rdcarray m_ThreadTempMem; rdcarray m_DebugMessages; + ReplayStatus m_FatalError = ReplayStatus::Succeeded; uint64_t m_TimeBase = 0; double m_TimeFrequency = 1.0f; @@ -799,7 +800,7 @@ public: void AddDebugMessage(MessageCategory c, MessageSeverity sv, MessageSource src, rdcstr d); void AddDebugMessage(const DebugMessage &msg); rdcarray GetDebugMessages(); - + ReplayStatus FatalErrorCheck() { return m_FatalError; } ResourceDescription &GetResourceDesc(ResourceId id); void AddResource(ResourceId id, ResourceType type, const char *defaultNamePrefix); void DerivedResource(ResourceId parent, ResourceId child); @@ -926,6 +927,7 @@ public: m_State = CaptureState::StructuredExport; } SDFile *GetStructuredFile() { return m_StructuredFile; } + void DetachStructuredFile() { m_StoredStructuredData = m_StructuredFile = NULL; } uint64_t GetTimeBase() { return m_TimeBase; } double GetTimeFrequency() { return m_TimeFrequency; } // interface for DXGI diff --git a/renderdoc/driver/d3d12/d3d12_replay.cpp b/renderdoc/driver/d3d12/d3d12_replay.cpp index aa940798d..3f7752a2f 100644 --- a/renderdoc/driver/d3d12/d3d12_replay.cpp +++ b/renderdoc/driver/d3d12/d3d12_replay.cpp @@ -32,6 +32,7 @@ #include "maths/camera.h" #include "maths/formatpacking.h" #include "maths/matrix.h" +#include "replay/dummy_driver.h" #include "serialise/rdcfile.h" #include "strings/string_utils.h" #include "d3d12_command_queue.h" @@ -114,6 +115,25 @@ void D3D12Replay::Initialise(IDXGIFactory1 *factory) } } +ReplayStatus D3D12Replay::FatalErrorCheck() +{ + return m_pDevice->FatalErrorCheck(); +} + +IReplayDriver *D3D12Replay::MakeDummyDriver() +{ + // gather up the shaders we've allocated to pass to the dummy driver + rdcarray shaders; + WrappedID3D12Shader::GetReflections(shaders); + + IReplayDriver *dummy = new DummyDriver(this, shaders); + + // the dummy driver now owns the file, remove our reference + m_pDevice->DetachStructuredFile(); + + return dummy; +} + void D3D12Replay::CreateResources() { m_DebugManager = new D3D12DebugManager(m_pDevice); diff --git a/renderdoc/driver/d3d12/d3d12_replay.h b/renderdoc/driver/d3d12/d3d12_replay.h index 62931d86c..1405faa33 100644 --- a/renderdoc/driver/d3d12/d3d12_replay.h +++ b/renderdoc/driver/d3d12/d3d12_replay.h @@ -84,6 +84,9 @@ public: void Initialise(IDXGIFactory1 *factory); void Shutdown(); + ReplayStatus FatalErrorCheck(); + IReplayDriver *MakeDummyDriver(); + void CreateResources(); void DestroyResources(); DriverInformation GetDriverInfo() { return m_DriverInfo; } diff --git a/renderdoc/driver/d3d12/d3d12_resources.h b/renderdoc/driver/d3d12/d3d12_resources.h index 848000951..e05a1f45e 100644 --- a/renderdoc/driver/d3d12/d3d12_resources.h +++ b/renderdoc/driver/d3d12/d3d12_resources.h @@ -744,6 +744,16 @@ public: return false; } + static void GetReflections(rdcarray &refls) + { + refls.clear(); + for(auto it = m_Shaders.begin(); it != m_Shaders.end(); ++it) + { + refls.push_back(it->second->m_Details); + it->second->m_Details = NULL; + } + } + void GetShaderExtSlot(uint32_t &slot, uint32_t &space) { slot = m_ShaderExtSlot; diff --git a/renderdoc/driver/gl/gl_driver.h b/renderdoc/driver/gl/gl_driver.h index f0a6dee98..1414f45af 100644 --- a/renderdoc/driver/gl/gl_driver.h +++ b/renderdoc/driver/gl/gl_driver.h @@ -111,11 +111,12 @@ private: GLPlatform &m_Platform; + ReplayStatus m_FatalError = ReplayStatus::Succeeded; rdcarray m_DebugMessages; template void Serialise_DebugMessages(SerialiserType &ser); rdcarray GetDebugMessages(); - + ReplayStatus FatalErrorCheck() { return m_FatalError; } rdcstr m_DebugMsgContext; bool m_SuppressDebugMessages; @@ -633,6 +634,7 @@ public: m_State = CaptureState::StructuredExport; } SDFile *GetStructuredFile() { return m_StructuredFile; } + void DetachStructuredFile() { m_StoredStructuredData = m_StructuredFile = NULL; } void SetFetchCounters(bool in) { m_FetchCounters = in; }; void SetDebugMsgContext(const rdcstr &context) { m_DebugMsgContext = context; } void AddDebugMessage(DebugMessage msg) diff --git a/renderdoc/driver/gl/gl_replay.cpp b/renderdoc/driver/gl/gl_replay.cpp index 4e9725397..ff3149ef0 100644 --- a/renderdoc/driver/gl/gl_replay.cpp +++ b/renderdoc/driver/gl/gl_replay.cpp @@ -29,6 +29,7 @@ #include "driver/ihv/arm/arm_counters.h" #include "driver/ihv/intel/intel_gl_counters.h" #include "maths/matrix.h" +#include "replay/dummy_driver.h" #include "serialise/rdcfile.h" #include "strings/string_utils.h" #include "gl_driver.h" @@ -86,6 +87,29 @@ void GLReplay::Shutdown() delete m_pDriver; } +ReplayStatus GLReplay::FatalErrorCheck() +{ + return m_pDriver->FatalErrorCheck(); +} + +IReplayDriver *GLReplay::MakeDummyDriver() +{ + // gather up the shaders we've allocated to pass to the dummy driver + rdcarray shaders; + for(auto it = m_pDriver->m_Shaders.begin(); it != m_pDriver->m_Shaders.end(); it++) + { + shaders.push_back(it->second.reflection); + it->second.reflection = NULL; + } + + IReplayDriver *dummy = new DummyDriver(this, shaders); + + // the dummy driver now owns the file, remove our reference + m_pDriver->DetachStructuredFile(); + + return dummy; +} + ReplayStatus GLReplay::ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers) { MakeCurrentReplayContext(&m_ReplayCtx); diff --git a/renderdoc/driver/gl/gl_replay.h b/renderdoc/driver/gl/gl_replay.h index 209b671d4..4a7d32efb 100644 --- a/renderdoc/driver/gl/gl_replay.h +++ b/renderdoc/driver/gl/gl_replay.h @@ -115,6 +115,9 @@ public: bool IsRemoteProxy() { return m_Proxy; } void Shutdown(); + ReplayStatus FatalErrorCheck(); + IReplayDriver *MakeDummyDriver(); + DriverInformation GetDriverInfo() { return m_DriverInfo; } rdcarray GetAvailableGPUs(); APIProperties GetAPIProperties(); diff --git a/renderdoc/driver/vulkan/vk_core.h b/renderdoc/driver/vulkan/vk_core.h index ab334de27..5b498eccd 100644 --- a/renderdoc/driver/vulkan/vk_core.h +++ b/renderdoc/driver/vulkan/vk_core.h @@ -286,10 +286,11 @@ private: void Serialise_DebugMessages(SerialiserType &ser); void ProcessDebugMessage(DebugMessage &DebugMessages); - rdcarray GetDebugMessages(); void AddDebugMessage(DebugMessage msg); + ReplayStatus m_FatalError = ReplayStatus::Succeeded; + ReplayStatus FatalErrorCheck() { return m_FatalError; } CaptureState m_State; bool m_AppControlledCapture = false; @@ -1040,6 +1041,7 @@ public: ReplayStatus ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers); SDFile *GetStructuredFile() { return m_StructuredFile; } + void DetachStructuredFile() { m_StoredStructuredData = m_StructuredFile = NULL; } const APIEvent &GetEvent(uint32_t eventId); uint32_t GetMaxEID() { return m_Events.back().eventId; } const ActionDescription *GetAction(uint32_t eventId); diff --git a/renderdoc/driver/vulkan/vk_replay.cpp b/renderdoc/driver/vulkan/vk_replay.cpp index 8fcabbfc1..957d9f5bb 100644 --- a/renderdoc/driver/vulkan/vk_replay.cpp +++ b/renderdoc/driver/vulkan/vk_replay.cpp @@ -32,6 +32,7 @@ #include "driver/shaders/spirv/spirv_compile.h" #include "maths/formatpacking.h" #include "maths/matrix.h" +#include "replay/dummy_driver.h" #include "serialise/rdcfile.h" #include "strings/string_utils.h" #include "vk_core.h" @@ -83,6 +84,34 @@ void VulkanReplay::Shutdown() delete m_pDriver; } +ReplayStatus VulkanReplay::FatalErrorCheck() +{ + return m_pDriver->FatalErrorCheck(); +} + +IReplayDriver *VulkanReplay::MakeDummyDriver() +{ + // gather up the shaders we've allocated to pass to the dummy driver + rdcarray shaders; + for(auto it = m_pDriver->m_CreationInfo.m_ShaderModule.begin(); + it != m_pDriver->m_CreationInfo.m_ShaderModule.end(); it++) + { + for(auto reflit = it->second.m_Reflections.begin(); reflit != it->second.m_Reflections.end(); + ++reflit) + { + shaders.push_back(reflit->second.refl); + reflit->second.refl = NULL; + } + } + + IReplayDriver *dummy = new DummyDriver(this, shaders); + + // the dummy driver now owns the file, remove our reference + m_pDriver->DetachStructuredFile(); + + return dummy; +} + rdcarray VulkanReplay::GetAvailableGPUs() { rdcarray ret; diff --git a/renderdoc/driver/vulkan/vk_replay.h b/renderdoc/driver/vulkan/vk_replay.h index b85cbcf80..884f02877 100644 --- a/renderdoc/driver/vulkan/vk_replay.h +++ b/renderdoc/driver/vulkan/vk_replay.h @@ -267,6 +267,9 @@ public: bool IsRemoteProxy() { return m_Proxy; } void Shutdown(); + ReplayStatus FatalErrorCheck(); + IReplayDriver *MakeDummyDriver(); + void CreateResources(); void DestroyResources(); diff --git a/renderdoc/renderdoc.vcxproj b/renderdoc/renderdoc.vcxproj index 8542f3c74..dae3df582 100644 --- a/renderdoc/renderdoc.vcxproj +++ b/renderdoc/renderdoc.vcxproj @@ -242,6 +242,7 @@ + @@ -656,6 +657,7 @@ + diff --git a/renderdoc/renderdoc.vcxproj.filters b/renderdoc/renderdoc.vcxproj.filters index d05fa6625..d80d8a8a2 100644 --- a/renderdoc/renderdoc.vcxproj.filters +++ b/renderdoc/renderdoc.vcxproj.filters @@ -570,6 +570,9 @@ 3rdparty\md5 + + Replay + @@ -986,6 +989,9 @@ 3rdparty\md5 + + Replay + diff --git a/renderdoc/replay/dummy_driver.cpp b/renderdoc/replay/dummy_driver.cpp new file mode 100644 index 000000000..6c7e521c8 --- /dev/null +++ b/renderdoc/replay/dummy_driver.cpp @@ -0,0 +1,505 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2021 Baldur Karlsson + * + * 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 "dummy_driver.h" + +DummyDriver::DummyDriver(IReplayDriver *original, const rdcarray &shaders) +{ + m_Shaders = shaders; + + m_Props = original->GetAPIProperties(); + m_Resources = original->GetResources(); + m_Buffers = original->GetBuffers(); + m_Textures = original->GetTextures(); + m_FrameRecord = original->GetFrameRecord(); + m_SDFile = original->GetStructuredFile(); + m_TargetEncodings = original->GetTargetShaderEncodings(); + m_DriverInfo = original->GetDriverInfo(); + + m_Proxy = original->IsRemoteProxy(); + m_GPUs = original->GetAvailableGPUs(); + m_WindowSystems = original->GetSupportedWindowSystems(); + m_CustomEncodings = original->GetCustomShaderEncodings(); +} + +DummyDriver::~DummyDriver() +{ + // we own the shaders + for(ShaderReflection *refl : m_Shaders) + delete refl; + + // and we own the structured file + delete m_SDFile; +} + +void DummyDriver::Shutdown() +{ + delete this; +} + +APIProperties DummyDriver::GetAPIProperties() +{ + return m_Props; +} + +rdcarray DummyDriver::GetResources() +{ + return m_Resources; +} + +rdcarray DummyDriver::GetBuffers() +{ + return m_Buffers; +} + +BufferDescription DummyDriver::GetBuffer(ResourceId id) +{ + for(const BufferDescription &buf : m_Buffers) + { + if(buf.resourceId == id) + return buf; + } + + return {}; +} + +rdcarray DummyDriver::GetTextures() +{ + return m_Textures; +} + +TextureDescription DummyDriver::GetTexture(ResourceId id) +{ + for(const TextureDescription &tex : m_Textures) + { + if(tex.resourceId == id) + return tex; + } + + return {}; +} + +rdcarray DummyDriver::GetDebugMessages() +{ + return {}; +} + +rdcarray DummyDriver::GetShaderEntryPoints(ResourceId shader) +{ + return {{"main", ShaderStage::Vertex}}; +} + +ShaderReflection *DummyDriver::GetShader(ResourceId pipeline, ResourceId shader, + ShaderEntryPoint entry) +{ + return NULL; +} + +rdcarray DummyDriver::GetDisassemblyTargets(bool withPipeline) +{ + return {"Disassembly"}; +} + +rdcstr DummyDriver::DisassembleShader(ResourceId pipeline, const ShaderReflection *refl, + const rdcstr &target) +{ + return "; No disassembly available due to unrecoverable error analysing capture."; +} + +rdcarray DummyDriver::GetUsage(ResourceId id) +{ + return {}; +} + +void DummyDriver::SetPipelineStates(D3D11Pipe::State *d3d11, D3D12Pipe::State *d3d12, + GLPipe::State *gl, VKPipe::State *vk) +{ +} + +void DummyDriver::SavePipelineState(uint32_t eventId) +{ +} + +FrameRecord DummyDriver::GetFrameRecord() +{ + return m_FrameRecord; +} + +ReplayStatus DummyDriver::ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers) +{ + return ReplayStatus::APIReplayFailed; +} + +void DummyDriver::ReplayLog(uint32_t endEventID, ReplayLogType replayType) +{ +} + +SDFile *DummyDriver::GetStructuredFile() +{ + return m_SDFile; +} + +rdcarray DummyDriver::GetPassEvents(uint32_t eventId) +{ + return {eventId}; +} + +void DummyDriver::InitPostVSBuffers(uint32_t eventId) +{ +} + +void DummyDriver::InitPostVSBuffers(const rdcarray &passEvents) +{ +} + +ResourceId DummyDriver::GetLiveID(ResourceId id) +{ + return id; +} + +MeshFormat DummyDriver::GetPostVSBuffers(uint32_t eventId, uint32_t instID, uint32_t viewID, + MeshDataStage stage) +{ + return {}; +} + +void DummyDriver::GetBufferData(ResourceId buff, uint64_t offset, uint64_t len, bytebuf &retData) +{ + retData.clear(); +} + +void DummyDriver::GetTextureData(ResourceId tex, const Subresource &sub, + const GetTextureDataParams ¶ms, bytebuf &data) +{ + data.clear(); +} + +void DummyDriver::BuildTargetShader(ShaderEncoding sourceEncoding, const bytebuf &source, + const rdcstr &entry, const ShaderCompileFlags &compileFlags, + ShaderStage type, ResourceId &id, rdcstr &errors) +{ + id = ResourceId(); + errors = "Unrecoverable error encountered while analysing capture"; +} + +rdcarray DummyDriver::GetTargetShaderEncodings() +{ + return {ShaderEncoding::HLSL, ShaderEncoding::GLSL}; +} + +void DummyDriver::ReplaceResource(ResourceId from, ResourceId to) +{ +} + +void DummyDriver::RemoveReplacement(ResourceId id) +{ +} + +void DummyDriver::FreeTargetResource(ResourceId id) +{ +} + +rdcarray DummyDriver::EnumerateCounters() +{ + return {}; +} + +CounterDescription DummyDriver::DescribeCounter(GPUCounter counterID) +{ + return {}; +} + +rdcarray DummyDriver::FetchCounters(const rdcarray &counterID) +{ + return {}; +} + +void DummyDriver::FillCBufferVariables(ResourceId pipeline, ResourceId shader, rdcstr entryPoint, + uint32_t cbufSlot, rdcarray &outvars, + const bytebuf &data) +{ + outvars.clear(); +} + +rdcarray DummyDriver::PixelHistory(rdcarray events, + ResourceId target, uint32_t x, uint32_t y, + const Subresource &sub, CompType typeCast) +{ + return {}; +} + +ShaderDebugTrace *DummyDriver::DebugVertex(uint32_t eventId, uint32_t vertid, uint32_t instid, + uint32_t idx, uint32_t view) +{ + return new ShaderDebugTrace; +} + +ShaderDebugTrace *DummyDriver::DebugPixel(uint32_t eventId, uint32_t x, uint32_t y, uint32_t sample, + uint32_t primitive) +{ + return new ShaderDebugTrace; +} + +ShaderDebugTrace *DummyDriver::DebugThread(uint32_t eventId, + const rdcfixedarray &groupid, + const rdcfixedarray &threadid) +{ + return new ShaderDebugTrace; +} + +rdcarray DummyDriver::ContinueDebug(ShaderDebugger *debugger) +{ + return {}; +} + +void DummyDriver::FreeDebugger(ShaderDebugger *debugger) +{ +} + +ResourceId DummyDriver::RenderOverlay(ResourceId texid, FloatVector clearCol, DebugOverlay overlay, + uint32_t eventId, const rdcarray &passEvents) +{ + return ResourceId(); +} + +bool DummyDriver::IsRenderOutput(ResourceId id) +{ + return false; +} + +void DummyDriver::FileChanged() +{ +} + +bool DummyDriver::NeedRemapForFetch(const ResourceFormat &format) +{ + return false; +} + +DriverInformation DummyDriver::GetDriverInfo() +{ + return m_DriverInfo; +} + +rdcarray DummyDriver::GetAvailableGPUs() +{ + return m_GPUs; +} + +bool DummyDriver::IsRemoteProxy() +{ + return m_Proxy; +} + +ReplayStatus DummyDriver::FatalErrorCheck() +{ + return ReplayStatus::Succeeded; +} + +IReplayDriver *DummyDriver::MakeDummyDriver() +{ + return NULL; +} + +rdcarray DummyDriver::GetSupportedWindowSystems() +{ + rdcarray ret; +#if ENABLED(RDOC_LINUX) + +#if ENABLED(RDOC_XLIB) + ret.push_back(WindowingSystem::Xlib); +#endif + +#if ENABLED(RDOC_XCB) + ret.push_back(WindowingSystem::XCB); +#endif + +#if ENABLED(RDOC_WAYLAND) + ret.push_back(WindowingSystem::Wayland); +#endif + +#elif ENABLED(RDOC_WIN32) + + ret.push_back(WindowingSystem::Win32); + +#elif ENABLED(RDOC_ANDROID) + + ret.push_back(WindowingSystem::Android); + +#elif ENABLED(RDOC_APPLE) + + ret.push_back(WindowingSystem::MacOS); + +#endif + + return ret; +} + +AMDRGPControl *DummyDriver::GetRGPControl() +{ + return NULL; +} + +uint64_t DummyDriver::MakeOutputWindow(WindowingData window, bool depth) +{ + return 1; +} + +void DummyDriver::DestroyOutputWindow(uint64_t id) +{ +} + +bool DummyDriver::CheckResizeOutputWindow(uint64_t id) +{ + return false; +} + +void DummyDriver::SetOutputWindowDimensions(uint64_t id, int32_t w, int32_t h) +{ +} + +void DummyDriver::GetOutputWindowDimensions(uint64_t id, int32_t &w, int32_t &h) +{ +} + +void DummyDriver::GetOutputWindowData(uint64_t id, bytebuf &retData) +{ +} + +void DummyDriver::ClearOutputWindowColor(uint64_t id, FloatVector col) +{ +} + +void DummyDriver::ClearOutputWindowDepth(uint64_t id, float depth, uint8_t stencil) +{ +} + +void DummyDriver::BindOutputWindow(uint64_t id, bool depth) +{ +} + +bool DummyDriver::IsOutputWindowVisible(uint64_t id) +{ + return true; +} + +void DummyDriver::FlipOutputWindow(uint64_t id) +{ +} + +bool DummyDriver::GetMinMax(ResourceId texid, const Subresource &sub, CompType typeCast, + float *minval, float *maxval) +{ + *minval = 0.0f; + *maxval = 1.0f; + return false; +} + +bool DummyDriver::GetHistogram(ResourceId texid, const Subresource &sub, CompType typeCast, + float minval, float maxval, const rdcfixedarray &channels, + rdcarray &histogram) +{ + histogram.fill(256, 0); + return false; +} + +void DummyDriver::PickPixel(ResourceId texture, uint32_t x, uint32_t y, const Subresource &sub, + CompType typeCast, float pixel[4]) +{ +} + +ResourceId DummyDriver::CreateProxyTexture(const TextureDescription &templateTex) +{ + return ResourceId(); +} + +void DummyDriver::SetProxyTextureData(ResourceId texid, const Subresource &sub, byte *data, + size_t dataSize) +{ +} + +bool DummyDriver::IsTextureSupported(const TextureDescription &tex) +{ + return true; +} + +ResourceId DummyDriver::CreateProxyBuffer(const BufferDescription &templateBuf) +{ + return ResourceId(); +} + +void DummyDriver::SetProxyBufferData(ResourceId bufid, byte *data, size_t dataSize) +{ +} + +void DummyDriver::RenderMesh(uint32_t eventId, const rdcarray &secondaryDraws, + const MeshDisplay &cfg) +{ +} + +bool DummyDriver::RenderTexture(TextureDisplay cfg) +{ + return false; +} + +void DummyDriver::SetCustomShaderIncludes(const rdcarray &directories) +{ +} + +void DummyDriver::BuildCustomShader(ShaderEncoding sourceEncoding, const bytebuf &source, + const rdcstr &entry, const ShaderCompileFlags &compileFlags, + ShaderStage type, ResourceId &id, rdcstr &errors) +{ + id = ResourceId(); + errors = "Unrecoverable error encountered while analysing capture"; +} + +rdcarray DummyDriver::GetCustomShaderEncodings() +{ + return {ShaderEncoding::HLSL, ShaderEncoding::GLSL}; +} + +ResourceId DummyDriver::ApplyCustomShader(ResourceId shader, ResourceId texid, + const Subresource &sub, CompType typeCast) +{ + return ResourceId(); +} + +void DummyDriver::FreeCustomShader(ResourceId id) +{ +} + +void DummyDriver::RenderCheckerboard(FloatVector dark, FloatVector light) +{ +} + +void DummyDriver::RenderHighlightBox(float w, float h, float scale) +{ +} + +uint32_t DummyDriver::PickVertex(uint32_t eventId, int32_t width, int32_t height, + const MeshDisplay &cfg, uint32_t x, uint32_t y) +{ + return ~0U; +} diff --git a/renderdoc/replay/dummy_driver.h b/renderdoc/replay/dummy_driver.h new file mode 100644 index 000000000..b424ab2c2 --- /dev/null +++ b/renderdoc/replay/dummy_driver.h @@ -0,0 +1,199 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2021 Baldur Karlsson + * + * 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. + ******************************************************************************/ + +#pragma once + +#include "replay_driver.h" + +// this is a dummy driver that can be swapped to if the underlying replay/remote driver encounters +// some fatal problem and shouldn't be used further. It saves significantly on needing to +// bullet-proof each driver since instead we just need to make error paths clear and short, and rely +// on the replay controller to detect errors and swap to a dummy driver immediately +class DummyDriver : public IReplayDriver +{ +public: + DummyDriver(IReplayDriver *original, const rdcarray &shaders); + + void Shutdown(); + + APIProperties GetAPIProperties(); + + rdcarray GetResources(); + + rdcarray GetBuffers(); + BufferDescription GetBuffer(ResourceId id); + + rdcarray GetTextures(); + TextureDescription GetTexture(ResourceId id); + + rdcarray GetDebugMessages(); + + rdcarray GetShaderEntryPoints(ResourceId shader); + ShaderReflection *GetShader(ResourceId pipeline, ResourceId shader, ShaderEntryPoint entry); + + rdcarray GetDisassemblyTargets(bool withPipeline); + rdcstr DisassembleShader(ResourceId pipeline, const ShaderReflection *refl, const rdcstr &target); + + rdcarray GetUsage(ResourceId id); + + void SetPipelineStates(D3D11Pipe::State *d3d11, D3D12Pipe::State *d3d12, GLPipe::State *gl, + VKPipe::State *vk); + void SavePipelineState(uint32_t eventId); + + FrameRecord GetFrameRecord(); + + ReplayStatus ReadLogInitialisation(RDCFile *rdc, bool storeStructuredBuffers); + void ReplayLog(uint32_t endEventID, ReplayLogType replayType); + SDFile *GetStructuredFile(); + + rdcarray GetPassEvents(uint32_t eventId); + + void InitPostVSBuffers(uint32_t eventId); + void InitPostVSBuffers(const rdcarray &passEvents); + + ResourceId GetLiveID(ResourceId id); + + MeshFormat GetPostVSBuffers(uint32_t eventId, uint32_t instID, uint32_t viewID, + MeshDataStage stage); + + void GetBufferData(ResourceId buff, uint64_t offset, uint64_t len, bytebuf &retData); + void GetTextureData(ResourceId tex, const Subresource &sub, const GetTextureDataParams ¶ms, + bytebuf &data); + + void BuildTargetShader(ShaderEncoding sourceEncoding, const bytebuf &source, const rdcstr &entry, + const ShaderCompileFlags &compileFlags, ShaderStage type, ResourceId &id, + rdcstr &errors); + rdcarray GetTargetShaderEncodings(); + void ReplaceResource(ResourceId from, ResourceId to); + void RemoveReplacement(ResourceId id); + void FreeTargetResource(ResourceId id); + + rdcarray EnumerateCounters(); + CounterDescription DescribeCounter(GPUCounter counterID); + rdcarray FetchCounters(const rdcarray &counterID); + + void FillCBufferVariables(ResourceId pipeline, ResourceId shader, rdcstr entryPoint, + uint32_t cbufSlot, rdcarray &outvars, + const bytebuf &data); + + rdcarray PixelHistory(rdcarray events, ResourceId target, uint32_t x, + uint32_t y, const Subresource &sub, CompType typeCast); + ShaderDebugTrace *DebugVertex(uint32_t eventId, uint32_t vertid, uint32_t instid, uint32_t idx, + uint32_t view); + ShaderDebugTrace *DebugPixel(uint32_t eventId, uint32_t x, uint32_t y, uint32_t sample, + uint32_t primitive); + ShaderDebugTrace *DebugThread(uint32_t eventId, const rdcfixedarray &groupid, + const rdcfixedarray &threadid); + rdcarray ContinueDebug(ShaderDebugger *debugger); + void FreeDebugger(ShaderDebugger *debugger); + + ResourceId RenderOverlay(ResourceId texid, FloatVector clearCol, DebugOverlay overlay, + uint32_t eventId, const rdcarray &passEvents); + + bool IsRenderOutput(ResourceId id); + + void FileChanged(); + + bool NeedRemapForFetch(const ResourceFormat &format); + + DriverInformation GetDriverInfo(); + + rdcarray GetAvailableGPUs(); + + // IReplayDriver + bool IsRemoteProxy(); + + ReplayStatus FatalErrorCheck(); + IReplayDriver *MakeDummyDriver(); + + rdcarray GetSupportedWindowSystems(); + + AMDRGPControl *GetRGPControl(); + + uint64_t MakeOutputWindow(WindowingData window, bool depth); + void DestroyOutputWindow(uint64_t id); + bool CheckResizeOutputWindow(uint64_t id); + void SetOutputWindowDimensions(uint64_t id, int32_t w, int32_t h); + void GetOutputWindowDimensions(uint64_t id, int32_t &w, int32_t &h); + void GetOutputWindowData(uint64_t id, bytebuf &retData); + void ClearOutputWindowColor(uint64_t id, FloatVector col); + void ClearOutputWindowDepth(uint64_t id, float depth, uint8_t stencil); + void BindOutputWindow(uint64_t id, bool depth); + bool IsOutputWindowVisible(uint64_t id); + void FlipOutputWindow(uint64_t id); + + bool GetMinMax(ResourceId texid, const Subresource &sub, CompType typeCast, float *minval, + float *maxval); + bool GetHistogram(ResourceId texid, const Subresource &sub, CompType typeCast, float minval, + float maxval, const rdcfixedarray &channels, + rdcarray &histogram); + void PickPixel(ResourceId texture, uint32_t x, uint32_t y, const Subresource &sub, + CompType typeCast, float pixel[4]); + + ResourceId CreateProxyTexture(const TextureDescription &templateTex); + void SetProxyTextureData(ResourceId texid, const Subresource &sub, byte *data, size_t dataSize); + bool IsTextureSupported(const TextureDescription &tex); + + ResourceId CreateProxyBuffer(const BufferDescription &templateBuf); + void SetProxyBufferData(ResourceId bufid, byte *data, size_t dataSize); + + void RenderMesh(uint32_t eventId, const rdcarray &secondaryDraws, + const MeshDisplay &cfg); + bool RenderTexture(TextureDisplay cfg); + + void SetCustomShaderIncludes(const rdcarray &directories); + void BuildCustomShader(ShaderEncoding sourceEncoding, const bytebuf &source, const rdcstr &entry, + const ShaderCompileFlags &compileFlags, ShaderStage type, ResourceId &id, + rdcstr &errors); + rdcarray GetCustomShaderEncodings(); + ResourceId ApplyCustomShader(ResourceId shader, ResourceId texid, const Subresource &sub, + CompType typeCast); + void FreeCustomShader(ResourceId id); + + void RenderCheckerboard(FloatVector dark, FloatVector light); + + void RenderHighlightBox(float w, float h, float scale); + + uint32_t PickVertex(uint32_t eventId, int32_t width, int32_t height, const MeshDisplay &cfg, + uint32_t x, uint32_t y); + +private: + virtual ~DummyDriver(); + + rdcarray m_Shaders; + SDFile *m_SDFile; + + APIProperties m_Props; + rdcarray m_Resources; + rdcarray m_Buffers; + rdcarray m_Textures; + FrameRecord m_FrameRecord; + rdcarray m_TargetEncodings; + DriverInformation m_DriverInfo; + + bool m_Proxy; + rdcarray m_GPUs; + rdcarray m_WindowSystems; + rdcarray m_CustomEncodings; +}; diff --git a/renderdoc/replay/replay_controller.cpp b/renderdoc/replay/replay_controller.cpp index 37ce43b1d..2610e9402 100644 --- a/renderdoc/replay/replay_controller.cpp +++ b/renderdoc/replay/replay_controller.cpp @@ -77,11 +77,13 @@ void ReplayController::SetFrameEvent(uint32_t eventId, bool force) m_EventID = eventId; m_pDevice->ReplayLog(eventId, eReplay_WithoutDraw); + FatalErrorCheck(); for(size_t i = 0; i < m_Outputs.size(); i++) m_Outputs[i]->SetFrameEvent(eventId); m_pDevice->ReplayLog(eventId, eReplay_OnlyDraw); + FatalErrorCheck(); FetchPipelineState(eventId); } @@ -154,7 +156,9 @@ rdcstr ReplayController::DisassembleShader(ResourceId pipeline, const ShaderRefl if(t == target) return GCNISA::Disassemble(refl->encoding, refl->stage, refl->rawBytes, target); - return m_pDevice->DisassembleShader(m_pDevice->GetLiveID(pipeline), refl, target); + rdcstr ret = m_pDevice->DisassembleShader(m_pDevice->GetLiveID(pipeline), refl, target); + FatalErrorCheck(); + return ret; } FrameDescription ReplayController::GetFrameInfo() @@ -414,7 +418,9 @@ rdcarray ReplayController::FetchCounters(const rdcarrayFetchCounters(counters); + rdcarray ret = m_pDevice->FetchCounters(counters); + FatalErrorCheck(); + return ret; } rdcarray ReplayController::EnumerateCounters() @@ -490,17 +496,18 @@ MeshFormat ReplayController::GetPostVSData(uint32_t instID, uint32_t viewID, Mes ActionDescription *action = GetActionByEID(m_EventID); - MeshFormat ret; - RDCEraseEl(ret); - if(action == NULL || !(action->flags & ActionFlags::Drawcall)) return MeshFormat(); instID = RDCMIN(instID, action->numInstances - 1); m_pDevice->InitPostVSBuffers(action->eventId); + FatalErrorCheck(); - return m_pDevice->GetPostVSBuffers(action->eventId, instID, viewID, stage); + MeshFormat ret = m_pDevice->GetPostVSBuffers(action->eventId, instID, viewID, stage); + FatalErrorCheck(); + + return ret; } bytebuf ReplayController::GetBufferData(ResourceId buff, uint64_t offset, uint64_t len) @@ -521,6 +528,7 @@ bytebuf ReplayController::GetBufferData(ResourceId buff, uint64_t offset, uint64 } m_pDevice->GetBufferData(liveId, offset, len, retData); + FatalErrorCheck(); return retData; } @@ -541,6 +549,7 @@ bytebuf ReplayController::GetTextureData(ResourceId tex, const Subresource &sub) } m_pDevice->GetTextureData(liveId, sub, GetTextureDataParams(), ret); + FatalErrorCheck(); return ret; } @@ -856,6 +865,7 @@ bool ReplayController::SaveTexture(const TextureSave &saveData, const rdcstr &pa bytebuf data; m_pDevice->GetTextureData(liveid, sub, params, data); + FatalErrorCheck(); if(data.empty()) { @@ -1530,6 +1540,7 @@ rdcarray ReplayController::PixelHistory(ResourceId target, ui return ret; ret = m_pDevice->PixelHistory(events, id, x, y, subresource, typeCast); + FatalErrorCheck(); SetFrameEvent(m_EventID, true); @@ -1550,6 +1561,7 @@ PixelValue ReplayController::PickPixel(ResourceId tex, uint32_t x, uint32_t y, return ret; m_pDevice->PickPixel(m_pDevice->GetLiveID(tex), x, y, sub, typeCast, ret.floatValue.data()); + FatalErrorCheck(); return ret; } @@ -1564,6 +1576,7 @@ rdcpair ReplayController::GetMinMax(ResourceId textureId m_pDevice->GetMinMax(m_pDevice->GetLiveID(textureId), sub, typeCast, &minval.floatValue[0], &maxval.floatValue[0]); + FatalErrorCheck(); return make_rdcpair(minval, maxval); } @@ -1578,6 +1591,7 @@ rdcarray ReplayController::GetHistogram(ResourceId textureId, const Su m_pDevice->GetHistogram(m_pDevice->GetLiveID(textureId), sub, typeCast, minval, maxval, channels, hist); + FatalErrorCheck(); return hist; } @@ -1590,9 +1604,13 @@ ShaderDebugTrace *ReplayController::DebugVertex(uint32_t vertid, uint32_t instid RENDERDOC_PROFILEFUNCTION(); ShaderDebugTrace *ret = m_pDevice->DebugVertex(m_EventID, vertid, instid, idx, view); + FatalErrorCheck(); SetFrameEvent(m_EventID, true); + if(ret->debugger) + m_Debuggers.push_back(ret->debugger); + return ret; } @@ -1604,9 +1622,13 @@ ShaderDebugTrace *ReplayController::DebugPixel(uint32_t x, uint32_t y, uint32_t RENDERDOC_PROFILEFUNCTION(); ShaderDebugTrace *ret = m_pDevice->DebugPixel(m_EventID, x, y, sample, primitive); + FatalErrorCheck(); SetFrameEvent(m_EventID, true); + if(ret->debugger) + m_Debuggers.push_back(ret->debugger); + return ret; } @@ -1618,9 +1640,13 @@ ShaderDebugTrace *ReplayController::DebugThread(const rdcfixedarray RENDERDOC_PROFILEFUNCTION(); ShaderDebugTrace *ret = m_pDevice->DebugThread(m_EventID, groupid, threadid); + FatalErrorCheck(); SetFrameEvent(m_EventID, true); + if(ret->debugger) + m_Debuggers.push_back(ret->debugger); + return ret; } @@ -1631,6 +1657,7 @@ rdcarray ReplayController::ContinueDebug(ShaderDebugger *debug RENDERDOC_PROFILEFUNCTION(); rdcarray ret = m_pDevice->ContinueDebug(debugger); + FatalErrorCheck(); return ret; } @@ -1641,6 +1668,7 @@ void ReplayController::FreeTrace(ShaderDebugTrace *trace) if(trace) { + m_Debuggers.removeOne(trace->debugger); m_pDevice->FreeDebugger(trace->debugger); delete trace; } @@ -1659,7 +1687,10 @@ rdcarray ReplayController::GetCBufferVariableContents( { buffer = m_pDevice->GetLiveID(buffer); if(buffer != ResourceId()) + { m_pDevice->GetBufferData(buffer, offset, length, data); + FatalErrorCheck(); + } } rdcarray v; @@ -1668,7 +1699,10 @@ rdcarray ReplayController::GetCBufferVariableContents( shader = m_pDevice->GetLiveID(shader); if(shader != ResourceId()) + { m_pDevice->FillCBufferVariables(pipeline, shader, entryPoint, cbufslot, v, data); + FatalErrorCheck(); + } return v; } @@ -1705,6 +1739,8 @@ rdcstr ReplayController::CreateRGPProfile(WindowingData window) for(int i = 0; i < 5; i++) { m_pDevice->ReplayLog(10000000, eReplay_Full); + if(FatalErrorCheck()) + return ""; output->Display(); } @@ -1730,12 +1766,16 @@ rdcstr ReplayController::CreateRGPProfile(WindowingData window) output->Display(); m_pDevice->ReplayLog(10000000, eReplay_Full); + if(FatalErrorCheck()) + return ""; } output->Display(); // restore back to where we were m_pDevice->ReplayLog(m_EventID, eReplay_Full); + if(FatalErrorCheck()) + return ""; ShutdownOutput(output); @@ -1783,12 +1823,14 @@ void ReplayController::ReplayLoop(WindowingData window, ResourceId texid) while(Atomic::CmpExch32(&m_ReplayLoopCancel, 0, 0) == 0) { m_pDevice->ReplayLog(10000000, eReplay_Full); + FatalErrorCheck(); output->Display(); } // restore back to where we were m_pDevice->ReplayLog(m_EventID, eReplay_Full); + FatalErrorCheck(); ShutdownOutput(output); @@ -1865,6 +1907,48 @@ void ReplayController::Shutdown() delete this; } +bool ReplayController::FatalErrorCheck() +{ + // we've already processed the fatal error if we have a status + if(m_FatalError != ReplayStatus::Succeeded) + return false; + + m_FatalError = m_pDevice->FatalErrorCheck(); + + if(m_FatalError != ReplayStatus::Succeeded) + { + RDCLOG("Fatal error detected: %s", ToStr(m_FatalError).c_str()); + + IReplayDriver *old = m_pDevice; + + // replace our driver with a dummy + m_pDevice = m_pDevice->MakeDummyDriver(); + + // replace the outputs as well + for(size_t i = 0; i < m_Outputs.size(); i++) + { + old->DestroyOutputWindow(m_Outputs[i]->m_MainOutput.outputID); + old->DestroyOutputWindow(m_Outputs[i]->m_PixelContext.outputID); + m_Outputs[i]->ClearThumbnails(); + + m_Outputs[i]->m_pDevice = m_pDevice; + } + + // delete the old replay + old->Shutdown(); + + // reset pipeline states to default + m_D3D11PipelineState = D3D11Pipe::State(); + m_D3D12PipelineState = D3D12Pipe::State(); + m_GLPipelineState = GLPipe::State(); + m_VulkanPipelineState = VKPipe::State(); + + return true; + } + + return false; +} + rdcarray ReplayController::GetCustomShaderEncodings() { CHECK_REPLAY_THREAD(); @@ -1912,6 +1996,7 @@ rdcpair ReplayController::BuildTargetShader( } m_pDevice->BuildTargetShader(sourceEncoding, source, entry, compileFlags, type, id, errs); + FatalErrorCheck(); if(id != ResourceId()) m_TargetResources.insert(id); @@ -1952,6 +2037,7 @@ rdcpair ReplayController::BuildCustomShader( RDCLOG("Building custom shader"); m_pDevice->BuildCustomShader(sourceEncoding, source, entry, compileFlags, type, id, errs); + FatalErrorCheck(); if(id != ResourceId()) { @@ -1987,6 +2073,7 @@ void ReplayController::ReplaceResource(ResourceId from, ResourceId to) CHECK_REPLAY_THREAD(); m_pDevice->ReplaceResource(from, to); + FatalErrorCheck(); SetFrameEvent(m_EventID, true); @@ -2000,6 +2087,7 @@ void ReplayController::RemoveReplacement(ResourceId id) CHECK_REPLAY_THREAD(); m_pDevice->RemoveReplacement(id); + FatalErrorCheck(); SetFrameEvent(m_EventID, true); @@ -2054,6 +2142,7 @@ ReplayStatus ReplayController::PostCreateInit(IReplayDriver *device, RDCFile *rd GCNISA::CacheSupport(m_APIProps.pipelineType); ReplayStatus status = m_pDevice->ReadLogInitialisation(rdc, false); + FatalErrorCheck(); m_pDevice->SetPipelineStates(&m_D3D11PipelineState, &m_D3D12PipelineState, &m_GLPipelineState, &m_VulkanPipelineState); @@ -2101,6 +2190,7 @@ void ReplayController::FetchPipelineState(uint32_t eventId) RENDERDOC_PROFILEFUNCTION(); m_pDevice->SavePipelineState(eventId); + FatalErrorCheck(); if(m_APIProps.pipelineType == GraphicsAPI::D3D11) m_PipeState.SetState(&m_D3D11PipelineState); diff --git a/renderdoc/replay/replay_controller.h b/renderdoc/replay/replay_controller.h index 43f07cb34..699f03ab3 100644 --- a/renderdoc/replay/replay_controller.h +++ b/renderdoc/replay/replay_controller.h @@ -77,7 +77,7 @@ private: uint64_t m_ThreadID; - ReplayController *m_pRenderer; + ReplayController *m_pController; bool m_OverlayDirty; bool m_ForceOverlayRefresh; @@ -173,7 +173,7 @@ public: const rdcarray &GetBuffers(); const rdcarray &GetResources(); rdcarray GetDebugMessages(); - + ReplayStatus GetFatalErrorStatus() { return m_FatalError; } rdcarray GetShaderEntryPoints(ResourceId shader); const ShaderReflection *GetShader(ResourceId pipeline, ResourceId shader, ShaderEntryPoint entry); @@ -218,6 +218,8 @@ public: void ShutdownOutput(IReplayOutput *output); void Shutdown(); + bool FatalErrorCheck(); + private: virtual ~ReplayController(); ReplayStatus PostCreateInit(IReplayDriver *device, RDCFile *rdc); @@ -240,6 +242,8 @@ private: int32_t m_ReplayLoopCancel = 0; int32_t m_ReplayLoopFinished = 0; + ReplayStatus m_FatalError = ReplayStatus::Succeeded; + uint32_t m_EventID; std::map m_EventRemap; @@ -258,6 +262,8 @@ private: IReplayDriver *m_pDevice; + rdcarray m_Debuggers; + std::set m_TargetResources; std::set m_CustomShaders; diff --git a/renderdoc/replay/replay_driver.h b/renderdoc/replay/replay_driver.h index 0263b7949..65bfed4f3 100644 --- a/renderdoc/replay/replay_driver.h +++ b/renderdoc/replay/replay_driver.h @@ -212,6 +212,7 @@ public: virtual bool IsRenderOutput(ResourceId id) = 0; virtual void FileChanged() = 0; + virtual ReplayStatus FatalErrorCheck() = 0; virtual bool NeedRemapForFetch(const ResourceFormat &format) = 0; @@ -225,6 +226,8 @@ class IReplayDriver : public IRemoteDriver public: virtual bool IsRemoteProxy() = 0; + virtual IReplayDriver *MakeDummyDriver() = 0; + virtual rdcarray GetSupportedWindowSystems() = 0; virtual AMDRGPControl *GetRGPControl() = 0; diff --git a/renderdoc/replay/replay_output.cpp b/renderdoc/replay/replay_output.cpp index b86dded84..e10fa3eef 100644 --- a/renderdoc/replay/replay_output.cpp +++ b/renderdoc/replay/replay_output.cpp @@ -88,7 +88,7 @@ ReplayOutput::ReplayOutput(ReplayController *parent, WindowingData window, Repla { m_ThreadID = Threading::GetCurrentID(); - m_pRenderer = parent; + m_pController = parent; m_MainOutput.dirty = true; @@ -118,6 +118,8 @@ ReplayOutput::ReplayOutput(ReplayController *parent, WindowingData window, Repla m_MainOutput.outputID = 0; m_MainOutput.texture = ResourceId(); + m_pController->FatalErrorCheck(); + m_pDevice->GetOutputWindowDimensions(m_MainOutput.outputID, m_Width, m_Height); m_CustomShaderResourceId = ResourceId(); @@ -130,11 +132,11 @@ ReplayOutput::~ReplayOutput() { CHECK_REPLAY_THREAD(); + m_CustomShaderResourceId = ResourceId(); + m_pDevice->DestroyOutputWindow(m_MainOutput.outputID); m_pDevice->DestroyOutputWindow(m_PixelContext.outputID); - m_CustomShaderResourceId = ResourceId(); - ClearThumbnails(); } @@ -142,7 +144,7 @@ void ReplayOutput::Shutdown() { CHECK_REPLAY_THREAD(); - m_pRenderer->ShutdownOutput(this); + m_pController->ShutdownOutput(this); } void ReplayOutput::SetDimensions(int32_t width, int32_t height) @@ -152,6 +154,7 @@ void ReplayOutput::SetDimensions(int32_t width, int32_t height) m_pDevice->SetOutputWindowDimensions(m_MainOutput.outputID, width > 0 ? width : 1, height > 0 ? height : 1); m_pDevice->GetOutputWindowDimensions(m_MainOutput.outputID, m_Width, m_Height); + m_pController->FatalErrorCheck(); } bytebuf ReplayOutput::ReadbackOutputTexture() @@ -160,6 +163,7 @@ bytebuf ReplayOutput::ReadbackOutputTexture() bytebuf data; m_pDevice->GetOutputWindowData(m_MainOutput.outputID, data); + m_pController->FatalErrorCheck(); return data; } @@ -226,7 +230,7 @@ void ReplayOutput::RefreshOverlay() { CHECK_REPLAY_THREAD(); - ActionDescription *action = m_pRenderer->GetActionByEID(m_EventID); + ActionDescription *action = m_pController->GetActionByEID(m_EventID); passEvents = m_pDevice->GetPassEvents(m_EventID); @@ -254,12 +258,15 @@ void ReplayOutput::RefreshOverlay() if(action != NULL && (action->flags & ActionFlags::Drawcall)) { m_pDevice->InitPostVSBuffers(action->eventId); + m_pController->FatalErrorCheck(); if(postVSWholePass && !passEvents.empty()) { m_pDevice->InitPostVSBuffers(passEvents); + m_pController->FatalErrorCheck(); m_pDevice->ReplayLog(m_EventID, eReplay_WithoutDraw); + m_pController->FatalErrorCheck(); } } } @@ -274,6 +281,7 @@ void ReplayOutput::RefreshOverlay() m_OverlayResourceId = m_pDevice->RenderOverlay(id, f, m_RenderData.texDisplay.overlay, m_EventID, passEvents); + m_pController->FatalErrorCheck(); m_OverlayDirty = false; } else @@ -301,8 +309,10 @@ ResourceId ReplayOutput::GetDebugOverlayTexID() if(m_OverlayDirty) { m_pDevice->ReplayLog(m_EventID, eReplay_WithoutDraw); + m_pController->FatalErrorCheck(); RefreshOverlay(); m_pDevice->ReplayLog(m_EventID, eReplay_OnlyDraw); + m_pController->FatalErrorCheck(); } return m_OverlayResourceId; @@ -326,6 +336,8 @@ bool ReplayOutput::SetPixelContext(WindowingData window) m_PixelContext.texture = ResourceId(); m_PixelContext.depthMode = false; + m_pController->FatalErrorCheck(); + RDCASSERT(m_PixelContext.outputID > 0); return m_PixelContext.outputID != 0; @@ -342,12 +354,12 @@ bool ReplayOutput::AddThumbnail(WindowingData window, ResourceId texID, const Su bool depthMode = false; - for(size_t t = 0; t < m_pRenderer->m_Textures.size(); t++) + for(size_t t = 0; t < m_pController->m_Textures.size(); t++) { - if(m_pRenderer->m_Textures[t].resourceId == texID) + if(m_pController->m_Textures[t].resourceId == texID) { - depthMode = (m_pRenderer->m_Textures[t].creationFlags & TextureCategory::DepthTarget) || - (m_pRenderer->m_Textures[t].format.compType == CompType::Depth); + depthMode = (m_pController->m_Textures[t].creationFlags & TextureCategory::DepthTarget) || + (m_pController->m_Textures[t].format.compType == CompType::Depth); break; } } @@ -374,6 +386,8 @@ bool ReplayOutput::AddThumbnail(WindowingData window, ResourceId texID, const Su p.typeCast = typeCast; p.dirty = true; + m_pController->FatalErrorCheck(); + RDCASSERT(p.outputID > 0); m_Thumbnails.push_back(p); @@ -387,7 +401,7 @@ rdcpair ReplayOutput::PickVertex(uint32_t x, uint32_t y) RENDERDOC_PROFILEFUNCTION(); - ActionDescription *action = m_pRenderer->GetActionByEID(m_EventID); + ActionDescription *action = m_pController->GetActionByEID(m_EventID); const rdcpair errorReturn = {~0U, ~0U}; @@ -432,6 +446,9 @@ rdcpair ReplayOutput::PickVertex(uint32_t x, uint32_t y) MeshFormat fmt = m_pDevice->GetPostVSBuffers(action->eventId, m_RenderData.meshDisplay.curInstance, m_RenderData.meshDisplay.curView, m_RenderData.meshDisplay.type); + + m_pController->FatalErrorCheck(); + uint64_t elemOffset = cfg.position.vertexByteOffset - fmt.vertexByteOffset; for(uint32_t inst = firstInst; inst < maxInst; inst++) @@ -439,10 +456,14 @@ rdcpair ReplayOutput::PickVertex(uint32_t x, uint32_t y) // find the start of this buffer, and apply the element offset, then pick in that instance fmt = m_pDevice->GetPostVSBuffers(action->eventId, inst, m_RenderData.meshDisplay.curView, m_RenderData.meshDisplay.type); + m_pController->FatalErrorCheck(); + if(fmt.vertexResourceId != ResourceId()) cfg.position.vertexByteOffset = fmt.vertexByteOffset + elemOffset; uint32_t vert = m_pDevice->PickVertex(m_EventID, m_Width, m_Height, cfg, x, y); + m_pController->FatalErrorCheck(); + if(vert != ~0U) { return make_rdcpair(vert, inst); @@ -454,6 +475,7 @@ rdcpair ReplayOutput::PickVertex(uint32_t x, uint32_t y) else { uint32_t vert = m_pDevice->PickVertex(m_EventID, m_Width, m_Height, cfg, x, y); + m_pController->FatalErrorCheck(); if(vert != ~0U) return make_rdcpair(vert, m_RenderData.meshDisplay.curInstance); @@ -492,6 +514,8 @@ void ReplayOutput::ClearBackground(uint64_t outputID, const FloatVector &backgro RenderDoc::Inst().LightCheckerboardColor()); else m_pDevice->ClearOutputWindowColor(outputID, ConvertSRGBToLinear(backgroundColor)); + + m_pController->FatalErrorCheck(); } void ReplayOutput::DisplayContext() @@ -500,13 +524,17 @@ void ReplayOutput::DisplayContext() if(m_PixelContext.outputID == 0) return; + m_pDevice->BindOutputWindow(m_PixelContext.outputID, false); + m_pController->FatalErrorCheck(); + ClearBackground(m_PixelContext.outputID, m_RenderData.texDisplay.backgroundColor); if((m_Type != ReplayOutputType::Texture) || (m_ContextX < 0.0f && m_ContextY < 0.0f) || (m_RenderData.texDisplay.resourceId == ResourceId())) { m_pDevice->FlipOutputWindow(m_PixelContext.outputID); + m_pController->FatalErrorCheck(); return; } @@ -552,10 +580,13 @@ void ReplayOutput::DisplayContext() disp.resourceId = m_pDevice->GetLiveID(disp.resourceId); m_pDevice->RenderTexture(disp); + m_pController->FatalErrorCheck(); m_pDevice->RenderHighlightBox(w, h, contextZoom); + m_pController->FatalErrorCheck(); m_pDevice->FlipOutputWindow(m_PixelContext.outputID); + m_pController->FatalErrorCheck(); } void ReplayOutput::Display() @@ -589,6 +620,7 @@ void ReplayOutput::Display() } m_pDevice->FlipOutputWindow(m_MainOutput.outputID); + m_pController->FatalErrorCheck(); DisplayContext(); } @@ -596,8 +628,10 @@ void ReplayOutput::Display() { m_pDevice->BindOutputWindow(m_MainOutput.outputID, false); m_pDevice->FlipOutputWindow(m_MainOutput.outputID); + m_pController->FatalErrorCheck(); m_pDevice->BindOutputWindow(m_PixelContext.outputID, false); m_pDevice->FlipOutputWindow(m_PixelContext.outputID); + m_pController->FatalErrorCheck(); } for(size_t i = 0; i < m_Thumbnails.size(); i++) @@ -632,8 +666,10 @@ void ReplayOutput::Display() light2.z = light.z; light2.w = 1.0f; m_pDevice->RenderCheckerboard(dark2, light2); + m_pController->FatalErrorCheck(); m_pDevice->FlipOutputWindow(m_Thumbnails[i].outputID); + m_pController->FatalErrorCheck(); continue; } @@ -667,8 +703,10 @@ void ReplayOutput::Display() disp.green = disp.blue = false; m_pDevice->RenderTexture(disp); + m_pController->FatalErrorCheck(); m_pDevice->FlipOutputWindow(m_Thumbnails[i].outputID); + m_pController->FatalErrorCheck(); m_Thumbnails[i].dirty = false; } @@ -680,7 +718,7 @@ void ReplayOutput::DisplayTex() RENDERDOC_PROFILEFUNCTION(); - ActionDescription *action = m_pRenderer->GetActionByEID(m_EventID); + ActionDescription *action = m_pController->GetActionByEID(m_EventID); if(m_MainOutput.outputID == 0) return; @@ -701,14 +739,17 @@ void ReplayOutput::DisplayTex() if(m_OverlayDirty) { m_pDevice->ReplayLog(m_EventID, eReplay_WithoutDraw); + m_pController->FatalErrorCheck(); RefreshOverlay(); m_pDevice->ReplayLog(m_EventID, eReplay_OnlyDraw); + m_pController->FatalErrorCheck(); } } else if(m_ForceOverlayRefresh) { m_ForceOverlayRefresh = false; m_pDevice->ReplayLog(m_EventID, eReplay_Full); + m_pController->FatalErrorCheck(); } if(m_RenderData.texDisplay.customShaderId != ResourceId()) @@ -716,6 +757,7 @@ void ReplayOutput::DisplayTex() m_CustomShaderResourceId = m_pDevice->ApplyCustomShader(m_RenderData.texDisplay.customShaderId, texDisplay.resourceId, texDisplay.subresource, texDisplay.typeCast); + m_pController->FatalErrorCheck(); texDisplay.resourceId = m_pDevice->GetLiveID(m_CustomShaderResourceId); texDisplay.typeCast = CompType::Typeless; @@ -739,6 +781,7 @@ void ReplayOutput::DisplayTex() } m_pDevice->RenderTexture(texDisplay); + m_pController->FatalErrorCheck(); ResourceId id = m_pDevice->GetLiveID(m_RenderData.texDisplay.resourceId); @@ -760,6 +803,7 @@ void ReplayOutput::DisplayTex() texDisplay.typeCast = CompType::Typeless; m_pDevice->RenderTexture(texDisplay); + m_pController->FatalErrorCheck(); } } @@ -769,7 +813,7 @@ void ReplayOutput::DisplayMesh() RENDERDOC_PROFILEFUNCTION(); - ActionDescription *action = m_pRenderer->GetActionByEID(m_EventID); + ActionDescription *action = m_pController->GetActionByEID(m_EventID); if(action == NULL || m_MainOutput.outputID == 0 || m_Width <= 0 || m_Height <= 0 || (m_RenderData.meshDisplay.type == MeshDataStage::Unknown) || @@ -781,6 +825,7 @@ void ReplayOutput::DisplayMesh() m_pDevice->ClearOutputWindowDepth(m_MainOutput.outputID, 1.0f, 0); m_pDevice->RenderCheckerboard(RenderDoc::Inst().DarkCheckerboardColor(), RenderDoc::Inst().LightCheckerboardColor()); + m_pController->FatalErrorCheck(); return; } @@ -788,8 +833,10 @@ void ReplayOutput::DisplayMesh() if(m_OverlayDirty) { m_pDevice->ReplayLog(m_EventID, eReplay_WithoutDraw); + m_pController->FatalErrorCheck(); RefreshOverlay(); m_pDevice->ReplayLog(m_EventID, eReplay_OnlyDraw); + m_pController->FatalErrorCheck(); } m_pDevice->BindOutputWindow(m_MainOutput.outputID, true); @@ -797,8 +844,10 @@ void ReplayOutput::DisplayMesh() m_pDevice->RenderCheckerboard(RenderDoc::Inst().DarkCheckerboardColor(), RenderDoc::Inst().LightCheckerboardColor()); + m_pController->FatalErrorCheck(); m_pDevice->ClearOutputWindowDepth(m_MainOutput.outputID, 1.0f, 0); + m_pController->FatalErrorCheck(); MeshDisplay mesh = m_RenderData.meshDisplay; mesh.position.vertexResourceId = m_pDevice->GetLiveID(mesh.position.vertexResourceId); @@ -833,7 +882,7 @@ void ReplayOutput::DisplayMesh() { for(size_t i = 0; m_RenderData.meshDisplay.showWholePass && i < passEvents.size(); i++) { - ActionDescription *d = m_pRenderer->GetActionByEID(passEvents[i]); + ActionDescription *d = m_pController->GetActionByEID(passEvents[i]); if(d) { @@ -842,9 +891,13 @@ void ReplayOutput::DisplayMesh() // get the 'most final' stage MeshFormat fmt = m_pDevice->GetPostVSBuffers( passEvents[i], inst, m_RenderData.meshDisplay.curView, MeshDataStage::GSOut); + m_pController->FatalErrorCheck(); if(fmt.vertexResourceId == ResourceId()) + { fmt = m_pDevice->GetPostVSBuffers(passEvents[i], inst, m_RenderData.meshDisplay.curView, MeshDataStage::VSOut); + m_pController->FatalErrorCheck(); + } fmt.meshColor = passDraws; @@ -869,9 +922,13 @@ void ReplayOutput::DisplayMesh() // get the 'most final' stage MeshFormat fmt = m_pDevice->GetPostVSBuffers( action->eventId, inst, m_RenderData.meshDisplay.curView, MeshDataStage::GSOut); + m_pController->FatalErrorCheck(); if(fmt.vertexResourceId == ResourceId()) + { fmt = m_pDevice->GetPostVSBuffers(action->eventId, inst, m_RenderData.meshDisplay.curView, MeshDataStage::VSOut); + m_pController->FatalErrorCheck(); + } fmt.meshColor = otherInstances; @@ -885,4 +942,5 @@ void ReplayOutput::DisplayMesh() mesh.position.meshColor = drawItself; m_pDevice->RenderMesh(m_EventID, secondaryDraws, mesh); + m_pController->FatalErrorCheck(); }