feat(asset/render): 添加材质系统和资源类实现
实现新的材质系统包括Material、MaterialInstance和MaterialPropertyBlock类 添加多种资源类实现:TextureAsset、FontAsset、ShaderAsset、AudioAsset等 重构SpriteRenderer以支持材质系统 添加UniformValue和UniformInfo类用于材质参数传递 实现MSDF字体渲染器和文本渲染功能
This commit is contained in:
parent
174d7327ef
commit
70c2806c77
|
|
@ -6,10 +6,16 @@
|
|||
#include <extra2d/core/types.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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<Asset> {
|
||||
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<AssetState> state_{AssetState::Unloaded};
|
||||
AssetID id_;
|
||||
std::string path_;
|
||||
std::atomic<AssetState> 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<size_t>(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<u8[]> 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<u8[]> 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<u8> data);
|
||||
|
||||
/**
|
||||
* @brief 释放字体数据
|
||||
*/
|
||||
void release();
|
||||
|
||||
private:
|
||||
std::vector<u8> data_;
|
||||
class Impl;
|
||||
Unique<Impl> 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<u8> 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<size_t>(sampleRate) * channels * (bitsPerSample / 8);
|
||||
if (bytesPerSecond > 0) {
|
||||
duration_ = static_cast<float>(data_.size()) /
|
||||
static_cast<float>(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<u8> 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<u8> data) {
|
||||
data_ = std::move(data);
|
||||
setState(AssetState::Loaded);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 释放数据
|
||||
*/
|
||||
void release() {
|
||||
data_.clear();
|
||||
setState(AssetState::Unloaded);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<u8> data_;
|
||||
friend class AssetCache;
|
||||
friend class AssetService;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/asset/asset.h>
|
||||
#include <extra2d/asset/asset_types.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <memory>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,13 @@
|
|||
|
||||
#include <extra2d/asset/asset.h>
|
||||
#include <extra2d/asset/asset_types.h>
|
||||
#include <extra2d/asset/texture_asset.h>
|
||||
#include <extra2d/asset/font_asset.h>
|
||||
#include <extra2d/asset/shader_asset.h>
|
||||
#include <extra2d/asset/audio_asset.h>
|
||||
#include <extra2d/asset/data_asset.h>
|
||||
#include <extra2d/render/uniform_value.h>
|
||||
#include <extra2d/render/render_types.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
|
@ -160,44 +167,93 @@ public:
|
|||
* @brief 着色器加载器
|
||||
*
|
||||
* 加载着色器源文件,支持以下格式:
|
||||
* - .json: JSON 配置文件(推荐)
|
||||
* - .vert/.frag: 分离的顶点/片段着色器
|
||||
* - .glsl: 合并的着色器文件(使用标记分隔)
|
||||
*/
|
||||
class ShaderLoader : public AssetLoader<ShaderAsset> {
|
||||
public:
|
||||
Ref<ShaderAsset> load(const std::string &path) override;
|
||||
Ref<ShaderAsset> 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<std::string> extensions() const override;
|
||||
Ref<ShaderAsset> load(const std::string &path) override;
|
||||
Ref<ShaderAsset> 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<std::string> 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<ShaderAsset> 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);
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -0,0 +1,103 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/asset/asset.h>
|
||||
#include <vector>
|
||||
|
||||
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<u8> data);
|
||||
|
||||
/**
|
||||
* @brief 释放音频数据
|
||||
*/
|
||||
void release();
|
||||
|
||||
private:
|
||||
AudioFormat format_ = AudioFormat::PCM;
|
||||
int channels_ = 0;
|
||||
int sampleRate_ = 0;
|
||||
int bitsPerSample_ = 0;
|
||||
float duration_ = 0.0f;
|
||||
std::vector<u8> data_;
|
||||
bool streaming_ = false;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/asset/asset.h>
|
||||
#include <vector>
|
||||
|
||||
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<u8> data);
|
||||
|
||||
/**
|
||||
* @brief 释放数据
|
||||
*/
|
||||
void release();
|
||||
|
||||
private:
|
||||
std::vector<u8> data_;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/asset/asset.h>
|
||||
#include <vector>
|
||||
|
||||
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<u8> data);
|
||||
|
||||
/**
|
||||
* @brief 释放字体数据
|
||||
*/
|
||||
void release();
|
||||
|
||||
private:
|
||||
std::vector<u8> data_;
|
||||
class Impl;
|
||||
Unique<Impl> impl_;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/asset/asset.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <glad/glad.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
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<char32_t> 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<char32_t, GlyphInfo> glyphs_;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/asset/asset_loader.h>
|
||||
#include <extra2d/asset/msdf_font_asset.h>
|
||||
#include <string>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief MSDF 字体加载器
|
||||
*
|
||||
* 加载内嵌元数据的 MSDF PNG 图集
|
||||
*/
|
||||
class MSDFFontLoader : public AssetLoader<MSDFFontAsset> {
|
||||
public:
|
||||
Ref<MSDFFontAsset> load(const std::string& path) override;
|
||||
Ref<MSDFFontAsset> 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<std::string> extensions() const override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 解析 PNG 内嵌元数据
|
||||
*/
|
||||
bool parseEmbeddedMetadata(const std::string& path, MSDFFontAsset* asset);
|
||||
|
||||
/**
|
||||
* @brief 从 tEXt chunk 读取数据
|
||||
*/
|
||||
bool readTextChunk(const std::vector<u8>& pngData,
|
||||
const std::string& keyword,
|
||||
std::vector<u8>& output);
|
||||
|
||||
/**
|
||||
* @brief 解析 JSON 元数据
|
||||
*/
|
||||
bool parseJsonMetadata(const std::string& json, MSDFFontAsset* asset);
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/asset/asset.h>
|
||||
#include <extra2d/render/uniform_value.h>
|
||||
#include <extra2d/render/uniform_info.h>
|
||||
#include <extra2d/render/render_types.h>
|
||||
#include <glad/glad.h>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
|
||||
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<std::string, UniformInfo>& 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<Material> 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<std::string, UniformInfo> uniforms_;
|
||||
std::unordered_map<std::string, GLint> uniformLocations_;
|
||||
BlendState blendState_;
|
||||
DepthState depthState_;
|
||||
std::string vertexSrc_;
|
||||
std::string fragmentSrc_;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/asset/asset.h>
|
||||
|
||||
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<size_t>(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<u8[]> data);
|
||||
|
||||
/**
|
||||
* @brief 释放纹理数据
|
||||
*/
|
||||
void release();
|
||||
|
||||
private:
|
||||
int width_ = 0;
|
||||
int height_ = 0;
|
||||
int channels_ = 0;
|
||||
Unique<u8[]> data_;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/asset/shader_asset.h>
|
||||
#include <extra2d/render/uniform_value.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// 前向声明
|
||||
class MaterialInstance;
|
||||
|
||||
/**
|
||||
* @brief 材质模板
|
||||
*
|
||||
* ShaderAsset 的参数配置,可被多个对象共享。
|
||||
* 定义了着色器的默认参数值。
|
||||
*/
|
||||
class Material : public std::enable_shared_from_this<Material> {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param shader 着色器资源
|
||||
*/
|
||||
explicit Material(Ref<ShaderAsset> 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<MaterialInstance> createInstance();
|
||||
|
||||
/**
|
||||
* @brief 应用材质(设置所有属性到着色器)
|
||||
*/
|
||||
void apply() const;
|
||||
|
||||
/**
|
||||
* @brief 获取所有属性
|
||||
* @return 属性映射表
|
||||
*/
|
||||
const std::unordered_map<std::string, UniformValue>& properties() const {
|
||||
return properties_;
|
||||
}
|
||||
|
||||
private:
|
||||
Ref<ShaderAsset> shader_;
|
||||
std::unordered_map<std::string, UniformValue> properties_;
|
||||
std::unordered_map<std::string, GLuint> textures_;
|
||||
mutable std::unordered_map<std::string, GLint> cachedLocations_;
|
||||
|
||||
/**
|
||||
* @brief 获取 Uniform 位置
|
||||
*/
|
||||
GLint getLocation(const std::string& name) const;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/render/material.h>
|
||||
#include <extra2d/render/uniform_value.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 材质实例
|
||||
*
|
||||
* Material 的实例,可覆盖属性值。
|
||||
* 类似 Unity 的 MaterialPropertyBlock。
|
||||
*/
|
||||
class MaterialInstance {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param material 材质模板
|
||||
*/
|
||||
explicit MaterialInstance(Ref<Material> 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> material_;
|
||||
std::unordered_map<std::string, UniformValue> overrides_;
|
||||
std::unordered_map<std::string, GLuint> textureOverrides_;
|
||||
mutable std::unordered_map<std::string, GLint> cachedLocations_;
|
||||
|
||||
/**
|
||||
* @brief 获取 Uniform 位置
|
||||
*/
|
||||
GLint getLocation(const std::string& name) const;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/render/uniform_value.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <glad/glad.h>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
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<std::string, UniformValue> properties_;
|
||||
std::vector<TextureBinding> textures_;
|
||||
mutable std::unordered_map<std::string, GLint> cachedLocations_;
|
||||
|
||||
/**
|
||||
* @brief 获取 Uniform 位置
|
||||
*/
|
||||
GLint getLocation(GLuint programId, const std::string& name) const;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/asset/msdf_font_asset.h>
|
||||
#include <extra2d/render/material.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief MSDF 文本渲染器
|
||||
*
|
||||
* 支持任意大小的字体渲染
|
||||
*/
|
||||
class MSDFTextRenderer {
|
||||
public:
|
||||
MSDFTextRenderer();
|
||||
~MSDFTextRenderer();
|
||||
|
||||
/**
|
||||
* @brief 初始化
|
||||
* @return 成功返回 true
|
||||
*/
|
||||
bool init();
|
||||
|
||||
/**
|
||||
* @brief 关闭
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
/**
|
||||
* @brief 设置字体
|
||||
* @param font 字体资源
|
||||
*/
|
||||
void setFont(Ref<MSDFFontAsset> font) { font_ = font; }
|
||||
|
||||
/**
|
||||
* @brief 获取字体
|
||||
* @return 字体资源
|
||||
*/
|
||||
Ref<MSDFFontAsset> 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<MaterialInstance> material) { material_ = material; }
|
||||
|
||||
/**
|
||||
* @brief 获取文本尺寸
|
||||
* @return 尺寸
|
||||
*/
|
||||
glm::vec2 getSize() const;
|
||||
|
||||
/**
|
||||
* @brief 渲染
|
||||
* @param projection 投影矩阵
|
||||
*/
|
||||
void render(const glm::mat4& projection);
|
||||
|
||||
private:
|
||||
Ref<MSDFFontAsset> font_;
|
||||
std::u32string text_;
|
||||
glm::vec2 position_ = glm::vec2(0.0f);
|
||||
float fontSize_ = 48.0f;
|
||||
float scale_ = 1.0f;
|
||||
Color color_ = Colors::White;
|
||||
Ref<MaterialInstance> material_;
|
||||
|
||||
struct Vertex {
|
||||
glm::vec2 position;
|
||||
glm::vec2 texCoord;
|
||||
};
|
||||
std::vector<Vertex> vertices_;
|
||||
std::vector<uint32_t> indices_;
|
||||
|
||||
GLuint vao_ = 0;
|
||||
GLuint vbo_ = 0;
|
||||
GLuint ibo_ = 0;
|
||||
GLuint shader_ = 0;
|
||||
|
||||
void updateGeometry();
|
||||
void draw();
|
||||
bool createShader();
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,8 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/render/render_types.h>
|
||||
#include <extra2d/render/render_context.h>
|
||||
#include <extra2d/render/buffer.h>
|
||||
#include <extra2d/render/material.h>
|
||||
#include <extra2d/render/material_instance.h>
|
||||
#include <extra2d/render/material_property_block.h>
|
||||
#include <extra2d/render/render_context.h>
|
||||
#include <extra2d/render/render_types.h>
|
||||
#include <extra2d/render/vao.h>
|
||||
#include <glad/glad.h>
|
||||
#include <glm/mat4x4.hpp>
|
||||
|
|
@ -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_ = material; }
|
||||
|
||||
/**
|
||||
* @brief 绘制精灵
|
||||
*/
|
||||
void draw(GLuint texture, const SpriteData& data);
|
||||
/**
|
||||
* @brief 获取材质
|
||||
* @return 材质模板
|
||||
*/
|
||||
Ref<Material> 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> vao_;
|
||||
std::unique_ptr<VertexBuffer> vbo_;
|
||||
std::unique_ptr<IndexBuffer> ibo_;
|
||||
std::unique_ptr<VAO> vao_;
|
||||
std::unique_ptr<VertexBuffer> vbo_;
|
||||
std::unique_ptr<IndexBuffer> ibo_;
|
||||
|
||||
Array<SpriteVertex> vertices_;
|
||||
size_t vertexCount_ = 0;
|
||||
Array<SpriteVertex> 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> 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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/render/uniform_value.h>
|
||||
#include <string>
|
||||
|
||||
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
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/core/color.h>
|
||||
#include <glad/glad.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <variant>
|
||||
#include <string>
|
||||
|
||||
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<typename T>
|
||||
const T* get() const {
|
||||
return std::get_if<T>(&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
|
||||
|
|
@ -8,6 +8,11 @@
|
|||
#include <extra2d/asset/asset_pack.h>
|
||||
#include <extra2d/asset/asset_types.h>
|
||||
#include <extra2d/asset/data_processor.h>
|
||||
#include <extra2d/asset/texture_asset.h>
|
||||
#include <extra2d/asset/font_asset.h>
|
||||
#include <extra2d/asset/shader_asset.h>
|
||||
#include <extra2d/asset/audio_asset.h>
|
||||
#include <extra2d/asset/data_asset.h>
|
||||
#include <extra2d/core/service_interface.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/services/logger_service.h>
|
||||
|
|
|
|||
|
|
@ -1,63 +1,8 @@
|
|||
#include <extra2d/asset/asset.h>
|
||||
|
||||
#define STB_TRUETYPE_IMPLEMENTATION
|
||||
#include <stb/stb_truetype.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// FontAsset::Impl - Pimpl 实现类
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
class FontAsset::Impl {
|
||||
public:
|
||||
stbtt_fontinfo info;
|
||||
bool initialized = false;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// FontAsset 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
FontAsset::FontAsset() : impl_(ptr::unique<Impl>()) {}
|
||||
|
||||
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<u8> 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
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include <extra2d/asset/asset_loader.h>
|
||||
#include <extra2d/services/logger_service.h>
|
||||
#include <fstream>
|
||||
#include <json/json.hpp>
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include <stb/stb_image.h>
|
||||
|
|
@ -166,47 +167,293 @@ std::vector<std::string> FontLoader::extensions() const {
|
|||
// ---------------------------------------------------------------------------
|
||||
|
||||
Ref<ShaderAsset> 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<ShaderAsset> 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<const char*>(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<ShaderAsset>();
|
||||
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<const char*>(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_t>(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<float>());
|
||||
case UniformType::Vec2:
|
||||
return UniformValue(glm::vec2(j[0].get<float>(), j[1].get<float>()));
|
||||
case UniformType::Vec3:
|
||||
return UniformValue(glm::vec3(j[0].get<float>(), j[1].get<float>(), j[2].get<float>()));
|
||||
case UniformType::Vec4:
|
||||
return UniformValue(glm::vec4(j[0].get<float>(), j[1].get<float>(), j[2].get<float>(), j[3].get<float>()));
|
||||
case UniformType::Int:
|
||||
return UniformValue(j.get<int>());
|
||||
case UniformType::Bool:
|
||||
return UniformValue(j.get<bool>());
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch (...) {}
|
||||
return UniformValue();
|
||||
}
|
||||
|
||||
Ref<ShaderAsset> ShaderLoader::loadFromMemory(const u8 *data, size_t size) {
|
||||
if (!data || size == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string content(reinterpret_cast<const char *>(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<ShaderAsset>();
|
||||
asset->setSource(std::move(vertexSrc), std::move(fragmentSrc));
|
||||
std::string content(reinterpret_cast<const char *>(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<ShaderAsset>();
|
||||
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<std::string> ShaderLoader::extensions() const {
|
||||
return {".vert", ".frag", ".glsl", ".vs", ".fs"};
|
||||
return {".json", ".vert", ".frag", ".glsl", ".vs", ".fs"};
|
||||
}
|
||||
|
||||
bool ShaderLoader::parseCombined(const std::string &content,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
#include <extra2d/asset/audio_asset.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
void AudioAsset::setData(AudioFormat format, int channels, int sampleRate,
|
||||
int bitsPerSample, std::vector<u8> 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<size_t>(sampleRate) * channels * (bitsPerSample / 8);
|
||||
if (bytesPerSecond > 0) {
|
||||
duration_ = static_cast<float>(data_.size()) /
|
||||
static_cast<float>(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
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
#include <extra2d/asset/data_asset.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
void DataAsset::setData(std::vector<u8> data) {
|
||||
data_ = std::move(data);
|
||||
setState(AssetState::Loaded);
|
||||
}
|
||||
|
||||
void DataAsset::release() {
|
||||
data_.clear();
|
||||
setState(AssetState::Unloaded);
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
#include <extra2d/asset/font_asset.h>
|
||||
|
||||
#define STB_TRUETYPE_IMPLEMENTATION
|
||||
#include <stb/stb_truetype.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class FontAsset::Impl {
|
||||
public:
|
||||
stbtt_fontinfo info;
|
||||
bool initialized = false;
|
||||
};
|
||||
|
||||
FontAsset::FontAsset() : impl_(ptr::unique<Impl>()) {}
|
||||
|
||||
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<u8> 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
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
#include <extra2d/asset/msdf_font_asset.h>
|
||||
|
||||
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<char32_t> MSDFFontAsset::getMissingChars(const std::u32string& text) const {
|
||||
std::vector<char32_t> 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
|
||||
|
|
@ -0,0 +1,183 @@
|
|||
#include <extra2d/asset/msdf_font_loader.h>
|
||||
#include <extra2d/services/logger_service.h>
|
||||
#include <json/json.hpp>
|
||||
#include <fstream>
|
||||
#include <glad/glad.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
namespace {
|
||||
|
||||
std::vector<u8> readFile(const std::string& path) {
|
||||
std::ifstream file(path, std::ios::binary | std::ios::ate);
|
||||
if (!file) {
|
||||
return {};
|
||||
}
|
||||
|
||||
size_t size = static_cast<size_t>(file.tellg());
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
std::vector<u8> data(size);
|
||||
if (!file.read(reinterpret_cast<char*>(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<MSDFFontAsset> MSDFFontLoader::load(const std::string& path) {
|
||||
auto asset = ptr::make<MSDFFontAsset>();
|
||||
|
||||
if (!parseEmbeddedMetadata(path, asset.get())) {
|
||||
E2D_ERROR(CAT_ASSET, "Failed to parse MSDF font metadata: {}", path);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
asset->markLoaded();
|
||||
return asset;
|
||||
}
|
||||
|
||||
Ref<MSDFFontAsset> 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<std::string> 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<u8> 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<u8>& pngData,
|
||||
const std::string& keyword,
|
||||
std::vector<u8>& 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<u32>(pngData[pos]) << 24) |
|
||||
(static_cast<u32>(pngData[pos + 1]) << 16) |
|
||||
(static_cast<u32>(pngData[pos + 2]) << 8) |
|
||||
static_cast<u32>(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
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
#include <extra2d/asset/shader_asset.h>
|
||||
#include <extra2d/render/material.h>
|
||||
#include <glad/glad.h>
|
||||
|
||||
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<ShaderAsset*>(this)->uniformLocations_[name] = loc;
|
||||
return loc;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void ShaderAsset::applyStates() const {
|
||||
if (blendState_.enabled) {
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFuncSeparate(
|
||||
static_cast<GLenum>(blendState_.srcRGB),
|
||||
static_cast<GLenum>(blendState_.dstRGB),
|
||||
static_cast<GLenum>(blendState_.srcAlpha),
|
||||
static_cast<GLenum>(blendState_.dstAlpha)
|
||||
);
|
||||
glBlendEquationSeparate(
|
||||
static_cast<GLenum>(blendState_.opRGB),
|
||||
static_cast<GLenum>(blendState_.opAlpha)
|
||||
);
|
||||
} else {
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
|
||||
if (depthState_.testEnabled) {
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glDepthMask(depthState_.writeEnabled ? GL_TRUE : GL_FALSE);
|
||||
glDepthFunc(static_cast<GLenum>(depthState_.compareFunc));
|
||||
} else {
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
}
|
||||
}
|
||||
|
||||
void ShaderAsset::bind() const {
|
||||
if (programId_ != 0) {
|
||||
glUseProgram(programId_);
|
||||
}
|
||||
}
|
||||
|
||||
void ShaderAsset::unbind() const {
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
Ref<Material> ShaderAsset::createMaterial() {
|
||||
return ptr::make<Material>(std::static_pointer_cast<ShaderAsset>(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
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
#include <extra2d/asset/texture_asset.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
void TextureAsset::setData(int width, int height, int channels, Unique<u8[]> 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
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
#include <extra2d/render/material.h>
|
||||
#include <extra2d/render/material_instance.h>
|
||||
#include <glad/glad.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
Material::Material(Ref<ShaderAsset> 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<MaterialInstance> Material::createInstance() {
|
||||
return ptr::make<MaterialInstance>(std::static_pointer_cast<Material>(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<GLuint>();
|
||||
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
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
#include <extra2d/render/material_instance.h>
|
||||
#include <glad/glad.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
MaterialInstance::MaterialInstance(Ref<Material> 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
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
#include <extra2d/render/material_property_block.h>
|
||||
#include <extra2d/asset/shader_asset.h>
|
||||
#include <glad/glad.h>
|
||||
|
||||
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
|
||||
|
|
@ -0,0 +1,217 @@
|
|||
#include <extra2d/render/msdf_text_renderer.h>
|
||||
#include <glad/glad.h>
|
||||
#include <simdutf/simdutf.h>
|
||||
|
||||
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<float>(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<uint32_t>(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<GLsizei>(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
|
||||
|
|
@ -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<GLsizei>(vertexCount_ / VERTICES_PER_SPRITE * INDICES_PER_SPRITE),
|
||||
GL_UNSIGNED_SHORT, nullptr);
|
||||
|
||||
E2D_RENDER_STATS().addDrawCall(static_cast<uint32>(vertexCount_),
|
||||
static_cast<uint32>(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<GLsizei>(vertexCount_ / VERTICES_PER_SPRITE * INDICES_PER_SPRITE),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,128 @@
|
|||
#include <extra2d/render/uniform_value.h>
|
||||
|
||||
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<std::monostate>(value_)) {
|
||||
return UniformType::None;
|
||||
} else if (std::holds_alternative<float>(value_)) {
|
||||
return UniformType::Float;
|
||||
} else if (std::holds_alternative<glm::vec2>(value_)) {
|
||||
return UniformType::Vec2;
|
||||
} else if (std::holds_alternative<glm::vec3>(value_)) {
|
||||
return UniformType::Vec3;
|
||||
} else if (std::holds_alternative<glm::vec4>(value_)) {
|
||||
return UniformType::Vec4;
|
||||
} else if (std::holds_alternative<glm::mat2>(value_)) {
|
||||
return UniformType::Mat2;
|
||||
} else if (std::holds_alternative<glm::mat3>(value_)) {
|
||||
return UniformType::Mat3;
|
||||
} else if (std::holds_alternative<glm::mat4>(value_)) {
|
||||
return UniformType::Mat4;
|
||||
} else if (std::holds_alternative<int>(value_)) {
|
||||
return UniformType::Int;
|
||||
} else if (std::holds_alternative<glm::ivec2>(value_)) {
|
||||
return UniformType::IVec2;
|
||||
} else if (std::holds_alternative<glm::ivec3>(value_)) {
|
||||
return UniformType::IVec3;
|
||||
} else if (std::holds_alternative<glm::ivec4>(value_)) {
|
||||
return UniformType::IVec4;
|
||||
} else if (std::holds_alternative<bool>(value_)) {
|
||||
return UniformType::Bool;
|
||||
} else if (std::holds_alternative<GLuint>(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<decltype(arg)>;
|
||||
|
||||
if constexpr (std::is_same_v<T, float>) {
|
||||
glUniform1f(location, arg);
|
||||
} else if constexpr (std::is_same_v<T, glm::vec2>) {
|
||||
glUniform2fv(location, 1, &arg[0]);
|
||||
} else if constexpr (std::is_same_v<T, glm::vec3>) {
|
||||
glUniform3fv(location, 1, &arg[0]);
|
||||
} else if constexpr (std::is_same_v<T, glm::vec4>) {
|
||||
glUniform4fv(location, 1, &arg[0]);
|
||||
} else if constexpr (std::is_same_v<T, glm::mat2>) {
|
||||
glUniformMatrix2fv(location, 1, GL_FALSE, &arg[0][0]);
|
||||
} else if constexpr (std::is_same_v<T, glm::mat3>) {
|
||||
glUniformMatrix3fv(location, 1, GL_FALSE, &arg[0][0]);
|
||||
} else if constexpr (std::is_same_v<T, glm::mat4>) {
|
||||
glUniformMatrix4fv(location, 1, GL_FALSE, &arg[0][0]);
|
||||
} else if constexpr (std::is_same_v<T, int>) {
|
||||
glUniform1i(location, arg);
|
||||
} else if constexpr (std::is_same_v<T, glm::ivec2>) {
|
||||
glUniform2iv(location, 1, &arg[0]);
|
||||
} else if constexpr (std::is_same_v<T, glm::ivec3>) {
|
||||
glUniform3iv(location, 1, &arg[0]);
|
||||
} else if constexpr (std::is_same_v<T, glm::ivec4>) {
|
||||
glUniform4iv(location, 1, &arg[0]);
|
||||
} else if constexpr (std::is_same_v<T, bool>) {
|
||||
glUniform1i(location, arg ? 1 : 0);
|
||||
} else if constexpr (std::is_same_v<T, GLuint>) {
|
||||
glUniform1i(location, static_cast<GLint>(arg));
|
||||
}
|
||||
}, value_);
|
||||
}
|
||||
|
||||
bool UniformValue::isEmpty() const {
|
||||
return std::holds_alternative<std::monostate>(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
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
#include <extra2d/window/event_converter.h>
|
||||
#include <extra2d/window/keys.h>
|
||||
|
||||
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<i32>(Key::A);
|
||||
case SDLK_b: return static_cast<i32>(Key::B);
|
||||
case SDLK_c: return static_cast<i32>(Key::C);
|
||||
case SDLK_d: return static_cast<i32>(Key::D);
|
||||
case SDLK_e: return static_cast<i32>(Key::E);
|
||||
case SDLK_f: return static_cast<i32>(Key::F);
|
||||
case SDLK_g: return static_cast<i32>(Key::G);
|
||||
case SDLK_h: return static_cast<i32>(Key::H);
|
||||
case SDLK_i: return static_cast<i32>(Key::I);
|
||||
case SDLK_j: return static_cast<i32>(Key::J);
|
||||
case SDLK_k: return static_cast<i32>(Key::K);
|
||||
case SDLK_l: return static_cast<i32>(Key::L);
|
||||
case SDLK_m: return static_cast<i32>(Key::M);
|
||||
case SDLK_n: return static_cast<i32>(Key::N);
|
||||
case SDLK_o: return static_cast<i32>(Key::O);
|
||||
case SDLK_p: return static_cast<i32>(Key::P);
|
||||
case SDLK_q: return static_cast<i32>(Key::Q);
|
||||
case SDLK_r: return static_cast<i32>(Key::R);
|
||||
case SDLK_s: return static_cast<i32>(Key::S);
|
||||
case SDLK_t: return static_cast<i32>(Key::T);
|
||||
case SDLK_u: return static_cast<i32>(Key::U);
|
||||
case SDLK_v: return static_cast<i32>(Key::V);
|
||||
case SDLK_w: return static_cast<i32>(Key::W);
|
||||
case SDLK_x: return static_cast<i32>(Key::X);
|
||||
case SDLK_y: return static_cast<i32>(Key::Y);
|
||||
case SDLK_z: return static_cast<i32>(Key::Z);
|
||||
case SDLK_0: return static_cast<i32>(Key::Num0);
|
||||
case SDLK_1: return static_cast<i32>(Key::Num1);
|
||||
case SDLK_2: return static_cast<i32>(Key::Num2);
|
||||
case SDLK_3: return static_cast<i32>(Key::Num3);
|
||||
case SDLK_4: return static_cast<i32>(Key::Num4);
|
||||
case SDLK_5: return static_cast<i32>(Key::Num5);
|
||||
case SDLK_6: return static_cast<i32>(Key::Num6);
|
||||
case SDLK_7: return static_cast<i32>(Key::Num7);
|
||||
case SDLK_8: return static_cast<i32>(Key::Num8);
|
||||
case SDLK_9: return static_cast<i32>(Key::Num9);
|
||||
case SDLK_F1: return static_cast<i32>(Key::F1);
|
||||
case SDLK_F2: return static_cast<i32>(Key::F2);
|
||||
case SDLK_F3: return static_cast<i32>(Key::F3);
|
||||
case SDLK_F4: return static_cast<i32>(Key::F4);
|
||||
case SDLK_F5: return static_cast<i32>(Key::F5);
|
||||
case SDLK_F6: return static_cast<i32>(Key::F6);
|
||||
case SDLK_F7: return static_cast<i32>(Key::F7);
|
||||
case SDLK_F8: return static_cast<i32>(Key::F8);
|
||||
case SDLK_F9: return static_cast<i32>(Key::F9);
|
||||
case SDLK_F10: return static_cast<i32>(Key::F10);
|
||||
case SDLK_F11: return static_cast<i32>(Key::F11);
|
||||
case SDLK_F12: return static_cast<i32>(Key::F12);
|
||||
case SDLK_SPACE: return static_cast<i32>(Key::Space);
|
||||
case SDLK_RETURN: return static_cast<i32>(Key::Enter);
|
||||
case SDLK_ESCAPE: return static_cast<i32>(Key::Escape);
|
||||
case SDLK_TAB: return static_cast<i32>(Key::Tab);
|
||||
case SDLK_BACKSPACE: return static_cast<i32>(Key::Backspace);
|
||||
case SDLK_INSERT: return static_cast<i32>(Key::Insert);
|
||||
case SDLK_DELETE: return static_cast<i32>(Key::Delete);
|
||||
case SDLK_HOME: return static_cast<i32>(Key::Home);
|
||||
case SDLK_END: return static_cast<i32>(Key::End);
|
||||
case SDLK_PAGEUP: return static_cast<i32>(Key::PageUp);
|
||||
case SDLK_PAGEDOWN: return static_cast<i32>(Key::PageDown);
|
||||
case SDLK_UP: return static_cast<i32>(Key::Up);
|
||||
case SDLK_DOWN: return static_cast<i32>(Key::Down);
|
||||
case SDLK_LEFT: return static_cast<i32>(Key::Left);
|
||||
case SDLK_RIGHT: return static_cast<i32>(Key::Right);
|
||||
case SDLK_LSHIFT: return static_cast<i32>(Key::LShift);
|
||||
case SDLK_RSHIFT: return static_cast<i32>(Key::RShift);
|
||||
case SDLK_LCTRL: return static_cast<i32>(Key::LCtrl);
|
||||
case SDLK_RCTRL: return static_cast<i32>(Key::RCtrl);
|
||||
case SDLK_LALT: return static_cast<i32>(Key::LAlt);
|
||||
case SDLK_RALT: return static_cast<i32>(Key::RAlt);
|
||||
case SDLK_CAPSLOCK: return static_cast<i32>(Key::CapsLock);
|
||||
case SDLK_NUMLOCKCLEAR: return static_cast<i32>(Key::NumLock);
|
||||
case SDLK_SCROLLLOCK: return static_cast<i32>(Key::ScrollLock);
|
||||
default: return static_cast<i32>(Key::None);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue