From 70c2806c779e25d8cf456b4448c9f5ea153866c2 Mon Sep 17 00:00:00 2001 From: ChestnutYueyue <952134128@qq.com> Date: Mon, 23 Feb 2026 15:40:42 +0800 Subject: [PATCH] =?UTF-8?q?feat(asset/render):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=9D=90=E8=B4=A8=E7=B3=BB=E7=BB=9F=E5=92=8C=E8=B5=84=E6=BA=90?= =?UTF-8?q?=E7=B1=BB=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 实现新的材质系统包括Material、MaterialInstance和MaterialPropertyBlock类 添加多种资源类实现:TextureAsset、FontAsset、ShaderAsset、AudioAsset等 重构SpriteRenderer以支持材质系统 添加UniformValue和UniformInfo类用于材质参数传递 实现MSDF字体渲染器和文本渲染功能 --- Extra2D/include/extra2d/asset/asset.h | 501 +++--------------- Extra2D/include/extra2d/asset/asset_handle.h | 1 + Extra2D/include/extra2d/asset/asset_loader.h | 112 +++- Extra2D/include/extra2d/asset/audio_asset.h | 103 ++++ Extra2D/include/extra2d/asset/data_asset.h | 50 ++ Extra2D/include/extra2d/asset/font_asset.h | 62 +++ .../include/extra2d/asset/msdf_font_asset.h | 184 +++++++ .../include/extra2d/asset/msdf_font_loader.h | 41 ++ Extra2D/include/extra2d/asset/shader_asset.h | 158 ++++++ Extra2D/include/extra2d/asset/texture_asset.h | 76 +++ Extra2D/include/extra2d/render/material.h | 131 +++++ .../extra2d/render/material_instance.h | 161 ++++++ .../extra2d/render/material_property_block.h | 152 ++++++ .../extra2d/render/msdf_text_renderer.h | 118 +++++ .../include/extra2d/render/sprite_renderer.h | 156 +++--- Extra2D/include/extra2d/render/uniform_info.h | 27 + .../include/extra2d/render/uniform_value.h | 171 ++++++ .../include/extra2d/services/asset_service.h | 5 + Extra2D/src/asset/asset.cpp | 59 +-- Extra2D/src/asset/asset_loader.cpp | 307 +++++++++-- Extra2D/src/asset/audio_asset.cpp | 37 ++ Extra2D/src/asset/data_asset.cpp | 15 + Extra2D/src/asset/font_asset.cpp | 55 ++ Extra2D/src/asset/msdf_font_asset.cpp | 87 +++ Extra2D/src/asset/msdf_font_loader.cpp | 183 +++++++ Extra2D/src/asset/shader_asset.cpp | 116 ++++ Extra2D/src/asset/texture_asset.cpp | 21 + Extra2D/src/render/material.cpp | 101 ++++ Extra2D/src/render/material_instance.cpp | 128 +++++ .../src/render/material_property_block.cpp | 129 +++++ Extra2D/src/render/msdf_text_renderer.cpp | 217 ++++++++ Extra2D/src/render/sprite_renderer.cpp | 101 +++- Extra2D/src/render/uniform_value.cpp | 128 +++++ Extra2D/src/window/event_converter.cpp | 147 ++--- 34 files changed, 3349 insertions(+), 691 deletions(-) create mode 100644 Extra2D/include/extra2d/asset/audio_asset.h create mode 100644 Extra2D/include/extra2d/asset/data_asset.h create mode 100644 Extra2D/include/extra2d/asset/font_asset.h create mode 100644 Extra2D/include/extra2d/asset/msdf_font_asset.h create mode 100644 Extra2D/include/extra2d/asset/msdf_font_loader.h create mode 100644 Extra2D/include/extra2d/asset/shader_asset.h create mode 100644 Extra2D/include/extra2d/asset/texture_asset.h create mode 100644 Extra2D/include/extra2d/render/material.h create mode 100644 Extra2D/include/extra2d/render/material_instance.h create mode 100644 Extra2D/include/extra2d/render/material_property_block.h create mode 100644 Extra2D/include/extra2d/render/msdf_text_renderer.h create mode 100644 Extra2D/include/extra2d/render/uniform_info.h create mode 100644 Extra2D/include/extra2d/render/uniform_value.h create mode 100644 Extra2D/src/asset/audio_asset.cpp create mode 100644 Extra2D/src/asset/data_asset.cpp create mode 100644 Extra2D/src/asset/font_asset.cpp create mode 100644 Extra2D/src/asset/msdf_font_asset.cpp create mode 100644 Extra2D/src/asset/msdf_font_loader.cpp create mode 100644 Extra2D/src/asset/shader_asset.cpp create mode 100644 Extra2D/src/asset/texture_asset.cpp create mode 100644 Extra2D/src/render/material.cpp create mode 100644 Extra2D/src/render/material_instance.cpp create mode 100644 Extra2D/src/render/material_property_block.cpp create mode 100644 Extra2D/src/render/msdf_text_renderer.cpp create mode 100644 Extra2D/src/render/uniform_value.cpp diff --git a/Extra2D/include/extra2d/asset/asset.h b/Extra2D/include/extra2d/asset/asset.h index b6e929e..de9b2c0 100644 --- a/Extra2D/include/extra2d/asset/asset.h +++ b/Extra2D/include/extra2d/asset/asset.h @@ -6,10 +6,16 @@ #include #include #include -#include namespace extra2d { +// 前向声明 +class TextureAsset; +class FontAsset; +class ShaderAsset; +class AudioAsset; +class DataAsset; + // --------------------------------------------------------------------------- // Asset - 资源基类 // --------------------------------------------------------------------------- @@ -22,456 +28,77 @@ namespace extra2d { */ class Asset : public std::enable_shared_from_this { public: - virtual ~Asset() = default; + virtual ~Asset() = default; - /** - * @brief 获取资源类型 - * @return 资源类型枚举值 - */ - virtual AssetType type() const = 0; + /** + * @brief 获取资源类型 + * @return 资源类型枚举值 + */ + virtual AssetType type() const = 0; - /** - * @brief 检查资源是否已加载 - * @return 已加载返回 true - */ - virtual bool loaded() const = 0; + /** + * @brief 检查资源是否已加载 + * @return 已加载返回 true + */ + virtual bool loaded() const = 0; - /** - * @brief 获取资源内存占用大小 - * @return 内存占用字节数 - */ - virtual size_t memSize() const = 0; + /** + * @brief 获取资源内存占用大小 + * @return 内存占用字节数 + */ + virtual size_t memSize() const = 0; - /** - * @brief 获取资源ID - * @return 资源ID - */ - const AssetID &id() const { return id_; } + /** + * @brief 获取资源ID + * @return 资源ID + */ + const AssetID &id() const { return id_; } - /** - * @brief 获取资源路径 - * @return 资源路径 - */ - const std::string &path() const { return path_; } + /** + * @brief 获取资源路径 + * @return 资源路径 + */ + const std::string &path() const { return path_; } - /** - * @brief 获取资源状态 - * @return 资源状态 - */ - AssetState state() const { return state_; } + /** + * @brief 获取资源状态 + * @return 资源状态 + */ + AssetState state() const { return state_; } - /** - * @brief 获取当前引用计数 - * @return 引用计数(用于调试和监控) - */ - long refs() const { return shared_from_this().use_count(); } + /** + * @brief 获取当前引用计数 + * @return 引用计数(用于调试和监控) + */ + long refs() const { return shared_from_this().use_count(); } protected: - AssetID id_; - std::string path_; - std::atomic state_{AssetState::Unloaded}; + AssetID id_; + std::string path_; + std::atomic state_{AssetState::Unloaded}; - /** - * @brief 设置资源状态 - * @param state 新状态 - */ - void setState(AssetState state) { - state_.store(state, std::memory_order_release); - } - - /** - * @brief 设置资源ID - * @param id 资源ID - */ - void setId(const AssetID &id) { id_ = id; } - - /** - * @brief 设置资源路径 - * @param path 资源路径 - */ - void setPath(const std::string &path) { path_ = path; } - - friend class AssetCache; - friend class AssetService; -}; - -// --------------------------------------------------------------------------- -// TextureAsset - 纹理资源 -// --------------------------------------------------------------------------- - -/** - * @brief 纹理资源类 - * - * 存储纹理图像数据,支持多种像素格式。 - */ -class TextureAsset : public Asset { -public: - AssetType type() const override { return AssetType::Texture; } - - bool loaded() const override { - return state_.load(std::memory_order_acquire) == AssetState::Loaded && - data_ != nullptr; - } - - size_t memSize() const override { - return static_cast(width_) * height_ * channels_; - } - - /** - * @brief 获取纹理宽度 - * @return 宽度(像素) - */ - int width() const { return width_; } - - /** - * @brief 获取纹理高度 - * @return 高度(像素) - */ - int height() const { return height_; } - - /** - * @brief 获取通道数 - * @return 通道数(1-4) - */ - int channels() const { return channels_; } - - /** - * @brief 获取像素数据 - * @return 像素数据指针 - */ - const u8 *data() const { return data_.get(); } - - /** - * @brief 获取像素数据大小 - * @return 数据大小(字节) - */ - size_t dataSize() const { return memSize(); } - - /** - * @brief 设置纹理数据 - * @param width 宽度 - * @param height 高度 - * @param channels 通道数 - * @param data 像素数据(转移所有权) - */ - void setData(int width, int height, int channels, Unique data) { - width_ = width; - height_ = height; - channels_ = channels; - data_ = std::move(data); - setState(AssetState::Loaded); - } - - /** - * @brief 释放纹理数据 - */ - void release() { - data_.reset(); - width_ = 0; - height_ = 0; - channels_ = 0; - setState(AssetState::Unloaded); - } - -private: - int width_ = 0; - int height_ = 0; - int channels_ = 0; - Unique data_; -}; - -// --------------------------------------------------------------------------- -// FontAsset - 字体资源 -// --------------------------------------------------------------------------- - -/** - * @brief 字体资源类 - * - * 存储TrueType字体数据,支持字形渲染。 - * 使用 Pimpl 模式隐藏 stbtt_fontinfo 实现细节。 - */ -class FontAsset : public Asset { -public: - FontAsset(); - ~FontAsset() override; - - AssetType type() const override { return AssetType::Font; } - - bool loaded() const override; - - size_t memSize() const override { return data_.size(); } - - /** - * @brief 获取指定像素高度的缩放因子 - * @param pixels 像素高度 - * @return 缩放因子 - */ - float scaleForPixelHeight(float pixels) const; - - /** - * @brief 获取字体数据 - * @return 字体数据指针 - */ - const u8 *data() const { return data_.data(); } - - /** - * @brief 获取字体数据大小 - * @return 数据大小(字节) - */ - size_t dataSize() const { return data_.size(); } - - /** - * @brief 设置字体数据 - * @param data 字体数据 - * @return 成功返回 true - */ - bool setData(std::vector data); - - /** - * @brief 释放字体数据 - */ - void release(); - -private: - std::vector data_; - class Impl; - Unique impl_; -}; - -// --------------------------------------------------------------------------- -// ShaderAsset - 着色器资源 -// --------------------------------------------------------------------------- - -/** - * @brief 着色器资源类 - * - * 存储顶点和片段着色器源代码。 - */ -class ShaderAsset : public Asset { -public: - AssetType type() const override { return AssetType::Shader; } - - bool loaded() const override { - return state_.load(std::memory_order_acquire) == AssetState::Loaded; - } - - size_t memSize() const override { - return vertexSrc_.size() + fragmentSrc_.size(); - } - - /** - * @brief 获取顶点着色器源码 - * @return 顶点着色器源码 - */ - const std::string &vertexSource() const { return vertexSrc_; } - - /** - * @brief 获取片段着色器源码 - * @return 片段着色器源码 - */ - const std::string &fragmentSource() const { return fragmentSrc_; } - - /** - * @brief 设置着色器源码 - * @param vertex 顶点着色器源码 - * @param fragment 片段着色器源码 - */ - void setSource(std::string vertex, std::string fragment) { - vertexSrc_ = std::move(vertex); - fragmentSrc_ = std::move(fragment); - setState(AssetState::Loaded); - } - - /** - * @brief 释放着色器源码 - */ - void release() { - vertexSrc_.clear(); - fragmentSrc_.clear(); - setState(AssetState::Unloaded); - } - -private: - std::string vertexSrc_; - std::string fragmentSrc_; -}; - -// --------------------------------------------------------------------------- -// AudioAsset - 音频资源 -// --------------------------------------------------------------------------- - -/** - * @brief 音频格式枚举 - */ -enum class AudioFormat : u8 { PCM = 0, MP3 = 1, OGG = 2, WAV = 3 }; - -/** - * @brief 音频资源类 - * - * 存储音频数据,支持多种格式。 - */ -class AudioAsset : public Asset { -public: - AssetType type() const override { return AssetType::Audio; } - - bool loaded() const override { - return state_.load(std::memory_order_acquire) == AssetState::Loaded && - !data_.empty(); - } - - size_t memSize() const override { return data_.size(); } - - /** - * @brief 获取音频格式 - * @return 音频格式 - */ - AudioFormat format() const { return format_; } - - /** - * @brief 获取声道数 - * @return 声道数 - */ - int channels() const { return channels_; } - - /** - * @brief 获取采样率 - * @return 采样率 - */ - int sampleRate() const { return sampleRate_; } - - /** - * @brief 获取每样本位数 - * @return 每样本位数 - */ - int bitsPerSample() const { return bitsPerSample_; } - - /** - * @brief 获取时长(秒) - * @return 时长 - */ - float duration() const { return duration_; } - - /** - * @brief 获取音频数据 - * @return 音频数据指针 - */ - const u8 *data() const { return data_.data(); } - - /** - * @brief 获取音频数据大小 - * @return 数据大小(字节) - */ - size_t dataSize() const { return data_.size(); } - - /** - * @brief 是否为流式音频 - * @return 流式音频返回 true - */ - bool streaming() const { return streaming_; } - - /** - * @brief 设置音频数据 - * @param format 音频格式 - * @param channels 声道数 - * @param sampleRate 采样率 - * @param bitsPerSample 每样本位数 - * @param data 音频数据 - */ - void setData(AudioFormat format, int channels, int sampleRate, - int bitsPerSample, std::vector data) { - format_ = format; - channels_ = channels; - sampleRate_ = sampleRate; - bitsPerSample_ = bitsPerSample; - data_ = std::move(data); - - if (sampleRate > 0 && channels > 0 && bitsPerSample > 0) { - size_t bytesPerSecond = - static_cast(sampleRate) * channels * (bitsPerSample / 8); - if (bytesPerSecond > 0) { - duration_ = static_cast(data_.size()) / - static_cast(bytesPerSecond); - } + /** + * @brief 设置资源状态 + * @param state 新状态 + */ + void setState(AssetState state) { + state_.store(state, std::memory_order_release); } - streaming_ = duration_ > 5.0f; - setState(AssetState::Loaded); - } + /** + * @brief 设置资源ID + * @param id 资源ID + */ + void setId(const AssetID &id) { id_ = id; } - /** - * @brief 释放音频数据 - */ - void release() { - data_.clear(); - format_ = AudioFormat::PCM; - channels_ = 0; - sampleRate_ = 0; - bitsPerSample_ = 0; - duration_ = 0.0f; - streaming_ = false; - setState(AssetState::Unloaded); - } + /** + * @brief 设置资源路径 + * @param path 资源路径 + */ + void setPath(const std::string &path) { path_ = path; } -private: - AudioFormat format_ = AudioFormat::PCM; - int channels_ = 0; - int sampleRate_ = 0; - int bitsPerSample_ = 0; - float duration_ = 0.0f; - std::vector data_; - bool streaming_ = false; -}; - -// --------------------------------------------------------------------------- -// DataAsset - 通用数据资源 -// --------------------------------------------------------------------------- - -/** - * @brief 通用数据资源类 - * - * 存储任意二进制数据。 - */ -class DataAsset : public Asset { -public: - AssetType type() const override { return AssetType::Data; } - - bool loaded() const override { - return state_.load(std::memory_order_acquire) == AssetState::Loaded; - } - - size_t memSize() const override { return data_.size(); } - - /** - * @brief 获取数据 - * @return 数据指针 - */ - const u8 *data() const { return data_.data(); } - - /** - * @brief 获取数据大小 - * @return 数据大小(字节) - */ - size_t size() const { return data_.size(); } - - /** - * @brief 设置数据 - * @param data 数据 - */ - void setData(std::vector data) { - data_ = std::move(data); - setState(AssetState::Loaded); - } - - /** - * @brief 释放数据 - */ - void release() { - data_.clear(); - setState(AssetState::Unloaded); - } - -private: - std::vector data_; + friend class AssetCache; + friend class AssetService; }; } // namespace extra2d diff --git a/Extra2D/include/extra2d/asset/asset_handle.h b/Extra2D/include/extra2d/asset/asset_handle.h index 7bdce63..c35df43 100644 --- a/Extra2D/include/extra2d/asset/asset_handle.h +++ b/Extra2D/include/extra2d/asset/asset_handle.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include diff --git a/Extra2D/include/extra2d/asset/asset_loader.h b/Extra2D/include/extra2d/asset/asset_loader.h index ccb64ce..c26bf9f 100644 --- a/Extra2D/include/extra2d/asset/asset_loader.h +++ b/Extra2D/include/extra2d/asset/asset_loader.h @@ -2,6 +2,13 @@ #include #include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -160,44 +167,93 @@ public: * @brief 着色器加载器 * * 加载着色器源文件,支持以下格式: + * - .json: JSON 配置文件(推荐) * - .vert/.frag: 分离的顶点/片段着色器 * - .glsl: 合并的着色器文件(使用标记分隔) */ class ShaderLoader : public AssetLoader { public: - Ref load(const std::string &path) override; - Ref loadFromMemory(const u8 *data, size_t size) override; - bool canLoad(const std::string &path) const override; - AssetType type() const override { return AssetType::Shader; } - std::vector extensions() const override; + Ref load(const std::string &path) override; + Ref loadFromMemory(const u8 *data, size_t size) override; + bool canLoad(const std::string &path) const override; + AssetType type() const override { return AssetType::Shader; } + std::vector extensions() const override; - /** - * @brief 设置顶点着色器标记 - * @param marker 标记字符串(默认 "[VERTEX]") - */ - void setVertexMarker(const std::string &marker) { vertexMarker_ = marker; } + /** + * @brief 设置顶点着色器标记 + * @param marker 标记字符串(默认 "[VERTEX]") + */ + void setVertexMarker(const std::string &marker) { vertexMarker_ = marker; } - /** - * @brief 设置片段着色器标记 - * @param marker 标记字符串(默认 "[FRAGMENT]") - */ - void setFragmentMarker(const std::string &marker) { - fragmentMarker_ = marker; - } + /** + * @brief 设置片段着色器标记 + * @param marker 标记字符串(默认 "[FRAGMENT]") + */ + void setFragmentMarker(const std::string &marker) { + fragmentMarker_ = marker; + } + + /** + * @brief 设置资源根目录 + * @param root 根目录路径 + */ + void setRoot(const std::string &root) { root_ = root; } private: - std::string vertexMarker_ = "[VERTEX]"; - std::string fragmentMarker_ = "[FRAGMENT]"; + std::string vertexMarker_ = "[VERTEX]"; + std::string fragmentMarker_ = "[FRAGMENT]"; + std::string root_; - /** - * @brief 解析合并的着色器文件 - * @param content 文件内容 - * @param vertex 输出顶点着色器源码 - * @param fragment 输出片段着色器源码 - * @return 成功返回 true - */ - bool parseCombined(const std::string &content, std::string &vertex, - std::string &fragment); + /** + * @brief 解析合并的着色器文件 + */ + bool parseCombined(const std::string &content, std::string &vertex, + std::string &fragment); + + /** + * @brief 从 JSON 配置加载 + */ + Ref loadFromJson(const std::string &path); + + /** + * @brief 加载着色器源码 + */ + std::string loadSource(const std::string &path); + + /** + * @brief 编译着色器 + */ + GLuint compileShader(const std::string &source, GLenum type); + + /** + * @brief 链接程序 + */ + GLuint linkProgram(GLuint vertexShader, GLuint fragmentShader); + + /** + * @brief 提取 Uniform 信息 + */ + void extractUniforms(GLuint program, ShaderAsset *asset); + + /** + * @brief 解析混合状态 + */ + BlendState parseBlendState(const std::string &json); + + /** + * @brief 解析深度状态 + */ + DepthState parseDepthState(const std::string &json); + + /** + * @brief 类型字符串转 UniformType + */ + UniformType parseUniformType(const std::string &typeStr); + + /** + * @brief 解析默认值 + */ + UniformValue parseDefaultValue(const std::string &json, UniformType type); }; // --------------------------------------------------------------------------- diff --git a/Extra2D/include/extra2d/asset/audio_asset.h b/Extra2D/include/extra2d/asset/audio_asset.h new file mode 100644 index 0000000..0c8d522 --- /dev/null +++ b/Extra2D/include/extra2d/asset/audio_asset.h @@ -0,0 +1,103 @@ +#pragma once + +#include +#include + +namespace extra2d { + +/** + * @brief 音频格式枚举 + */ +enum class AudioFormat : u8 { PCM = 0, MP3 = 1, OGG = 2, WAV = 3 }; + +/** + * @brief 音频资源类 + * + * 存储音频数据,支持多种格式。 + */ +class AudioAsset : public Asset { +public: + AssetType type() const override { return AssetType::Audio; } + + bool loaded() const override { + return state_.load(std::memory_order_acquire) == AssetState::Loaded && + !data_.empty(); + } + + size_t memSize() const override { return data_.size(); } + + /** + * @brief 获取音频格式 + * @return 音频格式 + */ + AudioFormat format() const { return format_; } + + /** + * @brief 获取声道数 + * @return 声道数 + */ + int channels() const { return channels_; } + + /** + * @brief 获取采样率 + * @return 采样率 + */ + int sampleRate() const { return sampleRate_; } + + /** + * @brief 获取每样本位数 + * @return 每样本位数 + */ + int bitsPerSample() const { return bitsPerSample_; } + + /** + * @brief 获取时长(秒) + * @return 时长 + */ + float duration() const { return duration_; } + + /** + * @brief 获取音频数据 + * @return 音频数据指针 + */ + const u8 *data() const { return data_.data(); } + + /** + * @brief 获取音频数据大小 + * @return 数据大小(字节) + */ + size_t dataSize() const { return data_.size(); } + + /** + * @brief 是否为流式音频 + * @return 流式音频返回 true + */ + bool streaming() const { return streaming_; } + + /** + * @brief 设置音频数据 + * @param format 音频格式 + * @param channels 声道数 + * @param sampleRate 采样率 + * @param bitsPerSample 每样本位数 + * @param data 音频数据 + */ + void setData(AudioFormat format, int channels, int sampleRate, + int bitsPerSample, std::vector data); + + /** + * @brief 释放音频数据 + */ + void release(); + +private: + AudioFormat format_ = AudioFormat::PCM; + int channels_ = 0; + int sampleRate_ = 0; + int bitsPerSample_ = 0; + float duration_ = 0.0f; + std::vector data_; + bool streaming_ = false; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/asset/data_asset.h b/Extra2D/include/extra2d/asset/data_asset.h new file mode 100644 index 0000000..5e6339f --- /dev/null +++ b/Extra2D/include/extra2d/asset/data_asset.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include + +namespace extra2d { + +/** + * @brief 通用数据资源类 + * + * 存储任意二进制数据。 + */ +class DataAsset : public Asset { +public: + AssetType type() const override { return AssetType::Data; } + + bool loaded() const override { + return state_.load(std::memory_order_acquire) == AssetState::Loaded; + } + + size_t memSize() const override { return data_.size(); } + + /** + * @brief 获取数据 + * @return 数据指针 + */ + const u8 *data() const { return data_.data(); } + + /** + * @brief 获取数据大小 + * @return 数据大小(字节) + */ + size_t size() const { return data_.size(); } + + /** + * @brief 设置数据 + * @param data 数据 + */ + void setData(std::vector data); + + /** + * @brief 释放数据 + */ + void release(); + +private: + std::vector data_; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/asset/font_asset.h b/Extra2D/include/extra2d/asset/font_asset.h new file mode 100644 index 0000000..307fac1 --- /dev/null +++ b/Extra2D/include/extra2d/asset/font_asset.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include + +namespace extra2d { + +/** + * @brief 字体资源类 + * + * 存储TrueType字体数据,支持字形渲染。 + * 使用 Pimpl 模式隐藏 stbtt_fontinfo 实现细节。 + */ +class FontAsset : public Asset { +public: + FontAsset(); + ~FontAsset() override; + + AssetType type() const override { return AssetType::Font; } + + bool loaded() const override; + + size_t memSize() const override { return data_.size(); } + + /** + * @brief 获取指定像素高度的缩放因子 + * @param pixels 像素高度 + * @return 缩放因子 + */ + float scaleForPixelHeight(float pixels) const; + + /** + * @brief 获取字体数据 + * @return 字体数据指针 + */ + const u8 *data() const { return data_.data(); } + + /** + * @brief 获取字体数据大小 + * @return 数据大小(字节) + */ + size_t dataSize() const { return data_.size(); } + + /** + * @brief 设置字体数据 + * @param data 字体数据 + * @return 成功返回 true + */ + bool setData(std::vector data); + + /** + * @brief 释放字体数据 + */ + void release(); + +private: + std::vector data_; + class Impl; + Unique impl_; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/asset/msdf_font_asset.h b/Extra2D/include/extra2d/asset/msdf_font_asset.h new file mode 100644 index 0000000..109403f --- /dev/null +++ b/Extra2D/include/extra2d/asset/msdf_font_asset.h @@ -0,0 +1,184 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +/** + * @brief 字符信息 + */ +struct GlyphInfo { + char32_t codepoint = 0; ///< Unicode 码点 + glm::vec2 uvMin; ///< UV 左下角 + glm::vec2 uvMax; ///< UV 右上角 + glm::vec2 size; ///< 字符尺寸(像素) + glm::vec2 bearing; ///< 基线偏移 + float advance = 0.0f; ///< 前进宽度 +}; + +/** + * @brief MSDF 字体资源 + * + * 加载内嵌元数据的 MSDF PNG 图集 + */ +class MSDFFontAsset : public Asset { +public: + MSDFFontAsset(); + ~MSDFFontAsset() override; + + AssetType type() const override { return AssetType::Font; } + bool loaded() const override; + size_t memSize() const override; + + /** + * @brief 获取图集纹理 ID + * @return OpenGL 纹理 ID + */ + GLuint textureId() const { return textureId_; } + + /** + * @brief 获取字符信息 + * @param codepoint Unicode 码点 + * @return 字符信息指针,不存在返回 nullptr + */ + const GlyphInfo* getGlyph(char32_t codepoint) const; + + /** + * @brief 检查是否有字符 + * @param codepoint Unicode 码点 + * @return 存在返回 true + */ + bool hasGlyph(char32_t codepoint) const; + + /** + * @brief 获取缺失的字符列表 + * @param text 文本 + * @return 缺失字符列表 + */ + std::vector getMissingChars(const std::u32string& text) const; + + /** + * @brief 检查文本是否可渲染 + * @param text 文本 + * @return 可渲染返回 true + */ + bool canRender(const std::u32string& text) const; + + /** + * @brief 计算文本尺寸 + * @param text 文本 + * @param scale 缩放比例 + * @return 文本尺寸 + */ + glm::vec2 measureText(const std::u32string& text, float scale = 1.0f) const; + + /** + * @brief 获取字体大小 + * @return 字体大小(像素) + */ + int fontSize() const { return fontSize_; } + + /** + * @brief 获取像素范围 + * @return 像素范围 + */ + float pxRange() const { return pxRange_; } + + /** + * @brief 获取图集宽度 + * @return 宽度 + */ + int atlasWidth() const { return atlasWidth_; } + + /** + * @brief 获取图集高度 + * @return 高度 + */ + int atlasHeight() const { return atlasHeight_; } + + /** + * @brief 获取行高 + * @return 行高 + */ + int lineHeight() const { return lineHeight_; } + + /** + * @brief 获取基线 + * @return 基线 + */ + int baseline() const { return baseline_; } + + /** + * @brief 设置纹理 ID + * @param id 纹理 ID + */ + void setTextureId(GLuint id) { textureId_ = id; } + + /** + * @brief 设置字体大小 + * @param size 字体大小 + */ + void setFontSize(int size) { fontSize_ = size; } + + /** + * @brief 设置像素范围 + * @param range 像素范围 + */ + void setPxRange(float range) { pxRange_ = range; } + + /** + * @brief 设置图集尺寸 + * @param width 宽度 + * @param height 高度 + */ + void setAtlasSize(int width, int height) { + atlasWidth_ = width; + atlasHeight_ = height; + } + + /** + * @brief 设置行高 + * @param height 行高 + */ + void setLineHeight(int height) { lineHeight_ = height; } + + /** + * @brief 设置基线 + * @param baseline 基线 + */ + void setBaseline(int baseline) { baseline_ = baseline; } + + /** + * @brief 添加字符信息 + * @param glyph 字符信息 + */ + void addGlyph(const GlyphInfo& glyph); + + /** + * @brief 标记为已加载 + */ + void markLoaded(); + + /** + * @brief 释放资源 + */ + void release(); + +private: + GLuint textureId_ = 0; + int fontSize_ = 48; + float pxRange_ = 4.0f; + int atlasWidth_ = 2048; + int atlasHeight_ = 2048; + int lineHeight_ = 60; + int baseline_ = 12; + std::unordered_map glyphs_; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/asset/msdf_font_loader.h b/Extra2D/include/extra2d/asset/msdf_font_loader.h new file mode 100644 index 0000000..065d7a3 --- /dev/null +++ b/Extra2D/include/extra2d/asset/msdf_font_loader.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include +#include + +namespace extra2d { + +/** + * @brief MSDF 字体加载器 + * + * 加载内嵌元数据的 MSDF PNG 图集 + */ +class MSDFFontLoader : public AssetLoader { +public: + Ref load(const std::string& path) override; + Ref loadFromMemory(const u8* data, size_t size) override; + bool canLoad(const std::string& path) const override; + AssetType type() const override { return AssetType::Font; } + std::vector extensions() const override; + +private: + /** + * @brief 解析 PNG 内嵌元数据 + */ + bool parseEmbeddedMetadata(const std::string& path, MSDFFontAsset* asset); + + /** + * @brief 从 tEXt chunk 读取数据 + */ + bool readTextChunk(const std::vector& pngData, + const std::string& keyword, + std::vector& output); + + /** + * @brief 解析 JSON 元数据 + */ + bool parseJsonMetadata(const std::string& json, MSDFFontAsset* asset); +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/asset/shader_asset.h b/Extra2D/include/extra2d/asset/shader_asset.h new file mode 100644 index 0000000..1c8482a --- /dev/null +++ b/Extra2D/include/extra2d/asset/shader_asset.h @@ -0,0 +1,158 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +// 前向声明 +class Material; + +/** + * @brief 着色器资源(增强版) + * + * 管理 OpenGL 着色器程序,支持: + * - 顶点和片段着色器编译 + * - Uniform 信息提取 + * - 默认渲染状态 + * - 材质模板创建 + */ +class ShaderAsset : public Asset { +public: + ShaderAsset(); + ~ShaderAsset() override; + + AssetType type() const override { return AssetType::Shader; } + bool loaded() const override; + size_t memSize() const override; + + /** + * @brief 获取 OpenGL 程序 ID + * @return 程序 ID + */ + GLuint programId() const { return programId_; } + + /** + * @brief 获取 Uniform 信息 + * @param name Uniform 名称 + * @return Uniform 信息指针,不存在返回 nullptr + */ + const UniformInfo* getUniformInfo(const std::string& name) const; + + /** + * @brief 获取 Uniform 位置 + * @param name Uniform 名称 + * @return Uniform 位置 + */ + GLint getUniformLocation(const std::string& name) const; + + /** + * @brief 获取所有 uniforms + * @return Uniform 映射表 + */ + const std::unordered_map& uniforms() const { + return uniforms_; + } + + /** + * @brief 获取默认混合状态 + * @return 混合状态 + */ + const BlendState& blendState() const { return blendState_; } + + /** + * @brief 获取默认深度状态 + * @return 深度状态 + */ + const DepthState& depthState() const { return depthState_; } + + /** + * @brief 应用渲染状态 + */ + void applyStates() const; + + /** + * @brief 使用着色器 + */ + void bind() const; + + /** + * @brief 解除着色器绑定 + */ + void unbind() const; + + /** + * @brief 创建材质模板 + * @return 材质引用 + */ + Ref createMaterial(); + + /** + * @brief 设置 OpenGL 程序(由 ShaderLoader 调用) + * @param programId 程序 ID + */ + void setProgram(GLuint programId); + + /** + * @brief 添加 Uniform 信息(由 ShaderLoader 调用) + * @param info Uniform 信息 + */ + void addUniform(const UniformInfo& info); + + /** + * @brief 设置混合状态 + * @param state 混合状态 + */ + void setBlendState(const BlendState& state) { blendState_ = state; } + + /** + * @brief 设置深度状态 + * @param state 深度状态 + */ + void setDepthState(const DepthState& state) { depthState_ = state; } + + /** + * @brief 标记为已加载 + */ + void markLoaded(); + + /** + * @brief 释放资源 + */ + void release(); + + /** + * @brief 获取顶点着色器源码 + * @return 顶点着色器源码 + */ + const std::string& vertexSource() const { return vertexSrc_; } + + /** + * @brief 获取片段着色器源码 + * @return 片段着色器源码 + */ + const std::string& fragmentSource() const { return fragmentSrc_; } + + /** + * @brief 设置着色器源码 + * @param vertex 顶点着色器源码 + * @param fragment 片段着色器源码 + */ + void setSource(std::string vertex, std::string fragment); + +private: + GLuint programId_ = 0; + std::unordered_map uniforms_; + std::unordered_map uniformLocations_; + BlendState blendState_; + DepthState depthState_; + std::string vertexSrc_; + std::string fragmentSrc_; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/asset/texture_asset.h b/Extra2D/include/extra2d/asset/texture_asset.h new file mode 100644 index 0000000..dd9694d --- /dev/null +++ b/Extra2D/include/extra2d/asset/texture_asset.h @@ -0,0 +1,76 @@ +#pragma once + +#include + +namespace extra2d { + +/** + * @brief 纹理资源类 + * + * 存储纹理图像数据,支持多种像素格式。 + */ +class TextureAsset : public Asset { +public: + AssetType type() const override { return AssetType::Texture; } + + bool loaded() const override { + return state_.load(std::memory_order_acquire) == AssetState::Loaded && + data_ != nullptr; + } + + size_t memSize() const override { + return static_cast(width_) * height_ * channels_; + } + + /** + * @brief 获取纹理宽度 + * @return 宽度(像素) + */ + int width() const { return width_; } + + /** + * @brief 获取纹理高度 + * @return 高度(像素) + */ + int height() const { return height_; } + + /** + * @brief 获取通道数 + * @return 通道数(1-4) + */ + int channels() const { return channels_; } + + /** + * @brief 获取像素数据 + * @return 像素数据指针 + */ + const u8 *data() const { return data_.get(); } + + /** + * @brief 获取像素数据大小 + * @return 数据大小(字节) + */ + size_t dataSize() const { return memSize(); } + + /** + * @brief 设置纹理数据 + * @param width 宽度 + * @param height 高度 + * @param channels 通道数 + * @param data 像素数据(转移所有权) + */ + void setData(int width, int height, int channels, Unique data); + + /** + * @brief 释放纹理数据 + */ + void release(); + +private: + int width_ = 0; + int height_ = 0; + int channels_ = 0; + Unique data_; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/render/material.h b/Extra2D/include/extra2d/render/material.h new file mode 100644 index 0000000..3ddc4df --- /dev/null +++ b/Extra2D/include/extra2d/render/material.h @@ -0,0 +1,131 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +// 前向声明 +class MaterialInstance; + +/** + * @brief 材质模板 + * + * ShaderAsset 的参数配置,可被多个对象共享。 + * 定义了着色器的默认参数值。 + */ +class Material : public std::enable_shared_from_this { +public: + /** + * @brief 构造函数 + * @param shader 着色器资源 + */ + explicit Material(Ref shader); + + /** + * @brief 设置 float 属性 + */ + void setFloat(const std::string& name, float value); + + /** + * @brief 设置 vec2 属性 + */ + void setVec2(const std::string& name, const glm::vec2& value); + + /** + * @brief 设置 vec3 属性 + */ + void setVec3(const std::string& name, const glm::vec3& value); + + /** + * @brief 设置 vec4 属性 + */ + void setVec4(const std::string& name, const glm::vec4& value); + + /** + * @brief 设置 mat4 属性 + */ + void setMat4(const std::string& name, const glm::mat4& value); + + /** + * @brief 设置 int 属性 + */ + void setInt(const std::string& name, int value); + + /** + * @brief 设置 bool 属性 + */ + void setBool(const std::string& name, bool value); + + /** + * @brief 设置纹理 + */ + void setTexture(const std::string& name, GLuint textureId); + + /** + * @brief 设置颜色 + */ + void setColor(const std::string& name, const Color& value); + + /** + * @brief 获取属性 + * @param name 属性名 + * @return 属性值指针,不存在返回 nullptr + */ + const UniformValue* getProperty(const std::string& name) const; + + /** + * @brief 检查是否有属性 + * @param name 属性名 + * @return 存在返回 true + */ + bool hasProperty(const std::string& name) const; + + /** + * @brief 获取着色器 + * @return 着色器指针 + */ + ShaderAsset* shader() const { return shader_.get(); } + + /** + * @brief 获取程序 ID + * @return OpenGL 程序 ID + */ + GLuint programId() const { return shader_ ? shader_->programId() : 0; } + + /** + * @brief 创建实例 + * @return 材质实例 + */ + Ref createInstance(); + + /** + * @brief 应用材质(设置所有属性到着色器) + */ + void apply() const; + + /** + * @brief 获取所有属性 + * @return 属性映射表 + */ + const std::unordered_map& properties() const { + return properties_; + } + +private: + Ref shader_; + std::unordered_map properties_; + std::unordered_map textures_; + mutable std::unordered_map cachedLocations_; + + /** + * @brief 获取 Uniform 位置 + */ + GLint getLocation(const std::string& name) const; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/render/material_instance.h b/Extra2D/include/extra2d/render/material_instance.h new file mode 100644 index 0000000..290f0d3 --- /dev/null +++ b/Extra2D/include/extra2d/render/material_instance.h @@ -0,0 +1,161 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace extra2d { + +/** + * @brief 材质实例 + * + * Material 的实例,可覆盖属性值。 + * 类似 Unity 的 MaterialPropertyBlock。 + */ +class MaterialInstance { +public: + /** + * @brief 构造函数 + * @param material 材质模板 + */ + explicit MaterialInstance(Ref material); + + /** + * @brief 覆盖 float 属性 + */ + void setFloat(const std::string& name, float value); + + /** + * @brief 覆盖 vec2 属性 + */ + void setVec2(const std::string& name, const glm::vec2& value); + + /** + * @brief 覆盖 vec3 属性 + */ + void setVec3(const std::string& name, const glm::vec3& value); + + /** + * @brief 覆盖 vec4 属性 + */ + void setVec4(const std::string& name, const glm::vec4& value); + + /** + * @brief 覆盖 mat4 属性 + */ + void setMat4(const std::string& name, const glm::mat4& value); + + /** + * @brief 覆盖 int 属性 + */ + void setInt(const std::string& name, int value); + + /** + * @brief 覆盖 bool 属性 + */ + void setBool(const std::string& name, bool value); + + /** + * @brief 覆盖纹理 + */ + void setTexture(const std::string& name, GLuint textureId); + + /** + * @brief 覆盖颜色 + */ + void setColor(const std::string& name, const Color& value); + + /** + * @brief 设置模型矩阵 + */ + void setModelMatrix(const glm::mat4& model); + + /** + * @brief 设置视图矩阵 + */ + void setViewMatrix(const glm::mat4& view); + + /** + * @brief 设置投影矩阵 + */ + void setProjectionMatrix(const glm::mat4& projection); + + /** + * @brief 设置 MVP 矩阵 + */ + void setMVP(const glm::mat4& mvp); + + /** + * @brief 设置颜色(快捷方法) + */ + void setColor(const Color& color); + + /** + * @brief 获取覆盖值 + * @param name 属性名 + * @return 覆盖值指针,不存在返回 nullptr + */ + const UniformValue* getOverride(const std::string& name) const; + + /** + * @brief 检查是否有覆盖 + * @param name 属性名 + * @return 存在返回 true + */ + bool hasOverride(const std::string& name) const; + + /** + * @brief 清除覆盖 + * @param name 属性名 + */ + void clearOverride(const std::string& name); + + /** + * @brief 清除所有覆盖 + */ + void clearAllOverrides(); + + /** + * @brief 获取材质 + * @return 材质指针 + */ + Material* material() const { return material_.get(); } + + /** + * @brief 获取着色器 + * @return 着色器指针 + */ + ShaderAsset* shader() const { return material_ ? material_->shader() : nullptr; } + + /** + * @brief 获取程序 ID + * @return OpenGL 程序 ID + */ + GLuint programId() const { return material_ ? material_->programId() : 0; } + + /** + * @brief 应用实例(先应用材质,再应用覆盖) + */ + void apply() const; + + /** + * @brief 检查是否有覆盖 + * @return 有覆盖返回 true + */ + bool hasOverrides() const { return !overrides_.empty(); } + +private: + Ref material_; + std::unordered_map overrides_; + std::unordered_map textureOverrides_; + mutable std::unordered_map cachedLocations_; + + /** + * @brief 获取 Uniform 位置 + */ + GLint getLocation(const std::string& name) const; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/render/material_property_block.h b/Extra2D/include/extra2d/render/material_property_block.h new file mode 100644 index 0000000..c3d56aa --- /dev/null +++ b/Extra2D/include/extra2d/render/material_property_block.h @@ -0,0 +1,152 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +// 前向声明 +class ShaderAsset; + +/** + * @brief 材质属性块 + * + * 用于快速设置每对象属性,避免创建 MaterialInstance。 + * 类似 Unity 的 MaterialPropertyBlock。 + * + * 使用场景: + * - 批量渲染时设置每实例不同的属性 + * - 临时修改属性而不影响材质 + */ +class MaterialPropertyBlock { +public: + MaterialPropertyBlock() = default; + + /** + * @brief 设置 float 属性 + */ + void setFloat(const std::string& name, float value); + + /** + * @brief 设置 vec2 属性 + */ + void setVec2(const std::string& name, const glm::vec2& value); + + /** + * @brief 设置 vec3 属性 + */ + void setVec3(const std::string& name, const glm::vec3& value); + + /** + * @brief 设置 vec4 属性 + */ + void setVec4(const std::string& name, const glm::vec4& value); + + /** + * @brief 设置 mat4 属性 + */ + void setMat4(const std::string& name, const glm::mat4& value); + + /** + * @brief 设置 int 属性 + */ + void setInt(const std::string& name, int value); + + /** + * @brief 设置 bool 属性 + */ + void setBool(const std::string& name, bool value); + + /** + * @brief 设置纹理 + * @param name 属性名 + * @param textureId 纹理 ID + * @param unit 纹理单元(-1 表示自动分配) + */ + void setTexture(const std::string& name, GLuint textureId, int unit = -1); + + /** + * @brief 设置颜色 + */ + void setColor(const std::string& name, const Color& value); + + /** + * @brief 设置模型矩阵 + */ + void setModelMatrix(const glm::mat4& model); + + /** + * @brief 设置视图矩阵 + */ + void setViewMatrix(const glm::mat4& view); + + /** + * @brief 设置投影矩阵 + */ + void setProjectionMatrix(const glm::mat4& projection); + + /** + * @brief 设置 MVP 矩阵 + */ + void setMVP(const glm::mat4& mvp); + + /** + * @brief 设置颜色(快捷方法) + */ + void setColor(const Color& color); + + /** + * @brief 应用到着色器 + * @param shader 着色器资源 + */ + void apply(ShaderAsset* shader) const; + + /** + * @brief 应用到着色器程序 + * @param programId 程序 ID + */ + void apply(GLuint programId) const; + + /** + * @brief 清除所有属性 + */ + void clear(); + + /** + * @brief 检查是否为空 + * @return 为空返回 true + */ + bool isEmpty() const { return properties_.empty() && textures_.empty(); } + + /** + * @brief 获取属性数量 + */ + size_t propertyCount() const { return properties_.size(); } + + /** + * @brief 获取纹理数量 + */ + size_t textureCount() const { return textures_.size(); } + +private: + struct TextureBinding { + std::string name; + GLuint textureId; + int unit; + }; + + std::unordered_map properties_; + std::vector textures_; + mutable std::unordered_map cachedLocations_; + + /** + * @brief 获取 Uniform 位置 + */ + GLint getLocation(GLuint programId, const std::string& name) const; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/render/msdf_text_renderer.h b/Extra2D/include/extra2d/render/msdf_text_renderer.h new file mode 100644 index 0000000..118c793 --- /dev/null +++ b/Extra2D/include/extra2d/render/msdf_text_renderer.h @@ -0,0 +1,118 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace extra2d { + +/** + * @brief MSDF 文本渲染器 + * + * 支持任意大小的字体渲染 + */ +class MSDFTextRenderer { +public: + MSDFTextRenderer(); + ~MSDFTextRenderer(); + + /** + * @brief 初始化 + * @return 成功返回 true + */ + bool init(); + + /** + * @brief 关闭 + */ + void shutdown(); + + /** + * @brief 设置字体 + * @param font 字体资源 + */ + void setFont(Ref font) { font_ = font; } + + /** + * @brief 获取字体 + * @return 字体资源 + */ + Ref font() const { return font_; } + + /** + * @brief 设置文本 + * @param text UTF-32 文本 + */ + void setText(const std::u32string& text) { text_ = text; } + + /** + * @brief 设置文本(UTF-8) + * @param utf8Text UTF-8 文本 + */ + void setText(const std::string& utf8Text); + + /** + * @brief 设置位置 + * @param pos 位置 + */ + void setPosition(const glm::vec2& pos) { position_ = pos; } + + /** + * @brief 设置字体大小 + * @param size 目标像素高度 + */ + void setFontSize(float size); + + /** + * @brief 设置颜色 + * @param color 颜色 + */ + void setColor(const Color& color) { color_ = color; } + + /** + * @brief 设置材质 + * @param material 材质实例 + */ + void setMaterial(Ref material) { material_ = material; } + + /** + * @brief 获取文本尺寸 + * @return 尺寸 + */ + glm::vec2 getSize() const; + + /** + * @brief 渲染 + * @param projection 投影矩阵 + */ + void render(const glm::mat4& projection); + +private: + Ref font_; + std::u32string text_; + glm::vec2 position_ = glm::vec2(0.0f); + float fontSize_ = 48.0f; + float scale_ = 1.0f; + Color color_ = Colors::White; + Ref material_; + + struct Vertex { + glm::vec2 position; + glm::vec2 texCoord; + }; + std::vector vertices_; + std::vector indices_; + + GLuint vao_ = 0; + GLuint vbo_ = 0; + GLuint ibo_ = 0; + GLuint shader_ = 0; + + void updateGeometry(); + void draw(); + bool createShader(); +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/render/sprite_renderer.h b/Extra2D/include/extra2d/render/sprite_renderer.h index 7badb2b..f5f0038 100644 --- a/Extra2D/include/extra2d/render/sprite_renderer.h +++ b/Extra2D/include/extra2d/render/sprite_renderer.h @@ -1,8 +1,11 @@ #pragma once -#include -#include #include +#include +#include +#include +#include +#include #include #include #include @@ -16,89 +19,124 @@ namespace extra2d { * @brief 精灵顶点 */ struct SpriteVertex { - float x, y; - float u, v; - float r, g, b, a; + float x, y; + float u, v; + float r, g, b, a; }; /** * @brief 精灵绘制数据 */ struct SpriteData { - glm::vec2 position = glm::vec2(0.0f); - glm::vec2 size = glm::vec2(1.0f); - glm::vec2 anchor = glm::vec2(0.5f); - glm::vec4 color = glm::vec4(1.0f); - glm::vec4 texRect = glm::vec4(0.0f, 0.0f, 1.0f, 1.0f); - float rotation = 0.0f; - float depth = 0.0f; + glm::vec2 position = glm::vec2(0.0f); + glm::vec2 size = glm::vec2(1.0f); + glm::vec2 anchor = glm::vec2(0.5f); + glm::vec4 color = glm::vec4(1.0f); + glm::vec4 texRect = glm::vec4(0.0f, 0.0f, 1.0f, 1.0f); + float rotation = 0.0f; + float depth = 0.0f; }; /** * @brief 精灵渲染器 - * - * 高性能批量精灵渲染器 + * + * 高性能批量精灵渲染器,支持 Material 系统 */ class SpriteRenderer { 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; + 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; - SpriteRenderer(); - ~SpriteRenderer(); + SpriteRenderer(); + ~SpriteRenderer(); - /** - * @brief 初始化渲染器 - */ - bool init(); + /** + * @brief 初始化渲染器 + */ + bool init(); - /** - * @brief 关闭渲染器 - */ - void shutdown(); + /** + * @brief 关闭渲染器 + */ + void shutdown(); - /** - * @brief 开始批量渲染 - */ - void begin(const glm::mat4& viewProjection); + /** + * @brief 设置材质 + * @param material 材质模板 + */ + void setMaterial(Ref material) { material_ = material; } - /** - * @brief 绘制精灵 - */ - void draw(GLuint texture, const SpriteData& data); + /** + * @brief 获取材质 + * @return 材质模板 + */ + Ref material() const { return material_; } - /** - * @brief 结束批量渲染 - */ - void end(); + /** + * @brief 开始批量渲染 + */ + void begin(const glm::mat4 &viewProjection); - /** - * @brief 获取统计信息 - */ - uint32 drawCalls() const { return drawCalls_; } - uint32 spriteCount() const { return spriteCount_; } + /** + * @brief 绘制精灵(使用默认材质) + * @param texture 纹理 ID + * @param data 精灵数据 + */ + void draw(GLuint texture, const SpriteData &data); + + /** + * @brief 绘制精灵(使用材质实例) + * @param texture 纹理 ID + * @param data 精灵数据 + * @param instance 材质实例 + */ + void draw(GLuint texture, const SpriteData &data, MaterialInstance *instance); + + /** + * @brief 绘制精灵(使用属性块) + * @param texture 纹理 ID + * @param data 精灵数据 + * @param block 属性块 + */ + void draw(GLuint texture, const SpriteData &data, + const MaterialPropertyBlock &block); + + /** + * @brief 结束批量渲染 + */ + void end(); + + /** + * @brief 获取统计信息 + */ + uint32 drawCalls() const { return drawCalls_; } + uint32 spriteCount() const { return spriteCount_; } private: - std::unique_ptr vao_; - std::unique_ptr vbo_; - std::unique_ptr ibo_; + std::unique_ptr vao_; + std::unique_ptr vbo_; + std::unique_ptr ibo_; - Array vertices_; - size_t vertexCount_ = 0; + Array vertices_; + size_t vertexCount_ = 0; - GLuint shader_ = 0; - GLuint currentTexture_ = 0; - glm::mat4 viewProjection_; + GLuint shader_ = 0; + GLuint currentTexture_ = 0; + glm::mat4 viewProjection_; - uint32 drawCalls_ = 0; - uint32 spriteCount_ = 0; + Ref material_; + MaterialInstance *currentInstance_ = nullptr; - bool createShader(); - void flush(); - void addQuad(const SpriteData& data, GLuint texture); + uint32 drawCalls_ = 0; + uint32 spriteCount_ = 0; + + bool createShader(); + void flush(); + void flushWithMaterial(); + void addQuad(const SpriteData &data, GLuint texture); }; } // namespace extra2d diff --git a/Extra2D/include/extra2d/render/uniform_info.h b/Extra2D/include/extra2d/render/uniform_info.h new file mode 100644 index 0000000..32965c2 --- /dev/null +++ b/Extra2D/include/extra2d/render/uniform_info.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +namespace extra2d { + +/** + * @brief Uniform 元信息 + * + * 描述着色器中的 uniform 变量信息 + */ +struct UniformInfo { + std::string name; ///< Uniform 名称 + UniformType type; ///< 数据类型 + GLint location = -1; ///< OpenGL 位置 + UniformValue defaultValue; ///< 默认值 + bool isArray = false; ///< 是否为数组 + size_t arraySize = 1; ///< 数组大小 + + UniformInfo() : type(UniformType::None) {} + + UniformInfo(const std::string& n, UniformType t, GLint loc = -1) + : name(n), type(t), location(loc) {} +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/render/uniform_value.h b/Extra2D/include/extra2d/render/uniform_value.h new file mode 100644 index 0000000..b21b411 --- /dev/null +++ b/Extra2D/include/extra2d/render/uniform_value.h @@ -0,0 +1,171 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +/** + * @brief Uniform 值类型枚举 + */ +enum class UniformType : u8 { + None = 0, + Float, + Vec2, + Vec3, + Vec4, + Mat2, + Mat3, + Mat4, + Int, + IVec2, + IVec3, + IVec4, + Bool, + Sampler2D, + SamplerCube +}; + +/** + * @brief Uniform 值变体类型 + */ +using UniformValueVariant = std::variant< + std::monostate, + float, + glm::vec2, + glm::vec3, + glm::vec4, + glm::mat2, + glm::mat3, + glm::mat4, + int, + glm::ivec2, + glm::ivec3, + glm::ivec4, + bool, + GLuint +>; + +/** + * @brief Uniform 值封装类 + * + * 支持多种数据类型的统一封装,用于材质参数传递 + */ +class UniformValue { +public: + UniformValue() = default; + + /** + * @brief 从 float 构造 + */ + explicit UniformValue(float v); + + /** + * @brief 从 vec2 构造 + */ + explicit UniformValue(const glm::vec2& v); + + /** + * @brief 从 vec3 构造 + */ + explicit UniformValue(const glm::vec3& v); + + /** + * @brief 从 vec4 构造 + */ + explicit UniformValue(const glm::vec4& v); + + /** + * @brief 从 mat2 构造 + */ + explicit UniformValue(const glm::mat2& v); + + /** + * @brief 从 mat3 构造 + */ + explicit UniformValue(const glm::mat3& v); + + /** + * @brief 从 mat4 构造 + */ + explicit UniformValue(const glm::mat4& v); + + /** + * @brief 从 int 构造 + */ + explicit UniformValue(int v); + + /** + * @brief 从 ivec2 构造 + */ + explicit UniformValue(const glm::ivec2& v); + + /** + * @brief 从 ivec3 构造 + */ + explicit UniformValue(const glm::ivec3& v); + + /** + * @brief 从 ivec4 构造 + */ + explicit UniformValue(const glm::ivec4& v); + + /** + * @brief 从 bool 构造 + */ + explicit UniformValue(bool v); + + /** + * @brief 从纹理 ID 构造 + */ + explicit UniformValue(GLuint textureId); + + /** + * @brief 从 Color 构造 + */ + explicit UniformValue(const Color& color); + + /** + * @brief 获取类型 + * @return Uniform 类型 + */ + UniformType type() const; + + /** + * @brief 获取值指针 + * @tparam T 值类型 + * @return 值指针,类型不匹配返回 nullptr + */ + template + const T* get() const { + return std::get_if(&value_); + } + + /** + * @brief 应用到着色器 + * @param location Uniform 位置 + */ + void apply(GLint location) const; + + /** + * @brief 检查是否为空 + * @return 为空返回 true + */ + bool isEmpty() const; + + /** + * @brief 获取类型名称 + * @param type Uniform 类型 + * @return 类型名称字符串 + */ + static const char* typeName(UniformType type); + +private: + UniformValueVariant value_; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/services/asset_service.h b/Extra2D/include/extra2d/services/asset_service.h index f8adf9a..db4ab27 100644 --- a/Extra2D/include/extra2d/services/asset_service.h +++ b/Extra2D/include/extra2d/services/asset_service.h @@ -8,6 +8,11 @@ #include #include #include +#include +#include +#include +#include +#include #include #include #include diff --git a/Extra2D/src/asset/asset.cpp b/Extra2D/src/asset/asset.cpp index 66aa9e4..1e16faa 100644 --- a/Extra2D/src/asset/asset.cpp +++ b/Extra2D/src/asset/asset.cpp @@ -1,63 +1,8 @@ #include -#define STB_TRUETYPE_IMPLEMENTATION -#include - namespace extra2d { -// --------------------------------------------------------------------------- -// FontAsset::Impl - Pimpl 实现类 -// --------------------------------------------------------------------------- - -class FontAsset::Impl { -public: - stbtt_fontinfo info; - bool initialized = false; -}; - -// --------------------------------------------------------------------------- -// FontAsset 实现 -// --------------------------------------------------------------------------- - -FontAsset::FontAsset() : impl_(ptr::unique()) {} - -FontAsset::~FontAsset() = default; - -bool FontAsset::loaded() const { - return state_.load(std::memory_order_acquire) == AssetState::Loaded && - impl_->initialized; -} - -float FontAsset::scaleForPixelHeight(float pixels) const { - if (!impl_->initialized || data_.empty()) { - return 0.0f; - } - return stbtt_ScaleForPixelHeight(&impl_->info, pixels); -} - -bool FontAsset::setData(std::vector data) { - if (data.empty()) { - return false; - } - - data_ = std::move(data); - - if (!stbtt_InitFont(&impl_->info, data_.data(), 0)) { - data_.clear(); - impl_->initialized = false; - setState(AssetState::Failed); - return false; - } - - impl_->initialized = true; - setState(AssetState::Loaded); - return true; -} - -void FontAsset::release() { - data_.clear(); - impl_->initialized = false; - setState(AssetState::Unloaded); -} +// Asset 基类目前没有需要单独实现的方法 +// 所有资源类型的实现都在各自的文件中 } // namespace extra2d diff --git a/Extra2D/src/asset/asset_loader.cpp b/Extra2D/src/asset/asset_loader.cpp index 078e43e..4bf24ad 100644 --- a/Extra2D/src/asset/asset_loader.cpp +++ b/Extra2D/src/asset/asset_loader.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #define STB_IMAGE_IMPLEMENTATION #include @@ -166,47 +167,293 @@ std::vector FontLoader::extensions() const { // --------------------------------------------------------------------------- Ref ShaderLoader::load(const std::string &path) { - auto data = readFile(path); - if (data.empty()) { - E2D_ERROR(CAT_ASSET, "Failed to read shader file: {}", path); - return nullptr; - } - return loadFromMemory(data.data(), data.size()); + std::string ext = getExtension(path); + + if (ext == ".json") { + return loadFromJson(path); + } + + auto data = readFile(path); + if (data.empty()) { + E2D_ERROR(CAT_ASSET, "Failed to read shader file: {}", path); + return nullptr; + } + return loadFromMemory(data.data(), data.size()); +} + +Ref ShaderLoader::loadFromJson(const std::string &path) { + auto data = readFile(path); + if (data.empty()) { + E2D_ERROR(CAT_ASSET, "Failed to read shader config: {}", path); + return nullptr; + } + + std::string content(reinterpret_cast(data.data()), data.size()); + + try { + auto json = nlohmann::json::parse(content); + + std::string vertexPath = json.value("vertex", ""); + std::string fragmentPath = json.value("fragment", ""); + + if (vertexPath.empty() || fragmentPath.empty()) { + E2D_ERROR(CAT_ASSET, "Shader config missing vertex or fragment path"); + return nullptr; + } + + std::string basePath; + size_t lastSlash = path.rfind('/'); + if (lastSlash != std::string::npos) { + basePath = path.substr(0, lastSlash + 1); + } + + std::string vertexSrc = loadSource(basePath + vertexPath); + std::string fragmentSrc = loadSource(basePath + fragmentPath); + + if (vertexSrc.empty() || fragmentSrc.empty()) { + E2D_ERROR(CAT_ASSET, "Failed to load shader sources"); + return nullptr; + } + + GLuint vertexShader = compileShader(vertexSrc, GL_VERTEX_SHADER); + GLuint fragmentShader = compileShader(fragmentSrc, GL_FRAGMENT_SHADER); + + if (vertexShader == 0 || fragmentShader == 0) { + if (vertexShader) glDeleteShader(vertexShader); + if (fragmentShader) glDeleteShader(fragmentShader); + return nullptr; + } + + GLuint program = linkProgram(vertexShader, fragmentShader); + + glDeleteShader(vertexShader); + glDeleteShader(fragmentShader); + + if (program == 0) { + return nullptr; + } + + auto asset = ptr::make(); + asset->setProgram(program); + asset->setSource(std::move(vertexSrc), std::move(fragmentSrc)); + + extractUniforms(program, asset.get()); + + if (json.contains("states")) { + auto states = json["states"]; + if (states.contains("blend")) { + asset->setBlendState(parseBlendState(states["blend"].dump())); + } + if (states.contains("depth")) { + asset->setDepthState(parseDepthState(states["depth"].dump())); + } + } + + asset->markLoaded(); + return asset; + + } catch (const std::exception& e) { + E2D_ERROR(CAT_ASSET, "Failed to parse shader config: {}", e.what()); + return nullptr; + } +} + +std::string ShaderLoader::loadSource(const std::string &path) { + auto data = readFile(path); + if (data.empty()) { + return ""; + } + return std::string(reinterpret_cast(data.data()), data.size()); +} + +GLuint ShaderLoader::compileShader(const std::string &source, GLenum type) { + GLuint shader = glCreateShader(type); + const char* src = source.c_str(); + glShaderSource(shader, 1, &src, nullptr); + glCompileShader(shader); + + GLint success; + glGetShaderiv(shader, GL_COMPILE_STATUS, &success); + + if (!success) { + char infoLog[512]; + glGetShaderInfoLog(shader, sizeof(infoLog), nullptr, infoLog); + E2D_ERROR(CAT_ASSET, "Shader compilation failed: {}", infoLog); + glDeleteShader(shader); + return 0; + } + + return shader; +} + +GLuint ShaderLoader::linkProgram(GLuint vertexShader, GLuint fragmentShader) { + GLuint program = glCreateProgram(); + glAttachShader(program, vertexShader); + glAttachShader(program, fragmentShader); + glLinkProgram(program); + + GLint success; + glGetProgramiv(program, GL_LINK_STATUS, &success); + + if (!success) { + char infoLog[512]; + glGetProgramInfoLog(program, sizeof(infoLog), nullptr, infoLog); + E2D_ERROR(CAT_ASSET, "Shader program linking failed: {}", infoLog); + glDeleteProgram(program); + return 0; + } + + return program; +} + +void ShaderLoader::extractUniforms(GLuint program, ShaderAsset *asset) { + GLint uniformCount = 0; + glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &uniformCount); + + for (GLint i = 0; i < uniformCount; ++i) { + char name[256]; + GLsizei nameLen = 0; + GLint size = 0; + GLenum type = 0; + + glGetActiveUniform(program, i, sizeof(name), &nameLen, &size, &type, name); + + UniformInfo info; + info.name = std::string(name, nameLen); + info.location = glGetUniformLocation(program, name); + info.isArray = (size > 1); + info.arraySize = static_cast(size); + + switch (type) { + case GL_FLOAT: info.type = UniformType::Float; break; + case GL_FLOAT_VEC2: info.type = UniformType::Vec2; break; + case GL_FLOAT_VEC3: info.type = UniformType::Vec3; break; + case GL_FLOAT_VEC4: info.type = UniformType::Vec4; break; + case GL_FLOAT_MAT2: info.type = UniformType::Mat2; break; + case GL_FLOAT_MAT3: info.type = UniformType::Mat3; break; + case GL_FLOAT_MAT4: info.type = UniformType::Mat4; break; + case GL_INT: info.type = UniformType::Int; break; + case GL_INT_VEC2: info.type = UniformType::IVec2; break; + case GL_INT_VEC3: info.type = UniformType::IVec3; break; + case GL_INT_VEC4: info.type = UniformType::IVec4; break; + case GL_BOOL: info.type = UniformType::Bool; break; + case GL_SAMPLER_2D: info.type = UniformType::Sampler2D; break; + case GL_SAMPLER_CUBE: info.type = UniformType::SamplerCube; break; + default: info.type = UniformType::None; break; + } + + asset->addUniform(info); + } +} + +BlendState ShaderLoader::parseBlendState(const std::string &json) { + BlendState state; + try { + auto j = nlohmann::json::parse(json); + state.enabled = j.value("enabled", false); + + auto parseBlendFactor = [](const std::string &s) -> BlendFactor { + if (s == "ZERO") return BlendFactor::Zero; + if (s == "ONE") return BlendFactor::One; + if (s == "SRC_COLOR") return BlendFactor::SrcColor; + if (s == "ONE_MINUS_SRC_COLOR") return BlendFactor::OneMinusSrcColor; + if (s == "DST_COLOR") return BlendFactor::DstColor; + if (s == "ONE_MINUS_DST_COLOR") return BlendFactor::OneMinusDstColor; + if (s == "SRC_ALPHA") return BlendFactor::SrcAlpha; + if (s == "ONE_MINUS_SRC_ALPHA") return BlendFactor::OneMinusSrcAlpha; + return BlendFactor::SrcAlpha; + }; + + if (j.contains("src")) state.srcRGB = parseBlendFactor(j["src"]); + if (j.contains("dst")) state.dstRGB = parseBlendFactor(j["dst"]); + + } catch (...) {} + return state; +} + +DepthState ShaderLoader::parseDepthState(const std::string &json) { + DepthState state; + try { + auto j = nlohmann::json::parse(json); + state.testEnabled = j.value("enabled", false); + state.writeEnabled = j.value("write", true); + } catch (...) {} + return state; +} + +UniformType ShaderLoader::parseUniformType(const std::string &typeStr) { + if (typeStr == "float") return UniformType::Float; + if (typeStr == "vec2") return UniformType::Vec2; + if (typeStr == "vec3") return UniformType::Vec3; + if (typeStr == "vec4") return UniformType::Vec4; + if (typeStr == "mat2") return UniformType::Mat2; + if (typeStr == "mat3") return UniformType::Mat3; + if (typeStr == "mat4") return UniformType::Mat4; + if (typeStr == "int") return UniformType::Int; + if (typeStr == "ivec2") return UniformType::IVec2; + if (typeStr == "ivec3") return UniformType::IVec3; + if (typeStr == "ivec4") return UniformType::IVec4; + if (typeStr == "bool") return UniformType::Bool; + if (typeStr == "sampler2D") return UniformType::Sampler2D; + if (typeStr == "samplerCube") return UniformType::SamplerCube; + return UniformType::None; +} +UniformValue ShaderLoader::parseDefaultValue(const std::string &json, UniformType type) { + try { + auto j = nlohmann::json::parse(json); + + switch (type) { + case UniformType::Float: + return UniformValue(j.get()); + case UniformType::Vec2: + return UniformValue(glm::vec2(j[0].get(), j[1].get())); + case UniformType::Vec3: + return UniformValue(glm::vec3(j[0].get(), j[1].get(), j[2].get())); + case UniformType::Vec4: + return UniformValue(glm::vec4(j[0].get(), j[1].get(), j[2].get(), j[3].get())); + case UniformType::Int: + return UniformValue(j.get()); + case UniformType::Bool: + return UniformValue(j.get()); + default: + break; + } + } catch (...) {} + return UniformValue(); } Ref ShaderLoader::loadFromMemory(const u8 *data, size_t size) { - if (!data || size == 0) { - return nullptr; - } - - std::string content(reinterpret_cast(data), size); - - std::string vertexSrc, fragmentSrc; - - if (content.find(vertexMarker_) != std::string::npos) { - if (!parseCombined(content, vertexSrc, fragmentSrc)) { - E2D_ERROR(CAT_ASSET, "Failed to parse combined shader file"); - return nullptr; + if (!data || size == 0) { + return nullptr; } - } else { - vertexSrc = content; - fragmentSrc = content; - } - auto asset = ptr::make(); - asset->setSource(std::move(vertexSrc), std::move(fragmentSrc)); + std::string content(reinterpret_cast(data), size); - return asset; + std::string vertexSrc, fragmentSrc; + + if (content.find(vertexMarker_) != std::string::npos) { + if (!parseCombined(content, vertexSrc, fragmentSrc)) { + E2D_ERROR(CAT_ASSET, "Failed to parse combined shader file"); + return nullptr; + } + } else { + vertexSrc = content; + fragmentSrc = content; + } + + auto asset = ptr::make(); + asset->setSource(std::move(vertexSrc), std::move(fragmentSrc)); + + return asset; } bool ShaderLoader::canLoad(const std::string &path) const { - std::string ext = getExtension(path); - auto exts = extensions(); - return std::find(exts.begin(), exts.end(), ext) != exts.end(); + std::string ext = getExtension(path); + auto exts = extensions(); + return std::find(exts.begin(), exts.end(), ext) != exts.end(); } - std::vector ShaderLoader::extensions() const { - return {".vert", ".frag", ".glsl", ".vs", ".fs"}; + return {".json", ".vert", ".frag", ".glsl", ".vs", ".fs"}; } bool ShaderLoader::parseCombined(const std::string &content, diff --git a/Extra2D/src/asset/audio_asset.cpp b/Extra2D/src/asset/audio_asset.cpp new file mode 100644 index 0000000..e781456 --- /dev/null +++ b/Extra2D/src/asset/audio_asset.cpp @@ -0,0 +1,37 @@ +#include + +namespace extra2d { + +void AudioAsset::setData(AudioFormat format, int channels, int sampleRate, + int bitsPerSample, std::vector data) { + format_ = format; + channels_ = channels; + sampleRate_ = sampleRate; + bitsPerSample_ = bitsPerSample; + data_ = std::move(data); + + if (sampleRate > 0 && channels > 0 && bitsPerSample > 0) { + size_t bytesPerSecond = + static_cast(sampleRate) * channels * (bitsPerSample / 8); + if (bytesPerSecond > 0) { + duration_ = static_cast(data_.size()) / + static_cast(bytesPerSecond); + } + } + + streaming_ = duration_ > 5.0f; + setState(AssetState::Loaded); +} + +void AudioAsset::release() { + data_.clear(); + format_ = AudioFormat::PCM; + channels_ = 0; + sampleRate_ = 0; + bitsPerSample_ = 0; + duration_ = 0.0f; + streaming_ = false; + setState(AssetState::Unloaded); +} + +} // namespace extra2d diff --git a/Extra2D/src/asset/data_asset.cpp b/Extra2D/src/asset/data_asset.cpp new file mode 100644 index 0000000..fd603d5 --- /dev/null +++ b/Extra2D/src/asset/data_asset.cpp @@ -0,0 +1,15 @@ +#include + +namespace extra2d { + +void DataAsset::setData(std::vector data) { + data_ = std::move(data); + setState(AssetState::Loaded); +} + +void DataAsset::release() { + data_.clear(); + setState(AssetState::Unloaded); +} + +} // namespace extra2d diff --git a/Extra2D/src/asset/font_asset.cpp b/Extra2D/src/asset/font_asset.cpp new file mode 100644 index 0000000..dad5611 --- /dev/null +++ b/Extra2D/src/asset/font_asset.cpp @@ -0,0 +1,55 @@ +#include + +#define STB_TRUETYPE_IMPLEMENTATION +#include + +namespace extra2d { + +class FontAsset::Impl { +public: + stbtt_fontinfo info; + bool initialized = false; +}; + +FontAsset::FontAsset() : impl_(ptr::unique()) {} + +FontAsset::~FontAsset() = default; + +bool FontAsset::loaded() const { + return state_.load(std::memory_order_acquire) == AssetState::Loaded && + impl_->initialized; +} + +float FontAsset::scaleForPixelHeight(float pixels) const { + if (!impl_->initialized || data_.empty()) { + return 0.0f; + } + return stbtt_ScaleForPixelHeight(&impl_->info, pixels); +} + +bool FontAsset::setData(std::vector data) { + if (data.empty()) { + return false; + } + + data_ = std::move(data); + + if (!stbtt_InitFont(&impl_->info, data_.data(), 0)) { + data_.clear(); + impl_->initialized = false; + setState(AssetState::Failed); + return false; + } + + impl_->initialized = true; + setState(AssetState::Loaded); + return true; +} + +void FontAsset::release() { + data_.clear(); + impl_->initialized = false; + setState(AssetState::Unloaded); +} + +} // namespace extra2d diff --git a/Extra2D/src/asset/msdf_font_asset.cpp b/Extra2D/src/asset/msdf_font_asset.cpp new file mode 100644 index 0000000..4576abd --- /dev/null +++ b/Extra2D/src/asset/msdf_font_asset.cpp @@ -0,0 +1,87 @@ +#include + +namespace extra2d { + +MSDFFontAsset::MSDFFontAsset() = default; + +MSDFFontAsset::~MSDFFontAsset() { + release(); +} + +bool MSDFFontAsset::loaded() const { + return state_.load(std::memory_order_acquire) == AssetState::Loaded && + textureId_ != 0; +} + +size_t MSDFFontAsset::memSize() const { + return glyphs_.size() * sizeof(GlyphInfo); +} + +const GlyphInfo* MSDFFontAsset::getGlyph(char32_t codepoint) const { + auto it = glyphs_.find(codepoint); + if (it != glyphs_.end()) { + return &it->second; + } + return nullptr; +} + +bool MSDFFontAsset::hasGlyph(char32_t codepoint) const { + return glyphs_.find(codepoint) != glyphs_.end(); +} + +std::vector MSDFFontAsset::getMissingChars(const std::u32string& text) const { + std::vector missing; + for (char32_t c : text) { + if (!hasGlyph(c)) { + missing.push_back(c); + } + } + return missing; +} + +bool MSDFFontAsset::canRender(const std::u32string& text) const { + for (char32_t c : text) { + if (!hasGlyph(c)) { + return false; + } + } + return true; +} + +glm::vec2 MSDFFontAsset::measureText(const std::u32string& text, float scale) const { + if (text.empty()) return glm::vec2(0.0f); + + float width = 0.0f; + float maxAscent = 0.0f; + float maxDescent = 0.0f; + + for (char32_t c : text) { + const GlyphInfo* glyph = getGlyph(c); + if (glyph) { + width += glyph->advance * scale; + maxAscent = glm::max(maxAscent, glyph->bearing.y * scale); + maxDescent = glm::max(maxDescent, (glyph->size.y - glyph->bearing.y) * scale); + } + } + + return glm::vec2(width, maxAscent + maxDescent); +} + +void MSDFFontAsset::addGlyph(const GlyphInfo& glyph) { + glyphs_[glyph.codepoint] = glyph; +} + +void MSDFFontAsset::markLoaded() { + setState(AssetState::Loaded); +} + +void MSDFFontAsset::release() { + if (textureId_ != 0) { + glDeleteTextures(1, &textureId_); + textureId_ = 0; + } + glyphs_.clear(); + setState(AssetState::Unloaded); +} + +} // namespace extra2d diff --git a/Extra2D/src/asset/msdf_font_loader.cpp b/Extra2D/src/asset/msdf_font_loader.cpp new file mode 100644 index 0000000..fc5ee4e --- /dev/null +++ b/Extra2D/src/asset/msdf_font_loader.cpp @@ -0,0 +1,183 @@ +#include +#include +#include +#include +#include + +namespace extra2d { + +namespace { + +std::vector readFile(const std::string& path) { + std::ifstream file(path, std::ios::binary | std::ios::ate); + if (!file) { + return {}; + } + + size_t size = static_cast(file.tellg()); + file.seekg(0, std::ios::beg); + + std::vector data(size); + if (!file.read(reinterpret_cast(data.data()), size)) { + return {}; + } + + return data; +} + +std::string getExtension(const std::string& path) { + size_t pos = path.rfind('.'); + if (pos == std::string::npos) { + return ""; + } + std::string ext = path.substr(pos); + std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower); + return ext; +} + +} + +Ref MSDFFontLoader::load(const std::string& path) { + auto asset = ptr::make(); + + if (!parseEmbeddedMetadata(path, asset.get())) { + E2D_ERROR(CAT_ASSET, "Failed to parse MSDF font metadata: {}", path); + return nullptr; + } + + asset->markLoaded(); + return asset; +} + +Ref MSDFFontLoader::loadFromMemory(const u8* data, size_t size) { + E2D_ERROR(CAT_ASSET, "MSDF font loading from memory not supported"); + return nullptr; +} + +bool MSDFFontLoader::canLoad(const std::string& path) const { + std::string ext = getExtension(path); + return ext == ".msdf" || ext == ".png"; +} + +std::vector MSDFFontLoader::extensions() const { + return {".msdf", ".png"}; +} + +bool MSDFFontLoader::parseEmbeddedMetadata(const std::string& path, MSDFFontAsset* asset) { + auto pngData = readFile(path); + if (pngData.empty()) { + return false; + } + + std::vector metadataJson; + if (!readTextChunk(pngData, "msdf", metadataJson)) { + return false; + } + + std::string jsonStr(metadataJson.begin(), metadataJson.end()); + if (!parseJsonMetadata(jsonStr, asset)) { + return false; + } + + GLuint texture; + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_2D, texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + asset->setTextureId(texture); + + return true; +} + +bool MSDFFontLoader::readTextChunk(const std::vector& pngData, + const std::string& keyword, + std::vector& output) { + if (pngData.size() < 8) return false; + + if (pngData[0] != 0x89 || pngData[1] != 'P' || pngData[2] != 'N' || pngData[3] != 'G') { + return false; + } + + size_t pos = 8; + while (pos < pngData.size()) { + if (pos + 8 > pngData.size()) break; + + u32 length = (static_cast(pngData[pos]) << 24) | + (static_cast(pngData[pos + 1]) << 16) | + (static_cast(pngData[pos + 2]) << 8) | + static_cast(pngData[pos + 3]); + + std::string chunkType(pngData.begin() + pos + 4, pngData.begin() + pos + 8); + + if (chunkType == "tEXt") { + size_t dataStart = pos + 8; + size_t nullPos = dataStart; + while (nullPos < pngData.size() && pngData[nullPos] != 0) { + nullPos++; + } + + std::string foundKeyword(pngData.begin() + dataStart, pngData.begin() + nullPos); + if (foundKeyword == keyword) { + output.assign(pngData.begin() + nullPos + 1, pngData.begin() + pos + 8 + length); + return true; + } + } + + pos += 12 + length; + } + + return false; +} + +bool MSDFFontLoader::parseJsonMetadata(const std::string& json, MSDFFontAsset* asset) { + try { + auto j = nlohmann::json::parse(json); + + asset->setFontSize(j.value("size", 48)); + asset->setPxRange(j.value("pxRange", 4.0f)); + asset->setLineHeight(j.value("lineHeight", 60)); + asset->setBaseline(j.value("baseline", 12)); + + if (j.contains("atlas")) { + auto atlas = j["atlas"]; + asset->setAtlasSize(atlas.value("width", 2048), atlas.value("height", 2048)); + } + + if (j.contains("glyphs")) { + for (const auto& glyphJson : j["glyphs"]) { + GlyphInfo glyph; + glyph.codepoint = glyphJson.value("unicode", 0u); + + if (glyphJson.contains("atlasBounds")) { + auto bounds = glyphJson["atlasBounds"]; + glyph.uvMin.x = bounds.value("left", 0.0f) / asset->atlasWidth(); + glyph.uvMin.y = bounds.value("top", 0.0f) / asset->atlasHeight(); + glyph.uvMax.x = bounds.value("right", 0.0f) / asset->atlasWidth(); + glyph.uvMax.y = bounds.value("bottom", 0.0f) / asset->atlasHeight(); + } + + if (glyphJson.contains("planeBounds")) { + auto plane = glyphJson["planeBounds"]; + glyph.size.x = plane.value("right", 0.0f) - plane.value("left", 0.0f); + glyph.size.y = plane.value("top", 0.0f) - plane.value("bottom", 0.0f); + glyph.bearing.x = plane.value("left", 0.0f); + glyph.bearing.y = plane.value("top", 0.0f); + } + + glyph.advance = glyphJson.value("advance", 0.0f); + + asset->addGlyph(glyph); + } + } + + return true; + } catch (const std::exception& e) { + E2D_ERROR(CAT_ASSET, "Failed to parse MSDF metadata: {}", e.what()); + return false; + } +} + +} // namespace extra2d diff --git a/Extra2D/src/asset/shader_asset.cpp b/Extra2D/src/asset/shader_asset.cpp new file mode 100644 index 0000000..ab2be7c --- /dev/null +++ b/Extra2D/src/asset/shader_asset.cpp @@ -0,0 +1,116 @@ +#include +#include +#include + +namespace extra2d { + +ShaderAsset::ShaderAsset() = default; + +ShaderAsset::~ShaderAsset() { + release(); +} + +bool ShaderAsset::loaded() const { + return state_.load(std::memory_order_acquire) == AssetState::Loaded && + programId_ != 0; +} + +size_t ShaderAsset::memSize() const { + return vertexSrc_.size() + fragmentSrc_.size() + uniforms_.size() * sizeof(UniformInfo); +} + +const UniformInfo* ShaderAsset::getUniformInfo(const std::string& name) const { + auto it = uniforms_.find(name); + if (it != uniforms_.end()) { + return &it->second; + } + return nullptr; +} + +GLint ShaderAsset::getUniformLocation(const std::string& name) const { + auto it = uniformLocations_.find(name); + if (it != uniformLocations_.end()) { + return it->second; + } + if (programId_ != 0) { + GLint loc = glGetUniformLocation(programId_, name.c_str()); + const_cast(this)->uniformLocations_[name] = loc; + return loc; + } + return -1; +} + +void ShaderAsset::applyStates() const { + if (blendState_.enabled) { + glEnable(GL_BLEND); + glBlendFuncSeparate( + static_cast(blendState_.srcRGB), + static_cast(blendState_.dstRGB), + static_cast(blendState_.srcAlpha), + static_cast(blendState_.dstAlpha) + ); + glBlendEquationSeparate( + static_cast(blendState_.opRGB), + static_cast(blendState_.opAlpha) + ); + } else { + glDisable(GL_BLEND); + } + + if (depthState_.testEnabled) { + glEnable(GL_DEPTH_TEST); + glDepthMask(depthState_.writeEnabled ? GL_TRUE : GL_FALSE); + glDepthFunc(static_cast(depthState_.compareFunc)); + } else { + glDisable(GL_DEPTH_TEST); + } +} + +void ShaderAsset::bind() const { + if (programId_ != 0) { + glUseProgram(programId_); + } +} + +void ShaderAsset::unbind() const { + glUseProgram(0); +} + +Ref ShaderAsset::createMaterial() { + return ptr::make(std::static_pointer_cast(shared_from_this())); +} + +void ShaderAsset::setProgram(GLuint programId) { + if (programId_ != 0 && programId_ != programId) { + glDeleteProgram(programId_); + } + programId_ = programId; +} + +void ShaderAsset::addUniform(const UniformInfo& info) { + uniforms_[info.name] = info; + uniformLocations_[info.name] = info.location; +} + +void ShaderAsset::markLoaded() { + setState(AssetState::Loaded); +} + +void ShaderAsset::release() { + if (programId_ != 0) { + glDeleteProgram(programId_); + programId_ = 0; + } + uniforms_.clear(); + uniformLocations_.clear(); + vertexSrc_.clear(); + fragmentSrc_.clear(); + setState(AssetState::Unloaded); +} + +void ShaderAsset::setSource(std::string vertex, std::string fragment) { + vertexSrc_ = std::move(vertex); + fragmentSrc_ = std::move(fragment); +} + +} // namespace extra2d diff --git a/Extra2D/src/asset/texture_asset.cpp b/Extra2D/src/asset/texture_asset.cpp new file mode 100644 index 0000000..1ffb6dc --- /dev/null +++ b/Extra2D/src/asset/texture_asset.cpp @@ -0,0 +1,21 @@ +#include + +namespace extra2d { + +void TextureAsset::setData(int width, int height, int channels, Unique data) { + width_ = width; + height_ = height; + channels_ = channels; + data_ = std::move(data); + setState(AssetState::Loaded); +} + +void TextureAsset::release() { + data_.reset(); + width_ = 0; + height_ = 0; + channels_ = 0; + setState(AssetState::Unloaded); +} + +} // namespace extra2d diff --git a/Extra2D/src/render/material.cpp b/Extra2D/src/render/material.cpp new file mode 100644 index 0000000..8ee4667 --- /dev/null +++ b/Extra2D/src/render/material.cpp @@ -0,0 +1,101 @@ +#include +#include +#include + +namespace extra2d { + +Material::Material(Ref shader) : shader_(std::move(shader)) {} + +void Material::setFloat(const std::string& name, float value) { + properties_[name] = UniformValue(value); +} + +void Material::setVec2(const std::string& name, const glm::vec2& value) { + properties_[name] = UniformValue(value); +} + +void Material::setVec3(const std::string& name, const glm::vec3& value) { + properties_[name] = UniformValue(value); +} + +void Material::setVec4(const std::string& name, const glm::vec4& value) { + properties_[name] = UniformValue(value); +} + +void Material::setMat4(const std::string& name, const glm::mat4& value) { + properties_[name] = UniformValue(value); +} + +void Material::setInt(const std::string& name, int value) { + properties_[name] = UniformValue(value); +} + +void Material::setBool(const std::string& name, bool value) { + properties_[name] = UniformValue(value); +} + +void Material::setTexture(const std::string& name, GLuint textureId) { + textures_[name] = textureId; + properties_[name] = UniformValue(textureId); +} + +void Material::setColor(const std::string& name, const Color& value) { + properties_[name] = UniformValue(value); +} + +const UniformValue* Material::getProperty(const std::string& name) const { + auto it = properties_.find(name); + if (it != properties_.end()) { + return &it->second; + } + return nullptr; +} + +bool Material::hasProperty(const std::string& name) const { + return properties_.find(name) != properties_.end(); +} + +Ref Material::createInstance() { + return ptr::make(std::static_pointer_cast(shared_from_this())); +} + +void Material::apply() const { + if (!shader_ || shader_->programId() == 0) return; + + shader_->bind(); + shader_->applyStates(); + + int textureUnit = 0; + for (const auto& [name, value] : properties_) { + GLint location = getLocation(name); + if (location < 0) continue; + + if (value.type() == UniformType::Sampler2D) { + const GLuint* texId = value.get(); + if (texId && *texId != 0) { + glActiveTexture(GL_TEXTURE0 + textureUnit); + glBindTexture(GL_TEXTURE_2D, *texId); + glUniform1i(location, textureUnit); + textureUnit++; + } + } else { + value.apply(location); + } + } +} + +GLint Material::getLocation(const std::string& name) const { + auto it = cachedLocations_.find(name); + if (it != cachedLocations_.end()) { + return it->second; + } + + GLint location = -1; + if (shader_) { + location = shader_->getUniformLocation(name); + } + cachedLocations_[name] = location; + return location; +} + +} // namespace extra2d diff --git a/Extra2D/src/render/material_instance.cpp b/Extra2D/src/render/material_instance.cpp new file mode 100644 index 0000000..d707707 --- /dev/null +++ b/Extra2D/src/render/material_instance.cpp @@ -0,0 +1,128 @@ +#include +#include + +namespace extra2d { + +MaterialInstance::MaterialInstance(Ref material) + : material_(std::move(material)) {} + +void MaterialInstance::setFloat(const std::string& name, float value) { + overrides_[name] = UniformValue(value); +} + +void MaterialInstance::setVec2(const std::string& name, const glm::vec2& value) { + overrides_[name] = UniformValue(value); +} + +void MaterialInstance::setVec3(const std::string& name, const glm::vec3& value) { + overrides_[name] = UniformValue(value); +} + +void MaterialInstance::setVec4(const std::string& name, const glm::vec4& value) { + overrides_[name] = UniformValue(value); +} + +void MaterialInstance::setMat4(const std::string& name, const glm::mat4& value) { + overrides_[name] = UniformValue(value); +} + +void MaterialInstance::setInt(const std::string& name, int value) { + overrides_[name] = UniformValue(value); +} + +void MaterialInstance::setBool(const std::string& name, bool value) { + overrides_[name] = UniformValue(value); +} + +void MaterialInstance::setTexture(const std::string& name, GLuint textureId) { + textureOverrides_[name] = textureId; + overrides_[name] = UniformValue(textureId); +} + +void MaterialInstance::setColor(const std::string& name, const Color& value) { + overrides_[name] = UniformValue(value); +} + +void MaterialInstance::setModelMatrix(const glm::mat4& model) { + overrides_["u_model"] = UniformValue(model); +} + +void MaterialInstance::setViewMatrix(const glm::mat4& view) { + overrides_["u_view"] = UniformValue(view); +} + +void MaterialInstance::setProjectionMatrix(const glm::mat4& projection) { + overrides_["u_projection"] = UniformValue(projection); +} + +void MaterialInstance::setMVP(const glm::mat4& mvp) { + overrides_["u_mvp"] = UniformValue(mvp); +} + +void MaterialInstance::setColor(const Color& color) { + overrides_["u_color"] = UniformValue(color); +} + +const UniformValue* MaterialInstance::getOverride(const std::string& name) const { + auto it = overrides_.find(name); + if (it != overrides_.end()) { + return &it->second; + } + return nullptr; +} + +bool MaterialInstance::hasOverride(const std::string& name) const { + return overrides_.find(name) != overrides_.end(); +} + +void MaterialInstance::clearOverride(const std::string& name) { + overrides_.erase(name); + textureOverrides_.erase(name); +} + +void MaterialInstance::clearAllOverrides() { + overrides_.clear(); + textureOverrides_.clear(); +} + +void MaterialInstance::apply() const { + if (!material_) return; + + material_->apply(); + + int textureUnit = 0; + for (const auto& [name, texId] : textureOverrides_) { + GLint location = getLocation(name); + if (location >= 0 && texId != 0) { + glActiveTexture(GL_TEXTURE0 + textureUnit); + glBindTexture(GL_TEXTURE_2D, texId); + glUniform1i(location, textureUnit); + textureUnit++; + } + } + + for (const auto& [name, value] : overrides_) { + if (textureOverrides_.find(name) != textureOverrides_.end()) continue; + + GLint location = getLocation(name); + if (location >= 0 && value.type() != UniformType::Sampler2D) { + value.apply(location); + } + } +} + +GLint MaterialInstance::getLocation(const std::string& name) const { + auto it = cachedLocations_.find(name); + if (it != cachedLocations_.end()) { + return it->second; + } + + GLint location = -1; + if (material_ && material_->shader()) { + location = material_->shader()->getUniformLocation(name); + } + cachedLocations_[name] = location; + return location; +} + +} // namespace extra2d diff --git a/Extra2D/src/render/material_property_block.cpp b/Extra2D/src/render/material_property_block.cpp new file mode 100644 index 0000000..a220f89 --- /dev/null +++ b/Extra2D/src/render/material_property_block.cpp @@ -0,0 +1,129 @@ +#include +#include +#include + +namespace extra2d { + +void MaterialPropertyBlock::setFloat(const std::string& name, float value) { + properties_[name] = UniformValue(value); +} + +void MaterialPropertyBlock::setVec2(const std::string& name, const glm::vec2& value) { + properties_[name] = UniformValue(value); +} + +void MaterialPropertyBlock::setVec3(const std::string& name, const glm::vec3& value) { + properties_[name] = UniformValue(value); +} + +void MaterialPropertyBlock::setVec4(const std::string& name, const glm::vec4& value) { + properties_[name] = UniformValue(value); +} + +void MaterialPropertyBlock::setMat4(const std::string& name, const glm::mat4& value) { + properties_[name] = UniformValue(value); +} + +void MaterialPropertyBlock::setInt(const std::string& name, int value) { + properties_[name] = UniformValue(value); +} + +void MaterialPropertyBlock::setBool(const std::string& name, bool value) { + properties_[name] = UniformValue(value); +} + +void MaterialPropertyBlock::setTexture(const std::string& name, GLuint textureId, int unit) { + for (auto& tex : textures_) { + if (tex.name == name) { + tex.textureId = textureId; + tex.unit = unit; + return; + } + } + textures_.push_back({name, textureId, unit}); + properties_[name] = UniformValue(textureId); +} + +void MaterialPropertyBlock::setColor(const std::string& name, const Color& value) { + properties_[name] = UniformValue(value); +} + +void MaterialPropertyBlock::setModelMatrix(const glm::mat4& model) { + properties_["u_model"] = UniformValue(model); +} + +void MaterialPropertyBlock::setViewMatrix(const glm::mat4& view) { + properties_["u_view"] = UniformValue(view); +} + +void MaterialPropertyBlock::setProjectionMatrix(const glm::mat4& projection) { + properties_["u_projection"] = UniformValue(projection); +} + +void MaterialPropertyBlock::setMVP(const glm::mat4& mvp) { + properties_["u_mvp"] = UniformValue(mvp); +} + +void MaterialPropertyBlock::setColor(const Color& color) { + properties_["u_color"] = UniformValue(color); +} + +void MaterialPropertyBlock::apply(ShaderAsset* shader) const { + if (shader) { + apply(shader->programId()); + } +} + +void MaterialPropertyBlock::apply(GLuint programId) const { + if (programId == 0) return; + + int nextUnit = 0; + for (const auto& tex : textures_) { + int unit = (tex.unit >= 0) ? tex.unit : nextUnit++; + if (tex.textureId != 0) { + glActiveTexture(GL_TEXTURE0 + unit); + glBindTexture(GL_TEXTURE_2D, tex.textureId); + + GLint location = getLocation(programId, tex.name); + if (location >= 0) { + glUniform1i(location, unit); + } + } + } + + for (const auto& [name, value] : properties_) { + bool isTexture = false; + for (const auto& tex : textures_) { + if (tex.name == name) { + isTexture = true; + break; + } + } + + if (!isTexture) { + GLint location = getLocation(programId, name); + if (location >= 0) { + value.apply(location); + } + } + } +} + +void MaterialPropertyBlock::clear() { + properties_.clear(); + textures_.clear(); + cachedLocations_.clear(); +} + +GLint MaterialPropertyBlock::getLocation(GLuint programId, const std::string& name) const { + auto it = cachedLocations_.find(name); + if (it != cachedLocations_.end()) { + return it->second; + } + + GLint location = glGetUniformLocation(programId, name.c_str()); + cachedLocations_[name] = location; + return location; +} + +} // namespace extra2d diff --git a/Extra2D/src/render/msdf_text_renderer.cpp b/Extra2D/src/render/msdf_text_renderer.cpp new file mode 100644 index 0000000..5a476fe --- /dev/null +++ b/Extra2D/src/render/msdf_text_renderer.cpp @@ -0,0 +1,217 @@ +#include +#include +#include + +namespace extra2d { + +static const char* MSDF_VERTEX_SHADER = R"( +#version 450 core +layout(location = 0) in vec2 a_position; +layout(location = 1) in vec2 a_texCoord; + +uniform mat4 u_projection; + +out vec2 v_texCoord; + +void main() { + gl_Position = u_projection * vec4(a_position, 0.0, 1.0); + v_texCoord = a_texCoord; +} +)"; + +static const char* MSDF_FRAGMENT_SHADER = R"( +#version 450 core +in vec2 v_texCoord; + +uniform sampler2D u_texture; +uniform float u_pxRange; +uniform vec4 u_color; + +out vec4 fragColor; + +float median(float r, float g, float b) { + return max(min(r, g), min(max(r, g), b)); +} + +void main() { + vec3 msdf = texture(u_texture, v_texCoord).rgb; + float sigDist = median(msdf.r, msdf.g, msdf.b); + + float pxRange = u_pxRange; + float alpha = smoothstep(0.5 - 0.5/pxRange, 0.5 + 0.5/pxRange, sigDist); + + fragColor = u_color * vec4(1.0, 1.0, 1.0, alpha); +} +)"; + +MSDFTextRenderer::MSDFTextRenderer() = default; + +MSDFTextRenderer::~MSDFTextRenderer() { + shutdown(); +} + +bool MSDFTextRenderer::init() { + return createShader(); +} + +void MSDFTextRenderer::shutdown() { + if (shader_) { + glDeleteProgram(shader_); + shader_ = 0; + } + if (vao_) { + glDeleteVertexArrays(1, &vao_); + vao_ = 0; + } + if (vbo_) { + glDeleteBuffers(1, &vbo_); + vbo_ = 0; + } + if (ibo_) { + glDeleteBuffers(1, &ibo_); + ibo_ = 0; + } +} + +void MSDFTextRenderer::setText(const std::string& utf8Text) { + size_t len = simdutf::utf32_length_from_utf8(utf8Text.data(), utf8Text.size()); + text_.resize(len); + simdutf::convert_utf8_to_utf32(utf8Text.data(), utf8Text.size(), text_.data()); +} + +void MSDFTextRenderer::setFontSize(float size) { + fontSize_ = size; + if (font_) { + scale_ = size / static_cast(font_->fontSize()); + } +} + +glm::vec2 MSDFTextRenderer::getSize() const { + if (font_) { + return font_->measureText(text_, scale_); + } + return glm::vec2(0.0f); +} + +void MSDFTextRenderer::render(const glm::mat4& projection) { + if (!font_ || text_.empty()) return; + + updateGeometry(); + + glUseProgram(shader_); + + GLint projLoc = glGetUniformLocation(shader_, "u_projection"); + glUniformMatrix4fv(projLoc, 1, GL_FALSE, &projection[0][0]); + + GLint texLoc = glGetUniformLocation(shader_, "u_texture"); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, font_->textureId()); + glUniform1i(texLoc, 0); + + GLint pxRangeLoc = glGetUniformLocation(shader_, "u_pxRange"); + glUniform1f(pxRangeLoc, font_->pxRange() * scale_); + + GLint colorLoc = glGetUniformLocation(shader_, "u_color"); + glUniform4f(colorLoc, color_.r, color_.g, color_.b, color_.a); + + draw(); +} + +void MSDFTextRenderer::updateGeometry() { + vertices_.clear(); + indices_.clear(); + + float x = position_.x; + float y = position_.y; + + for (char32_t c : text_) { + const GlyphInfo* glyph = font_->getGlyph(c); + if (!glyph) continue; + + float x0 = x + glyph->bearing.x * scale_; + float y0 = y - glyph->bearing.y * scale_; + float x1 = x0 + glyph->size.x * scale_; + float y1 = y0 + glyph->size.y * scale_; + + uint32_t base = static_cast(vertices_.size()); + + vertices_.push_back({{x0, y0}, {glyph->uvMin.x, glyph->uvMin.y}}); + vertices_.push_back({{x1, y0}, {glyph->uvMax.x, glyph->uvMin.y}}); + vertices_.push_back({{x1, y1}, {glyph->uvMax.x, glyph->uvMax.y}}); + vertices_.push_back({{x0, y1}, {glyph->uvMin.x, glyph->uvMax.y}}); + + 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); + + x += glyph->advance * scale_; + } +} + +void MSDFTextRenderer::draw() { + if (vertices_.empty()) return; + + if (!vao_) { + glGenVertexArrays(1, &vao_); + glGenBuffers(1, &vbo_); + glGenBuffers(1, &ibo_); + } + + glBindVertexArray(vao_); + + glBindBuffer(GL_ARRAY_BUFFER, vbo_); + glBufferData(GL_ARRAY_BUFFER, vertices_.size() * sizeof(Vertex), vertices_.data(), GL_DYNAMIC_DRAW); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices_.size() * sizeof(uint32_t), indices_.data(), 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)); + + glDrawElements(GL_TRIANGLES, static_cast(indices_.size()), GL_UNSIGNED_INT, nullptr); + + glBindVertexArray(0); +} + +bool MSDFTextRenderer::createShader() { + GLuint vs = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vs, 1, &MSDF_VERTEX_SHADER, nullptr); + glCompileShader(vs); + + GLint success; + glGetShaderiv(vs, GL_COMPILE_STATUS, &success); + if (!success) { + glDeleteShader(vs); + return false; + } + + GLuint fs = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(fs, 1, &MSDF_FRAGMENT_SHADER, nullptr); + glCompileShader(fs); + + glGetShaderiv(fs, GL_COMPILE_STATUS, &success); + if (!success) { + glDeleteShader(vs); + glDeleteShader(fs); + return false; + } + + shader_ = glCreateProgram(); + glAttachShader(shader_, vs); + glAttachShader(shader_, fs); + glLinkProgram(shader_); + + glGetProgramiv(shader_, GL_LINK_STATUS, &success); + glDeleteShader(vs); + glDeleteShader(fs); + + return success == GL_TRUE; +} + +} // namespace extra2d diff --git a/Extra2D/src/render/sprite_renderer.cpp b/Extra2D/src/render/sprite_renderer.cpp index 078129a..7fd7579 100644 --- a/Extra2D/src/render/sprite_renderer.cpp +++ b/Extra2D/src/render/sprite_renderer.cpp @@ -94,12 +94,14 @@ void SpriteRenderer::shutdown() { vao_.reset(); vbo_.reset(); ibo_.reset(); + material_.reset(); } void SpriteRenderer::begin(const glm::mat4& viewProjection) { viewProjection_ = viewProjection; vertexCount_ = 0; currentTexture_ = 0; + currentInstance_ = nullptr; drawCalls_ = 0; spriteCount_ = 0; } @@ -114,8 +116,41 @@ void SpriteRenderer::draw(GLuint texture, const SpriteData& data) { spriteCount_++; } -void SpriteRenderer::end() { +void SpriteRenderer::draw(GLuint texture, const SpriteData& data, MaterialInstance* instance) { + if (material_ && instance) { + if (instance != currentInstance_ || texture != currentTexture_ || + vertexCount_ + VERTICES_PER_SPRITE > MAX_VERTICES) { + flushWithMaterial(); + currentInstance_ = instance; + currentTexture_ = texture; + } + addQuad(data, texture); + spriteCount_++; + } else { + draw(texture, data); + } +} + +void SpriteRenderer::draw(GLuint texture, const SpriteData& data, const MaterialPropertyBlock& block) { flush(); + currentTexture_ = texture; + addQuad(data, texture); + spriteCount_++; + + flush(); + + if (material_ && material_->shader()) { + material_->shader()->bind(); + block.apply(material_->shader()); + } +} + +void SpriteRenderer::end() { + if (material_ && currentInstance_) { + flushWithMaterial(); + } else { + flush(); + } } bool SpriteRenderer::createShader() { @@ -158,14 +193,66 @@ void SpriteRenderer::flush() { vbo_->update(0, vertexCount_ * sizeof(SpriteVertex), vertices_.data()); - glUseProgram(shader_); - GLint vpLoc = glGetUniformLocation(shader_, "u_viewProjection"); - glUniformMatrix4fv(vpLoc, 1, GL_FALSE, &viewProjection_[0][0]); + if (material_ && material_->shader()) { + material_->shader()->bind(); + material_->shader()->applyStates(); + + GLint vpLoc = material_->shader()->getUniformLocation("u_viewProjection"); + if (vpLoc >= 0) { + glUniformMatrix4fv(vpLoc, 1, GL_FALSE, &viewProjection_[0][0]); + } + + GLint texLoc = material_->shader()->getUniformLocation("u_texture"); + if (texLoc >= 0) { + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, currentTexture_); + glUniform1i(texLoc, 0); + } + } else { + glUseProgram(shader_); + GLint vpLoc = glGetUniformLocation(shader_, "u_viewProjection"); + glUniformMatrix4fv(vpLoc, 1, GL_FALSE, &viewProjection_[0][0]); - GLint texLoc = glGetUniformLocation(shader_, "u_texture"); - glUniform1i(texLoc, 0); + GLint texLoc = glGetUniformLocation(shader_, "u_texture"); + glUniform1i(texLoc, 0); - glBindTextureUnit(0, currentTexture_); + glBindTextureUnit(0, currentTexture_); + } + + vao_->bind(); + glDrawElements(GL_TRIANGLES, static_cast(vertexCount_ / VERTICES_PER_SPRITE * INDICES_PER_SPRITE), + GL_UNSIGNED_SHORT, nullptr); + + E2D_RENDER_STATS().addDrawCall(static_cast(vertexCount_), + static_cast(vertexCount_ / 3)); + + drawCalls_++; + vertexCount_ = 0; +} + +void SpriteRenderer::flushWithMaterial() { + if (vertexCount_ == 0) return; + + vbo_->update(0, vertexCount_ * sizeof(SpriteVertex), vertices_.data()); + + if (currentInstance_) { + currentInstance_->apply(); + + ShaderAsset* shader = currentInstance_->shader(); + if (shader) { + GLint vpLoc = shader->getUniformLocation("u_viewProjection"); + if (vpLoc >= 0) { + glUniformMatrix4fv(vpLoc, 1, GL_FALSE, &viewProjection_[0][0]); + } + + GLint texLoc = shader->getUniformLocation("u_texture"); + if (texLoc >= 0) { + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, currentTexture_); + glUniform1i(texLoc, 0); + } + } + } vao_->bind(); glDrawElements(GL_TRIANGLES, static_cast(vertexCount_ / VERTICES_PER_SPRITE * INDICES_PER_SPRITE), diff --git a/Extra2D/src/render/uniform_value.cpp b/Extra2D/src/render/uniform_value.cpp new file mode 100644 index 0000000..c41b9ef --- /dev/null +++ b/Extra2D/src/render/uniform_value.cpp @@ -0,0 +1,128 @@ +#include + +namespace extra2d { + +UniformValue::UniformValue(float v) : value_(v) {} + +UniformValue::UniformValue(const glm::vec2& v) : value_(v) {} + +UniformValue::UniformValue(const glm::vec3& v) : value_(v) {} + +UniformValue::UniformValue(const glm::vec4& v) : value_(v) {} + +UniformValue::UniformValue(const glm::mat2& v) : value_(v) {} + +UniformValue::UniformValue(const glm::mat3& v) : value_(v) {} + +UniformValue::UniformValue(const glm::mat4& v) : value_(v) {} + +UniformValue::UniformValue(int v) : value_(v) {} + +UniformValue::UniformValue(const glm::ivec2& v) : value_(v) {} + +UniformValue::UniformValue(const glm::ivec3& v) : value_(v) {} + +UniformValue::UniformValue(const glm::ivec4& v) : value_(v) {} + +UniformValue::UniformValue(bool v) : value_(v) {} + +UniformValue::UniformValue(GLuint textureId) : value_(textureId) {} + +UniformValue::UniformValue(const Color& color) + : value_(glm::vec4(color.r, color.g, color.b, color.a)) {} + +UniformType UniformValue::type() const { + if (std::holds_alternative(value_)) { + return UniformType::None; + } else if (std::holds_alternative(value_)) { + return UniformType::Float; + } else if (std::holds_alternative(value_)) { + return UniformType::Vec2; + } else if (std::holds_alternative(value_)) { + return UniformType::Vec3; + } else if (std::holds_alternative(value_)) { + return UniformType::Vec4; + } else if (std::holds_alternative(value_)) { + return UniformType::Mat2; + } else if (std::holds_alternative(value_)) { + return UniformType::Mat3; + } else if (std::holds_alternative(value_)) { + return UniformType::Mat4; + } else if (std::holds_alternative(value_)) { + return UniformType::Int; + } else if (std::holds_alternative(value_)) { + return UniformType::IVec2; + } else if (std::holds_alternative(value_)) { + return UniformType::IVec3; + } else if (std::holds_alternative(value_)) { + return UniformType::IVec4; + } else if (std::holds_alternative(value_)) { + return UniformType::Bool; + } else if (std::holds_alternative(value_)) { + return UniformType::Sampler2D; + } + return UniformType::None; +} + +void UniformValue::apply(GLint location) const { + if (location < 0) return; + + std::visit([&](auto&& arg) { + using T = std::decay_t; + + if constexpr (std::is_same_v) { + glUniform1f(location, arg); + } else if constexpr (std::is_same_v) { + glUniform2fv(location, 1, &arg[0]); + } else if constexpr (std::is_same_v) { + glUniform3fv(location, 1, &arg[0]); + } else if constexpr (std::is_same_v) { + glUniform4fv(location, 1, &arg[0]); + } else if constexpr (std::is_same_v) { + glUniformMatrix2fv(location, 1, GL_FALSE, &arg[0][0]); + } else if constexpr (std::is_same_v) { + glUniformMatrix3fv(location, 1, GL_FALSE, &arg[0][0]); + } else if constexpr (std::is_same_v) { + glUniformMatrix4fv(location, 1, GL_FALSE, &arg[0][0]); + } else if constexpr (std::is_same_v) { + glUniform1i(location, arg); + } else if constexpr (std::is_same_v) { + glUniform2iv(location, 1, &arg[0]); + } else if constexpr (std::is_same_v) { + glUniform3iv(location, 1, &arg[0]); + } else if constexpr (std::is_same_v) { + glUniform4iv(location, 1, &arg[0]); + } else if constexpr (std::is_same_v) { + glUniform1i(location, arg ? 1 : 0); + } else if constexpr (std::is_same_v) { + glUniform1i(location, static_cast(arg)); + } + }, value_); +} + +bool UniformValue::isEmpty() const { + return std::holds_alternative(value_); +} + +const char* UniformValue::typeName(UniformType type) { + switch (type) { + case UniformType::None: return "None"; + case UniformType::Float: return "Float"; + case UniformType::Vec2: return "Vec2"; + case UniformType::Vec3: return "Vec3"; + case UniformType::Vec4: return "Vec4"; + case UniformType::Mat2: return "Mat2"; + case UniformType::Mat3: return "Mat3"; + case UniformType::Mat4: return "Mat4"; + case UniformType::Int: return "Int"; + case UniformType::IVec2: return "IVec2"; + case UniformType::IVec3: return "IVec3"; + case UniformType::IVec4: return "IVec4"; + case UniformType::Bool: return "Bool"; + case UniformType::Sampler2D: return "Sampler2D"; + case UniformType::SamplerCube: return "SamplerCube"; + default: return "Unknown"; + } +} + +} // namespace extra2d diff --git a/Extra2D/src/window/event_converter.cpp b/Extra2D/src/window/event_converter.cpp index 574e49e..5dcb079 100644 --- a/Extra2D/src/window/event_converter.cpp +++ b/Extra2D/src/window/event_converter.cpp @@ -1,4 +1,5 @@ #include +#include namespace extra2d { @@ -102,79 +103,79 @@ Event EventConverter::convertTouch(const SDL_TouchFingerEvent& e, EventType type i32 EventConverter::mapKey(SDL_Keycode key) { switch (key) { - case SDLK_a: return 0; - case SDLK_b: return 1; - case SDLK_c: return 2; - case SDLK_d: return 3; - case SDLK_e: return 4; - case SDLK_f: return 5; - case SDLK_g: return 6; - case SDLK_h: return 7; - case SDLK_i: return 8; - case SDLK_j: return 9; - case SDLK_k: return 10; - case SDLK_l: return 11; - case SDLK_m: return 12; - case SDLK_n: return 13; - case SDLK_o: return 14; - case SDLK_p: return 15; - case SDLK_q: return 16; - case SDLK_r: return 17; - case SDLK_s: return 18; - case SDLK_t: return 19; - case SDLK_u: return 20; - case SDLK_v: return 21; - case SDLK_w: return 22; - case SDLK_x: return 23; - case SDLK_y: return 24; - case SDLK_z: return 25; - case SDLK_0: return 26; - case SDLK_1: return 27; - case SDLK_2: return 28; - case SDLK_3: return 29; - case SDLK_4: return 30; - case SDLK_5: return 31; - case SDLK_6: return 32; - case SDLK_7: return 33; - case SDLK_8: return 34; - case SDLK_9: return 35; - case SDLK_F1: return 36; - case SDLK_F2: return 37; - case SDLK_F3: return 38; - case SDLK_F4: return 39; - case SDLK_F5: return 40; - case SDLK_F6: return 41; - case SDLK_F7: return 42; - case SDLK_F8: return 43; - case SDLK_F9: return 44; - case SDLK_F10: return 45; - case SDLK_F11: return 46; - case SDLK_F12: return 47; - case SDLK_SPACE: return 48; - case SDLK_RETURN: return 49; - case SDLK_ESCAPE: return 50; - case SDLK_TAB: return 51; - case SDLK_BACKSPACE: return 52; - case SDLK_INSERT: return 53; - case SDLK_DELETE: return 54; - case SDLK_HOME: return 55; - case SDLK_END: return 56; - case SDLK_PAGEUP: return 57; - case SDLK_PAGEDOWN: return 58; - case SDLK_UP: return 59; - case SDLK_DOWN: return 60; - case SDLK_LEFT: return 61; - case SDLK_RIGHT: return 62; - case SDLK_LSHIFT: return 63; - case SDLK_RSHIFT: return 64; - case SDLK_LCTRL: return 65; - case SDLK_RCTRL: return 66; - case SDLK_LALT: return 67; - case SDLK_RALT: return 68; - case SDLK_CAPSLOCK: return 69; - case SDLK_NUMLOCKCLEAR: return 70; - case SDLK_SCROLLLOCK: return 71; - default: return -1; + case SDLK_a: return static_cast(Key::A); + case SDLK_b: return static_cast(Key::B); + case SDLK_c: return static_cast(Key::C); + case SDLK_d: return static_cast(Key::D); + case SDLK_e: return static_cast(Key::E); + case SDLK_f: return static_cast(Key::F); + case SDLK_g: return static_cast(Key::G); + case SDLK_h: return static_cast(Key::H); + case SDLK_i: return static_cast(Key::I); + case SDLK_j: return static_cast(Key::J); + case SDLK_k: return static_cast(Key::K); + case SDLK_l: return static_cast(Key::L); + case SDLK_m: return static_cast(Key::M); + case SDLK_n: return static_cast(Key::N); + case SDLK_o: return static_cast(Key::O); + case SDLK_p: return static_cast(Key::P); + case SDLK_q: return static_cast(Key::Q); + case SDLK_r: return static_cast(Key::R); + case SDLK_s: return static_cast(Key::S); + case SDLK_t: return static_cast(Key::T); + case SDLK_u: return static_cast(Key::U); + case SDLK_v: return static_cast(Key::V); + case SDLK_w: return static_cast(Key::W); + case SDLK_x: return static_cast(Key::X); + case SDLK_y: return static_cast(Key::Y); + case SDLK_z: return static_cast(Key::Z); + case SDLK_0: return static_cast(Key::Num0); + case SDLK_1: return static_cast(Key::Num1); + case SDLK_2: return static_cast(Key::Num2); + case SDLK_3: return static_cast(Key::Num3); + case SDLK_4: return static_cast(Key::Num4); + case SDLK_5: return static_cast(Key::Num5); + case SDLK_6: return static_cast(Key::Num6); + case SDLK_7: return static_cast(Key::Num7); + case SDLK_8: return static_cast(Key::Num8); + case SDLK_9: return static_cast(Key::Num9); + case SDLK_F1: return static_cast(Key::F1); + case SDLK_F2: return static_cast(Key::F2); + case SDLK_F3: return static_cast(Key::F3); + case SDLK_F4: return static_cast(Key::F4); + case SDLK_F5: return static_cast(Key::F5); + case SDLK_F6: return static_cast(Key::F6); + case SDLK_F7: return static_cast(Key::F7); + case SDLK_F8: return static_cast(Key::F8); + case SDLK_F9: return static_cast(Key::F9); + case SDLK_F10: return static_cast(Key::F10); + case SDLK_F11: return static_cast(Key::F11); + case SDLK_F12: return static_cast(Key::F12); + case SDLK_SPACE: return static_cast(Key::Space); + case SDLK_RETURN: return static_cast(Key::Enter); + case SDLK_ESCAPE: return static_cast(Key::Escape); + case SDLK_TAB: return static_cast(Key::Tab); + case SDLK_BACKSPACE: return static_cast(Key::Backspace); + case SDLK_INSERT: return static_cast(Key::Insert); + case SDLK_DELETE: return static_cast(Key::Delete); + case SDLK_HOME: return static_cast(Key::Home); + case SDLK_END: return static_cast(Key::End); + case SDLK_PAGEUP: return static_cast(Key::PageUp); + case SDLK_PAGEDOWN: return static_cast(Key::PageDown); + case SDLK_UP: return static_cast(Key::Up); + case SDLK_DOWN: return static_cast(Key::Down); + case SDLK_LEFT: return static_cast(Key::Left); + case SDLK_RIGHT: return static_cast(Key::Right); + case SDLK_LSHIFT: return static_cast(Key::LShift); + case SDLK_RSHIFT: return static_cast(Key::RShift); + case SDLK_LCTRL: return static_cast(Key::LCtrl); + case SDLK_RCTRL: return static_cast(Key::RCtrl); + case SDLK_LALT: return static_cast(Key::LAlt); + case SDLK_RALT: return static_cast(Key::RAlt); + case SDLK_CAPSLOCK: return static_cast(Key::CapsLock); + case SDLK_NUMLOCKCLEAR: return static_cast(Key::NumLock); + case SDLK_SCROLLLOCK: return static_cast(Key::ScrollLock); + default: return static_cast(Key::None); } }