材质系统与 Shader API
materialsystem 把“画什么”(材质、纹理、Shader 参数)与“怎么画”(具体图形 API)隔开:上层通过 IMaterialSystem/IMatRenderContext 发命令,下层的 Shader API DLL(shaderapidx9 或 shaderapiempty)实现设备操作。它还支持把一帧的渲染命令排进队列,由专门线程执行。本页还说明材质代理(proxy)如何由 client.dll 提供、渲染目标何时可以分配,以及这份快照中纹理“流式加载”的实际状态。
相关页面:渲染管线总览 · 客户端视图渲染管线 · 引擎世界渲染 · 客户端启动链 · 专用服务器
相关源码文件#
materialsystem/cmaterialsystem.cpp、materialsystem/cmaterialsystem.hmaterialsystem/cmatrendercontext.cpp、materialsystem/cmatqueuedrendercontext.cppmaterialsystem/shaderapidx9/shaderapidx8.cpp、materialsystem/shaderapidx9/shaderdevicedx8.cppmaterialsystem/shaderapiempty/shaderapiempty.cppmaterialsystem/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#
- 启动器在 Connect 之前调用
SetShaderAPI:默认shaderapidx9,-noshaderapi改为shaderapiempty,-shaderapi可指定任意 DLL:launcher/launcher.cpp:860-872。专用服务器总是shaderapiempty:dedicated/sys_windows.cpp:291-292。 SetShaderAPI只能调用一次;为空时默认shaderapidx9;随后CreateShaderAPI用Sys_LoadModule加载并取得工厂:materialsystem/cmaterialsystem.cpp:710-774。PS3/OSX 下该函数直接返回:materialsystem/cmaterialsystem.cpp:748-750。Connect要求文件系统存在,并从 Shader API 工厂获取设备管理器等接口:materialsystem/cmaterialsystem.cpp:780-800。
材质系统本身以 IMaterialSystem 单例暴露,并额外暴露硬件配置与 cvar 接口:materialsystem/cmaterialsystem.cpp:145、materialsystem/cmaterialsystem.cpp:180、materialsystem/cmaterialsystem.cpp:190。
| 实现 | 暴露的接口 | 证据 |
|---|---|---|
| D3D9 | CShaderAPIDx8 同时实现 IShaderAPI、IShaderDevice、IDebugTextureInfo;CShaderDeviceMgrDx8 实现 IShaderDeviceMgr | materialsystem/shaderapidx9/shaderapidx8.cpp:740、materialsystem/shaderapidx9/shaderapidx8.cpp:2123-2129、materialsystem/shaderapidx9/shaderdevicedx8.cpp:95 |
| 空实现 | CShaderDeviceEmpty、CShaderDeviceMgrEmpty、CShaderAPIEmpty | materialsystem/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 时告警并返回 NULL | materialsystem/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 流程末尾调用 FinishRenderTargetAllocation | engine/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内部)未展开;光照贴图分配的调用方见 引擎世界渲染。