CD Code Digest Hub /GTA V · v2

游戏侧流式与场景流式器

54 分钟阅读 13 个章节 2 张图

本页解释 GTAV 游戏层如何驱动 RAGE 流式引擎:CStreaming 包装与每帧更新、游戏对引擎的回调接口(CGtaStreamingInterface)、场景流式器(CSceneStreamerMgr:把可见性扫描结果变成按分数排序的请求,并在内存压力下按同一分数回收)、各种“额外搜索”与流式体积(CStreamVolumeMgr)、加载场景(CLoadScene/CSceneLoader)、玩家切换时的预加载(scene/playerswitch)、流式请求列表(SRL),以及 scene/volume 与 glassPaneSyncing 的现状。

背景与相关页面#

相关源码文件#

文件内容
game/streaming/streaming.h/.cppCStreaming、CGtaStreamingInterface
game/streaming/streamingcleanup.*内存不足时的回收、实体垃圾回收、刷新
game/streaming/BoxStreamer.*游戏提供给 fwBoxStreamer 的额外搜索
game/streaming/streamingrequestlist*.*流式请求列表(SRL)录制/回放
game/streaming/populationstreaming.*、PrioritizedClipSetStreamer.*、SituationalClipSetStreamer.*、ScriptPreload.*、zonedassetmanager.*、IslandHopper.*其他专用流式器
game/streaming/streamingdebug*.*、ResourceVisualizer.*调试与可视化
game/scene/streamer/*CSceneStreamerMgr、CSceneStreamer、CStreamingLists、CSceneLoader、CStreamVolume(Mgr)
game/scene/LoadScene.*、FocusEntity.*、ContinuityMgr.*、WarpManager.*新式加载场景、焦点实体、连续性/速度状态、传送
game/scene/world/GameScan.*设置驱动场景流式的 cull shape
game/scene/playerswitch/*玩家切换(长/中/短)与预流式
game/scene/volume/*通用体积(被编译期关闭)
game/glassPaneSyncing/*玻璃板网络同步(被编译期关闭)

1. CStreaming:包装与组合根#

1.1 初始化#

  • InitStreamingEngine:从 CGameConfig 读取虚拟/物理流式缓冲大小(旧主机以 KB、PC/新主机以 MB 为单位)与归档数量,创建 CGtaStreamingInterface 并 strStreamingEngine::InitClass/Init,注册 STRVIS 实体类型与 LOD 类型 src/dev_ng/game/streaming/streaming.cpp:295-349。
  • Init(INIT_CORE):分配流式信息数组,设置流式清单路径 platformcrc:/data/streaming,为 drawable/frag/dwd 存储安装游戏的 place/defragment 函子,初始化 g_SceneStreamerMgr 与清理器 src/dev_ng/game/streaming/streaming.cpp:398-442。
  • Init(INIT_SESSION):重新启用场景流式,CMapFileMgr::Reset → strStreamingEngine::InitLevelWithMapLoaded → (首次或要求重载时)InitPackfiles → SetMapFileDependencies → Cleanup,再初始化区域资产与岛屿切换 src/dev_ng/game/streaming/streaming.cpp:443-460、src/dev_ng/game/streaming/streaming.cpp:504-507。
  • 注册:INIT_CORE 在 CGame 中直接调用,会话模式通过 gameSkeleton 注册,注释写明流式必须最先初始化 src/dev_ng/game/Core/game.cpp:996、src/dev_ng/game/Core/game.cpp:2286。

CStreaming 的大部分 API 只是把 (local index, module) 转成 strIndex 再转发给 strStreamingEngine(如 RequestObject、LoadAllRequestedObjects);模板重载以 T::__Not_A_Valid_Function() 禁止误用强类型索引 src/dev_ng/game/streaming/streaming.cpp:941-1000、src/dev_ng/game/streaming/streaming.h:115-137。

1.2 引擎 → 游戏回调:CGtaStreamingInterface#

引擎不直接依赖游戏,而是通过 strStreamingInterface 虚函数回调 src/dev_ng/game/streaming/streaming.cpp:131-197:

回调游戏实现
LoadAllRequestedObjectsCStreaming::LoadAllRequestedObjects
RequestFlush、MakeSpaceForResourceFile、MakeSpaceForMap、WasLastAllocationAborted/FragmentedCStreamingCleanup(见第 6 节)
RecordRequestgStreamingRequestList.RecordRequest(!__FINAL)
PostLoadRpf(index)CStreaming::PostLoadRpf → 读 RPF _manifest(见地图页)
DumpStreamingInterestsg_SceneStreamerMgr.DumpStreamingInterests
AddResidentObject/RemoveResidentObjectgPopStreaming
InstallFailureQuitf(注释标为 HACK)

1.3 每帧 CStreaming::Update#

在 CGame 的 “Streaming” 更新组中,顺序为情境/优先 clip set 流式器 → CPopulationStreamingWrapper::Update → CStreaming::Update src/dev_ng/game/Core/game.cpp:2760-2767。CStreaming::Update 本身 src/dev_ng/game/streaming/streaming.cpp:589-816:

  1. CIslandHopper::Update;回放的 IPL/实体集延迟处理。
  2. 突发模式:淡出、暂停菜单渲染相位或非默认渲染函数时 strStreamingEngine::SetBurstMode(true) src/dev_ng/game/streaming/streaming.cpp:602。
  3. ms_cleanup.Update();碎片整理(USE_DEFRAGMENTATION)。此处还有一段与 PC 联机安全相关的分支,属于安全子系统,本页不展开 src/dev_ng/game/streaming/streaming.cpp:604-618。
  4. !__FINAL:Scroll Lock 键切换“暂停流式”,暂停时直接返回 src/dev_ng/game/streaming/streaming.cpp:624-644;流式被禁用时返回。
  5. gStreamingRequestList.ComputeSearchesAndFocus() → strStreamingEngine::PreLoadedUpdate() → GetLoader().ProcessAsyncFiles()(预算 1ms 的同步资源放置)→ 额外文本 src/dev_ng/game/streaming/streaming.cpp:651-661。
  6. 玩家已定位时:IMAP 流式(实例优先级、高速时关闭 HD IMAP、剔除盒、区域资产、托管 IMAP 组、RequestAboutPos)、元数据流式、CPhysics::UpdateStreaming、ThePaths.UpdateStreaming src/dev_ng/game/streaming/streaming.cpp:668-788(细节见地图页第 4 节)。
  7. gStreamingRequestList.Update();无未完成请求时重新允许 HD 车辆;strStreamingEngine::Update();更新性能计数 src/dev_ng/game/streaming/streaming.cpp:790-816。

“玩家已定位”标志 ms_isPlayerPositioned 同时门控了 CScene::Update 的大部分世界更新 src/dev_ng/game/streaming/streaming.h:176-177、src/dev_ng/game/scene/scene.cpp:791。

1.4 焦点与连续性#

  • CFocusEntityMgr:流式中心可以是默认玩家 ped、覆盖实体或覆盖位置;另有独立的“人口位置/速度”覆盖。几乎所有流式中心都取 CFocusEntityMgr::GetMgr().GetPos() src/dev_ng/game/scene/FocusEntity.h:25-80。
  • CContinuityMgr:每帧汇总玩家运动状态——中速(≥28)/高速(≥40,单位推断为 m/s)、飞行/驾驶列车/地面车辆/跳伞、在高度图之上、第一人称/狙击、向后看、场景流式是否高压 src/dev_ng/game/scene/ContinuityMgr.h:66-93。它在 CGameScan::SetupScan 中于 LOD 缩放更新之后更新 src/dev_ng/game/scene/world/GameScan.cpp:138-139,被 IMAP、LOD、场景流式、实例优先级广泛使用。

2. 场景流式:从可见性到请求#

2.1 总体数据流#

证据:更新组注册 src/dev_ng/game/Core/game.cpp:2974、src/dev_ng/game/Core/game.cpp:3006;PreScanUpdate src/dev_ng/game/scene/streamer/SceneStreamerMgr.cpp:186-205;KickVisibilityJobs src/dev_ng/game/scene/world/GameScan.cpp:614-633;SetupScan 开头 src/dev_ng/game/scene/world/GameScan.cpp:130-154;PostScan 调用 Process 与体积加载状态 src/dev_ng/game/renderer/PostScan.cpp:657-673;Process src/dev_ng/game/scene/streamer/SceneStreamerMgr.cpp:228-247。

关键设计:场景流式复用可见性系统。除了主相机,CGameScan 还添加若干“只用于流式”的 cull shape(CULL_SHAPE_FLAG_ONLY_STREAM_ENTITIES),它们的可见实体也进入 PVS(标记 VIS_FLAG_ONLY_STREAM_ENTITY),这样 HD/LOD/内景/流式体积的需求可以用同一套空间遍历与 LOD 范围逻辑计算。可见性的 5 个流式位:HD、LOD、流式体积 1/2、内景 src/dev_ng/game/scene/world/GameScan.cpp:560-575。

2.2 流式 cull shape#

FULL_SPHERICAL_SCENE_STREAMING 在 PC/XB1/PS4 上为 1(新世代单一球形流式器),否则使用旧世代的多个 cull shape(HD、LOD、速度矢量)src/dev_ng/game/scene/world/GameScan.h:4、src/dev_ng/game/scene/world/GameScan.h:35-49。新世代流程 src/dev_ng/game/scene/world/GameScan.cpp:643-699:

  • 内景球:以焦点为中心、玩家在内景时半径 20m(否则 12m);从玩家所在房间节点出发、可穿越门户(含禁用门户)、只启用 HD/ORPHANHD;SRL 回放有内景提示时改用提示位置、半径 40m src/dev_ng/game/scene/world/GameScan.cpp:1127-1178、src/dev_ng/game/scene/streamer/SceneStreamerMgr.cpp:619-633。
  • 外景球:仅当没有加载 IMAP 的流式体积且没有阻塞加载场景时(或长距离玩家切换下降时)添加;半径 ≤100m 时放弃(注释:玩家可能在快速移动);从世界根出发、只流式外景、使用外景 LOD 范围;启用 LOD/SLOD1..4,非高速且非“仅低细节”时再启用 HD/ORPHANHD src/dev_ng/game/scene/world/GameScan.cpp:660-681、src/dev_ng/game/scene/world/GameScan.cpp:706-755。
  • 流式体积:主/次两个槽各一个 cull shape(见第 3 节)src/dev_ng/game/scene/world/GameScan.cpp:685-698。

球形流式中心与半径由 UpdateSphericalStreamerPosAndRadius 设定:中心为焦点(脚本可强制跟随相机一帧);LOD 半径 = 相机远裁剪 + 1000m(注释称之为 magdemo 的权宜系数),SRL 即将结束时为 6000m,SHRINK_LOD_STREAMER_AT_SPEED 时高速下每帧缩小 75m src/dev_ng/game/scene/streamer/SceneStreamerMgr.cpp:682-707。默认参数:HD 半径 80m/短 20m、内景 20m/短 12m、LOD 4000m src/dev_ng/game/scene/streamer/SceneStreamerMgr.cpp:76-80;玩家在内景且 10m 内没有外部门户时 HD 半径用短值 src/dev_ng/game/scene/streamer/SceneStreamerMgr.cpp:635-680。

2.3 桶与分数(CStreamingLists)#

每个候选实体得到一个 float 分数:整数部分是桶编号,小数部分是桶内重要度;数组排序后从高分到低分即“从最重要到最不重要”src/dev_ng/game/scene/streamer/SceneStreamerList.h:79-117。桶(从低到高)src/dev_ng/game/scene/streamer/SceneStreamerBase.h:28-43:

桶含义(源码注释为“删除优先级”视角)
ACTIVE_OUTSIDE_PVS在活动列表但不在 PVS
INVISIBLE_FAR不在视锥且远
INVISIBLE_EXTERIOR在内景时的外景物体
VEHICLESANDPEDS_CUTSCENE过场中的车/人
INVISIBLE_OTHERROOMS当前内景的其他房间
INVISIBLE不在视锥
VISIBLE_FAR在视锥但远到看不清
VEHICLESANDPEDS车/人
VISIBLE可见
ACTIVE_PED活动 ped

IdentifyBucket 的规则:距离因子 min(1/dist, 0.95) 作为小数;在内景时,不可见的外景实体进 INVISIBLE_EXTERIOR、其他房间进 INVISIBLE_OTHERROOMS;附近内景外壳视为 VISIBLE;驾驶内景模式下其他内景/房间降为 VISIBLE_FAR;不可见实体一般为 INVISIBLE_FAR,但中速驾驶且向后/侧看时,可见范围内的 HD 提到 VISIBLE_FAR、LOD/SLOD1 提到 VISIBLE;驾驶 LOD 模式按 LOD 类型调整小数;远处实体为 VISIBLE_FAR,其余 VISIBLE src/dev_ng/game/scene/streamer/SceneStreamerList.h:481-571。车辆与 ped 不走此路径(断言),由人口流式管理。

CStreamingLists 管理两张列表:active(已有 draw handler 的实体,链表 + 桶集合,用于回收)与 needed(PVS 选中但还没有 draw handler 的实体,用于请求)src/dev_ng/game/scene/streamer/SceneStreamerList.h:221-365。ProcessPvsEntry:有 draw handler 且在 active 列表的实体刷新“最后可见帧”与分数;没有 draw handler 但模型已加载的,直接创建 drawable(创建后自动进入 active);否则加入 needed src/dev_ng/game/scene/streamer/SceneStreamerList.h:367-405。排序在作业中完成(sortbucketsspu,批大小 BUCKET_ENTRY_BATCH_SIZE=1024)src/dev_ng/game/scene/streamer/SceneStreamerBase.h:95-110。BucketEntry 以“类型 << 24 | 池槽位”的实体 ID 校验指针仍有效(车辆/对象非 fwBasePool 时特殊处理)src/dev_ng/game/scene/streamer/SceneStreamerBase.h:179-200。

GenerateLists 的额外来源:CreateDrawablesForEntities(PVS)、AddFromMapGroupSwap(IMAP 组交换进行中)、AddFromStreamingVolume(有活动体积)、AddFromRequiredInteriors(玩家所在内景),最后 PostGenerateLists src/dev_ng/game/scene/streamer/SceneStreamerMgr.cpp:340-364。__BANK 下驾驶速度平方 ≥150 时禁止 box streamer 阻塞,以暴露碰撞加载过慢问题 src/dev_ng/game/scene/streamer/SceneStreamerMgr.cpp:321-338。

2.4 发出请求与“预算”(CSceneStreamer::IssueRequests)#

场景流式的“预算”不是字节数,而是每帧请求数量与分数截断:

  • 只考察 needed 列表最高分的 SECONDARYLIST_MAX=200 项(从高分往低分)src/dev_ng/game/scene/streamer/SceneStreamer.cpp:84-86、src/dev_ng/game/scene/streamer/SceneStreamerBase.h:50-54。
  • 每设备上限 TotalRequestMax = {200, 8};ALL_MAP_DATA_ON_HARDDRIVE=1 时所有地图数据按硬盘设备计 src/dev_ng/game/scene/streamer/SceneStreamer.cpp:44-59、src/dev_ng/game/scene/streamer/SceneStreamer.cpp:183-221。
  • 优先级:分数 ≥ 桶截断(m_BucketCutoff,初始化为 STREAMBUCKET_VEHICLESANDPEDS)为优先请求;非优先请求在优先请求数超过 PriorityCountMin 后被节流,≤LowPriorityCutoff(VEHICLESANDPEDS_CUTSCENE)的低优先请求在非低请求数超过阈值后被节流;突发模式不节流 src/dev_ng/game/scene/streamer/SceneStreamerMgr.cpp:164、src/dev_ng/game/scene/streamer/SceneStreamer.cpp:224-242。
  • 主截断(master cutoff):清理器在“churn 保护”触发、放弃为某次分配腾内存时,删除该候选的 drawable 并以其分数调用 MarkCutoffEvent,把主截断抬高到该分数 src/dev_ng/game/streaming/streamingcleanup.cpp:1008-1024、src/dev_ng/game/scene/streamer/SceneStreamerList.h:313-315;低于截断分数的需求不再请求,除非本帧没有任何请求,此时把截断下移到当前分数,让资源慢慢流入 src/dev_ng/game/scene/streamer/SceneStreamer.cpp:113-167。这避免“请求 A 时删 B、请求 B 时删 A”的内存抖动。
  • 玩家长距离切换需要固定资产或加载场景在淡出期间运行时,请求带 STRFLAG_LOADSCENE src/dev_ng/game/scene/streamer/SceneStreamer.cpp:97-105。
  • 请求后若模型立刻已加载(虚拟请求被满足),就地 CreateDrawable 且不计入请求数;RequestAssets 失败则 FlagModelAsMissing(注释:否则可能永远挂在加载画面)src/dev_ng/game/scene/streamer/SceneStreamer.cpp:250-313。

随后 IssueRequestsForLists 还遍历整个 needed 列表,对“所有依赖已加载但模型本身未加载”的条目强制装载虚拟请求并立即创建 drawable(instant fulfill)src/dev_ng/game/scene/streamer/SceneStreamerMgr.cpp:499-565。CacheNeededScore 记录 needed 列表最高分,供其他系统判断流式压力 src/dev_ng/game/scene/streamer/SceneStreamerMgr.cpp:211-222。

3. 流式体积(CStreamVolume/CStreamVolumeMgr)#

流式体积是“钉住并请求某一片地图”的接口,有球、视锥、线段三种形态;记录需要的 box streamer 资产类型(IMAP、静态包围盒等)、是否同时启用场景流式、所需 IMAP/静态包围盒列表、LOD 掩码、可选的相机矩阵、脚本所有者 src/dev_ng/game/scene/streamer/StreamVolume.h:56-161。只有两个槽:VOLUME_SLOT_PRIMARY(脚本、加载场景)与 VOLUME_SLOT_SECONDARY(仅玩家切换)src/dev_ng/game/scene/streamer/StreamVolume.h:164-191。体积通过两条路径生效:向 box streamer 添加搜索(AddSearches),以及作为流式 cull shape 让可见性系统挑出实体(AddFromStreamingVolume)。HasLoaded 由 PostScan 后的 UpdateVolumesLoadedState 计算 src/dev_ng/game/renderer/PostScan.cpp:669-670。

3.1 游戏额外的 box streamer 搜索#

CGTABoxStreamerInterfaceNew::GetGameSpecificSearches 为框架 box streamer 追加搜索 src/dev_ng/game/streaming/BoxStreamer.cpp:61-207:

  • 需要碰撞的脚本实体(排除报废车、死亡 ped、不需要碰撞或被固定的对象),各加 10m 球的 mover 静态包围盒搜索;MP 中高速可见飞机启用扩展搜索;最近驾驶过的飞机 src/dev_ng/game/streaming/BoxStreamer.cpp:67-125;
  • 流式体积、长距离玩家切换、SRL、网络(NetworkInterface::AddBoxStreamerSearches)src/dev_ng/game/streaming/BoxStreamer.cpp:133-143;
  • PC:按图形设置(m_MaxLodScale>0 时中等 IMAP 半径 500m)以焦点为中心“膨胀”低/中/高 IMAP 搜索,减少 PC 高画质下的 LOD 跳变 src/dev_ng/game/streaming/BoxStreamer.cpp:145-206。

4. 加载场景#

4.1 新式 CLoadScene(非阻塞,基于流式体积)#

g_LoadScene.Start(pos, dir, range, useFrustum, flags, owner)(所有者:默认/脚本/玩家切换/传送管理器/调试)启动后 src/dev_ng/game/scene/LoadScene.h:26-43、src/dev_ng/game/scene/LoadScene.cpp:46-103:

证据:状态枚举与更新分派 src/dev_ng/game/scene/LoadScene.h:102-116、src/dev_ng/game/scene/LoadScene.cpp:139-158;探测/填充 src/dev_ng/game/scene/LoadScene.cpp:211-260;停止 src/dev_ng/game/scene/LoadScene.cpp:177-205。

外景流式注册主槽体积:资产默认 MASK_MAPDATA(FLAG_INCLUDE_COLLISION 加 mover 包围盒),视锥或球形;FLAG_LONGSWITCH_CUTSCENE 时只要 HD/ORPHANHD src/dev_ng/game/scene/LoadScene.cpp:268-308。内景流式只有在 FLAG_INTERIOR_AND_EXTERIOR 时才包含 IMAP src/dev_ng/game/scene/LoadScene.cpp:341-381。IsLoaded() 即主槽体积 HasLoaded src/dev_ng/game/scene/LoadScene.cpp:164-171。g_LoadScene.Update() 在 CScene::Update 中、CGameWorld::Process 之前调用 src/dev_ng/game/scene/scene.cpp:800-801。

4.2 旧式 CSceneLoader(阻塞)#

CSceneStreamerMgr::LoadScene(pos) → CSceneLoader::LoadScene:先停止新式加载场景,然后分 4 个阶段,每阶段之间 CStreaming::LoadAllRequestedObjects() 阻塞 src/dev_ng/game/scene/streamer/SceneLoader.cpp:105-144:

  1. 冲刷被抑制的 dummy、刷新渲染线程、关闭植物管理器、清空请求列表、删除所有 drawable 引用、更新剔除盒、重置区域资产、RequestAboutPos IMAP 并移除不需要的 IMAP/ITYP;
  2. 全量 ManageObjectPopulation(true);在 600m 内请求 LOD、80m 内请求 HD(建筑/动画建筑/对象/dummy/MLO;跳过 DONT_STREAM 和当前小时不显示的时间模型;MLO 调用 RequestObjects);
  3. 路径服务器处理该位置;
  4. 清除 STRFLAG_LOADSCENE、植物预更新、CPhysics::LoadAboutPosition 确保碰撞 src/dev_ng/game/scene/streamer/SceneLoader.cpp:150-236、src/dev_ng/game/scene/streamer/SceneLoader.cpp:53-54、src/dev_ng/game/scene/streamer/SceneLoader.cpp:60-98。

它也有非阻塞版本 StartLoadScene/UpdateLoadScene(带异步超时,注释称部分 MP 脚本误以为异步加载一定完成)src/dev_ng/game/scene/streamer/SceneLoader.h:38-66。

4.3 传送(CWarpManager)#

CWarpManager::SetWarp(pos, vel, heading, teleportVehicle, snapToGround, radius) 以状态机 FADE_OUT → START_WARP → DO_WARP → END_WARP → FADE_IN 执行传送,可选不加载场景或加载 HD 资产,最长等待 MAX_WAIT_TIME_MS=15000;在 CScene::Update 之后的更新组中运行 src/dev_ng/game/scene/WarpManager.h:11-83、src/dev_ng/game/Core/game.cpp:2830。

5. 玩家切换时的场景预加载(scene/playerswitch)#

CPlayerSwitchInterface(全局 g_PlayerSwitch)提供 Start(type, oldPed, newPed, controlFlags),类型为 AUTO/LONG/MEDIUM/SHORT;控制标志可跳过 intro/outro/pan、暂停于各阶段、仅下降等 src/dev_ng/game/scene/playerswitch/PlayerSwitchInterface.h:71-163。每种类型的参数(范围、平移速度、天花板高度、天花板 LOD 级别、跳切间高度、跳切次数上下限等)来自 common:/data/playerswitch.meta src/dev_ng/game/scene/playerswitch/PlayerSwitchInterface.h:19-69、src/dev_ng/game/scene/playerswitch/PlayerSwitchInterface.cpp:297-302(在 PostProcessIplLoad 中装载 src/dev_ng/game/scene/FileLoader.cpp:2202)。

类型选择:XY 距离 ≥ 中距离范围,或起点/终点高度 ≥ 中距离天花板 → LONG;否则 3D 距离 ≤ 短距离范围 → SHORT;其余 MEDIUM src/dev_ng/game/scene/playerswitch/PlayerSwitchInterface.cpp:117-140。SHORT 由 CPlayerSwitchMgrShort(INTRO/OUTRO,旋转/平移/缩放等风格)处理,其余由 CPlayerSwitchMgrLong 处理 src/dev_ng/game/scene/playerswitch/PlayerSwitchInterface.h:123-142、src/dev_ng/game/scene/playerswitch/PlayerSwitchMgrShort.h:16-70。

长距离切换状态机:INTRO → (PREP_DESCENT) → PREP_FOR_CUT → JUMPCUT_ASCENT ×N → (WAITFORINPUT_*) → PAN → JUMPCUT_DESCENT ×N → OUTRO_HOLD → OUTRO_SWOOP → (ESTABLISHING_SHOT) → FINISHED src/dev_ng/game/scene/playerswitch/PlayerSwitchMgrLong.h:107-124。每个“跳切”都是一次闪白,掩盖下一镜头的预流式。CPlayerSwitchMgrLong_Streamer 为“下一个状态”启动流式,超时 4000/7000/9000/10000ms src/dev_ng/game/scene/playerswitch/PlayerSwitchMgrLong.h:65-100:

  • 上升跳切:计算该跳切的相机帧,以相机位置/朝向/远裁剪启动视锥加载场景(所有者 PLAYERSWITCH)并设置相机矩阵;若是最后一次上升(天花板),用 g_LodMgr.GetMapDataAssetMaskForLodLevel(ceilingLodLevel) 只请求天花板 LOD 级别对应的 IMAP 类型与 LOD 掩码;跳过内景检查 src/dev_ng/game/scene/playerswitch/PlayerSwitchMgrLong.cpp:134-189。
  • 平移:在上升天花板与下降天花板帧之间为低 LOD 场景建立流式 src/dev_ng/game/scene/playerswitch/PlayerSwitchMgrLong.cpp:191-204。
  • 固定资产:下降、准备下降或二级流式期间 ShouldPinAssets() 为真,场景流式请求加 STRFLAG_LOADSCENE src/dev_ng/game/scene/playerswitch/PlayerSwitchMgrLong.h:180-183。
  • 下降时外景球形 cull shape 以“仅低细节”方式仍然启用 src/dev_ng/game/scene/world/GameScan.cpp:674-680;切换管理器还可修改 LOD 缩放(ModifyLodScale)src/dev_ng/game/scene/playerswitch/PlayerSwitchInterface.cpp:352-362。

g_PlayerSwitch.Update() 在 CScene::Update 中(CGameWorld::Process 之后)运行 src/dev_ng/game/scene/scene.cpp:828-829。PlayerSwitchEstablishingShot 为可选的定场镜头元数据,PlayerSwitchDebug 为调试。

6. 内存压力下的回收(CStreamingCleanup)#

  • 为新资源腾空间 MakeSpaceForMap(map, index, keepMemory):计算资源映射的虚拟/物理大小(ONE_STREAMING_HEAP 时合并为物理),循环检查 HasSpaceFor 并尝试 pgRscBuilder::AllocateMap;空间不足或碎片化时删除最低分实体及其依赖;只能在更新线程为另一个文件释放内存 src/dev_ng/game/streaming/streamingcleanup.cpp:1568-1640。删除候选的选择沿用场景流式的桶分数(FindLowestScoringEntityToDelete)src/dev_ng/game/streaming/streamingcleanup.cpp:1042。“churn 保护”记录最近 3 帧删除的 draw handler(每帧最多 128)以避免反复删建 src/dev_ng/game/streaming/streamingcleanup.h:161-174。
  • 实体垃圾回收 RemoveExcessEntities(在 PreScanUpdate 中调用):active 列表超过阈值、流式内存使用超过百分比阈值(触发时还会删除最久未用的流式对象与 packfile)、已加载信息池接近上限(MaxLoadedInfo-100)或实体数达到硬上限时,按分数截断删除未使用实体;新主机/PC 跳过按实体数与车速触发的常规 GC src/dev_ng/game/streaming/streamingcleanup.cpp:1311-1382。
  • 刷新:RequestFlush/RequestFlushNonMission 在下一次 Update 中执行全面清理 src/dev_ng/game/streaming/streamingcleanup.h:57-62;PC 另有 HandleOutOfMemory src/dev_ng/game/streaming/streamingcleanup.h:66-70。

7. 流式请求列表(SRL)#

SRL 用于过场等可预知的镜头:录制时每帧记录“新增/移除/提升为 HD”的模型列表、相机位置/方向与公共集合,回放时按时间预先请求;数据由 parser 序列化(CStreamingRequestRecord{m_Frames[], m_CommonSets[], m_NewStyle}),所有已知 SRL 由主列表文件列出 src/dev_ng/game/streaming/streamingrequestlist.h:230-290。CStreamingRequestList 负责录制/回放与心跳,并向 box streamer 添加搜索、计算焦点 src/dev_ng/game/streaming/streamingrequestlist.h:292-411。SRL 回放影响:LOD 根缩放被限制为 ≤1 src/dev_ng/game/scene/lod/LodScale.cpp:145-150、内景流式球可使用“内景剪辑提示”src/dev_ng/game/scene/world/GameScan.cpp:1136-1143、即将结束时 LOD 流式半径扩大到 6000m src/dev_ng/game/scene/streamer/SceneStreamerMgr.cpp:691-695。过场本身见 ../10-animation/cutscenes.md。

8. 其他专用流式器(概览)#

组件作用证据
CPopulationStreaming(gPopStreaming)ped/车辆模型集合的流入流出与预算;新主机/PC ASSET_MULTIPLIER=3,至少保持 3*k 个车辆模型、2*k 个 ped 模型;HD 车辆上限 64(旧主机 2)src/dev_ng/game/streaming/populationstreaming.h:19-36、src/dev_ng/game/streaming/populationstreaming.h:126-199
CPrioritizedClipSetStreamer、CSituationalClipSetStreamer按优先级/情境流式动画 clip setsrc/dev_ng/game/streaming/PrioritizedClipSetStreamer.h:492、src/dev_ng/game/streaming/SituationalClipSetStreamer.h:21
CScriptPreloadData脚本预加载 DWD/TXD/CLD/FRAG/drawable 并跟踪完成状态src/dev_ng/game/streaming/ScriptPreload.h:20-50
CZonedAssetManager、CIslandHopper区域常驻模型、岛屿切换(见地图页)src/dev_ng/game/streaming/zonedassetmanager.cpp:195-235
CStreamingDebug、streamingdebuggraph*、CResourceVisualizer调试 UI、内存图、资源可视化(!__FINAL/__BANK)见“相关源码文件”

9. scene/volume 与 glassPaneSyncing:被编译关闭的系统#

  • scene/volume:CVolume : fwExtensibleBase 定义盒/圆柱/球/椭球/聚合体积及编辑器,但 #define VOLUME_SUPPORT 0,整个类在 #if VOLUME_SUPPORT 内,即当前代码中不编译 src/dev_ng/game/scene/volume/Volume.h:9-83;管理器同样受此宏控制 src/dev_ng/game/scene/volume/VolumeManager.h:16-80。游戏侧装饰器接口只在 DECORATOR_DEBUG & VOLUME_SUPPORT 时引用它(见实体页)。
  • glassPaneSyncing:设计目标是在网络中同步玻璃板破碎(CGlassPane : netGameObjectBase,最多 MAX_PANES=13;管理器最多 50 个玻璃几何对象、每帧 20 个候选),但 GLASS_PANE_SYNCING_ACTIVE 被定义为 0,存储数组与池都在该宏内 src/dev_ng/game/glassPaneSyncing/GlassPaneInfo.h:4-10、src/dev_ng/game/glassPaneSyncing/GlassPane.h:17-56、src/dev_ng/game/glassPaneSyncing/GlassPaneManager.h:59-124。CScene::Update 仍每帧调用 CGlassPaneManager::Update,InitPools 也被调用 src/dev_ng/game/scene/scene.cpp:839、src/dev_ng/game/scene/scene.cpp:449;这表明这些调用在当前配置下基本为空操作(推断,未逐行核对 .cpp 中各函数在宏关闭时的实现)。网络对象模型见 ../17-network-sync/network-object-model.md。

约束与边界行为#

  • 线程:MakeSpaceForMap 只允许更新线程为其他文件腾内存(断言)src/dev_ng/game/streaming/streamingcleanup.cpp:1575-1582;CLodScale::GetGlobalScale 只允许更新线程 src/dev_ng/game/scene/lod/LodScale.h:25。场景流式的 Process 在 PostScan 中执行,依赖可见性作业已经完成排序(WaitForSorting)src/dev_ng/game/scene/streamer/SceneStreamerMgr.cpp:506-508。
  • 场景图修改时序:注释要求所有修改场景图的工作(灯光实体、程序化对象、HUD 触发的同步加载等)必须在 CGameScan::KickVisibilityJobs 之前完成,因为可见性作业启动后会锁住场景图 src/dev_ng/game/Core/game.cpp:2976-2989。
  • 流式体积只有两个槽:新的 CLoadScene 会先注销主槽体积;脚本与加载场景共享主槽 src/dev_ng/game/scene/LoadScene.cpp:273-274、src/dev_ng/game/scene/streamer/StreamVolume.h:168-173。
  • 缺失模型:请求失败的模型被标记为缺失,以免流式器无限重试 src/dev_ng/game/scene/streamer/SceneStreamer.cpp:301-313。

未决问题 / 证据不足#

  • CStreamingLists::PreGenerateLists/PostGenerateLists、AddFromStreamingVolume/AddFromRequiredInteriors 与排序作业 sortbucketsspu.cpp 的细节未逐行追踪;主截断何时被重置/下调(除 IssueRequests 中的下移外)未核对。
  • CStreamVolume::UpdateLoadedState、线段体积在玩家切换平移中的具体用法未展开。
  • 旧世代(!FULL_SPHERICAL_SCENE_STREAMING)的 HD/LOD/速度矢量 cull shape 只列出入口 src/dev_ng/game/scene/world/GameScan.cpp:765。
  • CDownloadableTextureManager(云端/用户贴图下载到 TXD 存储)与 SurrogateCloudFileProvider 位于 game/scene 根目录,但功能上偏在线服务,本页未覆盖。
  • 流式缓冲大小、MaxLoadedInfo 等容量来自运行时配置(CGameConfig/fwConfigManager),不在 src 树中。