feat(渲染后端): 重构渲染系统支持多后端
- 新增渲染后端工厂类,支持OpenGL和Vulkan后端 - 将OpenGL相关代码移动到backends/opengl目录 - 添加Vulkan后端占位实现 - 重构Shader系统,支持JSON元数据定义多后端Shader - 新增shape和sprite的JSON定义及GLSL文件 - 移除旧的组合Shader文件格式 - 更新构建系统支持选择渲染后端 - 重命名相关头文件路径保持一致性
This commit is contained in:
parent
d2660a86bb
commit
a4276e4376
|
|
@ -0,0 +1,79 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/graphics/core/render_backend.h>
|
||||||
|
#include <extra2d/core/smart_ptr.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染后端类型枚举
|
||||||
|
*/
|
||||||
|
enum class BackendType {
|
||||||
|
OpenGL, // OpenGL 4.x
|
||||||
|
Vulkan, // Vulkan 1.x
|
||||||
|
Metal, // Metal (macOS/iOS)
|
||||||
|
D3D11, // Direct3D 11
|
||||||
|
D3D12, // Direct3D 12
|
||||||
|
OpenGLES, // OpenGL ES (移动平台)
|
||||||
|
Count
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 后端工厂类,用于创建渲染后端实例
|
||||||
|
*/
|
||||||
|
class BackendFactory {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 获取后端工厂单例
|
||||||
|
* @return 后端工厂实例引用
|
||||||
|
*/
|
||||||
|
static BackendFactory& getInstance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建渲染后端
|
||||||
|
* @param type 后端类型
|
||||||
|
* @return 渲染后端实例
|
||||||
|
*/
|
||||||
|
UniquePtr<RenderBackend> createBackend(BackendType type);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建默认渲染后端
|
||||||
|
* @return 默认渲染后端实例
|
||||||
|
*/
|
||||||
|
UniquePtr<RenderBackend> createDefaultBackend();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查后端是否可用
|
||||||
|
* @param type 后端类型
|
||||||
|
* @return 可用返回true,否则返回false
|
||||||
|
*/
|
||||||
|
bool isBackendAvailable(BackendType type) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前平台推荐的后端类型
|
||||||
|
* @return 推荐的后端类型
|
||||||
|
*/
|
||||||
|
BackendType getRecommendedBackend() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取后端类型名称
|
||||||
|
* @param type 后端类型
|
||||||
|
* @return 后端类型名称字符串
|
||||||
|
*/
|
||||||
|
const char* getBackendName(BackendType type) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从名称解析后端类型
|
||||||
|
* @param name 后端类型名称
|
||||||
|
* @return 后端类型,如果未知则返回OpenGL
|
||||||
|
*/
|
||||||
|
BackendType parseBackendType(const char* name) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
BackendFactory() = default;
|
||||||
|
~BackendFactory() = default;
|
||||||
|
BackendFactory(const BackendFactory&) = delete;
|
||||||
|
BackendFactory& operator=(const BackendFactory&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/graphics/backends/opengl/gl_texture.h>
|
||||||
|
#include <extra2d/graphics/texture/font.h>
|
||||||
|
|
||||||
|
#include <stb/stb_truetype.h>
|
||||||
|
#include <stb/stb_rect_pack.h>
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// OpenGL 字体图集实现 (使用 STB 库)
|
||||||
|
// ============================================================================
|
||||||
|
class GLFontAtlas : public FontAtlas {
|
||||||
|
public:
|
||||||
|
GLFontAtlas(const std::string& filepath, int fontSize, bool useSDF = false);
|
||||||
|
~GLFontAtlas() override;
|
||||||
|
|
||||||
|
// FontAtlas 接口实现
|
||||||
|
const Glyph* getGlyph(char32_t codepoint) const override;
|
||||||
|
Texture* getTexture() const override { return texture_.get(); }
|
||||||
|
int getFontSize() const override { return fontSize_; }
|
||||||
|
float getAscent() const override { return ascent_; }
|
||||||
|
float getDescent() const override { return descent_; }
|
||||||
|
float getLineGap() const override { return lineGap_; }
|
||||||
|
float getLineHeight() const override { return lineHeight_; }
|
||||||
|
bool isSDF() const override { return useSDF_; }
|
||||||
|
Vec2 measureText(const std::string& text) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct GlyphData {
|
||||||
|
float width;
|
||||||
|
float height;
|
||||||
|
float bearingX;
|
||||||
|
float bearingY;
|
||||||
|
float advance;
|
||||||
|
float u0, v0, u1, v1;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PackedCharData {
|
||||||
|
stbtt_packedchar packedChar;
|
||||||
|
bool valid;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool useSDF_;
|
||||||
|
int fontSize_;
|
||||||
|
|
||||||
|
Ptr<GLTexture> texture_;
|
||||||
|
std::unordered_map<char32_t, GlyphData> glyphs_;
|
||||||
|
std::unordered_map<char32_t, PackedCharData> packedChars_;
|
||||||
|
float lineHeight_;
|
||||||
|
float ascent_;
|
||||||
|
float descent_;
|
||||||
|
float lineGap_;
|
||||||
|
|
||||||
|
// 字体数据
|
||||||
|
std::vector<unsigned char> fontData_;
|
||||||
|
stbtt_fontinfo fontInfo_;
|
||||||
|
float scale_;
|
||||||
|
|
||||||
|
// 图集打包参数
|
||||||
|
static constexpr int ATLAS_WIDTH = 512;
|
||||||
|
static constexpr int ATLAS_HEIGHT = 512;
|
||||||
|
|
||||||
|
// 初始化字体
|
||||||
|
bool initFont(const std::string& filepath);
|
||||||
|
// 渲染字形到图集
|
||||||
|
bool renderGlyph(char32_t codepoint);
|
||||||
|
// 更新图集纹理
|
||||||
|
void updateAtlas(int x, int y, int width, int height,
|
||||||
|
const std::vector<uint8_t>& data);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <extra2d/graphics/opengl/gl_sprite_batch.h>
|
#include <extra2d/graphics/backends/opengl/gl_sprite_batch.h>
|
||||||
#include <extra2d/graphics/core/render_backend.h>
|
#include <extra2d/graphics/core/render_backend.h>
|
||||||
#include <extra2d/graphics/shader/shader_interface.h>
|
#include <extra2d/graphics/shader/shader_interface.h>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/graphics/backends/opengl/gl_texture.h>
|
||||||
|
#include <extra2d/graphics/batch/sprite_batch.h>
|
||||||
|
#include <extra2d/graphics/shader/shader_interface.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <glad/glad.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// OpenGL 精灵批处理渲染器
|
||||||
|
// 使用 batch/sprite_batch 作为后端无关的批处理层
|
||||||
|
// ============================================================================
|
||||||
|
class GLSpriteBatch {
|
||||||
|
public:
|
||||||
|
GLSpriteBatch();
|
||||||
|
~GLSpriteBatch();
|
||||||
|
|
||||||
|
// 初始化/关闭
|
||||||
|
bool init();
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
// 批处理生命周期
|
||||||
|
void begin(const glm::mat4& viewProjection);
|
||||||
|
void end();
|
||||||
|
|
||||||
|
// 绘制单个精灵
|
||||||
|
void draw(const Texture& texture, const SpriteData& data);
|
||||||
|
|
||||||
|
// 批量绘制(用于文本渲染优化)
|
||||||
|
void drawBatch(const Texture& texture, const std::vector<SpriteData>& sprites);
|
||||||
|
|
||||||
|
// 获取绘制调用次数
|
||||||
|
uint32_t getDrawCallCount() const { return drawCallCount_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
// OpenGL 对象
|
||||||
|
GLuint vao_;
|
||||||
|
GLuint vbo_;
|
||||||
|
GLuint ebo_;
|
||||||
|
|
||||||
|
// 后端无关的批处理层
|
||||||
|
SpriteBatch batch_;
|
||||||
|
|
||||||
|
// 批次管理
|
||||||
|
struct Batch {
|
||||||
|
const GLTexture* texture;
|
||||||
|
size_t startVertex;
|
||||||
|
size_t vertexCount;
|
||||||
|
};
|
||||||
|
std::vector<Batch> batches_;
|
||||||
|
const GLTexture* currentTexture_;
|
||||||
|
|
||||||
|
// 着色器和矩阵
|
||||||
|
Ptr<IShader> shader_;
|
||||||
|
uint32_t drawCallCount_;
|
||||||
|
|
||||||
|
// 内部方法
|
||||||
|
void flush();
|
||||||
|
void submitBatch();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/graphics/core/render_backend.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Vulkan 渲染器实现(占位)
|
||||||
|
*
|
||||||
|
* 这是一个占位实现,用于展示Vulkan后端应该包含的内容。
|
||||||
|
* 完整的实现需要包含Vulkan上下文、设备、交换链、管线等。
|
||||||
|
*/
|
||||||
|
class VulkanRenderer : public RenderBackend {
|
||||||
|
public:
|
||||||
|
VulkanRenderer();
|
||||||
|
~VulkanRenderer() override;
|
||||||
|
|
||||||
|
// RenderBackend 接口实现
|
||||||
|
bool init(IWindow* window) override;
|
||||||
|
void shutdown() override;
|
||||||
|
|
||||||
|
void beginFrame(const Color &clearColor) override;
|
||||||
|
void endFrame() override;
|
||||||
|
void setViewport(int x, int y, int width, int height) override;
|
||||||
|
void setVSync(bool enabled) override;
|
||||||
|
|
||||||
|
void setBlendMode(BlendMode mode) override;
|
||||||
|
void setViewProjection(const glm::mat4 &matrix) override;
|
||||||
|
|
||||||
|
void pushTransform(const glm::mat4 &transform) override;
|
||||||
|
void popTransform() override;
|
||||||
|
glm::mat4 getCurrentTransform() const override;
|
||||||
|
|
||||||
|
Ptr<Texture> createTexture(int width, int height, const uint8_t *pixels,
|
||||||
|
int channels) override;
|
||||||
|
Ptr<Texture> loadTexture(const std::string &filepath) override;
|
||||||
|
|
||||||
|
void beginSpriteBatch() override;
|
||||||
|
void drawSprite(const Texture &texture, const Rect &destRect,
|
||||||
|
const Rect &srcRect, const Color &tint, float rotation,
|
||||||
|
const Vec2 &anchor) override;
|
||||||
|
void drawSprite(const Texture &texture, const Vec2 &position,
|
||||||
|
const Color &tint) override;
|
||||||
|
void endSpriteBatch() override;
|
||||||
|
|
||||||
|
void drawLine(const Vec2 &start, const Vec2 &end, const Color &color,
|
||||||
|
float width) override;
|
||||||
|
void drawRect(const Rect &rect, const Color &color, float width) override;
|
||||||
|
void fillRect(const Rect &rect, const Color &color) override;
|
||||||
|
void drawCircle(const Vec2 ¢er, float radius, const Color &color,
|
||||||
|
int segments, float width) override;
|
||||||
|
void fillCircle(const Vec2 ¢er, float radius, const Color &color,
|
||||||
|
int segments) override;
|
||||||
|
void drawTriangle(const Vec2 &p1, const Vec2 &p2, const Vec2 &p3,
|
||||||
|
const Color &color, float width) override;
|
||||||
|
void fillTriangle(const Vec2 &p1, const Vec2 &p2, const Vec2 &p3,
|
||||||
|
const Color &color) override;
|
||||||
|
void drawPolygon(const std::vector<Vec2> &points, const Color &color,
|
||||||
|
float width) override;
|
||||||
|
void fillPolygon(const std::vector<Vec2> &points,
|
||||||
|
const Color &color) override;
|
||||||
|
|
||||||
|
Ptr<FontAtlas> createFontAtlas(const std::string &filepath, int fontSize,
|
||||||
|
bool useSDF = false) override;
|
||||||
|
void drawText(const FontAtlas &font, const std::string &text,
|
||||||
|
const Vec2 &position, const Color &color) override;
|
||||||
|
void drawText(const FontAtlas &font, const std::string &text, float x,
|
||||||
|
float y, const Color &color) override;
|
||||||
|
|
||||||
|
Stats getStats() const override { return stats_; }
|
||||||
|
void resetStats() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Stats stats_;
|
||||||
|
bool initialized_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/core/math_types.h>
|
||||||
|
#include <extra2d/core/color.h>
|
||||||
|
#include <extra2d/graphics/texture/texture.h>
|
||||||
|
|
||||||
|
#include <glm/mat4x4.hpp>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 三角函数查表 - 避免每帧计算 sin/cos
|
||||||
|
// ============================================================================
|
||||||
|
class TrigLookup {
|
||||||
|
public:
|
||||||
|
TrigLookup();
|
||||||
|
|
||||||
|
// 通过角度(0-360)获取 sin/cos
|
||||||
|
float sin(int angle) const;
|
||||||
|
float cos(int angle) const;
|
||||||
|
|
||||||
|
// 通过弧度获取 sin/cos
|
||||||
|
float sinRad(float rad) const;
|
||||||
|
float cosRad(float rad) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr int TABLE_SIZE = 360 * 4; // 0.25度精度
|
||||||
|
std::array<float, TABLE_SIZE> sinTable_;
|
||||||
|
std::array<float, TABLE_SIZE> cosTable_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 精灵批次数据 - 后端无关
|
||||||
|
// ============================================================================
|
||||||
|
struct SpriteVertex {
|
||||||
|
Vec2 position;
|
||||||
|
Vec2 texCoord;
|
||||||
|
Color color;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SpriteData {
|
||||||
|
Vec2 position;
|
||||||
|
Vec2 size;
|
||||||
|
float rotation;
|
||||||
|
Vec2 pivot;
|
||||||
|
Color color;
|
||||||
|
const Texture* texture;
|
||||||
|
Rect uvRect;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 通用精灵批处理 - 后端无关
|
||||||
|
// 负责:顶点生成、批次管理、三角函数查表
|
||||||
|
// ============================================================================
|
||||||
|
class SpriteBatch {
|
||||||
|
public:
|
||||||
|
static constexpr size_t MAX_SPRITES = 10000;
|
||||||
|
static constexpr size_t VERTICES_PER_SPRITE = 4;
|
||||||
|
static constexpr size_t INDICES_PER_SPRITE = 6;
|
||||||
|
static constexpr size_t MAX_VERTICES = MAX_SPRITES * VERTICES_PER_SPRITE;
|
||||||
|
static constexpr size_t MAX_INDICES = MAX_SPRITES * INDICES_PER_SPRITE;
|
||||||
|
|
||||||
|
SpriteBatch();
|
||||||
|
~SpriteBatch() = default;
|
||||||
|
|
||||||
|
// 开始批次
|
||||||
|
void begin(const glm::mat4& viewProjection);
|
||||||
|
|
||||||
|
// 结束批次 - 返回需要绘制的批次列表
|
||||||
|
void end();
|
||||||
|
|
||||||
|
// 绘制单个精灵
|
||||||
|
void draw(const SpriteData& sprite);
|
||||||
|
|
||||||
|
// 批量绘制 - 一次性处理多个精灵
|
||||||
|
void drawBatch(const std::vector<SpriteData>& sprites);
|
||||||
|
|
||||||
|
// 立即绘制 - 不缓存,直接提交
|
||||||
|
void drawImmediate(const SpriteData& sprite);
|
||||||
|
|
||||||
|
// 获取当前批次数据
|
||||||
|
const std::vector<SpriteVertex>& getVertices() const { return vertices_; }
|
||||||
|
const std::vector<uint16_t>& getIndices() const { return indices_; }
|
||||||
|
size_t getSpriteCount() const { return spriteCount_; }
|
||||||
|
|
||||||
|
// 检查是否需要刷新
|
||||||
|
bool needsFlush() const { return spriteCount_ >= MAX_SPRITES; }
|
||||||
|
|
||||||
|
// 清空批次
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
private:
|
||||||
|
// 三角函数查表
|
||||||
|
TrigLookup trigLookup_;
|
||||||
|
|
||||||
|
// 顶点数据 - 使用固定大小数组避免动态分配
|
||||||
|
std::vector<SpriteVertex> vertices_;
|
||||||
|
std::vector<uint16_t> indices_;
|
||||||
|
size_t spriteCount_;
|
||||||
|
|
||||||
|
// 变换矩阵
|
||||||
|
glm::mat4 viewProjection_;
|
||||||
|
glm::mat4 cachedVP_;
|
||||||
|
bool vpDirty_;
|
||||||
|
|
||||||
|
// 生成索引
|
||||||
|
void generateIndices();
|
||||||
|
|
||||||
|
// 生成顶点
|
||||||
|
void generateVertices(const SpriteData& sprite, size_t vertexOffset);
|
||||||
|
|
||||||
|
// 刷新批次
|
||||||
|
void flush();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
#include <extra2d/core/color.h>
|
||||||
#include <extra2d/core/types.h>
|
#include <extra2d/core/types.h>
|
||||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
#include <extra2d/graphics/backends/opengl/gl_texture.h>
|
||||||
#include <extra2d/graphics/texture/texture.h>
|
#include <extra2d/graphics/texture/texture.h>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
|
||||||
#include <extra2d/core/math_types.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/graphics/texture/font.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
|
||||||
#include <extra2d/graphics/texture/texture.h>
|
|
||||||
#include <memory>
|
|
||||||
#include <stb/stb_rect_pack.h>
|
|
||||||
#include <stb/stb_truetype.h>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// OpenGL 字体图集实现 - 使用 stb_rect_pack 进行矩形打包
|
|
||||||
// ============================================================================
|
|
||||||
class GLFontAtlas : public FontAtlas {
|
|
||||||
public:
|
|
||||||
GLFontAtlas(const std::string &filepath, int fontSize, bool useSDF = false);
|
|
||||||
~GLFontAtlas();
|
|
||||||
|
|
||||||
// FontAtlas 接口实现
|
|
||||||
const Glyph *getGlyph(char32_t codepoint) const override;
|
|
||||||
Texture *getTexture() const override { return texture_.get(); }
|
|
||||||
int getFontSize() const override { return fontSize_; }
|
|
||||||
float getAscent() const override { return ascent_; }
|
|
||||||
float getDescent() const override { return descent_; }
|
|
||||||
float getLineGap() const override { return lineGap_; }
|
|
||||||
float getLineHeight() const override { return ascent_ - descent_ + lineGap_; }
|
|
||||||
Vec2 measureText(const std::string &text) override;
|
|
||||||
bool isSDF() const override { return useSDF_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
// 图集配置 - 增大尺寸以支持更多字符
|
|
||||||
static constexpr int ATLAS_WIDTH = 1024;
|
|
||||||
static constexpr int ATLAS_HEIGHT = 1024;
|
|
||||||
static constexpr int PADDING = 2; // 字形之间的间距
|
|
||||||
|
|
||||||
int fontSize_;
|
|
||||||
bool useSDF_;
|
|
||||||
mutable std::unique_ptr<GLTexture> texture_;
|
|
||||||
mutable std::unordered_map<char32_t, Glyph> glyphs_;
|
|
||||||
|
|
||||||
// stb_rect_pack 上下文
|
|
||||||
mutable stbrp_context packContext_;
|
|
||||||
mutable std::vector<stbrp_node> packNodes_;
|
|
||||||
mutable int currentY_;
|
|
||||||
|
|
||||||
std::vector<unsigned char> fontData_;
|
|
||||||
stbtt_fontinfo fontInfo_;
|
|
||||||
float scale_;
|
|
||||||
float ascent_;
|
|
||||||
float descent_;
|
|
||||||
float lineGap_;
|
|
||||||
|
|
||||||
// 预分配字形位图缓冲区,避免每次动态分配
|
|
||||||
mutable std::vector<uint8_t> glyphBitmapCache_;
|
|
||||||
mutable std::vector<uint8_t> glyphRgbaCache_;
|
|
||||||
|
|
||||||
void createAtlas();
|
|
||||||
void cacheGlyph(char32_t codepoint) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <extra2d/core/color.h>
|
|
||||||
#include <extra2d/core/math_types.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_shader.h>
|
|
||||||
#include <extra2d/graphics/texture/texture.h>
|
|
||||||
#include <glm/mat4x4.hpp>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <glad/glad.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// OpenGL 精灵批渲染器 - 优化版本
|
|
||||||
// ============================================================================
|
|
||||||
class GLSpriteBatch {
|
|
||||||
public:
|
|
||||||
static constexpr size_t MAX_SPRITES = 10000;
|
|
||||||
static constexpr size_t VERTICES_PER_SPRITE = 4;
|
|
||||||
static constexpr size_t INDICES_PER_SPRITE = 6;
|
|
||||||
static constexpr size_t MAX_VERTICES = MAX_SPRITES * VERTICES_PER_SPRITE;
|
|
||||||
static constexpr size_t MAX_INDICES = MAX_SPRITES * INDICES_PER_SPRITE;
|
|
||||||
|
|
||||||
struct Vertex {
|
|
||||||
glm::vec2 position;
|
|
||||||
glm::vec2 texCoord;
|
|
||||||
glm::vec4 color;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SpriteData {
|
|
||||||
glm::vec2 position;
|
|
||||||
glm::vec2 size;
|
|
||||||
glm::vec2 texCoordMin;
|
|
||||||
glm::vec2 texCoordMax;
|
|
||||||
glm::vec4 color;
|
|
||||||
float rotation;
|
|
||||||
glm::vec2 anchor;
|
|
||||||
bool isSDF = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
GLSpriteBatch();
|
|
||||||
~GLSpriteBatch();
|
|
||||||
|
|
||||||
bool init();
|
|
||||||
void shutdown();
|
|
||||||
|
|
||||||
void begin(const glm::mat4 &viewProjection);
|
|
||||||
void draw(const Texture &texture, const SpriteData &data);
|
|
||||||
void end();
|
|
||||||
|
|
||||||
// 批量绘制接口 - 用于自动批处理
|
|
||||||
void drawBatch(const Texture &texture,
|
|
||||||
const std::vector<SpriteData> &sprites);
|
|
||||||
|
|
||||||
// 立即绘制(不缓存)
|
|
||||||
void drawImmediate(const Texture &texture, const SpriteData &data);
|
|
||||||
|
|
||||||
// 统计
|
|
||||||
uint32_t getDrawCallCount() const { return drawCallCount_; }
|
|
||||||
uint32_t getSpriteCount() const { return spriteCount_; }
|
|
||||||
uint32_t getBatchCount() const { return batchCount_; }
|
|
||||||
|
|
||||||
// 检查是否需要刷新
|
|
||||||
bool needsFlush(const Texture &texture, bool isSDF) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
GLuint vao_;
|
|
||||||
GLuint vbo_;
|
|
||||||
GLuint ibo_;
|
|
||||||
GLShader shader_;
|
|
||||||
|
|
||||||
// 使用固定大小数组减少内存分配
|
|
||||||
std::array<Vertex, MAX_VERTICES> vertexBuffer_;
|
|
||||||
size_t vertexCount_;
|
|
||||||
|
|
||||||
const Texture *currentTexture_;
|
|
||||||
bool currentIsSDF_;
|
|
||||||
glm::mat4 viewProjection_;
|
|
||||||
|
|
||||||
// 缓存上一帧的 viewProjection,避免重复设置
|
|
||||||
glm::mat4 cachedViewProjection_;
|
|
||||||
bool viewProjectionDirty_ = true;
|
|
||||||
|
|
||||||
uint32_t drawCallCount_;
|
|
||||||
uint32_t spriteCount_;
|
|
||||||
uint32_t batchCount_;
|
|
||||||
|
|
||||||
void flush();
|
|
||||||
void setupShader();
|
|
||||||
|
|
||||||
// 添加顶点到缓冲区
|
|
||||||
void addVertices(const SpriteData &data);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
|
@ -17,118 +16,113 @@ namespace extra2d {
|
||||||
// 文件变化事件
|
// 文件变化事件
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
struct FileChangeEvent {
|
struct FileChangeEvent {
|
||||||
std::string filepath;
|
std::string filepath;
|
||||||
|
|
||||||
enum class Type {
|
enum class Type { Created, Modified, Deleted, Renamed } type;
|
||||||
Created,
|
|
||||||
Modified,
|
uint64_t timestamp = 0;
|
||||||
Deleted,
|
|
||||||
Renamed
|
|
||||||
} type;
|
|
||||||
|
|
||||||
uint64_t timestamp = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 文件变化回调
|
// 文件变化回调
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
using FileChangeCallback = std::function<void(const FileChangeEvent&)>;
|
using FileChangeCallback = std::function<void(const FileChangeEvent &)>;
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Shader热重载管理器
|
// Shader热重载管理器
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
class ShaderHotReloader {
|
class ShaderHotReloader {
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief 获取单例实例
|
* @brief 获取单例实例
|
||||||
* @return 热重载管理器实例引用
|
* @return 热重载管理器实例引用
|
||||||
*/
|
*/
|
||||||
static ShaderHotReloader& getInstance();
|
static ShaderHotReloader &getInstance();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 初始化热重载系统
|
* @brief 初始化热重载系统
|
||||||
* @return 初始化成功返回true,失败返回false
|
* @return 初始化成功返回true,失败返回false
|
||||||
*/
|
*/
|
||||||
bool init();
|
bool init();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 关闭热重载系统
|
* @brief 关闭热重载系统
|
||||||
*/
|
*/
|
||||||
void shutdown();
|
void shutdown();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 注册Shader文件监视
|
* @brief 注册Shader文件监视
|
||||||
* @param shaderName Shader名称
|
* @param shaderName Shader名称
|
||||||
* @param filePaths 要监视的文件列表
|
* @param filePaths 要监视的文件列表
|
||||||
* @param callback 文件变化时的回调
|
* @param callback 文件变化时的回调
|
||||||
*/
|
*/
|
||||||
void watch(const std::string& shaderName,
|
void watch(const std::string &shaderName,
|
||||||
const std::vector<std::string>& filePaths,
|
const std::vector<std::string> &filePaths,
|
||||||
FileChangeCallback callback);
|
FileChangeCallback callback);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 取消监视
|
* @brief 取消监视
|
||||||
* @param shaderName Shader名称
|
* @param shaderName Shader名称
|
||||||
*/
|
*/
|
||||||
void unwatch(const std::string& shaderName);
|
void unwatch(const std::string &shaderName);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 更新文件监视(在主循环中调用)
|
* @brief 更新文件监视(在主循环中调用)
|
||||||
*/
|
*/
|
||||||
void update();
|
void update();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 启用/禁用热重载
|
* @brief 启用/禁用热重载
|
||||||
* @param enabled 是否启用
|
* @param enabled 是否启用
|
||||||
*/
|
*/
|
||||||
void setEnabled(bool enabled);
|
void setEnabled(bool enabled);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 检查是否启用
|
* @brief 检查是否启用
|
||||||
* @return 启用返回true,否则返回false
|
* @return 启用返回true,否则返回false
|
||||||
*/
|
*/
|
||||||
bool isEnabled() const { return enabled_; }
|
bool isEnabled() const { return enabled_; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 检查是否已初始化
|
* @brief 检查是否已初始化
|
||||||
* @return 已初始化返回true,否则返回false
|
* @return 已初始化返回true,否则返回false
|
||||||
*/
|
*/
|
||||||
bool isInitialized() const { return initialized_; }
|
bool isInitialized() const { return initialized_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ShaderHotReloader() = default;
|
ShaderHotReloader() = default;
|
||||||
~ShaderHotReloader() = default;
|
~ShaderHotReloader() = default;
|
||||||
ShaderHotReloader(const ShaderHotReloader&) = delete;
|
ShaderHotReloader(const ShaderHotReloader &) = delete;
|
||||||
ShaderHotReloader& operator=(const ShaderHotReloader&) = delete;
|
ShaderHotReloader &operator=(const ShaderHotReloader &) = delete;
|
||||||
|
|
||||||
bool enabled_ = false;
|
bool enabled_ = false;
|
||||||
bool initialized_ = false;
|
bool initialized_ = false;
|
||||||
|
|
||||||
struct WatchInfo {
|
struct WatchInfo {
|
||||||
std::vector<std::string> filePaths;
|
std::vector<std::string> filePaths;
|
||||||
FileChangeCallback callback;
|
FileChangeCallback callback;
|
||||||
std::unordered_map<std::string, uint64_t> modifiedTimes;
|
std::unordered_map<std::string, uint64_t> modifiedTimes;
|
||||||
};
|
};
|
||||||
std::unordered_map<std::string, WatchInfo> watchMap_;
|
std::unordered_map<std::string, WatchInfo> watchMap_;
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
HANDLE watchHandle_ = nullptr;
|
HANDLE watchHandle_ = nullptr;
|
||||||
std::vector<uint8_t> buffer_;
|
std::vector<uint8_t> buffer_;
|
||||||
std::string watchDir_;
|
std::string watchDir_;
|
||||||
bool watching_ = false;
|
bool watching_ = false;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 轮询检查文件变化
|
* @brief 轮询检查文件变化
|
||||||
*/
|
*/
|
||||||
void pollChanges();
|
void pollChanges();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取文件修改时间
|
* @brief 获取文件修改时间
|
||||||
* @param filepath 文件路径
|
* @param filepath 文件路径
|
||||||
* @return 修改时间戳
|
* @return 修改时间戳
|
||||||
*/
|
*/
|
||||||
static uint64_t getFileModifiedTime(const std::string& filepath);
|
static uint64_t getFileModifiedTime(const std::string &filepath);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 便捷宏
|
// 便捷宏
|
||||||
|
|
|
||||||
|
|
@ -177,6 +177,14 @@ public:
|
||||||
*/
|
*/
|
||||||
bool loadBuiltinShaders();
|
bool loadBuiltinShaders();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从JSON元数据文件加载Shader(多后端支持)
|
||||||
|
* @param jsonPath JSON元数据文件路径
|
||||||
|
* @param name Shader名称
|
||||||
|
* @return 加载的Shader实例
|
||||||
|
*/
|
||||||
|
Ptr<IShader> loadFromMetadata(const std::string& jsonPath, const std::string& name);
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// 工具方法
|
// 工具方法
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
#include <extra2d/core/math_types.h>
|
#include <extra2d/core/math_types.h>
|
||||||
#include <extra2d/core/types.h>
|
#include <extra2d/core/types.h>
|
||||||
#include <extra2d/graphics/texture/texture.h>
|
#include <extra2d/graphics/texture/texture.h>
|
||||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
#include <extra2d/graphics/backends/opengl/gl_texture.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@
|
||||||
#include <extra2d/graphics/texture/texture.h>
|
#include <extra2d/graphics/texture/texture.h>
|
||||||
#include <extra2d/utils/logger.h>
|
#include <extra2d/utils/logger.h>
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
@ -13,7 +12,6 @@
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
|
|
@ -25,161 +23,153 @@ class RenderBackend;
|
||||||
// 纹理加载选项
|
// 纹理加载选项
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
struct TextureLoadOptions {
|
struct TextureLoadOptions {
|
||||||
bool generateMipmaps = true; // 是否生成 mipmaps
|
bool generateMipmaps = true; // 是否生成 mipmaps
|
||||||
bool sRGB = true; // 是否使用 sRGB 色彩空间
|
bool sRGB = true; // 是否使用 sRGB 色彩空间
|
||||||
bool premultiplyAlpha = false; // 是否预乘 Alpha
|
bool premultiplyAlpha = false; // 是否预乘 Alpha
|
||||||
PixelFormat preferredFormat = PixelFormat::RGBA8; // 首选像素格式
|
PixelFormat preferredFormat = PixelFormat::RGBA8; // 首选像素格式
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 纹理键 - 用于唯一标识纹理缓存条目
|
// 纹理键 - 用于唯一标识纹理缓存条目
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
struct TextureKey {
|
struct TextureKey {
|
||||||
std::string path; // 纹理文件路径
|
std::string path; // 纹理文件路径
|
||||||
Rect region; // 纹理区域(用于纹理图集)
|
Rect region; // 纹理区域(用于纹理图集)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 默认构造函数
|
* @brief 默认构造函数
|
||||||
*/
|
*/
|
||||||
TextureKey() = default;
|
TextureKey() = default;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 构造函数(仅路径)
|
* @brief 构造函数(仅路径)
|
||||||
* @param p 纹理文件路径
|
* @param p 纹理文件路径
|
||||||
*/
|
*/
|
||||||
explicit TextureKey(const std::string& p) : path(p), region(Rect::Zero()) {}
|
explicit TextureKey(const std::string &p) : path(p), region(Rect::Zero()) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 构造函数(路径 + 区域)
|
* @brief 构造函数(路径 + 区域)
|
||||||
* @param p 纹理文件路径
|
* @param p 纹理文件路径
|
||||||
* @param r 纹理区域
|
* @param r 纹理区域
|
||||||
*/
|
*/
|
||||||
TextureKey(const std::string& p, const Rect& r) : path(p), region(r) {}
|
TextureKey(const std::string &p, const Rect &r) : path(p), region(r) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 相等比较运算符
|
* @brief 相等比较运算符
|
||||||
* @param other 另一个 TextureKey
|
* @param other 另一个 TextureKey
|
||||||
* @return 是否相等
|
* @return 是否相等
|
||||||
*/
|
*/
|
||||||
bool operator==(const TextureKey& other) const {
|
bool operator==(const TextureKey &other) const {
|
||||||
return path == other.path && region == other.region;
|
return path == other.path && region == other.region;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 不等比较运算符
|
* @brief 不等比较运算符
|
||||||
* @param other 另一个 TextureKey
|
* @param other 另一个 TextureKey
|
||||||
* @return 是否不等
|
* @return 是否不等
|
||||||
*/
|
*/
|
||||||
bool operator!=(const TextureKey& other) const {
|
bool operator!=(const TextureKey &other) const { return !(*this == other); }
|
||||||
return !(*this == other);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// TextureKey 哈希函子
|
// TextureKey 哈希函子
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
struct TextureKeyHash {
|
struct TextureKeyHash {
|
||||||
/**
|
/**
|
||||||
* @brief 计算 TextureKey 的哈希值
|
* @brief 计算 TextureKey 的哈希值
|
||||||
* @param key 纹理键
|
* @param key 纹理键
|
||||||
* @return 哈希值
|
* @return 哈希值
|
||||||
*/
|
*/
|
||||||
size_t operator()(const TextureKey& key) const {
|
size_t operator()(const TextureKey &key) const {
|
||||||
size_t h1 = std::hash<std::string>{}(key.path);
|
size_t h1 = std::hash<std::string>{}(key.path);
|
||||||
size_t h2 = std::hash<float>{}(key.region.origin.x);
|
size_t h2 = std::hash<float>{}(key.region.origin.x);
|
||||||
size_t h3 = std::hash<float>{}(key.region.origin.y);
|
size_t h3 = std::hash<float>{}(key.region.origin.y);
|
||||||
size_t h4 = std::hash<float>{}(key.region.size.width);
|
size_t h4 = std::hash<float>{}(key.region.size.width);
|
||||||
size_t h5 = std::hash<float>{}(key.region.size.height);
|
size_t h5 = std::hash<float>{}(key.region.size.height);
|
||||||
|
|
||||||
// 组合哈希值
|
// 组合哈希值
|
||||||
size_t result = h1;
|
size_t result = h1;
|
||||||
result ^= h2 + 0x9e3779b9 + (result << 6) + (result >> 2);
|
result ^= h2 + 0x9e3779b9 + (result << 6) + (result >> 2);
|
||||||
result ^= h3 + 0x9e3779b9 + (result << 6) + (result >> 2);
|
result ^= h3 + 0x9e3779b9 + (result << 6) + (result >> 2);
|
||||||
result ^= h4 + 0x9e3779b9 + (result << 6) + (result >> 2);
|
result ^= h4 + 0x9e3779b9 + (result << 6) + (result >> 2);
|
||||||
result ^= h5 + 0x9e3779b9 + (result << 6) + (result >> 2);
|
result ^= h5 + 0x9e3779b9 + (result << 6) + (result >> 2);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 纹理池条目
|
// 纹理池条目
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
struct TexturePoolEntry {
|
struct TexturePoolEntry {
|
||||||
Ptr<Texture> texture; // 纹理对象
|
Ptr<Texture> texture; // 纹理对象
|
||||||
mutable std::atomic<uint32_t> refCount; // 引用计数
|
mutable std::atomic<uint32_t> refCount; // 引用计数
|
||||||
TextureKey key; // 纹理键
|
TextureKey key; // 纹理键
|
||||||
size_t memorySize; // 内存占用(字节)
|
size_t memorySize; // 内存占用(字节)
|
||||||
mutable uint64_t lastAccessTime; // 最后访问时间戳
|
mutable uint64_t lastAccessTime; // 最后访问时间戳
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 默认构造函数
|
* @brief 默认构造函数
|
||||||
*/
|
*/
|
||||||
TexturePoolEntry()
|
TexturePoolEntry()
|
||||||
: texture(nullptr)
|
: texture(nullptr), refCount(0), key(), memorySize(0), lastAccessTime(0) {
|
||||||
, refCount(0)
|
}
|
||||||
, key()
|
|
||||||
, memorySize(0)
|
|
||||||
, lastAccessTime(0) {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 构造函数
|
* @brief 构造函数
|
||||||
* @param tex 纹理对象
|
* @param tex 纹理对象
|
||||||
* @param k 纹理键
|
* @param k 纹理键
|
||||||
* @param memSize 内存占用
|
* @param memSize 内存占用
|
||||||
*/
|
*/
|
||||||
TexturePoolEntry(Ptr<Texture> tex, const TextureKey& k, size_t memSize)
|
TexturePoolEntry(Ptr<Texture> tex, const TextureKey &k, size_t memSize)
|
||||||
: texture(tex)
|
: texture(tex), refCount(1), key(k), memorySize(memSize),
|
||||||
, refCount(1)
|
lastAccessTime(getCurrentTime()) {}
|
||||||
, key(k)
|
|
||||||
, memorySize(memSize)
|
|
||||||
, lastAccessTime(getCurrentTime()) {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 移动构造函数
|
* @brief 移动构造函数
|
||||||
* @param other 另一个条目
|
* @param other 另一个条目
|
||||||
*/
|
*/
|
||||||
TexturePoolEntry(TexturePoolEntry&& other) noexcept
|
TexturePoolEntry(TexturePoolEntry &&other) noexcept
|
||||||
: texture(std::move(other.texture))
|
: texture(std::move(other.texture)),
|
||||||
, refCount(other.refCount.load(std::memory_order_relaxed))
|
refCount(other.refCount.load(std::memory_order_relaxed)),
|
||||||
, key(std::move(other.key))
|
key(std::move(other.key)), memorySize(other.memorySize),
|
||||||
, memorySize(other.memorySize)
|
lastAccessTime(other.lastAccessTime) {}
|
||||||
, lastAccessTime(other.lastAccessTime) {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 移动赋值运算符
|
* @brief 移动赋值运算符
|
||||||
* @param other 另一个条目
|
* @param other 另一个条目
|
||||||
* @return 引用
|
* @return 引用
|
||||||
*/
|
*/
|
||||||
TexturePoolEntry& operator=(TexturePoolEntry&& other) noexcept {
|
TexturePoolEntry &operator=(TexturePoolEntry &&other) noexcept {
|
||||||
if (this != &other) {
|
if (this != &other) {
|
||||||
texture = std::move(other.texture);
|
texture = std::move(other.texture);
|
||||||
refCount.store(other.refCount.load(std::memory_order_relaxed), std::memory_order_relaxed);
|
refCount.store(other.refCount.load(std::memory_order_relaxed),
|
||||||
key = std::move(other.key);
|
std::memory_order_relaxed);
|
||||||
memorySize = other.memorySize;
|
key = std::move(other.key);
|
||||||
lastAccessTime = other.lastAccessTime;
|
memorySize = other.memorySize;
|
||||||
}
|
lastAccessTime = other.lastAccessTime;
|
||||||
return *this;
|
|
||||||
}
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
// 禁止拷贝
|
// 禁止拷贝
|
||||||
TexturePoolEntry(const TexturePoolEntry&) = delete;
|
TexturePoolEntry(const TexturePoolEntry &) = delete;
|
||||||
TexturePoolEntry& operator=(const TexturePoolEntry&) = delete;
|
TexturePoolEntry &operator=(const TexturePoolEntry &) = delete;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 更新最后访问时间
|
* @brief 更新最后访问时间
|
||||||
*/
|
*/
|
||||||
void touch() const { lastAccessTime = getCurrentTime(); }
|
void touch() const { lastAccessTime = getCurrentTime(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取当前时间戳
|
* @brief 获取当前时间戳
|
||||||
* @return 时间戳(毫秒)
|
* @return 时间戳(毫秒)
|
||||||
*/
|
*/
|
||||||
static uint64_t getCurrentTime() {
|
static uint64_t getCurrentTime() {
|
||||||
auto now = std::chrono::steady_clock::now();
|
auto now = std::chrono::steady_clock::now();
|
||||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
|
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
now.time_since_epoch());
|
now.time_since_epoch());
|
||||||
return static_cast<uint64_t>(duration.count());
|
return static_cast<uint64_t>(duration.count());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
@ -187,144 +177,143 @@ struct TexturePoolEntry {
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
class TextureRef {
|
class TextureRef {
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief 默认构造函数
|
* @brief 默认构造函数
|
||||||
*/
|
*/
|
||||||
TextureRef() : texture_(nullptr), entry_(nullptr), mutex_(nullptr) {}
|
TextureRef() : texture_(nullptr), entry_(nullptr), mutex_(nullptr) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 构造函数
|
* @brief 构造函数
|
||||||
* @param texture 纹理对象
|
* @param texture 纹理对象
|
||||||
* @param entry 纹理池条目
|
* @param entry 纹理池条目
|
||||||
* @param mutex 互斥锁
|
* @param mutex 互斥锁
|
||||||
*/
|
*/
|
||||||
TextureRef(Ptr<Texture> texture, TexturePoolEntry* entry, std::mutex* mutex)
|
TextureRef(Ptr<Texture> texture, TexturePoolEntry *entry, std::mutex *mutex)
|
||||||
: texture_(texture), entry_(entry), mutex_(mutex) {}
|
: texture_(texture), entry_(entry), mutex_(mutex) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 创建独立的纹理引用(不管理引用计数)
|
* @brief 创建独立的纹理引用(不管理引用计数)
|
||||||
* @param texture 纹理对象
|
* @param texture 纹理对象
|
||||||
* @return 独立的纹理引用
|
* @return 独立的纹理引用
|
||||||
*/
|
*/
|
||||||
static TextureRef fromTexture(Ptr<Texture> texture) {
|
static TextureRef fromTexture(Ptr<Texture> texture) {
|
||||||
return TextureRef(texture, nullptr, nullptr);
|
return TextureRef(texture, nullptr, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 拷贝构造函数
|
||||||
|
* @param other 另一个 TextureRef
|
||||||
|
*/
|
||||||
|
TextureRef(const TextureRef &other)
|
||||||
|
: texture_(other.texture_), entry_(other.entry_), mutex_(other.mutex_) {
|
||||||
|
if (entry_ && entry_->refCount.load(std::memory_order_relaxed) > 0) {
|
||||||
|
entry_->refCount.fetch_add(1, std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 拷贝构造函数
|
* @brief 移动构造函数
|
||||||
* @param other 另一个 TextureRef
|
* @param other 另一个 TextureRef
|
||||||
*/
|
*/
|
||||||
TextureRef(const TextureRef& other)
|
TextureRef(TextureRef &&other) noexcept
|
||||||
: texture_(other.texture_), entry_(other.entry_), mutex_(other.mutex_) {
|
: texture_(std::move(other.texture_)), entry_(other.entry_),
|
||||||
if (entry_ && entry_->refCount.load(std::memory_order_relaxed) > 0) {
|
mutex_(other.mutex_) {
|
||||||
entry_->refCount.fetch_add(1, std::memory_order_relaxed);
|
other.entry_ = nullptr;
|
||||||
}
|
other.mutex_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 析构函数
|
||||||
|
*/
|
||||||
|
~TextureRef() { reset(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 拷贝赋值运算符
|
||||||
|
* @param other 另一个 TextureRef
|
||||||
|
* @return 引用
|
||||||
|
*/
|
||||||
|
TextureRef &operator=(const TextureRef &other) {
|
||||||
|
if (this != &other) {
|
||||||
|
reset();
|
||||||
|
texture_ = other.texture_;
|
||||||
|
entry_ = other.entry_;
|
||||||
|
mutex_ = other.mutex_;
|
||||||
|
if (entry_ && entry_->refCount.load(std::memory_order_relaxed) > 0) {
|
||||||
|
entry_->refCount.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 移动构造函数
|
* @brief 移动赋值运算符
|
||||||
* @param other 另一个 TextureRef
|
* @param other 另一个 TextureRef
|
||||||
*/
|
* @return 引用
|
||||||
TextureRef(TextureRef&& other) noexcept
|
*/
|
||||||
: texture_(std::move(other.texture_))
|
TextureRef &operator=(TextureRef &&other) noexcept {
|
||||||
, entry_(other.entry_)
|
if (this != &other) {
|
||||||
, mutex_(other.mutex_) {
|
reset();
|
||||||
other.entry_ = nullptr;
|
texture_ = std::move(other.texture_);
|
||||||
other.mutex_ = nullptr;
|
entry_ = other.entry_;
|
||||||
|
mutex_ = other.mutex_;
|
||||||
|
other.entry_ = nullptr;
|
||||||
|
other.mutex_ = nullptr;
|
||||||
}
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 析构函数
|
* @brief 重置引用
|
||||||
*/
|
*/
|
||||||
~TextureRef() { reset(); }
|
void reset() {
|
||||||
|
if (entry_ && mutex_) {
|
||||||
/**
|
std::lock_guard<std::mutex> lock(*mutex_);
|
||||||
* @brief 拷贝赋值运算符
|
if (entry_->refCount.load(std::memory_order_relaxed) > 0) {
|
||||||
* @param other 另一个 TextureRef
|
entry_->refCount.fetch_sub(1, std::memory_order_relaxed);
|
||||||
* @return 引用
|
}
|
||||||
*/
|
|
||||||
TextureRef& operator=(const TextureRef& other) {
|
|
||||||
if (this != &other) {
|
|
||||||
reset();
|
|
||||||
texture_ = other.texture_;
|
|
||||||
entry_ = other.entry_;
|
|
||||||
mutex_ = other.mutex_;
|
|
||||||
if (entry_ && entry_->refCount.load(std::memory_order_relaxed) > 0) {
|
|
||||||
entry_->refCount.fetch_add(1, std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
}
|
||||||
|
texture_.reset();
|
||||||
|
entry_ = nullptr;
|
||||||
|
mutex_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 移动赋值运算符
|
* @brief 获取纹理对象
|
||||||
* @param other 另一个 TextureRef
|
* @return 纹理对象指针
|
||||||
* @return 引用
|
*/
|
||||||
*/
|
Texture *get() const { return texture_.get(); }
|
||||||
TextureRef& operator=(TextureRef&& other) noexcept {
|
|
||||||
if (this != &other) {
|
|
||||||
reset();
|
|
||||||
texture_ = std::move(other.texture_);
|
|
||||||
entry_ = other.entry_;
|
|
||||||
mutex_ = other.mutex_;
|
|
||||||
other.entry_ = nullptr;
|
|
||||||
other.mutex_ = nullptr;
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 重置引用
|
* @brief 获取纹理对象(智能指针)
|
||||||
*/
|
* @return 纹理对象智能指针
|
||||||
void reset() {
|
*/
|
||||||
if (entry_ && mutex_) {
|
Ptr<Texture> getPtr() const { return texture_; }
|
||||||
std::lock_guard<std::mutex> lock(*mutex_);
|
|
||||||
if (entry_->refCount.load(std::memory_order_relaxed) > 0) {
|
|
||||||
entry_->refCount.fetch_sub(1, std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
texture_.reset();
|
|
||||||
entry_ = nullptr;
|
|
||||||
mutex_ = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取纹理对象
|
* @brief 检查是否有效
|
||||||
* @return 纹理对象指针
|
* @return 是否有效
|
||||||
*/
|
*/
|
||||||
Texture* get() const { return texture_.get(); }
|
bool valid() const { return texture_ != nullptr; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取纹理对象(智能指针)
|
* @brief 布尔转换运算符
|
||||||
* @return 纹理对象智能指针
|
*/
|
||||||
*/
|
explicit operator bool() const { return valid(); }
|
||||||
Ptr<Texture> getPtr() const { return texture_; }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 检查是否有效
|
* @brief 箭头运算符
|
||||||
* @return 是否有效
|
*/
|
||||||
*/
|
Texture *operator->() const { return texture_.get(); }
|
||||||
bool valid() const { return texture_ != nullptr; }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 布尔转换运算符
|
* @brief 解引用运算符
|
||||||
*/
|
*/
|
||||||
explicit operator bool() const { return valid(); }
|
Texture &operator*() const { return *texture_; }
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 箭头运算符
|
|
||||||
*/
|
|
||||||
Texture* operator->() const { return texture_.get(); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 解引用运算符
|
|
||||||
*/
|
|
||||||
Texture& operator*() const { return *texture_; }
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ptr<Texture> texture_;
|
Ptr<Texture> texture_;
|
||||||
TexturePoolEntry* entry_;
|
TexturePoolEntry *entry_;
|
||||||
std::mutex* mutex_;
|
std::mutex *mutex_;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
@ -338,232 +327,235 @@ private:
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
class TexturePool {
|
class TexturePool {
|
||||||
public:
|
public:
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
// 统计信息
|
// 统计信息
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
struct Stats {
|
struct Stats {
|
||||||
size_t textureCount = 0; // 纹理数量
|
size_t textureCount = 0; // 纹理数量
|
||||||
size_t memoryUsage = 0; // 内存使用量(字节)
|
size_t memoryUsage = 0; // 内存使用量(字节)
|
||||||
size_t maxMemoryUsage = 0; // 最大内存使用量
|
size_t maxMemoryUsage = 0; // 最大内存使用量
|
||||||
size_t cacheHits = 0; // 缓存命中次数
|
size_t cacheHits = 0; // 缓存命中次数
|
||||||
size_t cacheMisses = 0; // 缓存未命中次数
|
size_t cacheMisses = 0; // 缓存未命中次数
|
||||||
size_t evictionCount = 0; // 淘汰次数
|
size_t evictionCount = 0; // 淘汰次数
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
// 构造和析构
|
// 构造和析构
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 默认构造函数
|
* @brief 默认构造函数
|
||||||
*/
|
*/
|
||||||
TexturePool();
|
TexturePool();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 构造函数
|
* @brief 构造函数
|
||||||
* @param scene 场景指针
|
* @param scene 场景指针
|
||||||
* @param maxMemoryUsage 最大内存使用量(0 表示无限制)
|
* @param maxMemoryUsage 最大内存使用量(0 表示无限制)
|
||||||
*/
|
*/
|
||||||
explicit TexturePool(Scene* scene, size_t maxMemoryUsage = 0);
|
explicit TexturePool(Scene *scene, size_t maxMemoryUsage = 0);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 析构函数
|
* @brief 析构函数
|
||||||
*/
|
*/
|
||||||
~TexturePool();
|
~TexturePool();
|
||||||
|
|
||||||
// 禁止拷贝
|
// 禁止拷贝
|
||||||
TexturePool(const TexturePool&) = delete;
|
TexturePool(const TexturePool &) = delete;
|
||||||
TexturePool& operator=(const TexturePool&) = delete;
|
TexturePool &operator=(const TexturePool &) = delete;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 初始化纹理池
|
* @brief 初始化纹理池
|
||||||
* @param scene 场景指针
|
* @param scene 场景指针
|
||||||
* @param maxMemoryUsage 最大内存使用量(0 表示无限制)
|
* @param maxMemoryUsage 最大内存使用量(0 表示无限制)
|
||||||
*/
|
*/
|
||||||
void init(Scene* scene, size_t maxMemoryUsage = 0);
|
void init(Scene *scene, size_t maxMemoryUsage = 0);
|
||||||
|
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
// 纹理加载
|
// 纹理加载
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 从文件加载纹理
|
* @brief 从文件加载纹理
|
||||||
* @param path 文件路径
|
* @param path 文件路径
|
||||||
* @param options 加载选项
|
* @param options 加载选项
|
||||||
* @return 纹理引用
|
* @return 纹理引用
|
||||||
*/
|
*/
|
||||||
TextureRef load(const std::string& path,
|
TextureRef load(const std::string &path,
|
||||||
const TextureLoadOptions& options = TextureLoadOptions());
|
const TextureLoadOptions &options = TextureLoadOptions());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 从文件加载纹理区域
|
* @brief 从文件加载纹理区域
|
||||||
* @param path 文件路径
|
* @param path 文件路径
|
||||||
* @param region 纹理区域
|
* @param region 纹理区域
|
||||||
* @param options 加载选项
|
* @param options 加载选项
|
||||||
* @return 纹理引用
|
* @return 纹理引用
|
||||||
*/
|
*/
|
||||||
TextureRef load(const std::string& path, const Rect& region,
|
TextureRef load(const std::string &path, const Rect ®ion,
|
||||||
const TextureLoadOptions& options = TextureLoadOptions());
|
const TextureLoadOptions &options = TextureLoadOptions());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 从内存加载纹理
|
* @brief 从内存加载纹理
|
||||||
* @param data 像素数据
|
* @param data 像素数据
|
||||||
* @param width 宽度
|
* @param width 宽度
|
||||||
* @param height 高度
|
* @param height 高度
|
||||||
* @param channels 通道数
|
* @param channels 通道数
|
||||||
* @param key 缓存键
|
* @param key 缓存键
|
||||||
* @return 纹理引用
|
* @return 纹理引用
|
||||||
*/
|
*/
|
||||||
TextureRef loadFromMemory(const uint8_t* data, int width, int height,
|
TextureRef loadFromMemory(const uint8_t *data, int width, int height,
|
||||||
int channels, const std::string& key);
|
int channels, const std::string &key);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取或加载纹理
|
* @brief 获取或加载纹理
|
||||||
* @param path 文件路径
|
* @param path 文件路径
|
||||||
* @param options 加载选项
|
* @param options 加载选项
|
||||||
* @return 纹理引用
|
* @return 纹理引用
|
||||||
*/
|
*/
|
||||||
TextureRef getOrLoad(const std::string& path,
|
TextureRef
|
||||||
const TextureLoadOptions& options = TextureLoadOptions());
|
getOrLoad(const std::string &path,
|
||||||
|
const TextureLoadOptions &options = TextureLoadOptions());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取或加载纹理区域
|
* @brief 获取或加载纹理区域
|
||||||
* @param path 文件路径
|
* @param path 文件路径
|
||||||
* @param region 纹理区域
|
* @param region 纹理区域
|
||||||
* @param options 加载选项
|
* @param options 加载选项
|
||||||
* @return 纹理引用
|
* @return 纹理引用
|
||||||
*/
|
*/
|
||||||
TextureRef getOrLoad(const std::string& path, const Rect& region,
|
TextureRef
|
||||||
const TextureLoadOptions& options = TextureLoadOptions());
|
getOrLoad(const std::string &path, const Rect ®ion,
|
||||||
|
const TextureLoadOptions &options = TextureLoadOptions());
|
||||||
|
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
// 引用计数管理
|
// 引用计数管理
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 增加引用计数
|
* @brief 增加引用计数
|
||||||
* @param key 纹理键
|
* @param key 纹理键
|
||||||
* @return 是否成功
|
* @return 是否成功
|
||||||
*/
|
*/
|
||||||
bool addRef(const TextureKey& key);
|
bool addRef(const TextureKey &key);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 减少引用计数
|
* @brief 减少引用计数
|
||||||
* @param key 纹理键
|
* @param key 纹理键
|
||||||
* @return 减少后的引用计数
|
* @return 减少后的引用计数
|
||||||
*/
|
*/
|
||||||
uint32_t release(const TextureKey& key);
|
uint32_t release(const TextureKey &key);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取引用计数
|
* @brief 获取引用计数
|
||||||
* @param key 纹理键
|
* @param key 纹理键
|
||||||
* @return 引用计数
|
* @return 引用计数
|
||||||
*/
|
*/
|
||||||
uint32_t getRefCount(const TextureKey& key) const;
|
uint32_t getRefCount(const TextureKey &key) const;
|
||||||
|
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
// 缓存管理
|
// 缓存管理
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 检查纹理是否已缓存
|
* @brief 检查纹理是否已缓存
|
||||||
* @param key 纹理键
|
* @param key 纹理键
|
||||||
* @return 是否已缓存
|
* @return 是否已缓存
|
||||||
*/
|
*/
|
||||||
bool isCached(const TextureKey& key) const;
|
bool isCached(const TextureKey &key) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 从缓存中移除纹理
|
* @brief 从缓存中移除纹理
|
||||||
* @param key 纹理键
|
* @param key 纹理键
|
||||||
* @return 是否成功
|
* @return 是否成功
|
||||||
*/
|
*/
|
||||||
bool removeFromCache(const TextureKey& key);
|
bool removeFromCache(const TextureKey &key);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 垃圾回收(移除引用计数为 0 的纹理)
|
* @brief 垃圾回收(移除引用计数为 0 的纹理)
|
||||||
* @return 移除的纹理数量
|
* @return 移除的纹理数量
|
||||||
*/
|
*/
|
||||||
size_t collectGarbage();
|
size_t collectGarbage();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 清空所有缓存
|
* @brief 清空所有缓存
|
||||||
*/
|
*/
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
// 内存管理
|
// 内存管理
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取当前内存使用量
|
* @brief 获取当前内存使用量
|
||||||
* @return 内存使用量(字节)
|
* @return 内存使用量(字节)
|
||||||
*/
|
*/
|
||||||
size_t getMemoryUsage() const;
|
size_t getMemoryUsage() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 设置最大内存使用量
|
* @brief 设置最大内存使用量
|
||||||
* @param maxMemory 最大内存使用量(0 表示无限制)
|
* @param maxMemory 最大内存使用量(0 表示无限制)
|
||||||
*/
|
*/
|
||||||
void setMaxMemoryUsage(size_t maxMemory);
|
void setMaxMemoryUsage(size_t maxMemory);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取最大内存使用量
|
* @brief 获取最大内存使用量
|
||||||
* @return 最大内存使用量
|
* @return 最大内存使用量
|
||||||
*/
|
*/
|
||||||
size_t getMaxMemoryUsage() const { return maxMemoryUsage_; }
|
size_t getMaxMemoryUsage() const { return maxMemoryUsage_; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 执行 LRU 淘汰
|
* @brief 执行 LRU 淘汰
|
||||||
* @param targetMemory 目标内存使用量
|
* @param targetMemory 目标内存使用量
|
||||||
* @return 淘汰的纹理数量
|
* @return 淘汰的纹理数量
|
||||||
*/
|
*/
|
||||||
size_t evictLRU(size_t targetMemory = 0);
|
size_t evictLRU(size_t targetMemory = 0);
|
||||||
|
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
// 统计信息
|
// 统计信息
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取统计信息
|
* @brief 获取统计信息
|
||||||
* @return 统计信息
|
* @return 统计信息
|
||||||
*/
|
*/
|
||||||
Stats getStats() const;
|
Stats getStats() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 重置统计信息
|
* @brief 重置统计信息
|
||||||
*/
|
*/
|
||||||
void resetStats();
|
void resetStats();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* @brief 计算纹理内存大小
|
* @brief 计算纹理内存大小
|
||||||
* @param texture 纹理对象
|
* @param texture 纹理对象
|
||||||
* @return 内存大小(字节)
|
* @return 内存大小(字节)
|
||||||
*/
|
*/
|
||||||
static size_t calculateTextureMemory(const Texture* texture);
|
static size_t calculateTextureMemory(const Texture *texture);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 检查是否需要淘汰
|
* @brief 检查是否需要淘汰
|
||||||
* @return 是否需要淘汰
|
* @return 是否需要淘汰
|
||||||
*/
|
*/
|
||||||
bool needsEviction() const;
|
bool needsEviction() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 尝试自动淘汰
|
* @brief 尝试自动淘汰
|
||||||
*/
|
*/
|
||||||
void tryAutoEvict();
|
void tryAutoEvict();
|
||||||
|
|
||||||
Scene* scene_; // 场景指针
|
Scene *scene_; // 场景指针
|
||||||
mutable std::mutex mutex_; // 互斥锁
|
mutable std::mutex mutex_; // 互斥锁
|
||||||
std::unordered_map<TextureKey, TexturePoolEntry, TextureKeyHash> cache_; // 纹理缓存
|
std::unordered_map<TextureKey, TexturePoolEntry, TextureKeyHash>
|
||||||
|
cache_; // 纹理缓存
|
||||||
|
|
||||||
size_t maxMemoryUsage_; // 最大内存使用量
|
size_t maxMemoryUsage_; // 最大内存使用量
|
||||||
size_t currentMemoryUsage_; // 当前内存使用量
|
size_t currentMemoryUsage_; // 当前内存使用量
|
||||||
|
|
||||||
// 统计信息
|
// 统计信息
|
||||||
mutable std::atomic<size_t> cacheHits_;
|
mutable std::atomic<size_t> cacheHits_;
|
||||||
mutable std::atomic<size_t> cacheMisses_;
|
mutable std::atomic<size_t> cacheMisses_;
|
||||||
mutable std::atomic<size_t> evictionCount_;
|
mutable std::atomic<size_t> evictionCount_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
#version 300 es
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
in vec4 v_color;
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
fragColor = v_color;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
#version 300 es
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 a_position;
|
||||||
|
layout(location = 1) in vec4 a_color;
|
||||||
|
|
||||||
|
uniform mat4 u_viewProjection;
|
||||||
|
|
||||||
|
out vec4 v_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_Position = u_viewProjection * vec4(a_position, 0.0, 1.0);
|
||||||
|
v_color = a_color;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
#version 300 es
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
in vec2 v_texCoord;
|
||||||
|
in vec4 v_color;
|
||||||
|
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
uniform float u_opacity;
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 texColor = texture(u_texture, v_texCoord);
|
||||||
|
fragColor = texColor * v_color;
|
||||||
|
fragColor.a *= u_opacity;
|
||||||
|
|
||||||
|
if (fragColor.a < 0.01) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
#version 300 es
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 a_position;
|
||||||
|
layout(location = 1) in vec2 a_texCoord;
|
||||||
|
layout(location = 2) in vec4 a_color;
|
||||||
|
|
||||||
|
uniform mat4 u_viewProjection;
|
||||||
|
uniform mat4 u_model;
|
||||||
|
|
||||||
|
out vec2 v_texCoord;
|
||||||
|
out vec4 v_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
|
||||||
|
v_texCoord = a_texCoord;
|
||||||
|
v_color = a_color;
|
||||||
|
}
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
// ============================================
|
|
||||||
// Extra2D Combined Shader File
|
|
||||||
// Name: font
|
|
||||||
// Category: builtin
|
|
||||||
// Version: 1.0
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
#meta
|
|
||||||
{
|
|
||||||
"name": "font",
|
|
||||||
"category": "builtin",
|
|
||||||
"author": "Extra2D Team",
|
|
||||||
"description": "字体渲染Shader,支持SDF"
|
|
||||||
}
|
|
||||||
|
|
||||||
#vertex
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec2 a_texCoord;
|
|
||||||
layout(location = 2) in vec4 a_color;
|
|
||||||
|
|
||||||
uniform mat4 u_viewProjection;
|
|
||||||
uniform mat4 u_model;
|
|
||||||
|
|
||||||
out vec2 v_texCoord;
|
|
||||||
out vec4 v_color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
|
|
||||||
v_texCoord = a_texCoord;
|
|
||||||
v_color = a_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fragment
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform float u_smoothing;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
float dist = texture(u_texture, v_texCoord).r;
|
|
||||||
float alpha = smoothstep(0.5 - u_smoothing, 0.5 + u_smoothing, dist);
|
|
||||||
fragColor = vec4(v_color.rgb, v_color.a * alpha);
|
|
||||||
|
|
||||||
if (fragColor.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
// ============================================
|
|
||||||
// Extra2D Combined Shader File
|
|
||||||
// Name: particle
|
|
||||||
// Category: builtin
|
|
||||||
// Version: 1.0
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
#meta
|
|
||||||
{
|
|
||||||
"name": "particle",
|
|
||||||
"category": "builtin",
|
|
||||||
"author": "Extra2D Team",
|
|
||||||
"description": "粒子渲染Shader"
|
|
||||||
}
|
|
||||||
|
|
||||||
#vertex
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec2 a_texCoord;
|
|
||||||
layout(location = 2) in vec4 a_color;
|
|
||||||
|
|
||||||
uniform mat4 u_viewProjection;
|
|
||||||
|
|
||||||
out vec2 v_texCoord;
|
|
||||||
out vec4 v_color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = u_viewProjection * vec4(a_position, 0.0, 1.0);
|
|
||||||
v_texCoord = a_texCoord;
|
|
||||||
v_color = a_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fragment
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec4 texColor = texture(u_texture, v_texCoord);
|
|
||||||
fragColor = texColor * v_color;
|
|
||||||
|
|
||||||
if (fragColor.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
// ============================================
|
|
||||||
// Extra2D Combined Shader File
|
|
||||||
// Name: postprocess
|
|
||||||
// Category: builtin
|
|
||||||
// Version: 1.0
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
#meta
|
|
||||||
{
|
|
||||||
"name": "postprocess",
|
|
||||||
"category": "builtin",
|
|
||||||
"author": "Extra2D Team",
|
|
||||||
"description": "后处理基础Shader"
|
|
||||||
}
|
|
||||||
|
|
||||||
#vertex
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec2 a_texCoord;
|
|
||||||
|
|
||||||
out vec2 v_texCoord;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
||||||
v_texCoord = a_texCoord;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fragment
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
fragColor = texture(u_texture, v_texCoord);
|
|
||||||
}
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
// ============================================
|
|
||||||
// Extra2D Combined Shader File
|
|
||||||
// Name: shape
|
|
||||||
// Category: builtin
|
|
||||||
// Version: 1.0
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
#meta
|
|
||||||
{
|
|
||||||
"name": "shape",
|
|
||||||
"category": "builtin",
|
|
||||||
"author": "Extra2D Team",
|
|
||||||
"description": "形状渲染Shader,支持顶点颜色批处理"
|
|
||||||
}
|
|
||||||
|
|
||||||
#vertex
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec4 a_color;
|
|
||||||
|
|
||||||
uniform mat4 u_viewProjection;
|
|
||||||
|
|
||||||
out vec4 v_color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = u_viewProjection * vec4(a_position, 0.0, 1.0);
|
|
||||||
v_color = a_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fragment
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
fragColor = v_color;
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
// ============================================
|
|
||||||
// Extra2D Combined Shader File
|
|
||||||
// Name: sprite
|
|
||||||
// Category: builtin
|
|
||||||
// Version: 1.0
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
#meta
|
|
||||||
{
|
|
||||||
"name": "sprite",
|
|
||||||
"category": "builtin",
|
|
||||||
"author": "Extra2D Team",
|
|
||||||
"description": "标准2D精灵渲染Shader",
|
|
||||||
"uniforms": {
|
|
||||||
"u_viewProjection": { "type": "mat4", "description": "视图投影矩阵" },
|
|
||||||
"u_model": { "type": "mat4", "description": "模型矩阵" },
|
|
||||||
"u_opacity": { "type": "float", "default": 1.0, "description": "透明度" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#vertex
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec2 a_texCoord;
|
|
||||||
layout(location = 2) in vec4 a_color;
|
|
||||||
|
|
||||||
uniform mat4 u_viewProjection;
|
|
||||||
uniform mat4 u_model;
|
|
||||||
|
|
||||||
out vec2 v_texCoord;
|
|
||||||
out vec4 v_color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
|
|
||||||
v_texCoord = a_texCoord;
|
|
||||||
v_color = a_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fragment
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform float u_opacity;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec4 texColor = texture(u_texture, v_texCoord);
|
|
||||||
fragColor = texColor * v_color;
|
|
||||||
fragColor.a *= u_opacity;
|
|
||||||
|
|
||||||
if (fragColor.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
// ============================================
|
|
||||||
// Extra2D Combined Shader File
|
|
||||||
// Name: blur
|
|
||||||
// Category: effects
|
|
||||||
// Version: 1.0
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
#meta
|
|
||||||
{
|
|
||||||
"name": "blur",
|
|
||||||
"category": "effects",
|
|
||||||
"author": "Extra2D Team",
|
|
||||||
"description": "模糊特效Shader"
|
|
||||||
}
|
|
||||||
|
|
||||||
#vertex
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec2 a_texCoord;
|
|
||||||
layout(location = 2) in vec4 a_color;
|
|
||||||
|
|
||||||
uniform mat4 u_viewProjection;
|
|
||||||
uniform mat4 u_model;
|
|
||||||
|
|
||||||
out vec2 v_texCoord;
|
|
||||||
out vec4 v_color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
|
|
||||||
v_texCoord = a_texCoord;
|
|
||||||
v_color = a_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fragment
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform float u_radius;
|
|
||||||
uniform vec2 u_textureSize;
|
|
||||||
uniform float u_opacity;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec2 texel = u_radius / u_textureSize;
|
|
||||||
|
|
||||||
vec4 sum = vec4(0.0);
|
|
||||||
sum += texture(u_texture, v_texCoord + texel * vec2(-1.0, -1.0));
|
|
||||||
sum += texture(u_texture, v_texCoord + texel * vec2( 0.0, -1.0));
|
|
||||||
sum += texture(u_texture, v_texCoord + texel * vec2( 1.0, -1.0));
|
|
||||||
sum += texture(u_texture, v_texCoord + texel * vec2(-1.0, 0.0));
|
|
||||||
sum += texture(u_texture, v_texCoord + texel * vec2( 0.0, 0.0));
|
|
||||||
sum += texture(u_texture, v_texCoord + texel * vec2( 1.0, 0.0));
|
|
||||||
sum += texture(u_texture, v_texCoord + texel * vec2(-1.0, 1.0));
|
|
||||||
sum += texture(u_texture, v_texCoord + texel * vec2( 0.0, 1.0));
|
|
||||||
sum += texture(u_texture, v_texCoord + texel * vec2( 1.0, 1.0));
|
|
||||||
|
|
||||||
vec4 texColor = sum / 9.0;
|
|
||||||
fragColor = texColor * v_color;
|
|
||||||
fragColor.a *= u_opacity;
|
|
||||||
|
|
||||||
if (fragColor.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
// ============================================
|
|
||||||
// Extra2D Combined Shader File
|
|
||||||
// Name: distortion
|
|
||||||
// Category: effects
|
|
||||||
// Version: 1.0
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
#meta
|
|
||||||
{
|
|
||||||
"name": "distortion",
|
|
||||||
"category": "effects",
|
|
||||||
"author": "Extra2D Team",
|
|
||||||
"description": "扭曲特效Shader"
|
|
||||||
}
|
|
||||||
|
|
||||||
#vertex
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec2 a_texCoord;
|
|
||||||
layout(location = 2) in vec4 a_color;
|
|
||||||
|
|
||||||
uniform mat4 u_viewProjection;
|
|
||||||
uniform mat4 u_model;
|
|
||||||
|
|
||||||
out vec2 v_texCoord;
|
|
||||||
out vec4 v_color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
|
|
||||||
v_texCoord = a_texCoord;
|
|
||||||
v_color = a_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fragment
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform float u_distortionAmount;
|
|
||||||
uniform float u_time;
|
|
||||||
uniform float u_timeScale;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec2 uv = v_texCoord;
|
|
||||||
|
|
||||||
float t = u_time * u_timeScale;
|
|
||||||
float dx = sin(uv.y * 10.0 + t) * u_distortionAmount;
|
|
||||||
float dy = cos(uv.x * 10.0 + t) * u_distortionAmount;
|
|
||||||
uv += vec2(dx, dy);
|
|
||||||
|
|
||||||
vec4 texColor = texture(u_texture, uv);
|
|
||||||
fragColor = texColor * v_color;
|
|
||||||
|
|
||||||
if (fragColor.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
// ============================================
|
|
||||||
// Extra2D Combined Shader File
|
|
||||||
// Name: grayscale
|
|
||||||
// Category: effects
|
|
||||||
// Version: 1.0
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
#meta
|
|
||||||
{
|
|
||||||
"name": "grayscale",
|
|
||||||
"category": "effects",
|
|
||||||
"author": "Extra2D Team",
|
|
||||||
"description": "灰度特效Shader"
|
|
||||||
}
|
|
||||||
|
|
||||||
#vertex
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec2 a_texCoord;
|
|
||||||
layout(location = 2) in vec4 a_color;
|
|
||||||
|
|
||||||
uniform mat4 u_viewProjection;
|
|
||||||
uniform mat4 u_model;
|
|
||||||
|
|
||||||
out vec2 v_texCoord;
|
|
||||||
out vec4 v_color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
|
|
||||||
v_texCoord = a_texCoord;
|
|
||||||
v_color = a_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fragment
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform float u_intensity;
|
|
||||||
uniform float u_opacity;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec4 texColor = texture(u_texture, v_texCoord) * v_color;
|
|
||||||
|
|
||||||
float gray = dot(texColor.rgb, vec3(0.299, 0.587, 0.114));
|
|
||||||
texColor.rgb = mix(texColor.rgb, vec3(gray), u_intensity);
|
|
||||||
|
|
||||||
fragColor = texColor;
|
|
||||||
fragColor.a *= u_opacity;
|
|
||||||
|
|
||||||
if (fragColor.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
// ============================================
|
|
||||||
// Extra2D Combined Shader File
|
|
||||||
// Name: grayscale_outline
|
|
||||||
// Category: effects
|
|
||||||
// Version: 1.0
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
#meta
|
|
||||||
{
|
|
||||||
"name": "grayscale_outline",
|
|
||||||
"category": "effects",
|
|
||||||
"author": "Extra2D Team",
|
|
||||||
"description": "灰度+描边组合效果Shader"
|
|
||||||
}
|
|
||||||
|
|
||||||
#vertex
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec2 a_texCoord;
|
|
||||||
layout(location = 2) in vec4 a_color;
|
|
||||||
|
|
||||||
uniform mat4 u_viewProjection;
|
|
||||||
uniform mat4 u_model;
|
|
||||||
|
|
||||||
out vec2 v_texCoord;
|
|
||||||
out vec4 v_color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
|
|
||||||
v_texCoord = a_texCoord;
|
|
||||||
v_color = a_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fragment
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform float u_grayIntensity;
|
|
||||||
uniform vec4 u_outlineColor;
|
|
||||||
uniform float u_thickness;
|
|
||||||
uniform vec2 u_textureSize;
|
|
||||||
uniform float u_opacity;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec4 color = texture(u_texture, v_texCoord) * v_color;
|
|
||||||
|
|
||||||
float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));
|
|
||||||
color.rgb = mix(color.rgb, vec3(gray), u_grayIntensity);
|
|
||||||
|
|
||||||
float alpha = 0.0;
|
|
||||||
vec2 offset = u_thickness / u_textureSize;
|
|
||||||
|
|
||||||
alpha += texture(u_texture, v_texCoord + vec2(-offset.x, 0.0)).a;
|
|
||||||
alpha += texture(u_texture, v_texCoord + vec2(offset.x, 0.0)).a;
|
|
||||||
alpha += texture(u_texture, v_texCoord + vec2(0.0, -offset.y)).a;
|
|
||||||
alpha += texture(u_texture, v_texCoord + vec2(0.0, offset.y)).a;
|
|
||||||
|
|
||||||
if (color.a < 0.1 && alpha > 0.0) {
|
|
||||||
fragColor = u_outlineColor;
|
|
||||||
} else {
|
|
||||||
fragColor = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
fragColor.a *= u_opacity;
|
|
||||||
|
|
||||||
if (fragColor.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
// ============================================
|
|
||||||
// Extra2D Combined Shader File
|
|
||||||
// Name: invert
|
|
||||||
// Category: effects
|
|
||||||
// Version: 1.0
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
#meta
|
|
||||||
{
|
|
||||||
"name": "invert",
|
|
||||||
"category": "effects",
|
|
||||||
"author": "Extra2D Team",
|
|
||||||
"description": "反相特效Shader"
|
|
||||||
}
|
|
||||||
|
|
||||||
#vertex
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec2 a_texCoord;
|
|
||||||
layout(location = 2) in vec4 a_color;
|
|
||||||
|
|
||||||
uniform mat4 u_viewProjection;
|
|
||||||
uniform mat4 u_model;
|
|
||||||
|
|
||||||
out vec2 v_texCoord;
|
|
||||||
out vec4 v_color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
|
|
||||||
v_texCoord = a_texCoord;
|
|
||||||
v_color = a_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fragment
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform float u_strength;
|
|
||||||
uniform float u_opacity;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec4 texColor = texture(u_texture, v_texCoord) * v_color;
|
|
||||||
vec3 inverted = vec3(1.0) - texColor.rgb;
|
|
||||||
texColor.rgb = mix(texColor.rgb, inverted, u_strength);
|
|
||||||
|
|
||||||
fragColor = texColor;
|
|
||||||
fragColor.a *= u_opacity;
|
|
||||||
|
|
||||||
if (fragColor.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
// ============================================
|
|
||||||
// Extra2D Combined Shader File
|
|
||||||
// Name: outline
|
|
||||||
// Category: effects
|
|
||||||
// Version: 1.0
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
#meta
|
|
||||||
{
|
|
||||||
"name": "outline",
|
|
||||||
"category": "effects",
|
|
||||||
"author": "Extra2D Team",
|
|
||||||
"description": "描边特效Shader"
|
|
||||||
}
|
|
||||||
|
|
||||||
#vertex
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec2 a_texCoord;
|
|
||||||
layout(location = 2) in vec4 a_color;
|
|
||||||
|
|
||||||
uniform mat4 u_viewProjection;
|
|
||||||
uniform mat4 u_model;
|
|
||||||
|
|
||||||
out vec2 v_texCoord;
|
|
||||||
out vec4 v_color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
|
|
||||||
v_texCoord = a_texCoord;
|
|
||||||
v_color = a_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fragment
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform vec4 u_outlineColor;
|
|
||||||
uniform float u_thickness;
|
|
||||||
uniform vec2 u_textureSize;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec4 color = texture(u_texture, v_texCoord);
|
|
||||||
|
|
||||||
float alpha = 0.0;
|
|
||||||
vec2 offset = u_thickness / u_textureSize;
|
|
||||||
|
|
||||||
alpha += texture(u_texture, v_texCoord + vec2(-offset.x, 0.0)).a;
|
|
||||||
alpha += texture(u_texture, v_texCoord + vec2(offset.x, 0.0)).a;
|
|
||||||
alpha += texture(u_texture, v_texCoord + vec2(0.0, -offset.y)).a;
|
|
||||||
alpha += texture(u_texture, v_texCoord + vec2(0.0, offset.y)).a;
|
|
||||||
|
|
||||||
if (color.a < 0.1 && alpha > 0.0) {
|
|
||||||
fragColor = u_outlineColor;
|
|
||||||
} else {
|
|
||||||
fragColor = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fragColor.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
// ============================================
|
|
||||||
// Extra2D Combined Shader File
|
|
||||||
// Name: pixelate
|
|
||||||
// Category: effects
|
|
||||||
// Version: 1.0
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
#meta
|
|
||||||
{
|
|
||||||
"name": "pixelate",
|
|
||||||
"category": "effects",
|
|
||||||
"author": "Extra2D Team",
|
|
||||||
"description": "像素化特效Shader"
|
|
||||||
}
|
|
||||||
|
|
||||||
#vertex
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec2 a_texCoord;
|
|
||||||
layout(location = 2) in vec4 a_color;
|
|
||||||
|
|
||||||
uniform mat4 u_viewProjection;
|
|
||||||
uniform mat4 u_model;
|
|
||||||
|
|
||||||
out vec2 v_texCoord;
|
|
||||||
out vec4 v_color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
|
|
||||||
v_texCoord = a_texCoord;
|
|
||||||
v_color = a_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fragment
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform float u_pixelSize;
|
|
||||||
uniform vec2 u_textureSize;
|
|
||||||
uniform float u_opacity;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec2 pixel = u_pixelSize / u_textureSize;
|
|
||||||
vec2 uv = floor(v_texCoord / pixel) * pixel + pixel * 0.5;
|
|
||||||
|
|
||||||
vec4 texColor = texture(u_texture, uv);
|
|
||||||
fragColor = texColor * v_color;
|
|
||||||
fragColor.a *= u_opacity;
|
|
||||||
|
|
||||||
if (fragColor.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
// ============================================
|
|
||||||
// Extra2D Combined Shader File
|
|
||||||
// Name: pixelate_invert
|
|
||||||
// Category: effects
|
|
||||||
// Version: 1.0
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
#meta
|
|
||||||
{
|
|
||||||
"name": "pixelate_invert",
|
|
||||||
"category": "effects",
|
|
||||||
"author": "Extra2D Team",
|
|
||||||
"description": "像素化+反相组合效果Shader"
|
|
||||||
}
|
|
||||||
|
|
||||||
#vertex
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec2 a_texCoord;
|
|
||||||
layout(location = 2) in vec4 a_color;
|
|
||||||
|
|
||||||
uniform mat4 u_viewProjection;
|
|
||||||
uniform mat4 u_model;
|
|
||||||
|
|
||||||
out vec2 v_texCoord;
|
|
||||||
out vec4 v_color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
|
|
||||||
v_texCoord = a_texCoord;
|
|
||||||
v_color = a_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fragment
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform float u_pixelSize;
|
|
||||||
uniform vec2 u_textureSize;
|
|
||||||
uniform float u_invertStrength;
|
|
||||||
uniform float u_opacity;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec2 pixel = u_pixelSize / u_textureSize;
|
|
||||||
vec2 uv = floor(v_texCoord / pixel) * pixel + pixel * 0.5;
|
|
||||||
|
|
||||||
vec4 color = texture(u_texture, uv) * v_color;
|
|
||||||
|
|
||||||
vec3 inverted = 1.0 - color.rgb;
|
|
||||||
color.rgb = mix(color.rgb, inverted, u_invertStrength);
|
|
||||||
|
|
||||||
fragColor = color;
|
|
||||||
fragColor.a *= u_opacity;
|
|
||||||
|
|
||||||
if (fragColor.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
// ============================================
|
|
||||||
// Extra2D Combined Shader File
|
|
||||||
// Name: water
|
|
||||||
// Category: effects
|
|
||||||
// Version: 1.0
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
#meta
|
|
||||||
{
|
|
||||||
"name": "water",
|
|
||||||
"category": "effects",
|
|
||||||
"author": "Extra2D Team",
|
|
||||||
"description": "水波纹特效Shader"
|
|
||||||
}
|
|
||||||
|
|
||||||
#vertex
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec2 a_texCoord;
|
|
||||||
layout(location = 2) in vec4 a_color;
|
|
||||||
|
|
||||||
uniform mat4 u_viewProjection;
|
|
||||||
uniform mat4 u_model;
|
|
||||||
|
|
||||||
out vec2 v_texCoord;
|
|
||||||
out vec4 v_color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
|
|
||||||
v_texCoord = a_texCoord;
|
|
||||||
v_color = a_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fragment
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform float u_waveSpeed;
|
|
||||||
uniform float u_waveAmplitude;
|
|
||||||
uniform float u_waveFrequency;
|
|
||||||
uniform float u_time;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec2 uv = v_texCoord;
|
|
||||||
|
|
||||||
float wave = sin(uv.y * u_waveFrequency + u_time * u_waveSpeed) * u_waveAmplitude;
|
|
||||||
uv.x += wave;
|
|
||||||
|
|
||||||
vec4 texColor = texture(u_texture, uv);
|
|
||||||
fragColor = texColor * v_color;
|
|
||||||
|
|
||||||
if (fragColor.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"name": "shape",
|
||||||
|
"category": "builtin",
|
||||||
|
"version": "1.0",
|
||||||
|
"description": "基本形状渲染Shader",
|
||||||
|
"uniforms": {
|
||||||
|
"u_viewProjection": { "type": "mat4", "description": "视图投影矩阵" }
|
||||||
|
},
|
||||||
|
"samplers": {},
|
||||||
|
"backends": {
|
||||||
|
"opengl": {
|
||||||
|
"vertex": "backends/opengl/builtin/shape.vert",
|
||||||
|
"fragment": "backends/opengl/builtin/shape.frag"
|
||||||
|
},
|
||||||
|
"vulkan": {
|
||||||
|
"vertex": "backends/vulkan/builtin/shape.vert.spv",
|
||||||
|
"fragment": "backends/vulkan/builtin/shape.frag.spv"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"name": "sprite",
|
||||||
|
"category": "builtin",
|
||||||
|
"version": "1.0",
|
||||||
|
"description": "标准2D精灵渲染Shader",
|
||||||
|
"uniforms": {
|
||||||
|
"u_viewProjection": { "type": "mat4", "description": "视图投影矩阵" },
|
||||||
|
"u_model": { "type": "mat4", "description": "模型矩阵" },
|
||||||
|
"u_opacity": { "type": "float", "default": 1.0, "description": "透明度" }
|
||||||
|
},
|
||||||
|
"samplers": {
|
||||||
|
"u_texture": { "binding": 0, "description": "纹理采样器" }
|
||||||
|
},
|
||||||
|
"backends": {
|
||||||
|
"opengl": {
|
||||||
|
"vertex": "backends/opengl/builtin/sprite.vert",
|
||||||
|
"fragment": "backends/opengl/builtin/sprite.frag"
|
||||||
|
},
|
||||||
|
"vulkan": {
|
||||||
|
"vertex": "backends/vulkan/builtin/sprite.vert.spv",
|
||||||
|
"fragment": "backends/vulkan/builtin/sprite.frag.spv"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
#include <extra2d/graphics/backends/backend_factory.h>
|
||||||
|
|
||||||
|
// 条件编译包含对应后端实现
|
||||||
|
#ifdef E2D_BACKEND_OPENGL
|
||||||
|
#include <extra2d/graphics/backends/opengl/gl_renderer.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef E2D_BACKEND_VULKAN
|
||||||
|
#include <extra2d/graphics/backends/vulkan/vk_renderer.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <extra2d/utils/logger.h>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
BackendFactory& BackendFactory::getInstance() {
|
||||||
|
static BackendFactory instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
UniquePtr<RenderBackend> BackendFactory::createBackend(BackendType type) {
|
||||||
|
switch (type) {
|
||||||
|
#ifdef E2D_BACKEND_OPENGL
|
||||||
|
case BackendType::OpenGL:
|
||||||
|
E2D_LOG_INFO("Creating OpenGL render backend");
|
||||||
|
return makeUnique<GLRenderer>();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef E2D_BACKEND_VULKAN
|
||||||
|
case BackendType::Vulkan:
|
||||||
|
E2D_LOG_INFO("Creating Vulkan render backend");
|
||||||
|
return makeUnique<VulkanRenderer>();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
default:
|
||||||
|
E2D_LOG_ERROR("Unsupported render backend type: {}", static_cast<int>(type));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UniquePtr<RenderBackend> BackendFactory::createDefaultBackend() {
|
||||||
|
BackendType recommended = getRecommendedBackend();
|
||||||
|
return createBackend(recommended);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BackendFactory::isBackendAvailable(BackendType type) const {
|
||||||
|
switch (type) {
|
||||||
|
#ifdef E2D_BACKEND_OPENGL
|
||||||
|
case BackendType::OpenGL:
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef E2D_BACKEND_VULKAN
|
||||||
|
case BackendType::Vulkan:
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BackendType BackendFactory::getRecommendedBackend() const {
|
||||||
|
// 平台特定的默认后端选择
|
||||||
|
// 优先级:Vulkan > OpenGL
|
||||||
|
|
||||||
|
#ifdef E2D_BACKEND_VULKAN
|
||||||
|
return BackendType::Vulkan;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef E2D_BACKEND_OPENGL
|
||||||
|
return BackendType::OpenGL;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// 如果没有可用的后端,返回OpenGL作为默认值
|
||||||
|
return BackendType::OpenGL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* BackendFactory::getBackendName(BackendType type) const {
|
||||||
|
switch (type) {
|
||||||
|
case BackendType::OpenGL:
|
||||||
|
return "OpenGL";
|
||||||
|
case BackendType::Vulkan:
|
||||||
|
return "Vulkan";
|
||||||
|
case BackendType::Metal:
|
||||||
|
return "Metal";
|
||||||
|
case BackendType::D3D11:
|
||||||
|
return "D3D11";
|
||||||
|
case BackendType::D3D12:
|
||||||
|
return "D3D12";
|
||||||
|
case BackendType::OpenGLES:
|
||||||
|
return "OpenGL ES";
|
||||||
|
default:
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BackendType BackendFactory::parseBackendType(const char* name) const {
|
||||||
|
if (!name) {
|
||||||
|
return BackendType::OpenGL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::strcmp(name, "opengl") == 0 || std::strcmp(name, "OpenGL") == 0) {
|
||||||
|
return BackendType::OpenGL;
|
||||||
|
}
|
||||||
|
if (std::strcmp(name, "vulkan") == 0 || std::strcmp(name, "Vulkan") == 0) {
|
||||||
|
return BackendType::Vulkan;
|
||||||
|
}
|
||||||
|
if (std::strcmp(name, "metal") == 0 || std::strcmp(name, "Metal") == 0) {
|
||||||
|
return BackendType::Metal;
|
||||||
|
}
|
||||||
|
if (std::strcmp(name, "d3d11") == 0 || std::strcmp(name, "D3D11") == 0) {
|
||||||
|
return BackendType::D3D11;
|
||||||
|
}
|
||||||
|
if (std::strcmp(name, "d3d12") == 0 || std::strcmp(name, "D3D12") == 0) {
|
||||||
|
return BackendType::D3D12;
|
||||||
|
}
|
||||||
|
if (std::strcmp(name, "opengles") == 0 || std::strcmp(name, "OpenGLES") == 0) {
|
||||||
|
return BackendType::OpenGLES;
|
||||||
|
}
|
||||||
|
|
||||||
|
E2D_LOG_WARN("Unknown backend type '{}', defaulting to OpenGL", name);
|
||||||
|
return BackendType::OpenGL;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,276 @@
|
||||||
|
#include <extra2d/graphics/backends/opengl/gl_font_atlas.h>
|
||||||
|
#include <extra2d/utils/logger.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
// 在实现文件中定义 STB 实现
|
||||||
|
#define STB_TRUETYPE_IMPLEMENTATION
|
||||||
|
#include <stb/stb_truetype.h>
|
||||||
|
|
||||||
|
#define STB_RECT_PACK_IMPLEMENTATION
|
||||||
|
#include <stb/stb_rect_pack.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// GLFontAtlas 构造函数
|
||||||
|
// 使用 STB 库加载字体并创建纹理图集
|
||||||
|
// ============================================================================
|
||||||
|
GLFontAtlas::GLFontAtlas(const std::string &filepath, int fontSize, bool useSDF)
|
||||||
|
: useSDF_(useSDF), fontSize_(fontSize),
|
||||||
|
lineHeight_(0.0f), ascent_(0.0f), descent_(0.0f), lineGap_(0.0f),
|
||||||
|
scale_(0.0f) {
|
||||||
|
if (!initFont(filepath)) {
|
||||||
|
E2D_LOG_ERROR("Failed to initialize font: {}", filepath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建纹理图集 (使用单通道格式)
|
||||||
|
texture_ = std::make_unique<GLTexture>(ATLAS_WIDTH, ATLAS_HEIGHT, nullptr, 1);
|
||||||
|
|
||||||
|
// 预加载 ASCII 字符
|
||||||
|
std::string asciiChars = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
|
||||||
|
for (char c : asciiChars) {
|
||||||
|
char32_t codepoint = static_cast<char32_t>(static_cast<unsigned char>(c));
|
||||||
|
renderGlyph(codepoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算字体度量
|
||||||
|
int ascent, descent, lineGap;
|
||||||
|
stbtt_GetFontVMetrics(&fontInfo_, &ascent, &descent, &lineGap);
|
||||||
|
|
||||||
|
scale_ = stbtt_ScaleForPixelHeight(&fontInfo_, static_cast<float>(fontSize));
|
||||||
|
|
||||||
|
ascent_ = static_cast<float>(ascent) * scale_;
|
||||||
|
descent_ = static_cast<float>(descent) * scale_;
|
||||||
|
lineGap_ = static_cast<float>(lineGap) * scale_;
|
||||||
|
lineHeight_ = ascent_ - descent_ + lineGap_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// GLFontAtlas 析构函数
|
||||||
|
// ============================================================================
|
||||||
|
GLFontAtlas::~GLFontAtlas() {
|
||||||
|
// STB 不需要显式清理,字体数据由 vector 自动管理
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 获取字形信息
|
||||||
|
// ============================================================================
|
||||||
|
const Glyph *GLFontAtlas::getGlyph(char32_t codepoint) const {
|
||||||
|
auto it = glyphs_.find(codepoint);
|
||||||
|
if (it == glyphs_.end()) {
|
||||||
|
// 尝试渲染该字符
|
||||||
|
const_cast<GLFontAtlas *>(this)->renderGlyph(codepoint);
|
||||||
|
it = glyphs_.find(codepoint);
|
||||||
|
if (it == glyphs_.end()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回静态存储的 Glyph 数据
|
||||||
|
static Glyph glyph;
|
||||||
|
const auto &data = it->second;
|
||||||
|
glyph.width = data.width;
|
||||||
|
glyph.height = data.height;
|
||||||
|
glyph.bearingX = data.bearingX;
|
||||||
|
glyph.bearingY = data.bearingY;
|
||||||
|
glyph.advance = data.advance;
|
||||||
|
glyph.u0 = data.u0;
|
||||||
|
glyph.v0 = data.v0;
|
||||||
|
glyph.u1 = data.u1;
|
||||||
|
glyph.v1 = data.v1;
|
||||||
|
|
||||||
|
return &glyph;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 测量文本尺寸
|
||||||
|
// ============================================================================
|
||||||
|
Vec2 GLFontAtlas::measureText(const std::string &text) {
|
||||||
|
float width = 0.0f;
|
||||||
|
float maxWidth = 0.0f;
|
||||||
|
float height = lineHeight_;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < text.length();) {
|
||||||
|
// 处理 UTF-8 编码
|
||||||
|
char32_t codepoint = 0;
|
||||||
|
unsigned char c = static_cast<unsigned char>(text[i]);
|
||||||
|
|
||||||
|
if ((c & 0x80) == 0) {
|
||||||
|
// 单字节 ASCII
|
||||||
|
codepoint = c;
|
||||||
|
i++;
|
||||||
|
} else if ((c & 0xE0) == 0xC0) {
|
||||||
|
// 2字节 UTF-8
|
||||||
|
if (i + 1 >= text.length()) break;
|
||||||
|
codepoint = ((c & 0x1F) << 6) | (static_cast<unsigned char>(text[i + 1]) & 0x3F);
|
||||||
|
i += 2;
|
||||||
|
} else if ((c & 0xF0) == 0xE0) {
|
||||||
|
// 3字节 UTF-8
|
||||||
|
if (i + 2 >= text.length()) break;
|
||||||
|
codepoint = ((c & 0x0F) << 12) |
|
||||||
|
((static_cast<unsigned char>(text[i + 1]) & 0x3F) << 6) |
|
||||||
|
(static_cast<unsigned char>(text[i + 2]) & 0x3F);
|
||||||
|
i += 3;
|
||||||
|
} else if ((c & 0xF8) == 0xF0) {
|
||||||
|
// 4字节 UTF-8
|
||||||
|
if (i + 3 >= text.length()) break;
|
||||||
|
codepoint = ((c & 0x07) << 18) |
|
||||||
|
((static_cast<unsigned char>(text[i + 1]) & 0x3F) << 12) |
|
||||||
|
((static_cast<unsigned char>(text[i + 2]) & 0x3F) << 6) |
|
||||||
|
(static_cast<unsigned char>(text[i + 3]) & 0x3F);
|
||||||
|
i += 4;
|
||||||
|
} else {
|
||||||
|
// 无效的 UTF-8,跳过
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理换行
|
||||||
|
if (codepoint == '\n') {
|
||||||
|
maxWidth = std::max(maxWidth, width);
|
||||||
|
width = 0.0f;
|
||||||
|
height += lineHeight_;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Glyph* glyph = getGlyph(codepoint);
|
||||||
|
if (glyph) {
|
||||||
|
width += glyph->advance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
maxWidth = std::max(maxWidth, width);
|
||||||
|
return Vec2(maxWidth, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 初始化字体 (使用 STB)
|
||||||
|
// ============================================================================
|
||||||
|
bool GLFontAtlas::initFont(const std::string &filepath) {
|
||||||
|
// 读取字体文件到内存
|
||||||
|
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
E2D_LOG_ERROR("Failed to open font file: {}", filepath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::streamsize size = file.tellg();
|
||||||
|
file.seekg(0, std::ios::beg);
|
||||||
|
|
||||||
|
fontData_.resize(static_cast<size_t>(size));
|
||||||
|
if (!file.read(reinterpret_cast<char *>(fontData_.data()), size)) {
|
||||||
|
E2D_LOG_ERROR("Failed to read font file: {}", filepath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化 STB 字体
|
||||||
|
if (!stbtt_InitFont(&fontInfo_, fontData_.data(), 0)) {
|
||||||
|
E2D_LOG_ERROR("Failed to initialize STB font: {}", filepath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 渲染字形到图集
|
||||||
|
// ============================================================================
|
||||||
|
bool GLFontAtlas::renderGlyph(char32_t codepoint) {
|
||||||
|
if (glyphs_.find(codepoint) != glyphs_.end()) {
|
||||||
|
return true; // 已存在
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取字形索引
|
||||||
|
int glyphIndex = stbtt_FindGlyphIndex(&fontInfo_, static_cast<int>(codepoint));
|
||||||
|
if (glyphIndex == 0) {
|
||||||
|
return false; // 字符不存在
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取字形位图尺寸
|
||||||
|
int width, height, xoff, yoff;
|
||||||
|
unsigned char *bitmap = stbtt_GetCodepointBitmap(
|
||||||
|
&fontInfo_, 0, scale_, static_cast<int>(codepoint),
|
||||||
|
&width, &height, &xoff, &yoff);
|
||||||
|
|
||||||
|
if (!bitmap) {
|
||||||
|
// 空白字符(如空格)
|
||||||
|
int advance, leftSideBearing;
|
||||||
|
stbtt_GetCodepointHMetrics(&fontInfo_, static_cast<int>(codepoint), &advance, &leftSideBearing);
|
||||||
|
|
||||||
|
GlyphData data;
|
||||||
|
data.width = 0;
|
||||||
|
data.height = 0;
|
||||||
|
data.bearingX = 0;
|
||||||
|
data.bearingY = 0;
|
||||||
|
data.advance = static_cast<float>(advance) * scale_;
|
||||||
|
data.u0 = data.v0 = data.u1 = data.v1 = 0;
|
||||||
|
glyphs_[codepoint] = data;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 stb_rect_pack 进行图集打包
|
||||||
|
stbrp_context context;
|
||||||
|
stbrp_node nodes[ATLAS_WIDTH];
|
||||||
|
stbrp_init_target(&context, ATLAS_WIDTH, ATLAS_HEIGHT, nodes, ATLAS_WIDTH);
|
||||||
|
|
||||||
|
stbrp_rect rect;
|
||||||
|
rect.id = static_cast<int>(codepoint);
|
||||||
|
rect.w = static_cast<stbrp_coord>(width);
|
||||||
|
rect.h = static_cast<stbrp_coord>(height);
|
||||||
|
|
||||||
|
if (!stbrp_pack_rects(&context, &rect, 1) || !rect.was_packed) {
|
||||||
|
E2D_LOG_WARN("Failed to pack glyph for codepoint: {}", codepoint);
|
||||||
|
stbtt_FreeBitmap(bitmap, nullptr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int x = rect.x;
|
||||||
|
int y = rect.y;
|
||||||
|
|
||||||
|
// 更新纹理
|
||||||
|
updateAtlas(x, y, width, height, std::vector<uint8_t>(bitmap, bitmap + width * height));
|
||||||
|
stbtt_FreeBitmap(bitmap, nullptr);
|
||||||
|
|
||||||
|
// 获取字形度量
|
||||||
|
int advance, leftSideBearing;
|
||||||
|
stbtt_GetCodepointHMetrics(&fontInfo_, static_cast<int>(codepoint), &advance, &leftSideBearing);
|
||||||
|
|
||||||
|
int ix0, iy0, ix1, iy1;
|
||||||
|
stbtt_GetCodepointBitmapBox(&fontInfo_, static_cast<int>(codepoint), scale_, scale_, &ix0, &iy0, &ix1, &iy1);
|
||||||
|
|
||||||
|
// 存储字形数据
|
||||||
|
GlyphData data;
|
||||||
|
data.width = static_cast<float>(width);
|
||||||
|
data.height = static_cast<float>(height);
|
||||||
|
data.bearingX = static_cast<float>(xoff);
|
||||||
|
data.bearingY = static_cast<float>(-yoff); // STB 使用不同的坐标系
|
||||||
|
data.advance = static_cast<float>(advance) * scale_;
|
||||||
|
|
||||||
|
// 计算 UV 坐标
|
||||||
|
data.u0 = static_cast<float>(x) / ATLAS_WIDTH;
|
||||||
|
data.v0 = static_cast<float>(y) / ATLAS_HEIGHT;
|
||||||
|
data.u1 = static_cast<float>(x + width) / ATLAS_WIDTH;
|
||||||
|
data.v1 = static_cast<float>(y + height) / ATLAS_HEIGHT;
|
||||||
|
|
||||||
|
glyphs_[codepoint] = data;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 更新图集纹理
|
||||||
|
// ============================================================================
|
||||||
|
void GLFontAtlas::updateAtlas(int x, int y, int width, int height,
|
||||||
|
const std::vector<uint8_t> &data) {
|
||||||
|
if (texture_) {
|
||||||
|
texture_->bind();
|
||||||
|
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, GL_RED,
|
||||||
|
GL_UNSIGNED_BYTE, data.data());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -2,9 +2,10 @@
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <extra2d/graphics/memory/gpu_context.h>
|
#include <extra2d/graphics/memory/gpu_context.h>
|
||||||
#include <extra2d/graphics/opengl/gl_font_atlas.h>
|
#include <extra2d/graphics/backends/opengl/gl_font_atlas.h>
|
||||||
#include <extra2d/graphics/opengl/gl_renderer.h>
|
#include <extra2d/graphics/backends/opengl/gl_renderer.h>
|
||||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
#include <extra2d/graphics/backends/opengl/gl_texture.h>
|
||||||
|
#include <extra2d/graphics/batch/sprite_batch.h>
|
||||||
#include <extra2d/graphics/shader/shader_manager.h>
|
#include <extra2d/graphics/shader/shader_manager.h>
|
||||||
#include <extra2d/graphics/memory/vram_manager.h>
|
#include <extra2d/graphics/memory/vram_manager.h>
|
||||||
#include <extra2d/platform/iwindow.h>
|
#include <extra2d/platform/iwindow.h>
|
||||||
|
|
@ -273,9 +274,9 @@ void GLRenderer::beginSpriteBatch() { spriteBatch_.begin(viewProjection_); }
|
||||||
void GLRenderer::drawSprite(const Texture &texture, const Rect &destRect,
|
void GLRenderer::drawSprite(const Texture &texture, const Rect &destRect,
|
||||||
const Rect &srcRect, const Color &tint,
|
const Rect &srcRect, const Color &tint,
|
||||||
float rotation, const Vec2 &anchor) {
|
float rotation, const Vec2 &anchor) {
|
||||||
GLSpriteBatch::SpriteData data;
|
SpriteData data;
|
||||||
data.position = glm::vec2(destRect.origin.x, destRect.origin.y);
|
data.position = Vec2(destRect.origin.x, destRect.origin.y);
|
||||||
data.size = glm::vec2(destRect.size.width, destRect.size.height);
|
data.size = Vec2(destRect.size.width, destRect.size.height);
|
||||||
|
|
||||||
Texture *tex = const_cast<Texture *>(&texture);
|
Texture *tex = const_cast<Texture *>(&texture);
|
||||||
float texW = static_cast<float>(tex->getWidth());
|
float texW = static_cast<float>(tex->getWidth());
|
||||||
|
|
@ -287,13 +288,14 @@ void GLRenderer::drawSprite(const Texture &texture, const Rect &destRect,
|
||||||
float v1 = srcRect.origin.y / texH;
|
float v1 = srcRect.origin.y / texH;
|
||||||
float v2 = (srcRect.origin.y + srcRect.size.height) / texH;
|
float v2 = (srcRect.origin.y + srcRect.size.height) / texH;
|
||||||
|
|
||||||
data.texCoordMin = glm::vec2(glm::min(u1, u2), glm::min(v1, v2));
|
data.uvRect = Rect(
|
||||||
data.texCoordMax = glm::vec2(glm::max(u1, u2), glm::max(v1, v2));
|
Vec2(glm::min(u1, u2), glm::min(v1, v2)),
|
||||||
|
Size(glm::abs(u2 - u1), glm::abs(v2 - v1))
|
||||||
|
);
|
||||||
|
|
||||||
data.color = glm::vec4(tint.r, tint.g, tint.b, tint.a);
|
data.color = tint;
|
||||||
data.rotation = rotation * 3.14159f / 180.0f;
|
data.rotation = rotation * 3.14159f / 180.0f;
|
||||||
data.anchor = glm::vec2(anchor.x, anchor.y);
|
data.pivot = Vec2(anchor.x, anchor.y);
|
||||||
data.isSDF = false;
|
|
||||||
|
|
||||||
spriteBatch_.draw(texture, data);
|
spriteBatch_.draw(texture, data);
|
||||||
}
|
}
|
||||||
|
|
@ -580,7 +582,7 @@ void GLRenderer::drawText(const FontAtlas &font, const std::string &text,
|
||||||
float baselineY = cursorY + font.getAscent();
|
float baselineY = cursorY + font.getAscent();
|
||||||
|
|
||||||
// 收集所有字符数据用于批处理
|
// 收集所有字符数据用于批处理
|
||||||
std::vector<GLSpriteBatch::SpriteData> sprites;
|
std::vector<SpriteData> sprites;
|
||||||
sprites.reserve(text.size()); // 预分配空间
|
sprites.reserve(text.size()); // 预分配空间
|
||||||
|
|
||||||
for (char c : text) {
|
for (char c : text) {
|
||||||
|
|
@ -604,15 +606,16 @@ void GLRenderer::drawText(const FontAtlas &font, const std::string &text,
|
||||||
float xPos = penX + glyph->bearingX;
|
float xPos = penX + glyph->bearingX;
|
||||||
float yPos = baselineY + glyph->bearingY;
|
float yPos = baselineY + glyph->bearingY;
|
||||||
|
|
||||||
GLSpriteBatch::SpriteData data;
|
SpriteData data;
|
||||||
data.position = glm::vec2(xPos, yPos);
|
data.position = Vec2(xPos, yPos);
|
||||||
data.size = glm::vec2(glyph->width, glyph->height);
|
data.size = Vec2(glyph->width, glyph->height);
|
||||||
data.texCoordMin = glm::vec2(glyph->u0, glyph->v0);
|
data.uvRect = Rect(
|
||||||
data.texCoordMax = glm::vec2(glyph->u1, glyph->v1);
|
Vec2(glyph->u0, glyph->v0),
|
||||||
data.color = glm::vec4(color.r, color.g, color.b, color.a);
|
Size(glyph->u1 - glyph->u0, glyph->v1 - glyph->v0)
|
||||||
|
);
|
||||||
|
data.color = color;
|
||||||
data.rotation = 0.0f;
|
data.rotation = 0.0f;
|
||||||
data.anchor = glm::vec2(0.0f, 0.0f);
|
data.pivot = Vec2(0.0f, 0.0f);
|
||||||
data.isSDF = font.isSDF();
|
|
||||||
|
|
||||||
sprites.push_back(data);
|
sprites.push_back(data);
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#include <extra2d/graphics/opengl/gl_shader.h>
|
#include <extra2d/graphics/backends/opengl/gl_shader.h>
|
||||||
#include <extra2d/utils/logger.h>
|
#include <extra2d/utils/logger.h>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
@ -0,0 +1,189 @@
|
||||||
|
#include <extra2d/graphics/backends/opengl/gl_sprite_batch.h>
|
||||||
|
#include <extra2d/graphics/backends/opengl/gl_texture.h>
|
||||||
|
#include <extra2d/graphics/shader/shader_manager.h>
|
||||||
|
#include <extra2d/utils/logger.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
GLSpriteBatch::GLSpriteBatch()
|
||||||
|
: vao_(0), vbo_(0), ebo_(0), currentTexture_(nullptr),
|
||||||
|
drawCallCount_(0) {}
|
||||||
|
|
||||||
|
GLSpriteBatch::~GLSpriteBatch() { shutdown(); }
|
||||||
|
|
||||||
|
bool GLSpriteBatch::init() {
|
||||||
|
// 从ShaderManager获取精灵着色器
|
||||||
|
shader_ = ShaderManager::getInstance().getBuiltin("sprite");
|
||||||
|
if (!shader_) {
|
||||||
|
E2D_LOG_ERROR("Failed to get builtin sprite shader");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建 VAO, VBO, EBO
|
||||||
|
glGenVertexArrays(1, &vao_);
|
||||||
|
glGenBuffers(1, &vbo_);
|
||||||
|
glGenBuffers(1, &ebo_);
|
||||||
|
|
||||||
|
glBindVertexArray(vao_);
|
||||||
|
|
||||||
|
// 设置 VBO
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
|
||||||
|
glBufferData(GL_ARRAY_BUFFER,
|
||||||
|
SpriteBatch::MAX_VERTICES * sizeof(SpriteVertex), nullptr,
|
||||||
|
GL_DYNAMIC_DRAW);
|
||||||
|
|
||||||
|
// 设置顶点属性
|
||||||
|
glEnableVertexAttribArray(0);
|
||||||
|
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(SpriteVertex),
|
||||||
|
reinterpret_cast<void *>(offsetof(SpriteVertex, position)));
|
||||||
|
|
||||||
|
glEnableVertexAttribArray(1);
|
||||||
|
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(SpriteVertex),
|
||||||
|
reinterpret_cast<void *>(offsetof(SpriteVertex, texCoord)));
|
||||||
|
|
||||||
|
glEnableVertexAttribArray(2);
|
||||||
|
glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(SpriteVertex),
|
||||||
|
reinterpret_cast<void *>(offsetof(SpriteVertex, color)));
|
||||||
|
|
||||||
|
// 设置 EBO(索引缓冲区)- 使用 batch 层生成的静态索引
|
||||||
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo_);
|
||||||
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
|
||||||
|
batch_.getIndices().size() * sizeof(uint16_t),
|
||||||
|
batch_.getIndices().data(), GL_STATIC_DRAW);
|
||||||
|
|
||||||
|
glBindVertexArray(0);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLSpriteBatch::shutdown() {
|
||||||
|
if (ebo_ != 0) {
|
||||||
|
glDeleteBuffers(1, &ebo_);
|
||||||
|
ebo_ = 0;
|
||||||
|
}
|
||||||
|
if (vbo_ != 0) {
|
||||||
|
glDeleteBuffers(1, &vbo_);
|
||||||
|
vbo_ = 0;
|
||||||
|
}
|
||||||
|
if (vao_ != 0) {
|
||||||
|
glDeleteVertexArrays(1, &vao_);
|
||||||
|
vao_ = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLSpriteBatch::begin(const glm::mat4 &viewProjection) {
|
||||||
|
batch_.begin(viewProjection);
|
||||||
|
batches_.clear();
|
||||||
|
currentTexture_ = nullptr;
|
||||||
|
drawCallCount_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLSpriteBatch::end() {
|
||||||
|
if (batch_.getSpriteCount() > 0) {
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLSpriteBatch::draw(const Texture &texture, const SpriteData &data) {
|
||||||
|
const GLTexture *glTex = dynamic_cast<const GLTexture *>(&texture);
|
||||||
|
if (!glTex) {
|
||||||
|
E2D_LOG_WARN("Invalid texture type for sprite batch");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果纹理改变或批次已满,先提交当前批次
|
||||||
|
if (currentTexture_ != glTex || batch_.needsFlush()) {
|
||||||
|
if (batch_.getSpriteCount() > 0) {
|
||||||
|
submitBatch();
|
||||||
|
}
|
||||||
|
currentTexture_ = glTex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 batch 层生成顶点
|
||||||
|
batch_.draw(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLSpriteBatch::drawBatch(const Texture &texture,
|
||||||
|
const std::vector<SpriteData> &sprites) {
|
||||||
|
const GLTexture *glTex = dynamic_cast<const GLTexture *>(&texture);
|
||||||
|
if (!glTex) {
|
||||||
|
E2D_LOG_WARN("Invalid texture type for sprite batch");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量处理精灵
|
||||||
|
for (const auto &data : sprites) {
|
||||||
|
// 如果纹理改变或批次已满,先提交当前批次
|
||||||
|
if (currentTexture_ != glTex || batch_.needsFlush()) {
|
||||||
|
if (batch_.getSpriteCount() > 0) {
|
||||||
|
submitBatch();
|
||||||
|
}
|
||||||
|
currentTexture_ = glTex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 batch 层生成顶点
|
||||||
|
batch_.draw(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLSpriteBatch::submitBatch() {
|
||||||
|
if (batch_.getSpriteCount() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录批次信息
|
||||||
|
Batch batchInfo;
|
||||||
|
batchInfo.texture = currentTexture_;
|
||||||
|
batchInfo.startVertex = 0; // 每次提交都是新的缓冲区
|
||||||
|
batchInfo.vertexCount = batch_.getSpriteCount() * 4;
|
||||||
|
batches_.push_back(batchInfo);
|
||||||
|
|
||||||
|
// 绑定着色器并设置uniform
|
||||||
|
if (shader_) {
|
||||||
|
shader_->bind();
|
||||||
|
// 注意:batch 层不存储 viewProjection,需要外部传入
|
||||||
|
// 这里简化处理,实际应该在 begin 时设置
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传顶点数据
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
|
||||||
|
glBufferSubData(GL_ARRAY_BUFFER, 0,
|
||||||
|
batch_.getVertices().size() * sizeof(SpriteVertex),
|
||||||
|
batch_.getVertices().data());
|
||||||
|
|
||||||
|
glBindVertexArray(vao_);
|
||||||
|
|
||||||
|
// 绘制
|
||||||
|
currentTexture_->bind(0);
|
||||||
|
|
||||||
|
size_t indexCount = batch_.getSpriteCount() * 6;
|
||||||
|
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(indexCount),
|
||||||
|
GL_UNSIGNED_SHORT, nullptr);
|
||||||
|
|
||||||
|
drawCallCount_++;
|
||||||
|
|
||||||
|
// 清空 batch 层,准备下一批
|
||||||
|
batch_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLSpriteBatch::flush() {
|
||||||
|
// 提交最后的批次
|
||||||
|
if (batch_.getSpriteCount() > 0) {
|
||||||
|
submitBatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绑定着色器并设置uniform
|
||||||
|
if (shader_) {
|
||||||
|
shader_->bind();
|
||||||
|
// 这里需要从某处获取 viewProjection
|
||||||
|
// 简化处理:假设 shader 已经在 begin 时设置了 viewProjection
|
||||||
|
shader_->setInt("u_texture", 0);
|
||||||
|
shader_->setFloat("u_opacity", 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置状态
|
||||||
|
batches_.clear();
|
||||||
|
currentTexture_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
#include <extra2d/graphics/backends/opengl/gl_texture.h>
|
||||||
#include <extra2d/graphics/memory/gpu_context.h>
|
#include <extra2d/graphics/memory/gpu_context.h>
|
||||||
#include <extra2d/graphics/memory/vram_manager.h>
|
#include <extra2d/graphics/memory/vram_manager.h>
|
||||||
#define STB_IMAGE_IMPLEMENTATION
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
#include <extra2d/graphics/backends/vulkan/vk_renderer.h>
|
||||||
|
#include <extra2d/utils/logger.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
VulkanRenderer::VulkanRenderer() = default;
|
||||||
|
|
||||||
|
VulkanRenderer::~VulkanRenderer() {
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VulkanRenderer::init(IWindow* window) {
|
||||||
|
E2D_LOG_WARN("Vulkan renderer is not fully implemented yet");
|
||||||
|
initialized_ = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::shutdown() {
|
||||||
|
initialized_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::beginFrame(const Color &clearColor) {
|
||||||
|
// TODO: 实现Vulkan帧开始
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::endFrame() {
|
||||||
|
// TODO: 实现Vulkan帧结束
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::setViewport(int x, int y, int width, int height) {
|
||||||
|
// TODO: 实现视口设置
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::setVSync(bool enabled) {
|
||||||
|
// TODO: 实现垂直同步设置
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::setBlendMode(BlendMode mode) {
|
||||||
|
// TODO: 实现混合模式设置
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::setViewProjection(const glm::mat4 &matrix) {
|
||||||
|
// TODO: 实现视图投影矩阵设置
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::pushTransform(const glm::mat4 &transform) {
|
||||||
|
// TODO: 实现变换矩阵入栈
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::popTransform() {
|
||||||
|
// TODO: 实现变换矩阵出栈
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::mat4 VulkanRenderer::getCurrentTransform() const {
|
||||||
|
return glm::mat4(1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ptr<Texture> VulkanRenderer::createTexture(int width, int height, const uint8_t *pixels, int channels) {
|
||||||
|
// TODO: 实现Vulkan纹理创建
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ptr<Texture> VulkanRenderer::loadTexture(const std::string &filepath) {
|
||||||
|
// TODO: 实现Vulkan纹理加载
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::beginSpriteBatch() {
|
||||||
|
// TODO: 实现精灵批处理开始
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::drawSprite(const Texture &texture, const Rect &destRect,
|
||||||
|
const Rect &srcRect, const Color &tint, float rotation,
|
||||||
|
const Vec2 &anchor) {
|
||||||
|
// TODO: 实现精灵绘制
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::drawSprite(const Texture &texture, const Vec2 &position,
|
||||||
|
const Color &tint) {
|
||||||
|
// TODO: 实现简化精灵绘制
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::endSpriteBatch() {
|
||||||
|
// TODO: 实现精灵批处理结束
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::drawLine(const Vec2 &start, const Vec2 &end, const Color &color,
|
||||||
|
float width) {
|
||||||
|
// TODO: 实现线条绘制
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::drawRect(const Rect &rect, const Color &color, float width) {
|
||||||
|
// TODO: 实现矩形边框绘制
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::fillRect(const Rect &rect, const Color &color) {
|
||||||
|
// TODO: 实现矩形填充
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::drawCircle(const Vec2 ¢er, float radius, const Color &color,
|
||||||
|
int segments, float width) {
|
||||||
|
// TODO: 实现圆形边框绘制
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::fillCircle(const Vec2 ¢er, float radius, const Color &color,
|
||||||
|
int segments) {
|
||||||
|
// TODO: 实现圆形填充
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::drawTriangle(const Vec2 &p1, const Vec2 &p2, const Vec2 &p3,
|
||||||
|
const Color &color, float width) {
|
||||||
|
// TODO: 实现三角形边框绘制
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::fillTriangle(const Vec2 &p1, const Vec2 &p2, const Vec2 &p3,
|
||||||
|
const Color &color) {
|
||||||
|
// TODO: 实现三角形填充
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::drawPolygon(const std::vector<Vec2> &points, const Color &color,
|
||||||
|
float width) {
|
||||||
|
// TODO: 实现多边形边框绘制
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::fillPolygon(const std::vector<Vec2> &points,
|
||||||
|
const Color &color) {
|
||||||
|
// TODO: 实现多边形填充
|
||||||
|
}
|
||||||
|
|
||||||
|
Ptr<FontAtlas> VulkanRenderer::createFontAtlas(const std::string &filepath, int fontSize,
|
||||||
|
bool useSDF) {
|
||||||
|
// TODO: 实现字体图集创建
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::drawText(const FontAtlas &font, const std::string &text,
|
||||||
|
const Vec2 &position, const Color &color) {
|
||||||
|
// TODO: 实现文本绘制
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::drawText(const FontAtlas &font, const std::string &text, float x,
|
||||||
|
float y, const Color &color) {
|
||||||
|
// TODO: 实现文本绘制
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::resetStats() {
|
||||||
|
stats_ = Stats{};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,200 @@
|
||||||
|
#include <extra2d/graphics/batch/sprite_batch.h>
|
||||||
|
#include <extra2d/utils/logger.h>
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// TrigLookup 实现 - 三角函数查表
|
||||||
|
// ============================================================================
|
||||||
|
TrigLookup::TrigLookup() {
|
||||||
|
constexpr float PI = 3.14159265359f;
|
||||||
|
constexpr float DEG2RAD = PI / 180.0f;
|
||||||
|
|
||||||
|
for (int i = 0; i < TABLE_SIZE; ++i) {
|
||||||
|
float angle = static_cast<float>(i) * (360.0f / TABLE_SIZE) * DEG2RAD;
|
||||||
|
sinTable_[i] = std::sin(angle);
|
||||||
|
cosTable_[i] = std::cos(angle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float TrigLookup::sin(int angle) const {
|
||||||
|
// 规范化角度到 0-360
|
||||||
|
angle = ((angle % 360) + 360) % 360;
|
||||||
|
return sinTable_[angle * 4];
|
||||||
|
}
|
||||||
|
|
||||||
|
float TrigLookup::cos(int angle) const {
|
||||||
|
// 规范化角度到 0-360
|
||||||
|
angle = ((angle % 360) + 360) % 360;
|
||||||
|
return cosTable_[angle * 4];
|
||||||
|
}
|
||||||
|
|
||||||
|
float TrigLookup::sinRad(float rad) const {
|
||||||
|
constexpr float RAD2DEG = 180.0f / 3.14159265359f;
|
||||||
|
int angle = static_cast<int>(rad * RAD2DEG);
|
||||||
|
return sin(angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
float TrigLookup::cosRad(float rad) const {
|
||||||
|
constexpr float RAD2DEG = 180.0f / 3.14159265359f;
|
||||||
|
int angle = static_cast<int>(rad * RAD2DEG);
|
||||||
|
return cos(angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// SpriteBatch 实现
|
||||||
|
// ============================================================================
|
||||||
|
SpriteBatch::SpriteBatch()
|
||||||
|
: spriteCount_(0)
|
||||||
|
, vpDirty_(true) {
|
||||||
|
// 预分配顶点缓冲区
|
||||||
|
vertices_.reserve(MAX_VERTICES);
|
||||||
|
indices_.reserve(MAX_INDICES);
|
||||||
|
|
||||||
|
// 生成静态索引缓冲区
|
||||||
|
generateIndices();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteBatch::generateIndices() {
|
||||||
|
indices_.clear();
|
||||||
|
for (size_t i = 0; i < MAX_SPRITES; ++i) {
|
||||||
|
uint16_t base = static_cast<uint16_t>(i * 4);
|
||||||
|
// 两个三角形: (0,1,2) 和 (0,2,3)
|
||||||
|
indices_.push_back(base + 0);
|
||||||
|
indices_.push_back(base + 1);
|
||||||
|
indices_.push_back(base + 2);
|
||||||
|
indices_.push_back(base + 0);
|
||||||
|
indices_.push_back(base + 2);
|
||||||
|
indices_.push_back(base + 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteBatch::begin(const glm::mat4& viewProjection) {
|
||||||
|
viewProjection_ = viewProjection;
|
||||||
|
vpDirty_ = true;
|
||||||
|
spriteCount_ = 0;
|
||||||
|
vertices_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteBatch::end() {
|
||||||
|
// 批次结束,数据已准备好供后端使用
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteBatch::draw(const SpriteData& sprite) {
|
||||||
|
if (spriteCount_ >= MAX_SPRITES) {
|
||||||
|
// 缓冲区已满,需要刷新
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
generateVertices(sprite, spriteCount_ * VERTICES_PER_SPRITE);
|
||||||
|
spriteCount_++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteBatch::drawBatch(const std::vector<SpriteData>& sprites) {
|
||||||
|
size_t index = 0;
|
||||||
|
while (index < sprites.size()) {
|
||||||
|
// 计算剩余空间
|
||||||
|
size_t remainingSpace = MAX_SPRITES - spriteCount_;
|
||||||
|
size_t batchSize = std::min(remainingSpace, sprites.size() - index);
|
||||||
|
|
||||||
|
// 批量生成顶点
|
||||||
|
for (size_t i = 0; i < batchSize; ++i) {
|
||||||
|
generateVertices(sprites[index + i], spriteCount_ * VERTICES_PER_SPRITE);
|
||||||
|
spriteCount_++;
|
||||||
|
}
|
||||||
|
|
||||||
|
index += batchSize;
|
||||||
|
|
||||||
|
// 如果缓冲区已满,需要刷新(由后端处理)
|
||||||
|
if (spriteCount_ >= MAX_SPRITES && index < sprites.size()) {
|
||||||
|
// 通知后端刷新,然后继续
|
||||||
|
// 注意:这里只是准备数据,实际 GPU 提交由后端决定
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteBatch::drawImmediate(const SpriteData& sprite) {
|
||||||
|
// 立即绘制模式:清空当前批次,只绘制这一个精灵
|
||||||
|
clear();
|
||||||
|
draw(sprite);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteBatch::generateVertices(const SpriteData& sprite, size_t vertexOffset) {
|
||||||
|
// 确保顶点缓冲区足够
|
||||||
|
if (vertices_.size() < vertexOffset + VERTICES_PER_SPRITE) {
|
||||||
|
vertices_.resize(vertexOffset + VERTICES_PER_SPRITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算旋转(使用查表)
|
||||||
|
float c = 1.0f;
|
||||||
|
float s = 0.0f;
|
||||||
|
if (sprite.rotation != 0.0f) {
|
||||||
|
c = trigLookup_.cosRad(sprite.rotation);
|
||||||
|
s = trigLookup_.sinRad(sprite.rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算精灵的四个角(相对于中心点)
|
||||||
|
float halfWidth = sprite.size.x * 0.5f;
|
||||||
|
float halfHeight = sprite.size.y * 0.5f;
|
||||||
|
|
||||||
|
// 考虑 pivot 偏移
|
||||||
|
float pivotOffsetX = (sprite.pivot.x - 0.5f) * sprite.size.x;
|
||||||
|
float pivotOffsetY = (sprite.pivot.y - 0.5f) * sprite.size.y;
|
||||||
|
|
||||||
|
// 四个角的本地坐标
|
||||||
|
Vec2 localCorners[4] = {
|
||||||
|
Vec2(-halfWidth - pivotOffsetX, -halfHeight - pivotOffsetY), // 左下
|
||||||
|
Vec2( halfWidth - pivotOffsetX, -halfHeight - pivotOffsetY), // 右下
|
||||||
|
Vec2( halfWidth - pivotOffsetX, halfHeight - pivotOffsetY), // 右上
|
||||||
|
Vec2(-halfWidth - pivotOffsetX, halfHeight - pivotOffsetY) // 左上
|
||||||
|
};
|
||||||
|
|
||||||
|
// 应用旋转和平移
|
||||||
|
Vec2 worldPos = sprite.position;
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
Vec2 rotated;
|
||||||
|
if (sprite.rotation != 0.0f) {
|
||||||
|
rotated.x = localCorners[i].x * c - localCorners[i].y * s;
|
||||||
|
rotated.y = localCorners[i].x * s + localCorners[i].y * c;
|
||||||
|
} else {
|
||||||
|
rotated = localCorners[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
vertices_[vertexOffset + i].position = worldPos + rotated;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置纹理坐标
|
||||||
|
float u1 = sprite.uvRect.origin.x;
|
||||||
|
float v1 = sprite.uvRect.origin.y;
|
||||||
|
float u2 = u1 + sprite.uvRect.size.width;
|
||||||
|
float v2 = v1 + sprite.uvRect.size.height;
|
||||||
|
|
||||||
|
vertices_[vertexOffset + 0].texCoord = Vec2(u1, v2); // 左下
|
||||||
|
vertices_[vertexOffset + 1].texCoord = Vec2(u2, v2); // 右下
|
||||||
|
vertices_[vertexOffset + 2].texCoord = Vec2(u2, v1); // 右上
|
||||||
|
vertices_[vertexOffset + 3].texCoord = Vec2(u1, v1); // 左上
|
||||||
|
|
||||||
|
// 设置颜色
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
vertices_[vertexOffset + i].color = sprite.color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteBatch::flush() {
|
||||||
|
// 标记需要刷新 - 实际刷新由后端处理
|
||||||
|
// 这里只是重置计数器,让后端知道需要提交当前批次
|
||||||
|
spriteCount_ = 0;
|
||||||
|
vertices_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteBatch::clear() {
|
||||||
|
spriteCount_ = 0;
|
||||||
|
vertices_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#include <extra2d/graphics/opengl/gl_renderer.h>
|
#include <extra2d/graphics/backends/opengl/gl_renderer.h>
|
||||||
#include <extra2d/graphics/core/render_backend.h>
|
#include <extra2d/graphics/core/render_backend.h>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#include <extra2d/graphics/core/render_module.h>
|
#include <extra2d/graphics/core/render_module.h>
|
||||||
#include <extra2d/graphics/opengl/gl_renderer.h>
|
#include <extra2d/graphics/backends/opengl/gl_renderer.h>
|
||||||
#include <extra2d/graphics/opengl/gl_shader.h>
|
#include <extra2d/graphics/backends/opengl/gl_shader.h>
|
||||||
#include <extra2d/graphics/shader/shader_manager.h>
|
#include <extra2d/graphics/shader/shader_manager.h>
|
||||||
#include <extra2d/core/registry.h>
|
#include <extra2d/core/registry.h>
|
||||||
#include <extra2d/core/service_locator.h>
|
#include <extra2d/core/service_locator.h>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#include <glad/glad.h>
|
#include <glad/glad.h>
|
||||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
#include <extra2d/graphics/backends/opengl/gl_texture.h>
|
||||||
#include <extra2d/graphics/core/render_target.h>
|
#include <extra2d/graphics/core/render_target.h>
|
||||||
#include <extra2d/utils/logger.h>
|
#include <extra2d/utils/logger.h>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,309 +0,0 @@
|
||||||
#include <extra2d/graphics/opengl/gl_font_atlas.h>
|
|
||||||
#include <extra2d/utils/logger.h>
|
|
||||||
#include <fstream>
|
|
||||||
#define STB_TRUETYPE_IMPLEMENTATION
|
|
||||||
#include <stb/stb_truetype.h>
|
|
||||||
#define STB_RECT_PACK_IMPLEMENTATION
|
|
||||||
#include <algorithm>
|
|
||||||
#include <stb/stb_rect_pack.h>
|
|
||||||
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 构造函数 - 初始化字体图集
|
|
||||||
// ============================================================================
|
|
||||||
/**
|
|
||||||
* @brief 构造函数,从字体文件初始化字体图集
|
|
||||||
* @param filepath 字体文件路径
|
|
||||||
* @param fontSize 字体大小(像素)
|
|
||||||
* @param useSDF 是否使用有符号距离场渲染
|
|
||||||
*/
|
|
||||||
GLFontAtlas::GLFontAtlas(const std::string &filepath, int fontSize, bool useSDF)
|
|
||||||
: fontSize_(fontSize), useSDF_(useSDF), currentY_(0), scale_(0.0f),
|
|
||||||
ascent_(0.0f), descent_(0.0f), lineGap_(0.0f) {
|
|
||||||
|
|
||||||
// 加载字体文件
|
|
||||||
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
|
|
||||||
if (!file.is_open()) {
|
|
||||||
E2D_LOG_ERROR("Failed to load font: {}", filepath);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::streamsize size = file.tellg();
|
|
||||||
file.seekg(0, std::ios::beg);
|
|
||||||
fontData_.resize(size);
|
|
||||||
if (!file.read(reinterpret_cast<char *>(fontData_.data()), size)) {
|
|
||||||
E2D_LOG_ERROR("Failed to read font file: {}", filepath);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化 stb_truetype
|
|
||||||
if (!stbtt_InitFont(&fontInfo_, fontData_.data(),
|
|
||||||
stbtt_GetFontOffsetForIndex(fontData_.data(), 0))) {
|
|
||||||
E2D_LOG_ERROR("Failed to init font: {}", filepath);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
scale_ = stbtt_ScaleForPixelHeight(&fontInfo_, static_cast<float>(fontSize_));
|
|
||||||
|
|
||||||
int ascent, descent, lineGap;
|
|
||||||
stbtt_GetFontVMetrics(&fontInfo_, &ascent, &descent, &lineGap);
|
|
||||||
ascent_ = static_cast<float>(ascent) * scale_;
|
|
||||||
descent_ = static_cast<float>(descent) * scale_;
|
|
||||||
lineGap_ = static_cast<float>(lineGap) * scale_;
|
|
||||||
|
|
||||||
createAtlas();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 析构函数
|
|
||||||
// ============================================================================
|
|
||||||
/**
|
|
||||||
* @brief 析构函数
|
|
||||||
*/
|
|
||||||
GLFontAtlas::~GLFontAtlas() = default;
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 获取字形 - 如果字形不存在则缓存它
|
|
||||||
// ============================================================================
|
|
||||||
/**
|
|
||||||
* @brief 获取字形信息,如果字形不存在则动态缓存
|
|
||||||
* @param codepoint Unicode码点
|
|
||||||
* @return 字形信息指针,如果获取失败返回nullptr
|
|
||||||
*/
|
|
||||||
const Glyph *GLFontAtlas::getGlyph(char32_t codepoint) const {
|
|
||||||
auto it = glyphs_.find(codepoint);
|
|
||||||
if (it == glyphs_.end()) {
|
|
||||||
cacheGlyph(codepoint);
|
|
||||||
it = glyphs_.find(codepoint);
|
|
||||||
}
|
|
||||||
return (it != glyphs_.end()) ? &it->second : nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 测量文本尺寸
|
|
||||||
// ============================================================================
|
|
||||||
/**
|
|
||||||
* @brief 测量文本渲染后的尺寸
|
|
||||||
* @param text 要测量的文本
|
|
||||||
* @return 文本的宽度和高度
|
|
||||||
*/
|
|
||||||
Vec2 GLFontAtlas::measureText(const std::string &text) {
|
|
||||||
float width = 0.0f;
|
|
||||||
float height = getAscent() - getDescent();
|
|
||||||
float currentWidth = 0.0f;
|
|
||||||
|
|
||||||
for (char c : text) {
|
|
||||||
char32_t codepoint = static_cast<char32_t>(static_cast<unsigned char>(c));
|
|
||||||
if (codepoint == '\n') {
|
|
||||||
width = std::max(width, currentWidth);
|
|
||||||
currentWidth = 0.0f;
|
|
||||||
height += getLineHeight();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Glyph *glyph = getGlyph(codepoint);
|
|
||||||
if (glyph) {
|
|
||||||
currentWidth += glyph->advance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
width = std::max(width, currentWidth);
|
|
||||||
return Vec2(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 创建图集纹理 - 初始化空白纹理和矩形打包上下文
|
|
||||||
// ============================================================================
|
|
||||||
/**
|
|
||||||
* @brief 创建字体图集纹理,初始化空白纹理和矩形打包上下文
|
|
||||||
*/
|
|
||||||
void GLFontAtlas::createAtlas() {
|
|
||||||
// 统一使用 4 通道格式
|
|
||||||
int channels = 4;
|
|
||||||
std::vector<uint8_t> emptyData(ATLAS_WIDTH * ATLAS_HEIGHT * channels, 0);
|
|
||||||
texture_ = std::make_unique<GLTexture>(ATLAS_WIDTH, ATLAS_HEIGHT,
|
|
||||||
emptyData.data(), channels);
|
|
||||||
texture_->setFilter(true);
|
|
||||||
|
|
||||||
// 初始化矩形打包上下文
|
|
||||||
packNodes_.resize(ATLAS_WIDTH);
|
|
||||||
stbrp_init_target(&packContext_, ATLAS_WIDTH, ATLAS_HEIGHT, packNodes_.data(),
|
|
||||||
ATLAS_WIDTH);
|
|
||||||
|
|
||||||
// 预分配字形缓冲区
|
|
||||||
// 假设最大字形尺寸为 fontSize * fontSize * 4 (RGBA)
|
|
||||||
size_t maxGlyphSize = static_cast<size_t>(fontSize_ * fontSize_ * 4 * 4);
|
|
||||||
glyphBitmapCache_.reserve(maxGlyphSize);
|
|
||||||
glyphRgbaCache_.reserve(maxGlyphSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 缓存字形 - 渲染字形到图集并存储信息
|
|
||||||
// 使用 stb_rect_pack 进行矩形打包
|
|
||||||
// ============================================================================
|
|
||||||
/**
|
|
||||||
* @brief 缓存字形到图集,渲染字形位图并存储字形信息
|
|
||||||
* @param codepoint Unicode码点
|
|
||||||
*/
|
|
||||||
void GLFontAtlas::cacheGlyph(char32_t codepoint) const {
|
|
||||||
int advance = 0;
|
|
||||||
stbtt_GetCodepointHMetrics(&fontInfo_, static_cast<int>(codepoint), &advance,
|
|
||||||
nullptr);
|
|
||||||
float advancePx = advance * scale_;
|
|
||||||
|
|
||||||
if (useSDF_) {
|
|
||||||
constexpr int SDF_PADDING = 8;
|
|
||||||
constexpr unsigned char ONEDGE_VALUE = 128;
|
|
||||||
constexpr float PIXEL_DIST_SCALE = 64.0f;
|
|
||||||
|
|
||||||
int w = 0, h = 0, xoff = 0, yoff = 0;
|
|
||||||
unsigned char *sdf = stbtt_GetCodepointSDF(
|
|
||||||
&fontInfo_, scale_, static_cast<int>(codepoint), SDF_PADDING,
|
|
||||||
ONEDGE_VALUE, PIXEL_DIST_SCALE, &w, &h, &xoff, &yoff);
|
|
||||||
if (!sdf || w <= 0 || h <= 0) {
|
|
||||||
if (sdf)
|
|
||||||
stbtt_FreeSDF(sdf, nullptr);
|
|
||||||
Glyph glyph{};
|
|
||||||
glyph.advance = advancePx;
|
|
||||||
glyphs_[codepoint] = glyph;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
stbrp_rect rect;
|
|
||||||
rect.id = static_cast<int>(codepoint);
|
|
||||||
rect.w = w + PADDING * 2;
|
|
||||||
rect.h = h + PADDING * 2;
|
|
||||||
|
|
||||||
stbrp_pack_rects(&packContext_, &rect, 1);
|
|
||||||
if (!rect.was_packed) {
|
|
||||||
E2D_LOG_WARN("Font atlas is full, cannot cache codepoint: {}",
|
|
||||||
static_cast<int>(codepoint));
|
|
||||||
stbtt_FreeSDF(sdf, nullptr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int atlasX = rect.x + PADDING;
|
|
||||||
int atlasY = rect.y + PADDING;
|
|
||||||
|
|
||||||
Glyph glyph;
|
|
||||||
glyph.width = static_cast<float>(w);
|
|
||||||
glyph.height = static_cast<float>(h);
|
|
||||||
glyph.bearingX = static_cast<float>(xoff);
|
|
||||||
glyph.bearingY = static_cast<float>(yoff);
|
|
||||||
glyph.advance = advancePx;
|
|
||||||
|
|
||||||
// stb_rect_pack 使用左上角为原点,OpenGL纹理使用左下角为原点
|
|
||||||
// 需要翻转V坐标
|
|
||||||
float v0 = static_cast<float>(atlasY) / ATLAS_HEIGHT;
|
|
||||||
float v1 = static_cast<float>(atlasY + h) / ATLAS_HEIGHT;
|
|
||||||
glyph.u0 = static_cast<float>(atlasX) / ATLAS_WIDTH;
|
|
||||||
glyph.v0 = 1.0f - v1; // 翻转V坐标
|
|
||||||
glyph.u1 = static_cast<float>(atlasX + w) / ATLAS_WIDTH;
|
|
||||||
glyph.v1 = 1.0f - v0; // 翻转V坐标
|
|
||||||
|
|
||||||
glyphs_[codepoint] = glyph;
|
|
||||||
|
|
||||||
// 将 SDF 单通道数据转换为 RGBA 格式(统一格式)
|
|
||||||
size_t pixelCount = static_cast<size_t>(w) * static_cast<size_t>(h);
|
|
||||||
glyphRgbaCache_.resize(pixelCount * 4);
|
|
||||||
for (size_t i = 0; i < pixelCount; ++i) {
|
|
||||||
uint8_t alpha = sdf[i];
|
|
||||||
glyphRgbaCache_[i * 4 + 0] = 255; // R
|
|
||||||
glyphRgbaCache_[i * 4 + 1] = 255; // G
|
|
||||||
glyphRgbaCache_[i * 4 + 2] = 255; // B
|
|
||||||
glyphRgbaCache_[i * 4 + 3] = alpha; // A - SDF 值存储在 Alpha 通道
|
|
||||||
}
|
|
||||||
|
|
||||||
// 直接设置像素对齐为 4,无需查询当前状态
|
|
||||||
glBindTexture(GL_TEXTURE_2D, texture_->getTextureID());
|
|
||||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
|
||||||
// OpenGL纹理坐标原点在左下角,需要将Y坐标翻转
|
|
||||||
glTexSubImage2D(GL_TEXTURE_2D, 0, atlasX, ATLAS_HEIGHT - atlasY - h, w, h,
|
|
||||||
GL_RGBA, GL_UNSIGNED_BYTE, glyphRgbaCache_.data());
|
|
||||||
|
|
||||||
stbtt_FreeSDF(sdf, nullptr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
|
|
||||||
stbtt_GetCodepointBitmapBox(&fontInfo_, static_cast<int>(codepoint), scale_,
|
|
||||||
scale_, &x0, &y0, &x1, &y1);
|
|
||||||
int w = x1 - x0;
|
|
||||||
int h = y1 - y0;
|
|
||||||
int xoff = x0;
|
|
||||||
int yoff = y0;
|
|
||||||
|
|
||||||
if (w <= 0 || h <= 0) {
|
|
||||||
Glyph glyph{};
|
|
||||||
glyph.advance = advancePx;
|
|
||||||
glyphs_[codepoint] = glyph;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用预分配缓冲区
|
|
||||||
size_t pixelCount = static_cast<size_t>(w) * static_cast<size_t>(h);
|
|
||||||
glyphBitmapCache_.resize(pixelCount);
|
|
||||||
stbtt_MakeCodepointBitmap(&fontInfo_, glyphBitmapCache_.data(), w, h, w,
|
|
||||||
scale_, scale_, static_cast<int>(codepoint));
|
|
||||||
|
|
||||||
// 使用 stb_rect_pack 打包矩形
|
|
||||||
stbrp_rect rect;
|
|
||||||
rect.id = static_cast<int>(codepoint);
|
|
||||||
rect.w = w + PADDING * 2;
|
|
||||||
rect.h = h + PADDING * 2;
|
|
||||||
|
|
||||||
stbrp_pack_rects(&packContext_, &rect, 1);
|
|
||||||
|
|
||||||
if (!rect.was_packed) {
|
|
||||||
// 图集已满,无法缓存更多字形
|
|
||||||
E2D_LOG_WARN("Font atlas is full, cannot cache codepoint: {}",
|
|
||||||
static_cast<int>(codepoint));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int atlasX = rect.x + PADDING;
|
|
||||||
int atlasY = rect.y + PADDING;
|
|
||||||
|
|
||||||
// 创建字形信息
|
|
||||||
Glyph glyph;
|
|
||||||
glyph.width = static_cast<float>(w);
|
|
||||||
glyph.height = static_cast<float>(h);
|
|
||||||
glyph.bearingX = static_cast<float>(xoff);
|
|
||||||
glyph.bearingY = static_cast<float>(yoff);
|
|
||||||
glyph.advance = advancePx;
|
|
||||||
|
|
||||||
// 计算纹理坐标(相对于图集)
|
|
||||||
// stb_rect_pack 使用左上角为原点,OpenGL纹理使用左下角为原点
|
|
||||||
// 需要翻转V坐标
|
|
||||||
float v0 = static_cast<float>(atlasY) / ATLAS_HEIGHT;
|
|
||||||
float v1 = static_cast<float>(atlasY + h) / ATLAS_HEIGHT;
|
|
||||||
glyph.u0 = static_cast<float>(atlasX) / ATLAS_WIDTH;
|
|
||||||
glyph.v0 = 1.0f - v1; // 翻转V坐标
|
|
||||||
glyph.u1 = static_cast<float>(atlasX + w) / ATLAS_WIDTH;
|
|
||||||
glyph.v1 = 1.0f - v0; // 翻转V坐标
|
|
||||||
|
|
||||||
// 存储字形
|
|
||||||
glyphs_[codepoint] = glyph;
|
|
||||||
|
|
||||||
// 将单通道字形数据转换为 RGBA 格式(白色字形,Alpha 通道存储灰度)
|
|
||||||
glyphRgbaCache_.resize(pixelCount * 4);
|
|
||||||
for (size_t i = 0; i < pixelCount; ++i) {
|
|
||||||
uint8_t alpha = glyphBitmapCache_[i];
|
|
||||||
glyphRgbaCache_[i * 4 + 0] = 255; // R
|
|
||||||
glyphRgbaCache_[i * 4 + 1] = 255; // G
|
|
||||||
glyphRgbaCache_[i * 4 + 2] = 255; // B
|
|
||||||
glyphRgbaCache_[i * 4 + 3] = alpha; // A
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新纹理 - 将字形数据上传到图集的指定位置
|
|
||||||
// 直接设置像素对齐为 4,无需查询当前状态
|
|
||||||
glBindTexture(GL_TEXTURE_2D, texture_->getTextureID());
|
|
||||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
|
||||||
// OpenGL纹理坐标原点在左下角,需要将Y坐标翻转
|
|
||||||
glTexSubImage2D(GL_TEXTURE_2D, 0, atlasX, ATLAS_HEIGHT - atlasY - h, w, h,
|
|
||||||
GL_RGBA, GL_UNSIGNED_BYTE, glyphRgbaCache_.data());
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,428 +0,0 @@
|
||||||
#include <cstring>
|
|
||||||
#include <extra2d/graphics/opengl/gl_sprite_batch.h>
|
|
||||||
#include <extra2d/utils/logger.h>
|
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 三角函数查表 - 避免每帧重复计算 sin/cos
|
|
||||||
// ============================================================================
|
|
||||||
/**
|
|
||||||
* @brief 三角函数查表类,避免每帧重复计算sin/cos
|
|
||||||
*/
|
|
||||||
class TrigLookup {
|
|
||||||
public:
|
|
||||||
static constexpr size_t TABLE_SIZE = 360 * 4; // 0.25度精度
|
|
||||||
static constexpr float INDEX_SCALE = 4.0f; // 每度4个采样点
|
|
||||||
static constexpr float RAD_TO_INDEX = INDEX_SCALE * 180.0f / 3.14159265359f;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 查表获取sin值
|
|
||||||
* @param radians 弧度值
|
|
||||||
* @return sin值
|
|
||||||
*/
|
|
||||||
static float sinRad(float radians) {
|
|
||||||
return table_.sinTable[normalizeIndexRad(radians)];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 查表获取cos值
|
|
||||||
* @param radians 弧度值
|
|
||||||
* @return cos值
|
|
||||||
*/
|
|
||||||
static float cosRad(float radians) {
|
|
||||||
return table_.cosTable[normalizeIndexRad(radians)];
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct Tables {
|
|
||||||
std::array<float, TABLE_SIZE> sinTable;
|
|
||||||
std::array<float, TABLE_SIZE> cosTable;
|
|
||||||
|
|
||||||
Tables() {
|
|
||||||
for (size_t i = 0; i < TABLE_SIZE; ++i) {
|
|
||||||
float angle = static_cast<float>(i) / INDEX_SCALE * 3.14159265359f / 180.0f;
|
|
||||||
sinTable[i] = std::sin(angle);
|
|
||||||
cosTable[i] = std::cos(angle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static size_t normalizeIndexRad(float radians) {
|
|
||||||
int idx = static_cast<int>(radians * RAD_TO_INDEX) % static_cast<int>(TABLE_SIZE);
|
|
||||||
if (idx < 0) {
|
|
||||||
idx += static_cast<int>(TABLE_SIZE);
|
|
||||||
}
|
|
||||||
return static_cast<size_t>(idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const Tables table_;
|
|
||||||
};
|
|
||||||
|
|
||||||
const TrigLookup::Tables TrigLookup::table_;
|
|
||||||
|
|
||||||
// 静态索引生成函数
|
|
||||||
/**
|
|
||||||
* @brief 获取静态索引数组,用于精灵绘制
|
|
||||||
* @return 索引数组的常量引用
|
|
||||||
*/
|
|
||||||
static const std::array<GLuint, GLSpriteBatch::MAX_INDICES>& getIndices() {
|
|
||||||
static std::array<GLuint, GLSpriteBatch::MAX_INDICES> indices = []() {
|
|
||||||
std::array<GLuint, GLSpriteBatch::MAX_INDICES> arr{};
|
|
||||||
for (size_t i = 0; i < GLSpriteBatch::MAX_SPRITES; ++i) {
|
|
||||||
GLuint base = static_cast<GLuint>(i * GLSpriteBatch::VERTICES_PER_SPRITE);
|
|
||||||
arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 0] = base + 0;
|
|
||||||
arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 1] = base + 1;
|
|
||||||
arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 2] = base + 2;
|
|
||||||
arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 3] = base + 0;
|
|
||||||
arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 4] = base + 2;
|
|
||||||
arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 5] = base + 3;
|
|
||||||
}
|
|
||||||
return arr;
|
|
||||||
}();
|
|
||||||
return indices;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 顶点着色器 (GLES 3.2)
|
|
||||||
static const char *SPRITE_VERTEX_SHADER = R"(
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
layout(location = 0) in vec2 aPosition;
|
|
||||||
layout(location = 1) in vec2 aTexCoord;
|
|
||||||
layout(location = 2) in vec4 aColor;
|
|
||||||
|
|
||||||
uniform mat4 uViewProjection;
|
|
||||||
|
|
||||||
out vec2 vTexCoord;
|
|
||||||
out vec4 vColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = uViewProjection * vec4(aPosition, 0.0, 1.0);
|
|
||||||
vTexCoord = aTexCoord;
|
|
||||||
vColor = aColor;
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
|
|
||||||
// 片段着色器 (GLES 3.2)
|
|
||||||
// SDF 常量硬编码:ONEDGE_VALUE=128/255=0.502, PIXEL_DIST_SCALE=255/64=3.98
|
|
||||||
// SDF 值存储在 Alpha 通道
|
|
||||||
static const char *SPRITE_FRAGMENT_SHADER = R"(
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
in vec2 vTexCoord;
|
|
||||||
in vec4 vColor;
|
|
||||||
|
|
||||||
uniform sampler2D uTexture;
|
|
||||||
uniform int uUseSDF;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
if (uUseSDF == 1) {
|
|
||||||
float dist = texture(uTexture, vTexCoord).a;
|
|
||||||
float sd = (dist - 0.502) * 3.98;
|
|
||||||
float w = fwidth(sd);
|
|
||||||
float alpha = smoothstep(-w, w, sd);
|
|
||||||
fragColor = vec4(vColor.rgb, vColor.a * alpha);
|
|
||||||
} else {
|
|
||||||
fragColor = texture(uTexture, vTexCoord) * vColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 构造函数,初始化精灵批处理器成员变量
|
|
||||||
*/
|
|
||||||
GLSpriteBatch::GLSpriteBatch()
|
|
||||||
: vao_(0), vbo_(0), ibo_(0), vertexCount_(0), currentTexture_(nullptr),
|
|
||||||
currentIsSDF_(false), drawCallCount_(0), spriteCount_(0), batchCount_(0) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 析构函数,调用shutdown释放资源
|
|
||||||
*/
|
|
||||||
GLSpriteBatch::~GLSpriteBatch() { shutdown(); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化精灵批处理器,创建VAO、VBO、IBO和编译着色器
|
|
||||||
* @return 初始化成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool GLSpriteBatch::init() {
|
|
||||||
// 创建并编译着色器
|
|
||||||
if (!shader_.compileFromSource(SPRITE_VERTEX_SHADER,
|
|
||||||
SPRITE_FRAGMENT_SHADER)) {
|
|
||||||
E2D_LOG_ERROR("Failed to compile sprite batch shader");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成 VAO、VBO、IBO
|
|
||||||
glGenVertexArrays(1, &vao_);
|
|
||||||
glGenBuffers(1, &vbo_);
|
|
||||||
glGenBuffers(1, &ibo_);
|
|
||||||
|
|
||||||
glBindVertexArray(vao_);
|
|
||||||
|
|
||||||
// 设置 VBO - 使用动态绘制模式
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
|
|
||||||
glBufferData(GL_ARRAY_BUFFER, MAX_VERTICES * sizeof(Vertex), nullptr,
|
|
||||||
GL_DYNAMIC_DRAW);
|
|
||||||
|
|
||||||
// 设置顶点属性
|
|
||||||
glEnableVertexAttribArray(0);
|
|
||||||
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex),
|
|
||||||
(void *)offsetof(Vertex, position));
|
|
||||||
|
|
||||||
glEnableVertexAttribArray(1);
|
|
||||||
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex),
|
|
||||||
(void *)offsetof(Vertex, texCoord));
|
|
||||||
|
|
||||||
glEnableVertexAttribArray(2);
|
|
||||||
glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex),
|
|
||||||
(void *)offsetof(Vertex, color));
|
|
||||||
|
|
||||||
// 使用编译期生成的静态索引缓冲区
|
|
||||||
const auto& indices = getIndices();
|
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_);
|
|
||||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(GLuint),
|
|
||||||
indices.data(), GL_STATIC_DRAW);
|
|
||||||
|
|
||||||
glBindVertexArray(0);
|
|
||||||
|
|
||||||
E2D_LOG_INFO("GLSpriteBatch initialized with capacity for {} sprites",
|
|
||||||
MAX_SPRITES);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 关闭精灵批处理器,释放OpenGL资源
|
|
||||||
*/
|
|
||||||
void GLSpriteBatch::shutdown() {
|
|
||||||
if (vao_ != 0) {
|
|
||||||
glDeleteVertexArrays(1, &vao_);
|
|
||||||
vao_ = 0;
|
|
||||||
}
|
|
||||||
if (vbo_ != 0) {
|
|
||||||
glDeleteBuffers(1, &vbo_);
|
|
||||||
vbo_ = 0;
|
|
||||||
}
|
|
||||||
if (ibo_ != 0) {
|
|
||||||
glDeleteBuffers(1, &ibo_);
|
|
||||||
ibo_ = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 开始批处理,重置状态并设置视图投影矩阵
|
|
||||||
* @param viewProjection 视图投影矩阵
|
|
||||||
*/
|
|
||||||
void GLSpriteBatch::begin(const glm::mat4 &viewProjection) {
|
|
||||||
viewProjection_ = viewProjection;
|
|
||||||
vertexCount_ = 0;
|
|
||||||
currentTexture_ = nullptr;
|
|
||||||
currentIsSDF_ = false;
|
|
||||||
drawCallCount_ = 0;
|
|
||||||
spriteCount_ = 0;
|
|
||||||
batchCount_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查是否需要刷新批次
|
|
||||||
* @param texture 当前纹理
|
|
||||||
* @param isSDF 是否为SDF渲染
|
|
||||||
* @return 需要刷新返回true,否则返回false
|
|
||||||
*/
|
|
||||||
bool GLSpriteBatch::needsFlush(const Texture &texture, bool isSDF) const {
|
|
||||||
if (currentTexture_ == nullptr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否需要刷新:纹理改变、SDF 状态改变或缓冲区已满
|
|
||||||
return (currentTexture_ != &texture) || (currentIsSDF_ != isSDF) ||
|
|
||||||
(vertexCount_ + VERTICES_PER_SPRITE > MAX_VERTICES);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 添加精灵顶点到顶点缓冲区
|
|
||||||
* @param data 精灵数据
|
|
||||||
*/
|
|
||||||
void GLSpriteBatch::addVertices(const SpriteData &data) {
|
|
||||||
// 计算锚点偏移
|
|
||||||
float anchorOffsetX = data.size.x * data.anchor.x;
|
|
||||||
float anchorOffsetY = data.size.y * data.anchor.y;
|
|
||||||
|
|
||||||
// 使用三角函数查表替代 cosf/sinf
|
|
||||||
float cosR = TrigLookup::cosRad(data.rotation);
|
|
||||||
float sinR = TrigLookup::sinRad(data.rotation);
|
|
||||||
|
|
||||||
glm::vec4 color(data.color.r, data.color.g, data.color.b, data.color.a);
|
|
||||||
|
|
||||||
// 直接计算变换后的位置
|
|
||||||
float rx0 = -anchorOffsetX;
|
|
||||||
float ry0 = -anchorOffsetY;
|
|
||||||
float rx1 = data.size.x - anchorOffsetX;
|
|
||||||
float ry1 = data.size.y - anchorOffsetY;
|
|
||||||
|
|
||||||
// 预计算旋转后的偏移
|
|
||||||
float cosRx0 = rx0 * cosR, sinRx0 = rx0 * sinR;
|
|
||||||
float cosRx1 = rx1 * cosR, sinRx1 = rx1 * sinR;
|
|
||||||
float cosRy0 = ry0 * cosR, sinRy0 = ry0 * sinR;
|
|
||||||
float cosRy1 = ry1 * cosR, sinRy1 = ry1 * sinR;
|
|
||||||
|
|
||||||
// v0: (0, 0) -> (rx0, ry0)
|
|
||||||
vertexBuffer_[vertexCount_++] = {
|
|
||||||
glm::vec2(data.position.x + cosRx0 - sinRy0, data.position.y + sinRx0 + cosRy0),
|
|
||||||
glm::vec2(data.texCoordMin.x, data.texCoordMin.y),
|
|
||||||
color
|
|
||||||
};
|
|
||||||
|
|
||||||
// v1: (size.x, 0) -> (rx1, ry0)
|
|
||||||
vertexBuffer_[vertexCount_++] = {
|
|
||||||
glm::vec2(data.position.x + cosRx1 - sinRy0, data.position.y + sinRx1 + cosRy0),
|
|
||||||
glm::vec2(data.texCoordMax.x, data.texCoordMin.y),
|
|
||||||
color
|
|
||||||
};
|
|
||||||
|
|
||||||
// v2: (size.x, size.y) -> (rx1, ry1)
|
|
||||||
vertexBuffer_[vertexCount_++] = {
|
|
||||||
glm::vec2(data.position.x + cosRx1 - sinRy1, data.position.y + sinRx1 + cosRy1),
|
|
||||||
glm::vec2(data.texCoordMax.x, data.texCoordMax.y),
|
|
||||||
color
|
|
||||||
};
|
|
||||||
|
|
||||||
// v3: (0, size.y) -> (rx0, ry1)
|
|
||||||
vertexBuffer_[vertexCount_++] = {
|
|
||||||
glm::vec2(data.position.x + cosRx0 - sinRy1, data.position.y + sinRx0 + cosRy1),
|
|
||||||
glm::vec2(data.texCoordMin.x, data.texCoordMax.y),
|
|
||||||
color
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制单个精灵
|
|
||||||
* @param texture 纹理引用
|
|
||||||
* @param data 精灵数据
|
|
||||||
*/
|
|
||||||
void GLSpriteBatch::draw(const Texture &texture, const SpriteData &data) {
|
|
||||||
// 如果需要刷新,先提交当前批次
|
|
||||||
if (needsFlush(texture, data.isSDF)) {
|
|
||||||
flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
currentTexture_ = &texture;
|
|
||||||
currentIsSDF_ = data.isSDF;
|
|
||||||
|
|
||||||
addVertices(data);
|
|
||||||
spriteCount_++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 批量绘制多个精灵
|
|
||||||
* @param texture 纹理引用
|
|
||||||
* @param sprites 精灵数据数组
|
|
||||||
*/
|
|
||||||
void GLSpriteBatch::drawBatch(const Texture &texture,
|
|
||||||
const std::vector<SpriteData> &sprites) {
|
|
||||||
if (sprites.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果当前有未提交的批次且纹理不同,先刷新
|
|
||||||
if (currentTexture_ != nullptr && currentTexture_ != &texture) {
|
|
||||||
flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
currentTexture_ = &texture;
|
|
||||||
currentIsSDF_ = sprites[0].isSDF; // 假设批量中的精灵 SDF 状态一致
|
|
||||||
|
|
||||||
// 分批处理,避免超过缓冲区大小
|
|
||||||
size_t index = 0;
|
|
||||||
while (index < sprites.size()) {
|
|
||||||
size_t remainingSpace = (MAX_VERTICES - vertexCount_) / VERTICES_PER_SPRITE;
|
|
||||||
size_t batchSize = std::min(sprites.size() - index, remainingSpace);
|
|
||||||
|
|
||||||
for (size_t i = 0; i < batchSize; ++i) {
|
|
||||||
addVertices(sprites[index + i]);
|
|
||||||
spriteCount_++;
|
|
||||||
}
|
|
||||||
|
|
||||||
index += batchSize;
|
|
||||||
|
|
||||||
// 如果还有更多精灵,刷新当前批次
|
|
||||||
if (index < sprites.size()) {
|
|
||||||
flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
batchCount_++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 立即绘制精灵,不缓存
|
|
||||||
* @param texture 纹理引用
|
|
||||||
* @param data 精灵数据
|
|
||||||
*/
|
|
||||||
void GLSpriteBatch::drawImmediate(const Texture &texture,
|
|
||||||
const SpriteData &data) {
|
|
||||||
// 立即绘制,不缓存 - 用于需要立即显示的情况
|
|
||||||
flush(); // 先提交当前批次
|
|
||||||
|
|
||||||
currentTexture_ = &texture;
|
|
||||||
currentIsSDF_ = data.isSDF;
|
|
||||||
addVertices(data);
|
|
||||||
spriteCount_++;
|
|
||||||
|
|
||||||
flush(); // 立即提交
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 结束批处理,提交所有待绘制的精灵
|
|
||||||
*/
|
|
||||||
void GLSpriteBatch::end() {
|
|
||||||
if (vertexCount_ > 0) {
|
|
||||||
flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 刷新批次,执行实际的OpenGL绘制调用
|
|
||||||
*/
|
|
||||||
void GLSpriteBatch::flush() {
|
|
||||||
if (vertexCount_ == 0 || currentTexture_ == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 绑定纹理
|
|
||||||
GLuint texID = static_cast<GLuint>(
|
|
||||||
reinterpret_cast<uintptr_t>(currentTexture_->getNativeHandle()));
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, texID);
|
|
||||||
|
|
||||||
// 使用着色器
|
|
||||||
shader_.bind();
|
|
||||||
shader_.setMat4("uViewProjection", viewProjection_);
|
|
||||||
shader_.setInt("uTexture", 0);
|
|
||||||
shader_.setInt("uUseSDF", currentIsSDF_ ? 1 : 0);
|
|
||||||
// SDF 常量已硬编码到着色器中
|
|
||||||
|
|
||||||
// 更新 VBO 数据 - 只更新实际使用的部分
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount_ * sizeof(Vertex),
|
|
||||||
vertexBuffer_.data());
|
|
||||||
|
|
||||||
// 绘制
|
|
||||||
glBindVertexArray(vao_);
|
|
||||||
GLsizei indexCount = static_cast<GLsizei>(
|
|
||||||
(vertexCount_ / VERTICES_PER_SPRITE) * INDICES_PER_SPRITE);
|
|
||||||
glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, nullptr);
|
|
||||||
|
|
||||||
drawCallCount_++;
|
|
||||||
batchCount_++;
|
|
||||||
|
|
||||||
// 重置状态
|
|
||||||
vertexCount_ = 0;
|
|
||||||
currentTexture_ = nullptr;
|
|
||||||
currentIsSDF_ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
#include <extra2d/graphics/shader/shader_manager.h>
|
#include <extra2d/graphics/shader/shader_manager.h>
|
||||||
#include <extra2d/utils/logger.h>
|
#include <extra2d/utils/logger.h>
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
namespace nl = nlohmann;
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -450,10 +454,76 @@ Ptr<IShader> ShaderManager::getBuiltin(const std::string& name) {
|
||||||
return shader;
|
return shader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 尝试从新的多后端JSON元数据加载
|
||||||
|
std::string jsonPath = shaderDir_ + "shared/builtin/" + name + ".json";
|
||||||
|
if (loader_.fileExists(jsonPath)) {
|
||||||
|
return loadFromMetadata(jsonPath, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回退到旧的组合文件格式
|
||||||
std::string path = shaderDir_ + "builtin/" + name + ".shader";
|
std::string path = shaderDir_ + "builtin/" + name + ".shader";
|
||||||
return loadFromCombinedFile(path);
|
return loadFromCombinedFile(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从JSON元数据文件加载Shader(多后端支持)
|
||||||
|
* @param jsonPath JSON元数据文件路径
|
||||||
|
* @param name Shader名称
|
||||||
|
* @return 加载的Shader实例
|
||||||
|
*/
|
||||||
|
Ptr<IShader> ShaderManager::loadFromMetadata(const std::string& jsonPath, const std::string& name) {
|
||||||
|
if (!initialized_) {
|
||||||
|
E2D_LOG_ERROR("ShaderManager not initialized");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否已加载
|
||||||
|
auto it = shaders_.find(name);
|
||||||
|
if (it != shaders_.end()) {
|
||||||
|
return it->second.shader;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取JSON文件
|
||||||
|
std::string jsonContent = loader_.readFile(jsonPath);
|
||||||
|
if (jsonContent.empty()) {
|
||||||
|
E2D_LOG_ERROR("Failed to read shader metadata: {}", jsonPath);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 使用nlohmann/json解析
|
||||||
|
nl::json j = nl::json::parse(jsonContent);
|
||||||
|
|
||||||
|
// 获取OpenGL后端路径
|
||||||
|
if (!j.contains("backends") || !j["backends"].contains("opengl")) {
|
||||||
|
E2D_LOG_ERROR("No OpenGL backend found in shader metadata: {}", jsonPath);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& opengl = j["backends"]["opengl"];
|
||||||
|
if (!opengl.contains("vertex") || !opengl.contains("fragment")) {
|
||||||
|
E2D_LOG_ERROR("Missing vertex or fragment path in shader metadata: {}", jsonPath);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string vertRelativePath = opengl["vertex"].get<std::string>();
|
||||||
|
std::string fragRelativePath = opengl["fragment"].get<std::string>();
|
||||||
|
|
||||||
|
// 构建完整路径
|
||||||
|
std::string vertPath = shaderDir_ + vertRelativePath;
|
||||||
|
std::string fragPath = shaderDir_ + fragRelativePath;
|
||||||
|
|
||||||
|
E2D_LOG_DEBUG("Loading shader from metadata: {} -> vert: {}, frag: {}", name, vertPath, fragPath);
|
||||||
|
|
||||||
|
// 使用分离文件加载
|
||||||
|
return loadFromFiles(name, vertPath, fragPath);
|
||||||
|
|
||||||
|
} catch (const nl::json::exception& e) {
|
||||||
|
E2D_LOG_ERROR("Failed to parse shader metadata {}: {}", jsonPath, e.what());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 加载所有内置Shader
|
* @brief 加载所有内置Shader
|
||||||
* @return 加载成功返回true,失败返回false
|
* @return 加载成功返回true,失败返回false
|
||||||
|
|
@ -475,17 +545,27 @@ bool ShaderManager::loadBuiltinShaders() {
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const char* name : builtinNames) {
|
for (const char* name : builtinNames) {
|
||||||
std::string path = shaderDir_ + "builtin/" + name + ".shader";
|
// 首先尝试新的多后端JSON格式
|
||||||
|
std::string jsonPath = shaderDir_ + "shared/builtin/" + name + ".json";
|
||||||
std::string shaderName = std::string("builtin_") + name;
|
std::string shaderName = std::string("builtin_") + name;
|
||||||
|
|
||||||
if (!loadFromCombinedFile(path)) {
|
Ptr<IShader> shader = nullptr;
|
||||||
E2D_LOG_ERROR("Failed to load builtin {} shader from: {}", name, path);
|
if (loader_.fileExists(jsonPath)) {
|
||||||
|
shader = loadFromMetadata(jsonPath, name);
|
||||||
|
} else {
|
||||||
|
// 回退到旧的组合文件格式
|
||||||
|
std::string path = shaderDir_ + "builtin/" + name + ".shader";
|
||||||
|
shader = loadFromCombinedFile(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shader) {
|
||||||
|
E2D_LOG_ERROR("Failed to load builtin {} shader", name);
|
||||||
allSuccess = false;
|
allSuccess = false;
|
||||||
} else {
|
} else {
|
||||||
|
// 同时注册带 builtin_ 前缀的名称
|
||||||
auto it = shaders_.find(name);
|
auto it = shaders_.find(name);
|
||||||
if (it != shaders_.end()) {
|
if (it != shaders_.end()) {
|
||||||
shaders_[shaderName] = it->second;
|
shaders_[shaderName] = it->second;
|
||||||
shaders_.erase(it);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,13 @@ option("window_backend")
|
||||||
set_values("sdl2", "glfw")
|
set_values("sdl2", "glfw")
|
||||||
option_end()
|
option_end()
|
||||||
|
|
||||||
|
option("render_backend")
|
||||||
|
set_default("opengl")
|
||||||
|
set_showmenu(true)
|
||||||
|
set_description("Render backend: opengl or vulkan")
|
||||||
|
set_values("opengl", "vulkan")
|
||||||
|
option_end()
|
||||||
|
|
||||||
-- ==============================================
|
-- ==============================================
|
||||||
-- 平台检测与配置
|
-- 平台检测与配置
|
||||||
-- ==============================================
|
-- ==============================================
|
||||||
|
|
|
||||||
|
|
@ -19,14 +19,29 @@ local function get_window_backend()
|
||||||
return get_config("window_backend") or "sdl2"
|
return get_config("window_backend") or "sdl2"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- 获取渲染后端
|
||||||
|
local function get_render_backend()
|
||||||
|
return get_config("render_backend") or "opengl"
|
||||||
|
end
|
||||||
|
|
||||||
-- 定义 Extra2D 引擎库目标
|
-- 定义 Extra2D 引擎库目标
|
||||||
function define_extra2d_engine()
|
function define_extra2d_engine()
|
||||||
target("extra2d")
|
target("extra2d")
|
||||||
set_kind("static")
|
set_kind("static")
|
||||||
|
|
||||||
-- 引擎核心源文件
|
-- 引擎核心源文件(后端无关)
|
||||||
add_files("Extra2D/src/**.cpp|platform/backends/**.cpp")
|
add_files("Extra2D/src/**.cpp|platform/backends/**.cpp|graphics/backends/**.cpp")
|
||||||
add_files("Extra2D/src/glad/glad.c")
|
|
||||||
|
-- 渲染后端源文件
|
||||||
|
local render_backend = get_render_backend()
|
||||||
|
if render_backend == "vulkan" then
|
||||||
|
add_files("Extra2D/src/graphics/backends/vulkan/*.cpp")
|
||||||
|
add_defines("E2D_BACKEND_VULKAN")
|
||||||
|
else
|
||||||
|
add_files("Extra2D/src/graphics/backends/opengl/*.cpp")
|
||||||
|
add_files("Extra2D/src/glad/glad.c")
|
||||||
|
add_defines("E2D_BACKEND_OPENGL")
|
||||||
|
end
|
||||||
|
|
||||||
-- 窗口后端源文件
|
-- 窗口后端源文件
|
||||||
local backend = get_window_backend()
|
local backend = get_window_backend()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue