Building a Simple Engine -- Mobile Development -- Performance optimizations

移动平台性能优化

移动设备的硬件约束与桌面系统存在显著差异。本节将探讨实现移动平台高性能表现所需的核心性能优化策略。

注:本章聚焦通用移动平台性能优化。针对基于瓦片的渲染器(Tile-Based Rendering,TBR)GPU 的专属优化实践,请参考Rendering Approaches: Tile-Based Rendering.。

纹理优化

本节聚焦移动平台专属的纹理优化决策。关于通用的 Vulkan 图像创建、暂存上传和描述符配置,请参考引擎系列前文章节 ------《资源管理》《渲染管线》,或 Vulkan 官方指南(https://docs.vulkan.org/guide/latest/)。

纹理通常是图形应用中最大的内存消耗项,优化纹理对移动和桌面平台的性能都至关重要。

高效纹理格式

选择合适的纹理格式对所有平台都很关键,不同之处在于特定设备 / 驱动原生支持的格式类型:

压缩格式

尽可能使用硬件支持的压缩格式:

  • ASTC(Adaptive Scalable Texture Compression):现代移动 GPU 广泛支持,桌面平台支持度也在提升;具备灵活的块尺寸,画质与文件体积比表现优异。
  • ETC2/EAC:OpenGL ES 3.0 强制要求支持,大多数 Android 设备均兼容;Vulkan 环境下也普遍可用。
  • PVRTC:主要支持搭载 PowerVR GPU 的 iOS 设备。
  • BC(Block Compression,又称 DXT/BCn):桌面平台通用;部分移动 GPU 支持。
基于内容与兼容性的格式选择

根据纹理类型和设备支持情况选择格式:

  • 高细节纹理(法线、粗糙度):支持时优先选择 ASTC 4x4 或 6x6;桌面平台常用 BC5/BC7 作为替代。
  • 基础色纹理(albedo/basecolor):可用时 ASTC 6x6--8x8 表现良好;桌面平台典型选择为 BC1/BC7。
  • 单通道数据:考虑使用 R8 格式,或可用的单通道压缩替代格式。

提示:此优化建议并非仅适用于移动平台 ------ 块压缩可降低所有平台的内存占用和带宽消耗。移动平台章节重点强调这一点,是因为手机 / 平板的带宽、功耗和散热预算更为紧张。桌面平台同样能获得收益,核心差异在于主流支持的格式类型(如桌面端的 BC 格式、多数移动设备的 ASTC/ETC2 格式)。

以下是 Vulkan 中检查并使用压缩格式的实现示例:

cpp 复制代码
bool is_format_supported(vk::PhysicalDevice physical_device, vk::Format format, vk::ImageTiling tiling,
                        vk::FormatFeatureFlags features) {
    vk::FormatProperties props = physical_device.getFormatProperties(format);

    if (tiling == vk::ImageTiling::eLinear) {
        return (props.linearTilingFeatures & features) == features;
    } else if (tiling == vk::ImageTiling::eOptimal) {
        return (props.optimalTilingFeatures & features) == features;
    }

    return false;
}

vk::Format find_supported_format(vk::PhysicalDevice physical_device,
                               const std::vector<vk::Format>& candidates,
                               vk::ImageTiling tiling,
                               vk::FormatFeatureFlags features) {
    for (vk::Format format : candidates) {
        if (is_format_supported(physical_device, format, tiling, features)) {
            return format;
        }
    }

    throw std::runtime_error("Failed to find supported format");
}

内存优化

内存是所有平台的宝贵资源,而在移动平台上,由于带宽、功耗和散热预算更紧张,内存优化对性能的影响尤为关键。以下是核心优化策略:

最小化内存分配

  1. 内存池化:使用内存池减少频繁分配 / 释放的开销。
  2. 基于大内存块的子分配:避免创建大量小尺寸 Vulkan 内存分配,改为分配大内存块并从中进行子分配:
cpp 复制代码
class VulkanMemoryPool {
public:
    VulkanMemoryPool(vk::Device device, vk::PhysicalDevice physical_device,
                    vk::DeviceSize block_size, uint32_t memory_type_index)
        : device(device), block_size(block_size), memory_type_index(memory_type_index) {
        allocate_new_block();
    }

    ~VulkanMemoryPool() {
        for (auto& block : memory_blocks) {
            device.freeMemory(block.memory);
        }
    }

    struct Allocation {
        vk::DeviceMemory memory;
        vk::DeviceSize offset;
        vk::DeviceSize size;
    };

    Allocation allocate(vk::DeviceSize size, vk::DeviceSize alignment) {
        // 查找有足够空间的内存块
        for (auto& block : memory_blocks) {
            vk::DeviceSize aligned_offset = align(block.next_offset, alignment);
            if (aligned_offset + size <= block_size) {
                Allocation alloc;
                alloc.memory = block.memory;
                alloc.offset = aligned_offset;
                alloc.size = size;

                block.next_offset = aligned_offset + size;
                return alloc;
            }
        }

        // 无足够空间的内存块,分配新块
        allocate_new_block();
        return allocate(size, alignment);  // 使用新块重试分配
    }

private:
    struct MemoryBlock {
        vk::DeviceMemory memory;
        vk::DeviceSize next_offset = 0;
    };

    void allocate_new_block() {
        vk::MemoryAllocateInfo alloc_info;
        alloc_info.setAllocationSize(block_size);
        alloc_info.setMemoryTypeIndex(memory_type_index);

        MemoryBlock block;
        block.memory = device.allocateMemory(alloc_info);
        block.next_offset = 0;

        memory_blocks.push_back(block);
    }

    vk::DeviceSize align(vk::DeviceSize offset, vk::DeviceSize alignment) {
        return (offset + alignment - 1) & ~(alignment - 1);
    }

    vk::Device device;
    vk::DeviceSize block_size;
    uint32_t memory_type_index;
    std::vector<MemoryBlock> memory_blocks;
};

降低带宽占用

  1. 最小化状态变更:按材质分组绘制调用(draw calls)以减少状态切换。
  2. 使用更小的数据类型:合理使用 16 位索引和半精度浮点数。
  3. 优化顶点格式:使用紧凑的顶点格式降低内存带宽:
cpp 复制代码
// 传统顶点格式(每个顶点48字节)
struct Vertex {
    glm::vec3 position;   // 12字节
    glm::vec3 normal;     // 12字节
    glm::vec2 texCoord;   // 8字节
    glm::vec4 color;      // 16字节
};

// 优化后的顶点格式(每个顶点16字节)
struct OptimizedVertex {
    // 位置:3个分量,各16位浮点数
    uint16_t position[3]; // 6字节

    // 法线:2个分量(可重构Z轴),8位有符号归一化值
    int8_t normal[2];     // 2字节

    // 纹理坐标:2个分量,各16位浮点数
    uint16_t texCoord[2]; // 4字节

    // 颜色:4个分量,8位无符号归一化值
    uint8_t color[4];     // 4字节
};

若面向基于瓦片的 GPU(TBR)开发,附件的加载 / 存储行为和瓦片刷新会严重影响带宽。具体优化指南请参考Rendering Approaches 章节中的 "瓦片渲染器的附件加载 / 存储操作" 和 "瓦片渲染器的流水线:子通道依赖与 BY_REGION" 部分。

绘制调用优化

移动 GPU 对绘制调用开销尤为敏感:

  1. 实例化渲染:使用实例化(instancing)减少重复对象的绘制调用。
  2. 批处理:尽可能将多个对象合并为单个网格。
  3. 细节层次(LOD):实现 LOD 系统,降低远处物体的几何复杂度。

对于基于瓦片的 GPU,降低 CPU 开销固然重要,但通过合理的流水线设计和子通道(subpasses)将计算任务和数据保留在片上(on-chip),往往能带来更大的性能提升。具体的内存屏障 / 子通道设计模式请参考《渲染策略》章节的 "瓦片渲染器的流水线:子通道依赖与 BY_REGION" 部分;避免外部内存访问的 loadOp/storeOp 配置建议请参考 "瓦片渲染器的附件加载 / 存储操作" 部分。

厂商专属优化

不同移动 GPU 厂商的架构特性不同,针对性优化可获得额外收益:

厂商专属 GPU 优化

  1. 内存管理:多数移动 SoC 采用统一内存架构(Unified Memory Architecture):

    • 尽可能使用VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT类型的内存
    • 利用统一内存架构中高速的 CPU-GPU 内存传输特性
  2. 纹理压缩:不同设备支持不同的纹理压缩格式:

cpp 复制代码
// 检查纹理压缩格式支持性
bool supports_texture_format(vk::PhysicalDevice physical_device, vk::Format format) {
    vk::FormatProperties props = physical_device.getFormatProperties(format);
    return (props.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImage);
}

// 根据设备能力获取最优纹理格式
vk::Format get_optimal_texture_format(vk::PhysicalDevice physical_device) {
    vk::PhysicalDeviceProperties props = physical_device.getProperties();
    vk::PhysicalDeviceFeatures features = physical_device.getFeatures();

    // 检查ASTC支持性(现代移动GPU广泛支持)
    // 多数游戏会基于资源压缩格式进行开发,因此标准做法是仅确保所需格式受支持即可
    if (features.textureCompressionASTC_LDR) {
        return vk::Format::eAstc8x8SrgbBlock;
    }
    // 可继续添加其他格式的检查逻辑
    // ...
}
  1. 性能监控:多数厂商提供性能监控工具,可定位其硬件特有的性能瓶颈。

移动平台性能最佳实践

  1. 在目标设备上做性能分析:不同移动设备的性能特征差异显著,需在不同厂商、不同 GPU 架构的硬件上完成测试。
  2. 监控设备温度:移动设备过热时会触发性能降频,需设计能适配热节流的引擎架构。
  3. 平衡画质与性能:提供图形设置选项,让用户根据设备能力平衡画质与性能。
  4. 实现自适应分辨率:基于性能指标动态调整渲染分辨率。

在下一节中,我们将探讨移动 GPU 的不同渲染策略,重点分析基于瓦片的渲染(TBR)与立即模式渲染(IMR)的差异。

关键点回顾

  1. 移动平台纹理优化优先选择硬件原生支持的压缩格式(ASTC/ETC2/PVRTC),需通过 Vulkan 特性检测动态适配;
  2. 内存优化核心是池化分配、大块内存子分配,以及使用紧凑数据格式降低带宽;
  3. 移动 GPU 对绘制调用开销敏感,需结合实例化、批处理、LOD 等手段优化,TBR 架构还需重点优化附件操作和流水线设计。
相关推荐
weixin_409383121 天前
cocosshader像素风沙消散
shader·cocos
千里马-horse2 天前
Building a Simple Engine -- Tooling -- Crash minidumps
rendering·vulkan·崩溃处理
千里马-horse3 天前
Building a Simple Engine -- Tooling -- Introduction
pipeline·shader·rendering·vulkan
知无不研19 天前
内存碎片与内存优化
开发语言·c++·内存优化·内存碎片·内存操作
千里马-horse23 天前
Ray Tracing -- Ray query shadows
c++·rendering·vulkan
千里马-horse24 天前
Multithreading with Vulkan
shader·rendering·vulkan·vertex·multithreaded
weixin_4093831224 天前
cocos shader消失
shader·cocos
weixin_4093831225 天前
cocos魔法阵shader
shader·cocos
weixin_409383121 个月前
cocos 按钮光环shader
shader·cocos