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:手动分配一块内存,并确保返回的地址满足“对齐”要求.

特点:

  1. 实际申请的内存,比用户需要的内存多一点,多alignment + sizeof(void*)
  2. 对齐地址alignedPtr,可用该地址做对齐访问;
  3. 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_sharedmakeAlignedBuffer可分配一块对齐内存(包含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也提供了选择:makeBuffershared_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.hImageUtils.cpp

图像工具类ImageUtils

posted @ 2025-05-18 16:54  明明1109  阅读(27)  评论(0)    收藏  举报