Initial engine repository

This commit is contained in:
2026-06-08 19:58:35 +08:00
commit 7a925c3736
137 changed files with 162421 additions and 0 deletions
@@ -0,0 +1,68 @@
#pragma once
#include <frostbite2D/graphics/types.h>
#include <frostbite2D/graphics/texture.h>
#include <frostbite2D/graphics/shader.h>
#include <frostbite2D/types/type_alias.h>
namespace frostbite2D {
struct BatchKey {
uint32_t shaderID;
uint32_t textureID;
uint32_t blendMode;
bool operator<(const BatchKey& other) const {
if (shaderID != other.shaderID) return shaderID < other.shaderID;
if (textureID != other.textureID) return textureID < other.textureID;
return blendMode < other.blendMode;
}
};
class Batch {
public:
static constexpr int MAX_QUADS = 2048;
static constexpr int MAX_VERTICES = MAX_QUADS * 4;
static constexpr int MAX_INDICES = MAX_QUADS * 6;
Batch();
~Batch();
bool init();
void shutdown();
void begin();
void end();
void submitQuad(const Quad& quad, const Transform2D& transform,
Ptr<Texture> texture, Shader* shader,
BlendMode blendMode = BlendMode::Normal);
void flush();
void flushIfNeeded(const BatchKey& newKey, Shader* shader, Ptr<Texture> texture);
uint32 drawCallCount() const { return drawCallCount_; }
private:
void setupMesh();
void flushCurrentBatch();
struct BatchData {
std::vector<Vertex> vertices;
std::vector<uint16> indices;
BatchKey key;
Shader* shader = nullptr;
Ptr<Texture> texture;
};
uint32_t vao_ = 0;
uint32_t vbo_ = 0;
uint32_t ibo_ = 0;
BatchData currentBatch_;
std::vector<BatchData> batches_;
bool begun_ = false;
uint32 drawCallCount_ = 0;
};
}
@@ -0,0 +1,43 @@
#pragma once
#include <frostbite2D/types/type_math.h>
namespace frostbite2D {
class Camera {
public:
Camera();
void setPosition(const Vec2& pos);
void setZoom(float zoom);
void setViewport(int width, int height);
void setFlipY(bool flip) { flipY_ = flip; } // Debug Y-axis flip.
void setPixelSnapEnabled(bool enabled) { pixelSnapEnabled_ = enabled; }
const Vec2& getPosition() const { return position_; }
Vec2 getRenderPosition() const;
Vec2 snapWorldPosition(const Vec2& position) const;
float getZoom() const { return zoom_; }
int getViewportWidth() const { return viewportWidth_; }
int getViewportHeight() const { return viewportHeight_; }
float getVisibleWidth() const;
float getVisibleHeight() const;
bool isPixelSnapEnabled() const { return pixelSnapEnabled_; }
void lookAt(const Vec2& target);
void move(const Vec2& delta);
void zoomAt(float factor, const Vec2& screenPos);
glm::mat4 getViewMatrix() const;
glm::mat4 getProjectionMatrix() const;
private:
Vec2 position_;
float zoom_ = 1.0f;
int viewportWidth_ = 1280;
int viewportHeight_ = 720;
bool flipY_ = false; // Debug Y-axis flip.
bool pixelSnapEnabled_ = false;
};
} // namespace frostbite2D
@@ -0,0 +1,43 @@
#pragma once
#include <SDL2/SDL_ttf.h>
#include <frostbite2D/types/type_alias.h>
#include <optional>
#include <string>
#include <unordered_map>
namespace frostbite2D {
class FontManager {
public:
static FontManager& get();
bool init();
void shutdown();
bool registerFont(const std::string& name, const std::string& path, int fontSize);
TTF_Font* getFont(const std::string& name);
bool hasFont(const std::string& name) const;
void unregisterFont(const std::string& name);
void unloadAll();
struct FontInfo {
std::string name;
std::string path;
int size = 0;
};
std::optional<FontInfo> getFontInfo(const std::string& name) const;
FontManager(const FontManager&) = delete;
FontManager& operator=(const FontManager&) = delete;
private:
FontManager() = default;
~FontManager();
std::unordered_map<std::string, TTF_Font*> fonts_;
std::unordered_map<std::string, FontInfo> fontInfos_;
};
}
@@ -0,0 +1,46 @@
#pragma once
#include <frostbite2D/types/type_math.h>
namespace frostbite2D {
/**
* @brief 虚拟分辨率缩放模式
*/
enum class ResolutionScaleMode {
Disabled, ///< 不保持纵横比,直接按窗口宽高分别缩放
Fit, ///< 保持纵横比并完整显示,按较小缩放因子适配
FitHeight, ///< 以高度优先适配,宽度不足时回退为 Fit
};
struct RenderResolutionState {
int windowWidth = 1280;
int windowHeight = 720;
int drawableWidth = 1280;
int drawableHeight = 720;
int windowViewportX = 0;
int windowViewportY = 0;
int windowViewportWidth = 1280;
int windowViewportHeight = 720;
int viewportX = 0;
int viewportY = 0;
int viewportWidth = 1280;
int viewportHeight = 720;
float contentScaleX = 1.0f;
float contentScaleY = 1.0f;
float scaleX = 1.0f;
float scaleY = 1.0f;
Rect getWindowViewportRect() const {
return Rect(static_cast<float>(windowViewportX),
static_cast<float>(windowViewportY),
static_cast<float>(windowViewportWidth),
static_cast<float>(windowViewportHeight));
}
};
} // namespace frostbite2D
@@ -0,0 +1,72 @@
#pragma once
namespace frostbite2D {
/**
* @brief 2D 渲染风格预设 ID
*/
enum class RenderStyleProfileId {
PixelArt2D, ///< 世界与 UI 都按像素风策略渲染
Smooth2D, ///< 世界与 UI 都按平滑 2D 策略渲染
Hybrid2D ///< 世界走像素风,UI 走平滑策略
};
/**
* @brief 渲染风格作用层级
*/
enum class RenderStyleLayerRole {
World, ///< 世界层,如地图、角色、场景特效
UI ///< UI 层,如 HUD、菜单、文字
};
/**
* @brief 单个层级的渲染风格开关集合
*/
struct RenderStyleLayerSettings {
bool cameraPixelSnap = false; ///< 是否让相机位置吸附到像素网格
bool vertexPixelSnap = false; ///< 是否让世界顶点在提交前做像素对齐
bool pixelArtSampling = false; ///< 是否优先使用像素风采样(如 GL_NEAREST
bool shrinkSubTextureUVs = false; ///< 是否对子纹理 UV 做向内收缩,减轻图集边缘闪烁
};
/**
* @brief 一套完整的 2D 渲染风格设置
*/
struct RenderStyleSettings {
RenderStyleLayerSettings world; ///< 世界层使用的渲染风格
RenderStyleLayerSettings ui; ///< UI 层使用的渲染风格
/**
* @brief 根据预设 ID 生成对应的渲染风格
*/
static RenderStyleSettings FromProfile(RenderStyleProfileId profile) {
RenderStyleSettings settings;
switch (profile) {
case RenderStyleProfileId::PixelArt2D:
settings.world = {true, true, true, true};
settings.ui = {true, true, true, true};
break;
case RenderStyleProfileId::Smooth2D:
settings.world = {false, false, false, false};
settings.ui = {false, false, false, false};
break;
case RenderStyleProfileId::Hybrid2D:
default:
settings.world = {true, true, true, true};
settings.ui = {false, false, false, false};
break;
}
return settings;
}
/**
* @brief 获取指定层级对应的风格设置
*/
const RenderStyleLayerSettings& layer(RenderStyleLayerRole role) const {
return role == RenderStyleLayerRole::UI ? ui : world;
}
};
} // namespace frostbite2D
@@ -0,0 +1,34 @@
#pragma once
#include <frostbite2D/base/RefObject.h>
#include <frostbite2D/graphics/texture.h>
namespace frostbite2D {
class Renderer;
/// @brief 2D 离屏渲染目标,封装颜色纹理和对应的 framebuffer。
class RenderTexture : public RefObject {
public:
RenderTexture() = default;
~RenderTexture() override;
bool Init(int width, int height);
bool Resize(int width, int height);
void Reset();
bool IsValid() const { return framebufferID_ != 0 && texture_ != nullptr; }
int GetWidth() const { return width_; }
int GetHeight() const { return height_; }
Ptr<Texture> GetTexture() const { return texture_; }
private:
Ptr<Texture> texture_ = nullptr;
uint32 framebufferID_ = 0;
int width_ = 0;
int height_ = 0;
friend class Renderer;
};
} // namespace frostbite2D
@@ -0,0 +1,179 @@
#pragma once
#include <frostbite2D/graphics/batch.h>
#include <frostbite2D/graphics/camera.h>
#include <frostbite2D/graphics/render_resolution.h>
#include <frostbite2D/graphics/render_style.h>
#include <frostbite2D/graphics/shader.h>
#include <frostbite2D/graphics/shader_manager.h>
#include <frostbite2D/graphics/texture.h>
#include <frostbite2D/graphics/types.h>
#include <string>
#include <vector>
namespace frostbite2D {
class RenderTexture;
class Renderer {
public:
static Renderer& get();
bool init();
void shutdown();
void beginFrame();
void endFrame();
void flush();
bool beginRenderToTexture(RenderTexture& target, Camera* camera,
const Color& clearColor = Color(0.0f, 0.0f, 0.0f, 0.0f),
bool clear = true);
bool endRenderToTexture();
void setViewport(int x, int y, int width, int height);
void setWindowSize(int width, int height, float contentScaleX = 1.0f,
float contentScaleY = 1.0f);
void setVirtualResolutionEnabled(bool enabled);
void setVirtualResolution(int width, int height);
void setResolutionScaleMode(ResolutionScaleMode mode);
void setSeparateUIVirtualResolutionEnabled(bool enabled);
void setUIVirtualResolution(int width, int height);
void setUIResolutionScaleMode(ResolutionScaleMode mode);
void setClearColor(float r, float g, float b, float a = 1.0f);
void setClearColor(const Color& color);
void clear(uint32_t flags);
/**
* @brief 设置应用默认渲染风格预设
*/
void setDefaultRenderStyleProfile(RenderStyleProfileId profile);
/**
* @brief 获取应用默认渲染风格预设
*/
RenderStyleProfileId getDefaultRenderStyleProfile() const {
return defaultRenderStyleProfile_;
}
/**
* @brief 切换当前正在使用的渲染风格上下文
*/
void setActiveRenderStyleProfile(RenderStyleProfileId profile,
RenderStyleLayerRole role);
/**
* @brief 将指定风格作用到某个相机
*/
void applyRenderStyleToCamera(Camera* camera, RenderStyleProfileId profile,
RenderStyleLayerRole role) const;
void setCamera(Camera* camera);
Camera* getCamera() { return camera_; }
int getVirtualWidth() const;
int getVirtualHeight() const;
int getUIVirtualWidth() const;
int getUIVirtualHeight() const;
Rect getViewportRect() const;
Rect getUIViewportRect() const;
const RenderResolutionState& getResolutionState() const {
return resolutionState_;
}
const RenderResolutionState& getUIResolutionState() const {
return uiResolutionState_;
}
bool isInitialized() const { return initialized_; }
bool isFrameActive() const { return frameActive_; }
bool isRenderingToTexture() const { return !renderTargetStack_.empty(); }
void setCullingEnabled(bool enabled) { cullingEnabled_ = enabled; }
bool isCullingEnabled() const { return cullingEnabled_; }
void applyWorldViewport() const;
void applyUIViewport() const;
Vec2 screenToVirtual(const Vec2& screenPos) const;
Vec2 screenToUIVirtual(const Vec2& screenPos) const;
Vec2 virtualToScreen(const Vec2& virtualPos) const;
void drawQuad(const Vec2& pos, const Size& size, float cr = 1.0f,
float cg = 1.0f, float cb = 1.0f, float ca = 1.0f);
void drawQuad(const Vec2& pos, const Size& size, Ptr<Texture> texture);
void drawQuad(const Rect& rect, const Color& color);
void drawSprite(const Vec2& pos, const Size& size, Ptr<Texture> texture);
void drawSprite(const Vec2& pos, const Rect& srcRect, const Vec2& texSize,
Ptr<Texture> texture,
const Color& color = Color(1, 1, 1, 1));
void setupBlendMode(BlendMode mode);
/**
* @brief 获取当前激活的层级风格设置
*/
const RenderStyleLayerSettings& getActiveRenderStyle() const {
return activeRenderStyle_;
}
/**
* @brief 当前风格是否要求顶点像素对齐
*/
bool shouldSnapVertices() const { return activeRenderStyle_.vertexPixelSnap; }
/**
* @brief 当前风格是否优先使用像素风采样
*/
bool shouldUsePixelArtSampling() const {
return activeRenderStyle_.pixelArtSampling;
}
/**
* @brief 当前风格是否需要对子纹理 UV 做向内收缩
*/
bool shouldShrinkSubTextureUVs() const {
return activeRenderStyle_.shrinkSubTextureUVs;
}
ShaderManager& getShaderManager() { return shaderManager_; }
Batch& getBatch() { return batch_; }
private:
Renderer();
void updateUniforms();
void recalculateResolutionState();
void recalculateSingleResolutionState(RenderResolutionState& state,
bool useVirtualResolution,
int virtualWidth, int virtualHeight,
ResolutionScaleMode mode) const;
void syncCameraViewport();
struct RenderTargetState {
uint32 framebuffer = 0;
int viewportX = 0;
int viewportY = 0;
int viewportWidth = 1;
int viewportHeight = 1;
float clearColor[4] = {0.0f, 0.0f, 0.0f, 0.0f};
Camera* camera = nullptr;
bool startedStandaloneBatch = false;
bool cullingEnabled = true;
};
ShaderManager shaderManager_;
Batch batch_;
Camera* camera_ = nullptr;
RenderStyleProfileId defaultRenderStyleProfile_ = RenderStyleProfileId::Hybrid2D;
RenderStyleProfileId activeRenderStyleProfile_ = RenderStyleProfileId::Hybrid2D;
RenderStyleLayerRole activeRenderStyleRole_ = RenderStyleLayerRole::World;
RenderStyleLayerSettings activeRenderStyle_;
uint32_t clearColor_[4] = {0, 0, 0, 255};
bool useVirtualResolution_ = false;
int virtualWidth_ = 1280;
int virtualHeight_ = 720;
ResolutionScaleMode resolutionMode_ = ResolutionScaleMode::Fit;
bool useSeparateUIVirtualResolution_ = false;
int uiVirtualWidth_ = 0;
int uiVirtualHeight_ = 0;
ResolutionScaleMode uiResolutionMode_ = ResolutionScaleMode::Fit;
RenderResolutionState resolutionState_;
RenderResolutionState uiResolutionState_;
bool initialized_ = false;
bool frameActive_ = false;
bool cullingEnabled_ = true;
std::vector<RenderTargetState> renderTargetStack_;
Renderer(const Renderer&) = delete;
Renderer& operator=(const Renderer&) = delete;
};
} // namespace frostbite2D
@@ -0,0 +1,49 @@
#pragma once
#include <frostbite2D/types/type_alias.h>
#include <frostbite2D/types/type_math.h>
#include <frostbite2D/base/RefObject.h>
#include <string>
#include <unordered_map>
namespace frostbite2D {
class Shader : public RefObject {
public:
Shader() = default;
~Shader();
void use();
void unuse();
bool isValid() const { return programID_ != 0; }
uint32_t getID() const { return programID_; }
const std::string& getName() const { return name_; }
void setInt(const std::string& name, int value);
void setFloat(const std::string& name, float value);
void setVec2(const std::string& name, const Vec2& value);
void setVec3(const std::string& name, const glm::vec3& value);
void setVec4(const std::string& name, const glm::vec4& value);
void setMat3(const std::string& name, const glm::mat3& value);
void setMat4(const std::string& name, const glm::mat4& value);
void setTexture(const std::string& name, int slot);
private:
Shader(const std::string& name, uint32_t programID);
friend class ShaderManager;
bool compile(uint32_t type, const std::string& source);
bool link(uint32_t vertexID, uint32_t fragmentID);
int getUniformLocation(const std::string& name);
uint32_t programID_ = 0;
std::string name_;
std::unordered_map<std::string, int> uniformLocations_;
Shader(const Shader&) = delete;
Shader& operator=(const Shader&) = delete;
};
}
@@ -0,0 +1,42 @@
#pragma once
#include <frostbite2D/graphics/shader.h>
#include <frostbite2D/types/type_alias.h>
#include <string>
#include <unordered_map>
namespace frostbite2D {
class Renderer;
class ShaderManager {
public:
static ShaderManager& get();
bool init(const std::string& shadersDir);
void shutdown();
Shader* getShader(const std::string& name);
bool hasShader(const std::string& name) const;
const std::unordered_map<std::string, Ptr<Shader>>& getLoadedShaders() const {
return shaders_;
}
~ShaderManager() = default;
ShaderManager() = default;
private:
std::unordered_map<std::string, Ptr<Shader>> shaders_;
std::string shadersDir_;
bool loadShadersFromConfig(const std::string& configPath);
bool loadShader(const std::string& name, const std::string& vertPath,
const std::string& fragPath);
ShaderManager(const ShaderManager&) = delete;
ShaderManager& operator=(const ShaderManager&) = delete;
};
}
@@ -0,0 +1,56 @@
#pragma once
#include <frostbite2D/graphics/types.h>
#include <frostbite2D/types/type_alias.h>
#include <frostbite2D/base/RefObject.h>
#include <string>
#include <memory>
namespace frostbite2D {
enum class TextureSampling {
Linear,
PixelArt
};
class Texture : public RefObject {
public:
static Ptr<Texture> loadFromFile(const std::string& path);
static Ptr<Texture> createFromMemory(const uint8* data, int width, int height, int channels);
static Ptr<Texture> createEmpty(int width, int height);
~Texture();
void bind(uint32_t slot = 0, bool preferPixelArtSampling = false);
void unbind();
void setWrapMode(uint32_t wrapS, uint32_t wrapT);
void setFilterMode(uint32_t minFilter, uint32_t magFilter);
void setSampling(TextureSampling sampling);
TextureSampling getSampling() const { return sampling_; }
int getWidth() const { return width_; }
int getHeight() const { return height_; }
uint32_t getID() const { return textureID_; }
const std::string& getPath() const { return path_; }
private:
Texture(int width, int height, uint32_t id);
void applyFilter(uint32_t minFilter, uint32_t magFilter);
void applySampling(bool preferPixelArtSampling);
uint32_t textureID_ = 0;
int width_ = 0;
int height_ = 0;
int channels_ = 0;
std::string path_;
TextureSampling sampling_ = TextureSampling::Linear;
uint32_t appliedMinFilter_ = 0;
uint32_t appliedMagFilter_ = 0;
Texture() = default;
Texture(const Texture&) = delete;
Texture& operator=(const Texture&) = delete;
};
}
@@ -0,0 +1,111 @@
#pragma once
#include <frostbite2D/types/type_alias.h>
#include <frostbite2D/types/type_math.h>
#include <frostbite2D/types/type_color.h>
#include <cstdint>
namespace frostbite2D {
enum class BlendMode {
None,
Normal,
Additive,
Screen,
Premultiplied,
Subtractive,
Multiply
};
enum class PrimitiveType {
Triangles,
TriangleStrip,
TriangleFan,
Quads
};
struct Vertex {
Vec2 position;
Vec2 texCoord;
float r, g, b, a;
Vertex() = default;
Vertex(const Vec2& pos, const Vec2& uv, float cr, float cg, float cb, float ca)
: position(pos), texCoord(uv), r(cr), g(cg), b(cb), a(ca) {}
};
struct Quad {
Vertex vertices[4];
Quad() = default;
static Quad create(const Rect& rect, float cr = 1.0f, float cg = 1.0f,
float cb = 1.0f, float ca = 1.0f) {
Quad q;
Vec2 bl(rect.left(), rect.bottom());
Vec2 br(rect.right(), rect.bottom());
Vec2 tl(rect.left(), rect.top());
Vec2 tr(rect.right(), rect.top());
q.vertices[0] = Vertex(bl, Vec2(0, 0), cr, cg, cb, ca);
q.vertices[1] = Vertex(br, Vec2(1, 0), cr, cg, cb, ca);
q.vertices[2] = Vertex(tl, Vec2(0, 1), cr, cg, cb, ca);
q.vertices[3] = Vertex(tr, Vec2(1, 1), cr, cg, cb, ca);
return q;
}
static Quad createTextured(const Rect& destRect, const Rect& srcRect,
const Vec2& texSize, float cr = 1.0f,
float cg = 1.0f, float cb = 1.0f, float ca = 1.0f,
bool shrinkSubTextureUVs = false) {
Quad q;
Vec2 bl(destRect.left(), destRect.bottom());
Vec2 br(destRect.right(), destRect.bottom());
Vec2 tl(destRect.left(), destRect.top());
Vec2 tr(destRect.right(), destRect.top());
auto resolveUvX = [&srcRect, &texSize, shrinkSubTextureUVs](bool useRightEdge) {
float pixelInset = 0.0f;
if (!shrinkSubTextureUVs) {
return (useRightEdge ? srcRect.right() : srcRect.left()) / texSize.x;
}
if (useRightEdge && srcRect.right() < texSize.x) {
pixelInset = -0.5f;
} else if (!useRightEdge && srcRect.left() > 0.0f) {
pixelInset = 0.5f;
}
return (useRightEdge ? srcRect.right() : srcRect.left()) / texSize.x +
pixelInset / texSize.x;
};
auto resolveUvY = [&srcRect, &texSize, shrinkSubTextureUVs](bool useBottomEdge) {
float pixelInset = 0.0f;
if (!shrinkSubTextureUVs) {
return (useBottomEdge ? srcRect.bottom() : srcRect.top()) / texSize.y;
}
if (useBottomEdge && srcRect.bottom() < texSize.y) {
pixelInset = -0.5f;
} else if (!useBottomEdge && srcRect.top() > 0.0f) {
pixelInset = 0.5f;
}
return (useBottomEdge ? srcRect.bottom() : srcRect.top()) / texSize.y +
pixelInset / texSize.y;
};
Vec2 uvBL(resolveUvX(false), resolveUvY(true));
Vec2 uvBR(resolveUvX(true), resolveUvY(true));
Vec2 uvTL(resolveUvX(false), resolveUvY(false));
Vec2 uvTR(resolveUvX(true), resolveUvY(false));
q.vertices[0] = Vertex(bl, uvBL, cr, cg, cb, ca);
q.vertices[1] = Vertex(br, uvBR, cr, cg, cb, ca);
q.vertices[2] = Vertex(tl, uvTL, cr, cg, cb, ca);
q.vertices[3] = Vertex(tr, uvTR, cr, cg, cb, ca);
return q;
}
};
}