
游戏开发桌面应用【免费下载链接】Terraria-Map-EditorTEdit - Terraria Map Editor - TEdit is a stand alone, open source map editor for Terraria. It lets you edit maps just like (almost) paint! It also lets you change world settings (time, bosses downed etc), edit chests and change sign, make epic dungeons, castles, cities, and add rewards for your adventurers!项目地址https://gitcode.com/gh_mirrors/te/Terraria-Map-Editor点击查看免费下载TEditTerraria Map Editor是一款独立的开源泰拉瑞亚地图编辑器。本文基于仓库中的性能优化计划文档 docs/todo/plan-brush-render-speed-improvements.md深入剖析大尺寸画笔Brush操作造成明显卡顿的根因并给出从“消除冗余 UV 缓存重置”到“异步小地图更新”再到“并行颜色计算”的五阶段改造方案。读完本文你将掌握 TEdit 画笔绘制的核心热路径、PixelMap 分块缓冲架构、UV 缓存延迟重置机制以及一套可直接指导同类瓦片编辑器优化工作的工程思路。问题背景一次笔划为何会让渲染循环“饿死”在 TEdit 中画笔尺寸可达 50×50 甚至更大。文档明确指出大型画笔操作会带来可见的卡顿——画笔光标出现“拖影/抖动”cursor stutters同时 XNA 渲染循环被“饿死”render loop starves。其根因在于笔划过程中的每个瓦片级操作undo 保存、像素修改、渲染更新、UV 缓存重置都在每个鼠标移动事件MouseMove里于 UI 线程上同步执行。笔划越粗、绘制路径越长UI 线程被占用的时间就越长渲染循环自然得不到调度。更新优先级用户定义优先级组件理由1关键PixelMap 更新绘制发生后的主要视觉反馈在所有缩放级别都可见2低UV 缓存 / RenderBlender仅在纹理缩放级别可见且该缩放级别下瓦片数量通常较小3从不关键小地图当前通过WriteableBitmap在 UI 线程渲染整张地图绝不应阻塞这条优先级排序是整个优化方案的核心指导思想优化顺序应严格与视觉重要程度对齐——先保证 PixelMap 这一“最快视觉反馈路径”再处理仅在特定缩放级别可见的次要开销。当前架构逐瓦片同步通知的笔划热路径文档给出了笔划热路径的调用链结合仓库源码可完整还原BrushTool.MouseMove → ProcessDraw → DrawLine → FillSolid FOR EACH pixel in brush area: 1. UndoManager.SaveTile(pixel) — lock dictionary insert 2. wvm.SetPixel(pixel.X, pixel.Y) — modify world tile → WorldEditor.SetPixel → _notifyTileChanged(x, y, 1, 1) ├→ UpdateRenderPixel(x, y) — GetTileColor PixelMap.SetPixelColor │ ├→ FilterOverlay.SetMask(x, y) │ └→ BuffTileCache.UpdateTile(x, y) └→ rb.UpdateTile(x, y, 1, 1) — ResetUVCache (3×3 neighborhood) 3. BlendRules.ResetUVCache(wvm, x, y, 1, 1) — SAME 3×3 reset AGAIN ← redundant注意第 3 步文档明确指出FillSolid在逐瓦片调用BlendRules.ResetUVCache(wvm, x, y, 1, 1)的同时_notifyTileChanged委托内也调用了rb.UpdateTile(x, y, 1, 1)——而RenderBlender.UpdateTile内部做的正是同一个ResetUVCache见 src/TEdit/Render/BlendRules.cs。也就是说每个瓦片的 UV 缓存被重置了两次这是一处明显的冗余开销。通知委托按瓦片触发而非按笔划触发文档引用的WorldViewModel.cs中构造的通知委托在仓库 src/TEdit/ViewModel/WorldViewModel.cs 中完整存在NotifyTileChanged updateTiles (x, y, width, height) { UpdateRenderPixel(x, y); // PixelMap FilterOverlay BuffTileCache rb.UpdateTile(x, y, width, height); // ResetUVCache };该委托被注入WorldEditor、UndoManager、ClipboardManager并在 src/TEdit.Editor/WorldEditor.cs 中定义。它对WorldEditor.SetPixel的每一次_notifyTileChanged调用都会触发——按瓦片、不按笔划。以 50×50 画笔计算单次笔划就有 2,500 次委托调用、2,500 次逐瓦片 PixelMap 写入、2,500 次实际是 5,000 次UV 缓存重置。PixelMap 分块缓冲为异步更新而设计文档描述的 PixelMap 分块架构在 src/TEdit/Render/PixelMapManager.cs 中完整对应PixelMapManager ├── ColorBuffers[chunkIndex][pixelIndex] — Color[] per chunk ├── BufferUpdated[chunkIndex] — dirty flag per chunk ├── TileWidth/TileHeight — chunk size (100-256, divides world evenly) └── SetPixelColor(x, y, color) — writes single pixel, marks chunk dirtyInitializeBufferssrc/TEdit/Render/PixelMapManager.cs从MaxTextureSize 256向下寻找能整除世界宽/高的分块尺寸下限 100SetPixelColorsrc/TEdit/Render/PixelMapManager.cs直接写入ColorBuffers[tileIndex][pixelIndex]并置脏标记。关键结论与文档一致SetPixelColor无锁直接写Color[]数组并置一个布尔脏标记。XNA 渲染循环在下一帧通过Texture2D.SetData()拾取脏块——见 src/TEdit/View/WorldRenderXna.xaml.csif (_wvm.PixelMap.BufferUpdated[i] || init) { _tileMap[i].SetData(_wvm.PixelMap.ColorBuffers[i]); _wvm.PixelMap.BufferUpdated[i] false; }渲染循环按“缩放级别”分流缩小到像素图级别时DrawPixelTiles()绘制块纹理放大到纹理级别时绘制瓦片背景与瓦片纹理文档中的DrawTileBackgrounds()调用见 src/TEdit/View/WorldRenderXna.xaml.cs。这正说明 PixelMap 架构天生支持异步更新——只是当前画笔路径并未利用这一点。五阶段优化方案优化计划按“见效快慢 风险高低”编排为五个阶段。以下每阶段均对照仓库现状说明实施要点。Phase 1消除冗余 UV 缓存重置工作量低 | 收益UV 缓存工作减半约 2×文档指出BrushTool.FillSolid在第 796 行附近逐瓦片调用BlendRules.ResetUVCache(wvm, x, y, 1, 1)而通知路径中的rb.UpdateTile(x, y, 1, 1)已做同样的重置二者重复。一个值得注意的佐证在 src/TEdit.Editor/WorldEditor.cs 的FillHollow中该行BlendRules.ResetUVCache(_wvm, pixel.X, pixel.Y, 1, 1)已被注释掉证明仓库维护者已经认识到这一点并开始清理。文档建议把BrushTool.cs中FillSolid、FillHollowCached、FillHollow、CommitCadPath、DrawSingleTile对应 src/TEdit/Editor/Tools/BrushTool.cs 中约 765/817/917/501/536 行的逻辑里的ResetUVCache全部移除统一交给通知路径处理。Phase 2笔划批量更新本次优化的最大单项收益工作量中 | 收益显著——消除逐瓦片通知开销核心思路笔划填充循环期间不逐瓦片通知循环结束后一次性批量更新脏区域。Step 2a给WorldEditor.SetPixel增加notify参数默认trueBrushTool 传入false。文档同时提示_notifyTileChanged在SetPixel的多个 switch 分支SetTrack、SetPlatform 等中都有调用仓库中可见于 [src/TEdit.Editor/WorldEditor.cs](https://link.gitcode.com/i/4b63eb67014ec4569da14163a240027e#L573、L647、L693、L699、L729 等因此用字段开关_suppressNotify比逐参数传递更干净——仓库后续实际实现正是这样做的下文“仓库落地现状”会展开。Step 2bFillSolid收集脏区域边界minX/minY/maxX/maxY循环结束后调用_wvm.UpdateRenderRegionSync(minX, minY, w, h)仅更新 PixelMap与_wvm.QueueUVCacheReset(minX, minY, w, h)延迟 UV 重置。Step 2c新增UpdateRenderRegionSync对矩形区域做 PixelMap 颜色重算并配合BuffTileCache.UpdateRegion做缓冲瓦片缓存区域更新。优化细节GetBackgroundColor(y)按行计算一次而非每个瓦片计算一次——把背景色计算从 2,500 次降到 50 次每行一次。Phase 3延迟 UV 缓存重置工作量低 | 收益把 UV 工作从笔划路径完全移除UV 缓存uvTileCache、uvWallCache、lazyMergeId只影响纹理缩放渲染在像素图缩放级别完全不可见因此不值得在笔划热路径上同步处理。Option A推荐更安全显式WorldRenderXna维护一个待重置的脏矩形队列_pendingUVReset在渲染循环开始绘制纹理前合并处理private RectangleInt32? _pendingUVReset; public void QueueUVCacheReset(int x, int y, int w, int h) { if (_pendingUVReset null) _pendingUVReset new RectangleInt32(x, y, w, h); else _pendingUVReset _pendingUVReset.Value.Union(x, y, w, h); } // In render loop, before drawing textures: if (_pendingUVReset ! null AreTexturesVisible()) { BlendRules.ResetUVCache(_world, _tilePicker, ...); _pendingUVReset null; }Option B完全跳过笔划期间的 UV 重置。UV 缓存有哨兵值0xFFFF仓库中 src/TEdit/Render/BlendRules.cs 的重置实现正是把uvTileCache 0xFFFF; lazyMergeId 0xFF; hasLazyChecked false; uvWallCache 0xFFFF纹理渲染器遇到哨兵会按需重算——即SetPixelAutomatic修改瓦片时直接把uvTileCache 0xFFFF让渲染循环自动“自愈”。文档提示 Option B需要先审计渲染循环能否正确处理哨兵值风险更高。Phase 4异步小地图更新工作量低 | 收益把小地图卡顿移出 UI 线程将小地图渲染移到后台线程并做合并coalesce用一个 500ms 定时器 脏标记渲染周期内多次修改只触发一次全量重绘private Timer _minimapTimer; private bool _minimapDirty; // Replace direct UpdateMinimap() calls with: public void InvalidateMinimap() { _minimapDirty true; } // Timer callback (e.g., every 500ms): private void MinimapTimerTick(object state) { if (!_minimapDirty) return; _minimapDirty false; // Render to byte[] on background thread var pixels RenderMiniMap.RenderToBuffer(CurrentWorld, ...); // Blit to WriteableBitmap on UI thread Dispatcher.BeginInvoke(() { MinimapImage.Lock(); Marshal.Copy(pixels, 0, MinimapImage.BackBuffer, pixels.Length); MinimapImage.AddDirtyRect(new Int32Rect(0, 0, MinimapImage.PixelWidth, MinimapImage.PixelHeight)); MinimapImage.Unlock(); }); }文档同时指出UpdateMinimap原由_undoAppliedSaveUndo内直接调用会迭代小地图位图的每个像素并对WriteableBitmap执行 Lock/Unlock——这正是把它移出 UI 线程的动机。Phase 5PixelMap 颜色计算的并行化未来阶段工作量高 | 收益对大规模操作显著PixelMap.SetPixelColor写入ColorBuffers[chunkIndex][pixelIndex]每个像素是一次独立的数组写GetTileColor只读World.Tiles[x, y]更新期间不可变。因此脏区域的颜色计算可按行Parallel.ForParallel.For(y, y height, ty { Color bgColor new Color(GetBackgroundColor(ty).PackedValue); for (int tx x; tx x width; tx) { PixelMap.SetPixelColor(tx, ty, PixelMap.GetTileColor(CurrentWorld.Tiles[tx, ty], bgColor, ...)); } });注意事项文档明确列出BufferUpdated[chunkIndex]是按块chunk的脏标记——同一块内不同行的并发写入可能在该标记上竞争。由于它只是被置true这种竞争是良性的不存在 true→false→true 竞态但建议用Volatile.Write保证可见性前置条件并行计算期间世界瓦片不得被修改。Undo/Redo 场景下安全修改先于渲染更新发生画笔场景下若在笔划循环仍在运行时计算颜色则需要特别小心。各阶段工作量削减汇总以 50×50 画笔单次笔划为例阶段每笔划消除的工作量Phase 12,500 × ResetUVCache(3×3) 22,500 次缓存写入Phase 22,500 次独立通知调用 → 1 次批量调用GetBackgroundColor2,500 → 50按行Phase 3剩余 UV 缓存工作延迟到下一个纹理渲染帧或在像素图缩放级别完全跳过Phase 4整个小地图重渲染移出 UI 线程Phase 5PixelMap 颜色计算跨 CPU 核并行化实施优先级顺序Phase 1—— 微不足道的修复立即获得 2× UV 提升Phase 2—— 最大的单项改进批量更新Phase 4—— 轻松获胜小地图移出 UI 线程Phase 3—— 延迟 UV 缓存移除剩余逐瓦片开销Phase 5—— 仅在剖析profiling证明 PixelMap 颜色计算成为瓶颈后再做。Undo/Redo 渲染路径独立于画笔Undo/Redo 路径src/TEdit/ViewModel/WorldViewModel.Commands.cs已经是部分批量的var changedTiles UndoManager?.Undo(); if (changedTiles ! null) { UpdateRenderPixels(changedTiles); // per-tile loop but single call _renderBlender?.UpdateTiles(changedTiles); // batched ResetUVCache }其中_renderBlender.UpdateTiles(tiles)走的是BlendRules.ResetUVCache(world, tiles)的批量重载src/TEdit/Render/BlendRules.cs对每个瓦片连同 3×3 邻域一次性重置而不是逐瓦片调用。该路径可受益于Phase 4—— 小地图改为定时器驱动不再由_undoApplied直接调用Phase 5——UpdateRenderPixels可使用Parallel.For因为瓦片此时已提交Phase 2b/2c—— 复用UpdateRenderRegionSync做包围矩形优化。仓库落地现状文档方案已在源码中实现值得注意的是这份计划文档所描述的核心机制已在当前仓库中落地可作为“改造后目标形态”的参照批量填充 抑制逐瓦片通知src/TEdit/Editor/Tools/BrushTool.cs 的FillSolid在填充循环前设置_wvm.WorldEditor.SuppressNotify true循环结束后在finally中恢复并在anyModified时计算脏区域包围盒后调用_wvm.QueueUVCacheReset(minX, minY, w, h)——与文档 Step 2a/2b 完全对应用字段开关而非参数线程化。批量区域渲染更新src/TEdit/ViewModel/WorldViewModel.Editor.cs 的UpdateRenderRegionSync实现了“按行一次GetBackgroundColor”的优化并批量更新PixelMap.SetPixelColor、FilterOverlay、BuffTileCache。延迟 UV 重置的队列与合并src/TEdit/ViewModel/WorldViewModel.Editor.cs 的QueueUVCacheReset用_pendingUVReset元组维护待重置矩形并做包围盒合并FlushPendingUVCacheReset在渲染循环调用src/TEdit/View/WorldRenderXna.xaml.cs——正是文档 Phase 3 Option A 的实现。异步小地图合并更新src/TEdit/ViewModel/WorldViewModel.cs 的_minimapDirty脏标记 System.Threading.Timer(MinimapTimerTick, null, 500, 500)定时器500ms 周期MinimapTimerTick在 UI 线程通过Dispatcher.BeginInvoke调用RenderMiniMap.UpdateMinimap——即文档 Phase 4 的合并且基于现有RenderMiniMapAPI 的实现比文档草案更进一步直接在 UI 线程做更新但移除了逐笔划调用。并行化的存在痕迹仓库中Parallel.ForAsync出现在 Skia 渲染端 src/TEdit5/Controls/SkiaWorldRenderBox.cs说明并行化思路已在新一代渲染引擎中尝试但 XNA 端Phase 5仍属于文档规划的未来阶段。受影响文件清单文件涉及阶段src/TEdit/Editor/Tools/BrushTool.cs1、2src/TEdit.Editor/WorldEditor.cs2src/TEdit/ViewModel/WorldViewModel.cs2、4src/TEdit/ViewModel/WorldViewModel.Editor.cs2、5src/TEdit/View/WorldRenderXna.xaml.cs3src/TEdit/Render/BlendRules.cs1、3src/TEdit/Render/RenderMiniMap.cs4src/TEdit/Render/PixelMapManager.cs5成功验收标准改造完成后应以以下标准验收文档原文50×50 画笔尺寸下画笔光标跟踪鼠标位置无可感知卡顿PixelMap 更新仍是绘制发生后的最快视觉反馈路径UV 缓存重置变为惰性执行仅在纹理缩放激活时发生小地图永不阻塞 UI 线程笔划后无视觉伪影或陈旧渲染。总结这份优化计划的本质是把“笔划期间的每瓦片同步全链路更新”重构为批量、延迟、异步、并行四类策略的组合用批量填充 抑制通知消除 2,500 次委托调用用延迟 UV 重置把纹理级开销移出热路径用定时合并的小地图更新把整图重绘移出 UI 线程再用并行颜色计算压榨多核性能。其更新优先级排序PixelMap UV 缓存 小地图对任何瓦片编辑器/地图编辑器的渲染性能优化都具有直接借鉴价值而当前仓库的 src/TEdit/Editor/Tools/BrushTool.cs 与 src/TEdit/ViewModel/WorldViewModel.Editor.cs 已经可以作为 Phase 2/3/4 落地的可运行参考实现。赞分享游戏开发桌面应用【免费下载链接】Terraria-Map-EditorTEdit - Terraria Map Editor - TEdit is a stand alone, open source map editor for Terraria. It lets you edit maps just like (almost) paint! It also lets you change world settings (time, bosses downed etc), edit chests and change sign, make epic dungeons, castles, cities, and add rewards for your adventurers!项目地址https://gitcode.com/gh_mirrors/te/Terraria-Map-Editor点击查看免费下载相关推荐wangEditor 5性能瓶颈分析使用Chrome DevTools定位渲染问题wangEditor 5性能瓶颈分析使用Chrome DevTools定位渲染问题 在富文本编辑器开发中性能问题往往隐蔽而棘手。当用户抱怨编辑器卡顿、输入延前端富文本UI组件Flet 大列表渲染性能优化ListView、GridView 与批量更新实战Flet 大列表渲染性能优化ListView、GridView 与批量更新实战 导读 在 Flet 中展示包含数百甚至数千个条目的列表时直接使用 Colum前端跨平台桌面应用移动开发OpenLLMetry性能优化从毫秒级延迟到批量处理OpenLLMetry性能优化从毫秒级延迟到批量处理 你还在为LLM应用的性能问题头疼吗 当用户抱怨你的AI应用反应迟钝时你是否知道一次未优化的OpLLMOps可观测性上一篇MikroORM 多 Schema 使用指南实体定义、运行时切换与 SQLite ATTACH DATABASE 实战下一篇如何用Mermaid Live Editor免费创建专业图表新手终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考