feat(渲染后端): 重构渲染系统支持多后端

- 新增渲染后端工厂类,支持OpenGL和Vulkan后端
- 将OpenGL相关代码移动到backends/opengl目录
- 添加Vulkan后端占位实现
- 重构Shader系统,支持JSON元数据定义多后端Shader
- 新增shape和sprite的JSON定义及GLSL文件
- 移除旧的组合Shader文件格式
- 更新构建系统支持选择渲染后端
- 重命名相关头文件路径保持一致性
This commit is contained in:
ChestnutYueyue 2026-02-17 14:48:04 +08:00
parent d2660a86bb
commit a4276e4376
53 changed files with 2126 additions and 2307 deletions

View File

@ -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 truefalse
*/
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

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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 &center, float radius, const Color &color,
int segments, float width) override;
void fillCircle(const Vec2 &center, 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

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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
@ -19,12 +18,7 @@ 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,
Deleted,
Renamed
} type;
uint64_t timestamp = 0; uint64_t timestamp = 0;
}; };
@ -32,7 +26,7 @@ struct FileChangeEvent {
// ============================================================================ // ============================================================================
// 文件变化回调 // 文件变化回调
// ============================================================================ // ============================================================================
using FileChangeCallback = std::function<void(const FileChangeEvent&)>; using FileChangeCallback = std::function<void(const FileChangeEvent &)>;
// ============================================================================ // ============================================================================
// Shader热重载管理器 // Shader热重载管理器
@ -43,7 +37,7 @@ public:
* @brief * @brief
* @return * @return
*/ */
static ShaderHotReloader& getInstance(); static ShaderHotReloader &getInstance();
/** /**
* @brief * @brief
@ -62,15 +56,15 @@ public:
* @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
@ -98,8 +92,8 @@ public:
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;
@ -128,7 +122,7 @@ private:
* @param filepath * @param filepath
* @return * @return
*/ */
static uint64_t getFileModifiedTime(const std::string& filepath); static uint64_t getFileModifiedTime(const std::string &filepath);
}; };
// 便捷宏 // 便捷宏

View File

@ -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);
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 工具方法 // 工具方法
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------

View File

@ -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>

View File

@ -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 {
@ -47,21 +45,21 @@ struct TextureKey {
* @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;
} }
@ -70,9 +68,7 @@ struct TextureKey {
* @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);
}
}; };
// ============================================================================ // ============================================================================
@ -84,7 +80,7 @@ struct TextureKeyHash {
* @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);
@ -115,11 +111,8 @@ struct TexturePoolEntry {
* @brief * @brief
*/ */
TexturePoolEntry() TexturePoolEntry()
: texture(nullptr) : texture(nullptr), refCount(0), key(), memorySize(0), lastAccessTime(0) {
, refCount(0) }
, key()
, memorySize(0)
, lastAccessTime(0) {}
/** /**
* @brief * @brief
@ -127,33 +120,30 @@ struct TexturePoolEntry {
* @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),
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;
@ -162,8 +152,8 @@ struct TexturePoolEntry {
} }
// 禁止拷贝 // 禁止拷贝
TexturePoolEntry(const TexturePoolEntry&) = delete; TexturePoolEntry(const TexturePoolEntry &) = delete;
TexturePoolEntry& operator=(const TexturePoolEntry&) = delete; TexturePoolEntry &operator=(const TexturePoolEntry &) = delete;
/** /**
* @brief 访 * @brief 访
@ -198,7 +188,7 @@ public:
* @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) {}
/** /**
@ -214,7 +204,7 @@ public:
* @brief * @brief
* @param other TextureRef * @param other TextureRef
*/ */
TextureRef(const TextureRef& other) TextureRef(const TextureRef &other)
: texture_(other.texture_), entry_(other.entry_), mutex_(other.mutex_) { : texture_(other.texture_), entry_(other.entry_), mutex_(other.mutex_) {
if (entry_ && entry_->refCount.load(std::memory_order_relaxed) > 0) { if (entry_ && entry_->refCount.load(std::memory_order_relaxed) > 0) {
entry_->refCount.fetch_add(1, std::memory_order_relaxed); entry_->refCount.fetch_add(1, std::memory_order_relaxed);
@ -225,10 +215,9 @@ public:
* @brief * @brief
* @param other TextureRef * @param other TextureRef
*/ */
TextureRef(TextureRef&& other) noexcept TextureRef(TextureRef &&other) noexcept
: texture_(std::move(other.texture_)) : texture_(std::move(other.texture_)), entry_(other.entry_),
, entry_(other.entry_) mutex_(other.mutex_) {
, mutex_(other.mutex_) {
other.entry_ = nullptr; other.entry_ = nullptr;
other.mutex_ = nullptr; other.mutex_ = nullptr;
} }
@ -243,7 +232,7 @@ public:
* @param other TextureRef * @param other TextureRef
* @return * @return
*/ */
TextureRef& operator=(const TextureRef& other) { TextureRef &operator=(const TextureRef &other) {
if (this != &other) { if (this != &other) {
reset(); reset();
texture_ = other.texture_; texture_ = other.texture_;
@ -261,7 +250,7 @@ public:
* @param other TextureRef * @param other TextureRef
* @return * @return
*/ */
TextureRef& operator=(TextureRef&& other) noexcept { TextureRef &operator=(TextureRef &&other) noexcept {
if (this != &other) { if (this != &other) {
reset(); reset();
texture_ = std::move(other.texture_); texture_ = std::move(other.texture_);
@ -292,7 +281,7 @@ public:
* @brief * @brief
* @return * @return
*/ */
Texture* get() const { return texture_.get(); } Texture *get() const { return texture_.get(); }
/** /**
* @brief * @brief
@ -314,17 +303,17 @@ public:
/** /**
* @brief * @brief
*/ */
Texture* operator->() const { return texture_.get(); } Texture *operator->() const { return texture_.get(); }
/** /**
* @brief * @brief
*/ */
Texture& operator*() const { return *texture_; } Texture &operator*() const { return *texture_; }
private: private:
Ptr<Texture> texture_; Ptr<Texture> texture_;
TexturePoolEntry* entry_; TexturePoolEntry *entry_;
std::mutex* mutex_; std::mutex *mutex_;
}; };
// ============================================================================ // ============================================================================
@ -364,7 +353,7 @@ public:
* @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
@ -372,15 +361,15 @@ public:
~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);
// ======================================================================== // ========================================================================
// 纹理加载 // 纹理加载
@ -392,8 +381,8 @@ public:
* @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
@ -402,8 +391,8 @@ public:
* @param options * @param options
* @return * @return
*/ */
TextureRef load(const std::string& path, const Rect& region, TextureRef load(const std::string &path, const Rect &region,
const TextureLoadOptions& options = TextureLoadOptions()); const TextureLoadOptions &options = TextureLoadOptions());
/** /**
* @brief * @brief
@ -414,8 +403,8 @@ public:
* @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
@ -423,8 +412,9 @@ public:
* @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
@ -433,8 +423,9 @@ public:
* @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 &region,
const TextureLoadOptions &options = TextureLoadOptions());
// ======================================================================== // ========================================================================
// 引用计数管理 // 引用计数管理
@ -445,21 +436,21 @@ public:
* @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;
// ======================================================================== // ========================================================================
// 缓存管理 // 缓存管理
@ -470,14 +461,14 @@ public:
* @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
@ -540,7 +531,7 @@ private:
* @param texture * @param texture
* @return * @return
*/ */
static size_t calculateTextureMemory(const Texture* texture); static size_t calculateTextureMemory(const Texture *texture);
/** /**
* @brief * @brief
@ -553,9 +544,10 @@ private:
*/ */
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_; // 当前内存使用量

View File

@ -0,0 +1,10 @@
#version 300 es
precision highp float;
in vec4 v_color;
out vec4 fragColor;
void main() {
fragColor = v_color;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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"
}
}
}

View File

@ -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"
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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);
} }

View File

@ -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 {

View File

@ -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

View File

@ -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

View File

@ -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 &center, float radius, const Color &color,
int segments, float width) {
// TODO: 实现圆形边框绘制
}
void VulkanRenderer::fillCircle(const Vec2 &center, 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

View File

@ -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

View File

@ -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 {

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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 VAOVBOIBO和编译着色器
* @return truefalse
*/
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 truefalse
*/
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

View File

@ -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 truefalse * @return truefalse
@ -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);
} }
} }
} }

View File

@ -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()
-- ============================================== -- ==============================================
-- 平台检测与配置 -- 平台检测与配置
-- ============================================== -- ==============================================

View File

@ -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")
-- 渲染后端源文件
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_files("Extra2D/src/glad/glad.c")
add_defines("E2D_BACKEND_OPENGL")
end
-- 窗口后端源文件 -- 窗口后端源文件
local backend = get_window_backend() local backend = get_window_backend()