SoftGLRender源码:(工具类)StringUtils、FileUtils、MemoryUtils
由于工具类(名为“xxxUtils”)大多比较短小,因此本文一次解析多个.
StringUtils
文件:StringUtils.h
字符串工具类StringUtils,主要用来检测字符串路径的前缀名、后缀名.
用std::string::compare比较字符串.
class StringUtils {
public:
// 判断后缀
inline static bool endsWith(const std::string& str, const std::string& suffix) {
return str.size() >= suffix.size()
&& 0 == str.compare(str.size() - suffix.size(), suffix.size(), suffix);
}
// 判断前缀
inline static bool startsWith(const std::string& str, const std::string& prefix) {
return str.size() >= prefix.size()
&& 0 == str.compare(0, prefix.size(), prefix);
}
};
FileUtils
文件:FileUtils.h
文件工具类FileUtils,主要用来读文件、写文件. 用于访问GLSL等小文件,文件大小 < 100KB. 因此,一次读取全部文件,写完全部文件.
有2种访问文件模式:二进制(std::ios::binary)、文本(默认).
class FileUtils {
public:
// 判断指定路径的文件是否存在
static bool exists(const std::string& path) {
std::ifstream file(path);
return file.good();
}
// 以二进制形式读取指定路径的文件的所有内容
static std::vector<uint8_t> readBytes(const std::string& path) {
std::vector<uint8_t> ret;
// std::ios::ate <=> file.seekg(end);
std::ifstream file(path, std::ios::in | std::ios::binary | std::ios::ate);
if (!file.is_open()) {
LOGE("failed to open file: %s", path.c_str());
return ret;
}
size_t size = file.tellg();
if (size <= 0) { // reason: 1. failed to open file; 2. stream error
LOGE("failed to read file, invalid size: %d", size);
return ret;
}
ret.resize(size);
file.seekg(0, std::ios::beg);
file.read(reinterpret_cast<char*>(ret.data()), (std::streamsize)size);
return ret;
}
// 以ASCII文本形式读取指定路径的文件的所有内容
static std::string readText(const std::string& path) {
auto data = readBytes(path);
if (data.empty()) {
return "";
}
return { (char*)data.data(), data.size() };
}
// 以二进制形式写data[0..length-1]到指定路径的文件. 默认会清空原有文件内容
static bool writeBytes(const std::string& path, const char* data, size_t length) {
std::ofstream file(path, std::ios::out | std::ios::binary);
if (!file.is_open()) {
LOGE("failed to open file: %s", path.c_str());
return false;
}
file.write(data, length);
return true;
}
// 以文本形式写字符串str到指定路径的文件. 默认会清空原有文件内容
static bool writeText(const std::string& path, const std::string& str) {
std::ofstream file(path, std::ios::out);
if (!file.is_open()) {
LOGE("failed to open file: %s", path.c_str());
return false;
}
file.write(str.c_str(), str.length());
file.close();
return true;
}
};
MemoryUtils
文件:MemoryUtils.h
内存工具类:MemoryUtils. 提供内存对齐分配、释放以及智能指针包装的功能.
#define SOFTGL_ALIGNMENT 32 // 默认对齐内存大小 bytes
设置默认的内存对齐大小为 32 bytes,常见的SIMD对齐要求(如AVX指令集要求 32 字节对齐).
内存布局
用户需要的是对齐内存地址alignedPtr,是在addr基础上,往前偏移addr%alignment bytes. 当然,大小为sizeof(void*)存放data指针的区域也会往前移,只有最开始的alignment区域会被压缩.
对齐的目的:让用户使用的elements的内存大小为“对齐大小”(默认SOFTGL_ALIGNMENT)的整数倍. 对齐后,对内存的操作会更高效.
// 对齐内存布局
//
// 对齐之前:
// ------------------------------------------------------------
// | head | elements |
// ------------------------------------------------------------
// | alignment | sizeof(void*) | size element |
// ------------------------------------------------------------
// ↑ ↑ ↑
// data ↑ addr
// alignedPtr=(addr-(addr%alignment))
// 对齐之后: ↓
// ↓
// ------------------------------------------------------------
// | head | elements |
// ------------------------------------------------------------
// | alig... | sizeof... | size element |
// ------------------------------------------------------------
分配对齐内存
alignedMalloc:手动分配一块内存,并确保返回的地址满足“对齐”要求.
特点:
- 实际申请的内存,比用户需要的内存多一点,多
alignment + sizeof(void*); - 对齐地址
alignedPtr,可用该地址做对齐访问; - 在
alignedPtr - sizeof(void*)位置存放原始malloc返回的地址data,方便后续用alignedFree()释放.
class MemoryUtils {
public:
// 分配一块对齐的内存
// @param size 需要分配的内存大小
// @param alignment 对齐大小
static void* alignedMalloc(size_t size, size_t alignment = SOFTGL_ALIGNMENT) {
if ((alignment & (alignment - 1)) != 0) { // alignment is not 2^n
LOGE("failed to malloc, invalid alignment: %d", alignment);
return nullptr;
}
size_t extra = alignment + sizeof(void*);
void* data = malloc(size + extra);
if (!data) {
LOGE("failed to malloc with size: %d", size);
return nullptr;
}
size_t addr = (size_t)data + extra;
// addr % alignment: addr相对于alignment 的偏移量(即未对齐的部分)
void* alignedPtr = (void*)(addr - (addr % alignment));
*((void**)alignedPtr - 1) = data; // alignedPtr - 1 存放指针data值
return alignedPtr;
}
...
}
释放对齐内存
alignedFree:释放由alignedMalloc分配的内存.
原理:通过指针 ptr[-1] 获取原始 malloc 返回的 data,然后 free().
// ptr对应实参是用户通过alignedMalloc分配得到的对齐地址alignedPtr
static void alignedFree(void* ptr) {
if (ptr) {
free(((void**)ptr)[-1]); // 释放data所指内存
}
}
计算最小对齐大小
alignedSize:计算最小对齐后的内存大小,向上取整为对齐单位的整数倍.
不涉及内存操作,只根据用户提供的内存大小,来计算对齐后的内存大小.
static size_t alignedSize(size_t size) {
if (size == 0) {
return 0;
}
// std::ceill(x) : 对浮点数 x 向上取整
// std::floor(x) : 对浮点数 x 向下取整
return static_cast<size_t>(SOFTGL_ALIGNMENT * std::ceil((float)size / (float)SOFTGL_ALIGNMENT));
}
e.g.
alignedSize(33) → 64
alignedSize(32) → 32
alignedSize(0) → 0
智能指针管理对齐内存
内存管理是一个难题. 用户可能不希望手动管理void*类型的raw pointer,能否得到一个共享指针std::shared_ptr管理的对齐内存呢?
答案是可以. 类似于std::make_shared,makeAlignedBuffer可分配一块对齐内存(包含elemCnt个T类型元素),并用 std::shared_ptr 管理其生命周期.
-
对于对齐内存的申请,可以用
alignedMalloc; -
对于对齐内存的释放,在构造
shared_ptr时,指定删除器alignedFree;引用计数为0时,自动调用删除器释放内存.
tips: 关于shared_ptr的删除器,可参见:C++11 智能指针
// T 元素类型
// elemCnt 元素数量
template<typename T>
static std::shared_ptr<T> makeAlignedBuffer(size_t elemCnt) {
if (elemCnt == 0) {
return nullptr;
}
return std::shared_ptr<T>((T*)MemoryUtils::alignedMalloc(elemCnt * sizeof(T)),
[](const T* ptr) { MemoryUtils::alignedFree((void*)ptr); });
}
如果用户需要非对齐内存,SoftGLRender也提供了选择:makeBuffer用shared_ptr对其进行包装. 如果是用户申请的内存,由用户负责释放;如果makeBuffer申请的内存(数组T[elemCnt]),由删除器释放.
// elemCnt 元素数量
template<typename T>
static std::shared_ptr<T> makeBuffer(size_t elemCnt, const uint8_t* data = nullptr) {
if (elemCnt == 0) {
return nullptr;
}
if (data != nullptr) {
// data deleted by invoker
return std::shared_ptr<T>((T*)data, [](const T* ptr) {});
}
else {
// malloc memory
return std::shared_ptr<T>(new T[elemCnt], [](const T* ptr) { delete[] ptr; });
}
}
ImageUtils
文件:ImageUtils.h,ImageUtils.cpp
图像工具类ImageUtils

浙公网安备 33010602011771号