CD Code Digest Hub /CS:GO

材质系统与 Shader API

21 分钟阅读 9 个章节 1 张图

materialsystem 把“画什么”(材质、纹理、Shader 参数)与“怎么画”(具体图形 API)隔开:上层通过 IMaterialSystem/IMatRenderContext 发命令,下层的 Shader API DLL(shaderapidx9 或 shaderapiempty)实现设备操作。它还支持把一帧的渲染命令排进队列,由专门线程执行。本页还说明材质代理(proxy)如何由 client.dll 提供、渲染目标何时可以分配,以及这份快照中纹理“流式加载”的实际状态。

相关页面:渲染管线总览 · 客户端视图渲染管线 · 引擎世界渲染 · 客户端启动链 · 专用服务器

相关源码文件#

  • materialsystem/cmaterialsystem.cpp、materialsystem/cmaterialsystem.h
  • materialsystem/cmatrendercontext.cpp、materialsystem/cmatqueuedrendercontext.cpp
  • materialsystem/shaderapidx9/shaderapidx8.cpp、materialsystem/shaderapidx9/shaderdevicedx8.cpp
  • materialsystem/shaderapiempty/shaderapiempty.cpp
  • materialsystem/stdshaders/vertexlitgeneric_dx9.cpp
  • 材质代理:materialsystem/cmaterial.cpp、engine/materialproxyfactory.cpp、game/client/imaterialproxydict.h
  • 渲染目标:engine/matsys_interface.cpp、game/client/baseclientrendertargets.cpp
  • 纹理:materialsystem/ctexture.cpp、materialsystem/texturemanager.cpp、materialsystem/shaderapidx9/textureheap.cpp

1. 目录构成#

materialsystem/ 共 1,733 个跟踪文件,其中 stdshaders 1,520 个(.inc 991、.fxc 256、.cpp 166 等,大量 .inc 是由 .fxc 生成的 Shader 组合头文件),shaderapidx9 75 个,ps3gcm 45 个,glmgr 9 个,shaderlib 9 个,shaderapiempty 4 个(本次 git ls-files 统计)。目录中还有一个 shaderapidx9.rar 归档文件。

2. 选择与加载 Shader API#

  1. 启动器在 Connect 之前调用 SetShaderAPI:默认 shaderapidx9,-noshaderapi 改为 shaderapiempty,-shaderapi 可指定任意 DLL:launcher/launcher.cpp:860-872。专用服务器总是 shaderapiempty:dedicated/sys_windows.cpp:291-292。
  2. SetShaderAPI 只能调用一次;为空时默认 shaderapidx9;随后 CreateShaderAPI 用 Sys_LoadModule 加载并取得工厂:materialsystem/cmaterialsystem.cpp:710-774。PS3/OSX 下该函数直接返回:materialsystem/cmaterialsystem.cpp:748-750。
  3. Connect 要求文件系统存在,并从 Shader API 工厂获取设备管理器等接口:materialsystem/cmaterialsystem.cpp:780-800。

材质系统本身以 IMaterialSystem 单例暴露,并额外暴露硬件配置与 cvar 接口:materialsystem/cmaterialsystem.cpp:145、materialsystem/cmaterialsystem.cpp:180、materialsystem/cmaterialsystem.cpp:190。

实现暴露的接口证据
D3D9CShaderAPIDx8 同时实现 IShaderAPI、IShaderDevice、IDebugTextureInfo;CShaderDeviceMgrDx8 实现 IShaderDeviceMgrmaterialsystem/shaderapidx9/shaderapidx8.cpp:740、materialsystem/shaderapidx9/shaderapidx8.cpp:2123-2129、materialsystem/shaderapidx9/shaderdevicedx8.cpp:95
空实现CShaderDeviceEmpty、CShaderDeviceMgrEmpty、CShaderAPIEmptymaterialsystem/shaderapiempty/shaderapiempty.cpp:236、materialsystem/shaderapiempty/shaderapiempty.cpp:293、materialsystem/shaderapiempty/shaderapiempty.cpp:329-336

shaderapidx9 目录中还有 dx9asmtogl*.cpp、dxabstract.cpp 等 D3D→GL 转译相关文件,与 togl 模块配合服务非 Windows 平台(推断,依据文件名与 togl 工程条件 vpc_scripts/projects.vgc:1296-1299)。

3. 材质与 Shader#

  • 材质通过 FindMaterial(name, textureGroup, ...) 获取:materialsystem/cmaterialsystem.cpp:3026。
  • Shader 用 BEGIN_VS_SHADER 等宏定义,例如 VertexLitGeneric:materialsystem/stdshaders/vertexlitgeneric_dx9.cpp:19。.fxc 源码需离线编译(见 内容工具链 中的 shadercompile),csgo_partner 组包含 stdshader_dx9 与 stdshader_dbg:vpc_scripts/groups.vgc:46-47。

4. 帧与队列渲染#

  • BeginFrame 开始一帧:materialsystem/cmaterialsystem.cpp:3948。GetRenderContext 返回当前渲染上下文(默认是硬件上下文):materialsystem/cmaterialsystem.cpp:1290-1299。
  • 启用条件:AllowThreading 在 -threads 小于 2 时拒绝;只有物理核心数 ≥ 2(或强制允许)时才切换到 MATERIAL_QUEUED_THREADED:materialsystem/cmaterialsystem.cpp:1244-1281。
  • 帧末切换:EndFrame 只接受主线程且在帧内的调用;按 mat_queue_mode(或理想模式)决定下一帧线程模式,设备不可用、不允许队列或被强制单线程时退回单线程:materialsystem/cmaterialsystem.cpp:4090-4126。
  • 双缓冲队列:队列模式下先等待上一帧的异步作业完成,然后在当前队列末尾排入“释放线程所有权”,切换到下一个队列上下文并排入“获取线程所有权”,把上一帧上下文封装成 CFunctorJob( ThreadExecuteQueuedContext ) 投递(可指定服务线程):materialsystem/cmaterialsystem.cpp:4154-4229、materialsystem/cmaterialsystem.cpp:3987。

设计含义(推断):主线程录制第 N+1 帧的同时,渲染线程执行第 N 帧的命令;D3D 设备的线程所有权随队列在两线程间交接。

5. 材质代理(Proxies)#

材质代理是 VMT 中 Proxies 段声明的小对象,在每次绑定材质时根据实体或全局状态改写材质变量(例如后处理用到的 engine_post、BloomAdd、MotionBlur 代理都在 client.dll 中实现:game/client/viewpostprocess.cpp:1617、game/client/viewpostprocess.cpp:2062、game/client/viewpostprocess.cpp:2957)。

证据:

  • 引擎在连接材质系统时把 s_MaterialProxyFactory 设为代理工厂:engine/gl_shader.cpp:43、engine/gl_shader.cpp:81-82。
  • CMaterialProxyFactory::CreateProxy 先问 client.dll,工具模式下再问工具框架;找不到时(非专用服务器)打印 Can't find material proxy 并返回空;DEDICATED 构建恒返回空:engine/materialproxyfactory.cpp:25-45。
  • client.dll 的 InstantiateMaterialProxy(GAMEUI_UISYSTEM2_ENABLED 时先问 GameUI)查询代理字典:game/client/cdll_client_int.cpp:4016-4024;代理类通过 EXPOSE_MATERIAL_PROXY 宏定义的静态对象在 DLL 加载时把工厂函数注册进字典:game/client/imaterialproxydict.h:15-44。
  • CMaterial::InitializeMaterialProxy 遍历 Proxies 子键逐个创建并 Init;找不到或初始化失败只告警并跳过;单个材质最多 256 个代理:materialsystem/cmaterial.cpp:798-850。
  • CallBindProxy 按 VMT 中的顺序调用每个代理的 OnBind;在队列模式下调用前后开启/关闭材质变量的线程化访问:materialsystem/cmaterial.cpp:3065-3101。
  • 调用时机与线程:硬件上下文的 Bind 只在单线程模式或材质声明了“队列友好”代理时调用代理:materialsystem/cmatrendercontext.cpp:2284-2290;队列上下文的 Bind 在主线程上(带外部调用队列)先调用代理,再把硬件 Bind 排入队列;队列友好代理则推迟到渲染线程的硬件 Bind 中执行:materialsystem/cmatqueuedrendercontext.cpp:1425-1451。

含义(推断):普通代理总在主线程上运行、读取的是录制命令时的实体状态,因此代理实现可以安全访问客户端实体;只有显式“队列友好”的代理会在渲染线程运行。

6. 渲染目标分配#

规则实现证据
只能在分配块内创建CreateNamedRenderTargetTextureEx/Ex2 在 m_nAllocatingRenderTargets == 0 时告警并返回 NULLmaterialsystem/cmaterialsystem.cpp:5017-5031、materialsystem/cmaterialsystem.cpp:5053-5068
块结束时模拟 Alt-Tab最外层 EndRenderTargetAllocation 在 PC 上释放并重新获取设备资源,使 RT 先于其他纹理分配materialsystem/cmaterialsystem.cpp:5123-5139
启动完成后封禁FinishRenderTargetAllocation 设置永久禁止标志(-tools 或请求了编辑器材质时除外);之后 BeginRenderTargetAllocation 只告警materialsystem/cmaterialsystem.cpp:5141-5150、materialsystem/cmaterialsystem.cpp:5113-5121
主机初始化末尾封禁Host_Init 流程末尾调用 FinishRenderTargetAllocationengine/host.cpp:5834-5835
例外重开ReEnableRenderTargetAllocation_IRealizeIfICallThisAllTexturesWillBeUnloadedAndLoadTimeWillSufferHorribly:HDR 切换、CSM 质量变化或开关、投影光深度纹理开关时使用materialsystem/cmaterialsystem.cpp:5152-5155、engine/modelloader.cpp:1153-1173、game/client/c_env_cascade_light.cpp:1541-1557、game/client/clientshadowmgr.cpp:4317-4324

InitWellKnownRenderTargets 是标准分配块:引擎先创建 _rt_PowerOfTwoFB(PC)、四分之一尺寸 FB 0/1(CSTRIKE15 下不创建 2/3)、Teeny FB(PC)、全帧 FB 0/1、全帧深度(非 PS3),然后调用客户端 IClientRenderTargets::InitClientRenderTargets,最后结束分配块:engine/matsys_interface.cpp:866-982。客户端 CBaseClientRenderTargets::SetupClientRenderTargets 创建水面反射/折射(cl_disable_water_render_targets 可禁用)、监视器 _rt_Camera(CS:GO PS3 除外)、_rt_eyeglint、render_to_rt_helper,并让 CSM 与客户端阴影管理器分配各自的 RT:game/client/baseclientrendertargets.cpp:103-144;该类以 IClientRenderTargets 暴露:game/client/baseclientrendertargets.cpp:173-175。这些 RT 在视图管线中的用途见 客户端视图渲染管线。

7. 纹理加载:异步下载、纹理排除与(未启用的)流式加载#

  • PC 上没有 mip 流式加载:实现纹理堆/流式的 textureheap.cpp 只在 $X360 下编译,且 SUPPORTS_TEXTURE_STREAMING 宏被注释掉;未定义该宏时 g_bUseBasePools(注释:“do texture streaming”)被强制为 false:materialsystem/shaderapidx9/shaderapidx9_inc.vpc:25、materialsystem/shaderapidx9/textureheap.h:15、materialsystem/shaderapidx9/textureheap.cpp:37-67。
  • 异步下载:CTexture::Download 收到 TEXTUREFLAGS_ASYNC_DOWNLOAD 时改为 ScheduleAsyncDownload;调度失败则清掉该标志、回落到同步下载(文件缺失时使用错误纹理):materialsystem/ctexture.cpp:2843-2867。ScheduleAsyncDownload 解析 materials/<name>.vtf、确认文件存在,每个纹理最多一个在途请求,通过文件系统 AsyncRead 回调完成:materialsystem/ctexture.cpp:5142-5203。复合纹理在预加载输入纹理时使用该标志:materialsystem/composite_texture.cpp:432。
  • 纹理排除(按尺寸上限降级):地图加载时,仅在主机平台或 mat_excludetextures 1(默认 0)时读取 maps/_exclude.lst 与 maps/<地图名>_exclude.lst,为列出的纹理设置尺寸上限或排除,然后(未使用队列加载器时)UpdateExcludedTextures 遍历所有纹理应用新状态:engine/modelloader.cpp:82、engine/modelloader.cpp:4222-4242、engine/modelloader.cpp:4268-4274、materialsystem/texturemanager.cpp:1374-1417。

约束与边界#

  • csgo_partner 组不包含 shaderapidx9,Windows 客户端默认又加载它(见 构建与物理组织),因此合作方构建预期使用外部提供的 shaderapidx9.dll(推断)。
  • 渲染目标必须在启动阶段的分配块内创建;运行时重开分配会导致全部纹理卸载重载(函数名本身即警告),只在 HDR/CSM/投影光开关等少数路径使用。
  • 材质代理缺失或初始化失败不会使材质加载失败,只会告警并少一个代理;专用服务器构建的代理工厂恒返回空。
  • 色彩校正在客户端的使用见 客户端视图渲染管线;材质系统内部的查找表(AddLookup/SetLookupWeight)实现、ps3gcm、togl、各 Shader 的 .fxc 组合与 Shader 编译未展开。
  • 纹理排除与异步下载的完成回调(FinishAsyncDownload)、CTextureManager 的引用计数与清理、光照贴图页打包算法(AllocateLightmap 内部)未展开;光照贴图分配的调用方见 引擎世界渲染。