refactor: 移除图形渲染相关代码并简化核心结构
移除图形渲染模块、着色器、纹理、字体等组件 删除过渡场景和精灵节点等场景管理功能 简化应用程序类,移除渲染和场景相关接口 更新GLFW窗口模块,移除OpenGL相关初始化 清理不再使用的头文件和实现文件
This commit is contained in:
parent
9e911db53c
commit
f9e244299f
|
|
@ -1,311 +0,0 @@
|
||||||
#ifndef __khrplatform_h_
|
|
||||||
#define __khrplatform_h_
|
|
||||||
|
|
||||||
/*
|
|
||||||
** Copyright (c) 2008-2018 The Khronos Group Inc.
|
|
||||||
**
|
|
||||||
** Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
** copy of this software and/or associated documentation files (the
|
|
||||||
** "Materials"), to deal in the Materials without restriction, including
|
|
||||||
** without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
** distribute, sublicense, and/or sell copies of the Materials, and to
|
|
||||||
** permit persons to whom the Materials are furnished to do so, subject to
|
|
||||||
** the following conditions:
|
|
||||||
**
|
|
||||||
** The above copyright notice and this permission notice shall be included
|
|
||||||
** in all copies or substantial portions of the Materials.
|
|
||||||
**
|
|
||||||
** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
||||||
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
||||||
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
||||||
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
||||||
** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Khronos platform-specific types and definitions.
|
|
||||||
*
|
|
||||||
* The master copy of khrplatform.h is maintained in the Khronos EGL
|
|
||||||
* Registry repository at https://github.com/KhronosGroup/EGL-Registry
|
|
||||||
* The last semantic modification to khrplatform.h was at commit ID:
|
|
||||||
* 67a3e0864c2d75ea5287b9f3d2eb74a745936692
|
|
||||||
*
|
|
||||||
* Adopters may modify this file to suit their platform. Adopters are
|
|
||||||
* encouraged to submit platform specific modifications to the Khronos
|
|
||||||
* group so that they can be included in future versions of this file.
|
|
||||||
* Please submit changes by filing pull requests or issues on
|
|
||||||
* the EGL Registry repository linked above.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* See the Implementer's Guidelines for information about where this file
|
|
||||||
* should be located on your system and for more details of its use:
|
|
||||||
* http://www.khronos.org/registry/implementers_guide.pdf
|
|
||||||
*
|
|
||||||
* This file should be included as
|
|
||||||
* #include <KHR/khrplatform.h>
|
|
||||||
* by Khronos client API header files that use its types and defines.
|
|
||||||
*
|
|
||||||
* The types in khrplatform.h should only be used to define API-specific types.
|
|
||||||
*
|
|
||||||
* Types defined in khrplatform.h:
|
|
||||||
* khronos_int8_t signed 8 bit
|
|
||||||
* khronos_uint8_t unsigned 8 bit
|
|
||||||
* khronos_int16_t signed 16 bit
|
|
||||||
* khronos_uint16_t unsigned 16 bit
|
|
||||||
* khronos_int32_t signed 32 bit
|
|
||||||
* khronos_uint32_t unsigned 32 bit
|
|
||||||
* khronos_int64_t signed 64 bit
|
|
||||||
* khronos_uint64_t unsigned 64 bit
|
|
||||||
* khronos_intptr_t signed same number of bits as a pointer
|
|
||||||
* khronos_uintptr_t unsigned same number of bits as a pointer
|
|
||||||
* khronos_ssize_t signed size
|
|
||||||
* khronos_usize_t unsigned size
|
|
||||||
* khronos_float_t signed 32 bit floating point
|
|
||||||
* khronos_time_ns_t unsigned 64 bit time in nanoseconds
|
|
||||||
* khronos_utime_nanoseconds_t unsigned time interval or absolute time in
|
|
||||||
* nanoseconds
|
|
||||||
* khronos_stime_nanoseconds_t signed time interval in nanoseconds
|
|
||||||
* khronos_boolean_enum_t enumerated boolean type. This should
|
|
||||||
* only be used as a base type when a client API's boolean type is
|
|
||||||
* an enum. Client APIs which use an integer or other type for
|
|
||||||
* booleans cannot use this as the base type for their boolean.
|
|
||||||
*
|
|
||||||
* Tokens defined in khrplatform.h:
|
|
||||||
*
|
|
||||||
* KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values.
|
|
||||||
*
|
|
||||||
* KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0.
|
|
||||||
* KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0.
|
|
||||||
*
|
|
||||||
* Calling convention macros defined in this file:
|
|
||||||
* KHRONOS_APICALL
|
|
||||||
* KHRONOS_APIENTRY
|
|
||||||
* KHRONOS_APIATTRIBUTES
|
|
||||||
*
|
|
||||||
* These may be used in function prototypes as:
|
|
||||||
*
|
|
||||||
* KHRONOS_APICALL void KHRONOS_APIENTRY funcname(
|
|
||||||
* int arg1,
|
|
||||||
* int arg2) KHRONOS_APIATTRIBUTES;
|
|
||||||
*/
|
|
||||||
|
|
||||||
#if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC)
|
|
||||||
# define KHRONOS_STATIC 1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*-------------------------------------------------------------------------
|
|
||||||
* Definition of KHRONOS_APICALL
|
|
||||||
*-------------------------------------------------------------------------
|
|
||||||
* This precedes the return type of the function in the function prototype.
|
|
||||||
*/
|
|
||||||
#if defined(KHRONOS_STATIC)
|
|
||||||
/* If the preprocessor constant KHRONOS_STATIC is defined, make the
|
|
||||||
* header compatible with static linking. */
|
|
||||||
# define KHRONOS_APICALL
|
|
||||||
#elif defined(_WIN32)
|
|
||||||
# define KHRONOS_APICALL __declspec(dllimport)
|
|
||||||
#elif defined (__SYMBIAN32__)
|
|
||||||
# define KHRONOS_APICALL IMPORT_C
|
|
||||||
#elif defined(__ANDROID__)
|
|
||||||
# define KHRONOS_APICALL __attribute__((visibility("default")))
|
|
||||||
#else
|
|
||||||
# define KHRONOS_APICALL
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*-------------------------------------------------------------------------
|
|
||||||
* Definition of KHRONOS_APIENTRY
|
|
||||||
*-------------------------------------------------------------------------
|
|
||||||
* This follows the return type of the function and precedes the function
|
|
||||||
* name in the function prototype.
|
|
||||||
*/
|
|
||||||
#if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__)
|
|
||||||
/* Win32 but not WinCE */
|
|
||||||
# define KHRONOS_APIENTRY __stdcall
|
|
||||||
#else
|
|
||||||
# define KHRONOS_APIENTRY
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*-------------------------------------------------------------------------
|
|
||||||
* Definition of KHRONOS_APIATTRIBUTES
|
|
||||||
*-------------------------------------------------------------------------
|
|
||||||
* This follows the closing parenthesis of the function prototype arguments.
|
|
||||||
*/
|
|
||||||
#if defined (__ARMCC_2__)
|
|
||||||
#define KHRONOS_APIATTRIBUTES __softfp
|
|
||||||
#else
|
|
||||||
#define KHRONOS_APIATTRIBUTES
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*-------------------------------------------------------------------------
|
|
||||||
* basic type definitions
|
|
||||||
*-----------------------------------------------------------------------*/
|
|
||||||
#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__)
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Using <stdint.h>
|
|
||||||
*/
|
|
||||||
#include <stdint.h>
|
|
||||||
typedef int32_t khronos_int32_t;
|
|
||||||
typedef uint32_t khronos_uint32_t;
|
|
||||||
typedef int64_t khronos_int64_t;
|
|
||||||
typedef uint64_t khronos_uint64_t;
|
|
||||||
#define KHRONOS_SUPPORT_INT64 1
|
|
||||||
#define KHRONOS_SUPPORT_FLOAT 1
|
|
||||||
/*
|
|
||||||
* To support platform where unsigned long cannot be used interchangeably with
|
|
||||||
* inptr_t (e.g. CHERI-extended ISAs), we can use the stdint.h intptr_t.
|
|
||||||
* Ideally, we could just use (u)intptr_t everywhere, but this could result in
|
|
||||||
* ABI breakage if khronos_uintptr_t is changed from unsigned long to
|
|
||||||
* unsigned long long or similar (this results in different C++ name mangling).
|
|
||||||
* To avoid changes for existing platforms, we restrict usage of intptr_t to
|
|
||||||
* platforms where the size of a pointer is larger than the size of long.
|
|
||||||
*/
|
|
||||||
#if defined(__SIZEOF_LONG__) && defined(__SIZEOF_POINTER__)
|
|
||||||
#if __SIZEOF_POINTER__ > __SIZEOF_LONG__
|
|
||||||
#define KHRONOS_USE_INTPTR_T
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#elif defined(__VMS ) || defined(__sgi)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Using <inttypes.h>
|
|
||||||
*/
|
|
||||||
#include <inttypes.h>
|
|
||||||
typedef int32_t khronos_int32_t;
|
|
||||||
typedef uint32_t khronos_uint32_t;
|
|
||||||
typedef int64_t khronos_int64_t;
|
|
||||||
typedef uint64_t khronos_uint64_t;
|
|
||||||
#define KHRONOS_SUPPORT_INT64 1
|
|
||||||
#define KHRONOS_SUPPORT_FLOAT 1
|
|
||||||
|
|
||||||
#elif defined(_WIN32) && !defined(__SCITECH_SNAP__)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Win32
|
|
||||||
*/
|
|
||||||
typedef __int32 khronos_int32_t;
|
|
||||||
typedef unsigned __int32 khronos_uint32_t;
|
|
||||||
typedef __int64 khronos_int64_t;
|
|
||||||
typedef unsigned __int64 khronos_uint64_t;
|
|
||||||
#define KHRONOS_SUPPORT_INT64 1
|
|
||||||
#define KHRONOS_SUPPORT_FLOAT 1
|
|
||||||
|
|
||||||
#elif defined(__sun__) || defined(__digital__)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Sun or Digital
|
|
||||||
*/
|
|
||||||
typedef int khronos_int32_t;
|
|
||||||
typedef unsigned int khronos_uint32_t;
|
|
||||||
#if defined(__arch64__) || defined(_LP64)
|
|
||||||
typedef long int khronos_int64_t;
|
|
||||||
typedef unsigned long int khronos_uint64_t;
|
|
||||||
#else
|
|
||||||
typedef long long int khronos_int64_t;
|
|
||||||
typedef unsigned long long int khronos_uint64_t;
|
|
||||||
#endif /* __arch64__ */
|
|
||||||
#define KHRONOS_SUPPORT_INT64 1
|
|
||||||
#define KHRONOS_SUPPORT_FLOAT 1
|
|
||||||
|
|
||||||
#elif 0
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Hypothetical platform with no float or int64 support
|
|
||||||
*/
|
|
||||||
typedef int khronos_int32_t;
|
|
||||||
typedef unsigned int khronos_uint32_t;
|
|
||||||
#define KHRONOS_SUPPORT_INT64 0
|
|
||||||
#define KHRONOS_SUPPORT_FLOAT 0
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Generic fallback
|
|
||||||
*/
|
|
||||||
#include <stdint.h>
|
|
||||||
typedef int32_t khronos_int32_t;
|
|
||||||
typedef uint32_t khronos_uint32_t;
|
|
||||||
typedef int64_t khronos_int64_t;
|
|
||||||
typedef uint64_t khronos_uint64_t;
|
|
||||||
#define KHRONOS_SUPPORT_INT64 1
|
|
||||||
#define KHRONOS_SUPPORT_FLOAT 1
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Types that are (so far) the same on all platforms
|
|
||||||
*/
|
|
||||||
typedef signed char khronos_int8_t;
|
|
||||||
typedef unsigned char khronos_uint8_t;
|
|
||||||
typedef signed short int khronos_int16_t;
|
|
||||||
typedef unsigned short int khronos_uint16_t;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Types that differ between LLP64 and LP64 architectures - in LLP64,
|
|
||||||
* pointers are 64 bits, but 'long' is still 32 bits. Win64 appears
|
|
||||||
* to be the only LLP64 architecture in current use.
|
|
||||||
*/
|
|
||||||
#ifdef KHRONOS_USE_INTPTR_T
|
|
||||||
typedef intptr_t khronos_intptr_t;
|
|
||||||
typedef uintptr_t khronos_uintptr_t;
|
|
||||||
#elif defined(_WIN64)
|
|
||||||
typedef signed long long int khronos_intptr_t;
|
|
||||||
typedef unsigned long long int khronos_uintptr_t;
|
|
||||||
#else
|
|
||||||
typedef signed long int khronos_intptr_t;
|
|
||||||
typedef unsigned long int khronos_uintptr_t;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(_WIN64)
|
|
||||||
typedef signed long long int khronos_ssize_t;
|
|
||||||
typedef unsigned long long int khronos_usize_t;
|
|
||||||
#else
|
|
||||||
typedef signed long int khronos_ssize_t;
|
|
||||||
typedef unsigned long int khronos_usize_t;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if KHRONOS_SUPPORT_FLOAT
|
|
||||||
/*
|
|
||||||
* Float type
|
|
||||||
*/
|
|
||||||
typedef float khronos_float_t;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if KHRONOS_SUPPORT_INT64
|
|
||||||
/* Time types
|
|
||||||
*
|
|
||||||
* These types can be used to represent a time interval in nanoseconds or
|
|
||||||
* an absolute Unadjusted System Time. Unadjusted System Time is the number
|
|
||||||
* of nanoseconds since some arbitrary system event (e.g. since the last
|
|
||||||
* time the system booted). The Unadjusted System Time is an unsigned
|
|
||||||
* 64 bit value that wraps back to 0 every 584 years. Time intervals
|
|
||||||
* may be either signed or unsigned.
|
|
||||||
*/
|
|
||||||
typedef khronos_uint64_t khronos_utime_nanoseconds_t;
|
|
||||||
typedef khronos_int64_t khronos_stime_nanoseconds_t;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Dummy value used to pad enum types to 32 bits.
|
|
||||||
*/
|
|
||||||
#ifndef KHRONOS_MAX_ENUM
|
|
||||||
#define KHRONOS_MAX_ENUM 0x7FFFFFFF
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Enumerated boolean type
|
|
||||||
*
|
|
||||||
* Values other than zero should be considered to be true. Therefore
|
|
||||||
* comparisons should not be made against KHRONOS_TRUE.
|
|
||||||
*/
|
|
||||||
typedef enum {
|
|
||||||
KHRONOS_FALSE = 0,
|
|
||||||
KHRONOS_TRUE = 1,
|
|
||||||
KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM
|
|
||||||
} khronos_boolean_enum_t;
|
|
||||||
|
|
||||||
#endif /* __khrplatform_h_ */
|
|
||||||
|
|
@ -9,9 +9,7 @@
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
class GLFWWindow;
|
class GLFWWindow;
|
||||||
class Renderer;
|
|
||||||
class WindowModule;
|
class WindowModule;
|
||||||
class RenderModule;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 应用程序类
|
* @brief 应用程序类
|
||||||
|
|
@ -87,18 +85,6 @@ public:
|
||||||
*/
|
*/
|
||||||
GLFWWindow *window();
|
GLFWWindow *window();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取渲染器
|
|
||||||
* @return 渲染器指针
|
|
||||||
*/
|
|
||||||
Renderer *renderer();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 进入场景
|
|
||||||
* @param scene 场景指针
|
|
||||||
*/
|
|
||||||
void enterScene(Ptr<class Scene> scene);
|
|
||||||
|
|
||||||
float deltaTime() const { return deltaTime_; }
|
float deltaTime() const { return deltaTime_; }
|
||||||
float totalTime() const { return totalTime_; }
|
float totalTime() const { return totalTime_; }
|
||||||
int fps() const { return currentFps_; }
|
int fps() const { return currentFps_; }
|
||||||
|
|
@ -110,7 +96,6 @@ private:
|
||||||
void mainLoop();
|
void mainLoop();
|
||||||
void update();
|
void update();
|
||||||
void render();
|
void render();
|
||||||
void configureCameraService();
|
|
||||||
|
|
||||||
bool initialized_ = false;
|
bool initialized_ = false;
|
||||||
bool running_ = false;
|
bool running_ = false;
|
||||||
|
|
|
||||||
|
|
@ -10,32 +10,11 @@
|
||||||
#include <extra2d/core/registry.h>
|
#include <extra2d/core/registry.h>
|
||||||
#include <extra2d/core/types.h>
|
#include <extra2d/core/types.h>
|
||||||
|
|
||||||
// Config removed - app info now in Application class
|
|
||||||
|
|
||||||
// Platform
|
// Platform
|
||||||
#include <extra2d/platform/glfw/glfw_window.h>
|
#include <extra2d/platform/glfw/glfw_window.h>
|
||||||
#include <extra2d/platform/keys.h>
|
#include <extra2d/platform/keys.h>
|
||||||
#include <extra2d/platform/window_module.h>
|
#include <extra2d/platform/window_module.h>
|
||||||
|
|
||||||
// Graphics
|
|
||||||
#include <extra2d/graphics/camera/camera.h>
|
|
||||||
#include <extra2d/graphics/camera/viewport_adapter.h>
|
|
||||||
#include <extra2d/graphics/core/render_module.h>
|
|
||||||
#include <extra2d/graphics/core/render_target.h>
|
|
||||||
#include <extra2d/graphics/memory/vram_manager.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_renderer.h>
|
|
||||||
#include <extra2d/graphics/shader/shader_manager.h>
|
|
||||||
#include <extra2d/graphics/texture/font.h>
|
|
||||||
#include <extra2d/graphics/texture/texture.h>
|
|
||||||
#include <extra2d/graphics/texture/texture_pool.h>
|
|
||||||
|
|
||||||
// Scene
|
|
||||||
#include <extra2d/scene/node.h>
|
|
||||||
#include <extra2d/scene/scene.h>
|
|
||||||
#include <extra2d/scene/scene_manager.h>
|
|
||||||
#include <extra2d/scene/shape_node.h>
|
|
||||||
#include <extra2d/scene/sprite.h>
|
|
||||||
|
|
||||||
// Event
|
// Event
|
||||||
#include <extra2d/event/event.h>
|
#include <extra2d/event/event.h>
|
||||||
#include <extra2d/event/event_dispatcher.h>
|
#include <extra2d/event/event_dispatcher.h>
|
||||||
|
|
@ -46,10 +25,8 @@
|
||||||
#include <extra2d/utils/timer.h>
|
#include <extra2d/utils/timer.h>
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
#include <extra2d/services/camera_service.h>
|
|
||||||
#include <extra2d/services/event_service.h>
|
#include <extra2d/services/event_service.h>
|
||||||
#include <extra2d/services/logger_service.h>
|
#include <extra2d/services/logger_service.h>
|
||||||
#include <extra2d/services/scene_service.h>
|
|
||||||
#include <extra2d/services/timer_service.h>
|
#include <extra2d/services/timer_service.h>
|
||||||
|
|
||||||
// Application
|
// Application
|
||||||
|
|
|
||||||
|
|
@ -1,157 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
|
||||||
#include <extra2d/core/math_types.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <glm/mat4x4.hpp>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 形状顶点结构
|
|
||||||
// ============================================================================
|
|
||||||
struct ShapeVertex {
|
|
||||||
float x, y; // 位置
|
|
||||||
float r, g, b, a; // 颜色
|
|
||||||
|
|
||||||
ShapeVertex() = default;
|
|
||||||
ShapeVertex(float px, float py, const Color& c)
|
|
||||||
: x(px), y(py), r(c.r), g(c.g), b(c.b), a(c.a) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 形状批处理抽象接口 - 后端无关
|
|
||||||
// ============================================================================
|
|
||||||
class ShapeBatch {
|
|
||||||
public:
|
|
||||||
virtual ~ShapeBatch() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化形状批处理
|
|
||||||
* @return 成功返回 true
|
|
||||||
*/
|
|
||||||
virtual bool init() = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 关闭形状批处理,释放资源
|
|
||||||
*/
|
|
||||||
virtual void shutdown() = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 开始批处理
|
|
||||||
* @param viewProjection 视图投影矩阵
|
|
||||||
*/
|
|
||||||
virtual void begin(const glm::mat4& viewProjection) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 结束批处理并提交绘制
|
|
||||||
*/
|
|
||||||
virtual void end() = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制线段
|
|
||||||
* @param start 起点
|
|
||||||
* @param end 终点
|
|
||||||
* @param color 颜色
|
|
||||||
* @param width 线宽
|
|
||||||
*/
|
|
||||||
virtual void drawLine(const Vec2& start, const Vec2& end,
|
|
||||||
const Color& color, float width = 1.0f) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制矩形边框
|
|
||||||
* @param rect 矩形区域
|
|
||||||
* @param color 颜色
|
|
||||||
* @param width 边框宽度
|
|
||||||
*/
|
|
||||||
virtual void drawRect(const Rect& rect, const Color& color,
|
|
||||||
float width = 1.0f) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 填充矩形
|
|
||||||
* @param rect 矩形区域
|
|
||||||
* @param color 颜色
|
|
||||||
*/
|
|
||||||
virtual void fillRect(const Rect& rect, const Color& color) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制圆形边框
|
|
||||||
* @param center 圆心
|
|
||||||
* @param radius 半径
|
|
||||||
* @param color 颜色
|
|
||||||
* @param segments 分段数
|
|
||||||
* @param width 边框宽度
|
|
||||||
*/
|
|
||||||
virtual void drawCircle(const Vec2& center, float radius,
|
|
||||||
const Color& color, int segments = 32,
|
|
||||||
float width = 1.0f) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 填充圆形
|
|
||||||
* @param center 圆心
|
|
||||||
* @param radius 半径
|
|
||||||
* @param color 颜色
|
|
||||||
* @param segments 分段数
|
|
||||||
*/
|
|
||||||
virtual void fillCircle(const Vec2& center, float radius,
|
|
||||||
const Color& color, int segments = 32) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制三角形边框
|
|
||||||
* @param p1 顶点1
|
|
||||||
* @param p2 顶点2
|
|
||||||
* @param p3 顶点3
|
|
||||||
* @param color 颜色
|
|
||||||
* @param width 边框宽度
|
|
||||||
*/
|
|
||||||
virtual void drawTriangle(const Vec2& p1, const Vec2& p2, const Vec2& p3,
|
|
||||||
const Color& color, float width = 1.0f) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 填充三角形
|
|
||||||
* @param p1 顶点1
|
|
||||||
* @param p2 顶点2
|
|
||||||
* @param p3 顶点3
|
|
||||||
* @param color 颜色
|
|
||||||
*/
|
|
||||||
virtual void fillTriangle(const Vec2& p1, const Vec2& p2, const Vec2& p3,
|
|
||||||
const Color& color) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制多边形边框
|
|
||||||
* @param points 顶点数组
|
|
||||||
* @param color 颜色
|
|
||||||
* @param width 边框宽度
|
|
||||||
*/
|
|
||||||
virtual void drawPolygon(const std::vector<Vec2>& points,
|
|
||||||
const Color& color, float width = 1.0f) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 填充多边形
|
|
||||||
* @param points 顶点数组
|
|
||||||
* @param color 颜色
|
|
||||||
*/
|
|
||||||
virtual void fillPolygon(const std::vector<Vec2>& points,
|
|
||||||
const Color& color) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取绘制调用次数
|
|
||||||
* @return 绘制调用次数
|
|
||||||
*/
|
|
||||||
virtual uint32_t getDrawCallCount() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 重置绘制调用计数
|
|
||||||
*/
|
|
||||||
virtual void resetDrawCallCount() = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查是否有效
|
|
||||||
* @return 有效返回 true
|
|
||||||
*/
|
|
||||||
virtual bool isValid() const = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,121 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/core/math_types.h>
|
|
||||||
#include <extra2d/core/color.h>
|
|
||||||
#include <extra2d/graphics/texture/texture.h>
|
|
||||||
|
|
||||||
#include <glm/mat4x4.hpp>
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <vector>
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 三角函数查表 - 避免每帧计算 sin/cos
|
|
||||||
// ============================================================================
|
|
||||||
class TrigLookup {
|
|
||||||
public:
|
|
||||||
TrigLookup();
|
|
||||||
|
|
||||||
// 通过角度(0-360)获取 sin/cos
|
|
||||||
float sin(int angle) const;
|
|
||||||
float cos(int angle) const;
|
|
||||||
|
|
||||||
// 通过弧度获取 sin/cos
|
|
||||||
float sinRad(float rad) const;
|
|
||||||
float cosRad(float rad) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
static constexpr int TABLE_SIZE = 360 * 4; // 0.25度精度
|
|
||||||
std::array<float, TABLE_SIZE> sinTable_;
|
|
||||||
std::array<float, TABLE_SIZE> cosTable_;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 精灵批次数据 - 后端无关
|
|
||||||
// ============================================================================
|
|
||||||
struct SpriteVertex {
|
|
||||||
Vec2 position;
|
|
||||||
Vec2 texCoord;
|
|
||||||
Color color;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SpriteData {
|
|
||||||
Vec2 position;
|
|
||||||
Vec2 size;
|
|
||||||
float rotation;
|
|
||||||
Vec2 pivot;
|
|
||||||
Color color;
|
|
||||||
const Texture* texture;
|
|
||||||
Rect uvRect;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 通用精灵批处理 - 后端无关
|
|
||||||
// 负责:顶点生成、批次管理、三角函数查表
|
|
||||||
// ============================================================================
|
|
||||||
class SpriteBatch {
|
|
||||||
public:
|
|
||||||
static constexpr size_t MAX_SPRITES = 10000;
|
|
||||||
static constexpr size_t VERTICES_PER_SPRITE = 4;
|
|
||||||
static constexpr size_t INDICES_PER_SPRITE = 6;
|
|
||||||
static constexpr size_t MAX_VERTICES = MAX_SPRITES * VERTICES_PER_SPRITE;
|
|
||||||
static constexpr size_t MAX_INDICES = MAX_SPRITES * INDICES_PER_SPRITE;
|
|
||||||
|
|
||||||
SpriteBatch();
|
|
||||||
~SpriteBatch() = default;
|
|
||||||
|
|
||||||
// 开始批次
|
|
||||||
void begin(const glm::mat4& viewProjection);
|
|
||||||
|
|
||||||
// 结束批次 - 返回需要绘制的批次列表
|
|
||||||
void end();
|
|
||||||
|
|
||||||
// 绘制单个精灵
|
|
||||||
void draw(const SpriteData& sprite);
|
|
||||||
|
|
||||||
// 批量绘制 - 一次性处理多个精灵
|
|
||||||
void drawBatch(const std::vector<SpriteData>& sprites);
|
|
||||||
|
|
||||||
// 立即绘制 - 不缓存,直接提交
|
|
||||||
void drawImmediate(const SpriteData& sprite);
|
|
||||||
|
|
||||||
// 获取当前批次数据
|
|
||||||
const std::vector<SpriteVertex>& getVertices() const { return vertices_; }
|
|
||||||
const std::vector<uint16_t>& getIndices() const { return indices_; }
|
|
||||||
size_t getSpriteCount() const { return spriteCount_; }
|
|
||||||
|
|
||||||
// 检查是否需要刷新
|
|
||||||
bool needsFlush() const { return spriteCount_ >= MAX_SPRITES; }
|
|
||||||
|
|
||||||
// 清空批次
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
private:
|
|
||||||
// 三角函数查表
|
|
||||||
TrigLookup trigLookup_;
|
|
||||||
|
|
||||||
// 顶点数据 - 使用固定大小数组避免动态分配
|
|
||||||
std::vector<SpriteVertex> vertices_;
|
|
||||||
std::vector<uint16_t> indices_;
|
|
||||||
size_t spriteCount_;
|
|
||||||
|
|
||||||
// 变换矩阵
|
|
||||||
glm::mat4 viewProjection_;
|
|
||||||
glm::mat4 cachedVP_;
|
|
||||||
bool vpDirty_;
|
|
||||||
|
|
||||||
// 生成索引
|
|
||||||
void generateIndices();
|
|
||||||
|
|
||||||
// 生成顶点
|
|
||||||
void generateVertices(const SpriteData& sprite, size_t vertexOffset);
|
|
||||||
|
|
||||||
// 刷新批次
|
|
||||||
void flush();
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,111 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
|
||||||
#include <extra2d/core/math_types.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <glm/mat4x4.hpp>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
class ViewportAdapter;
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 2D 正交相机
|
|
||||||
// ============================================================================
|
|
||||||
class Camera {
|
|
||||||
public:
|
|
||||||
Camera();
|
|
||||||
Camera(float left, float right, float bottom, float top);
|
|
||||||
Camera(const Size &viewport);
|
|
||||||
~Camera() = default;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 位置和变换
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setPos(const Vec2 &position);
|
|
||||||
void setPos(float x, float y);
|
|
||||||
Vec2 getPosition() const { return position_; }
|
|
||||||
|
|
||||||
void setRotation(float degrees);
|
|
||||||
float getRotation() const { return rotation_; }
|
|
||||||
|
|
||||||
void setZoom(float zoom);
|
|
||||||
float getZoom() const { return zoom_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 视口设置
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setViewport(float left, float right, float bottom, float top);
|
|
||||||
void setViewport(const Rect &rect);
|
|
||||||
Rect getViewport() const;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 矩阵获取
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
glm::mat4 getViewMatrix() const;
|
|
||||||
glm::mat4 getProjectionMatrix() const;
|
|
||||||
glm::mat4 getViewProjectionMatrix() const;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 坐标转换
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
Vec2 screenToWorld(const Vec2 &screenPos) const;
|
|
||||||
Vec2 worldToScreen(const Vec2 &worldPos) const;
|
|
||||||
Vec2 screenToWorld(float x, float y) const;
|
|
||||||
Vec2 worldToScreen(float x, float y) const;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 移动相机
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void move(const Vec2 &offset);
|
|
||||||
void move(float x, float y);
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 边界限制
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setBounds(const Rect &bounds);
|
|
||||||
void clearBounds();
|
|
||||||
void clampToBounds();
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 视口适配器
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
/**
|
|
||||||
* @brief 设置视口适配器
|
|
||||||
* @param adapter 视口适配器指针
|
|
||||||
*/
|
|
||||||
void setViewportAdapter(ViewportAdapter *adapter);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 根据视口适配器自动设置视口
|
|
||||||
*/
|
|
||||||
void applyViewportAdapter();
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 快捷方法:看向某点
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void lookAt(const Vec2 &target);
|
|
||||||
|
|
||||||
private:
|
|
||||||
Vec2 position_ = Vec2::Zero();
|
|
||||||
float rotation_ = 0.0f;
|
|
||||||
float zoom_ = 1.0f;
|
|
||||||
|
|
||||||
float left_ = -1.0f;
|
|
||||||
float right_ = 1.0f;
|
|
||||||
float bottom_ = -1.0f;
|
|
||||||
float top_ = 1.0f;
|
|
||||||
|
|
||||||
Rect bounds_;
|
|
||||||
bool hasBounds_ = false;
|
|
||||||
|
|
||||||
ViewportAdapter *viewportAdapter_ = nullptr;
|
|
||||||
|
|
||||||
mutable glm::mat4 viewMatrix_;
|
|
||||||
mutable glm::mat4 projMatrix_;
|
|
||||||
mutable glm::mat4 vpMatrix_;
|
|
||||||
mutable bool viewDirty_ = true;
|
|
||||||
mutable bool projDirty_ = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,332 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
|
||||||
#include <extra2d/core/math_types.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <glm/mat4x4.hpp>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 视口适配模式枚举
|
|
||||||
// ============================================================================
|
|
||||||
enum class ViewportMode {
|
|
||||||
AspectRatio,
|
|
||||||
Stretch,
|
|
||||||
Center,
|
|
||||||
Custom
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 黑边位置枚举
|
|
||||||
// ============================================================================
|
|
||||||
enum class LetterboxPosition {
|
|
||||||
Center,
|
|
||||||
LeftTop,
|
|
||||||
RightTop,
|
|
||||||
LeftBottom,
|
|
||||||
RightBottom
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 视口配置结构体
|
|
||||||
// ============================================================================
|
|
||||||
struct ViewportConfig {
|
|
||||||
float logicWidth = 1920.0f;
|
|
||||||
float logicHeight = 1080.0f;
|
|
||||||
ViewportMode mode = ViewportMode::AspectRatio;
|
|
||||||
LetterboxPosition letterboxPosition = LetterboxPosition::Center;
|
|
||||||
Color letterboxColor = Colors::Black;
|
|
||||||
bool autoScaleInCenterMode = true;
|
|
||||||
float customScale = 1.0f;
|
|
||||||
Vec2 customOffset = Vec2::Zero();
|
|
||||||
Rect customViewport = Rect::Zero();
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 视口计算结果结构体
|
|
||||||
// ============================================================================
|
|
||||||
struct ViewportResult {
|
|
||||||
Rect viewport;
|
|
||||||
float scaleX = 1.0f;
|
|
||||||
float scaleY = 1.0f;
|
|
||||||
float uniformScale = 1.0f;
|
|
||||||
Vec2 offset;
|
|
||||||
bool hasLetterbox = false;
|
|
||||||
|
|
||||||
struct Letterbox {
|
|
||||||
Rect top;
|
|
||||||
Rect bottom;
|
|
||||||
Rect left;
|
|
||||||
Rect right;
|
|
||||||
} letterbox;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 视口适配器类
|
|
||||||
// ============================================================================
|
|
||||||
class ViewportAdapter {
|
|
||||||
public:
|
|
||||||
ViewportAdapter();
|
|
||||||
ViewportAdapter(float logicWidth, float logicHeight);
|
|
||||||
~ViewportAdapter() = default;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 配置设置
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置视口配置
|
|
||||||
* @param config 视口配置结构体
|
|
||||||
*/
|
|
||||||
void setConfig(const ViewportConfig &config);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前视口配置
|
|
||||||
* @return 当前视口配置
|
|
||||||
*/
|
|
||||||
const ViewportConfig &getConfig() const { return config_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置逻辑分辨率
|
|
||||||
* @param width 逻辑宽度
|
|
||||||
* @param height 逻辑高度
|
|
||||||
*/
|
|
||||||
void setLogicSize(float width, float height);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置视口适配模式
|
|
||||||
* @param mode 适配模式
|
|
||||||
*/
|
|
||||||
void setMode(ViewportMode mode);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置黑边位置
|
|
||||||
* @param position 黑边位置
|
|
||||||
*/
|
|
||||||
void setLetterboxPosition(LetterboxPosition position);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置黑边颜色
|
|
||||||
* @param color 黑边颜色
|
|
||||||
*/
|
|
||||||
void setLetterboxColor(const Color &color);
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 更新和计算
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 更新视口适配计算
|
|
||||||
* @param screenWidth 屏幕宽度
|
|
||||||
* @param screenHeight 屏幕高度
|
|
||||||
*/
|
|
||||||
void update(int screenWidth, int screenHeight);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取计算结果
|
|
||||||
* @return 视口计算结果
|
|
||||||
*/
|
|
||||||
const ViewportResult &getResult() const { return result_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 坐标转换
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 屏幕坐标转逻辑坐标
|
|
||||||
* @param screenPos 屏幕坐标
|
|
||||||
* @return 逻辑坐标
|
|
||||||
*/
|
|
||||||
Vec2 screenToLogic(const Vec2 &screenPos) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 逻辑坐标转屏幕坐标
|
|
||||||
* @param logicPos 逻辑坐标
|
|
||||||
* @return 屏幕坐标
|
|
||||||
*/
|
|
||||||
Vec2 logicToScreen(const Vec2 &logicPos) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 屏幕坐标转逻辑坐标(分量形式)
|
|
||||||
* @param x 屏幕X坐标
|
|
||||||
* @param y 屏幕Y坐标
|
|
||||||
* @return 逻辑坐标
|
|
||||||
*/
|
|
||||||
Vec2 screenToLogic(float x, float y) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 逻辑坐标转屏幕坐标(分量形式)
|
|
||||||
* @param x 逻辑X坐标
|
|
||||||
* @param y 逻辑Y坐标
|
|
||||||
* @return 屏幕坐标
|
|
||||||
*/
|
|
||||||
Vec2 logicToScreen(float x, float y) const;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 矩阵获取
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取视口变换矩阵
|
|
||||||
* @return 视口变换矩阵(从逻辑坐标到屏幕坐标)
|
|
||||||
*/
|
|
||||||
glm::mat4 getMatrix() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取反向视口变换矩阵
|
|
||||||
* @return 反向视口变换矩阵(从屏幕坐标到逻辑坐标)
|
|
||||||
*/
|
|
||||||
glm::mat4 getInvMatrix() const;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 区域检测
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查屏幕坐标是否在视口内
|
|
||||||
* @param screenPos 屏幕坐标
|
|
||||||
* @return 如果在视口内返回 true
|
|
||||||
*/
|
|
||||||
bool isInViewport(const Vec2 &screenPos) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查屏幕坐标是否在黑边区域
|
|
||||||
* @param screenPos 屏幕坐标
|
|
||||||
* @return 如果在黑边区域返回 true
|
|
||||||
*/
|
|
||||||
bool isInLetterbox(const Vec2 &screenPos) const;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// Getter 方法
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取逻辑宽度
|
|
||||||
* @return 逻辑宽度
|
|
||||||
*/
|
|
||||||
float getLogicWidth() const { return config_.logicWidth; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取逻辑高度
|
|
||||||
* @return 逻辑高度
|
|
||||||
*/
|
|
||||||
float getLogicHeight() const { return config_.logicHeight; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取逻辑尺寸
|
|
||||||
* @return 逻辑尺寸
|
|
||||||
*/
|
|
||||||
Size getLogicSize() const {
|
|
||||||
return Size(config_.logicWidth, config_.logicHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取屏幕宽度
|
|
||||||
* @return 屏幕宽度
|
|
||||||
*/
|
|
||||||
int getScreenWidth() const { return screenWidth_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取屏幕高度
|
|
||||||
* @return 屏幕高度
|
|
||||||
*/
|
|
||||||
int getScreenHeight() const { return screenHeight_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取屏幕尺寸
|
|
||||||
* @return 屏幕尺寸
|
|
||||||
*/
|
|
||||||
Size getScreenSize() const {
|
|
||||||
return Size(static_cast<float>(screenWidth_),
|
|
||||||
static_cast<float>(screenHeight_));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取X方向缩放比例
|
|
||||||
* @return X方向缩放比例
|
|
||||||
*/
|
|
||||||
float getScaleX() const { return result_.scaleX; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取Y方向缩放比例
|
|
||||||
* @return Y方向缩放比例
|
|
||||||
*/
|
|
||||||
float getScaleY() const { return result_.scaleY; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取统一缩放比例
|
|
||||||
* @return 统一缩放比例
|
|
||||||
*/
|
|
||||||
float getUniformScale() const { return result_.uniformScale; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取视口偏移
|
|
||||||
* @return 视口偏移
|
|
||||||
*/
|
|
||||||
Vec2 getOffset() const { return result_.offset; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取视口矩形
|
|
||||||
* @return 视口矩形
|
|
||||||
*/
|
|
||||||
Rect getViewport() const { return result_.viewport; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查是否有黑边
|
|
||||||
* @return 如果有黑边返回 true
|
|
||||||
*/
|
|
||||||
bool hasLetterbox() const { return result_.hasLetterbox; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取黑边信息
|
|
||||||
* @return 黑边信息结构体
|
|
||||||
*/
|
|
||||||
const ViewportResult::Letterbox &getLetterbox() const {
|
|
||||||
return result_.letterbox;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
/**
|
|
||||||
* @brief 计算宽高比适配模式
|
|
||||||
*/
|
|
||||||
void calculateAspectRatio();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 计算拉伸适配模式
|
|
||||||
*/
|
|
||||||
void calculateStretch();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 计算居中适配模式
|
|
||||||
*/
|
|
||||||
void calculateCenter();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 计算自定义适配模式
|
|
||||||
*/
|
|
||||||
void calculateCustom();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 计算黑边区域
|
|
||||||
*/
|
|
||||||
void calculateLetterbox();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 根据黑边位置调整偏移
|
|
||||||
* @param extraWidth 额外宽度
|
|
||||||
* @param extraHeight 额外高度
|
|
||||||
*/
|
|
||||||
void applyLetterboxPosition(float extraWidth, float extraHeight);
|
|
||||||
|
|
||||||
ViewportConfig config_;
|
|
||||||
ViewportResult result_;
|
|
||||||
int screenWidth_ = 0;
|
|
||||||
int screenHeight_ = 0;
|
|
||||||
|
|
||||||
mutable glm::mat4 viewportMatrix_;
|
|
||||||
mutable glm::mat4 inverseViewportMatrix_;
|
|
||||||
mutable bool matrixDirty_ = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,223 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
|
||||||
#include <extra2d/core/math_types.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/graphics/texture/texture.h>
|
|
||||||
#include <glm/mat4x4.hpp>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <variant>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// 前向声明
|
|
||||||
class Texture;
|
|
||||||
class FontAtlas;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 渲染命令类型枚举
|
|
||||||
*/
|
|
||||||
enum class RenderCommandType : uint8_t {
|
|
||||||
None = 0,
|
|
||||||
Sprite, // 精灵绘制
|
|
||||||
Line, // 线条绘制
|
|
||||||
Rect, // 矩形绘制
|
|
||||||
FilledRect, // 填充矩形
|
|
||||||
Circle, // 圆形绘制
|
|
||||||
FilledCircle, // 填充圆形
|
|
||||||
Triangle, // 三角形绘制
|
|
||||||
FilledTriangle, // 填充三角形
|
|
||||||
Polygon, // 多边形绘制
|
|
||||||
FilledPolygon, // 填充多边形
|
|
||||||
Text, // 文本绘制
|
|
||||||
Custom // 自定义绘制
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 精灵渲染命令数据
|
|
||||||
*/
|
|
||||||
struct SpriteCommandData {
|
|
||||||
const Texture* texture;
|
|
||||||
Rect destRect;
|
|
||||||
Rect srcRect;
|
|
||||||
Color tint;
|
|
||||||
float rotation;
|
|
||||||
Vec2 anchor;
|
|
||||||
uint32_t sortKey; // 用于自动排序的键值
|
|
||||||
|
|
||||||
SpriteCommandData()
|
|
||||||
: texture(nullptr), destRect(), srcRect(), tint(Colors::White),
|
|
||||||
rotation(0.0f), anchor(0.0f, 0.0f), sortKey(0) {}
|
|
||||||
SpriteCommandData(const Texture* tex, const Rect& dest, const Rect& src,
|
|
||||||
const Color& t, float rot, const Vec2& anc, uint32_t key)
|
|
||||||
: texture(tex), destRect(dest), srcRect(src), tint(t),
|
|
||||||
rotation(rot), anchor(anc), sortKey(key) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 线条渲染命令数据
|
|
||||||
*/
|
|
||||||
struct LineCommandData {
|
|
||||||
Vec2 start;
|
|
||||||
Vec2 end;
|
|
||||||
Color color;
|
|
||||||
float width;
|
|
||||||
|
|
||||||
LineCommandData() : start(), end(), color(Colors::White), width(1.0f) {}
|
|
||||||
LineCommandData(const Vec2& s, const Vec2& e, const Color& c, float w)
|
|
||||||
: start(s), end(e), color(c), width(w) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 矩形渲染命令数据
|
|
||||||
*/
|
|
||||||
struct RectCommandData {
|
|
||||||
Rect rect;
|
|
||||||
Color color;
|
|
||||||
float width;
|
|
||||||
bool filled;
|
|
||||||
|
|
||||||
RectCommandData() : rect(), color(Colors::White), width(1.0f), filled(false) {}
|
|
||||||
RectCommandData(const Rect& r, const Color& c, float w, bool f)
|
|
||||||
: rect(r), color(c), width(w), filled(f) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 圆形渲染命令数据
|
|
||||||
*/
|
|
||||||
struct CircleCommandData {
|
|
||||||
Vec2 center;
|
|
||||||
float radius;
|
|
||||||
Color color;
|
|
||||||
int segments;
|
|
||||||
float width;
|
|
||||||
bool filled;
|
|
||||||
|
|
||||||
CircleCommandData() : center(), radius(0.0f), color(Colors::White),
|
|
||||||
segments(32), width(1.0f), filled(false) {}
|
|
||||||
CircleCommandData(const Vec2& c, float r, const Color& col, int seg, float w, bool f)
|
|
||||||
: center(c), radius(r), color(col), segments(seg), width(w), filled(f) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 三角形渲染命令数据
|
|
||||||
*/
|
|
||||||
struct TriangleCommandData {
|
|
||||||
Vec2 p1, p2, p3;
|
|
||||||
Color color;
|
|
||||||
float width;
|
|
||||||
bool filled;
|
|
||||||
|
|
||||||
TriangleCommandData() : p1(), p2(), p3(), color(Colors::White),
|
|
||||||
width(1.0f), filled(false) {}
|
|
||||||
TriangleCommandData(const Vec2& a, const Vec2& b, const Vec2& c, const Color& col, float w, bool f)
|
|
||||||
: p1(a), p2(b), p3(c), color(col), width(w), filled(f) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 多边形渲染命令数据
|
|
||||||
*/
|
|
||||||
struct PolygonCommandData {
|
|
||||||
std::vector<Vec2> points;
|
|
||||||
Color color;
|
|
||||||
float width;
|
|
||||||
bool filled;
|
|
||||||
|
|
||||||
PolygonCommandData() : color(Colors::White), width(1.0f), filled(false) {}
|
|
||||||
PolygonCommandData(std::vector<Vec2> pts, const Color& col, float w, bool f)
|
|
||||||
: points(std::move(pts)), color(col), width(w), filled(f) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 文本渲染命令数据
|
|
||||||
*/
|
|
||||||
struct TextCommandData {
|
|
||||||
const FontAtlas* font;
|
|
||||||
std::string text;
|
|
||||||
Vec2 position;
|
|
||||||
Color color;
|
|
||||||
|
|
||||||
TextCommandData() : font(nullptr), text(), position(), color(Colors::White) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 统一渲染命令结构
|
|
||||||
* 使用 variant 存储不同类型的命令数据,减少内存分配
|
|
||||||
*/
|
|
||||||
struct RenderCommand {
|
|
||||||
RenderCommandType type;
|
|
||||||
uint32_t layer; // 渲染层级,用于排序
|
|
||||||
uint32_t order; // 提交顺序,保证同层级内稳定排序
|
|
||||||
glm::mat4 transform; // 变换矩阵
|
|
||||||
|
|
||||||
// 使用 variant 存储具体数据
|
|
||||||
std::variant<
|
|
||||||
SpriteCommandData,
|
|
||||||
LineCommandData,
|
|
||||||
RectCommandData,
|
|
||||||
CircleCommandData,
|
|
||||||
TriangleCommandData,
|
|
||||||
PolygonCommandData,
|
|
||||||
TextCommandData
|
|
||||||
> data;
|
|
||||||
|
|
||||||
RenderCommand() : type(RenderCommandType::None), layer(0), order(0),
|
|
||||||
transform(1.0f) {}
|
|
||||||
|
|
||||||
// 便捷构造函数
|
|
||||||
static RenderCommand makeSprite(const Texture* tex, const Rect& dest,
|
|
||||||
const Rect& src, const Color& tint,
|
|
||||||
float rot = 0.0f, const Vec2& anc = Vec2(0, 0),
|
|
||||||
uint32_t lyr = 0);
|
|
||||||
static RenderCommand makeLine(const Vec2& s, const Vec2& e, const Color& c,
|
|
||||||
float w = 1.0f, uint32_t lyr = 0);
|
|
||||||
static RenderCommand makeRect(const Rect& r, const Color& c,
|
|
||||||
float w = 1.0f, bool fill = false, uint32_t lyr = 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 渲染命令缓冲区
|
|
||||||
* 用于收集和批量处理渲染命令
|
|
||||||
*/
|
|
||||||
class RenderCommandBuffer {
|
|
||||||
public:
|
|
||||||
static constexpr size_t INITIAL_CAPACITY = 1024;
|
|
||||||
static constexpr size_t MAX_CAPACITY = 65536;
|
|
||||||
|
|
||||||
RenderCommandBuffer();
|
|
||||||
~RenderCommandBuffer();
|
|
||||||
|
|
||||||
// 添加渲染命令
|
|
||||||
void addCommand(const RenderCommand& cmd);
|
|
||||||
void addCommand(RenderCommand&& cmd);
|
|
||||||
|
|
||||||
// 批量添加(预留空间后使用)
|
|
||||||
RenderCommand& emplaceCommand();
|
|
||||||
|
|
||||||
// 排序命令(按纹理、层级等)
|
|
||||||
void sortCommands();
|
|
||||||
|
|
||||||
// 清空缓冲区
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
// 获取命令列表
|
|
||||||
const std::vector<RenderCommand>& getCommands() const { return commands_; }
|
|
||||||
std::vector<RenderCommand>& getCommands() { return commands_; }
|
|
||||||
|
|
||||||
// 统计
|
|
||||||
size_t size() const { return commands_.size(); }
|
|
||||||
bool empty() const { return commands_.empty(); }
|
|
||||||
size_t capacity() const { return commands_.capacity(); }
|
|
||||||
|
|
||||||
// 预分配空间
|
|
||||||
void reserve(size_t capacity);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<RenderCommand> commands_;
|
|
||||||
uint32_t nextOrder_;
|
|
||||||
|
|
||||||
// 排序比较函数
|
|
||||||
static bool compareCommands(const RenderCommand& a, const RenderCommand& b);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/module.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_renderer.h>
|
|
||||||
#include <extra2d/platform/window_module.h>
|
|
||||||
#include <functional>
|
|
||||||
#include <typeindex>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 渲染模块配置结构
|
|
||||||
*/
|
|
||||||
struct RenderCfg {
|
|
||||||
int targetFPS;
|
|
||||||
bool vsync;
|
|
||||||
int multisamples;
|
|
||||||
int priority;
|
|
||||||
|
|
||||||
RenderCfg() : targetFPS(60), vsync(true), multisamples(0), priority(10) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 渲染模块
|
|
||||||
* 管理 OpenGL 渲染器
|
|
||||||
*/
|
|
||||||
class RenderModule : public Module {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief 构造函数(Lambda 配置)
|
|
||||||
* @param configFn 配置函数
|
|
||||||
*/
|
|
||||||
explicit RenderModule(std::function<void(RenderCfg &)> configFn);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 析构函数
|
|
||||||
*/
|
|
||||||
~RenderModule() override;
|
|
||||||
|
|
||||||
bool init() override;
|
|
||||||
void shutdown() override;
|
|
||||||
bool ok() const override { return initialized_; }
|
|
||||||
const char *name() const override { return "render"; }
|
|
||||||
int priority() const override { return cfg_.priority; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取依赖
|
|
||||||
* @return 依赖模块类型列表
|
|
||||||
*/
|
|
||||||
std::vector<std::type_index> deps() const override {
|
|
||||||
return {std::type_index(typeid(WindowModule))};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 是否允许并行初始化
|
|
||||||
* RenderModule 需要 OpenGL 上下文,必须在主线程初始化
|
|
||||||
* @return 不允许并行初始化返回 false
|
|
||||||
*/
|
|
||||||
bool allowParallelInit() const override { return false; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取渲染器
|
|
||||||
* @return 渲染器指针
|
|
||||||
*/
|
|
||||||
Renderer *renderer() const { return renderer_.get(); }
|
|
||||||
|
|
||||||
private:
|
|
||||||
RenderCfg cfg_;
|
|
||||||
UniquePtr<Renderer> renderer_;
|
|
||||||
bool initialized_ = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,313 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
|
||||||
#include <extra2d/graphics/texture/texture.h>
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 渲染目标配置
|
|
||||||
// ============================================================================
|
|
||||||
struct RenderTargetConfig {
|
|
||||||
int width = 800; // 宽度
|
|
||||||
int height = 600; // 高度
|
|
||||||
PixelFormat colorFormat = PixelFormat::RGBA8; // 颜色格式
|
|
||||||
bool hasDepth = true; // 是否包含深度缓冲
|
|
||||||
bool hasStencil = false; // 是否包含模板缓冲
|
|
||||||
int samples = 1; // 多重采样数 (1 = 无MSAA)
|
|
||||||
bool autoResize = true; // 是否自动调整大小
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 渲染目标 - 基于FBO的离屏渲染
|
|
||||||
// ============================================================================
|
|
||||||
class RenderTarget {
|
|
||||||
public:
|
|
||||||
RenderTarget();
|
|
||||||
~RenderTarget();
|
|
||||||
|
|
||||||
// 禁止拷贝
|
|
||||||
RenderTarget(const RenderTarget &) = delete;
|
|
||||||
RenderTarget &operator=(const RenderTarget &) = delete;
|
|
||||||
|
|
||||||
// 允许移动
|
|
||||||
RenderTarget(RenderTarget &&other) noexcept;
|
|
||||||
RenderTarget &operator=(RenderTarget &&other) noexcept;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 创建和销毁
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建渲染目标
|
|
||||||
*/
|
|
||||||
bool create(const RenderTargetConfig &config);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从现有纹理创建渲染目标
|
|
||||||
*/
|
|
||||||
bool createFromTexture(Ptr<Texture> texture, bool hasDepth = false);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 销毁渲染目标
|
|
||||||
*/
|
|
||||||
void destroy();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查是否有效
|
|
||||||
*/
|
|
||||||
bool isValid() const { return fbo_ != 0; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 尺寸和格式
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
int getWidth() const { return width_; }
|
|
||||||
int getHeight() const { return height_; }
|
|
||||||
Vec2 getSize() const {
|
|
||||||
return Vec2(static_cast<float>(width_), static_cast<float>(height_));
|
|
||||||
}
|
|
||||||
PixelFormat getColorFormat() const { return colorFormat_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 绑定和解绑
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绑定为当前渲染目标
|
|
||||||
*/
|
|
||||||
void bind();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 解绑(恢复默认渲染目标)
|
|
||||||
*/
|
|
||||||
void unbind();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 清除渲染目标
|
|
||||||
*/
|
|
||||||
void clear(const Color &color = Colors::Transparent);
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 纹理访问
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取颜色纹理
|
|
||||||
*/
|
|
||||||
Ptr<Texture> getColorTexture() const { return colorTexture_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取深度纹理(如果有)
|
|
||||||
*/
|
|
||||||
Ptr<Texture> getDepthTexture() const { return depthTexture_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 视口和裁剪
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置视口(相对于渲染目标)
|
|
||||||
*/
|
|
||||||
void setViewport(int x, int y, int width, int height);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取完整视口
|
|
||||||
*/
|
|
||||||
void getFullViewport(int &x, int &y, int &width, int &height) const;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 工具方法
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 调整大小(会销毁并重新创建)
|
|
||||||
*/
|
|
||||||
bool resize(int width, int height);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 复制到另一个渲染目标
|
|
||||||
*/
|
|
||||||
void copyTo(RenderTarget &target);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 复制到屏幕
|
|
||||||
*/
|
|
||||||
void copyToScreen(int screenWidth, int screenHeight);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 保存为图像文件
|
|
||||||
*/
|
|
||||||
bool saveToFile(const std::string &filepath);
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 静态方法
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建渲染目标的静态工厂方法
|
|
||||||
*/
|
|
||||||
static Ptr<RenderTarget> createFromConfig(const RenderTargetConfig &config);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前绑定的渲染目标ID
|
|
||||||
*/
|
|
||||||
static GLuint getCurrentFBO();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绑定默认渲染目标(屏幕)
|
|
||||||
*/
|
|
||||||
static void bindDefault();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取FBO ID(供内部使用)
|
|
||||||
*/
|
|
||||||
GLuint getFBO() const { return fbo_; }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
GLuint fbo_ = 0; // 帧缓冲对象
|
|
||||||
GLuint rbo_ = 0; // 渲染缓冲对象(深度/模板)
|
|
||||||
|
|
||||||
Ptr<Texture> colorTexture_; // 颜色纹理
|
|
||||||
Ptr<Texture> depthTexture_; // 深度纹理(可选)
|
|
||||||
|
|
||||||
int width_ = 0;
|
|
||||||
int height_ = 0;
|
|
||||||
PixelFormat colorFormat_ = PixelFormat::RGBA8;
|
|
||||||
bool hasDepth_ = false;
|
|
||||||
bool hasStencil_ = false;
|
|
||||||
int samples_ = 1;
|
|
||||||
|
|
||||||
bool createFBO();
|
|
||||||
void deleteFBO();
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 多重采样渲染目标(用于MSAA)
|
|
||||||
// ============================================================================
|
|
||||||
class MultisampleRenderTarget : public RenderTarget {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief 创建多重采样渲染目标
|
|
||||||
*/
|
|
||||||
bool create(int width, int height, int samples = 4);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 解析到普通渲染目标(用于显示)
|
|
||||||
*/
|
|
||||||
void resolveTo(RenderTarget &target);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 销毁渲染目标
|
|
||||||
*/
|
|
||||||
void destroy();
|
|
||||||
|
|
||||||
private:
|
|
||||||
GLuint colorRBO_ = 0; // 多重采样颜色渲染缓冲
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 渲染目标栈(用于嵌套渲染)
|
|
||||||
// ============================================================================
|
|
||||||
class RenderTargetStack {
|
|
||||||
public:
|
|
||||||
static RenderTargetStack &get();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 压入渲染目标
|
|
||||||
*/
|
|
||||||
void push(RenderTarget *target);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 弹出渲染目标
|
|
||||||
*/
|
|
||||||
void pop();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前渲染目标
|
|
||||||
*/
|
|
||||||
RenderTarget *getCurrent() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取栈大小
|
|
||||||
*/
|
|
||||||
size_t size() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 清空栈
|
|
||||||
*/
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
private:
|
|
||||||
RenderTargetStack() = default;
|
|
||||||
~RenderTargetStack() = default;
|
|
||||||
|
|
||||||
std::vector<RenderTarget *> stack_;
|
|
||||||
mutable std::mutex mutex_;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 渲染目标管理器 - 全局渲染目标管理
|
|
||||||
// ============================================================================
|
|
||||||
class RenderTargetMgr {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief 获取单例实例
|
|
||||||
*/
|
|
||||||
static RenderTargetMgr &get();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化渲染目标管理器
|
|
||||||
* @param width 默认宽度
|
|
||||||
* @param height 默认高度
|
|
||||||
*/
|
|
||||||
bool init(int width, int height);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 关闭渲染目标管理器
|
|
||||||
*/
|
|
||||||
void shutdown();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建新的渲染目标
|
|
||||||
*/
|
|
||||||
Ptr<RenderTarget> createRenderTarget(const RenderTargetConfig &config);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取默认渲染目标
|
|
||||||
*/
|
|
||||||
RenderTarget *getDefaultRenderTarget() const {
|
|
||||||
return defaultRenderTarget_.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 调整所有受管渲染目标的大小
|
|
||||||
*/
|
|
||||||
void resize(int width, int height);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查是否已初始化
|
|
||||||
*/
|
|
||||||
bool isInitialized() const { return initialized_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
RenderTargetMgr() = default;
|
|
||||||
~RenderTargetMgr() = default;
|
|
||||||
RenderTargetMgr(const RenderTargetMgr &) = delete;
|
|
||||||
RenderTargetMgr &operator=(const RenderTargetMgr &) = delete;
|
|
||||||
|
|
||||||
Ptr<RenderTarget> defaultRenderTarget_;
|
|
||||||
std::vector<Ptr<RenderTarget>> renderTargets_;
|
|
||||||
bool initialized_ = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 便捷宏
|
|
||||||
// ============================================================================
|
|
||||||
#define E2D_RENDER_TARGET_STACK() ::extra2d::RenderTargetStack::get()
|
|
||||||
#define E2D_RENDER_TARGET_MGR() ::extra2d::RenderTargetMgr::get()
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// GPU 上下文状态管理器
|
|
||||||
// 用于跟踪 OpenGL/Vulkan 等 GPU 上下文的生命周期状态
|
|
||||||
// 确保在 GPU 资源析构时能安全地检查上下文是否有效
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
class GPUContext {
|
|
||||||
public:
|
|
||||||
/// 获取单例实例
|
|
||||||
static GPUContext& get();
|
|
||||||
|
|
||||||
/// 标记 GPU 上下文为有效(在初始化完成后调用)
|
|
||||||
void markValid();
|
|
||||||
|
|
||||||
/// 标记 GPU 上下文为无效(在销毁前调用)
|
|
||||||
void markInvalid();
|
|
||||||
|
|
||||||
/// 检查 GPU 上下文是否有效
|
|
||||||
bool isValid() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
GPUContext() = default;
|
|
||||||
~GPUContext() = default;
|
|
||||||
GPUContext(const GPUContext&) = delete;
|
|
||||||
GPUContext& operator=(const GPUContext&) = delete;
|
|
||||||
|
|
||||||
std::atomic<bool> valid_{false};
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// VRAM 管理器 - 跟踪显存使用情况
|
|
||||||
// ============================================================================
|
|
||||||
class VRAMMgr {
|
|
||||||
public:
|
|
||||||
static VRAMMgr& get();
|
|
||||||
|
|
||||||
// 纹理显存跟踪
|
|
||||||
void allocTexture(size_t size);
|
|
||||||
void freeTexture(size_t size);
|
|
||||||
|
|
||||||
// VBO/FBO 显存跟踪
|
|
||||||
void allocBuffer(size_t size);
|
|
||||||
void freeBuffer(size_t size);
|
|
||||||
|
|
||||||
// 查询显存使用情况
|
|
||||||
size_t getUsedVRAM() const;
|
|
||||||
size_t getTextureVRAM() const;
|
|
||||||
size_t getBufferVRAM() const;
|
|
||||||
size_t getAvailableVRAM() const;
|
|
||||||
|
|
||||||
// 显存预算管理
|
|
||||||
void setVRAMBudget(size_t budget);
|
|
||||||
size_t getVRAMBudget() const;
|
|
||||||
bool isOverBudget() const;
|
|
||||||
|
|
||||||
// 统计信息
|
|
||||||
void printStats() const;
|
|
||||||
|
|
||||||
// 重置计数器
|
|
||||||
void reset();
|
|
||||||
|
|
||||||
private:
|
|
||||||
VRAMMgr();
|
|
||||||
~VRAMMgr() = default;
|
|
||||||
VRAMMgr(const VRAMMgr&) = delete;
|
|
||||||
VRAMMgr& operator=(const VRAMMgr&) = delete;
|
|
||||||
|
|
||||||
mutable std::mutex mutex_;
|
|
||||||
|
|
||||||
size_t textureVRAM_;
|
|
||||||
size_t bufferVRAM_;
|
|
||||||
size_t vramBudget_;
|
|
||||||
|
|
||||||
// 统计
|
|
||||||
uint32_t textureAllocCount_;
|
|
||||||
uint32_t textureFreeCount_;
|
|
||||||
uint32_t bufferAllocCount_;
|
|
||||||
uint32_t bufferFreeCount_;
|
|
||||||
size_t peakTextureVRAM_;
|
|
||||||
size_t peakBufferVRAM_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/graphics/resources/buffer.h>
|
|
||||||
#include <glad/glad.h>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// OpenGL 缓冲区实现
|
|
||||||
// ============================================================================
|
|
||||||
class GLBuffer : public Buffer {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief 构造函数
|
|
||||||
*/
|
|
||||||
GLBuffer();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 析构函数
|
|
||||||
*/
|
|
||||||
~GLBuffer() override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化缓冲区
|
|
||||||
* @param desc 缓冲区描述
|
|
||||||
* @return 成功返回 true
|
|
||||||
*/
|
|
||||||
bool init(const BufferDesc& desc);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 关闭缓冲区,释放资源
|
|
||||||
*/
|
|
||||||
void shutdown();
|
|
||||||
|
|
||||||
// Buffer 接口实现
|
|
||||||
void bind() override;
|
|
||||||
void unbind() override;
|
|
||||||
void setData(const void* data, size_t size) override;
|
|
||||||
void updateData(const void* data, size_t offset, size_t size) override;
|
|
||||||
void* map() override;
|
|
||||||
void unmap() override;
|
|
||||||
size_t getSize() const override { return size_; }
|
|
||||||
BufferType getType() const override { return type_; }
|
|
||||||
BufferUsage getUsage() const override { return usage_; }
|
|
||||||
bool isValid() const override { return bufferID_ != 0; }
|
|
||||||
uintptr_t getNativeHandle() const override { return static_cast<uintptr_t>(bufferID_); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取 OpenGL 缓冲区 ID
|
|
||||||
* @return 缓冲区 ID
|
|
||||||
*/
|
|
||||||
GLuint getBufferID() const { return bufferID_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取 OpenGL 缓冲区目标类型
|
|
||||||
* @return 缓冲区目标类型
|
|
||||||
*/
|
|
||||||
GLenum getTarget() const { return target_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
GLuint bufferID_ = 0;
|
|
||||||
GLenum target_ = GL_ARRAY_BUFFER;
|
|
||||||
size_t size_ = 0;
|
|
||||||
BufferType type_ = BufferType::Vertex;
|
|
||||||
BufferUsage usage_ = BufferUsage::Static;
|
|
||||||
GLenum glUsage_ = GL_STATIC_DRAW;
|
|
||||||
bool mapped_ = false;
|
|
||||||
void* mappedPtr_ = nullptr;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 转换使用模式到 OpenGL 枚举
|
|
||||||
*/
|
|
||||||
static GLenum convertUsage(BufferUsage usage);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 转换缓冲区类型到 OpenGL 目标
|
|
||||||
*/
|
|
||||||
static GLenum convertType(BufferType type);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,138 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <glad/glad.h>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// OpenGL 版本信息
|
|
||||||
// ============================================================================
|
|
||||||
struct GLVersion {
|
|
||||||
int major = 0;
|
|
||||||
int minor = 0;
|
|
||||||
bool es = false; // 是否为 ES 版本
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// OpenGL 上下文管理类
|
|
||||||
// ============================================================================
|
|
||||||
class GLContext {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief 获取全局 GLContext 实例
|
|
||||||
*/
|
|
||||||
static GLContext &get();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化 OpenGL 上下文
|
|
||||||
* @return 成功返回 true
|
|
||||||
*/
|
|
||||||
bool init();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 关闭 OpenGL 上下文
|
|
||||||
*/
|
|
||||||
void shutdown();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查上下文是否有效
|
|
||||||
* @return 有效返回 true
|
|
||||||
*/
|
|
||||||
bool isValid() const { return initialized_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取 OpenGL 版本信息
|
|
||||||
*/
|
|
||||||
const GLVersion &getVersion() const { return version_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取 OpenGL 版本字符串
|
|
||||||
*/
|
|
||||||
std::string getVersionString() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取 GPU 厂商信息
|
|
||||||
*/
|
|
||||||
std::string getVendor() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取 GPU 渲染器信息
|
|
||||||
*/
|
|
||||||
std::string getRenderer() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查是否支持指定扩展
|
|
||||||
* @param extension 扩展名称
|
|
||||||
* @return 支持返回 true
|
|
||||||
*/
|
|
||||||
bool hasExtension(const std::string &extension) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取最大纹理尺寸
|
|
||||||
*/
|
|
||||||
int getMaxTextureSize() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取最大纹理单元数
|
|
||||||
*/
|
|
||||||
int getMaxTextureUnits() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取最大顶点属性数
|
|
||||||
*/
|
|
||||||
int getMaxVertexAttribs() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取最大 uniform 缓冲区绑定点数
|
|
||||||
*/
|
|
||||||
int getMaxUniformBufferBindings() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查是否为 OpenGL ES
|
|
||||||
*/
|
|
||||||
bool isGLES() const { return version_.es; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查是否支持 VAO
|
|
||||||
*/
|
|
||||||
bool hasVAO() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查是否支持 FBO
|
|
||||||
*/
|
|
||||||
bool hasFBO() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查是否支持 Shader
|
|
||||||
*/
|
|
||||||
bool hasShader() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
GLContext() = default;
|
|
||||||
~GLContext() = default;
|
|
||||||
|
|
||||||
GLContext(const GLContext &) = delete;
|
|
||||||
GLContext &operator=(const GLContext &) = delete;
|
|
||||||
|
|
||||||
bool initialized_ = false;
|
|
||||||
GLVersion version_;
|
|
||||||
|
|
||||||
// 缓存的限制值
|
|
||||||
mutable int maxTextureSize_ = -1;
|
|
||||||
mutable int maxTextureUnits_ = -1;
|
|
||||||
mutable int maxVertexAttribs_ = -1;
|
|
||||||
mutable int maxUniformBufferBindings_ = -1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 解析 OpenGL 版本
|
|
||||||
*/
|
|
||||||
void parseVersion();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 加载 OpenGL 扩展
|
|
||||||
*/
|
|
||||||
bool loadExtensions();
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
|
||||||
#include <extra2d/graphics/texture/font.h>
|
|
||||||
|
|
||||||
#include <stb/stb_rect_pack.h>
|
|
||||||
#include <stb/stb_truetype.h>
|
|
||||||
|
|
||||||
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// OpenGL 字体图集实现 (使用 STB 库)
|
|
||||||
// 使用 stb_rect_pack 进行动态矩形打包,支持动态缓存字形
|
|
||||||
// ============================================================================
|
|
||||||
class GLFontAtlas : public FontAtlas {
|
|
||||||
public:
|
|
||||||
GLFontAtlas(const std::string &filepath, int fontSize, bool useSDF = false);
|
|
||||||
~GLFontAtlas() override;
|
|
||||||
|
|
||||||
// FontAtlas 接口实现
|
|
||||||
const Glyph *getGlyph(char32_t codepoint) const override;
|
|
||||||
Texture *getTexture() const override { return texture_.get(); }
|
|
||||||
int getFontSize() const override { return fontSize_; }
|
|
||||||
float getAscent() const override { return ascent_; }
|
|
||||||
float getDescent() const override { return descent_; }
|
|
||||||
float getLineGap() const override { return lineGap_; }
|
|
||||||
float getLineHeight() const override { return lineHeight_; }
|
|
||||||
bool isSDF() const override { return useSDF_; }
|
|
||||||
Vec2 measureText(const std::string &text) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
// 字形数据内部结构
|
|
||||||
struct GlyphData {
|
|
||||||
float width;
|
|
||||||
float height;
|
|
||||||
float bearingX;
|
|
||||||
float bearingY;
|
|
||||||
float advance;
|
|
||||||
float u0, v0, u1, v1;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 图集配置 - 增大尺寸以支持更多字符
|
|
||||||
static constexpr int ATLAS_WIDTH = 1024;
|
|
||||||
static constexpr int ATLAS_HEIGHT = 1024;
|
|
||||||
static constexpr int PADDING = 2; // 字形之间的间距
|
|
||||||
|
|
||||||
bool useSDF_;
|
|
||||||
int fontSize_;
|
|
||||||
|
|
||||||
Ptr<GLTexture> texture_;
|
|
||||||
std::unordered_map<char32_t, GlyphData> glyphs_;
|
|
||||||
float lineHeight_;
|
|
||||||
float ascent_;
|
|
||||||
float descent_;
|
|
||||||
float lineGap_;
|
|
||||||
|
|
||||||
// 字体数据
|
|
||||||
std::vector<unsigned char> fontData_;
|
|
||||||
stbtt_fontinfo fontInfo_;
|
|
||||||
float scale_;
|
|
||||||
|
|
||||||
// stb_rect_pack 上下文 - 持久化以支持增量打包
|
|
||||||
mutable stbrp_context packContext_;
|
|
||||||
mutable std::vector<stbrp_node> packNodes_;
|
|
||||||
|
|
||||||
// 预分配缓冲区,避免每次动态分配
|
|
||||||
mutable std::vector<uint8_t> glyphBitmapCache_;
|
|
||||||
mutable std::vector<uint8_t> glyphRgbaCache_;
|
|
||||||
|
|
||||||
// 初始化字体
|
|
||||||
bool initFont(const std::string &filepath);
|
|
||||||
// 创建空白图集纹理
|
|
||||||
void createAtlas();
|
|
||||||
// 缓存字形到图集
|
|
||||||
void cacheGlyph(char32_t codepoint);
|
|
||||||
// 更新图集纹理区域
|
|
||||||
void updateAtlas(int x, int y, int width, int height,
|
|
||||||
const std::vector<uint8_t> &data);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,105 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/graphics/resources/framebuffer.h>
|
|
||||||
#include <glad/glad.h>
|
|
||||||
#include <array>
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// OpenGL 帧缓冲实现
|
|
||||||
// ============================================================================
|
|
||||||
class GLFramebuffer : public Framebuffer {
|
|
||||||
public:
|
|
||||||
// 最大颜色附件数
|
|
||||||
static constexpr int MAX_COLOR_ATTACHMENTS = 8;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 构造函数
|
|
||||||
*/
|
|
||||||
GLFramebuffer();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 析构函数
|
|
||||||
*/
|
|
||||||
~GLFramebuffer() override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化帧缓冲
|
|
||||||
* @param desc 帧缓冲描述
|
|
||||||
* @return 成功返回 true
|
|
||||||
*/
|
|
||||||
bool init(const FramebufferDesc& desc);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 关闭帧缓冲,释放资源
|
|
||||||
*/
|
|
||||||
void shutdown();
|
|
||||||
|
|
||||||
// Framebuffer 接口实现
|
|
||||||
void bind() override;
|
|
||||||
void unbind() override;
|
|
||||||
void attachColorTexture(Ptr<Texture> texture, int attachment = 0) override;
|
|
||||||
void attachDepthTexture(Ptr<Texture> texture) override;
|
|
||||||
void attachDepthStencilTexture(Ptr<Texture> texture) override;
|
|
||||||
bool isComplete() override;
|
|
||||||
Ptr<Texture> getColorTexture(int attachment = 0) const override;
|
|
||||||
Ptr<Texture> getDepthTexture() const override;
|
|
||||||
int getWidth() const override { return width_; }
|
|
||||||
int getHeight() const override { return height_; }
|
|
||||||
Size getSize() const override { return Size(static_cast<float>(width_), static_cast<float>(height_)); }
|
|
||||||
bool isValid() const override { return fboID_ != 0; }
|
|
||||||
uintptr_t getNativeHandle() const override { return static_cast<uintptr_t>(fboID_); }
|
|
||||||
void clear(const Color& color, bool clearColor = true,
|
|
||||||
bool clearDepth = true, bool clearStencil = false) override;
|
|
||||||
void setViewport(int x, int y, int width, int height) override;
|
|
||||||
bool readPixels(int x, int y, int width, int height,
|
|
||||||
std::vector<uint8_t>& outData) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取 OpenGL FBO ID
|
|
||||||
* @return FBO ID
|
|
||||||
*/
|
|
||||||
GLuint getFboID() const { return fboID_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建带内置纹理的帧缓冲(便捷方法)
|
|
||||||
* @param width 宽度
|
|
||||||
* @param height 高度
|
|
||||||
* @param colorFormat 颜色格式
|
|
||||||
* @param depthFormat 深度格式(可选)
|
|
||||||
* @return 成功返回 true
|
|
||||||
*/
|
|
||||||
bool createWithTextures(int width, int height,
|
|
||||||
PixelFormat colorFormat = PixelFormat::RGBA8,
|
|
||||||
PixelFormat depthFormat = PixelFormat::Depth24);
|
|
||||||
|
|
||||||
private:
|
|
||||||
GLuint fboID_ = 0;
|
|
||||||
int width_ = 0;
|
|
||||||
int height_ = 0;
|
|
||||||
int numColorAttachments_ = 1;
|
|
||||||
bool hasDepth_ = false;
|
|
||||||
bool hasStencil_ = false;
|
|
||||||
|
|
||||||
// 附件纹理
|
|
||||||
std::array<Ptr<Texture>, MAX_COLOR_ATTACHMENTS> colorTextures_;
|
|
||||||
Ptr<Texture> depthTexture_;
|
|
||||||
Ptr<Texture> depthStencilTexture_;
|
|
||||||
|
|
||||||
// 是否为内置纹理(需要自动清理)
|
|
||||||
bool hasInternalTextures_ = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查并更新完整状态
|
|
||||||
*/
|
|
||||||
bool checkStatus();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取 OpenGL 附件枚举
|
|
||||||
*/
|
|
||||||
static GLenum getColorAttachment(int index);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,131 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/graphics/resources/pipeline.h>
|
|
||||||
#include <glad/glad.h>
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// OpenGL 管线状态实现
|
|
||||||
// ============================================================================
|
|
||||||
class GLPipeline : public Pipeline {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief 构造函数
|
|
||||||
*/
|
|
||||||
GLPipeline();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 析构函数
|
|
||||||
*/
|
|
||||||
~GLPipeline() override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化管线
|
|
||||||
* @param desc 管线描述
|
|
||||||
* @return 成功返回 true
|
|
||||||
*/
|
|
||||||
bool init(const PipelineDesc& desc);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 关闭管线,释放资源
|
|
||||||
*/
|
|
||||||
void shutdown();
|
|
||||||
|
|
||||||
// Pipeline 接口实现
|
|
||||||
void bind() override;
|
|
||||||
void unbind() override;
|
|
||||||
void setBlendMode(BlendMode mode) override;
|
|
||||||
BlendMode getBlendMode() const override { return blendMode_; }
|
|
||||||
void setDepthTest(bool enabled) override;
|
|
||||||
void setDepthWrite(bool enabled) override;
|
|
||||||
void setDepthFunc(DepthFunc func) override;
|
|
||||||
void setCullMode(CullMode mode) override;
|
|
||||||
bool isValid() const override { return initialized_; }
|
|
||||||
uintptr_t getNativeHandle() const override { return 0; } // OpenGL 管线没有单一句柄
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置视口
|
|
||||||
* @param x 视口左下角X坐标
|
|
||||||
* @param y 视口左下角Y坐标
|
|
||||||
* @param width 视口宽度
|
|
||||||
* @param height 视口高度
|
|
||||||
*/
|
|
||||||
void setViewport(int x, int y, int width, int height);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前视口
|
|
||||||
* @param x 输出X坐标
|
|
||||||
* @param y 输出Y坐标
|
|
||||||
* @param width 输出宽度
|
|
||||||
* @param height 输出高度
|
|
||||||
*/
|
|
||||||
void getViewport(int& x, int& y, int& width, int& height) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 应用所有状态(用于初始化或重置)
|
|
||||||
*/
|
|
||||||
void applyAllStates();
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool initialized_ = false;
|
|
||||||
|
|
||||||
// 当前状态
|
|
||||||
BlendMode blendMode_ = BlendMode::Alpha;
|
|
||||||
bool blendEnabled_ = true;
|
|
||||||
bool depthTest_ = false;
|
|
||||||
bool depthWrite_ = false;
|
|
||||||
DepthFunc depthFunc_ = DepthFunc::Less;
|
|
||||||
CullMode cullMode_ = CullMode::None;
|
|
||||||
|
|
||||||
// 视口
|
|
||||||
int viewportX_ = 0;
|
|
||||||
int viewportY_ = 0;
|
|
||||||
int viewportWidth_ = 0;
|
|
||||||
int viewportHeight_ = 0;
|
|
||||||
|
|
||||||
// 状态缓存(避免冗余 GL 调用)
|
|
||||||
BlendMode cachedBlendMode_ = BlendMode::None;
|
|
||||||
bool cachedBlendEnabled_ = false;
|
|
||||||
bool cachedDepthTest_ = false;
|
|
||||||
bool cachedDepthWrite_ = false;
|
|
||||||
DepthFunc cachedDepthFunc_ = DepthFunc::Less;
|
|
||||||
CullMode cachedCullMode_ = CullMode::None;
|
|
||||||
int cachedViewportX_ = -1;
|
|
||||||
int cachedViewportY_ = -1;
|
|
||||||
int cachedViewportWidth_ = -1;
|
|
||||||
int cachedViewportHeight_ = -1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 应用混合状态
|
|
||||||
*/
|
|
||||||
void applyBlendState();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 应用深度状态
|
|
||||||
*/
|
|
||||||
void applyDepthState();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 应用裁剪状态
|
|
||||||
*/
|
|
||||||
void applyCullState();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 转换混合模式到 OpenGL 枚举
|
|
||||||
*/
|
|
||||||
static void getBlendFactors(BlendMode mode, GLenum& srcFactor, GLenum& dstFactor);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 转换深度函数到 OpenGL 枚举
|
|
||||||
*/
|
|
||||||
static GLenum convertDepthFunc(DepthFunc func);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 转换裁剪模式到 OpenGL 枚举
|
|
||||||
*/
|
|
||||||
static GLenum convertCullMode(CullMode mode);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,158 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
|
||||||
#include <extra2d/core/math_types.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_buffer.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_framebuffer.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_pipeline.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_sprite_batch.h>
|
|
||||||
#include <extra2d/graphics/resources/pipeline.h>
|
|
||||||
#include <extra2d/graphics/shader/shader_interface.h>
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <glad/glad.h>
|
|
||||||
#include <glm/mat4x4.hpp>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
class GLFWWindow;
|
|
||||||
class GLContext;
|
|
||||||
class GLFramebuffer;
|
|
||||||
class FontAtlas;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 渲染统计信息
|
|
||||||
*/
|
|
||||||
struct RenderStats {
|
|
||||||
uint32_t drawCalls = 0;
|
|
||||||
uint32_t triangleCount = 0;
|
|
||||||
uint32_t textureBinds = 0;
|
|
||||||
uint32_t shaderBinds = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief OpenGL 渲染器实现
|
|
||||||
*/
|
|
||||||
class Renderer {
|
|
||||||
public:
|
|
||||||
Renderer();
|
|
||||||
~Renderer();
|
|
||||||
|
|
||||||
bool init(GLFWWindow *window);
|
|
||||||
void shutdown();
|
|
||||||
|
|
||||||
void beginFrame(const Color &clearColor);
|
|
||||||
void endFrame();
|
|
||||||
void setViewport(int x, int y, int width, int height);
|
|
||||||
void setVSync(bool enabled);
|
|
||||||
|
|
||||||
void setBlendMode(BlendMode mode);
|
|
||||||
void setViewProjection(const glm::mat4 &matrix);
|
|
||||||
|
|
||||||
void pushTransform(const glm::mat4 &transform);
|
|
||||||
void popTransform();
|
|
||||||
glm::mat4 getCurrentTransform() const;
|
|
||||||
|
|
||||||
Ptr<Texture> createTexture(int width, int height, const uint8_t *pixels,
|
|
||||||
int channels);
|
|
||||||
Ptr<Texture> loadTexture(const std::string &filepath);
|
|
||||||
|
|
||||||
void beginSpriteBatch();
|
|
||||||
void drawSprite(const Texture &texture, const Rect &destRect,
|
|
||||||
const Rect &srcRect, const Color &tint, float rotation,
|
|
||||||
const Vec2 &anchor);
|
|
||||||
void drawSprite(const Texture &texture, const Vec2 &position,
|
|
||||||
const Color &tint);
|
|
||||||
void endSpriteBatch();
|
|
||||||
void flush();
|
|
||||||
|
|
||||||
void drawLine(const Vec2 &start, const Vec2 &end, const Color &color,
|
|
||||||
float width = 1.0f);
|
|
||||||
void drawRect(const Rect &rect, const Color &color, float width = 1.0f);
|
|
||||||
void fillRect(const Rect &rect, const Color &color);
|
|
||||||
void drawCircle(const Vec2 ¢er, float radius, const Color &color,
|
|
||||||
int segments = 32, float width = 1.0f);
|
|
||||||
void fillCircle(const Vec2 ¢er, float radius, const Color &color,
|
|
||||||
int segments = 32);
|
|
||||||
void drawTriangle(const Vec2 &p1, const Vec2 &p2, const Vec2 &p3,
|
|
||||||
const Color &color, float width = 1.0f);
|
|
||||||
void fillTriangle(const Vec2 &p1, const Vec2 &p2, const Vec2 &p3,
|
|
||||||
const Color &color);
|
|
||||||
void drawPolygon(const std::vector<Vec2> &points, const Color &color,
|
|
||||||
float width = 1.0f);
|
|
||||||
void fillPolygon(const std::vector<Vec2> &points, const Color &color);
|
|
||||||
|
|
||||||
Ptr<FontAtlas> createFontAtlas(const std::string &filepath, int fontSize,
|
|
||||||
bool useSDF = false);
|
|
||||||
void drawText(const FontAtlas &font, const std::string &text,
|
|
||||||
const Vec2 &position, const Color &color);
|
|
||||||
void drawText(const FontAtlas &font, const std::string &text, float x,
|
|
||||||
float y, const Color &color);
|
|
||||||
|
|
||||||
RenderStats getStats() const { return stats_; }
|
|
||||||
void resetStats();
|
|
||||||
|
|
||||||
Ptr<GLFramebuffer> createFramebuffer(const FramebufferDesc &desc);
|
|
||||||
void bindFramebuffer(GLFramebuffer *framebuffer);
|
|
||||||
void unbindFramebuffer();
|
|
||||||
Ptr<GLFramebuffer> getDefaultFramebuffer() const;
|
|
||||||
void clearFramebuffer(const Color &color, bool clearColor = true,
|
|
||||||
bool clearDepth = true, bool clearStencil = false);
|
|
||||||
|
|
||||||
private:
|
|
||||||
static constexpr size_t MAX_CIRCLE_SEGMENTS = 128;
|
|
||||||
static constexpr size_t MAX_SHAPE_VERTICES = 8192;
|
|
||||||
static constexpr size_t MAX_LINE_VERTICES = 16384;
|
|
||||||
|
|
||||||
struct ShapeVertex {
|
|
||||||
float x, y;
|
|
||||||
float r, g, b, a;
|
|
||||||
};
|
|
||||||
|
|
||||||
GLFWWindow *window_;
|
|
||||||
GLSpriteBatch spriteBatch_;
|
|
||||||
Ptr<IShader> shapeShader_;
|
|
||||||
Ptr<IShader> sdfFontShader_;
|
|
||||||
|
|
||||||
GLuint shapeVao_;
|
|
||||||
GLBuffer shapeBuffer_;
|
|
||||||
GLuint lineVao_;
|
|
||||||
GLBuffer lineBuffer_;
|
|
||||||
|
|
||||||
glm::mat4 viewProjection_;
|
|
||||||
std::vector<glm::mat4> transformStack_;
|
|
||||||
RenderStats stats_;
|
|
||||||
bool vsync_;
|
|
||||||
|
|
||||||
std::array<ShapeVertex, MAX_SHAPE_VERTICES> shapeVertexCache_;
|
|
||||||
size_t shapeVertexCount_ = 0;
|
|
||||||
GLenum currentShapeMode_ = GL_TRIANGLES;
|
|
||||||
|
|
||||||
std::array<ShapeVertex, MAX_LINE_VERTICES> lineVertexCache_;
|
|
||||||
size_t lineVertexCount_ = 0;
|
|
||||||
float currentLineWidth_ = 1.0f;
|
|
||||||
|
|
||||||
GLPipeline pipeline_;
|
|
||||||
|
|
||||||
bool batchActive_ = false;
|
|
||||||
bool autoBatchEnabled_ = true;
|
|
||||||
const Texture *currentBatchTexture_ = nullptr;
|
|
||||||
std::vector<SpriteData> pendingSprites_;
|
|
||||||
static constexpr size_t MAX_BATCH_SPRITES = 1000;
|
|
||||||
|
|
||||||
mutable Ptr<GLFramebuffer> defaultFramebuffer_;
|
|
||||||
GLFramebuffer *currentFramebuffer_ = nullptr;
|
|
||||||
|
|
||||||
void initShapeRendering();
|
|
||||||
void ensureBatchActive();
|
|
||||||
void submitPendingSprites();
|
|
||||||
void flushShapeBatch();
|
|
||||||
void flushLineBatch();
|
|
||||||
void addShapeVertex(float x, float y, const Color &color);
|
|
||||||
void addLineVertex(float x, float y, const Color &color);
|
|
||||||
void submitShapeBatch(GLenum mode);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,196 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
|
||||||
#include <extra2d/graphics/shader/shader_interface.h>
|
|
||||||
#include <glad/glad.h>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
class GLShader : public IShader {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief 构造函数
|
|
||||||
*/
|
|
||||||
GLShader();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 析构函数
|
|
||||||
*/
|
|
||||||
~GLShader() override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绑定Shader程序
|
|
||||||
*/
|
|
||||||
void bind() const override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 解绑Shader程序
|
|
||||||
*/
|
|
||||||
void unbind() const override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置布尔类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 布尔值
|
|
||||||
*/
|
|
||||||
void setBool(const std::string& name, bool value) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置整数类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 整数值
|
|
||||||
*/
|
|
||||||
void setInt(const std::string& name, int value) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置浮点类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 浮点值
|
|
||||||
*/
|
|
||||||
void setFloat(const std::string& name, float value) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置二维向量类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 二维向量值
|
|
||||||
*/
|
|
||||||
void setVec2(const std::string& name, const glm::vec2& value) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置三维向量类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 三维向量值
|
|
||||||
*/
|
|
||||||
void setVec3(const std::string& name, const glm::vec3& value) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置四维向量类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 四维向量值
|
|
||||||
*/
|
|
||||||
void setVec4(const std::string& name, const glm::vec4& value) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置4x4矩阵类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 4x4矩阵值
|
|
||||||
*/
|
|
||||||
void setMat4(const std::string& name, const glm::mat4& value) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置颜色类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param color 颜色值
|
|
||||||
*/
|
|
||||||
void setColor(const std::string& name, const Color& color) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查Shader是否有效
|
|
||||||
* @return 有效返回true,否则返回false
|
|
||||||
*/
|
|
||||||
bool isValid() const override { return programID_ != 0; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取原生句柄(OpenGL程序ID)
|
|
||||||
* @return OpenGL程序ID
|
|
||||||
*/
|
|
||||||
uint32_t getNativeHandle() const override { return programID_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取Shader名称
|
|
||||||
* @return Shader名称
|
|
||||||
*/
|
|
||||||
const std::string& getName() const override { return name_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置Shader名称
|
|
||||||
* @param name Shader名称
|
|
||||||
*/
|
|
||||||
void setName(const std::string& name) override { name_ = name; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从源码编译Shader
|
|
||||||
* @param vertexSource 顶点着色器源码
|
|
||||||
* @param fragmentSource 片段着色器源码
|
|
||||||
* @return 编译成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool compileFromSource(const char* vertexSource, const char* fragmentSource);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从二进制数据创建Shader
|
|
||||||
* @param binary 二进制数据
|
|
||||||
* @return 创建成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool compileFromBinary(const std::vector<uint8_t>& binary);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取Shader二进制数据
|
|
||||||
* @param outBinary 输出的二进制数据
|
|
||||||
* @return 成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool getBinary(std::vector<uint8_t>& outBinary);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取OpenGL程序ID
|
|
||||||
* @return OpenGL程序ID
|
|
||||||
*/
|
|
||||||
GLuint getProgramID() const { return programID_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
GLuint programID_ = 0;
|
|
||||||
std::string name_;
|
|
||||||
std::unordered_map<std::string, GLint> uniformCache_;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 编译单个着色器
|
|
||||||
* @param type 着色器类型
|
|
||||||
* @param source 着色器源码
|
|
||||||
* @return 着色器ID,失败返回0
|
|
||||||
*/
|
|
||||||
GLuint compileShader(GLenum type, const char* source);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取uniform位置
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @return uniform位置
|
|
||||||
*/
|
|
||||||
GLint getUniformLocation(const std::string& name);
|
|
||||||
};
|
|
||||||
|
|
||||||
class GLShaderFactory : public IShaderFactory {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief 从源码创建Shader
|
|
||||||
* @param name Shader名称
|
|
||||||
* @param vertSource 顶点着色器源码
|
|
||||||
* @param fragSource 片段着色器源码
|
|
||||||
* @return 创建的Shader实例
|
|
||||||
*/
|
|
||||||
Ptr<IShader> createFromSource(
|
|
||||||
const std::string& name,
|
|
||||||
const std::string& vertSource,
|
|
||||||
const std::string& fragSource) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从缓存二进制创建Shader
|
|
||||||
* @param name Shader名称
|
|
||||||
* @param binary 编译后的二进制数据
|
|
||||||
* @return 创建的Shader实例
|
|
||||||
*/
|
|
||||||
Ptr<IShader> createFromBinary(
|
|
||||||
const std::string& name,
|
|
||||||
const std::vector<uint8_t>& binary) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取Shader的二进制数据
|
|
||||||
* @param shader Shader实例
|
|
||||||
* @param outBinary 输出的二进制数据
|
|
||||||
* @return 成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool getShaderBinary(const IShader& shader,
|
|
||||||
std::vector<uint8_t>& outBinary) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/graphics/batch/sprite_batch.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_buffer.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
|
||||||
#include <extra2d/graphics/shader/shader_interface.h>
|
|
||||||
#include <extra2d/graphics/shader/shader_manager.h>
|
|
||||||
|
|
||||||
|
|
||||||
#include <glad/glad.h>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// OpenGL 精灵批处理渲染器
|
|
||||||
// 使用 batch/sprite_batch 作为后端无关的批处理层
|
|
||||||
// ============================================================================
|
|
||||||
class GLSpriteBatch {
|
|
||||||
public:
|
|
||||||
GLSpriteBatch();
|
|
||||||
~GLSpriteBatch();
|
|
||||||
|
|
||||||
// 初始化/关闭
|
|
||||||
bool init();
|
|
||||||
void shutdown();
|
|
||||||
|
|
||||||
// 批处理生命周期
|
|
||||||
void begin(const glm::mat4 &viewProjection);
|
|
||||||
void end();
|
|
||||||
|
|
||||||
// 使用指定着色器开始批处理
|
|
||||||
void begin(const glm::mat4 &viewProjection, Ptr<IShader> shader);
|
|
||||||
|
|
||||||
// 绘制单个精灵
|
|
||||||
void draw(const Texture &texture, const SpriteData &data);
|
|
||||||
|
|
||||||
// 批量绘制(用于文本渲染优化)
|
|
||||||
void drawBatch(const Texture &texture,
|
|
||||||
const std::vector<SpriteData> &sprites);
|
|
||||||
|
|
||||||
// 获取绘制调用次数
|
|
||||||
uint32_t getDrawCallCount() const { return drawCallCount_; }
|
|
||||||
|
|
||||||
// 设置自定义着色器(用于SDF字体等特殊渲染)
|
|
||||||
void setShader(Ptr<IShader> shader);
|
|
||||||
|
|
||||||
// 获取当前着色器
|
|
||||||
Ptr<IShader> getShader() const { return shader_; }
|
|
||||||
|
|
||||||
// 设置额外的uniform值(用于SDF字体等特殊渲染)
|
|
||||||
void setExtraUniforms(const UniformValueMap &uniforms) {
|
|
||||||
extraUniforms_ = uniforms;
|
|
||||||
}
|
|
||||||
void clearExtraUniforms() { extraUniforms_.clear(); }
|
|
||||||
|
|
||||||
private:
|
|
||||||
// OpenGL 对象
|
|
||||||
GLuint vao_;
|
|
||||||
GLBuffer vbo_; // 顶点缓冲区(动态)
|
|
||||||
GLBuffer ebo_; // 索引缓冲区(静态)
|
|
||||||
|
|
||||||
// 后端无关的批处理层
|
|
||||||
SpriteBatch batch_;
|
|
||||||
|
|
||||||
// 批次管理
|
|
||||||
struct Batch {
|
|
||||||
const GLTexture *texture;
|
|
||||||
size_t startVertex;
|
|
||||||
size_t vertexCount;
|
|
||||||
};
|
|
||||||
std::vector<Batch> batches_;
|
|
||||||
const GLTexture *currentTexture_;
|
|
||||||
|
|
||||||
// 着色器和矩阵
|
|
||||||
Ptr<IShader> shader_;
|
|
||||||
uint32_t drawCallCount_;
|
|
||||||
glm::mat4 viewProjection_;
|
|
||||||
|
|
||||||
// 额外的uniform值(用于SDF字体等特殊渲染)
|
|
||||||
UniformValueMap extraUniforms_;
|
|
||||||
|
|
||||||
// 内部方法
|
|
||||||
void flush();
|
|
||||||
void submitBatch();
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/graphics/texture/texture.h>
|
|
||||||
#include <extra2d/graphics/texture/alpha_mask.h>
|
|
||||||
|
|
||||||
#include <glad/glad.h>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// OpenGL 纹理实现
|
|
||||||
// ============================================================================
|
|
||||||
class GLTexture : public Texture {
|
|
||||||
public:
|
|
||||||
GLTexture(int width, int height, const uint8_t* pixels, int channels);
|
|
||||||
GLTexture(const std::string& filepath);
|
|
||||||
~GLTexture();
|
|
||||||
|
|
||||||
// Texture 接口实现
|
|
||||||
int getWidth() const override { return width_; }
|
|
||||||
int getHeight() const override { return height_; }
|
|
||||||
Size getSize() const override { return Size(static_cast<float>(width_), static_cast<float>(height_)); }
|
|
||||||
int getChannels() const override { return channels_; }
|
|
||||||
PixelFormat getFormat() const override;
|
|
||||||
void* getNativeHandle() const override { return reinterpret_cast<void*>(static_cast<uintptr_t>(textureID_)); }
|
|
||||||
bool isValid() const override { return textureID_ != 0; }
|
|
||||||
void setFilter(bool linear) override;
|
|
||||||
void setWrap(bool repeat) override;
|
|
||||||
|
|
||||||
// 从参数创建纹理的工厂方法
|
|
||||||
static Ptr<Texture> create(int width, int height, PixelFormat format);
|
|
||||||
|
|
||||||
// 加载压缩纹理(KTX/DDS 格式)
|
|
||||||
bool loadCompressed(const std::string& filepath);
|
|
||||||
|
|
||||||
// OpenGL 特定
|
|
||||||
GLuint getTextureID() const { return textureID_; }
|
|
||||||
void bind(unsigned int slot = 0) const;
|
|
||||||
void unbind() const;
|
|
||||||
|
|
||||||
// 获取纹理数据大小(字节),用于 VRAM 跟踪
|
|
||||||
size_t getDataSize() const { return dataSize_; }
|
|
||||||
|
|
||||||
// Alpha 遮罩
|
|
||||||
bool hasAlphaMask() const { return alphaMask_ != nullptr && alphaMask_->isValid(); }
|
|
||||||
const AlphaMask* getAlphaMask() const { return alphaMask_.get(); }
|
|
||||||
void generateAlphaMask(); // 从当前纹理数据生成遮罩
|
|
||||||
|
|
||||||
private:
|
|
||||||
GLuint textureID_;
|
|
||||||
int width_;
|
|
||||||
int height_;
|
|
||||||
int channels_;
|
|
||||||
PixelFormat format_;
|
|
||||||
size_t dataSize_;
|
|
||||||
|
|
||||||
// 原始像素数据(用于生成遮罩)
|
|
||||||
std::vector<uint8_t> pixelData_;
|
|
||||||
std::unique_ptr<AlphaMask> alphaMask_;
|
|
||||||
|
|
||||||
void createTexture(const uint8_t* pixels);
|
|
||||||
|
|
||||||
// KTX 文件加载
|
|
||||||
bool loadKTX(const std::string& filepath);
|
|
||||||
// DDS 文件加载
|
|
||||||
bool loadDDS(const std::string& filepath);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,111 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 缓冲区类型枚举
|
|
||||||
// ============================================================================
|
|
||||||
enum class BufferType {
|
|
||||||
Vertex, // 顶点缓冲
|
|
||||||
Index, // 索引缓冲
|
|
||||||
Uniform // 统一缓冲
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 缓冲区使用模式枚举
|
|
||||||
// ============================================================================
|
|
||||||
enum class BufferUsage {
|
|
||||||
Static, // 静态数据,很少更新
|
|
||||||
Dynamic, // 动态数据,频繁更新
|
|
||||||
Stream // 流式数据,每帧更新
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 缓冲区描述结构
|
|
||||||
// ============================================================================
|
|
||||||
struct BufferDesc {
|
|
||||||
BufferType type = BufferType::Vertex;
|
|
||||||
BufferUsage usage = BufferUsage::Static;
|
|
||||||
size_t size = 0; // 缓冲区大小(字节)
|
|
||||||
const void* initialData = nullptr; // 初始数据
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 缓冲区抽象接口 - 渲染后端无关
|
|
||||||
// ============================================================================
|
|
||||||
class Buffer {
|
|
||||||
public:
|
|
||||||
virtual ~Buffer() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绑定缓冲区
|
|
||||||
*/
|
|
||||||
virtual void bind() = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 解绑缓冲区
|
|
||||||
*/
|
|
||||||
virtual void unbind() = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置缓冲区数据(完全替换)
|
|
||||||
* @param data 数据指针
|
|
||||||
* @param size 数据大小(字节)
|
|
||||||
*/
|
|
||||||
virtual void setData(const void* data, size_t size) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 更新缓冲区部分数据
|
|
||||||
* @param data 数据指针
|
|
||||||
* @param offset 偏移量(字节)
|
|
||||||
* @param size 数据大小(字节)
|
|
||||||
*/
|
|
||||||
virtual void updateData(const void* data, size_t offset, size_t size) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 映射缓冲区到内存(用于直接写入)
|
|
||||||
* @return 映射后的内存指针,失败返回 nullptr
|
|
||||||
*/
|
|
||||||
virtual void* map() = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 解除缓冲区映射
|
|
||||||
*/
|
|
||||||
virtual void unmap() = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取缓冲区大小
|
|
||||||
* @return 缓冲区大小(字节)
|
|
||||||
*/
|
|
||||||
virtual size_t getSize() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取缓冲区类型
|
|
||||||
* @return 缓冲区类型
|
|
||||||
*/
|
|
||||||
virtual BufferType getType() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取缓冲区使用模式
|
|
||||||
* @return 使用模式
|
|
||||||
*/
|
|
||||||
virtual BufferUsage getUsage() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查缓冲区是否有效
|
|
||||||
* @return 有效返回 true
|
|
||||||
*/
|
|
||||||
virtual bool isValid() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取原生句柄(后端特定)
|
|
||||||
* @return 原生句柄值
|
|
||||||
*/
|
|
||||||
virtual uintptr_t getNativeHandle() const = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,131 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
|
||||||
#include <extra2d/core/math_types.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/graphics/texture/texture.h>
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 字形信息结构
|
|
||||||
// ============================================================================
|
|
||||||
struct Glyph {
|
|
||||||
float width = 0; // 字形宽度
|
|
||||||
float height = 0; // 字形高度
|
|
||||||
float bearingX = 0; // 水平偏移
|
|
||||||
float bearingY = 0; // 垂直偏移(从基线到字形顶部)
|
|
||||||
float advance = 0; // 水平步进
|
|
||||||
float u0 = 0, v0 = 0; // 纹理坐标左下角
|
|
||||||
float u1 = 0, v1 = 0; // 纹理坐标右上角
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 字体图集描述结构
|
|
||||||
// ============================================================================
|
|
||||||
struct FontAtlasDesc {
|
|
||||||
std::string filepath; // 字体文件路径
|
|
||||||
int fontSize = 16; // 字体大小
|
|
||||||
bool useSDF = false; // 是否使用SDF渲染
|
|
||||||
int atlasSize = 512; // 图集大小
|
|
||||||
int padding = 2; // 字形间距
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 字体图集抽象接口 - 渲染后端无关
|
|
||||||
// ============================================================================
|
|
||||||
class FontAtlas {
|
|
||||||
public:
|
|
||||||
virtual ~FontAtlas() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化字体图集
|
|
||||||
* @param desc 字体图集描述
|
|
||||||
* @return 成功返回 true
|
|
||||||
*/
|
|
||||||
virtual bool init(const FontAtlasDesc& desc) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 关闭字体图集,释放资源
|
|
||||||
*/
|
|
||||||
virtual void shutdown() = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取字形信息
|
|
||||||
* @param codepoint Unicode 码点
|
|
||||||
* @return 字形信息指针,未找到返回 nullptr
|
|
||||||
*/
|
|
||||||
virtual const Glyph* getGlyph(char32_t codepoint) const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取纹理
|
|
||||||
* @return 纹理对象
|
|
||||||
*/
|
|
||||||
virtual Ptr<Texture> getTexture() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取字体大小
|
|
||||||
* @return 字体大小
|
|
||||||
*/
|
|
||||||
virtual int getFontSize() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取行高
|
|
||||||
* @return 行高
|
|
||||||
*/
|
|
||||||
virtual float getLineHeight() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取基线到顶部的距离
|
|
||||||
* @return 上升高度
|
|
||||||
*/
|
|
||||||
virtual float getAscent() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取基线到底部的距离
|
|
||||||
* @return 下降高度
|
|
||||||
*/
|
|
||||||
virtual float getDescent() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 计算文本宽度
|
|
||||||
* @param text 文本内容
|
|
||||||
* @return 文本宽度
|
|
||||||
*/
|
|
||||||
virtual float measureText(const std::string& text) const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 计算文本尺寸
|
|
||||||
* @param text 文本内容
|
|
||||||
* @return 文本尺寸
|
|
||||||
*/
|
|
||||||
virtual Size measureTextSize(const std::string& text) const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 是否使用SDF渲染
|
|
||||||
* @return 使用SDF返回 true
|
|
||||||
*/
|
|
||||||
virtual bool isSDF() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查字体图集是否有效
|
|
||||||
* @return 有效返回 true
|
|
||||||
*/
|
|
||||||
virtual bool isValid() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 预加载字符到图集
|
|
||||||
* @param text 需要预加载的文本
|
|
||||||
* @return 成功加载的字符数
|
|
||||||
*/
|
|
||||||
virtual int preloadGlyphs(const std::string& text) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 清空已加载的字形缓存
|
|
||||||
*/
|
|
||||||
virtual void clearCache() = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,140 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/graphics/texture/texture.h>
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 帧缓冲描述结构
|
|
||||||
// ============================================================================
|
|
||||||
struct FramebufferDesc {
|
|
||||||
int width = 0; // 帧缓冲宽度
|
|
||||||
int height = 0; // 帧缓冲高度
|
|
||||||
int colorAttachments = 1; // 颜色附件数量
|
|
||||||
bool hasDepth = false; // 是否有深度附件
|
|
||||||
bool hasStencil = false; // 是否有模板附件
|
|
||||||
bool multisample = false; // 是否多重采样
|
|
||||||
int samples = 4; // 采样数(多重采样时有效)
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 帧缓冲抽象接口 - 渲染后端无关
|
|
||||||
// ============================================================================
|
|
||||||
class Framebuffer {
|
|
||||||
public:
|
|
||||||
virtual ~Framebuffer() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绑定帧缓冲(作为渲染目标)
|
|
||||||
*/
|
|
||||||
virtual void bind() = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 解绑帧缓冲(恢复到默认帧缓冲)
|
|
||||||
*/
|
|
||||||
virtual void unbind() = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 附加颜色纹理
|
|
||||||
* @param texture 纹理对象
|
|
||||||
* @param attachment 附件索引(0-7)
|
|
||||||
*/
|
|
||||||
virtual void attachColorTexture(Ptr<Texture> texture, int attachment = 0) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 附加深度纹理
|
|
||||||
* @param texture 纹理对象
|
|
||||||
*/
|
|
||||||
virtual void attachDepthTexture(Ptr<Texture> texture) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 附加深度模板纹理
|
|
||||||
* @param texture 纹理对象
|
|
||||||
*/
|
|
||||||
virtual void attachDepthStencilTexture(Ptr<Texture> texture) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查帧缓冲是否完整
|
|
||||||
* @return 完整返回 true
|
|
||||||
*/
|
|
||||||
virtual bool isComplete() = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取颜色附件纹理
|
|
||||||
* @param attachment 附件索引
|
|
||||||
* @return 纹理对象
|
|
||||||
*/
|
|
||||||
virtual Ptr<Texture> getColorTexture(int attachment = 0) const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取深度附件纹理
|
|
||||||
* @return 纹理对象
|
|
||||||
*/
|
|
||||||
virtual Ptr<Texture> getDepthTexture() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取帧缓冲宽度
|
|
||||||
* @return 宽度
|
|
||||||
*/
|
|
||||||
virtual int getWidth() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取帧缓冲高度
|
|
||||||
* @return 高度
|
|
||||||
*/
|
|
||||||
virtual int getHeight() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取尺寸
|
|
||||||
* @return 尺寸
|
|
||||||
*/
|
|
||||||
virtual Size getSize() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查帧缓冲是否有效
|
|
||||||
* @return 有效返回 true
|
|
||||||
*/
|
|
||||||
virtual bool isValid() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取原生句柄(后端特定)
|
|
||||||
* @return 原生句柄值
|
|
||||||
*/
|
|
||||||
virtual uintptr_t getNativeHandle() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 清除帧缓冲
|
|
||||||
* @param color 清除颜色
|
|
||||||
* @param clearColor 是否清除颜色缓冲
|
|
||||||
* @param clearDepth 是否清除深度缓冲
|
|
||||||
* @param clearStencil 是否清除模板缓冲
|
|
||||||
*/
|
|
||||||
virtual void clear(const Color& color, bool clearColor = true,
|
|
||||||
bool clearDepth = true, bool clearStencil = false) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置视口
|
|
||||||
* @param x 视口左下角X坐标
|
|
||||||
* @param y 视口左下角Y坐标
|
|
||||||
* @param width 视口宽度
|
|
||||||
* @param height 视口高度
|
|
||||||
*/
|
|
||||||
virtual void setViewport(int x, int y, int width, int height) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 读取像素数据
|
|
||||||
* @param x 起始X坐标
|
|
||||||
* @param y 起始Y坐标
|
|
||||||
* @param width 宽度
|
|
||||||
* @param height 高度
|
|
||||||
* @param outData 输出数据缓冲区
|
|
||||||
* @return 成功返回 true
|
|
||||||
*/
|
|
||||||
virtual bool readPixels(int x, int y, int width, int height,
|
|
||||||
std::vector<uint8_t>& outData) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,162 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 混合模式枚举
|
|
||||||
// ============================================================================
|
|
||||||
enum class BlendMode {
|
|
||||||
None, // 不混合
|
|
||||||
Alpha, // 标准 Alpha 混合
|
|
||||||
Additive, // 加法混合
|
|
||||||
Multiply // 乘法混合
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 深度测试函数枚举
|
|
||||||
// ============================================================================
|
|
||||||
enum class DepthFunc {
|
|
||||||
Never, // 永不通过
|
|
||||||
Less, // 小于
|
|
||||||
Equal, // 等于
|
|
||||||
LessEqual, // 小于等于
|
|
||||||
Greater, // 大于
|
|
||||||
NotEqual, // 不等于
|
|
||||||
GreaterEqual,// 大于等于
|
|
||||||
Always // 总是通过
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 裁剪模式枚举
|
|
||||||
// ============================================================================
|
|
||||||
enum class CullMode {
|
|
||||||
None, // 不裁剪
|
|
||||||
Front, // 裁剪正面
|
|
||||||
Back, // 裁剪背面
|
|
||||||
Both // 裁剪双面
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 顶点属性格式枚举
|
|
||||||
// ============================================================================
|
|
||||||
enum class VertexFormat {
|
|
||||||
Float1, // 1个float
|
|
||||||
Float2, // 2个float
|
|
||||||
Float3, // 3个float
|
|
||||||
Float4, // 4个float
|
|
||||||
Byte4, // 4个byte
|
|
||||||
UByte4, // 4个ubyte
|
|
||||||
Short2, // 2个short
|
|
||||||
Short4 // 4个short
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 顶点属性描述
|
|
||||||
// ============================================================================
|
|
||||||
struct VertexAttribute {
|
|
||||||
uint32_t location = 0; // 属性位置
|
|
||||||
VertexFormat format = VertexFormat::Float3; // 数据格式
|
|
||||||
uint32_t offset = 0; // 在顶点结构中的偏移
|
|
||||||
uint32_t stride = 0; // 顶点结构大小
|
|
||||||
bool normalized = false; // 是否归一化
|
|
||||||
|
|
||||||
VertexAttribute() = default;
|
|
||||||
VertexAttribute(uint32_t loc, VertexFormat fmt, uint32_t off, uint32_t str, bool norm = false)
|
|
||||||
: location(loc), format(fmt), offset(off), stride(str), normalized(norm) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 管线描述结构
|
|
||||||
// ============================================================================
|
|
||||||
struct PipelineDesc {
|
|
||||||
// 混合状态
|
|
||||||
BlendMode blendMode = BlendMode::Alpha;
|
|
||||||
bool blendEnabled = true;
|
|
||||||
|
|
||||||
// 深度状态
|
|
||||||
bool depthTest = false;
|
|
||||||
bool depthWrite = false;
|
|
||||||
DepthFunc depthFunc = DepthFunc::Less;
|
|
||||||
|
|
||||||
// 裁剪状态
|
|
||||||
CullMode cullMode = CullMode::None;
|
|
||||||
|
|
||||||
// 顶点布局
|
|
||||||
std::vector<VertexAttribute> vertexAttributes;
|
|
||||||
|
|
||||||
// 着色器(由后端特定实现设置)
|
|
||||||
void* vertexShader = nullptr;
|
|
||||||
void* fragmentShader = nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 渲染管线抽象接口 - 渲染后端无关
|
|
||||||
// ============================================================================
|
|
||||||
class Pipeline {
|
|
||||||
public:
|
|
||||||
virtual ~Pipeline() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绑定管线
|
|
||||||
*/
|
|
||||||
virtual void bind() = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 解绑管线
|
|
||||||
*/
|
|
||||||
virtual void unbind() = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置混合模式
|
|
||||||
* @param mode 混合模式
|
|
||||||
*/
|
|
||||||
virtual void setBlendMode(BlendMode mode) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前混合模式
|
|
||||||
* @return 混合模式
|
|
||||||
*/
|
|
||||||
virtual BlendMode getBlendMode() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置深度测试
|
|
||||||
* @param enabled 是否启用
|
|
||||||
*/
|
|
||||||
virtual void setDepthTest(bool enabled) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置深度写入
|
|
||||||
* @param enabled 是否启用
|
|
||||||
*/
|
|
||||||
virtual void setDepthWrite(bool enabled) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置深度测试函数
|
|
||||||
* @param func 深度测试函数
|
|
||||||
*/
|
|
||||||
virtual void setDepthFunc(DepthFunc func) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置裁剪模式
|
|
||||||
* @param mode 裁剪模式
|
|
||||||
*/
|
|
||||||
virtual void setCullMode(CullMode mode) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查管线是否有效
|
|
||||||
* @return 有效返回 true
|
|
||||||
*/
|
|
||||||
virtual bool isValid() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取原生句柄(后端特定)
|
|
||||||
* @return 原生句柄值
|
|
||||||
*/
|
|
||||||
virtual uintptr_t getNativeHandle() const = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,134 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <glm/mat4x4.hpp>
|
|
||||||
#include <glm/vec2.hpp>
|
|
||||||
#include <glm/vec3.hpp>
|
|
||||||
#include <glm/vec4.hpp>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 着色器类型枚举
|
|
||||||
// ============================================================================
|
|
||||||
enum class ShaderType {
|
|
||||||
Vertex, // 顶点着色器
|
|
||||||
Fragment, // 片段着色器
|
|
||||||
Geometry, // 几何着色器
|
|
||||||
Compute // 计算着色器
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 着色器描述结构
|
|
||||||
// ============================================================================
|
|
||||||
struct ShaderDesc {
|
|
||||||
std::string name; // 着色器名称
|
|
||||||
std::string vertexSource; // 顶点着色器源码
|
|
||||||
std::string fragmentSource; // 片段着色器源码
|
|
||||||
std::string geometrySource; // 几何着色器源码(可选)
|
|
||||||
std::vector<uint8_t> binaryData; // 预编译二进制数据(可选)
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 着色器抽象接口 - 渲染后端无关
|
|
||||||
// ============================================================================
|
|
||||||
class Shader {
|
|
||||||
public:
|
|
||||||
virtual ~Shader() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绑定着色器程序
|
|
||||||
*/
|
|
||||||
virtual void bind() = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 解绑着色器程序
|
|
||||||
*/
|
|
||||||
virtual void unbind() = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置布尔类型 uniform 变量
|
|
||||||
* @param name uniform 变量名
|
|
||||||
* @param value 布尔值
|
|
||||||
*/
|
|
||||||
virtual void setBool(const std::string& name, bool value) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置整数类型 uniform 变量
|
|
||||||
* @param name uniform 变量名
|
|
||||||
* @param value 整数值
|
|
||||||
*/
|
|
||||||
virtual void setInt(const std::string& name, int value) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置浮点类型 uniform 变量
|
|
||||||
* @param name uniform 变量名
|
|
||||||
* @param value 浮点值
|
|
||||||
*/
|
|
||||||
virtual void setFloat(const std::string& name, float value) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置二维向量类型 uniform 变量
|
|
||||||
* @param name uniform 变量名
|
|
||||||
* @param value 二维向量值
|
|
||||||
*/
|
|
||||||
virtual void setVec2(const std::string& name, const glm::vec2& value) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置三维向量类型 uniform 变量
|
|
||||||
* @param name uniform 变量名
|
|
||||||
* @param value 三维向量值
|
|
||||||
*/
|
|
||||||
virtual void setVec3(const std::string& name, const glm::vec3& value) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置四维向量类型 uniform 变量
|
|
||||||
* @param name uniform 变量名
|
|
||||||
* @param value 四维向量值
|
|
||||||
*/
|
|
||||||
virtual void setVec4(const std::string& name, const glm::vec4& value) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置 4x4 矩阵类型 uniform 变量
|
|
||||||
* @param name uniform 变量名
|
|
||||||
* @param value 4x4 矩阵值
|
|
||||||
*/
|
|
||||||
virtual void setMat4(const std::string& name, const glm::mat4& value) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置颜色类型 uniform 变量
|
|
||||||
* @param name uniform 变量名
|
|
||||||
* @param color 颜色值
|
|
||||||
*/
|
|
||||||
virtual void setColor(const std::string& name, const Color& color) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置纹理采样器
|
|
||||||
* @param name uniform 变量名
|
|
||||||
* @param slot 纹理槽位
|
|
||||||
*/
|
|
||||||
virtual void setTexture(const std::string& name, int slot) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取着色器名称
|
|
||||||
* @return 着色器名称
|
|
||||||
*/
|
|
||||||
virtual const std::string& getName() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查着色器是否有效
|
|
||||||
* @return 有效返回 true
|
|
||||||
*/
|
|
||||||
virtual bool isValid() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取原生句柄(后端特定)
|
|
||||||
* @return 原生句柄值
|
|
||||||
*/
|
|
||||||
virtual uintptr_t getNativeHandle() const = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,131 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Shader缓存条目
|
|
||||||
// ============================================================================
|
|
||||||
struct ShaderCacheEntry {
|
|
||||||
std::string name;
|
|
||||||
std::string sourceHash;
|
|
||||||
uint64_t compileTime = 0;
|
|
||||||
std::vector<uint8_t> binary;
|
|
||||||
std::vector<std::string> dependencies;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Shader缓存管理器
|
|
||||||
// ============================================================================
|
|
||||||
class ShaderCache {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief 获取单例实例
|
|
||||||
* @return 缓存管理器实例引用
|
|
||||||
*/
|
|
||||||
static ShaderCache& getInstance();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化缓存系统
|
|
||||||
* @param cacheDir 缓存目录路径
|
|
||||||
* @return 初始化成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool init(const std::string& cacheDir);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 关闭缓存系统
|
|
||||||
*/
|
|
||||||
void shutdown();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查缓存是否有效
|
|
||||||
* @param name Shader名称
|
|
||||||
* @param sourceHash 源码哈希值
|
|
||||||
* @return 缓存有效返回true,否则返回false
|
|
||||||
*/
|
|
||||||
bool hasValidCache(const std::string& name, const std::string& sourceHash);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 加载缓存的二进制数据
|
|
||||||
* @param name Shader名称
|
|
||||||
* @return 缓存条目指针,不存在返回nullptr
|
|
||||||
*/
|
|
||||||
Ptr<ShaderCacheEntry> loadCache(const std::string& name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 保存编译结果到缓存
|
|
||||||
* @param entry 缓存条目
|
|
||||||
* @return 保存成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool saveCache(const ShaderCacheEntry& entry);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 使缓存失效
|
|
||||||
* @param name Shader名称
|
|
||||||
*/
|
|
||||||
void invalidate(const std::string& name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 清除所有缓存
|
|
||||||
*/
|
|
||||||
void clearAll();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 计算源码哈希值
|
|
||||||
* @param vertSource 顶点着色器源码
|
|
||||||
* @param fragSource 片段着色器源码
|
|
||||||
* @return 哈希值字符串
|
|
||||||
*/
|
|
||||||
static std::string computeHash(const std::string& vertSource,
|
|
||||||
const std::string& fragSource);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查是否已初始化
|
|
||||||
* @return 已初始化返回true,否则返回false
|
|
||||||
*/
|
|
||||||
bool isInitialized() const { return initialized_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
ShaderCache() = default;
|
|
||||||
~ShaderCache() = default;
|
|
||||||
ShaderCache(const ShaderCache&) = delete;
|
|
||||||
ShaderCache& operator=(const ShaderCache&) = delete;
|
|
||||||
|
|
||||||
std::string cacheDir_;
|
|
||||||
std::unordered_map<std::string, ShaderCacheEntry> cacheMap_;
|
|
||||||
bool initialized_ = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 加载缓存索引
|
|
||||||
* @return 加载成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool loadCacheIndex();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 保存缓存索引
|
|
||||||
* @return 保存成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool saveCacheIndex();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取缓存文件路径
|
|
||||||
* @param name Shader名称
|
|
||||||
* @return 缓存文件完整路径
|
|
||||||
*/
|
|
||||||
std::string getCachePath(const std::string& name) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 确保缓存目录存在
|
|
||||||
* @return 目录存在或创建成功返回true,否则返回false
|
|
||||||
*/
|
|
||||||
bool ensureCacheDirectory();
|
|
||||||
};
|
|
||||||
|
|
||||||
// 便捷宏
|
|
||||||
#define E2D_SHADER_CACHE() ::extra2d::ShaderCache::getInstance()
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,131 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <functional>
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <windows.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 文件变化事件
|
|
||||||
// ============================================================================
|
|
||||||
struct FileChangeEvent {
|
|
||||||
std::string filepath;
|
|
||||||
|
|
||||||
enum class Type { Created, Modified, Deleted, Renamed } type;
|
|
||||||
|
|
||||||
uint64_t timestamp = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 文件变化回调
|
|
||||||
// ============================================================================
|
|
||||||
using FileChangeCallback = std::function<void(const FileChangeEvent &)>;
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Shader热重载管理器
|
|
||||||
// ============================================================================
|
|
||||||
class ShaderHotReloader {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief 获取单例实例
|
|
||||||
* @return 热重载管理器实例引用
|
|
||||||
*/
|
|
||||||
static ShaderHotReloader &getInstance();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化热重载系统
|
|
||||||
* @return 初始化成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool init();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 关闭热重载系统
|
|
||||||
*/
|
|
||||||
void shutdown();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 注册Shader文件监视
|
|
||||||
* @param shaderName Shader名称
|
|
||||||
* @param filePaths 要监视的文件列表
|
|
||||||
* @param callback 文件变化时的回调
|
|
||||||
*/
|
|
||||||
void watch(const std::string &shaderName,
|
|
||||||
const std::vector<std::string> &filePaths,
|
|
||||||
FileChangeCallback callback);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 取消监视
|
|
||||||
* @param shaderName Shader名称
|
|
||||||
*/
|
|
||||||
void unwatch(const std::string &shaderName);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 更新文件监视(在主循环中调用)
|
|
||||||
*/
|
|
||||||
void update();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 启用/禁用热重载
|
|
||||||
* @param enabled 是否启用
|
|
||||||
*/
|
|
||||||
void setEnabled(bool enabled);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查是否启用
|
|
||||||
* @return 启用返回true,否则返回false
|
|
||||||
*/
|
|
||||||
bool isEnabled() const { return enabled_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查是否已初始化
|
|
||||||
* @return 已初始化返回true,否则返回false
|
|
||||||
*/
|
|
||||||
bool isInitialized() const { return initialized_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
ShaderHotReloader() = default;
|
|
||||||
~ShaderHotReloader() = default;
|
|
||||||
ShaderHotReloader(const ShaderHotReloader &) = delete;
|
|
||||||
ShaderHotReloader &operator=(const ShaderHotReloader &) = delete;
|
|
||||||
|
|
||||||
bool enabled_ = false;
|
|
||||||
bool initialized_ = false;
|
|
||||||
|
|
||||||
struct WatchInfo {
|
|
||||||
std::vector<std::string> filePaths;
|
|
||||||
FileChangeCallback callback;
|
|
||||||
std::unordered_map<std::string, uint64_t> modifiedTimes;
|
|
||||||
};
|
|
||||||
std::unordered_map<std::string, WatchInfo> watchMap_;
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
HANDLE watchHandle_ = nullptr;
|
|
||||||
std::vector<uint8_t> buffer_;
|
|
||||||
std::string watchDir_;
|
|
||||||
bool watching_ = false;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 轮询检查文件变化
|
|
||||||
*/
|
|
||||||
void pollChanges();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取文件修改时间
|
|
||||||
* @param filepath 文件路径
|
|
||||||
* @return 修改时间戳
|
|
||||||
*/
|
|
||||||
static uint64_t getFileModifiedTime(const std::string &filepath);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 便捷宏
|
|
||||||
#define E2D_SHADER_HOT_RELOADER() ::extra2d::ShaderHotReloader::getInstance()
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,152 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <glm/mat4x4.hpp>
|
|
||||||
#include <glm/vec2.hpp>
|
|
||||||
#include <glm/vec3.hpp>
|
|
||||||
#include <glm/vec4.hpp>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
class Color;
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Shader抽象接口 - 渲染后端无关
|
|
||||||
// ============================================================================
|
|
||||||
class IShader {
|
|
||||||
public:
|
|
||||||
virtual ~IShader() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绑定Shader程序
|
|
||||||
*/
|
|
||||||
virtual void bind() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 解绑Shader程序
|
|
||||||
*/
|
|
||||||
virtual void unbind() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置布尔类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 布尔值
|
|
||||||
*/
|
|
||||||
virtual void setBool(const std::string& name, bool value) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置整数类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 整数值
|
|
||||||
*/
|
|
||||||
virtual void setInt(const std::string& name, int value) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置浮点类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 浮点值
|
|
||||||
*/
|
|
||||||
virtual void setFloat(const std::string& name, float value) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置二维向量类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 二维向量值
|
|
||||||
*/
|
|
||||||
virtual void setVec2(const std::string& name, const glm::vec2& value) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置三维向量类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 三维向量值
|
|
||||||
*/
|
|
||||||
virtual void setVec3(const std::string& name, const glm::vec3& value) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置四维向量类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 四维向量值
|
|
||||||
*/
|
|
||||||
virtual void setVec4(const std::string& name, const glm::vec4& value) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置4x4矩阵类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 4x4矩阵值
|
|
||||||
*/
|
|
||||||
virtual void setMat4(const std::string& name, const glm::mat4& value) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置颜色类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param color 颜色值
|
|
||||||
*/
|
|
||||||
virtual void setColor(const std::string& name, const Color& color) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查Shader是否有效
|
|
||||||
* @return 有效返回true,否则返回false
|
|
||||||
*/
|
|
||||||
virtual bool isValid() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取原生句柄(如OpenGL程序ID)
|
|
||||||
* @return 原生句柄值
|
|
||||||
*/
|
|
||||||
virtual uint32_t getNativeHandle() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取Shader名称
|
|
||||||
* @return Shader名称
|
|
||||||
*/
|
|
||||||
virtual const std::string& getName() const = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置Shader名称
|
|
||||||
* @param name Shader名称
|
|
||||||
*/
|
|
||||||
virtual void setName(const std::string& name) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Shader工厂接口 - 用于创建渲染后端特定的Shader实例
|
|
||||||
// ============================================================================
|
|
||||||
class IShaderFactory {
|
|
||||||
public:
|
|
||||||
virtual ~IShaderFactory() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从源码创建Shader
|
|
||||||
* @param name Shader名称
|
|
||||||
* @param vertSource 顶点着色器源码
|
|
||||||
* @param fragSource 片段着色器源码
|
|
||||||
* @return 创建的Shader实例
|
|
||||||
*/
|
|
||||||
virtual Ptr<IShader> createFromSource(
|
|
||||||
const std::string& name,
|
|
||||||
const std::string& vertSource,
|
|
||||||
const std::string& fragSource) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从缓存二进制创建Shader
|
|
||||||
* @param name Shader名称
|
|
||||||
* @param binary 编译后的二进制数据
|
|
||||||
* @return 创建的Shader实例
|
|
||||||
*/
|
|
||||||
virtual Ptr<IShader> createFromBinary(
|
|
||||||
const std::string& name,
|
|
||||||
const std::vector<uint8_t>& binary) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取Shader的二进制数据(用于缓存)
|
|
||||||
* @param shader Shader实例
|
|
||||||
* @param outBinary 输出的二进制数据
|
|
||||||
* @return 成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
virtual bool getShaderBinary(const IShader& shader,
|
|
||||||
std::vector<uint8_t>& outBinary) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,294 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <glm/glm.hpp>
|
|
||||||
#include <glm/gtc/type_ptr.hpp>
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Shader加载结果
|
|
||||||
// ============================================================================
|
|
||||||
struct ShaderLoadResult {
|
|
||||||
bool success = false;
|
|
||||||
std::string errorMessage;
|
|
||||||
std::string vertSource;
|
|
||||||
std::string fragSource;
|
|
||||||
std::vector<std::string> dependencies;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Shader Uniform 定义
|
|
||||||
// ============================================================================
|
|
||||||
struct ShaderUniformDef {
|
|
||||||
std::string type;
|
|
||||||
std::string description;
|
|
||||||
float defaultValue = 0.0f; // 默认值(用于float类型)
|
|
||||||
float defaultVec2[2] = {0, 0}; // 默认值(用于vec2类型)
|
|
||||||
float defaultVec3[3] = {0, 0, 0}; // 默认值(用于vec3类型)
|
|
||||||
float defaultVec4[4] = {0, 0, 0, 0}; // 默认值(用于vec4类型)
|
|
||||||
float defaultMat4[16] = {
|
|
||||||
1, 0, 0, 0, 0, 1, 0, 0,
|
|
||||||
0, 0, 1, 0, 0, 0, 0, 1}; // 默认值(用于mat4类型,默认单位矩阵)
|
|
||||||
int defaultInt = 0; // 默认值(用于int类型)
|
|
||||||
bool defaultBool = false; // 默认值(用于bool类型)
|
|
||||||
bool hasDefault = false; // 是否有默认值
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Shader Uniform 值类型
|
|
||||||
// ============================================================================
|
|
||||||
struct ShaderUniformValue {
|
|
||||||
enum class Type { Float, Int, Bool, Vec2, Vec3, Vec4, Mat4 } type;
|
|
||||||
|
|
||||||
union {
|
|
||||||
float f[16]; // 足够存储 mat4
|
|
||||||
int i;
|
|
||||||
bool b;
|
|
||||||
} data;
|
|
||||||
|
|
||||||
// 构造函数
|
|
||||||
ShaderUniformValue() : type(Type::Float) { data.f[0] = 0; }
|
|
||||||
ShaderUniformValue(float v) : type(Type::Float) { data.f[0] = v; }
|
|
||||||
ShaderUniformValue(int v) : type(Type::Int) { data.i = v; }
|
|
||||||
ShaderUniformValue(bool v) : type(Type::Bool) { data.b = v; }
|
|
||||||
ShaderUniformValue(const glm::vec2 &v) : type(Type::Vec2) {
|
|
||||||
data.f[0] = v.x;
|
|
||||||
data.f[1] = v.y;
|
|
||||||
}
|
|
||||||
ShaderUniformValue(const glm::vec3 &v) : type(Type::Vec3) {
|
|
||||||
data.f[0] = v.x;
|
|
||||||
data.f[1] = v.y;
|
|
||||||
data.f[2] = v.z;
|
|
||||||
}
|
|
||||||
ShaderUniformValue(const glm::vec4 &v) : type(Type::Vec4) {
|
|
||||||
data.f[0] = v.x;
|
|
||||||
data.f[1] = v.y;
|
|
||||||
data.f[2] = v.z;
|
|
||||||
data.f[3] = v.w;
|
|
||||||
}
|
|
||||||
ShaderUniformValue(const glm::mat4 &m) : type(Type::Mat4) {
|
|
||||||
const float *ptr = glm::value_ptr(m);
|
|
||||||
for (int i = 0; i < 16; ++i)
|
|
||||||
data.f[i] = ptr[i];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Uniform 值映射表
|
|
||||||
using UniformValueMap = std::unordered_map<std::string, ShaderUniformValue>;
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Shader Sampler 定义
|
|
||||||
// ============================================================================
|
|
||||||
struct ShaderSamplerDef {
|
|
||||||
std::string type;
|
|
||||||
std::string description;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Shader元数据
|
|
||||||
// ============================================================================
|
|
||||||
struct ShaderMetadata {
|
|
||||||
std::string name;
|
|
||||||
std::string category;
|
|
||||||
std::string version;
|
|
||||||
std::string description;
|
|
||||||
std::string vertPath;
|
|
||||||
std::string fragPath;
|
|
||||||
std::string combinedPath;
|
|
||||||
uint64_t lastModified = 0;
|
|
||||||
std::vector<std::string> defines;
|
|
||||||
std::unordered_map<std::string, std::string> uniforms;
|
|
||||||
std::unordered_map<std::string, ShaderUniformDef> uniformDefs;
|
|
||||||
std::unordered_map<std::string, ShaderSamplerDef> samplerDefs;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// ShaderLoader接口 - 支持多种文件格式加载
|
|
||||||
// ============================================================================
|
|
||||||
class IShaderLoader {
|
|
||||||
public:
|
|
||||||
virtual ~IShaderLoader() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从分离文件加载Shader (.vert + .frag)
|
|
||||||
* @param name Shader名称
|
|
||||||
* @param vertPath 顶点着色器文件路径
|
|
||||||
* @param fragPath 片段着色器文件路径
|
|
||||||
* @return 加载结果
|
|
||||||
*/
|
|
||||||
virtual ShaderLoadResult
|
|
||||||
loadFromSeparateFiles(const std::string &name, const std::string &vertPath,
|
|
||||||
const std::string &fragPath) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从组合文件加载Shader (.shader)
|
|
||||||
* @param path 组合Shader文件路径
|
|
||||||
* @return 加载结果
|
|
||||||
*/
|
|
||||||
virtual ShaderLoadResult loadFromCombinedFile(const std::string &path) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从源码字符串加载Shader
|
|
||||||
* @param vertSource 顶点着色器源码
|
|
||||||
* @param fragSource 片段着色器源码
|
|
||||||
* @return 加载结果
|
|
||||||
*/
|
|
||||||
virtual ShaderLoadResult loadFromSource(const std::string &vertSource,
|
|
||||||
const std::string &fragSource) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 处理Shader源码中的#include指令
|
|
||||||
* @param source 原始源码
|
|
||||||
* @param baseDir 基础目录
|
|
||||||
* @param outDependencies 输出依赖列表
|
|
||||||
* @return 处理后的源码
|
|
||||||
*/
|
|
||||||
virtual std::string
|
|
||||||
processIncludes(const std::string &source, const std::string &baseDir,
|
|
||||||
std::vector<std::string> &outDependencies) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 应用预处理器定义
|
|
||||||
* @param source 原始源码
|
|
||||||
* @param defines 预处理器定义列表
|
|
||||||
* @return 处理后的源码
|
|
||||||
*/
|
|
||||||
virtual std::string applyDefines(const std::string &source,
|
|
||||||
const std::vector<std::string> &defines) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取Shader元数据
|
|
||||||
* @param path Shader文件路径
|
|
||||||
* @return 元数据
|
|
||||||
*/
|
|
||||||
virtual ShaderMetadata getMetadata(const std::string &path) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 默认ShaderLoader实现
|
|
||||||
// ============================================================================
|
|
||||||
class ShaderLoader : public IShaderLoader {
|
|
||||||
public:
|
|
||||||
ShaderLoader();
|
|
||||||
~ShaderLoader() override = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从分离文件加载Shader (.vert + .frag)
|
|
||||||
* @param name Shader名称
|
|
||||||
* @param vertPath 顶点着色器文件路径
|
|
||||||
* @param fragPath 片段着色器文件路径
|
|
||||||
* @return 加载结果
|
|
||||||
*/
|
|
||||||
ShaderLoadResult loadFromSeparateFiles(const std::string &name,
|
|
||||||
const std::string &vertPath,
|
|
||||||
const std::string &fragPath) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从组合文件加载Shader (.shader)
|
|
||||||
* @param path 组合Shader文件路径
|
|
||||||
* @return 加载结果
|
|
||||||
*/
|
|
||||||
ShaderLoadResult loadFromCombinedFile(const std::string &path) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从源码字符串加载Shader
|
|
||||||
* @param vertSource 顶点着色器源码
|
|
||||||
* @param fragSource 片段着色器源码
|
|
||||||
* @return 加载结果
|
|
||||||
*/
|
|
||||||
ShaderLoadResult loadFromSource(const std::string &vertSource,
|
|
||||||
const std::string &fragSource) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 处理Shader源码中的#include指令
|
|
||||||
* @param source 原始源码
|
|
||||||
* @param baseDir 基础目录
|
|
||||||
* @param outDependencies 输出依赖列表
|
|
||||||
* @return 处理后的源码
|
|
||||||
*/
|
|
||||||
std::string
|
|
||||||
processIncludes(const std::string &source, const std::string &baseDir,
|
|
||||||
std::vector<std::string> &outDependencies) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 应用预处理器定义
|
|
||||||
* @param source 原始源码
|
|
||||||
* @param defines 预处理器定义列表
|
|
||||||
* @return 处理后的源码
|
|
||||||
*/
|
|
||||||
std::string applyDefines(const std::string &source,
|
|
||||||
const std::vector<std::string> &defines) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取Shader元数据
|
|
||||||
* @param path Shader文件路径
|
|
||||||
* @return 元数据
|
|
||||||
*/
|
|
||||||
ShaderMetadata getMetadata(const std::string &path) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 添加include搜索路径
|
|
||||||
* @param path 搜索路径
|
|
||||||
*/
|
|
||||||
void addIncludePath(const std::string &path);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 读取文件内容
|
|
||||||
* @param filepath 文件路径
|
|
||||||
* @return 文件内容字符串
|
|
||||||
*/
|
|
||||||
static std::string readFile(const std::string &filepath);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取文件修改时间
|
|
||||||
* @param filepath 文件路径
|
|
||||||
* @return 修改时间戳
|
|
||||||
*/
|
|
||||||
static uint64_t getFileModifiedTime(const std::string &filepath);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查文件是否存在
|
|
||||||
* @param filepath 文件路径
|
|
||||||
* @return 存在返回true,否则返回false
|
|
||||||
*/
|
|
||||||
static bool fileExists(const std::string &filepath);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<std::string> includePaths_;
|
|
||||||
std::unordered_map<std::string, std::string> includeCache_;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 解析组合Shader文件
|
|
||||||
* @param content 文件内容
|
|
||||||
* @param outVert 输出顶点着色器源码
|
|
||||||
* @param outFrag 输出片段着色器源码
|
|
||||||
* @param outMetadata 输出元数据
|
|
||||||
* @return 解析成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool parseCombinedFile(const std::string &content, std::string &outVert,
|
|
||||||
std::string &outFrag, ShaderMetadata &outMetadata);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 解析元数据JSON块
|
|
||||||
* @param jsonContent JSON内容
|
|
||||||
* @param outMetadata 输出元数据
|
|
||||||
* @return 解析成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool parseMetadata(const std::string &jsonContent,
|
|
||||||
ShaderMetadata &outMetadata);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 查找include文件路径
|
|
||||||
* @param includeName include文件名
|
|
||||||
* @param baseDir 基础目录
|
|
||||||
* @return 找到的完整路径,未找到返回空字符串
|
|
||||||
*/
|
|
||||||
std::string findIncludeFile(const std::string &includeName,
|
|
||||||
const std::string &baseDir);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,291 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/graphics/shader/shader_cache.h>
|
|
||||||
#include <extra2d/graphics/shader/shader_hot_reloader.h>
|
|
||||||
#include <extra2d/graphics/shader/shader_interface.h>
|
|
||||||
#include <extra2d/graphics/shader/shader_loader.h>
|
|
||||||
#include <functional>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Shader重载回调
|
|
||||||
// ============================================================================
|
|
||||||
using ShaderReloadCallback = std::function<void(Ptr<IShader> newShader)>;
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Shader管理器 - 统一入口
|
|
||||||
// ============================================================================
|
|
||||||
class ShaderManager {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief 获取单例实例
|
|
||||||
* @return Shader管理器实例引用
|
|
||||||
*/
|
|
||||||
static ShaderManager& getInstance();
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 初始化和关闭
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 使用平台默认路径初始化Shader系统
|
|
||||||
* 自动检测平台并使用正确的路径(romfs/sdmc/相对路径)
|
|
||||||
* @param factory 渲染后端Shader工厂
|
|
||||||
* @param appName 应用名称(用于缓存目录)
|
|
||||||
* @return 初始化成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool init(Ptr<IShaderFactory> factory, const std::string& appName = "extra2d");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化Shader系统
|
|
||||||
* @param shaderDir Shader文件目录
|
|
||||||
* @param cacheDir 缓存目录
|
|
||||||
* @param factory 渲染后端Shader工厂
|
|
||||||
* @return 初始化成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool init(const std::string& shaderDir,
|
|
||||||
const std::string& cacheDir,
|
|
||||||
Ptr<IShaderFactory> factory);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 关闭Shader系统
|
|
||||||
*/
|
|
||||||
void shutdown();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查是否已初始化
|
|
||||||
* @return 已初始化返回true,否则返回false
|
|
||||||
*/
|
|
||||||
bool isInitialized() const { return initialized_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查当前平台是否支持热重载
|
|
||||||
* Switch平台使用romfs,不支持热重载
|
|
||||||
* @return 支持热重载返回true
|
|
||||||
*/
|
|
||||||
bool isHotReloadSupported() const { return hotReloadSupported_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// Shader加载
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从分离文件加载Shader
|
|
||||||
* @param name Shader名称
|
|
||||||
* @param vertPath 顶点着色器文件路径
|
|
||||||
* @param fragPath 片段着色器文件路径
|
|
||||||
* @return 加载的Shader实例
|
|
||||||
*/
|
|
||||||
Ptr<IShader> loadFromFiles(const std::string& name,
|
|
||||||
const std::string& vertPath,
|
|
||||||
const std::string& fragPath);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从组合文件加载Shader
|
|
||||||
* @param path 组合Shader文件路径
|
|
||||||
* @return 加载的Shader实例
|
|
||||||
*/
|
|
||||||
Ptr<IShader> loadFromCombinedFile(const std::string& path);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从源码加载Shader
|
|
||||||
* @param name Shader名称
|
|
||||||
* @param vertSource 顶点着色器源码
|
|
||||||
* @param fragSource 片段着色器源码
|
|
||||||
* @return 加载的Shader实例
|
|
||||||
*/
|
|
||||||
Ptr<IShader> loadFromSource(const std::string& name,
|
|
||||||
const std::string& vertSource,
|
|
||||||
const std::string& fragSource);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取已加载的Shader
|
|
||||||
* @param name Shader名称
|
|
||||||
* @return Shader实例,不存在返回nullptr
|
|
||||||
*/
|
|
||||||
Ptr<IShader> get(const std::string& name) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查Shader是否存在
|
|
||||||
* @param name Shader名称
|
|
||||||
* @return 存在返回true,否则返回false
|
|
||||||
*/
|
|
||||||
bool has(const std::string& name) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 移除Shader
|
|
||||||
* @param name Shader名称
|
|
||||||
*/
|
|
||||||
void remove(const std::string& name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 清除所有Shader
|
|
||||||
*/
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 热重载
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 注册重载回调
|
|
||||||
* @param name Shader名称
|
|
||||||
* @param callback 重载回调函数
|
|
||||||
*/
|
|
||||||
void setReloadCallback(const std::string& name, ShaderReloadCallback callback);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 启用/禁用热重载
|
|
||||||
* @param enabled 是否启用
|
|
||||||
*/
|
|
||||||
void setHotReloadEnabled(bool enabled);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查热重载是否启用
|
|
||||||
* @return 启用返回true,否则返回false
|
|
||||||
*/
|
|
||||||
bool isHotReloadEnabled() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 更新热重载系统(主循环调用)
|
|
||||||
*/
|
|
||||||
void update();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 手动重载Shader
|
|
||||||
* @param name Shader名称
|
|
||||||
* @return 重载成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool reload(const std::string& name);
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 内置Shader
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取内置Shader
|
|
||||||
* @param name 内置Shader名称
|
|
||||||
* @return Shader实例
|
|
||||||
*/
|
|
||||||
Ptr<IShader> getBuiltin(const std::string& name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 加载所有内置Shader
|
|
||||||
* @return 加载成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool loadBuiltinShaders();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从JSON元数据文件加载Shader(多后端支持)
|
|
||||||
* @param jsonPath JSON元数据文件路径
|
|
||||||
* @param name Shader名称
|
|
||||||
* @return 加载的Shader实例
|
|
||||||
*/
|
|
||||||
Ptr<IShader> loadFromMetadata(const std::string& jsonPath, const std::string& name);
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 工具方法
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取Shader目录
|
|
||||||
* @return Shader目录路径
|
|
||||||
*/
|
|
||||||
const std::string& getShaderDir() const { return shaderDir_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取ShaderLoader
|
|
||||||
* @return ShaderLoader引用
|
|
||||||
*/
|
|
||||||
ShaderLoader& getLoader() { return loader_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取Shader元数据
|
|
||||||
* @param name Shader名称
|
|
||||||
* @return Shader元数据,不存在返回空元数据
|
|
||||||
*/
|
|
||||||
ShaderMetadata getMetadata(const std::string& name) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取Shader的uniform定义
|
|
||||||
* @param name Shader名称
|
|
||||||
* @return uniform定义映射,不存在返回空映射
|
|
||||||
*/
|
|
||||||
std::unordered_map<std::string, ShaderUniformDef> getUniformDefs(const std::string& name) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取Shader的sampler定义
|
|
||||||
* @param name Shader名称
|
|
||||||
* @return sampler定义映射,不存在返回空映射
|
|
||||||
*/
|
|
||||||
std::unordered_map<std::string, ShaderSamplerDef> getSamplerDefs(const std::string& name) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 自动应用uniform值到着色器
|
|
||||||
* 根据JSON元数据中的uniform定义,自动设置对应的uniform值
|
|
||||||
* @param shader 目标着色器
|
|
||||||
* @param shaderName Shader名称(用于查找元数据)
|
|
||||||
* @param values uniform值映射表
|
|
||||||
*/
|
|
||||||
void applyUniforms(Ptr<IShader> shader, const std::string& shaderName, const UniformValueMap& values);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 自动应用sampler绑定到着色器
|
|
||||||
* 根据JSON元数据中的sampler定义,自动设置对应的纹理单元
|
|
||||||
* @param shader 目标着色器
|
|
||||||
* @param shaderName Shader名称(用于查找元数据)
|
|
||||||
*/
|
|
||||||
void applySamplers(Ptr<IShader> shader, const std::string& shaderName);
|
|
||||||
|
|
||||||
private:
|
|
||||||
ShaderManager() = default;
|
|
||||||
~ShaderManager() = default;
|
|
||||||
ShaderManager(const ShaderManager&) = delete;
|
|
||||||
ShaderManager& operator=(const ShaderManager&) = delete;
|
|
||||||
|
|
||||||
std::string shaderDir_;
|
|
||||||
std::string cacheDir_;
|
|
||||||
Ptr<IShaderFactory> factory_;
|
|
||||||
ShaderLoader loader_;
|
|
||||||
|
|
||||||
struct ShaderInfo {
|
|
||||||
Ptr<IShader> shader;
|
|
||||||
ShaderMetadata metadata;
|
|
||||||
ShaderReloadCallback reloadCallback;
|
|
||||||
std::string vertSource;
|
|
||||||
std::string fragSource;
|
|
||||||
std::vector<std::string> filePaths;
|
|
||||||
};
|
|
||||||
std::unordered_map<std::string, ShaderInfo> shaders_;
|
|
||||||
|
|
||||||
bool initialized_ = false;
|
|
||||||
bool hotReloadEnabled_ = false;
|
|
||||||
bool hotReloadSupported_ = true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从缓存加载Shader
|
|
||||||
* @param name Shader名称
|
|
||||||
* @param sourceHash 源码哈希值
|
|
||||||
* @param vertSource 顶点着色器源码
|
|
||||||
* @param fragSource 片段着色器源码
|
|
||||||
* @return Shader实例
|
|
||||||
*/
|
|
||||||
Ptr<IShader> loadFromCache(const std::string& name,
|
|
||||||
const std::string& sourceHash,
|
|
||||||
const std::string& vertSource,
|
|
||||||
const std::string& fragSource);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 处理文件变化事件
|
|
||||||
* @param shaderName Shader名称
|
|
||||||
* @param event 文件变化事件
|
|
||||||
*/
|
|
||||||
void handleFileChange(const std::string& shaderName, const FileChangeEvent& event);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 便捷宏
|
|
||||||
#define E2D_SHADER_MANAGER() ::extra2d::ShaderManager::getInstance()
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,112 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/graphics/shader/shader_interface.h>
|
|
||||||
#include <glm/vec4.hpp>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
struct WaterParams {
|
|
||||||
float waveSpeed = 1.0f;
|
|
||||||
float waveAmplitude = 0.02f;
|
|
||||||
float waveFrequency = 4.0f;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct OutlineParams {
|
|
||||||
Color color = Colors::Black;
|
|
||||||
float thickness = 2.0f;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DistortionParams {
|
|
||||||
float distortionAmount = 0.02f;
|
|
||||||
float timeScale = 1.0f;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PixelateParams {
|
|
||||||
float pixelSize = 8.0f;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct InvertParams {
|
|
||||||
float strength = 1.0f;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GrayscaleParams {
|
|
||||||
float intensity = 1.0f;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct BlurParams {
|
|
||||||
float radius = 5.0f;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ShaderPreset {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief 创建水波纹效果着色器
|
|
||||||
* @param params 水波纹效果参数
|
|
||||||
* @return 配置好的着色器
|
|
||||||
*/
|
|
||||||
static Ptr<IShader> Water(const WaterParams& params = {});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建描边效果着色器
|
|
||||||
* @param params 描边效果参数
|
|
||||||
* @return 配置好的着色器
|
|
||||||
*/
|
|
||||||
static Ptr<IShader> Outline(const OutlineParams& params = {});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建扭曲效果着色器
|
|
||||||
* @param params 扭曲效果参数
|
|
||||||
* @return 配置好的着色器
|
|
||||||
*/
|
|
||||||
static Ptr<IShader> Distortion(const DistortionParams& params = {});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建像素化效果着色器
|
|
||||||
* @param params 像素化效果参数
|
|
||||||
* @return 配置好的着色器
|
|
||||||
*/
|
|
||||||
static Ptr<IShader> Pixelate(const PixelateParams& params = {});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建反相效果着色器
|
|
||||||
* @param params 反相效果参数
|
|
||||||
* @return 配置好的着色器
|
|
||||||
*/
|
|
||||||
static Ptr<IShader> Invert(const InvertParams& params = {});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建灰度效果着色器
|
|
||||||
* @param params 灰度效果参数
|
|
||||||
* @return 配置好的着色器
|
|
||||||
*/
|
|
||||||
static Ptr<IShader> Grayscale(const GrayscaleParams& params = {});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建模糊效果着色器
|
|
||||||
* @param params 模糊效果参数
|
|
||||||
* @return 配置好的着色器
|
|
||||||
*/
|
|
||||||
static Ptr<IShader> Blur(const BlurParams& params = {});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建灰度+描边组合效果着色器
|
|
||||||
* @param grayParams 灰度效果参数
|
|
||||||
* @param outlineParams 描边效果参数
|
|
||||||
* @return 配置好的着色器
|
|
||||||
*/
|
|
||||||
static Ptr<IShader> GrayscaleOutline(const GrayscaleParams& grayParams,
|
|
||||||
const OutlineParams& outlineParams);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建像素化+反相组合效果着色器
|
|
||||||
* @param pixParams 像素化效果参数
|
|
||||||
* @param invParams 反相效果参数
|
|
||||||
* @return 配置好的着色器
|
|
||||||
*/
|
|
||||||
static Ptr<IShader> PixelateInvert(const PixelateParams& pixParams,
|
|
||||||
const InvertParams& invParams);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/math_types.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Alpha 遮罩 - 存储图片的非透明区域信息
|
|
||||||
// ============================================================================
|
|
||||||
class AlphaMask {
|
|
||||||
public:
|
|
||||||
AlphaMask() = default;
|
|
||||||
AlphaMask(int width, int height);
|
|
||||||
|
|
||||||
/// 从像素数据创建遮罩
|
|
||||||
static AlphaMask createFromPixels(const uint8_t *pixels, int width,
|
|
||||||
int height, int channels);
|
|
||||||
|
|
||||||
/// 获取指定位置的透明度(0-255)
|
|
||||||
uint8_t getAlpha(int x, int y) const;
|
|
||||||
|
|
||||||
/// 检查指定位置是否不透明
|
|
||||||
bool isOpaque(int x, int y, uint8_t threshold = 128) const;
|
|
||||||
|
|
||||||
/// 检查指定位置是否在遮罩范围内
|
|
||||||
bool isValid(int x, int y) const;
|
|
||||||
|
|
||||||
/// 获取遮罩尺寸
|
|
||||||
int getWidth() const { return width_; }
|
|
||||||
int getHeight() const { return height_; }
|
|
||||||
Size getSize() const {
|
|
||||||
return Size(static_cast<float>(width_), static_cast<float>(height_));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 获取原始数据
|
|
||||||
const std::vector<uint8_t> &getData() const { return data_; }
|
|
||||||
|
|
||||||
/// 检查遮罩是否有效
|
|
||||||
bool isValid() const { return !data_.empty() && width_ > 0 && height_ > 0; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
int width_ = 0;
|
|
||||||
int height_ = 0;
|
|
||||||
std::vector<uint8_t> data_; // Alpha值数组
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
|
||||||
#include <extra2d/core/math_types.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 字形信息
|
|
||||||
// ============================================================================
|
|
||||||
struct Glyph {
|
|
||||||
float u0, v0; // 纹理坐标左下角
|
|
||||||
float u1, v1; // 纹理坐标右上角
|
|
||||||
float width; // 字形宽度(像素)
|
|
||||||
float height; // 字形高度(像素)
|
|
||||||
float bearingX; // 水平偏移
|
|
||||||
float bearingY; // 垂直偏移
|
|
||||||
float advance; // 前进距离
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 字体图集接口
|
|
||||||
// ============================================================================
|
|
||||||
class FontAtlas {
|
|
||||||
public:
|
|
||||||
virtual ~FontAtlas() = default;
|
|
||||||
|
|
||||||
// 获取字形信息
|
|
||||||
virtual const Glyph *getGlyph(char32_t codepoint) const = 0;
|
|
||||||
|
|
||||||
// 获取纹理
|
|
||||||
virtual class Texture *getTexture() const = 0;
|
|
||||||
|
|
||||||
// 获取字体大小
|
|
||||||
virtual int getFontSize() const = 0;
|
|
||||||
|
|
||||||
virtual float getAscent() const = 0;
|
|
||||||
virtual float getDescent() const = 0;
|
|
||||||
virtual float getLineGap() const = 0;
|
|
||||||
virtual float getLineHeight() const = 0;
|
|
||||||
|
|
||||||
// 计算文字尺寸
|
|
||||||
virtual Vec2 measureText(const std::string &text) = 0;
|
|
||||||
|
|
||||||
// 是否支持 SDF 渲染
|
|
||||||
virtual bool isSDF() const = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/core/math_types.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 像素格式枚举
|
|
||||||
// ============================================================================
|
|
||||||
enum class PixelFormat {
|
|
||||||
R8, // 单通道灰度
|
|
||||||
RG8, // 双通道
|
|
||||||
RGB8, // RGB 24位
|
|
||||||
RGBA8, // RGBA 32位(默认)
|
|
||||||
RGB16F, // RGB 半精度浮点
|
|
||||||
RGBA16F, // RGBA 半精度浮点
|
|
||||||
RGB32F, // RGB 全精度浮点
|
|
||||||
RGBA32F, // RGBA 全精度浮点
|
|
||||||
Depth16, // 16位深度
|
|
||||||
Depth24, // 24位深度
|
|
||||||
Depth32F, // 32位浮点深度
|
|
||||||
Depth24Stencil8, // 24位深度 + 8位模板
|
|
||||||
|
|
||||||
// 压缩纹理格式
|
|
||||||
ETC2_RGB8, // ETC2 RGB 压缩
|
|
||||||
ETC2_RGBA8, // ETC2 RGBA 压缩
|
|
||||||
ASTC_4x4, // ASTC 4x4 压缩
|
|
||||||
ASTC_6x6, // ASTC 6x6 压缩
|
|
||||||
ASTC_8x8 // ASTC 8x8 压缩
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 纹理接口
|
|
||||||
// ============================================================================
|
|
||||||
class Texture {
|
|
||||||
public:
|
|
||||||
virtual ~Texture() = default;
|
|
||||||
|
|
||||||
// 获取尺寸
|
|
||||||
virtual int getWidth() const = 0;
|
|
||||||
virtual int getHeight() const = 0;
|
|
||||||
virtual Size getSize() const = 0;
|
|
||||||
|
|
||||||
// 获取通道数
|
|
||||||
virtual int getChannels() const = 0;
|
|
||||||
|
|
||||||
// 获取像素格式
|
|
||||||
virtual PixelFormat getFormat() const = 0;
|
|
||||||
|
|
||||||
// 获取原始句柄(用于底层渲染)
|
|
||||||
virtual void* getNativeHandle() const = 0;
|
|
||||||
|
|
||||||
// 是否有效
|
|
||||||
virtual bool isValid() const = 0;
|
|
||||||
|
|
||||||
// 设置过滤模式
|
|
||||||
virtual void setFilter(bool linear) = 0;
|
|
||||||
|
|
||||||
// 设置环绕模式
|
|
||||||
virtual void setWrap(bool repeat) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,185 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
|
||||||
#include <extra2d/core/math_types.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
|
||||||
#include <extra2d/graphics/texture/texture.h>
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 纹理图集 - 自动将小纹理合并到大图集以减少 DrawCall
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 图集中的单个纹理条目
|
|
||||||
*/
|
|
||||||
struct AtlasEntry {
|
|
||||||
std::string name; // 原始纹理名称/路径
|
|
||||||
Rect uvRect; // 在图集中的 UV 坐标范围
|
|
||||||
Vec2 originalSize; // 原始纹理尺寸
|
|
||||||
uint32_t padding; // 边距(用于避免纹理 bleeding)
|
|
||||||
|
|
||||||
AtlasEntry() : uvRect(), originalSize(), padding(2) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 纹理图集页面
|
|
||||||
* 当单个图集放不下时,创建多个页面
|
|
||||||
*/
|
|
||||||
class TextureAtlasPage {
|
|
||||||
public:
|
|
||||||
static constexpr int DEFAULT_SIZE = 2048;
|
|
||||||
static constexpr int MAX_SIZE = 4096;
|
|
||||||
static constexpr int MIN_TEXTURE_SIZE = 32; // 小于此大小的纹理才考虑合并
|
|
||||||
static constexpr int PADDING = 2; // 纹理间边距
|
|
||||||
|
|
||||||
TextureAtlasPage(int width = DEFAULT_SIZE, int height = DEFAULT_SIZE);
|
|
||||||
~TextureAtlasPage();
|
|
||||||
|
|
||||||
// 尝试添加纹理到图集
|
|
||||||
// 返回是否成功,如果成功则输出 uvRect
|
|
||||||
bool tryAddTexture(const std::string &name, int texWidth, int texHeight,
|
|
||||||
const uint8_t *pixels, Rect &outUvRect);
|
|
||||||
|
|
||||||
// 获取图集纹理
|
|
||||||
Ptr<Texture> getTexture() const { return texture_; }
|
|
||||||
|
|
||||||
// 获取条目
|
|
||||||
const AtlasEntry *getEntry(const std::string &name) const;
|
|
||||||
|
|
||||||
// 获取使用率
|
|
||||||
float getUsageRatio() const;
|
|
||||||
|
|
||||||
// 获取尺寸
|
|
||||||
int getWidth() const { return width_; }
|
|
||||||
int getHeight() const { return height_; }
|
|
||||||
|
|
||||||
// 是否已满
|
|
||||||
bool isFull() const { return isFull_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
int width_, height_;
|
|
||||||
Ptr<Texture> texture_;
|
|
||||||
std::unordered_map<std::string, AtlasEntry> entries_;
|
|
||||||
|
|
||||||
// 矩形打包数据
|
|
||||||
struct PackNode {
|
|
||||||
int x, y, width, height;
|
|
||||||
bool used;
|
|
||||||
std::unique_ptr<PackNode> left;
|
|
||||||
std::unique_ptr<PackNode> right;
|
|
||||||
|
|
||||||
PackNode(int x_, int y_, int w, int h)
|
|
||||||
: x(x_), y(y_), width(w), height(h), used(false) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::unique_ptr<PackNode> root_;
|
|
||||||
bool isFull_;
|
|
||||||
int usedArea_;
|
|
||||||
|
|
||||||
// 递归插入
|
|
||||||
PackNode *insert(PackNode *node, int width, int height);
|
|
||||||
void writePixels(int x, int y, int w, int h, const uint8_t *pixels);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 纹理图集管理器
|
|
||||||
* 自动管理多个图集页面,提供统一的纹理查询接口
|
|
||||||
*/
|
|
||||||
class TextureAtlas {
|
|
||||||
public:
|
|
||||||
TextureAtlas();
|
|
||||||
~TextureAtlas();
|
|
||||||
|
|
||||||
// 初始化
|
|
||||||
void init(int pageSize = TextureAtlasPage::DEFAULT_SIZE);
|
|
||||||
|
|
||||||
// 添加纹理到图集
|
|
||||||
// 如果纹理太大,返回 false,应该作为独立纹理加载
|
|
||||||
bool addTexture(const std::string &name, int width, int height,
|
|
||||||
const uint8_t *pixels);
|
|
||||||
|
|
||||||
// 查询纹理是否在图集中
|
|
||||||
bool contains(const std::string &name) const;
|
|
||||||
|
|
||||||
// 获取纹理在图集中的信息
|
|
||||||
// 返回图集纹理和 UV 坐标
|
|
||||||
const Texture *getAtlasTexture(const std::string &name) const;
|
|
||||||
Rect getUVRect(const std::string &name) const;
|
|
||||||
|
|
||||||
// 获取原始纹理尺寸
|
|
||||||
Vec2 getOriginalSize(const std::string &name) const;
|
|
||||||
|
|
||||||
// 获取所有图集页面
|
|
||||||
const std::vector<std::unique_ptr<TextureAtlasPage>> &getPages() const {
|
|
||||||
return pages_;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取总使用率
|
|
||||||
float getTotalUsageRatio() const;
|
|
||||||
|
|
||||||
// 清空所有图集
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
// 设置是否启用自动图集
|
|
||||||
void setEnabled(bool enabled) { enabled_ = enabled; }
|
|
||||||
bool isEnabled() const { return enabled_; }
|
|
||||||
|
|
||||||
// 设置纹理大小阈值(小于此大小的纹理才进入图集)
|
|
||||||
void setSizeThreshold(int threshold) { sizeThreshold_ = threshold; }
|
|
||||||
int getSizeThreshold() const { return sizeThreshold_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<std::unique_ptr<TextureAtlasPage>> pages_;
|
|
||||||
std::unordered_map<std::string, TextureAtlasPage *> entryToPage_;
|
|
||||||
|
|
||||||
int pageSize_;
|
|
||||||
int sizeThreshold_;
|
|
||||||
bool enabled_;
|
|
||||||
bool initialized_;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 全局图集管理器(单例)
|
|
||||||
*/
|
|
||||||
class TextureAtlasMgr {
|
|
||||||
public:
|
|
||||||
static TextureAtlasMgr &get();
|
|
||||||
|
|
||||||
// 获取主图集
|
|
||||||
TextureAtlas &getAtlas() { return atlas_; }
|
|
||||||
|
|
||||||
// 快捷方法
|
|
||||||
bool addTexture(const std::string &name, int width, int height,
|
|
||||||
const uint8_t *pixels) {
|
|
||||||
return atlas_.addTexture(name, width, height, pixels);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool contains(const std::string &name) const { return atlas_.contains(name); }
|
|
||||||
|
|
||||||
const Texture *getAtlasTexture(const std::string &name) const {
|
|
||||||
return atlas_.getAtlasTexture(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
Rect getUVRect(const std::string &name) const {
|
|
||||||
return atlas_.getUVRect(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
TextureAtlasMgr() = default;
|
|
||||||
~TextureAtlasMgr() = default;
|
|
||||||
|
|
||||||
TextureAtlasMgr(const TextureAtlasMgr &) = delete;
|
|
||||||
TextureAtlasMgr &operator=(const TextureAtlasMgr &) = delete;
|
|
||||||
|
|
||||||
TextureAtlas atlas_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,560 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <chrono>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <extra2d/core/math_types.h>
|
|
||||||
#include <extra2d/core/service_locator.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/graphics/texture/texture.h>
|
|
||||||
#include <extra2d/services/logger_service.h>
|
|
||||||
#include <functional>
|
|
||||||
#include <mutex>
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
class Scene;
|
|
||||||
class Renderer;
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 纹理加载选项
|
|
||||||
// ============================================================================
|
|
||||||
struct TextureLoadOptions {
|
|
||||||
bool generateMipmaps = true; // 是否生成 mipmaps
|
|
||||||
bool sRGB = true; // 是否使用 sRGB 色彩空间
|
|
||||||
bool premultiplyAlpha = false; // 是否预乘 Alpha
|
|
||||||
PixelFormat preferredFormat = PixelFormat::RGBA8; // 首选像素格式
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 纹理键 - 用于唯一标识纹理缓存条目
|
|
||||||
// ============================================================================
|
|
||||||
struct TextureKey {
|
|
||||||
std::string path; // 纹理文件路径
|
|
||||||
Rect region; // 纹理区域(用于纹理图集)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 默认构造函数
|
|
||||||
*/
|
|
||||||
TextureKey() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 构造函数(仅路径)
|
|
||||||
* @param p 纹理文件路径
|
|
||||||
*/
|
|
||||||
explicit TextureKey(const std::string &p) : path(p), region(Rect::Zero()) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 构造函数(路径 + 区域)
|
|
||||||
* @param p 纹理文件路径
|
|
||||||
* @param r 纹理区域
|
|
||||||
*/
|
|
||||||
TextureKey(const std::string &p, const Rect &r) : path(p), region(r) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 相等比较运算符
|
|
||||||
* @param other 另一个 TextureKey
|
|
||||||
* @return 是否相等
|
|
||||||
*/
|
|
||||||
bool operator==(const TextureKey &other) const {
|
|
||||||
return path == other.path && region == other.region;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 不等比较运算符
|
|
||||||
* @param other 另一个 TextureKey
|
|
||||||
* @return 是否不等
|
|
||||||
*/
|
|
||||||
bool operator!=(const TextureKey &other) const { return !(*this == other); }
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// TextureKey 哈希函子
|
|
||||||
// ============================================================================
|
|
||||||
struct TextureKeyHash {
|
|
||||||
/**
|
|
||||||
* @brief 计算 TextureKey 的哈希值
|
|
||||||
* @param key 纹理键
|
|
||||||
* @return 哈希值
|
|
||||||
*/
|
|
||||||
size_t operator()(const TextureKey &key) const {
|
|
||||||
size_t h1 = std::hash<std::string>{}(key.path);
|
|
||||||
size_t h2 = std::hash<float>{}(key.region.origin.x);
|
|
||||||
size_t h3 = std::hash<float>{}(key.region.origin.y);
|
|
||||||
size_t h4 = std::hash<float>{}(key.region.size.width);
|
|
||||||
size_t h5 = std::hash<float>{}(key.region.size.height);
|
|
||||||
|
|
||||||
// 组合哈希值
|
|
||||||
size_t result = h1;
|
|
||||||
result ^= h2 + 0x9e3779b9 + (result << 6) + (result >> 2);
|
|
||||||
result ^= h3 + 0x9e3779b9 + (result << 6) + (result >> 2);
|
|
||||||
result ^= h4 + 0x9e3779b9 + (result << 6) + (result >> 2);
|
|
||||||
result ^= h5 + 0x9e3779b9 + (result << 6) + (result >> 2);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 纹理池条目
|
|
||||||
// ============================================================================
|
|
||||||
struct TexturePoolEntry {
|
|
||||||
Ptr<Texture> texture; // 纹理对象
|
|
||||||
mutable std::atomic<uint32_t> refCount; // 引用计数
|
|
||||||
TextureKey key; // 纹理键
|
|
||||||
size_t memorySize; // 内存占用(字节)
|
|
||||||
mutable uint64_t lastAccessTime; // 最后访问时间戳
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 默认构造函数
|
|
||||||
*/
|
|
||||||
TexturePoolEntry()
|
|
||||||
: texture(nullptr), refCount(0), key(), memorySize(0), lastAccessTime(0) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 构造函数
|
|
||||||
* @param tex 纹理对象
|
|
||||||
* @param k 纹理键
|
|
||||||
* @param memSize 内存占用
|
|
||||||
*/
|
|
||||||
TexturePoolEntry(Ptr<Texture> tex, const TextureKey &k, size_t memSize)
|
|
||||||
: texture(tex), refCount(1), key(k), memorySize(memSize),
|
|
||||||
lastAccessTime(getCurrentTime()) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 移动构造函数
|
|
||||||
* @param other 另一个条目
|
|
||||||
*/
|
|
||||||
TexturePoolEntry(TexturePoolEntry &&other) noexcept
|
|
||||||
: texture(std::move(other.texture)),
|
|
||||||
refCount(other.refCount.load(std::memory_order_relaxed)),
|
|
||||||
key(std::move(other.key)), memorySize(other.memorySize),
|
|
||||||
lastAccessTime(other.lastAccessTime) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 移动赋值运算符
|
|
||||||
* @param other 另一个条目
|
|
||||||
* @return 引用
|
|
||||||
*/
|
|
||||||
TexturePoolEntry &operator=(TexturePoolEntry &&other) noexcept {
|
|
||||||
if (this != &other) {
|
|
||||||
texture = std::move(other.texture);
|
|
||||||
refCount.store(other.refCount.load(std::memory_order_relaxed),
|
|
||||||
std::memory_order_relaxed);
|
|
||||||
key = std::move(other.key);
|
|
||||||
memorySize = other.memorySize;
|
|
||||||
lastAccessTime = other.lastAccessTime;
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 禁止拷贝
|
|
||||||
TexturePoolEntry(const TexturePoolEntry &) = delete;
|
|
||||||
TexturePoolEntry &operator=(const TexturePoolEntry &) = delete;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 更新最后访问时间
|
|
||||||
*/
|
|
||||||
void touch() const { lastAccessTime = getCurrentTime(); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前时间戳
|
|
||||||
* @return 时间戳(毫秒)
|
|
||||||
*/
|
|
||||||
static uint64_t getCurrentTime() {
|
|
||||||
auto now = std::chrono::steady_clock::now();
|
|
||||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
||||||
now.time_since_epoch());
|
|
||||||
return static_cast<uint64_t>(duration.count());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 纹理引用智能指针 - 自动管理纹理池引用计数
|
|
||||||
// ============================================================================
|
|
||||||
class TextureRef {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief 默认构造函数
|
|
||||||
*/
|
|
||||||
TextureRef() : texture_(nullptr), entry_(nullptr), mutex_(nullptr) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 构造函数
|
|
||||||
* @param texture 纹理对象
|
|
||||||
* @param entry 纹理池条目
|
|
||||||
* @param mutex 互斥锁
|
|
||||||
*/
|
|
||||||
TextureRef(Ptr<Texture> texture, TexturePoolEntry *entry, std::mutex *mutex)
|
|
||||||
: texture_(texture), entry_(entry), mutex_(mutex) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建独立的纹理引用(不管理引用计数)
|
|
||||||
* @param texture 纹理对象
|
|
||||||
* @return 独立的纹理引用
|
|
||||||
*/
|
|
||||||
static TextureRef fromTexture(Ptr<Texture> texture) {
|
|
||||||
return TextureRef(texture, nullptr, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 拷贝构造函数
|
|
||||||
* @param other 另一个 TextureRef
|
|
||||||
*/
|
|
||||||
TextureRef(const TextureRef &other)
|
|
||||||
: texture_(other.texture_), entry_(other.entry_), mutex_(other.mutex_) {
|
|
||||||
if (entry_ && entry_->refCount.load(std::memory_order_relaxed) > 0) {
|
|
||||||
entry_->refCount.fetch_add(1, std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 移动构造函数
|
|
||||||
* @param other 另一个 TextureRef
|
|
||||||
*/
|
|
||||||
TextureRef(TextureRef &&other) noexcept
|
|
||||||
: texture_(std::move(other.texture_)), entry_(other.entry_),
|
|
||||||
mutex_(other.mutex_) {
|
|
||||||
other.entry_ = nullptr;
|
|
||||||
other.mutex_ = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 析构函数
|
|
||||||
*/
|
|
||||||
~TextureRef() { reset(); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 拷贝赋值运算符
|
|
||||||
* @param other 另一个 TextureRef
|
|
||||||
* @return 引用
|
|
||||||
*/
|
|
||||||
TextureRef &operator=(const TextureRef &other) {
|
|
||||||
if (this != &other) {
|
|
||||||
reset();
|
|
||||||
texture_ = other.texture_;
|
|
||||||
entry_ = other.entry_;
|
|
||||||
mutex_ = other.mutex_;
|
|
||||||
if (entry_ && entry_->refCount.load(std::memory_order_relaxed) > 0) {
|
|
||||||
entry_->refCount.fetch_add(1, std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 移动赋值运算符
|
|
||||||
* @param other 另一个 TextureRef
|
|
||||||
* @return 引用
|
|
||||||
*/
|
|
||||||
TextureRef &operator=(TextureRef &&other) noexcept {
|
|
||||||
if (this != &other) {
|
|
||||||
reset();
|
|
||||||
texture_ = std::move(other.texture_);
|
|
||||||
entry_ = other.entry_;
|
|
||||||
mutex_ = other.mutex_;
|
|
||||||
other.entry_ = nullptr;
|
|
||||||
other.mutex_ = nullptr;
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 重置引用
|
|
||||||
*/
|
|
||||||
void reset() {
|
|
||||||
if (entry_ && mutex_) {
|
|
||||||
std::lock_guard<std::mutex> lock(*mutex_);
|
|
||||||
if (entry_->refCount.load(std::memory_order_relaxed) > 0) {
|
|
||||||
entry_->refCount.fetch_sub(1, std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
texture_.reset();
|
|
||||||
entry_ = nullptr;
|
|
||||||
mutex_ = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取纹理对象
|
|
||||||
* @return 纹理对象指针
|
|
||||||
*/
|
|
||||||
Texture *get() const { return texture_.get(); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取纹理对象(智能指针)
|
|
||||||
* @return 纹理对象智能指针
|
|
||||||
*/
|
|
||||||
Ptr<Texture> getPtr() const { return texture_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查是否有效
|
|
||||||
* @return 是否有效
|
|
||||||
*/
|
|
||||||
bool valid() const { return texture_ != nullptr; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 布尔转换运算符
|
|
||||||
*/
|
|
||||||
explicit operator bool() const { return valid(); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 箭头运算符
|
|
||||||
*/
|
|
||||||
Texture *operator->() const { return texture_.get(); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 解引用运算符
|
|
||||||
*/
|
|
||||||
Texture &operator*() const { return *texture_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
Ptr<Texture> texture_;
|
|
||||||
TexturePoolEntry *entry_;
|
|
||||||
std::mutex *mutex_;
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 纹理池 - 纹理缓存和内存管理系统
|
|
||||||
// 特性:
|
|
||||||
// - 纹理缓存和复用
|
|
||||||
// - 引用计数管理
|
|
||||||
// - 内存使用限制
|
|
||||||
// - LRU 淘汰策略
|
|
||||||
// - 线程安全
|
|
||||||
// ============================================================================
|
|
||||||
class TexturePool {
|
|
||||||
public:
|
|
||||||
// ========================================================================
|
|
||||||
// 统计信息
|
|
||||||
// ========================================================================
|
|
||||||
struct Stats {
|
|
||||||
size_t textureCount = 0; // 纹理数量
|
|
||||||
size_t memoryUsage = 0; // 内存使用量(字节)
|
|
||||||
size_t maxMemoryUsage = 0; // 最大内存使用量
|
|
||||||
size_t cacheHits = 0; // 缓存命中次数
|
|
||||||
size_t cacheMisses = 0; // 缓存未命中次数
|
|
||||||
size_t evictionCount = 0; // 淘汰次数
|
|
||||||
};
|
|
||||||
|
|
||||||
// ========================================================================
|
|
||||||
// 构造和析构
|
|
||||||
// ========================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 默认构造函数
|
|
||||||
*/
|
|
||||||
TexturePool();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 构造函数
|
|
||||||
* @param scene 场景指针
|
|
||||||
* @param maxMemoryUsage 最大内存使用量(0 表示无限制)
|
|
||||||
*/
|
|
||||||
explicit TexturePool(Scene *scene, size_t maxMemoryUsage = 0);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 析构函数
|
|
||||||
*/
|
|
||||||
~TexturePool();
|
|
||||||
|
|
||||||
// 禁止拷贝
|
|
||||||
TexturePool(const TexturePool &) = delete;
|
|
||||||
TexturePool &operator=(const TexturePool &) = delete;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化纹理池
|
|
||||||
* @param scene 场景指针
|
|
||||||
* @param maxMemoryUsage 最大内存使用量(0 表示无限制)
|
|
||||||
*/
|
|
||||||
void init(Scene *scene, size_t maxMemoryUsage = 0);
|
|
||||||
|
|
||||||
// ========================================================================
|
|
||||||
// 纹理加载
|
|
||||||
// ========================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从文件加载纹理
|
|
||||||
* @param path 文件路径
|
|
||||||
* @param options 加载选项
|
|
||||||
* @return 纹理引用
|
|
||||||
*/
|
|
||||||
TextureRef load(const std::string &path,
|
|
||||||
const TextureLoadOptions &options = TextureLoadOptions());
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从文件加载纹理区域
|
|
||||||
* @param path 文件路径
|
|
||||||
* @param region 纹理区域
|
|
||||||
* @param options 加载选项
|
|
||||||
* @return 纹理引用
|
|
||||||
*/
|
|
||||||
TextureRef load(const std::string &path, const Rect ®ion,
|
|
||||||
const TextureLoadOptions &options = TextureLoadOptions());
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从内存加载纹理
|
|
||||||
* @param data 像素数据
|
|
||||||
* @param width 宽度
|
|
||||||
* @param height 高度
|
|
||||||
* @param channels 通道数
|
|
||||||
* @param key 缓存键
|
|
||||||
* @return 纹理引用
|
|
||||||
*/
|
|
||||||
TextureRef loadFromMemory(const uint8_t *data, int width, int height,
|
|
||||||
int channels, const std::string &key);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取或加载纹理
|
|
||||||
* @param path 文件路径
|
|
||||||
* @param options 加载选项
|
|
||||||
* @return 纹理引用
|
|
||||||
*/
|
|
||||||
TextureRef
|
|
||||||
getOrLoad(const std::string &path,
|
|
||||||
const TextureLoadOptions &options = TextureLoadOptions());
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取或加载纹理区域
|
|
||||||
* @param path 文件路径
|
|
||||||
* @param region 纹理区域
|
|
||||||
* @param options 加载选项
|
|
||||||
* @return 纹理引用
|
|
||||||
*/
|
|
||||||
TextureRef
|
|
||||||
getOrLoad(const std::string &path, const Rect ®ion,
|
|
||||||
const TextureLoadOptions &options = TextureLoadOptions());
|
|
||||||
|
|
||||||
// ========================================================================
|
|
||||||
// 引用计数管理
|
|
||||||
// ========================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 增加引用计数
|
|
||||||
* @param key 纹理键
|
|
||||||
* @return 是否成功
|
|
||||||
*/
|
|
||||||
bool addRef(const TextureKey &key);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 减少引用计数
|
|
||||||
* @param key 纹理键
|
|
||||||
* @return 减少后的引用计数
|
|
||||||
*/
|
|
||||||
uint32_t release(const TextureKey &key);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取引用计数
|
|
||||||
* @param key 纹理键
|
|
||||||
* @return 引用计数
|
|
||||||
*/
|
|
||||||
uint32_t getRefCount(const TextureKey &key) const;
|
|
||||||
|
|
||||||
// ========================================================================
|
|
||||||
// 缓存管理
|
|
||||||
// ========================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查纹理是否已缓存
|
|
||||||
* @param key 纹理键
|
|
||||||
* @return 是否已缓存
|
|
||||||
*/
|
|
||||||
bool isCached(const TextureKey &key) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从缓存中移除纹理
|
|
||||||
* @param key 纹理键
|
|
||||||
* @return 是否成功
|
|
||||||
*/
|
|
||||||
bool removeFromCache(const TextureKey &key);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 垃圾回收(移除引用计数为 0 的纹理)
|
|
||||||
* @return 移除的纹理数量
|
|
||||||
*/
|
|
||||||
size_t collectGarbage();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 清空所有缓存
|
|
||||||
*/
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
// ========================================================================
|
|
||||||
// 内存管理
|
|
||||||
// ========================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前内存使用量
|
|
||||||
* @return 内存使用量(字节)
|
|
||||||
*/
|
|
||||||
size_t getMemoryUsage() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置最大内存使用量
|
|
||||||
* @param maxMemory 最大内存使用量(0 表示无限制)
|
|
||||||
*/
|
|
||||||
void setMaxMemoryUsage(size_t maxMemory);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取最大内存使用量
|
|
||||||
* @return 最大内存使用量
|
|
||||||
*/
|
|
||||||
size_t getMaxMemoryUsage() const { return maxMemoryUsage_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 执行 LRU 淘汰
|
|
||||||
* @param targetMemory 目标内存使用量
|
|
||||||
* @return 淘汰的纹理数量
|
|
||||||
*/
|
|
||||||
size_t evictLRU(size_t targetMemory = 0);
|
|
||||||
|
|
||||||
// ========================================================================
|
|
||||||
// 统计信息
|
|
||||||
// ========================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取统计信息
|
|
||||||
* @return 统计信息
|
|
||||||
*/
|
|
||||||
Stats getStats() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 重置统计信息
|
|
||||||
*/
|
|
||||||
void resetStats();
|
|
||||||
|
|
||||||
private:
|
|
||||||
/**
|
|
||||||
* @brief 计算纹理内存大小
|
|
||||||
* @param texture 纹理对象
|
|
||||||
* @return 内存大小(字节)
|
|
||||||
*/
|
|
||||||
static size_t calculateTextureMemory(const Texture *texture);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查是否需要淘汰
|
|
||||||
* @return 是否需要淘汰
|
|
||||||
*/
|
|
||||||
bool needsEviction() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 尝试自动淘汰
|
|
||||||
*/
|
|
||||||
void tryAutoEvict();
|
|
||||||
|
|
||||||
Scene *scene_; // 场景指针
|
|
||||||
mutable std::mutex mutex_; // 互斥锁
|
|
||||||
std::unordered_map<TextureKey, TexturePoolEntry, TextureKeyHash>
|
|
||||||
cache_; // 纹理缓存
|
|
||||||
|
|
||||||
size_t maxMemoryUsage_; // 最大内存使用量
|
|
||||||
size_t currentMemoryUsage_; // 当前内存使用量
|
|
||||||
|
|
||||||
// 统计信息
|
|
||||||
mutable std::atomic<size_t> cacheHits_;
|
|
||||||
mutable std::atomic<size_t> cacheMisses_;
|
|
||||||
mutable std::atomic<size_t> evictionCount_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,238 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
|
||||||
#include <extra2d/core/math_types.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/event/event_dispatcher.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_renderer.h>
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
class Scene;
|
|
||||||
class Renderer;
|
|
||||||
struct RenderCommand;
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 节点基类 - 场景图的基础
|
|
||||||
// ============================================================================
|
|
||||||
class Node : public std::enable_shared_from_this<Node> {
|
|
||||||
public:
|
|
||||||
Node();
|
|
||||||
virtual ~Node();
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 层级管理
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void addChild(Ptr<Node> child);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 批量添加子节点
|
|
||||||
* @param children 子节点列表
|
|
||||||
*/
|
|
||||||
void addChildren(std::vector<Ptr<Node>> &&children);
|
|
||||||
|
|
||||||
void removeChild(Ptr<Node> child);
|
|
||||||
void removeChildByName(const std::string &name);
|
|
||||||
void detach();
|
|
||||||
void clearChildren();
|
|
||||||
|
|
||||||
Ptr<Node> getParent() const { return parent_.lock(); }
|
|
||||||
const std::vector<Ptr<Node>> &getChildren() const { return children_; }
|
|
||||||
Ptr<Node> findChild(const std::string &name) const;
|
|
||||||
Ptr<Node> findChildByTag(int tag) const;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 变换属性
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setPos(const Vec2 &pos);
|
|
||||||
void setPos(float x, float y);
|
|
||||||
Vec2 getPosition() const { return position_; }
|
|
||||||
|
|
||||||
void setRotation(float degrees);
|
|
||||||
float getRotation() const { return rotation_; }
|
|
||||||
|
|
||||||
void setScale(const Vec2 &scale);
|
|
||||||
void setScale(float scale);
|
|
||||||
void setScale(float x, float y);
|
|
||||||
Vec2 getScale() const { return scale_; }
|
|
||||||
|
|
||||||
void setAnchor(const Vec2 &anchor);
|
|
||||||
void setAnchor(float x, float y);
|
|
||||||
Vec2 getAnchor() const { return anchor_; }
|
|
||||||
|
|
||||||
void setSkew(const Vec2 &skew);
|
|
||||||
void setSkew(float x, float y);
|
|
||||||
Vec2 getSkew() const { return skew_; }
|
|
||||||
|
|
||||||
void setOpacity(float opacity);
|
|
||||||
float getOpacity() const { return opacity_; }
|
|
||||||
|
|
||||||
void setVisible(bool visible);
|
|
||||||
bool isVisible() const { return visible_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置颜色
|
|
||||||
* @param color RGB颜色
|
|
||||||
*/
|
|
||||||
void setColor(const Color3B &color);
|
|
||||||
Color3B getColor() const { return color_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置X轴翻转
|
|
||||||
*/
|
|
||||||
void setFlipX(bool flipX);
|
|
||||||
bool isFlipX() const { return flipX_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置Y轴翻转
|
|
||||||
*/
|
|
||||||
void setFlipY(bool flipY);
|
|
||||||
bool isFlipY() const { return flipY_; }
|
|
||||||
|
|
||||||
void setZOrder(int zOrder);
|
|
||||||
int getZOrder() const { return zOrder_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 世界变换
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
Vec2 toWorld(const Vec2 &localPos) const;
|
|
||||||
Vec2 toLocal(const Vec2 &worldPos) const;
|
|
||||||
|
|
||||||
glm::mat4 getLocalTransform() const;
|
|
||||||
glm::mat4 getWorldTransform() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 标记变换矩阵为脏状态,并传播到所有子节点
|
|
||||||
*/
|
|
||||||
void markTransformDirty();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 批量更新变换矩阵
|
|
||||||
* 在渲染前统一计算所有脏节点的变换矩阵,避免逐节点计算时的重复递归
|
|
||||||
*/
|
|
||||||
void batchTransforms();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取变换脏标记状态
|
|
||||||
*/
|
|
||||||
bool isTransformDirty() const { return transformDirty_; }
|
|
||||||
bool isWorldTransformDirty() const { return worldTransformDirty_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 名称和标签
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setName(const std::string &name) { name_ = name; }
|
|
||||||
const std::string &getName() const { return name_; }
|
|
||||||
|
|
||||||
void setTag(int tag) { tag_ = tag; }
|
|
||||||
int getTag() const { return tag_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 生命周期回调
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
virtual void onEnter();
|
|
||||||
virtual void onExit();
|
|
||||||
virtual void onUpdate(float dt);
|
|
||||||
virtual void onRender(Renderer &renderer);
|
|
||||||
virtual void onAttachToScene(Scene *scene);
|
|
||||||
virtual void onDetachFromScene();
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 边界框
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
virtual Rect getBounds() const;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 事件系统
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
EventDispatcher &getEventDispatcher() { return eventDispatcher_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 内部方法
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void update(float dt);
|
|
||||||
void render(Renderer &renderer);
|
|
||||||
void sortChildren();
|
|
||||||
|
|
||||||
bool isRunning() const { return running_; }
|
|
||||||
Scene *getScene() const { return scene_; }
|
|
||||||
|
|
||||||
// 多线程渲染命令收集
|
|
||||||
virtual void collectRenderCommands(std::vector<RenderCommand> &commands,
|
|
||||||
int parentZOrder = 0);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
// 子类重写
|
|
||||||
virtual void onDraw(Renderer &renderer) {}
|
|
||||||
virtual void onUpdateNode(float dt) {}
|
|
||||||
virtual void generateRenderCommand(std::vector<RenderCommand> &commands,
|
|
||||||
int zOrder) {};
|
|
||||||
|
|
||||||
// 供子类访问的内部状态
|
|
||||||
Vec2 &getPositionRef() { return position_; }
|
|
||||||
Vec2 &getScaleRef() { return scale_; }
|
|
||||||
Vec2 &getAnchorRef() { return anchor_; }
|
|
||||||
float getRotationRef() { return rotation_; }
|
|
||||||
float getOpacityRef() { return opacity_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
// ==========================================================================
|
|
||||||
// 成员变量按类型大小降序排列,减少内存对齐填充
|
|
||||||
// 64位系统对齐:std::string(32) > glm::mat4(64) > std::vector(24) >
|
|
||||||
// double(8) > float(4) > int(4) > bool(1)
|
|
||||||
// ==========================================================================
|
|
||||||
|
|
||||||
// 1. 大块内存(64字节)
|
|
||||||
mutable glm::mat4 localTransform_; // 64 bytes
|
|
||||||
mutable glm::mat4 worldTransform_; // 64 bytes
|
|
||||||
|
|
||||||
// 2. 字符串和容器(24-32字节)
|
|
||||||
std::string name_; // 32 bytes
|
|
||||||
std::vector<Ptr<Node>> children_; // 24 bytes
|
|
||||||
|
|
||||||
// 3. 子节点索引(加速查找)
|
|
||||||
std::unordered_map<std::string, WeakPtr<Node>> nameIndex_; // 56 bytes
|
|
||||||
std::unordered_map<int, WeakPtr<Node>> tagIndex_; // 56 bytes
|
|
||||||
|
|
||||||
// 4. 事件分发器
|
|
||||||
EventDispatcher eventDispatcher_; // 大小取决于实现
|
|
||||||
|
|
||||||
// 5. 父节点引用
|
|
||||||
WeakPtr<Node> parent_; // 16 bytes
|
|
||||||
|
|
||||||
// 7. 变换属性(按访问频率分组)
|
|
||||||
Vec2 position_ = Vec2::Zero(); // 8 bytes
|
|
||||||
Vec2 scale_ = Vec2(1.0f, 1.0f); // 8 bytes
|
|
||||||
Vec2 anchor_ = Vec2(0.5f, 0.5f); // 8 bytes
|
|
||||||
Vec2 skew_ = Vec2::Zero(); // 8 bytes
|
|
||||||
|
|
||||||
// 8. 浮点属性
|
|
||||||
float rotation_ = 0.0f; // 4 bytes
|
|
||||||
float opacity_ = 1.0f; // 4 bytes
|
|
||||||
|
|
||||||
// 10. 颜色属性
|
|
||||||
Color3B color_ = Color3B(255, 255, 255); // 3 bytes
|
|
||||||
|
|
||||||
// 11. 整数属性
|
|
||||||
int zOrder_ = 0; // 4 bytes
|
|
||||||
int tag_ = -1; // 4 bytes
|
|
||||||
|
|
||||||
// 12. 布尔属性
|
|
||||||
bool flipX_ = false; // 1 byte
|
|
||||||
bool flipY_ = false; // 1 byte
|
|
||||||
|
|
||||||
// 13. 场景指针
|
|
||||||
Scene *scene_ = nullptr; // 8 bytes
|
|
||||||
|
|
||||||
// 14. 布尔标志(打包在一起)
|
|
||||||
mutable bool transformDirty_ = true; // 1 byte
|
|
||||||
mutable bool worldTransformDirty_ = true; // 1 byte
|
|
||||||
bool childrenOrderDirty_ = false; // 1 byte
|
|
||||||
bool visible_ = true; // 1 byte
|
|
||||||
bool running_ = false; // 1 byte
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,89 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
|
||||||
#include <extra2d/graphics/camera/camera.h>
|
|
||||||
#include <extra2d/scene/node.h>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// 前向声明
|
|
||||||
struct RenderCommand;
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 场景类 - 节点容器,管理整个场景图
|
|
||||||
// ============================================================================
|
|
||||||
class Scene : public Node {
|
|
||||||
public:
|
|
||||||
Scene();
|
|
||||||
~Scene() override = default;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 场景属性
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setBackgroundColor(const Color &color) { backgroundColor_ = color; }
|
|
||||||
Color getBackgroundColor() const { return backgroundColor_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 摄像机
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setCamera(Ptr<Camera> camera);
|
|
||||||
Ptr<Camera> getCamera() const { return camera_; }
|
|
||||||
|
|
||||||
Camera *getActiveCamera() const {
|
|
||||||
return camera_ ? camera_.get() : defaultCamera_.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 视口和尺寸
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setViewportSize(float width, float height);
|
|
||||||
void setViewportSize(const Size &size);
|
|
||||||
Size getViewportSize() const { return viewportSize_; }
|
|
||||||
|
|
||||||
float getWidth() const { return viewportSize_.width; }
|
|
||||||
float getHeight() const { return viewportSize_.height; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 场景状态
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
bool isPaused() const { return paused_; }
|
|
||||||
void pause() { paused_ = true; }
|
|
||||||
void resume() { paused_ = false; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 渲染和更新
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void renderScene(Renderer &renderer);
|
|
||||||
virtual void renderContent(Renderer &renderer);
|
|
||||||
void updateScene(float dt);
|
|
||||||
void collectRenderCommands(std::vector<RenderCommand> &commands,
|
|
||||||
int parentZOrder = 0) override;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 静态创建方法
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
static Ptr<Scene> create();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void onEnter() override;
|
|
||||||
void onExit() override;
|
|
||||||
|
|
||||||
// 过渡场景生命周期回调(供 TransitionScene 使用)
|
|
||||||
virtual void onExitTransitionDidStart() {}
|
|
||||||
virtual void onEnterTransitionDidFinish() {}
|
|
||||||
|
|
||||||
friend class SceneManager;
|
|
||||||
friend class TransitionScene;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Color backgroundColor_ = Colors::Black;
|
|
||||||
Size viewportSize_ = Size::Zero();
|
|
||||||
|
|
||||||
Ptr<Camera> camera_;
|
|
||||||
Ptr<Camera> defaultCamera_;
|
|
||||||
|
|
||||||
bool paused_ = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,189 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/scene/scene.h>
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <stack>
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
struct RenderCommand;
|
|
||||||
class TransitionScene;
|
|
||||||
enum class TransitionType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 场景管理器 - 管理场景的生命周期和切换
|
|
||||||
*/
|
|
||||||
class SceneManager {
|
|
||||||
public:
|
|
||||||
using TransitionCallback = std::function<void()>;
|
|
||||||
|
|
||||||
static SceneManager &get();
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 基本场景操作(无过渡效果)
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void runWithScene(Ptr<Scene> scene);
|
|
||||||
void replaceScene(Ptr<Scene> scene);
|
|
||||||
void pushScene(Ptr<Scene> scene);
|
|
||||||
void popScene();
|
|
||||||
void popToRootScene();
|
|
||||||
void popToScene(const std::string &name);
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 带过渡效果的场景操作
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 替换当前场景(带过渡效果)
|
|
||||||
* @param scene 新场景
|
|
||||||
* @param transition 过渡类型
|
|
||||||
* @param duration 过渡持续时间(秒)
|
|
||||||
*/
|
|
||||||
void replaceScene(Ptr<Scene> scene, TransitionType transition,
|
|
||||||
float duration = 0.5f);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 压入新场景(带过渡效果)
|
|
||||||
* @param scene 新场景
|
|
||||||
* @param transition 过渡类型
|
|
||||||
* @param duration 过渡持续时间(秒)
|
|
||||||
*/
|
|
||||||
void pushScene(Ptr<Scene> scene, TransitionType transition,
|
|
||||||
float duration = 0.5f);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 弹出当前场景(带过渡效果)
|
|
||||||
* @param transition 过渡类型
|
|
||||||
* @param duration 过渡持续时间(秒)
|
|
||||||
*/
|
|
||||||
void popScene(TransitionType transition, float duration = 0.5f);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 弹出到根场景(带过渡效果)
|
|
||||||
* @param transition 过渡类型
|
|
||||||
* @param duration 过渡持续时间(秒)
|
|
||||||
*/
|
|
||||||
void popToRootScene(TransitionType transition, float duration = 0.5f);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 弹出到指定场景(带过渡效果)
|
|
||||||
* @param name 目标场景名称
|
|
||||||
* @param transition 过渡类型
|
|
||||||
* @param duration 过渡持续时间(秒)
|
|
||||||
*/
|
|
||||||
void popToScene(const std::string &name, TransitionType transition,
|
|
||||||
float duration = 0.5f);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 使用自定义过渡场景进入场景
|
|
||||||
* @param scene 新场景
|
|
||||||
* @param transitionScene 过渡场景
|
|
||||||
*/
|
|
||||||
void enterScene(Ptr<Scene> scene, Ptr<TransitionScene> transitionScene);
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 场景查询
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
Ptr<Scene> getCurrentScene() const;
|
|
||||||
Ptr<Scene> getPreviousScene() const;
|
|
||||||
Ptr<Scene> getRootScene() const;
|
|
||||||
Ptr<Scene> getSceneByName(const std::string &name) const;
|
|
||||||
|
|
||||||
size_t getSceneCount() const { return sceneStack_.size(); }
|
|
||||||
bool isEmpty() const { return sceneStack_.empty(); }
|
|
||||||
bool hasScene(const std::string &name) const;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 更新和渲染
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void update(float dt);
|
|
||||||
void render(Renderer &renderer);
|
|
||||||
void collectRenderCommands(std::vector<RenderCommand> &commands);
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 过渡状态
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
bool isTransitioning() const { return isTransitioning_; }
|
|
||||||
void setTransitionCallback(TransitionCallback callback) {
|
|
||||||
transitionCallback_ = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
void end();
|
|
||||||
void purgeCachedScenes();
|
|
||||||
|
|
||||||
public:
|
|
||||||
SceneManager() = default;
|
|
||||||
~SceneManager() = default;
|
|
||||||
SceneManager(const SceneManager &) = delete;
|
|
||||||
SceneManager &operator=(const SceneManager &) = delete;
|
|
||||||
|
|
||||||
void enterScene(Ptr<Scene> scene);
|
|
||||||
|
|
||||||
private:
|
|
||||||
/**
|
|
||||||
* @brief 启动过渡
|
|
||||||
* @param from 源场景
|
|
||||||
* @param to 目标场景
|
|
||||||
* @param type 过渡类型
|
|
||||||
* @param duration 过渡持续时间
|
|
||||||
* @param stackAction 过渡完成后的栈操作
|
|
||||||
*/
|
|
||||||
void startTransition(Ptr<Scene> from, Ptr<Scene> to, TransitionType type,
|
|
||||||
float duration, std::function<void()> stackAction);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建过渡场景
|
|
||||||
* @param type 过渡类型
|
|
||||||
* @param duration 过渡持续时间
|
|
||||||
* @param inScene 目标场景
|
|
||||||
* @return 过渡场景智能指针
|
|
||||||
*/
|
|
||||||
Ptr<TransitionScene> createTransitionScene(TransitionType type,
|
|
||||||
float duration,
|
|
||||||
Ptr<Scene> inScene);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 完成过渡
|
|
||||||
*/
|
|
||||||
void finishTransition();
|
|
||||||
|
|
||||||
void doSceneSwitch();
|
|
||||||
void dispatchPointerEvents(Scene &scene);
|
|
||||||
void setupEventListeners();
|
|
||||||
|
|
||||||
std::stack<Ptr<Scene>> sceneStack_;
|
|
||||||
std::unordered_map<std::string, Ptr<Scene>> namedScenes_;
|
|
||||||
|
|
||||||
bool isTransitioning_ = false;
|
|
||||||
TransitionCallback transitionCallback_;
|
|
||||||
|
|
||||||
Ptr<Scene> nextScene_;
|
|
||||||
bool sendCleanupToScene_ = false;
|
|
||||||
|
|
||||||
Ptr<TransitionScene> activeTransitionScene_;
|
|
||||||
std::function<void()> transitionStackAction_;
|
|
||||||
|
|
||||||
Node *hoverTarget_ = nullptr;
|
|
||||||
Node *captureTarget_ = nullptr;
|
|
||||||
Vec2 lastPointerWorld_ = Vec2::Zero();
|
|
||||||
bool hasLastPointerWorld_ = false;
|
|
||||||
|
|
||||||
// 鼠标状态(通过事件更新)
|
|
||||||
Vec2 mousePos_ = Vec2::Zero();
|
|
||||||
Vec2 mouseDelta_ = Vec2::Zero();
|
|
||||||
float scrollDelta_ = 0.0f;
|
|
||||||
bool mouseLeftPressed_ = false;
|
|
||||||
bool mouseLeftReleased_ = false;
|
|
||||||
bool mouseLeftDown_ = false;
|
|
||||||
ListenerId mouseMoveListener_ = 0;
|
|
||||||
ListenerId mousePressListener_ = 0;
|
|
||||||
ListenerId mouseReleaseListener_ = 0;
|
|
||||||
ListenerId scrollListener_ = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,104 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
|
||||||
#include <extra2d/core/math_types.h>
|
|
||||||
#include <extra2d/scene/node.h>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 形状类型
|
|
||||||
// ============================================================================
|
|
||||||
enum class ShapeType { Point, Line, Rect, Circle, Triangle, Polygon };
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 形状节点 - 用于绘制几何形状
|
|
||||||
// ============================================================================
|
|
||||||
class ShapeNode : public Node {
|
|
||||||
public:
|
|
||||||
ShapeNode();
|
|
||||||
~ShapeNode() override = default;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 静态创建方法
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
static Ptr<ShapeNode> create();
|
|
||||||
|
|
||||||
// 点
|
|
||||||
static Ptr<ShapeNode> createPoint(const Vec2 &pos, const Color &color);
|
|
||||||
|
|
||||||
// 线
|
|
||||||
static Ptr<ShapeNode> createLine(const Vec2 &start, const Vec2 &end,
|
|
||||||
const Color &color, float width = 1.0f);
|
|
||||||
|
|
||||||
// 矩形
|
|
||||||
static Ptr<ShapeNode> createRect(const Rect &rect, const Color &color,
|
|
||||||
float width = 1.0f);
|
|
||||||
static Ptr<ShapeNode> createFilledRect(const Rect &rect, const Color &color);
|
|
||||||
|
|
||||||
// 圆形
|
|
||||||
static Ptr<ShapeNode> createCircle(const Vec2 ¢er, float radius,
|
|
||||||
const Color &color, int segments = 32,
|
|
||||||
float width = 1.0f);
|
|
||||||
static Ptr<ShapeNode> createFilledCircle(const Vec2 ¢er, float radius,
|
|
||||||
const Color &color,
|
|
||||||
int segments = 32);
|
|
||||||
|
|
||||||
// 三角形
|
|
||||||
static Ptr<ShapeNode> createTriangle(const Vec2 &p1, const Vec2 &p2,
|
|
||||||
const Vec2 &p3, const Color &color,
|
|
||||||
float width = 1.0f);
|
|
||||||
static Ptr<ShapeNode> createFilledTriangle(const Vec2 &p1, const Vec2 &p2,
|
|
||||||
const Vec2 &p3,
|
|
||||||
const Color &color);
|
|
||||||
|
|
||||||
// 多边形
|
|
||||||
static Ptr<ShapeNode> createPolygon(const std::vector<Vec2> &points,
|
|
||||||
const Color &color, float width = 1.0f);
|
|
||||||
static Ptr<ShapeNode> createFilledPolygon(const std::vector<Vec2> &points,
|
|
||||||
const Color &color);
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 属性设置
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setShapeType(ShapeType type) { shapeType_ = type; }
|
|
||||||
ShapeType getShapeType() const { return shapeType_; }
|
|
||||||
|
|
||||||
void setColor(const Color &color) { color_ = color; }
|
|
||||||
Color getColor() const { return color_; }
|
|
||||||
|
|
||||||
void setFilled(bool filled) { filled_ = filled; }
|
|
||||||
bool isFilled() const { return filled_; }
|
|
||||||
|
|
||||||
void setLineWidth(float width) { lineWidth_ = width; }
|
|
||||||
float getLineWidth() const { return lineWidth_; }
|
|
||||||
|
|
||||||
void setSegments(int segments) { segments_ = segments; }
|
|
||||||
int getSegments() const { return segments_; }
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 点设置
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void setPoints(const std::vector<Vec2> &points);
|
|
||||||
const std::vector<Vec2> &getPoints() const { return points_; }
|
|
||||||
void addPoint(const Vec2 &point);
|
|
||||||
void clearPoints();
|
|
||||||
|
|
||||||
Rect getBounds() const override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void onDraw(Renderer &renderer) override;
|
|
||||||
void generateRenderCommand(std::vector<RenderCommand> &commands,
|
|
||||||
int zOrder) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
ShapeType shapeType_ = ShapeType::Rect;
|
|
||||||
Color color_ = Colors::White;
|
|
||||||
bool filled_ = false;
|
|
||||||
float lineWidth_ = 1.0f;
|
|
||||||
int segments_ = 32;
|
|
||||||
std::vector<Vec2> points_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/graphics/texture/texture.h>
|
|
||||||
#include <extra2d/scene/node.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 精灵节点
|
|
||||||
// ============================================================================
|
|
||||||
class Sprite : public Node {
|
|
||||||
public:
|
|
||||||
Sprite();
|
|
||||||
explicit Sprite(Ptr<Texture> texture);
|
|
||||||
~Sprite() override = default;
|
|
||||||
|
|
||||||
// 纹理
|
|
||||||
void setTexture(Ptr<Texture> texture);
|
|
||||||
Ptr<Texture> getTexture() const { return texture_; }
|
|
||||||
|
|
||||||
// 纹理矩形 (用于图集)
|
|
||||||
void setTextureRect(const Rect &rect);
|
|
||||||
Rect getTextureRect() const { return textureRect_; }
|
|
||||||
|
|
||||||
// 颜色混合
|
|
||||||
void setColor(const Color &color);
|
|
||||||
Color getColor() const { return color_; }
|
|
||||||
|
|
||||||
// 翻转
|
|
||||||
void setFlipX(bool flip);
|
|
||||||
void setFlipY(bool flip);
|
|
||||||
bool isFlipX() const { return flipX_; }
|
|
||||||
bool isFlipY() const { return flipY_; }
|
|
||||||
|
|
||||||
// 静态创建方法
|
|
||||||
static Ptr<Sprite> create();
|
|
||||||
static Ptr<Sprite> create(Ptr<Texture> texture);
|
|
||||||
static Ptr<Sprite> create(Ptr<Texture> texture, const Rect &rect);
|
|
||||||
|
|
||||||
Rect getBounds() const override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void onDraw(Renderer &renderer) override;
|
|
||||||
void generateRenderCommand(std::vector<RenderCommand> &commands,
|
|
||||||
int zOrder) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Ptr<Texture> texture_;
|
|
||||||
Rect textureRect_;
|
|
||||||
Color color_ = Colors::White;
|
|
||||||
bool flipX_ = false;
|
|
||||||
bool flipY_ = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/scene/transition_scene.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 方块/马赛克过渡场景
|
|
||||||
// 实现原理:
|
|
||||||
// 1. 将屏幕分成多个方块
|
|
||||||
// 2. 方块逐个消失,显示新场景
|
|
||||||
// ============================================================================
|
|
||||||
class TransitionBoxScene : public TransitionScene {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief 创建方块过渡场景
|
|
||||||
* @param duration 过渡持续时间(秒)
|
|
||||||
* @param inScene 要进入的目标场景
|
|
||||||
* @param divisions 方块分割数(默认为 8,表示 8x8 网格)
|
|
||||||
*/
|
|
||||||
TransitionBoxScene(float duration, Ptr<Scene> inScene, int divisions = 8);
|
|
||||||
|
|
||||||
static Ptr<TransitionBoxScene> create(float duration, Ptr<Scene> inScene,
|
|
||||||
int divisions = 8);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void onTransitionStart() override;
|
|
||||||
void renderContent(Renderer &renderer) override;
|
|
||||||
void updateTransition(float dt) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
int divisions_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
|
||||||
#include <extra2d/scene/transition_scene.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 淡入淡出过渡场景
|
|
||||||
// 实现原理:
|
|
||||||
// 1. 创建一个纯色精灵作为遮罩层
|
|
||||||
// 2. 第一阶段:遮罩从透明淡入到不透明(黑屏),同时显示旧场景
|
|
||||||
// 3. 切换显示新场景
|
|
||||||
// 4. 第二阶段:遮罩从不透明淡出到透明,显示新场景
|
|
||||||
// ============================================================================
|
|
||||||
class TransitionFadeScene : public TransitionScene {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief 创建淡入淡出过渡场景
|
|
||||||
* @param duration 过渡持续时间(秒)
|
|
||||||
* @param inScene 要进入的目标场景
|
|
||||||
* @param color 遮罩颜色(默认为黑色)
|
|
||||||
*/
|
|
||||||
TransitionFadeScene(float duration, Ptr<Scene> inScene,
|
|
||||||
const Color &color = Colors::Black);
|
|
||||||
|
|
||||||
static Ptr<TransitionFadeScene> create(float duration, Ptr<Scene> inScene,
|
|
||||||
const Color &color = Colors::Black);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
/**
|
|
||||||
* @brief 启动过渡动画
|
|
||||||
* 创建遮罩层并运行动作序列
|
|
||||||
*/
|
|
||||||
void onTransitionStart() override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 更新过渡进度
|
|
||||||
* @param dt 帧间隔时间(秒)
|
|
||||||
*/
|
|
||||||
void updateTransition(float dt) override;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 渲染内容
|
|
||||||
* 根据进度控制新旧场景的显示
|
|
||||||
*/
|
|
||||||
void renderContent(Renderer &renderer) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
/**
|
|
||||||
* @brief 隐藏退出场景,显示进入场景
|
|
||||||
*/
|
|
||||||
void hideOutShowIn();
|
|
||||||
|
|
||||||
Color maskColor_; // 遮罩颜色
|
|
||||||
bool hasSwitched_ = false; // 是否已经切换场景
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/scene/transition_scene.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 翻页过渡场景
|
|
||||||
// 实现原理:
|
|
||||||
// 1. 前半段:旧场景翻转消失
|
|
||||||
// 2. 后半段:新场景翻转出现
|
|
||||||
// ============================================================================
|
|
||||||
class TransitionFlipScene : public TransitionScene {
|
|
||||||
public:
|
|
||||||
enum class Axis { Horizontal, Vertical };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建翻页过渡场景
|
|
||||||
* @param duration 过渡持续时间(秒)
|
|
||||||
* @param inScene 要进入的目标场景
|
|
||||||
* @param axis 翻转轴(水平或垂直)
|
|
||||||
*/
|
|
||||||
TransitionFlipScene(float duration, Ptr<Scene> inScene,
|
|
||||||
Axis axis = Axis::Horizontal);
|
|
||||||
|
|
||||||
static Ptr<TransitionFlipScene> create(float duration, Ptr<Scene> inScene,
|
|
||||||
Axis axis = Axis::Horizontal);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void onTransitionStart() override;
|
|
||||||
void renderContent(Renderer &renderer) override;
|
|
||||||
void updateTransition(float dt) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
Axis axis_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/scene/transition_scene.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 缩放过渡场景
|
|
||||||
// 实现原理:
|
|
||||||
// 1. 旧场景缩小消失
|
|
||||||
// 2. 新场景放大出现
|
|
||||||
// ============================================================================
|
|
||||||
class TransitionScaleScene : public TransitionScene {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief 创建缩放过渡场景
|
|
||||||
* @param duration 过渡持续时间(秒)
|
|
||||||
* @param inScene 要进入的目标场景
|
|
||||||
*/
|
|
||||||
TransitionScaleScene(float duration, Ptr<Scene> inScene);
|
|
||||||
|
|
||||||
static Ptr<TransitionScaleScene> create(float duration, Ptr<Scene> inScene);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void onTransitionStart() override;
|
|
||||||
void renderContent(Renderer &renderer) override;
|
|
||||||
void updateTransition(float dt) override;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,150 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/scene/scene.h>
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 过渡方向
|
|
||||||
// ============================================================================
|
|
||||||
enum class TransitionDirection { Left, Right, Up, Down };
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 过渡效果类型
|
|
||||||
// ============================================================================
|
|
||||||
enum class TransitionType {
|
|
||||||
None,
|
|
||||||
Fade,
|
|
||||||
SlideLeft,
|
|
||||||
SlideRight,
|
|
||||||
SlideUp,
|
|
||||||
SlideDown,
|
|
||||||
Scale,
|
|
||||||
Flip,
|
|
||||||
Box
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 过渡场景基类 - 继承自 Scene,作为中介场景管理过渡效果
|
|
||||||
// 设计参考 Cocos2d-x 的 TransitionScene
|
|
||||||
// ============================================================================
|
|
||||||
class TransitionScene : public Scene {
|
|
||||||
public:
|
|
||||||
using FinishCallback = std::function<void()>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建过渡场景
|
|
||||||
* @param duration 过渡持续时间(秒)
|
|
||||||
* @param inScene 要进入的目标场景
|
|
||||||
*/
|
|
||||||
TransitionScene(float duration, Ptr<Scene> inScene);
|
|
||||||
~TransitionScene() override = default;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 场景管理
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取要进入的场景
|
|
||||||
*/
|
|
||||||
Ptr<Scene> getInScene() const { return inScene_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取要退出的场景
|
|
||||||
*/
|
|
||||||
Ptr<Scene> getOutScene() const { return outScene_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置要退出的场景(由 SceneManager 调用)
|
|
||||||
*/
|
|
||||||
void setOutScene(Ptr<Scene> outScene) { outScene_ = outScene; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置过渡完成回调
|
|
||||||
*/
|
|
||||||
void setFinishCallback(FinishCallback callback) {
|
|
||||||
finishCallback_ = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取过渡持续时间
|
|
||||||
*/
|
|
||||||
float getDuration() const { return duration_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前进度 [0, 1]
|
|
||||||
*/
|
|
||||||
float getProgress() const { return progress_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 是否已完成
|
|
||||||
*/
|
|
||||||
bool isFinished() const { return isFinished_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 是否已取消
|
|
||||||
*/
|
|
||||||
bool isCancelled() const { return isCancelled_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 完成过渡,通知 SceneManager 切换到目标场景
|
|
||||||
*/
|
|
||||||
void finish();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 取消过渡
|
|
||||||
* @param immediate 是否立即完成(false则回滚到原场景)
|
|
||||||
*/
|
|
||||||
void cancel(bool immediate = false);
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 渲染 - 在 TransitionScene 上渲染新旧两个子场景
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void renderContent(Renderer &renderer) override;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// 生命周期
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
void onEnter() override;
|
|
||||||
void onExit() override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
/**
|
|
||||||
* @brief 子类实现具体的过渡逻辑
|
|
||||||
* 在 onEnter 中设置动画,动画完成后调用 finish()
|
|
||||||
*/
|
|
||||||
virtual void onTransitionStart() = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制源场景(旧场景)
|
|
||||||
*/
|
|
||||||
virtual void drawOutScene(Renderer &renderer);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制目标场景(新场景)
|
|
||||||
*/
|
|
||||||
virtual void drawInScene(Renderer &renderer);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 更新过渡进度(子类重写此方法更新动画)
|
|
||||||
* @param dt 帧间隔时间(秒)
|
|
||||||
*/
|
|
||||||
virtual void updateTransition(float dt);
|
|
||||||
|
|
||||||
float duration_;
|
|
||||||
float elapsed_ = 0.0f;
|
|
||||||
float progress_ = 0.0f;
|
|
||||||
bool isFinished_ = false;
|
|
||||||
bool isCancelled_ = false;
|
|
||||||
|
|
||||||
Ptr<Scene> inScene_; // 要进入的场景
|
|
||||||
Ptr<Scene> outScene_; // 要退出的场景
|
|
||||||
|
|
||||||
FinishCallback finishCallback_;
|
|
||||||
FinishCallback cancelCallback_; // 取消回调
|
|
||||||
|
|
||||||
friend class SceneManager;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/scene/transition_scene.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 滑动过渡场景
|
|
||||||
// 实现原理:
|
|
||||||
// 1. 旧场景向指定方向滑出
|
|
||||||
// 2. 新场景从相反方向滑入
|
|
||||||
// ============================================================================
|
|
||||||
class TransitionSlideScene : public TransitionScene {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief 创建滑动过渡场景
|
|
||||||
* @param duration 过渡持续时间(秒)
|
|
||||||
* @param inScene 要进入的目标场景
|
|
||||||
* @param direction 滑动方向
|
|
||||||
*/
|
|
||||||
TransitionSlideScene(float duration, Ptr<Scene> inScene,
|
|
||||||
TransitionDirection direction);
|
|
||||||
|
|
||||||
static Ptr<TransitionSlideScene> create(float duration, Ptr<Scene> inScene,
|
|
||||||
TransitionDirection direction);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void onTransitionStart() override;
|
|
||||||
void renderContent(Renderer &renderer) override;
|
|
||||||
void updateTransition(float dt) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
TransitionDirection direction_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,124 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/service_interface.h>
|
|
||||||
#include <extra2d/core/service_locator.h>
|
|
||||||
#include <extra2d/graphics/camera/camera.h>
|
|
||||||
#include <extra2d/graphics/camera/viewport_adapter.h>
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 相机服务接口
|
|
||||||
*/
|
|
||||||
class ICameraService : public IService {
|
|
||||||
public:
|
|
||||||
virtual ~ICameraService() = default;
|
|
||||||
|
|
||||||
virtual void setPosition(const Vec2& position) = 0;
|
|
||||||
virtual void setPosition(float x, float y) = 0;
|
|
||||||
virtual Vec2 getPosition() const = 0;
|
|
||||||
|
|
||||||
virtual void setRotation(float degrees) = 0;
|
|
||||||
virtual float getRotation() const = 0;
|
|
||||||
|
|
||||||
virtual void setZoom(float zoom) = 0;
|
|
||||||
virtual float getZoom() const = 0;
|
|
||||||
|
|
||||||
virtual void setViewport(float left, float right, float bottom, float top) = 0;
|
|
||||||
virtual Rect getViewport() const = 0;
|
|
||||||
|
|
||||||
virtual glm::mat4 getViewMatrix() const = 0;
|
|
||||||
virtual glm::mat4 getProjectionMatrix() const = 0;
|
|
||||||
virtual glm::mat4 getViewProjectionMatrix() const = 0;
|
|
||||||
|
|
||||||
virtual Vec2 screenToWorld(const Vec2& screenPos) const = 0;
|
|
||||||
virtual Vec2 worldToScreen(const Vec2& worldPos) const = 0;
|
|
||||||
|
|
||||||
virtual void move(const Vec2& offset) = 0;
|
|
||||||
virtual void move(float x, float y) = 0;
|
|
||||||
|
|
||||||
virtual void setBounds(const Rect& bounds) = 0;
|
|
||||||
virtual void clearBounds() = 0;
|
|
||||||
|
|
||||||
virtual void lookAt(const Vec2& target) = 0;
|
|
||||||
|
|
||||||
virtual void setViewportConfig(const ViewportConfig& config) = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 使用 Lambda 配置视口
|
|
||||||
* @param configFn 配置函数,接收 ViewportConfig 引用进行修改
|
|
||||||
*/
|
|
||||||
virtual void setViewportConfig(std::function<void(ViewportConfig&)> configFn) = 0;
|
|
||||||
|
|
||||||
virtual const ViewportConfig& getViewportConfig() const = 0;
|
|
||||||
virtual void updateViewport(int screenWidth, int screenHeight) = 0;
|
|
||||||
virtual const ViewportResult& getViewportResult() const = 0;
|
|
||||||
|
|
||||||
virtual void applyViewportAdapter() = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 相机服务实现
|
|
||||||
*/
|
|
||||||
class CameraService : public ICameraService {
|
|
||||||
public:
|
|
||||||
CameraService();
|
|
||||||
explicit CameraService(float left, float right, float bottom, float top);
|
|
||||||
~CameraService() override = default;
|
|
||||||
|
|
||||||
ServiceInfo getServiceInfo() const override;
|
|
||||||
|
|
||||||
bool initialize() override;
|
|
||||||
void shutdown() override;
|
|
||||||
|
|
||||||
void setPosition(const Vec2& position) override;
|
|
||||||
void setPosition(float x, float y) override;
|
|
||||||
Vec2 getPosition() const override;
|
|
||||||
|
|
||||||
void setRotation(float degrees) override;
|
|
||||||
float getRotation() const override;
|
|
||||||
|
|
||||||
void setZoom(float zoom) override;
|
|
||||||
float getZoom() const override;
|
|
||||||
|
|
||||||
void setViewport(float left, float right, float bottom, float top) override;
|
|
||||||
Rect getViewport() const override;
|
|
||||||
|
|
||||||
glm::mat4 getViewMatrix() const override;
|
|
||||||
glm::mat4 getProjectionMatrix() const override;
|
|
||||||
glm::mat4 getViewProjectionMatrix() const override;
|
|
||||||
|
|
||||||
Vec2 screenToWorld(const Vec2& screenPos) const override;
|
|
||||||
Vec2 worldToScreen(const Vec2& worldPos) const override;
|
|
||||||
|
|
||||||
void move(const Vec2& offset) override;
|
|
||||||
void move(float x, float y) override;
|
|
||||||
|
|
||||||
void setBounds(const Rect& bounds) override;
|
|
||||||
void clearBounds() override;
|
|
||||||
|
|
||||||
void lookAt(const Vec2& target) override;
|
|
||||||
|
|
||||||
void setViewportConfig(const ViewportConfig& config) override;
|
|
||||||
void setViewportConfig(std::function<void(ViewportConfig&)> configFn) override;
|
|
||||||
const ViewportConfig& getViewportConfig() const override;
|
|
||||||
void updateViewport(int screenWidth, int screenHeight) override;
|
|
||||||
const ViewportResult& getViewportResult() const override;
|
|
||||||
|
|
||||||
void applyViewportAdapter() override;
|
|
||||||
|
|
||||||
Camera& getCamera() { return camera_; }
|
|
||||||
const Camera& getCamera() const { return camera_; }
|
|
||||||
ViewportAdapter& getViewportAdapter() { return viewportAdapter_; }
|
|
||||||
const ViewportAdapter& getViewportAdapter() const { return viewportAdapter_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
Camera camera_;
|
|
||||||
ViewportAdapter viewportAdapter_;
|
|
||||||
|
|
||||||
// 服务注册元数据
|
|
||||||
E2D_AUTO_REGISTER_SERVICE(ICameraService, CameraService);
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -272,21 +272,13 @@ std::string format_str(const char *fmt, Args &&...args) {
|
||||||
} \
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
#define E2D_LOG_TRACE(...) E2D_LOG(::extra2d::LogLevel::Trace, __VA_ARGS__)
|
#define E2D_TRACE(...) E2D_LOG(::extra2d::LogLevel::Trace, __VA_ARGS__)
|
||||||
#define E2D_LOG_DEBUG(...) E2D_LOG(::extra2d::LogLevel::Debug, __VA_ARGS__)
|
#define E2D_DEBUG(...) E2D_LOG(::extra2d::LogLevel::Debug, __VA_ARGS__)
|
||||||
#define E2D_LOG_INFO(...) E2D_LOG(::extra2d::LogLevel::Info, __VA_ARGS__)
|
#define E2D_INFO(...) E2D_LOG(::extra2d::LogLevel::Info, __VA_ARGS__)
|
||||||
#define E2D_LOG_REGISTRY(...) \
|
#define E2D_REGISTRY(...) E2D_LOG(::extra2d::LogLevel::Registry, __VA_ARGS__)
|
||||||
E2D_LOG(::extra2d::LogLevel::Registry, __VA_ARGS__)
|
#define E2D_WARN(...) E2D_LOG(::extra2d::LogLevel::Warn, __VA_ARGS__)
|
||||||
#define E2D_LOG_WARN(...) E2D_LOG(::extra2d::LogLevel::Warn, __VA_ARGS__)
|
#define E2D_ERROR(...) E2D_LOG(::extra2d::LogLevel::Error, __VA_ARGS__)
|
||||||
#define E2D_LOG_ERROR(...) E2D_LOG(::extra2d::LogLevel::Error, __VA_ARGS__)
|
#define E2D_FATAL(...) E2D_LOG(::extra2d::LogLevel::Fatal, __VA_ARGS__)
|
||||||
#define E2D_LOG_FATAL(...) E2D_LOG(::extra2d::LogLevel::Fatal, __VA_ARGS__)
|
|
||||||
|
|
||||||
// 简写宏
|
|
||||||
#define E2D_INFO(...) E2D_LOG_INFO(__VA_ARGS__)
|
|
||||||
#define E2D_REGISTRY(...) E2D_LOG_REGISTRY(__VA_ARGS__)
|
|
||||||
#define E2D_WARN(...) E2D_LOG_WARN(__VA_ARGS__)
|
|
||||||
#define E2D_ERROR(...) E2D_LOG_ERROR(__VA_ARGS__)
|
|
||||||
#define E2D_FATAL(...) E2D_LOG_FATAL(__VA_ARGS__)
|
|
||||||
|
|
||||||
// 带颜色参数的日志宏
|
// 带颜色参数的日志宏
|
||||||
#define E2D_LOG_COLOR(level, color, ...) \
|
#define E2D_LOG_COLOR(level, color, ...) \
|
||||||
|
|
@ -302,33 +294,17 @@ std::string format_str(const char *fmt, Args &&...args) {
|
||||||
} \
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
#define E2D_LOG_TRACE_COLOR(color, ...) \
|
#define E2D_TRACE_COLOR(color, ...) \
|
||||||
E2D_LOG_COLOR(::extra2d::LogLevel::Trace, color, __VA_ARGS__)
|
E2D_LOG_COLOR(::extra2d::LogLevel::Trace, color, __VA_ARGS__)
|
||||||
#define E2D_LOG_DEBUG_COLOR(color, ...) \
|
#define E2D_DEBUG_COLOR(color, ...) \
|
||||||
E2D_LOG_COLOR(::extra2d::LogLevel::Debug, color, __VA_ARGS__)
|
E2D_LOG_COLOR(::extra2d::LogLevel::Debug, color, __VA_ARGS__)
|
||||||
#define E2D_LOG_INFO_COLOR(color, ...) \
|
#define E2D_INFO_COLOR(color, ...) \
|
||||||
E2D_LOG_COLOR(::extra2d::LogLevel::Info, color, __VA_ARGS__)
|
E2D_LOG_COLOR(::extra2d::LogLevel::Info, color, __VA_ARGS__)
|
||||||
#define E2D_LOG_REGISTRY_COLOR(color, ...) \
|
|
||||||
E2D_LOG_COLOR(::extra2d::LogLevel::Registry, color, __VA_ARGS__)
|
|
||||||
#define E2D_LOG_WARN_COLOR(color, ...) \
|
|
||||||
E2D_LOG_COLOR(::extra2d::LogLevel::Warn, color, __VA_ARGS__)
|
|
||||||
#define E2D_LOG_ERROR_COLOR(color, ...) \
|
|
||||||
E2D_LOG_COLOR(::extra2d::LogLevel::Error, color, __VA_ARGS__)
|
|
||||||
#define E2D_LOG_FATAL_COLOR(color, ...) \
|
|
||||||
E2D_LOG_COLOR(::extra2d::LogLevel::Fatal, color, __VA_ARGS__)
|
|
||||||
|
|
||||||
// 简写带颜色宏
|
|
||||||
#define E2D_INFO_COLOR(color, ...) E2D_LOG_INFO_COLOR(color, __VA_ARGS__)
|
|
||||||
#define E2D_REGISTRY_COLOR(color, ...) \
|
#define E2D_REGISTRY_COLOR(color, ...) \
|
||||||
E2D_LOG_REGISTRY_COLOR(color, __VA_ARGS__)
|
E2D_LOG_COLOR(::extra2d::LogLevel::Registry, color, __VA_ARGS__)
|
||||||
#define E2D_WARN_COLOR(color, ...) E2D_LOG_WARN_COLOR(color, __VA_ARGS__)
|
#define E2D_WARN_COLOR(color, ...) \
|
||||||
#define E2D_ERROR_COLOR(color, ...) E2D_LOG_ERROR_COLOR(color, __VA_ARGS__)
|
E2D_LOG_COLOR(::extra2d::LogLevel::Warn, color, __VA_ARGS__)
|
||||||
#define E2D_FATAL_COLOR(color, ...) E2D_LOG_FATAL_COLOR(color, __VA_ARGS__)
|
#define E2D_ERROR_COLOR(color, ...) \
|
||||||
|
E2D_LOG_COLOR(::extra2d::LogLevel::Error, color, __VA_ARGS__)
|
||||||
#ifdef E2D_DEBUG
|
#define E2D_FATAL_COLOR(color, ...) \
|
||||||
#define E2D_DEBUG_LOG(...) E2D_LOG_DEBUG(__VA_ARGS__)
|
E2D_LOG_COLOR(::extra2d::LogLevel::Fatal, color, __VA_ARGS__)
|
||||||
#define E2D_TRACE(...) E2D_LOG_TRACE(__VA_ARGS__)
|
|
||||||
#else
|
|
||||||
#define E2D_DEBUG_LOG(...)
|
|
||||||
#define E2D_TRACE(...)
|
|
||||||
#endif
|
|
||||||
|
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/service_interface.h>
|
|
||||||
#include <extra2d/core/service_locator.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_renderer.h>
|
|
||||||
#include <extra2d/scene/scene_manager.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 场景服务接口
|
|
||||||
* 定义场景管理的抽象接口,便于测试和Mock
|
|
||||||
*/
|
|
||||||
class ISceneService : public IService {
|
|
||||||
public:
|
|
||||||
virtual ~ISceneService() = default;
|
|
||||||
|
|
||||||
virtual void runWithScene(Ptr<Scene> scene) = 0;
|
|
||||||
virtual void replaceScene(Ptr<Scene> scene) = 0;
|
|
||||||
virtual void pushScene(Ptr<Scene> scene) = 0;
|
|
||||||
virtual void popScene() = 0;
|
|
||||||
virtual void popToRootScene() = 0;
|
|
||||||
virtual void popToScene(const std::string &name) = 0;
|
|
||||||
|
|
||||||
virtual Ptr<Scene> getCurrentScene() const = 0;
|
|
||||||
virtual Ptr<Scene> getPreviousScene() const = 0;
|
|
||||||
virtual Ptr<Scene> getRootScene() const = 0;
|
|
||||||
virtual Ptr<Scene> getSceneByName(const std::string &name) const = 0;
|
|
||||||
|
|
||||||
virtual size_t getSceneCount() const = 0;
|
|
||||||
virtual bool isEmpty() const = 0;
|
|
||||||
virtual bool hasScene(const std::string &name) const = 0;
|
|
||||||
|
|
||||||
virtual void render(Renderer &renderer) = 0;
|
|
||||||
virtual void collectRenderCommands(std::vector<RenderCommand> &commands) = 0;
|
|
||||||
|
|
||||||
virtual bool isTransitioning() const = 0;
|
|
||||||
virtual void
|
|
||||||
setTransitionCallback(SceneManager::TransitionCallback callback) = 0;
|
|
||||||
|
|
||||||
virtual void end() = 0;
|
|
||||||
virtual void purgeCachedScenes() = 0;
|
|
||||||
virtual void enterScene(Ptr<Scene> scene) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 场景服务实现
|
|
||||||
* 包装 SceneManager,实现 IService 接口
|
|
||||||
*/
|
|
||||||
class SceneService : public ISceneService {
|
|
||||||
public:
|
|
||||||
SceneService();
|
|
||||||
~SceneService() override = default;
|
|
||||||
|
|
||||||
ServiceInfo getServiceInfo() const override;
|
|
||||||
|
|
||||||
bool initialize() override;
|
|
||||||
void shutdown() override;
|
|
||||||
void update(float deltaTime) override;
|
|
||||||
|
|
||||||
void runWithScene(Ptr<Scene> scene) override;
|
|
||||||
void replaceScene(Ptr<Scene> scene) override;
|
|
||||||
void pushScene(Ptr<Scene> scene) override;
|
|
||||||
void popScene() override;
|
|
||||||
void popToRootScene() override;
|
|
||||||
void popToScene(const std::string &name) override;
|
|
||||||
|
|
||||||
Ptr<Scene> getCurrentScene() const override;
|
|
||||||
Ptr<Scene> getPreviousScene() const override;
|
|
||||||
Ptr<Scene> getRootScene() const override;
|
|
||||||
Ptr<Scene> getSceneByName(const std::string &name) const override;
|
|
||||||
|
|
||||||
size_t getSceneCount() const override;
|
|
||||||
bool isEmpty() const override;
|
|
||||||
bool hasScene(const std::string &name) const override;
|
|
||||||
|
|
||||||
void render(Renderer &renderer) override;
|
|
||||||
void collectRenderCommands(std::vector<RenderCommand> &commands) override;
|
|
||||||
|
|
||||||
bool isTransitioning() const override;
|
|
||||||
void
|
|
||||||
setTransitionCallback(SceneManager::TransitionCallback callback) override;
|
|
||||||
|
|
||||||
void end() override;
|
|
||||||
void purgeCachedScenes() override;
|
|
||||||
void enterScene(Ptr<Scene> scene) override;
|
|
||||||
|
|
||||||
SceneManager &getManager() { return manager_; }
|
|
||||||
const SceneManager &getManager() const { return manager_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
SceneManager manager_;
|
|
||||||
|
|
||||||
// 服务注册元数据
|
|
||||||
E2D_AUTO_REGISTER_SERVICE(ISceneService, SceneService);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -1,428 +0,0 @@
|
||||||
// stb_perlin.h - v0.5 - perlin noise
|
|
||||||
// public domain single-file C implementation by Sean Barrett
|
|
||||||
//
|
|
||||||
// LICENSE
|
|
||||||
//
|
|
||||||
// See end of file.
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// to create the implementation,
|
|
||||||
// #define STB_PERLIN_IMPLEMENTATION
|
|
||||||
// in *one* C/CPP file that includes this file.
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Documentation:
|
|
||||||
//
|
|
||||||
// float stb_perlin_noise3( float x,
|
|
||||||
// float y,
|
|
||||||
// float z,
|
|
||||||
// int x_wrap=0,
|
|
||||||
// int y_wrap=0,
|
|
||||||
// int z_wrap=0)
|
|
||||||
//
|
|
||||||
// This function computes a random value at the coordinate (x,y,z).
|
|
||||||
// Adjacent random values are continuous but the noise fluctuates
|
|
||||||
// its randomness with period 1, i.e. takes on wholly unrelated values
|
|
||||||
// at integer points. Specifically, this implements Ken Perlin's
|
|
||||||
// revised noise function from 2002.
|
|
||||||
//
|
|
||||||
// The "wrap" parameters can be used to create wraparound noise that
|
|
||||||
// wraps at powers of two. The numbers MUST be powers of two. Specify
|
|
||||||
// 0 to mean "don't care". (The noise always wraps every 256 due
|
|
||||||
// details of the implementation, even if you ask for larger or no
|
|
||||||
// wrapping.)
|
|
||||||
//
|
|
||||||
// float stb_perlin_noise3_seed( float x,
|
|
||||||
// float y,
|
|
||||||
// float z,
|
|
||||||
// int x_wrap=0,
|
|
||||||
// int y_wrap=0,
|
|
||||||
// int z_wrap=0,
|
|
||||||
// int seed)
|
|
||||||
//
|
|
||||||
// As above, but 'seed' selects from multiple different variations of the
|
|
||||||
// noise function. The current implementation only uses the bottom 8 bits
|
|
||||||
// of 'seed', but possibly in the future more bits will be used.
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Fractal Noise:
|
|
||||||
//
|
|
||||||
// Three common fractal noise functions are included, which produce
|
|
||||||
// a wide variety of nice effects depending on the parameters
|
|
||||||
// provided. Note that each function will call stb_perlin_noise3
|
|
||||||
// 'octaves' times, so this parameter will affect runtime.
|
|
||||||
//
|
|
||||||
// float stb_perlin_ridge_noise3(float x, float y, float z,
|
|
||||||
// float lacunarity, float gain, float offset, int octaves)
|
|
||||||
//
|
|
||||||
// float stb_perlin_fbm_noise3(float x, float y, float z,
|
|
||||||
// float lacunarity, float gain, int octaves)
|
|
||||||
//
|
|
||||||
// float stb_perlin_turbulence_noise3(float x, float y, float z,
|
|
||||||
// float lacunarity, float gain, int octaves)
|
|
||||||
//
|
|
||||||
// Typical values to start playing with:
|
|
||||||
// octaves = 6 -- number of "octaves" of noise3() to sum
|
|
||||||
// lacunarity = ~ 2.0 -- spacing between successive octaves (use exactly 2.0 for wrapping output)
|
|
||||||
// gain = 0.5 -- relative weighting applied to each successive octave
|
|
||||||
// offset = 1.0? -- used to invert the ridges, may need to be larger, not sure
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Contributors:
|
|
||||||
// Jack Mott - additional noise functions
|
|
||||||
// Jordan Peck - seeded noise
|
|
||||||
//
|
|
||||||
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
extern float stb_perlin_noise3(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap);
|
|
||||||
extern float stb_perlin_noise3_seed(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, int seed);
|
|
||||||
extern float stb_perlin_ridge_noise3(float x, float y, float z, float lacunarity, float gain, float offset, int octaves);
|
|
||||||
extern float stb_perlin_fbm_noise3(float x, float y, float z, float lacunarity, float gain, int octaves);
|
|
||||||
extern float stb_perlin_turbulence_noise3(float x, float y, float z, float lacunarity, float gain, int octaves);
|
|
||||||
extern float stb_perlin_noise3_wrap_nonpow2(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, unsigned char seed);
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef STB_PERLIN_IMPLEMENTATION
|
|
||||||
|
|
||||||
#include <math.h> // fabs()
|
|
||||||
|
|
||||||
// not same permutation table as Perlin's reference to avoid copyright issues;
|
|
||||||
// Perlin's table can be found at http://mrl.nyu.edu/~perlin/noise/
|
|
||||||
static unsigned char stb__perlin_randtab[512] =
|
|
||||||
{
|
|
||||||
23, 125, 161, 52, 103, 117, 70, 37, 247, 101, 203, 169, 124, 126, 44, 123,
|
|
||||||
152, 238, 145, 45, 171, 114, 253, 10, 192, 136, 4, 157, 249, 30, 35, 72,
|
|
||||||
175, 63, 77, 90, 181, 16, 96, 111, 133, 104, 75, 162, 93, 56, 66, 240,
|
|
||||||
8, 50, 84, 229, 49, 210, 173, 239, 141, 1, 87, 18, 2, 198, 143, 57,
|
|
||||||
225, 160, 58, 217, 168, 206, 245, 204, 199, 6, 73, 60, 20, 230, 211, 233,
|
|
||||||
94, 200, 88, 9, 74, 155, 33, 15, 219, 130, 226, 202, 83, 236, 42, 172,
|
|
||||||
165, 218, 55, 222, 46, 107, 98, 154, 109, 67, 196, 178, 127, 158, 13, 243,
|
|
||||||
65, 79, 166, 248, 25, 224, 115, 80, 68, 51, 184, 128, 232, 208, 151, 122,
|
|
||||||
26, 212, 105, 43, 179, 213, 235, 148, 146, 89, 14, 195, 28, 78, 112, 76,
|
|
||||||
250, 47, 24, 251, 140, 108, 186, 190, 228, 170, 183, 139, 39, 188, 244, 246,
|
|
||||||
132, 48, 119, 144, 180, 138, 134, 193, 82, 182, 120, 121, 86, 220, 209, 3,
|
|
||||||
91, 241, 149, 85, 205, 150, 113, 216, 31, 100, 41, 164, 177, 214, 153, 231,
|
|
||||||
38, 71, 185, 174, 97, 201, 29, 95, 7, 92, 54, 254, 191, 118, 34, 221,
|
|
||||||
131, 11, 163, 99, 234, 81, 227, 147, 156, 176, 17, 142, 69, 12, 110, 62,
|
|
||||||
27, 255, 0, 194, 59, 116, 242, 252, 19, 21, 187, 53, 207, 129, 64, 135,
|
|
||||||
61, 40, 167, 237, 102, 223, 106, 159, 197, 189, 215, 137, 36, 32, 22, 5,
|
|
||||||
|
|
||||||
// and a second copy so we don't need an extra mask or static initializer
|
|
||||||
23, 125, 161, 52, 103, 117, 70, 37, 247, 101, 203, 169, 124, 126, 44, 123,
|
|
||||||
152, 238, 145, 45, 171, 114, 253, 10, 192, 136, 4, 157, 249, 30, 35, 72,
|
|
||||||
175, 63, 77, 90, 181, 16, 96, 111, 133, 104, 75, 162, 93, 56, 66, 240,
|
|
||||||
8, 50, 84, 229, 49, 210, 173, 239, 141, 1, 87, 18, 2, 198, 143, 57,
|
|
||||||
225, 160, 58, 217, 168, 206, 245, 204, 199, 6, 73, 60, 20, 230, 211, 233,
|
|
||||||
94, 200, 88, 9, 74, 155, 33, 15, 219, 130, 226, 202, 83, 236, 42, 172,
|
|
||||||
165, 218, 55, 222, 46, 107, 98, 154, 109, 67, 196, 178, 127, 158, 13, 243,
|
|
||||||
65, 79, 166, 248, 25, 224, 115, 80, 68, 51, 184, 128, 232, 208, 151, 122,
|
|
||||||
26, 212, 105, 43, 179, 213, 235, 148, 146, 89, 14, 195, 28, 78, 112, 76,
|
|
||||||
250, 47, 24, 251, 140, 108, 186, 190, 228, 170, 183, 139, 39, 188, 244, 246,
|
|
||||||
132, 48, 119, 144, 180, 138, 134, 193, 82, 182, 120, 121, 86, 220, 209, 3,
|
|
||||||
91, 241, 149, 85, 205, 150, 113, 216, 31, 100, 41, 164, 177, 214, 153, 231,
|
|
||||||
38, 71, 185, 174, 97, 201, 29, 95, 7, 92, 54, 254, 191, 118, 34, 221,
|
|
||||||
131, 11, 163, 99, 234, 81, 227, 147, 156, 176, 17, 142, 69, 12, 110, 62,
|
|
||||||
27, 255, 0, 194, 59, 116, 242, 252, 19, 21, 187, 53, 207, 129, 64, 135,
|
|
||||||
61, 40, 167, 237, 102, 223, 106, 159, 197, 189, 215, 137, 36, 32, 22, 5,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// perlin's gradient has 12 cases so some get used 1/16th of the time
|
|
||||||
// and some 2/16ths. We reduce bias by changing those fractions
|
|
||||||
// to 5/64ths and 6/64ths
|
|
||||||
|
|
||||||
// this array is designed to match the previous implementation
|
|
||||||
// of gradient hash: indices[stb__perlin_randtab[i]&63]
|
|
||||||
static unsigned char stb__perlin_randtab_grad_idx[512] =
|
|
||||||
{
|
|
||||||
7, 9, 5, 0, 11, 1, 6, 9, 3, 9, 11, 1, 8, 10, 4, 7,
|
|
||||||
8, 6, 1, 5, 3, 10, 9, 10, 0, 8, 4, 1, 5, 2, 7, 8,
|
|
||||||
7, 11, 9, 10, 1, 0, 4, 7, 5, 0, 11, 6, 1, 4, 2, 8,
|
|
||||||
8, 10, 4, 9, 9, 2, 5, 7, 9, 1, 7, 2, 2, 6, 11, 5,
|
|
||||||
5, 4, 6, 9, 0, 1, 1, 0, 7, 6, 9, 8, 4, 10, 3, 1,
|
|
||||||
2, 8, 8, 9, 10, 11, 5, 11, 11, 2, 6, 10, 3, 4, 2, 4,
|
|
||||||
9, 10, 3, 2, 6, 3, 6, 10, 5, 3, 4, 10, 11, 2, 9, 11,
|
|
||||||
1, 11, 10, 4, 9, 4, 11, 0, 4, 11, 4, 0, 0, 0, 7, 6,
|
|
||||||
10, 4, 1, 3, 11, 5, 3, 4, 2, 9, 1, 3, 0, 1, 8, 0,
|
|
||||||
6, 7, 8, 7, 0, 4, 6, 10, 8, 2, 3, 11, 11, 8, 0, 2,
|
|
||||||
4, 8, 3, 0, 0, 10, 6, 1, 2, 2, 4, 5, 6, 0, 1, 3,
|
|
||||||
11, 9, 5, 5, 9, 6, 9, 8, 3, 8, 1, 8, 9, 6, 9, 11,
|
|
||||||
10, 7, 5, 6, 5, 9, 1, 3, 7, 0, 2, 10, 11, 2, 6, 1,
|
|
||||||
3, 11, 7, 7, 2, 1, 7, 3, 0, 8, 1, 1, 5, 0, 6, 10,
|
|
||||||
11, 11, 0, 2, 7, 0, 10, 8, 3, 5, 7, 1, 11, 1, 0, 7,
|
|
||||||
9, 0, 11, 5, 10, 3, 2, 3, 5, 9, 7, 9, 8, 4, 6, 5,
|
|
||||||
|
|
||||||
// and a second copy so we don't need an extra mask or static initializer
|
|
||||||
7, 9, 5, 0, 11, 1, 6, 9, 3, 9, 11, 1, 8, 10, 4, 7,
|
|
||||||
8, 6, 1, 5, 3, 10, 9, 10, 0, 8, 4, 1, 5, 2, 7, 8,
|
|
||||||
7, 11, 9, 10, 1, 0, 4, 7, 5, 0, 11, 6, 1, 4, 2, 8,
|
|
||||||
8, 10, 4, 9, 9, 2, 5, 7, 9, 1, 7, 2, 2, 6, 11, 5,
|
|
||||||
5, 4, 6, 9, 0, 1, 1, 0, 7, 6, 9, 8, 4, 10, 3, 1,
|
|
||||||
2, 8, 8, 9, 10, 11, 5, 11, 11, 2, 6, 10, 3, 4, 2, 4,
|
|
||||||
9, 10, 3, 2, 6, 3, 6, 10, 5, 3, 4, 10, 11, 2, 9, 11,
|
|
||||||
1, 11, 10, 4, 9, 4, 11, 0, 4, 11, 4, 0, 0, 0, 7, 6,
|
|
||||||
10, 4, 1, 3, 11, 5, 3, 4, 2, 9, 1, 3, 0, 1, 8, 0,
|
|
||||||
6, 7, 8, 7, 0, 4, 6, 10, 8, 2, 3, 11, 11, 8, 0, 2,
|
|
||||||
4, 8, 3, 0, 0, 10, 6, 1, 2, 2, 4, 5, 6, 0, 1, 3,
|
|
||||||
11, 9, 5, 5, 9, 6, 9, 8, 3, 8, 1, 8, 9, 6, 9, 11,
|
|
||||||
10, 7, 5, 6, 5, 9, 1, 3, 7, 0, 2, 10, 11, 2, 6, 1,
|
|
||||||
3, 11, 7, 7, 2, 1, 7, 3, 0, 8, 1, 1, 5, 0, 6, 10,
|
|
||||||
11, 11, 0, 2, 7, 0, 10, 8, 3, 5, 7, 1, 11, 1, 0, 7,
|
|
||||||
9, 0, 11, 5, 10, 3, 2, 3, 5, 9, 7, 9, 8, 4, 6, 5,
|
|
||||||
};
|
|
||||||
|
|
||||||
static float stb__perlin_lerp(float a, float b, float t)
|
|
||||||
{
|
|
||||||
return a + (b-a) * t;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int stb__perlin_fastfloor(float a)
|
|
||||||
{
|
|
||||||
int ai = (int) a;
|
|
||||||
return (a < ai) ? ai-1 : ai;
|
|
||||||
}
|
|
||||||
|
|
||||||
// different grad function from Perlin's, but easy to modify to match reference
|
|
||||||
static float stb__perlin_grad(int grad_idx, float x, float y, float z)
|
|
||||||
{
|
|
||||||
static float basis[12][4] =
|
|
||||||
{
|
|
||||||
{ 1, 1, 0 },
|
|
||||||
{ -1, 1, 0 },
|
|
||||||
{ 1,-1, 0 },
|
|
||||||
{ -1,-1, 0 },
|
|
||||||
{ 1, 0, 1 },
|
|
||||||
{ -1, 0, 1 },
|
|
||||||
{ 1, 0,-1 },
|
|
||||||
{ -1, 0,-1 },
|
|
||||||
{ 0, 1, 1 },
|
|
||||||
{ 0,-1, 1 },
|
|
||||||
{ 0, 1,-1 },
|
|
||||||
{ 0,-1,-1 },
|
|
||||||
};
|
|
||||||
|
|
||||||
float *grad = basis[grad_idx];
|
|
||||||
return grad[0]*x + grad[1]*y + grad[2]*z;
|
|
||||||
}
|
|
||||||
|
|
||||||
float stb_perlin_noise3_internal(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, unsigned char seed)
|
|
||||||
{
|
|
||||||
float u,v,w;
|
|
||||||
float n000,n001,n010,n011,n100,n101,n110,n111;
|
|
||||||
float n00,n01,n10,n11;
|
|
||||||
float n0,n1;
|
|
||||||
|
|
||||||
unsigned int x_mask = (x_wrap-1) & 255;
|
|
||||||
unsigned int y_mask = (y_wrap-1) & 255;
|
|
||||||
unsigned int z_mask = (z_wrap-1) & 255;
|
|
||||||
int px = stb__perlin_fastfloor(x);
|
|
||||||
int py = stb__perlin_fastfloor(y);
|
|
||||||
int pz = stb__perlin_fastfloor(z);
|
|
||||||
int x0 = px & x_mask, x1 = (px+1) & x_mask;
|
|
||||||
int y0 = py & y_mask, y1 = (py+1) & y_mask;
|
|
||||||
int z0 = pz & z_mask, z1 = (pz+1) & z_mask;
|
|
||||||
int r0,r1, r00,r01,r10,r11;
|
|
||||||
|
|
||||||
#define stb__perlin_ease(a) (((a*6-15)*a + 10) * a * a * a)
|
|
||||||
|
|
||||||
x -= px; u = stb__perlin_ease(x);
|
|
||||||
y -= py; v = stb__perlin_ease(y);
|
|
||||||
z -= pz; w = stb__perlin_ease(z);
|
|
||||||
|
|
||||||
r0 = stb__perlin_randtab[x0+seed];
|
|
||||||
r1 = stb__perlin_randtab[x1+seed];
|
|
||||||
|
|
||||||
r00 = stb__perlin_randtab[r0+y0];
|
|
||||||
r01 = stb__perlin_randtab[r0+y1];
|
|
||||||
r10 = stb__perlin_randtab[r1+y0];
|
|
||||||
r11 = stb__perlin_randtab[r1+y1];
|
|
||||||
|
|
||||||
n000 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r00+z0], x , y , z );
|
|
||||||
n001 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r00+z1], x , y , z-1 );
|
|
||||||
n010 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r01+z0], x , y-1, z );
|
|
||||||
n011 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r01+z1], x , y-1, z-1 );
|
|
||||||
n100 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r10+z0], x-1, y , z );
|
|
||||||
n101 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r10+z1], x-1, y , z-1 );
|
|
||||||
n110 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r11+z0], x-1, y-1, z );
|
|
||||||
n111 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r11+z1], x-1, y-1, z-1 );
|
|
||||||
|
|
||||||
n00 = stb__perlin_lerp(n000,n001,w);
|
|
||||||
n01 = stb__perlin_lerp(n010,n011,w);
|
|
||||||
n10 = stb__perlin_lerp(n100,n101,w);
|
|
||||||
n11 = stb__perlin_lerp(n110,n111,w);
|
|
||||||
|
|
||||||
n0 = stb__perlin_lerp(n00,n01,v);
|
|
||||||
n1 = stb__perlin_lerp(n10,n11,v);
|
|
||||||
|
|
||||||
return stb__perlin_lerp(n0,n1,u);
|
|
||||||
}
|
|
||||||
|
|
||||||
float stb_perlin_noise3(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap)
|
|
||||||
{
|
|
||||||
return stb_perlin_noise3_internal(x,y,z,x_wrap,y_wrap,z_wrap,0);
|
|
||||||
}
|
|
||||||
|
|
||||||
float stb_perlin_noise3_seed(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, int seed)
|
|
||||||
{
|
|
||||||
return stb_perlin_noise3_internal(x,y,z,x_wrap,y_wrap,z_wrap, (unsigned char) seed);
|
|
||||||
}
|
|
||||||
|
|
||||||
float stb_perlin_ridge_noise3(float x, float y, float z, float lacunarity, float gain, float offset, int octaves)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
float frequency = 1.0f;
|
|
||||||
float prev = 1.0f;
|
|
||||||
float amplitude = 0.5f;
|
|
||||||
float sum = 0.0f;
|
|
||||||
|
|
||||||
for (i = 0; i < octaves; i++) {
|
|
||||||
float r = stb_perlin_noise3_internal(x*frequency,y*frequency,z*frequency,0,0,0,(unsigned char)i);
|
|
||||||
r = offset - (float) fabs(r);
|
|
||||||
r = r*r;
|
|
||||||
sum += r*amplitude*prev;
|
|
||||||
prev = r;
|
|
||||||
frequency *= lacunarity;
|
|
||||||
amplitude *= gain;
|
|
||||||
}
|
|
||||||
return sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
float stb_perlin_fbm_noise3(float x, float y, float z, float lacunarity, float gain, int octaves)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
float frequency = 1.0f;
|
|
||||||
float amplitude = 1.0f;
|
|
||||||
float sum = 0.0f;
|
|
||||||
|
|
||||||
for (i = 0; i < octaves; i++) {
|
|
||||||
sum += stb_perlin_noise3_internal(x*frequency,y*frequency,z*frequency,0,0,0,(unsigned char)i)*amplitude;
|
|
||||||
frequency *= lacunarity;
|
|
||||||
amplitude *= gain;
|
|
||||||
}
|
|
||||||
return sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
float stb_perlin_turbulence_noise3(float x, float y, float z, float lacunarity, float gain, int octaves)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
float frequency = 1.0f;
|
|
||||||
float amplitude = 1.0f;
|
|
||||||
float sum = 0.0f;
|
|
||||||
|
|
||||||
for (i = 0; i < octaves; i++) {
|
|
||||||
float r = stb_perlin_noise3_internal(x*frequency,y*frequency,z*frequency,0,0,0,(unsigned char)i)*amplitude;
|
|
||||||
sum += (float) fabs(r);
|
|
||||||
frequency *= lacunarity;
|
|
||||||
amplitude *= gain;
|
|
||||||
}
|
|
||||||
return sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
float stb_perlin_noise3_wrap_nonpow2(float x, float y, float z, int x_wrap, int y_wrap, int z_wrap, unsigned char seed)
|
|
||||||
{
|
|
||||||
float u,v,w;
|
|
||||||
float n000,n001,n010,n011,n100,n101,n110,n111;
|
|
||||||
float n00,n01,n10,n11;
|
|
||||||
float n0,n1;
|
|
||||||
|
|
||||||
int px = stb__perlin_fastfloor(x);
|
|
||||||
int py = stb__perlin_fastfloor(y);
|
|
||||||
int pz = stb__perlin_fastfloor(z);
|
|
||||||
int x_wrap2 = (x_wrap ? x_wrap : 256);
|
|
||||||
int y_wrap2 = (y_wrap ? y_wrap : 256);
|
|
||||||
int z_wrap2 = (z_wrap ? z_wrap : 256);
|
|
||||||
int x0 = px % x_wrap2, x1;
|
|
||||||
int y0 = py % y_wrap2, y1;
|
|
||||||
int z0 = pz % z_wrap2, z1;
|
|
||||||
int r0,r1, r00,r01,r10,r11;
|
|
||||||
|
|
||||||
if (x0 < 0) x0 += x_wrap2;
|
|
||||||
if (y0 < 0) y0 += y_wrap2;
|
|
||||||
if (z0 < 0) z0 += z_wrap2;
|
|
||||||
x1 = (x0+1) % x_wrap2;
|
|
||||||
y1 = (y0+1) % y_wrap2;
|
|
||||||
z1 = (z0+1) % z_wrap2;
|
|
||||||
|
|
||||||
#define stb__perlin_ease(a) (((a*6-15)*a + 10) * a * a * a)
|
|
||||||
|
|
||||||
x -= px; u = stb__perlin_ease(x);
|
|
||||||
y -= py; v = stb__perlin_ease(y);
|
|
||||||
z -= pz; w = stb__perlin_ease(z);
|
|
||||||
|
|
||||||
r0 = stb__perlin_randtab[x0];
|
|
||||||
r0 = stb__perlin_randtab[r0+seed];
|
|
||||||
r1 = stb__perlin_randtab[x1];
|
|
||||||
r1 = stb__perlin_randtab[r1+seed];
|
|
||||||
|
|
||||||
r00 = stb__perlin_randtab[r0+y0];
|
|
||||||
r01 = stb__perlin_randtab[r0+y1];
|
|
||||||
r10 = stb__perlin_randtab[r1+y0];
|
|
||||||
r11 = stb__perlin_randtab[r1+y1];
|
|
||||||
|
|
||||||
n000 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r00+z0], x , y , z );
|
|
||||||
n001 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r00+z1], x , y , z-1 );
|
|
||||||
n010 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r01+z0], x , y-1, z );
|
|
||||||
n011 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r01+z1], x , y-1, z-1 );
|
|
||||||
n100 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r10+z0], x-1, y , z );
|
|
||||||
n101 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r10+z1], x-1, y , z-1 );
|
|
||||||
n110 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r11+z0], x-1, y-1, z );
|
|
||||||
n111 = stb__perlin_grad(stb__perlin_randtab_grad_idx[r11+z1], x-1, y-1, z-1 );
|
|
||||||
|
|
||||||
n00 = stb__perlin_lerp(n000,n001,w);
|
|
||||||
n01 = stb__perlin_lerp(n010,n011,w);
|
|
||||||
n10 = stb__perlin_lerp(n100,n101,w);
|
|
||||||
n11 = stb__perlin_lerp(n110,n111,w);
|
|
||||||
|
|
||||||
n0 = stb__perlin_lerp(n00,n01,v);
|
|
||||||
n1 = stb__perlin_lerp(n10,n11,v);
|
|
||||||
|
|
||||||
return stb__perlin_lerp(n0,n1,u);
|
|
||||||
}
|
|
||||||
#endif // STB_PERLIN_IMPLEMENTATION
|
|
||||||
|
|
||||||
/*
|
|
||||||
------------------------------------------------------------------------------
|
|
||||||
This software is available under 2 licenses -- choose whichever you prefer.
|
|
||||||
------------------------------------------------------------------------------
|
|
||||||
ALTERNATIVE A - MIT License
|
|
||||||
Copyright (c) 2017 Sean Barrett
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
||||||
this software and associated documentation files (the "Software"), to deal in
|
|
||||||
the Software without restriction, including without limitation the rights to
|
|
||||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
||||||
of the Software, and to permit persons to whom the Software is furnished to do
|
|
||||||
so, subject to the following conditions:
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
------------------------------------------------------------------------------
|
|
||||||
ALTERNATIVE B - Public Domain (www.unlicense.org)
|
|
||||||
This is free and unencumbered software released into the public domain.
|
|
||||||
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
|
|
||||||
software, either in source code form or as a compiled binary, for any purpose,
|
|
||||||
commercial or non-commercial, and by any means.
|
|
||||||
In jurisdictions that recognize copyright laws, the author or authors of this
|
|
||||||
software dedicate any and all copyright interest in the software to the public
|
|
||||||
domain. We make this dedication for the benefit of the public at large and to
|
|
||||||
the detriment of our heirs and successors. We intend this dedication to be an
|
|
||||||
overt act of relinquishment in perpetuity of all present and future rights to
|
|
||||||
this software under copyright law.
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
||||||
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
------------------------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform float u_opacity;
|
|
||||||
uniform float u_sdfThreshold;
|
|
||||||
uniform float u_sdfSmoothness;
|
|
||||||
uniform vec2 u_textureSize;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
// 采样 SDF 纹理(SDF 值存储在 alpha 通道,范围 0-255 已映射到 0-1)
|
|
||||||
float sdfValue = texture(u_texture, v_texCoord).a;
|
|
||||||
|
|
||||||
// 使用 fwidth 计算屏幕空间的变化率
|
|
||||||
float fw = fwidth(sdfValue);
|
|
||||||
|
|
||||||
// 平衡的抗锯齿:根据屏幕空间变化率调整平滑范围
|
|
||||||
// 在放大时更平滑,缩小时更锐利
|
|
||||||
float smoothRange = max(u_sdfSmoothness, fw * 0.5);
|
|
||||||
|
|
||||||
// 使用 smoothstep 进行抗锯齿
|
|
||||||
float alpha = smoothstep(u_sdfThreshold - smoothRange,
|
|
||||||
u_sdfThreshold + smoothRange,
|
|
||||||
sdfValue);
|
|
||||||
|
|
||||||
// 应用颜色和透明度
|
|
||||||
fragColor = v_color;
|
|
||||||
fragColor.a *= alpha * u_opacity;
|
|
||||||
|
|
||||||
// 丢弃完全透明的像素
|
|
||||||
if (fragColor.a < 0.001) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
fragColor = v_color;
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec4 a_color;
|
|
||||||
|
|
||||||
uniform mat4 u_viewProjection;
|
|
||||||
|
|
||||||
out vec4 v_color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = u_viewProjection * vec4(a_position, 0.0, 1.0);
|
|
||||||
v_color = a_color;
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform float u_opacity;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec4 texColor = texture(u_texture, v_texCoord);
|
|
||||||
fragColor = texColor * v_color;
|
|
||||||
fragColor.a *= u_opacity;
|
|
||||||
|
|
||||||
if (fragColor.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec2 a_texCoord;
|
|
||||||
layout(location = 2) in vec4 a_color;
|
|
||||||
|
|
||||||
uniform mat4 u_viewProjection;
|
|
||||||
uniform mat4 u_model;
|
|
||||||
|
|
||||||
out vec2 v_texCoord;
|
|
||||||
out vec4 v_color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
|
|
||||||
v_texCoord = a_texCoord;
|
|
||||||
v_color = a_color;
|
|
||||||
}
|
|
||||||
|
|
@ -1,137 +0,0 @@
|
||||||
// ============================================
|
|
||||||
// Common Color Functions
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
#ifndef E2D_COLOR_GLSL
|
|
||||||
#define E2D_COLOR_GLSL
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief RGB转灰度
|
|
||||||
* @param color RGB颜色
|
|
||||||
* @return 灰度值
|
|
||||||
*/
|
|
||||||
float rgbToGrayscale(vec3 color) {
|
|
||||||
return dot(color, vec3(0.299, 0.587, 0.114));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief RGB转HSV
|
|
||||||
* @param c RGB颜色
|
|
||||||
* @return HSV颜色
|
|
||||||
*/
|
|
||||||
vec3 rgbToHsv(vec3 c) {
|
|
||||||
vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
|
|
||||||
vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
|
|
||||||
vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
|
|
||||||
float d = q.x - min(q.w, q.y);
|
|
||||||
float e = 1.0e-10;
|
|
||||||
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief HSV转RGB
|
|
||||||
* @param c HSV颜色
|
|
||||||
* @return RGB颜色
|
|
||||||
*/
|
|
||||||
vec3 hsvToRgb(vec3 c) {
|
|
||||||
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
|
|
||||||
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
|
|
||||||
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 调整亮度
|
|
||||||
* @param color 原始颜色
|
|
||||||
* @param amount 亮度调整量
|
|
||||||
* @return 调整后的颜色
|
|
||||||
*/
|
|
||||||
vec3 adjustBrightness(vec3 color, float amount) {
|
|
||||||
return color + amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 调整对比度
|
|
||||||
* @param color 原始颜色
|
|
||||||
* @param amount 对比度调整量
|
|
||||||
* @return 调整后的颜色
|
|
||||||
*/
|
|
||||||
vec3 adjustContrast(vec3 color, float amount) {
|
|
||||||
return (color - 0.5) * amount + 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 调整饱和度
|
|
||||||
* @param color 原始颜色
|
|
||||||
* @param amount 饱和度调整量
|
|
||||||
* @return 调整后的颜色
|
|
||||||
*/
|
|
||||||
vec3 adjustSaturation(vec3 color, float amount) {
|
|
||||||
float gray = rgbToGrayscale(color);
|
|
||||||
return mix(vec3(gray), color, amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 颜色混合(正片叠底)
|
|
||||||
* @param a 底色
|
|
||||||
* @param b 混合色
|
|
||||||
* @return 混合结果
|
|
||||||
*/
|
|
||||||
vec3 blendMultiply(vec3 a, vec3 b) {
|
|
||||||
return a * b;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 颜色混合(滤色)
|
|
||||||
* @param a 底色
|
|
||||||
* @param b 混合色
|
|
||||||
* @return 混合结果
|
|
||||||
*/
|
|
||||||
vec3 blendScreen(vec3 a, vec3 b) {
|
|
||||||
return 1.0 - (1.0 - a) * (1.0 - b);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 颜色混合(叠加)
|
|
||||||
* @param a 底色
|
|
||||||
* @param b 混合色
|
|
||||||
* @return 混合结果
|
|
||||||
*/
|
|
||||||
vec3 blendOverlay(vec3 a, vec3 b) {
|
|
||||||
return mix(
|
|
||||||
2.0 * a * b,
|
|
||||||
1.0 - 2.0 * (1.0 - a) * (1.0 - b),
|
|
||||||
step(0.5, a)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 颜色调色
|
|
||||||
* @param color 原始颜色
|
|
||||||
* @param tintColor 色调颜色
|
|
||||||
* @param amount 色调强度
|
|
||||||
* @return 调色结果
|
|
||||||
*/
|
|
||||||
vec3 tint(vec3 color, vec3 tintColor, float amount) {
|
|
||||||
return mix(color, tintColor, amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 预乘Alpha
|
|
||||||
* @param color RGBA颜色
|
|
||||||
* @return 预乘后的RGB颜色
|
|
||||||
*/
|
|
||||||
vec3 premultiplyAlpha(vec4 color) {
|
|
||||||
return color.rgb * color.a;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 取消预乘Alpha
|
|
||||||
* @param color RGB颜色
|
|
||||||
* @param alpha Alpha值
|
|
||||||
* @return 未预乘的RGB颜色
|
|
||||||
*/
|
|
||||||
vec3 unpremultiplyAlpha(vec3 color, float alpha) {
|
|
||||||
return alpha > 0.0 ? color / alpha : color;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // E2D_COLOR_GLSL
|
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
// ============================================
|
|
||||||
// Common Math Functions
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
#ifndef E2D_MATH_GLSL
|
|
||||||
#define E2D_MATH_GLSL
|
|
||||||
|
|
||||||
const float PI = 3.14159265359;
|
|
||||||
const float E = 2.71828182846;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 角度转弧度
|
|
||||||
* @param deg 角度值
|
|
||||||
* @return 弧度值
|
|
||||||
*/
|
|
||||||
float degToRad(float deg) {
|
|
||||||
return deg * PI / 180.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 弧度转角度
|
|
||||||
* @param rad 弧度值
|
|
||||||
* @return 角度值
|
|
||||||
*/
|
|
||||||
float radToDeg(float rad) {
|
|
||||||
return rad * 180.0 / PI;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 线性插值
|
|
||||||
* @param a 起始值
|
|
||||||
* @param b 结束值
|
|
||||||
* @param t 插值因子 [0, 1]
|
|
||||||
* @return 插值结果
|
|
||||||
*/
|
|
||||||
float lerp(float a, float b, float t) {
|
|
||||||
return a + (b - a) * t;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 平滑插值
|
|
||||||
* @param edge0 下边界
|
|
||||||
* @param edge1 上边界
|
|
||||||
* @param x 输入值
|
|
||||||
* @return 平滑插值结果
|
|
||||||
*/
|
|
||||||
float smoothStep(float edge0, float edge1, float x) {
|
|
||||||
float t = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
|
|
||||||
return t * t * (3.0 - 2.0 * t);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 2D向量线性插值
|
|
||||||
*/
|
|
||||||
vec2 lerpVec2(vec2 a, vec2 b, float t) {
|
|
||||||
return a + (b - a) * t;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 计算两点之间的距离
|
|
||||||
*/
|
|
||||||
float distance2D(vec2 a, vec2 b) {
|
|
||||||
return length(b - a);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 计算两点之间的距离平方
|
|
||||||
*/
|
|
||||||
float distance2DSquared(vec2 a, vec2 b) {
|
|
||||||
vec2 diff = b - a;
|
|
||||||
return dot(diff, diff);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 将值限制在范围内
|
|
||||||
*/
|
|
||||||
float clamp01(float x) {
|
|
||||||
return clamp(x, 0.0, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 重复平铺
|
|
||||||
*/
|
|
||||||
float repeat(float x, float period) {
|
|
||||||
return mod(x, period);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 镜像重复
|
|
||||||
*/
|
|
||||||
float mirrorRepeat(float x, float period) {
|
|
||||||
float m = mod(x, period * 2.0);
|
|
||||||
return m > period ? period * 2.0 - m : m;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // E2D_MATH_GLSL
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
{
|
|
||||||
"name": "sdf_font",
|
|
||||||
"category": "builtin",
|
|
||||||
"version": "1.0",
|
|
||||||
"description": "SDF字体渲染Shader,支持高质量抗锯齿",
|
|
||||||
"uniforms": {
|
|
||||||
"u_viewProjection": {
|
|
||||||
"type": "mat4",
|
|
||||||
"default": [
|
|
||||||
1, 0, 0, 0,
|
|
||||||
0, 1, 0, 0,
|
|
||||||
0, 0, 1, 0,
|
|
||||||
0, 0, 0, 1
|
|
||||||
],
|
|
||||||
"description": "视图投影矩阵"
|
|
||||||
},
|
|
||||||
"u_model": {
|
|
||||||
"type": "mat4",
|
|
||||||
"default": [
|
|
||||||
1, 0, 0, 0,
|
|
||||||
0, 1, 0, 0,
|
|
||||||
0, 0, 1, 0,
|
|
||||||
0, 0, 0, 1
|
|
||||||
],
|
|
||||||
"description": "模型矩阵"
|
|
||||||
},
|
|
||||||
"u_opacity": {
|
|
||||||
"type": "float",
|
|
||||||
"default": 1.0,
|
|
||||||
"description": "透明度"
|
|
||||||
},
|
|
||||||
"u_sdfThreshold": {
|
|
||||||
"type": "float",
|
|
||||||
"default": 0.5,
|
|
||||||
"description": "SDF阈值,默认0.5,范围0-1。值越小字形越粗,越大越细"
|
|
||||||
},
|
|
||||||
"u_sdfSmoothness": {
|
|
||||||
"type": "float",
|
|
||||||
"default": 0.02,
|
|
||||||
"description": "SDF平滑度,控制抗锯齿范围,越小越锐利,默认0.02"
|
|
||||||
},
|
|
||||||
"u_textureSize": {
|
|
||||||
"type": "vec2",
|
|
||||||
"default": [512, 512],
|
|
||||||
"description": "SDF纹理尺寸,用于计算像素级平滑"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"samplers": {
|
|
||||||
"u_texture": {
|
|
||||||
"binding": 0,
|
|
||||||
"description": "SDF纹理采样器"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"backends": {
|
|
||||||
"opengl": {
|
|
||||||
"vertex": "backends/opengl/builtin/sprite.vert",
|
|
||||||
"fragment": "backends/opengl/builtin/sdf_font.frag"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
{
|
|
||||||
"name": "shape",
|
|
||||||
"category": "builtin",
|
|
||||||
"version": "1.0",
|
|
||||||
"description": "基本形状渲染Shader",
|
|
||||||
"uniforms": {
|
|
||||||
"u_viewProjection": {
|
|
||||||
"type": "mat4",
|
|
||||||
"default": [
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
1
|
|
||||||
],
|
|
||||||
"description": "视图投影矩阵"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"samplers": {},
|
|
||||||
"backends": {
|
|
||||||
"opengl": {
|
|
||||||
"vertex": "backends/opengl/builtin/shape.vert",
|
|
||||||
"fragment": "backends/opengl/builtin/shape.frag"
|
|
||||||
},
|
|
||||||
"vulkan": {
|
|
||||||
"vertex": "backends/vulkan/builtin/shape.vert.spv",
|
|
||||||
"fragment": "backends/vulkan/builtin/shape.frag.spv"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
{
|
|
||||||
"name": "sprite",
|
|
||||||
"category": "builtin",
|
|
||||||
"version": "1.0",
|
|
||||||
"description": "标准2D精灵渲染Shader",
|
|
||||||
"uniforms": {
|
|
||||||
"u_viewProjection": {
|
|
||||||
"type": "mat4",
|
|
||||||
"default": [
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
1
|
|
||||||
],
|
|
||||||
"description": "视图投影矩阵"
|
|
||||||
},
|
|
||||||
"u_model": {
|
|
||||||
"type": "mat4",
|
|
||||||
"default": [
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
1
|
|
||||||
],
|
|
||||||
"description": "模型矩阵"
|
|
||||||
},
|
|
||||||
"u_opacity": {
|
|
||||||
"type": "float",
|
|
||||||
"default": 1.0,
|
|
||||||
"description": "透明度"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"samplers": {
|
|
||||||
"u_texture": {
|
|
||||||
"binding": 0,
|
|
||||||
"description": "纹理采样器"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"backends": {
|
|
||||||
"opengl": {
|
|
||||||
"vertex": "backends/opengl/builtin/sprite.vert",
|
|
||||||
"fragment": "backends/opengl/builtin/sprite.frag"
|
|
||||||
},
|
|
||||||
"vulkan": {
|
|
||||||
"vertex": "backends/vulkan/builtin/sprite.vert.spv",
|
|
||||||
"fragment": "backends/vulkan/builtin/sprite.frag.spv"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +1,9 @@
|
||||||
#include <extra2d/app/application.h>
|
#include <extra2d/app/application.h>
|
||||||
#include <extra2d/core/registry.h>
|
#include <extra2d/core/registry.h>
|
||||||
#include <extra2d/graphics/core/render_module.h>
|
|
||||||
#include <extra2d/graphics/memory/vram_manager.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_renderer.h>
|
|
||||||
#include <extra2d/platform/glfw/glfw_window.h>
|
#include <extra2d/platform/glfw/glfw_window.h>
|
||||||
#include <extra2d/platform/window_module.h>
|
#include <extra2d/platform/window_module.h>
|
||||||
#include <extra2d/services/camera_service.h>
|
|
||||||
#include <extra2d/services/event_service.h>
|
#include <extra2d/services/event_service.h>
|
||||||
#include <extra2d/services/logger_service.h>
|
#include <extra2d/services/logger_service.h>
|
||||||
#include <extra2d/services/scene_service.h>
|
|
||||||
#include <extra2d/services/timer_service.h>
|
#include <extra2d/services/timer_service.h>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
@ -51,9 +46,6 @@ bool Application::init() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 配置相机服务(需要窗口信息)
|
|
||||||
configureCameraService();
|
|
||||||
|
|
||||||
// 初始化所有服务
|
// 初始化所有服务
|
||||||
ServiceLocator::instance().initializeAll();
|
ServiceLocator::instance().initializeAll();
|
||||||
|
|
||||||
|
|
@ -62,40 +54,10 @@ bool Application::init() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::configureCameraService() {
|
|
||||||
auto *winMod = get<WindowModule>();
|
|
||||||
if (!winMod || !winMod->win()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto cameraService = ServiceLocator::instance().getService<ICameraService>();
|
|
||||||
if (!cameraService) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto *win = winMod->win();
|
|
||||||
cameraService->setViewport(0, static_cast<float>(win->width()),
|
|
||||||
static_cast<float>(win->height()), 0);
|
|
||||||
|
|
||||||
ViewportConfig vpConfig;
|
|
||||||
vpConfig.logicWidth = static_cast<float>(win->width());
|
|
||||||
vpConfig.logicHeight = static_cast<float>(win->height());
|
|
||||||
vpConfig.mode = ViewportMode::AspectRatio;
|
|
||||||
cameraService->setViewportConfig(vpConfig);
|
|
||||||
cameraService->updateViewport(win->width(), win->height());
|
|
||||||
|
|
||||||
win->onResize([cameraService](int width, int height) {
|
|
||||||
cameraService->updateViewport(width, height);
|
|
||||||
cameraService->applyViewportAdapter();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Application::shutdown() {
|
void Application::shutdown() {
|
||||||
if (!initialized_)
|
if (!initialized_)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
VRAMMgr::get().printStats();
|
|
||||||
|
|
||||||
ServiceLocator::instance().shutdownAll();
|
ServiceLocator::instance().shutdownAll();
|
||||||
ServiceLocator::instance().clear();
|
ServiceLocator::instance().clear();
|
||||||
Registry::instance().shutdown();
|
Registry::instance().shutdown();
|
||||||
|
|
@ -170,43 +132,15 @@ void Application::mainLoop() {
|
||||||
}
|
}
|
||||||
|
|
||||||
render();
|
render();
|
||||||
|
|
||||||
// 帧率限制
|
|
||||||
auto *renderMod = get<RenderModule>();
|
|
||||||
if (renderMod && renderMod->renderer()) {
|
|
||||||
// 这里可以添加帧率限制逻辑
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::update() { ServiceLocator::instance().updateAll(deltaTime_); }
|
void Application::update() { ServiceLocator::instance().updateAll(deltaTime_); }
|
||||||
|
|
||||||
void Application::render() {
|
void Application::render() {
|
||||||
auto *renderMod = get<RenderModule>();
|
|
||||||
if (!renderMod || !renderMod->renderer())
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto *renderer = renderMod->renderer();
|
|
||||||
auto *winMod = get<WindowModule>();
|
auto *winMod = get<WindowModule>();
|
||||||
if (!winMod || !winMod->win())
|
if (!winMod || !winMod->win())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto cameraService = ServiceLocator::instance().getService<ICameraService>();
|
|
||||||
if (cameraService) {
|
|
||||||
const auto &vp = cameraService->getViewportResult().viewport;
|
|
||||||
renderer->setViewport(
|
|
||||||
static_cast<int>(vp.origin.x), static_cast<int>(vp.origin.y),
|
|
||||||
static_cast<int>(vp.size.width), static_cast<int>(vp.size.height));
|
|
||||||
renderer->setViewProjection(cameraService->getViewProjectionMatrix());
|
|
||||||
} else {
|
|
||||||
renderer->setViewport(0, 0, winMod->win()->width(),
|
|
||||||
winMod->win()->height());
|
|
||||||
}
|
|
||||||
|
|
||||||
auto sceneService = ServiceLocator::instance().getService<ISceneService>();
|
|
||||||
if (sceneService) {
|
|
||||||
sceneService->render(*renderer);
|
|
||||||
}
|
|
||||||
|
|
||||||
winMod->win()->swap();
|
winMod->win()->swap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -215,19 +149,4 @@ GLFWWindow *Application::window() {
|
||||||
return winMod ? winMod->win() : nullptr;
|
return winMod ? winMod->win() : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
Renderer *Application::renderer() {
|
|
||||||
auto *renderMod = get<RenderModule>();
|
|
||||||
return renderMod ? renderMod->renderer() : nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Application::enterScene(Ptr<Scene> scene) {
|
|
||||||
auto sceneService = ServiceLocator::instance().getService<ISceneService>();
|
|
||||||
auto *winMod = get<WindowModule>();
|
|
||||||
if (sceneService && scene && winMod && winMod->win()) {
|
|
||||||
scene->setViewportSize(static_cast<float>(winMod->win()->width()),
|
|
||||||
static_cast<float>(winMod->win()->height()));
|
|
||||||
sceneService->enterScene(scene);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
12868
Extra2D/src/glad/glad.c
12868
Extra2D/src/glad/glad.c
File diff suppressed because one or more lines are too long
|
|
@ -1,72 +0,0 @@
|
||||||
#include <extra2d/graphics/batch/shape_batch.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// ShapeBatch 基础实现(后端无关部分)
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
// 这里可以添加后端无关的工具函数
|
|
||||||
// 例如:计算圆形的顶点、三角化多边形等
|
|
||||||
|
|
||||||
// 计算圆形顶点
|
|
||||||
void calculateCircleVertices(std::vector<Vec2>& outVertices,
|
|
||||||
const Vec2& center, float radius,
|
|
||||||
int segments, bool fill) {
|
|
||||||
outVertices.clear();
|
|
||||||
outVertices.reserve(fill ? segments + 1 : segments);
|
|
||||||
|
|
||||||
if (fill) {
|
|
||||||
// 填充圆形:中心点 + 边缘点
|
|
||||||
outVertices.push_back(center);
|
|
||||||
for (int i = 0; i <= segments; ++i) {
|
|
||||||
float angle = 2.0f * 3.14159265359f * static_cast<float>(i) / static_cast<float>(segments);
|
|
||||||
outVertices.emplace_back(
|
|
||||||
center.x + radius * cosf(angle),
|
|
||||||
center.y + radius * sinf(angle)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 圆形边框:只保留边缘点
|
|
||||||
for (int i = 0; i < segments; ++i) {
|
|
||||||
float angle = 2.0f * 3.14159265359f * static_cast<float>(i) / static_cast<float>(segments);
|
|
||||||
outVertices.emplace_back(
|
|
||||||
center.x + radius * cosf(angle),
|
|
||||||
center.y + radius * sinf(angle)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算矩形顶点
|
|
||||||
void calculateRectVertices(std::vector<Vec2>& outVertices, const Rect& rect) {
|
|
||||||
outVertices.clear();
|
|
||||||
outVertices.reserve(4);
|
|
||||||
|
|
||||||
float x1 = rect.origin.x;
|
|
||||||
float y1 = rect.origin.y;
|
|
||||||
float x2 = rect.origin.x + rect.size.width;
|
|
||||||
float y2 = rect.origin.y + rect.size.height;
|
|
||||||
|
|
||||||
outVertices.emplace_back(x1, y1); // 左上
|
|
||||||
outVertices.emplace_back(x2, y1); // 右上
|
|
||||||
outVertices.emplace_back(x2, y2); // 右下
|
|
||||||
outVertices.emplace_back(x1, y2); // 左下
|
|
||||||
}
|
|
||||||
|
|
||||||
// 简单的多边形三角化(扇形三角化,适用于凸多边形)
|
|
||||||
void triangulatePolygon(std::vector<uint16_t>& outIndices, int vertexCount) {
|
|
||||||
outIndices.clear();
|
|
||||||
if (vertexCount < 3) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 扇形三角化:以第一个顶点为扇形中心
|
|
||||||
for (int i = 1; i < vertexCount - 1; ++i) {
|
|
||||||
outIndices.push_back(0);
|
|
||||||
outIndices.push_back(i);
|
|
||||||
outIndices.push_back(i + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,206 +0,0 @@
|
||||||
#include <extra2d/core/service_locator.h>
|
|
||||||
#include <extra2d/graphics/batch/sprite_batch.h>
|
|
||||||
#include <extra2d/services/logger_service.h>
|
|
||||||
|
|
||||||
|
|
||||||
#include <cmath>
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// TrigLookup 实现 - 三角函数查表
|
|
||||||
// ============================================================================
|
|
||||||
TrigLookup::TrigLookup() {
|
|
||||||
constexpr float PI = 3.14159265359f;
|
|
||||||
constexpr float DEG2RAD = PI / 180.0f;
|
|
||||||
|
|
||||||
for (int i = 0; i < TABLE_SIZE; ++i) {
|
|
||||||
float angle = static_cast<float>(i) * (360.0f / TABLE_SIZE) * DEG2RAD;
|
|
||||||
sinTable_[i] = std::sin(angle);
|
|
||||||
cosTable_[i] = std::cos(angle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
float TrigLookup::sin(int angle) const {
|
|
||||||
// 规范化角度到 0-360
|
|
||||||
angle = ((angle % 360) + 360) % 360;
|
|
||||||
return sinTable_[angle * 4];
|
|
||||||
}
|
|
||||||
|
|
||||||
float TrigLookup::cos(int angle) const {
|
|
||||||
// 规范化角度到 0-360
|
|
||||||
angle = ((angle % 360) + 360) % 360;
|
|
||||||
return cosTable_[angle * 4];
|
|
||||||
}
|
|
||||||
|
|
||||||
float TrigLookup::sinRad(float rad) const {
|
|
||||||
constexpr float RAD2DEG = 180.0f / 3.14159265359f;
|
|
||||||
int angle = static_cast<int>(rad * RAD2DEG);
|
|
||||||
return sin(angle);
|
|
||||||
}
|
|
||||||
|
|
||||||
float TrigLookup::cosRad(float rad) const {
|
|
||||||
constexpr float RAD2DEG = 180.0f / 3.14159265359f;
|
|
||||||
int angle = static_cast<int>(rad * RAD2DEG);
|
|
||||||
return cos(angle);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// SpriteBatch 实现
|
|
||||||
// ============================================================================
|
|
||||||
SpriteBatch::SpriteBatch() : spriteCount_(0), vpDirty_(true) {
|
|
||||||
// 预分配顶点缓冲区
|
|
||||||
vertices_.reserve(MAX_VERTICES);
|
|
||||||
indices_.reserve(MAX_INDICES);
|
|
||||||
|
|
||||||
// 生成静态索引缓冲区
|
|
||||||
generateIndices();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpriteBatch::generateIndices() {
|
|
||||||
indices_.clear();
|
|
||||||
for (size_t i = 0; i < MAX_SPRITES; ++i) {
|
|
||||||
uint16_t base = static_cast<uint16_t>(i * 4);
|
|
||||||
// 两个三角形: (0,1,2) 和 (0,2,3)
|
|
||||||
indices_.push_back(base + 0);
|
|
||||||
indices_.push_back(base + 1);
|
|
||||||
indices_.push_back(base + 2);
|
|
||||||
indices_.push_back(base + 0);
|
|
||||||
indices_.push_back(base + 2);
|
|
||||||
indices_.push_back(base + 3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpriteBatch::begin(const glm::mat4 &viewProjection) {
|
|
||||||
viewProjection_ = viewProjection;
|
|
||||||
vpDirty_ = true;
|
|
||||||
spriteCount_ = 0;
|
|
||||||
vertices_.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpriteBatch::end() {
|
|
||||||
// 批次结束,数据已准备好供后端使用
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpriteBatch::draw(const SpriteData &sprite) {
|
|
||||||
if (spriteCount_ >= MAX_SPRITES) {
|
|
||||||
// 缓冲区已满,需要刷新
|
|
||||||
flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
generateVertices(sprite, spriteCount_ * VERTICES_PER_SPRITE);
|
|
||||||
spriteCount_++;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpriteBatch::drawBatch(const std::vector<SpriteData> &sprites) {
|
|
||||||
size_t index = 0;
|
|
||||||
while (index < sprites.size()) {
|
|
||||||
// 计算剩余空间
|
|
||||||
size_t remainingSpace = MAX_SPRITES - spriteCount_;
|
|
||||||
size_t batchSize = std::min(remainingSpace, sprites.size() - index);
|
|
||||||
|
|
||||||
// 批量生成顶点
|
|
||||||
for (size_t i = 0; i < batchSize; ++i) {
|
|
||||||
generateVertices(sprites[index + i], spriteCount_ * VERTICES_PER_SPRITE);
|
|
||||||
spriteCount_++;
|
|
||||||
}
|
|
||||||
|
|
||||||
index += batchSize;
|
|
||||||
|
|
||||||
// 如果缓冲区已满,需要刷新(由后端处理)
|
|
||||||
if (spriteCount_ >= MAX_SPRITES && index < sprites.size()) {
|
|
||||||
// 通知后端刷新,然后继续
|
|
||||||
// 注意:这里只是准备数据,实际 GPU 提交由后端决定
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpriteBatch::drawImmediate(const SpriteData &sprite) {
|
|
||||||
// 立即绘制模式:清空当前批次,只绘制这一个精灵
|
|
||||||
clear();
|
|
||||||
draw(sprite);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpriteBatch::generateVertices(const SpriteData &sprite,
|
|
||||||
size_t vertexOffset) {
|
|
||||||
// 确保顶点缓冲区足够
|
|
||||||
if (vertices_.size() < vertexOffset + VERTICES_PER_SPRITE) {
|
|
||||||
vertices_.resize(vertexOffset + VERTICES_PER_SPRITE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算旋转(使用查表)
|
|
||||||
float c = 1.0f;
|
|
||||||
float s = 0.0f;
|
|
||||||
if (sprite.rotation != 0.0f) {
|
|
||||||
c = trigLookup_.cosRad(sprite.rotation);
|
|
||||||
s = trigLookup_.sinRad(sprite.rotation);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算精灵的四个角(相对于中心点)
|
|
||||||
float halfWidth = sprite.size.x * 0.5f;
|
|
||||||
float halfHeight = sprite.size.y * 0.5f;
|
|
||||||
|
|
||||||
// 考虑 pivot 偏移
|
|
||||||
float pivotOffsetX = (sprite.pivot.x - 0.5f) * sprite.size.x;
|
|
||||||
float pivotOffsetY = (sprite.pivot.y - 0.5f) * sprite.size.y;
|
|
||||||
|
|
||||||
// 四个角的本地坐标
|
|
||||||
Vec2 localCorners[4] = {
|
|
||||||
Vec2(-halfWidth - pivotOffsetX, -halfHeight - pivotOffsetY), // 左下
|
|
||||||
Vec2(halfWidth - pivotOffsetX, -halfHeight - pivotOffsetY), // 右下
|
|
||||||
Vec2(halfWidth - pivotOffsetX, halfHeight - pivotOffsetY), // 右上
|
|
||||||
Vec2(-halfWidth - pivotOffsetX, halfHeight - pivotOffsetY) // 左上
|
|
||||||
};
|
|
||||||
|
|
||||||
// 应用旋转和平移
|
|
||||||
Vec2 worldPos = sprite.position;
|
|
||||||
|
|
||||||
for (int i = 0; i < 4; ++i) {
|
|
||||||
Vec2 rotated;
|
|
||||||
if (sprite.rotation != 0.0f) {
|
|
||||||
rotated.x = localCorners[i].x * c - localCorners[i].y * s;
|
|
||||||
rotated.y = localCorners[i].x * s + localCorners[i].y * c;
|
|
||||||
} else {
|
|
||||||
rotated = localCorners[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
vertices_[vertexOffset + i].position = worldPos + rotated;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置纹理坐标
|
|
||||||
// uvRect.origin = (u0, v0) - 左下
|
|
||||||
// uvRect.size = (width, height) - 从左上到右下的尺寸
|
|
||||||
float u0 = sprite.uvRect.origin.x;
|
|
||||||
float v0 = sprite.uvRect.origin.y;
|
|
||||||
float u1 = u0 + sprite.uvRect.size.width;
|
|
||||||
float v1 = v0 + sprite.uvRect.size.height;
|
|
||||||
|
|
||||||
// 顶点顺序: 左下, 右下, 右上, 左上
|
|
||||||
// 注意: 在 gl_font_atlas 中 v0 > v1 (因为翻转了V坐标)
|
|
||||||
// 所以 v0 对应底部,v1 对应顶部
|
|
||||||
vertices_[vertexOffset + 0].texCoord = Vec2(u0, v0); // 左下
|
|
||||||
vertices_[vertexOffset + 1].texCoord = Vec2(u1, v0); // 右下
|
|
||||||
vertices_[vertexOffset + 2].texCoord = Vec2(u1, v1); // 右上
|
|
||||||
vertices_[vertexOffset + 3].texCoord = Vec2(u0, v1); // 左上
|
|
||||||
|
|
||||||
// 设置颜色
|
|
||||||
for (int i = 0; i < 4; ++i) {
|
|
||||||
vertices_[vertexOffset + i].color = sprite.color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpriteBatch::flush() {
|
|
||||||
// 标记需要刷新 - 实际刷新由后端处理
|
|
||||||
// 这里只是重置计数器,让后端知道需要提交当前批次
|
|
||||||
spriteCount_ = 0;
|
|
||||||
vertices_.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpriteBatch::clear() {
|
|
||||||
spriteCount_ = 0;
|
|
||||||
vertices_.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,351 +0,0 @@
|
||||||
#include <algorithm>
|
|
||||||
#include <extra2d/graphics/camera/camera.h>
|
|
||||||
#include <extra2d/graphics/camera/viewport_adapter.h>
|
|
||||||
#include <glm/gtc/matrix_inverse.hpp>
|
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
|
||||||
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 默认构造函数
|
|
||||||
*
|
|
||||||
* 创建一个默认的正交相机,视口范围为 (-1, -1) 到 (1, 1)
|
|
||||||
*/
|
|
||||||
Camera::Camera() : left_(-1.0f), right_(1.0f), bottom_(-1.0f), top_(1.0f) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 构造函数
|
|
||||||
* @param left 视口左边界
|
|
||||||
* @param right 视口右边界
|
|
||||||
* @param bottom 视口底边界
|
|
||||||
* @param top 视口顶边界
|
|
||||||
*
|
|
||||||
* 创建一个指定视口范围的正交相机
|
|
||||||
*/
|
|
||||||
Camera::Camera(float left, float right, float bottom, float top)
|
|
||||||
: left_(left), right_(right), bottom_(bottom), top_(top) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 构造函数
|
|
||||||
* @param viewport 视口尺寸
|
|
||||||
*
|
|
||||||
* 根据视口尺寸创建相机,视口原点在左上角
|
|
||||||
*/
|
|
||||||
Camera::Camera(const Size &viewport)
|
|
||||||
: left_(0.0f), right_(viewport.width), bottom_(viewport.height),
|
|
||||||
top_(0.0f) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置相机位置
|
|
||||||
* @param position 新的位置坐标
|
|
||||||
*
|
|
||||||
* 设置相机在世界空间中的位置,会标记视图矩阵为脏
|
|
||||||
*/
|
|
||||||
void Camera::setPos(const Vec2 &position) {
|
|
||||||
position_ = position;
|
|
||||||
viewDirty_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置相机位置
|
|
||||||
* @param x X坐标
|
|
||||||
* @param y Y坐标
|
|
||||||
*
|
|
||||||
* 设置相机在世界空间中的位置,会标记视图矩阵为脏
|
|
||||||
*/
|
|
||||||
void Camera::setPos(float x, float y) {
|
|
||||||
position_.x = x;
|
|
||||||
position_.y = y;
|
|
||||||
viewDirty_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置相机旋转角度
|
|
||||||
* @param degrees 旋转角度(度数)
|
|
||||||
*
|
|
||||||
* 设置相机的旋转角度,会标记视图矩阵为脏
|
|
||||||
*/
|
|
||||||
void Camera::setRotation(float degrees) {
|
|
||||||
rotation_ = degrees;
|
|
||||||
viewDirty_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置相机缩放级别
|
|
||||||
* @param zoom 缩放值(1.0为正常大小)
|
|
||||||
*
|
|
||||||
* 设置相机的缩放级别,会同时标记视图矩阵和投影矩阵为脏
|
|
||||||
*/
|
|
||||||
void Camera::setZoom(float zoom) {
|
|
||||||
zoom_ = zoom;
|
|
||||||
viewDirty_ = true;
|
|
||||||
projDirty_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置视口范围
|
|
||||||
* @param left 左边界
|
|
||||||
* @param right 右边界
|
|
||||||
* @param bottom 底边界
|
|
||||||
* @param top 顶边界
|
|
||||||
*
|
|
||||||
* 设置相机的正交投影视口范围,会标记投影矩阵为脏
|
|
||||||
*/
|
|
||||||
void Camera::setViewport(float left, float right, float bottom, float top) {
|
|
||||||
left_ = left;
|
|
||||||
right_ = right;
|
|
||||||
bottom_ = bottom;
|
|
||||||
top_ = top;
|
|
||||||
projDirty_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置视口范围
|
|
||||||
* @param rect 视口矩形
|
|
||||||
*
|
|
||||||
* 使用矩形设置相机的正交投影视口范围,会标记投影矩阵为脏
|
|
||||||
*/
|
|
||||||
void Camera::setViewport(const Rect &rect) {
|
|
||||||
left_ = rect.left();
|
|
||||||
right_ = rect.right();
|
|
||||||
bottom_ = rect.bottom();
|
|
||||||
top_ = rect.top();
|
|
||||||
projDirty_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取视口矩形
|
|
||||||
* @return 当前视口的矩形表示
|
|
||||||
*
|
|
||||||
* 返回当前相机的视口范围
|
|
||||||
*/
|
|
||||||
Rect Camera::getViewport() const {
|
|
||||||
return Rect(left_, top_, right_ - left_, bottom_ - top_);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取视图矩阵
|
|
||||||
* @return 视图矩阵
|
|
||||||
*
|
|
||||||
* 变换顺序:平移 -> 旋转 -> 缩放(逆序应用)
|
|
||||||
* View = T(-position) × R(-rotation) × S(1/zoom)
|
|
||||||
*/
|
|
||||||
glm::mat4 Camera::getViewMatrix() const {
|
|
||||||
if (viewDirty_) {
|
|
||||||
viewMatrix_ = glm::mat4(1.0f);
|
|
||||||
|
|
||||||
// 1. 平移(最后应用)
|
|
||||||
viewMatrix_ = glm::translate(viewMatrix_,
|
|
||||||
glm::vec3(-position_.x, -position_.y, 0.0f));
|
|
||||||
|
|
||||||
// 2. 旋转(中间应用)
|
|
||||||
if (rotation_ != 0.0f) {
|
|
||||||
viewMatrix_ = glm::rotate(viewMatrix_, -rotation_ * DEG_TO_RAD,
|
|
||||||
glm::vec3(0.0f, 0.0f, 1.0f));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 缩放(最先应用)
|
|
||||||
if (zoom_ != 1.0f) {
|
|
||||||
viewMatrix_ =
|
|
||||||
glm::scale(viewMatrix_, glm::vec3(1.0f / zoom_, 1.0f / zoom_, 1.0f));
|
|
||||||
}
|
|
||||||
|
|
||||||
viewDirty_ = false;
|
|
||||||
}
|
|
||||||
return viewMatrix_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取投影矩阵
|
|
||||||
* @return 正交投影矩阵
|
|
||||||
*
|
|
||||||
* 对于2D游戏,Y轴向下增长(屏幕坐标系)
|
|
||||||
* OpenGL默认Y轴向上,所以需要反转Y轴
|
|
||||||
*/
|
|
||||||
glm::mat4 Camera::getProjectionMatrix() const {
|
|
||||||
if (projDirty_) {
|
|
||||||
// 对于2D游戏,Y轴向下增长(屏幕坐标系)
|
|
||||||
// OpenGL默认Y轴向上,所以需要反转Y轴
|
|
||||||
// glm::ortho(left, right, bottom, top)
|
|
||||||
// 为了Y轴向下:传入 (bottom=height, top=0),这样Y轴翻转
|
|
||||||
projMatrix_ = glm::ortho(
|
|
||||||
left_, right_, // X轴:从左到右
|
|
||||||
bottom_, top_, // Y轴:从下到上(传入bottom>top,实现Y轴向下增长)
|
|
||||||
-1.0f, 1.0f);
|
|
||||||
projDirty_ = false;
|
|
||||||
}
|
|
||||||
return projMatrix_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取视图-投影矩阵
|
|
||||||
* @return 视图-投影矩阵
|
|
||||||
*/
|
|
||||||
glm::mat4 Camera::getViewProjectionMatrix() const {
|
|
||||||
return getProjectionMatrix() * getViewMatrix();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 将屏幕坐标转换为世界坐标
|
|
||||||
* @param screenPos 屏幕坐标
|
|
||||||
* @return 世界坐标
|
|
||||||
*/
|
|
||||||
Vec2 Camera::screenToWorld(const Vec2 &screenPos) const {
|
|
||||||
Vec2 logicPos = screenPos;
|
|
||||||
|
|
||||||
// 如果有视口适配器,先转换到逻辑坐标
|
|
||||||
if (viewportAdapter_) {
|
|
||||||
logicPos = viewportAdapter_->screenToLogic(screenPos);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用逆视图-投影矩阵转换
|
|
||||||
glm::mat4 invVP = glm::inverse(getViewProjectionMatrix());
|
|
||||||
glm::vec4 ndc(logicPos.x, logicPos.y, 0.0f, 1.0f);
|
|
||||||
glm::vec4 world = invVP * ndc;
|
|
||||||
return Vec2(world.x, world.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 将世界坐标转换为屏幕坐标
|
|
||||||
* @param worldPos 世界坐标
|
|
||||||
* @return 屏幕坐标
|
|
||||||
*/
|
|
||||||
Vec2 Camera::worldToScreen(const Vec2 &worldPos) const {
|
|
||||||
glm::vec4 world(worldPos.x, worldPos.y, 0.0f, 1.0f);
|
|
||||||
glm::vec4 screen = getViewProjectionMatrix() * world;
|
|
||||||
Vec2 logicPos(screen.x, screen.y);
|
|
||||||
|
|
||||||
// 如果有视口适配器,转换到屏幕坐标
|
|
||||||
if (viewportAdapter_) {
|
|
||||||
return viewportAdapter_->logicToScreen(logicPos);
|
|
||||||
}
|
|
||||||
return logicPos;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 将屏幕坐标转换为世界坐标
|
|
||||||
* @param x 屏幕X坐标
|
|
||||||
* @param y 屏幕Y坐标
|
|
||||||
* @return 世界坐标
|
|
||||||
*/
|
|
||||||
Vec2 Camera::screenToWorld(float x, float y) const {
|
|
||||||
return screenToWorld(Vec2(x, y));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 将世界坐标转换为屏幕坐标
|
|
||||||
* @param x 世界X坐标
|
|
||||||
* @param y 世界Y坐标
|
|
||||||
* @return 屏幕坐标
|
|
||||||
*/
|
|
||||||
Vec2 Camera::worldToScreen(float x, float y) const {
|
|
||||||
return worldToScreen(Vec2(x, y));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 移动相机位置
|
|
||||||
* @param offset 位置偏移量
|
|
||||||
*
|
|
||||||
* 按指定偏移量移动相机位置,会标记视图矩阵为脏
|
|
||||||
*/
|
|
||||||
void Camera::move(const Vec2 &offset) {
|
|
||||||
position_ += offset;
|
|
||||||
viewDirty_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 移动相机位置
|
|
||||||
* @param x X方向偏移量
|
|
||||||
* @param y Y方向偏移量
|
|
||||||
*
|
|
||||||
* 按指定偏移量移动相机位置,会标记视图矩阵为脏
|
|
||||||
*/
|
|
||||||
void Camera::move(float x, float y) {
|
|
||||||
position_.x += x;
|
|
||||||
position_.y += y;
|
|
||||||
viewDirty_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置相机边界限制
|
|
||||||
* @param bounds 边界矩形
|
|
||||||
*
|
|
||||||
* 设置相机的移动边界,相机位置将被限制在此边界内
|
|
||||||
*/
|
|
||||||
void Camera::setBounds(const Rect &bounds) {
|
|
||||||
bounds_ = bounds;
|
|
||||||
hasBounds_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 清除相机边界限制
|
|
||||||
*
|
|
||||||
* 移除相机的移动边界限制
|
|
||||||
*/
|
|
||||||
void Camera::clearBounds() { hasBounds_ = false; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 将相机位置限制在边界内
|
|
||||||
*
|
|
||||||
* 如果设置了边界,将相机位置限制在边界矩形内
|
|
||||||
*/
|
|
||||||
void Camera::clampToBounds() {
|
|
||||||
if (!hasBounds_)
|
|
||||||
return;
|
|
||||||
|
|
||||||
float viewportWidth = (right_ - left_) / zoom_;
|
|
||||||
float viewportHeight = (bottom_ - top_) / zoom_;
|
|
||||||
|
|
||||||
float minX = bounds_.left() + viewportWidth * 0.5f;
|
|
||||||
float maxX = bounds_.right() - viewportWidth * 0.5f;
|
|
||||||
float minY = bounds_.top() + viewportHeight * 0.5f;
|
|
||||||
float maxY = bounds_.bottom() - viewportHeight * 0.5f;
|
|
||||||
|
|
||||||
if (minX > maxX) {
|
|
||||||
position_.x = bounds_.center().x;
|
|
||||||
} else {
|
|
||||||
position_.x = std::clamp(position_.x, minX, maxX);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (minY > maxY) {
|
|
||||||
position_.y = bounds_.center().y;
|
|
||||||
} else {
|
|
||||||
position_.y = std::clamp(position_.y, minY, maxY);
|
|
||||||
}
|
|
||||||
|
|
||||||
viewDirty_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 将相机移动到目标位置
|
|
||||||
* @param target 目标位置
|
|
||||||
*
|
|
||||||
* 设置相机位置到指定的世界坐标
|
|
||||||
*/
|
|
||||||
void Camera::lookAt(const Vec2 &target) {
|
|
||||||
position_ = target;
|
|
||||||
viewDirty_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置视口适配器
|
|
||||||
* @param adapter 视口适配器指针
|
|
||||||
*/
|
|
||||||
void Camera::setViewportAdapter(ViewportAdapter *adapter) {
|
|
||||||
viewportAdapter_ = adapter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 根据视口适配器自动设置视口
|
|
||||||
*
|
|
||||||
* 如果设置了视口适配器,根据其配置自动设置相机的视口范围
|
|
||||||
*/
|
|
||||||
void Camera::applyViewportAdapter() {
|
|
||||||
if (viewportAdapter_) {
|
|
||||||
const auto &config = viewportAdapter_->getConfig();
|
|
||||||
setViewport(0.0f, config.logicWidth, config.logicHeight, 0.0f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,451 +0,0 @@
|
||||||
#include <extra2d/graphics/camera/viewport_adapter.h>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 默认构造函数
|
|
||||||
*
|
|
||||||
* 创建一个未配置的视口适配器
|
|
||||||
*/
|
|
||||||
ViewportAdapter::ViewportAdapter() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 构造函数
|
|
||||||
* @param logicWidth 逻辑宽度
|
|
||||||
* @param logicHeight 逻辑高度
|
|
||||||
*
|
|
||||||
* 创建一个指定逻辑尺寸的视口适配器
|
|
||||||
*/
|
|
||||||
ViewportAdapter::ViewportAdapter(float logicWidth, float logicHeight) {
|
|
||||||
config_.logicWidth = logicWidth;
|
|
||||||
config_.logicHeight = logicHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置视口配置
|
|
||||||
* @param config 视口配置
|
|
||||||
*
|
|
||||||
* 设置完整的视口配置并标记矩阵为脏
|
|
||||||
*/
|
|
||||||
void ViewportAdapter::setConfig(const ViewportConfig &config) {
|
|
||||||
config_ = config;
|
|
||||||
matrixDirty_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置逻辑尺寸
|
|
||||||
* @param width 逻辑宽度
|
|
||||||
* @param height 逻辑高度
|
|
||||||
*
|
|
||||||
* 设置视口的逻辑尺寸并标记矩阵为脏
|
|
||||||
*/
|
|
||||||
void ViewportAdapter::setLogicSize(float width, float height) {
|
|
||||||
config_.logicWidth = width;
|
|
||||||
config_.logicHeight = height;
|
|
||||||
matrixDirty_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置视口模式
|
|
||||||
* @param mode 视口模式
|
|
||||||
*
|
|
||||||
* 设置视口适配模式(宽高比、拉伸、居中等)
|
|
||||||
*/
|
|
||||||
void ViewportAdapter::setMode(ViewportMode mode) {
|
|
||||||
config_.mode = mode;
|
|
||||||
matrixDirty_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置黑边位置
|
|
||||||
* @param position 黑边位置
|
|
||||||
*
|
|
||||||
* 设置黑边相对于视口的位置
|
|
||||||
*/
|
|
||||||
void ViewportAdapter::setLetterboxPosition(LetterboxPosition position) {
|
|
||||||
config_.letterboxPosition = position;
|
|
||||||
matrixDirty_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置黑边颜色
|
|
||||||
* @param color 黑边颜色
|
|
||||||
*
|
|
||||||
* 设置黑边区域的填充颜色
|
|
||||||
*/
|
|
||||||
void ViewportAdapter::setLetterboxColor(const Color &color) {
|
|
||||||
config_.letterboxColor = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 更新视口适配
|
|
||||||
* @param screenWidth 屏幕宽度
|
|
||||||
* @param screenHeight 屏幕高度
|
|
||||||
*
|
|
||||||
* 根据屏幕尺寸和配置计算视口参数
|
|
||||||
*/
|
|
||||||
void ViewportAdapter::update(int screenWidth, int screenHeight) {
|
|
||||||
if (screenWidth_ == screenWidth && screenHeight_ == screenHeight &&
|
|
||||||
!matrixDirty_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
screenWidth_ = screenWidth;
|
|
||||||
screenHeight_ = screenHeight;
|
|
||||||
matrixDirty_ = true;
|
|
||||||
|
|
||||||
result_.hasLetterbox = false;
|
|
||||||
result_.letterbox.top = Rect::Zero();
|
|
||||||
result_.letterbox.bottom = Rect::Zero();
|
|
||||||
result_.letterbox.left = Rect::Zero();
|
|
||||||
result_.letterbox.right = Rect::Zero();
|
|
||||||
|
|
||||||
switch (config_.mode) {
|
|
||||||
case ViewportMode::AspectRatio:
|
|
||||||
calculateAspectRatio();
|
|
||||||
break;
|
|
||||||
case ViewportMode::Stretch:
|
|
||||||
calculateStretch();
|
|
||||||
break;
|
|
||||||
case ViewportMode::Center:
|
|
||||||
calculateCenter();
|
|
||||||
break;
|
|
||||||
case ViewportMode::Custom:
|
|
||||||
calculateCustom();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 计算宽高比适配模式
|
|
||||||
*
|
|
||||||
* 保持逻辑宽高比,根据屏幕尺寸计算缩放和偏移
|
|
||||||
*/
|
|
||||||
void ViewportAdapter::calculateAspectRatio() {
|
|
||||||
if (config_.logicHeight <= 0.0f || screenHeight_ <= 0) {
|
|
||||||
result_ = ViewportResult();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
float logicAspect = config_.logicWidth / config_.logicHeight;
|
|
||||||
float screenAspect = static_cast<float>(screenWidth_) / screenHeight_;
|
|
||||||
|
|
||||||
if (screenAspect > logicAspect) {
|
|
||||||
result_.uniformScale = static_cast<float>(screenHeight_) / config_.logicHeight;
|
|
||||||
result_.scaleX = result_.uniformScale;
|
|
||||||
result_.scaleY = result_.uniformScale;
|
|
||||||
result_.viewport.size.width = config_.logicWidth * result_.uniformScale;
|
|
||||||
result_.viewport.size.height = static_cast<float>(screenHeight_);
|
|
||||||
result_.offset.x = (screenWidth_ - result_.viewport.size.width) / 2.0f;
|
|
||||||
result_.offset.y = 0.0f;
|
|
||||||
} else {
|
|
||||||
result_.uniformScale = static_cast<float>(screenWidth_) / config_.logicWidth;
|
|
||||||
result_.scaleX = result_.uniformScale;
|
|
||||||
result_.scaleY = result_.uniformScale;
|
|
||||||
result_.viewport.size.width = static_cast<float>(screenWidth_);
|
|
||||||
result_.viewport.size.height = config_.logicHeight * result_.uniformScale;
|
|
||||||
result_.offset.x = 0.0f;
|
|
||||||
result_.offset.y = (screenHeight_ - result_.viewport.size.height) / 2.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
result_.viewport.origin = result_.offset;
|
|
||||||
|
|
||||||
applyLetterboxPosition(
|
|
||||||
static_cast<float>(screenWidth_) - result_.viewport.size.width,
|
|
||||||
static_cast<float>(screenHeight_) - result_.viewport.size.height);
|
|
||||||
|
|
||||||
calculateLetterbox();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 计算拉伸模式
|
|
||||||
*
|
|
||||||
* 拉伸逻辑视口以填满整个屏幕
|
|
||||||
*/
|
|
||||||
void ViewportAdapter::calculateStretch() {
|
|
||||||
result_.scaleX = static_cast<float>(screenWidth_) / config_.logicWidth;
|
|
||||||
result_.scaleY = static_cast<float>(screenHeight_) / config_.logicHeight;
|
|
||||||
result_.uniformScale = std::min(result_.scaleX, result_.scaleY);
|
|
||||||
|
|
||||||
result_.viewport.origin = Vec2::Zero();
|
|
||||||
result_.viewport.size.width = static_cast<float>(screenWidth_);
|
|
||||||
result_.viewport.size.height = static_cast<float>(screenHeight_);
|
|
||||||
|
|
||||||
result_.offset = Vec2::Zero();
|
|
||||||
result_.hasLetterbox = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 计算居中模式
|
|
||||||
*
|
|
||||||
* 将逻辑视口居中显示,可选自动缩放
|
|
||||||
*/
|
|
||||||
void ViewportAdapter::calculateCenter() {
|
|
||||||
float displayWidth = config_.logicWidth;
|
|
||||||
float displayHeight = config_.logicHeight;
|
|
||||||
|
|
||||||
if (config_.autoScaleInCenterMode) {
|
|
||||||
float scaleX = static_cast<float>(screenWidth_) / config_.logicWidth;
|
|
||||||
float scaleY = static_cast<float>(screenHeight_) / config_.logicHeight;
|
|
||||||
result_.uniformScale = std::min(scaleX, scaleY);
|
|
||||||
|
|
||||||
if (result_.uniformScale < 1.0f) {
|
|
||||||
displayWidth = config_.logicWidth * result_.uniformScale;
|
|
||||||
displayHeight = config_.logicHeight * result_.uniformScale;
|
|
||||||
} else {
|
|
||||||
result_.uniformScale = 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
result_.scaleX = result_.uniformScale;
|
|
||||||
result_.scaleY = result_.uniformScale;
|
|
||||||
} else {
|
|
||||||
result_.scaleX = 1.0f;
|
|
||||||
result_.scaleY = 1.0f;
|
|
||||||
result_.uniformScale = 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
result_.offset.x = (screenWidth_ - displayWidth) / 2.0f;
|
|
||||||
result_.offset.y = (screenHeight_ - displayHeight) / 2.0f;
|
|
||||||
|
|
||||||
result_.viewport.origin = result_.offset;
|
|
||||||
result_.viewport.size.width = displayWidth;
|
|
||||||
result_.viewport.size.height = displayHeight;
|
|
||||||
|
|
||||||
applyLetterboxPosition(
|
|
||||||
static_cast<float>(screenWidth_) - displayWidth,
|
|
||||||
static_cast<float>(screenHeight_) - displayHeight);
|
|
||||||
|
|
||||||
calculateLetterbox();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 计算自定义模式
|
|
||||||
*
|
|
||||||
* 使用自定义缩放和偏移参数
|
|
||||||
*/
|
|
||||||
void ViewportAdapter::calculateCustom() {
|
|
||||||
result_.scaleX = config_.customScale;
|
|
||||||
result_.scaleY = config_.customScale;
|
|
||||||
result_.uniformScale = config_.customScale;
|
|
||||||
|
|
||||||
if (config_.customViewport.empty()) {
|
|
||||||
float displayWidth = config_.logicWidth * config_.customScale;
|
|
||||||
float displayHeight = config_.logicHeight * config_.customScale;
|
|
||||||
|
|
||||||
result_.offset = config_.customOffset;
|
|
||||||
result_.viewport.origin = result_.offset;
|
|
||||||
result_.viewport.size.width = displayWidth;
|
|
||||||
result_.viewport.size.height = displayHeight;
|
|
||||||
} else {
|
|
||||||
result_.viewport = config_.customViewport;
|
|
||||||
result_.offset = config_.customViewport.origin;
|
|
||||||
}
|
|
||||||
|
|
||||||
calculateLetterbox();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 计算黑边区域
|
|
||||||
*
|
|
||||||
* 根据视口偏移计算上下左右黑边矩形
|
|
||||||
*/
|
|
||||||
void ViewportAdapter::calculateLetterbox() {
|
|
||||||
result_.hasLetterbox = false;
|
|
||||||
|
|
||||||
float screenW = static_cast<float>(screenWidth_);
|
|
||||||
float screenH = static_cast<float>(screenHeight_);
|
|
||||||
|
|
||||||
if (result_.offset.y > 0.0f) {
|
|
||||||
result_.hasLetterbox = true;
|
|
||||||
result_.letterbox.top =
|
|
||||||
Rect(0.0f, 0.0f, screenW, result_.offset.y);
|
|
||||||
result_.letterbox.bottom =
|
|
||||||
Rect(0.0f, result_.offset.y + result_.viewport.size.height, screenW,
|
|
||||||
result_.offset.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result_.offset.x > 0.0f) {
|
|
||||||
result_.hasLetterbox = true;
|
|
||||||
result_.letterbox.left =
|
|
||||||
Rect(0.0f, 0.0f, result_.offset.x, screenH);
|
|
||||||
result_.letterbox.right =
|
|
||||||
Rect(result_.offset.x + result_.viewport.size.width, 0.0f,
|
|
||||||
result_.offset.x, screenH);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 应用黑边位置
|
|
||||||
* @param extraWidth 额外宽度
|
|
||||||
* @param extraHeight 额外高度
|
|
||||||
*
|
|
||||||
* 根据配置调整视口偏移以实现不同的黑边位置
|
|
||||||
*/
|
|
||||||
void ViewportAdapter::applyLetterboxPosition(float extraWidth,
|
|
||||||
float extraHeight) {
|
|
||||||
if (extraWidth <= 0.0f && extraHeight <= 0.0f) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (config_.letterboxPosition) {
|
|
||||||
case LetterboxPosition::Center:
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LetterboxPosition::LeftTop:
|
|
||||||
if (extraWidth > 0.0f) {
|
|
||||||
result_.offset.x = 0.0f;
|
|
||||||
}
|
|
||||||
if (extraHeight > 0.0f) {
|
|
||||||
result_.offset.y = 0.0f;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LetterboxPosition::RightTop:
|
|
||||||
if (extraWidth > 0.0f) {
|
|
||||||
result_.offset.x = extraWidth;
|
|
||||||
}
|
|
||||||
if (extraHeight > 0.0f) {
|
|
||||||
result_.offset.y = 0.0f;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LetterboxPosition::LeftBottom:
|
|
||||||
if (extraWidth > 0.0f) {
|
|
||||||
result_.offset.x = 0.0f;
|
|
||||||
}
|
|
||||||
if (extraHeight > 0.0f) {
|
|
||||||
result_.offset.y = extraHeight;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LetterboxPosition::RightBottom:
|
|
||||||
if (extraWidth > 0.0f) {
|
|
||||||
result_.offset.x = extraWidth;
|
|
||||||
}
|
|
||||||
if (extraHeight > 0.0f) {
|
|
||||||
result_.offset.y = extraHeight;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
result_.viewport.origin = result_.offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 将屏幕坐标转换为逻辑坐标
|
|
||||||
* @param screenPos 屏幕坐标
|
|
||||||
* @return 逻辑坐标
|
|
||||||
*
|
|
||||||
* 根据当前缩放和偏移计算对应的逻辑坐标
|
|
||||||
*/
|
|
||||||
Vec2 ViewportAdapter::screenToLogic(const Vec2 &screenPos) const {
|
|
||||||
return Vec2((screenPos.x - result_.offset.x) / result_.scaleX,
|
|
||||||
(screenPos.y - result_.offset.y) / result_.scaleY);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 将逻辑坐标转换为屏幕坐标
|
|
||||||
* @param logicPos 逻辑坐标
|
|
||||||
* @return 屏幕坐标
|
|
||||||
*
|
|
||||||
* 根据当前缩放和偏移计算对应的屏幕坐标
|
|
||||||
*/
|
|
||||||
Vec2 ViewportAdapter::logicToScreen(const Vec2 &logicPos) const {
|
|
||||||
return Vec2(logicPos.x * result_.scaleX + result_.offset.x,
|
|
||||||
logicPos.y * result_.scaleY + result_.offset.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 将屏幕坐标转换为逻辑坐标
|
|
||||||
* @param x 屏幕X坐标
|
|
||||||
* @param y 屏幕Y坐标
|
|
||||||
* @return 逻辑坐标
|
|
||||||
*/
|
|
||||||
Vec2 ViewportAdapter::screenToLogic(float x, float y) const {
|
|
||||||
return screenToLogic(Vec2(x, y));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 将逻辑坐标转换为屏幕坐标
|
|
||||||
* @param x 逻辑X坐标
|
|
||||||
* @param y 逻辑Y坐标
|
|
||||||
* @return 屏幕坐标
|
|
||||||
*/
|
|
||||||
Vec2 ViewportAdapter::logicToScreen(float x, float y) const {
|
|
||||||
return logicToScreen(Vec2(x, y));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取视口变换矩阵
|
|
||||||
* @return 4x4变换矩阵
|
|
||||||
*
|
|
||||||
* 返回用于将逻辑坐标转换为屏幕坐标的变换矩阵
|
|
||||||
*/
|
|
||||||
glm::mat4 ViewportAdapter::getMatrix() const {
|
|
||||||
if (matrixDirty_) {
|
|
||||||
viewportMatrix_ = glm::mat4(1.0f);
|
|
||||||
viewportMatrix_ = glm::translate(viewportMatrix_,
|
|
||||||
glm::vec3(result_.offset.x, result_.offset.y, 0.0f));
|
|
||||||
viewportMatrix_ = glm::scale(viewportMatrix_,
|
|
||||||
glm::vec3(result_.scaleX, result_.scaleY, 1.0f));
|
|
||||||
matrixDirty_ = false;
|
|
||||||
}
|
|
||||||
return viewportMatrix_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取视口逆变换矩阵
|
|
||||||
* @return 4x4逆变换矩阵
|
|
||||||
*
|
|
||||||
* 返回用于将屏幕坐标转换为逻辑坐标的逆变换矩阵
|
|
||||||
*/
|
|
||||||
glm::mat4 ViewportAdapter::getInvMatrix() const {
|
|
||||||
if (matrixDirty_) {
|
|
||||||
getMatrix();
|
|
||||||
}
|
|
||||||
inverseViewportMatrix_ = glm::inverse(viewportMatrix_);
|
|
||||||
return inverseViewportMatrix_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查屏幕坐标是否在视口内
|
|
||||||
* @param screenPos 屏幕坐标
|
|
||||||
* @return 在视口内返回true,否则返回false
|
|
||||||
*/
|
|
||||||
bool ViewportAdapter::isInViewport(const Vec2 &screenPos) const {
|
|
||||||
return result_.viewport.containsPoint(screenPos);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查屏幕坐标是否在黑边区域内
|
|
||||||
* @param screenPos 屏幕坐标
|
|
||||||
* @return 在黑边区域内返回true,否则返回false
|
|
||||||
*/
|
|
||||||
bool ViewportAdapter::isInLetterbox(const Vec2 &screenPos) const {
|
|
||||||
if (!result_.hasLetterbox) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!result_.letterbox.top.empty() &&
|
|
||||||
result_.letterbox.top.containsPoint(screenPos)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!result_.letterbox.bottom.empty() &&
|
|
||||||
result_.letterbox.bottom.containsPoint(screenPos)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!result_.letterbox.left.empty() &&
|
|
||||||
result_.letterbox.left.containsPoint(screenPos)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!result_.letterbox.right.empty() &&
|
|
||||||
result_.letterbox.right.containsPoint(screenPos)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,266 +0,0 @@
|
||||||
#include <extra2d/graphics/core/render_command.h>
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// RenderCommand 便捷构造函数
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建精灵渲染命令
|
|
||||||
*
|
|
||||||
* 创建一个用于渲染2D精灵的RenderCommand对象,包含纹理、目标区域、源区域、
|
|
||||||
* 着色、旋转、锚点和层级等信息
|
|
||||||
*
|
|
||||||
* @param tex 指向纹理对象的指针
|
|
||||||
* @param dest 目标渲染区域
|
|
||||||
* @param src 源纹理区域
|
|
||||||
* @param tint 着色颜色
|
|
||||||
* @param rot 旋转角度(弧度)
|
|
||||||
* @param anc 锚点位置(0.0-1.0范围)
|
|
||||||
* @param lyr 渲染层级
|
|
||||||
* @return 配置好的RenderCommand对象
|
|
||||||
*/
|
|
||||||
RenderCommand RenderCommand::makeSprite(const Texture* tex, const Rect& dest,
|
|
||||||
const Rect& src, const Color& tint,
|
|
||||||
float rot, const Vec2& anc,
|
|
||||||
uint32_t lyr) {
|
|
||||||
RenderCommand cmd;
|
|
||||||
cmd.type = RenderCommandType::Sprite;
|
|
||||||
cmd.layer = lyr;
|
|
||||||
cmd.transform = glm::mat4(1.0f);
|
|
||||||
|
|
||||||
SpriteCommandData data;
|
|
||||||
data.texture = tex;
|
|
||||||
data.destRect = dest;
|
|
||||||
data.srcRect = src;
|
|
||||||
data.tint = tint;
|
|
||||||
data.rotation = rot;
|
|
||||||
data.anchor = anc;
|
|
||||||
// 生成排序键:纹理指针的高位 + 层级的低位
|
|
||||||
data.sortKey = (reinterpret_cast<uintptr_t>(tex) >> 4) & 0xFFFFFFF0;
|
|
||||||
data.sortKey |= (lyr & 0xF);
|
|
||||||
|
|
||||||
cmd.data = data;
|
|
||||||
return cmd;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建线段渲染命令
|
|
||||||
*
|
|
||||||
* 创建一个用于渲染线段的RenderCommand对象
|
|
||||||
*
|
|
||||||
* @param s 线段起点坐标
|
|
||||||
* @param e 线段终点坐标
|
|
||||||
* @param c 线段颜色
|
|
||||||
* @param w 线段宽度
|
|
||||||
* @param lyr 渲染层级
|
|
||||||
* @return 配置好的RenderCommand对象
|
|
||||||
*/
|
|
||||||
RenderCommand RenderCommand::makeLine(const Vec2& s, const Vec2& e, const Color& c,
|
|
||||||
float w, uint32_t lyr) {
|
|
||||||
RenderCommand cmd;
|
|
||||||
cmd.type = RenderCommandType::Line;
|
|
||||||
cmd.layer = lyr;
|
|
||||||
cmd.transform = glm::mat4(1.0f);
|
|
||||||
|
|
||||||
LineCommandData data;
|
|
||||||
data.start = s;
|
|
||||||
data.end = e;
|
|
||||||
data.color = c;
|
|
||||||
data.width = w;
|
|
||||||
|
|
||||||
cmd.data = data;
|
|
||||||
return cmd;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建矩形渲染命令
|
|
||||||
*
|
|
||||||
* 创建一个用于渲染矩形的RenderCommand对象,可选择填充或描边模式
|
|
||||||
*
|
|
||||||
* @param r 矩形区域
|
|
||||||
* @param c 矩形颜色
|
|
||||||
* @param w 线条宽度(仅描边模式有效)
|
|
||||||
* @param fill 是否填充矩形
|
|
||||||
* @param lyr 渲染层级
|
|
||||||
* @return 配置好的RenderCommand对象
|
|
||||||
*/
|
|
||||||
RenderCommand RenderCommand::makeRect(const Rect& r, const Color& c,
|
|
||||||
float w, bool fill, uint32_t lyr) {
|
|
||||||
RenderCommand cmd;
|
|
||||||
cmd.type = fill ? RenderCommandType::FilledRect : RenderCommandType::Rect;
|
|
||||||
cmd.layer = lyr;
|
|
||||||
cmd.transform = glm::mat4(1.0f);
|
|
||||||
|
|
||||||
RectCommandData data;
|
|
||||||
data.rect = r;
|
|
||||||
data.color = c;
|
|
||||||
data.width = w;
|
|
||||||
data.filled = fill;
|
|
||||||
|
|
||||||
cmd.data = data;
|
|
||||||
return cmd;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// RenderCommandBuffer 实现
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 默认构造函数
|
|
||||||
*
|
|
||||||
* 初始化渲染命令缓冲区,预留初始容量
|
|
||||||
*/
|
|
||||||
RenderCommandBuffer::RenderCommandBuffer() : nextOrder_(0) {
|
|
||||||
commands_.reserve(INITIAL_CAPACITY);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 析构函数
|
|
||||||
*
|
|
||||||
* 释放渲染命令缓冲区资源
|
|
||||||
*/
|
|
||||||
RenderCommandBuffer::~RenderCommandBuffer() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 添加渲染命令(左值引用版本)
|
|
||||||
*
|
|
||||||
* 将渲染命令以拷贝方式添加到缓冲区,自动分配顺序号
|
|
||||||
*
|
|
||||||
* @param cmd 要添加的渲染命令
|
|
||||||
*/
|
|
||||||
void RenderCommandBuffer::addCommand(const RenderCommand& cmd) {
|
|
||||||
if (commands_.size() >= MAX_CAPACITY) {
|
|
||||||
// 缓冲区已满,可能需要立即刷新
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderCommand copy = cmd;
|
|
||||||
copy.order = nextOrder_++;
|
|
||||||
commands_.push_back(std::move(copy));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 添加渲染命令(右值引用版本)
|
|
||||||
*
|
|
||||||
* 将渲染命令以移动方式添加到缓冲区,自动分配顺序号
|
|
||||||
*
|
|
||||||
* @param cmd 要添加的渲染命令(右值引用)
|
|
||||||
*/
|
|
||||||
void RenderCommandBuffer::addCommand(RenderCommand&& cmd) {
|
|
||||||
if (commands_.size() >= MAX_CAPACITY) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.order = nextOrder_++;
|
|
||||||
commands_.push_back(std::move(cmd));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 原地构造渲染命令
|
|
||||||
*
|
|
||||||
* 在缓冲区中直接构造一个渲染命令对象,避免额外的拷贝或移动操作
|
|
||||||
*
|
|
||||||
* @return 新构造的渲染命令对象的引用
|
|
||||||
*/
|
|
||||||
RenderCommand& RenderCommandBuffer::emplaceCommand() {
|
|
||||||
if (commands_.size() >= MAX_CAPACITY) {
|
|
||||||
// 如果已满,返回一个虚拟命令(不应该发生)
|
|
||||||
static RenderCommand dummy;
|
|
||||||
return dummy;
|
|
||||||
}
|
|
||||||
|
|
||||||
commands_.emplace_back();
|
|
||||||
commands_.back().order = nextOrder_++;
|
|
||||||
return commands_.back();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 对渲染命令进行排序
|
|
||||||
*
|
|
||||||
* 按层级、命令类型、纹理/材质和提交顺序对命令进行排序,
|
|
||||||
* 以优化渲染性能和批处理效率
|
|
||||||
*/
|
|
||||||
void RenderCommandBuffer::sortCommands() {
|
|
||||||
// 按以下优先级排序:
|
|
||||||
// 1. 层级 (layer) - 低层级先渲染
|
|
||||||
// 2. 命令类型 - 精灵类命令优先批处理
|
|
||||||
// 3. 纹理/材质 - 相同纹理的精灵连续渲染
|
|
||||||
// 4. 提交顺序 - 保证稳定性
|
|
||||||
|
|
||||||
std::sort(commands_.begin(), commands_.end(), compareCommands);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 清空缓冲区
|
|
||||||
*
|
|
||||||
* 移除所有渲染命令并重置顺序计数器
|
|
||||||
*/
|
|
||||||
void RenderCommandBuffer::clear() {
|
|
||||||
commands_.clear();
|
|
||||||
nextOrder_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 预留缓冲区容量
|
|
||||||
*
|
|
||||||
* 预先分配缓冲区内存以减少动态分配开销
|
|
||||||
*
|
|
||||||
* @param capacity 要预留的容量大小
|
|
||||||
*/
|
|
||||||
void RenderCommandBuffer::reserve(size_t capacity) {
|
|
||||||
if (capacity <= MAX_CAPACITY) {
|
|
||||||
commands_.reserve(capacity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 渲染命令比较函数
|
|
||||||
*
|
|
||||||
* 用于排序的比较函数,按层级、类型、纹理和顺序进行比较
|
|
||||||
*
|
|
||||||
* @param a 第一个渲染命令
|
|
||||||
* @param b 第二个渲染命令
|
|
||||||
* @return 如果a应排在b前面返回true,否则返回false
|
|
||||||
*/
|
|
||||||
bool RenderCommandBuffer::compareCommands(const RenderCommand& a, const RenderCommand& b) {
|
|
||||||
// 首先按层级排序
|
|
||||||
if (a.layer != b.layer) {
|
|
||||||
return a.layer < b.layer;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 然后按类型排序(精灵类命令放在一起以便批处理)
|
|
||||||
if (a.type != b.type) {
|
|
||||||
// 精灵和文本命令优先(需要纹理)
|
|
||||||
bool aIsSprite = (a.type == RenderCommandType::Sprite ||
|
|
||||||
a.type == RenderCommandType::Text);
|
|
||||||
bool bIsSprite = (b.type == RenderCommandType::Sprite ||
|
|
||||||
b.type == RenderCommandType::Text);
|
|
||||||
|
|
||||||
if (aIsSprite != bIsSprite) {
|
|
||||||
return aIsSprite > bIsSprite; // 精灵类命令在前
|
|
||||||
}
|
|
||||||
|
|
||||||
return static_cast<uint8_t>(a.type) < static_cast<uint8_t>(b.type);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 对于精灵命令,按纹理排序
|
|
||||||
if (a.type == RenderCommandType::Sprite && b.type == RenderCommandType::Sprite) {
|
|
||||||
const auto& dataA = std::get<SpriteCommandData>(a.data);
|
|
||||||
const auto& dataB = std::get<SpriteCommandData>(b.data);
|
|
||||||
if (dataA.texture != dataB.texture) {
|
|
||||||
return dataA.texture < dataB.texture;
|
|
||||||
}
|
|
||||||
// 相同纹理时按 sortKey 排序
|
|
||||||
if (dataA.sortKey != dataB.sortKey) {
|
|
||||||
return dataA.sortKey < dataB.sortKey;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 最后按提交顺序排序(保证稳定性)
|
|
||||||
return a.order < b.order;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
#include <extra2d/app/application.h>
|
|
||||||
#include <extra2d/core/registry.h>
|
|
||||||
#include <extra2d/core/service_locator.h>
|
|
||||||
#include <extra2d/graphics/core/render_module.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_renderer.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_shader.h>
|
|
||||||
#include <extra2d/graphics/shader/shader_manager.h>
|
|
||||||
#include <extra2d/platform/window_module.h>
|
|
||||||
#include <extra2d/services/logger_service.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
RenderModule::RenderModule(std::function<void(RenderCfg &)> configFn) {
|
|
||||||
configFn(cfg_);
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderModule::~RenderModule() {
|
|
||||||
if (initialized_) {
|
|
||||||
shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RenderModule::init() {
|
|
||||||
if (initialized_)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
auto *winMod = Registry::instance().get<WindowModule>();
|
|
||||||
if (!winMod || !winMod->win()) {
|
|
||||||
E2D_LOG_ERROR("窗口模块不可用");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ShaderManager::getInstance().isInitialized()) {
|
|
||||||
auto factory = makeShared<GLShaderFactory>();
|
|
||||||
if (!ShaderManager::getInstance().init(factory)) {
|
|
||||||
E2D_LOG_WARN("使用默认路径初始化 ShaderManager 失败");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
E2D_LOG_INFO("正在创建 OpenGL 渲染后端");
|
|
||||||
renderer_ = makeUnique<Renderer>();
|
|
||||||
|
|
||||||
if (!renderer_) {
|
|
||||||
E2D_LOG_ERROR("创建渲染后端失败");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!renderer_->init(winMod->win())) {
|
|
||||||
E2D_LOG_ERROR("初始化渲染后端失败");
|
|
||||||
renderer_.reset();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
E2D_LOG_INFO("渲染模块初始化成功");
|
|
||||||
initialized_ = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderModule::shutdown() {
|
|
||||||
if (!initialized_)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (renderer_) {
|
|
||||||
renderer_->shutdown();
|
|
||||||
renderer_.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
initialized_ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,779 +0,0 @@
|
||||||
#include <extra2d/core/service_locator.h>
|
|
||||||
#include <extra2d/graphics/core/render_target.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
|
||||||
#include <extra2d/services/logger_service.h>
|
|
||||||
#include <glad/glad.h>
|
|
||||||
|
|
||||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
|
||||||
#include <stb/stb_image_write.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// RenderTarget实现
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 默认构造函数
|
|
||||||
*
|
|
||||||
* 创建一个空的渲染目标对象
|
|
||||||
*/
|
|
||||||
RenderTarget::RenderTarget() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 析构函数
|
|
||||||
*
|
|
||||||
* 销毁渲染目标并释放相关资源
|
|
||||||
*/
|
|
||||||
RenderTarget::~RenderTarget() { destroy(); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 移动构造函数
|
|
||||||
* @param other 源渲染目标对象
|
|
||||||
*
|
|
||||||
* 将其他渲染目标的资源转移到新对象
|
|
||||||
*/
|
|
||||||
RenderTarget::RenderTarget(RenderTarget &&other) noexcept
|
|
||||||
: fbo_(other.fbo_), rbo_(other.rbo_),
|
|
||||||
colorTexture_(std::move(other.colorTexture_)),
|
|
||||||
depthTexture_(std::move(other.depthTexture_)), width_(other.width_),
|
|
||||||
height_(other.height_), colorFormat_(other.colorFormat_),
|
|
||||||
hasDepth_(other.hasDepth_), hasStencil_(other.hasStencil_),
|
|
||||||
samples_(other.samples_) {
|
|
||||||
other.fbo_ = 0;
|
|
||||||
other.rbo_ = 0;
|
|
||||||
other.width_ = 0;
|
|
||||||
other.height_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 移动赋值运算符
|
|
||||||
* @param other 源渲染目标对象
|
|
||||||
* @return 当前对象引用
|
|
||||||
*
|
|
||||||
* 将其他渲染目标的资源转移到当前对象
|
|
||||||
*/
|
|
||||||
RenderTarget &RenderTarget::operator=(RenderTarget &&other) noexcept {
|
|
||||||
if (this != &other) {
|
|
||||||
destroy();
|
|
||||||
|
|
||||||
fbo_ = other.fbo_;
|
|
||||||
rbo_ = other.rbo_;
|
|
||||||
colorTexture_ = std::move(other.colorTexture_);
|
|
||||||
depthTexture_ = std::move(other.depthTexture_);
|
|
||||||
width_ = other.width_;
|
|
||||||
height_ = other.height_;
|
|
||||||
colorFormat_ = other.colorFormat_;
|
|
||||||
hasDepth_ = other.hasDepth_;
|
|
||||||
hasStencil_ = other.hasStencil_;
|
|
||||||
samples_ = other.samples_;
|
|
||||||
|
|
||||||
other.fbo_ = 0;
|
|
||||||
other.rbo_ = 0;
|
|
||||||
other.width_ = 0;
|
|
||||||
other.height_ = 0;
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 根据配置创建渲染目标
|
|
||||||
* @param config 渲染目标配置
|
|
||||||
* @return 创建成功返回true,失败返回false
|
|
||||||
*
|
|
||||||
* 根据指定的配置参数创建帧缓冲对象
|
|
||||||
*/
|
|
||||||
bool RenderTarget::create(const RenderTargetConfig &config) {
|
|
||||||
destroy();
|
|
||||||
|
|
||||||
width_ = config.width;
|
|
||||||
height_ = config.height;
|
|
||||||
colorFormat_ = config.colorFormat;
|
|
||||||
hasDepth_ = config.hasDepth;
|
|
||||||
hasStencil_ = config.hasStencil;
|
|
||||||
samples_ = config.samples;
|
|
||||||
|
|
||||||
if (!createFBO()) {
|
|
||||||
E2D_ERROR("创建渲染目标失败: {}x{}", width_, height_);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
E2D_INFO("创建渲染目标: {}x{} (深度:{}, 模板:{}, 采样:{})", width_, height_,
|
|
||||||
hasDepth_, hasStencil_, samples_);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从现有纹理创建渲染目标
|
|
||||||
* @param texture 颜色纹理
|
|
||||||
* @param hasDepth 是否创建深度缓冲
|
|
||||||
* @return 创建成功返回true,失败返回false
|
|
||||||
*
|
|
||||||
* 使用现有纹理作为颜色附件创建帧缓冲对象
|
|
||||||
*/
|
|
||||||
bool RenderTarget::createFromTexture(Ptr<Texture> texture, bool hasDepth) {
|
|
||||||
if (!texture || !texture->isValid()) {
|
|
||||||
E2D_ERROR("无效的颜色纹理");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy();
|
|
||||||
|
|
||||||
width_ = texture->getWidth();
|
|
||||||
height_ = texture->getHeight();
|
|
||||||
colorFormat_ = texture->getFormat();
|
|
||||||
hasDepth_ = hasDepth;
|
|
||||||
hasStencil_ = false;
|
|
||||||
samples_ = 1;
|
|
||||||
|
|
||||||
colorTexture_ = texture;
|
|
||||||
|
|
||||||
// 创建FBO
|
|
||||||
glGenFramebuffers(1, &fbo_);
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
|
|
||||||
|
|
||||||
// 附加颜色纹理
|
|
||||||
GLuint texId = static_cast<GLuint>(
|
|
||||||
reinterpret_cast<uintptr_t>(texture->getNativeHandle()));
|
|
||||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
|
|
||||||
texId, 0);
|
|
||||||
|
|
||||||
// 创建深度缓冲(如果需要)
|
|
||||||
if (hasDepth_) {
|
|
||||||
glGenRenderbuffers(1, &rbo_);
|
|
||||||
glBindRenderbuffer(GL_RENDERBUFFER, rbo_);
|
|
||||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width_,
|
|
||||||
height_);
|
|
||||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
|
|
||||||
GL_RENDERBUFFER, rbo_);
|
|
||||||
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查完整性
|
|
||||||
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
||||||
|
|
||||||
if (status != GL_FRAMEBUFFER_COMPLETE) {
|
|
||||||
E2D_ERROR("FBO不完整: {:#x}", status);
|
|
||||||
destroy();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
E2D_INFO("从纹理创建渲染目标: {}x{}", width_, height_);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 销毁渲染目标
|
|
||||||
*
|
|
||||||
* 释放帧缓冲对象和相关资源
|
|
||||||
*/
|
|
||||||
void RenderTarget::destroy() {
|
|
||||||
deleteFBO();
|
|
||||||
|
|
||||||
colorTexture_.reset();
|
|
||||||
depthTexture_.reset();
|
|
||||||
|
|
||||||
width_ = 0;
|
|
||||||
height_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绑定渲染目标
|
|
||||||
*
|
|
||||||
* 将此渲染目标绑定为当前渲染目标
|
|
||||||
*/
|
|
||||||
void RenderTarget::bind() {
|
|
||||||
if (!isValid()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
|
|
||||||
glViewport(0, 0, width_, height_);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 解绑渲染目标
|
|
||||||
*
|
|
||||||
* 恢复默认帧缓冲
|
|
||||||
*/
|
|
||||||
void RenderTarget::unbind() { bindDefault(); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 清除渲染目标
|
|
||||||
* @param color 清除颜色
|
|
||||||
*
|
|
||||||
* 使用指定颜色清除颜色缓冲,如有深度/模板缓冲也会清除
|
|
||||||
*/
|
|
||||||
void RenderTarget::clear(const Color &color) {
|
|
||||||
if (!isValid()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bind();
|
|
||||||
|
|
||||||
GLbitfield mask = GL_COLOR_BUFFER_BIT;
|
|
||||||
if (hasDepth_) {
|
|
||||||
mask |= GL_DEPTH_BUFFER_BIT;
|
|
||||||
glClearDepthf(1.0f); // GLES 使用 glClearDepthf
|
|
||||||
}
|
|
||||||
if (hasStencil_) {
|
|
||||||
mask |= GL_STENCIL_BUFFER_BIT;
|
|
||||||
glClearStencil(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
glClearColor(color.r, color.g, color.b, color.a);
|
|
||||||
glClear(mask);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置视口区域
|
|
||||||
* @param x 视口左下角X坐标
|
|
||||||
* @param y 视口左下角Y坐标
|
|
||||||
* @param width 视口宽度
|
|
||||||
* @param height 视口高度
|
|
||||||
*
|
|
||||||
* 设置渲染目标的视口区域
|
|
||||||
*/
|
|
||||||
void RenderTarget::setViewport(int x, int y, int width, int height) {
|
|
||||||
if (!isValid()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bind();
|
|
||||||
glViewport(x, y, width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取完整视口区域
|
|
||||||
* @param[out] x 视口左下角X坐标
|
|
||||||
* @param[out] y 视口左下角Y坐标
|
|
||||||
* @param[out] width 视口宽度
|
|
||||||
* @param[out] height 视口高度
|
|
||||||
*
|
|
||||||
* 获取渲染目标的完整视口区域
|
|
||||||
*/
|
|
||||||
void RenderTarget::getFullViewport(int &x, int &y, int &width,
|
|
||||||
int &height) const {
|
|
||||||
x = 0;
|
|
||||||
y = 0;
|
|
||||||
width = width_;
|
|
||||||
height = height_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 调整渲染目标大小
|
|
||||||
* @param width 新宽度
|
|
||||||
* @param height 新高度
|
|
||||||
* @return 调整成功返回true,失败返回false
|
|
||||||
*
|
|
||||||
* 重新创建指定大小的渲染目标
|
|
||||||
*/
|
|
||||||
bool RenderTarget::resize(int width, int height) {
|
|
||||||
if (!isValid()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
RenderTargetConfig config;
|
|
||||||
config.width = width;
|
|
||||||
config.height = height;
|
|
||||||
config.colorFormat = colorFormat_;
|
|
||||||
config.hasDepth = hasDepth_;
|
|
||||||
config.hasStencil = hasStencil_;
|
|
||||||
config.samples = samples_;
|
|
||||||
|
|
||||||
return create(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 复制到另一个渲染目标
|
|
||||||
* @param target 目标渲染目标
|
|
||||||
*
|
|
||||||
* 使用glBlitFramebuffer将内容复制到目标渲染目标
|
|
||||||
*/
|
|
||||||
void RenderTarget::copyTo(RenderTarget &target) {
|
|
||||||
if (!isValid() || !target.isValid()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用glBlitFramebuffer复制
|
|
||||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_);
|
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, target.fbo_);
|
|
||||||
|
|
||||||
GLbitfield mask = GL_COLOR_BUFFER_BIT;
|
|
||||||
if (hasDepth_ && target.hasDepth_) {
|
|
||||||
mask |= GL_DEPTH_BUFFER_BIT;
|
|
||||||
}
|
|
||||||
|
|
||||||
glBlitFramebuffer(0, 0, width_, height_, 0, 0, target.width_, target.height_,
|
|
||||||
mask, GL_LINEAR);
|
|
||||||
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 复制到屏幕
|
|
||||||
* @param screenWidth 屏幕宽度
|
|
||||||
* @param screenHeight 屏幕高度
|
|
||||||
*
|
|
||||||
* 将渲染目标内容复制到默认帧缓冲(屏幕)
|
|
||||||
*/
|
|
||||||
void RenderTarget::copyToScreen(int screenWidth, int screenHeight) {
|
|
||||||
if (!isValid()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_);
|
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
|
||||||
|
|
||||||
glBlitFramebuffer(0, 0, width_, height_, 0, 0, screenWidth, screenHeight,
|
|
||||||
GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
|
||||||
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 保存渲染目标到文件
|
|
||||||
* @param filepath 文件路径
|
|
||||||
* @return 保存成功返回true,失败返回false
|
|
||||||
*
|
|
||||||
* 将渲染目标内容保存为PNG图片文件
|
|
||||||
*/
|
|
||||||
bool RenderTarget::saveToFile(const std::string &filepath) {
|
|
||||||
if (!isValid() || !colorTexture_) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 读取像素数据
|
|
||||||
std::vector<uint8_t> pixels(width_ * height_ * 4);
|
|
||||||
|
|
||||||
bind();
|
|
||||||
glReadPixels(0, 0, width_, height_, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
|
|
||||||
unbind();
|
|
||||||
|
|
||||||
// 翻转Y轴(OpenGL坐标系原点在左下角,PNG需要左上角原点)
|
|
||||||
std::vector<uint8_t> flipped(width_ * height_ * 4);
|
|
||||||
for (int y = 0; y < height_; ++y) {
|
|
||||||
for (int x = 0; x < width_; ++x) {
|
|
||||||
int srcIdx = ((height_ - 1 - y) * width_ + x) * 4;
|
|
||||||
int dstIdx = (y * width_ + x) * 4;
|
|
||||||
for (int c = 0; c < 4; ++c) {
|
|
||||||
flipped[dstIdx + c] = pixels[srcIdx + c];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用stb_image_write保存为PNG
|
|
||||||
int result = stbi_write_png(filepath.c_str(), width_, height_, 4,
|
|
||||||
flipped.data(), width_ * 4);
|
|
||||||
|
|
||||||
if (result == 0) {
|
|
||||||
E2D_ERROR("保存渲染目标到PNG失败: {}", filepath);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
E2D_INFO("保存渲染目标到: {}", filepath);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 根据配置创建渲染目标
|
|
||||||
* @param config 渲染目标配置
|
|
||||||
* @return 创建成功返回渲染目标指针,失败返回nullptr
|
|
||||||
*
|
|
||||||
* 静态工厂方法,创建并初始化渲染目标
|
|
||||||
*/
|
|
||||||
Ptr<RenderTarget>
|
|
||||||
RenderTarget::createFromConfig(const RenderTargetConfig &config) {
|
|
||||||
auto rt = std::make_shared<RenderTarget>();
|
|
||||||
if (rt->create(config)) {
|
|
||||||
return rt;
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前绑定的帧缓冲对象ID
|
|
||||||
* @return 当前绑定的FBO ID
|
|
||||||
*
|
|
||||||
* 查询OpenGL当前绑定的帧缓冲对象
|
|
||||||
*/
|
|
||||||
GLuint RenderTarget::getCurrentFBO() {
|
|
||||||
GLint fbo = 0;
|
|
||||||
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &fbo);
|
|
||||||
return static_cast<GLuint>(fbo);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绑定默认帧缓冲
|
|
||||||
*
|
|
||||||
* 将默认帧缓冲(屏幕)绑定为当前渲染目标
|
|
||||||
*/
|
|
||||||
void RenderTarget::bindDefault() { glBindFramebuffer(GL_FRAMEBUFFER, 0); }
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 内部方法
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建帧缓冲对象
|
|
||||||
* @return 创建成功返回true,失败返回false
|
|
||||||
*
|
|
||||||
* 内部方法,创建FBO及相关附件
|
|
||||||
*/
|
|
||||||
bool RenderTarget::createFBO() {
|
|
||||||
// 创建颜色纹理
|
|
||||||
colorTexture_ = GLTexture::create(width_, height_, colorFormat_);
|
|
||||||
if (!colorTexture_ || !colorTexture_->isValid()) {
|
|
||||||
E2D_ERROR("创建颜色纹理失败");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建FBO
|
|
||||||
glGenFramebuffers(1, &fbo_);
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
|
|
||||||
|
|
||||||
// 附加颜色纹理
|
|
||||||
GLuint colorTexId =
|
|
||||||
static_cast<GLTexture *>(colorTexture_.get())->getTextureID();
|
|
||||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
|
|
||||||
colorTexId, 0);
|
|
||||||
|
|
||||||
// 创建深度/模板缓冲
|
|
||||||
if (hasDepth_ || hasStencil_) {
|
|
||||||
glGenRenderbuffers(1, &rbo_);
|
|
||||||
glBindRenderbuffer(GL_RENDERBUFFER, rbo_);
|
|
||||||
|
|
||||||
if (hasDepth_ && hasStencil_) {
|
|
||||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width_,
|
|
||||||
height_);
|
|
||||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
|
|
||||||
GL_RENDERBUFFER, rbo_);
|
|
||||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
|
|
||||||
GL_RENDERBUFFER, rbo_);
|
|
||||||
} else if (hasDepth_) {
|
|
||||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width_,
|
|
||||||
height_);
|
|
||||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
|
|
||||||
GL_RENDERBUFFER, rbo_);
|
|
||||||
}
|
|
||||||
|
|
||||||
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查完整性
|
|
||||||
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
||||||
|
|
||||||
if (status != GL_FRAMEBUFFER_COMPLETE) {
|
|
||||||
E2D_ERROR("FBO创建失败,状态: {:#x}", status);
|
|
||||||
deleteFBO();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 删除帧缓冲对象
|
|
||||||
*
|
|
||||||
* 内部方法,删除FBO和渲染缓冲对象
|
|
||||||
*/
|
|
||||||
void RenderTarget::deleteFBO() {
|
|
||||||
if (rbo_ != 0) {
|
|
||||||
glDeleteRenderbuffers(1, &rbo_);
|
|
||||||
rbo_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fbo_ != 0) {
|
|
||||||
glDeleteFramebuffers(1, &fbo_);
|
|
||||||
fbo_ = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// MultisampleRenderTarget实现
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建多重采样渲染目标
|
|
||||||
* @param width 宽度
|
|
||||||
* @param height 高度
|
|
||||||
* @param samples 采样数
|
|
||||||
* @return 创建成功返回true,失败返回false
|
|
||||||
*
|
|
||||||
* 创建支持多重采样抗锯齿的渲染目标
|
|
||||||
*/
|
|
||||||
bool MultisampleRenderTarget::create(int width, int height, int samples) {
|
|
||||||
// 先销毁现有的
|
|
||||||
destroy();
|
|
||||||
|
|
||||||
width_ = width;
|
|
||||||
height_ = height;
|
|
||||||
samples_ = samples;
|
|
||||||
hasDepth_ = true;
|
|
||||||
hasStencil_ = false;
|
|
||||||
colorFormat_ = PixelFormat::RGBA8;
|
|
||||||
|
|
||||||
// 创建FBO
|
|
||||||
glGenFramebuffers(1, &fbo_);
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
|
|
||||||
|
|
||||||
// 创建多重采样颜色渲染缓冲
|
|
||||||
glGenRenderbuffers(1, &colorRBO_);
|
|
||||||
glBindRenderbuffer(GL_RENDERBUFFER, colorRBO_);
|
|
||||||
glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_RGBA8, width,
|
|
||||||
height);
|
|
||||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
|
|
||||||
GL_RENDERBUFFER, colorRBO_);
|
|
||||||
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
|
||||||
|
|
||||||
// 创建多重采样深度渲染缓冲
|
|
||||||
glGenRenderbuffers(1, &rbo_);
|
|
||||||
glBindRenderbuffer(GL_RENDERBUFFER, rbo_);
|
|
||||||
glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples,
|
|
||||||
GL_DEPTH_COMPONENT24, width, height);
|
|
||||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
|
|
||||||
GL_RENDERBUFFER, rbo_);
|
|
||||||
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
|
||||||
|
|
||||||
// 检查完整性
|
|
||||||
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
||||||
|
|
||||||
if (status != GL_FRAMEBUFFER_COMPLETE) {
|
|
||||||
E2D_ERROR("多重采样FBO创建失败,状态: {:#x}", status);
|
|
||||||
destroy();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
E2D_INFO("创建多重采样渲染目标: {}x{} (采样数: {})", width, height, samples);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 销毁多重采样渲染目标
|
|
||||||
*
|
|
||||||
* 释放多重采样渲染缓冲和帧缓冲对象
|
|
||||||
*/
|
|
||||||
void MultisampleRenderTarget::destroy() {
|
|
||||||
// 删除颜色渲染缓冲
|
|
||||||
if (colorRBO_ != 0) {
|
|
||||||
glDeleteRenderbuffers(1, &colorRBO_);
|
|
||||||
colorRBO_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 调用基类destroy
|
|
||||||
RenderTarget::destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 解析多重采样到目标渲染目标
|
|
||||||
* @param target 目标渲染目标
|
|
||||||
*
|
|
||||||
* 将多重采样渲染目标解析到普通渲染目标
|
|
||||||
*/
|
|
||||||
void MultisampleRenderTarget::resolveTo(RenderTarget &target) {
|
|
||||||
if (!isValid() || !target.isValid()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用glBlitFramebuffer解析多重采样
|
|
||||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_);
|
|
||||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, target.getFBO());
|
|
||||||
|
|
||||||
glBlitFramebuffer(0, 0, width_, height_, 0, 0, target.getWidth(),
|
|
||||||
target.getHeight(), GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
|
||||||
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// RenderTargetStack实现
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取RenderTargetStack单例实例
|
|
||||||
* @return RenderTargetStack单例的引用
|
|
||||||
*
|
|
||||||
* 使用静态局部变量实现线程安全的单例模式
|
|
||||||
*/
|
|
||||||
RenderTargetStack &RenderTargetStack::get() {
|
|
||||||
static RenderTargetStack instance;
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 压入渲染目标到栈
|
|
||||||
* @param target 渲染目标指针
|
|
||||||
*
|
|
||||||
* 将渲染目标压入栈并绑定为当前渲染目标
|
|
||||||
*/
|
|
||||||
void RenderTargetStack::push(RenderTarget *target) {
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
|
|
||||||
if (target) {
|
|
||||||
stack_.push_back(target);
|
|
||||||
target->bind();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 弹出栈顶渲染目标
|
|
||||||
*
|
|
||||||
* 弹出当前渲染目标并恢复前一个渲染目标
|
|
||||||
*/
|
|
||||||
void RenderTargetStack::pop() {
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
|
|
||||||
if (!stack_.empty()) {
|
|
||||||
stack_.pop_back();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 绑定新的当前渲染目标
|
|
||||||
if (!stack_.empty()) {
|
|
||||||
stack_.back()->bind();
|
|
||||||
} else {
|
|
||||||
RenderTarget::bindDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前渲染目标
|
|
||||||
* @return 当前渲染目标指针,栈为空返回nullptr
|
|
||||||
*/
|
|
||||||
RenderTarget *RenderTargetStack::getCurrent() const {
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
|
|
||||||
if (stack_.empty()) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
return stack_.back();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取栈大小
|
|
||||||
* @return 栈中渲染目标的数量
|
|
||||||
*/
|
|
||||||
size_t RenderTargetStack::size() const {
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
return stack_.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 清空渲染目标栈
|
|
||||||
*
|
|
||||||
* 清空栈并恢复默认帧缓冲
|
|
||||||
*/
|
|
||||||
void RenderTargetStack::clear() {
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
stack_.clear();
|
|
||||||
RenderTarget::bindDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// RenderTargetMgr实现
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取RenderTargetMgr单例实例
|
|
||||||
* @return RenderTargetMgr单例的引用
|
|
||||||
*
|
|
||||||
* 使用静态局部变量实现线程安全的单例模式
|
|
||||||
*/
|
|
||||||
RenderTargetMgr &RenderTargetMgr::get() {
|
|
||||||
static RenderTargetMgr instance;
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化渲染目标管理器
|
|
||||||
* @param width 默认渲染目标宽度
|
|
||||||
* @param height 默认渲染目标高度
|
|
||||||
* @return 初始化成功返回true,失败返回false
|
|
||||||
*
|
|
||||||
* 创建默认渲染目标
|
|
||||||
*/
|
|
||||||
bool RenderTargetMgr::init(int width, int height) {
|
|
||||||
if (initialized_) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建默认渲染目标
|
|
||||||
RenderTargetConfig config;
|
|
||||||
config.width = width;
|
|
||||||
config.height = height;
|
|
||||||
config.hasDepth = true;
|
|
||||||
config.hasStencil = false;
|
|
||||||
|
|
||||||
defaultRenderTarget_ = RenderTarget::createFromConfig(config);
|
|
||||||
if (!defaultRenderTarget_) {
|
|
||||||
E2D_ERROR("创建默认渲染目标失败");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
initialized_ = true;
|
|
||||||
E2D_INFO("渲染目标管理器初始化完成: {}x{}", width, height);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 关闭渲染目标管理器
|
|
||||||
*
|
|
||||||
* 清理所有渲染目标资源
|
|
||||||
*/
|
|
||||||
void RenderTargetMgr::shutdown() {
|
|
||||||
if (!initialized_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderTargets_.clear();
|
|
||||||
defaultRenderTarget_.reset();
|
|
||||||
initialized_ = false;
|
|
||||||
|
|
||||||
E2D_INFO("渲染目标管理器已关闭");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建渲染目标
|
|
||||||
* @param config 渲染目标配置
|
|
||||||
* @return 创建成功返回渲染目标指针,失败返回nullptr
|
|
||||||
*
|
|
||||||
* 创建新的渲染目标并由管理器管理
|
|
||||||
*/
|
|
||||||
Ptr<RenderTarget>
|
|
||||||
RenderTargetMgr::createRenderTarget(const RenderTargetConfig &config) {
|
|
||||||
if (!initialized_) {
|
|
||||||
E2D_ERROR("渲染目标管理器未初始化");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto rt = RenderTarget::createFromConfig(config);
|
|
||||||
if (rt) {
|
|
||||||
renderTargets_.push_back(rt);
|
|
||||||
}
|
|
||||||
return rt;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 调整所有渲染目标大小
|
|
||||||
* @param width 新宽度
|
|
||||||
* @param height 新高度
|
|
||||||
*
|
|
||||||
* 调整默认渲染目标的大小
|
|
||||||
*/
|
|
||||||
void RenderTargetMgr::resize(int width, int height) {
|
|
||||||
if (!initialized_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 调整默认渲染目标大小
|
|
||||||
if (defaultRenderTarget_) {
|
|
||||||
defaultRenderTarget_->resize(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
E2D_INFO("渲染目标管理器调整大小: {}x{}", width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
#include <extra2d/graphics/memory/gpu_context.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取GPUContext单例实例
|
|
||||||
* @return GPUContext单例的引用
|
|
||||||
*
|
|
||||||
* 使用静态局部变量实现线程安全的单例模式
|
|
||||||
*/
|
|
||||||
GPUContext& GPUContext::get() {
|
|
||||||
static GPUContext instance;
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 标记GPU上下文为有效状态
|
|
||||||
*
|
|
||||||
* 使用原子操作设置有效标志为true,使用release内存序
|
|
||||||
*/
|
|
||||||
void GPUContext::markValid() {
|
|
||||||
valid_.store(true, std::memory_order_release);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 标记GPU上下文为无效状态
|
|
||||||
*
|
|
||||||
* 使用原子操作设置有效标志为false,使用release内存序
|
|
||||||
*/
|
|
||||||
void GPUContext::markInvalid() {
|
|
||||||
valid_.store(false, std::memory_order_release);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查GPU上下文是否有效
|
|
||||||
* @return 如果GPU上下文有效返回true,否则返回false
|
|
||||||
*
|
|
||||||
* 使用原子操作读取有效标志,使用acquire内存序
|
|
||||||
*/
|
|
||||||
bool GPUContext::isValid() const {
|
|
||||||
return valid_.load(std::memory_order_acquire);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,194 +0,0 @@
|
||||||
#include <algorithm>
|
|
||||||
#include <extra2d/core/service_locator.h>
|
|
||||||
#include <extra2d/graphics/memory/vram_manager.h>
|
|
||||||
#include <extra2d/services/logger_service.h>
|
|
||||||
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// Switch 推荐 VRAM 预算 ~400MB
|
|
||||||
static constexpr size_t DEFAULT_VRAM_BUDGET = 400 * 1024 * 1024;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 默认构造函数
|
|
||||||
*
|
|
||||||
* 初始化VRAM管理器,设置默认预算为400MB
|
|
||||||
*/
|
|
||||||
VRAMMgr::VRAMMgr()
|
|
||||||
: textureVRAM_(0), bufferVRAM_(0), vramBudget_(DEFAULT_VRAM_BUDGET),
|
|
||||||
textureAllocCount_(0), textureFreeCount_(0), bufferAllocCount_(0),
|
|
||||||
bufferFreeCount_(0), peakTextureVRAM_(0), peakBufferVRAM_(0) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取VRAMMgr单例实例
|
|
||||||
* @return VRAMMgr单例的引用
|
|
||||||
*
|
|
||||||
* 使用静态局部变量实现线程安全的单例模式
|
|
||||||
*/
|
|
||||||
VRAMMgr &VRAMMgr::get() {
|
|
||||||
static VRAMMgr instance;
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 分配纹理VRAM
|
|
||||||
* @param size 分配的字节数
|
|
||||||
*
|
|
||||||
* 增加纹理VRAM使用量并更新峰值,如果超出预算则输出警告
|
|
||||||
*/
|
|
||||||
void VRAMMgr::allocTexture(size_t size) {
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
textureVRAM_ += size;
|
|
||||||
textureAllocCount_++;
|
|
||||||
peakTextureVRAM_ = std::max(peakTextureVRAM_, textureVRAM_);
|
|
||||||
|
|
||||||
if (isOverBudget()) {
|
|
||||||
E2D_LOG_WARN("显存超出预算! 已使用: {} MB / 预算: {} MB",
|
|
||||||
getUsedVRAM() / (1024 * 1024), vramBudget_ / (1024 * 1024));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 释放纹理VRAM
|
|
||||||
* @param size 释放的字节数
|
|
||||||
*
|
|
||||||
* 减少纹理VRAM使用量
|
|
||||||
*/
|
|
||||||
void VRAMMgr::freeTexture(size_t size) {
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
if (size <= textureVRAM_) {
|
|
||||||
textureVRAM_ -= size;
|
|
||||||
} else {
|
|
||||||
textureVRAM_ = 0;
|
|
||||||
}
|
|
||||||
textureFreeCount_++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 分配缓冲VRAM
|
|
||||||
* @param size 分配的字节数
|
|
||||||
*
|
|
||||||
* 增加缓冲VRAM使用量并更新峰值,如果超出预算则输出警告
|
|
||||||
*/
|
|
||||||
void VRAMMgr::allocBuffer(size_t size) {
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
bufferVRAM_ += size;
|
|
||||||
bufferAllocCount_++;
|
|
||||||
peakBufferVRAM_ = std::max(peakBufferVRAM_, bufferVRAM_);
|
|
||||||
|
|
||||||
if (isOverBudget()) {
|
|
||||||
E2D_LOG_WARN("显存超出预算! 已使用: {} MB / 预算: {} MB",
|
|
||||||
getUsedVRAM() / (1024 * 1024), vramBudget_ / (1024 * 1024));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 释放缓冲VRAM
|
|
||||||
* @param size 释放的字节数
|
|
||||||
*
|
|
||||||
* 减少缓冲VRAM使用量
|
|
||||||
*/
|
|
||||||
void VRAMMgr::freeBuffer(size_t size) {
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
if (size <= bufferVRAM_) {
|
|
||||||
bufferVRAM_ -= size;
|
|
||||||
} else {
|
|
||||||
bufferVRAM_ = 0;
|
|
||||||
}
|
|
||||||
bufferFreeCount_++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取总VRAM使用量
|
|
||||||
* @return 总使用字节数
|
|
||||||
*
|
|
||||||
* 返回纹理和缓冲VRAM使用量的总和
|
|
||||||
*/
|
|
||||||
size_t VRAMMgr::getUsedVRAM() const { return textureVRAM_ + bufferVRAM_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取纹理VRAM使用量
|
|
||||||
* @return 纹理使用字节数
|
|
||||||
*/
|
|
||||||
size_t VRAMMgr::getTextureVRAM() const { return textureVRAM_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取缓冲VRAM使用量
|
|
||||||
* @return 缓冲使用字节数
|
|
||||||
*/
|
|
||||||
size_t VRAMMgr::getBufferVRAM() const { return bufferVRAM_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取可用VRAM
|
|
||||||
* @return 可用字节数
|
|
||||||
*
|
|
||||||
* 返回预算内剩余的VRAM空间
|
|
||||||
*/
|
|
||||||
size_t VRAMMgr::getAvailableVRAM() const {
|
|
||||||
size_t used = getUsedVRAM();
|
|
||||||
return (used < vramBudget_) ? (vramBudget_ - used) : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置VRAM预算
|
|
||||||
* @param budget 预算字节数
|
|
||||||
*
|
|
||||||
* 设置VRAM使用上限
|
|
||||||
*/
|
|
||||||
void VRAMMgr::setVRAMBudget(size_t budget) {
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
vramBudget_ = budget;
|
|
||||||
E2D_LOG_INFO("显存预算设置为 {} MB", budget / (1024 * 1024));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取VRAM预算
|
|
||||||
* @return 预算字节数
|
|
||||||
*/
|
|
||||||
size_t VRAMMgr::getVRAMBudget() const { return vramBudget_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查是否超出预算
|
|
||||||
* @return 超出预算返回true,否则返回false
|
|
||||||
*/
|
|
||||||
bool VRAMMgr::isOverBudget() const { return getUsedVRAM() > vramBudget_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 打印VRAM统计信息
|
|
||||||
*
|
|
||||||
* 输出纹理和缓冲的VRAM使用情况、峰值和分配/释放次数
|
|
||||||
*/
|
|
||||||
void VRAMMgr::printStats() const {
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
E2D_LOG_INFO("=== 显存统计 ===");
|
|
||||||
E2D_LOG_INFO(" 纹理显存: {} MB (峰值: {} MB)",
|
|
||||||
textureVRAM_ / (1024 * 1024), peakTextureVRAM_ / (1024 * 1024));
|
|
||||||
E2D_LOG_INFO(" 缓冲显存: {} MB (峰值: {} MB)",
|
|
||||||
bufferVRAM_ / (1024 * 1024), peakBufferVRAM_ / (1024 * 1024));
|
|
||||||
E2D_LOG_INFO(" 总计使用: {} MB / {} MB 预算",
|
|
||||||
(textureVRAM_ + bufferVRAM_) / (1024 * 1024),
|
|
||||||
vramBudget_ / (1024 * 1024));
|
|
||||||
E2D_LOG_INFO(" 纹理分配/释放次数: {} / {}", textureAllocCount_,
|
|
||||||
textureFreeCount_);
|
|
||||||
E2D_LOG_INFO(" 缓冲分配/释放次数: {} / {}", bufferAllocCount_,
|
|
||||||
bufferFreeCount_);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 重置所有统计信息
|
|
||||||
*
|
|
||||||
* 清零所有VRAM计数器和峰值记录
|
|
||||||
*/
|
|
||||||
void VRAMMgr::reset() {
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
textureVRAM_ = 0;
|
|
||||||
bufferVRAM_ = 0;
|
|
||||||
textureAllocCount_ = 0;
|
|
||||||
textureFreeCount_ = 0;
|
|
||||||
bufferAllocCount_ = 0;
|
|
||||||
bufferFreeCount_ = 0;
|
|
||||||
peakTextureVRAM_ = 0;
|
|
||||||
peakBufferVRAM_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,171 +0,0 @@
|
||||||
#include <cstring>
|
|
||||||
#include <extra2d/graphics/memory/vram_manager.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_buffer.h>
|
|
||||||
#include <extra2d/services/logger_service.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// GLBuffer 实现
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
GLBuffer::GLBuffer() = default;
|
|
||||||
|
|
||||||
GLBuffer::~GLBuffer() { shutdown(); }
|
|
||||||
|
|
||||||
bool GLBuffer::init(const BufferDesc &desc) {
|
|
||||||
if (bufferID_ != 0) {
|
|
||||||
shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
type_ = desc.type;
|
|
||||||
usage_ = desc.usage;
|
|
||||||
size_ = desc.size;
|
|
||||||
target_ = convertType(type_);
|
|
||||||
glUsage_ = convertUsage(usage_);
|
|
||||||
|
|
||||||
// 生成缓冲区
|
|
||||||
glGenBuffers(1, &bufferID_);
|
|
||||||
if (bufferID_ == 0) {
|
|
||||||
E2D_LOG_ERROR("生成 OpenGL 缓冲区失败");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 绑定并分配缓冲区
|
|
||||||
glBindBuffer(target_, bufferID_);
|
|
||||||
glBufferData(target_, static_cast<GLsizeiptr>(size_), desc.initialData,
|
|
||||||
glUsage_);
|
|
||||||
glBindBuffer(target_, 0);
|
|
||||||
|
|
||||||
// 追踪显存使用
|
|
||||||
VRAMMgr::get().allocBuffer(size_);
|
|
||||||
|
|
||||||
E2D_LOG_DEBUG("GLBuffer 已创建: ID={}, 大小={}, 类型={}, 用途={}", bufferID_,
|
|
||||||
size_, static_cast<int>(type_), static_cast<int>(usage_));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLBuffer::shutdown() {
|
|
||||||
if (bufferID_ != 0) {
|
|
||||||
if (mapped_) {
|
|
||||||
unmap();
|
|
||||||
}
|
|
||||||
// 释放显存追踪
|
|
||||||
VRAMMgr::get().freeBuffer(size_);
|
|
||||||
glDeleteBuffers(1, &bufferID_);
|
|
||||||
E2D_LOG_DEBUG("GLBuffer 已销毁: ID={}", bufferID_);
|
|
||||||
bufferID_ = 0;
|
|
||||||
}
|
|
||||||
size_ = 0;
|
|
||||||
mapped_ = false;
|
|
||||||
mappedPtr_ = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLBuffer::bind() {
|
|
||||||
if (bufferID_ != 0) {
|
|
||||||
glBindBuffer(target_, bufferID_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLBuffer::unbind() { glBindBuffer(target_, 0); }
|
|
||||||
|
|
||||||
void GLBuffer::setData(const void *data, size_t size) {
|
|
||||||
if (bufferID_ == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bind();
|
|
||||||
|
|
||||||
// 如果大小相同,使用 glBufferSubData 更高效
|
|
||||||
if (size == size_) {
|
|
||||||
glBufferSubData(target_, 0, static_cast<GLsizeiptr>(size), data);
|
|
||||||
} else {
|
|
||||||
// 大小不同,重新分配
|
|
||||||
size_ = size;
|
|
||||||
glBufferData(target_, static_cast<GLsizeiptr>(size_), data, glUsage_);
|
|
||||||
}
|
|
||||||
|
|
||||||
unbind();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLBuffer::updateData(const void *data, size_t offset, size_t size) {
|
|
||||||
if (bufferID_ == 0 || data == nullptr || size == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (offset + size > size_) {
|
|
||||||
E2D_LOG_WARN(
|
|
||||||
"GLBuffer updateData out of bounds: offset={}, size={}, bufferSize={}",
|
|
||||||
offset, size, size_);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bind();
|
|
||||||
glBufferSubData(target_, static_cast<GLintptr>(offset),
|
|
||||||
static_cast<GLsizeiptr>(size), data);
|
|
||||||
unbind();
|
|
||||||
}
|
|
||||||
|
|
||||||
void *GLBuffer::map() {
|
|
||||||
if (bufferID_ == 0 || mapped_) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
bind();
|
|
||||||
|
|
||||||
// 使用 glMapBufferRange 替代 glMapBuffer,更现代且安全
|
|
||||||
GLbitfield access = GL_MAP_WRITE_BIT;
|
|
||||||
if (usage_ == BufferUsage::Dynamic || usage_ == BufferUsage::Stream) {
|
|
||||||
access |= GL_MAP_INVALIDATE_BUFFER_BIT; // 暗示驱动可以丢弃旧数据
|
|
||||||
}
|
|
||||||
|
|
||||||
mappedPtr_ =
|
|
||||||
glMapBufferRange(target_, 0, static_cast<GLsizeiptr>(size_), access);
|
|
||||||
if (mappedPtr_) {
|
|
||||||
mapped_ = true;
|
|
||||||
} else {
|
|
||||||
E2D_LOG_ERROR("映射 GLBuffer 失败");
|
|
||||||
}
|
|
||||||
|
|
||||||
return mappedPtr_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLBuffer::unmap() {
|
|
||||||
if (!mapped_ || bufferID_ == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
glUnmapBuffer(target_);
|
|
||||||
mapped_ = false;
|
|
||||||
mappedPtr_ = nullptr;
|
|
||||||
unbind();
|
|
||||||
}
|
|
||||||
|
|
||||||
GLenum GLBuffer::convertUsage(BufferUsage usage) {
|
|
||||||
switch (usage) {
|
|
||||||
case BufferUsage::Static:
|
|
||||||
return GL_STATIC_DRAW;
|
|
||||||
case BufferUsage::Dynamic:
|
|
||||||
return GL_DYNAMIC_DRAW;
|
|
||||||
case BufferUsage::Stream:
|
|
||||||
return GL_STREAM_DRAW;
|
|
||||||
default:
|
|
||||||
return GL_STATIC_DRAW;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GLenum GLBuffer::convertType(BufferType type) {
|
|
||||||
switch (type) {
|
|
||||||
case BufferType::Vertex:
|
|
||||||
return GL_ARRAY_BUFFER;
|
|
||||||
case BufferType::Index:
|
|
||||||
return GL_ELEMENT_ARRAY_BUFFER;
|
|
||||||
case BufferType::Uniform:
|
|
||||||
return GL_UNIFORM_BUFFER;
|
|
||||||
default:
|
|
||||||
return GL_ARRAY_BUFFER;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,169 +0,0 @@
|
||||||
#include <extra2d/graphics/memory/gpu_context.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_context.h>
|
|
||||||
#include <extra2d/services/logger_service.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// GLContext 实现
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
GLContext &GLContext::get() {
|
|
||||||
static GLContext instance;
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GLContext::init() {
|
|
||||||
if (initialized_) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析 OpenGL 版本
|
|
||||||
parseVersion();
|
|
||||||
|
|
||||||
// 加载扩展(GLAD 已在 glad.c 中完成)
|
|
||||||
if (!loadExtensions()) {
|
|
||||||
E2D_LOG_ERROR("加载 OpenGL 扩展失败");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
initialized_ = true;
|
|
||||||
|
|
||||||
// 标记 GPU 上下文为有效
|
|
||||||
GPUContext::get().markValid();
|
|
||||||
|
|
||||||
E2D_LOG_INFO("OpenGL 上下文已初始化");
|
|
||||||
E2D_LOG_INFO(" 版本: {}", getVersionString());
|
|
||||||
E2D_LOG_INFO(" 供应商: {}", getVendor());
|
|
||||||
E2D_LOG_INFO(" 渲染器: {}", getRenderer());
|
|
||||||
E2D_LOG_INFO(" 最大纹理大小: {}", getMaxTextureSize());
|
|
||||||
E2D_LOG_INFO(" 最大纹理单元数: {}", getMaxTextureUnits());
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLContext::shutdown() {
|
|
||||||
// 标记 GPU 上下文为无效
|
|
||||||
GPUContext::get().markInvalid();
|
|
||||||
|
|
||||||
initialized_ = false;
|
|
||||||
version_ = GLVersion{};
|
|
||||||
maxTextureSize_ = -1;
|
|
||||||
maxTextureUnits_ = -1;
|
|
||||||
maxVertexAttribs_ = -1;
|
|
||||||
maxUniformBufferBindings_ = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string GLContext::getVersionString() const {
|
|
||||||
const char *version = reinterpret_cast<const char *>(glGetString(GL_VERSION));
|
|
||||||
return version ? version : "Unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string GLContext::getVendor() const {
|
|
||||||
const char *vendor = reinterpret_cast<const char *>(glGetString(GL_VENDOR));
|
|
||||||
return vendor ? vendor : "Unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string GLContext::getRenderer() const {
|
|
||||||
const char *renderer =
|
|
||||||
reinterpret_cast<const char *>(glGetString(GL_RENDERER));
|
|
||||||
return renderer ? renderer : "Unknown";
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GLContext::hasExtension(const std::string &extension) const {
|
|
||||||
GLint numExtensions = 0;
|
|
||||||
glGetIntegerv(GL_NUM_EXTENSIONS, &numExtensions);
|
|
||||||
|
|
||||||
for (GLint i = 0; i < numExtensions; ++i) {
|
|
||||||
const char *ext =
|
|
||||||
reinterpret_cast<const char *>(glGetStringi(GL_EXTENSIONS, i));
|
|
||||||
if (ext && extension == ext) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int GLContext::getMaxTextureSize() const {
|
|
||||||
if (maxTextureSize_ < 0) {
|
|
||||||
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize_);
|
|
||||||
}
|
|
||||||
return maxTextureSize_;
|
|
||||||
}
|
|
||||||
|
|
||||||
int GLContext::getMaxTextureUnits() const {
|
|
||||||
if (maxTextureUnits_ < 0) {
|
|
||||||
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureUnits_);
|
|
||||||
}
|
|
||||||
return maxTextureUnits_;
|
|
||||||
}
|
|
||||||
|
|
||||||
int GLContext::getMaxVertexAttribs() const {
|
|
||||||
if (maxVertexAttribs_ < 0) {
|
|
||||||
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs_);
|
|
||||||
}
|
|
||||||
return maxVertexAttribs_;
|
|
||||||
}
|
|
||||||
|
|
||||||
int GLContext::getMaxUniformBufferBindings() const {
|
|
||||||
if (maxUniformBufferBindings_ < 0) {
|
|
||||||
glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &maxUniformBufferBindings_);
|
|
||||||
}
|
|
||||||
return maxUniformBufferBindings_;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GLContext::hasVAO() const {
|
|
||||||
// OpenGL 3.0+ 或 OpenGL ES 3.0+ 原生支持 VAO
|
|
||||||
if (version_.es) {
|
|
||||||
return version_.major >= 3;
|
|
||||||
}
|
|
||||||
return version_.major > 3 || (version_.major == 3 && version_.minor >= 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GLContext::hasFBO() const {
|
|
||||||
// OpenGL 3.0+ 或 OpenGL ES 2.0+ 原生支持 FBO
|
|
||||||
if (version_.es) {
|
|
||||||
return version_.major >= 2;
|
|
||||||
}
|
|
||||||
return version_.major >= 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GLContext::hasShader() const {
|
|
||||||
// OpenGL 2.0+ 或 OpenGL ES 2.0+ 原生支持 Shader
|
|
||||||
if (version_.es) {
|
|
||||||
return version_.major >= 2;
|
|
||||||
}
|
|
||||||
return version_.major >= 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLContext::parseVersion() {
|
|
||||||
const char *versionStr =
|
|
||||||
reinterpret_cast<const char *>(glGetString(GL_VERSION));
|
|
||||||
if (!versionStr) {
|
|
||||||
version_ = GLVersion{0, 0, false};
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string version(versionStr);
|
|
||||||
|
|
||||||
// 检查是否为 OpenGL ES
|
|
||||||
if (version.find("OpenGL ES") != std::string::npos) {
|
|
||||||
version_.es = true;
|
|
||||||
// 解析 ES 版本号,格式如 "OpenGL ES 3.0"
|
|
||||||
std::sscanf(version.c_str(), "OpenGL ES %d.%d", &version_.major,
|
|
||||||
&version_.minor);
|
|
||||||
} else {
|
|
||||||
version_.es = false;
|
|
||||||
// 解析桌面版本号,格式如 "3.3.0 NVIDIA"
|
|
||||||
std::sscanf(version.c_str(), "%d.%d", &version_.major, &version_.minor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GLContext::loadExtensions() {
|
|
||||||
// GLAD 已经在 glad.c 中加载了所有扩展
|
|
||||||
// 这里可以添加额外的扩展检查
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,361 +0,0 @@
|
||||||
#include <extra2d/graphics/opengl/gl_font_atlas.h>
|
|
||||||
#include <extra2d/services/logger_service.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cstring>
|
|
||||||
#include <fstream>
|
|
||||||
#include <simdutf/simdutf.h>
|
|
||||||
|
|
||||||
// 在实现文件中定义 STB 实现
|
|
||||||
#define STB_TRUETYPE_IMPLEMENTATION
|
|
||||||
#include <stb/stb_truetype.h>
|
|
||||||
|
|
||||||
#define STB_RECT_PACK_IMPLEMENTATION
|
|
||||||
#include <stb/stb_rect_pack.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// GLFontAtlas 构造函数
|
|
||||||
// 加载字体文件并初始化图集
|
|
||||||
// ============================================================================
|
|
||||||
GLFontAtlas::GLFontAtlas(const std::string &filepath, int fontSize, bool useSDF)
|
|
||||||
: useSDF_(useSDF), fontSize_(fontSize), lineHeight_(0.0f), ascent_(0.0f),
|
|
||||||
descent_(0.0f), lineGap_(0.0f), scale_(0.0f) {
|
|
||||||
|
|
||||||
// 加载字体文件
|
|
||||||
if (!initFont(filepath)) {
|
|
||||||
E2D_LOG_ERROR("初始化字体失败: {}", filepath);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算字体缩放比例和度量
|
|
||||||
scale_ = stbtt_ScaleForPixelHeight(&fontInfo_, static_cast<float>(fontSize_));
|
|
||||||
|
|
||||||
int ascent, descent, lineGap;
|
|
||||||
stbtt_GetFontVMetrics(&fontInfo_, &ascent, &descent, &lineGap);
|
|
||||||
|
|
||||||
ascent_ = static_cast<float>(ascent) * scale_;
|
|
||||||
descent_ = static_cast<float>(descent) * scale_;
|
|
||||||
lineGap_ = static_cast<float>(lineGap) * scale_;
|
|
||||||
lineHeight_ = ascent_ - descent_ + lineGap_;
|
|
||||||
|
|
||||||
// 创建图集纹理和打包上下文
|
|
||||||
createAtlas();
|
|
||||||
|
|
||||||
// 预加载常用 ASCII 字符
|
|
||||||
std::string asciiChars = " !\"#$%&'()*+,-./"
|
|
||||||
"0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`"
|
|
||||||
"abcdefghijklmnopqrstuvwxyz{|}~";
|
|
||||||
for (char c : asciiChars) {
|
|
||||||
char32_t codepoint = static_cast<char32_t>(static_cast<unsigned char>(c));
|
|
||||||
cacheGlyph(codepoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
E2D_LOG_INFO("字体图集已创建: {} ({}px, {}x{})", filepath, fontSize_,
|
|
||||||
ATLAS_WIDTH, ATLAS_HEIGHT);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// GLFontAtlas 析构函数
|
|
||||||
// ============================================================================
|
|
||||||
GLFontAtlas::~GLFontAtlas() {
|
|
||||||
// 智能指针自动管理纹理资源
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 获取字形信息 - 如果字形不存在则动态缓存
|
|
||||||
// ============================================================================
|
|
||||||
const Glyph *GLFontAtlas::getGlyph(char32_t codepoint) const {
|
|
||||||
auto it = glyphs_.find(codepoint);
|
|
||||||
if (it == glyphs_.end()) {
|
|
||||||
// 动态缓存新字形
|
|
||||||
const_cast<GLFontAtlas *>(this)->cacheGlyph(codepoint);
|
|
||||||
it = glyphs_.find(codepoint);
|
|
||||||
if (it == glyphs_.end()) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 返回静态存储的 Glyph 数据
|
|
||||||
static Glyph glyph;
|
|
||||||
const auto &data = it->second;
|
|
||||||
glyph.width = data.width;
|
|
||||||
glyph.height = data.height;
|
|
||||||
glyph.bearingX = data.bearingX;
|
|
||||||
glyph.bearingY = data.bearingY;
|
|
||||||
glyph.advance = data.advance;
|
|
||||||
glyph.u0 = data.u0;
|
|
||||||
glyph.v0 = data.v0;
|
|
||||||
glyph.u1 = data.u1;
|
|
||||||
glyph.v1 = data.v1;
|
|
||||||
|
|
||||||
return &glyph;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 测量文本尺寸 - 支持多行文本
|
|
||||||
// ============================================================================
|
|
||||||
Vec2 GLFontAtlas::measureText(const std::string &text) {
|
|
||||||
float maxWidth = 0.0f;
|
|
||||||
float height = lineHeight_;
|
|
||||||
float currentWidth = 0.0f;
|
|
||||||
|
|
||||||
size_t utf32Length = simdutf::count_utf8(text.data(), text.size());
|
|
||||||
std::vector<char32_t> utf32Buffer(utf32Length);
|
|
||||||
size_t converted = simdutf::convert_utf8_to_utf32(text.data(), text.size(),
|
|
||||||
utf32Buffer.data());
|
|
||||||
|
|
||||||
for (size_t i = 0; i < converted; ++i) {
|
|
||||||
char32_t codepoint = utf32Buffer[i];
|
|
||||||
|
|
||||||
if (codepoint == '\n') {
|
|
||||||
maxWidth = std::max(maxWidth, currentWidth);
|
|
||||||
currentWidth = 0.0f;
|
|
||||||
height += lineHeight_;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Glyph *glyph = getGlyph(codepoint);
|
|
||||||
if (glyph) {
|
|
||||||
currentWidth += glyph->advance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
maxWidth = std::max(maxWidth, currentWidth);
|
|
||||||
return Vec2(maxWidth, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 初始化字体 - 加载字体文件到内存
|
|
||||||
// ============================================================================
|
|
||||||
bool GLFontAtlas::initFont(const std::string &filepath) {
|
|
||||||
// 读取字体文件到内存
|
|
||||||
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
|
|
||||||
if (!file.is_open()) {
|
|
||||||
E2D_LOG_ERROR("打开字体文件失败: {}", filepath);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::streamsize size = file.tellg();
|
|
||||||
file.seekg(0, std::ios::beg);
|
|
||||||
|
|
||||||
fontData_.resize(static_cast<size_t>(size));
|
|
||||||
if (!file.read(reinterpret_cast<char *>(fontData_.data()), size)) {
|
|
||||||
E2D_LOG_ERROR("读取字体文件失败: {}", filepath);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化 STB 字体
|
|
||||||
if (!stbtt_InitFont(&fontInfo_, fontData_.data(), 0)) {
|
|
||||||
E2D_LOG_ERROR("初始化 STB 字体失败: {}", filepath);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 创建图集纹理 - 初始化空白纹理和矩形打包上下文
|
|
||||||
// ============================================================================
|
|
||||||
void GLFontAtlas::createAtlas() {
|
|
||||||
// 统一使用 4 通道格式 (RGBA)
|
|
||||||
int channels = 4;
|
|
||||||
std::vector<uint8_t> emptyData(ATLAS_WIDTH * ATLAS_HEIGHT * channels, 0);
|
|
||||||
texture_ = std::make_unique<GLTexture>(ATLAS_WIDTH, ATLAS_HEIGHT,
|
|
||||||
emptyData.data(), channels);
|
|
||||||
// 所有字体都使用线性过滤,SDF的抗锯齿由着色器处理
|
|
||||||
texture_->setFilter(true);
|
|
||||||
|
|
||||||
// 初始化矩形打包上下文 - 持久化以支持增量打包
|
|
||||||
packNodes_.resize(ATLAS_WIDTH);
|
|
||||||
stbrp_init_target(&packContext_, ATLAS_WIDTH, ATLAS_HEIGHT, packNodes_.data(),
|
|
||||||
ATLAS_WIDTH);
|
|
||||||
|
|
||||||
// 预分配字形缓冲区
|
|
||||||
// 假设最大字形尺寸为 fontSize * fontSize * 4 (RGBA)
|
|
||||||
size_t maxGlyphSize = static_cast<size_t>(fontSize_ * fontSize_ * 4 * 4);
|
|
||||||
glyphBitmapCache_.reserve(maxGlyphSize);
|
|
||||||
glyphRgbaCache_.reserve(maxGlyphSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 缓存字形 - 渲染字形到图集并存储信息
|
|
||||||
// 使用 stb_rect_pack 进行矩形打包
|
|
||||||
// ============================================================================
|
|
||||||
void GLFontAtlas::cacheGlyph(char32_t codepoint) {
|
|
||||||
// 检查是否已存在
|
|
||||||
if (glyphs_.find(codepoint) != glyphs_.end()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取字形水平度量
|
|
||||||
int advance, leftSideBearing;
|
|
||||||
stbtt_GetCodepointHMetrics(&fontInfo_, static_cast<int>(codepoint), &advance,
|
|
||||||
&leftSideBearing);
|
|
||||||
float advancePx = static_cast<float>(advance) * scale_;
|
|
||||||
|
|
||||||
// SDF 渲染模式
|
|
||||||
if (useSDF_) {
|
|
||||||
constexpr int SDF_PADDING = 8;
|
|
||||||
constexpr unsigned char ONEDGE_VALUE = 128;
|
|
||||||
constexpr float PIXEL_DIST_SCALE = 64.0f;
|
|
||||||
|
|
||||||
int w = 0, h = 0, xoff = 0, yoff = 0;
|
|
||||||
unsigned char *sdf = stbtt_GetCodepointSDF(
|
|
||||||
&fontInfo_, scale_, static_cast<int>(codepoint), SDF_PADDING,
|
|
||||||
ONEDGE_VALUE, PIXEL_DIST_SCALE, &w, &h, &xoff, &yoff);
|
|
||||||
|
|
||||||
if (!sdf || w <= 0 || h <= 0) {
|
|
||||||
if (sdf)
|
|
||||||
stbtt_FreeSDF(sdf, nullptr);
|
|
||||||
// 创建空白字形(如空格)
|
|
||||||
GlyphData data{};
|
|
||||||
data.advance = advancePx;
|
|
||||||
glyphs_[codepoint] = data;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用 stb_rect_pack 打包矩形
|
|
||||||
stbrp_rect rect;
|
|
||||||
rect.id = static_cast<int>(codepoint);
|
|
||||||
rect.w = w + PADDING * 2;
|
|
||||||
rect.h = h + PADDING * 2;
|
|
||||||
|
|
||||||
stbrp_pack_rects(&packContext_, &rect, 1);
|
|
||||||
if (!rect.was_packed) {
|
|
||||||
E2D_LOG_WARN("字体图集已满,无法缓存字符码点: {}", codepoint);
|
|
||||||
stbtt_FreeSDF(sdf, nullptr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int atlasX = rect.x + PADDING;
|
|
||||||
int atlasY = rect.y + PADDING;
|
|
||||||
|
|
||||||
// 创建字形数据
|
|
||||||
GlyphData data;
|
|
||||||
data.width = static_cast<float>(w);
|
|
||||||
data.height = static_cast<float>(h);
|
|
||||||
data.bearingX = static_cast<float>(xoff);
|
|
||||||
data.bearingY = static_cast<float>(yoff);
|
|
||||||
data.advance = advancePx;
|
|
||||||
|
|
||||||
// 计算 UV 坐标
|
|
||||||
// stb_rect_pack 使用左上角为原点,OpenGL纹理使用左下角为原点
|
|
||||||
// 需要翻转V坐标
|
|
||||||
float v0 = static_cast<float>(atlasY) / ATLAS_HEIGHT;
|
|
||||||
float v1 = static_cast<float>(atlasY + h) / ATLAS_HEIGHT;
|
|
||||||
data.u0 = static_cast<float>(atlasX) / ATLAS_WIDTH;
|
|
||||||
data.v0 = 1.0f - v1; // 翻转V坐标
|
|
||||||
data.u1 = static_cast<float>(atlasX + w) / ATLAS_WIDTH;
|
|
||||||
data.v1 = 1.0f - v0; // 翻转V坐标
|
|
||||||
|
|
||||||
glyphs_[codepoint] = data;
|
|
||||||
|
|
||||||
// 将 SDF 单通道数据转换为 RGBA 格式(统一格式)
|
|
||||||
size_t pixelCount = static_cast<size_t>(w) * static_cast<size_t>(h);
|
|
||||||
glyphRgbaCache_.resize(pixelCount * 4);
|
|
||||||
for (size_t i = 0; i < pixelCount; ++i) {
|
|
||||||
uint8_t alpha = sdf[i];
|
|
||||||
glyphRgbaCache_[i * 4 + 0] = 255; // R
|
|
||||||
glyphRgbaCache_[i * 4 + 1] = 255; // G
|
|
||||||
glyphRgbaCache_[i * 4 + 2] = 255; // B
|
|
||||||
glyphRgbaCache_[i * 4 + 3] = alpha; // A - SDF 值存储在 Alpha 通道
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新纹理 - OpenGL纹理坐标原点在左下角,需要将Y坐标翻转
|
|
||||||
updateAtlas(atlasX, ATLAS_HEIGHT - atlasY - h, w, h, glyphRgbaCache_);
|
|
||||||
|
|
||||||
stbtt_FreeSDF(sdf, nullptr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 普通位图渲染模式
|
|
||||||
int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
|
|
||||||
stbtt_GetCodepointBitmapBox(&fontInfo_, static_cast<int>(codepoint), scale_,
|
|
||||||
scale_, &x0, &y0, &x1, &y1);
|
|
||||||
int w = x1 - x0;
|
|
||||||
int h = y1 - y0;
|
|
||||||
int xoff = x0;
|
|
||||||
// y0 是相对于基线的偏移(通常为负值,表示在基线上方)
|
|
||||||
// bearingY 应该是字形顶部相对于基线的偏移
|
|
||||||
int yoff = y0;
|
|
||||||
|
|
||||||
if (w <= 0 || h <= 0) {
|
|
||||||
// 空白字符(如空格)
|
|
||||||
GlyphData data{};
|
|
||||||
data.advance = advancePx;
|
|
||||||
glyphs_[codepoint] = data;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用预分配缓冲区渲染字形
|
|
||||||
size_t pixelCount = static_cast<size_t>(w) * static_cast<size_t>(h);
|
|
||||||
glyphBitmapCache_.resize(pixelCount);
|
|
||||||
stbtt_MakeCodepointBitmap(&fontInfo_, glyphBitmapCache_.data(), w, h, w,
|
|
||||||
scale_, scale_, static_cast<int>(codepoint));
|
|
||||||
|
|
||||||
// 使用 stb_rect_pack 打包矩形
|
|
||||||
stbrp_rect rect;
|
|
||||||
rect.id = static_cast<int>(codepoint);
|
|
||||||
rect.w = w + PADDING * 2;
|
|
||||||
rect.h = h + PADDING * 2;
|
|
||||||
|
|
||||||
stbrp_pack_rects(&packContext_, &rect, 1);
|
|
||||||
|
|
||||||
if (!rect.was_packed) {
|
|
||||||
E2D_LOG_WARN("字体图集已满,无法缓存字符码点: {}", codepoint);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int atlasX = rect.x + PADDING;
|
|
||||||
int atlasY = rect.y + PADDING;
|
|
||||||
|
|
||||||
// 创建字形数据
|
|
||||||
GlyphData data;
|
|
||||||
data.width = static_cast<float>(w);
|
|
||||||
data.height = static_cast<float>(h);
|
|
||||||
data.bearingX = static_cast<float>(xoff);
|
|
||||||
data.bearingY = static_cast<float>(yoff);
|
|
||||||
data.advance = advancePx;
|
|
||||||
|
|
||||||
// 计算 UV 坐标
|
|
||||||
// stb_rect_pack 使用左上角为原点,OpenGL纹理使用左下角为原点
|
|
||||||
// 需要翻转V坐标
|
|
||||||
float v0 = static_cast<float>(atlasY) / ATLAS_HEIGHT;
|
|
||||||
float v1 = static_cast<float>(atlasY + h) / ATLAS_HEIGHT;
|
|
||||||
data.u0 = static_cast<float>(atlasX) / ATLAS_WIDTH;
|
|
||||||
data.v0 = 1.0f - v1; // 翻转V坐标
|
|
||||||
data.u1 = static_cast<float>(atlasX + w) / ATLAS_WIDTH;
|
|
||||||
data.v1 = 1.0f - v0; // 翻转V坐标
|
|
||||||
|
|
||||||
glyphs_[codepoint] = data;
|
|
||||||
|
|
||||||
// 将单通道字形数据转换为 RGBA 格式(白色字形,Alpha 通道存储灰度)
|
|
||||||
glyphRgbaCache_.resize(pixelCount * 4);
|
|
||||||
for (size_t i = 0; i < pixelCount; ++i) {
|
|
||||||
uint8_t alpha = glyphBitmapCache_[i];
|
|
||||||
glyphRgbaCache_[i * 4 + 0] = 255; // R
|
|
||||||
glyphRgbaCache_[i * 4 + 1] = 255; // G
|
|
||||||
glyphRgbaCache_[i * 4 + 2] = 255; // B
|
|
||||||
glyphRgbaCache_[i * 4 + 3] = alpha; // A
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新纹理 - OpenGL纹理坐标原点在左下角,需要将Y坐标翻转
|
|
||||||
updateAtlas(atlasX, ATLAS_HEIGHT - atlasY - h, w, h, glyphRgbaCache_);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 更新图集纹理区域
|
|
||||||
// ============================================================================
|
|
||||||
void GLFontAtlas::updateAtlas(int x, int y, int width, int height,
|
|
||||||
const std::vector<uint8_t> &data) {
|
|
||||||
if (texture_) {
|
|
||||||
texture_->bind();
|
|
||||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
|
||||||
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, GL_RGBA,
|
|
||||||
GL_UNSIGNED_BYTE, data.data());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,267 +0,0 @@
|
||||||
#include <extra2d/graphics/opengl/gl_framebuffer.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
|
||||||
#include <extra2d/services/logger_service.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// GLFramebuffer 实现
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
GLFramebuffer::GLFramebuffer() = default;
|
|
||||||
|
|
||||||
GLFramebuffer::~GLFramebuffer() { shutdown(); }
|
|
||||||
|
|
||||||
bool GLFramebuffer::init(const FramebufferDesc &desc) {
|
|
||||||
if (fboID_ != 0) {
|
|
||||||
shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
width_ = desc.width;
|
|
||||||
height_ = desc.height;
|
|
||||||
numColorAttachments_ = desc.colorAttachments;
|
|
||||||
hasDepth_ = desc.hasDepth;
|
|
||||||
hasStencil_ = desc.hasStencil;
|
|
||||||
|
|
||||||
// 限制颜色附件数
|
|
||||||
if (numColorAttachments_ > MAX_COLOR_ATTACHMENTS) {
|
|
||||||
numColorAttachments_ = MAX_COLOR_ATTACHMENTS;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成 FBO
|
|
||||||
glGenFramebuffers(1, &fboID_);
|
|
||||||
if (fboID_ == 0) {
|
|
||||||
E2D_LOG_ERROR("生成 OpenGL 帧缓冲区失败");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
E2D_LOG_DEBUG("GLFramebuffer 已创建: ID={}, 大小={}x{}, 颜色附件={}", fboID_,
|
|
||||||
width_, height_, numColorAttachments_);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLFramebuffer::shutdown() {
|
|
||||||
if (fboID_ != 0) {
|
|
||||||
glDeleteFramebuffers(1, &fboID_);
|
|
||||||
E2D_LOG_DEBUG("GLFramebuffer 已销毁: ID={}", fboID_);
|
|
||||||
fboID_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清理纹理引用
|
|
||||||
for (auto &tex : colorTextures_) {
|
|
||||||
tex.reset();
|
|
||||||
}
|
|
||||||
depthTexture_.reset();
|
|
||||||
depthStencilTexture_.reset();
|
|
||||||
|
|
||||||
hasInternalTextures_ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLFramebuffer::bind() {
|
|
||||||
if (fboID_ != 0) {
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, fboID_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLFramebuffer::unbind() { glBindFramebuffer(GL_FRAMEBUFFER, 0); }
|
|
||||||
|
|
||||||
void GLFramebuffer::attachColorTexture(Ptr<Texture> texture, int attachment) {
|
|
||||||
if (fboID_ == 0 || !texture || attachment < 0 ||
|
|
||||||
attachment >= MAX_COLOR_ATTACHMENTS) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bind();
|
|
||||||
|
|
||||||
// 获取 OpenGL 纹理 ID
|
|
||||||
GLuint texID = static_cast<GLuint>(
|
|
||||||
reinterpret_cast<uintptr_t>(texture->getNativeHandle()));
|
|
||||||
|
|
||||||
glFramebufferTexture2D(GL_FRAMEBUFFER, getColorAttachment(attachment),
|
|
||||||
GL_TEXTURE_2D, texID, 0);
|
|
||||||
|
|
||||||
colorTextures_[attachment] = texture;
|
|
||||||
|
|
||||||
unbind();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLFramebuffer::attachDepthTexture(Ptr<Texture> texture) {
|
|
||||||
if (fboID_ == 0 || !texture) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bind();
|
|
||||||
|
|
||||||
// 获取 OpenGL 纹理 ID
|
|
||||||
GLuint texID = static_cast<GLuint>(
|
|
||||||
reinterpret_cast<uintptr_t>(texture->getNativeHandle()));
|
|
||||||
|
|
||||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D,
|
|
||||||
texID, 0);
|
|
||||||
|
|
||||||
depthTexture_ = texture;
|
|
||||||
hasDepth_ = true;
|
|
||||||
hasStencil_ = false;
|
|
||||||
|
|
||||||
unbind();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLFramebuffer::attachDepthStencilTexture(Ptr<Texture> texture) {
|
|
||||||
if (fboID_ == 0 || !texture) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bind();
|
|
||||||
|
|
||||||
// 获取 OpenGL 纹理 ID
|
|
||||||
GLuint texID = static_cast<GLuint>(
|
|
||||||
reinterpret_cast<uintptr_t>(texture->getNativeHandle()));
|
|
||||||
|
|
||||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
|
|
||||||
GL_TEXTURE_2D, texID, 0);
|
|
||||||
|
|
||||||
depthStencilTexture_ = texture;
|
|
||||||
hasDepth_ = true;
|
|
||||||
hasStencil_ = true;
|
|
||||||
|
|
||||||
unbind();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GLFramebuffer::isComplete() {
|
|
||||||
if (fboID_ == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bind();
|
|
||||||
bool complete = checkStatus();
|
|
||||||
unbind();
|
|
||||||
|
|
||||||
return complete;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<Texture> GLFramebuffer::getColorTexture(int attachment) const {
|
|
||||||
if (attachment >= 0 && attachment < MAX_COLOR_ATTACHMENTS) {
|
|
||||||
return colorTextures_[attachment];
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<Texture> GLFramebuffer::getDepthTexture() const { return depthTexture_; }
|
|
||||||
|
|
||||||
void GLFramebuffer::clear(const Color &color, bool clearColor, bool clearDepth,
|
|
||||||
bool clearStencil) {
|
|
||||||
if (fboID_ == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bind();
|
|
||||||
|
|
||||||
GLbitfield mask = 0;
|
|
||||||
|
|
||||||
if (clearColor) {
|
|
||||||
mask |= GL_COLOR_BUFFER_BIT;
|
|
||||||
glClearColor(color.r, color.g, color.b, color.a);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clearDepth) {
|
|
||||||
mask |= GL_DEPTH_BUFFER_BIT;
|
|
||||||
glClearDepthf(1.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clearStencil) {
|
|
||||||
mask |= GL_STENCIL_BUFFER_BIT;
|
|
||||||
glClearStencil(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mask != 0) {
|
|
||||||
glClear(mask);
|
|
||||||
}
|
|
||||||
|
|
||||||
unbind();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLFramebuffer::setViewport(int x, int y, int width, int height) {
|
|
||||||
glViewport(x, y, width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GLFramebuffer::readPixels(int x, int y, int width, int height,
|
|
||||||
std::vector<uint8_t> &outData) {
|
|
||||||
if (fboID_ == 0 || width <= 0 || height <= 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算需要的缓冲区大小 (RGBA8)
|
|
||||||
size_t dataSize = width * height * 4;
|
|
||||||
outData.resize(dataSize);
|
|
||||||
|
|
||||||
bind();
|
|
||||||
glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, outData.data());
|
|
||||||
unbind();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GLFramebuffer::createWithTextures(int width, int height,
|
|
||||||
PixelFormat colorFormat,
|
|
||||||
PixelFormat depthFormat) {
|
|
||||||
FramebufferDesc desc;
|
|
||||||
desc.width = width;
|
|
||||||
desc.height = height;
|
|
||||||
desc.colorAttachments = 1;
|
|
||||||
desc.hasDepth = (depthFormat != PixelFormat::RGBA8);
|
|
||||||
desc.hasStencil = (depthFormat == PixelFormat::Depth24Stencil8);
|
|
||||||
|
|
||||||
if (!init(desc)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
hasInternalTextures_ = true;
|
|
||||||
|
|
||||||
// 创建颜色纹理
|
|
||||||
// 注意:这里简化处理,实际应该通过纹理工厂创建
|
|
||||||
// 暂时返回 true,实际纹理创建由调用者处理
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GLFramebuffer::checkStatus() {
|
|
||||||
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
|
||||||
|
|
||||||
switch (status) {
|
|
||||||
case GL_FRAMEBUFFER_COMPLETE:
|
|
||||||
return true;
|
|
||||||
case GL_FRAMEBUFFER_UNDEFINED:
|
|
||||||
E2D_LOG_ERROR("帧缓冲区不完整: GL_FRAMEBUFFER_UNDEFINED");
|
|
||||||
break;
|
|
||||||
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
|
|
||||||
E2D_LOG_ERROR("帧缓冲区不完整: GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT");
|
|
||||||
break;
|
|
||||||
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
|
|
||||||
E2D_LOG_ERROR(
|
|
||||||
"帧缓冲区不完整: GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT");
|
|
||||||
break;
|
|
||||||
#ifndef GL_ES_VERSION_2_0
|
|
||||||
case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
|
|
||||||
E2D_LOG_ERROR("帧缓冲区不完整: GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER");
|
|
||||||
break;
|
|
||||||
case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
|
|
||||||
E2D_LOG_ERROR("帧缓冲区不完整: GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER");
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
case GL_FRAMEBUFFER_UNSUPPORTED:
|
|
||||||
E2D_LOG_ERROR("帧缓冲区不完整: GL_FRAMEBUFFER_UNSUPPORTED");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
E2D_LOG_ERROR("帧缓冲区不完整: 未知错误 {}", status);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLenum GLFramebuffer::getColorAttachment(int index) {
|
|
||||||
return GL_COLOR_ATTACHMENT0 + index;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,233 +0,0 @@
|
||||||
#include <extra2d/graphics/opengl/gl_pipeline.h>
|
|
||||||
#include <extra2d/services/logger_service.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// GLPipeline 实现
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
GLPipeline::GLPipeline() = default;
|
|
||||||
|
|
||||||
GLPipeline::~GLPipeline() { shutdown(); }
|
|
||||||
|
|
||||||
bool GLPipeline::init(const PipelineDesc &desc) {
|
|
||||||
if (initialized_) {
|
|
||||||
shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
blendMode_ = desc.blendMode;
|
|
||||||
blendEnabled_ = desc.blendEnabled;
|
|
||||||
depthTest_ = desc.depthTest;
|
|
||||||
depthWrite_ = desc.depthWrite;
|
|
||||||
depthFunc_ = desc.depthFunc;
|
|
||||||
cullMode_ = desc.cullMode;
|
|
||||||
|
|
||||||
initialized_ = true;
|
|
||||||
|
|
||||||
E2D_LOG_DEBUG(
|
|
||||||
"GLPipeline initialized: blendMode={}, depthTest={}, cullMode={}",
|
|
||||||
static_cast<int>(blendMode_), depthTest_, static_cast<int>(cullMode_));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLPipeline::shutdown() { initialized_ = false; }
|
|
||||||
|
|
||||||
void GLPipeline::bind() {
|
|
||||||
if (!initialized_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
applyAllStates();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLPipeline::unbind() {
|
|
||||||
// OpenGL 不需要显式解绑管线
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLPipeline::setBlendMode(BlendMode mode) {
|
|
||||||
blendMode_ = mode;
|
|
||||||
applyBlendState();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLPipeline::setDepthTest(bool enabled) {
|
|
||||||
depthTest_ = enabled;
|
|
||||||
applyDepthState();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLPipeline::setDepthWrite(bool enabled) {
|
|
||||||
depthWrite_ = enabled;
|
|
||||||
applyDepthState();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLPipeline::setDepthFunc(DepthFunc func) {
|
|
||||||
depthFunc_ = func;
|
|
||||||
applyDepthState();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLPipeline::setCullMode(CullMode mode) {
|
|
||||||
cullMode_ = mode;
|
|
||||||
applyCullState();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLPipeline::setViewport(int x, int y, int width, int height) {
|
|
||||||
viewportX_ = x;
|
|
||||||
viewportY_ = y;
|
|
||||||
viewportWidth_ = width;
|
|
||||||
viewportHeight_ = height;
|
|
||||||
|
|
||||||
// 检查缓存,避免冗余调用
|
|
||||||
if (x != cachedViewportX_ || y != cachedViewportY_ ||
|
|
||||||
width != cachedViewportWidth_ || height != cachedViewportHeight_) {
|
|
||||||
glViewport(x, y, width, height);
|
|
||||||
cachedViewportX_ = x;
|
|
||||||
cachedViewportY_ = y;
|
|
||||||
cachedViewportWidth_ = width;
|
|
||||||
cachedViewportHeight_ = height;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLPipeline::getViewport(int &x, int &y, int &width, int &height) const {
|
|
||||||
x = viewportX_;
|
|
||||||
y = viewportY_;
|
|
||||||
width = viewportWidth_;
|
|
||||||
height = viewportHeight_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLPipeline::applyAllStates() {
|
|
||||||
applyBlendState();
|
|
||||||
applyDepthState();
|
|
||||||
applyCullState();
|
|
||||||
|
|
||||||
// 应用视口
|
|
||||||
if (viewportWidth_ > 0 && viewportHeight_ > 0) {
|
|
||||||
setViewport(viewportX_, viewportY_, viewportWidth_, viewportHeight_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLPipeline::applyBlendState() {
|
|
||||||
// 检查是否需要启用/禁用混合
|
|
||||||
if (blendEnabled_ != cachedBlendEnabled_) {
|
|
||||||
if (blendEnabled_) {
|
|
||||||
glEnable(GL_BLEND);
|
|
||||||
} else {
|
|
||||||
glDisable(GL_BLEND);
|
|
||||||
}
|
|
||||||
cachedBlendEnabled_ = blendEnabled_;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果禁用了混合,不需要设置混合函数
|
|
||||||
if (!blendEnabled_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查混合模式是否改变
|
|
||||||
if (blendMode_ != cachedBlendMode_) {
|
|
||||||
GLenum srcFactor, dstFactor;
|
|
||||||
getBlendFactors(blendMode_, srcFactor, dstFactor);
|
|
||||||
glBlendFunc(srcFactor, dstFactor);
|
|
||||||
cachedBlendMode_ = blendMode_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLPipeline::applyDepthState() {
|
|
||||||
// 深度测试
|
|
||||||
if (depthTest_ != cachedDepthTest_) {
|
|
||||||
if (depthTest_) {
|
|
||||||
glEnable(GL_DEPTH_TEST);
|
|
||||||
} else {
|
|
||||||
glDisable(GL_DEPTH_TEST);
|
|
||||||
}
|
|
||||||
cachedDepthTest_ = depthTest_;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 深度写入
|
|
||||||
if (depthWrite_ != cachedDepthWrite_) {
|
|
||||||
glDepthMask(depthWrite_ ? GL_TRUE : GL_FALSE);
|
|
||||||
cachedDepthWrite_ = depthWrite_;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 深度函数
|
|
||||||
if (depthFunc_ != cachedDepthFunc_) {
|
|
||||||
glDepthFunc(convertDepthFunc(depthFunc_));
|
|
||||||
cachedDepthFunc_ = depthFunc_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLPipeline::applyCullState() {
|
|
||||||
// 检查裁剪模式是否改变
|
|
||||||
if (cullMode_ != cachedCullMode_) {
|
|
||||||
if (cullMode_ == CullMode::None) {
|
|
||||||
glDisable(GL_CULL_FACE);
|
|
||||||
} else {
|
|
||||||
glEnable(GL_CULL_FACE);
|
|
||||||
glCullFace(convertCullMode(cullMode_));
|
|
||||||
}
|
|
||||||
cachedCullMode_ = cullMode_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLPipeline::getBlendFactors(BlendMode mode, GLenum &srcFactor,
|
|
||||||
GLenum &dstFactor) {
|
|
||||||
switch (mode) {
|
|
||||||
case BlendMode::None:
|
|
||||||
srcFactor = GL_ONE;
|
|
||||||
dstFactor = GL_ZERO;
|
|
||||||
break;
|
|
||||||
case BlendMode::Alpha:
|
|
||||||
srcFactor = GL_SRC_ALPHA;
|
|
||||||
dstFactor = GL_ONE_MINUS_SRC_ALPHA;
|
|
||||||
break;
|
|
||||||
case BlendMode::Additive:
|
|
||||||
srcFactor = GL_SRC_ALPHA;
|
|
||||||
dstFactor = GL_ONE;
|
|
||||||
break;
|
|
||||||
case BlendMode::Multiply:
|
|
||||||
srcFactor = GL_DST_COLOR;
|
|
||||||
dstFactor = GL_ONE_MINUS_SRC_ALPHA;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
srcFactor = GL_SRC_ALPHA;
|
|
||||||
dstFactor = GL_ONE_MINUS_SRC_ALPHA;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GLenum GLPipeline::convertDepthFunc(DepthFunc func) {
|
|
||||||
switch (func) {
|
|
||||||
case DepthFunc::Never:
|
|
||||||
return GL_NEVER;
|
|
||||||
case DepthFunc::Less:
|
|
||||||
return GL_LESS;
|
|
||||||
case DepthFunc::Equal:
|
|
||||||
return GL_EQUAL;
|
|
||||||
case DepthFunc::LessEqual:
|
|
||||||
return GL_LEQUAL;
|
|
||||||
case DepthFunc::Greater:
|
|
||||||
return GL_GREATER;
|
|
||||||
case DepthFunc::NotEqual:
|
|
||||||
return GL_NOTEQUAL;
|
|
||||||
case DepthFunc::GreaterEqual:
|
|
||||||
return GL_GEQUAL;
|
|
||||||
case DepthFunc::Always:
|
|
||||||
return GL_ALWAYS;
|
|
||||||
default:
|
|
||||||
return GL_LESS;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GLenum GLPipeline::convertCullMode(CullMode mode) {
|
|
||||||
switch (mode) {
|
|
||||||
case CullMode::Front:
|
|
||||||
return GL_FRONT;
|
|
||||||
case CullMode::Back:
|
|
||||||
return GL_BACK;
|
|
||||||
case CullMode::Both:
|
|
||||||
return GL_FRONT_AND_BACK;
|
|
||||||
default:
|
|
||||||
return GL_BACK;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,348 +0,0 @@
|
||||||
#include <extra2d/graphics/opengl/gl_shader.h>
|
|
||||||
#include <extra2d/services/logger_service.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 构造函数,初始化着色器程序ID为0
|
|
||||||
*/
|
|
||||||
GLShader::GLShader() : programID_(0) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 析构函数,删除OpenGL着色器程序
|
|
||||||
*/
|
|
||||||
GLShader::~GLShader() {
|
|
||||||
if (programID_ != 0) {
|
|
||||||
glDeleteProgram(programID_);
|
|
||||||
programID_ = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绑定Shader程序
|
|
||||||
*/
|
|
||||||
void GLShader::bind() const { glUseProgram(programID_); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 解绑Shader程序
|
|
||||||
*/
|
|
||||||
void GLShader::unbind() const { glUseProgram(0); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置布尔类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 布尔值
|
|
||||||
*/
|
|
||||||
void GLShader::setBool(const std::string &name, bool value) {
|
|
||||||
glUniform1i(getUniformLocation(name), value ? 1 : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置整数类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 整数值
|
|
||||||
*/
|
|
||||||
void GLShader::setInt(const std::string &name, int value) {
|
|
||||||
glUniform1i(getUniformLocation(name), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置浮点类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 浮点值
|
|
||||||
*/
|
|
||||||
void GLShader::setFloat(const std::string &name, float value) {
|
|
||||||
glUniform1f(getUniformLocation(name), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置二维向量类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 二维向量值
|
|
||||||
*/
|
|
||||||
void GLShader::setVec2(const std::string &name, const glm::vec2 &value) {
|
|
||||||
glUniform2fv(getUniformLocation(name), 1, &value[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置三维向量类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 三维向量值
|
|
||||||
*/
|
|
||||||
void GLShader::setVec3(const std::string &name, const glm::vec3 &value) {
|
|
||||||
glUniform3fv(getUniformLocation(name), 1, &value[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置四维向量类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 四维向量值
|
|
||||||
*/
|
|
||||||
void GLShader::setVec4(const std::string &name, const glm::vec4 &value) {
|
|
||||||
glUniform4fv(getUniformLocation(name), 1, &value[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置4x4矩阵类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param value 4x4矩阵值
|
|
||||||
*/
|
|
||||||
void GLShader::setMat4(const std::string &name, const glm::mat4 &value) {
|
|
||||||
glUniformMatrix4fv(getUniformLocation(name), 1, GL_FALSE, &value[0][0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置颜色类型uniform变量
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @param color 颜色值
|
|
||||||
*/
|
|
||||||
void GLShader::setColor(const std::string &name, const Color &color) {
|
|
||||||
glUniform4f(getUniformLocation(name), color.r, color.g, color.b, color.a);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从源码编译Shader
|
|
||||||
* @param vertexSource 顶点着色器源码
|
|
||||||
* @param fragmentSource 片段着色器源码
|
|
||||||
* @return 编译成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool GLShader::compileFromSource(const char *vertexSource,
|
|
||||||
const char *fragmentSource) {
|
|
||||||
GLuint vertexShader = compileShader(GL_VERTEX_SHADER, vertexSource);
|
|
||||||
if (vertexShader == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLuint fragmentShader = compileShader(GL_FRAGMENT_SHADER, fragmentSource);
|
|
||||||
if (fragmentShader == 0) {
|
|
||||||
glDeleteShader(vertexShader);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (programID_ != 0) {
|
|
||||||
glDeleteProgram(programID_);
|
|
||||||
uniformCache_.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
programID_ = glCreateProgram();
|
|
||||||
glAttachShader(programID_, vertexShader);
|
|
||||||
glAttachShader(programID_, fragmentShader);
|
|
||||||
glLinkProgram(programID_);
|
|
||||||
|
|
||||||
GLint success;
|
|
||||||
glGetProgramiv(programID_, GL_LINK_STATUS, &success);
|
|
||||||
if (!success) {
|
|
||||||
char infoLog[512];
|
|
||||||
glGetProgramInfoLog(programID_, 512, nullptr, infoLog);
|
|
||||||
E2D_LOG_ERROR("着色器程序链接失败: {}", infoLog);
|
|
||||||
glDeleteProgram(programID_);
|
|
||||||
programID_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
glDeleteShader(vertexShader);
|
|
||||||
glDeleteShader(fragmentShader);
|
|
||||||
|
|
||||||
return success == GL_TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从二进制数据创建Shader
|
|
||||||
* @param binary 二进制数据
|
|
||||||
* @return 创建成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool GLShader::compileFromBinary(const std::vector<uint8_t> &binary) {
|
|
||||||
if (binary.empty()) {
|
|
||||||
E2D_LOG_ERROR("二进制数据为空");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLint numFormats = 0;
|
|
||||||
glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &numFormats);
|
|
||||||
if (numFormats == 0) {
|
|
||||||
E2D_LOG_ERROR("不支持程序二进制格式");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (programID_ != 0) {
|
|
||||||
glDeleteProgram(programID_);
|
|
||||||
uniformCache_.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
programID_ = glCreateProgram();
|
|
||||||
|
|
||||||
GLenum binaryFormat = 0;
|
|
||||||
glGetIntegerv(GL_PROGRAM_BINARY_FORMATS,
|
|
||||||
reinterpret_cast<GLint *>(&binaryFormat));
|
|
||||||
|
|
||||||
glProgramBinary(programID_, binaryFormat, binary.data(),
|
|
||||||
static_cast<GLsizei>(binary.size()));
|
|
||||||
|
|
||||||
GLint success = 0;
|
|
||||||
glGetProgramiv(programID_, GL_LINK_STATUS, &success);
|
|
||||||
if (!success) {
|
|
||||||
char infoLog[512];
|
|
||||||
glGetProgramInfoLog(programID_, 512, nullptr, infoLog);
|
|
||||||
E2D_LOG_ERROR("从二进制加载着色器失败: {}", infoLog);
|
|
||||||
glDeleteProgram(programID_);
|
|
||||||
programID_ = 0;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取Shader二进制数据
|
|
||||||
* @param outBinary 输出的二进制数据
|
|
||||||
* @return 成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool GLShader::getBinary(std::vector<uint8_t> &outBinary) {
|
|
||||||
if (programID_ == 0) {
|
|
||||||
E2D_LOG_WARN("无法获取二进制数据: 着色器程序为 0");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLint binaryLength = 0;
|
|
||||||
glGetProgramiv(programID_, GL_PROGRAM_BINARY_LENGTH, &binaryLength);
|
|
||||||
|
|
||||||
E2D_LOG_DEBUG("着色器二进制数据长度: {}", binaryLength);
|
|
||||||
|
|
||||||
if (binaryLength <= 0) {
|
|
||||||
E2D_LOG_WARN("着色器二进制数据长度为 0 或负数");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
outBinary.resize(binaryLength);
|
|
||||||
|
|
||||||
GLenum binaryFormat = 0;
|
|
||||||
GLsizei actualLength = 0;
|
|
||||||
glGetProgramBinary(programID_, binaryLength, &actualLength, &binaryFormat,
|
|
||||||
outBinary.data());
|
|
||||||
|
|
||||||
GLenum err = glGetError();
|
|
||||||
if (err != GL_NO_ERROR) {
|
|
||||||
E2D_LOG_ERROR("glGetProgramBinary 失败,错误码: {}", err);
|
|
||||||
outBinary.clear();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (actualLength == 0) {
|
|
||||||
E2D_LOG_WARN("glGetProgramBinary 返回 0 字节");
|
|
||||||
outBinary.clear();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (actualLength != binaryLength) {
|
|
||||||
outBinary.resize(actualLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
E2D_LOG_DEBUG("着色器二进制数据已获取: {} 字节, 格式: {}", actualLength,
|
|
||||||
binaryFormat);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 编译单个着色器
|
|
||||||
* @param type 着色器类型
|
|
||||||
* @param source 着色器源码
|
|
||||||
* @return 着色器ID,失败返回0
|
|
||||||
*/
|
|
||||||
GLuint GLShader::compileShader(GLenum type, const char *source) {
|
|
||||||
GLuint shader = glCreateShader(type);
|
|
||||||
glShaderSource(shader, 1, &source, nullptr);
|
|
||||||
glCompileShader(shader);
|
|
||||||
|
|
||||||
GLint success;
|
|
||||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
|
|
||||||
if (!success) {
|
|
||||||
char infoLog[512];
|
|
||||||
glGetShaderInfoLog(shader, 512, nullptr, infoLog);
|
|
||||||
E2D_LOG_ERROR("着色器编译失败: {}", infoLog);
|
|
||||||
glDeleteShader(shader);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取uniform位置
|
|
||||||
* @param name uniform变量名
|
|
||||||
* @return uniform位置
|
|
||||||
*/
|
|
||||||
GLint GLShader::getUniformLocation(const std::string &name) {
|
|
||||||
auto it = uniformCache_.find(name);
|
|
||||||
if (it != uniformCache_.end()) {
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
GLint location = glGetUniformLocation(programID_, name.c_str());
|
|
||||||
uniformCache_[name] = location;
|
|
||||||
return location;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// GLShaderFactory 实现
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从源码创建Shader
|
|
||||||
* @param name Shader名称
|
|
||||||
* @param vertSource 顶点着色器源码
|
|
||||||
* @param fragSource 片段着色器源码
|
|
||||||
* @return 创建的Shader实例
|
|
||||||
*/
|
|
||||||
Ptr<IShader> GLShaderFactory::createFromSource(const std::string &name,
|
|
||||||
const std::string &vertSource,
|
|
||||||
const std::string &fragSource) {
|
|
||||||
|
|
||||||
auto shader = std::make_shared<GLShader>();
|
|
||||||
shader->setName(name);
|
|
||||||
|
|
||||||
if (!shader->compileFromSource(vertSource.c_str(), fragSource.c_str())) {
|
|
||||||
E2D_LOG_ERROR("从源码编译着色器失败: {}", name);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从缓存二进制创建Shader
|
|
||||||
* @param name Shader名称
|
|
||||||
* @param binary 编译后的二进制数据
|
|
||||||
* @return 创建的Shader实例
|
|
||||||
*/
|
|
||||||
Ptr<IShader>
|
|
||||||
GLShaderFactory::createFromBinary(const std::string &name,
|
|
||||||
const std::vector<uint8_t> &binary) {
|
|
||||||
|
|
||||||
auto shader = std::make_shared<GLShader>();
|
|
||||||
shader->setName(name);
|
|
||||||
|
|
||||||
if (!shader->compileFromBinary(binary)) {
|
|
||||||
E2D_LOG_ERROR("从二进制创建着色器失败: {}", name);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取Shader的二进制数据
|
|
||||||
* @param shader Shader实例
|
|
||||||
* @param outBinary 输出的二进制数据
|
|
||||||
* @return 成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool GLShaderFactory::getShaderBinary(const IShader &shader,
|
|
||||||
std::vector<uint8_t> &outBinary) {
|
|
||||||
const GLShader *glShader = dynamic_cast<const GLShader *>(&shader);
|
|
||||||
if (!glShader) {
|
|
||||||
E2D_LOG_ERROR("着色器不是 GLShader 实例");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return const_cast<GLShader *>(glShader)->getBinary(outBinary);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,230 +0,0 @@
|
||||||
#include <extra2d/graphics/opengl/gl_sprite_batch.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
|
||||||
#include <extra2d/graphics/shader/shader_manager.h>
|
|
||||||
#include <extra2d/services/logger_service.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
GLSpriteBatch::GLSpriteBatch()
|
|
||||||
: vao_(0), currentTexture_(nullptr), drawCallCount_(0) {}
|
|
||||||
|
|
||||||
GLSpriteBatch::~GLSpriteBatch() { shutdown(); }
|
|
||||||
|
|
||||||
bool GLSpriteBatch::init() {
|
|
||||||
// 从ShaderManager获取精灵着色器
|
|
||||||
shader_ = ShaderManager::getInstance().getBuiltin("sprite");
|
|
||||||
if (!shader_) {
|
|
||||||
E2D_LOG_ERROR("获取内置精灵着色器失败");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建 VAO
|
|
||||||
glGenVertexArrays(1, &vao_);
|
|
||||||
glBindVertexArray(vao_);
|
|
||||||
|
|
||||||
// 初始化 VBO(顶点缓冲区)- 动态使用模式
|
|
||||||
BufferDesc vboDesc;
|
|
||||||
vboDesc.type = BufferType::Vertex;
|
|
||||||
vboDesc.usage = BufferUsage::Dynamic;
|
|
||||||
vboDesc.size = SpriteBatch::MAX_VERTICES * sizeof(SpriteVertex);
|
|
||||||
vboDesc.initialData = nullptr;
|
|
||||||
if (!vbo_.init(vboDesc)) {
|
|
||||||
E2D_LOG_ERROR("初始化精灵批处理 VBO 失败");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
vbo_.bind();
|
|
||||||
|
|
||||||
// 设置顶点属性
|
|
||||||
glEnableVertexAttribArray(0);
|
|
||||||
glVertexAttribPointer(
|
|
||||||
0, 2, GL_FLOAT, GL_FALSE, sizeof(SpriteVertex),
|
|
||||||
reinterpret_cast<void *>(offsetof(SpriteVertex, position)));
|
|
||||||
|
|
||||||
glEnableVertexAttribArray(1);
|
|
||||||
glVertexAttribPointer(
|
|
||||||
1, 2, GL_FLOAT, GL_FALSE, sizeof(SpriteVertex),
|
|
||||||
reinterpret_cast<void *>(offsetof(SpriteVertex, texCoord)));
|
|
||||||
|
|
||||||
glEnableVertexAttribArray(2);
|
|
||||||
glVertexAttribPointer(
|
|
||||||
2, 4, GL_FLOAT, GL_FALSE, sizeof(SpriteVertex),
|
|
||||||
reinterpret_cast<void *>(offsetof(SpriteVertex, color)));
|
|
||||||
|
|
||||||
// 初始化 EBO(索引缓冲区)- 静态使用模式
|
|
||||||
BufferDesc eboDesc;
|
|
||||||
eboDesc.type = BufferType::Index;
|
|
||||||
eboDesc.usage = BufferUsage::Static;
|
|
||||||
eboDesc.size = batch_.getIndices().size() * sizeof(uint16_t);
|
|
||||||
eboDesc.initialData = batch_.getIndices().data();
|
|
||||||
if (!ebo_.init(eboDesc)) {
|
|
||||||
E2D_LOG_ERROR("初始化精灵批处理 EBO 失败");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
ebo_.bind();
|
|
||||||
|
|
||||||
glBindVertexArray(0);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLSpriteBatch::shutdown() {
|
|
||||||
// 使用 GLBuffer::shutdown() 释放缓冲区资源
|
|
||||||
vbo_.shutdown();
|
|
||||||
ebo_.shutdown();
|
|
||||||
|
|
||||||
if (vao_ != 0) {
|
|
||||||
glDeleteVertexArrays(1, &vao_);
|
|
||||||
vao_ = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLSpriteBatch::begin(const glm::mat4 &viewProjection) {
|
|
||||||
batch_.begin(viewProjection);
|
|
||||||
batches_.clear();
|
|
||||||
currentTexture_ = nullptr;
|
|
||||||
drawCallCount_ = 0;
|
|
||||||
// 保存 viewProjection 矩阵供后续使用
|
|
||||||
viewProjection_ = viewProjection;
|
|
||||||
|
|
||||||
// 绑定 VAO 和缓冲区
|
|
||||||
glBindVertexArray(vao_);
|
|
||||||
vbo_.bind();
|
|
||||||
ebo_.bind();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLSpriteBatch::begin(const glm::mat4 &viewProjection,
|
|
||||||
Ptr<IShader> shader) {
|
|
||||||
// 设置自定义着色器
|
|
||||||
if (shader) {
|
|
||||||
shader_ = shader;
|
|
||||||
}
|
|
||||||
begin(viewProjection);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLSpriteBatch::setShader(Ptr<IShader> shader) {
|
|
||||||
// 如果当前有未提交的批次,先提交
|
|
||||||
if (batch_.getSpriteCount() > 0) {
|
|
||||||
flush();
|
|
||||||
}
|
|
||||||
shader_ = shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLSpriteBatch::end() {
|
|
||||||
if (batch_.getSpriteCount() > 0) {
|
|
||||||
flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解绑缓冲区
|
|
||||||
vbo_.unbind();
|
|
||||||
ebo_.unbind();
|
|
||||||
glBindVertexArray(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLSpriteBatch::draw(const Texture &texture, const SpriteData &data) {
|
|
||||||
const GLTexture *glTex = dynamic_cast<const GLTexture *>(&texture);
|
|
||||||
if (!glTex) {
|
|
||||||
E2D_LOG_WARN("精灵批处理纹理类型无效");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果纹理改变或批次已满,先提交当前批次
|
|
||||||
if (currentTexture_ != glTex || batch_.needsFlush()) {
|
|
||||||
if (batch_.getSpriteCount() > 0) {
|
|
||||||
submitBatch();
|
|
||||||
}
|
|
||||||
currentTexture_ = glTex;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用 batch 层生成顶点
|
|
||||||
batch_.draw(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLSpriteBatch::drawBatch(const Texture &texture,
|
|
||||||
const std::vector<SpriteData> &sprites) {
|
|
||||||
const GLTexture *glTex = dynamic_cast<const GLTexture *>(&texture);
|
|
||||||
if (!glTex) {
|
|
||||||
E2D_LOG_WARN("精灵批处理纹理类型无效");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 批量处理精灵
|
|
||||||
for (const auto &data : sprites) {
|
|
||||||
// 如果纹理改变或批次已满,先提交当前批次
|
|
||||||
if (currentTexture_ != glTex || batch_.needsFlush()) {
|
|
||||||
if (batch_.getSpriteCount() > 0) {
|
|
||||||
submitBatch();
|
|
||||||
}
|
|
||||||
currentTexture_ = glTex;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用 batch 层生成顶点
|
|
||||||
batch_.draw(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLSpriteBatch::submitBatch() {
|
|
||||||
if (batch_.getSpriteCount() == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 记录批次信息
|
|
||||||
Batch batchInfo;
|
|
||||||
batchInfo.texture = currentTexture_;
|
|
||||||
batchInfo.startVertex = 0; // 每次提交都是新的缓冲区
|
|
||||||
batchInfo.vertexCount = batch_.getSpriteCount() * 4;
|
|
||||||
batches_.push_back(batchInfo);
|
|
||||||
|
|
||||||
// 绑定着色器并设置uniform
|
|
||||||
if (shader_) {
|
|
||||||
shader_->bind();
|
|
||||||
|
|
||||||
// 只提供需要动态计算的值,其他值使用JSON中定义的默认值
|
|
||||||
UniformValueMap uniformValues;
|
|
||||||
uniformValues["u_viewProjection"] = viewProjection_;
|
|
||||||
|
|
||||||
// 合并额外的uniform值(如SDF字体的u_textureSize)
|
|
||||||
for (const auto &[name, value] : extraUniforms_) {
|
|
||||||
uniformValues[name] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用ShaderManager自动应用uniform值(未提供的值使用JSON中的默认值)
|
|
||||||
// 使用着色器自己的名称(从JSON中解析的name字段)
|
|
||||||
ShaderManager::getInstance().applyUniforms(shader_, shader_->getName(),
|
|
||||||
uniformValues);
|
|
||||||
|
|
||||||
// 设置纹理采样器
|
|
||||||
shader_->setInt("u_texture", 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上传顶点数据 - 使用 orphaning 策略优化动态缓冲区
|
|
||||||
// 通过传入 nullptr 进行 orphaning,告诉驱动器可以丢弃旧缓冲区并分配新内存
|
|
||||||
// 这样可以避免 GPU 等待,提高性能
|
|
||||||
size_t vertexDataSize = batch_.getVertices().size() * sizeof(SpriteVertex);
|
|
||||||
vbo_.setData(nullptr, vertexDataSize); // orphaning
|
|
||||||
vbo_.updateData(batch_.getVertices().data(), 0, vertexDataSize);
|
|
||||||
|
|
||||||
// 绘制
|
|
||||||
currentTexture_->bind(0);
|
|
||||||
|
|
||||||
size_t indexCount = batch_.getSpriteCount() * 6;
|
|
||||||
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(indexCount),
|
|
||||||
GL_UNSIGNED_SHORT, nullptr);
|
|
||||||
|
|
||||||
drawCallCount_++;
|
|
||||||
|
|
||||||
// 清空 batch 层,准备下一批
|
|
||||||
batch_.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLSpriteBatch::flush() {
|
|
||||||
// 提交最后的批次
|
|
||||||
if (batch_.getSpriteCount() > 0) {
|
|
||||||
submitBatch();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重置状态
|
|
||||||
batches_.clear();
|
|
||||||
currentTexture_ = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,504 +0,0 @@
|
||||||
#include <extra2d/graphics/memory/gpu_context.h>
|
|
||||||
#include <extra2d/graphics/memory/vram_manager.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
|
||||||
|
|
||||||
#define STB_IMAGE_IMPLEMENTATION
|
|
||||||
#include <cstring>
|
|
||||||
#include <extra2d/core/service_locator.h>
|
|
||||||
#include <extra2d/services/logger_service.h>
|
|
||||||
#include <fstream>
|
|
||||||
#include <stb/stb_image.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// KTX 文件头结构
|
|
||||||
// ============================================================================
|
|
||||||
#pragma pack(push, 1)
|
|
||||||
struct KTXHeader {
|
|
||||||
uint8_t identifier[12];
|
|
||||||
uint32_t endianness;
|
|
||||||
uint32_t glType;
|
|
||||||
uint32_t glTypeSize;
|
|
||||||
uint32_t glFormat;
|
|
||||||
uint32_t glInternalFormat;
|
|
||||||
uint32_t glBaseInternalFormat;
|
|
||||||
uint32_t pixelWidth;
|
|
||||||
uint32_t pixelHeight;
|
|
||||||
uint32_t pixelDepth;
|
|
||||||
uint32_t numberOfArrayElements;
|
|
||||||
uint32_t numberOfFaces;
|
|
||||||
uint32_t numberOfMipmapLevels;
|
|
||||||
uint32_t bytesOfKeyValueData;
|
|
||||||
};
|
|
||||||
#pragma pack(pop)
|
|
||||||
|
|
||||||
// KTX 文件标识符
|
|
||||||
static const uint8_t KTX_IDENTIFIER[12] = {0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31,
|
|
||||||
0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A};
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// DDS 文件头结构
|
|
||||||
// ============================================================================
|
|
||||||
#pragma pack(push, 1)
|
|
||||||
struct DDSPixelFormat {
|
|
||||||
uint32_t size;
|
|
||||||
uint32_t flags;
|
|
||||||
uint32_t fourCC;
|
|
||||||
uint32_t rgbBitCount;
|
|
||||||
uint32_t rBitMask;
|
|
||||||
uint32_t gBitMask;
|
|
||||||
uint32_t bBitMask;
|
|
||||||
uint32_t aBitMask;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DDSHeader {
|
|
||||||
uint32_t magic;
|
|
||||||
uint32_t size;
|
|
||||||
uint32_t flags;
|
|
||||||
uint32_t height;
|
|
||||||
uint32_t width;
|
|
||||||
uint32_t pitchOrLinearSize;
|
|
||||||
uint32_t depth;
|
|
||||||
uint32_t mipMapCount;
|
|
||||||
uint32_t reserved1[11];
|
|
||||||
DDSPixelFormat pixelFormat;
|
|
||||||
uint32_t caps;
|
|
||||||
uint32_t caps2;
|
|
||||||
uint32_t caps3;
|
|
||||||
uint32_t caps4;
|
|
||||||
uint32_t reserved2;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DDSHeaderDXT10 {
|
|
||||||
uint32_t dxgiFormat;
|
|
||||||
uint32_t resourceDimension;
|
|
||||||
uint32_t miscFlag;
|
|
||||||
uint32_t arraySize;
|
|
||||||
uint32_t miscFlags2;
|
|
||||||
};
|
|
||||||
#pragma pack(pop)
|
|
||||||
|
|
||||||
static constexpr uint32_t DDS_MAGIC = 0x20534444; // "DDS "
|
|
||||||
static constexpr uint32_t DDPF_FOURCC = 0x04;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 生成四字符代码(FourCC)
|
|
||||||
* @param a 第一个字符
|
|
||||||
* @param b 第二个字符
|
|
||||||
* @param c 第三个字符
|
|
||||||
* @param d 第四个字符
|
|
||||||
* @return 组合后的32位无符号整数
|
|
||||||
*/
|
|
||||||
static uint32_t makeFourCC(char a, char b, char c, char d) {
|
|
||||||
return static_cast<uint32_t>(a) | (static_cast<uint32_t>(b) << 8) |
|
|
||||||
(static_cast<uint32_t>(c) << 16) | (static_cast<uint32_t>(d) << 24);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// GLTexture 实现
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从像素数据构造纹理对象
|
|
||||||
* @param width 纹理宽度(像素)
|
|
||||||
* @param height 纹理高度(像素)
|
|
||||||
* @param pixels 像素数据指针,可为nullptr创建空纹理
|
|
||||||
* @param channels 颜色通道数(1=R, 3=RGB, 4=RGBA)
|
|
||||||
*/
|
|
||||||
GLTexture::GLTexture(int width, int height, const uint8_t *pixels, int channels)
|
|
||||||
: textureID_(0), width_(width), height_(height), channels_(channels),
|
|
||||||
format_(PixelFormat::RGBA8), dataSize_(0) {
|
|
||||||
// 保存像素数据用于生成遮罩
|
|
||||||
if (pixels) {
|
|
||||||
pixelData_.resize(width * height * channels);
|
|
||||||
std::memcpy(pixelData_.data(), pixels, pixelData_.size());
|
|
||||||
}
|
|
||||||
createTexture(pixels);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从文件路径构造纹理对象
|
|
||||||
* @param filepath 纹理文件路径,支持普通图片格式和压缩格式(KTX/DDS)
|
|
||||||
*/
|
|
||||||
GLTexture::GLTexture(const std::string &filepath)
|
|
||||||
: textureID_(0), width_(0), height_(0), channels_(0),
|
|
||||||
format_(PixelFormat::RGBA8), dataSize_(0) {
|
|
||||||
// 检查是否为压缩纹理格式
|
|
||||||
std::string ext = filepath.substr(filepath.find_last_of('.') + 1);
|
|
||||||
if (ext == "ktx" || ext == "KTX") {
|
|
||||||
loadCompressed(filepath);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (ext == "dds" || ext == "DDS") {
|
|
||||||
loadCompressed(filepath);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 不翻转图片,保持原始方向
|
|
||||||
stbi_set_flip_vertically_on_load(false);
|
|
||||||
uint8_t *data = stbi_load(filepath.c_str(), &width_, &height_, &channels_, 0);
|
|
||||||
if (data) {
|
|
||||||
// 保存像素数据用于生成遮罩
|
|
||||||
pixelData_.resize(width_ * height_ * channels_);
|
|
||||||
std::memcpy(pixelData_.data(), data, pixelData_.size());
|
|
||||||
|
|
||||||
createTexture(data);
|
|
||||||
stbi_image_free(data);
|
|
||||||
} else {
|
|
||||||
E2D_LOG_ERROR("加载纹理失败: {}", filepath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GLTexture::~GLTexture() {
|
|
||||||
if (textureID_ != 0) {
|
|
||||||
// 检查 GPU 上下文是否仍然有效
|
|
||||||
// 如果 OpenGL 上下文已销毁,则跳过 glDeleteTextures 调用
|
|
||||||
if (GPUContext::get().isValid()) {
|
|
||||||
glDeleteTextures(1, &textureID_);
|
|
||||||
}
|
|
||||||
// VRAM 跟踪: 释放纹理显存(无论上下文是否有效都需要更新统计)
|
|
||||||
if (dataSize_ > 0) {
|
|
||||||
VRAMMgr::get().freeTexture(dataSize_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLTexture::setFilter(bool linear) {
|
|
||||||
bind();
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
|
|
||||||
linear ? GL_LINEAR : GL_NEAREST);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
|
|
||||||
linear ? GL_LINEAR : GL_NEAREST);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLTexture::setWrap(bool repeat) {
|
|
||||||
bind();
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
|
|
||||||
repeat ? GL_REPEAT : GL_CLAMP_TO_EDGE);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,
|
|
||||||
repeat ? GL_REPEAT : GL_CLAMP_TO_EDGE);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLTexture::bind(unsigned int slot) const {
|
|
||||||
glActiveTexture(GL_TEXTURE0 + slot);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, textureID_);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 解绑当前纹理
|
|
||||||
*/
|
|
||||||
void GLTexture::unbind() const { glBindTexture(GL_TEXTURE_2D, 0); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建OpenGL纹理对象并上传像素数据
|
|
||||||
* @param pixels 像素数据指针
|
|
||||||
*/
|
|
||||||
void GLTexture::createTexture(const uint8_t *pixels) {
|
|
||||||
GLenum format = GL_RGBA;
|
|
||||||
GLenum internalFormat = GL_RGBA8;
|
|
||||||
int unpackAlignment = 4;
|
|
||||||
if (channels_ == 1) {
|
|
||||||
format = GL_RED;
|
|
||||||
internalFormat = GL_R8;
|
|
||||||
unpackAlignment = 1;
|
|
||||||
format_ = PixelFormat::R8;
|
|
||||||
} else if (channels_ == 3) {
|
|
||||||
format = GL_RGB;
|
|
||||||
internalFormat = GL_RGB8;
|
|
||||||
unpackAlignment = 1;
|
|
||||||
format_ = PixelFormat::RGB8;
|
|
||||||
} else if (channels_ == 4) {
|
|
||||||
format = GL_RGBA;
|
|
||||||
internalFormat = GL_RGBA8;
|
|
||||||
unpackAlignment = 4;
|
|
||||||
format_ = PixelFormat::RGBA8;
|
|
||||||
}
|
|
||||||
|
|
||||||
glGenTextures(1, &textureID_);
|
|
||||||
bind();
|
|
||||||
|
|
||||||
GLint prevUnpackAlignment = 4;
|
|
||||||
glGetIntegerv(GL_UNPACK_ALIGNMENT, &prevUnpackAlignment);
|
|
||||||
glPixelStorei(GL_UNPACK_ALIGNMENT, unpackAlignment);
|
|
||||||
|
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width_, height_, 0, format,
|
|
||||||
GL_UNSIGNED_BYTE, pixels);
|
|
||||||
glPixelStorei(GL_UNPACK_ALIGNMENT, prevUnpackAlignment);
|
|
||||||
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
||||||
// 使用 NEAREST 过滤器,更适合像素艺术风格的精灵
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
||||||
|
|
||||||
glGenerateMipmap(GL_TEXTURE_2D);
|
|
||||||
|
|
||||||
// VRAM 跟踪
|
|
||||||
dataSize_ = static_cast<size_t>(width_ * height_ * channels_);
|
|
||||||
VRAMMgr::get().allocTexture(dataSize_);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 压缩纹理加载
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
bool GLTexture::loadCompressed(const std::string &filepath) {
|
|
||||||
std::string ext = filepath.substr(filepath.find_last_of('.') + 1);
|
|
||||||
if (ext == "ktx" || ext == "KTX") {
|
|
||||||
return loadKTX(filepath);
|
|
||||||
}
|
|
||||||
if (ext == "dds" || ext == "DDS") {
|
|
||||||
return loadDDS(filepath);
|
|
||||||
}
|
|
||||||
E2D_LOG_ERROR("不支持的压缩纹理格式: {}", filepath);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 加载KTX格式压缩纹理
|
|
||||||
* @param filepath KTX文件路径
|
|
||||||
* @return 加载成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool GLTexture::loadKTX(const std::string &filepath) {
|
|
||||||
std::ifstream file(filepath, std::ios::binary);
|
|
||||||
if (!file.is_open()) {
|
|
||||||
E2D_LOG_ERROR("打开 KTX 文件失败: {}", filepath);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
KTXHeader header;
|
|
||||||
file.read(reinterpret_cast<char *>(&header), sizeof(header));
|
|
||||||
if (!file) {
|
|
||||||
E2D_LOG_ERROR("读取 KTX 文件头失败: {}", filepath);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证标识符
|
|
||||||
if (std::memcmp(header.identifier, KTX_IDENTIFIER, 12) != 0) {
|
|
||||||
E2D_LOG_ERROR("无效的 KTX 标识符: {}", filepath);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
width_ = static_cast<int>(header.pixelWidth);
|
|
||||||
height_ = static_cast<int>(header.pixelHeight);
|
|
||||||
channels_ = 4; // 压缩纹理通常解压为 RGBA
|
|
||||||
|
|
||||||
// 确定压缩格式
|
|
||||||
GLenum glInternalFormat = header.glInternalFormat;
|
|
||||||
switch (glInternalFormat) {
|
|
||||||
case GL_COMPRESSED_RGB8_ETC2:
|
|
||||||
format_ = PixelFormat::ETC2_RGB8;
|
|
||||||
channels_ = 3;
|
|
||||||
break;
|
|
||||||
case GL_COMPRESSED_RGBA8_ETC2_EAC:
|
|
||||||
format_ = PixelFormat::ETC2_RGBA8;
|
|
||||||
break;
|
|
||||||
case GL_COMPRESSED_RGBA_ASTC_4x4:
|
|
||||||
format_ = PixelFormat::ASTC_4x4;
|
|
||||||
break;
|
|
||||||
case GL_COMPRESSED_RGBA_ASTC_6x6:
|
|
||||||
format_ = PixelFormat::ASTC_6x6;
|
|
||||||
break;
|
|
||||||
case GL_COMPRESSED_RGBA_ASTC_8x8:
|
|
||||||
format_ = PixelFormat::ASTC_8x8;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
E2D_LOG_ERROR("不支持的 KTX 内部格式: {:#06x}", glInternalFormat);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 跳过 key-value 数据
|
|
||||||
file.seekg(header.bytesOfKeyValueData, std::ios::cur);
|
|
||||||
|
|
||||||
// 读取第一个 mipmap level
|
|
||||||
uint32_t imageSize = 0;
|
|
||||||
file.read(reinterpret_cast<char *>(&imageSize), sizeof(imageSize));
|
|
||||||
if (!file || imageSize == 0) {
|
|
||||||
E2D_LOG_ERROR("读取 KTX 图像大小失败: {}", filepath);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<uint8_t> compressedData(imageSize);
|
|
||||||
file.read(reinterpret_cast<char *>(compressedData.data()), imageSize);
|
|
||||||
if (!file) {
|
|
||||||
E2D_LOG_ERROR("读取 KTX 图像数据失败: {}", filepath);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建 GL 纹理
|
|
||||||
glGenTextures(1, &textureID_);
|
|
||||||
bind();
|
|
||||||
|
|
||||||
glCompressedTexImage2D(GL_TEXTURE_2D, 0, glInternalFormat, width_, height_, 0,
|
|
||||||
static_cast<GLsizei>(imageSize),
|
|
||||||
compressedData.data());
|
|
||||||
|
|
||||||
GLenum err = glGetError();
|
|
||||||
if (err != GL_NO_ERROR) {
|
|
||||||
E2D_LOG_ERROR("KTX 纹理上传失败: {:#06x}", err);
|
|
||||||
glDeleteTextures(1, &textureID_);
|
|
||||||
textureID_ = 0;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
||||||
|
|
||||||
// VRAM 跟踪
|
|
||||||
dataSize_ = imageSize;
|
|
||||||
VRAMMgr::get().allocTexture(dataSize_);
|
|
||||||
|
|
||||||
E2D_LOG_INFO("已加载 KTX 压缩纹理: {} ({}x{}, 格式={:#06x})", filepath,
|
|
||||||
width_, height_, glInternalFormat);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 加载DDS格式压缩纹理
|
|
||||||
* @param filepath DDS文件路径
|
|
||||||
* @return 加载成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool GLTexture::loadDDS(const std::string &filepath) {
|
|
||||||
std::ifstream file(filepath, std::ios::binary);
|
|
||||||
if (!file.is_open()) {
|
|
||||||
E2D_LOG_ERROR("打开 DDS 文件失败: {}", filepath);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
DDSHeader header;
|
|
||||||
file.read(reinterpret_cast<char *>(&header), sizeof(header));
|
|
||||||
if (!file) {
|
|
||||||
E2D_LOG_ERROR("读取 DDS 文件头失败: {}", filepath);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (header.magic != DDS_MAGIC) {
|
|
||||||
E2D_LOG_ERROR("无效的 DDS 魔数: {}", filepath);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
width_ = static_cast<int>(header.width);
|
|
||||||
height_ = static_cast<int>(header.height);
|
|
||||||
channels_ = 4;
|
|
||||||
|
|
||||||
GLenum glInternalFormat = 0;
|
|
||||||
|
|
||||||
// 检查 DX10 扩展头
|
|
||||||
if ((header.pixelFormat.flags & DDPF_FOURCC) &&
|
|
||||||
header.pixelFormat.fourCC == makeFourCC('D', 'X', '1', '0')) {
|
|
||||||
DDSHeaderDXT10 dx10Header;
|
|
||||||
file.read(reinterpret_cast<char *>(&dx10Header), sizeof(dx10Header));
|
|
||||||
if (!file) {
|
|
||||||
E2D_LOG_ERROR("读取 DDS DX10 文件头失败: {}", filepath);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// DXGI_FORMAT 映射到 GL 格式
|
|
||||||
switch (dx10Header.dxgiFormat) {
|
|
||||||
case 147: // DXGI_FORMAT_ETC2_RGB8
|
|
||||||
glInternalFormat = GL_COMPRESSED_RGB8_ETC2;
|
|
||||||
format_ = PixelFormat::ETC2_RGB8;
|
|
||||||
channels_ = 3;
|
|
||||||
break;
|
|
||||||
case 148: // DXGI_FORMAT_ETC2_RGBA8
|
|
||||||
glInternalFormat = GL_COMPRESSED_RGBA8_ETC2_EAC;
|
|
||||||
format_ = PixelFormat::ETC2_RGBA8;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
E2D_LOG_ERROR("不支持的 DDS DX10 格式: {}", dx10Header.dxgiFormat);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
E2D_LOG_ERROR("DDS 文件未使用 DX10 扩展,不支持: {}", filepath);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算压缩数据大小
|
|
||||||
size_t blockSize = (glInternalFormat == GL_COMPRESSED_RGB8_ETC2) ? 8 : 16;
|
|
||||||
size_t blocksWide = (width_ + 3) / 4;
|
|
||||||
size_t blocksHigh = (height_ + 3) / 4;
|
|
||||||
size_t imageSize = blocksWide * blocksHigh * blockSize;
|
|
||||||
|
|
||||||
std::vector<uint8_t> compressedData(imageSize);
|
|
||||||
file.read(reinterpret_cast<char *>(compressedData.data()), imageSize);
|
|
||||||
if (!file) {
|
|
||||||
E2D_LOG_ERROR("读取 DDS 图像数据失败: {}", filepath);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建 GL 纹理
|
|
||||||
glGenTextures(1, &textureID_);
|
|
||||||
bind();
|
|
||||||
|
|
||||||
glCompressedTexImage2D(GL_TEXTURE_2D, 0, glInternalFormat, width_, height_, 0,
|
|
||||||
static_cast<GLsizei>(imageSize),
|
|
||||||
compressedData.data());
|
|
||||||
|
|
||||||
GLenum err = glGetError();
|
|
||||||
if (err != GL_NO_ERROR) {
|
|
||||||
E2D_LOG_ERROR("DDS 纹理上传失败: {:#06x}", err);
|
|
||||||
glDeleteTextures(1, &textureID_);
|
|
||||||
textureID_ = 0;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
||||||
|
|
||||||
// VRAM 跟踪
|
|
||||||
dataSize_ = imageSize;
|
|
||||||
VRAMMgr::get().allocTexture(dataSize_);
|
|
||||||
|
|
||||||
E2D_LOG_INFO("已加载 DDS 压缩纹理: {} ({}x{})", filepath, width_, height_);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GLTexture::generateAlphaMask() {
|
|
||||||
if (pixelData_.empty() || width_ <= 0 || height_ <= 0) {
|
|
||||||
E2D_LOG_WARN("无法生成透明遮罩: 没有可用的像素数据");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
alphaMask_ = std::make_unique<AlphaMask>(AlphaMask::createFromPixels(
|
|
||||||
pixelData_.data(), width_, height_, channels_));
|
|
||||||
|
|
||||||
E2D_LOG_DEBUG("已为纹理生成透明遮罩: {}x{}", width_, height_);
|
|
||||||
}
|
|
||||||
|
|
||||||
PixelFormat GLTexture::getFormat() const { return format_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 静态工厂方法,创建指定格式的空纹理
|
|
||||||
* @param width 纹理宽度
|
|
||||||
* @param height 纹理高度
|
|
||||||
* @param format 像素格式
|
|
||||||
* @return 创建的纹理智能指针
|
|
||||||
*/
|
|
||||||
Ptr<Texture> GLTexture::create(int width, int height, PixelFormat format) {
|
|
||||||
int channels = 4;
|
|
||||||
switch (format) {
|
|
||||||
case PixelFormat::R8:
|
|
||||||
channels = 1;
|
|
||||||
break;
|
|
||||||
case PixelFormat::RG8:
|
|
||||||
channels = 2;
|
|
||||||
break;
|
|
||||||
case PixelFormat::RGB8:
|
|
||||||
channels = 3;
|
|
||||||
break;
|
|
||||||
case PixelFormat::RGBA8:
|
|
||||||
channels = 4;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
channels = 4;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return makePtr<GLTexture>(width, height, nullptr, channels);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,293 +0,0 @@
|
||||||
#include <chrono>
|
|
||||||
#include <extra2d/core/service_locator.h>
|
|
||||||
#include <extra2d/graphics/shader/shader_cache.h>
|
|
||||||
#include <extra2d/services/logger_service.h>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <fstream>
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取单例实例
|
|
||||||
* @return 缓存管理器实例引用
|
|
||||||
*/
|
|
||||||
ShaderCache &ShaderCache::getInstance() {
|
|
||||||
static ShaderCache instance;
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化缓存系统
|
|
||||||
* @param cacheDir 缓存目录路径
|
|
||||||
* @return 初始化成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool ShaderCache::init(const std::string &cacheDir) {
|
|
||||||
cacheDir_ = cacheDir;
|
|
||||||
|
|
||||||
if (!ensureCacheDirectory()) {
|
|
||||||
E2D_LOG_ERROR("创建缓存目录失败: {}", cacheDir);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!loadCacheIndex()) {
|
|
||||||
E2D_LOG_WARN("加载缓存索引失败,重新开始");
|
|
||||||
}
|
|
||||||
|
|
||||||
initialized_ = true;
|
|
||||||
E2D_LOG_INFO("着色器缓存已初始化,位置: {}", cacheDir);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 关闭缓存系统
|
|
||||||
*/
|
|
||||||
void ShaderCache::shutdown() {
|
|
||||||
if (!initialized_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
saveCacheIndex();
|
|
||||||
cacheMap_.clear();
|
|
||||||
initialized_ = false;
|
|
||||||
E2D_LOG_INFO("着色器缓存已关闭");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查缓存是否有效
|
|
||||||
* @param name Shader名称
|
|
||||||
* @param sourceHash 源码哈希值
|
|
||||||
* @return 缓存有效返回true,否则返回false
|
|
||||||
*/
|
|
||||||
bool ShaderCache::hasValidCache(const std::string &name,
|
|
||||||
const std::string &sourceHash) {
|
|
||||||
auto it = cacheMap_.find(name);
|
|
||||||
if (it == cacheMap_.end()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return it->second.sourceHash == sourceHash;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 加载缓存的二进制数据
|
|
||||||
* @param name Shader名称
|
|
||||||
* @return 缓存条目指针,不存在返回nullptr
|
|
||||||
*/
|
|
||||||
Ptr<ShaderCacheEntry> ShaderCache::loadCache(const std::string &name) {
|
|
||||||
auto it = cacheMap_.find(name);
|
|
||||||
if (it == cacheMap_.end()) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string cachePath = getCachePath(name);
|
|
||||||
std::ifstream file(cachePath, std::ios::binary);
|
|
||||||
if (!file.is_open()) {
|
|
||||||
E2D_LOG_WARN("打开缓存文件失败: {}", cachePath);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto entry = std::make_shared<ShaderCacheEntry>(it->second);
|
|
||||||
entry->binary.clear();
|
|
||||||
|
|
||||||
file.seekg(0, std::ios::end);
|
|
||||||
size_t fileSize = static_cast<size_t>(file.tellg());
|
|
||||||
file.seekg(0, std::ios::beg);
|
|
||||||
|
|
||||||
entry->binary.resize(fileSize);
|
|
||||||
file.read(reinterpret_cast<char *>(entry->binary.data()), fileSize);
|
|
||||||
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 保存编译结果到缓存
|
|
||||||
* @param entry 缓存条目
|
|
||||||
* @return 保存成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool ShaderCache::saveCache(const ShaderCacheEntry &entry) {
|
|
||||||
if (!initialized_) {
|
|
||||||
E2D_LOG_WARN("着色器缓存未初始化,无法保存缓存");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry.binary.empty()) {
|
|
||||||
E2D_LOG_WARN("着色器二进制数据为空,跳过缓存保存: {}",
|
|
||||||
entry.name);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string cachePath = getCachePath(entry.name);
|
|
||||||
E2D_LOG_DEBUG("正在保存着色器缓存到: {} ({} 字节)", cachePath,
|
|
||||||
entry.binary.size());
|
|
||||||
|
|
||||||
std::ofstream file(cachePath, std::ios::binary);
|
|
||||||
if (!file.is_open()) {
|
|
||||||
E2D_LOG_ERROR("创建缓存文件失败: {}", cachePath);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
file.write(reinterpret_cast<const char *>(entry.binary.data()),
|
|
||||||
entry.binary.size());
|
|
||||||
file.close();
|
|
||||||
|
|
||||||
cacheMap_[entry.name] = entry;
|
|
||||||
saveCacheIndex();
|
|
||||||
|
|
||||||
E2D_LOG_INFO("着色器缓存已保存: {} ({} 字节)", entry.name,
|
|
||||||
entry.binary.size());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 使缓存失效
|
|
||||||
* @param name Shader名称
|
|
||||||
*/
|
|
||||||
void ShaderCache::invalidate(const std::string &name) {
|
|
||||||
auto it = cacheMap_.find(name);
|
|
||||||
if (it == cacheMap_.end()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string cachePath = getCachePath(name);
|
|
||||||
fs::remove(cachePath);
|
|
||||||
|
|
||||||
cacheMap_.erase(it);
|
|
||||||
saveCacheIndex();
|
|
||||||
|
|
||||||
E2D_LOG_DEBUG("着色器缓存已失效: {}", name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 清除所有缓存
|
|
||||||
*/
|
|
||||||
void ShaderCache::clearAll() {
|
|
||||||
for (const auto &pair : cacheMap_) {
|
|
||||||
std::string cachePath = getCachePath(pair.first);
|
|
||||||
fs::remove(cachePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
cacheMap_.clear();
|
|
||||||
saveCacheIndex();
|
|
||||||
|
|
||||||
E2D_LOG_INFO("所有着色器缓存已清除");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 计算源码哈希值
|
|
||||||
* @param vertSource 顶点着色器源码
|
|
||||||
* @param fragSource 片段着色器源码
|
|
||||||
* @return 哈希值字符串
|
|
||||||
*/
|
|
||||||
std::string ShaderCache::computeHash(const std::string &vertSource,
|
|
||||||
const std::string &fragSource) {
|
|
||||||
std::string combined = vertSource + fragSource;
|
|
||||||
|
|
||||||
uint32_t hash = 5381;
|
|
||||||
for (char c : combined) {
|
|
||||||
hash = ((hash << 5) + hash) + static_cast<uint32_t>(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << std::hex << hash;
|
|
||||||
return ss.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 加载缓存索引
|
|
||||||
* @return 加载成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool ShaderCache::loadCacheIndex() {
|
|
||||||
std::string indexPath = cacheDir_ + "/.cache_index";
|
|
||||||
|
|
||||||
if (!fs::exists(indexPath)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ifstream file(indexPath);
|
|
||||||
if (!file.is_open()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string line;
|
|
||||||
while (std::getline(file, line)) {
|
|
||||||
if (line.empty() || line[0] == '#') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t pos = line.find('=');
|
|
||||||
if (pos == std::string::npos) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string name = line.substr(0, pos);
|
|
||||||
std::string hash = line.substr(pos + 1);
|
|
||||||
|
|
||||||
std::string cachePath = getCachePath(name);
|
|
||||||
if (fs::exists(cachePath)) {
|
|
||||||
ShaderCacheEntry entry;
|
|
||||||
entry.name = name;
|
|
||||||
entry.sourceHash = hash;
|
|
||||||
entry.compileTime = static_cast<uint64_t>(
|
|
||||||
std::chrono::system_clock::now().time_since_epoch().count());
|
|
||||||
cacheMap_[name] = entry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 保存缓存索引
|
|
||||||
* @return 保存成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool ShaderCache::saveCacheIndex() {
|
|
||||||
std::string indexPath = cacheDir_ + "/.cache_index";
|
|
||||||
|
|
||||||
std::ofstream file(indexPath);
|
|
||||||
if (!file.is_open()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
file << "# Extra2D Shader Cache Index\n";
|
|
||||||
file << "# Format: name=hash\n";
|
|
||||||
|
|
||||||
for (const auto &pair : cacheMap_) {
|
|
||||||
file << pair.first << "=" << pair.second.sourceHash << "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取缓存文件路径
|
|
||||||
* @param name Shader名称
|
|
||||||
* @return 缓存文件完整路径
|
|
||||||
*/
|
|
||||||
std::string ShaderCache::getCachePath(const std::string &name) const {
|
|
||||||
return cacheDir_ + "/" + name + ".cache";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 确保缓存目录存在
|
|
||||||
* @return 目录存在或创建成功返回true,否则返回false
|
|
||||||
*/
|
|
||||||
bool ShaderCache::ensureCacheDirectory() {
|
|
||||||
if (cacheDir_.empty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::error_code ec;
|
|
||||||
if (!fs::exists(cacheDir_)) {
|
|
||||||
if (!fs::create_directories(cacheDir_, ec)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,167 +0,0 @@
|
||||||
#include <chrono>
|
|
||||||
#include <extra2d/core/service_locator.h>
|
|
||||||
#include <extra2d/graphics/shader/shader_hot_reloader.h>
|
|
||||||
#include <extra2d/services/logger_service.h>
|
|
||||||
#include <filesystem>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取单例实例
|
|
||||||
* @return 热重载管理器实例引用
|
|
||||||
*/
|
|
||||||
ShaderHotReloader &ShaderHotReloader::getInstance() {
|
|
||||||
static ShaderHotReloader instance;
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化热重载系统
|
|
||||||
* @return 初始化成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool ShaderHotReloader::init() {
|
|
||||||
if (initialized_) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
buffer_.resize(4096);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
initialized_ = true;
|
|
||||||
E2D_LOG_INFO("着色器热重载器已初始化");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 关闭热重载系统
|
|
||||||
*/
|
|
||||||
void ShaderHotReloader::shutdown() {
|
|
||||||
if (!initialized_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
if (watchHandle_ != nullptr) {
|
|
||||||
FindCloseChangeNotification(watchHandle_);
|
|
||||||
watchHandle_ = nullptr;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
watchMap_.clear();
|
|
||||||
initialized_ = false;
|
|
||||||
enabled_ = false;
|
|
||||||
E2D_LOG_INFO("着色器热重载器已关闭");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 注册Shader文件监视
|
|
||||||
* @param shaderName Shader名称
|
|
||||||
* @param filePaths 要监视的文件列表
|
|
||||||
* @param callback 文件变化时的回调
|
|
||||||
*/
|
|
||||||
void ShaderHotReloader::watch(const std::string &shaderName,
|
|
||||||
const std::vector<std::string> &filePaths,
|
|
||||||
FileChangeCallback callback) {
|
|
||||||
if (!initialized_) {
|
|
||||||
E2D_LOG_WARN("热重载器未初始化");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
WatchInfo info;
|
|
||||||
info.filePaths = filePaths;
|
|
||||||
info.callback = callback;
|
|
||||||
|
|
||||||
for (const auto &path : filePaths) {
|
|
||||||
info.modifiedTimes[path] = getFileModifiedTime(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
watchMap_[shaderName] = std::move(info);
|
|
||||||
E2D_LOG_DEBUG("正在监视着色器: {} ({} 个文件)", shaderName, filePaths.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 取消监视
|
|
||||||
* @param shaderName Shader名称
|
|
||||||
*/
|
|
||||||
void ShaderHotReloader::unwatch(const std::string &shaderName) {
|
|
||||||
auto it = watchMap_.find(shaderName);
|
|
||||||
if (it != watchMap_.end()) {
|
|
||||||
watchMap_.erase(it);
|
|
||||||
E2D_LOG_DEBUG("停止监视着色器: {}", shaderName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 更新文件监视(在主循环中调用)
|
|
||||||
*/
|
|
||||||
void ShaderHotReloader::update() {
|
|
||||||
if (!initialized_ || !enabled_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
pollChanges();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 启用/禁用热重载
|
|
||||||
* @param enabled 是否启用
|
|
||||||
*/
|
|
||||||
void ShaderHotReloader::setEnabled(bool enabled) {
|
|
||||||
enabled_ = enabled;
|
|
||||||
E2D_LOG_DEBUG("热重载已{}", enabled ? "Enable" : "Disable");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 轮询检查文件变化
|
|
||||||
*/
|
|
||||||
void ShaderHotReloader::pollChanges() {
|
|
||||||
auto now = static_cast<uint64_t>(
|
|
||||||
std::chrono::system_clock::now().time_since_epoch().count());
|
|
||||||
|
|
||||||
for (auto &pair : watchMap_) {
|
|
||||||
WatchInfo &info = pair.second;
|
|
||||||
|
|
||||||
for (const auto &filePath : info.filePaths) {
|
|
||||||
uint64_t currentModTime = getFileModifiedTime(filePath);
|
|
||||||
uint64_t lastModTime = info.modifiedTimes[filePath];
|
|
||||||
|
|
||||||
if (currentModTime != 0 && lastModTime != 0 &&
|
|
||||||
currentModTime != lastModTime) {
|
|
||||||
info.modifiedTimes[filePath] = currentModTime;
|
|
||||||
|
|
||||||
FileChangeEvent event;
|
|
||||||
event.filepath = filePath;
|
|
||||||
event.type = FileChangeEvent::Type::Modified;
|
|
||||||
event.timestamp = now;
|
|
||||||
|
|
||||||
E2D_LOG_DEBUG("着色器文件已更改: {}", filePath);
|
|
||||||
|
|
||||||
if (info.callback) {
|
|
||||||
info.callback(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取文件修改时间
|
|
||||||
* @param filepath 文件路径
|
|
||||||
* @return 修改时间戳
|
|
||||||
*/
|
|
||||||
uint64_t ShaderHotReloader::getFileModifiedTime(const std::string &filepath) {
|
|
||||||
try {
|
|
||||||
auto ftime = fs::last_write_time(filepath);
|
|
||||||
auto sctp = std::chrono::time_point_cast<std::chrono::seconds>(
|
|
||||||
ftime - fs::file_time_type::clock::now() +
|
|
||||||
std::chrono::system_clock::now());
|
|
||||||
return static_cast<uint64_t>(sctp.time_since_epoch().count());
|
|
||||||
} catch (...) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,452 +0,0 @@
|
||||||
#include <algorithm>
|
|
||||||
#include <extra2d/core/service_locator.h>
|
|
||||||
#include <extra2d/graphics/shader/shader_loader.h>
|
|
||||||
#include <extra2d/services/logger_service.h>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <fstream>
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 构造函数,初始化Shader加载器
|
|
||||||
*/
|
|
||||||
ShaderLoader::ShaderLoader() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从分离文件加载Shader (.vert + .frag)
|
|
||||||
* @param name Shader名称
|
|
||||||
* @param vertPath 顶点着色器文件路径
|
|
||||||
* @param fragPath 片段着色器文件路径
|
|
||||||
* @return 加载结果
|
|
||||||
*/
|
|
||||||
ShaderLoadResult
|
|
||||||
ShaderLoader::loadFromSeparateFiles(const std::string &name,
|
|
||||||
const std::string &vertPath,
|
|
||||||
const std::string &fragPath) {
|
|
||||||
|
|
||||||
ShaderLoadResult result;
|
|
||||||
|
|
||||||
if (!fileExists(vertPath)) {
|
|
||||||
result.errorMessage = "Vertex shader file not found: " + vertPath;
|
|
||||||
E2D_LOG_ERROR("{}", result.errorMessage);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fileExists(fragPath)) {
|
|
||||||
result.errorMessage = "Fragment shader file not found: " + fragPath;
|
|
||||||
E2D_LOG_ERROR("{}", result.errorMessage);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string vertSource = readFile(vertPath);
|
|
||||||
std::string fragSource = readFile(fragPath);
|
|
||||||
|
|
||||||
if (vertSource.empty()) {
|
|
||||||
result.errorMessage = "Failed to read vertex shader file: " + vertPath;
|
|
||||||
E2D_LOG_ERROR("{}", result.errorMessage);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fragSource.empty()) {
|
|
||||||
result.errorMessage = "Failed to read fragment shader file: " + fragPath;
|
|
||||||
E2D_LOG_ERROR("{}", result.errorMessage);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
fs::path vertDir = fs::path(vertPath).parent_path();
|
|
||||||
fs::path fragDir = fs::path(fragPath).parent_path();
|
|
||||||
|
|
||||||
vertSource =
|
|
||||||
processIncludes(vertSource, vertDir.string(), result.dependencies);
|
|
||||||
fragSource =
|
|
||||||
processIncludes(fragSource, fragDir.string(), result.dependencies);
|
|
||||||
|
|
||||||
result.vertSource = vertSource;
|
|
||||||
result.fragSource = fragSource;
|
|
||||||
result.success = true;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从组合文件加载Shader (.shader)
|
|
||||||
* @param path 组合Shader文件路径
|
|
||||||
* @return 加载结果
|
|
||||||
*/
|
|
||||||
ShaderLoadResult ShaderLoader::loadFromCombinedFile(const std::string &path) {
|
|
||||||
ShaderLoadResult result;
|
|
||||||
|
|
||||||
if (!fileExists(path)) {
|
|
||||||
result.errorMessage = "Shader file not found: " + path;
|
|
||||||
E2D_LOG_ERROR("{}", result.errorMessage);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string content = readFile(path);
|
|
||||||
if (content.empty()) {
|
|
||||||
result.errorMessage = "Failed to read shader file: " + path;
|
|
||||||
E2D_LOG_ERROR("{}", result.errorMessage);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
ShaderMetadata metadata;
|
|
||||||
std::string vertSource, fragSource;
|
|
||||||
|
|
||||||
if (!parseCombinedFile(content, vertSource, fragSource, metadata)) {
|
|
||||||
result.errorMessage = "Failed to parse combined shader file: " + path;
|
|
||||||
E2D_LOG_ERROR("{}", result.errorMessage);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
fs::path baseDir = fs::path(path).parent_path();
|
|
||||||
vertSource =
|
|
||||||
processIncludes(vertSource, baseDir.string(), result.dependencies);
|
|
||||||
fragSource =
|
|
||||||
processIncludes(fragSource, baseDir.string(), result.dependencies);
|
|
||||||
|
|
||||||
result.vertSource = vertSource;
|
|
||||||
result.fragSource = fragSource;
|
|
||||||
result.success = true;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从源码字符串加载Shader
|
|
||||||
* @param vertSource 顶点着色器源码
|
|
||||||
* @param fragSource 片段着色器源码
|
|
||||||
* @return 加载结果
|
|
||||||
*/
|
|
||||||
ShaderLoadResult ShaderLoader::loadFromSource(const std::string &vertSource,
|
|
||||||
const std::string &fragSource) {
|
|
||||||
|
|
||||||
ShaderLoadResult result;
|
|
||||||
result.vertSource = vertSource;
|
|
||||||
result.fragSource = fragSource;
|
|
||||||
result.success = true;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 处理Shader源码中的#include指令
|
|
||||||
* @param source 原始源码
|
|
||||||
* @param baseDir 基础目录
|
|
||||||
* @param outDependencies 输出依赖列表
|
|
||||||
* @return 处理后的源码
|
|
||||||
*/
|
|
||||||
std::string
|
|
||||||
ShaderLoader::processIncludes(const std::string &source,
|
|
||||||
const std::string &baseDir,
|
|
||||||
std::vector<std::string> &outDependencies) {
|
|
||||||
|
|
||||||
std::string result;
|
|
||||||
std::istringstream stream(source);
|
|
||||||
std::string line;
|
|
||||||
|
|
||||||
while (std::getline(stream, line)) {
|
|
||||||
size_t includePos = line.find("#include");
|
|
||||||
if (includePos != std::string::npos) {
|
|
||||||
size_t startQuote = line.find('"', includePos);
|
|
||||||
size_t endQuote = line.find('"', startQuote + 1);
|
|
||||||
|
|
||||||
if (startQuote != std::string::npos && endQuote != std::string::npos) {
|
|
||||||
std::string includeName =
|
|
||||||
line.substr(startQuote + 1, endQuote - startQuote - 1);
|
|
||||||
std::string includePath = findIncludeFile(includeName, baseDir);
|
|
||||||
|
|
||||||
if (!includePath.empty()) {
|
|
||||||
auto cacheIt = includeCache_.find(includePath);
|
|
||||||
std::string includeContent;
|
|
||||||
|
|
||||||
if (cacheIt != includeCache_.end()) {
|
|
||||||
includeContent = cacheIt->second;
|
|
||||||
} else {
|
|
||||||
includeContent = readFile(includePath);
|
|
||||||
includeCache_[includePath] = includeContent;
|
|
||||||
}
|
|
||||||
|
|
||||||
outDependencies.push_back(includePath);
|
|
||||||
result += includeContent;
|
|
||||||
result += "\n";
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
E2D_LOG_WARN("未找到包含文件: {}", includeName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result += line;
|
|
||||||
result += "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 应用预处理器定义
|
|
||||||
* @param source 原始源码
|
|
||||||
* @param defines 预处理器定义列表
|
|
||||||
* @return 处理后的源码
|
|
||||||
*/
|
|
||||||
std::string
|
|
||||||
ShaderLoader::applyDefines(const std::string &source,
|
|
||||||
const std::vector<std::string> &defines) {
|
|
||||||
|
|
||||||
if (defines.empty()) {
|
|
||||||
return source;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string defineBlock;
|
|
||||||
for (const auto &def : defines) {
|
|
||||||
defineBlock += "#define " + def + "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string result;
|
|
||||||
std::istringstream stream(source);
|
|
||||||
std::string line;
|
|
||||||
bool inserted = false;
|
|
||||||
|
|
||||||
while (std::getline(stream, line)) {
|
|
||||||
if (!inserted && (line.find("#version") != std::string::npos ||
|
|
||||||
line.find("precision") != std::string::npos)) {
|
|
||||||
result += line + "\n";
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!inserted) {
|
|
||||||
result += defineBlock;
|
|
||||||
inserted = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
result += line + "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取Shader元数据
|
|
||||||
* @param path Shader文件路径
|
|
||||||
* @return 元数据
|
|
||||||
*/
|
|
||||||
ShaderMetadata ShaderLoader::getMetadata(const std::string &path) {
|
|
||||||
ShaderMetadata metadata;
|
|
||||||
|
|
||||||
if (!fileExists(path)) {
|
|
||||||
return metadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
metadata.combinedPath = path;
|
|
||||||
metadata.lastModified = getFileModifiedTime(path);
|
|
||||||
|
|
||||||
fs::path p(path);
|
|
||||||
metadata.name = p.stem().string();
|
|
||||||
|
|
||||||
return metadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 添加include搜索路径
|
|
||||||
* @param path 搜索路径
|
|
||||||
*/
|
|
||||||
void ShaderLoader::addIncludePath(const std::string &path) {
|
|
||||||
if (std::find(includePaths_.begin(), includePaths_.end(), path) ==
|
|
||||||
includePaths_.end()) {
|
|
||||||
includePaths_.push_back(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 读取文件内容
|
|
||||||
* @param filepath 文件路径
|
|
||||||
* @return 文件内容字符串
|
|
||||||
*/
|
|
||||||
std::string ShaderLoader::readFile(const std::string &filepath) {
|
|
||||||
std::ifstream file(filepath, std::ios::binary);
|
|
||||||
if (!file.is_open()) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ostringstream content;
|
|
||||||
content << file.rdbuf();
|
|
||||||
return content.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取文件修改时间
|
|
||||||
* @param filepath 文件路径
|
|
||||||
* @return 修改时间戳
|
|
||||||
*/
|
|
||||||
uint64_t ShaderLoader::getFileModifiedTime(const std::string &filepath) {
|
|
||||||
#ifdef __SWITCH__
|
|
||||||
(void)filepath;
|
|
||||||
return 1;
|
|
||||||
#else
|
|
||||||
try {
|
|
||||||
auto ftime = fs::last_write_time(filepath);
|
|
||||||
auto sctp = std::chrono::time_point_cast<std::chrono::seconds>(
|
|
||||||
ftime - fs::file_time_type::clock::now() +
|
|
||||||
std::chrono::system_clock::now());
|
|
||||||
return static_cast<uint64_t>(sctp.time_since_epoch().count());
|
|
||||||
} catch (...) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查文件是否存在
|
|
||||||
* @param filepath 文件路径
|
|
||||||
* @return 存在返回true,否则返回false
|
|
||||||
*/
|
|
||||||
bool ShaderLoader::fileExists(const std::string &filepath) {
|
|
||||||
return fs::exists(filepath);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 解析组合Shader文件
|
|
||||||
* @param content 文件内容
|
|
||||||
* @param outVert 输出顶点着色器源码
|
|
||||||
* @param outFrag 输出片段着色器源码
|
|
||||||
* @param outMetadata 输出元数据
|
|
||||||
* @return 解析成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool ShaderLoader::parseCombinedFile(const std::string &content,
|
|
||||||
std::string &outVert, std::string &outFrag,
|
|
||||||
ShaderMetadata &outMetadata) {
|
|
||||||
enum class Section { None, Meta, Vertex, Fragment };
|
|
||||||
|
|
||||||
Section currentSection = Section::None;
|
|
||||||
std::string metaContent;
|
|
||||||
std::string vertContent;
|
|
||||||
std::string fragContent;
|
|
||||||
|
|
||||||
std::istringstream stream(content);
|
|
||||||
std::string line;
|
|
||||||
|
|
||||||
while (std::getline(stream, line)) {
|
|
||||||
std::string trimmedLine = line;
|
|
||||||
size_t start = trimmedLine.find_first_not_of(" \t\r\n");
|
|
||||||
if (start != std::string::npos) {
|
|
||||||
trimmedLine = trimmedLine.substr(start);
|
|
||||||
}
|
|
||||||
size_t end = trimmedLine.find_last_not_of(" \t\r\n");
|
|
||||||
if (end != std::string::npos) {
|
|
||||||
trimmedLine = trimmedLine.substr(0, end + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trimmedLine == "#meta") {
|
|
||||||
currentSection = Section::Meta;
|
|
||||||
continue;
|
|
||||||
} else if (trimmedLine == "#vertex") {
|
|
||||||
currentSection = Section::Vertex;
|
|
||||||
continue;
|
|
||||||
} else if (trimmedLine == "#fragment") {
|
|
||||||
currentSection = Section::Fragment;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (currentSection) {
|
|
||||||
case Section::Meta:
|
|
||||||
metaContent += line + "\n";
|
|
||||||
break;
|
|
||||||
case Section::Vertex:
|
|
||||||
vertContent += line + "\n";
|
|
||||||
break;
|
|
||||||
case Section::Fragment:
|
|
||||||
fragContent += line + "\n";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vertContent.empty() || fragContent.empty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!metaContent.empty()) {
|
|
||||||
parseMetadata(metaContent, outMetadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
outVert = vertContent;
|
|
||||||
outFrag = fragContent;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 解析元数据JSON块
|
|
||||||
* @param jsonContent JSON内容
|
|
||||||
* @param outMetadata 输出元数据
|
|
||||||
* @return 解析成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool ShaderLoader::parseMetadata(const std::string &jsonContent,
|
|
||||||
ShaderMetadata &outMetadata) {
|
|
||||||
std::string content = jsonContent;
|
|
||||||
|
|
||||||
size_t start = content.find('{');
|
|
||||||
size_t end = content.rfind('}');
|
|
||||||
if (start == std::string::npos || end == std::string::npos || end <= start) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
content = content.substr(start, end - start + 1);
|
|
||||||
|
|
||||||
auto extractString = [&content](const std::string &key) -> std::string {
|
|
||||||
std::string searchKey = "\"" + key + "\"";
|
|
||||||
size_t keyPos = content.find(searchKey);
|
|
||||||
if (keyPos == std::string::npos) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t colonPos = content.find(':', keyPos);
|
|
||||||
if (colonPos == std::string::npos) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t quoteStart = content.find('"', colonPos);
|
|
||||||
if (quoteStart == std::string::npos) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t quoteEnd = content.find('"', quoteStart + 1);
|
|
||||||
if (quoteEnd == std::string::npos) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return content.substr(quoteStart + 1, quoteEnd - quoteStart - 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
outMetadata.name = extractString("name");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 查找include文件路径
|
|
||||||
* @param includeName include文件名
|
|
||||||
* @param baseDir 基础目录
|
|
||||||
* @return 找到的完整路径,未找到返回空字符串
|
|
||||||
*/
|
|
||||||
std::string ShaderLoader::findIncludeFile(const std::string &includeName,
|
|
||||||
const std::string &baseDir) {
|
|
||||||
fs::path basePath(baseDir);
|
|
||||||
fs::path includePath = basePath / includeName;
|
|
||||||
|
|
||||||
if (fs::exists(includePath)) {
|
|
||||||
return includePath.string();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto &searchPath : includePaths_) {
|
|
||||||
includePath = fs::path(searchPath) / includeName;
|
|
||||||
if (fs::exists(includePath)) {
|
|
||||||
return includePath.string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,855 +0,0 @@
|
||||||
#include <extra2d/core/service_locator.h>
|
|
||||||
#include <extra2d/graphics/shader/shader_manager.h>
|
|
||||||
#include <extra2d/services/logger_service.h>
|
|
||||||
|
|
||||||
#include <filesystem>
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
|
|
||||||
namespace nl = nlohmann;
|
|
||||||
namespace fs = std::filesystem;
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取单例实例
|
|
||||||
* @return Shader管理器实例引用
|
|
||||||
*/
|
|
||||||
ShaderManager &ShaderManager::getInstance() {
|
|
||||||
static ShaderManager instance;
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 使用平台默认路径初始化Shader系统
|
|
||||||
* 自动检测平台并使用正确的路径(romfs/sdmc/相对路径)
|
|
||||||
* @param factory 渲染后端Shader工厂
|
|
||||||
* @param appName 应用名称(用于缓存目录)
|
|
||||||
* @return 初始化成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool ShaderManager::init(Ptr<IShaderFactory> factory,
|
|
||||||
const std::string &appName) {
|
|
||||||
// 使用相对路径作为Shader目录
|
|
||||||
fs::path shaderDir = "shaders";
|
|
||||||
fs::path cacheDir = "cache/shaders";
|
|
||||||
|
|
||||||
// 非Switch平台支持热重载
|
|
||||||
#ifndef __SWITCH__
|
|
||||||
hotReloadSupported_ = true;
|
|
||||||
#else
|
|
||||||
hotReloadSupported_ = false;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
E2D_LOG_INFO("ShaderManager 初始化 (热重载: {})",
|
|
||||||
hotReloadSupported_ ? "supported" : "not supported");
|
|
||||||
|
|
||||||
return init(shaderDir.string(), cacheDir.string(), factory);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化Shader系统
|
|
||||||
* @param shaderDir Shader文件目录
|
|
||||||
* @param cacheDir 缓存目录
|
|
||||||
* @param factory 渲染后端Shader工厂
|
|
||||||
* @return 初始化成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool ShaderManager::init(const std::string &shaderDir,
|
|
||||||
const std::string &cacheDir,
|
|
||||||
Ptr<IShaderFactory> factory) {
|
|
||||||
if (initialized_) {
|
|
||||||
E2D_LOG_WARN("ShaderManager 已初始化");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!factory) {
|
|
||||||
E2D_LOG_ERROR("Shader 工厂为空");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
shaderDir_ = shaderDir;
|
|
||||||
cacheDir_ = cacheDir;
|
|
||||||
factory_ = factory;
|
|
||||||
|
|
||||||
// 非Switch平台支持热重载
|
|
||||||
#ifndef __SWITCH__
|
|
||||||
hotReloadSupported_ = true;
|
|
||||||
#else
|
|
||||||
hotReloadSupported_ = false;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __SWITCH__
|
|
||||||
if (!ShaderCache::getInstance().init(cacheDir_)) {
|
|
||||||
E2D_LOG_WARN("Switch 平台初始化着色器缓存失败");
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
if (!ShaderCache::getInstance().init(cacheDir_)) {
|
|
||||||
E2D_LOG_WARN("初始化着色器缓存失败,已禁用缓存");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (hotReloadSupported_) {
|
|
||||||
if (!ShaderHotReloader::getInstance().init()) {
|
|
||||||
E2D_LOG_WARN("初始化热重载器失败");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loader_.addIncludePath(shaderDir_ + "common");
|
|
||||||
|
|
||||||
initialized_ = true;
|
|
||||||
E2D_LOG_INFO("ShaderManager 初始化成功");
|
|
||||||
E2D_LOG_INFO(" 着色器目录: {}", shaderDir_);
|
|
||||||
E2D_LOG_INFO(" 缓存目录: {}", cacheDir_);
|
|
||||||
E2D_LOG_INFO(" 热重载: {}", hotReloadSupported_ ? "支持" : "不支持");
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 关闭Shader系统
|
|
||||||
*/
|
|
||||||
void ShaderManager::shutdown() {
|
|
||||||
if (!initialized_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hotReloadSupported_) {
|
|
||||||
ShaderHotReloader::getInstance().shutdown();
|
|
||||||
}
|
|
||||||
ShaderCache::getInstance().shutdown();
|
|
||||||
|
|
||||||
shaders_.clear();
|
|
||||||
factory_.reset();
|
|
||||||
initialized_ = false;
|
|
||||||
|
|
||||||
E2D_LOG_INFO("ShaderManager 已关闭");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从分离文件加载Shader
|
|
||||||
* @param name Shader名称
|
|
||||||
* @param vertPath 顶点着色器文件路径
|
|
||||||
* @param fragPath 片段着色器文件路径
|
|
||||||
* @return 加载的Shader实例
|
|
||||||
*/
|
|
||||||
Ptr<IShader> ShaderManager::loadFromFiles(const std::string &name,
|
|
||||||
const std::string &vertPath,
|
|
||||||
const std::string &fragPath) {
|
|
||||||
if (!initialized_) {
|
|
||||||
E2D_LOG_ERROR("ShaderManager 未初始化");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto it = shaders_.find(name);
|
|
||||||
if (it != shaders_.end()) {
|
|
||||||
return it->second.shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
ShaderLoadResult result =
|
|
||||||
loader_.loadFromSeparateFiles(name, vertPath, fragPath);
|
|
||||||
if (!result.success) {
|
|
||||||
E2D_LOG_ERROR("加载着色器文件失败: {} - {}", vertPath, fragPath);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string sourceHash =
|
|
||||||
ShaderCache::computeHash(result.vertSource, result.fragSource);
|
|
||||||
Ptr<IShader> shader =
|
|
||||||
loadFromCache(name, sourceHash, result.vertSource, result.fragSource);
|
|
||||||
|
|
||||||
if (!shader) {
|
|
||||||
E2D_LOG_DEBUG("未找到有效缓存,从源码编译着色器: {}", name);
|
|
||||||
shader =
|
|
||||||
factory_->createFromSource(name, result.vertSource, result.fragSource);
|
|
||||||
if (!shader) {
|
|
||||||
E2D_LOG_ERROR("从源码创建着色器失败: {}", name);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<uint8_t> binary;
|
|
||||||
if (factory_->getShaderBinary(*shader, binary)) {
|
|
||||||
E2D_LOG_DEBUG("获取到着色器二进制数据,大小: {} 字节", binary.size());
|
|
||||||
ShaderCacheEntry entry;
|
|
||||||
entry.name = name;
|
|
||||||
entry.sourceHash = sourceHash;
|
|
||||||
entry.binary = binary;
|
|
||||||
entry.dependencies = result.dependencies;
|
|
||||||
ShaderCache::getInstance().saveCache(entry);
|
|
||||||
} else {
|
|
||||||
E2D_LOG_WARN("获取着色器二进制数据失败: {}", name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ShaderInfo info;
|
|
||||||
info.shader = shader;
|
|
||||||
info.vertSource = result.vertSource;
|
|
||||||
info.fragSource = result.fragSource;
|
|
||||||
info.filePaths = {vertPath, fragPath};
|
|
||||||
info.filePaths.insert(info.filePaths.end(), result.dependencies.begin(),
|
|
||||||
result.dependencies.end());
|
|
||||||
|
|
||||||
info.metadata.name = name;
|
|
||||||
info.metadata.vertPath = vertPath;
|
|
||||||
info.metadata.fragPath = fragPath;
|
|
||||||
|
|
||||||
shaders_[name] = std::move(info);
|
|
||||||
|
|
||||||
if (hotReloadEnabled_ && hotReloadSupported_) {
|
|
||||||
auto callback = [this, name](const FileChangeEvent &event) {
|
|
||||||
this->handleFileChange(name, event);
|
|
||||||
};
|
|
||||||
ShaderHotReloader::getInstance().watch(name, shaders_[name].filePaths,
|
|
||||||
callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
E2D_LOG_DEBUG("着色器已加载: {}", name);
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从组合文件加载Shader
|
|
||||||
* @param path 组合Shader文件路径
|
|
||||||
* @return 加载的Shader实例
|
|
||||||
*/
|
|
||||||
Ptr<IShader> ShaderManager::loadFromCombinedFile(const std::string &path) {
|
|
||||||
if (!initialized_) {
|
|
||||||
E2D_LOG_ERROR("ShaderManager 未初始化");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
ShaderMetadata metadata = loader_.getMetadata(path);
|
|
||||||
std::string name = metadata.name.empty() ? path : metadata.name;
|
|
||||||
|
|
||||||
auto it = shaders_.find(name);
|
|
||||||
if (it != shaders_.end()) {
|
|
||||||
return it->second.shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
ShaderLoadResult result = loader_.loadFromCombinedFile(path);
|
|
||||||
if (!result.success) {
|
|
||||||
E2D_LOG_ERROR("加载组合着色器文件失败: {}", path);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string sourceHash =
|
|
||||||
ShaderCache::computeHash(result.vertSource, result.fragSource);
|
|
||||||
Ptr<IShader> shader =
|
|
||||||
loadFromCache(name, sourceHash, result.vertSource, result.fragSource);
|
|
||||||
|
|
||||||
if (!shader) {
|
|
||||||
E2D_LOG_DEBUG("No valid cache found, compiling shader from source: {}",
|
|
||||||
name);
|
|
||||||
shader =
|
|
||||||
factory_->createFromSource(name, result.vertSource, result.fragSource);
|
|
||||||
if (!shader) {
|
|
||||||
E2D_LOG_ERROR("从源码创建着色器失败: {}", name);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<uint8_t> binary;
|
|
||||||
if (factory_->getShaderBinary(*shader, binary)) {
|
|
||||||
E2D_LOG_DEBUG("获取到着色器二进制数据,大小: {} 字节", binary.size());
|
|
||||||
ShaderCacheEntry entry;
|
|
||||||
entry.name = name;
|
|
||||||
entry.sourceHash = sourceHash;
|
|
||||||
entry.binary = binary;
|
|
||||||
entry.dependencies = result.dependencies;
|
|
||||||
ShaderCache::getInstance().saveCache(entry);
|
|
||||||
} else {
|
|
||||||
E2D_LOG_WARN("获取着色器二进制数据失败: {}", name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ShaderInfo info;
|
|
||||||
info.shader = shader;
|
|
||||||
info.vertSource = result.vertSource;
|
|
||||||
info.fragSource = result.fragSource;
|
|
||||||
info.filePaths = {path};
|
|
||||||
info.filePaths.insert(info.filePaths.end(), result.dependencies.begin(),
|
|
||||||
result.dependencies.end());
|
|
||||||
info.metadata = metadata;
|
|
||||||
|
|
||||||
shaders_[name] = std::move(info);
|
|
||||||
|
|
||||||
if (hotReloadEnabled_ && hotReloadSupported_) {
|
|
||||||
auto callback = [this, name](const FileChangeEvent &event) {
|
|
||||||
this->handleFileChange(name, event);
|
|
||||||
};
|
|
||||||
ShaderHotReloader::getInstance().watch(name, shaders_[name].filePaths,
|
|
||||||
callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
E2D_LOG_DEBUG("从组合文件加载着色器成功: {}", name);
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从源码加载Shader
|
|
||||||
* @param name Shader名称
|
|
||||||
* @param vertSource 顶点着色器源码
|
|
||||||
* @param fragSource 片段着色器源码
|
|
||||||
* @return 加载的Shader实例
|
|
||||||
*/
|
|
||||||
Ptr<IShader> ShaderManager::loadFromSource(const std::string &name,
|
|
||||||
const std::string &vertSource,
|
|
||||||
const std::string &fragSource) {
|
|
||||||
if (!initialized_) {
|
|
||||||
E2D_LOG_ERROR("ShaderManager 未初始化");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto it = shaders_.find(name);
|
|
||||||
if (it != shaders_.end()) {
|
|
||||||
return it->second.shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<IShader> shader =
|
|
||||||
factory_->createFromSource(name, vertSource, fragSource);
|
|
||||||
if (!shader) {
|
|
||||||
E2D_LOG_ERROR("从源码创建着色器失败: {}", name);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
ShaderInfo info;
|
|
||||||
info.shader = shader;
|
|
||||||
info.vertSource = vertSource;
|
|
||||||
info.fragSource = fragSource;
|
|
||||||
info.metadata.name = name;
|
|
||||||
|
|
||||||
shaders_[name] = std::move(info);
|
|
||||||
|
|
||||||
E2D_LOG_DEBUG("从源码加载着色器成功: {}", name);
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取已加载的Shader
|
|
||||||
* @param name Shader名称
|
|
||||||
* @return Shader实例,不存在返回nullptr
|
|
||||||
*/
|
|
||||||
Ptr<IShader> ShaderManager::get(const std::string &name) const {
|
|
||||||
auto it = shaders_.find(name);
|
|
||||||
if (it != shaders_.end()) {
|
|
||||||
return it->second.shader;
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查Shader是否存在
|
|
||||||
* @param name Shader名称
|
|
||||||
* @return 存在返回true,否则返回false
|
|
||||||
*/
|
|
||||||
bool ShaderManager::has(const std::string &name) const {
|
|
||||||
return shaders_.find(name) != shaders_.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 移除Shader
|
|
||||||
* @param name Shader名称
|
|
||||||
*/
|
|
||||||
void ShaderManager::remove(const std::string &name) {
|
|
||||||
auto it = shaders_.find(name);
|
|
||||||
if (it != shaders_.end()) {
|
|
||||||
ShaderHotReloader::getInstance().unwatch(name);
|
|
||||||
shaders_.erase(it);
|
|
||||||
E2D_LOG_DEBUG("着色器已移除: {}", name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 清除所有Shader
|
|
||||||
*/
|
|
||||||
void ShaderManager::clear() {
|
|
||||||
if (hotReloadSupported_) {
|
|
||||||
for (const auto &pair : shaders_) {
|
|
||||||
ShaderHotReloader::getInstance().unwatch(pair.first);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
shaders_.clear();
|
|
||||||
E2D_LOG_DEBUG("所有着色器已清除");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 注册重载回调
|
|
||||||
* @param name Shader名称
|
|
||||||
* @param callback 重载回调函数
|
|
||||||
*/
|
|
||||||
void ShaderManager::setReloadCallback(const std::string &name,
|
|
||||||
ShaderReloadCallback callback) {
|
|
||||||
auto it = shaders_.find(name);
|
|
||||||
if (it != shaders_.end()) {
|
|
||||||
it->second.reloadCallback = callback;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 启用/禁用热重载
|
|
||||||
* @param enabled 是否启用
|
|
||||||
*/
|
|
||||||
void ShaderManager::setHotReloadEnabled(bool enabled) {
|
|
||||||
if (!hotReloadSupported_) {
|
|
||||||
E2D_LOG_WARN("当前平台不支持热重载");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
hotReloadEnabled_ = enabled;
|
|
||||||
ShaderHotReloader::getInstance().setEnabled(enabled);
|
|
||||||
E2D_LOG_INFO("热重载已{}", enabled ? "Enable" : "Disable");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查热重载是否启用
|
|
||||||
* @return 启用返回true,否则返回false
|
|
||||||
*/
|
|
||||||
bool ShaderManager::isHotReloadEnabled() const {
|
|
||||||
return hotReloadEnabled_ && hotReloadSupported_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 更新热重载系统(主循环调用)
|
|
||||||
*/
|
|
||||||
void ShaderManager::update() {
|
|
||||||
if (hotReloadEnabled_ && hotReloadSupported_) {
|
|
||||||
ShaderHotReloader::getInstance().update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 手动重载Shader
|
|
||||||
* @param name Shader名称
|
|
||||||
* @return 重载成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool ShaderManager::reload(const std::string &name) {
|
|
||||||
auto it = shaders_.find(name);
|
|
||||||
if (it == shaders_.end()) {
|
|
||||||
E2D_LOG_WARN("未找到要重载的着色器: {}", name);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ShaderInfo &info = it->second;
|
|
||||||
|
|
||||||
std::string vertSource = info.vertSource;
|
|
||||||
std::string fragSource = info.fragSource;
|
|
||||||
|
|
||||||
if (!info.metadata.vertPath.empty() && !info.metadata.fragPath.empty()) {
|
|
||||||
ShaderLoadResult result = loader_.loadFromSeparateFiles(
|
|
||||||
name, info.metadata.vertPath, info.metadata.fragPath);
|
|
||||||
if (result.success) {
|
|
||||||
vertSource = result.vertSource;
|
|
||||||
fragSource = result.fragSource;
|
|
||||||
}
|
|
||||||
} else if (!info.metadata.combinedPath.empty()) {
|
|
||||||
ShaderLoadResult result =
|
|
||||||
loader_.loadFromCombinedFile(info.metadata.combinedPath);
|
|
||||||
if (result.success) {
|
|
||||||
vertSource = result.vertSource;
|
|
||||||
fragSource = result.fragSource;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<IShader> newShader =
|
|
||||||
factory_->createFromSource(name, vertSource, fragSource);
|
|
||||||
if (!newShader) {
|
|
||||||
E2D_LOG_ERROR("重载着色器失败: {}", name);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
info.shader = newShader;
|
|
||||||
info.vertSource = vertSource;
|
|
||||||
info.fragSource = fragSource;
|
|
||||||
|
|
||||||
if (info.reloadCallback) {
|
|
||||||
info.reloadCallback(newShader);
|
|
||||||
}
|
|
||||||
|
|
||||||
E2D_LOG_INFO("着色器重载成功: {}", name);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取内置Shader
|
|
||||||
* @param name 内置Shader名称
|
|
||||||
* @return Shader实例
|
|
||||||
*/
|
|
||||||
Ptr<IShader> ShaderManager::getBuiltin(const std::string &name) {
|
|
||||||
Ptr<IShader> shader = get(name);
|
|
||||||
if (shader) {
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从JSON元数据文件加载内置着色器
|
|
||||||
fs::path jsonPath =
|
|
||||||
fs::path(shaderDir_) / "shared" / "builtin" / (name + ".json");
|
|
||||||
if (loader_.fileExists(jsonPath.string())) {
|
|
||||||
return loadFromMetadata(jsonPath.string(), name);
|
|
||||||
}
|
|
||||||
|
|
||||||
E2D_LOG_ERROR("未找到内置着色器: {}", jsonPath.string());
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从JSON元数据文件加载Shader(多后端支持)
|
|
||||||
* @param jsonPath JSON元数据文件路径
|
|
||||||
* @param name Shader名称
|
|
||||||
* @return 加载的Shader实例
|
|
||||||
*/
|
|
||||||
Ptr<IShader> ShaderManager::loadFromMetadata(const std::string &jsonPath,
|
|
||||||
const std::string &name) {
|
|
||||||
if (!initialized_) {
|
|
||||||
E2D_LOG_ERROR("ShaderManager 未初始化");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否已加载
|
|
||||||
auto it = shaders_.find(name);
|
|
||||||
if (it != shaders_.end()) {
|
|
||||||
return it->second.shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 读取JSON文件
|
|
||||||
std::string jsonContent = loader_.readFile(jsonPath);
|
|
||||||
if (jsonContent.empty()) {
|
|
||||||
E2D_LOG_ERROR("读取着色器元数据失败: {}", jsonPath);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 使用nlohmann/json解析
|
|
||||||
nl::json j = nl::json::parse(jsonContent);
|
|
||||||
|
|
||||||
// 解析基本元数据
|
|
||||||
ShaderMetadata metadata;
|
|
||||||
metadata.name = name;
|
|
||||||
|
|
||||||
if (j.contains("category")) {
|
|
||||||
metadata.category = j["category"].get<std::string>();
|
|
||||||
}
|
|
||||||
if (j.contains("version")) {
|
|
||||||
metadata.version = j["version"].get<std::string>();
|
|
||||||
}
|
|
||||||
if (j.contains("description")) {
|
|
||||||
metadata.description = j["description"].get<std::string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析 uniforms 详细定义
|
|
||||||
if (j.contains("uniforms")) {
|
|
||||||
for (auto &[key, value] : j["uniforms"].items()) {
|
|
||||||
ShaderUniformDef def;
|
|
||||||
if (value.contains("type")) {
|
|
||||||
def.type = value["type"].get<std::string>();
|
|
||||||
}
|
|
||||||
if (value.contains("description")) {
|
|
||||||
def.description = value["description"].get<std::string>();
|
|
||||||
}
|
|
||||||
// 解析默认值
|
|
||||||
if (value.contains("default")) {
|
|
||||||
def.hasDefault = true;
|
|
||||||
if (def.type == "float") {
|
|
||||||
def.defaultValue = value["default"].get<float>();
|
|
||||||
} else if (def.type == "int") {
|
|
||||||
def.defaultInt = value["default"].get<int>();
|
|
||||||
} else if (def.type == "bool") {
|
|
||||||
def.defaultBool = value["default"].get<bool>();
|
|
||||||
} else if (def.type == "vec2" && value["default"].is_array()) {
|
|
||||||
auto arr = value["default"].get<std::vector<float>>();
|
|
||||||
for (size_t i = 0; i < arr.size() && i < 2; ++i) {
|
|
||||||
def.defaultVec2[i] = arr[i];
|
|
||||||
}
|
|
||||||
} else if (def.type == "vec3" && value["default"].is_array()) {
|
|
||||||
auto arr = value["default"].get<std::vector<float>>();
|
|
||||||
for (size_t i = 0; i < arr.size() && i < 3; ++i) {
|
|
||||||
def.defaultVec3[i] = arr[i];
|
|
||||||
}
|
|
||||||
} else if (def.type == "vec4" && value["default"].is_array()) {
|
|
||||||
auto arr = value["default"].get<std::vector<float>>();
|
|
||||||
for (size_t i = 0; i < arr.size() && i < 4; ++i) {
|
|
||||||
def.defaultVec4[i] = arr[i];
|
|
||||||
}
|
|
||||||
} else if (def.type == "mat4" && value["default"].is_array()) {
|
|
||||||
auto arr = value["default"].get<std::vector<float>>();
|
|
||||||
for (size_t i = 0; i < arr.size() && i < 16; ++i) {
|
|
||||||
def.defaultMat4[i] = arr[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
metadata.uniformDefs[key] = def;
|
|
||||||
// 同时填充旧的uniforms映射以保持兼容性
|
|
||||||
metadata.uniforms[key] = def.type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析 samplers 定义
|
|
||||||
if (j.contains("samplers")) {
|
|
||||||
for (auto &[key, value] : j["samplers"].items()) {
|
|
||||||
ShaderSamplerDef def;
|
|
||||||
if (value.contains("type")) {
|
|
||||||
def.type = value["type"].get<std::string>();
|
|
||||||
}
|
|
||||||
if (value.contains("description")) {
|
|
||||||
def.description = value["description"].get<std::string>();
|
|
||||||
}
|
|
||||||
metadata.samplerDefs[key] = def;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取OpenGL后端路径
|
|
||||||
if (!j.contains("backends") || !j["backends"].contains("opengl")) {
|
|
||||||
E2D_LOG_ERROR("着色器元数据中未找到 OpenGL 后端: {}", jsonPath);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto &opengl = j["backends"]["opengl"];
|
|
||||||
if (!opengl.contains("vertex") || !opengl.contains("fragment")) {
|
|
||||||
E2D_LOG_ERROR("着色器元数据中缺少顶点或片段路径: {}", jsonPath);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string vertRelativePath = opengl["vertex"].get<std::string>();
|
|
||||||
std::string fragRelativePath = opengl["fragment"].get<std::string>();
|
|
||||||
|
|
||||||
// 使用C++17文件系统构建完整路径
|
|
||||||
fs::path vertPath = fs::path(shaderDir_) / vertRelativePath;
|
|
||||||
fs::path fragPath = fs::path(shaderDir_) / fragRelativePath;
|
|
||||||
|
|
||||||
E2D_LOG_DEBUG("从元数据加载着色器: {} -> 顶点: {}, 片段: {}", name,
|
|
||||||
vertPath.string(), fragPath.string());
|
|
||||||
|
|
||||||
// 使用分离文件加载
|
|
||||||
Ptr<IShader> shader =
|
|
||||||
loadFromFiles(name, vertPath.string(), fragPath.string());
|
|
||||||
|
|
||||||
// 更新着色器元数据
|
|
||||||
if (shader && shaders_.find(name) != shaders_.end()) {
|
|
||||||
shaders_[name].metadata = metadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
return shader;
|
|
||||||
|
|
||||||
} catch (const nl::json::exception &e) {
|
|
||||||
E2D_LOG_ERROR("解析着色器元数据 {} 失败: {}", jsonPath, e.what());
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 加载所有内置Shader
|
|
||||||
* @return 加载成功返回true,失败返回false
|
|
||||||
*/
|
|
||||||
bool ShaderManager::loadBuiltinShaders() {
|
|
||||||
if (!initialized_) {
|
|
||||||
E2D_LOG_ERROR("ShaderManager 未初始化");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool allSuccess = true;
|
|
||||||
|
|
||||||
const char *builtinNames[] = {"sprite", "particle", "shape", "postprocess",
|
|
||||||
"font"};
|
|
||||||
|
|
||||||
for (const char *name : builtinNames) {
|
|
||||||
fs::path jsonPath = fs::path(shaderDir_) / "shared" / "builtin" /
|
|
||||||
(std::string(name) + ".json");
|
|
||||||
std::string shaderName = std::string("builtin_") + name;
|
|
||||||
|
|
||||||
Ptr<IShader> shader = nullptr;
|
|
||||||
if (loader_.fileExists(jsonPath.string())) {
|
|
||||||
shader = loadFromMetadata(jsonPath.string(), name);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!shader) {
|
|
||||||
E2D_LOG_ERROR("加载内置 {} 着色器失败", name);
|
|
||||||
allSuccess = false;
|
|
||||||
} else {
|
|
||||||
// 同时注册带 builtin_ 前缀的名称
|
|
||||||
auto it = shaders_.find(name);
|
|
||||||
if (it != shaders_.end()) {
|
|
||||||
shaders_[shaderName] = it->second;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allSuccess) {
|
|
||||||
E2D_LOG_INFO("所有内置着色器加载成功");
|
|
||||||
}
|
|
||||||
|
|
||||||
return allSuccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从缓存加载Shader
|
|
||||||
* @param name Shader名称
|
|
||||||
* @param sourceHash 源码哈希值
|
|
||||||
* @param vertSource 顶点着色器源码
|
|
||||||
* @param fragSource 片段着色器源码
|
|
||||||
* @return Shader实例
|
|
||||||
*/
|
|
||||||
Ptr<IShader> ShaderManager::loadFromCache(const std::string &name,
|
|
||||||
const std::string &sourceHash,
|
|
||||||
const std::string &vertSource,
|
|
||||||
const std::string &fragSource) {
|
|
||||||
if (!ShaderCache::getInstance().isInitialized()) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ShaderCache::getInstance().hasValidCache(name, sourceHash)) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<ShaderCacheEntry> entry = ShaderCache::getInstance().loadCache(name);
|
|
||||||
if (!entry || entry->binary.empty()) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<IShader> shader = factory_->createFromBinary(name, entry->binary);
|
|
||||||
if (shader) {
|
|
||||||
E2D_LOG_DEBUG("从缓存加载着色器成功: {}", name);
|
|
||||||
}
|
|
||||||
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 处理文件变化事件
|
|
||||||
* @param shaderName Shader名称
|
|
||||||
* @param event 文件变化事件
|
|
||||||
*/
|
|
||||||
void ShaderManager::handleFileChange(const std::string &shaderName,
|
|
||||||
const FileChangeEvent &event) {
|
|
||||||
E2D_LOG_DEBUG("着色器文件已更改: {} -> {}", shaderName, event.filepath);
|
|
||||||
reload(shaderName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取Shader元数据
|
|
||||||
* @param name Shader名称
|
|
||||||
* @return Shader元数据,不存在返回空元数据
|
|
||||||
*/
|
|
||||||
ShaderMetadata ShaderManager::getMetadata(const std::string &name) const {
|
|
||||||
auto it = shaders_.find(name);
|
|
||||||
if (it != shaders_.end()) {
|
|
||||||
return it->second.metadata;
|
|
||||||
}
|
|
||||||
return ShaderMetadata{};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取Shader的uniform定义
|
|
||||||
* @param name Shader名称
|
|
||||||
* @return uniform定义映射,不存在返回空映射
|
|
||||||
*/
|
|
||||||
std::unordered_map<std::string, ShaderUniformDef>
|
|
||||||
ShaderManager::getUniformDefs(const std::string &name) const {
|
|
||||||
auto it = shaders_.find(name);
|
|
||||||
if (it != shaders_.end()) {
|
|
||||||
return it->second.metadata.uniformDefs;
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取Shader的sampler定义
|
|
||||||
* @param name Shader名称
|
|
||||||
* @return sampler定义映射,不存在返回空映射
|
|
||||||
*/
|
|
||||||
std::unordered_map<std::string, ShaderSamplerDef>
|
|
||||||
ShaderManager::getSamplerDefs(const std::string &name) const {
|
|
||||||
auto it = shaders_.find(name);
|
|
||||||
if (it != shaders_.end()) {
|
|
||||||
return it->second.metadata.samplerDefs;
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 自动应用uniform值到着色器
|
|
||||||
* 根据JSON元数据中的uniform定义,自动设置对应的uniform值
|
|
||||||
* 如果值映射表中不存在该uniform,则使用JSON中定义的默认值
|
|
||||||
* @param shader 目标着色器
|
|
||||||
* @param shaderName Shader名称(用于查找元数据)
|
|
||||||
* @param values uniform值映射表(可选,用于覆盖默认值)
|
|
||||||
*/
|
|
||||||
void ShaderManager::applyUniforms(Ptr<IShader> shader,
|
|
||||||
const std::string &shaderName,
|
|
||||||
const UniformValueMap &values) {
|
|
||||||
if (!shader)
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto uniformDefs = getUniformDefs(shaderName);
|
|
||||||
for (const auto &[name, def] : uniformDefs) {
|
|
||||||
auto it = values.find(name);
|
|
||||||
|
|
||||||
if (it != values.end()) {
|
|
||||||
// 使用提供的值
|
|
||||||
const auto &value = it->second;
|
|
||||||
if (def.type == "float" &&
|
|
||||||
value.type == ShaderUniformValue::Type::Float) {
|
|
||||||
shader->setFloat(name, value.data.f[0]);
|
|
||||||
} else if (def.type == "int" &&
|
|
||||||
value.type == ShaderUniformValue::Type::Int) {
|
|
||||||
shader->setInt(name, value.data.i);
|
|
||||||
} else if (def.type == "bool" &&
|
|
||||||
value.type == ShaderUniformValue::Type::Bool) {
|
|
||||||
shader->setBool(name, value.data.b);
|
|
||||||
} else if (def.type == "vec2" &&
|
|
||||||
value.type == ShaderUniformValue::Type::Vec2) {
|
|
||||||
shader->setVec2(name, glm::vec2(value.data.f[0], value.data.f[1]));
|
|
||||||
} else if (def.type == "vec3" &&
|
|
||||||
value.type == ShaderUniformValue::Type::Vec3) {
|
|
||||||
shader->setVec3(
|
|
||||||
name, glm::vec3(value.data.f[0], value.data.f[1], value.data.f[2]));
|
|
||||||
} else if (def.type == "vec4" &&
|
|
||||||
value.type == ShaderUniformValue::Type::Vec4) {
|
|
||||||
shader->setVec4(name, glm::vec4(value.data.f[0], value.data.f[1],
|
|
||||||
value.data.f[2], value.data.f[3]));
|
|
||||||
} else if (def.type == "mat4" &&
|
|
||||||
value.type == ShaderUniformValue::Type::Mat4) {
|
|
||||||
glm::mat4 mat;
|
|
||||||
for (int i = 0; i < 4; ++i)
|
|
||||||
for (int j = 0; j < 4; ++j)
|
|
||||||
mat[i][j] = value.data.f[i * 4 + j];
|
|
||||||
shader->setMat4(name, mat);
|
|
||||||
}
|
|
||||||
} else if (def.hasDefault) {
|
|
||||||
// 使用JSON中定义的默认值
|
|
||||||
if (def.type == "float") {
|
|
||||||
shader->setFloat(name, def.defaultValue);
|
|
||||||
} else if (def.type == "int") {
|
|
||||||
shader->setInt(name, def.defaultInt);
|
|
||||||
} else if (def.type == "bool") {
|
|
||||||
shader->setBool(name, def.defaultBool);
|
|
||||||
} else if (def.type == "vec2") {
|
|
||||||
shader->setVec2(name,
|
|
||||||
glm::vec2(def.defaultVec2[0], def.defaultVec2[1]));
|
|
||||||
} else if (def.type == "vec3") {
|
|
||||||
shader->setVec3(name, glm::vec3(def.defaultVec3[0], def.defaultVec3[1],
|
|
||||||
def.defaultVec3[2]));
|
|
||||||
} else if (def.type == "vec4") {
|
|
||||||
shader->setVec4(name,
|
|
||||||
glm::vec4(def.defaultVec4[0], def.defaultVec4[1],
|
|
||||||
def.defaultVec4[2], def.defaultVec4[3]));
|
|
||||||
} else if (def.type == "mat4") {
|
|
||||||
glm::mat4 mat;
|
|
||||||
for (int i = 0; i < 4; ++i)
|
|
||||||
for (int j = 0; j < 4; ++j)
|
|
||||||
mat[i][j] = def.defaultMat4[i * 4 + j];
|
|
||||||
shader->setMat4(name, mat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 自动应用sampler绑定到着色器
|
|
||||||
* 根据JSON元数据中的sampler定义,自动设置对应的纹理单元
|
|
||||||
* @param shader 目标着色器
|
|
||||||
* @param shaderName Shader名称(用于查找元数据)
|
|
||||||
*/
|
|
||||||
void ShaderManager::applySamplers(Ptr<IShader> shader,
|
|
||||||
const std::string &shaderName) {
|
|
||||||
if (!shader)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// TODO: 从JSON中解析binding并设置
|
|
||||||
// 目前sampler绑定在submitBatch中通过setInt设置
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,186 +0,0 @@
|
||||||
#include <extra2d/core/service_locator.h>
|
|
||||||
#include <extra2d/graphics/shader/shader_manager.h>
|
|
||||||
#include <extra2d/graphics/shader/shader_preset.h>
|
|
||||||
#include <extra2d/services/logger_service.h>
|
|
||||||
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建水波纹效果着色器
|
|
||||||
* @param params 水波纹效果参数
|
|
||||||
* @return 配置好的着色器
|
|
||||||
*/
|
|
||||||
Ptr<IShader> ShaderPreset::Water(const WaterParams ¶ms) {
|
|
||||||
Ptr<IShader> shader = ShaderManager::getInstance().get("water");
|
|
||||||
if (!shader) {
|
|
||||||
E2D_LOG_ERROR("获取水波纹着色器失败");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
shader->setFloat("u_waveSpeed", params.waveSpeed);
|
|
||||||
shader->setFloat("u_waveAmplitude", params.waveAmplitude);
|
|
||||||
shader->setFloat("u_waveFrequency", params.waveFrequency);
|
|
||||||
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建描边效果着色器
|
|
||||||
* @param params 描边效果参数
|
|
||||||
* @return 配置好的着色器
|
|
||||||
*/
|
|
||||||
Ptr<IShader> ShaderPreset::Outline(const OutlineParams ¶ms) {
|
|
||||||
Ptr<IShader> shader = ShaderManager::getInstance().get("outline");
|
|
||||||
if (!shader) {
|
|
||||||
E2D_LOG_ERROR("获取描边着色器失败");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
shader->setVec4("u_outlineColor", glm::vec4(params.color.r, params.color.g,
|
|
||||||
params.color.b, params.color.a));
|
|
||||||
shader->setFloat("u_thickness", params.thickness);
|
|
||||||
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建扭曲效果着色器
|
|
||||||
* @param params 扭曲效果参数
|
|
||||||
* @return 配置好的着色器
|
|
||||||
*/
|
|
||||||
Ptr<IShader> ShaderPreset::Distortion(const DistortionParams ¶ms) {
|
|
||||||
Ptr<IShader> shader = ShaderManager::getInstance().get("distortion");
|
|
||||||
if (!shader) {
|
|
||||||
E2D_LOG_ERROR("获取扭曲效果着色器失败");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
shader->setFloat("u_distortionAmount", params.distortionAmount);
|
|
||||||
shader->setFloat("u_timeScale", params.timeScale);
|
|
||||||
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建像素化效果着色器
|
|
||||||
* @param params 像素化效果参数
|
|
||||||
* @return 配置好的着色器
|
|
||||||
*/
|
|
||||||
Ptr<IShader> ShaderPreset::Pixelate(const PixelateParams ¶ms) {
|
|
||||||
Ptr<IShader> shader = ShaderManager::getInstance().get("pixelate");
|
|
||||||
if (!shader) {
|
|
||||||
E2D_LOG_ERROR("获取像素化着色器失败");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
shader->setFloat("u_pixelSize", params.pixelSize);
|
|
||||||
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建反相效果着色器
|
|
||||||
* @param params 反相效果参数
|
|
||||||
* @return 配置好的着色器
|
|
||||||
*/
|
|
||||||
Ptr<IShader> ShaderPreset::Invert(const InvertParams ¶ms) {
|
|
||||||
Ptr<IShader> shader = ShaderManager::getInstance().get("invert");
|
|
||||||
if (!shader) {
|
|
||||||
E2D_LOG_ERROR("获取反相着色器失败");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
shader->setFloat("u_strength", params.strength);
|
|
||||||
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建灰度效果着色器
|
|
||||||
* @param params 灰度效果参数
|
|
||||||
* @return 配置好的着色器
|
|
||||||
*/
|
|
||||||
Ptr<IShader> ShaderPreset::Grayscale(const GrayscaleParams ¶ms) {
|
|
||||||
Ptr<IShader> shader = ShaderManager::getInstance().get("grayscale");
|
|
||||||
if (!shader) {
|
|
||||||
E2D_LOG_ERROR("获取灰度着色器失败");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
shader->setFloat("u_intensity", params.intensity);
|
|
||||||
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建模糊效果着色器
|
|
||||||
* @param params 模糊效果参数
|
|
||||||
* @return 配置好的着色器
|
|
||||||
*/
|
|
||||||
Ptr<IShader> ShaderPreset::Blur(const BlurParams ¶ms) {
|
|
||||||
Ptr<IShader> shader = ShaderManager::getInstance().get("blur");
|
|
||||||
if (!shader) {
|
|
||||||
E2D_LOG_ERROR("获取模糊着色器失败");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
shader->setFloat("u_radius", params.radius);
|
|
||||||
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建灰度+描边组合效果着色器
|
|
||||||
* @param grayParams 灰度效果参数
|
|
||||||
* @param outlineParams 描边效果参数
|
|
||||||
* @return 配置好的着色器
|
|
||||||
*/
|
|
||||||
Ptr<IShader>
|
|
||||||
ShaderPreset::GrayscaleOutline(const GrayscaleParams &grayParams,
|
|
||||||
const OutlineParams &outlineParams) {
|
|
||||||
std::string shaderDir = ShaderManager::getInstance().getShaderDir();
|
|
||||||
std::string jsonPath = shaderDir + "effects/grayscale_outline.json";
|
|
||||||
|
|
||||||
Ptr<IShader> shader =
|
|
||||||
ShaderManager::getInstance().loadFromMetadata(jsonPath, "grayscale_outline");
|
|
||||||
if (!shader) {
|
|
||||||
E2D_LOG_ERROR("从 {} 加载灰度描边组合着色器失败",
|
|
||||||
jsonPath);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
shader->setFloat("u_grayIntensity", grayParams.intensity);
|
|
||||||
shader->setVec4("u_outlineColor",
|
|
||||||
glm::vec4(outlineParams.color.r, outlineParams.color.g,
|
|
||||||
outlineParams.color.b, outlineParams.color.a));
|
|
||||||
shader->setFloat("u_thickness", outlineParams.thickness);
|
|
||||||
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建像素化+反相组合效果着色器
|
|
||||||
* @param pixParams 像素化效果参数
|
|
||||||
* @param invParams 反相效果参数
|
|
||||||
* @return 配置好的着色器
|
|
||||||
*/
|
|
||||||
Ptr<IShader> ShaderPreset::PixelateInvert(const PixelateParams &pixParams,
|
|
||||||
const InvertParams &invParams) {
|
|
||||||
std::string shaderDir = ShaderManager::getInstance().getShaderDir();
|
|
||||||
std::string jsonPath = shaderDir + "effects/pixelate_invert.json";
|
|
||||||
|
|
||||||
Ptr<IShader> shader =
|
|
||||||
ShaderManager::getInstance().loadFromMetadata(jsonPath, "pixelate_invert");
|
|
||||||
if (!shader) {
|
|
||||||
E2D_LOG_ERROR("从 {} 加载像素化反相组合着色器失败", jsonPath);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
shader->setFloat("u_pixelSize", pixParams.pixelSize);
|
|
||||||
shader->setFloat("u_invertStrength", invParams.strength);
|
|
||||||
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,103 +0,0 @@
|
||||||
#include <extra2d/graphics/texture/alpha_mask.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 构造函数
|
|
||||||
*
|
|
||||||
* 创建指定尺寸的Alpha遮罩,初始时所有像素均为不透明(值为255)
|
|
||||||
*
|
|
||||||
* @param width 遮罩宽度(像素)
|
|
||||||
* @param height 遮罩高度(像素)
|
|
||||||
*/
|
|
||||||
AlphaMask::AlphaMask(int width, int height)
|
|
||||||
: width_(width), height_(height), data_(width * height, 255) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从像素数据创建Alpha遮罩
|
|
||||||
*
|
|
||||||
* 根据输入的像素数据提取Alpha通道创建遮罩,
|
|
||||||
* 支持RGBA(4通道)、RGB(3通道)和灰度(1通道)格式
|
|
||||||
*
|
|
||||||
* @param pixels 像素数据指针
|
|
||||||
* @param width 图像宽度
|
|
||||||
* @param height 图像高度
|
|
||||||
* @param channels 通道数量(1、3或4)
|
|
||||||
* @return 创建的Alpha遮罩对象
|
|
||||||
*/
|
|
||||||
AlphaMask AlphaMask::createFromPixels(const uint8_t *pixels, int width,
|
|
||||||
int height, int channels) {
|
|
||||||
AlphaMask mask(width, height);
|
|
||||||
|
|
||||||
if (!pixels || width <= 0 || height <= 0) {
|
|
||||||
return mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 根据通道数提取Alpha值
|
|
||||||
for (int y = 0; y < height; ++y) {
|
|
||||||
for (int x = 0; x < width; ++x) {
|
|
||||||
int pixelIndex = (y * width + x) * channels;
|
|
||||||
uint8_t alpha = 255;
|
|
||||||
|
|
||||||
if (channels == 4) {
|
|
||||||
// RGBA格式,Alpha在第四个通道
|
|
||||||
alpha = pixels[pixelIndex + 3];
|
|
||||||
} else if (channels == 1) {
|
|
||||||
// 灰度图,直接作为Alpha
|
|
||||||
alpha = pixels[pixelIndex];
|
|
||||||
} else if (channels == 3) {
|
|
||||||
// RGB格式,没有Alpha通道,视为不透明
|
|
||||||
alpha = 255;
|
|
||||||
}
|
|
||||||
|
|
||||||
mask.data_[y * width + x] = alpha;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取指定位置的Alpha值
|
|
||||||
*
|
|
||||||
* 返回指定坐标处的Alpha值,如果坐标无效则返回0
|
|
||||||
*
|
|
||||||
* @param x X坐标
|
|
||||||
* @param y Y坐标
|
|
||||||
* @return Alpha值(0-255),坐标无效时返回0
|
|
||||||
*/
|
|
||||||
uint8_t AlphaMask::getAlpha(int x, int y) const {
|
|
||||||
if (!isValid(x, y)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return data_[y * width_ + x];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查指定位置是否不透明
|
|
||||||
*
|
|
||||||
* 判断指定坐标处的Alpha值是否大于等于给定的阈值
|
|
||||||
*
|
|
||||||
* @param x X坐标
|
|
||||||
* @param y Y坐标
|
|
||||||
* @param threshold 不透明度阈值(默认为255,即完全不透明)
|
|
||||||
* @return 如果Alpha值大于等于阈值返回true,否则返回false
|
|
||||||
*/
|
|
||||||
bool AlphaMask::isOpaque(int x, int y, uint8_t threshold) const {
|
|
||||||
return getAlpha(x, y) >= threshold;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查坐标是否有效
|
|
||||||
*
|
|
||||||
* 判断给定的坐标是否在遮罩的有效范围内
|
|
||||||
*
|
|
||||||
* @param x X坐标
|
|
||||||
* @param y Y坐标
|
|
||||||
* @return 如果坐标在有效范围内返回true,否则返回false
|
|
||||||
*/
|
|
||||||
bool AlphaMask::isValid(int x, int y) const {
|
|
||||||
return x >= 0 && x < width_ && y >= 0 && y < height_;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,386 +0,0 @@
|
||||||
#include <extra2d/core/service_locator.h>
|
|
||||||
#include <extra2d/graphics/texture/texture_atlas.h>
|
|
||||||
#include <extra2d/services/logger_service.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// TextureAtlasPage 实现
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 构造函数
|
|
||||||
* @param width 页面宽度
|
|
||||||
* @param height 页面高度
|
|
||||||
*
|
|
||||||
* 创建指定尺寸的纹理图集页面,初始化空白纹理和打包根节点
|
|
||||||
*/
|
|
||||||
TextureAtlasPage::TextureAtlasPage(int width, int height)
|
|
||||||
: width_(width), height_(height), isFull_(false), usedArea_(0) {
|
|
||||||
// 创建空白纹理
|
|
||||||
std::vector<uint8_t> emptyData(width * height * 4, 0);
|
|
||||||
texture_ = makePtr<GLTexture>(width, height, emptyData.data(), 4);
|
|
||||||
|
|
||||||
// 初始化矩形打包根节点
|
|
||||||
root_ = std::make_unique<PackNode>(0, 0, width, height);
|
|
||||||
|
|
||||||
E2D_LOG_INFO("已创建纹理图集页面: {}x{}", width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 析构函数
|
|
||||||
*
|
|
||||||
* 释放纹理图集页面资源
|
|
||||||
*/
|
|
||||||
TextureAtlasPage::~TextureAtlasPage() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 尝试添加纹理到图集页面
|
|
||||||
* @param name 纹理名称
|
|
||||||
* @param texWidth 纹理宽度
|
|
||||||
* @param texHeight 纹理高度
|
|
||||||
* @param pixels 像素数据
|
|
||||||
* @param[out] outUvRect 输出的UV坐标矩形
|
|
||||||
* @return 添加成功返回true,失败返回false
|
|
||||||
*
|
|
||||||
* 尝试将纹理添加到图集页面中,使用矩形打包算法找到合适位置
|
|
||||||
*/
|
|
||||||
bool TextureAtlasPage::tryAddTexture(const std::string &name, int texWidth,
|
|
||||||
int texHeight, const uint8_t *pixels,
|
|
||||||
Rect &outUvRect) {
|
|
||||||
if (isFull_) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加边距
|
|
||||||
int paddedWidth = texWidth + 2 * PADDING;
|
|
||||||
int paddedHeight = texHeight + 2 * PADDING;
|
|
||||||
|
|
||||||
// 如果纹理太大,无法放入
|
|
||||||
if (paddedWidth > width_ || paddedHeight > height_) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 尝试插入
|
|
||||||
PackNode *node = insert(root_.get(), paddedWidth, paddedHeight);
|
|
||||||
if (node == nullptr) {
|
|
||||||
// 无法放入,标记为满
|
|
||||||
isFull_ = true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 写入像素数据(跳过边距区域)
|
|
||||||
writePixels(node->x + PADDING, node->y + PADDING, texWidth, texHeight,
|
|
||||||
pixels);
|
|
||||||
|
|
||||||
// 创建条目
|
|
||||||
AtlasEntry entry;
|
|
||||||
entry.name = name;
|
|
||||||
entry.originalSize =
|
|
||||||
Vec2(static_cast<float>(texWidth), static_cast<float>(texHeight));
|
|
||||||
entry.padding = PADDING;
|
|
||||||
|
|
||||||
// 计算 UV 坐标(考虑边距)
|
|
||||||
float u1 = static_cast<float>(node->x + PADDING) / width_;
|
|
||||||
float v1 = static_cast<float>(node->y + PADDING) / height_;
|
|
||||||
float u2 = static_cast<float>(node->x + PADDING + texWidth) / width_;
|
|
||||||
float v2 = static_cast<float>(node->y + PADDING + texHeight) / height_;
|
|
||||||
|
|
||||||
entry.uvRect = Rect(u1, v1, u2 - u1, v2 - v1);
|
|
||||||
outUvRect = entry.uvRect;
|
|
||||||
|
|
||||||
entries_[name] = std::move(entry);
|
|
||||||
usedArea_ += paddedWidth * paddedHeight;
|
|
||||||
|
|
||||||
E2D_LOG_DEBUG("已将纹理 '{}' 添加到图集: {}x{} 位置 ({}, {})", name,
|
|
||||||
texWidth, texHeight, node->x, node->y);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 插入纹理到打包树
|
|
||||||
* @param node 当前节点
|
|
||||||
* @param width 纹理宽度
|
|
||||||
* @param height 纹理高度
|
|
||||||
* @return 找到合适的节点返回节点指针,否则返回nullptr
|
|
||||||
*
|
|
||||||
* 使用二叉树算法递归查找合适的空间位置
|
|
||||||
*/
|
|
||||||
TextureAtlasPage::PackNode *TextureAtlasPage::insert(PackNode *node, int width,
|
|
||||||
int height) {
|
|
||||||
if (node == nullptr) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果节点已被使用,尝试子节点
|
|
||||||
if (node->used) {
|
|
||||||
PackNode *result = insert(node->left.get(), width, height);
|
|
||||||
if (result != nullptr) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
return insert(node->right.get(), width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否适合
|
|
||||||
if (width > node->width || height > node->height) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果刚好合适,使用此节点
|
|
||||||
if (width == node->width && height == node->height) {
|
|
||||||
node->used = true;
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 需要分割节点
|
|
||||||
int dw = node->width - width;
|
|
||||||
int dh = node->height - height;
|
|
||||||
|
|
||||||
if (dw > dh) {
|
|
||||||
// 水平分割
|
|
||||||
node->left =
|
|
||||||
std::make_unique<PackNode>(node->x, node->y, width, node->height);
|
|
||||||
node->right =
|
|
||||||
std::make_unique<PackNode>(node->x + width, node->y, dw, node->height);
|
|
||||||
} else {
|
|
||||||
// 垂直分割
|
|
||||||
node->left =
|
|
||||||
std::make_unique<PackNode>(node->x, node->y, node->width, height);
|
|
||||||
node->right =
|
|
||||||
std::make_unique<PackNode>(node->x, node->y + height, node->width, dh);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 递归插入到左子节点
|
|
||||||
return insert(node->left.get(), width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 写入像素数据到纹理
|
|
||||||
* @param x 起始X坐标
|
|
||||||
* @param y 起始Y坐标
|
|
||||||
* @param w 宽度
|
|
||||||
* @param h 高度
|
|
||||||
* @param pixels 像素数据
|
|
||||||
*
|
|
||||||
* 使用glTexSubImage2D更新纹理的指定区域
|
|
||||||
*/
|
|
||||||
void TextureAtlasPage::writePixels(int x, int y, int w, int h,
|
|
||||||
const uint8_t *pixels) {
|
|
||||||
if (texture_ == nullptr || pixels == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用 glTexSubImage2D 更新纹理数据
|
|
||||||
GLuint texID = static_cast<GLuint>(
|
|
||||||
reinterpret_cast<uintptr_t>(texture_->getNativeHandle()));
|
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, texID);
|
|
||||||
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE,
|
|
||||||
pixels);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取图集中的纹理条目信息
|
|
||||||
* @param name 纹理名称
|
|
||||||
* @return 找到返回条目指针,未找到返回nullptr
|
|
||||||
*/
|
|
||||||
const AtlasEntry *TextureAtlasPage::getEntry(const std::string &name) const {
|
|
||||||
auto it = entries_.find(name);
|
|
||||||
if (it != entries_.end()) {
|
|
||||||
return &it->second;
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取页面使用率
|
|
||||||
* @return 使用率(0.0到1.0之间)
|
|
||||||
*
|
|
||||||
* 计算已使用面积占总面积的比例
|
|
||||||
*/
|
|
||||||
float TextureAtlasPage::getUsageRatio() const {
|
|
||||||
return static_cast<float>(usedArea_) / (width_ * height_);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// TextureAtlas 实现
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 默认构造函数
|
|
||||||
*
|
|
||||||
* 创建一个使用默认页面大小的纹理图集
|
|
||||||
*/
|
|
||||||
TextureAtlas::TextureAtlas()
|
|
||||||
: pageSize_(TextureAtlasPage::DEFAULT_SIZE), sizeThreshold_(256),
|
|
||||||
enabled_(true), initialized_(false) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 析构函数
|
|
||||||
*
|
|
||||||
* 释放纹理图集资源
|
|
||||||
*/
|
|
||||||
TextureAtlas::~TextureAtlas() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化纹理图集
|
|
||||||
* @param pageSize 页面大小
|
|
||||||
*
|
|
||||||
* 设置图集页面大小并标记为已初始化
|
|
||||||
*/
|
|
||||||
void TextureAtlas::init(int pageSize) {
|
|
||||||
pageSize_ = pageSize;
|
|
||||||
initialized_ = true;
|
|
||||||
E2D_LOG_INFO("纹理图集已初始化,页面大小: {}", pageSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 添加纹理到图集
|
|
||||||
* @param name 纹理名称
|
|
||||||
* @param width 纹理宽度
|
|
||||||
* @param height 纹理高度
|
|
||||||
* @param pixels 像素数据
|
|
||||||
* @return 添加成功返回true,失败返回false
|
|
||||||
*
|
|
||||||
* 尝试将纹理添加到现有页面,如空间不足则创建新页面
|
|
||||||
*/
|
|
||||||
bool TextureAtlas::addTexture(const std::string &name, int width, int height,
|
|
||||||
const uint8_t *pixels) {
|
|
||||||
if (!enabled_ || !initialized_) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否已存在
|
|
||||||
if (contains(name)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查纹理大小
|
|
||||||
if (width > sizeThreshold_ || height > sizeThreshold_) {
|
|
||||||
E2D_LOG_DEBUG("纹理 '{}' 太大无法加入图集 ({}x{} > {}),跳过",
|
|
||||||
name, width, height, sizeThreshold_);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 尝试添加到现有页面
|
|
||||||
Rect uvRect;
|
|
||||||
for (auto &page : pages_) {
|
|
||||||
if (page->tryAddTexture(name, width, height, pixels, uvRect)) {
|
|
||||||
entryToPage_[name] = page.get();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建新页面
|
|
||||||
auto newPage = std::make_unique<TextureAtlasPage>(pageSize_, pageSize_);
|
|
||||||
if (newPage->tryAddTexture(name, width, height, pixels, uvRect)) {
|
|
||||||
entryToPage_[name] = newPage.get();
|
|
||||||
pages_.push_back(std::move(newPage));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
E2D_LOG_WARN("添加纹理 '{}' 到图集失败", name);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查纹理是否已存在于图集中
|
|
||||||
* @param name 纹理名称
|
|
||||||
* @return 存在返回true,不存在返回false
|
|
||||||
*/
|
|
||||||
bool TextureAtlas::contains(const std::string &name) const {
|
|
||||||
return entryToPage_.find(name) != entryToPage_.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取纹理所在的图集纹理
|
|
||||||
* @param name 纹理名称
|
|
||||||
* @return 找到返回纹理指针,未找到返回nullptr
|
|
||||||
*/
|
|
||||||
const Texture *TextureAtlas::getAtlasTexture(const std::string &name) const {
|
|
||||||
auto it = entryToPage_.find(name);
|
|
||||||
if (it != entryToPage_.end()) {
|
|
||||||
return it->second->getTexture().get();
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取纹理在图集中的UV坐标矩形
|
|
||||||
* @param name 纹理名称
|
|
||||||
* @return UV坐标矩形,未找到返回默认值
|
|
||||||
*/
|
|
||||||
Rect TextureAtlas::getUVRect(const std::string &name) const {
|
|
||||||
auto it = entryToPage_.find(name);
|
|
||||||
if (it != entryToPage_.end()) {
|
|
||||||
const AtlasEntry *entry = it->second->getEntry(name);
|
|
||||||
if (entry != nullptr) {
|
|
||||||
return entry->uvRect;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Rect(0, 0, 1, 1); // 默认 UV
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取纹理的原始尺寸
|
|
||||||
* @param name 纹理名称
|
|
||||||
* @return 原始尺寸,未找到返回零向量
|
|
||||||
*/
|
|
||||||
Vec2 TextureAtlas::getOriginalSize(const std::string &name) const {
|
|
||||||
auto it = entryToPage_.find(name);
|
|
||||||
if (it != entryToPage_.end()) {
|
|
||||||
const AtlasEntry *entry = it->second->getEntry(name);
|
|
||||||
if (entry != nullptr) {
|
|
||||||
return entry->originalSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Vec2(0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取总使用率
|
|
||||||
* @return 所有页面的平均使用率
|
|
||||||
*
|
|
||||||
* 计算所有页面的平均空间使用率
|
|
||||||
*/
|
|
||||||
float TextureAtlas::getTotalUsageRatio() const {
|
|
||||||
if (pages_.empty()) {
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
float total = 0.0f;
|
|
||||||
for (const auto &page : pages_) {
|
|
||||||
total += page->getUsageRatio();
|
|
||||||
}
|
|
||||||
return total / pages_.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 清空图集
|
|
||||||
*
|
|
||||||
* 移除所有页面和条目映射
|
|
||||||
*/
|
|
||||||
void TextureAtlas::clear() {
|
|
||||||
pages_.clear();
|
|
||||||
entryToPage_.clear();
|
|
||||||
E2D_LOG_INFO("纹理图集已清空");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// TextureAtlasMgr 单例实现
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取TextureAtlasMgr单例实例
|
|
||||||
* @return TextureAtlasMgr单例的引用
|
|
||||||
*
|
|
||||||
* 使用静态局部变量实现线程安全的单例模式
|
|
||||||
*/
|
|
||||||
TextureAtlasMgr &TextureAtlasMgr::get() {
|
|
||||||
static TextureAtlasMgr instance;
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,651 +0,0 @@
|
||||||
#include <extra2d/graphics/opengl/gl_renderer.h>
|
|
||||||
#include <extra2d/graphics/texture/texture_pool.h>
|
|
||||||
#include <extra2d/scene/scene.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// TexturePool 实现
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 默认构造函数
|
|
||||||
*
|
|
||||||
* 创建一个未初始化的纹理池
|
|
||||||
*/
|
|
||||||
TexturePool::TexturePool()
|
|
||||||
: scene_(nullptr), maxMemoryUsage_(0), currentMemoryUsage_(0),
|
|
||||||
cacheHits_(0), cacheMisses_(0), evictionCount_(0) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 构造函数
|
|
||||||
* @param scene 场景指针
|
|
||||||
* @param maxMemoryUsage 最大内存使用量(0 表示无限制)
|
|
||||||
*
|
|
||||||
* 创建一个指定场景和内存限制的纹理池
|
|
||||||
*/
|
|
||||||
TexturePool::TexturePool(Scene *scene, size_t maxMemoryUsage)
|
|
||||||
: scene_(scene), maxMemoryUsage_(maxMemoryUsage), currentMemoryUsage_(0),
|
|
||||||
cacheHits_(0), cacheMisses_(0), evictionCount_(0) {
|
|
||||||
E2D_LOG_INFO("TexturePool 已创建,最大内存: {} 字节", maxMemoryUsage);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化纹理池
|
|
||||||
* @param scene 场景指针
|
|
||||||
* @param maxMemoryUsage 最大内存使用量(0 表示无限制)
|
|
||||||
*
|
|
||||||
* 设置纹理池的场景和内存限制
|
|
||||||
*/
|
|
||||||
void TexturePool::init(Scene *scene, size_t maxMemoryUsage) {
|
|
||||||
scene_ = scene;
|
|
||||||
maxMemoryUsage_ = maxMemoryUsage;
|
|
||||||
E2D_LOG_INFO("TexturePool 已初始化,最大内存: {} 字节", maxMemoryUsage);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 析构函数
|
|
||||||
*
|
|
||||||
* 清理纹理池并释放所有资源
|
|
||||||
*/
|
|
||||||
TexturePool::~TexturePool() {
|
|
||||||
clear();
|
|
||||||
E2D_LOG_INFO("TexturePool 已销毁");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 纹理加载
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从文件加载纹理
|
|
||||||
* @param path 文件路径
|
|
||||||
* @param options 加载选项
|
|
||||||
* @return 纹理引用
|
|
||||||
*
|
|
||||||
* 加载完整纹理文件到纹理池
|
|
||||||
*/
|
|
||||||
TextureRef TexturePool::load(const std::string &path,
|
|
||||||
const TextureLoadOptions &options) {
|
|
||||||
return load(path, Rect::Zero(), options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从文件加载纹理区域
|
|
||||||
* @param path 文件路径
|
|
||||||
* @param region 纹理区域
|
|
||||||
* @param options 加载选项
|
|
||||||
* @return 纹理引用
|
|
||||||
*
|
|
||||||
* 加载纹理文件的指定区域到纹理池
|
|
||||||
*/
|
|
||||||
TextureRef TexturePool::load(const std::string &path, const Rect ®ion,
|
|
||||||
const TextureLoadOptions &options) {
|
|
||||||
TextureKey key(path, region);
|
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
|
|
||||||
// 检查缓存
|
|
||||||
auto it = cache_.find(key);
|
|
||||||
if (it != cache_.end()) {
|
|
||||||
// 缓存命中
|
|
||||||
it->second.touch();
|
|
||||||
it->second.refCount.fetch_add(1, std::memory_order_relaxed);
|
|
||||||
cacheHits_.fetch_add(1, std::memory_order_relaxed);
|
|
||||||
|
|
||||||
E2D_LOG_DEBUG("纹理缓存命中: {}", path);
|
|
||||||
return TextureRef(it->second.texture, &it->second, &mutex_);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 缓存未命中
|
|
||||||
cacheMisses_.fetch_add(1, std::memory_order_relaxed);
|
|
||||||
|
|
||||||
// 获取渲染后端
|
|
||||||
Renderer *backend = nullptr;
|
|
||||||
if (scene_) {
|
|
||||||
// 假设 Scene 有获取 Renderer 的方法
|
|
||||||
// 这里需要根据实际接口调整
|
|
||||||
backend = nullptr; // TODO: 从 Scene 获取 Renderer
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!backend) {
|
|
||||||
E2D_LOG_ERROR("TexturePool: 渲染后端不可用");
|
|
||||||
return TextureRef();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载纹理
|
|
||||||
Ptr<Texture> texture = backend->loadTexture(path);
|
|
||||||
if (!texture) {
|
|
||||||
E2D_LOG_ERROR("TexturePool: 加载纹理失败: {}", path);
|
|
||||||
return TextureRef();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算内存大小
|
|
||||||
size_t memorySize = calculateTextureMemory(texture.get());
|
|
||||||
|
|
||||||
// 检查内存限制
|
|
||||||
if (maxMemoryUsage_ > 0 &&
|
|
||||||
currentMemoryUsage_ + memorySize > maxMemoryUsage_) {
|
|
||||||
// 尝试淘汰
|
|
||||||
evictLRU(currentMemoryUsage_ + memorySize - maxMemoryUsage_);
|
|
||||||
|
|
||||||
// 再次检查
|
|
||||||
if (currentMemoryUsage_ + memorySize > maxMemoryUsage_) {
|
|
||||||
E2D_LOG_WARN("TexturePool: 内存限制超出,无法加载纹理: {}", path);
|
|
||||||
return TextureRef();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建缓存条目
|
|
||||||
auto result = cache_.emplace(key, TexturePoolEntry(nullptr, key, 0));
|
|
||||||
if (result.second) {
|
|
||||||
result.first->second.texture = texture;
|
|
||||||
result.first->second.memorySize = memorySize;
|
|
||||||
result.first->second.refCount.store(1, std::memory_order_relaxed);
|
|
||||||
result.first->second.touch();
|
|
||||||
currentMemoryUsage_ += memorySize;
|
|
||||||
E2D_LOG_INFO("TexturePool: 已加载纹理: {} ({} 字节)", path, memorySize);
|
|
||||||
return TextureRef(texture, &result.first->second, &mutex_);
|
|
||||||
}
|
|
||||||
|
|
||||||
return TextureRef();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从内存加载纹理
|
|
||||||
* @param data 像素数据
|
|
||||||
* @param width 宽度
|
|
||||||
* @param height 高度
|
|
||||||
* @param channels 通道数
|
|
||||||
* @param key 缓存键
|
|
||||||
* @return 纹理引用
|
|
||||||
*
|
|
||||||
* 从内存中的像素数据创建纹理并加入纹理池
|
|
||||||
*/
|
|
||||||
TextureRef TexturePool::loadFromMemory(const uint8_t *data, int width,
|
|
||||||
int height, int channels,
|
|
||||||
const std::string &key) {
|
|
||||||
TextureKey textureKey(key);
|
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
|
|
||||||
// 检查缓存
|
|
||||||
auto it = cache_.find(textureKey);
|
|
||||||
if (it != cache_.end()) {
|
|
||||||
it->second.touch();
|
|
||||||
it->second.refCount.fetch_add(1, std::memory_order_relaxed);
|
|
||||||
cacheHits_.fetch_add(1, std::memory_order_relaxed);
|
|
||||||
return TextureRef(it->second.texture, &it->second, &mutex_);
|
|
||||||
}
|
|
||||||
|
|
||||||
cacheMisses_.fetch_add(1, std::memory_order_relaxed);
|
|
||||||
|
|
||||||
// 获取渲染后端
|
|
||||||
Renderer *backend = nullptr;
|
|
||||||
if (scene_) {
|
|
||||||
backend = nullptr; // TODO: 从 Scene 获取 Renderer
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!backend) {
|
|
||||||
E2D_LOG_ERROR("TexturePool: 渲染后端不可用");
|
|
||||||
return TextureRef();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建纹理
|
|
||||||
Ptr<Texture> texture = backend->createTexture(width, height, data, channels);
|
|
||||||
if (!texture) {
|
|
||||||
E2D_LOG_ERROR("TexturePool: 从内存创建纹理失败");
|
|
||||||
return TextureRef();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算内存大小
|
|
||||||
size_t memorySize = calculateTextureMemory(texture.get());
|
|
||||||
|
|
||||||
// 检查内存限制
|
|
||||||
if (maxMemoryUsage_ > 0 &&
|
|
||||||
currentMemoryUsage_ + memorySize > maxMemoryUsage_) {
|
|
||||||
evictLRU(currentMemoryUsage_ + memorySize - maxMemoryUsage_);
|
|
||||||
|
|
||||||
if (currentMemoryUsage_ + memorySize > maxMemoryUsage_) {
|
|
||||||
E2D_LOG_WARN("TexturePool: 内存限制超出");
|
|
||||||
return TextureRef();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建缓存条目
|
|
||||||
auto result =
|
|
||||||
cache_.emplace(textureKey, TexturePoolEntry(nullptr, textureKey, 0));
|
|
||||||
if (result.second) {
|
|
||||||
result.first->second.texture = texture;
|
|
||||||
result.first->second.memorySize = memorySize;
|
|
||||||
result.first->second.refCount.store(1, std::memory_order_relaxed);
|
|
||||||
result.first->second.touch();
|
|
||||||
currentMemoryUsage_ += memorySize;
|
|
||||||
E2D_LOG_INFO("TexturePool: 已从内存创建纹理 ({} 字节)", memorySize);
|
|
||||||
return TextureRef(texture, &result.first->second, &mutex_);
|
|
||||||
}
|
|
||||||
|
|
||||||
return TextureRef();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取或加载纹理
|
|
||||||
* @param path 文件路径
|
|
||||||
* @param options 加载选项
|
|
||||||
* @return 纹理引用
|
|
||||||
*
|
|
||||||
* 如果纹理已缓存则返回缓存,否则加载纹理
|
|
||||||
*/
|
|
||||||
TextureRef TexturePool::getOrLoad(const std::string &path,
|
|
||||||
const TextureLoadOptions &options) {
|
|
||||||
return getOrLoad(path, Rect::Zero(), options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取或加载纹理区域
|
|
||||||
* @param path 文件路径
|
|
||||||
* @param region 纹理区域
|
|
||||||
* @param options 加载选项
|
|
||||||
* @return 纹理引用
|
|
||||||
*
|
|
||||||
* 如果纹理区域已缓存则返回缓存,否则加载纹理区域
|
|
||||||
*/
|
|
||||||
TextureRef TexturePool::getOrLoad(const std::string &path, const Rect ®ion,
|
|
||||||
const TextureLoadOptions &options) {
|
|
||||||
TextureKey key(path, region);
|
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
|
|
||||||
// 检查缓存
|
|
||||||
auto it = cache_.find(key);
|
|
||||||
if (it != cache_.end()) {
|
|
||||||
it->second.touch();
|
|
||||||
it->second.refCount.fetch_add(1, std::memory_order_relaxed);
|
|
||||||
cacheHits_.fetch_add(1, std::memory_order_relaxed);
|
|
||||||
return TextureRef(it->second.texture, &it->second, &mutex_);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 释放锁后调用 load
|
|
||||||
// 注意:这里需要重新设计以避免死锁
|
|
||||||
// 简化处理:直接在这里加载
|
|
||||||
|
|
||||||
cacheMisses_.fetch_add(1, std::memory_order_relaxed);
|
|
||||||
|
|
||||||
Renderer *backend = nullptr;
|
|
||||||
if (scene_) {
|
|
||||||
backend = nullptr; // TODO: 从 Scene 获取 Renderer
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!backend) {
|
|
||||||
E2D_LOG_ERROR("TexturePool: 渲染后端不可用");
|
|
||||||
return TextureRef();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<Texture> texture = backend->loadTexture(path);
|
|
||||||
if (!texture) {
|
|
||||||
E2D_LOG_ERROR("TexturePool: 加载纹理失败: {}", path);
|
|
||||||
return TextureRef();
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t memorySize = calculateTextureMemory(texture.get());
|
|
||||||
|
|
||||||
if (maxMemoryUsage_ > 0 &&
|
|
||||||
currentMemoryUsage_ + memorySize > maxMemoryUsage_) {
|
|
||||||
evictLRU(currentMemoryUsage_ + memorySize - maxMemoryUsage_);
|
|
||||||
|
|
||||||
if (currentMemoryUsage_ + memorySize > maxMemoryUsage_) {
|
|
||||||
E2D_LOG_WARN("TexturePool: 内存限制超出");
|
|
||||||
return TextureRef();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto result = cache_.emplace(key, TexturePoolEntry(nullptr, key, 0));
|
|
||||||
if (result.second) {
|
|
||||||
result.first->second.texture = texture;
|
|
||||||
result.first->second.memorySize = memorySize;
|
|
||||||
result.first->second.refCount.store(1, std::memory_order_relaxed);
|
|
||||||
result.first->second.touch();
|
|
||||||
currentMemoryUsage_ += memorySize;
|
|
||||||
return TextureRef(texture, &result.first->second, &mutex_);
|
|
||||||
}
|
|
||||||
|
|
||||||
return TextureRef();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 引用计数管理
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 增加引用计数
|
|
||||||
* @param key 纹理键
|
|
||||||
* @return 是否成功
|
|
||||||
*
|
|
||||||
* 增加指定纹理的引用计数
|
|
||||||
*/
|
|
||||||
bool TexturePool::addRef(const TextureKey &key) {
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
|
|
||||||
auto it = cache_.find(key);
|
|
||||||
if (it != cache_.end()) {
|
|
||||||
it->second.touch();
|
|
||||||
it->second.refCount.fetch_add(1, std::memory_order_relaxed);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 减少引用计数
|
|
||||||
* @param key 纹理键
|
|
||||||
* @return 减少后的引用计数
|
|
||||||
*
|
|
||||||
* 减少指定纹理的引用计数并返回新值
|
|
||||||
*/
|
|
||||||
uint32_t TexturePool::release(const TextureKey &key) {
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
|
|
||||||
auto it = cache_.find(key);
|
|
||||||
if (it != cache_.end()) {
|
|
||||||
uint32_t count =
|
|
||||||
it->second.refCount.fetch_sub(1, std::memory_order_relaxed);
|
|
||||||
return count > 0 ? count - 1 : 0;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取引用计数
|
|
||||||
* @param key 纹理键
|
|
||||||
* @return 引用计数
|
|
||||||
*
|
|
||||||
* 获取指定纹理的当前引用计数
|
|
||||||
*/
|
|
||||||
uint32_t TexturePool::getRefCount(const TextureKey &key) const {
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
|
|
||||||
auto it = cache_.find(key);
|
|
||||||
if (it != cache_.end()) {
|
|
||||||
return it->second.refCount.load(std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 缓存管理
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查纹理是否已缓存
|
|
||||||
* @param key 纹理键
|
|
||||||
* @return 是否已缓存
|
|
||||||
*
|
|
||||||
* 检查指定纹理是否存在于缓存中
|
|
||||||
*/
|
|
||||||
bool TexturePool::isCached(const TextureKey &key) const {
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
return cache_.find(key) != cache_.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从缓存中移除纹理
|
|
||||||
* @param key 纹理键
|
|
||||||
* @return 是否成功
|
|
||||||
*
|
|
||||||
* 从缓存中移除指定的纹理
|
|
||||||
*/
|
|
||||||
bool TexturePool::removeFromCache(const TextureKey &key) {
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
|
|
||||||
auto it = cache_.find(key);
|
|
||||||
if (it != cache_.end()) {
|
|
||||||
currentMemoryUsage_ -= it->second.memorySize;
|
|
||||||
cache_.erase(it);
|
|
||||||
E2D_LOG_DEBUG("TexturePool: 已从缓存移除纹理");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 垃圾回收(移除引用计数为 0 的纹理)
|
|
||||||
* @return 移除的纹理数量
|
|
||||||
*
|
|
||||||
* 清理所有引用计数为0的纹理,释放内存
|
|
||||||
*/
|
|
||||||
size_t TexturePool::collectGarbage() {
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
|
|
||||||
size_t removed = 0;
|
|
||||||
for (auto it = cache_.begin(); it != cache_.end();) {
|
|
||||||
if (it->second.refCount.load(std::memory_order_relaxed) == 0) {
|
|
||||||
currentMemoryUsage_ -= it->second.memorySize;
|
|
||||||
it = cache_.erase(it);
|
|
||||||
++removed;
|
|
||||||
} else {
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (removed > 0) {
|
|
||||||
E2D_LOG_INFO("TexturePool: 垃圾回收 {} 个纹理", removed);
|
|
||||||
}
|
|
||||||
|
|
||||||
return removed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 清空所有缓存
|
|
||||||
*
|
|
||||||
* 移除纹理池中的所有纹理
|
|
||||||
*/
|
|
||||||
void TexturePool::clear() {
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
|
|
||||||
cache_.clear();
|
|
||||||
currentMemoryUsage_ = 0;
|
|
||||||
|
|
||||||
E2D_LOG_INFO("TexturePool: 已清除所有纹理");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 内存管理
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前内存使用量
|
|
||||||
* @return 内存使用量(字节)
|
|
||||||
*
|
|
||||||
* 返回纹理池当前的内存使用量
|
|
||||||
*/
|
|
||||||
size_t TexturePool::getMemoryUsage() const {
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
return currentMemoryUsage_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置最大内存使用量
|
|
||||||
* @param maxMemory 最大内存使用量(0 表示无限制)
|
|
||||||
*
|
|
||||||
* 设置纹理池的内存上限,如果当前使用量超过新上限则执行淘汰
|
|
||||||
*/
|
|
||||||
void TexturePool::setMaxMemoryUsage(size_t maxMemory) {
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
maxMemoryUsage_ = maxMemory;
|
|
||||||
|
|
||||||
// 如果当前内存超过新的限制,执行淘汰
|
|
||||||
if (maxMemoryUsage_ > 0 && currentMemoryUsage_ > maxMemoryUsage_) {
|
|
||||||
evictLRU(maxMemoryUsage_);
|
|
||||||
}
|
|
||||||
|
|
||||||
E2D_LOG_INFO("TexturePool: 最大内存设置为 {} 字节", maxMemory);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 执行 LRU 淘汰
|
|
||||||
* @param targetMemory 目标内存使用量
|
|
||||||
* @return 淘汰的纹理数量
|
|
||||||
*
|
|
||||||
* 根据LRU算法淘汰最少使用的纹理以达到目标内存使用量
|
|
||||||
*/
|
|
||||||
size_t TexturePool::evictLRU(size_t targetMemory) {
|
|
||||||
// 注意:调用者应该已持有锁
|
|
||||||
|
|
||||||
if (cache_.empty()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 收集所有条目并按最后访问时间排序
|
|
||||||
std::vector<std::pair<TextureKey, uint64_t>> entries;
|
|
||||||
entries.reserve(cache_.size());
|
|
||||||
|
|
||||||
for (const auto &pair : cache_) {
|
|
||||||
// 只淘汰引用计数为 0 的纹理
|
|
||||||
if (pair.second.refCount.load(std::memory_order_relaxed) == 0) {
|
|
||||||
entries.emplace_back(pair.first, pair.second.lastAccessTime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 按访问时间升序排序(最旧的在前)
|
|
||||||
std::sort(entries.begin(), entries.end(),
|
|
||||||
[](const auto &a, const auto &b) { return a.second < b.second; });
|
|
||||||
|
|
||||||
size_t evicted = 0;
|
|
||||||
size_t target = targetMemory > 0 ? targetMemory : 0;
|
|
||||||
|
|
||||||
for (const auto &entry : entries) {
|
|
||||||
if (targetMemory > 0 && currentMemoryUsage_ <= target) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto it = cache_.find(entry.first);
|
|
||||||
if (it != cache_.end()) {
|
|
||||||
currentMemoryUsage_ -= it->second.memorySize;
|
|
||||||
cache_.erase(it);
|
|
||||||
++evicted;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (evicted > 0) {
|
|
||||||
evictionCount_.fetch_add(evicted, std::memory_order_relaxed);
|
|
||||||
E2D_LOG_INFO("TexturePool: LRU 淘汰 {} 个纹理", evicted);
|
|
||||||
}
|
|
||||||
|
|
||||||
return evicted;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 统计信息
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取统计信息
|
|
||||||
* @return 统计信息结构体
|
|
||||||
*
|
|
||||||
* 返回纹理池的统计信息,包括纹理数量、内存使用、缓存命中率等
|
|
||||||
*/
|
|
||||||
TexturePool::Stats TexturePool::getStats() const {
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
|
||||||
|
|
||||||
Stats stats;
|
|
||||||
stats.textureCount = cache_.size();
|
|
||||||
stats.memoryUsage = currentMemoryUsage_;
|
|
||||||
stats.maxMemoryUsage = maxMemoryUsage_;
|
|
||||||
stats.cacheHits = cacheHits_.load(std::memory_order_relaxed);
|
|
||||||
stats.cacheMisses = cacheMisses_.load(std::memory_order_relaxed);
|
|
||||||
stats.evictionCount = evictionCount_.load(std::memory_order_relaxed);
|
|
||||||
|
|
||||||
return stats;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 重置统计信息
|
|
||||||
*
|
|
||||||
* 清零缓存命中、未命中和淘汰计数
|
|
||||||
*/
|
|
||||||
void TexturePool::resetStats() {
|
|
||||||
cacheHits_.store(0, std::memory_order_relaxed);
|
|
||||||
cacheMisses_.store(0, std::memory_order_relaxed);
|
|
||||||
evictionCount_.store(0, std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 私有方法
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 计算纹理内存大小
|
|
||||||
* @param texture 纹理对象
|
|
||||||
* @return 内存大小(字节)
|
|
||||||
*
|
|
||||||
* 根据纹理的尺寸、通道数和像素格式计算内存占用
|
|
||||||
*/
|
|
||||||
size_t TexturePool::calculateTextureMemory(const Texture *texture) {
|
|
||||||
if (!texture) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int width = texture->getWidth();
|
|
||||||
int height = texture->getHeight();
|
|
||||||
int channels = texture->getChannels();
|
|
||||||
|
|
||||||
// 基础内存计算
|
|
||||||
size_t baseSize = static_cast<size_t>(width) * height * channels;
|
|
||||||
|
|
||||||
// 根据像素格式调整
|
|
||||||
PixelFormat format = texture->getFormat();
|
|
||||||
switch (format) {
|
|
||||||
case PixelFormat::RGB16F:
|
|
||||||
case PixelFormat::RGBA16F:
|
|
||||||
baseSize *= 2; // 半精度浮点
|
|
||||||
break;
|
|
||||||
case PixelFormat::RGB32F:
|
|
||||||
case PixelFormat::RGBA32F:
|
|
||||||
baseSize *= 4; // 全精度浮点
|
|
||||||
break;
|
|
||||||
case PixelFormat::Depth16:
|
|
||||||
baseSize = static_cast<size_t>(width) * height * 2;
|
|
||||||
break;
|
|
||||||
case PixelFormat::Depth24:
|
|
||||||
case PixelFormat::Depth24Stencil8:
|
|
||||||
baseSize = static_cast<size_t>(width) * height * 4;
|
|
||||||
break;
|
|
||||||
case PixelFormat::Depth32F:
|
|
||||||
baseSize = static_cast<size_t>(width) * height * 4;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 考虑 Mipmaps(大约增加 33% 内存)
|
|
||||||
// 注意:这里假设生成了 mipmaps,实际应该根据 TextureLoadOptions 判断
|
|
||||||
// baseSize = baseSize * 4 / 3;
|
|
||||||
|
|
||||||
return baseSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查是否需要淘汰
|
|
||||||
* @return 是否需要淘汰
|
|
||||||
*
|
|
||||||
* 检查当前内存使用量是否超过限制
|
|
||||||
*/
|
|
||||||
bool TexturePool::needsEviction() const {
|
|
||||||
return maxMemoryUsage_ > 0 && currentMemoryUsage_ > maxMemoryUsage_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 尝试自动淘汰
|
|
||||||
*
|
|
||||||
* 如果内存使用量超过限制,执行LRU淘汰
|
|
||||||
*/
|
|
||||||
void TexturePool::tryAutoEvict() {
|
|
||||||
if (needsEviction()) {
|
|
||||||
evictLRU(maxMemoryUsage_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
#include <extra2d/platform/keys.h>
|
#include <extra2d/platform/keys.h>
|
||||||
#include <extra2d/services/event_service.h>
|
#include <extra2d/services/event_service.h>
|
||||||
#include <extra2d/services/logger_service.h>
|
#include <extra2d/services/logger_service.h>
|
||||||
#include <glad/glad.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
|
|
@ -18,13 +17,8 @@ bool GLFWWindow::create(const std::string &title, int width, int height,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
|
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
|
||||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
|
||||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
|
|
||||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
|
||||||
glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE);
|
glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE);
|
||||||
glfwWindowHint(GLFW_DEPTH_BITS, 24);
|
|
||||||
glfwWindowHint(GLFW_STENCIL_BITS, 8);
|
|
||||||
|
|
||||||
#ifdef __SWITCH__
|
#ifdef __SWITCH__
|
||||||
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
|
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
|
||||||
|
|
@ -43,7 +37,7 @@ bool GLFWWindow::create(const std::string &title, int width, int height,
|
||||||
glfwCreateWindow(width, height, title.c_str(), monitor, nullptr);
|
glfwCreateWindow(width, height, title.c_str(), monitor, nullptr);
|
||||||
|
|
||||||
if (!glfwWindow_) {
|
if (!glfwWindow_) {
|
||||||
E2D_LOG_ERROR("创建 GLFW 窗口失败");
|
E2D_ERROR("创建 GLFW 窗口失败");
|
||||||
deinitGLFW();
|
deinitGLFW();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -65,15 +59,6 @@ bool GLFWWindow::create(const std::string &title, int width, int height,
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
glfwMakeContextCurrent(glfwWindow_);
|
glfwMakeContextCurrent(glfwWindow_);
|
||||||
|
|
||||||
if (!gladLoadGLES2Loader((GLADloadproc)glfwGetProcAddress)) {
|
|
||||||
E2D_LOG_ERROR("初始化 GLAD GLES2 失败");
|
|
||||||
glfwDestroyWindow(glfwWindow_);
|
|
||||||
glfwWindow_ = nullptr;
|
|
||||||
deinitGLFW();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
glfwSwapInterval(vsync ? 1 : 0);
|
glfwSwapInterval(vsync ? 1 : 0);
|
||||||
vsync_ = vsync;
|
vsync_ = vsync;
|
||||||
|
|
||||||
|
|
@ -91,7 +76,7 @@ bool GLFWWindow::create(const std::string &title, int width, int height,
|
||||||
glfwSetKeyCallback(glfwWindow_, keyCallback);
|
glfwSetKeyCallback(glfwWindow_, keyCallback);
|
||||||
glfwSetJoystickCallback(joystickCallback);
|
glfwSetJoystickCallback(joystickCallback);
|
||||||
|
|
||||||
E2D_LOG_INFO("GLFW 窗口创建成功: {}x{}", width_, height_);
|
E2D_INFO("GLFW 窗口创建成功: {}x{}", width_, height_);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -289,7 +274,7 @@ bool GLFWWindow::initGLFW() {
|
||||||
static int glfwInitCount = 0;
|
static int glfwInitCount = 0;
|
||||||
if (glfwInitCount == 0) {
|
if (glfwInitCount == 0) {
|
||||||
if (!glfwInit()) {
|
if (!glfwInit()) {
|
||||||
E2D_LOG_ERROR("初始化 GLFW 失败");
|
E2D_ERROR("初始化 GLFW 失败");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
glfwInitCount++;
|
glfwInitCount++;
|
||||||
|
|
|
||||||
|
|
@ -24,21 +24,21 @@ bool WindowModule::init() {
|
||||||
if (initialized_)
|
if (initialized_)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
E2D_LOG_INFO("正在创建 GLFW 窗口,尺寸 {}x{}", cfg_.w, cfg_.h);
|
E2D_INFO("正在创建 GLFW 窗口,尺寸 {}x{}", cfg_.w, cfg_.h);
|
||||||
|
|
||||||
win_ = makeUnique<GLFWWindow>();
|
win_ = makeUnique<GLFWWindow>();
|
||||||
if (!win_) {
|
if (!win_) {
|
||||||
E2D_LOG_ERROR("创建窗口失败");
|
E2D_ERROR("创建窗口失败");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!win_->create(cfg_.title, cfg_.w, cfg_.h, cfg_.vsync)) {
|
if (!win_->create(cfg_.title, cfg_.w, cfg_.h, cfg_.vsync)) {
|
||||||
E2D_LOG_ERROR("创建窗口失败");
|
E2D_ERROR("创建窗口失败");
|
||||||
shutdown();
|
shutdown();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
E2D_LOG_INFO("窗口创建成功");
|
E2D_INFO("窗口创建成功");
|
||||||
initialized_ = true;
|
initialized_ = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,653 +0,0 @@
|
||||||
#include <algorithm>
|
|
||||||
#include <cmath>
|
|
||||||
#include <extra2d/graphics/core/render_command.h>
|
|
||||||
#include <extra2d/scene/node.h>
|
|
||||||
#include <extra2d/scene/scene.h>
|
|
||||||
#include <extra2d/services/logger_service.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 默认构造函数
|
|
||||||
*
|
|
||||||
* 创建一个空的节点对象
|
|
||||||
*/
|
|
||||||
Node::Node() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 析构函数
|
|
||||||
*
|
|
||||||
* 清除所有子节点
|
|
||||||
*/
|
|
||||||
Node::~Node() { clearChildren(); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 添加子节点
|
|
||||||
* @param child 要添加的子节点智能指针
|
|
||||||
*
|
|
||||||
* 将子节点添加到当前节点的子节点列表中,自动从原父节点分离
|
|
||||||
*/
|
|
||||||
void Node::addChild(Ptr<Node> child) {
|
|
||||||
if (!child || child.get() == this) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
child->detach();
|
|
||||||
child->parent_ = weak_from_this();
|
|
||||||
children_.push_back(child);
|
|
||||||
childrenOrderDirty_ = true;
|
|
||||||
|
|
||||||
// 更新索引
|
|
||||||
if (!child->getName().empty()) {
|
|
||||||
nameIndex_[child->getName()] = child;
|
|
||||||
}
|
|
||||||
if (child->getTag() != -1) {
|
|
||||||
tagIndex_[child->getTag()] = child;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (running_) {
|
|
||||||
child->onEnter();
|
|
||||||
if (scene_) {
|
|
||||||
child->onAttachToScene(scene_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 批量添加子节点
|
|
||||||
* @param children 要添加的子节点数组(右值引用)
|
|
||||||
*
|
|
||||||
* 高效地批量添加多个子节点,预分配内存以减少扩容次数
|
|
||||||
*/
|
|
||||||
void Node::addChildren(std::vector<Ptr<Node>> &&children) {
|
|
||||||
// 预留空间,避免多次扩容
|
|
||||||
size_t newSize = children_.size() + children.size();
|
|
||||||
if (newSize > children_.capacity()) {
|
|
||||||
children_.reserve(newSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto &child : children) {
|
|
||||||
if (!child || child.get() == this) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
child->detach();
|
|
||||||
child->parent_ = weak_from_this();
|
|
||||||
children_.push_back(child);
|
|
||||||
|
|
||||||
// 更新索引
|
|
||||||
if (!child->getName().empty()) {
|
|
||||||
nameIndex_[child->getName()] = child;
|
|
||||||
}
|
|
||||||
if (child->getTag() != -1) {
|
|
||||||
tagIndex_[child->getTag()] = child;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (running_) {
|
|
||||||
child->onEnter();
|
|
||||||
if (scene_) {
|
|
||||||
child->onAttachToScene(scene_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!children.empty()) {
|
|
||||||
childrenOrderDirty_ = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 移除子节点
|
|
||||||
* @param child 要移除的子节点智能指针
|
|
||||||
*
|
|
||||||
* 从子节点列表中移除指定节点,并触发相应的退出回调
|
|
||||||
*/
|
|
||||||
void Node::removeChild(Ptr<Node> child) {
|
|
||||||
if (!child)
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto it = std::find(children_.begin(), children_.end(), child);
|
|
||||||
if (it != children_.end()) {
|
|
||||||
(*it)->onDetachFromScene();
|
|
||||||
|
|
||||||
if (running_) {
|
|
||||||
(*it)->onExit();
|
|
||||||
}
|
|
||||||
if (!(*it)->getName().empty()) {
|
|
||||||
nameIndex_.erase((*it)->getName());
|
|
||||||
}
|
|
||||||
if ((*it)->getTag() != -1) {
|
|
||||||
tagIndex_.erase((*it)->getTag());
|
|
||||||
}
|
|
||||||
(*it)->parent_.reset();
|
|
||||||
children_.erase(it);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 通过名称移除子节点
|
|
||||||
* @param name 子节点的名称
|
|
||||||
*
|
|
||||||
* 查找并移除具有指定名称的子节点
|
|
||||||
*/
|
|
||||||
void Node::removeChildByName(const std::string &name) {
|
|
||||||
auto child = findChild(name);
|
|
||||||
if (child) {
|
|
||||||
removeChild(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从父节点分离
|
|
||||||
*
|
|
||||||
* 将当前节点从其父节点的子节点列表中移除
|
|
||||||
*/
|
|
||||||
void Node::detach() {
|
|
||||||
auto p = parent_.lock();
|
|
||||||
if (p) {
|
|
||||||
// 安全获取 shared_ptr,避免在对象未由 shared_ptr 管理时崩溃
|
|
||||||
Ptr<Node> self;
|
|
||||||
try {
|
|
||||||
self = shared_from_this();
|
|
||||||
} catch (const std::bad_weak_ptr &) {
|
|
||||||
// 对象不是由 shared_ptr 管理的,直接重置父节点引用
|
|
||||||
parent_.reset();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
p->removeChild(self);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 清除所有子节点
|
|
||||||
*
|
|
||||||
* 移除所有子节点并触发相应的退出回调
|
|
||||||
*/
|
|
||||||
void Node::clearChildren() {
|
|
||||||
for (auto &child : children_) {
|
|
||||||
if (running_) {
|
|
||||||
child->onDetachFromScene();
|
|
||||||
child->onExit();
|
|
||||||
}
|
|
||||||
child->parent_.reset();
|
|
||||||
}
|
|
||||||
children_.clear();
|
|
||||||
nameIndex_.clear();
|
|
||||||
tagIndex_.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 通过名称查找子节点
|
|
||||||
* @param name 子节点的名称
|
|
||||||
* @return 找到的子节点智能指针,未找到返回nullptr
|
|
||||||
*
|
|
||||||
* 使用哈希索引进行O(1)时间复杂度查找
|
|
||||||
*/
|
|
||||||
Ptr<Node> Node::findChild(const std::string &name) const {
|
|
||||||
// 使用哈希索引,O(1) 查找
|
|
||||||
auto it = nameIndex_.find(name);
|
|
||||||
if (it != nameIndex_.end()) {
|
|
||||||
return it->second.lock();
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 通过标签查找子节点
|
|
||||||
* @param tag 子节点的标签值
|
|
||||||
* @return 找到的子节点智能指针,未找到返回nullptr
|
|
||||||
*
|
|
||||||
* 使用哈希索引进行O(1)时间复杂度查找
|
|
||||||
*/
|
|
||||||
Ptr<Node> Node::findChildByTag(int tag) const {
|
|
||||||
// 使用哈希索引,O(1) 查找
|
|
||||||
auto it = tagIndex_.find(tag);
|
|
||||||
if (it != tagIndex_.end()) {
|
|
||||||
return it->second.lock();
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置节点位置
|
|
||||||
* @param pos 新的位置坐标
|
|
||||||
*/
|
|
||||||
void Node::setPos(const Vec2 &pos) {
|
|
||||||
position_ = pos;
|
|
||||||
markTransformDirty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置节点位置
|
|
||||||
* @param x X坐标
|
|
||||||
* @param y Y坐标
|
|
||||||
*/
|
|
||||||
void Node::setPos(float x, float y) { setPos(Vec2(x, y)); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置节点旋转角度
|
|
||||||
* @param degrees 旋转角度(度数)
|
|
||||||
*/
|
|
||||||
void Node::setRotation(float degrees) {
|
|
||||||
rotation_ = degrees;
|
|
||||||
markTransformDirty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置节点缩放
|
|
||||||
* @param scale 缩放向量
|
|
||||||
*/
|
|
||||||
void Node::setScale(const Vec2 &scale) {
|
|
||||||
scale_ = scale;
|
|
||||||
markTransformDirty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置节点统一缩放
|
|
||||||
* @param scale 统一缩放值
|
|
||||||
*/
|
|
||||||
void Node::setScale(float scale) { setScale(Vec2(scale, scale)); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置节点缩放
|
|
||||||
* @param x X轴缩放值
|
|
||||||
* @param y Y轴缩放值
|
|
||||||
*/
|
|
||||||
void Node::setScale(float x, float y) { setScale(Vec2(x, y)); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置节点锚点
|
|
||||||
* @param anchor 锚点位置(0-1范围)
|
|
||||||
*/
|
|
||||||
void Node::setAnchor(const Vec2 &anchor) {
|
|
||||||
anchor_ = anchor;
|
|
||||||
markTransformDirty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置节点锚点
|
|
||||||
* @param x 锚点X坐标(0-1范围)
|
|
||||||
* @param y 锚点Y坐标(0-1范围)
|
|
||||||
*/
|
|
||||||
void Node::setAnchor(float x, float y) { setAnchor(Vec2(x, y)); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置节点斜切
|
|
||||||
* @param skew 斜切角度向量
|
|
||||||
*/
|
|
||||||
void Node::setSkew(const Vec2 &skew) {
|
|
||||||
skew_ = skew;
|
|
||||||
markTransformDirty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置节点斜切
|
|
||||||
* @param x X轴斜切角度
|
|
||||||
* @param y Y轴斜切角度
|
|
||||||
*/
|
|
||||||
void Node::setSkew(float x, float y) { setSkew(Vec2(x, y)); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置节点透明度
|
|
||||||
* @param opacity 透明度值(0.0-1.0范围)
|
|
||||||
*/
|
|
||||||
void Node::setOpacity(float opacity) {
|
|
||||||
opacity_ = std::clamp(opacity, 0.0f, 1.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置节点可见性
|
|
||||||
* @param visible 是否可见
|
|
||||||
*/
|
|
||||||
void Node::setVisible(bool visible) { visible_ = visible; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置节点颜色
|
|
||||||
* @param color RGB颜色值
|
|
||||||
*/
|
|
||||||
void Node::setColor(const Color3B &color) { color_ = color; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置水平翻转
|
|
||||||
* @param flipX 是否水平翻转
|
|
||||||
*/
|
|
||||||
void Node::setFlipX(bool flipX) { flipX_ = flipX; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置垂直翻转
|
|
||||||
* @param flipY 是否垂直翻转
|
|
||||||
*/
|
|
||||||
void Node::setFlipY(bool flipY) { flipY_ = flipY; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置Z序
|
|
||||||
* @param zOrder 渲染层级顺序
|
|
||||||
*
|
|
||||||
* 较大的Z序值会在上层渲染
|
|
||||||
*/
|
|
||||||
void Node::setZOrder(int zOrder) {
|
|
||||||
if (zOrder_ != zOrder) {
|
|
||||||
zOrder_ = zOrder;
|
|
||||||
childrenOrderDirty_ = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 将本地坐标转换为世界坐标
|
|
||||||
* @param localPos 本地坐标位置
|
|
||||||
* @return 世界坐标位置
|
|
||||||
*/
|
|
||||||
Vec2 Node::toWorld(const Vec2 &localPos) const {
|
|
||||||
glm::vec4 worldPos =
|
|
||||||
getWorldTransform() * glm::vec4(localPos.x, localPos.y, 0.0f, 1.0f);
|
|
||||||
return Vec2(worldPos.x, worldPos.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 将世界坐标转换为本地坐标
|
|
||||||
* @param worldPos 世界坐标位置
|
|
||||||
* @return 本地坐标位置
|
|
||||||
*/
|
|
||||||
Vec2 Node::toLocal(const Vec2 &worldPos) const {
|
|
||||||
glm::mat4 invWorld = glm::inverse(getWorldTransform());
|
|
||||||
glm::vec4 localPos = invWorld * glm::vec4(worldPos.x, worldPos.y, 0.0f, 1.0f);
|
|
||||||
return Vec2(localPos.x, localPos.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取本地变换矩阵
|
|
||||||
* @return 本地变换矩阵
|
|
||||||
*
|
|
||||||
* 计算包含位置、旋转、斜切和缩放的本地变换矩阵
|
|
||||||
*/
|
|
||||||
glm::mat4 Node::getLocalTransform() const {
|
|
||||||
if (transformDirty_) {
|
|
||||||
localTransform_ = glm::mat4(1.0f);
|
|
||||||
|
|
||||||
// T - R - S order
|
|
||||||
localTransform_ = glm::translate(localTransform_,
|
|
||||||
glm::vec3(position_.x, position_.y, 0.0f));
|
|
||||||
|
|
||||||
if (rotation_ != 0.0f) {
|
|
||||||
localTransform_ = glm::rotate(localTransform_, rotation_ * DEG_TO_RAD,
|
|
||||||
glm::vec3(0.0f, 0.0f, 1.0f));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (skew_.x != 0.0f || skew_.y != 0.0f) {
|
|
||||||
glm::mat4 skewMatrix(1.0f);
|
|
||||||
skewMatrix[1][0] = std::tan(skew_.x * DEG_TO_RAD);
|
|
||||||
skewMatrix[0][1] = std::tan(skew_.y * DEG_TO_RAD);
|
|
||||||
localTransform_ *= skewMatrix;
|
|
||||||
}
|
|
||||||
|
|
||||||
localTransform_ =
|
|
||||||
glm::scale(localTransform_, glm::vec3(scale_.x, scale_.y, 1.0f));
|
|
||||||
|
|
||||||
// 注意:锚点偏移在渲染时处理,不在本地变换中处理
|
|
||||||
// 这样可以避免锚点偏移被父节点的缩放影响
|
|
||||||
|
|
||||||
transformDirty_ = false;
|
|
||||||
}
|
|
||||||
return localTransform_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取世界变换矩阵
|
|
||||||
* @return 世界变换矩阵
|
|
||||||
*
|
|
||||||
* 计算从根节点到当前节点的累积变换矩阵
|
|
||||||
*/
|
|
||||||
glm::mat4 Node::getWorldTransform() const {
|
|
||||||
if (worldTransformDirty_) {
|
|
||||||
// 使用线程局部存储的固定数组,避免每帧内存分配
|
|
||||||
// 限制最大深度为 256 层,足以覆盖绝大多数场景
|
|
||||||
thread_local std::array<const Node *, 256> nodeChainCache;
|
|
||||||
thread_local size_t chainCount = 0;
|
|
||||||
|
|
||||||
chainCount = 0;
|
|
||||||
const Node *current = this;
|
|
||||||
while (current && chainCount < nodeChainCache.size()) {
|
|
||||||
nodeChainCache[chainCount++] = current;
|
|
||||||
auto p = current->parent_.lock();
|
|
||||||
current = p.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从根节点开始计算
|
|
||||||
glm::mat4 transform = glm::mat4(1.0f);
|
|
||||||
for (size_t i = chainCount; i > 0; --i) {
|
|
||||||
transform = transform * nodeChainCache[i - 1]->getLocalTransform();
|
|
||||||
}
|
|
||||||
worldTransform_ = transform;
|
|
||||||
worldTransformDirty_ = false;
|
|
||||||
}
|
|
||||||
return worldTransform_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 标记变换为脏
|
|
||||||
*
|
|
||||||
* 标记本地变换和世界变换需要重新计算,并递归标记所有子节点
|
|
||||||
*/
|
|
||||||
void Node::markTransformDirty() {
|
|
||||||
// 避免重复标记,提高性能
|
|
||||||
if (!transformDirty_ || !worldTransformDirty_) {
|
|
||||||
transformDirty_ = true;
|
|
||||||
worldTransformDirty_ = true;
|
|
||||||
|
|
||||||
// 递归标记所有子节点
|
|
||||||
for (auto &child : children_) {
|
|
||||||
child->markTransformDirty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 批量更新变换
|
|
||||||
*
|
|
||||||
* 从父节点到子节点依次更新世界变换矩阵
|
|
||||||
*/
|
|
||||||
void Node::batchTransforms() {
|
|
||||||
// 如果本地变换脏了,先计算本地变换
|
|
||||||
if (transformDirty_) {
|
|
||||||
(void)getLocalTransform(); // 这会计算并缓存本地变换
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果世界变换脏了,需要重新计算
|
|
||||||
if (worldTransformDirty_) {
|
|
||||||
auto parent = parent_.lock();
|
|
||||||
if (parent) {
|
|
||||||
// 使用父节点的世界变换(确保父节点已经更新)
|
|
||||||
worldTransform_ = parent->getWorldTransform() * localTransform_;
|
|
||||||
} else {
|
|
||||||
// 根节点
|
|
||||||
worldTransform_ = localTransform_;
|
|
||||||
}
|
|
||||||
worldTransformDirty_ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 递归更新子节点
|
|
||||||
for (auto &child : children_) {
|
|
||||||
child->batchTransforms();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 节点进入时的回调
|
|
||||||
*
|
|
||||||
* 标记节点为运行状态,并递归调用所有子节点的onEnter
|
|
||||||
*/
|
|
||||||
void Node::onEnter() {
|
|
||||||
running_ = true;
|
|
||||||
for (auto &child : children_) {
|
|
||||||
child->onEnter();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 节点退出时的回调
|
|
||||||
*
|
|
||||||
* 标记节点为非运行状态,并递归调用所有子节点的onExit
|
|
||||||
*/
|
|
||||||
void Node::onExit() {
|
|
||||||
running_ = false;
|
|
||||||
for (auto &child : children_) {
|
|
||||||
child->onExit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 更新回调
|
|
||||||
* @param dt 帧间隔时间(秒)
|
|
||||||
*
|
|
||||||
* 先调用节点自身的更新逻辑,再更新所有子节点
|
|
||||||
*/
|
|
||||||
void Node::onUpdate(float dt) {
|
|
||||||
onUpdateNode(dt);
|
|
||||||
|
|
||||||
// Update children
|
|
||||||
for (auto &child : children_) {
|
|
||||||
child->onUpdate(dt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 渲染节点及其子节点
|
|
||||||
* @param renderer 渲染器引用
|
|
||||||
*
|
|
||||||
* 如果可见则绘制自身,然后递归渲染所有子节点
|
|
||||||
*/
|
|
||||||
void Node::onRender(Renderer &renderer) {
|
|
||||||
if (!visible_)
|
|
||||||
return;
|
|
||||||
|
|
||||||
renderer.pushTransform(getLocalTransform());
|
|
||||||
|
|
||||||
onDraw(renderer);
|
|
||||||
|
|
||||||
for (auto &child : children_) {
|
|
||||||
child->onRender(renderer);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderer.popTransform();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 附加到场景时的回调
|
|
||||||
* @param scene 所属场景指针
|
|
||||||
*
|
|
||||||
* 设置场景引用并递归通知所有子节点
|
|
||||||
*/
|
|
||||||
void Node::onAttachToScene(Scene *scene) {
|
|
||||||
scene_ = scene;
|
|
||||||
|
|
||||||
for (auto &child : children_) {
|
|
||||||
child->onAttachToScene(scene);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从场景分离时的回调
|
|
||||||
*
|
|
||||||
* 清除场景引用并递归通知所有子节点
|
|
||||||
*/
|
|
||||||
void Node::onDetachFromScene() {
|
|
||||||
scene_ = nullptr;
|
|
||||||
for (auto &child : children_) {
|
|
||||||
child->onDetachFromScene();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取节点边界矩形
|
|
||||||
* @return 节点的边界矩形
|
|
||||||
*
|
|
||||||
* 默认返回以位置为中心的空矩形,子类应重写此方法
|
|
||||||
*/
|
|
||||||
Rect Node::getBounds() const { return Rect(position_.x, position_.y, 0, 0); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 更新节点
|
|
||||||
* @param dt 帧间隔时间(秒)
|
|
||||||
*
|
|
||||||
* 调用onUpdate进行更新
|
|
||||||
*/
|
|
||||||
void Node::update(float dt) { onUpdate(dt); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 渲染节点
|
|
||||||
* @param renderer 渲染后端引用
|
|
||||||
*
|
|
||||||
* 如果需要则对子节点排序,然后调用onRender进行渲染
|
|
||||||
*/
|
|
||||||
void Node::render(Renderer &renderer) {
|
|
||||||
if (childrenOrderDirty_) {
|
|
||||||
sortChildren();
|
|
||||||
}
|
|
||||||
onRender(renderer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 对子节点按Z序排序
|
|
||||||
*
|
|
||||||
* 小数组使用插入排序,大数组使用标准排序以优化性能
|
|
||||||
*/
|
|
||||||
void Node::sortChildren() {
|
|
||||||
// 使用插入排序优化小范围更新场景
|
|
||||||
// 插入排序在大部分已有序的情况下性能接近O(n)
|
|
||||||
size_t n = children_.size();
|
|
||||||
if (n <= 1) {
|
|
||||||
childrenOrderDirty_ = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 小数组使用插入排序,大数组使用std::sort
|
|
||||||
if (n < 32) {
|
|
||||||
// 插入排序
|
|
||||||
for (size_t i = 1; i < n; ++i) {
|
|
||||||
auto key = children_[i];
|
|
||||||
int keyZOrder = key->getZOrder();
|
|
||||||
int j = static_cast<int>(i) - 1;
|
|
||||||
|
|
||||||
while (j >= 0 && children_[j]->getZOrder() > keyZOrder) {
|
|
||||||
children_[j + 1] = children_[j];
|
|
||||||
--j;
|
|
||||||
}
|
|
||||||
children_[j + 1] = key;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 大数组使用标准排序
|
|
||||||
std::sort(children_.begin(), children_.end(),
|
|
||||||
[](const Ptr<Node> &a, const Ptr<Node> &b) {
|
|
||||||
return a->getZOrder() < b->getZOrder();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
childrenOrderDirty_ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 收集渲染命令
|
|
||||||
* @param commands 渲染命令输出向量
|
|
||||||
* @param parentZOrder 父节点的Z序
|
|
||||||
*
|
|
||||||
* 递归收集当前节点和所有子节点的渲染命令
|
|
||||||
*/
|
|
||||||
void Node::collectRenderCommands(std::vector<RenderCommand> &commands,
|
|
||||||
int parentZOrder) {
|
|
||||||
if (!visible_)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// 计算累积 Z 序
|
|
||||||
int accumulatedZOrder = parentZOrder + zOrder_;
|
|
||||||
|
|
||||||
// 生成当前节点的渲染命令
|
|
||||||
generateRenderCommand(commands, accumulatedZOrder);
|
|
||||||
|
|
||||||
// 递归收集子节点的渲染命令
|
|
||||||
// 注意:这里假设子节点已经按 Z 序排序
|
|
||||||
for (auto &child : children_) {
|
|
||||||
child->collectRenderCommands(commands, accumulatedZOrder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,128 +0,0 @@
|
||||||
#include <extra2d/graphics/core/render_command.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_renderer.h>
|
|
||||||
#include <extra2d/scene/scene.h>
|
|
||||||
#include <extra2d/services/logger_service.h>
|
|
||||||
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 构造函数,初始化场景对象
|
|
||||||
*
|
|
||||||
* 创建默认相机实例
|
|
||||||
*/
|
|
||||||
Scene::Scene() { defaultCamera_ = makePtr<Camera>(); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置场景相机
|
|
||||||
* @param camera 要设置的相机智能指针
|
|
||||||
*/
|
|
||||||
void Scene::setCamera(Ptr<Camera> camera) { camera_ = camera; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置视口大小
|
|
||||||
* @param width 视口宽度
|
|
||||||
* @param height 视口高度
|
|
||||||
*
|
|
||||||
* 同时更新活动相机的视口参数
|
|
||||||
*/
|
|
||||||
void Scene::setViewportSize(float width, float height) {
|
|
||||||
viewportSize_ = Size(width, height);
|
|
||||||
if (defaultCamera_) {
|
|
||||||
defaultCamera_->setViewport(0, width, height, 0);
|
|
||||||
} else if (camera_) {
|
|
||||||
camera_->setViewport(0, width, height, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置视口大小
|
|
||||||
* @param size 视口尺寸结构体
|
|
||||||
*/
|
|
||||||
void Scene::setViewportSize(const Size &size) {
|
|
||||||
setViewportSize(size.width, size.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 渲染场景
|
|
||||||
* @param renderer 渲染后端引用
|
|
||||||
*
|
|
||||||
* 如果场景不可见则直接返回,否则开始帧渲染、渲染内容并结束帧
|
|
||||||
*/
|
|
||||||
void Scene::renderScene(Renderer &renderer) {
|
|
||||||
if (!isVisible())
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Begin frame with background color
|
|
||||||
renderer.beginFrame(backgroundColor_);
|
|
||||||
renderContent(renderer);
|
|
||||||
renderer.endFrame();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 渲染场景内容
|
|
||||||
* @param renderer 渲染后端引用
|
|
||||||
*
|
|
||||||
* 批量更新节点变换,开始精灵批处理并渲染
|
|
||||||
* 注意:视图投影矩阵由 Application 通过 CameraService 设置
|
|
||||||
*/
|
|
||||||
void Scene::renderContent(Renderer &renderer) {
|
|
||||||
if (!isVisible())
|
|
||||||
return;
|
|
||||||
|
|
||||||
batchTransforms();
|
|
||||||
|
|
||||||
renderer.beginSpriteBatch();
|
|
||||||
render(renderer);
|
|
||||||
renderer.endSpriteBatch();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 更新场景
|
|
||||||
* @param dt 帧间隔时间(秒)
|
|
||||||
*
|
|
||||||
* 如果场景未暂停则调用update方法
|
|
||||||
*/
|
|
||||||
void Scene::updateScene(float dt) {
|
|
||||||
if (!paused_) {
|
|
||||||
update(dt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 场景进入时的回调函数
|
|
||||||
*
|
|
||||||
* 调用父类Node的onEnter方法
|
|
||||||
*/
|
|
||||||
void Scene::onEnter() { Node::onEnter(); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 场景退出时的回调函数
|
|
||||||
*
|
|
||||||
* 调用父类Node的onExit方法
|
|
||||||
*/
|
|
||||||
void Scene::onExit() { Node::onExit(); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 收集渲染命令
|
|
||||||
* @param commands 渲染命令输出向量
|
|
||||||
* @param parentZOrder 父节点的Z序
|
|
||||||
*
|
|
||||||
* 如果场景不可见则直接返回,否则从场景的子节点开始收集渲染命令
|
|
||||||
*/
|
|
||||||
void Scene::collectRenderCommands(std::vector<RenderCommand> &commands,
|
|
||||||
int parentZOrder) {
|
|
||||||
if (!isVisible())
|
|
||||||
return;
|
|
||||||
|
|
||||||
// 从场景的子节点开始收集渲染命令
|
|
||||||
Node::collectRenderCommands(commands, parentZOrder);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建场景对象
|
|
||||||
* @return 新创建的场景智能指针
|
|
||||||
*/
|
|
||||||
Ptr<Scene> Scene::create() { return makePtr<Scene>(); }
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,888 +0,0 @@
|
||||||
#include <algorithm>
|
|
||||||
#include <extra2d/app/application.h>
|
|
||||||
#include <extra2d/graphics/core/render_command.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_renderer.h>
|
|
||||||
#include <extra2d/platform/keys.h>
|
|
||||||
#include <extra2d/scene/scene_manager.h>
|
|
||||||
#include <extra2d/scene/transition_box_scene.h>
|
|
||||||
#include <extra2d/scene/transition_fade_scene.h>
|
|
||||||
#include <extra2d/scene/transition_flip_scene.h>
|
|
||||||
#include <extra2d/scene/transition_scale_scene.h>
|
|
||||||
#include <extra2d/scene/transition_scene.h>
|
|
||||||
#include <extra2d/scene/transition_slide_scene.h>
|
|
||||||
#include <extra2d/services/event_service.h>
|
|
||||||
#include <extra2d/services/logger_service.h>
|
|
||||||
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 命中测试 - 从节点树中找到最上层的可交互节点
|
|
||||||
* @param node 要测试的节点
|
|
||||||
* @param worldPos 世界坐标位置
|
|
||||||
* @return 命中的节点指针,未命中返回nullptr
|
|
||||||
*/
|
|
||||||
Node *hitTestTopmost(const Ptr<Node> &node, const Vec2 &worldPos) {
|
|
||||||
if (!node || !node->isVisible()) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Ptr<Node>> children = node->getChildren();
|
|
||||||
std::stable_sort(children.begin(), children.end(),
|
|
||||||
[](const Ptr<Node> &a, const Ptr<Node> &b) {
|
|
||||||
return a->getZOrder() < b->getZOrder();
|
|
||||||
});
|
|
||||||
|
|
||||||
for (auto it = children.rbegin(); it != children.rend(); ++it) {
|
|
||||||
if (Node *hit = hitTestTopmost(*it, worldPos)) {
|
|
||||||
return hit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node->getEventDispatcher().getTotalListenerCount() == 0) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
Rect bounds = node->getBounds();
|
|
||||||
if (!bounds.empty() && bounds.containsPoint(worldPos)) {
|
|
||||||
return node.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 向节点分发事件
|
|
||||||
* @param node 目标节点
|
|
||||||
* @param event 要分发的事件
|
|
||||||
*/
|
|
||||||
void dispatchToNode(Node *node, Event &event) {
|
|
||||||
if (!node) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
node->getEventDispatcher().dispatch(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取场景管理器单例
|
|
||||||
* @return 场景管理器的全局唯一实例引用
|
|
||||||
*/
|
|
||||||
SceneManager &SceneManager::get() {
|
|
||||||
static SceneManager instance;
|
|
||||||
static bool initialized = false;
|
|
||||||
if (!initialized) {
|
|
||||||
instance.setupEventListeners();
|
|
||||||
initialized = true;
|
|
||||||
}
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置事件监听器
|
|
||||||
*/
|
|
||||||
void SceneManager::setupEventListeners() {
|
|
||||||
auto eventService = ServiceLocator::instance().getService<IEventService>();
|
|
||||||
if (!eventService) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mouseMoveListener_ =
|
|
||||||
eventService->addListener(EventType::MouseMoved, [this](Event &e) {
|
|
||||||
auto &mouseEvent = std::get<MouseMoveEvent>(e.data);
|
|
||||||
mousePos_ = mouseEvent.position;
|
|
||||||
mouseDelta_ = mouseEvent.delta;
|
|
||||||
});
|
|
||||||
|
|
||||||
mousePressListener_ = eventService->addListener(
|
|
||||||
EventType::MouseButtonPressed, [this](Event &e) {
|
|
||||||
auto &mouseEvent = std::get<MouseButtonEvent>(e.data);
|
|
||||||
if (mouseEvent.button == static_cast<int>(Mouse::Left)) {
|
|
||||||
mouseLeftPressed_ = true;
|
|
||||||
mouseLeftDown_ = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
mouseReleaseListener_ = eventService->addListener(
|
|
||||||
EventType::MouseButtonReleased, [this](Event &e) {
|
|
||||||
auto &mouseEvent = std::get<MouseButtonEvent>(e.data);
|
|
||||||
if (mouseEvent.button == static_cast<int>(Mouse::Left)) {
|
|
||||||
mouseLeftReleased_ = true;
|
|
||||||
mouseLeftDown_ = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
scrollListener_ =
|
|
||||||
eventService->addListener(EventType::MouseScrolled, [this](Event &e) {
|
|
||||||
auto &scrollEvent = std::get<MouseScrollEvent>(e.data);
|
|
||||||
scrollDelta_ = scrollEvent.offset.y;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 运行指定场景
|
|
||||||
* @param scene 要运行的场景智能指针
|
|
||||||
*
|
|
||||||
* 此方法应在应用启动时调用一次,设置初始场景
|
|
||||||
*/
|
|
||||||
void SceneManager::runWithScene(Ptr<Scene> scene) {
|
|
||||||
if (!scene) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sceneStack_.empty()) {
|
|
||||||
E2D_LOG_WARN("SceneManager: runWithScene 应该只被调用一次");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
scene->onEnter();
|
|
||||||
scene->onAttachToScene(scene.get());
|
|
||||||
sceneStack_.push(scene);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 替换当前场景
|
|
||||||
* @param scene 新场景智能指针
|
|
||||||
*
|
|
||||||
* 移除当前场景并替换为新场景,场景栈大小保持不变
|
|
||||||
*/
|
|
||||||
void SceneManager::replaceScene(Ptr<Scene> scene) {
|
|
||||||
if (!scene || isTransitioning_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sceneStack_.empty()) {
|
|
||||||
runWithScene(scene);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto oldScene = sceneStack_.top();
|
|
||||||
oldScene->onExit();
|
|
||||||
oldScene->onDetachFromScene();
|
|
||||||
sceneStack_.pop();
|
|
||||||
|
|
||||||
scene->onEnter();
|
|
||||||
scene->onAttachToScene(scene.get());
|
|
||||||
sceneStack_.push(scene);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 替换当前场景(带过渡效果)
|
|
||||||
* @param scene 新场景
|
|
||||||
* @param transition 过渡类型
|
|
||||||
* @param duration 过渡持续时间
|
|
||||||
*/
|
|
||||||
void SceneManager::replaceScene(Ptr<Scene> scene, TransitionType transition,
|
|
||||||
float duration) {
|
|
||||||
if (!scene || isTransitioning_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sceneStack_.empty()) {
|
|
||||||
runWithScene(scene);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto oldScene = sceneStack_.top();
|
|
||||||
|
|
||||||
startTransition(oldScene, scene, transition, duration, [this]() {
|
|
||||||
if (!sceneStack_.empty() && activeTransitionScene_) {
|
|
||||||
if (sceneStack_.top().get() == activeTransitionScene_.get()) {
|
|
||||||
sceneStack_.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto outScene = activeTransitionScene_->getOutScene();
|
|
||||||
if (outScene) {
|
|
||||||
outScene->onExit();
|
|
||||||
outScene->onDetachFromScene();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activeTransitionScene_) {
|
|
||||||
auto inScene = activeTransitionScene_->getInScene();
|
|
||||||
if (inScene) {
|
|
||||||
inScene->onAttachToScene(inScene.get());
|
|
||||||
sceneStack_.push(inScene);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 进入场景
|
|
||||||
* @param scene 要进入的场景智能指针
|
|
||||||
*
|
|
||||||
* 如果场景栈为空则运行场景,否则替换当前场景
|
|
||||||
*/
|
|
||||||
void SceneManager::enterScene(Ptr<Scene> scene) {
|
|
||||||
if (!scene || isTransitioning_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sceneStack_.empty()) {
|
|
||||||
runWithScene(scene);
|
|
||||||
} else {
|
|
||||||
replaceScene(scene);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 使用自定义过渡场景进入场景
|
|
||||||
* @param scene 新场景
|
|
||||||
* @param transitionScene 过渡场景
|
|
||||||
*/
|
|
||||||
void SceneManager::enterScene(Ptr<Scene> scene,
|
|
||||||
Ptr<TransitionScene> transitionScene) {
|
|
||||||
if (!scene || isTransitioning_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!transitionScene) {
|
|
||||||
enterScene(scene);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto current = getCurrentScene();
|
|
||||||
if (!current) {
|
|
||||||
enterScene(scene);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hoverTarget_) {
|
|
||||||
Event evt;
|
|
||||||
evt.type = EventType::UIHoverExit;
|
|
||||||
evt.data = CustomEvent{0, hoverTarget_};
|
|
||||||
dispatchToNode(hoverTarget_, evt);
|
|
||||||
hoverTarget_ = nullptr;
|
|
||||||
}
|
|
||||||
captureTarget_ = nullptr;
|
|
||||||
hasLastPointerWorld_ = false;
|
|
||||||
|
|
||||||
transitionScene->setOutScene(current);
|
|
||||||
transitionScene->setFinishCallback([this]() { finishTransition(); });
|
|
||||||
|
|
||||||
current->pause();
|
|
||||||
|
|
||||||
transitionScene->onEnter();
|
|
||||||
transitionScene->onAttachToScene(transitionScene.get());
|
|
||||||
sceneStack_.push(transitionScene);
|
|
||||||
|
|
||||||
isTransitioning_ = true;
|
|
||||||
activeTransitionScene_ = transitionScene;
|
|
||||||
transitionStackAction_ = [this, transitionScene]() {
|
|
||||||
auto outScene = transitionScene->getOutScene();
|
|
||||||
if (!sceneStack_.empty() && outScene) {
|
|
||||||
if (sceneStack_.top().get() == transitionScene.get()) {
|
|
||||||
sceneStack_.pop();
|
|
||||||
}
|
|
||||||
outScene->onExit();
|
|
||||||
outScene->onDetachFromScene();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto inScene = transitionScene->getInScene();
|
|
||||||
if (inScene) {
|
|
||||||
inScene->onAttachToScene(inScene.get());
|
|
||||||
sceneStack_.push(inScene);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 压入场景到栈顶
|
|
||||||
* @param scene 要压入的场景智能指针
|
|
||||||
*
|
|
||||||
* 将新场景压入栈顶,暂停当前场景
|
|
||||||
*/
|
|
||||||
void SceneManager::pushScene(Ptr<Scene> scene) {
|
|
||||||
if (!scene || isTransitioning_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sceneStack_.empty()) {
|
|
||||||
sceneStack_.top()->pause();
|
|
||||||
}
|
|
||||||
|
|
||||||
scene->onEnter();
|
|
||||||
scene->onAttachToScene(scene.get());
|
|
||||||
sceneStack_.push(scene);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 压入场景到栈顶(带过渡效果)
|
|
||||||
* @param scene 新场景
|
|
||||||
* @param transition 过渡类型
|
|
||||||
* @param duration 过渡持续时间
|
|
||||||
*/
|
|
||||||
void SceneManager::pushScene(Ptr<Scene> scene, TransitionType transition,
|
|
||||||
float duration) {
|
|
||||||
if (!scene || isTransitioning_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sceneStack_.empty()) {
|
|
||||||
runWithScene(scene);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
sceneStack_.top()->pause();
|
|
||||||
|
|
||||||
auto currentScene = sceneStack_.top();
|
|
||||||
|
|
||||||
startTransition(currentScene, scene, transition, duration, [this]() {
|
|
||||||
if (!sceneStack_.empty() && activeTransitionScene_) {
|
|
||||||
if (sceneStack_.top().get() == activeTransitionScene_.get()) {
|
|
||||||
sceneStack_.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activeTransitionScene_) {
|
|
||||||
auto inScene = activeTransitionScene_->getInScene();
|
|
||||||
if (inScene) {
|
|
||||||
inScene->onAttachToScene(inScene.get());
|
|
||||||
sceneStack_.push(inScene);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 弹出当前场景
|
|
||||||
*
|
|
||||||
* 移除栈顶场景并恢复上一个场景
|
|
||||||
*/
|
|
||||||
void SceneManager::popScene() {
|
|
||||||
if (sceneStack_.size() <= 1 || isTransitioning_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto current = sceneStack_.top();
|
|
||||||
current->onExit();
|
|
||||||
current->onDetachFromScene();
|
|
||||||
sceneStack_.pop();
|
|
||||||
|
|
||||||
if (!sceneStack_.empty()) {
|
|
||||||
sceneStack_.top()->resume();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 弹出当前场景(带过渡效果)
|
|
||||||
* @param transition 过渡类型
|
|
||||||
* @param duration 过渡持续时间
|
|
||||||
*/
|
|
||||||
void SceneManager::popScene(TransitionType transition, float duration) {
|
|
||||||
if (sceneStack_.size() <= 1 || isTransitioning_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto current = sceneStack_.top();
|
|
||||||
auto previous = getPreviousScene();
|
|
||||||
|
|
||||||
startTransition(current, previous, transition, duration, [this]() {
|
|
||||||
if (!sceneStack_.empty() && activeTransitionScene_) {
|
|
||||||
if (sceneStack_.top().get() == activeTransitionScene_.get()) {
|
|
||||||
sceneStack_.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto outScene = activeTransitionScene_->getOutScene();
|
|
||||||
if (outScene) {
|
|
||||||
outScene->onExit();
|
|
||||||
outScene->onDetachFromScene();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activeTransitionScene_) {
|
|
||||||
auto inScene = activeTransitionScene_->getInScene();
|
|
||||||
if (inScene && !sceneStack_.empty() && sceneStack_.top() == inScene) {
|
|
||||||
inScene->resume();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 弹出到根场景
|
|
||||||
*
|
|
||||||
* 移除所有场景直到只剩根场景
|
|
||||||
*/
|
|
||||||
void SceneManager::popToRootScene() {
|
|
||||||
if (sceneStack_.size() <= 1 || isTransitioning_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (sceneStack_.size() > 1) {
|
|
||||||
auto scene = sceneStack_.top();
|
|
||||||
scene->onExit();
|
|
||||||
scene->onDetachFromScene();
|
|
||||||
sceneStack_.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
sceneStack_.top()->resume();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 弹出到根场景(带过渡效果)
|
|
||||||
* @param transition 过渡类型
|
|
||||||
* @param duration 过渡持续时间
|
|
||||||
*/
|
|
||||||
void SceneManager::popToRootScene(TransitionType transition, float duration) {
|
|
||||||
if (sceneStack_.size() <= 1 || isTransitioning_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto root = getRootScene();
|
|
||||||
auto current = sceneStack_.top();
|
|
||||||
|
|
||||||
startTransition(current, root, transition, duration, [this, root]() {
|
|
||||||
while (!sceneStack_.empty() && sceneStack_.top().get() != root.get()) {
|
|
||||||
auto scene = sceneStack_.top();
|
|
||||||
scene->onExit();
|
|
||||||
scene->onDetachFromScene();
|
|
||||||
sceneStack_.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sceneStack_.empty() && sceneStack_.top().get() == root.get()) {
|
|
||||||
root->resume();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 弹出到指定名称的场景
|
|
||||||
* @param name 目标场景的名称
|
|
||||||
*
|
|
||||||
* 移除栈顶场景直到找到指定名称的场景
|
|
||||||
*/
|
|
||||||
void SceneManager::popToScene(const std::string &name) {
|
|
||||||
if (isTransitioning_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::stack<Ptr<Scene>> tempStack;
|
|
||||||
Ptr<Scene> target = nullptr;
|
|
||||||
|
|
||||||
while (!sceneStack_.empty()) {
|
|
||||||
auto scene = sceneStack_.top();
|
|
||||||
if (scene->getName() == name) {
|
|
||||||
target = scene;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
scene->onExit();
|
|
||||||
scene->onDetachFromScene();
|
|
||||||
sceneStack_.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (target) {
|
|
||||||
target->resume();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 弹出到指定名称的场景(带过渡效果)
|
|
||||||
* @param name 目标场景名称
|
|
||||||
* @param transition 过渡类型
|
|
||||||
* @param duration 过渡持续时间
|
|
||||||
*/
|
|
||||||
void SceneManager::popToScene(const std::string &name,
|
|
||||||
TransitionType transition, float duration) {
|
|
||||||
if (isTransitioning_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto target = getSceneByName(name);
|
|
||||||
if (target && target != sceneStack_.top()) {
|
|
||||||
auto current = sceneStack_.top();
|
|
||||||
|
|
||||||
startTransition(current, target, transition, duration, [this, target]() {
|
|
||||||
while (!sceneStack_.empty() && sceneStack_.top() != target) {
|
|
||||||
auto scene = sceneStack_.top();
|
|
||||||
scene->onExit();
|
|
||||||
scene->onDetachFromScene();
|
|
||||||
sceneStack_.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sceneStack_.empty() && sceneStack_.top() == target) {
|
|
||||||
target->resume();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前场景
|
|
||||||
* @return 当前栈顶场景的智能指针,栈为空时返回nullptr
|
|
||||||
*/
|
|
||||||
Ptr<Scene> SceneManager::getCurrentScene() const {
|
|
||||||
if (sceneStack_.empty()) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
return sceneStack_.top();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取前一个场景
|
|
||||||
* @return 栈顶下一个场景的智能指针,不存在时返回nullptr
|
|
||||||
*/
|
|
||||||
Ptr<Scene> SceneManager::getPreviousScene() const {
|
|
||||||
if (sceneStack_.size() < 2) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto tempStack = sceneStack_;
|
|
||||||
tempStack.pop();
|
|
||||||
return tempStack.top();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取根场景
|
|
||||||
* @return 栈底场景的智能指针,栈为空时返回nullptr
|
|
||||||
*/
|
|
||||||
Ptr<Scene> SceneManager::getRootScene() const {
|
|
||||||
if (sceneStack_.empty()) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto tempStack = sceneStack_;
|
|
||||||
Ptr<Scene> root;
|
|
||||||
while (!tempStack.empty()) {
|
|
||||||
root = tempStack.top();
|
|
||||||
tempStack.pop();
|
|
||||||
}
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 通过名称获取场景
|
|
||||||
* @param name 场景名称
|
|
||||||
* @return 找到的场景智能指针,未找到返回nullptr
|
|
||||||
*/
|
|
||||||
Ptr<Scene> SceneManager::getSceneByName(const std::string &name) const {
|
|
||||||
auto it = namedScenes_.find(name);
|
|
||||||
if (it != namedScenes_.end()) {
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto tempStack = sceneStack_;
|
|
||||||
while (!tempStack.empty()) {
|
|
||||||
auto scene = tempStack.top();
|
|
||||||
if (scene->getName() == name) {
|
|
||||||
return scene;
|
|
||||||
}
|
|
||||||
tempStack.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查是否存在指定名称的场景
|
|
||||||
* @param name 场景名称
|
|
||||||
* @return 存在返回true,否则返回false
|
|
||||||
*/
|
|
||||||
bool SceneManager::hasScene(const std::string &name) const {
|
|
||||||
return getSceneByName(name) != nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 更新场景管理器
|
|
||||||
* @param dt 帧间隔时间(秒)
|
|
||||||
*
|
|
||||||
* 更新当前场景并分发指针事件
|
|
||||||
*/
|
|
||||||
void SceneManager::update(float dt) {
|
|
||||||
if (isTransitioning_ && activeTransitionScene_) {
|
|
||||||
activeTransitionScene_->updateTransition(dt);
|
|
||||||
hoverTarget_ = nullptr;
|
|
||||||
captureTarget_ = nullptr;
|
|
||||||
hasLastPointerWorld_ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sceneStack_.empty()) {
|
|
||||||
auto &scene = *sceneStack_.top();
|
|
||||||
scene.updateScene(dt);
|
|
||||||
if (!isTransitioning_) {
|
|
||||||
dispatchPointerEvents(scene);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 渲染当前场景
|
|
||||||
* @param renderer 渲染后端引用
|
|
||||||
*
|
|
||||||
* 使用当前场景的背景色清除帧缓冲并渲染场景内容
|
|
||||||
*/
|
|
||||||
void SceneManager::render(Renderer &renderer) {
|
|
||||||
Color clearColor = Colors::Black;
|
|
||||||
if (!sceneStack_.empty()) {
|
|
||||||
clearColor = sceneStack_.top()->getBackgroundColor();
|
|
||||||
}
|
|
||||||
|
|
||||||
E2D_LOG_TRACE("SceneManager::render - beginFrame 颜色({}, {}, {})",
|
|
||||||
clearColor.r, clearColor.g, clearColor.b);
|
|
||||||
renderer.beginFrame(clearColor);
|
|
||||||
|
|
||||||
if (!sceneStack_.empty()) {
|
|
||||||
E2D_LOG_TRACE("SceneManager::render - 正在渲染场景内容");
|
|
||||||
sceneStack_.top()->renderContent(renderer);
|
|
||||||
} else {
|
|
||||||
E2D_LOG_WARN("SceneManager::render - 没有场景可渲染");
|
|
||||||
}
|
|
||||||
|
|
||||||
renderer.endFrame();
|
|
||||||
E2D_LOG_TRACE("SceneManager::render - endFrame");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 收集渲染命令
|
|
||||||
* @param commands 渲染命令输出向量
|
|
||||||
*
|
|
||||||
* 从当前场景收集所有渲染命令
|
|
||||||
*/
|
|
||||||
void SceneManager::collectRenderCommands(std::vector<RenderCommand> &commands) {
|
|
||||||
if (!sceneStack_.empty()) {
|
|
||||||
sceneStack_.top()->collectRenderCommands(commands, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 结束场景管理器
|
|
||||||
*
|
|
||||||
* 清空场景栈并触发所有场景的退出回调
|
|
||||||
*/
|
|
||||||
void SceneManager::end() {
|
|
||||||
while (!sceneStack_.empty()) {
|
|
||||||
auto scene = sceneStack_.top();
|
|
||||||
scene->onExit();
|
|
||||||
scene->onDetachFromScene();
|
|
||||||
sceneStack_.pop();
|
|
||||||
}
|
|
||||||
namedScenes_.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 清除缓存的场景
|
|
||||||
*
|
|
||||||
* 清除命名场景缓存
|
|
||||||
*/
|
|
||||||
void SceneManager::purgeCachedScenes() { namedScenes_.clear(); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 启动过渡
|
|
||||||
* @param from 源场景
|
|
||||||
* @param to 目标场景
|
|
||||||
* @param type 过渡类型
|
|
||||||
* @param duration 过渡持续时间
|
|
||||||
* @param stackAction 过渡完成后的栈操作
|
|
||||||
*/
|
|
||||||
void SceneManager::startTransition(Ptr<Scene> from, Ptr<Scene> to,
|
|
||||||
TransitionType type, float duration,
|
|
||||||
std::function<void()> stackAction) {
|
|
||||||
if (!from || !to) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto transitionScene = createTransitionScene(type, duration, to);
|
|
||||||
if (!transitionScene) {
|
|
||||||
replaceScene(to);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hoverTarget_) {
|
|
||||||
Event evt;
|
|
||||||
evt.type = EventType::UIHoverExit;
|
|
||||||
evt.data = CustomEvent{0, hoverTarget_};
|
|
||||||
dispatchToNode(hoverTarget_, evt);
|
|
||||||
hoverTarget_ = nullptr;
|
|
||||||
}
|
|
||||||
captureTarget_ = nullptr;
|
|
||||||
hasLastPointerWorld_ = false;
|
|
||||||
|
|
||||||
transitionScene->setOutScene(from);
|
|
||||||
transitionScene->setFinishCallback([this]() { finishTransition(); });
|
|
||||||
|
|
||||||
from->pause();
|
|
||||||
|
|
||||||
transitionScene->onEnter();
|
|
||||||
transitionScene->onAttachToScene(transitionScene.get());
|
|
||||||
sceneStack_.push(transitionScene);
|
|
||||||
|
|
||||||
isTransitioning_ = true;
|
|
||||||
activeTransitionScene_ = transitionScene;
|
|
||||||
transitionStackAction_ = std::move(stackAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建过渡场景
|
|
||||||
* @param type 过渡类型
|
|
||||||
* @param duration 过渡持续时间
|
|
||||||
* @param inScene 目标场景
|
|
||||||
* @return 过渡场景智能指针
|
|
||||||
*/
|
|
||||||
Ptr<TransitionScene> SceneManager::createTransitionScene(TransitionType type,
|
|
||||||
float duration,
|
|
||||||
Ptr<Scene> inScene) {
|
|
||||||
if (!inScene) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case TransitionType::Fade:
|
|
||||||
return TransitionFadeScene::create(duration, inScene);
|
|
||||||
case TransitionType::SlideLeft:
|
|
||||||
return TransitionSlideScene::create(duration, inScene,
|
|
||||||
TransitionDirection::Left);
|
|
||||||
case TransitionType::SlideRight:
|
|
||||||
return TransitionSlideScene::create(duration, inScene,
|
|
||||||
TransitionDirection::Right);
|
|
||||||
case TransitionType::SlideUp:
|
|
||||||
return TransitionSlideScene::create(duration, inScene,
|
|
||||||
TransitionDirection::Up);
|
|
||||||
case TransitionType::SlideDown:
|
|
||||||
return TransitionSlideScene::create(duration, inScene,
|
|
||||||
TransitionDirection::Down);
|
|
||||||
case TransitionType::Scale:
|
|
||||||
return TransitionScaleScene::create(duration, inScene);
|
|
||||||
case TransitionType::Flip:
|
|
||||||
return TransitionFlipScene::create(duration, inScene);
|
|
||||||
case TransitionType::Box:
|
|
||||||
return TransitionBoxScene::create(duration, inScene);
|
|
||||||
default:
|
|
||||||
return TransitionFadeScene::create(duration, inScene);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 完成过渡
|
|
||||||
*
|
|
||||||
* 执行栈操作并清理过渡状态
|
|
||||||
*/
|
|
||||||
void SceneManager::finishTransition() {
|
|
||||||
Node *lastHoverTarget = hoverTarget_;
|
|
||||||
|
|
||||||
isTransitioning_ = false;
|
|
||||||
hoverTarget_ = nullptr;
|
|
||||||
captureTarget_ = nullptr;
|
|
||||||
hasLastPointerWorld_ = false;
|
|
||||||
|
|
||||||
if (transitionStackAction_) {
|
|
||||||
transitionStackAction_();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastHoverTarget) {
|
|
||||||
Event evt;
|
|
||||||
evt.type = EventType::UIHoverExit;
|
|
||||||
evt.data = CustomEvent{0, lastHoverTarget};
|
|
||||||
dispatchToNode(lastHoverTarget, evt);
|
|
||||||
}
|
|
||||||
|
|
||||||
activeTransitionScene_.reset();
|
|
||||||
transitionStackAction_ = nullptr;
|
|
||||||
|
|
||||||
if (transitionCallback_) {
|
|
||||||
transitionCallback_();
|
|
||||||
transitionCallback_ = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 分发指针事件
|
|
||||||
* @param scene 目标场景
|
|
||||||
*
|
|
||||||
* 处理鼠标悬停、移动、点击和滚轮事件
|
|
||||||
*/
|
|
||||||
void SceneManager::dispatchPointerEvents(Scene &scene) {
|
|
||||||
Vec2 screenPos = mousePos_;
|
|
||||||
|
|
||||||
Vec2 worldPos = screenPos;
|
|
||||||
if (auto *camera = scene.getActiveCamera()) {
|
|
||||||
worldPos = camera->screenToWorld(screenPos);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<Node> root = scene.shared_from_this();
|
|
||||||
Node *newHover = hitTestTopmost(root, worldPos);
|
|
||||||
|
|
||||||
if (newHover != hoverTarget_) {
|
|
||||||
if (hoverTarget_) {
|
|
||||||
Event evt;
|
|
||||||
evt.type = EventType::UIHoverExit;
|
|
||||||
evt.data = CustomEvent{0, hoverTarget_};
|
|
||||||
dispatchToNode(hoverTarget_, evt);
|
|
||||||
}
|
|
||||||
hoverTarget_ = newHover;
|
|
||||||
if (hoverTarget_) {
|
|
||||||
Event evt;
|
|
||||||
evt.type = EventType::UIHoverEnter;
|
|
||||||
evt.data = CustomEvent{0, hoverTarget_};
|
|
||||||
dispatchToNode(hoverTarget_, evt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasLastPointerWorld_) {
|
|
||||||
lastPointerWorld_ = worldPos;
|
|
||||||
hasLastPointerWorld_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vec2 delta = worldPos - lastPointerWorld_;
|
|
||||||
if (hoverTarget_ && (delta.x != 0.0f || delta.y != 0.0f)) {
|
|
||||||
Event evt = Event::createMouseMove(worldPos, delta);
|
|
||||||
dispatchToNode(hoverTarget_, evt);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hoverTarget_ && scrollDelta_ != 0.0f) {
|
|
||||||
Event evt = Event::createMouseScroll(Vec2(0.0f, scrollDelta_), worldPos);
|
|
||||||
dispatchToNode(hoverTarget_, evt);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mouseLeftPressed_) {
|
|
||||||
captureTarget_ = hoverTarget_;
|
|
||||||
if (captureTarget_) {
|
|
||||||
Event evt = Event::createMouseButtonPress(static_cast<int>(Mouse::Left),
|
|
||||||
0, worldPos);
|
|
||||||
dispatchToNode(captureTarget_, evt);
|
|
||||||
|
|
||||||
Event pressed;
|
|
||||||
pressed.type = EventType::UIPressed;
|
|
||||||
pressed.data = CustomEvent{0, captureTarget_};
|
|
||||||
dispatchToNode(captureTarget_, pressed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mouseLeftReleased_) {
|
|
||||||
Node *target = captureTarget_ ? captureTarget_ : hoverTarget_;
|
|
||||||
if (target) {
|
|
||||||
Event evt = Event::createMouseButtonRelease(static_cast<int>(Mouse::Left),
|
|
||||||
0, worldPos);
|
|
||||||
dispatchToNode(target, evt);
|
|
||||||
|
|
||||||
Event released;
|
|
||||||
released.type = EventType::UIReleased;
|
|
||||||
released.data = CustomEvent{0, target};
|
|
||||||
dispatchToNode(target, released);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (captureTarget_ && captureTarget_ == hoverTarget_) {
|
|
||||||
Event clicked;
|
|
||||||
clicked.type = EventType::UIClicked;
|
|
||||||
clicked.data = CustomEvent{0, captureTarget_};
|
|
||||||
dispatchToNode(captureTarget_, clicked);
|
|
||||||
}
|
|
||||||
|
|
||||||
captureTarget_ = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastPointerWorld_ = worldPos;
|
|
||||||
|
|
||||||
// 重置每帧状态
|
|
||||||
mouseLeftPressed_ = false;
|
|
||||||
mouseLeftReleased_ = false;
|
|
||||||
scrollDelta_ = 0.0f;
|
|
||||||
mouseDelta_ = Vec2::Zero();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SceneManager::doSceneSwitch() {}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,443 +0,0 @@
|
||||||
#include <algorithm>
|
|
||||||
#include <cmath>
|
|
||||||
#include <extra2d/graphics/core/render_command.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_renderer.h>
|
|
||||||
#include <extra2d/scene/shape_node.h>
|
|
||||||
#include <limits>
|
|
||||||
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 默认构造函数
|
|
||||||
*
|
|
||||||
* 创建一个空的形状节点
|
|
||||||
*/
|
|
||||||
ShapeNode::ShapeNode() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建空的形状节点
|
|
||||||
* @return 新创建的形状节点智能指针
|
|
||||||
*/
|
|
||||||
Ptr<ShapeNode> ShapeNode::create() { return makePtr<ShapeNode>(); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建点形状节点
|
|
||||||
* @param pos 点的位置坐标
|
|
||||||
* @param color 点的颜色
|
|
||||||
* @return 新创建的点形状节点智能指针
|
|
||||||
*/
|
|
||||||
Ptr<ShapeNode> ShapeNode::createPoint(const Vec2 &pos, const Color &color) {
|
|
||||||
auto node = makePtr<ShapeNode>();
|
|
||||||
node->shapeType_ = ShapeType::Point;
|
|
||||||
node->color_ = color;
|
|
||||||
node->points_ = {pos};
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建线段形状节点
|
|
||||||
* @param start 线段起点坐标
|
|
||||||
* @param end 线段终点坐标
|
|
||||||
* @param color 线段颜色
|
|
||||||
* @param width 线段宽度
|
|
||||||
* @return 新创建的线段形状节点智能指针
|
|
||||||
*/
|
|
||||||
Ptr<ShapeNode> ShapeNode::createLine(const Vec2 &start, const Vec2 &end,
|
|
||||||
const Color &color, float width) {
|
|
||||||
auto node = makePtr<ShapeNode>();
|
|
||||||
node->shapeType_ = ShapeType::Line;
|
|
||||||
node->color_ = color;
|
|
||||||
node->lineWidth_ = width;
|
|
||||||
node->points_ = {start, end};
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建矩形形状节点(空心)
|
|
||||||
* @param rect 矩形区域
|
|
||||||
* @param color 矩形边框颜色
|
|
||||||
* @param width 边框线宽
|
|
||||||
* @return 新创建的矩形形状节点智能指针
|
|
||||||
*/
|
|
||||||
Ptr<ShapeNode> ShapeNode::createRect(const Rect &rect, const Color &color,
|
|
||||||
float width) {
|
|
||||||
auto node = makePtr<ShapeNode>();
|
|
||||||
node->shapeType_ = ShapeType::Rect;
|
|
||||||
node->color_ = color;
|
|
||||||
node->lineWidth_ = width;
|
|
||||||
node->filled_ = false;
|
|
||||||
node->points_ = {
|
|
||||||
Vec2(rect.left(), rect.top()), Vec2(rect.right(), rect.top()),
|
|
||||||
Vec2(rect.right(), rect.bottom()), Vec2(rect.left(), rect.bottom())};
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建填充矩形形状节点
|
|
||||||
* @param rect 矩形区域
|
|
||||||
* @param color 矩形填充颜色
|
|
||||||
* @return 新创建的填充矩形形状节点智能指针
|
|
||||||
*/
|
|
||||||
Ptr<ShapeNode> ShapeNode::createFilledRect(const Rect &rect,
|
|
||||||
const Color &color) {
|
|
||||||
auto node = createRect(rect, color, 0);
|
|
||||||
node->filled_ = true;
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建圆形形状节点(空心)
|
|
||||||
* @param center 圆心坐标
|
|
||||||
* @param radius 圆的半径
|
|
||||||
* @param color 圆的边框颜色
|
|
||||||
* @param segments 圆的分段数(边数)
|
|
||||||
* @param width 边框线宽
|
|
||||||
* @return 新创建的圆形形状节点智能指针
|
|
||||||
*/
|
|
||||||
Ptr<ShapeNode> ShapeNode::createCircle(const Vec2 ¢er, float radius,
|
|
||||||
const Color &color, int segments,
|
|
||||||
float width) {
|
|
||||||
auto node = makePtr<ShapeNode>();
|
|
||||||
node->shapeType_ = ShapeType::Circle;
|
|
||||||
node->color_ = color;
|
|
||||||
node->lineWidth_ = width;
|
|
||||||
node->segments_ = segments;
|
|
||||||
node->filled_ = false;
|
|
||||||
node->points_ = {center};
|
|
||||||
// Store radius in a point for simplicity
|
|
||||||
node->addPoint(Vec2(radius, 0));
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建填充圆形形状节点
|
|
||||||
* @param center 圆心坐标
|
|
||||||
* @param radius 圆的半径
|
|
||||||
* @param color 圆的填充颜色
|
|
||||||
* @param segments 圆的分段数(边数)
|
|
||||||
* @return 新创建的填充圆形形状节点智能指针
|
|
||||||
*/
|
|
||||||
Ptr<ShapeNode> ShapeNode::createFilledCircle(const Vec2 ¢er, float radius,
|
|
||||||
const Color &color, int segments) {
|
|
||||||
auto node = createCircle(center, radius, color, segments, 0);
|
|
||||||
node->filled_ = true;
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建三角形形状节点(空心)
|
|
||||||
* @param p1 三角形第一个顶点坐标
|
|
||||||
* @param p2 三角形第二个顶点坐标
|
|
||||||
* @param p3 三角形第三个顶点坐标
|
|
||||||
* @param color 三角形边框颜色
|
|
||||||
* @param width 边框线宽
|
|
||||||
* @return 新创建的三角形形状节点智能指针
|
|
||||||
*/
|
|
||||||
Ptr<ShapeNode> ShapeNode::createTriangle(const Vec2 &p1, const Vec2 &p2,
|
|
||||||
const Vec2 &p3, const Color &color,
|
|
||||||
float width) {
|
|
||||||
auto node = makePtr<ShapeNode>();
|
|
||||||
node->shapeType_ = ShapeType::Triangle;
|
|
||||||
node->color_ = color;
|
|
||||||
node->lineWidth_ = width;
|
|
||||||
node->filled_ = false;
|
|
||||||
node->points_ = {p1, p2, p3};
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建填充三角形形状节点
|
|
||||||
* @param p1 三角形第一个顶点坐标
|
|
||||||
* @param p2 三角形第二个顶点坐标
|
|
||||||
* @param p3 三角形第三个顶点坐标
|
|
||||||
* @param color 三角形填充颜色
|
|
||||||
* @return 新创建的填充三角形形状节点智能指针
|
|
||||||
*/
|
|
||||||
Ptr<ShapeNode> ShapeNode::createFilledTriangle(const Vec2 &p1, const Vec2 &p2,
|
|
||||||
const Vec2 &p3,
|
|
||||||
const Color &color) {
|
|
||||||
auto node = createTriangle(p1, p2, p3, color, 0);
|
|
||||||
node->filled_ = true;
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建多边形形状节点(空心)
|
|
||||||
* @param points 多边形顶点坐标数组
|
|
||||||
* @param color 多边形边框颜色
|
|
||||||
* @param width 边框线宽
|
|
||||||
* @return 新创建的多边形形状节点智能指针
|
|
||||||
*/
|
|
||||||
Ptr<ShapeNode> ShapeNode::createPolygon(const std::vector<Vec2> &points,
|
|
||||||
const Color &color, float width) {
|
|
||||||
auto node = makePtr<ShapeNode>();
|
|
||||||
node->shapeType_ = ShapeType::Polygon;
|
|
||||||
node->color_ = color;
|
|
||||||
node->lineWidth_ = width;
|
|
||||||
node->filled_ = false;
|
|
||||||
node->points_ = points;
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建填充多边形形状节点
|
|
||||||
* @param points 多边形顶点坐标数组
|
|
||||||
* @param color 多边形填充颜色
|
|
||||||
* @return 新创建的填充多边形形状节点智能指针
|
|
||||||
*/
|
|
||||||
Ptr<ShapeNode> ShapeNode::createFilledPolygon(const std::vector<Vec2> &points,
|
|
||||||
const Color &color) {
|
|
||||||
auto node = createPolygon(points, color, 0);
|
|
||||||
node->filled_ = true;
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置形状的所有顶点
|
|
||||||
* @param points 顶点坐标数组
|
|
||||||
*/
|
|
||||||
void ShapeNode::setPoints(const std::vector<Vec2> &points) { points_ = points; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 添加一个顶点到形状
|
|
||||||
* @param point 要添加的顶点坐标
|
|
||||||
*/
|
|
||||||
void ShapeNode::addPoint(const Vec2 &point) { points_.push_back(point); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 清除所有顶点
|
|
||||||
*/
|
|
||||||
void ShapeNode::clearPoints() { points_.clear(); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取形状的边界矩形
|
|
||||||
* @return 包围形状的轴对齐边界矩形
|
|
||||||
*
|
|
||||||
* 计算形状在世界坐标系中的边界框,考虑位置偏移和线宽
|
|
||||||
*/
|
|
||||||
Rect ShapeNode::getBounds() const {
|
|
||||||
if (points_.empty()) {
|
|
||||||
return Rect();
|
|
||||||
}
|
|
||||||
|
|
||||||
Vec2 offset = getPosition();
|
|
||||||
|
|
||||||
if (shapeType_ == ShapeType::Circle && points_.size() >= 2) {
|
|
||||||
float radius = std::abs(points_[1].x);
|
|
||||||
Vec2 center = points_[0] + offset;
|
|
||||||
return Rect(center.x - radius, center.y - radius, radius * 2.0f,
|
|
||||||
radius * 2.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
float minX = std::numeric_limits<float>::infinity();
|
|
||||||
float minY = std::numeric_limits<float>::infinity();
|
|
||||||
float maxX = -std::numeric_limits<float>::infinity();
|
|
||||||
float maxY = -std::numeric_limits<float>::infinity();
|
|
||||||
|
|
||||||
for (const auto &p : points_) {
|
|
||||||
Vec2 world = p + offset;
|
|
||||||
minX = std::min(minX, world.x);
|
|
||||||
minY = std::min(minY, world.y);
|
|
||||||
maxX = std::max(maxX, world.x);
|
|
||||||
maxY = std::max(maxY, world.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
float inflate = 0.0f;
|
|
||||||
if (!filled_ &&
|
|
||||||
(shapeType_ == ShapeType::Line || shapeType_ == ShapeType::Rect ||
|
|
||||||
shapeType_ == ShapeType::Triangle || shapeType_ == ShapeType::Polygon ||
|
|
||||||
shapeType_ == ShapeType::Point)) {
|
|
||||||
inflate = std::max(0.0f, lineWidth_ * 0.5f);
|
|
||||||
}
|
|
||||||
if (shapeType_ == ShapeType::Point) {
|
|
||||||
inflate = std::max(inflate, lineWidth_ * 0.5f);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Rect(minX - inflate, minY - inflate, (maxX - minX) + inflate * 2.0f,
|
|
||||||
(maxY - minY) + inflate * 2.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制形状节点
|
|
||||||
* @param renderer 渲染后端引用
|
|
||||||
*
|
|
||||||
* 根据形状类型调用相应的渲染方法进行绘制
|
|
||||||
* 注意:变换矩阵已由 Node::onRender 通过 pushTransform 应用,
|
|
||||||
* 此处直接使用本地坐标即可。
|
|
||||||
*/
|
|
||||||
void ShapeNode::onDraw(Renderer &renderer) {
|
|
||||||
if (points_.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (shapeType_) {
|
|
||||||
case ShapeType::Point:
|
|
||||||
if (!points_.empty()) {
|
|
||||||
renderer.fillCircle(points_[0], lineWidth_ * 0.5f, color_, 8);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ShapeType::Line:
|
|
||||||
if (points_.size() >= 2) {
|
|
||||||
renderer.drawLine(points_[0], points_[1], color_, lineWidth_);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ShapeType::Rect:
|
|
||||||
if (points_.size() >= 4) {
|
|
||||||
if (filled_) {
|
|
||||||
Rect rect(points_[0].x, points_[0].y, points_[2].x - points_[0].x,
|
|
||||||
points_[2].y - points_[0].y);
|
|
||||||
renderer.fillRect(rect, color_);
|
|
||||||
} else {
|
|
||||||
for (size_t i = 0; i < points_.size(); ++i) {
|
|
||||||
Vec2 start = points_[i];
|
|
||||||
Vec2 end = points_[(i + 1) % points_.size()];
|
|
||||||
renderer.drawLine(start, end, color_, lineWidth_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ShapeType::Circle:
|
|
||||||
if (points_.size() >= 2) {
|
|
||||||
float radius = points_[1].x;
|
|
||||||
if (filled_) {
|
|
||||||
renderer.fillCircle(points_[0], radius, color_, segments_);
|
|
||||||
} else {
|
|
||||||
renderer.drawCircle(points_[0], radius, color_, segments_, lineWidth_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ShapeType::Triangle:
|
|
||||||
if (points_.size() >= 3) {
|
|
||||||
if (filled_) {
|
|
||||||
renderer.fillTriangle(points_[0], points_[1], points_[2], color_);
|
|
||||||
} else {
|
|
||||||
renderer.drawLine(points_[0], points_[1], color_, lineWidth_);
|
|
||||||
renderer.drawLine(points_[1], points_[2], color_, lineWidth_);
|
|
||||||
renderer.drawLine(points_[2], points_[0], color_, lineWidth_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ShapeType::Polygon:
|
|
||||||
if (!points_.empty()) {
|
|
||||||
if (filled_) {
|
|
||||||
renderer.fillPolygon(points_, color_);
|
|
||||||
} else {
|
|
||||||
renderer.drawPolygon(points_, color_, lineWidth_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 生成渲染命令
|
|
||||||
* @param commands 渲染命令输出向量
|
|
||||||
* @param zOrder 渲染层级
|
|
||||||
*
|
|
||||||
* 根据形状类型生成对应的渲染命令并添加到命令列表
|
|
||||||
*/
|
|
||||||
void ShapeNode::generateRenderCommand(std::vector<RenderCommand> &commands,
|
|
||||||
int zOrder) {
|
|
||||||
if (points_.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vec2 offset = getPosition();
|
|
||||||
RenderCommand cmd;
|
|
||||||
cmd.layer = zOrder;
|
|
||||||
|
|
||||||
switch (shapeType_) {
|
|
||||||
case ShapeType::Point:
|
|
||||||
if (!points_.empty()) {
|
|
||||||
cmd.type = RenderCommandType::FilledCircle;
|
|
||||||
cmd.data = CircleCommandData{
|
|
||||||
points_[0] + offset, lineWidth_ * 0.5f, color_, 8, 0.0f, true};
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ShapeType::Line:
|
|
||||||
if (points_.size() >= 2) {
|
|
||||||
cmd.type = RenderCommandType::Line;
|
|
||||||
cmd.data = LineCommandData{points_[0] + offset, points_[1] + offset,
|
|
||||||
color_, lineWidth_};
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ShapeType::Rect:
|
|
||||||
if (points_.size() >= 4) {
|
|
||||||
if (filled_) {
|
|
||||||
cmd.type = RenderCommandType::FilledRect;
|
|
||||||
Rect rect(points_[0].x, points_[0].y, points_[2].x - points_[0].x,
|
|
||||||
points_[2].y - points_[0].y);
|
|
||||||
cmd.data = RectCommandData{Rect(rect.origin + offset, rect.size),
|
|
||||||
color_, 0.0f, true};
|
|
||||||
} else {
|
|
||||||
cmd.type = RenderCommandType::Rect;
|
|
||||||
Rect rect(points_[0].x, points_[0].y, points_[2].x - points_[0].x,
|
|
||||||
points_[2].y - points_[0].y);
|
|
||||||
cmd.data = RectCommandData{Rect(rect.origin + offset, rect.size),
|
|
||||||
color_, lineWidth_, false};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ShapeType::Circle:
|
|
||||||
if (points_.size() >= 2) {
|
|
||||||
float radius = points_[1].x;
|
|
||||||
if (filled_) {
|
|
||||||
cmd.type = RenderCommandType::FilledCircle;
|
|
||||||
cmd.data = CircleCommandData{points_[0] + offset, radius, color_,
|
|
||||||
segments_, 0.0f, true};
|
|
||||||
} else {
|
|
||||||
cmd.type = RenderCommandType::Circle;
|
|
||||||
cmd.data = CircleCommandData{points_[0] + offset, radius, color_,
|
|
||||||
segments_, lineWidth_, false};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ShapeType::Triangle:
|
|
||||||
if (points_.size() >= 3) {
|
|
||||||
Vec2 p1 = points_[0] + offset;
|
|
||||||
Vec2 p2 = points_[1] + offset;
|
|
||||||
Vec2 p3 = points_[2] + offset;
|
|
||||||
if (filled_) {
|
|
||||||
cmd.type = RenderCommandType::FilledTriangle;
|
|
||||||
cmd.data = TriangleCommandData{p1, p2, p3, color_, 0.0f, true};
|
|
||||||
} else {
|
|
||||||
cmd.type = RenderCommandType::Triangle;
|
|
||||||
cmd.data = TriangleCommandData{p1, p2, p3, color_, lineWidth_, false};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ShapeType::Polygon:
|
|
||||||
if (!points_.empty()) {
|
|
||||||
std::vector<Vec2> transformedPoints;
|
|
||||||
transformedPoints.reserve(points_.size());
|
|
||||||
for (const auto &p : points_) {
|
|
||||||
transformedPoints.push_back(p + offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filled_) {
|
|
||||||
cmd.type = RenderCommandType::FilledPolygon;
|
|
||||||
cmd.data = PolygonCommandData{transformedPoints, color_, 0.0f, true};
|
|
||||||
} else {
|
|
||||||
cmd.type = RenderCommandType::Polygon;
|
|
||||||
cmd.data =
|
|
||||||
PolygonCommandData{transformedPoints, color_, lineWidth_, false};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
commands.push_back(std::move(cmd));
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,234 +0,0 @@
|
||||||
#include <algorithm>
|
|
||||||
#include <cmath>
|
|
||||||
#include <extra2d/graphics/core/render_command.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_renderer.h>
|
|
||||||
#include <extra2d/graphics/texture/texture.h>
|
|
||||||
#include <extra2d/scene/sprite.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 默认构造函数
|
|
||||||
*
|
|
||||||
* 创建一个空的精灵对象
|
|
||||||
*/
|
|
||||||
Sprite::Sprite() = default;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 带纹理的构造函数
|
|
||||||
* @param texture 精灵使用的纹理智能指针
|
|
||||||
*
|
|
||||||
* 创建精灵并设置纹理,纹理区域默认为整个纹理
|
|
||||||
*/
|
|
||||||
Sprite::Sprite(Ptr<Texture> texture) { setTexture(texture); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置精灵纹理
|
|
||||||
* @param texture 要设置的纹理智能指针
|
|
||||||
*
|
|
||||||
* 设置纹理并将纹理区域初始化为整个纹理大小
|
|
||||||
*/
|
|
||||||
void Sprite::setTexture(Ptr<Texture> texture) {
|
|
||||||
texture_ = texture;
|
|
||||||
if (texture_) {
|
|
||||||
textureRect_ = Rect(0, 0, static_cast<float>(texture_->getWidth()),
|
|
||||||
static_cast<float>(texture_->getHeight()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置纹理区域
|
|
||||||
* @param rect 纹理上的矩形区域
|
|
||||||
*
|
|
||||||
* 设置精灵显示纹理的哪一部分
|
|
||||||
*/
|
|
||||||
void Sprite::setTextureRect(const Rect &rect) { textureRect_ = rect; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置精灵颜色
|
|
||||||
* @param color 要设置的颜色
|
|
||||||
*
|
|
||||||
* 颜色会与纹理颜色混合
|
|
||||||
*/
|
|
||||||
void Sprite::setColor(const Color &color) { color_ = color; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置水平翻转
|
|
||||||
* @param flip 是否水平翻转
|
|
||||||
*/
|
|
||||||
void Sprite::setFlipX(bool flip) { flipX_ = flip; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置垂直翻转
|
|
||||||
* @param flip 是否垂直翻转
|
|
||||||
*/
|
|
||||||
void Sprite::setFlipY(bool flip) { flipY_ = flip; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建空精灵
|
|
||||||
* @return 新创建的精灵智能指针
|
|
||||||
*/
|
|
||||||
Ptr<Sprite> Sprite::create() { return makePtr<Sprite>(); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建带纹理的精灵
|
|
||||||
* @param texture 精灵使用的纹理
|
|
||||||
* @return 新创建的精灵智能指针
|
|
||||||
*/
|
|
||||||
Ptr<Sprite> Sprite::create(Ptr<Texture> texture) {
|
|
||||||
return makePtr<Sprite>(texture);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建带纹理和纹理区域的精灵
|
|
||||||
* @param texture 精灵使用的纹理
|
|
||||||
* @param rect 纹理区域
|
|
||||||
* @return 新创建的精灵智能指针
|
|
||||||
*/
|
|
||||||
Ptr<Sprite> Sprite::create(Ptr<Texture> texture, const Rect &rect) {
|
|
||||||
auto sprite = makePtr<Sprite>(texture);
|
|
||||||
sprite->setTextureRect(rect);
|
|
||||||
return sprite;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取精灵的边界矩形
|
|
||||||
* @return 精灵在世界坐标系中的轴对齐边界矩形
|
|
||||||
*
|
|
||||||
* 考虑位置、锚点、缩放等因素计算边界框
|
|
||||||
*/
|
|
||||||
Rect Sprite::getBounds() const {
|
|
||||||
if (!texture_ || !texture_->isValid()) {
|
|
||||||
return Rect();
|
|
||||||
}
|
|
||||||
|
|
||||||
float width = textureRect_.width();
|
|
||||||
float height = textureRect_.height();
|
|
||||||
|
|
||||||
auto pos = getPosition();
|
|
||||||
auto anchor = getAnchor();
|
|
||||||
auto scale = getScale();
|
|
||||||
|
|
||||||
float w = width * scale.x;
|
|
||||||
float h = height * scale.y;
|
|
||||||
float x0 = pos.x - width * anchor.x * scale.x;
|
|
||||||
float y0 = pos.y - height * anchor.y * scale.y;
|
|
||||||
float x1 = x0 + w;
|
|
||||||
float y1 = y0 + h;
|
|
||||||
|
|
||||||
float l = std::min(x0, x1);
|
|
||||||
float t = std::min(y0, y1);
|
|
||||||
return Rect(l, t, std::abs(w), std::abs(h));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制精灵
|
|
||||||
* @param renderer 渲染后端引用
|
|
||||||
*
|
|
||||||
* 使用世界变换计算最终位置、缩放和旋转,然后绘制精灵
|
|
||||||
*/
|
|
||||||
void Sprite::onDraw(Renderer &renderer) {
|
|
||||||
if (!texture_ || !texture_->isValid()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate destination rectangle based on texture rect
|
|
||||||
float width = textureRect_.width();
|
|
||||||
float height = textureRect_.height();
|
|
||||||
|
|
||||||
// 使用世界变换来获取最终的位置
|
|
||||||
auto worldTransform = getWorldTransform();
|
|
||||||
|
|
||||||
// 从世界变换矩阵中提取位置(第四列)
|
|
||||||
float worldX = worldTransform[3][0];
|
|
||||||
float worldY = worldTransform[3][1];
|
|
||||||
|
|
||||||
// 从世界变换矩阵中提取缩放
|
|
||||||
float worldScaleX =
|
|
||||||
glm::length(glm::vec2(worldTransform[0][0], worldTransform[0][1]));
|
|
||||||
float worldScaleY =
|
|
||||||
glm::length(glm::vec2(worldTransform[1][0], worldTransform[1][1]));
|
|
||||||
|
|
||||||
auto anchor = getAnchor();
|
|
||||||
|
|
||||||
// 锚点由 Renderer 在绘制时处理,这里只传递位置和尺寸
|
|
||||||
Rect destRect(worldX, worldY, width * worldScaleX, height * worldScaleY);
|
|
||||||
|
|
||||||
// Adjust source rect for flipping
|
|
||||||
Rect srcRect = textureRect_;
|
|
||||||
if (flipX_) {
|
|
||||||
srcRect.origin.x = srcRect.right();
|
|
||||||
srcRect.size.width = -srcRect.size.width;
|
|
||||||
}
|
|
||||||
if (flipY_) {
|
|
||||||
srcRect.origin.y = srcRect.bottom();
|
|
||||||
srcRect.size.height = -srcRect.size.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从世界变换矩阵中提取旋转角度
|
|
||||||
float worldRotation = std::atan2(worldTransform[0][1], worldTransform[0][0]);
|
|
||||||
|
|
||||||
renderer.drawSprite(*texture_, destRect, srcRect, color_, worldRotation,
|
|
||||||
anchor);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 生成渲染命令
|
|
||||||
* @param commands 渲染命令输出向量
|
|
||||||
* @param zOrder 渲染层级
|
|
||||||
*
|
|
||||||
* 根据精灵的纹理、变换和颜色生成精灵渲染命令
|
|
||||||
*/
|
|
||||||
void Sprite::generateRenderCommand(std::vector<RenderCommand> &commands,
|
|
||||||
int zOrder) {
|
|
||||||
if (!texture_ || !texture_->isValid()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算目标矩形(与 onDraw 一致,使用世界变换)
|
|
||||||
float width = textureRect_.width();
|
|
||||||
float height = textureRect_.height();
|
|
||||||
|
|
||||||
// 使用世界变换来获取最终的位置
|
|
||||||
auto worldTransform = getWorldTransform();
|
|
||||||
|
|
||||||
// 从世界变换矩阵中提取位置(第四列)
|
|
||||||
float worldX = worldTransform[3][0];
|
|
||||||
float worldY = worldTransform[3][1];
|
|
||||||
|
|
||||||
// 从世界变换矩阵中提取缩放
|
|
||||||
float worldScaleX =
|
|
||||||
glm::length(glm::vec2(worldTransform[0][0], worldTransform[0][1]));
|
|
||||||
float worldScaleY =
|
|
||||||
glm::length(glm::vec2(worldTransform[1][0], worldTransform[1][1]));
|
|
||||||
|
|
||||||
auto anchor = getAnchor();
|
|
||||||
|
|
||||||
// 锚点由 Renderer 在绘制时处理,这里只传递位置和尺寸
|
|
||||||
Rect destRect(worldX, worldY, width * worldScaleX, height * worldScaleY);
|
|
||||||
|
|
||||||
// 调整源矩形(翻转)
|
|
||||||
Rect srcRect = textureRect_;
|
|
||||||
if (flipX_) {
|
|
||||||
srcRect.origin.x = srcRect.right();
|
|
||||||
srcRect.size.width = -srcRect.size.width;
|
|
||||||
}
|
|
||||||
if (flipY_) {
|
|
||||||
srcRect.origin.y = srcRect.bottom();
|
|
||||||
srcRect.size.height = -srcRect.size.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从世界变换矩阵中提取旋转角度
|
|
||||||
float worldRotation = std::atan2(worldTransform[0][1], worldTransform[0][0]);
|
|
||||||
|
|
||||||
// 创建渲染命令
|
|
||||||
RenderCommand cmd;
|
|
||||||
cmd.type = RenderCommandType::Sprite;
|
|
||||||
cmd.layer = zOrder;
|
|
||||||
cmd.data = SpriteCommandData{texture_.get(), destRect, srcRect, color_,
|
|
||||||
worldRotation, anchor, 0};
|
|
||||||
|
|
||||||
commands.push_back(std::move(cmd));
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue