65.9K
CodeProject 正在变化。 阅读更多。
Home

Investigo 介绍:使用代理 DLL 和嵌入式 HTTP 服务器进行 DirectX9 性能分析、调试和自动化性能测试。

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (6投票s)

2012 年 11 月 10 日

MIT

48分钟阅读

viewsIcon

59702

downloadIcon

1469

Investigo 介绍:使用代理 DLL 和嵌入式 HTTP 服务器进行 DirectX9 性能分析、调试和自动化性能测试

目录

引言
使用 Investigo
实现
参考
替代工具
资源

引言

这是什么?

Investigo 是一款软件工具包,用于辅助 性能分析调试 DirectX9 应用程序。它可以衡量 DirectX 应用程序的性能,还可以帮助您深入了解 DirectX 应用程序,或者更普遍地了解 DirectX。

Investigo 是开源的,根据 MIT 许可证发布,并可在 SourceForge 上获取。

屏幕截图

这是 Investigo 的 概览 实时性能图表的屏幕截图。在 概览 页面上,您可以监控 DirectX 9 应用程序的 帧率帧时间绘图调用




下一个屏幕截图显示了 Investigo 的 调试器 页面。单击此处某个特定 DirectX 函数的按钮,下次调用该函数时会触发 Visual Studio 断点。




下一个屏幕截图显示了 Investigo 的 配置 页面。它显示了 DirectX 设备配置和功能的详细信息。



主要功能一览

  • 显示实时性能指标,如帧率、帧时间、绘图调用次数。
  • 显示 Direct API 调用的时间和调用次数。
  • 可扩展 - 允许应用程序定义的指标和代码计时。
  • 将性能指标捕获到日志文件中,以便进行离线分析和报告生成。
  • 轻松触发 Visual Studio 断点上的 DirectX API 调用。
有关潜在的未来功能,请参阅 未来功能

谁适合使用?

Investigo 适用于任何想要衡量 DirectX 应用程序性能的人。

对于程序员来说,它可以帮助理解和调试 DirectX 代码。学习新的代码库总会有一个艰难的学习曲线。Investigo 可以通过帮助您自下而上地理解代码库来帮助您入门,稍后将详细介绍。

说真的,它是什么?

Investigo 的核心是一个 代理 DLL,它会拦截 DirectX API 调用。其主要目的是记录 DirectX9 应用程序生成的实时性能指标。

Investigo 具有 Web UI,该 UI 通过嵌入式 HTTP 服务器提供。Web UI 显示实时性能图表,帮助您广泛了解应用程序的性能特征和瓶颈。

实时图表仅用于快速评估性能。当您认真进行性能分析时,需要捕获性能指标进行离线分析。Investigo 将性能指标输出到 CSV 文件。我包含的一个 C# 控制台应用程序 ReportGen 将捕获的性能数据转换为基于 HTML 的报告,下面是一个示例屏幕截图。



Investigo 可以帮助您构建自动性能测试系统,但这将在后面的部分进行介绍。

对于程序员来说,Investigo 可能非常有帮助,当您开始处理新的 DirectX9 代码库时,可以帮助您入门。它有助于您自下而上地理解代码库,并可以加速您对应用程序 DirectX 用法的理解。Web UI 通过允许您单击按钮在 DirectX API 函数被调用时触发 Visual Studio 断点 来支持这一点。



当然,您必须在 Visual Studio 调试器下运行该应用程序才能使其正常工作,否则当您尝试触发断点时,应用程序将崩溃。

当您使用 Investigo 深入了解 DirectX 时,它可能还有助于建立您对 DirectX 的一般理解。

假设知识

如果您对 DirectX 有一些先前的了解,并且有进行 DirectX 应用程序性能分析的理由,那么本文对您来说将最有价值。本文将迅速深入技术细节,涵盖大量内容。我将包含许多指向辅助信息的链接。

如果您想深入了解 Investigo 的实现,您可能已经对 C++ 和 DirectX API 有一定的了解。如果您想了解 Investigo UI 的实现,那么对 JavaScript 如何用于构建 Web 应用程序有所了解将会有所帮助。同样,我将包含大量链接,帮助您边学边用。

预期和非预期

开箱即用,Investigo 可以轻松衡量性能的各个方面,并且如前所述,它还可以触发 DirectX API 函数中的断点。

从应用程序的角度来看,Investigo 是可扩展的。应用程序可以通过其 API 控制 Investigo,并且可以添加自定义应用程序指标,这些指标将显示在实时图表中并输出到性能日志中。

但是,如果您是程序员,您可能想要 Investigo 提供更多功能。事实上,有许多潜在功能我不得不省略,以便我可以发布这篇文章并公开 Investigo。这是在将 Investigo 推向市场和将其保密更长时间之间的权衡。我决定将其推向市场,看看人们的看法,然后如果需求存在,就继续我的工作。我也希望其他人能够为 Investigo 做出贡献并帮助其向前发展。

新功能将随着时间推移而实现,但目前您可能需要动手尝试并修改其他所需功能。在修改 Investigo 之前,请阅读 实现 部分,不要害怕深入代码。

如果您进行了任何有用的修改,请将其贡献回 SourceForge 上的代码库

背景

Investigo 最初是一个相当简单的代理 DLL,目的是帮助我理解和优化一个复杂的 DirectX 代码库的性能。

经验丰富的程序员知道,性能分析优化之前至关重要。我们需要构建应用程序性能的准确图景,以便识别和隔离潜在的优化瓶颈

通常在这种情况下,我本来会立即转向 NVPerfHUD,尽管当时我刚装好一台新电脑,只有一块 ATI 显卡。使用 NVPerfHUD 需要一块 NVIDIA 卡(通常我会在我的电脑里同时安装这两种显卡)。当时我无法使用 NVPerfHUD,于是我创建了一个代理 DirectX DLL 来帮助我的性能分析任务。

代理 DLL 还有助于加深我对代码库的理解,这是一个令人欣慰的副作用。我能够在一个重要的 DirectX API 调用上设置断点。这使我能够回答诸如代码库中的Present调用来自哪里?之类的问题。

Investigo 的原始版本比我现在提供的版本要小得多,也简单得多。它没有 UI,数据也不够复杂,不需要在多个线程中进行缓冲。最初,它只是将 API 调用直接转发给 DirectX 并保留一些简单的指标,例如每帧的绘图调用次数

随着时间的推移,代理 DLL 变得越来越复杂。我添加了各种其他日志记录功能,并根据需要进行修改。例如,我曾经添加了列出帧 X 的纹理集更改的功能(一个我希望将来能正式放回 Investigo 的功能)。随着时间的推移,这些修改不断积累,DLL 的代码库变得杂乱不堪。我开始重写它,目的是通过 UI 公开最常用和最有用的功能。

重写的结果就是我现在提供的 Investigo 版本。

自顶向下与自下而上理解

自顶向下和自下而上是理解代码的两种基本方法。在学习新代码库时,两者都有其用武之地。

Investigo 可以帮助您自下而上地理解代码库。通常,当您接触新的代码库时,您可能会从顶向下学习。也就是说,您会从 main 函数开始,看看它调用了什么,然后跟踪这些函数以查看它们调用了什么,以此类推,逐步深入调用层次结构。

自下而上则相反,您从调用层次结构的底部开始,逐步向上建立您的理解。例如,您可以从 DirectX Present 函数开始,看看它被什么调用,然后移动到下一个函数,看看它被什么调用,以此类推,逐步发展您在调用层次结构中的理解。

Investigo 通过允许您在 DirectX 函数中设置断点,以查看它们在应用程序中的调用位置和使用方式,来帮助自下而上的理解。在 Investigo 的原始版本中,我使用 Visual Studio 直接在代理 DLL 代码中设置断点。最新版本的 Investigo 允许通过 Web UI 中的按钮单击来触发断点。

但请再次记住,断点要工作,应用程序必须已经在调试器下运行!在未在调试器下运行的应用程序中设置断点会导致应用程序崩溃

使用 Investigo

入门

让我们开始使用 Investigo。只需解压 zip 文件,然后将代理 DLL 和配置文件复制到 DirectX 应用程序所在的目录即可。

1. 从本文下载 Investigo.zip,或从 SourceForge 下载页面获取最新版本。本文的另一个下载项是 InvestigoSrc.zip,其中包含源代码,但如果您只想使用 Investigo,则不需要代码。

2. 解压 Investigo.zip 的内容到一个目录。您应该看到以下文件



3.d3d9.dll(代理 DLL)和 Investigo.cfg(Investigo 配置文件)复制到应用程序目录。这些文件应放在 DirectX 应用程序可执行文件旁边。

Investigo.cfg 配置 Investigo 的各个方面,并且必须放置在与 d3d9.dll 代理 DLL 相同的目录中。

有关 Investigo 配置文件格式的详细信息,请参阅 配置文件参考

4. 应用程序目录是否在 Program Files 中?

如果是,则需要编辑配置文件并将 OutputDirectory 改为指向另一个目录。Investigo 没有权限写入 Program Files(除非您以管理员身份运行 DirectX 应用程序),因此它需要一个具有写入权限的输出目录。

当您查看配置文件时,还应检查其 DirectX DLL 的路径是否对于您的操作系统是正确的。




5. 现在您可以运行 DirectX 应用程序了。Investigo 应该正在运行,您应该会在左上角看到 Investigo HUD



如果您没有看到 HUD,请回顾设置说明,确保您的设置正确。还要确保您实际上正在运行 DirectX9 应用程序,Investigo 不适用于 OpenGL 或其他 DirectX 版本。

如果出现任何问题,请检查输出目录中的 Investigo.log 文件以获取错误和潜在的解决方案。

如果它拒绝工作,请通过 SourceForge 给我留言。

6. 现在打开您的浏览器,指向 localhost:8080。您应该会在浏览器中看到 Investigo Web UI 的主页。



如果您在端口 8080 上运行 HTTP 服务器时遇到问题,可以通过在配置文件中添加 HTTPPort 行来更改它。



Investigo HUD

Investigo HUD 是应用程序渲染窗口上的一个小覆盖层。当 Investigo 代理 DLL 加载时,它始终显示,这样您就知道 Investigo 正在运行。



性能日志记录的状态显示在 HUD 中



Investigo Web UI

Investigo 代理 DLL 包含一个嵌入式 HTTP 服务器。当您运行加载了 Investigo 的应用程序时,可以通过将浏览器指向 localhost:8080 来访问 Web UI。

Web UI 显示性能指标的实时图表,并允许您与 Investigo DirectX 代理 DLL 进行交互。

Web UI 的好处在于它可以跨网络工作。您可以在一台 PC 上运行 DirectX 应用程序,然后在 LAN 上的另一台 PC 或甚至平板设备上查看 Web UI。只需将浏览器指向运行应用程序的 PC 的 IP 地址即可。但请注意,您的网络路由器可能会阻止网络上的某些端口,这可能意味着例如您无法在公司 LAN 上使用 Investigo,并且需要与您的网络管理员联系以解除端口阻塞。



下表简要介绍了 Web UI 的各种页面。

Home 应用程序的起始页面,包含指向其他页面的链接。
配置 一个列表,显示 DirectX 设备的的配置和能力。
性能 指向显示实时性能图表的子页面的链接。
调试器 一个可过滤的 DirectX API 函数列表,每个函数都可以单击以在 Visual Studio 中触发断点。
关于 有关 Investigo 和我自己的信息。

实时性能图表

实时性能图表显示渲染帧的过去帧(换句话说,随时间推移)的性能指标值。例如,帧率概览



图表底部的数字表示帧号。绿色的垂直条表示当前帧以及新数据将插入的位置。绿色条右侧的数字表示指标的当前值。

捕获性能数据以进行离线分析

实时性能图表仅用于粗略评估应用程序的性能。为了进行准确的性能分析,您必须捕获性能数据以进行离线分析。Investigo 将性能数据捕获到 CSV 文件中。Investigo 将性能数据组织成组,输出到单独的 CSV 文件中,这些日志文件统称为性能日志

这是一个示例 CSV 性能日志的屏幕截图



这些 CSV 文件可以轻松导入 Excel 或 Open Office 中作为电子表格进行处理。

性能日志默认禁用,有多种方法可以启用它。最简单但精度最低的方法是通过 Web UI。

右上角的按钮可切换性能日志记录。



为了获得更高的精度,例如指定应在哪个帧开始和停止性能日志记录,可以通过 配置文件 启用性能日志记录。

为了获得最佳精度,特别是如果应用程序不是确定性的,应用程序代码可以通过 Investio API 来开始和停止性能日志记录。

为了获得完整的性能测量精度,您的 DirectX 应用程序应该是确定性的。也就是说,对于应用程序的每次运行,在性能日志记录期间,它应该在相同的时间处于相同状态。使游戏或图形应用程序确定性需要付出努力,但其回报是代码更容易测试、调试和分析。

本文的下载文件中包含了 ReportGen 命令行应用程序。捕获性能数据后,您可以从命令行运行 ReportGen,将捕获的性能数据转换为基于 HTML 的性能报告。


下面是 ReportGen 生成的基于 HTML 的性能报告的屏幕截图。



ReportGen 实际上仅被用作示例程序,它生成的报告相当简单。在 Investigo 的未来版本中,这方面还有很多可以做的地方,但目前,如果您需要额外功能或希望性能报告看起来不同,那么您可能需要深入研究 ReportGen 的代码并开始修改。

Investigo API

Investigo C++ API 允许应用程序直接与 Investigo 代理 DLL 进行交互。

整个 API 都被包装在 #define INVESTIGO_ENABLED 中。这使得 Investigo 可以轻松地从生产版本中移除。要使用 API,您必须在应用程序的主头文件中定义 INVESTIGO_ENABLED,或者最好在项目设置中定义。

如果您从项目设置中省略 INVESTIGO_ENABLED,您将无法使用 Investigo API。除了正常的 Debug Release 构建之外,您可能还需要一个额外的构建,我通常称之为ProfilingProfiling 构建是在定义了 INVESTIGO_ENABLED 的Release 构建。 Profiling 构建通常包含应用程序特定的代码,用于性能分析和监控。

Investigo 概念

在查看 API 之前,让我们先概述一下 Investigo 的概念和数据结构。

变量

一个变量是 Investigo 存储在内存中的命名数据单元。

历史记录会为每个变量维护。历史缓冲区的大小默认为 1000 帧,尽管可以在 配置文件 中设置。

每个变量的历史记录都可以在实时性能图表中查看。



页面和组

变量被组织成页面

一个页面是变量的集合。页面定义了变量在 Web UI 中的组织和显示方式,它们可以嵌套形成页面层次结构。



一个也是变量的集合,但是,与页面不同,组不能嵌套。组将变量组织起来以输出到性能日志。每个组都映射到一个 CSV 文件,该文件记录组中所有变量的历史记录。



定时器

一个计时器用于测量代码块的执行时间。计时器的结果(以毫秒为单位)将输出到一个变量。



一个计时器本质上只是一个变量,因此它的历史记录也可以在实时性能图表中查看。

API 基础

API 包含一组宏、函数和接口,用于将 Investigo 功能暴露给应用程序。要使用 Investigo API,您必须#include InvestigoApplicationInterface.h

INVESTIGO_ENABLED 必须在您的项目中启用才能启用 Investigo API。当定义了 INVESTIGO_ENABLED 时,Investigo 函数和类会被有条件地编译进来,否则这些函数和类将不可用。

当未定义 INVESTIGO_ENABLED 时,Investigo 宏将评估为无操作。

Investigo 函数只能在定义了 INVESTIGO_ENABLED 时调用,因此您应该将此类调用包装在 INVESTIGO_ENABLED 中,以便它们可以从生产版本中编译出去。

#ifdef INVESTIGO_ENABLED

 // ... make calls to Investigo functions ...

#endif // INVESTIGO_ENABLED

此外,API 中的宏和函数仅在 d3d9.dll 代理 DLL 放置在调用应用程序的同一目录中时才有效。当 DLL 不存在时,Investigo 宏和函数不起任何作用。

应用程序定义的变量和性能指标

应用程序可以定义自己的变量和计时器,这些变量和计时器可以在 Web UI 中查看,并输出到性能日志(启用时)。这些宏完全抽象了与 Investigo DLL 的接口。

变量

变量可以随时设置。要设置整数变量的值
int value = 10;
INVESTIGO_INT_SET_VALUE("MyPage", "MyVariable", value);

要递增整数变量的值
INVESTIGO_INT_INCREMENT("MyPage", "MyVariable");

要将整数变量的值重置为零
INVESTIGO_INT_RESET("MyPage", "MyVariable");

对于设置和重置双精度变量,也存在类似的宏,但是没有用于双精度变量的递增
计时器

INVESTIGO_TIMER 宏用于分析代码块并确定其执行时间。

{ // Start of code block.
 INVESTIGO_TIMER("MyPage", "MyTimer");

 // ... code to profile ...

} // End of code block.


Investigo 内联函数

API 提供了一组全局内联函数,这些函数与各种其他 Investigo 功能进行交互。这些函数抽象了 Investigo DLL 的接口,因此您可以调用它们而无需担心加载或卸载 Investigo DLL。

例如,设置 Investigo 的输出目录

const char* newOutputDirectory = ...
Investigo::Config::SetOutputDirectory(newOutputDirectory);

开始和停止性能日志记录

Investigo::Performance::StartLogging()

// ... some time passes ...

Investigo::Performance::StopLogging();

启用和禁用绘图调用

Investigo::Experiements::DisableDrawCalls();

// ... do some rendering ...

Investigo::Experiments::EnableDrawCalls();

当 Investigo DLL 不存在时,Investigo 内联函数不起任何作用。如果它们无法加载 DLL,它们将什么也不做,并且在后续调用中不会重新尝试加载 DLL。

Investigo 资源注解

Investigo API 提供了允许 DirectX 资源命名的函数。

要从应用程序代码设置 DirectX 资源的名称,请将资源加载代码用 INVESTIGO_RESOURCE_BEGININVESTIGO_RESOURCE_END 宏括起来。这些宏只是包装了 Investigo::ResourceBeginInvestigo::ResourceEnd 函数,并且仅在定义了 INVESTIGO_ENABLED 时才会被编译进来。
这些函数的调用会通知 Investigo 正在加载的资源(例如纹理和着色器)的名称。Investigo 会用提供的名称注解每个加载的资源。

如以下代码片段所示,资源名称也可以嵌套

INVESTIGO_RESOURCE_BEGIN("my special resource");

// ... any resources loaded here will be annotated with the name 'my special resource' ...

INVESTIGO_RESOURCE_BEGIN("textures");

// ... any resources loaded here will be annotated with the name 'my special resource/textures' ... 

INVESTIGO_RESOURCE_END;

INVESTIGO_RESOURCE_END;

可以使用 INVESTIGO_RESOURCE_NAME 宏在代码中检索特定资源的名称。

IDirect3DTexture9* someTexture = ...
const char* resourceName = INVESTIGO_RESOURCE_NAME(someTexture);

与所有其他宏一样,INVESTIGO_RESOURCE_NAME 的正常功能是在定义了 INVESTIGO_ENABLED 时有条件地编译进来的,否则它返回字符串undefined。当定义了 INVESTIGO_ENABLED 但资源未命名时,它 返回字符串unnamed

资源及其详细信息尚未在 Investigo Web UI 中显示,但这可能是在将来添加的功能。

为资源指定名称有助于您在调试器中更轻松地识别可能遇到的 DirectX 资源。给定一个 DirectX 资源指针(例如纹理),您可以在调试器中查看资源的名称。



INVESTIGO_RESOURCE_NAME 宏很方便,简化了对 Investigo Investigo::IResource 接口的访问。如果您需要完全访问接口,可以通过调用 QueryInterface 手动提取它。

IDirect3DTexture9* texture = ...
Investigo::IResource* investigoResource = NULL;

if (SUCCEEDED(texture->QueryInterface(__uuidof(Investigo::IResource), (void**) &investigoResource))
{
    string name = investigoResource->GetName();

    // ... do something with the resource ...

    // Release when finished so there is no memory leak.
    investigoResource->Release();
}

如前所述,请确保将 Investigo 类和函数的用法包装在 INVESTIGO_ENABLED 中。此外,非常重要的是,通过 QueryInterface 获取的 COM 接口必须调用其Release 方法。

Investigo 应用程序接口

如果宏和内联函数不够用,您可以显式加载 Investigo DLL 并手动提取应用程序接口。应用程序接口是一个纯虚拟 C++ 类,它直接公开代理 DLL 中的 Investigo 单例。

GetApplicationInterface 是检索 Investigo 接口指针的 DLL 导出函数,它通过调用 GetProcAddress. 来检索。

HMODULE hInvestigo = LoadLibraryA("d3d9.dll");
if (!hInvestigo)
{
    // ... The DLL couldn't be loaded, handle this error gracefully ...
}

Investigo::pfnGetInterface getApplicationInterface = 
	(Investigo::pfnGetInterface) GetProcAddress(hInvestigo, "GetApplicationInterface");
if (!getApplicationInterface)
{
    // ... The DLL doesn't export the right function, handle this error gracefully ...
}

同样,请记住将您的代码包装在 INVESTIGO_ENABLED 中,并优雅地处理 Investigo 代理 DLL 不存在的情况。

接下来,调用GetApplicationInterface 来检索应用程序接口。

Investigo::Interface* investigo = getApplicationInterface();

// ... you can now use investigo to interact with the Investigo proxy DLL ...

当 Investigo 使用完毕后,应卸载 DLL。

FreeLibrary(hInvestigo);
hInvestigo = NULL;

作为替代方案,如果您想在应用程序的整个生命周期中使用 Investigo 并且不关心卸载 DLL,那么只需调用 Investigo 的EnsureLoaded 函数。

Investigo::Interface* investigo = Investigo::EnsureLoaded();

// ... you can now use investigo to interact with the Investigo proxy DLL ...

EnsureLoaded
会惰性加载并缓存 DLL 句柄和应用程序接口。后续调用将简单地返回缓存的应用程序接口指针。

宏和内联函数包装并简化了应用程序接口,并提供了大部分相同的功能。因此,您只需要在使用较低级别或更精细的控制时才需要使用应用程序接口。

有关 Investigo 类和函数的更多详细信息,请参阅 API 参考部分。

使用 Investigo 进行自动化性能测试

Investigo 的一大优势在于其在自动化性能测试方面的潜力。

我将举一个自动化性能测试的例子

  1. 您执行一个测试脚本。我通常喜欢用Python编写此类脚本。
  2. 脚本启动 DirectX 应用程序。
  3. 通过命令行参数或其他通信机制,脚本指示应用程序运行特定的图形测试套件。例如,在测试游戏时,您可能会指示它加载特定关卡,并让 AI 播放特定的场景(或过场动画)。
  4. Investigo 性能日志记录在特定点开始,应用程序直接通过 Investigo API 启动,或者通过 Investigo 配置文件安排启动。
  5. 性能日志记录持续到指定停止点,可以通过 API 或配置文件停止。
  6. 应用程序退出,可能由 Investigo 强制退出(通过配置文件或 API 指定),或者在应用程序控制下更优雅地中止。
  7. 测试脚本现在将捕获的性能数据复制到合适的存储位置。
  8. 如果还有更多测试要做,则返回步骤 2。
  9. 测试完成后,使用 ReportGen 为测试运行生成性能报告。当前测试数据将与先前测试数据进行图形比较。
  10. 测试脚本然后将性能报告通过电子邮件发送给相关人员,并存档性能数据。

通过这种方式,性能测试可以成为每日自动化构建过程的一部分。通过检查生成的性能报告并与历史数据进行图形比较,您可以每日(或每周/每月)掌握应用程序性能的趋势。它还应该能够让您尽可能快地发现提交到代码库中的重大性能问题。如果能让测试脚本足够智能,甚至可能可以自动检测性能何时下降,并将潜在的性能问题与版本控制存储库中的特定更改集相关联。结合 Mercurialbisect 功能,您应该能够构建一个系统,该系统可以自主地在版本控制中搜索引入特定性能问题的修订。

当然,为了使这一切准确工作,您的应用程序确实必须是确定性的,并且在每次运行性能测试套件时都必须以相同的方式执行。这在游戏开发中可能尤其困难,因为多线程、CPU 定时器、随机数和浮点运算都会影响确定性。但我相信,确定性是值得奋斗的,因为它大大提高了您重现错误和运行自动化测试的能力。

结论

本文关于 Investigo 用法的章节到此结束。希望现在您可以使用 Investigo 来辅助您的 DirectX 性能分析和调试。

如果您需要深入了解 Investigo 的内部原理,想帮助我为 Investigo 添加功能,或者只是对这一切如何工作感兴趣,请继续阅读。

请将反馈和问题作为消息发送到 code project。Bug 和功能请求也可以通过 Investigo 的 SourceForge 页面记录。

实现

代理 DLL 如何工作?

DirectX 实现为一个动态链接库,通常称为DLL。应用程序(如游戏)使用 DirectX DLL 与图形硬件通信以渲染几何体。

通常,DirectX DLL 从 Windows 系统目录加载。Windows 的 DLL 解析规则使得用与应用程序位于同一目录下的同名 DLL 来替换系统 DLL 成为可能。



这意味着我们可以用我们自己的代理替换 DirectX DLL,该代理伪装成 DirectX DLL。代理 DLL 加载原始 DLL 并将所有调用转发给它(因此称为代理)。

这使我们能够拦截所有对导出 DLL 函数的调用。

代理 DLL 在其他情况下也很有用。一个值得注意的用途,与 Investigo 非常相似,是GLIntercept,一个用于OpenGL 的代理和拦截工具。


代理 DLL 何时不起作用

需要指出的是,代理 DLL 并非在所有情况下都那么容易起作用。

我自己在尝试代理winsock API DLL 时发现了这一点。当时我用它来调试和分析网络应用程序的输入和输出。winsock DLL 是一个已知 DLL 的例子,一个被 Windows (在某种程度上)保护的 DLL。这意味着系统版本的 DLL 通常不能被代理 DLL 覆盖。但是,通过添加一个注册表项将该 DLL 从已知 DLL 集中排除,可以轻松绕过这个问题。

对于 winsock,ws_32.dll 必须添加到以下注册表项:

HKLM\System\CurrentControlSet\Control\Session Manager\ExcludeFromKnownDlls

有关更多详细信息,请参阅 Microsoft 关于此问题的页面

出于某种原因,d3d9.dll 不是已知 DLL。Windows 可能会对此 DLL 进行某种保护以防止代理。但是,由于未知原因(对我们有利),d3d9.dll 可以轻松地进行代理,而无需修改注册表。

创建代理 DLL

构建初始代理 DLL

那么,我是如何创建初始代理 DLL 的?最困难的方法是手动实现 DLL 及其所有导出函数。

聪明的方法是使用wrappit,由Michael Chourdakis 编写。使用 Michael 的 wrappit 工具并遵循他的说明,您可以轻松创建裸机代理 DLL,该 DLL 将所有调用转发给原始 DLL。生成代理 DLL 后,您需要确保代理正确加载了真实的系统 DLL。一旦您完成了这项工作,您就拥有了一个最小化的可用代理 DLL,这是进行第一次测试并确保该 DLL 确实可以与真实应用程序配合使用的好时机。听起来很简单?别太得意,还有一些工作要做。

生成的代理 DLL 中的所有导出函数都设置为 __declspec(naked)。为了有用地拦截调用,您必须遍历所有导出函数,并实现与原始 DLL 一致的函数签名和调用约定。这是一个耗时且容易出错的过程。通常需要 DLL 附带的头文件或文档,以便您知道每个导出函数的返回类型和参数类型。在我们的例子中,我们需要参考 DirectX 头文件和 MSDN 上的 SDK 文档。

那么,为什么这个过程如此容易出错?原因是,如果任何特定函数的函数签名不正确,很可能会导致一些严重问题,例如内存损坏和崩溃。当您使用与真实 DLL 不兼容的函数签名编译代理 DLL 时,您很可能会在调用该函数时损坏堆栈,这可能是一个特别痛苦的问题,尤其是当您不确定是哪个函数签名导致问题时。更糟糕的是,这种类型的 Bug 有时会发生,问题并不总是立即显现。

为了降低引入问题的风险,应一次转换一个函数签名,并彻底测试每个更改。如果在任何时候发生崩溃,您就会知道这是由于您上一个转换的函数引起的。逐步、渐进式和经过充分测试的更改通常是一个好的编程实践,在这种情况下,它肯定会为您节省很多麻烦。

在分析 DLL 的导出函数或检查其依赖项时, Dependency Walker,也称为 Depends.exe,是 Visual Studio 以前包含的一个非常有价值的工具。



此时,我们已经生成了初始 DLL 并实现了正确的函数签名。然而,我们仍然只有一个哑巴代理 DLL。对于代理简单的 DLL(如我的 winsock 代理)来说,这可能就足够了,尽管对于 DirectX,我们仍然需要拦截 DirectX API 函数,为此我们必须实现 DirectX COM 接口。

实现 DirectX 接口

现在我将讨论为每个 DirectX 接口创建代理类。首先,我将官方 DirectX 接口复制到 Investigo 项目的新头文件中。然后,我将每个接口转换为一个继承并实现该特定接口的类。

代理类的名称是原始接口名称,前面加上Proxy

例如,IDirect3DDevice9 的代理类名为ProxyIDirect3DDevice9


每个代理类都有一个名为original的数据成员,它是一个指向真实 DirectX 对象的指针。

class ProxyIDirect3D9 : public IDirect3D9
{
public:

    // ...

private:

    // ...

    IDirect3D9* original; // Pointer to real DirectX interface.
};
代理类的构造函数接受一个指向真实 DirectX 对象的指针并将其存储在成员中。
ProxyIDirect3D9::ProxyIDirect3D9(IDirect3D9 *_original) :
    original(_original) // Save pointer to real interface.
{
    // ...
}

然后使用存储的指针来转发 DirectX API 调用,例如:

UINT __stdcall ProxyIDirect3D9::GetAdapterCount()
{
    return original->GetAdapterCount();
}

创建代理 DLL 时,您只需要实现您真正关心的接口的代理。对于 Investigo,我实现了所有 DirectX 接口,因为我想让 Investigo 捕获关于所有 DirectX API 调用信息。


资源的代理类不仅派生自 DirectX 接口,还派生自 Investigo 资源接口和 Investigo 资源基类

这里的例子是ProxyIDirect3DVertexShader9

您可以在图表中看到它实现了 Investigo::IResource 并派生自 InvestigoResource。前者是通过 QueryInterface 访问的 Investigo 资源接口,后者是资源基类。

基类具有检索真实 DirectX 设备、资源 ID 和应用程序定义的资源名称(当已分配名称时)的函数。

这是 Investigo 代理 DLL 如何构建的简要概述。现在让我们继续详细介绍 Investigo 的一些组件。

获取代码

Investigo 的代码可以在本文附带的 InvestigoSrc.zip 中找到。
要获取最新代码,请访问 SourceForge

如果您安装了 Mercurial,您可以克隆存储库的只读副本。

hg clone http://hg.code.sf.net/p/investigo/src investigo-src

随附的解决方案适用于 Visual Studio 2010。

解决方案演练

Investigo 解决方案包含三个项目。




ProxyDX 是构建代理 DLL 的项目。

还有一个最小化的 D3DX 代理 DLL,可用于 Visual Studio 中的调试,但除此之外目前并不特别可用。

ReportGen 是简单的 HTML 性能报告生成器的项目。

ProxyDX 项目中可以找到 DirectX 代理类。DllMain.cpp 是其中最重要的文件之一。它包含 DLL 入口点,并且是代理 DLL 导出函数的地方。其中定义了两个非常重要的函数。

第一个是 GetApplicationInterface,这是检索 Investigo 应用程序接口的 DLL 导出函数,该接口实现了 Investigo API。

第二个是 Direct3DCreate9 的代理实现。这是创建代理 DirectX 设备并将其返回给应用程序的地方。

Investigo 代理 DLL

主要的 Investigo 类,将所有内容联系在一起的是一个名为 InvestigoSingleton 的单例。

代理 DLL 中的核心数据结构是管理变量、页面和组的数据结构。实现这些的数据结构是:VariableVariablePageVariableGroupHistoryBuffer 类管理单个变量的历史记录。 VariableManager 是一个单例类,负责管理所有变量、页面和组。 核心数据结构类的每个类都知道如何将自身格式化为 JSON 以传递给 Web UI。

HttpServer 类包装了 Mongoose HTTP 服务器DXHttpServer 是一个更高级别的类,它包装了 HttpServer ,并负责通过更通用的 HTTP 服务器提供 Investigo 的 DirectX 特定服务。 许多 URL 由 DXHttpServer 处理,作为响应,动态生成 JSON 数据并返回给客户端。其他 URL 由 HttpServer 处理,并转发给 DLLResourceManager,该类通过提取和返回 DLL 嵌入的资源来响应请求。

PerformanceLog 类负责将性能数据输出到 CSV 格式日志文件。

此外,还有许多类实现了 DirectX 接口,我们之前已经看过其中几个。

Investigo API

InvestigoSingleton 直接实现了 Investigo 应用程序接口 Investigo::Interface

GetApplicationInterface 是一个 DLL 导出函数,它通过 Investigo::Interface 返回指向 InvestigoSingleton 的指针。应用程序可以加载 DLL,提取应用程序接口,然后与 Investigo 代理 DLL 进行交互。

更简单的宏和内联函数已分层在应用程序接口之上,以使其更方便使用。

多线程数据访问

在实现 Investigo 的过程中,保护多线程之间的数据访问一直是一个非常重要的问题。

首先,DirectX 渲染线程不应被耗时的操作(如写入性能日志文件等)阻塞。这样做会影响其性能,并会使性能分析的目的失效。

其次,HTTP 服务器默认是并发的,并使用多个线程来服务 Web UI。

渲染线程生成的性能指标使用无锁队列进行缓冲。也就是说,一个允许多线程访问而无需锁定进行同步的队列。引入了一个单独的线程,我称之为历史更新线程,它从无锁队列检索数据并将其复制到每个变量的历史缓冲区。当启用性能日志记录时,历史更新线程还将输出性能日志。 历史更新线程VariableManager 单例类控制。

如以下图表所示,互斥锁用于保护由历史更新线程修改且由HTTP 服务器线程读取的数据。



Investigo 使用 Boost 线程库,该库提供了快速开始多线程编程的便捷方法,有关更多详细信息,请参阅这些文章

http://antonym.org/2009/05/threading-with-boost---part-i-creating-threads.html
http://antonym.org/2010/01/threading-with-boost---part-ii-threading-challenges.html

注解 DirectX API 调用


Investigo 有一个宏用于标记每个代理 DirectX API 调用。

例如,SetTexture 的代理版本

HRESULT ProxyIDirect3DDevice9::SetTexture(DWORD Stage,IDirect3DBaseTexture9* pTexture)
{
    DX_RECORD_API_CALL(IDirect3DDevice9, SetTexture);

    // ...
}
DX_RECORD_API_CALL 将一个特定的 DirectX 函数注册给 Investigo。

该函数将添加到计时和指标系统中。调用持续时间将被计时,每帧调用次数将被记录。它还定义了一个可以从 Web UI 触发断点的位置。

断点数据由 InvestigoSingleton 管理。计时指标和每帧计数存储在由 VariableManager 处理的变量中。

当 Web UI 中请求断点时,断点位置由 InvestigoSingleton 记录。当执行到达特定函数时,会触发硬编码断点(通过调用 DebugBreak)。

实现 Web UI

您可能首先想问的问题是,为什么首先要有一个 Web UI?

答案很简单。HTTP 方便、易于实现、成熟,并且由于 Mongoose,将 HTTP 服务器嵌入 C++ 应用程序中非常简单。

HTML、JavaScript、CSS 和图像资源直接从嵌入的 DLL 资源提供,这意味着 Web 服务器(在本例中是代理 DLL)可以作为一体式包分发。在中断很长时间后回到 Web 应用程序开发,我印象深刻于如今 Web 技术是多么的先进。与过去相比,使用 jQuery 和 jQuery Mobile 开发一个小型 Web 应用程序非常容易 - 至少在您征服了学习曲线之后。更不用说可用的资源、学习材料和软件的数量是多么惊人。现在不再是选择“是否有足够好的东西”或“我是否要自己写代码”的问题,而是“在十几个好 API 之间选择一个,并艰难地选择最适合的那个!”

与普通 UI 相比,开发 Web UI 的一个优势是许多问题已经解决了。例如,使用 jQuery 的 AJAX 功能实现AJAX 调用非常简单。套接字将是更有效的通信机制(并且可能是 Investigo 的未来选择),但它们比 AJAX 更难使用(至少考虑到 jQuery 使 AJAX 变得多么简单)。

对于显示 Web UI 的实时性能图表,存在许多现有选项。我尝试了几种,但找不到具有我想要的实时性能特征的图形 API。所以我创建了自己的简单图形渲染器,它基于 HTML5 Canvas API。尽管如此,图形渲染的性能并非最佳,也不是评估性能的最准确方法。它应该仅用作性能指示。严肃的性能工作需要记录性能指标以进行离线分析。

开发 Web UI 的另一个好处是,您的迭代开发速度会大大提高。您的应用程序可以设计为在离线测试模式下运行,并直接在浏览器中从文件系统查看。因此,设计/编码/测试周期的周转时间大大缩短。

最后,我应该提到,使用 HTTP 服务器和 Web 应用程序会打开许多大门。它可以在大多数平台上运行,并且可以远程通过网络运行。

JavaScript 库

Web UI 是基于 jQueryjQuery Mobile 构建的,两者都是不可或缺的 JavaScript 库。 

jQuery 提供浏览器独立的DOM 操作、事件、AJAX 和使用模板的 UI 生成。它带有一系列您将依赖的绝佳实用函数。

jQuery Mobile 意味着 Web UI 可以在平板设备上使用。例如,DirectX 应用程序运行在 PC 上,Web UI 可以在您面前的平板设备上打开。这意味着您可以全屏运行您的应用程序,并且 Web UI 在单独的设备上运行意味着它不会干扰全屏应用程序。

jQuery Mobile 使构建一个外观不错的、多页面的、跨浏览器和支持平板电脑的 Web 应用程序变得几乎微不足道。它为您做了这么多,真是令人惊讶。能够无需太多 CSS 就能构建一个不错的 UI 对我来说非常重要。我主要是一名开发者,而不是一名设计师(尽管我喜欢涉猎)。jQuery Mobile 仍然相当新,并非没有问题,但如果您的 Web 应用程序保持简单,jQuery Mobile 将使您的生活更轻松,而且只会变得越来越好。

Web UI 的部分内容是使用 jQuery 模板动态生成的,这些模板将 JSON 数据渲染为 HTML。数据被注入到模板中,扩展为 HTML,然后插入到 DOM 中。

对于非 Web 设计师来说,使用 HTML 和 CSS 进行布局可能非常痛苦。处理布局时您需要武装起来的第一个工具是一个好的网格布局系统。我使用了 jQuery Mobile 960,因为它与 jQuery Mobile 很好地结合,并满足您的网格布局需求。尽管由于 Investigo 当前简单的布局要求,我还没有太多机会使用它,但我已经在可能成为 Investigo 未来一部分的各种实验功能中使用过它。

如果我现在重写 Web UI,我可能会想研究使用现有的 MVC 框架,而不是手动编写那么多从数据模型生成 UI 的代码。我最近发现了这篇文章,它可能有助于您选择众多可用库和框架中的一个。

图形渲染

实时性能图表是使用 HTML5 Canvas API 渲染的。链接的维基百科页面有一些简单的使用示例。API 在 Investigo 中的使用相当直接,我主要做的是设置前景颜色,然后调用 moveTolineTo 来渲染线条。有关完整代码,请参阅 Graph.js

离线测试模式

Web UI 有一个特殊的测试模式,允许它直接在浏览器中从文件系统运行。使用测试模式时不需要 HTTP 服务器。这意味着只需在 Windows 资源管理器中双击主 HTML 文件即可在本地运行 Web UI 进行测试。 测试模式使用模拟的 JSON 数据来模拟实时环境,其他资源(HTML、CSS 和 JavaScript)直接从文件系统加载。 不必启动和停止 HTTP 服务器,可以快速地迭代开发 Web UI。它还使重现和修复 Bug 更加容易。

当 Web UI 从文件系统运行时,测试模式会自动启用。

var testMode = false;

// ...

if (document.URL.startsWith("file://")) {
    console.log("Automatically entering test mode.");

    testMode = true;

}

模拟测试数据包含在主 HTML 文件(UI.html)中,通常方式如下:

<script type="text/javascript" src="test_config.js"></script>

测试数据文件将测试数据分配给一个全局变量。

var test_config = [

    // ... test data defined here ...

]

当 test mode 处于活动状态时,通常会通过 AJAX 从 HTTP 服务器动态检索的所有 JSON 数据将被替换为测试数据。DataManager 类(data_manager.js)负责进行切换。例如,检索 DirectX 配置的函数

var get_config = function (success_callback) {
    if (testMode) {
        success_callback(test_config);
    } else {
        $.ajax({url: '/get_config',
            dataType: 'json',
            data: {},
            async: false,
            success: success_callback
            });
    }
};

由于测试数据包含在主 HTML 文件中,因此即使 Web UI 在实时模式下运行(此时测试数据是不必要的),浏览器仍会尝试加载测试数据。通常,HTTP 服务器会对不存在文件的请求返回错误。但是,HTTP 服务器会特殊处理测试数据,它会对任何以test_开头的 URL 请求响应一个空文件。

掌握 JavaScript

不得不说,尽管 JavaScript 语言本身不错,但它常常被其糟糕的方面所掩盖。推荐阅读 Javascript: The Good Parts,以了解应将学习精力集中在哪里以及哪些 JavaScript 领域最好避免。

确保您使用以下工具,它们将为您节省大量解决 JavaScript 或 JSON 问题时的挫败感。

http://www.jslint.com/
http://www.jshint.com/
http://jsonlint.com/

绝对要利用 Chrome 开发者工具。我也听说过 Firefox 的优秀 Web 开发者插件。这些工具允许 JavaScript 调试,提供控制台,允许 DOM 检查,提供性能工具等等。

实现 HTTP 服务器

Investigo 嵌入了 Mongoose HTTP 服务器,该服务器将 Web UI 提供给浏览器。

关于 Mongoose 设置的文档不多,但幸运的是,通过反复试验并不难弄清楚。

包含 Mongoose 代码

首先,您需要将 mongoose.cmongoose.h 包含在您的项目中。

运行服务器

Investigo 的 HttpServer 类是 Mongoose 的包装器,它负责启动和停止服务器以及管理 URL 回调。

构造函数启动服务器。

HttpServer::HttpServer() :
    httpServerContext(NULL)
{
    const char *options[] = {

    // ... various Mongoose options ...
    };

    httpServerContext = mg_start(::HttpServerCallback, this, options);
}
注意 mg_start 的参数。最重要的是处理 URL 回调的函数。另请注意,this 指针作为用户数据对象传入。

析构函数停止服务器。

HttpServer::~HttpServer()
{
    mg_stop(httpServerContext);
}

处理 URL

Mongoose 是用 C 实现的,需要一个全局函数来处理 URL 回调。例如,Investigo 的全局 URL 处理程序

static void* HttpServerCallback(enum mg_event event, struct mg_connection* conn, 
    const struct mg_request_info* request_info)
{
    HttpServer* httpServer = (HttpServer*)request_info->user_data;
    return httpServer->HttpServerCallback(event, conn, request_info);
}
Mongoose 的用户数据对象被强制转换为HttpServer,然后 URL 回调被转发到成员函数。

生成资源以满足 URL 请求

HttpServerCallback 成员函数是实际工作发生的地方。它首先将特定的 URL 分派给特定的处理程序函数。

void* HttpServer::HttpServerCallback(enum mg_event event, struct mg_connection* conn, const struct mg_request_info* request_info)
{
    if (event == MG_NEW_REQUEST)
    {
        // Is there a handler for this particular URL?
        UrlCallbackMap::iterator found = urlCallbacks.find(request_info->uri); 
        if (found != urlCallbacks.end())
        {
            // ... setup omitted ...

            // Dispatch to a handler for a specific URL.
            found->second(request_info->uri, ... other params ...);

            // ... generated response is written to the client ...

            return ""; // Don't need to do anything else.
        }

        // ...
    }

    // ...
}
Web UI 对 JSON 数据的请求以这种方式处理。 DXHttpServer 是一个更高级别的类,它注册了许多生成 JSON 数据的 URL 处理程序。这就是代理 DLL 如何将相关的 DirectX 数据返回给 Web UI。例如,localhost:8080/get_config(在 Investigo 运行时在浏览器中尝试,您将看到生成的 JSON 数据!) 被路由到函数 DXHttpServer::GetConfigCallback,该函数将 DirectX 配置信息格式化为 JSON。

Mongoose 函数 mg_write mg_printf 用于将响应写入客户端。

提供嵌入的 DLL 资源

没有特定 URL 处理程序的 URL 被分派给 DllResourceManager 类。 当 URL 匹配 DLL 嵌入的资源时,该资源将被提取并提供给客户端。

void* HttpServer::HttpServerCallback(enum mg_event event, 
    struct mg_connection* conn, const struct mg_request_info* request_info)
{
    if (event == MG_NEW_REQUEST)
    {
        // Is there a handler for this particular URL?
        UrlCallbackMap::iterator found = urlCallbacks.find(request_info->uri); 
        if (found != urlCallbacks.end())
        {
            // ... Dispatch to a handler for a specific URL ...
        }

        // ... setup omitted ...

        if (resourceManager.GetResourceData(request_info->uri, ... other params ...))
        {
            // Requested URL has been found in DLL resources.

            // .... serve the static DLL resource to the client ...

            return ""; // Content has been served.
        }

        // ...

    }

    return NULL; // Allow files to be loaded from the file system, but only from the directory specified in the configuration.
}
从 DLL 资源提供 Web UI 的静态部分对于不需要动态生成的资源来说效果很好。Investigo 代理 DLL 包含许多此类资源,包括主页面 UI.html 及其背后的 JavaScript。

测试数据

处理的最后一类 URL 是以 test_ 开头的 URL。当 Web UI 在测试模式下运行时,这些 URL 用于加载测试数据。在实时模式下运行时(即从嵌入式 HTTP 服务器提供),这些资源是不必要的,HTTP 服务器会响应一个空字符串。

这几乎太简单了,不需要代码片段,尽管我还是会提供一个,因为它是一个展示 mg_printf 的好机会。

void* HttpServer::HttpServerCallback(enum mg_event event, 
    struct mg_connection* conn, const struct mg_request_info* request_info) 
{
    if (event == MG_NEW_REQUEST)
    {
        // Is there a handler for this particular URL?
        UrlCallbackMap::iterator found = urlCallbacks.find(request_info->uri); 
        if (found != urlCallbacks.end())
        {
            // ... Dispatch to a handler for a specific URL ...
        }

        // ... setup omitted ...

        if (resourceManager.GetResourceData(request_info->uri, ... other params ...))
        {
            // .... serve the static DLL resource to the client ...
        }

        if (strncmp(request_info->uri, "/test_", 6) == 0 || // Does the URL begin with 'test_'?
        strncmp(request_info->uri, "test_", 5) == 0)
        {
            //
            // Any URL that is not found and begins with 'test_' is a test file that is 
            // unavailable when the web server is live.
            // Just return a blank document.
            //
            mg_printf(conn, "HTTP/1.1 200 OK\r\n"
                "Content-Type: text/javascript\r\n\r\n");

            return "";
        }
    }

    // ...
}

从文件系统提供资源

请注意 URL 回调中的最后一行,它返回 NULL。这是对未被先前检查满足的 URL 的回退。当发生这种情况时,我们允许资源从文件系统提供(仅当存在匹配文件时)。直接从文件系统提供文件有助于在尚未将其打包为 DLL 资源之前,为正在构建的资源提供更便捷的开发。

生成 JSON 以满足 AJAX 请求

JSON 是将动态生成的性能数据从服务器传递到 Web 应用程序的数据格式。

为什么选择 JSON?

JSON 是从服务器到 Web 应用程序传递数据的标准机制。JSON 格式直接受 jQueryAJAX 函数支持,并且(前提是 JSON 格式正确)会自动从文本数据转换为 JavaScript 对象树。

在 C++ 端,JSON 是通过简单的文本操作生成的。但是,随着数据需求的增长以及当您拥有深度嵌套的数据结构时,JSON 生成代码会变得更加复杂。为了减轻这种混淆,我超越了简单的文本操作,创建了一个名为 jsonstream 的更高级别的 C++ 类,以帮助输出 JSON 格式的数据。 jsonstream 的行为与标准 C++ 流对象非常相似,尽管它的实现要简单得多。

生成性能报告

示例代码中包含了一个 C# 控制台应用程序,演示了如何生成基于 HTML 的性能报告。这个程序实际上只是一个简单的例子,并非旨在满足所有人的所有需求。如果您需要以不同的方式处理性能报告,我鼓励您深入其中并开始修改代码。

ReportGen 的用法 之前已解释过。在本节中,我将提供一个简短的演示,以帮助您熟悉代码。  

ReportGen 接收 Investigo 生成的性能日志(又名 CSV 文件),并将它们转换为 HTML 性能报告。CSV 文件中的实际数据被转换为 Javascript 数据文件,这些文件采用 Google Charts API 所需的格式。   CsvFile 类负责加载和转换 CSV 文件为 Javascript。

Main 函数位于 Program.cs 中。  它调用 GenerateReport 来完成提取嵌入式资源和扩展 HTML 模板以创建性能报告的大部分工作。   Razor 模板引擎 用于生成报告的 HTML 页面。通常,Razor 用于 ASP.NET Web 应用程序,但它也可以嵌入到应用程序中,用于从模板和数据生成 HTML 页面。生成性能所需的资源和模板存储为嵌入式资源。使用嵌入式资源使得 ReportGen 成为一个集成式包,没有任何外部依赖。生成报告时,模板会从资源中提取,使用 Razor 扩展,然后写入文件系统。

结论

到此,我关于 Investigo 的介绍文章就结束了。感谢您的阅读,希望 Investigo 对您有所帮助。

如果您想帮助 Investigo 发展,请加入我的 SourceForge 项目。如果您喜欢这篇文章和项目,请告知您的朋友和同事。

如果您无法直接贡献,请考虑进行 捐赠,这将有助于支付硬件和软件费用。如果您不能贡献太多,即使是足够买一杯啤酒的钱也会很棒;)

未来功能

我有很多想要添加到 Investigo 中的功能,但一直没有时间。这只是一个小列表,还有很多其他的想法。
  • DX11 支持。
  • D3DX 支持。
  • Inspector(部分完成,允许在检查渲染状态、资源时暂停 DX,允许逐个绘制调用)。
  • 帧分析(当前帧使用了哪些纹理、着色器等?)
  • 资源分析(启动以来加载了哪些纹理、着色器等?)
  • 更好的离线性能查看器。
  • 性能实验页面(例如,强制所有纹理缩小到 2x2)
  • 基于渲染状态的条件断点。
  • 帧捕获和重放(WebGL?NativeClient?)
  • 截图(部分完成)
  • 重新配置设备状态和重置。

参考

Investigo 配置文件

Investigo 配置文件应命名为 Investigo.cfg,并放置在 Investigo 的 DirectX 代理 DLL d3d9.dll 所在的同一个目录中。

名称 描述
DirectXPath


设置真实 DirectX DLL 的路径,默认为 c:\Windows\System32\d3d9.dll
OutputDirectory 设置 Investigo 性能日志的输出目录。
PerformanceLoggingStartFrame 性能日志应开始记录的帧号,从 1 开始。
PerformanceLoggingDuration 性能日志应停止的持续帧数。
ExitAfterPerformanceLogging 设置为 true 以在性能日志停止后中止应用程序(通过调用 exit())。
日志记录已停止。
PortNo HTTP 服务器监听的端口号(默认为 8080)。
HistoryBufferSize 指定变量历史缓冲区的大小(默认为 1000)。

API 参考

文件、类型和命名空间

名称 类型 描述
InvestigoApplicationInterface.h


头文件 包含 Investigo API 的头文件,允许应用程序与 Investigo 集成。
Investigo
Investigo 包含 Investigo 类和函数的命名空间。
VariableType
枚举 指定 Investigo 变量的类型。
IVariable
接口 变量接口。
IResource 接口 Investigo 资源接口(例如,代理的 DirectX 资源)。
接口 接口 应用程序与 Investigo 的接口。
由应用程序直接与 Investigo 交互使用。
您应该优先不使用此接口,下面定义了方便且更简单的内联函数。
VariableHandle Investigo 变量的句柄。
TimingBlock 计时代码块,将经过的时间记录到一个变量中。



名称 描述
INVESTIGO_INT_SET_VALUE(page, name, value)


设置 Investigo int 变量的值。
INVESTIGO_INT_INCREMENT(page, name)
增加 Investigo int 变量的值。
INVESTIGO_INT_RESET(page, name) 重置 Investigo int 变量的值。
INVESTIGO_DOUBLE_SET_VALUE(page, name, value) 设置 Investigo double 变量的值。
INVESTIGO_DOUBLE_RESET(page, name) 重置 Investigo double 变量的值。
INVESTIGO_TIMER(page, name) 测量代码块的执行持续时间。
INVESTIGO_RESOURCE_NAME(iface) 检索已分配给 DirectX 资源的名称。
INVESTIGO_RESOURCE_BEGIN(name) 开始一个代码块,在该代码块中,加载的 DirectX 资源将使用指定的名称进行注释。
INVESTIGO_RESOURCE_END 结束一个代码块,在该代码块中,加载的 DirectX 资源将使用指定的名称进行注释。

内联函数(Investigo 命名空间)

名称 描述
Investigo::Interface* EnsureLoaded() 加载 Investigo DLL 并检索应用程序接口。
该接口是缓存的,后续调用直接返回它。
void EnableDrawCalls(bool enable) 启用或禁用绘制调用。
void EnableDrawCalls() 启用绘制调用。
void DisableDrawCalls() 禁用绘制调用。
void ResourceBegin(const char* resourceName) 开始一个加载资源的函数。
void ResourceEnd() 结束一个加载资源的函数。
std::string GetResourceName(IUnknown* iface) 获取 DirectX 资源的名称。
void TechniqueBegin(const char* techniqueName) 开始一个效果技术(technique)的渲染。
void TechniqueEnd() 结束一个效果技术(technique)的渲染。
void SetOutputDirectory(const char* outputDirectory) 设置 Investigo 输出日志文件的目录。
在开始性能日志记录之前必须设置此项。
bool IsPerformanceLoggingEnabled() 如果性能日志记录当前已启用或计划在下一帧开始,则返回 true。
void StartPerformanceLogging() 开始性能日志记录。
void StopPerformanceLogging() 停止性能日志记录。
int GetCurrentFrameNumber() 获取当前帧号(从 1 开始)。
当 Investigo 未加载时返回 0。
void StartPerformanceLoggingAt(int frameNumber) 在指定的帧号开始性能日志记录。
如果指定的帧已过去,则无效。
void StopPerformanceLoggingAfter(int numFrames) 在 X 帧后停止性能日志记录。
void ExitWhenPerformanceLoggingFinished() 导致应用程序在性能日志记录完成后被强制退出。

Investigo::IVariable: 变量接口。

名称 描述
void SetValue(int value)
将变量值设置为 int。
void Increment()
增加变量值(仅限 int)。
void SetValue(double value)
将变量值设置为 double。
void Reset()
重置变量的值。

Investigo::IResource: Investigo 资源接口(例如,代理的 DirectX 资源)。

名称 描述
int GetId() const 获取资源的唯一 ID。
const std::string& GetName() const 获取资源的易读名称。

Investigo::Interface

应用程序与 Investigo 的接口。
由应用程序直接与 Investigo 交互使用。
您应该优先不使用此接口,下面定义了方便且更简单的内联函数。

名称 描述
int GetFrameNumber() const 获取当前帧号,从 1 开始。
double GetTime() const 获取 Investigo 测量到的当前时间(以秒为单位)。
void SetOutputDirectory(const char* outputDirectory) 设置 Investigo 输出日志文件的目录。
在开始性能日志记录之前必须设置此项。
void EnableDrawCalls(bool enable) 启用/禁用绘制调用。
void BeginResource(const char* resourceName) 开始一个加载资源的函数。
加载的资源(例如纹理和着色器)将继承指定的资源名称。
函数可以嵌套调用。
void EndResource() 结束一个加载资源的函数。
void BeginTechnique(const char* techinqueName) 开始一个渲染技术的函数。
设置的纹理/着色器可以与技术关联(这由
D3DX 代理使用)。 
void EndTechnique() 结束一个渲染技术的函数。       
IVariable* GetVariable(const char* pageName,
    const char* variableName, VariableType variableType)
从指定名称的页面检索变量。
如果变量和页面不存在,则会创建它们。
bool IsPerformanceLoggingEnabled() 如果性能日志记录当前已启用或计划在下一帧开始,则返回 true。
void StartPerformanceLogging() 开始性能日志记录。
void StopPerformanceLogging() 停止性能日志记录。
int GetCurrentFrameNumber() const 获取当前帧号(从 1 开始)。
void StartPerformanceLoggingAt(int frameNumber) 在指定的帧号开始性能日志记录。
如果指定的帧已过去,则无效。
void StopPerformanceLoggingAfter(int numFrames) 在 X 帧后停止性能日志记录。
void ExitWhenPerformanceLoggingFinished() 导致应用程序在性能日志记录完成后被强制退出。

替代工具

有许多工具与 Investigo 类似,或者具有重叠的功能。

Fraps
  • 用于测量 DirectX 应用程序每秒帧数。
  • 它还可以为 DirectX 应用程序截屏和录制视频。

NVPerfHUD
  • 非常适合检查 DirectX 应用程序的实时性能。U
  • 不幸的是,它只能与 NVidia 显卡一起使用,并且不能与任意 DirectX 应用程序一起使用。
  • 与 Investigo 不同,它不与 Visual Studio 集成,也无法触发断点。

GPU PerfStudio
  • ATI 对 NVPerfHUD 的回应,值得一看,尽管我很难让它对 32 位应用程序正常工作。

PIX
  • 非常适合理解简单场景,但对于复杂场景,数据量庞大,可能会让人不知所措。

GLIntercept
  • 一个很棒的开源 OpenGL API 拦截器。
  • 它使用了与 Investigo 相同的代理 DLL 技术。

DxExplorer
  • 这个看起来与 Investigo 类似,尽管我没有试过。
  • 它似乎不是开源的。


资源

© . All rights reserved.