乐于分享
好东西不私藏

WPF BitmapCache 源码解构(二):托管属性到 GPU 纹理的完整链路

WPF BitmapCache 源码解构(二):托管属性到 GPU 纹理的完整链路

系列:WPF BitmapCache 源码解构 · 第二篇 | 承接第一篇对 CacheMode 的入门介绍目标读者:具备 WPF 中级以上开发经验,对 DUCE 通道和 D3D 纹理有基本认知的开发者


一、概要

本文以 F12 逐层跟进的方式,完整拆解 BitmapCache 从托管属性设置到 GPU 纹理创建的每一条代码路径,覆盖三层架构:托管层(BitmapCache 的 DUCE 序列化与 MILCMD_BITMAPCACHE 命令结构)→ DUCE 通道(共享内存传输)→ MIL 非托管层(离屏渲染、D3D9 纹理分配与合成)。同时给出完整的 Mermaid 序列图展示从 CacheMode 赋值到 Present 的全链路时序,以及 .NET Framework 4.0 到 .NET Core 3.0+ 的协议演进差异。每个代码路径标注源码文件路径。MIL C++ 层的示意代码块标注为教学示例,以 WpfGfx/ 目录下的实际源码为准。


二、详细内容

2.1 托管层——BitmapCache 的属性序列化

2.1.1 类继承体系

从 dotnet/wpf 仓库源码来看,BitmapCache 的类继承关系如下:

System.Windows.Media.Animatable └─ System.Windows.Media.CacheMode              [abstract partial class]      └─ System.Windows.Media.BitmapCache

CacheMode 是一个 abstract partial class,这意味着它的完整定义由手写部分 + 自动生成部分拼接而成。自动生成部分由 MilCodeGen——WPF 团队维护的内部代码生成工具——根据 DUCE 通道协议的模式自动产出:属性变更通知、序列化入口、资源句柄管理等"机械性"代码均由工具生成,开发者只需手写业务逻辑。自动生成代码位于 Generated/CacheMode.cs,手写部分在 CacheMode.cs

源文件位置(dotnet/wpf 仓库,main 分支):

  • 手写部分:src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/CacheMode.cs
  • 自动生成部分:src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/Generated/CacheMode.cs
  • 手写 BitmapCache:src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/BitmapCache.cs

注意: 在 .NET Framework 4.0 RTM 中,CacheMode 的托管代码集中在一个文件中(可通过 dotnetframework.org 浏览)。.NET Core 3.0+ 开源迁移后,CacheMode 被拆分为 partial class:手写部分在 CacheMode.cs,自动生成部分在 Generated/CacheMode.csBitmapCache 是独立的 sealed class,继承自 CacheMode(非 CacheMode 的 partial 部分),其手写和自动生成代码同样拆分在两个文件中。

2.1.2 CacheMode 如何通过 DUCE.IResource 参与资源生命周期

CacheMode 的完整类声明:

publicabstractpartialclassCacheMode : AnimatableDUCE.IResource

其中 DUCE.IResource 是 WPF 托管层与非托管渲染线程之间资源管理的核心契约接口。其定义(位于 DUCE 通道源码中)如下:

internalinterfaceIResource{    DUCE.ResourceHandle AddRefOnChannel(Channel channel);voidReleaseOnChannel(Channel channel);    DUCE.ResourceHandle GetHandle(Channel channel);intGetChannelCount();    DUCE.Channel      GetChannel(int index);    DUCE.ResourceHandle Get3DHandle(Channel channel);   // 仅 Visual3D 实现voidRemoveChildFromParent(IResource parent, DUCE.Channel channel);}

资源生命周期流程:

[创建 BitmapCache 对象]   ← 托管堆分配,无 GPU 资源        │        ▼[首次赋值 UIElement.CacheMode]        │        ▼[渲染遍历首次引用] → AddRefOnChannel(channel)        │        ├── CreateOrAddRefOnChannel()  → 分配 DUCE 资源句柄(GPU 端)        │   ├── refCount = 1(首引用)        │   └── 注册 UpdateResource 回调 → 发送 MILCMD_BITMAPCACHE_CREATE        │        ▼[CacheMode = null 或移除元素] → ReleaseOnChannel(channel)        │        ├── refCount--        ├── refCount == 0 on all channels → 释放 DUCE 资源        └── 移除 MediaContext.ResourcesUpdated 事件处理器

这个引用计数设计的精妙之处在于:一个 BitmapCache 对象可以被多个 Visual 共享(通过 BitmapCacheBrush),DUCE 通道会自动处理多引用场景下的资源生命周期。只有当所有通道都释放引用后,底层的 GPU 纹理才会被回收。

2.1.3 BitmapCache 的三个核心属性

BitmapCache 定义了三个依赖属性(Dependency Properties),每个都对应一项渲染行为:

(1)RenderAtScale(double,默认 1.0)

// 源文件:BitmapCache.cspublicstaticreadonly DependencyProperty RenderAtScaleProperty =    DependencyProperty.Register(nameof(RenderAtScale),typeof(double),typeof(BitmapCache),new PropertyMetadata(1.0));

RenderAtScale 控制缓存纹理相对于元素布局尺寸的倍数。当设为 2.0 时,内部渲染会生成一张2 倍分辨率的纹理,然后在合成时还原为目标尺寸——等价于超采样抗锯齿(SSAA)策略。典型场景:父元素施加了 ScaleTransform 导致最终展示尺寸大于布局尺寸,此时预放大可以避免模糊。

(2)SnapsToDevicePixels(bool,默认 false)

publicstaticreadonly DependencyProperty SnapsToDevicePixelsProperty =    DependencyProperty.Register(nameof(SnapsToDevicePixels),typeof(bool),typeof(BitmapCache),new PropertyMetadata(false));

控制光栅化时是否吸附到物理像素网格。这对于需要像素级对齐的内容(如单像素线条、ClearType 文字)至关重要。

(3)EnableClearType(bool,默认 false)

publicstaticreadonly DependencyProperty EnableClearTypeProperty =    DependencyProperty.Register(nameof(EnableClearType),typeof(bool),typeof(BitmapCache),new PropertyMetadata(false));

默认为 false 的原因非常值得深究:ClearType 利用 LCD 子像素(R/G/B 条纹)的独立发光来增加水平分辨率,但这种效果依赖于最终合成位置的背景颜色和像素对齐状态。当一个 ClearType 渲染的文本被缓存到纹理中后,如果纹理被移动到不同颜色的背景上,预先烘焙的子像素着色会导致严重的色彩伪影(color fringing)。因此,除非你能保证缓存纹理始终落在不透明、固定颜色的背景上,否则应该保持此属性为 false,此时文本使用灰度抗锯齿(grayscale anti-aliasing)。

2.1.4 DUCE 序列化过程——UpdateResource 到 channel.SendCommand

DUCE 通道是 WPF 双线程架构的通信脊梁:一条在托管堆和渲染线程之间传递命令的"邮件管道",基于进程内共享内存实现,不是 Socket/命名管道。托管层序列化一个命令结构体(如 MILCMD_BITMAPCACHE),调用 channel.SendCommand 写入共享内存缓冲区;渲染线程在下一帧从缓冲区读出并执行——写入方不等待执行结果,因此是异步的。

当一个 BitmapCache 的属性发生变化时,WPF 的依赖属性系统触发 OnPropertyChanged 回调链:

// 伪代码,展示属性变更 → DUCE 通道的传播路径OnPropertyChanged(DependencyPropertyChangedEventArgs e)    → base.OnPropertyChanged(e)          // Animatable 层处理        → _duceResource.OnPropertyChanged(e)            → MediaContext.ResourcesUpdated += UpdateResourceHandler                → [下一个渲染帧]                → UpdateResource(channel, skipOnChannelCheck: false)

核心方法:UpdateResource

以下为基于 MilCodeGen 生成模式的简化示意伪代码,非实际源码。实际的 UpdateResource 由代码生成工具根据 BitmapCache 的属性元数据自动产生,位于 Generated/BitmapCache.cs 中。

// 简化示意伪代码(非实际源码)——展示属性变更 → DUCE 通道的传播路径internaloverridevoidUpdateResource(DUCE.Channel channel, bool skipOnChannelCheck){// 1. 如果资源不在通道上且不可跳过,则先 AddRefif (!skipOnChannelCheck && !_duceResource.IsOnChannel(channel))    {        AddRefOnChannel(channel);return;    }// 2. 构造命令结构并序列化属性    DUCE.MILCMD_BITMAPCACHE cmd;    cmd.Type      = MILCMD.MilCmdBitmapCache;    cmd.Handle    = _duceResource.GetHandle(channel);    cmd.RenderAtScale       = (float)RenderAtScale;    cmd.EnableClearType     = CompositionResourceManager.BooleanToUInt32(EnableClearType);    cmd.SnapsToDevicePixels = CompositionResourceManager.BooleanToUInt32(SnapsToDevicePixels);// 3. 通过共享内存通道发送到渲染线程unsafe    {        channel.SendCommand((byte*)&cmd, sizeof(DUCE.MILCMD_BITMAPCACHE));    }}

2.1.5 MILCMD_BITMAPCACHE_CREATE / UPDATE 命令结构字段详解

MIL 命令结构采用打包二进制格式(packed binary),定义于 dotnet/wpf 仓库的 wgx_commands.h。以下为基于该头文件的命令结构示意(C# 侧对应的托管定义为 DUCE.MILCMD_BITMAPCACHE,位于 PresentationCore 的生成代码中):

┌─────────────────────────────────────────────────────────────┐│                  MILCMD_BITMAPCACHE_CREATE                   │├────────────┬───────────────┬────────────────────────────────┤│ 偏移(byte) │ 字段           │ 含义                            │├────────────┼───────────────┼────────────────────────────────┤│ 0          │ Type (uint32) │ MilCmdBitmapCacheCreate ID      ││ 4          │ Handle (uint32)│ 新分配的 DUCE 资源句柄          ││ 8          │ RenderAtScale │ float32 — 纹理尺寸倍率           ││ 12         │ EnableClearType│ uint32 — 布尔值(0 或 1)        ││ 16         │ SnapsToDevicePixels│ uint32 — 布尔值(0 或 1)   ││ 20         │ Padding/Flags │ 对齐填充或附加 flags              │└────────────┴───────────────┴────────────────────────────────┘┌─────────────────────────────────────────────────────────────┐│                  MILCMD_BITMAPCACHE_UPDATE                   │├────────────┬───────────────┬────────────────────────────────┤│ 偏移(byte) │ 字段           │ 含义                            │├────────────┼───────────────┼────────────────────────────────┤│ 0          │ Type (uint32) │ MilCmdBitmapCacheUpdate ID      ││ 4          │ Handle (uint32)│ 已存在的 DUCE 资源句柄          ││ 8          │ RenderAtScale │ float32                         ││ 12         │ EnableClearType│ uint32                          ││ 16         │ SnapsToDevicePixels│ uint32                     │└────────────┴───────────────┴────────────────────────────────┘

关键设计决策——CREATE vs UPDATE:

CREATE 和 UPDATE 结构体的字段布局高度相似,差异仅在于 Type 字段和 Handle 字段的语义:

  • CREATE 的 Handle 由 DUCE 通道分配,随后回填到 _duceResource 的通道-句柄映射表中
  • UPDATE 使用已存在的 Handle,仅更新属性值

这种"结构几乎相同,仅类型 tag 不同"的设计是 DUCE 通道协议中的常见模式:渲染线程基于统一的 switch-case 解析 CREATE 和 UPDATE 两种变体。具体命令类型枚举常量定义于 MILCMD 静态类(托管侧)和 wgx_commands.h(C++ 侧),实际字段布局以仓库中 MilCodeGen 工具生成的代码为准。

2.1.6 RenderAtScale、EnableClearType、SnapsToDevicePixels 的 DUCE 编码

在托管层到 DUCE 命令的编码过程中,三个属性的传递细节:

托管属性
托管类型
DUCE 编码类型
转换逻辑
RenderAtScaledoublefloat
 (32-bit IEEE 754)
(float)RenderAtScale
,精度从 64-bit 截断为 32-bit
EnableClearTypebooluint32EnableClearType ? 1u : 0u
SnapsToDevicePixelsbooluint32SnapsToDevicePixels ? 1u : 0u

RenderAtScale 精度截断问题: 从 double 到 float 的转换是有损的。对于典型值(1.0, 2.0, 3.0),这个截断不会导致问题,但当用户设置如 RenderAtScale = 1.333333... 时,实际传递给渲染线程的值是 1.3333333f,与原始值存在约 1e-7 量级的误差。不过,由于 GPU 纹理尺寸最终会被量化为整数像素,这个微小误差在绝大多数场景下不可观察。


2.2 渲染管道拦截点

2.2.1 UIElement 在 Arrange/Render 过程中对 CacheMode 的检查

在 WPF 的每帧渲染循环中,布局(Measure → Arrange)和渲染(Render)是两个独立阶段。CacheMode 的检查发生在 Render 阶段。以下流程基于 dotnet/wpf 仓库中 MediaContextPresentationCore/System/Windows/Media/MediaContext.cs)和 UIElement 的公开源码:

CompositionTarget.Rendering (每帧触发,UI 线程)  → MediaContext.RenderMessageHandler    → MediaContext.RenderMessageHandlerCore      → VisualTreeWalker.Walk(rootVisual)        → [对每个 Visual]          → UIElement.ArrangeOverride / RenderOpen            → 检查 this.CacheMode != null ?              ├── 是 → 检查缓存是否有效?              │   ├── 有效(Cache Hit) → PushCachedBitmap()              │   └── 无效(Cache Miss) → RenderSubtreeToOffscreen()              └── 否 → 正常递归渲染子树

UIElement.CacheMode 是一个标准的依赖属性:

// 源文件:UIElement.cs(PresentationCore)public CacheMode CacheMode{get { return (CacheMode)GetValue(CacheModeProperty); }set { SetValue(CacheModeProperty, value); }}

当 CacheMode 被设置为非 null 值时,依赖属性系统触发 OnCacheModeChanged 回调。在这个回调中,WPF 会:

  1. 将当前 Visual 标记为 "需要重新渲染缓存"(invalidate cached content)
  2. 通过 MediaContext.PostRender() 向 Dispatcher 投递一个 Render 优先级的渲染请求
  3. 渲染线程在下一个合成帧中执行缓存填充

2.2.2 渲染遍历中的 CacheMode 分支判断

WPF 的渲染遍历(RenderWalk)由 MediaContext 驱动,核心数据结构是 Composition Tree(合成树)。合成树是一个独立于逻辑可视树的渲染专用结构,由 DUCE 通道维护在渲染线程上。

对于设置了 CacheMode 的 Visual,渲染遍历中存在如下分支逻辑:

(1)缓存命中路径——Cache Hit

RenderWalk(visual)  ├── 检查 visual.CacheMode  ├── 检查缓存状态:IsCacheValid(visual, currentTransform, currentClip)  │   └── true → 缓存命中!  ├── 获取缓存的 GPU 纹理  ├── 构造纹理四边形(texture quad):  │   ├── 位置 = visual 的布局偏移  │   ├── 缩放 = RenderAtScale 的倒数(将超采样纹理缩回原位)  │   └── UV 坐标 = (0,0) → (1,1)  ├── 将纹理四边形提交给合成引擎  └── **跳过子树遍历** ← 性能收益的关键!

性能收益量化分析: 假设缓存的子树包含 500 个 Visual 节点,每个节点触发一次 tessellation 和一次 rasterization。缓存命中后,这 500 个节点的渲染开销被替换为单个纹理四边形的合成开销——在 GPU 上,这本质是一次纹理采样 + 一个 quad 的像素着色器执行,开销是 ~O(像素数) 而非 ~O(节点数) 的。

(2)缓存未命中路径——Cache Miss

RenderWalk(visual)  ├── 检查 visual.CacheMode  ├── 检查缓存状态:IsCacheValid(...)  │   └── false → 缓存未命中(首次渲染,或缓存已脏)  ├── 分配(或复用)离屏渲染目标:  │   ├── 纹理尺寸 = (ActualWidth × RenderAtScale × DPI_Scale,  │   │               ActualHeight × RenderAtScale × DPI_Scale)  │   └── 尺寸上限 = 2048 × 2048 像素(软件回退)或 GPU 最大纹理尺寸  ├── 将渲染目标设置为 off-screen surface  ├── **递归渲染整个子树到渲染目标中** ← 开销大!  ├── 将 off-screen surface 保存为缓存纹理  ├── 标记缓存为有效  ├── 恢复原始渲染目标(back buffer)  └── 继续合成流程(用刚生成的纹理)

缓存脏标记(Dirty Flag)的触发条件:

  • 子树中任何 Visual 的几何或布局发生变化(InvalidateVisualInvalidateArrange
  • BitmapCache.RenderAtScale 属性变更
  • BitmapCache.EnableClearType 属性变更
  • BitmapCache.SnapsToDevicePixels 属性变更
  • 关联的 Effect 或 OpacityMask 变更

不会触发重新缓存的情况(这是 CacheMode 的核心优化价值):

  • 父级元素施加的 RenderTransform(平移、旋转、缩放)
  • 父级元素的 Opacity 变化
  • 父级元素的 Clip 变化
  • 父级的 Effect 变化(作用在已缓存的纹理上)

2.2.3 DrawingContext 层的对应操作

DrawingContext 抽象类(实际类型是内部的 RenderDataDrawingContext)提供了一组与缓存位图相关的 Push 操作:

// DrawingContext 中的缓存相关操作(系统内部使用)publicabstractclassDrawingContext{// 将缓存位图推入绘制栈——对应 Cache Hit 路径publicabstractvoidDrawImage(ImageSource imageSource, Rect rectangle);// 将效果/位图效果推入绘制栈publicabstractvoidPushEffect(BitmapEffect effect, BitmapEffectInput input);// 推送裁剪区域publicabstractvoidPushClip(Geometry clipGeometry);}

在 CacheMode 的上下文中,DrawImage 是缓存命中时的核心调用——它接收缓存的 GPU 纹理作为 ImageSource,在一个矩形区域内绘制纹理四边形。


2.3 MIL 核心层机制

MIL(Media Integration Layer)是 WPF 渲染栈中以非托管 C++ 实现的核心层,编译为 wpfgfx_cor3.dll(.NET Core 3.0+)或 wpfgfx_v0400.dll(.NET Framework),与 DirectX 9 深度集成。自 2018 年底 WPF 开源后,该层 C++ 源码已在 dotnet/wpf 仓库的 src/WpfGfx/ 目录下公开。以下基于仓库源码和公开架构进行分析。

2.3.1 离屏渲染目标的创建与复用

在 MIL 核心中,每个 BitmapCache 的离屏渲染目标由 WPF 内部的渲染资源管理对象维护。

在展开流程之前,需要先理解 D3D9 中两个基础概念:

  • Render-to-Texture(渲染到纹理):D3D9 允许把绘制输出从默认的后备缓冲区(back buffer,即最终显示在屏幕上的帧缓冲)重定向到一张 GPU 纹理。相当于让 GPU 把画"画"到一张纸上,而不是直接画到屏幕上——然后这张纸可以作为贴图贴到后续的帧中。
  • 后备缓冲区(back buffer)与交换链(swap chain):D3D 通常使用双缓冲——一个前缓冲正在被显示器扫描输出,一个后缓冲正在被 GPU 绘制。GPU 绘完一帧后调用 Present() 交换两个缓冲。整个过程叫交换链。

BitmapCache 利用 Render-to-Texture:先将子树渲染到一张离屏纹理,后续帧中把这张纹理作为普通贴图合成到 back buffer,从而跳过子树的重复绘制。

说明:下文涉及的内部类名和流程细节为基于 WPF 公开架构和 D3D9 Render-to-Texture 标准模式的教学推理,非 dotnet/wpf 仓库中实际 C++ 源码的直接引用。MIL 层以 C++ 实现,编译为 wpfgfx_cor3.dll(.NET Core 3.0+)或 wpfgfx_v0400.dll(.NET Framework),源码位于仓库 src/WpfGfx/ 目录。

离屏渲染流程(基于 WPF 架构和 D3D9 Render-to-Texture 机制的推理):

  1. 计算纹理尺寸——基于 ActualSize × RenderAtScale × DPI 缩放因子,像素值向上取整
  2. 钳制尺寸——限制在 [1, MaxTextureSize] 范围内(MaxTextureSize 由 RenderCapability.MaxHardwareTextureSize 提供)
  3. 分配渲染目标——硬件加速模式下,通过 D3D9 的 CreateTexture API 分配 GPU 显存纹理,参数 D3DUSAGE_RENDERTARGET(标记"此纹理将被用作渲染目标")和 D3DPOOL_DEFAULT(纹理驻留在 GPU 显存而非系统内存)表明这是一张"给 GPU 画画用的纸";软件回退模式下,分配系统内存位图
  4. 切换渲染目标——D3D9 的标准 Render-to-Texture 流程:GetRenderTarget 保存原始 back buffer → SetRenderTarget 切换到离屏纹理 → 子树完整渲染到此纹理 → SetRenderTarget 恢复 back buffer
  5. 纹理合成——渲染完成后,渲染引擎保存对该纹理的引用。在后续帧中,若缓存有效,该纹理作为单次纹理采样操作(textured quad,2 个三角形 / 4 个顶点)直接合成到 back buffer,跳过子树遍历

推理依据:以上流程遵循 D3D9 SDK 文档规定的 Render-to-Texture 标准模式(GetRenderTarget → SetRenderTarget → Clear → Draw → SetRenderTarget),以及 WPF 公开的 RenderCapability.MaxHardwareTextureSize API。步骤 1-2 的公式在 MS-WPFXV-2019 协议规范中有对应描述。具体内部类名和方法签名为 MIL 实现细节。

复用策略: 当子树内容不变但纹理尺寸发生变化(例如元素 resize)时,MIL 层采用内部尺寸分级复用策略。若新尺寸接近旧尺寸,直接重映射现有纹理(避免 CreateTexture 的 GPU 驱动开销);否则重新分配。具体阈值未在公开 API 或文档中暴露,属于内部启发式算法。

2.3.3 纹理尺寸计算逻辑

纹理的物理像素尺寸由四个因素共同决定:

pixelWidth  = Ceiling(ActualWidth  × RenderAtScale × (DPI_X / 96.0))pixelHeight = Ceiling(ActualHeight × RenderAtScale × (DPI_Y / 96.0))

逐项解析:

因子
来源
典型值
说明
ActualWidth/Height
布局系统
100.0
WPF 布局的最终宽度/高度(设备无关单位)
RenderAtScale
BitmapCache 属性
1.0 / 2.0
用户指定的超采样倍率
DPI_X / 96.0
系统 DPI 设置
1.0 (96dpi) / 1.25 (120dpi) / 2.0 (192dpi)
WPF 基准 DPI 为 96

示例计算(高 DPI 场景):

元素尺寸:200 × 100 WPF 单位RenderAtScale:2.0(用于后续缩放动画)系统 DPI:192(200% 显示缩放)pixelWidth  = Ceiling(200 × 2.0 × (192 / 96)) = Ceiling(200 × 2.0 × 2.0) = Ceiling(800)  = 800pxpixelHeight = Ceiling(100 × 2.0 × (192 / 96)) = Ceiling(100 × 2.0 × 2.0) = Ceiling(400)  = 400px

结果:一张 800×400 像素的 GPU 纹理,在 200% DPI 屏幕上以 200×100 设备无关单位展示时,每个单位对应 4 个物理像素——足以提供超清晰的渲染效果,且当父元素施加 2× 缩放时也不会有模糊。

2.3.4 ClearType 在缓存纹理中的处理策略

ClearType 的处理是 BitmapCache 设计中最微妙的问题之一。理清它需要先理解 ClearType 的工作原理:

ClearType 基础:ClearType 利用 LCD 面板的物理结构——每个逻辑像素由 R、G、B 三个独立子像素(sub-pixel)水平排列组成。通过独立控制每个子像素的亮度,ClearType 能在水平方向上实现约 3 倍的有效分辨率。但这种优化依赖于:

  1. 知道子像素在屏幕上的精确物理位置
  2. 知道文本下方的背景颜色(用于正确的 alpha 混合)

为何 EnableClearType 默认为 false:

当文本被渲染到缓存纹理中时,它失去了与"最终显示位置"的关联:

场景 A:白色背景上的黑色文字,CacheMode → 纹理中烘焙了 R/G/B 子像素着色信息场景 B:此纹理被移动到蓝色背景上展示结果:纹理中的子像素着色是基于"白色背景"计算的,但现在显示在蓝色背景上。     色彩偏差导致严重的色彩伪影——文字边缘出现品红/青色色晕。

当 EnableClearType = true 时,你必须确保:

  • 被缓存的元素始终位于不透明且颜色固定的背景上方
  • SnapsToDevicePixels = true(确保子像素定位准确)
  • 元素不会被移动到不同颜色的背景区域

当 EnableClearType = false(默认)时,MIL 使用灰度抗锯齿渲染文本。灰度抗锯齿不依赖子像素定位,使用传统的 alpha 混合,因此当纹理被移动或放在不同背景上时,不会出现色彩伪影——代价是牺牲了水平方向的有效分辨率。

2.3.5 软件渲染回退路径

当以下任一条件满足时,WPF 会降级到软件渲染路径:

  1. 显卡不支持 Direct3D 9 Pixel Shader 2.0
  2. 显存分配失败CreateTexture 返回 E_OUTOFMEMORY
  3. 用户强制软件渲染(注册表 HKEY_CURRENT_USER\Software\Microsoft\Avalon.Graphics\DisableHWAcceleration = 1
  4. 通过 RenderOptions.ProcessRenderMode 显式设置(.NET 4.0+)
  5. 系统处于远程桌面会话(RDP)中

软件渲染路径的关键差异:

特性
硬件渲染(GPU D3D9 纹理)
软件渲染(CPU 内存位图)
纹理存储位置
GPU 显存 (VRAM)
系统内存 (RAM)
最大纹理尺寸
取决于 GPU(通常是 8192 或 16384)
2048 × 2048 像素
(硬限制)
纹理格式
D3DFMT_A8R8G8B8 / D3DFMT_R8G8B8A8
32-bit BGRA 位图
ClearType 支持
支持(需 EnableClearType = true)
不支持
(强制灰度抗锯齿)
纹理合成开销
GPU 纹理采样 → 显示帧缓冲(极快)
CPU 位块传输(BitBlt)→ 显示帧缓冲(较慢)
多纹理并发
受显存容量限制
受系统内存限制(通常更高)

降级检测与通知:

WPF 通过 RenderCapability.Tier 属性和 RenderCapability.TierChanged 事件暴露渲染层级变更:

// Tier 0 = 软件渲染// Tier 1 = 部分硬件加速(Pixel Shader 2.0)// Tier 2 = 完全硬件加速(DirectX 9.0+, VRAM >= 120MB, PS 2.0+, VS 2.0+, 最大纹理 >= 4096)int currentTier = RenderCapability.Tier >> 16;

当用户插拔显示器或切换远程桌面时,渲染层级可能动态变化,已创建的离屏渲染目标需要销毁并重新分配。


2.4 完整链路时序图

以下 Mermaid 序列图展示从 CacheMode 赋值到最终屏幕呈现的完整链路:

sequenceDiagram    actor Developer as 开发者代码    participant UIThread as UI 线程 (托管层)    participant DP as 依赖属性系统    participant MediaCtx as MediaContext    participant Channel as DUCE Channel (共享内存)    participant RenderThread as 渲染线程 (MIL Core)    participant GPU as GPU / DirectX    Note over Developer,GPU: === 第一阶段:属性设置 & 缓存失效 ===    Developer->>UIThread: element.CacheMode = new BitmapCache(2.0)    UIThread->>DP: SetValue(CacheModeProperty, bitmapCache)    DP->>UIThread: OnCacheModeChanged(oldValue, newValue)    UIThread->>UIThread: InvalidateVisual()    UIThread->>MediaCtx: PostRender() — 投递 Render 优先级消息    MediaCtx->>MediaCtx: 将请求加入 Dispatcher 队列    Note over Developer,GPU: === 第二阶段:资源创建 & DUCE 序列化 ===    MediaCtx->>UIThread: Dispatcher 调度 → RenderMessageHandler    UIThread->>Channel: BitmapCache.AddRefOnChannel(channel)    Channel->>Channel: CreateOrAddRefOnChannel → 分配 DUCE ResourceHandle    UIThread->>UIThread: BitmapCache.UpdateResource(channel)    UIThread->>UIThread: 构造 MILCMD_BITMAPCACHE_CREATE 结构    UIThread->>Channel: channel.SendCommand(&cmd, sizeof(MILCMD))    Channel-->>RenderThread: 共享内存传输命令包    Note over Developer,GPU: === 第三阶段:离屏渲染 (Cache Miss) ===    RenderThread->>RenderThread: 解析 MILCMD_BITMAPCACHE_CREATE    RenderThread->>RenderThread: 计算纹理尺寸 (ActualWidth × Scale × DPI)    RenderThread->>GPU: CreateTexture(800, 400, A8R8G8B8, RENDERTARGET)    GPU-->>RenderThread: IDirect3DTexture9* (VRAM 中)    RenderThread->>GPU: GetSurfaceLevel(0) → SetRenderTarget(0, surface)    RenderThread->>GPU: Clear(RGBA=0,0,0,0)    RenderThread->>RenderThread: 递归绘制子树到离屏渲染目标    RenderThread->>GPU: [全部子树绘制命令]    RenderThread->>GPU: GetRenderTarget(0) → 恢复原始 back buffer    RenderThread->>RenderThread: 保存缓存纹理引用    RenderThread->>RenderThread: 标记缓存为 Valid    Note over Developer,GPU: === 第四阶段:缓存合成 (后续每帧) ===    RenderThread->>RenderThread: 下一帧开始    RenderThread->>RenderThread: RenderWalk → 检查 CacheMode → 缓存 Valid!    RenderThread->>GPU: SetTexture(0, cachedTexture)    RenderThread->>GPU: DrawPrimitive(TRIANGLELIST, 0, 2) — 纹理四边形    GPU->>GPU: 像素着色器采样纹理,合成到 back buffer    Note over Developer,GPU: === 第五阶段:呈现 (Present) ===    GPU->>GPU: Present() — 交换前后缓冲区    GPU-->>Developer: 屏幕显示更新    Note over Developer,GPU: === 后续帧:缓存命中 (零开销) ===    rect rgb(200, 255, 200)        Note over RenderThread,GPU: 父级 Transform/Opacity 变化时:        RenderThread->>GPU: 直接合成缓存的纹理四边形        Note right of GPU: 子树完全跳过重渲染!    end    Note over Developer,GPU: === 子树变更:缓存重新生成 ===    Developer->>UIThread: 子树中元素 InvalidateVisual()    UIThread->>MediaCtx: PostRender() — 子树变更触发    MediaCtx->>RenderThread: DUCE 标记缓存为 Dirty    RenderThread->>RenderThread: 重复"离屏渲染"阶段

关键时序观察:

  1. 第一阶段(属性设置) 和 第二阶段(DUCE 序列化) 发生在 UI 线程上,但 SendCommand 是非阻塞的——它只是将数据写入共享内存缓冲区,不等待渲染线程的确认。

  2. 第三阶段(离屏渲染) 是缓存性能开销最大的步骤,但仅发生一次(直到缓存被标记为脏)。这解释了为什么 BitmapCache 在静态内容 + 频繁变换场景中最有优势。

  3. 第四阶段→后续帧 展示了缓存的核心价值:父级变换(平移、旋转、缩放)仅改变纹理四边形的顶点坐标,GPU 的顶点着色器计算开销极低,而子树的重光栅化开销完全消除。


2.5 版本差异

2.5.1 .NET Framework 4.0 → 4.8 的演进

BitmapCache 首次引入于 .NET Framework 4.0(2010 年),作为 "Cached Composition" 功能的核心组件。在不同 .NET Framework 版本中:

版本
变更
4.0
首次引入 BitmapCacheBitmapCacheBrushViewport2DVisual3D 缓存支持。DUCE 命令协议版本为 v0400。
4.5
改进了 DPI 感知场景下的纹理尺寸计算,修复了高 DPI 时纹理尺寸偏小导致模糊的问题。
4.6
优化了离屏渲染目标的复用策略:4.6 引入了尺寸分级的复用逻辑,减少了 GPU 显存碎片(此前纹理尺寸变化时总是重新分配)。此信息来自 .NET Framework 4.6 发布说明中对 WPF 渲染性能的描述。
4.6.2
修复了 RenderAtScale 在非整数 DPI 设置下的精度问题(四舍五入 vs 向上取整的差异)。
4.7
改进了 EnableClearType = false 时的灰度抗锯齿质量。
4.8
修复了在 RemoteApp 和 RDP 场景中软件回退路径的缓冲区溢出问题。

DLL 命名惯例: .NET Framework 中 MIL 核心以 wpfgfx_v0400.dll 命名,v0400 对应 .NET 4.0 的 DUCE 协议版本。协议版本号内嵌在 DLL 名中,确保不同 Framework 版本之间的二进制兼容性隔离。

2.5.2 .NET Core 3.0+ 迁移中的协议变更

.NET Core 3.0(2019 年)将 WPF 从 .NET Framework 移植到 .NET Core 运行时。此次迁移中与 BitmapCache 相关的关键变化:

(1)DLL 重命名

.NET Framework:  wpfgfx_v0400.dll   → DUCE 协议版本标识 v0400.NET Core 3.0+:  wpfgfx_cor3.dll    → DUCE 协议版本标识 cor3

v0400 和 cor3 是 DLL 文件名中的协议版本标识,确保了不同 Framework 版本之间的二进制兼容性隔离。托管层 MILCMD_BITMAPCACHE 命令结构的核心字段布局在迁移中保持不变,确保 XAML 内容的向后兼容。

(2)COM 互操作层面的变更

.NET Framework 中 DUCE 通道依赖 COM STA 来管理跨线程调用;.NET Core 3.0+ 中,WPF 改用基于 System.Threading 的跨线程通信机制,绕过了 COM 编组(marshaling)开销。对 BitmapCache 的影响是:

  • SendCommand 调用不再经过 COM proxy/stub 层
  • DUCE.ResourceHandle 在 .NET Core 中不再依赖 COM 的 IMarshal 接口,改为直接使用值类型句柄

(3)已知回归

  • #8960(dotnet/wpf): 从 .NET 6 开始,使用 BitmapCacheBrush 引用的 ProgressBar 的 Indeterminate 动画停止更新——这是缓存脏标记传播逻辑的回归,与 .NET Core 中 MediaContext 的通知机制重构相关。

2.5.3 已确认的关键 Bug 与未解决问题

以下是从 dotnet/wpf 仓库中筛选出的与 BitmapCache 直接相关的关键 issue:

Issue #8919 —— BitmapCache 在多窗口 + Display Reset 后的渲染冻结

  • 现象: 当应用有多个窗口,且非首窗口使用了 BitmapCache 时,按下 Ctrl+Alt+Del(或锁屏、UAC 弹窗)后,这些窗口停止渲染
  • 根因: Display Reset 后,WM_PAINT 消息处理失败,导致操作系统图形管理器(DWM)不断投递 WM_PAINT,形成死循环。底层原因是 WPF 的 dirty region 支持(g_fDirtyRegion_Enabled)在 DWM 重置后未能正确恢复
  • 已知绕过方法:
    1. 在第一个窗口上也设置 BitmapCache(任何元素均可)
    2. 通过 Registry Hack 禁用 dirty region 支持
    3. 监听 D3DImage.IsFrontBufferAvailableChanged,检测到 Display Reset 后切换渲染模式

Issue #8031 —— 大量 BitmapCache 导致 UCEERR_RENDERTHREADFAILURE

  • 现象: 在约 5000+ 个元素上设置 BitmapCache 后触发 COMException (0x88980406)
  • 根因: 每个缓存的 Visual 创建一个 GDI 区域对象(region),触发了 Windows 的每进程 10,000 GDI 对象硬限制
  • 建议:BitmapCache 适合应用于较大的容器元素而非大量叶子元素

Issue #4276 —— 隐藏使用 BitmapCache 的首窗口后所有窗口停止渲染

  • 根因: 首窗口(prime window)持有渲染基础设施的关键引用;隐藏首窗口后,DWM 的 dirty region 跟踪状态被破坏
  • .NET 版本影响: 影响 .NET Core 3.0+ 所有版本

三、总结

本文以 F12 逐层跟进的视角,完整拆解了 WPF BitmapCache 从托管属性赋值到 GPU 纹理创建的完整链路。核心要点回顾:

  1. 托管层序列化:BitmapCache 继承自 CacheMode : Animatable, DUCE.IResource。通过自动代码生成(MilCodeGen)+ 手写 partial class 的模式,实现了依赖属性变更 → OnPropertyChanged → UpdateResource → channel.SendCommand 的完整传播路径。MILCMD_BITMAPCACHE 命令结构以紧凑二进制打包格式,将 RenderAtScaleEnableClearTypeSnapsToDevicePixels 三个属性传递到渲染线程。

  2. 渲染管道拦截:MediaContext.PostRender → Dispatcher 调度 → RenderWalk 遍历中,每遇到 CacheMode != null 的 Visual 即执行分支决策。缓存命中时,遍历被截断,以单个纹理四边形合成取代整个子树的递归渲染;缓存未命中时,触发子树完整渲染到离屏渲染目标。

  3. MIL 核心: D3D9 纹理的创建、离屏渲染切换和恢复过程由 MIL 内部渲染目标管理对象封装。纹理尺寸由 ActualSize × RenderAtScale × DPI_Scale 三重因素决定。ClearType 默认关闭是因为预烘焙的子像素着色在纹理移动时会与不同背景产生色彩冲突。软件回退路径将最大纹理尺寸限制为 2048×2048 并强制灰度抗锯齿。

  4. 协议演进: .NET Framework 到 .NET Core 的迁移中,DUCE 核心命令结构的二进制布局保持不变(向后兼容),底层传输从 COM 编组改为基于 System.Threading 的跨线程通信机制。

BitmapCache 的最优使用场景口诀:

  • 静态内容 + 频繁变换(平移/旋转/缩放动画)→ 最优
  • 大量静态子元素(图标网格、数据可视化)→ 很好
  • 频繁变化的内容(视频、实时数据更新)→ 不适用(每帧重新缓存反而慢)
  • 极多小元素各设缓存(如 5000+ 按钮)→ 禁止(GDI 对象耗尽)

四、引用

4.1 关键源码文件(dotnet/wpf 仓库,main 分支)

文件
路径
CacheMode.cs(手写部分)
src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/CacheMode.cs
CacheMode.cs(生成部分)
src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/Generated/CacheMode.cs
BitmapCache.cs
src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/BitmapCache.cs
BitmapCacheBrush.cs
src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/BitmapCacheBrush.cs
UIElement.cs(CacheMode 属性)
src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/UIElement.cs
DUCE 通道核心
src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/DUCE.cs
 及相关生成文件
MIL 命令定义
src/Microsoft.DotNet.Wpf/src/Graphics/include/Generated/wgx_commands.cs

4.2 .NET Reference Source(.NET Framework 4.0 RTM)

文件
URL
CacheMode.cs
http://www.dotnetframework.org/default.aspx/4@0/4@0/untmp/DEVDIV_TFS/Dev10/Releases/RTMRel/wpf/src/Core/CSharp/System/Windows/Media/Generated/CacheMode@cs/1305600/CacheMode@cs
BitmapCache.cs
通过 Microsoft Reference Source 的 PresentationCore 索引访问

4.3 关键技术博客与文档

标题
来源
New WPF Features: Cached Composition
Lester Lobo (Microsoft), MSDN Blogs
What's New for Performance in WPF in .NET 4
Jossef Goldberg (Microsoft), MSDN Blogs
What's New in Graphics for 4.0 Beta 2
WPF 3D Team, MSDN Blogs
WPF 渲染原理
dotnet-campus.github.io
dotnet 读 WPF 源代码笔记
blog.lindexi.com
[MS-WPFXV-2019]: BitmapCache
Microsoft Open Specifications

4.4 关键 GitHub Issues(dotnet/wpf)

Issue
标题
#8919
Regarding WPF BitmapCache Issue + R&D + Solutions
#8031
Massive use of BitmapCache leads to UCEERR_RENDERTHREADFAILURE
#4276
WPF windows stop rendering when using BitmapCache and hiding the first window
#8960
Indeterminate progress bar used as BitmapCacheBrush target do not animate

声明: 本文托管层代码路径基于 dotnet/wpf 开源仓库(main 分支,2026-06)。MIL 非托管层的 C++ 源码已在仓库 src/WpfGfx/ 目录下部分开源;文中标注为"示意代码"的 C++ 代码块为基于公开架构和 D3D9 标准模式的教学示例,非实际源码副本。DUCE 命令结构定义参见仓库中的 wgx_commands.h 和 wgx_commands.cs 生成文件。读者可结合 WPF Performance Suite、GPUView 和 ETW(Event Tracing for Windows)工具进行实验验证。

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-06-24 22:30:47 HTTP/1.1 GET : https://www.yeyulingfeng.com/a/787156.html
  2. 运行时间 : 0.126248s [ 吞吐率:7.92req/s ] 内存消耗:4,857.82kb 文件加载:145
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=0dab3e4bc17b2625897dc9ea927e18e8
  1. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/autoload_static.php ( 6.05 KB )
  7. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/ralouphie/getallheaders/src/getallheaders.php ( 1.60 KB )
  10. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  11. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  12. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  13. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  14. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  15. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  16. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  17. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  18. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  19. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/guzzlehttp/guzzle/src/functions_include.php ( 0.16 KB )
  21. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/guzzlehttp/guzzle/src/functions.php ( 5.54 KB )
  22. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  23. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  24. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  25. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/provider.php ( 0.19 KB )
  26. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  27. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  28. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  29. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/common.php ( 0.03 KB )
  30. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  32. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/alipay.php ( 3.59 KB )
  33. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  34. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/app.php ( 0.95 KB )
  35. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/cache.php ( 0.78 KB )
  36. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/console.php ( 0.23 KB )
  37. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/cookie.php ( 0.56 KB )
  38. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/database.php ( 2.48 KB )
  39. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/filesystem.php ( 0.61 KB )
  40. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/lang.php ( 0.91 KB )
  41. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/log.php ( 1.35 KB )
  42. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/middleware.php ( 0.19 KB )
  43. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/route.php ( 1.89 KB )
  44. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/session.php ( 0.57 KB )
  45. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/trace.php ( 0.34 KB )
  46. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/view.php ( 0.82 KB )
  47. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/event.php ( 0.25 KB )
  48. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  49. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/service.php ( 0.13 KB )
  50. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/AppService.php ( 0.26 KB )
  51. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  52. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  53. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  54. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  55. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  56. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/services.php ( 0.14 KB )
  57. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  58. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  59. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  60. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  61. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  62. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  63. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  64. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  65. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  66. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  67. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  68. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  69. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  70. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  71. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  72. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  73. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  74. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  75. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  76. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  77. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  78. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  79. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  80. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  81. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  82. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  83. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  84. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  85. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  86. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  87. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/Request.php ( 0.09 KB )
  88. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  89. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/middleware.php ( 0.25 KB )
  90. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  91. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  92. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  93. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  94. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  95. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  96. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  97. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  98. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  99. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  100. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  101. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  102. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  103. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/route/app.php ( 3.94 KB )
  104. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  105. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  106. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/controller/Index.php ( 9.87 KB )
  108. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/BaseController.php ( 2.05 KB )
  109. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  110. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  111. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  112. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  113. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  114. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  115. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  116. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  117. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  118. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  119. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  120. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  121. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  122. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  123. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  124. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  125. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  126. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  127. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  128. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  129. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  130. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  131. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  132. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  133. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  134. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  135. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/controller/Es.php ( 3.30 KB )
  136. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  137. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  138. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  139. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  140. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  141. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  142. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  143. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  144. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/runtime/temp/c935550e3e8a3a4c27dd94e439343fdf.php ( 31.50 KB )
  145. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000798s ] mysql:host=127.0.0.1;port=3306;dbname=wenku;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.000862s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000313s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000279s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.000498s ]
  6. SELECT * FROM `set` [ RunTime:0.000234s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.000720s ]
  8. SELECT * FROM `article` WHERE `id` = 787156 LIMIT 1 [ RunTime:0.000574s ]
  9. UPDATE `article` SET `lasttime` = 1782311447 WHERE `id` = 787156 [ RunTime:0.004231s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 64 LIMIT 1 [ RunTime:0.000254s ]
  11. SELECT * FROM `article` WHERE `id` < 787156 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.000415s ]
  12. SELECT * FROM `article` WHERE `id` > 787156 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.000378s ]
  13. SELECT * FROM `article` WHERE `id` < 787156 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.000555s ]
  14. SELECT * FROM `article` WHERE `id` < 787156 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.007002s ]
  15. SELECT * FROM `article` WHERE `id` < 787156 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.008619s ]
0.128068s