feat(asset/render): 添加材质系统和资源类实现

实现新的材质系统包括Material、MaterialInstance和MaterialPropertyBlock类
添加多种资源类实现:TextureAsset、FontAsset、ShaderAsset、AudioAsset等
重构SpriteRenderer以支持材质系统
添加UniformValue和UniformInfo类用于材质参数传递
实现MSDF字体渲染器和文本渲染功能
This commit is contained in:
ChestnutYueyue 2026-02-23 15:40:42 +08:00
parent 174d7327ef
commit 70c2806c77
34 changed files with 3349 additions and 691 deletions

View File

@ -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 - 资源基类
// ---------------------------------------------------------------------------
@ -95,383 +101,4 @@ protected:
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);
}
}
streaming_ = duration_ > 5.0f;
setState(AssetState::Loaded);
}
/**
* @brief
*/
void release() {
data_.clear();
format_ = AudioFormat::PCM;
channels_ = 0;
sampleRate_ = 0;
bitsPerSample_ = 0;
duration_ = 0.0f;
streaming_ = false;
setState(AssetState::Unloaded);
}
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_;
};
} // namespace extra2d

View File

@ -1,5 +1,6 @@
#pragma once
#include <extra2d/asset/asset.h>
#include <extra2d/asset/asset_types.h>
#include <extra2d/core/types.h>
#include <memory>

View File

@ -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,6 +167,7 @@ public:
* @brief
*
*
* - .json: JSON
* - .vert/.frag: /
* - .glsl: 使
*/
@ -185,19 +193,67 @@ public:
fragmentMarker_ = marker;
}
/**
* @brief
* @param root
*/
void setRoot(const std::string &root) { root_ = root; }
private:
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 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);
};
// ---------------------------------------------------------------------------

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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>
@ -37,7 +40,7 @@ struct SpriteData {
/**
* @brief
*
*
* Material
*/
class SpriteRenderer {
public:
@ -61,14 +64,45 @@ public:
void shutdown();
/**
* @brief
* @brief
* @param material
*/
void begin(const glm::mat4& viewProjection);
void setMaterial(Ref<Material> material) { material_ = material; }
/**
* @brief
* @brief
* @return
*/
void draw(GLuint texture, const SpriteData& data);
Ref<Material> material() const { return material_; }
/**
* @brief
*/
void begin(const glm::mat4 &viewProjection);
/**
* @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
@ -93,12 +127,16 @@ private:
GLuint currentTexture_ = 0;
glm::mat4 viewProjection_;
Ref<Material> material_;
MaterialInstance *currentInstance_ = nullptr;
uint32 drawCalls_ = 0;
uint32 spriteCount_ = 0;
bool createShader();
void flush();
void addQuad(const SpriteData& data, GLuint texture);
void flushWithMaterial();
void addQuad(const SpriteData &data, GLuint texture);
};
} // namespace extra2d

View File

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

View File

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

View File

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

View File

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

View File

@ -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,6 +167,12 @@ std::vector<std::string> FontLoader::extensions() const {
// ---------------------------------------------------------------------------
Ref<ShaderAsset> ShaderLoader::load(const std::string &path) {
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);
@ -174,6 +181,247 @@ Ref<ShaderAsset> ShaderLoader::load(const std::string &path) {
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;
@ -204,9 +452,8 @@ bool ShaderLoader::canLoad(const std::string &path) const {
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,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,6 +193,22 @@ void SpriteRenderer::flush() {
vbo_->update(0, vertexCount_ * sizeof(SpriteVertex), vertices_.data());
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]);
@ -166,6 +217,42 @@ void SpriteRenderer::flush() {
glUniform1i(texLoc, 0);
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),

View File

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

View File

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