feat: initial DX12 foundation framework

This commit is contained in:
SpecialX
2026-03-19 18:27:49 +08:00
commit 60f73b525d
70 changed files with 8993 additions and 0 deletions

27
.gitignore vendored Normal file
View File

@@ -0,0 +1,27 @@
.vs/
**/x64/
**/Debug/
**/DebugEditor/
**/Release/
**/ReleaseEditor/
*.VC.db
*.VC.opendb
*.suo
*.user
*.ipch
*.obj
*.o
*.iobj
*.ipdb
*.pdb
*.idb
*.ilk
*.tlog
*.lastbuildstate
*.log
*.recipe
*.cache
*.db
*.db-shm
*.db-wal
Engine/Graphics_Beta/

View File

@@ -0,0 +1,102 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="DebugEditor|x64">
<Configuration>DebugEditor</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="ReleaseEditor|x64">
<Configuration>ReleaseEditor</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClInclude Include="FbxImporter.h" />
<ClInclude Include="Geometry.h" />
<ClInclude Include="MeshPrimitives.h" />
<ClInclude Include="ToolsCommon.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="FbxImporter.cpp" />
<ClCompile Include="Geometry.cpp" />
<ClCompile Include="MeshPrimitives.cpp" />
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>17.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{d98da990-9d7c-4064-ab98-6f6c405c10f0}</ProjectGuid>
<RootNamespace>ContentTools</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugEditor|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseEditor|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='DebugEditor|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='ReleaseEditor|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='DebugEditor|x64'">
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;CONTENTTOOLS_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<PrecompiledHeaderFile>
</PrecompiledHeaderFile>
<LanguageStandard>stdcpp17</LanguageStandard>
<AdditionalIncludeDirectories>$(SolutionDir)Engine;$(SolutionDir)Engine\Common;C:\Program Files\Autodesk\FBX\FBX SDK\2020.3.2\include</AdditionalIncludeDirectories>
<AdditionalOptions>/Ignore:4099 %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseEditor|x64'">
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;CONTENTTOOLS_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<PrecompiledHeaderFile>
</PrecompiledHeaderFile>
<LanguageStandard>stdcpp17</LanguageStandard>
<AdditionalIncludeDirectories>$(SolutionDir)Engine;$(SolutionDir)Engine\Common;</AdditionalIncludeDirectories>
<AdditionalOptions>/Ignore:4099 %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
</Link>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="源文件">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="头文件">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="资源文件">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="ToolsCommon.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="MeshPrimitives.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="Geometry.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="FbxImporter.h">
<Filter>头文件</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="MeshPrimitives.cpp">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="Geometry.cpp">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="FbxImporter.cpp">
<Filter>源文件</Filter>
</ClCompile>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,352 @@
#include "FbxImporter.h"
#include "Geometry.h"
#if _DEBUG
#pragma comment (lib, "C:\\Program Files\\Autodesk\\FBX\\FBX SDK\\2020.3.2\\lib\\vs2019\\x64\\debug\\libfbxsdk-md.lib")
#pragma comment (lib, "C:\\Program Files\\Autodesk\\FBX\\FBX SDK\\2020.3.2\\lib\\vs2019\\x64\\debug\\libxml2-md.lib")
#pragma comment (lib, "C:\\Program Files\\Autodesk\\FBX\\FBX SDK\\2020.3.2\\lib\\vs2019\\x64\\debug\\zlib-md.lib")
#else
#pragma comment (lib, "C:\\Program Files\\Autodesk\\FBX\\FBX SDK\\2020.3.2\\lib\\vs2019\\x64\\release\\libfbxsdk-md.lib")
#pragma comment (lib, "C:\\Program Files\\Autodesk\\FBX\\FBX SDK\\2020.3.2\\lib\\vs2019\\x64\\release\\libxml2-md.lib")
#pragma comment (lib, "C:\\Program Files\\Autodesk\\FBX\\FBX SDK\\2020.3.2\\lib\\vs2019\\x64\\release\\zlib-md.lib")
#endif
namespace XEngine::tools {
namespace {
std::mutex fbx_mutex{};
} // namespace
bool
fbx_context::initialize_fbx()
{
assert(!is_valid());
_fbx_manager = FbxManager::Create();
if (!_fbx_manager)
{
return false;
}
FbxIOSettings* ios{ FbxIOSettings::Create(_fbx_manager, IOSROOT) };
assert(ios);
_fbx_manager->SetIOSettings(ios);
return true;
}
void
fbx_context::load_fbx_file(const char* file)
{
assert(_fbx_manager && !_fbx_scene);
_fbx_scene = FbxScene::Create(_fbx_manager, "Importer Scene");
if (!_fbx_scene)
{
return;
}
FbxImporter* importer{ FbxImporter::Create(_fbx_manager, "Importer") };
if (!(importer &&
importer->Initialize(file, -1, _fbx_manager->GetIOSettings()) &&
importer->Import(_fbx_scene)))
{
return;
}
importer->Destroy();
_scene_scale = (f32)_fbx_scene->GetGlobalSettings().GetSystemUnit().GetConversionFactorTo(FbxSystemUnit::m);
}
void
fbx_context::get_scene(FbxNode* root /* = nullptr*/)
{
assert(is_valid());
if (!root)
{
root = _fbx_scene->GetRootNode();
if (!root)return;
}
const s32 num_nodes{ root->GetChildCount() };
for (s32 i{ 0 }; i < num_nodes; ++i)
{
FbxNode* node{ root->GetChild(i) };
if (!node) continue;
lod_group lod{};
get_meshes(node, lod.meshes, 0, -1);
if (lod.meshes.size())
{
lod.name = lod.meshes[0].name;
_scene->lod_groups.emplace_back(lod);
}
}
}
void
fbx_context::get_meshes(FbxNode* node, utl::vector<mesh>& meshes, u32 lod_id, f32 lod_threshold)
{
assert(node && lod_id != u32_invalid_id);
bool is_lod_group{ false };
if (const s32 num_attributes{ node->GetNodeAttributeCount() })
{
for (s32 i{ 0 }; i < num_attributes; ++i)
{
FbxNodeAttribute* attribute{ node->GetNodeAttributeByIndex(i) };
const FbxNodeAttribute::EType attribute_type{ attribute->GetAttributeType() };
if (attribute_type == FbxNodeAttribute::eMesh)
{
get_mesh(attribute, meshes, lod_id, lod_threshold);
}
else
{
get_lod_group(attribute);
is_lod_group = true;
}
}
}
if (!is_lod_group)
{
if (const s32 num_children{ node->GetChildCount() })
{
for (s32 i{ 0 }; i < num_children; ++i)
{
get_meshes(node->GetChild(i), meshes, lod_id, lod_threshold);
}
}
}
}
void
fbx_context::get_mesh(FbxNodeAttribute* attribute, utl::vector<mesh>& meshes, u32 lod_id, f32 lod_threshold)
{
assert(attribute);
FbxMesh * fbx_mesh{ (FbxMesh*)attribute };
if (fbx_mesh->RemoveBadPolygons() < 0) return;
FbxGeometryConverter gc{ _fbx_manager };
fbx_mesh = (FbxMesh*)(gc.Triangulate(fbx_mesh, true));
if (!fbx_mesh || fbx_mesh->RemoveBadPolygons() < 0)return;
FbxNode *const node{ fbx_mesh->GetNode() };
mesh m{};
m.lod_id = lod_id;
m.lod_threshold = lod_threshold;
m.name = (node->GetName()[0] != '\0') ? node->GetName() : fbx_mesh->GetName();
if (get_mesh_data(fbx_mesh, m))
{
meshes.emplace_back(m);
}
}
bool
fbx_context::get_mesh_data(FbxMesh* fbx_mesh, mesh& m)
{
assert(fbx_mesh);
FbxNode *const node{ fbx_mesh->GetNode() };
FbxAMatrix geometricTransform;
geometricTransform.SetT(node->GetGeometricTranslation(FbxNode::eSourcePivot));
geometricTransform.SetR(node->GetGeometricRotation(FbxNode::eSourcePivot));
geometricTransform.SetS(node->GetGeometricScaling(FbxNode::eSourcePivot));
FbxAMatrix transform{ node->EvaluateGlobalTransform() * geometricTransform };
FbxAMatrix inverse_transpose{ transform.Inverse().Transpose() };
const s32 num_polys{ fbx_mesh->GetPolygonCount() };
if (num_polys <= 0) return false;
const s32 num_vertices{ fbx_mesh->GetControlPointsCount() };
FbxVector4* vertices{ fbx_mesh->GetControlPoints() };
const s32 num_indices{ fbx_mesh->GetPolygonVertexCount() };
s32* indices{ fbx_mesh->GetPolygonVertices() };
assert(num_vertices > 0 && vertices && num_indices > 0 && indices);
if (!(num_vertices > 0 && vertices && num_indices > 0 && indices)) return false;
m.raw_indices.resize(num_indices);
utl::vector vertex_ref(num_vertices, u32_invalid_id);
for (s32 i{ 0 }; i < num_indices; ++i)
{
const u32 v_idx{ (u32)indices[i] };
if (vertex_ref[v_idx] != u32_invalid_id)
{
m.raw_indices[i] = vertex_ref[v_idx];
}
else
{
FbxVector4 v = transform.MultT( vertices[v_idx]) * _scene_scale;
m.raw_indices[i] = (u32)m.positions.size();
vertex_ref[v_idx] = m.raw_indices[i];
m.positions.emplace_back((f32)v[0], (f32)v[1], (f32)v[2]);
}
}
assert(m.raw_indices.size() % 3 == 0);
FbxLayerElementArrayTemplate<s32>* mtl_indices;
if (fbx_mesh->GetMaterialIndices(&mtl_indices))
{
for (s32 i{ 0 }; i < num_polys; ++i)
{
const s32 mtl_index{ mtl_indices->GetAt(i) };
assert(mtl_index >= 0);
m.material_indices.emplace_back((u32)mtl_index);
if (std::find(m.material_used.begin(), m.material_used.end(), (u32)mtl_index) == m.material_used.end())
{
m.material_used.emplace_back((u32)mtl_index);
}
}
}
// Importing normals in ON by default
const bool import_normals{ !_scene_data->settings.calculate_normals };
// Importing normals in ON by default
const bool import_tangents{ !_scene_data->settings.calculate_tangents };
if (import_normals)
{
FbxArray<FbxVector4> normals;
if (fbx_mesh->GenerateNormals() &&
fbx_mesh->GetPolygonVertexNormals(normals) && normals.Size() > 0)
{
const s32 num_normals{ normals.Size() };
for (s32 i{ 0 }; i < num_normals; ++i)
{
FbxVector4 n{ inverse_transpose.MultT(normals[i]) };
n.Normalize();
m.normals.emplace_back((f32)n[0], (f32)n[1], (f32)n[2]);
}
}
else
{
_scene_data->settings.calculate_normals = true;
}
}
if (import_tangents)
{
FbxLayerElementArrayTemplate<FbxVector4>* tangents{ nullptr };
if (fbx_mesh->GenerateTangentsData() &&
fbx_mesh->GetTangents(&tangents) &&
tangents && tangents->GetCount() > 0)
{
const s32 num_tangent{ tangents->GetCount() };
for (s32 i{ 0 }; i < num_tangent; ++i)
{
FbxVector4 t{ tangents->GetAt(i) };
const f32 handedness{ (f32)t[3] };
t[3] = 0.0;
t = transform.MultT(t);
t.Normalize();
m.tangents.emplace_back((f32)t[0], (f32)t[1], (f32)t[2], handedness);
}
}
else
{
_scene_data->settings.calculate_tangents = true;
}
}
FbxStringList uv_names;
fbx_mesh->GetUVSetNames(uv_names);
const s32 uv_set_count{ uv_names.GetCount() };
m.uv_sets.resize(uv_set_count);
for (s32 i{ 0 }; i < uv_set_count; ++i)
{
FbxArray<FbxVector2> uvs;
if (fbx_mesh->GetPolygonVertexUVs(uv_names.GetStringAt(i), uvs))
{
const s32 num_uvs{ uvs.Size() };
for (s32 j{ 0 }; j < num_uvs; ++j)
{
m.uv_sets[i].emplace_back((f32)uvs[j][0], (f32)uvs[j][1]);
}
}
}
return true;
}
void
fbx_context::get_lod_group(FbxNodeAttribute* attribute)
{
assert(attribute);
FbxLODGroup * lod_grp{ (FbxLODGroup*)attribute };
FbxNode *const node{ lod_grp->GetNode() };
lod_group lod{};
lod.name = (node->GetName()[0] != '\0') ? node->GetName() : lod_grp->GetName();
const s32 num_nodes{ node->GetChildCount() };
assert(num_nodes > 0 && lod_grp->GetNumThresholds() == (num_nodes - 1));
for (s32 i{ 0 }; i < num_nodes; ++i)
{
f32 lod_threshold{ -1.f };
if (i>0)
{
FbxDistance threshold;
lod_grp->GetThreshold(i-1, threshold);
lod_threshold = threshold.value() * _scene_scale;
}
get_meshes(node->GetChild(i), lod.meshes, (u32)lod.meshes.size(), lod_threshold);
}
if (lod.meshes.size()) _scene->lod_groups.emplace_back(lod);
}
EDITOR_INTERFACE void
ImportFbx(const char* file, scene_data* data)
{
assert(file && data);
scene scene{};
// 注意: 任何调用此函数都需要使用单线程
{
std::lock_guard lock{ fbx_mutex };
fbx_context fbx_context{ file, &scene, data };
if (fbx_context.is_valid())
{
fbx_context.get_scene();
}
else
{
return;
}
}
process_scene(scene, data->settings);
pack_data(scene, *data);
}
}

View File

@@ -0,0 +1,54 @@
#pragma once
#include "ToolsCommon.h"
#include <fbxsdk.h>
namespace XEngine::tools {
struct scene_data;
struct scene;
struct mesh;
struct geometry_import_settings;
class fbx_context {
public:
fbx_context(const char* file, scene* scene, scene_data* data)
:_scene_data{ data }, _scene{ scene }
{
assert(file && _scene && _scene_data);
if (initialize_fbx())
{
load_fbx_file(file);
assert(is_valid());
}
}
~fbx_context()
{
_fbx_scene->Destroy();
_fbx_manager->Destroy();
ZeroMemory(this, sizeof(fbx_context));
}
void get_scene(FbxNode* root = nullptr);
constexpr bool is_valid() const { return _fbx_manager && _fbx_scene; }
constexpr f32 scene_scale() const { return _scene_scale; }
private:
bool initialize_fbx();
void load_fbx_file(const char* file);
void get_meshes(FbxNode* node, utl::vector<mesh>& meshes, u32 lod_id, f32 lod_threshold);
void get_mesh(FbxNodeAttribute* attribute, utl::vector<mesh>& meshes, u32 lod_id, f32 lod_threshold);
void get_lod_group(FbxNodeAttribute* attribute);
bool get_mesh_data(FbxMesh* fbx_mesh, mesh& m);
scene* _scene{ nullptr };
scene_data* _scene_data{ nullptr };
FbxManager* _fbx_manager{ nullptr };
FbxScene* _fbx_scene{ nullptr };
f32 _scene_scale{ 1.0f };
};
}

View File

@@ -0,0 +1,677 @@
#include "Geometry.h"
#include "..\Engine\Utilities\IOStream.h"
namespace XEngine::tools {
namespace {
using namespace math;
using namespace DirectX;
void
recalculate_normals(mesh& m)
{
const u32 num_indeices{ (u32)m.raw_indices.size() };
m.normals.resize(num_indeices);
for (u32 i{ 0 }; i < num_indeices; ++i)
{
const u32 i0{ m.raw_indices[i] };
const u32 i1{ m.raw_indices[++i] };
const u32 i2{ m.raw_indices[++i] };
vector v0{ XMLoadFloat3(&m.positions[i0]) };
vector v1{ XMLoadFloat3(&m.positions[i1]) };
vector v2{ XMLoadFloat3(&m.positions[i2]) };
vector e0{ v1 - v0 };
vector e1{ v2 - v0 };
vector n{ XMVector3Normalize(XMVector3Cross(e0,e1)) };
XMStoreFloat3(&m.normals[i], n);
m.normals[i - 1] = m.normals[i];
m.normals[i - 2] = m.normals[i];
}
}
/**
* @brief 处理网格的法线,实现平滑或硬边效果。
*
* 该函数根据给定的平滑角度smoothing_angle处理网格mesh的法线。
* 如果平滑角度接近 180 度,则生成硬边法线;如果接近 0 度,则生成软边法线;否则,根据角度阈值进行平滑。
*
* @param m 要处理的网格对象,包含原始索引、顶点位置和法线。
* @param smoothing_angle 法线平滑角度,单位为度。
*/
void
process_normals(mesh& m, f32 smoothing_angle)
{
// 计算角度的余弦值,作为平滑阈值
const f32 cos_alpha{ XMScalarCos(pi - smoothing_angle * pi / 180.f) };
// 判断是否为硬边或软边
const bool is_hard_edge{ XMScalarNearEqual(smoothing_angle, 180.f, epsilon) };
const bool is_soft_edge{ XMScalarNearEqual(smoothing_angle, 0.f, epsilon) };
// 获取索引和顶点数量
const u32 num_idices{ (u32)m.raw_indices.size() };
const u32 num_vertices{ (u32)m.positions.size() };
// 确保索引和顶点数量有效
assert(num_idices && num_vertices);
// 调整索引缓冲区的大小,用于存储新的顶点索引
m.indices.resize(num_idices);
// 创建一个索引引用表,用于存储每个顶点索引对应的原始索引列表
// 这里是每一个vertice对应4个point参考houdini
utl::vector<utl::vector<u32>> idx_ref(num_vertices);
for (u32 i{ 0 }; i < num_idices; ++i)
idx_ref[m.raw_indices[i]].emplace_back(i);
// 遍历每个vertice
for (u32 i{ 0 }; i < num_vertices; ++i)
{
// 获取当前顶点的原始索引列表
auto& refs{ idx_ref[i] };
u32 num_refs{ (u32)refs.size() };
// 遍历当前顶点的每个原始索引
for (u32 j{ 0 }; j < num_refs; ++j)
{
m.indices[refs[j]] = (u32)m.vertices.size();
vertex& v{ m.vertices.emplace_back() };
v.position = m.positions[m.raw_indices[refs[j]]];
vector n1{ XMLoadFloat3(&m.normals[refs[j]]) };
// 如果不是硬边,则尝试平滑法线
if (!is_hard_edge)
{
// 遍历剩余的原始索引
for (u32 k{ j + 1 }; k < num_refs; ++k)
{
f32 cos_theta{ 0.f };
vector n2{ XMLoadFloat3(&m.normals[refs[k]]) };
// 如果不是软边,则计算法线之间的点积
// 这里的n2是已经normalize过的所有他的长度为1这里就没有进行计算长度
// 这里的读取道德n1虽然也已经是normalize过的但是他可能会在后续改变
// 以需要计算长度
// cos(angle) = dot(n1,n2) / (||n1||*||n2||)
if (!is_soft_edge)
{
XMStoreFloat(&cos_theta, XMVector3Dot(n1, n2) * XMVector3ReciprocalLength(n1));
}
if (is_soft_edge || cos_theta >= cos_alpha)
{
n1 += n2;
m.indices[refs[k]] = m.indices[refs[j]];
refs.erase(refs.begin() + k);
--num_refs;
--k;
}
}
}
// 归一化合并后的法线,并存储到新的顶点中
XMStoreFloat3(&v.normal, XMVector3Normalize(n1));
}
}
}
void
process_uvs(mesh& m)
{
utl::vector<vertex> old_vertices;
old_vertices.swap(m.vertices);
utl::vector<u32> old_indices(m.indices.size());
old_indices.swap(m.indices);
const u32 num_vertices{ (u32)old_vertices.size() };
const u32 num_indices{ (u32)old_indices.size() };
assert(num_vertices && num_indices);
utl::vector<utl::vector<u32>> idx_ref(num_vertices);
for (u32 i{ 0 }; i < num_indices; ++i)
idx_ref[old_indices[i]].emplace_back(i);
for (u32 i{ 0 }; i < num_vertices; ++i)
{
auto& refs{ idx_ref[i] };
u32 num_refs{ (u32)refs.size() };
for (u32 j{ 0 }; j < num_refs; ++j)
{
m.indices[refs[j]] = (u32)m.vertices.size();
vertex& v{ old_vertices[old_indices[refs[j]]] };
v.uv = m.uv_sets[0][refs[j]];
m.vertices.emplace_back(v);
for (u32 k{ j + 1 }; k < num_refs; ++k)
{
v2& uv1{ m.uv_sets[0][refs[k]] };
if (XMScalarNearEqual(v.uv.x, uv1.x, epsilon) &&
XMScalarNearEqual(v.uv.y, uv1.y, epsilon))
{
m.indices[refs[k]] = m.indices[refs[j]];
refs.erase(refs.begin() + k);
--num_refs;
--k;
}
}
}
}
}
u64
get_vertex_element_size(elements::element_type::type elements_type)
{
using namespace elements;
switch (elements_type)
{
case element_type::static_normal: return sizeof(static_normal);
case element_type::static_normal_texture: return sizeof(static_normal_texture);
case element_type::static_color: return sizeof(static_color);
case element_type::skeletal: return sizeof(skeletal);
case element_type::skeletal_color: return sizeof(skeletal_color);
case element_type::skeletal_normal: return sizeof(skeletal_normal);
case element_type::skeletal_normal_color: return sizeof(skeletal_normal_color);
case element_type::skeletal_normal_texture: return sizeof(skeletal_normal_texture);
case element_type::skeletal_normal_texture_color: return sizeof(skeletal_normal_texture_color);
}
return 0;
}
void
pack_vertices(mesh& m)
{
const u32 num_vertices{ (u32)m.vertices.size() };
assert(num_vertices);
m.position_buffer.resize(sizeof(math::v3) * num_vertices);
math::v3 *const position_buffer{ (math::v3 *const)m.position_buffer.data() };
for (u32 i{ 0 }; i < num_vertices; ++i)
{
position_buffer[i] = m.vertices[i].position;
}
struct u16v2 { u16 x, y; };
struct u8v3 { u8 x, y, z; };
utl::vector<u8> t_signs(num_vertices);
utl::vector<u16v2> normals(num_vertices);
utl::vector<u16v2> tangents(num_vertices);
utl::vector<u8v3> joint_weights(num_vertices);
if (m.elements_type & elements::element_type::static_normal)
{
for (u32 i{ 0 }; i < num_vertices; ++i)
{
vertex& v{ m.vertices[i] };
t_signs[i] = { (u8)((v.normal.z > 0.f) << 1) };
normals[i] = {
(u16)pack_float<16>(v.normal.x, -1.f, 1.f),
(u16)pack_float<16>(v.normal.y, -1.f, 1.f)
};
}
if (m.elements_type & elements::element_type::static_normal_texture) {
for (u32 i{ 0 }; i < num_vertices; ++i)
{
vertex& v{ m.vertices[i] };
t_signs[i] |= (u8)((v.tangent.w > 0.f) && (v.normal.z > 0.f));
tangents[i] = {
(u16)pack_float<16>(v.tangent.x, -1.f, 1.f),
(u16)pack_float<16>(v.tangent.y, -1.f, 1.f)
};
}
}
}
if (m.elements_type & elements::element_type::skeletal) {
for (u32 i{ 0 }; i < num_vertices; ++i)
{
vertex& v{ m.vertices[i] };
//pack [0,1] to [0,255]
joint_weights[i] = {
(u8)pack_unit_float<8>(v.joint_weights.x),
(u8)pack_unit_float<8>(v.joint_weights.y),
(u8)pack_unit_float<8>(v.joint_weights.z)
};
}
}
m.element_buffer.resize(get_vertex_element_size(m.elements_type) * num_vertices);
using namespace elements;
switch (m.elements_type)
{
case element_type::static_color:
{
static_color *const element_buffer{ (static_color *const)m.element_buffer.data() };
for (u32 i{ 0 }; i < num_vertices; ++i)
{
vertex& v{ m.vertices[i] };
element_buffer[i] = { {v.red, v.gree, v.blue}, {} };
}
}
break;
case element_type::static_normal:
{
static_normal *const element_buffer{ (static_normal *const)m.element_buffer.data() };
for (u32 i{ 0 }; i < num_vertices; ++i)
{
vertex& v{ m.vertices[i] };
element_buffer[i] = { {v.red, v.gree, v.blue}, t_signs[i], {normals[i].x, normals[i].y} };
}
}
break;
case element_type::static_normal_texture:
{
static_normal_texture *const element_buffer{ (static_normal_texture *const)m.element_buffer.data() };
for (u32 i{ 0 }; i < num_vertices; ++i)
{
vertex& v{ m.vertices[i] };
element_buffer[i] = { {v.red, v.gree, v.blue}, t_signs[i],
{normals[i].x, normals[i].y}, {tangents[i].x, tangents[i].y},
v.uv };
}
}
break;
case element_type::skeletal:
{
skeletal *const element_buffer{ (skeletal *const)m.element_buffer.data() };
for (u32 i{ 0 }; i < num_vertices; ++i)
{
vertex& v{ m.vertices[i] };
const u16 indices[4]{ (u16)v.joint_indices.x, (u16)v.joint_indices.y, (u16)v.joint_indices.z,(u16)v.joint_indices.w };
element_buffer[i] = { {joint_weights[i].x,joint_weights[i].y,joint_weights[i].z},{},
{indices[0],indices[1],indices[2],indices[3]}
};
}
}
break;
case element_type::skeletal_color:
{
skeletal_color *const element_buffer{ (skeletal_color *const)m.element_buffer.data() };
for (u32 i{ 0 }; i < num_vertices; ++i)
{
vertex& v{ m.vertices[i] };
const u16 indices[4]{ (u16)v.joint_indices.x, (u16)v.joint_indices.y, (u16)v.joint_indices.z,(u16)v.joint_indices.w };
element_buffer[i] = { {joint_weights[i].x,joint_weights[i].y,joint_weights[i].z},{},
{indices[0],indices[1],indices[2],indices[3]},
{v.red, v.gree, v.blue},{}
};
}
}
break;
case element_type::skeletal_normal:
{
skeletal_normal *const element_buffer{ (skeletal_normal *const)m.element_buffer.data() };
for (u32 i{ 0 }; i < num_vertices; ++i)
{
vertex& v{ m.vertices[i] };
const u16 indices[4]{ (u16)v.joint_indices.x, (u16)v.joint_indices.y, (u16)v.joint_indices.z,(u16)v.joint_indices.w };
element_buffer[i] = { {joint_weights[i].x,joint_weights[i].y,joint_weights[i].z},t_signs[i],
{indices[0],indices[1],indices[2],indices[3]},
{normals[i].x, normals[i].y}
};
}
}
break;
case element_type::skeletal_normal_color:
{
skeletal_normal_color *const element_buffer{ (skeletal_normal_color *const)m.element_buffer.data() };
for (u32 i{ 0 }; i < num_vertices; ++i)
{
vertex& v{ m.vertices[i] };
const u16 indices[4]{ (u16)v.joint_indices.x, (u16)v.joint_indices.y, (u16)v.joint_indices.z,(u16)v.joint_indices.w };
element_buffer[i] = { {joint_weights[i].x,joint_weights[i].y,joint_weights[i].z},t_signs[i],
{indices[0],indices[1],indices[2],indices[3]},
{normals[i].x, normals[i].y},{v.red, v.gree, v.blue},{}
};
}
}
break;
case element_type::skeletal_normal_texture:
{
skeletal_normal_texture *const element_buffer{ (skeletal_normal_texture *const)m.element_buffer.data() };
for (u32 i{ 0 }; i < num_vertices; ++i)
{
vertex& v{ m.vertices[i] };
const u16 indices[4]{ (u16)v.joint_indices.x, (u16)v.joint_indices.y, (u16)v.joint_indices.z,(u16)v.joint_indices.w };
element_buffer[i] = { {joint_weights[i].x,joint_weights[i].y,joint_weights[i].z},t_signs[i],
{indices[0],indices[1],indices[2],indices[3]},
{normals[i].x, normals[i].y},{tangents[i].x, tangents[i].y},v.uv
};
}
}
break;
case element_type::skeletal_normal_texture_color:
{
skeletal_normal_texture_color *const element_buffer{ (skeletal_normal_texture_color *const)m.element_buffer.data() };
for (u32 i{ 0 }; i < num_vertices; ++i)
{
vertex& v{ m.vertices[i] };
const u16 indices[4]{ (u16)v.joint_indices.x, (u16)v.joint_indices.y, (u16)v.joint_indices.z,(u16)v.joint_indices.w };
element_buffer[i] = { {joint_weights[i].x,joint_weights[i].y,joint_weights[i].z},t_signs[i],
{indices[0],indices[1],indices[2],indices[3]},
{normals[i].x, normals[i].y},{tangents[i].x, tangents[i].y},v.uv,
{v.red, v.gree, v.blue},{}
};
}
}
break;
}
}
void
determin_elements_type(mesh&m)
{
using namespace elements;
if (m.normals.size())
{
if (m.uv_sets.size() && m.uv_sets[0].size())
{
m.elements_type = element_type::static_normal_texture;
}
else
{
m.elements_type = element_type::static_normal;
}
}
else if (m.colors.size())
{
m.elements_type = element_type::static_color;
}
}
void
process_vertices(mesh & m, const geometry_import_settings & settings)
{
assert((m.raw_indices.size() % 3) == 0);
if (settings.calculate_normals || m.normals.empty())
{
recalculate_normals(m);
}
process_normals(m, settings.smoothing_angle);
if (!m.uv_sets.empty())
{
process_uvs(m);
}
determin_elements_type(m);
pack_vertices(m);
}
u64
get_mesh_size(const mesh & m)
{
const u64 num_vertices{ m.vertices.size() };
const u64 position_buffer_size{ m.position_buffer.size() };
assert(position_buffer_size == sizeof(math::v3) * num_vertices);
const u64 element_buffer_size{ m.element_buffer.size() };
assert(element_buffer_size == get_vertex_element_size(m.elements_type) * num_vertices);
const u64 index_size{ (num_vertices < (1 << 16)) ? sizeof(u16) : sizeof(u32) };
const u64 index_buffer_size{ index_size * m.indices.size() };
constexpr u64 su32{ sizeof(u32) };
const u64 size{
su32 + m.name.size() + // mesh name length ad room for mesh name string
su32 + // lod id
su32 + // vertex size
su32 + // element type
su32 + // number of vertices
su32 + // index size( 16 or 32 )
su32 + // number of indices
sizeof(f32) + // LOD threshold
position_buffer_size + //room for vertices positions
element_buffer_size + //room for vertex elements
index_buffer_size // room for indices
};
return size;
}
u64
get_scene_size(const scene & scene)
{
constexpr u64 su32(sizeof(u32));
u64 size
{
su32 + // name
scene.name.size() + // room for scene name string
su32 // number of LODS
};
for (auto& lod : scene.lod_groups)
{
u64 lod_size
{
su32 + lod.name.size() + // LOD name length and room for LPD name string
su32 // number of meshes in this LOD
};
for (auto& m : lod.meshes)
{
lod_size += get_mesh_size(m);
}
size += lod_size;
}
return size;
}
void
pack_mesh_data(const mesh & m, utl::blob_stream_writer& blob)
{
// mesh name
blob.write((u32)m.name.size());
blob.write(m.name.c_str(), m.name.size());
// lod id
blob.write(m.lod_id);
// vertex element size
const u32 elements_size{ (u32)get_vertex_element_size(m.elements_type) };
blob.write(elements_size);
// elements type enumeration
blob.write((u32)m.elements_type);
// number of vertices
const u32 num_vertices{ (u32)m.vertices.size() };
blob.write(num_vertices);
// index size (16 or 32)
const u32 index_size{ (num_vertices < (1 << 16)) ? sizeof(u16) : sizeof(u32) };
blob.write(index_size);
// number of indices
const u32 num_indices{ (u32)m.indices.size() };
blob.write(num_indices);
// LOD threshold
blob.write(m.lod_threshold);
// position buffer
assert(m.position_buffer.size() == sizeof(math::v3) * num_vertices);
blob.write(m.position_buffer.data(), m.position_buffer.size());
// element buffer
assert(m.element_buffer.size() == elements_size * num_vertices);
blob.write(m.element_buffer.data(), m.element_buffer.size());
// index data
const u32 index_buffer_size{ index_size * num_indices };
const u8* data{ (const u8*)m.indices.data() };
utl::vector<u16> indices;
if (index_size == sizeof(u16))
{
indices.resize(num_indices);
for (u32 i{ 0 }; i < num_indices; ++i) indices[i] = (u16)m.indices[i];
data = (const u8*)indices.data();
}
blob.write(data, index_buffer_size);
}
bool
split_meshes_by_material(u32 material_idx, const mesh & m, mesh & submesh)
{
submesh.name = m.name;
submesh.lod_threshold = m.lod_threshold;
submesh.lod_id = m.lod_id;
submesh.material_used.emplace_back(material_idx);
submesh.uv_sets.resize(m.uv_sets.size());
const u32 num_polys{ (u32)m.raw_indices.size() / 3 };
utl::vector<u32> vertex_ref(m.positions.size(), u32_invalid_id);
for (u32 i{ 0 }; i < num_polys; ++i)
{
const u32 mtl_idx{ m.material_indices[i] };
if (mtl_idx != material_idx) continue;
const u32 index{ i * 3 };
for (u32 j = index; j < index + 3; ++j)
{
const u32 v_idx{ m.raw_indices[i] };
if (vertex_ref[v_idx] != u32_invalid_id)
{
submesh.raw_indices.emplace_back(vertex_ref[v_idx]);
}
else
{
submesh.raw_indices.emplace_back((u32)submesh.positions.size());
vertex_ref[v_idx] = submesh.raw_indices.back();
submesh.positions.emplace_back(m.positions[v_idx]);
}
if (m.normals.size())
{
submesh.normals.emplace_back(m.normals[j]);
}
if (m.tangents.size())
{
submesh.tangents.emplace_back(m.tangents[j]);
}
for (u32 k{ 0 }; k < m.uv_sets.size(); ++k)
{
if (m.uv_sets[k].size())
{
submesh.uv_sets[k].emplace_back(m.uv_sets[k][j]);
}
}
}
}
assert((submesh.raw_indices.size() % 3) == 0);
return !submesh.raw_indices.empty();
}
void
split_meshes_by_material(scene & scene)
{
for (auto& lod : scene.lod_groups)
{
utl::vector<mesh> new_meshes;
for (auto& m : lod.meshes)
{
const u32 num_materials{ (u32)m.material_used.size() };
if (num_materials > 1)
{
for (u32 i{ 0 }; i < num_materials; ++i)
{
mesh submesh{};
if (split_meshes_by_material(m.material_used[i], m, submesh))
{
new_meshes.emplace_back(submesh);
}
}
}
else
{
new_meshes.emplace_back(m);
}
}
new_meshes.swap(lod.meshes);
}
}
} // namespace
void
process_scene(scene& scene, const geometry_import_settings& settings)
{
split_meshes_by_material(scene);
for (auto& lod : scene.lod_groups)
for (auto& m : lod.meshes)
{
process_vertices(m, settings);
}
}
void
pack_data(const scene& scene, scene_data& data)
{
const u64 scene_size{ get_scene_size(scene) };
data.buffer_size = (u32)scene_size;
data.buffer = (u8*)CoTaskMemAlloc(scene_size);
assert(data.buffer);
utl::blob_stream_writer blob{ data.buffer, data.buffer_size };
// scene name
blob.write((u32)scene.name.size());
blob.write(scene.name.c_str(), scene.name.size());
// number of LODS
blob.write((u32)scene.lod_groups.size());
for (auto& lod : scene.lod_groups)
{
// LOD name
blob.write((u32)lod.name.size());
blob.write(lod.name.c_str(), lod.name.size());
// Number of meshes in this LOD
blob.write((u32)lod.meshes.size());
for (auto& m : lod.meshes)
{
pack_mesh_data(m, blob);
}
}
assert(scene_size == blob.offset());
}
}

View File

@@ -0,0 +1,175 @@
#pragma once
#include "ToolsCommon.h"
namespace XEngine::tools {
namespace elements {
struct element_type {
enum type :u32 {
position_only = 0x00,
static_normal = 0x01,
static_normal_texture = 0x03,
static_color = 0x04,
skeletal = 0x08,
skeletal_color = skeletal | static_color,
skeletal_normal = skeletal | static_normal,
skeletal_normal_color = skeletal_normal | skeletal_color,
skeletal_normal_texture = skeletal | static_normal_texture,
skeletal_normal_texture_color = skeletal_normal_texture | static_color
};
};
struct static_color
{
u8 color[3];
u8 pad;
};
struct static_normal
{
u8 color[3];
u8 t_sign; //bit 0 : tangent handedness * (tangent.z sign), bit 1 : normal.z sign (0 means -1, 1 means +1).
u16 normal[2];
};
struct static_normal_texture
{
u8 color[3];
u8 t_sign; //bit 0 : tangent handedness * (tangent.z sign), bit 1 : normal.z sign (0 means -1, 1 means +1).
u16 normal[2];
u16 tangent[2];
math::v2 uv;
};
struct skeletal
{
u8 joint_weights[3];
u8 pad;
u16 joint_indices[4];
};
struct skeletal_color
{
u8 joint_weights[3];
u8 pad;
u16 joint_indices[4];
u8 color[3];
u8 pad2;
};
struct skeletal_normal
{
u8 joint_weights[3];
u8 t_sign; //bit 0 : tangent handedness * (tangent.z sign), bit 1 : normal.z sign (0 means -1, 1 means +1).
u16 joint_indices[4];
u16 normal[2];
};
struct skeletal_normal_color
{
u8 joint_weights[3];
u8 t_sign; //bit 0 : tangent handedness * (tangent.z sign), bit 1 : normal.z sign (0 means -1, 1 means +1).
u16 joint_indices[4];
u16 normal[2];
u8 color[3];
u8 pad;
};
struct skeletal_normal_texture
{
u8 joint_weights[3];
u8 t_sign; //bit 0 : tangent handedness * (tangent.z sign), bit 1 : normal.z sign (0 means -1, 1 means +1).
u16 joint_indices[4];
u16 normal[2];
u16 tangent[2];
math::v2 uv;
};
struct skeletal_normal_texture_color
{
u8 joint_weights[3];
u8 t_sign; //bit 0 : tangent handedness * (tangent.z sign), bit 1 : normal.z sign (0 means -1, 1 means +1).
u16 joint_indices[4];
u16 normal[2];
u16 tangent[2];
math::v2 uv;
u8 color[3];
u8 pad;
};
} // namespace elements
struct vertex
{
math::v4 tangent{};
math::v4 joint_weights{};
math::u32v4 joint_indices{ u32_invalid_id,u32_invalid_id ,u32_invalid_id ,u32_invalid_id };
math::v3 position{};
math::v3 normal{};
math::v2 uv{};
u8 red{}, gree{}, blue{};
u8 pad;
};
struct mesh
{
utl::vector<math::v3> positions;
utl::vector<math::v3> normals;
utl::vector<math::v3> colors;
utl::vector<math::v4> tangents;
utl::vector<utl::vector<math::v2>> uv_sets;
utl::vector<u32> material_indices;
utl::vector<u32> material_used;
utl::vector<u32> raw_indices;
// Intermediate data
utl::vector<vertex> vertices;
utl::vector<u32> indices;
// Output data
std::string name;
elements::element_type::type elements_type;
utl::vector<u8> position_buffer;
utl::vector<u8> element_buffer;
f32 lod_threshold{ -1.f };
u32 lod_id{ u32_invalid_id };
};
struct lod_group
{
std::string name;
utl::vector<mesh> meshes;
};
struct scene
{
std::string name;
utl::vector<lod_group> lod_groups;
};
struct geometry_import_settings
{
f32 smoothing_angle;
u8 calculate_normals;
u8 calculate_tangents;
u8 reverse_handedness;
u8 import_embeded_textures;
u8 import_animations;
};
struct scene_data
{
u8* buffer;
u32 buffer_size;
geometry_import_settings settings;
};
void process_scene(scene& scene, const geometry_import_settings& settings);
void pack_data(const scene& scene, scene_data& data);
}

View File

@@ -0,0 +1,341 @@
#include "MeshPrimitives.h"
#include "Geometry.h"
namespace XEngine::tools
{
using namespace math;
using namespace DirectX;
namespace {
using primitive_mesh_creator = void(*)(scene&, const primitive_init_info& info);
void create_plane(scene& scene, const primitive_init_info& info);
void create_cube(scene& scene, const primitive_init_info& info);
void create_uv_sphere(scene& scene, const primitive_init_info& info);
void create_ico_sphere(scene& scene, const primitive_init_info& info);
void create_cylinder(scene& scene, const primitive_init_info& info);
void create_capsule(scene& scene, const primitive_init_info& info);
primitive_mesh_creator creators[]
{
create_plane,
create_cube,
create_uv_sphere,
create_ico_sphere,
create_cylinder,
create_capsule,
};
struct axis
{
enum : u32 {
x = 0,
y = 1,
z = 2,
};
};
/**
* @brief 创建一个平面网格mesh对象。
*
* 该平面网格可以通过参数定制其尺寸、分段、方向、UV 坐标范围以及顶点缠绕顺序。
* 需要注意positionuvs记录的是顶点在后面的indicesuv_set中细节记录点和uv的分配。
*
* @param info 包含平面尺寸和分段信息的结构体。
* @param horizontal_index 水平方向的轴索引 (0: x, 1: y, 2: z),默认为 axis::x。
* @param vertical_index 垂直方向的轴索引 (0: x, 1: y, 2: z),默认为 axis::z。
* @param flip_winding 是否翻转顶点缠绕顺序,默认为 false。
* @param offset 平面的初始位置偏移,默认为 {-0.5f, 0.f, -0.5f}。
* @param u_range U 坐标的范围,默认为 {0.f, 1.f}。
* @param v_range V 坐标的范围,默认为 {0.f, 1.f}。
* @return 创建的平面网格对象。
*/
mesh
create_plane(const primitive_init_info& info,
u32 horizontal_index = axis::x, u32 vertical_index = axis::z, bool flip_winding = false,
v3 offset = { -0.5f,0.f,-0.5f }, v2 u_range = { 0.f,1.f }, v2 v_range = { 0.f, 1.f })
{
// 参数合法性检查
assert(horizontal_index < 3 && vertical_index < 3);
assert(horizontal_index != vertical_index);
// 计算水平和垂直方向的分段数,并限制在合理范围内
const u32 horizontal_count{ clamp(info.segments[horizontal_index], 1u ,10u) };
const u32 vertical_count{ clamp(info.segments[vertical_index], 1u ,10u) };
// 计算水平和垂直方向的步长
const f32 horizontal_step{ 1.f / horizontal_count };
const f32 vertical_step{ 1.f / vertical_count };
// 计算 UV 坐标的步长
const f32 u_step{ (u_range.y - u_range.x) / horizontal_count };
const f32 v_step{ (v_range.y - v_range.x) / vertical_count };
// 创建网格对象和 UV 坐标向量
mesh m{};
m.name = "plane";
utl::vector<v2> uvs;
// 生成顶点和 UV 坐标
for (u32 j{ 0 }; j <= vertical_count; ++j)
for (u32 i{ 0 }; i <= horizontal_count; ++i)
{
// 计算顶点位置
v3 position{ offset };
f32* const as_array{ &position.x };
as_array[horizontal_index] += i * horizontal_step;
as_array[vertical_index] += j * vertical_step;
// 添加顶点位置到网格对象
m.positions.emplace_back(position.x * info.size.x, position.y * info.size.y, position.z * info.size.z);
// 计算 UV 坐标
v2 uv{ u_range.x, 1.f - v_range.x };
uv.x += i * u_step;
uv.y -= j * v_step;
// 添加 UV 坐标到向量
uvs.emplace_back(uv);
}
// 顶点数量检查
assert(m.positions.size() == (((u64)horizontal_count + 1) * ((u64)vertical_count + 1)));
// 计算每行顶点数
const u32 row_length{ horizontal_count + 1 };
// 生成三角形索引
for (u32 j{ 0 }; j < vertical_count; ++j)
{
for (u32 i{ 0 }; i < horizontal_count; ++i)
{
// 计算四边形四个顶点的索引
const u32 index[4]
{
i + j * row_length,
i + (j + 1) * row_length,
(i + 1) + j * row_length,
(i + 1) + (j + 1) * row_length,
};
// 添加三角形索引到网格对象,并根据 flip_winding 参数调整顶点顺序
// 这里索引是对应到position的序号。
m.raw_indices.emplace_back(index[0]);
m.raw_indices.emplace_back(index[flip_winding ? 2 : 1]);
m.raw_indices.emplace_back(index[flip_winding ? 1 : 2]);
m.raw_indices.emplace_back(index[2]);
m.raw_indices.emplace_back(index[flip_winding ? 3 : 1]);
m.raw_indices.emplace_back(index[flip_winding ? 1 : 3]);
}
}
// 索引数量检查
const u32 num_indices{ 3 * 2 * horizontal_count * vertical_count };
assert(m.raw_indices.size() == num_indices);
m.uv_sets.resize(1);
// 根据索引分配 UV 坐标
// 对应的是每个顶点。
for (u32 i{ 0 }; i < num_indices; ++i)
{
m.uv_sets[0].emplace_back(uvs[m.raw_indices[i]]);
}
return m;
}
mesh
create_uv_sphere(const primitive_init_info& info)
{
const u32 phi_count{ clamp(info.segments[axis::x], 3u, 64u) };
const u32 theta_count{ clamp(info.segments[axis::y], 2u, 64u) };
const f32 theta_step{ pi / theta_count };
const f32 phi_step{ two_pi / phi_count };
const u32 num_vertices{ 2 + phi_count * (theta_count - 1) };
const u32 num_indices{ 2 * 3 * phi_count + 2 * 3 * phi_count * (theta_count - 2) };
mesh m{};
m.name = "uv_sphere";
m.positions.resize(num_vertices);
// add the top vertex
u32 c{ 0 };
m.positions[c++] = { 0.f, info.size.y, 0.f };
for (u32 j{ 1 }; j <= (theta_count - 1); ++j)
{
const f32 theta{ j * theta_step };
for (u32 i{ 0 }; i < phi_count; ++i)
{
const f32 phi{ i * phi_step };
m.positions[c++] = {
info.size.x * XMScalarSin(theta) * XMScalarCos(phi),
info.size.y * XMScalarCos(theta),
-info.size.z * XMScalarSin(theta) * XMScalarSin(phi)
};
}
}
// add bottom vertex
m.positions[c++] = { 0.f, -info.size.y,0.f };
assert(c == num_vertices);
c = 0;
m.raw_indices.resize(num_indices);
utl::vector<v2> uvs(num_indices);
const f32 inv_theta_count{ 1.f / theta_count };
const f32 inv_phi_count{ 1.f / phi_count };
// indices for the top cap
for (u32 i{ 0 }; i < phi_count - 1; ++i)
{
uvs[c] = { (2 * i + 1) * 0.5f * inv_phi_count, 1.f };
m.raw_indices[c++] = 0;
uvs[c] = { i * inv_phi_count, 1.f - inv_theta_count };
m.raw_indices[c++] = i + 1;
uvs[c] = { (i + 1) * inv_phi_count, 1.f - inv_theta_count };
m.raw_indices[c++] = i + 2;
}
uvs[c] = { 1.f - 0.5f * inv_phi_count, 1.f };
m.raw_indices[c++] = 0;
uvs[c] = { 1.f - inv_phi_count, 1.f - inv_theta_count };
m.raw_indices[c++] = phi_count;
uvs[c] = { 1.f, 1.f - inv_theta_count };
m.raw_indices[c++] = 1;
// indices for the section
for (u32 j{ 0 }; j < (theta_count - 2); ++j)
{
for (u32 i{ 0 }; i < (phi_count - 1); ++i)
{
const u32 index[4]{
1 + i + j * phi_count,
1 + i + (j + 1) * phi_count,
1 + (i + 1) + (j + 1) * phi_count,
1 + (i + 1) + j * phi_count,
};
uvs[c] = { (i)*inv_phi_count, 1.f - (j + 1) * inv_theta_count };
m.raw_indices[c++] = index[0];
uvs[c] = { (i)*inv_phi_count, 1.f - (j + 2) * inv_theta_count };
m.raw_indices[c++] = index[1];
uvs[c] = { (i + 1) * inv_phi_count, 1.f - (j + 2) * inv_theta_count };
m.raw_indices[c++] = index[2];
uvs[c] = { (i)*inv_phi_count, 1.f - (j + 1) * inv_theta_count };
m.raw_indices[c++] = index[0];
uvs[c] = { (i + 1) * inv_phi_count, 1.f - (j + 2) * inv_theta_count };
m.raw_indices[c++] = index[2];
uvs[c] = { (i + 1) * inv_phi_count, 1.f - (j + 1) * inv_theta_count };
m.raw_indices[c++] = index[3];
}
const u32 index[4]{
phi_count + j * phi_count,
phi_count + (j + 1) * phi_count,
1 + (j + 1) * phi_count,
1 + j * phi_count
};
uvs[c] = { (1.f) - inv_phi_count, 1.f - (j + 1) * inv_theta_count };
m.raw_indices[c++] = index[0];
uvs[c] = { (1.f) - inv_phi_count, 1.f - (j + 2) * inv_theta_count };
m.raw_indices[c++] = index[1];
uvs[c] = { (1.f), 1.f - (j + 2) * inv_theta_count };
m.raw_indices[c++] = index[2];
uvs[c] = { (1.f) - inv_phi_count, 1.f - (j + 1) * inv_theta_count };
m.raw_indices[c++] = index[0];
uvs[c] = { (1.f), 1.f - (j + 2) * inv_theta_count };
m.raw_indices[c++] = index[2];
uvs[c] = { (1.f), 1.f - (j + 1) * inv_theta_count };
m.raw_indices[c++] = index[3];
}
// indices for the bottom
const u32 south_pole_index{ (u32)m.positions.size() - 1 };
for (u32 i{ 0 }; i < (phi_count - 1); ++i)
{
uvs[c] = { (2 * i + 1) * 0.5f * inv_phi_count, 0.f };
m.raw_indices[c++] = south_pole_index;
uvs[c] = { (i + 1) * inv_phi_count, inv_theta_count };
m.raw_indices[c++] = south_pole_index - phi_count + i + 1;
uvs[c] = { (i)*inv_phi_count, inv_theta_count };
m.raw_indices[c++] = south_pole_index - phi_count + i;
}
uvs[c] = { 1.f - 0.5f * inv_phi_count, 0.f };
m.raw_indices[c++] = south_pole_index;
uvs[c] = { 1.f, inv_theta_count };
m.raw_indices[c++] = south_pole_index - phi_count;
uvs[c] = { 1.f - inv_phi_count, inv_theta_count };
m.raw_indices[c++] = south_pole_index - 1;
m.uv_sets.emplace_back(uvs);
return m;
}
void
create_plane(scene& scene, const primitive_init_info& info)
{
lod_group lod{};
lod.name = "plane";
lod.meshes.emplace_back(create_plane(info));
scene.lod_groups.emplace_back(lod);
}
void
create_cube(scene& scene, const primitive_init_info& info)
{
}
void
create_uv_sphere(scene& scene, const primitive_init_info& info)
{
lod_group lod{};
lod.name = "uv_sphere";
lod.meshes.emplace_back(create_uv_sphere(info));
scene.lod_groups.emplace_back(lod);
}
void
create_ico_sphere(scene& scene, const primitive_init_info& info)
{
}
void
create_cylinder(scene& scene, const primitive_init_info& info)
{
}
void
create_capsule(scene& scene, const primitive_init_info& info)
{
}
static_assert(_countof(creators) == primitive_mesh_type::count);
} // namespace
EDITOR_INTERFACE void
CreatePrimitiveMesh(scene_data* data, primitive_init_info* info)
{
assert(data && info);
assert(info->type < primitive_mesh_type::count);
scene scene{};
creators[info->type](scene, *info);
data->settings.calculate_normals = 1;
process_scene(scene, data->settings);
pack_data(scene, *data);
}
}

View File

@@ -0,0 +1,26 @@
#pragma once
#include "ToolsCommon.h"
namespace XEngine::tools {
enum primitive_mesh_type : u32
{
plane,
cube,
uv_sphere,
ico_sphere,
cylinder,
capsule,
count
};
struct primitive_init_info
{
primitive_mesh_type type;
u32 segments[3]{ 1,1,1 };
math::v3 size{ 1,1,1 };
u32 lod{ 0 };
};
}

View File

@@ -0,0 +1,11 @@
#pragma once
#include "CommonHeader.h"
#include <combaseapi.h>
#ifndef EDITOR_INTERFACE
#define EDITOR_INTERFACE extern "C" __declspec(dllexport)
#endif

175
ContentTools/Geometry.h Normal file
View File

@@ -0,0 +1,175 @@
#pragma once
#include "ToolsCommon.h"
namespace XEngine::tools {
namespace elements {
struct element_type {
enum type :u32 {
position_only = 0x00,
static_normal = 0x01,
static_normal_texture = 0x03,
static_color = 0x04,
skeletal = 0x08,
skeletal_color = skeletal | static_color,
skeletal_normal = skeletal | static_normal,
skeletal_normal_color = skeletal_normal | skeletal_color,
skeletal_normal_texture = skeletal | static_normal_texture,
skeletal_normal_texture_color = skeletal_normal_texture | static_color
};
};
struct static_color
{
u8 color[3];
u8 pad;
};
struct static_normal
{
u8 color[3];
u8 t_sign; //bit 0 : tangent handedness * (tangent.z sign), bit 1 : normal.z sign (0 means -1, 1 means +1).
u16 normal[2];
};
struct static_normal_texture
{
u8 color[3];
u8 t_sign; //bit 0 : tangent handedness * (tangent.z sign), bit 1 : normal.z sign (0 means -1, 1 means +1).
u16 normal[2];
u16 tangent[2];
math::v2 uv;
};
struct skeletal
{
u8 joint_weights[3];
u8 pad;
u16 joint_indices[4];
};
struct skeletal_color
{
u8 joint_weights[3];
u8 pad;
u16 joint_indices[4];
u8 color[3];
u8 pad2;
};
struct skeletal_normal
{
u8 joint_weights[3];
u8 t_sign; //bit 0 : tangent handedness * (tangent.z sign), bit 1 : normal.z sign (0 means -1, 1 means +1).
u16 joint_indices[4];
u16 normal[2];
};
struct skeletal_normal_color
{
u8 joint_weights[3];
u8 t_sign; //bit 0 : tangent handedness * (tangent.z sign), bit 1 : normal.z sign (0 means -1, 1 means +1).
u16 joint_indices[4];
u16 normal[2];
u8 color[3];
u8 pad;
};
struct skeletal_normal_texture
{
u8 joint_weights[3];
u8 t_sign; //bit 0 : tangent handedness * (tangent.z sign), bit 1 : normal.z sign (0 means -1, 1 means +1).
u16 joint_indices[4];
u16 normal[2];
u16 tangent[2];
math::v2 uv;
};
struct skeletal_normal_texture_color
{
u8 joint_weights[3];
u8 t_sign; //bit 0 : tangent handedness * (tangent.z sign), bit 1 : normal.z sign (0 means -1, 1 means +1).
u16 joint_indices[4];
u16 normal[2];
u16 tangent[2];
math::v2 uv;
u8 color[3];
u8 pad;
};
} // namespace elements
struct vertex
{
math::v4 tangent{};
math::v4 joint_weights{};
math::u32v4 joint_indices{ u32_invalid_id,u32_invalid_id ,u32_invalid_id ,u32_invalid_id };
math::v3 position{};
math::v3 normal{};
math::v2 uv{};
u8 red{}, gree{}, blue{};
u8 pad;
};
struct mesh
{
utl::vector<math::v3> positions;
utl::vector<math::v3> normals;
utl::vector<math::v3> colors;
utl::vector<math::v4> tangents;
utl::vector<utl::vector<math::v2>> uv_sets;
utl::vector<u32> material_indices;
utl::vector<u32> material_used;
utl::vector<u32> raw_indices;
// Intermediate data
utl::vector<vertex> vertices;
utl::vector<u32> indices;
// Output data
std::string name;
elements::element_type::type elements_type;
utl::vector<u8> position_buffer;
utl::vector<u8> element_buffer;
f32 lod_threshold{ -1.f };
u32 lod_id{ u32_invalid_id };
};
struct lod_group
{
std::string name;
utl::vector<mesh> meshes;
};
struct scene
{
std::string name;
utl::vector<lod_group> lod_groups;
};
struct geometry_import_settings
{
f32 smoothing_angle;
u8 calculate_normals;
u8 calculate_tangents;
u8 reverse_handedness;
u8 import_embeded_textures;
u8 import_animations;
};
struct scene_data
{
u8* buffer;
u32 buffer_size;
geometry_import_settings settings;
};
void process_scene(scene& scene, const geometry_import_settings& settings);
void pack_data(const scene& scene, scene_data& data);
}

View File

@@ -0,0 +1,11 @@
#pragma once
#include "CommonHeader.h"
#include <combaseapi.h>
#ifndef EDITOR_INTERFACE
#define EDITOR_INTERFACE extern "C" __declspec(dllexport)
#endif

View File

@@ -0,0 +1,55 @@
#pragma once
#ifdef _WIN64
#pragma warning(disable: 4530)
#endif // _WIN64
#include <stdint.h>
#include <assert.h>
#include <typeinfo>
#include <memory>
#include <unordered_map>
#include <string>
#include <mutex>
#include <cstring>
//#define USESTDFUNC
#if defined USESTDFUNC
#include <functional>
#endif
#if defined(_WIN64)
#include <DirectXMath.h>
#endif
#ifndef DISABLE_COPY
#define DISABLE_COPY(T) \
explicit T(const T&) = delete; \
T& operator=(const T&) = delete;
#endif
#ifndef DISABLE_MOVE
#define DISABLE_MOVE(T) \
explicit T(T&&) = delete; \
T& operator=(T&&) = delete
#endif
#ifndef DISABLE_COPY_AND_MOVE
#define DISABLE_COPY_AND_MOVE(T) DISABLE_COPY(T) DISABLE_MOVE(T);
#endif
#ifdef _DEBUG
#define DEBUG_OP(x) x
#else
#define DEBUG_OP(x)
#endif
//Common Header
#include "XEnginType.h"
#include "..\Utilities\Math.h"
#include "..\Utilities\MathTypes.h"
#include "..\Utilities\Utilities.h"
#include "Id.h"

143
Engine/Common/Id.h Normal file
View File

@@ -0,0 +1,143 @@
/**
* @file Id.h
* @brief 使用“索引 + 代数”打包方案的 32 位标识符工具。
* @details
* 位布局:
* - 低位:索引
* - 高位:代数
*
* 当索引被复用时,通过递增代数降低悬挂句柄误用风险。
*/
#pragma once
#include "CommonHeader.h"
namespace XEngine::id
{
/**
* @brief 标识符相关类型与常量。
*/
using id_type = u32;
namespace detail {
/**
* @brief 高位代数字段的位宽。
*/
constexpr u32 generation_bits{ 8 };
/**
* @brief 低位索引字段的位宽。
*/
constexpr u32 index_bit{ sizeof(id_type) * 8 - generation_bits };
/**
* @brief 用于提取索引字段的掩码。
*/
constexpr id_type index_mask{ (id_type{1} << index_bit) - 1 };
/**
* @brief 右移后用于提取代数字段的掩码。
*/
constexpr id_type generation_mask{ (id_type{1} << generation_bits) - 1 };
}
/**
* @brief 表示无效标识符的哨兵值。
*/
constexpr id_type invalid_id{ (id_type)-1 };
/**
* @brief 延迟回收删除项时的触发阈值。
*/
constexpr u32 min_deleted_elements{ 1024 };
/**
* @brief 可容纳代数字段的最小无符号整型。
*/
using generation_type = std::conditional_t<detail::generation_bits <= 16, std::conditional_t<detail::generation_bits <= 8, u8, u16>, u32>;
static_assert(sizeof(generation_type) * 8 >= detail::generation_bits);
static_assert((sizeof(id_type) - sizeof(generation_type)) > 0);
/**
* @brief 判断标识符是否有效。
* @param id 打包后的标识符。
* @return 当 @p id 不是无效哨兵值时返回 true。
*/
constexpr bool
is_valid(id_type id)
{
return id != invalid_id;
}
/**
* @brief 从打包标识符中提取索引字段。
* @param id 打包后的标识符。
* @return 低位索引字段。
* @pre 提取后的索引不能等于保留的全 1 索引值。
*/
constexpr id_type
index(id_type id)
{
id_type index{ id & detail::index_mask };
assert(index != detail::index_mask);
return index;
}
/**
* @brief 从打包标识符中提取代数字段。
* @param id 打包后的标识符。
* @return 高位代数字段。
*/
constexpr id_type
generation(id_type id)
{
return (id >> detail::index_bit) & detail::generation_mask;
}
/**
* @brief 为同一索引生成下一代标识符。
* @param id 当前打包标识符。
* @return 索引不变且代数递增后的标识符。
* @pre 代数递增后不能溢出到保留范围。
*/
constexpr id_type
new_generation(id_type id)
{
const id_type generation{ id::generation(id) + 1 };
assert(generation < (((u64)1 << detail::generation_bits) - 1));
return index(id) | (generation << detail::index_bit);
}
#if _DEBUG
namespace detail {
struct id_base
{
constexpr explicit id_base(id_type id) : _id(id) {}
constexpr operator id_type() const { return _id; }
private:
id_type _id;
};
}
/**
* @brief 在调试构建下声明强类型标识符。
* @details
* 调试构建使用派生自 id_base 的包装类型。
* 发布构建退化为 id_type 别名以保持零开销抽象。
*/
#define DEFINE_TYPED_ID(name) \
struct name final : id::detail::id_base \
{ \
constexpr explicit name(id::id_type id) \
: id_base{id}{} \
constexpr name() : id_base{ 0 }{} \
};
#else
/**
* @brief 发布模式下的零运行时开销类型别名。
*/
#define DEFINE_TYPED_ID(name) using name = id::id_type;
#endif
}

View File

@@ -0,0 +1,23 @@
#pragma once
#include <stdint.h>
using u8 = uint8_t;
using u16 = uint16_t;
using u32 = uint32_t;
using u64 = uint64_t;
using s8 = int8_t;
using s16 = int16_t;
using s32 = int32_t;
using s64 = int64_t;
constexpr u64 u64_invalid_id{ 0xffffffffffffffff };
constexpr u32 u32_invalid_id{ 0xffffffff };
constexpr u16 u16_invalid_id{ 0xffff };
constexpr u8 u8_invalid_id { 0xff };
using f32 = float;

View File

@@ -0,0 +1,13 @@
/**
* @file ComponentsCommon.h
* @brief Components 模块公共依赖头。
*/
#pragma once
#include "..\Common\CommonHeader.h"
#include "..\EngineAPI\GameEntity.h"
#include "..\EngineAPI\TransformComponent.h"
#include "..\EngineAPI\ScriptComponent.h"
namespace XEngine::game_entity {
}

View File

@@ -0,0 +1,108 @@
#include "Entity.h"
#include "Script.h"
#include "Transform.h"
namespace XEngine::game_entity {
namespace {
utl::vector<transform::component> transforms;
utl::vector<script::component> scripts;
utl::vector<id::generation_type> generations;
utl::deque<entity_id> free_ids;
} // anonymous namespace
entity
create(entity_info info)
{
assert(info.transform);
if (!info.transform) return entity{ };
entity_id id;
if (free_ids.size() > id::min_deleted_elements)
{
id = free_ids.front();
assert(!is_alive( id ));
free_ids.pop_front();
id = entity_id{ id::new_generation(id) };
++generations[id::index(id)];
}
else
{
id = entity_id{ (id::id_type)generations.size() };
generations.push_back(0);
transforms.emplace_back();
scripts.emplace_back();
}
const entity new_entity{ id };
const id::id_type index{ id::index(id) };
//Create Transform Component
assert(!transforms[index].is_valid());
transforms[index] = transform::create(*info.transform, new_entity);
if (!transforms[index].is_valid()) return{ };
//Create Script Component
if (info.script && info.script->script_creator)
{
assert(!scripts[index].is_valid());
scripts[index] = script::create(*info.script, new_entity);
assert(scripts[index].is_valid());
}
return new_entity;
}
void
remove(entity_id id)
{
const id::id_type index{ id::index(id) };
assert(is_alive(id));
if (scripts[index].is_valid())
{
script::remove(scripts[index]);
scripts[index] = {};
}
transform::remove(transforms[index]);
transforms[index] = {};
free_ids.push_back(id);
}
bool
is_alive(entity_id id)
{
assert(id::is_valid(id));
const id::id_type index{ id::index(id) };
assert(index < generations.size());
return (generations[index] == id::generation(id) && transforms[index].is_valid());
}
transform::component
entity::transform() const
{
assert(is_alive(_id));
const id::id_type index{ id::index(_id) };
return transforms[index];
}
script::component
entity::script() const
{
assert(is_alive(_id));
const id::id_type index{ id::index(_id) };
return scripts[index];
}
}

View File

@@ -0,0 +1,51 @@
/**
* @file Entity.h
* @brief 实体组件生命周期管理接口。
*/
#pragma once
#include "ComponentsCommon.h"
#include "Transform.h"
namespace XEngine {
/**
* @brief 前置声明组件初始化参数结构。
*/
#define INIT_INFO(component) namespace component { struct init_info; }
INIT_INFO(transform);
INIT_INFO(script);
#undef INIT_INFO
namespace game_entity {
/**
* @brief 创建实体时可选的组件初始化信息。
*/
struct entity_info
{
transform::init_info* transform{ nullptr };
script::init_info* script{ nullptr };
};
/**
* @brief 创建实体并按需附加组件。
* @param info 实体初始化信息。
* @return 新建实体句柄。
*/
entity create(entity_info info);
/**
* @brief 删除实体及其关联组件。
* @param id 实体标识符。
*/
void remove(entity_id id);
/**
* @brief 判断实体是否仍然存活。
* @param id 实体标识符。
* @return 存活返回 true否则返回 false。
*/
bool is_alive(entity_id id);
}
}

View File

@@ -0,0 +1,367 @@
/**
* @file Script.cpp
* @brief 脚本组件存储、脚本注册表与逐帧更新实现。
* @details
* 该文件负责脚本系统运行时核心流程:
* - 维护脚本实例数组与 script_id 到实例下标映射;
* - 维护脚本工厂注册表,支持按哈希查询创建函数;
* - 在脚本回调中缓存变换修改,并在帧末批量提交到 Transform 系统。
*/
#include "Script.h"
#include "Entity.h"
#include "Transform.h"
#define USE_TRANSFORM_CACHE_MAP 1
namespace XEngine::script {
namespace {
/**
* @brief 活跃脚本实例数组。
*/
utl::vector<detail::script_ptr> entity_scripts;
/**
* @brief script_id 索引到实例数组下标的映射。
*/
utl::vector<id::id_type> id_mapping;
/**
* @brief script_id 代数数组。
*/
utl::vector<id::generation_type> generations;
/**
* @brief 可复用 script_id 队列。
*/
utl::deque<script_id> free_ids;
/**
* @brief 脚本对 Transform 的延迟修改缓存。
*/
utl::vector<transform::component_cache> transform_cache;
#if USE_TRANSFORM_CACHE_MAP
std::unordered_map<id::id_type, u32> cache_map;
#endif
using script_registry = std::unordered_map<size_t, detail::script_creator>;
/**
* @brief 获取全局脚本工厂注册表。
* @return 注册表引用。
*/
script_registry&
registery()
{
static script_registry reg;
return reg;
}
#ifdef USE_WITH_EDITOR
/**
* @brief 获取编辑器脚本名集合。
* @return 脚本名数组引用。
*/
utl::vector<std::string>&
script_names()
{
static utl::vector<std::string> names;
return names;
}
#endif
/**
* @brief 判断脚本组件是否存在且有效。
* @param id 脚本组件 ID。
* @return 存在返回 true。
*/
bool
exists(script_id id)
{
assert(id::is_valid(id));
const id::id_type index{ id::index(id) };
assert(index < generations.size() && id_mapping[index] < entity_scripts.size());
assert(generations[index] == id::generation(id));
return (generations[index] == id::generation(id) &&
entity_scripts[id_mapping[index]] &&
entity_scripts[id_mapping[index]]->is_valid());
}
#if USE_TRANSFORM_CACHE_MAP
/**
* @brief 获取实体对应的 Transform 缓存项。
* @param entity 目标实体。
* @return 缓存项指针。
*/
transform::component_cache *const
get_cache_ptr(const game_entity::entity *const entity)
{
assert(game_entity::is_alive((*entity).get_id()));
const transform::transform_id id{ (*entity).transform().get_id() };
u32 index{ u32_invalid_id };
auto pair = cache_map.try_emplace(id, id::invalid_id);
if (pair.second)
{
index = (u32)transform_cache.size();
transform_cache.emplace_back();
transform_cache.back().id = id;
cache_map[id] = index;
}
else
{
index = cache_map[id];
}
assert(index < transform_cache.size());
return &transform_cache[index];
}
#else
/**
* @brief 线性查找方式获取 Transform 缓存项。
* @param entity 目标实体。
* @return 缓存项指针。
*/
transform::component_cache *const
get_cache_ptr(const game_entity::entity *const entity)
{
assert(game_entity::is_alive((*entity).get_id()));
const transform::transform_id id{ (*entity).transform().get_id() };
for (auto& cache : transform_cache)
{
if (cache.id == id)
{
return &cache;
}
}
transform_cache.emplace_back();
transform_cache.back().id = id;
return &transform_cache.back();
}
#endif
}// namespace
namespace detail {
/**
* @brief 注册脚本工厂函数。
* @param tag 脚本名哈希。
* @param func 工厂函数。
* @return 注册成功返回 1。
*/
u8
register_script(size_t tag, script_creator func)
{
bool result{ registery().insert(script_registry::value_type{tag, func}).second };
assert(result);
return result;
}
/**
* @brief 按哈希获取脚本工厂函数。
* @param tag 脚本名哈希。
* @return 脚本工厂函数。
*/
script_creator
get_script_creator(size_t tag)
{
auto script = XEngine::script::registery().find(tag);
assert(script != XEngine::script::registery().end() && script->first == tag);
return script->second;
}
#ifdef USE_WITH_EDITOR
/**
* @brief 记录脚本名称供编辑器使用。
* @param name 脚本类名。
* @return 写入成功返回 1。
*/
u8
add_script_name(const char* name)
{
script_names().emplace_back(name);
return true;
}
#endif
}// namespace detail
/**
* @brief 创建脚本组件并实例化脚本对象。
* @param info 脚本初始化参数。
* @param entity 绑定实体。
* @return 脚本组件句柄。
*/
component
create(init_info info, game_entity::entity entity)
{
assert(entity.is_valid());
assert(info.script_creator);
script_id id{};
if (free_ids.size() > id::min_deleted_elements)
{
id = free_ids.front();
assert(!exists(id));
free_ids.pop_front();
id = script_id{ id::new_generation(id) };
++generations[id::index(id)];
}
else
{
id = script_id{ (id::id_type)id_mapping.size() };
id_mapping.emplace_back();
generations.push_back(0);
}
assert(id::is_valid(id));
const id::id_type index{ (id::id_type)entity_scripts.size() };
entity_scripts.emplace_back(info.script_creator(entity));
assert(entity_scripts.back()->get_id() == entity.get_id());
id_mapping[id::index(id)] = index;
return component{ id };
}
/**
* @brief 删除脚本组件并更新映射关系。
* @param c 脚本组件句柄。
*/
void
remove(component c)
{
assert(c.is_valid() && exists(c.get_id()));
const script_id id{ c.get_id() };
const id::id_type index{ id::index(id) };
const script_id last_id{ entity_scripts.back()->script().get_id() };
utl::erase_unordered(entity_scripts, index);
id_mapping[id::index(last_id)] = index;
id_mapping[id::index(id)] = id::invalid_id;
}
/**
* @brief 执行脚本逐帧更新并提交变换缓存。
* @param dt 帧时间步长。
*/
void
update(float dt) {
for (auto& ptr : entity_scripts)
{
ptr->update(dt);
}
if (transform_cache.size())
{
transform::update(transform_cache.data(), (u32)transform_cache.size());
transform_cache.clear();
#if USE_TRANSFORM_CACHE_MAP
cache_map.clear();
#endif
}
}
/**
* @brief 缓存脚本写入的旋转变更。
* @param entity 目标实体。
* @param rotation_quaternion 旋转四元数。
*/
void
entity_script::set_rotation(const game_entity::entity *const entity, math::v4 rotation_quaternion)
{
transform::component_cache& cache{ *get_cache_ptr(entity) };
cache.flags |= transform::component_flags::rotation;
cache.rotation = rotation_quaternion;
}
/**
* @brief 缓存脚本写入的朝向变更。
* @param entity 目标实体。
* @param orientation_vector 朝向向量。
*/
void
entity_script::set_orientation(const game_entity::entity *const entity, math::v3 orientation_vector)
{
transform::component_cache& cache{ *get_cache_ptr(entity) };
cache.flags |= transform::component_flags::orientation;
cache.orientation = orientation_vector;
}
/**
* @brief 缓存脚本写入的位置变更。
* @param entity 目标实体。
* @param position 位置向量。
*/
void
entity_script::set_position(const game_entity::entity *const entity, math::v3 position)
{
transform::component_cache& cache{ *get_cache_ptr(entity) };
cache.flags |= transform::component_flags::position;
cache.position = position;
}
/**
* @brief 缓存脚本写入的缩放变更。
* @param entity 目标实体。
* @param scale 缩放向量。
*/
void
entity_script::set_scale(const game_entity::entity *const entity, math::v3 scale)
{
transform::component_cache& cache{ *get_cache_ptr(entity) };
cache.flags |= transform::component_flags::scale;
cache.scale = scale;
}
} // namespace script
#ifdef USE_WITH_EDITOR
/**
* @brief ATL 的 SAFEARRAY 封装头。
* @details
* 该头文件提供 CComSafeArray用于在编辑器导出路径中构造 COM 安全数组,
* 以便把脚本名列表跨 DLL 边界返回给外部调用方。
*/
#include <atlsafe.h>
/**
* @brief 导出脚本名列表给编辑器端。
* @details
* 返回值为 LPSAFEARRAYSAFEARRAY*),元素类型为 BSTR。
* 内部流程:
* - 从 script_names() 读取脚本名数量;
* - 为空时返回 nullptr
* - 使用 CComSafeArray<BSTR> 分配数组;
* - 逐项把 std::string 转成 BSTR 填入数组;
* - 通过 Detach 转移 SAFEARRAY 所有权给调用方。
* 调用方在不再使用后应负责释放 SAFEARRAY。
*/
extern "C" __declspec(dllexport)
LPSAFEARRAY
get_script_names()
{
// 读取当前已注册脚本名数量。
const u32 size{ (u32)XEngine::script::script_names().size() };
// 没有脚本时返回空指针,避免创建空 SAFEARRAY。
if (!size) return nullptr;
// 创建固定长度的 BSTR 安全数组。
CComSafeArray<BSTR> names(size);
// 将脚本名逐项转换为 BSTR 并写入数组。
for (u32 i{ 0 }; i < size; ++i)
{
names.SetAt(i, A2BSTR_EX(XEngine::script::script_names()[i].c_str()), false);
}
// 释放 CComSafeArray 对底层 SAFEARRAY 的管理并返回给调用方。
return names.Detach();
}
#endif

View File

@@ -0,0 +1,35 @@
/**
* @file Script.h
* @brief 脚本组件创建、销毁与更新接口。
*/
#pragma once
#include "ComponentsCommon.h"
namespace XEngine::script {
/**
* @brief 脚本组件初始化信息。
*/
struct init_info
{
detail::script_creator script_creator;
};
/**
* @brief 为实体创建脚本组件。
* @param info 脚本初始化信息。
* @param entity 目标实体。
* @return 创建后的脚本组件句柄。
*/
component create(init_info info, game_entity::entity entity);
/**
* @brief 移除脚本组件。
* @param c 脚本组件句柄。
*/
void remove(component c);
/**
* @brief 更新所有脚本组件。
* @param dt 帧时间间隔(秒)。
*/
void update(float dt);
}

View File

@@ -0,0 +1,213 @@
#include "Transform.h"
#include "Entity.h"
namespace XEngine::transform {
namespace {
utl::vector<math::m4x4> to_world;
utl::vector<math::m4x4> inv_world;
utl::vector<math::v3> positions;
utl::vector<math::v3> orientations;
utl::vector<math::v4> rotations;
utl::vector<math::v3> scales;
utl::vector<u8> has_transform;
utl::vector<u8> changes_from_previous_frame;
u8 read_write_flag;
void
calculate_transform_matrices(id::id_type index)
{
assert(rotations.size() >= index);
assert(positions.size() >= index);
assert(scales.size() >= index);
using namespace DirectX;
XMVECTOR r{ XMLoadFloat4(&rotations[index]) };
XMVECTOR t{ XMLoadFloat3(&positions[index]) };
XMVECTOR s{ XMLoadFloat3(&scales[index]) };
XMMATRIX world{ XMMatrixAffineTransformation(s, XMQuaternionIdentity(), r, t) };
XMStoreFloat4x4(&to_world[index], world);
world.r[3] = XMVectorSet(0.f, 0.f, 0.f, 1.f);
XMMATRIX inverse_world{ XMMatrixInverse(nullptr,world) };
XMStoreFloat4x4(&inv_world[index], inverse_world);
has_transform[index] = 1;
}
math::v3
calculate_orientation(math::v4 rotation)
{
using namespace DirectX;
XMVECTOR rotation_quat{ XMLoadFloat4(&rotation) };
XMVECTOR front{ XMVectorSet(0.f,0.f,1.f,0.f) };
math::v3 orientation;
XMStoreFloat3(&orientation, XMVector3Rotate(front, rotation_quat));
return orientation;
}
void
set_rotation(transform_id id, const math::v4& rotation_quaternion)
{
const u32 index{ id::index(id) };
rotations[index] = rotation_quaternion;
orientations[index] = calculate_orientation(rotation_quaternion);
has_transform[index] = 0;
changes_from_previous_frame[index] |= component_flags::rotation;
}
void
set_orientation(transform_id id, const math::v3& rotation_quaternion)
{
}
void
set_position(transform_id id, const math::v3& position)
{
const u32 index{ id::index(id) };
positions[index] = position;
has_transform[index] = 0;
changes_from_previous_frame[index] |= component_flags::position;
}
void
set_scale(transform_id id, const math::v3& scale)
{
const u32 index{ id::index(id) };
scales[index] = scale;
has_transform[index] = 0;
changes_from_previous_frame[index] |= component_flags::scale;
}
} // namespace
component
create(init_info info, game_entity::entity entity)
{
assert(entity.is_valid());
const id::id_type entity_index{ id::index(entity.get_id()) };
if (positions.size() > entity_index)
{
math::v4 rotation{ info.rotation };
rotations[entity_index] = rotation;
orientations[entity_index] = calculate_orientation(rotation);
positions[entity_index] = math::v3{ info.position };
scales[entity_index] = math::v3{ info.scale };
has_transform[entity_index] = 0;
changes_from_previous_frame[entity_index] = (u8)component_flags::all;
}
else
{
assert(positions.size() == entity_index);
rotations.emplace_back(info.rotation);
orientations.emplace_back(calculate_orientation(math::v4{ info.rotation }));
positions.emplace_back(info.position);
scales.emplace_back(info.scale);
has_transform.emplace_back((u8)0);
to_world.emplace_back();
inv_world.emplace_back();
changes_from_previous_frame.emplace_back((u8)component_flags::all);
}
return component{ transform_id{ entity.get_id() } };
}
void
remove([[maybe_unused]] component c)
{
assert(c.is_valid());
}
void
get_transform_matrices(const game_entity::entity_id id, math::m4x4& world, math::m4x4& inverse_world)
{
assert(game_entity::entity{ id }.is_valid());
const id::id_type entity_index{ id::index(id) };
if (!has_transform[entity_index])
{
calculate_transform_matrices(entity_index);
}
world = to_world[entity_index];
inverse_world = inv_world[entity_index];
}
void
get_update_component_flags(const game_entity::entity_id *const ids, u32 count, u8 *const flags)
{
assert(ids && count && flags);
read_write_flag = 1;
for (u32 i{ 0 }; i < count; ++i)
{
assert(game_entity::entity{ ids[i] }.is_valid());
flags[i] = changes_from_previous_frame[id::index(ids[i])];
}
}
void
update(const component_cache *const cache, u32 count)
{
assert(cache && count);
if (read_write_flag)
{
memset(changes_from_previous_frame.data(), 0, changes_from_previous_frame.size());
read_write_flag = 0;
}
for (u32 i{ 0 }; i < count; ++i)
{
const component_cache& c{ cache[i] };
assert(component{ c.id }.is_valid());
if (c.flags & component_flags::rotation)
{
set_rotation(c.id, c.rotation);
}
if (c.flags & component_flags::orientation)
{
set_orientation(c.id, c.orientation);
}
if (c.flags & component_flags::position)
{
set_position(c.id, c.position);
}
if (c.flags & component_flags::scale)
{
set_scale(c.id, c.scale);
}
}
}
math::v3
component::orientation() const
{
assert(is_valid());
return orientations[id::index(_id)];
}
math::v3
component::position() const
{
assert(is_valid());
return positions[id::index(_id)];
}
math::v3
component::scale() const
{
assert(is_valid());
return scales[id::index(_id)];
}
math::v4
component::rotation() const
{
assert(is_valid());
return rotations[id::index(_id)];
}
}

View File

@@ -0,0 +1,83 @@
/**
* @file Transform.h
* @brief 变换组件数据结构与更新接口。
*/
#pragma once
#include "ComponentsCommon.h"
namespace XEngine::transform {
/**
* @brief 变换组件初始化参数。
*/
struct init_info
{
f32 position[3]{};
f32 rotation[4]{};
f32 scale[3]{1.f, 1.f, 1.f};
};
/**
* @brief 变换组件缓存更新标记位。
*/
struct component_flags
{
enum flags :u32 {
rotation = 0x01,
orientation = 0x02,
position = 0x04,
scale = 0x08,
all = rotation | orientation | position| scale
};
};
/**
* @brief 变换组件批量更新缓存项。
*/
struct component_cache
{
math::v4 rotation;
math::v3 orientation;
math::v3 position;
math::v3 scale;
transform_id id;
u32 flags;
};
/**
* @brief 获取实体的世界矩阵与逆世界矩阵。
* @param id 实体标识符。
* @param world 输出世界矩阵。
* @param inverse_world 输出逆世界矩阵。
*/
void get_transform_matrices(const game_entity::entity_id id, math::m4x4& world, math::m4x4& inverse_world);
/**
* @brief 为实体创建变换组件。
* @param info 变换初始化参数。
* @param entity 目标实体。
* @return 创建后的变换组件句柄。
*/
component create( init_info info, game_entity::entity entity);
/**
* @brief 移除变换组件。
* @param c 变换组件句柄。
*/
void remove(component c);
/**
* @brief 查询指定实体集合的变换更新标记。
* @param ids 实体标识符数组。
* @param count 实体数量。
* @param flags 输出标记数组。
*/
void get_update_component_flags(const game_entity::entity_id *const ids, u32 count, u8 *const flags);
/**
* @brief 批量提交变换缓存更新。
* @param cache 缓存数组首地址。
* @param count 缓存项数量。
*/
void update(const component_cache *const cache, u32 count);
}

View File

@@ -0,0 +1,217 @@
/**
* @file ContentLoader.cpp
* @brief 关卡内容反序列化与实体创建流程实现。
* @details
* 主要流程为:读取二进制关卡文件 -> 解析组件块 -> 创建实体并绑定脚本/变换。
* 同时提供运行期清理逻辑,确保测试场景退出时能回收创建的实体与资源。
* 本实现仅在非 SHIPPING 且 Win64 条件下启用。
*/
#include "ContentLoader.h"
#include "..\Components\Script.h"
#include "..\EngineAPI\GameEntity.h"
#include "..\EngineAPI\TransformComponent.h"
#include "..\EngineAPI\ScriptComponent.h"
#include "..\Components\Entity.h"
#include "Graphics\Renderer.h"
#include <fstream>
#include <filesystem>
#include <Windows.h>
#if !defined(SHIPPING) && defined(_WIN64)
namespace XEngine::content {
namespace {
/**
* @brief 关卡文件中的组件类型标签。
*/
enum component_type
{
transform,
script,
count
};
/**
* @brief 已创建的实体缓存,用于卸载阶段统一回收。
*/
utl::vector<game_entity::entity> entities;
/**
* @brief 解析变换组件时复用的临时结构。
*/
transform::init_info transform_info{};
/**
* @brief 解析脚本组件时复用的临时结构。
*/
script::init_info script_info{};
/**
* @brief 从二进制流读取变换组件数据。
* @param data 当前读取游标,函数返回后移动到组件末尾。
* @param info 目标实体初始化信息。
* @return 读取成功返回 true。
*/
bool
read_transform(const u8*& data, game_entity::entity_info& info)
{
using namespace DirectX;
f32 rotation[3];
assert(!info.transform);
memcpy(&transform_info.position[0], data, sizeof(transform_info.position)); data += sizeof(transform_info.position);
memcpy(&rotation[0], data, sizeof(rotation)); data += sizeof(rotation);
memcpy(&transform_info.scale[0], data, sizeof(transform_info.scale)); data += sizeof(transform_info.scale);
XMFLOAT3A rot{ &rotation[0] };
XMVECTOR quat{ XMQuaternionRotationRollPitchYawFromVector(XMLoadFloat3A(&rot)) };
XMFLOAT4A rot_quat{};
XMStoreFloat4A(&rot_quat, quat);
memcpy(&transform_info.rotation[0], &rot_quat.x, sizeof(transform_info.rotation));
info.transform = &transform_info;
return true;
}
/**
* @brief 从二进制流读取脚本组件并查找脚本工厂。
* @param data 当前读取游标,函数返回后移动到组件末尾。
* @param info 目标实体初始化信息。
* @return 解析并找到脚本工厂返回 true。
*/
bool
read_script(const u8*& data, game_entity::entity_info& info)
{
assert(!info.script);
const u32 name_length{ *data }; data += sizeof(u32);
if (!name_length) return false;
assert(name_length < 256);
char script_name[256];
memcpy(&script_name, data, name_length); data += name_length;
script_name[name_length] = 0;
script_info.script_creator = script::detail::get_script_creator(script::detail::string_hash()(script_name));
info.script = &script_info;
return script_info.script_creator != nullptr;
}
using compoent_reader = bool(*)(const u8*&, game_entity::entity_info&);
compoent_reader component_readers[]
{
read_transform,
read_script,
};
static_assert(_countof(component_readers) == component_type::count);
/**
* @brief 读取完整二进制文件到内存缓冲区。
* @param path 文件路径。
* @param data 输出缓冲区。
* @param size 输出字节数。
* @return 成功返回 true。
*/
bool
read_file(std::filesystem::path path, std::unique_ptr<u8[]>&data, u64& size)
{
if (!std::filesystem::exists(path)) return false;
size = std::filesystem::file_size(path);
assert(size);
if (!size) return false;
data = std::make_unique<u8[]>(size);
std::ifstream file{ path, std::ios::in | std::ios::binary };
if (!file || !file.read((char*)data.get(), size))
{
file.close();
return false;
}
file.close();
return true;
}
} // namespace
/**
* @brief 加载 game.bin 并批量创建实体。
* @return 全部实体创建成功返回 true。
*/
bool
load_games()
{
std::unique_ptr<u8[]> game_data{};
u64 size{ 0 };
if (!read_file("game.bin", game_data, size)) return false;
assert(game_data.get());
const u8* at{ game_data.get() };
constexpr u32 su32{ sizeof(u32) };
const u32 num_entities{ *at }; at += su32;
if (!num_entities) return false;
for (u32 entity_index{ 0 }; entity_index < num_entities; ++entity_index)
{
game_entity::entity_info info{};
const u32 entity_type{ *at }; at += su32;
const u32 num_components{ *at }; at += su32;
if (!num_components)return false;
for (u32 component_index{ 0 }; component_index < num_components; ++component_index)
{
const u32 component_type{ *at }; at += su32;
assert(component_type < component_type::count);
if (!component_readers[component_type](at, info)) return false;
}
assert(info.transform);
game_entity::entity entity{ game_entity::create(info) };
if (!entity.is_valid()) return false;
entities.emplace_back(entity);
}
assert(at == game_data.get() + size);
return true;
}
/**
* @brief 回收 load_games 创建的实体对象。
*/
void
unload_game()
{
for (auto entity : entities)
{
game_entity::remove(entity.get_id());
}
}
/**
* @brief 加载引擎内置着色器二进制文件。
* @param shaders 输出字节缓冲区。
* @param size 输出字节大小。
* @return 加载成功返回 true。
*/
//bool
//load_engine_shaders(std::unique_ptr<u8[]>&shaders, u64& size)
//{
// auto path = graphics::get_engine_shaders_path();
// return read_file(path, shaders, size);
//}
}
#endif //!defined(SHIPPING)

View File

@@ -0,0 +1,35 @@
/**
* @file ContentLoader.h
* @brief 编辑器/测试构建下的关卡内容加载入口声明。
* @details
* 本文件定义内容系统对外暴露的高层加载接口,负责:
* - 从磁盘加载测试关卡与实体描述;
* - 卸载已创建的实体与运行时资源;
* - 读取并返回引擎着色器二进制数据供渲染模块初始化。
*/
#pragma once
#include "CommonHeader.h"
#if !defined(SHIPPING)
namespace XEngine::content {
/**
* @brief 加载测试游戏内容并创建对应运行时对象。
* @return 加载成功返回 true否则返回 false。
*/
bool load_games();
/**
* @brief 卸载由 load_games 创建的内容与对象。
*/
void unload_game();
/**
* @brief 从内容目录读取引擎着色器二进制文件。
* @param shader 输出的字节缓冲区。
* @param size 输出的缓冲区大小(字节)。
* @return 读取成功返回 true否则返回 false。
*/
bool load_engine_shaders(std::unique_ptr<u8[]>&shader, u64 size);
}
#endif //!defined(SHIPPING)

View File

@@ -0,0 +1,528 @@
/**
* @file ContentToEngine.cpp
* @brief 内容二进制数据到渲染/资源句柄的转换实现。
* @details
* 本文件完成导入数据的运行时落地,包含:
* - 网格层级(LOD)结构重排与子网格 GPU 资源创建;
* - 着色器分组缓存与 key 索引查询;
* - 材质/纹理/几何资源的统一创建与释放入口。
*/
#include "ContentToEngine.h"
#include "Graphics\Renderer.h"
#include "Utilities\IOStream.h"
namespace XEngine::content {
namespace {
/**
* @brief 几何层级缓冲区的视图封装。
* @details
* 对连续内存中的几何层级布局进行字段映射提供阈值、LOD 偏移与 GPU ID 访问。
*/
class geometry_hierarchy_stream
{
public:
DISABLE_COPY_AND_MOVE(geometry_hierarchy_stream);
geometry_hierarchy_stream(u8 *const buffer, u32 lods = u32_invalid_id)
:_buffer{ buffer }
{
assert(buffer && lods);
if (lods != u32_invalid_id)
{
*((u32*)buffer) = lods;
}
_lod_count = *((u32*)buffer);
_thresholds = (f32*)(&buffer[sizeof(u32)]);
_lod_offsets = (lod_offset*)(&_thresholds[_lod_count]);
_gpu_ids = (id::id_type*)(&_lod_offsets[_lod_count]);
}
void gpu_ids(u32 lod, id::id_type*& ids, u32& id_count)
{
assert(lod < _lod_count);
ids = &_gpu_ids[_lod_offsets[lod].offset];
id_count = _lod_offsets[lod].count;
}
u32 lod_from_threshold(f32 threshold)
{
assert(threshold > 0);
if (_lod_count == 1) return 0;
for (u32 i{ _lod_count - 1 }; i > 0; --i)
{
if (_thresholds[i] <= threshold) return i;
}
assert(false);
return 0;
}
[[nodiscard]] constexpr u32 lod_count() const { return _lod_count; }
[[nodiscard]] constexpr f32* thresholds() const { return _thresholds; }
[[nodiscard]] constexpr lod_offset* lod_offsets() const { return _lod_offsets; }
[[nodiscard]] constexpr id::id_type* gpu_ids() const { return _gpu_ids; }
private:
u8 *const _buffer;
f32* _thresholds;
lod_offset* _lod_offsets;
id::id_type* _gpu_ids;
u32 _lod_count;
};
/**
* @brief 支持 noexcept 移动的着色器组缓存容器。
*/
struct noexcept_map {
std::unordered_map<u32, std::unique_ptr<u8[]>> map;
noexcept_map() = default;
noexcept_map(const noexcept_map&) = default;
noexcept_map(noexcept_map&&) noexcept = default;
noexcept_map& operator=(const noexcept_map&) = default;
noexcept_map& operator=(noexcept_map&&) noexcept = default;
};
/**
* @brief 单子网格资源的指针标记位。
*/
constexpr uintptr_t single_mesh_marker{ (uintptr_t)0x01 };
/**
* @brief 几何层级资源池。
*/
utl::free_list<u8*> geometry_hierarchies;
/**
* @brief 几何资源互斥锁。
*/
std::mutex geometry_mutex;
/**
* @brief 着色器组资源池。
*/
utl::free_list<noexcept_map> shader_groups;
/**
* @brief 着色器资源互斥锁。
*/
std::mutex shader_mutex;
/**
* @brief 预估并计算几何层级缓冲区所需字节数。
* @param data 几何二进制输入数据。
* @return 所需缓冲区大小(字节)。
*/
u32
get_geometry_hierarchy_buffer_size(const void *const data)
{
assert(data);
utl::blob_stream_reader blob{ (const u8*)data };
const u32 lod_count{ blob.reader<u32>() };
assert(lod_count);
constexpr u32 su32{ sizeof(u32) };
u32 size{ su32 + (sizeof(f32) + sizeof(lod_offset)) * lod_count };
for (u32 lod_idx{ 0 }; lod_idx < lod_count; ++lod_idx)
{
// skip threshold
blob.skip(sizeof(f32));
// add size of gpu_ids
size += sizeof(id::id_type) * blob.reader<u32>();
// skip submesh data and goto next LOD
blob.skip(blob.reader<u32>());
}
return size;
}
/**
* @brief 创建多 LOD 网格层级资源。
* @param data 几何二进制输入数据。
* @return 几何内容资源 ID。
*/
//id::id_type
//create_mesh_hierarchy(const void *const data)
//{
// assert(data);
//
// const u32 size{ get_geometry_hierarchy_buffer_size(data) };
// u8 *const hierarchy_buffer{ (u8 *const)malloc(size) };
//
// utl::blob_stream_reader blob{ (const u8*)data };
// const u32 lod_count{ blob.reader<u32>() };
// assert(lod_count);
// geometry_hierarchy_stream stream{ hierarchy_buffer, lod_count };
// u32 submesh_index{ 0 };
// id::id_type *const gpu_ids{ stream.gpu_ids() };
//
// for (u32 lod_idx{ 0 }; lod_idx < lod_count; ++lod_idx)
// {
// stream.thresholds()[lod_idx] = blob.reader<f32>();
// const u32 id_count{ blob.reader<u32>() };
// assert(id_count < (1 << 16));
// stream.lod_offsets()[lod_idx] = { (u16)submesh_index, (u16)id_count };
// blob.skip(sizeof(u32)); // skip sizeof submeshes
// for (u32 id_idx{ 0 }; id_idx < id_count; ++id_idx)
// {
// const u8* at{ blob.position() };
// gpu_ids[submesh_index++] = graphics::add_submesh(at);
// blob.skip((u32)(at - blob.position()));
// assert(submesh_index < (1 << 16));
// }
// }
//
// assert([&]()
// {
// f32 previous_theshold{ stream.thresholds()[0] };
// for (u32 i{ 1 }; i < lod_count; ++i)
// {
// if (stream.thresholds()[i] <= previous_theshold) return false;
// previous_theshold = stream.thresholds()[i];
// }
// return true;
// }());
//
// static_assert(alignof(void*) > 2, "We need the least significant bit for th single mesh marker.");
// std::lock_guard lock{ geometry_mutex };
// return geometry_hierarchies.add(hierarchy_buffer);
//}
/**
* @brief 判断几何数据是否为单 LOD 单子网格。
* @param data 几何二进制输入数据。
* @return 单子网格返回 true。
*/
bool
is_single_mesh(const void *const data)
{
assert(data);
utl::blob_stream_reader blob{ (const u8*)data };
const u32 lod_count{ blob.reader<u32>() };
assert(lod_count);
if (lod_count > 1) return false;
blob.skip(sizeof(f32)); // skip threhold
const u32 submesh_count{ blob.reader<u32>() };
assert(submesh_count);
return submesh_count == 1;
}
/**
* @brief 创建单子网格资源并编码为伪指针句柄。
* @param data 几何二进制输入数据。
* @return 几何内容资源 ID。
*/
//id::id_type
//create_single_submesh(const void *const data)
//{
// assert(data);
// utl::blob_stream_reader blob{ (const u8*)data };
// // skip lod_count, lod_threshold, submesh_count, and sizeof_submeshes
// blob.skip(sizeof(u32) + sizeof(f32) + sizeof(u32) + sizeof(u32));
// const u8* at{ blob.position() };
// const id::id_type gpu_id{ graphics::add_submesh(at) };
//
// // create a fake pointer and put it in the geometry_hierarchies.
// static_assert(sizeof(uintptr_t) > sizeof(id::id_type));
// constexpr u8 shift_bits{ (sizeof(uintptr_t) - sizeof(id::id_type)) << 3 };
// u8 *const fake_pointer{ (u8 *const)((((uintptr_t)gpu_id) << shift_bits) | single_mesh_marker) };
// std::lock_guard lock{ geometry_mutex };
// return geometry_hierarchies.add(fake_pointer);
//}
/**
* @brief 从单子网格伪指针解码 GPU 资源 ID。
* @param pointer 编码后的伪指针。
* @return GPU 子网格 ID。
*/
constexpr id::id_type
gpu_id_from_fake_pointer(u8 *const pointer)
{
assert((uintptr_t)pointer & single_mesh_marker);
static_assert(sizeof(uintptr_t) > sizeof(id::id_type));
constexpr u8 shift_bits{ (sizeof(uintptr_t) - sizeof(id::id_type)) << 3 };
return (((uintptr_t)pointer) >> shift_bits) & (uintptr_t)id::invalid_id;
}
//
// data contain:
//struct{
// u32 lod_count,
// struct{
// f32 lod_threshold,
// u32 submesh_count,
// u32 sizeof_submeshes,
// struct{
// u32 element_size, u32 vertex_count,
// u32 index_count, u32 elements_type, u32 primitive_topology
// u8 positions[sizeof(f32) * 3 * vertex_count],
// u8 elements[sizeof(element_size) * vertex_count],
// u8 indices[index_size * index_count],
// } submeshes[submesh_count]
// }mesh_lods[lod_count]
// } geometry;
//
// Output format
//
// struct{
// u32 lod_count,
// f32 thresholds[lod_count]
// struct{
// u16 offset,
// u16 count
// }lod_offsets[lod_count],
// id::idtype gpu_ids[total_number_of_submeshes]
//}geometry_hierarchy
//
//
//
//
/**
* @brief 按输入几何形态创建对应资源。
* @param data 几何二进制输入数据。
* @return 几何内容资源 ID。
*/
//id::id_type
//create_geometry_resource(const void *const data)
//{
// assert(data);
// return is_single_mesh(data) ? create_single_submesh(data) : create_mesh_hierarchy(data);
//}
/**
* @brief 销毁几何资源并释放关联子网格。
* @param id 几何内容资源 ID。
*/
//void
//destory_geometry_resource(id::id_type id)
//{
// std::lock_guard lock{ geometry_mutex };
// u8 *const pointer{ geometry_hierarchies[id] };
// if ((uintptr_t)pointer & single_mesh_marker)
// {
// graphics::remove_submesh(gpu_id_from_fake_pointer(pointer));
// }
// else
// {
// geometry_hierarchy_stream stream{ pointer };
// const u32 lod_count{ stream.lod_count() };
// u32 id_index{ 0 };
// for (u32 lod{ 0 }; lod < lod_count; ++lod)
// {
// for (u32 i{ 0 }; i < stream.lod_offsets()[lod].count; ++i)
// {
// graphics::remove_submesh(stream.gpu_ids()[id_index++]);
// }
// }
//
// free(pointer);
// }
//
// geometry_hierarchies.remove(id);
//}
/**
* @brief 创建材质资源。
* @param data 材质初始化数据。
* @return 材质资源 ID。
*/
//id::id_type
//create_material_resource(const void *const data)
//{
// assert(data);
// return graphics::add_material(*(const graphics::material_init_info *const)data);
//}
} // namespace
/**
* @brief 销毁材质资源。
* @param id 材质资源 ID。
*/
//void
//destory_material_resource(id::id_type id)
//{
// graphics::remove_material(id);
//}
/**
* @brief 统一资源创建入口。
* @param data 资源二进制数据。
* @param type 资源类型。
* @return 资源 ID。
*/
//id::id_type
//create_resource(const void *const data, asset_type::type type)
//{
// assert(data);
// id::id_type id{ id::invalid_id };
//
// switch (type)
// {
// case XEngine::content::asset_type::unknown: break;
// case XEngine::content::asset_type::animation: break;
// case XEngine::content::asset_type::audio: break;
// case XEngine::content::asset_type::material: id = create_material_resource(data); break;
// case XEngine::content::asset_type::mesh: id = create_geometry_resource(data); break;
// case XEngine::content::asset_type::skeleton: break;
// case XEngine::content::asset_type::texture: break;
// }
//
// assert(id::is_valid(id));
// return id;
//
//}
/**
* @brief 统一资源销毁入口。
* @param id 资源 ID。
* @param type 资源类型。
*/
//void
//destroy_resource(id::id_type id, asset_type::type type)
//{
// assert(id::is_valid(id));
// switch (type)
// {
// case XEngine::content::asset_type::unknown: break;
// case XEngine::content::asset_type::animation: break;
// case XEngine::content::asset_type::audio: break;
// case XEngine::content::asset_type::material: destory_material_resource(id); break;
// case XEngine::content::asset_type::mesh: destory_geometry_resource(id); break;
// case XEngine::content::asset_type::skeleton: break;
// case XEngine::content::asset_type::texture: break;
// default:
// assert(false);
// break;
// }
//}
/**
* @brief 注册着色器组并复制其二进制数据。
* @param shaders 着色器数组。
* @param num_shaders 着色器数量。
* @param keys 查询键数组。
* @return 着色器组 ID。
*/
id::id_type
add_shader_group(const u8 *const * shaders, u32 num_shaders, const u32 *const keys)
{
assert(shaders && num_shaders && keys);
noexcept_map group;
for (u32 i{ 0 }; i < num_shaders; ++i)
{
assert(shaders[i]);
const compiled_shader_ptr shader_ptr{ (const compiled_shader_ptr)shaders[i] };
const u64 size{ shader_ptr->buffer_size()};
std::unique_ptr<u8[]> shader{ std::make_unique<u8[]>(size) };
memcpy(shader.get(), shaders[i], size);
group.map[keys[i]] = std::move(shader);
}
std::lock_guard lock{ shader_mutex };
return shader_groups.add(std::move(group));
}
/**
* @brief 删除着色器组。
* @param id 着色器组 ID。
*/
void
remove_shader_group(id::id_type id)
{
std::lock_guard lock{ shader_mutex };
assert(id::is_valid(id));
shader_groups[id].map.clear();
shader_groups.remove(id);
}
/**
* @brief 在着色器组内按键查找着色器。
* @param id 着色器组 ID。
* @param shader_key 查询键。
* @return 命中时返回着色器指针。
*/
compiled_shader_ptr
get_shader(id::id_type id, u32 shader_key)
{
std::lock_guard lock{ shader_mutex };
assert(id::is_valid(id));
for (const auto& [key, value] : shader_groups[id].map)
{
if (key == shader_key)
{
return (const compiled_shader_ptr)value.get();
}
}
assert(false);
return nullptr;
}
/**
* @brief 获取几何资源包含的全部子网格 GPU ID。
* @param geometry_content_id 几何内容资源 ID。
* @param id_count 输出数组长度。
* @param gpu_ids 输出数组。
*/
void
get_submesh_gpu_id(id::id_type geometry_content_id, u32 id_count, id::id_type *const gpu_ids)
{
std::lock_guard lock{ geometry_mutex };
u8 *const pointer{ geometry_hierarchies[geometry_content_id] };
if ((uintptr_t)pointer & single_mesh_marker)
{
assert(id_count == 1);
*gpu_ids = gpu_id_from_fake_pointer(pointer);
}
else
{
geometry_hierarchy_stream stream{ pointer };
assert([&]() {
const u32 lod_count{ stream.lod_count() };
const lod_offset lod_offset{ stream.lod_offsets()[lod_count - 1] };
const u32 gpu_id_count{ (u32)lod_offset.offset + (u32)lod_offset.count };
return gpu_id_count == id_count;
}());
memcpy(gpu_ids, stream.gpu_ids(), sizeof(id::id_type) * id_count);
}
}
/**
* @brief 按阈值查询各几何实例使用的 LOD 偏移。
* @param geometry_ids 几何资源 ID 数组。
* @param thresholds 每个实例的阈值数组。
* @param id_count 查询数量。
* @param offsets 输出 LOD 偏移数组。
*/
void
get_lod_offset(const id::id_type *const geometry_ids, const f32 *const thresholds, u32 id_count, utl::vector<lod_offset>& offsets)
{
assert(geometry_ids && thresholds && id_count);
assert(offsets.empty());
std::lock_guard lock{ geometry_mutex };
for (u32 i{ 0 }; i < id_count; ++i)
{
u8 *const pointer{ geometry_hierarchies[geometry_ids[i]] };
if ((uintptr_t)pointer & single_mesh_marker)
{
assert(id_count == 1);
offsets.emplace_back(lod_offset{ 0,1 });
}
else
{
geometry_hierarchy_stream stream{ pointer };
const u32 lod{ stream.lod_from_threshold(thresholds[i]) };
offsets.emplace_back(stream.lod_offsets()[lod]);
}
}
}
}

View File

@@ -0,0 +1,133 @@
/**
* @file ContentToEngine.h
* @brief 内容系统到运行时资源系统的数据桥接接口。
* @details
* 该文件描述资源导入后的统一输入格式与运行时句柄接口,核心能力包括:
* - 依据 asset_type 创建/销毁网格、材质、纹理等资源;
* - 管理变体着色器组并按 key 查询编译结果;
* - 提供几何 LOD 偏移与 GPU 子网格 ID 的查询能力。
*/
#pragma once
#include "CommonHeader.h"
namespace XEngine::content {
/**
* @brief 资源类型枚举容器。
*/
struct asset_type {
/**
* @brief 运行时可识别的内容资源类型。
*/
enum type : u32
{
unknown = 0,
animation,
audio,
material,
mesh,
skeleton,
texture,
count
};
};
/**
* @brief 指定某个 LOD 在子网格数组中的起始偏移与数量。
*/
struct lod_offset
{
u16 offset;
u16 count;
};
typedef struct compiled_shader {
/**
* @brief 着色器哈希的固定字节数。
*/
static constexpr u32 hash_length{ 16 };
/**
* @brief 获取字节码长度。
*/
constexpr u64 byte_code_size() const { return _byte_code_size; }
/**
* @brief 获取哈希数据首地址。
*/
constexpr const u8 *const hash() const { return &_hash[0]; }
/**
* @brief 获取字节码首地址。
*/
constexpr const u8 *const byte_code() const { return &_byte_code; }
/**
* @brief 获取当前对象对应的总缓冲区长度。
*/
constexpr const u64 buffer_size() const { return sizeof(_byte_code_size ) + hash_length + _byte_code_size; }
/**
* @brief 根据字节码大小计算总缓冲区长度。
*/
constexpr static u64 buffer_size(u64 size) { return sizeof(_byte_code_size ) + hash_length + size; }
private:
u64 _byte_code_size;
u8 _hash[hash_length];
u8 _byte_code;
}const *compiled_shader_ptr;
/**
* @brief 根据二进制内容创建对应运行时资源。
* @param data 编译后的资源二进制数据。
* @param type 资源类型。
* @return 新创建资源的 ID。
*/
id::id_type create_resource(const void *const data, asset_type::type type);
/**
* @brief 销毁指定资源类型下的运行时资源。
* @param id 资源 ID。
* @param type 资源类型。
*/
void destroy_resource(id::id_type id, asset_type::type type);
/**
* @brief 注册一组着色器变体。
* @param shaders 着色器二进制列表。
* @param num_shaders 着色器数量。
* @param keys 每个着色器对应的查询键。
* @return 着色器组 ID。
*/
id::id_type add_shader_group(const u8 *const * shaders, u32 num_shaders, const u32 *const keys);
/**
* @brief 删除着色器组及其缓存数据。
* @param id 着色器组 ID。
*/
void remove_shader_group(id::id_type id);
/**
* @brief 按组 ID 与键查询编译后着色器。
* @param id 着色器组 ID。
* @param shader_key 着色器键值。
* @return 命中时返回着色器描述,否则返回空。
*/
compiled_shader_ptr get_shader(id::id_type id, u32 shader_key);
/**
* @brief 根据几何内容 ID 批量获取子网格 GPU 资源 ID。
* @param geometry_content_id 几何内容资源 ID。
* @param id_count 输出数组长度。
* @param gpu_ids 输出的 GPU ID 数组。
*/
void get_submesh_gpu_id(id::id_type geometry_content_id, u32 id_count, id::id_type *const gpu_ids);
/**
* @brief 按阈值批量查询几何资源对应的 LOD 偏移信息。
* @param geometry_ids 几何资源 ID 数组。
* @param thresholds 每个几何对应的 LOD 阈值。
* @param id_count 查询数量。
* @param offsets 输出的 LOD 偏移信息。
*/
void get_lod_offset(const id::id_type *const geometry_ids, const f32 *const thresholds, u32 id_count, utl::vector<lod_offset>& offsets);
}

103
Engine/Core/EngineWin32.cpp Normal file
View File

@@ -0,0 +1,103 @@
/**
* @file EngineWin32.cpp
* @brief Win32 运行模式下的引擎生命周期实现。
* @details
* 该文件负责连接平台消息循环与引擎帧循环:
* - 初始化阶段加载内容并创建主窗口;
* - 更新阶段驱动脚本系统与帧间休眠;
* - 关闭阶段销毁窗口并卸载内容资源。
*/
#if !defined(SHIPPING) && defined(_WIN64)
#include "..\Content\ContentLoader.h"
#include "..\Components\Script.h"
#include "..\Platform\PlatformTypes.h"
#include "..\Platform\Platform.h"
#include "..\Graphics\Renderer.h"
#include <thread>
using namespace XEngine;
namespace {
/**
* @brief 游戏窗口渲染表面实例。
*/
graphics::render_surface game_window{};
/**
* @brief 主窗口消息回调。
* @param hwnd 窗口句柄。
* @param msg 消息类型。
* @param wparam 消息参数。
* @param lparam 消息参数。
* @return 消息处理结果。
*/
LRESULT win_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
switch (msg)
{
case WM_DESTROY:
{
if (game_window.window.is_closed())
{
PostQuitMessage(0);
return 0;
}
}
case WM_SYSCHAR:
if (wparam == VK_RETURN && (HIWORD(lparam) & KF_ALTDOWN))
{
game_window.window.set_fullscreen(!game_window.window.is_fullscreen());
return 0;
}
default:
break;
}
return DefWindowProc(hwnd, msg, wparam, lparam);
}
}
/**
* @brief 初始化引擎运行环境。
* @return 初始化成功返回 true。
*/
bool
engine_initialize()
{
if (!XEngine::content::load_games()) return false;
platform::window_init_info info
{
&win_proc, nullptr, L"XGame"
};
game_window.window = platform::create_window(&info);
if (!game_window.window.is_valid()) return false;
return true;
}
/**
* @brief 执行单帧更新。
*/
void
engine_update()
{
XEngine::script::update(10.f);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
/**
* @brief 关闭引擎并释放运行时资源。
*/
void
engine_shutdown()
{
platform::remove_window(game_window.window.get_id());
XEngine::content::unload_game();
}
#endif //!defined(SHIPPING)

77
Engine/Core/MainWin32.cpp Normal file
View File

@@ -0,0 +1,77 @@
/**
* @file MainWin32.cpp
* @brief Win32 可执行程序入口与消息泵实现。
* @details
* 负责设置工作目录、初始化调试内存检测,并驱动主循环:
* - 轮询并分发 Windows 消息;
* - 在消息空闲阶段调用 engine_update
* - 退出时由 engine_shutdown 完成资源收尾。
*/
#ifdef _WIN64
#include "CommonHeader.h"
#include <filesystem>
#ifndef WIN32_MEAN_AND_LEAN
#define WIN32_MEAN_AND_LEAN
#endif
#include <Windows.h>
#include <crtdbg.h>
namespace {
/**
* @brief 将当前工作目录切换到可执行文件所在目录。
* @return 切换后的当前目录;失败时返回空路径。
*/
std::filesystem::path
set_current_directory_to_executable_path()
{
wchar_t path[MAX_PATH]{};
const uint32_t length{ GetModuleFileName(0, &path[0], MAX_PATH) };
if (!length || GetLastError() == ERROR_INSUFFICIENT_BUFFER) return {};
std::filesystem::path p{ path };
std::filesystem::current_path(p.parent_path());
return std::filesystem::current_path();
}
}
#ifndef USE_WITH_EDITOR
extern bool engine_initialize();
extern void engine_update();
extern void engine_shutdown();
/**
* @brief Win32 程序入口函数。
* @return 进程退出码。
*/
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
#if _DEBUG
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif
set_current_directory_to_executable_path();
if (engine_initialize())
{
MSG msg{};
bool is_running{ true };
while (is_running)
{
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
is_running &= (msg.message != WM_QUIT);
}
engine_update();
}
}
}
#endif // USE_WITH_EDITOR
#endif // _WIN64

227
Engine/Engine.vcxproj Normal file
View File

@@ -0,0 +1,227 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="DebugEditor|x64">
<Configuration>DebugEditor</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="ReleaseEditor|x64">
<Configuration>ReleaseEditor</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Common\CommonHeader.h" />
<ClInclude Include="Common\XEnginType.h" />
<ClInclude Include="Common\Id.h" />
<ClInclude Include="Components\ComponentsCommon.h" />
<ClInclude Include="Components\Entity.h" />
<ClInclude Include="Components\Script.h" />
<ClCompile Include="Components\Transform.h" />
<ClInclude Include="Content\ContentLoader.h" />
<ClInclude Include="Content\ContentToEngine.h" />
<ClInclude Include="EngineAPI\Camera.h" />
<ClInclude Include="EngineAPI\GameEntity.h" />
<ClInclude Include="EngineAPI\ScriptComponent.h" />
<ClInclude Include="EngineAPI\TransformComponent.h" />
<ClInclude Include="EngineAPI\Light.h" />
<ClInclude Include="Graphics\Renderer.h" />
<ClInclude Include="Platform\IncludeWindowCpp.h" />
<ClInclude Include="Platform\Platform.h" />
<ClInclude Include="Platform\PlatformTypes.h" />
<ClInclude Include="Platform\Window.h" />
<ClInclude Include="Utilities\FreeList.h" />
<ClInclude Include="Utilities\IOStream.h" />
<ClInclude Include="Utilities\Math.h" />
<ClInclude Include="Utilities\MathTypes.h" />
<ClInclude Include="Utilities\Utilities.h" />
<ClInclude Include="Utilities\Vector.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="Components\Entity.cpp" />
<ClCompile Include="Components\Script.cpp" />
<ClCompile Include="Components\Transform.cpp" />
<ClCompile Include="Content\ContentLoader.cpp" />
<ClCompile Include="Content\ContentToEngine.cpp" />
<ClCompile Include="Core\EngineWin32.cpp" />
<ClCompile Include="Core\MainWin32.cpp" />
<ClCompile Include="Graphics\Renderer.cpp" />
<ClCompile Include="Platform\PlatformWin32.cpp" />
<ClCompile Include="Platform\Window.cpp" />
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>17.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{838f1eae-48a4-40ba-9b8f-332d37ea66cd}</ProjectGuid>
<RootNamespace>Engine</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugEditor|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseEditor|x64'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='DebugEditor|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseEditor|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<SDLCheck>
</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<PrecompiledHeaderFile>
</PrecompiledHeaderFile>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<ExceptionHandling>false</ExceptionHandling>
<FloatingPointModel>Fast</FloatingPointModel>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
<LanguageStandard>stdcpp17</LanguageStandard>
<CallingConvention>FastCall</CallingConvention>
<BufferSecurityCheck>false</BufferSecurityCheck>
<AdditionalIncludeDirectories>$(ProjectDir);$(ProjectDir)/Common</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>
</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='DebugEditor|x64'">
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<SDLCheck>
</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_LIB;USE_WITH_EDITOR;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<PrecompiledHeaderFile>
</PrecompiledHeaderFile>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<ExceptionHandling>false</ExceptionHandling>
<FloatingPointModel>Fast</FloatingPointModel>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
<LanguageStandard>stdcpp17</LanguageStandard>
<CallingConvention>FastCall</CallingConvention>
<BufferSecurityCheck>false</BufferSecurityCheck>
<AdditionalIncludeDirectories>$(ProjectDir);$(ProjectDir)/Common</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>
</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>
</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<PrecompiledHeaderFile>
</PrecompiledHeaderFile>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<ExceptionHandling>false</ExceptionHandling>
<BufferSecurityCheck>false</BufferSecurityCheck>
<ControlFlowGuard>false</ControlFlowGuard>
<EnableParallelCodeGeneration>true</EnableParallelCodeGeneration>
<FloatingPointModel>Fast</FloatingPointModel>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
<LanguageStandard>stdcpp17</LanguageStandard>
<CallingConvention>FastCall</CallingConvention>
<AdditionalIncludeDirectories>$(ProjectDir);$(ProjectDir)/Common</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>
</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseEditor|x64'">
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>
</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_LIB;USE_WITH_EDITOR;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<PrecompiledHeaderFile>
</PrecompiledHeaderFile>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<ExceptionHandling>false</ExceptionHandling>
<BufferSecurityCheck>false</BufferSecurityCheck>
<ControlFlowGuard>false</ControlFlowGuard>
<EnableParallelCodeGeneration>true</EnableParallelCodeGeneration>
<FloatingPointModel>Fast</FloatingPointModel>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
<LanguageStandard>stdcpp17</LanguageStandard>
<CallingConvention>FastCall</CallingConvention>
<AdditionalIncludeDirectories>$(ProjectDir);$(ProjectDir)/Common</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>
</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ClInclude Include="Common\CommonHeader.h" />
<ClInclude Include="Common\XEnginType.h" />
<ClInclude Include="Common\Id.h" />
<ClInclude Include="Components\Entity.h" />
<ClInclude Include="Components\ComponentsCommon.h" />
<ClInclude Include="Utilities\Utilities.h" />
<ClInclude Include="EngineAPI\GameEntity.h" />
<ClInclude Include="EngineAPI\TransformComponent.h" />
<ClInclude Include="Utilities\MathTypes.h" />
<ClInclude Include="EngineAPI\ScriptComponent.h" />
<ClInclude Include="Components\Script.h" />
<ClInclude Include="Content\ContentLoader.h" />
<ClInclude Include="Platform\Window.h" />
<ClInclude Include="Platform\Platform.h" />
<ClInclude Include="Platform\PlatformTypes.h" />
<ClInclude Include="Utilities\Math.h" />
<ClInclude Include="Utilities\FreeList.h" />
<ClInclude Include="Utilities\Vector.h" />
<ClInclude Include="Platform\IncludeWindowCpp.h" />
<ClInclude Include="Utilities\IOStream.h" />
<ClInclude Include="Content\ContentToEngine.h" />
<ClInclude Include="EngineAPI\Camera.h" />
<ClInclude Include="EngineAPI\Light.h" />
<ClInclude Include="Graphics\Renderer.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="Components\Entity.cpp" />
<ClCompile Include="Components\Transform.h" />
<ClCompile Include="Components\Transform.cpp" />
<ClCompile Include="Components\Script.cpp" />
<ClCompile Include="Core\MainWin32.cpp" />
<ClCompile Include="Core\EngineWin32.cpp" />
<ClCompile Include="Content\ContentLoader.cpp" />
<ClCompile Include="Platform\PlatformWin32.cpp" />
<ClCompile Include="Platform\Window.cpp" />
<ClCompile Include="Content\ContentToEngine.cpp" />
</ItemGroup>
</Project>

128
Engine/EngineAPI/Camera.h Normal file
View File

@@ -0,0 +1,128 @@
/**
* @file Camera.h
* @brief 相机句柄与视图/投影参数接口定义。
* @details
* camera 提供对渲染相机实例的统一访问,覆盖:
* - 透视/正交两类投影参数设置;
* - 视图矩阵、投影矩阵及其逆矩阵查询;
* - 近远裁剪面、宽高与绑定实体 ID 读取。
*/
#pragma once
#include "CommonHeader.h"
namespace XEngine::graphics {
/**
* @brief 相机强类型标识符。
*/
DEFINE_TYPED_ID(camera_id);
/**
* @brief 渲染相机句柄对象。
*/
class camera
{
public:
/**
* @brief 投影模式枚举。
*/
enum type :u32
{
perspective,
orthographic,
};
constexpr explicit camera(camera_id id) : _id{ id } {}
constexpr camera() = default;
constexpr camera_id get_id() const { return _id; }
constexpr bool is_valid() const { return id::is_valid(_id); }
/**
* @brief 设置相机上方向。
*/
void up(math::v3 up) const;
/**
* @brief 设置透视投影视场角。
*/
void field_of_view(f32 fov) const;
/**
* @brief 设置视口宽高比。
*/
void aspect_ratio(f32 aspect_ratio) const;
/**
* @brief 设置正交视口宽度。
*/
void view_width(f32 width) const;
/**
* @brief 设置正交视口高度。
*/
void view_height(f32 height) const;
/**
* @brief 设置近远裁剪面。
*/
void range(f32 near_z, f32 far_z) const;
/**
* @brief 获取视图矩阵。
*/
math::m4x4 view() const;
/**
* @brief 获取投影矩阵。
*/
math::m4x4 projection() const;
/**
* @brief 获取投影矩阵逆矩阵。
*/
math::m4x4 inverse_projection() const;
/**
* @brief 获取视图投影矩阵。
*/
math::m4x4 view_projection() const;
/**
* @brief 获取视图投影逆矩阵。
*/
math::m4x4 inverse_view_projection() const;
/**
* @brief 获取上方向向量。
*/
math::v3 up() const;
/**
* @brief 获取近裁剪面。
*/
f32 near_z() const;
/**
* @brief 获取远裁剪面。
*/
f32 far_z() const;
/**
* @brief 获取视场角。
*/
f32 field_of_view() const;
/**
* @brief 获取宽高比。
*/
f32 aspect_ratio() const;
/**
* @brief 获取视口宽度。
*/
f32 view_width() const;
/**
* @brief 获取视口高度。
*/
f32 view_height() const;
/**
* @brief 获取投影类型。
*/
type projection_type() const;
/**
* @brief 获取绑定实体 ID。
*/
id::id_type entity_id() const;
private:
camera_id _id{ id::invalid_id };
};
}

View File

@@ -0,0 +1,191 @@
/**
* @file GameEntity.h
* @brief 实体句柄、脚本基类与脚本注册机制定义。
* @details
* 该文件是引擎 ECS 对外脚本接口的核心入口:
* - game_entity::entity 提供实体句柄及常用变换访问;
* - script::entity_script 定义脚本生命周期与变换写接口;
* - script::detail 提供脚本工厂注册、按名称哈希检索与宏封装。
*/
#pragma once
#include "..\Components\ComponentsCommon.h"
#include "TransformComponent.h"
#include "ScriptComponent.h"
namespace XEngine{
namespace game_entity {
/**
* @brief 实体强类型标识符。
*/
DEFINE_TYPED_ID(entity_id);
/**
* @brief 运行时实体句柄封装。
*/
class entity {
public:
/**
* @brief 由实体 ID 构造句柄。
*/
constexpr explicit entity(entity_id id) : _id{ id } {}
/**
* @brief 构造无效实体句柄。
*/
constexpr entity() : _id{ id::invalid_id } {}
/**
* @brief 获取实体 ID。
*/
[[nodiscard]] constexpr entity_id get_id() const { return _id; }
/**
* @brief 判断实体句柄是否有效。
*/
[[nodiscard]] constexpr bool is_valid() const { return id::is_valid(_id); }
/**
* @brief 获取实体变换组件句柄。
*/
[[nodiscard]] transform::component transform() const;
/**
* @brief 获取实体脚本组件句柄。
*/
[[nodiscard]] script::component script() const;
[[nodiscard]] math::v4 rotation() const { return transform().rotation(); }
[[nodiscard]] math::v3 orientation() const { return transform().orientation(); }
[[nodiscard]] math::v3 position() const { return transform().position(); }
[[nodiscard]] math::v3 scale() const { return transform().scale(); }
private:
entity_id _id;
};
}
namespace script {
/**
* @brief 实体脚本基类。
* @details
* 派生类可覆盖 begin_play/update并通过受保护接口修改实体变换。
*/
class entity_script : public game_entity::entity {
public:
/**
* @brief 析构脚本实例。
*/
virtual ~entity_script() = default;
/**
* @brief 脚本启动回调。
*/
virtual void begin_play() {}
/**
* @brief 脚本逐帧更新回调。
* @param dt 帧时间步长。
*/
virtual void update(float) {}
protected:
constexpr explicit entity_script(game_entity::entity entity)
: game_entity::entity{ entity.get_id() }{}
void set_rotation(math::v4 rotation_quaternion) const {set_rotation(this, rotation_quaternion);}
void set_orientation(math::v3 orientation_vector) const {set_orientation(this, orientation_vector);}
void set_position(math::v3 position) const {set_position(this, position);}
void set_scale(math::v3 scale) const { set_scale(this, scale); }
static void set_rotation(const game_entity::entity *const entity, math::v4 rotation_quaternion);
static void set_orientation(const game_entity::entity *const entity, math::v3 orientation_vector);
static void set_position(const game_entity::entity *const entity, math::v3 position);
static void set_scale(const game_entity::entity *const entity, math::v3 scale);
};
namespace detail {
/**
* @brief 脚本实例智能指针类型。
*/
using script_ptr = std::unique_ptr<entity_script>;
#ifdef USESTDFUNC
/**
* @brief 基于 std::function 的脚本工厂签名。
*/
using script_creator = std::function<script_ptr(game_entity::entity entity)>;
#else
/**
* @brief 基于函数指针的脚本工厂签名。
*/
using script_creator = script_ptr(*)(game_entity::entity entity);
#endif // USESTDFUNC
/**
* @brief 脚本名哈希器类型。
*/
using string_hash = std::hash<std::string>;
/**
* @brief 注册脚本工厂到脚本注册表。
* @param tag 脚本哈希标签。
* @param creator 脚本工厂函数。
* @return 注册结果码。
*/
u8 register_script(size_t tag, script_creator creator);
#ifdef USE_WITH_EDITOR
extern "C" __declspec(dllexport)
#endif
/**
* @brief 按哈希标签查询脚本工厂。
* @param tag 脚本名哈希。
* @return 对应脚本工厂函数。
*/
script_creator get_script_creator(size_t tag);
/**
* @brief 默认脚本工厂模板函数。
* @tparam script_class 脚本类型。
* @param entity 绑定实体。
* @return 新创建脚本实例。
*/
template<class script_class>
script_ptr create_script(game_entity::entity entity)
{
assert(entity.is_valid());
return std::make_unique<script_class>(entity);
}
#ifdef USE_WITH_EDITOR
/**
* @brief 向编辑器导出脚本名称。
* @param name 脚本类名。
* @return 写入结果码。
*/
u8 add_script_name(const char* name);
#define REGISTER_SCRIPT(TYPE) \
namespace { \
u8 _reg_##TYPE{ \
XEngine::script::detail::register_script( \
XEngine::script::detail::string_hash()(#TYPE), \
&XEngine::script::detail::create_script<TYPE>) }; \
const u8 _name_##TYPE \
{ XEngine::script::detail::add_script_name(#TYPE) }; \
}
#else
#define REGISTER_SCRIPT(TYPE) \
class TYPE; \
namespace { \
u8 _reg_##TYPE{ \
XEngine::script::detail::register_script( \
XEngine::script::detail::string_hash()(#TYPE), \
&XEngine::script::detail::create_script<TYPE>) }; \
}
#endif // USE_WITH_EDITOR
}//namespace detail
}//namespace script
}

97
Engine/EngineAPI/Light.h Normal file
View File

@@ -0,0 +1,97 @@
/**
* @file Light.h
* @brief 光源句柄与光照参数访问接口定义。
* @details
* light 封装渲染层光源实例的轻量句柄语义,支持:
* - 启停、强度、颜色等常用光照属性读写;
* - 查询光源类型与绑定实体;
* - 通过 light_set_key 区分不同光照集合。
*/
#pragma once
#include "CommonHeader.h"
namespace XEngine::graphics {
/**
* @brief 光源强类型标识符。
*/
DEFINE_TYPED_ID(light_id);
/**
* @brief 光源句柄对象。
*/
class light
{
public:
/**
* @brief 光源类型枚举。
*/
enum type :u32
{
directioinal,
point,
spot,
count
};
constexpr explicit light(light_id id, u64 light_set_key)
:_light_set_key{ light_set_key }, _id { id } {}
constexpr light() = default;
constexpr light_id get_id() const { return _id; }
constexpr u64 get_light_set_key() const { return _light_set_key; }
constexpr bool is_valid() const { return id::is_valid(_id); }
/**
* @brief 设置光源启用状态。
* @param is_enabled true 表示启用。
*/
void is_enabled(bool is_enabled) const;
/**
* @brief 设置光照强度。
* @param intensity 光照强度值。
*/
void intensity(f32 intensity) const;
/**
* @brief 设置光照颜色。
* @param color RGB 颜色。
*/
void color(math::v3 color) const;
/**
* @brief 查询光源启用状态。
* @return 启用返回 true。
*/
bool is_enabled( ) const;
/**
* @brief 查询光照强度。
* @return 当前强度值。
*/
f32 intensity( ) const;
/**
* @brief 查询光照颜色。
* @return 当前 RGB 颜色。
*/
math::v3 color( ) const;
/**
* @brief 查询光源类型。
* @return 光源类型枚举值。
*/
type light_type() const;
/**
* @brief 查询绑定实体 ID。
* @return 实体 ID。
*/
id::id_type entity_id() const;
private:
u64 _light_set_key{ 0 };
light_id _id{ id::invalid_id };
};
}

View File

@@ -0,0 +1,51 @@
/**
* @file ScriptComponent.h
* @brief 脚本组件句柄定义。
* @details
* 该文件提供脚本组件在 ECS 中的强类型 ID 封装,用于:
* - 标识实体绑定的脚本组件实例;
* - 与组件系统统一的有效性检查;
* - 在不暴露内部存储结构的前提下传递脚本组件引用。
*/
#pragma once
#include "..\Components\ComponentsCommon.h"
namespace XEngine::script {
/**
* @brief 脚本组件强类型标识符。
*/
DEFINE_TYPED_ID(script_id);
/**
* @brief 脚本组件句柄对象。
*/
class component final
{
public:
/**
* @brief 由脚本组件 ID 构造句柄。
* @param id 脚本组件 ID。
*/
constexpr explicit component(script_id id) : _id{ id } {}
/**
* @brief 构造无效脚本组件句柄。
*/
constexpr component() : _id{ id::invalid_id } {}
/**
* @brief 获取脚本组件 ID。
* @return 组件 ID。
*/
constexpr script_id get_id() const { return _id; }
/**
* @brief 判断句柄是否有效。
* @return 有效返回 true。
*/
constexpr bool is_valid() const { return id::is_valid(_id); }
private:
script_id _id;
};
}

View File

@@ -0,0 +1,53 @@
/**
* @file TransformComponent.h
* @brief 变换组件句柄与只读查询接口定义。
* @details
* component 以轻量句柄方式访问 ECS 中的变换数据,提供:
* - 位置(position)、朝向(orientation)、缩放(scale)查询;
* - 四元数旋转(rotation)读取;
* - 与 id 系统一致的有效性判断语义。
*/
#pragma once
#include "..\Components\ComponentsCommon.h"
namespace XEngine::transform {
/**
* @brief 变换组件强类型标识符。
*/
DEFINE_TYPED_ID(transform_id);
/**
* @brief 变换组件句柄对象。
*/
class component final
{
public:
constexpr explicit component(transform_id id) : _id{ id } {}
constexpr component() : _id{ id::invalid_id } {}
constexpr transform_id get_id() const { return _id; }
constexpr bool is_valid() const { return id::is_valid(_id); }
/**
* @brief 获取世界空间位置。
*/
math::v3 position() const;
/**
* @brief 获取朝向向量。
*/
math::v3 orientation() const;
/**
* @brief 获取缩放向量。
*/
math::v3 scale() const;
/**
* @brief 获取旋转四元数。
*/
math::v4 rotation() const;
private:
transform_id _id;
};
}

View File

@@ -0,0 +1,20 @@
/**
* @file IncludeWindowCpp.h
* @brief 以单编译单元方式引入 Window.cpp 的桥接头。
* @details
* 某些构建路径通过宏控制将 Window.cpp 以内联包含方式编译。
* 该头文件负责:
* - 定义 INCLUDE_WINDOW_CPP 触发实现体暴露;
* - 包含 Window.cpp
* - 立即取消宏定义以避免污染后续编译单元。
*/
#pragma once
/**
* @brief 启用 Window.cpp 头内实现编译模式。
*/
#define INCLUDE_WINDOW_CPP 1
#include "Window.cpp"
/**
* @brief 关闭 Window.cpp 头内实现编译模式。
*/
#undef INCLUDE_WINDOW_CPP

View File

@@ -0,0 +1,33 @@
/**
* @file Platform.h
* @brief 平台窗口生命周期管理接口。
* @details
* 对外提供窗口创建与销毁能力:
* - create_window 根据可选初始化参数构建窗口;
* - remove_window 根据 window_id 释放窗口资源;
* - 保持与平台无关上层模块之间的最小依赖面。
*/
#pragma once
#include "CommonHeader.h"
#include "Window.h"
namespace XEngine::platform {
struct window_init_info;
/**
* @brief 创建平台窗口。
* @param init_info 可选初始化参数。
* @return 创建后的窗口句柄对象。
*/
window create_window(const window_init_info *const init_info = nullptr);
/**
* @brief 销毁平台窗口。
* @param id 目标窗口 ID。
*/
void remove_window(window_id id);
}

View File

@@ -0,0 +1,47 @@
/**
* @file PlatformTypes.h
* @brief 平台窗口系统公共类型定义。
* @details
* 该文件集中声明平台窗口创建所需的数据结构,包括:
* - window_proc应用层窗口消息回调签名
* - window_handle原生窗口句柄别名
* - window_init_info窗口创建参数父窗口、标题、位置、尺寸
*/
#pragma once
#include "CommonHeader.h"
#ifdef _WIN64
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <Windows.h>
namespace XEngine::platform {
/**
* @brief 窗口消息回调函数签名。
*/
using window_proc = LRESULT(*)(HWND, UINT, WPARAM, LPARAM);
/**
* @brief 原生窗口句柄类型别名。
*/
using window_handle = HWND;
/**
* @brief 窗口初始化参数。
*/
struct window_init_info
{
window_proc callback{ nullptr };
window_handle parent{ nullptr };
const wchar_t* caption{ nullptr };
s32 left{ 0 };
s32 top{ 0 };
s32 width{ 0 };
s32 height{ 0 };
};
}
#endif // _WIN64

View File

@@ -0,0 +1,372 @@
/**
* @file PlatformWin32.cpp
* @brief Win32 平台窗口系统实现。
* @details
* 该文件实现平台窗口后端的完整行为,包括:
* - Win32 窗口类注册与窗口创建;
* - 窗口 ID 与 HWND 的双向关联管理;
* - 大小变化、销毁、全屏切换等消息处理;
* - 对 Platform.h / Window.h 暴露接口的底层支撑。
*/
#ifdef _WIN64
#include "Platform.h"
#include "PlatformTypes.h"
namespace XEngine::platform {
namespace {
/**
* @brief Win32 窗口实例运行时状态。
*/
struct window_info
{
HWND hwnd{ nullptr };
RECT client_area{ 0,0,1920,1080 };
RECT fullscreen_area{};
POINT top_left{ 0,0 };
DWORD style{ WS_VISIBLE };
bool is_fullscreen{ false };
bool is_closed{ false };
};
/**
* @brief 窗口对象存储池。
*/
utl::free_list<window_info> windows;
/**
* @brief 根据窗口 ID 获取窗口状态对象。
* @param id 窗口 ID。
* @return 对应窗口状态引用。
*/
window_info&
get_from_id(window_id id)
{
assert(windows[id].hwnd);
return windows[id];
}
/**
* @brief 根据 HWND 获取窗口状态对象。
* @param handle 原生窗口句柄。
* @return 对应窗口状态引用。
*/
window_info&
get_from_handle(window_handle handle)
{
const window_id id{ (id::id_type)GetWindowLongPtr(handle, GWLP_USERDATA) };
return get_from_id(id);
}
/**
* @brief 标记窗口是否发生了可见尺寸变更。
*/
bool resized{ false };
/**
* @brief 按目标客户区更新窗口尺寸与位置。
* @param info 窗口状态。
* @param area 目标客户区矩形。
*/
void
resize_window(const window_info& info, const RECT& area)
{
RECT window_rect{ area };
AdjustWindowRect(&window_rect, info.style, FALSE);
const s32 width{ window_rect.right - window_rect.left };
const s32 height{ window_rect.bottom - window_rect.top };
MoveWindow(info.hwnd, info.top_left.x, info.top_left.y, width, height, true);
}
/// <Window parameter implementation>
/**
* @brief 查询窗口是否处于全屏状态。
* @param id 窗口 ID。
* @return 全屏返回 true。
*/
bool
is_window_fullscreen(window_id id)
{
assert(id != u32_invalid_id);
return get_from_id(id).is_fullscreen;
}
/**
* @brief 查询窗口原生句柄。
* @param id 窗口 ID。
* @return HWND 句柄。
*/
window_handle
get_window_handle(window_id id)
{
return get_from_id(id).hwnd;
}
/**
* @brief 设置窗口标题文本。
* @param id 窗口 ID。
* @param caption 标题文本。
*/
void
set_window_caption(window_id id, const wchar_t* caption)
{
window_info& info{ get_from_id(id) };
SetWindowText(info.hwnd, caption);
}
/**
* @brief 查询窗口矩形(客户区或全屏区)。
* @param id 窗口 ID。
* @return 左上右下坐标向量。
*/
math::u32v4
get_window_size(window_id id)
{
window_info& info{ get_from_id(id) };
RECT& rect{ info.is_fullscreen ? info.fullscreen_area : info.client_area };
return { (u32)rect.left,(u32)rect.top,(u32)rect.right,(u32)rect.bottom };
}
/**
* @brief 调整窗口客户区尺寸。
* @param id 窗口 ID。
* @param width 新宽度。
* @param height 新高度。
*/
void
resize_window(window_id id, u32 width, u32 height)
{
window_info& info{ get_from_id(id) };
// when we host the window in the level editor we just update
// the internal data.
if (info.style & WS_CHILD)
{
GetClientRect(info.hwnd, &info.client_area);
}
else
{
RECT& area{ info.is_fullscreen ? info.fullscreen_area : info.client_area };
area.bottom = area.top + height;
area.right = area.left + width;
resize_window(info, area);
}
}
/**
* @brief 查询窗口是否已被关闭。
* @param id 窗口 ID。
* @return 已关闭返回 true。
*/
bool
is_window_close(window_id id)
{
return get_from_id(id).is_closed;
}
/// <Window parameter implementation>
/**
* @brief 平台内部默认窗口过程。
* @param hwnd 窗口句柄。
* @param msg 消息类型。
* @param wparam 消息参数。
* @param lparam 消息参数。
* @return 消息处理结果。
*/
LRESULT CALLBACK internal_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
switch (msg)
{
case WM_NCCREATE:
{
DEBUG_OP(SetLastError(0));
const window_id id{ windows.add() };
windows[id].hwnd = hwnd;
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)id);
assert(GetLastError() == 0);
}
break;
case WM_DESTROY:
get_from_handle(hwnd).is_closed = true;
break;
case WM_SIZE:
resized = (wparam != SIZE_MINIMIZED);
break;
default:
break;
}
if (resized && GetAsyncKeyState(VK_LBUTTON) >= 0)
{
window_info& info{ get_from_handle(hwnd) };
assert(info.hwnd);
GetClientRect(info.hwnd, info.is_fullscreen ? &info.fullscreen_area : &info.client_area);
resized = false;
}
LONG_PTR long_ptr{ GetWindowLongPtr(hwnd, 0) };
return long_ptr
? ((window_proc)long_ptr)(hwnd, msg, wparam, lparam)
: DefWindowProc(hwnd, msg, wparam, lparam);
assert(GetLastError() == 0);
}
/**
* @brief 切换窗口全屏状态。
* @param id 窗口 ID。
* @param is_fullscreen 目标全屏状态。
*/
void
set_window_fullscreen(window_id id, bool is_fullscreen)
{
window_info& info{ windows[id] };
if (info.is_fullscreen != is_fullscreen)
{
info.is_fullscreen = is_fullscreen;
if (is_fullscreen)
{
GetClientRect(info.hwnd, &info.client_area);
RECT rect;
GetWindowRect(info.hwnd, &rect);
info.top_left.x = rect.left;
info.top_left.y = rect.top;
SetWindowLongPtr(info.hwnd, GWL_STYLE, info.style);
ShowWindow(info.hwnd, SW_MAXIMIZE);
}
else
{
SetWindowLongPtr(info.hwnd, GWL_STYLE, info.style);
resize_window(info, info.client_area);
ShowWindow(info.hwnd, SW_SHOWNORMAL);
}
}
}
} // namespace
/**
* @brief 创建 Win32 窗口并注册到窗口池。
* @param init_info 可选窗口初始化参数。
* @return 创建成功的窗口句柄对象。
*/
window
create_window(const window_init_info* init_info /* = nullptr */)
{
window_proc callback{ init_info ? init_info->callback : nullptr }; // 回调窗口过程:由初始化参数提供;未提供时为 nullptr。
window_handle parent{ init_info ? init_info->parent : nullptr }; // 父窗口句柄:用于子窗口挂载;未提供时创建顶层窗口。
WNDCLASSEX wc;
ZeroMemory(&wc, sizeof(WNDCLASSEX));
// 初始化 WNDCLASSEX 字段。
wc.cbSize = sizeof(WNDCLASSEX); // 结构体大小。
wc.style = CS_HREDRAW | CS_VREDRAW; // 水平/垂直尺寸变化时重绘。
wc.lpfnWndProc = internal_window_proc; // 默认窗口过程入口。
wc.cbClsExtra = 0; // 类额外字节数。
wc.cbWndExtra = callback ? sizeof(callback) : 0; // 实例额外字节数:有回调时用于存储回调指针。
wc.hInstance = 0; // 当前模块实例句柄(使用默认)。
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); // 大图标。
wc.hCursor = LoadCursor(NULL, IDC_ARROW); // 光标。
wc.hbrBackground = CreateSolidBrush(RGB(26, 48, 76)); // 背景画刷。
wc.lpszMenuName = NULL; // 菜单名(无)。
wc.lpszClassName = L"XWindow"; // 窗口类名。
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); // 小图标。
// 注册窗口类。
if (!RegisterClassEx(&wc))
{
// 若类已注册或注册失败,后续创建窗口时由系统返回结果。
}
window_info info{};
info.client_area.right = (init_info && init_info->width) ? info.client_area.left + init_info->width : info.client_area.right;
info.client_area.bottom = (init_info && init_info->height) ? info.client_area.top + init_info->height : info.client_area.bottom;
info.style |= parent ? WS_CHILD : WS_OVERLAPPEDWINDOW;
RECT rect{ info.client_area };
AdjustWindowRect(&rect, info.style, FALSE);
const wchar_t* caption{ (init_info && init_info->caption) ? init_info->caption : L"XGame" };
const s32 left{ (init_info) ? init_info->left : info.top_left.x };
const s32 top{ (init_info) ? init_info->top : info.top_left.y };
const s32 width{ rect.right - rect.left };
const s32 height{ rect.bottom - rect.top };
// 创建窗口实例。
info.hwnd = CreateWindowEx(
0, // 扩展样式。
wc.lpszClassName, // 窗口类名。
caption, // 窗口标题。
info.style, // 窗口样式。
// 尺寸与位置。
left, top, width, height,
parent, // 父窗口句柄nullptr 表示顶层窗口)。
nullptr, // 菜单句柄(未使用)。
nullptr, // 实例句柄(未显式传入)。
nullptr // 创建参数(未使用)。
);
if (info.hwnd)
{
DEBUG_OP(SetLastError(0));
if (callback) SetWindowLongPtr(info.hwnd, 0, (LONG_PTR)callback);
assert(GetLastError() == 0);
ShowWindow(info.hwnd, SW_SHOWNORMAL);
UpdateWindow(info.hwnd);
window_id id{ (id::id_type)GetWindowLongPtr(info.hwnd, GWLP_USERDATA) };
windows[id] = info;
return window{ id };
}
return {}; // 创建失败时返回无效窗口句柄。
}
/**
* @brief 销毁窗口并从窗口池移除。
* @param id 窗口 ID。
*/
void
remove_window(window_id id)
{
window_info& info{ get_from_id(id) };
DestroyWindow(info.hwnd);
windows.remove(id);
}
}
#include "IncludeWindowCpp.h"
#endif // _WIN64

116
Engine/Platform/Window.cpp Normal file
View File

@@ -0,0 +1,116 @@
/**
* @file Window.cpp
* @brief window 句柄对象成员函数实现。
* @details
* 本文件将 window 类的高层接口转发到平台后端函数:
* - full screen、caption、resize 等操作;
* - 原生句柄与尺寸查询;
* - 窗口关闭状态查询。
* 通过 INCLUDE_WINDOW_CPP 宏控制其编译包含方式。
*/
#if INCLUDE_WINDOW_CPP
#include "Window.h"
namespace XEngine::platform {
/**
* @brief 设置窗口全屏状态。
* @param is_fullscreen 目标全屏状态。
*/
void
window::set_fullscreen(bool is_fullscreen) const
{
assert(is_valid());
set_window_fullscreen(_id, is_fullscreen);
}
/**
* @brief 查询窗口是否处于全屏状态。
* @return 全屏返回 true。
*/
bool
window::is_fullscreen() const
{
assert(is_valid());
return is_window_fullscreen(_id);
}
/**
* @brief 获取原生窗口句柄。
* @return 平台相关窗口句柄指针。
*/
void*
window::handle() const
{
assert(is_valid());
return get_window_handle(_id);
}
/**
* @brief 设置窗口标题。
* @param caption 标题文本。
*/
void
window::set_caption(const wchar_t* caption) const
{
assert(is_valid());
set_window_caption(_id, caption);
}
/**
* @brief 获取窗口矩形坐标。
* @return 左上右下坐标向量。
*/
math::u32v4 window::size() const
{
assert(is_valid());
return get_window_size(_id);
}
/**
* @brief 调整窗口客户区尺寸。
* @param width 目标宽度。
* @param height 目标高度。
*/
void
window::resize(u32 width, u32 height)const
{
assert(is_valid());
resize_window(_id, width, height);
}
/**
* @brief 获取窗口当前宽度。
* @return 宽度像素值。
*/
u32
window::width()const
{
assert(is_valid());
math::u32v4 s{ size() };
return s.z - s.x;
}
/**
* @brief 获取窗口当前高度。
* @return 高度像素值。
*/
u32
window::height()const
{
assert(is_valid());
math::u32v4 s{ size() };
return s.w - s.y;
}
/**
* @brief 查询窗口是否已关闭。
* @return 已关闭返回 true。
*/
bool
window::is_closed()const
{
assert(is_valid());
return is_window_close(_id);
}
}
#endif //INCLUDE_WINDOW_CPP

99
Engine/Platform/Window.h Normal file
View File

@@ -0,0 +1,99 @@
/**
* @file Window.h
* @brief 跨模块窗口句柄对象与基础窗口操作接口。
* @details
* window 是平台层窗口对象的轻量包装,向上层提供:
* - 全屏切换、标题设置、尺寸调整;
* - 原生窗口句柄访问;
* - 关闭状态与尺寸查询。
* 具体平台行为由 PlatformWin32.cpp 等实现体完成。
*/
#pragma once
#include "CommonHeader.h"
namespace XEngine::platform {
/**
* @brief 窗口强类型标识符。
*/
DEFINE_TYPED_ID(window_id);
/**
* @brief 平台窗口句柄对象。
*/
class window
{
public:
/**
* @brief 由窗口 ID 构造句柄。
* @param id 窗口 ID。
*/
constexpr explicit window(window_id id) : _id{ id } {}
/**
* @brief 构造无效窗口句柄。
*/
constexpr window() = default;
/**
* @brief 获取窗口 ID。
* @return 窗口 ID。
*/
constexpr window_id get_id() const { return _id; }
/**
* @brief 判断窗口句柄是否有效。
* @return 有效返回 true。
*/
constexpr bool is_valid() const { return id::is_valid(_id); }
/**
* @brief 设置全屏状态。
* @param is_fullscreen true 表示全屏。
*/
void set_fullscreen(bool is_fullscreen) const;
/**
* @brief 查询是否全屏。
* @return 全屏返回 true。
*/
bool is_fullscreen() const;
/**
* @brief 获取原生窗口句柄。
* @return 平台句柄指针。
*/
void* handle() const;
/**
* @brief 设置窗口标题文本。
* @param caption 标题字符串。
*/
void set_caption(const wchar_t* caption) const;
/**
* @brief 获取窗口矩形坐标。
* @return 左上右下坐标向量。
*/
math::u32v4 size() const;
/**
* @brief 调整窗口大小。
* @param width 目标宽度。
* @param height 目标高度。
*/
void resize(u32 width, u32 height)const;
/**
* @brief 获取窗口宽度。
* @return 宽度像素值。
*/
u32 width()const;
/**
* @brief 获取窗口高度。
* @return 高度像素值。
*/
u32 height()const;
/**
* @brief 查询窗口是否已关闭。
* @return 已关闭返回 true。
*/
bool is_closed()const;
private:
window_id _id{ id::invalid_id };
};
}

169
Engine/Utilities/FreeList.h Normal file
View File

@@ -0,0 +1,169 @@
/**
* @file FreeList.h
* @brief 带复用槽位的稀疏对象容器模板。
* @details
* free_list 维护“已使用槽位 + 空闲链”结构,支持:
* - O(1) 近似开销的新增与删除;
* - 删除后槽位复用,降低频繁分配释放成本;
* - 以索引作为外部句柄,与 id 系统协同使用。
*/
#pragma once
#include "CommonHeader.h"
namespace XEngine::utl {
#if USE_STL_VECTOR
#pragma message("WARNING: using utl::free_list with std::vector result in duplicate calls to class constructor!");
#endif
template<typename T>
class free_list
{
static_assert(sizeof(T) >= sizeof(u32));
public:
/**
* @brief 构造空容器。
*/
free_list() = default;
/**
* @brief 预留底层存储容量。
* @param count 预留元素数量。
*/
explicit free_list(u32 count)
{
_array.reserve(count);
}
/**
* @brief 销毁容器并校验无存活元素。
*/
~free_list()
{
assert(!_size);
#if USE_STL_VECTOR
memset(_array.data(), 0, _array.size() * sizeof(T));
#endif
}
/**
* @brief 新增一个元素并返回槽位 ID。
* @tparam params 构造参数类型。
* @param p 构造参数。
* @return 新元素 ID。
*/
template<class ... params>
constexpr u32 add(params&&... p)
{
u32 id{ u32_invalid_id };
if (_next_free_index == u32_invalid_id)
{
id = (u32)_array.size();
_array.emplace_back(std::forward<params>(p)...);
}
else
{
id = _next_free_index;
assert(id < _array.size() && already_removed(id));
_next_free_index = *(const u32 *const)std::addressof(_array[id]);
new (std::addressof(_array[id])) T(std::forward<params>(p)...);
}
++_size;
return id;
}
/**
* @brief 删除指定 ID 的元素并回收到空闲链。
* @param id 元素 ID。
*/
constexpr void remove(u32 id)
{
assert(id < _array.size() && !already_removed(id));
T& item{ _array[id] };
item.~T();
DEBUG_OP(memset(std::addressof(_array[id]), 0xcc, sizeof(T)));
*(u32 *const)std::addressof(_array[id]) = _next_free_index;
_next_free_index = id;
--_size;
}
/**
* @brief 获取当前有效元素数量。
* @return 元素数量。
*/
constexpr u32 size() const
{
return _size;
}
/**
* @brief 获取当前已分配槽位总数。
* @return 容量值。
*/
constexpr u32 capacity() const
{
return _array.size();
}
/**
* @brief 判断容器是否为空。
* @return 空返回 true。
*/
constexpr bool empty() const
{
return _size == 0;
}
/**
* @brief 按 ID 访问元素。
* @param id 元素 ID。
* @return 元素引用。
*/
[[nodiscard]] constexpr T& operator[](u32 id)
{
assert(id < _array.size() && !already_removed(id));
return _array[id];
}
/**
* @brief 按 ID 访问常量元素。
* @param id 元素 ID。
* @return 常量元素引用。
*/
[[nodiscard]] constexpr const T& operator[](u32 id) const
{
assert(id < _array.size() && !already_removed(id));
return _array[id];
}
private:
constexpr bool already_removed(u32 id) const
{
if constexpr (sizeof(T) > sizeof(u32))
{
u32 i{ sizeof(u32) };
const u8 *const p{ (const u8 *const)std::addressof(_array[id]) };
while ((p[i] == 0xcc) && (i < sizeof(T))) ++i;
return i == sizeof(T);
}
else
{
return true;
}
}
#if USE_STL_VECTOR
utl::vector<T> _array;
#else
utl::vector<T, false> _array;
#endif
u32 _next_free_index{ u32_invalid_id };
u32 _size{ 0 };
};
}

154
Engine/Utilities/IOStream.h Normal file
View File

@@ -0,0 +1,154 @@
/**
* @file IOStream.h
* @brief 面向内存缓冲区的二进制流读写工具。
* @details
* 定义 reader/writer 两个轻量类,用于在连续内存上顺序序列化:
* - 支持算术类型按字节写入与读取;
* - 支持原始块拷贝与位置跳过;
* - 通过边界断言降低越界读写风险。
*/
#pragma once
#include "CommonHeader.h"
namespace XEngine::utl {
/**
* @brief 只读内存二进制流。
*/
class blob_stream_reader
{
public:
DISABLE_COPY_AND_MOVE(blob_stream_reader);
/**
* @brief 绑定外部只读缓冲区。
* @param buffer 缓冲区首地址。
*/
explicit blob_stream_reader(const u8* buffer)
:_buffer{buffer}, _position{buffer}
{
assert(buffer);
}
/**
* @brief 按类型读取一个值并推进游标。
* @tparam T 算术类型。
* @return 读取到的值。
*/
template<typename T>
[[nodiscard]] T reader()
{
static_assert(std::is_arithmetic_v<T>, "Template argument should be a primitive type.");
T value{ *((T*)_position) };
_position += sizeof(T);
return value;
}
/**
* @brief 读取原始字节块并推进游标。
* @param buffer 输出缓冲区。
* @param length 读取字节数。
*/
void read(u8* buffer, size_t length)
{
memcpy(buffer, _position, length);
_position += length;
}
/**
* @brief 跳过指定字节数。
* @param offset 偏移字节数。
*/
void skip(size_t offset)
{
_position += offset;
}
[[nodiscard]] constexpr const u8 *const buffer_start() const { return _buffer; }
[[nodiscard]] constexpr const u8 *const position() const { return _position; }
[[nodiscard]] constexpr size_t offset() const { return _position - _buffer; }
private:
const u8 *const _buffer;
const u8* _position;
};
/**
* @brief 可写内存二进制流。
*/
class blob_stream_writer
{
public:
DISABLE_COPY_AND_MOVE(blob_stream_writer);
/**
* @brief 绑定外部可写缓冲区。
* @param buffer 缓冲区首地址。
* @param buffer_size 缓冲区大小。
*/
explicit blob_stream_writer(u8* buffer, size_t buffer_size)
:_buffer{ buffer }, _position{ buffer }, _buffer_size(buffer_size)
{
assert(buffer && buffer_size);
}
/**
* @brief 写入一个算术类型值并推进游标。
* @tparam T 算术类型。
* @param value 待写入值。
*/
template<typename T>
void write(T value)
{
static_assert(std::is_arithmetic_v<T>, "Template argument should be a primitive type.");
assert(&_position[sizeof(T)] <= &_buffer[_buffer_size]);
*((T*)_position) = value;
_position += sizeof(T);
}
/**
* @brief 写入字符缓冲区。
* @param buffer 输入缓冲区。
* @param length 写入字节数。
*/
void write(const char* buffer, size_t length)
{
assert(&_position[length] <= &_buffer[_buffer_size]);
memcpy(_position, buffer, length);
_position += length;
}
/**
* @brief 写入字节缓冲区。
* @param buffer 输入缓冲区。
* @param length 写入字节数。
*/
void write(const u8* buffer, size_t length)
{
assert(&_position[length] <= &_buffer[_buffer_size]);
memcpy(_position, buffer, length);
_position += length;
}
/**
* @brief 跳过指定字节数。
* @param offset 偏移字节数。
*/
void skip(size_t offset)
{
assert(&_position[offset] <= &_buffer[_buffer_size]);
_position += offset;
}
[[nodiscard]] constexpr const u8 *const buffer_start() const { return _buffer; }
[[nodiscard]] constexpr const u8 *const buffer_end() const { return &_buffer[_buffer_size]; }
[[nodiscard]] constexpr const u8 *const position() const { return _position; }
[[nodiscard]] constexpr size_t offset() const { return _position - _buffer; }
private:
u8 *const _buffer;
u8* _position;
size_t _buffer_size;
};
}

185
Engine/Utilities/Math.h Normal file
View File

@@ -0,0 +1,185 @@
/**
* @file Math.h
* @brief 数学工具函数与二进制对齐辅助函数。
* @details
* 提供模板化的数值打包/解包与内存对齐能力,包括:
* - clamp 与归一化浮点量化打包;
* - 固定/动态对齐粒度下的向上与向下对齐;
* - 基于 SIMD 指令的 CRC32 快速计算辅助。
*/
#pragma once
#include "CommonHeader.h"
#include "MathTypes.h"
namespace XEngine::math {
/**
* @brief 将值限制在给定区间内。
* @tparam T 标量类型。
* @param value 输入值。
* @param min 下界。
* @param max 上界。
* @return 限制后的值。
*/
template<typename T>
[[nodiscard]] constexpr T
clamp(T value, T min, T max)
{
return (value < min) ? min : (value > max) ? max : value;
}
/**
* @brief 将 [0,1] 浮点值量化到指定位宽的无符号整数。
* @tparam bits 量化位宽。
* @param i 归一化输入值。
* @return 量化结果。
*/
template<u32 bits>
[[nodiscard]] constexpr u32
pack_unit_float(f32 i)
{
static_assert(bits <= sizeof(u32) * 8);
assert(i >= 0.f && i <= 1.f);
constexpr f32 intervals{ (f32)(((u32)1 << bits) - 1) };
return (u32)(intervals * i + 0.5f);
}
/**
* @brief 将 [min,max] 浮点值量化到指定位宽。
* @tparam bits 量化位宽。
* @param i 输入值。
* @param min 下界。
* @param max 上界。
* @return 量化结果。
*/
template<u32 bits>
[[nodiscard]] constexpr u32
pack_float(f32 i, f32 min, f32 max)
{
assert(min < max);
assert(i <= max && i >= min);
const f32 distance{ (i - min) / (max - min) };
return pack_unit_float<bits>(distance);
}
/**
* @brief 将量化整数还原为 [0,1] 浮点值。
* @tparam bits 量化位宽。
* @param i 量化输入值。
* @return 反量化结果。
*/
template<u32 bits>
[[nodiscard]] constexpr f32
unpack_unit_float(u32 i)
{
static_assert(bits <= sizeof(u32) * 8);
assert(i < ((u32)i << bits));
constexpr f32 intervals{ (f32)(((u32)1 << bits) - 1) };
return (f32)i / intervals;
}
/**
* @brief 将量化值还原到 [min,max] 区间。
* @tparam bits 量化位宽。
* @param i 量化输入值。
* @param min 下界。
* @param max 上界。
* @return 反量化结果。
*/
template<u32 bits>
[[nodiscard]] constexpr f32
unpack_float(f32 i, f32 min, f32 max)
{
assert(min < max);
return unpack_unit_float<bits>(i) * (max - min) + min;
}
/**
* @brief 按固定对齐值向上对齐。
* @tparam alignment 对齐粒度2 的幂)。
* @param size 原始大小。
* @return 向上对齐后的大小。
*/
template<u64 alignment>
[[nodiscard]] constexpr u64
align_size_up(u64 size)
{
static_assert(alignment, "Alignment must be non-zero.");
constexpr u64 mask{ alignment - 1 };
static_assert(!(alignment & mask), "Alignment should be a power of 2.");
return ((size + mask) & ~mask);
}
/**
* @brief 按固定对齐值向下对齐。
* @tparam alignment 对齐粒度2 的幂)。
* @param size 原始大小。
* @return 向下对齐后的大小。
*/
template<u64 alignment>
[[nodiscard]] constexpr u64
align_size_down(u64 size)
{
static_assert(alignment, "Alignment must be non-zero.");
constexpr u64 mask{ alignment - 1 };
static_assert(!(alignment & mask), "Alignment should be a power of 2.");
return (size & ~mask);
}
/**
* @brief 按运行时对齐值向上对齐。
* @param size 原始大小。
* @param alignment 对齐粒度2 的幂)。
* @return 向上对齐后的大小。
*/
[[nodiscard]] constexpr u64
align_size_up(u64 size, u64 alignment)
{
assert(alignment && "Alignment must be non-zero.");
const u64 mask{ alignment - 1 };
assert(!(alignment & mask) && "Alignment should be a power of 2.");
return ((size + mask) & ~mask);
}
/**
* @brief 按运行时对齐值向下对齐。
* @param size 原始大小。
* @param alignment 对齐粒度2 的幂)。
* @return 向下对齐后的大小。
*/
[[nodiscard]] constexpr u64
align_size_down(u64 size, u64 alignment)
{
assert(alignment && "Alignment must be non-zero.");
const u64 mask{ alignment - 1 };
assert(!(alignment & mask) && "Alignment should be a power of 2.");
return (size & ~mask);
}
/**
* @brief 计算数据块的 64 位 CRC32 累积值。
* @param data 输入字节流。
* @param size 输入长度。
* @return CRC32 累积结果。
*/
[[nodiscard]] constexpr u64
calcect_crc32_u64(const u8 *const data, u64 size)
{
assert(size >= sizeof(u64));
u64 crc{ 0 };
const u8* at{ data };
const u8 *const end{ data + align_size_down<sizeof(u64)>(size) };
while (at < end)
{
crc = _mm_crc32_u64(crc, *((const u64*)at));
at += sizeof(u64);
}
return crc;
}
}

View File

@@ -0,0 +1,80 @@
/**
* @file MathTypes.h
* @brief 数学常量与 DirectX 数学类型别名集合。
* @details
* 统一定义引擎常用数学基础元素:
* - 常量pi、two_pi、epsilon
* - 向量/矩阵别名v2/v3/v4、m3x3/m4x4 等;
* - 与 Win64 下 DirectXMath 类型的映射与对齐变体。
*/
#pragma once
#include "CommonHeader.h"
namespace XEngine::math {
//constexpr long double pi = 3.141592653589793238462643383279L;
/**
* @brief 圆周率常量。
*/
constexpr float pi = 3.1415927f;
/**
* @brief 两倍圆周率常量。
*/
constexpr float two_pi = 6.2831853f;
/**
* @brief 浮点比较容差常量。
*/
constexpr float epsilon = 1e-5f;
#if defined(_WIN64)
/**
* @brief DirectXMath 向量类型别名。
* @details
* - 无 A 后缀类型使用常规布局;
* - A 后缀类型要求 16 字节对齐,适合 SIMD 对齐访问场景。
*/
using v2 = DirectX::XMFLOAT2;
using v2a = DirectX::XMFLOAT2A;
using v3 = DirectX::XMFLOAT3;
using v3a = DirectX::XMFLOAT3A;
using v4 = DirectX::XMFLOAT4;
using v4a = DirectX::XMFLOAT4A;
/**
* @brief DirectXMath 整型向量别名。
*/
using u32v2 = DirectX::XMUINT2;
using u32v3 = DirectX::XMUINT3;
using u32v4 = DirectX::XMUINT4;
using s32v2 = DirectX::XMINT2;
using s32v3 = DirectX::XMINT3;
using s32v4 = DirectX::XMINT4;
/**
* @brief 标量浮点向量别名。
*/
using f32v2 = DirectX::XMFLOAT2;
using f32v3 = DirectX::XMFLOAT3;
using f32v4 = DirectX::XMFLOAT4;
/**
* @brief DirectXMath 矩阵与寄存器向量别名。
*/
using Vec4 = DirectX::XMVECTOR;
using Mat4 = DirectX::XMMATRIX;
using Mat3 = DirectX::XMFLOAT3X3;
using Mat4f = DirectX::XMFLOAT4X4;
using m3x3 = DirectX::XMFLOAT3X3;
using m4x4 = DirectX::XMFLOAT4X4;
using m4x4a = DirectX::XMFLOAT4X4A;
using vector4 = DirectX::XMVECTOR;
using MAT4 = DirectX::XMMATRIX;
using vector = DirectX::XMVECTOR;
using Quat = DirectX::XMVECTOR;
#endif // (_WIN64)
}

View File

@@ -0,0 +1,86 @@
/**
* @file Utilities.h
* @brief 公共容器与工具模块聚合入口。
* @details
* 通过编译开关统一切换 STL 与自定义容器实现,并集中导出:
* - vector/deque 等基础容器别名;
* - erase_unordered 辅助函数;
* - FreeList 等常用数据结构头文件。
*/
#pragma once
/**
* @brief 是否使用 STL vector 作为基础容器。
*/
#define USE_STL_VECTOR 0
/**
* @brief 是否启用 STL deque 别名。
*/
#define USE_STL_DRQUE 1
#if USE_STL_VECTOR
#include <vector>
#include <algorithm>
namespace XEngine::utl {
template<typename T>
using vector = std::vector<T>;
/**
* @brief 通过交换尾元素方式无序删除。
* @tparam T 容器类型。
* @param v 目标容器。
* @param index 删除下标。
*/
template<typename T>
void erase_unordered(T& v, size_t index)
{
if (v.size() > 1)
{
std::iter_swap(v.begin() + index, v.end() - 1);
v.pop_back();
}
else
{
v.clear();
}
}
}
#else
#include "Vector.h"
namespace XEngine::utl {
/**
* @brief 调用自定义容器的无序删除接口。
* @tparam T 容器类型。
* @param v 目标容器。
* @param index 删除下标。
*/
template<typename T>
void erase_unordered(T& v, size_t index)
{
v.erase_unordered(index);
}
}
#endif
#if USE_STL_DRQUE
#include <deque>
namespace XEngine::utl {
template<typename T>
using deque = std::deque<T>;
}
#endif
namespace XEngine::utl {
}
#include "FreeList.h"

463
Engine/Utilities/Vector.h Normal file
View File

@@ -0,0 +1,463 @@
/**
* @file Vector.h
* @brief 自定义动态数组容器实现。
* @details
* 该容器提供接近 std::vector 的常用能力,并支持可选析构策略:
* - 动态扩容、插入、删除与无序删除;
* - 连续内存访问与迭代器接口;
* - 在性能敏感场景下复用底层内存分配行为。
*/
#pragma once
#include "CommonHeader.h"
namespace XEngine::utl {
/**
* @brief 自定义连续内存动态数组。
* @tparam T 元素类型。
* @tparam destruct 是否在删除时调用析构。
*/
template<typename T, bool destruct = true>
class vector
{
public:
// Default constructor. Doesn~t allocate memory.
/**
* @brief 构造空容器。
*/
vector() = default;
/**
* @brief 构造并调整到指定元素数量。
* @param count 目标元素数量。
*/
constexpr explicit vector(u64 count)
{
resize(count);
}
/**
* @brief 构造并填充指定数量元素。
* @param count 目标元素数量。
* @param value 填充值。
*/
constexpr explicit vector(u64 count, const T& value)
{
resize(count, value);
}
constexpr vector(const vector& o)
{
*this = o;
}
constexpr vector(vector&& o)
:_capacity{o._capacity},
_size{o._size},
_data{o._data}
{
o.reset();
}
constexpr vector& operator=(const vector& o)
{
assert(this != std::addressof(o));
if (this != std::addressof(o))
{
clear();
reserve(o._size);
for (auto& item : o)
{
emplace_back(item);
}
assert(_size == o._size);
}
return *this;
}
constexpr vector& operator=(vector&& o)
{
assert(this != std::addressof(o));
if (this != std::addressof(o))
{
destroy();
move(o);
}
return *this;
}
~vector() { destroy(); }
/**
* @brief 追加拷贝元素。
* @param value 元素值。
*/
constexpr void push_back(const T& value)
{
emplace_back(value);
}
/**
* @brief 追加右值元素。
* @param value 元素值。
*/
constexpr void push_back(const T&& value)
{
emplace_back(std::move(value));
}
/**
* @brief 原位构造并追加元素。
* @tparam params 构造参数类型。
* @param p 构造参数。
* @return 新元素引用。
*/
template<typename... params>
constexpr decltype(auto) emplace_back(params&&... p)
{
if (_size == _capacity)
{
reserve(((_capacity + 1) * 3) >> 1); // reserve 50% more
}
assert(_size < _capacity);
T *const item{ new (std::addressof(_data[_size])) T(std::forward<params>(p)...) };
++_size;
return *item;
}
/**
* @brief 调整元素数量,新增元素默认构造。
* @param new_size 新大小。
*/
constexpr void resize(u64 new_size)
{
static_assert(std::is_default_constructible<T>::value,
"Type must be default-constructible.");
if (new_size > _size)
{
reserve(new_size);
while (_size < new_size)
{
emplace_back();
}
}
else if (new_size < _size)
{
if constexpr (destruct)
{
destruct_range(new_size, _size);
}
_size = new_size;
}
assert(new_size == _size);
}
/**
* @brief 调整元素数量,新增元素使用给定值填充。
* @param new_size 新大小。
* @param value 填充值。
*/
constexpr void resize(u64 new_size, const T& value)
{
static_assert(std::is_copy_constructible<T>::value,
"Type must be copy-constructible.");
if (new_size > _size)
{
reserve(new_size);
while (_size < new_size)
{
emplace_back(value);
}
}
else if (new_size < _size)
{
if constexpr (destruct)
{
destruct_range(new_size, _size);
}
_size = new_size;
}
assert(new_size == _size);
}
/**
* @brief 预留最小容量。
* @param new_capacity 目标容量。
*/
constexpr void reserve(u64 new_capacity)
{
if (new_capacity > _capacity)
{
// NOTE: realoc() will automatically copy the data in the buffer
// if a new region of memory iss allocated.
void* new_buffer{ realloc(_data, new_capacity * sizeof(T)) };
assert(new_buffer);
if (new_buffer)
{
_data = static_cast<T*>(new_buffer);
_capacity = new_capacity;
}
}
}
/**
* @brief 删除指定下标元素并保持顺序。
* @param index 删除下标。
* @return 指向删除位置的指针。
*/
constexpr T *const erase(u64 index)
{
assert(_data && index < _size);
return erase(std::addressof(_data[index]));
}
/**
* @brief 删除指定位置元素并保持顺序。
* @param item 待删除元素指针。
* @return 指向删除位置的指针。
*/
constexpr T *const erase(T *const item)
{
assert(_data && item >= std::addressof(_data[0]) &&
item < std::addressof(_data[_size]));
if constexpr (destruct) item->~T();
--_size;
if (item < std::addressof(_data[_size]))
{
memcpy(item, item + 1, (std::addressof(_data[_size]) - item) * sizeof(T));
}
return item;
}
/**
* @brief 无序删除指定下标元素。
* @param index 删除下标。
* @return 指向删除位置的指针。
*/
constexpr T *const erase_unordered(u64 index)
{
assert(_data && index < _size);
return erase_unordered(std::addressof(_data[index]));
}
/**
* @brief 无序删除指定位置元素。
* @param item 待删除元素指针。
* @return 指向删除位置的指针。
*/
constexpr T *const erase_unordered(T *const item)
{
assert(_data && item >= std::addressof(_data[0]) &&
item < std::addressof(_data[_size]));
if constexpr (destruct) item->~T();
--_size;
if (item < std::addressof(_data[_size]))
{
memcpy(item, std::addressof(_data[_size]), sizeof(T));
}
return item;
}
/**
* @brief 清空容器中的有效元素。
*/
constexpr void clear()
{
if constexpr (destruct)
{
destruct_range(0, _size);
}
_size = 0;
}
/**
* @brief 与另一个容器交换内容。
* @param o 目标容器。
*/
constexpr void swap(vector& o)
{
if (this != std::addressof(o))
{
auto temp(std::move(o));
o.move(*this);
move(temp);
}
}
/**
* @brief 返回底层数据指针。
* @return 数据首地址。
*/
[[nodiscard]] constexpr T* data()
{
return _data;
}
/**
* @brief 返回只读底层数据指针。
* @return 数据首地址。
*/
[[nodiscard]] constexpr T *const data() const
{
return _data;
}
/**
* @brief 判断容器是否为空。
* @return 空返回 true。
*/
[[nodiscard]] constexpr bool empty() const
{
return _size == 0;
}
/**
* @brief 获取元素个数。
* @return 元素数量。
*/
[[nodiscard]] constexpr u64 size() const
{
return _size;
}
/**
* @brief 获取当前容量。
* @return 容量值。
*/
[[nodiscard]] constexpr u64 capacity() const
{
return _capacity;
}
[[nodiscard]] constexpr T& operator [](u64 index)
{
assert(_data && index < _size);
return _data[index];
}
[[nodiscard]] constexpr const T& operator [](u64 index) const
{
assert(_data && index < _size);
return _data[index];
}
[[nodiscard]] constexpr T& front()
{
assert(_data && _size);
return _data[0];
}
[[nodiscard]] constexpr const T& front() const
{
assert(_data && _size);
return _data[0];
}
[[nodiscard]] constexpr T& back()
{
assert(_data && _size);
return _data[_size -1];
}
[[nodiscard]] constexpr const T& back() const
{
assert(_data && _size);
return _data[_size - 1];
}
[[nodiscard]] constexpr T* begin()
{
return std::addressof(_data[0]);
}
[[nodiscard]] constexpr const T* begin() const
{
return std::addressof(_data[0]);
}
[[nodiscard]] constexpr T* end()
{
assert(!(_data == nullptr && _size > 0));
return std::addressof(_data[_size]);
}
[[nodiscard]] constexpr const T* end() const
{
assert(!(_data == nullptr && _size > 0));
return std::addressof(_data[_size]);
}
private:
constexpr void move(vector& o)
{
_capacity = o._capacity;
_size = o._size;
_data = o._data;
o.reset();
}
constexpr void reset()
{
_capacity = 0;
_size = 0;
_data = nullptr;
}
constexpr void destruct_range(u64 first, u64 last)
{
assert(destruct);
assert(first <= _size && last <= _size && first <= last);
if (_data)
{
for (; first != last; ++first)
{
_data[first].~T();
}
}
}
constexpr void destroy()
{
assert([&] {return _capacity ? _data != nullptr : _data == nullptr; }());
clear();
_capacity = 0;
if (_data) free(_data);
_data = nullptr;
}
u64 _capacity{ 0 };
u64 _size{ 0 };
T* _data{ nullptr };
};
}

View File

@@ -0,0 +1,194 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="DebugEditor|x64">
<Configuration>DebugEditor</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="ReleaseEditor|x64">
<Configuration>ReleaseEditor</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>17.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{d1fb8bbd-6836-431b-a43d-f6ea248b8965}</ProjectGuid>
<RootNamespace>EngineTest</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugEditor|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseEditor|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='DebugEditor|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseEditor|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<PreBuildEventUseInBuild>false</PreBuildEventUseInBuild>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='DebugEditor|x64'">
<PreBuildEventUseInBuild>false</PreBuildEventUseInBuild>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<PreBuildEventUseInBuild>false</PreBuildEventUseInBuild>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseEditor|x64'">
<PreBuildEventUseInBuild>false</PreBuildEventUseInBuild>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(SolutionDir)Engine\Common;$(SolutionDir)Engine</AdditionalIncludeDirectories>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalLibraryDirectories>$(OutDir)</AdditionalLibraryDirectories>
</Link>
<PreBuildEvent>
<Command>xcopy /Y /D "$(SolutionDir)packages\dxCompiler\dxcompiler.dll" "$(OutDir)"</Command>
</PreBuildEvent>
<PostBuildEvent>
<Command>
</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='DebugEditor|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(SolutionDir)Engine\Common;$(SolutionDir)Engine</AdditionalIncludeDirectories>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalLibraryDirectories>$(OutDir)</AdditionalLibraryDirectories>
</Link>
<PreBuildEvent>
<Command>xcopy /Y /D "$(SolutionDir)packages\dxCompiler\dxcompiler.dll" "$(OutDir)"</Command>
</PreBuildEvent>
<PostBuildEvent>
<Command>
</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(SolutionDir)Engine\Common;$(SolutionDir)Engine</AdditionalIncludeDirectories>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalLibraryDirectories>$(OutDir)</AdditionalLibraryDirectories>
</Link>
<PreBuildEvent>
<Command>xcopy /Y /D "$(SolutionDir)packages\dxCompiler\dxcompiler.dll" "$(OutDir)"</Command>
</PreBuildEvent>
<PostBuildEvent>
<Command>
</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='ReleaseEditor|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(SolutionDir)Engine\Common;$(SolutionDir)Engine</AdditionalIncludeDirectories>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalLibraryDirectories>$(OutDir)</AdditionalLibraryDirectories>
</Link>
<PreBuildEvent>
<Command>xcopy /Y /D "$(SolutionDir)packages\dxCompiler\dxcompiler.dll" "$(OutDir)"</Command>
</PreBuildEvent>
<PostBuildEvent>
<Command>
</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="Main.cpp" />
<ClCompile Include="TestRenderer.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="Test.h" />
<ClInclude Include="TestEntityComponent.h" />
<ClInclude Include="TestRenderer.h" />
<ClInclude Include="TestWindow.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ClCompile Include="Main.cpp" />
<ClCompile Include="TestRenderer.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="Test.h" />
<ClInclude Include="TestEntityComponent.h" />
<ClInclude Include="TestWindow.h" />
<ClInclude Include="TestRenderer.h" />
</ItemGroup>
</Project>

84
EngineTest/Lights.cpp Normal file
View File

@@ -0,0 +1,84 @@
/**
* @file Lights.cpp
* @brief 渲染测试场景灯光创建与销毁逻辑。
*/
#include "EngineAPI/GameEntity.h"
#include "EngineAPI/Light.h"
#include "EngineAPI/TransformComponent.h"
#include "Graphics_Beta/Renderer.h"
#include "Test.h"
#if TEST_RENDERER
using namespace XEngine;
game_entity::entity create_one_game_entity(math::v3 position, math::v3 rotation, const char* script_name);
void remove_game_entity(game_entity::entity_id id);
const u64 left_set{ 0 };
const u64 right_set{ 1 };
utl::vector<graphics::light> lights;
constexpr math::v3
rgb_to_color(u8 r, u8 g, u8 b) { return { r / 255.f, g / 255.f, b / 255.f }; }
void
generate_lights()
{
// LEFT_SET
graphics::light_init_info info{};
info.entity_id = create_one_game_entity({}, {}, nullptr).get_id();
info.type = graphics::light::directioinal;
info.light_set_key = left_set;
info.intensity = 1.f;
info.color = rgb_to_color(174, 174, 174);
lights.emplace_back(graphics::create_light(info));
info.entity_id = create_one_game_entity({}, { math::pi * 0.5f,0,0 }, nullptr).get_id();
info.color = rgb_to_color(17, 27, 28);
lights.emplace_back(graphics::create_light(info));
info.entity_id = create_one_game_entity({}, { -math::pi * 0.5f,0,0 }, nullptr).get_id();
info.color = rgb_to_color(63, 47, 38);
lights.emplace_back(graphics::create_light(info));
//// RIGHT_SET
//info.entity_id = create_one_game_entity({}, {}, nullptr).get_id();
//info.type = graphics::light::directioinal;
//info.light_set_key = right_set;
//info.intensity = 1.f;
//info.color = rgb_to_color(174, 174, 174);
//lights.emplace_back(graphics::create_light(info));
//info.entity_id = create_one_game_entity({}, { math::pi * 0.5f,0,0 }, nullptr).get_id();
//info.color = rgb_to_color(17, 27, 28);
//lights.emplace_back(graphics::create_light(info));
//info.entity_id = create_one_game_entity({}, { -math::pi * 0.5f,0,0 }, nullptr).get_id();
//info.color = rgb_to_color(63, 47, 38);
//lights.emplace_back(graphics::create_light(info));
}
void
remove_lights()
{
for (auto& light : lights)
{
const game_entity::entity_id id{ light.entity_id() };
graphics::remove_light(light.get_id(), light.get_light_set_key());
remove_game_entity(id);
}
lights.clear();
}
#endif

85
EngineTest/Main.cpp Normal file
View File

@@ -0,0 +1,85 @@
/**
* @file Main.cpp
* @brief 测试程序入口与主循环。
*/
#include "Test.h"
#pragma comment(lib, "engine.lib")
#if TEST_ENTITY_COMPONENTS
#include "TestEntityComponent.h"
#elif TEST_WINDOW_COMPONENTS
#include "TestWindow.h"
#elif TEST_RENDERER
#include "TestRenderer.h"
#endif
#ifdef _WIN64
#include <Windows.h>
#include <filesystem>
// TODO: duplicate
std::filesystem::path
set_current_directory_to_executable_path()
{
wchar_t path[MAX_PATH]{};
const u32 length{ GetModuleFileName(0, &path[0], MAX_PATH) };
if (!length || GetLastError() == ERROR_INSUFFICIENT_BUFFER) return {};
std::filesystem::path p{ path };
std::filesystem::current_path(p.parent_path());
return std::filesystem::current_path();
}
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
#if _DEBUG
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif
set_current_directory_to_executable_path();
engine_test test{};
if (test.initialize())
{
MSG msg{};
bool is_running{ true };
while (is_running)
{
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
is_running &= (msg.message != WM_QUIT);
}
test.run();
}
}
test.shutdown();
}
#else
int main()
{
#if _DEBUG
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif
engine_test test{};
if (test.initialize())
{
test.run();
}
test.shutdown();
}
#endif

141
EngineTest/RenderItem.cpp Normal file
View File

@@ -0,0 +1,141 @@
/**
* @file RenderItem.cpp
* @brief 渲染项创建与销毁测试逻辑。
*/
#include "CommonHeader.h"
#include <filesystem>
#include "Content/ContentToEngine.h"
#include "ShaderComponents.h"
#include "Components/Entity.h"
#include "Graphics_Beta/Renderer.h"
#include "../ContentTools/Geometry.h"
#include "Test.h"
#if TEST_RENDERER
using namespace XEngine;
bool read_file(std::filesystem::path, std::unique_ptr<u8[]>&, u64&);
namespace {
id::id_type model_id{ id::invalid_id };
id::id_type vs_id{ id::invalid_id };
id::id_type ps_id{ id::invalid_id };
id::id_type mtl_id{ id::invalid_id };
std::unordered_map<id::id_type, id::id_type> render_item_entity_map;
void
load_model()
{
std::unique_ptr<u8[]> model;
u64 size{ 0 };
read_file("..\\..\\enginetest\\model.model", model, size);
model_id = content::create_resource(model.get(), content::asset_type::mesh);
assert(id::is_valid(model_id));
}
void
load_shaders()
{
shader_file_info info{};
info.file_name = "TestShader.hlsl";
info.function = "TestShaderVS";
info.type = shader_type::vertex;
const char* shader_path{ "..\\..\\enginetest\\" };
std::wstring defines[]{ L"ELEMENTS_TYPE=1", L"ELEMENTS_TYPE=3" };
utl::vector<u32> keys;
keys.emplace_back(tools::elements::element_type::static_normal);
keys.emplace_back(tools::elements::element_type::static_normal_texture);
utl::vector<std::wstring> extra_args{};
utl::vector<std::unique_ptr<u8[]>> vertex_shaders;
utl::vector<u8*> vertex_shaders_pointers;
for (u32 i{ 0 }; i < _countof(defines); ++i)
{
extra_args.clear();
extra_args.emplace_back(L"-D");
extra_args.emplace_back(defines[i]);
vertex_shaders.emplace_back(std::move(compile_shader(info, shader_path, extra_args)));
assert(vertex_shaders.back().get());
vertex_shaders_pointers.emplace_back(vertex_shaders.back().get());
}
extra_args.clear();
info.function = "TestShaderPS";
info.type = shader_type::pixel;
auto pixel_shader = compile_shader(info, shader_path, extra_args);
assert(pixel_shader.get());
vs_id = content::add_shader_group(vertex_shaders_pointers.data(), (u32)vertex_shaders_pointers.size(), keys.data());
const u8* pixel_shaders[]{ pixel_shader.get() };
ps_id = content::add_shader_group(&pixel_shaders[0], 1, &u32_invalid_id);
}
void
create_material()
{
graphics::material_init_info info{};
info.shader_ids[graphics::shader_type::vertex] = vs_id;
info.shader_ids[graphics::shader_type::pixel] = ps_id;
info.type = graphics::material_type::opaque;
mtl_id = content::create_resource(&info, content::asset_type::material);
}
}//namespace
id::id_type
create_render_item(id::id_type entity_id)
{
auto _1 = std::thread{ [] {load_model(); } };
auto _2 = std::thread{ [] {load_shaders(); } };
_1.join();
_2.join();
create_material();
id::id_type materials[]{ mtl_id };
id::id_type item_id{ graphics::add_render_item(entity_id, model_id, _countof(materials), &materials[0]) };
render_item_entity_map[item_id] = entity_id;
return item_id;
}
void
destroy_render_item(id::id_type item_id)
{
if (id::is_valid(item_id))
{
graphics::remove_render_item(item_id);
auto pair = render_item_entity_map.find(item_id);
if (pair != render_item_entity_map.end())
{
game_entity::remove(game_entity::entity_id{ pair->second });
}
}
if (id::is_valid(mtl_id))
{
content::destroy_resource(mtl_id, content::asset_type::material);
}
if (id::is_valid(vs_id))
{
content::remove_shader_group(vs_id);
}
if (id::is_valid(ps_id))
{
content::remove_shader_group(ps_id);
}
if (id::is_valid(model_id))
{
content::destroy_resource(model_id, content::asset_type::mesh);
}
}
#endif

View File

@@ -0,0 +1,326 @@
/**
* @file ShaderComponents.cpp
* @brief 测试工程着色器编译与缓存实现。
*/
#include <fstream>
#include <filesystem>
#include <d3d12shader.h>
#include "CommonHeader.h"
#include "Graphics_Beta\Direct3D12\D3D12Shaders.h"
#include "Graphics_Beta\Direct3D12\D3D12CommonHeaders.h"
#include "Graphics_Beta\Direct3D12\D3D12Core.h"
#include "ShaderComponents.h"
#include "Content/ContentToEngine.h"
#include "Utilities/IOStream.h"
#include <dxcapi.h>
using namespace XEngine;
using namespace XEngine::graphics::d3d12::shaders;
using namespace Microsoft::WRL;
namespace {
constexpr const char* shaders_source_path{ "../../Engine/Graphics_Beta/Direct3D12/Shaders/" };
struct engine_shader_info
{
engine_shader::id id;
shader_file_info info;
};
constexpr engine_shader_info engine_shader_files[]
{
engine_shader::fullscreen_triangle_vs,{"FullScreenTriangle.hlsl", "FullScreenTriangleVS",shader_type::vertex},
engine_shader::fill_color_ps,{"FillColor.hlsl", "FillColorPS", shader_type::pixel},
engine_shader::post_process_ps,{"PostProcess.hlsl", "PostProcessPS", shader_type::pixel},
};
static_assert(_countof(engine_shader_files) == engine_shader::count);
struct dxc_compiled_shader
{
ComPtr<IDxcBlob> byte_code;
ComPtr<IDxcBlobUtf8> disassembly;
DxcShaderHash hash;
};
std::wstring
to_wstring(const char* c)
{
std::string s{ c };
return { s.begin(), s.end() };
}
class shader_compiler
{
public:
shader_compiler()
{
HRESULT hr{ S_OK };
DXCall(hr = DxcCreateInstance(CLSID_DxcCompiler, IID_PPV_ARGS(&_compiler)));
if (FAILED(hr)) return;
DXCall(hr = DxcCreateInstance(CLSID_DxcUtils, IID_PPV_ARGS(&_utils)));
if (FAILED(hr)) return;
DXCall(hr = _utils->CreateDefaultIncludeHandler(&_include_handler));
if (FAILED(hr)) return;
}
DISABLE_COPY_AND_MOVE(shader_compiler);
dxc_compiled_shader compile(shader_file_info info, std::filesystem::path full_path, XEngine::utl::vector<std::wstring>& extra_args)
{
assert(_compiler && _utils && _include_handler);
HRESULT hr{ S_OK };
ComPtr<IDxcBlobEncoding> source_blob{ nullptr };
DXCall(hr = _utils->LoadFile(full_path.c_str(), nullptr, &source_blob));
if (FAILED(hr)) return {};
assert(source_blob && source_blob->GetBufferSize());
OutputDebugStringA("Compiling ");
OutputDebugStringA(info.file_name);
return compile(source_blob.Get(), get_args(info, extra_args));
}
dxc_compiled_shader compile(IDxcBlobEncoding* source_blob, XEngine::utl::vector<std::wstring> compiler_args)
{
DxcBuffer buffer{};
buffer.Encoding = DXC_CP_ACP;
buffer.Ptr = source_blob->GetBufferPointer();
buffer.Size = source_blob->GetBufferSize();
utl::vector<LPCWSTR> args;
for (const auto& arg : compiler_args)
{
args.emplace_back(arg.c_str());
}
HRESULT hr{ S_OK };
ComPtr<IDxcResult> results{ nullptr };
DXCall(hr = _compiler->Compile(&buffer, args.data(), args.size(), _include_handler.Get(), IID_PPV_ARGS(&results)));
if (FAILED(hr)) return {};
ComPtr<IDxcBlobUtf8> errors{ nullptr };
DXCall(hr = results->GetOutput(DXC_OUT_ERRORS, IID_PPV_ARGS(&errors), nullptr));
if (FAILED(hr)) return {};
if (errors && errors->GetStringLength())
{
OutputDebugStringA("\nShader compilation error : \n");
OutputDebugStringA(errors->GetStringPointer());
}
else
{
OutputDebugStringA(" [ Succeeded ] ");
}
OutputDebugStringA("\n");
HRESULT status{ S_OK };
DXCall(hr = results->GetStatus(&status));
if (FAILED(hr) || FAILED(status)) return {};
ComPtr<IDxcBlob> hash{ nullptr };
DXCall(hr = results->GetOutput(DXC_OUT_SHADER_HASH, IID_PPV_ARGS(&hash), nullptr));
if (FAILED(hr)) return {};
DxcShaderHash *const hash_buffer{ (DxcShaderHash *const)hash->GetBufferPointer() };
assert(!(hash_buffer->Flags & DXC_HASHFLAG_INCLUDES_SOURCE));
OutputDebugStringA(" [ Shader Hash: ] ");
for (u32 i{ 0 }; i < _countof(hash_buffer->HashDigest); ++i)
{
char hash_bytes[3]{};
sprintf_s(hash_bytes, "%02x", (u32)hash_buffer->HashDigest[i]);
OutputDebugStringA(hash_bytes);
OutputDebugStringA(" ");
}
OutputDebugStringA("\n");
ComPtr<IDxcBlob> shader{ nullptr };
DXCall(hr = results->GetOutput(DXC_OUT_OBJECT, IID_PPV_ARGS(&shader), nullptr));
if (FAILED(hr)) return {};
buffer.Ptr = shader->GetBufferPointer();
buffer.Size = shader->GetBufferSize();
ComPtr<IDxcResult> disasm_results{ nullptr };
DXCall(hr = _compiler->Disassemble(&buffer, IID_PPV_ARGS(&disasm_results)));
ComPtr<IDxcBlobUtf8> disassembly{ nullptr };
DXCall(hr = disasm_results->GetOutput(DXC_OUT_DISASSEMBLY, IID_PPV_ARGS(&disassembly), nullptr));
dxc_compiled_shader result{ shader.Detach(), disassembly.Detach() };
memcpy(&result.hash.HashDigest[0], &hash_buffer->HashDigest[0], _countof(hash_buffer->HashDigest));
return result;
}
private:
utl::vector<std::wstring>
get_args(const shader_file_info& info, utl::vector<std::wstring>& extra_args)
{
utl::vector<std::wstring> args{};
args.emplace_back(to_wstring(info.file_name));
args.emplace_back(L"-E");
args.emplace_back(to_wstring(info.function));
args.emplace_back(L"-T");
args.emplace_back(to_wstring(_profile_strings[(u32)info.type]));
args.emplace_back(L"-I");
args.emplace_back(to_wstring(shaders_source_path));
args.emplace_back(L"-enable-16bit-types");
args.emplace_back(DXC_ARG_ALL_RESOURCES_BOUND);
#if _DEBUG
args.emplace_back(DXC_ARG_DEBUG);
args.emplace_back(DXC_ARG_SKIP_OPTIMIZATIONS);
#else
args.emplace_back(DXC_ARG_OPTIMIZATION_LEVEL3);
#endif // _DEBUG
args.emplace_back(DXC_ARG_WARNINGS_ARE_ERRORS);
args.emplace_back(L"-Qstrip_reflect");
args.emplace_back(L"-Qstrip_debug");
for (const auto& arg : extra_args)
{
args.emplace_back(arg.c_str());
}
return args;
}
constexpr static const char* _profile_strings[]{ "vs_6_6", "hs_6_6", "ds_6_6", "gs_6_6", "ps_6_6", "cs_6_6", "as_6_6", "ms_6_6" };
static_assert(_countof(_profile_strings) == shader_type::count);
ComPtr<IDxcCompiler3> _compiler{ nullptr };
ComPtr<IDxcUtils> _utils{ nullptr };
ComPtr<IDxcIncludeHandler> _include_handler{ nullptr };
};
decltype(auto)
get_engine_shaders_path()
{
return std::filesystem::absolute(graphics::get_engine_shaders_path(graphics::graphics_platform::direct3d12));
}
bool
compiled_shaders_are_up_to_date()
{
auto engine_shaders_path = get_engine_shaders_path();
if (!std::filesystem::exists(engine_shaders_path))return false;
auto shaders_compilation_time = std::filesystem::last_write_time(engine_shaders_path);
std::filesystem::path full_path{};
for (u32 i{ 0 }; i < engine_shader::count; ++i) {
auto& info = engine_shader_files[i];
full_path = shaders_source_path;
full_path += info.info.file_name;
if (!std::filesystem::exists(full_path)) return false;
auto shader_file_time = std::filesystem::last_write_time(full_path);
if (shader_file_time > shaders_compilation_time)
{
return false;
}
}
return true;
}
bool
save_compiled_shaders(utl::vector<dxc_compiled_shader>& shaders)
{
auto engine_shaders_path = get_engine_shaders_path();
std::filesystem::create_directories(engine_shaders_path.parent_path());
std::ofstream file(engine_shaders_path, std::ios::out | std::ios::binary);
if (!file || !std::filesystem::exists(engine_shaders_path))
{
file.close();
return false;
}
for (const auto& shader : shaders)
{
const D3D12_SHADER_BYTECODE byte_code{ shader.byte_code->GetBufferPointer(), shader.byte_code->GetBufferSize() };
file.write((char*)&byte_code.BytecodeLength, sizeof(byte_code.BytecodeLength));
file.write((char*)&shader.hash.HashDigest[0], _countof(shader.hash.HashDigest));
file.write((char*)byte_code.pShaderBytecode, byte_code.BytecodeLength);
}
file.close();
return true;
}
} // namespace
std::unique_ptr<u8[]>
compile_shader(shader_file_info info, const char* file_path, utl::vector<std::wstring>& extra_args)
{
std::filesystem::path full_path{ file_path };
full_path += info.file_name;
if (!std::filesystem::exists(full_path)) return {};
shader_compiler compiler{};
dxc_compiled_shader compiled_shader{ compiler.compile(info, full_path, extra_args) };
if (compiled_shader.byte_code != nullptr && compiled_shader.byte_code->GetBufferPointer() && compiled_shader.byte_code->GetBufferSize())
{
static_assert(content::compiled_shader::hash_length == _countof(DxcShaderHash::HashDigest));
const u64 buffer_size{ sizeof(u64) + content::compiled_shader::hash_length + compiled_shader.byte_code->GetBufferSize() };
std::unique_ptr<u8[]> buffer{ std::make_unique<u8[]>(buffer_size) };
utl::blob_stream_writer blob{ buffer.get(), buffer_size };
blob.write(compiled_shader.byte_code->GetBufferSize());
blob.write(compiled_shader.hash.HashDigest, content::compiled_shader::hash_length);
blob.write((u8*)compiled_shader.byte_code->GetBufferPointer(), compiled_shader.byte_code->GetBufferSize());
assert(blob.offset() == buffer_size);
return buffer;
}
return {};
}
bool
compile_shaders()
{
if (compiled_shaders_are_up_to_date()) return true;
shader_compiler compiler{};
utl::vector<dxc_compiled_shader> shaders;
std::filesystem::path full_path{};
for (u32 i{ 0 }; i < engine_shader::count; ++i)
{
auto& file = engine_shader_files[i];
full_path = shaders_source_path;
full_path += file.info.file_name;
if (!std::filesystem::exists(full_path)) return false;
utl::vector<std::wstring> extra_args{};
dxc_compiled_shader compiled_shader{ compiler.compile(file.info, full_path, extra_args) };
if (compiled_shader.byte_code != nullptr && compiled_shader.byte_code->GetBufferPointer() && compiled_shader.byte_code->GetBufferSize())
{
shaders.emplace_back(std::move(compiled_shader));
}
else
{
return false;
}
}
return save_compiled_shaders(shaders);
}

View File

@@ -0,0 +1,35 @@
/**
* @file ShaderComponents.h
* @brief 测试工程着色器编译接口定义。
*/
#pragma once
#include "CommonHeader.h"
#pragma comment(lib, "dxcompiler.lib")
struct shader_type {
enum type :u32 {
vertex = 0,
hull,
domain,
geometry,
pixel,
compute,
amplification,
mesh,
count
};
};
struct shader_file_info
{
const char* file_name;
const char* function;
shader_type::type type;
};
std::unique_ptr<u8[]> compile_shader(shader_file_info info, const char* file_path, XEngine::utl::vector<std::wstring>& extra_args);
bool compile_shaders();

72
EngineTest/Test.h Normal file
View File

@@ -0,0 +1,72 @@
/**
* @file Test.h
* @brief 测试基类与计时工具定义。
*/
#pragma once
#include <thread>
#include <chrono>
#include <string>
#include <iostream>
#include "Id.h"
#define TEST_ENTITY_COMPONENTS 0
#define TEST_WINDOW_COMPONENTS 0
#define TEST_RENDERER 1
class Test
{
public:
virtual bool initialize() = 0;
virtual void run() = 0;
virtual bool shutdown() = 0;
};
#if _WIN64
#include <Windows.h>
class time_it
{
public:
using clock = std::chrono::high_resolution_clock;
using time_stamp = std::chrono::steady_clock::time_point;
constexpr float dt_avg() const { return _dt_avg * 1e-3f; }
void begin()
{
_start = clock::now();
}
void end()
{
auto dt = clock::now() - _start;
_msg_avg += ((float)std::chrono::duration_cast<std::chrono::milliseconds>(dt).count() - _msg_avg) / (float)_counter;
++_counter;
_dt_avg = _msg_avg;
if (std::chrono::duration_cast<std::chrono::seconds>(clock::now() - _seconds).count() >= 1)
{
OutputDebugStringA("Avg. frame (ms): ");
OutputDebugStringA(std::to_string(_msg_avg).c_str());
OutputDebugStringA((" " + std::to_string(_counter)).c_str());
OutputDebugStringA(" fps");
OutputDebugStringA("\n");
_msg_avg = 0.f;
_counter = 1;
_seconds = clock::now();
}
}
private:
float _dt_avg{ 16.7f };
float _msg_avg{ 0.f };
int _counter{ 1 };
time_stamp _start;
time_stamp _seconds{ clock::now() };
};
#endif

View File

@@ -0,0 +1,98 @@
/**
* @file TestEntityComponent.h
* @brief 实体与组件系统测试用例定义。
*/
#pragma once
#include "Test.h"
#include "..\Engine\Components\Entity.h"
#include "..\Engine\Components\Transform.h"
#include <iostream>
#include <ctime>
using namespace XEngine;
class engine_test : public Test
{
public:
bool initialize() override
{
srand((u32)time(nullptr));
return true;
}
void run() override
{
//do {
for (u32 i{ 0 }; i < 10; ++i)
{
create_random();
remove_random();
_num_entities = (u32)_entities.size();
}
print_results();
//} while (getchar() != 'q');
}
bool shutdown() override
{
return true;
}
private:
utl::vector<game_entity::entity> _entities;
u32 _added{ 0 };
u32 _removed{ 0 };
u32 _num_entities{ 0 };
void create_random()
{
u32 count = rand() % 20;
if (_entities.empty()) count = 10;
transform::init_info transform_info{};
game_entity::entity_info entity_info
{
&transform_info,
nullptr,
};
while (count > 0)
{
++_added;
game_entity::entity entity{ game_entity::create(entity_info) };
assert(entity.is_valid() && id::is_valid(entity.get_id()));
_entities.push_back(entity);
assert(game_entity::is_alive(entity.get_id()));
--count;
}
}
void print_results()
{
std::cout << "Entities created:" << _added << std::endl;
std::cout << "Entities deleted:" << _removed << std::endl;
}
void remove_random()
{
u32 count = rand() % 20;
if (_entities.size() < 1000)return;
while (count > 0)
{
++_removed;
const u32 index{ (u32)rand() % (u32)_entities.size() };
auto entity = _entities[index];
assert(entity.is_valid() && id::is_valid(entity.get_id()));
if (entity.is_valid())
{
game_entity::remove(entity.get_id());
_entities.erase(_entities.begin() + index);
assert(!game_entity::is_alive(entity.get_id()));
}
--count;
}
}
};

View File

@@ -0,0 +1,355 @@
/**
* @file TestRenderer.cpp
* @brief 渲染功能综合测试实现。
*/
#include "TestRenderer.h"
#include "Platform/Platform.h"
#include "Platform/PlatformTypes.h"
#include "Graphics_Beta/Renderer.h"
#include "ShaderComponents.h"
#include "Content/ContentToEngine.h"
#include "Graphics_Beta/Direct3D12/D3D12Core.h"
#include "Components/Script.h"
#include "Components/Entity.h"
#include "Components/Transform.h"
#include <filesystem>
#include <fstream>
#if TEST_RENDERER
using namespace XEngine;
class rotator_script;
REGISTER_SCRIPT(rotator_script);
class rotator_script :public script::entity_script
{
public:
constexpr explicit rotator_script(game_entity::entity entity)
: script::entity_script{ entity }{}
void begin_play() override {}
void update(float dt) override
{
_angle += 0.25f * dt * math::two_pi;
if (_angle > math::two_pi) _angle -= math::two_pi;
math::v3a rot{ 0.f, _angle, 0.f };
DirectX::XMVECTOR quat{ DirectX::XMQuaternionRotationRollPitchYawFromVector(DirectX::XMLoadFloat3A(&rot)) };
math::v4 rot_quat{};
DirectX::XMStoreFloat4(&rot_quat, quat);
set_rotation(rot_quat);
}
private:
f32 _angle{ 0.f };
};
// Multithreading test worker spawn code ///////////////////
#define ENABLE_TEST_WORKERS 0
constexpr u32 num_threads{ 8 };
bool wshutdown{ false };
std::thread workers[num_threads];
utl::vector<u8> buffer(1024 * 1024, 0);
void buffer_test_worker()
{
while (!wshutdown)
{
auto* resource = graphics::d3d12::d3dx::create_buffer(buffer.data(), (u32)buffer.size());
graphics::d3d12::core::deferred_release(resource);
}
}
template<class FnPtr, class... Args>
void init_test_workers(FnPtr&& fnPtr, Args&&... args)
{
#if ENABLE_TEST_WORKERS
wshutdown = false;
for (auto& w : workers)
w = std::thread(std::forward<FnPtr>(fnPtr), std::forward<Args>(args)...);
#endif
}
void joint_test_workers()
{
#if ENABLE_TEST_WORKERS
wshutdown = true;
for (auto&w : workers) w.join();
#endif
}
//////////////////////
struct camera_surface {
game_entity::entity entity{};
graphics::camera camera{};
graphics::render_surface surface{};
};
id::id_type item_id{ id::invalid_id };
id::id_type model_id{ id::invalid_id };
camera_surface _surfaces[3]{};
time_it timer{};
bool resized{ false };
bool is_restarting{ false };
void destroy_camera_surface(camera_surface& surface);
game_entity::entity create_one_game_entity(math::v3 position, math::v3 rotation, const char* script_name);
bool test_initialize();
void test_shutdown();
id::id_type create_render_item(id::id_type entity_id);
void destroy_render_item(id::id_type item_id);
void generate_lights();
void remove_lights();
LRESULT win_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
bool toggle_fullscreen{ false };
switch (msg)
{
case WM_DESTROY:
{
bool all_closed{ true };
for (u32 i{ 0 }; i < _countof(_surfaces); ++i)
{
if (_surfaces[i].surface.window.is_valid())
{
if (_surfaces[i].surface.window.is_closed())
{
destroy_camera_surface(_surfaces[i]);
}
else
{
all_closed = false;
}
}
}
if (all_closed && !is_restarting)
{
PostQuitMessage(0);
return 0;
}
}
case WM_SIZE:
resized = (wparam != SIZE_MINIMIZED);
break;
case WM_SYSCHAR:
toggle_fullscreen = (wparam == VK_RETURN && (HIWORD(lparam) & KF_ALTDOWN));
break;
case WM_KEYDOWN:
if (wparam == VK_ESCAPE)
{
PostMessage(hwnd, WM_CLOSE, 0, 0);
return 0;
}
else if (wparam == VK_F11)
{
is_restarting = true;
test_shutdown();
test_initialize();
}
}
if ((resized & (GetAsyncKeyState(VK_LBUTTON) >= 0)) || toggle_fullscreen)
{
platform::window win{ platform::window_id{(id::id_type)GetWindowLongPtr(hwnd, GWLP_USERDATA)} };
for (u32 i{ 0 }; i < _countof(_surfaces); ++i)
{
if (win.get_id() == _surfaces[i].surface.window.get_id())
{
if (toggle_fullscreen)
{
win.set_fullscreen(!win.is_fullscreen());
return 0;
}
else
{
_surfaces[i].surface.surface.resize(win.width(), win.height());
_surfaces[i].camera.aspect_ratio((f32)win.width() / win.height());
resized = false;
}
break;
}
}
}
return DefWindowProc(hwnd, msg, wparam, lparam);
}
void
create_camera_surface(camera_surface& surface, platform::window_init_info info)
{
surface.surface.window = platform::create_window(&info);
surface.surface.surface = graphics::create_surface(surface.surface.window);
surface.entity = create_one_game_entity({ 0.0f,0.f,6.0f }, { 0.f, 3.14f, 0.f }, nullptr);
surface.camera = graphics::create_camera(graphics::perspective_camera_init_info{ surface.entity.get_id() });
surface.camera.aspect_ratio((f32)surface.surface.window.width() / surface.surface.window.height());
}
void
destroy_camera_surface(camera_surface& surface)
{
camera_surface temp{ surface };
surface = {};
if (temp.surface.surface.is_valid()) graphics::remove_surface(temp.surface.surface.get_id());
if (temp.surface.window.is_valid()) platform::remove_window(temp.surface.window.get_id());
if (temp.camera.is_valid()) graphics::remove_camera(temp.camera.get_id());
if (temp.entity.is_valid()) game_entity::remove(temp.entity.get_id());
}
game_entity::entity
create_one_game_entity(math::v3 position, math::v3 rotation, const char* script_name)
{
transform::init_info transform_info{};
DirectX::XMVECTOR quat{ DirectX::XMQuaternionRotationRollPitchYawFromVector(DirectX::XMLoadFloat3(&rotation)) };
math::v4a rot_quat;
DirectX::XMStoreFloat4A(&rot_quat, quat);
memcpy(&transform_info.rotation[0], &rot_quat.x, sizeof(transform_info.rotation));
memcpy(&transform_info.position[0], &position.x, sizeof(transform_info.position));
script::init_info script_info{};
if (script_name)
{
script_info.script_creator = script::detail::get_script_creator(script::detail::string_hash()(script_name));
assert(script_info.script_creator);
}
game_entity::entity_info entity_info{};
entity_info.transform = &transform_info;
entity_info.script = &script_info;
game_entity::entity ntt{ game_entity::create(entity_info) };
assert(ntt.is_valid());
return ntt;
}
void
remove_game_entity(game_entity::entity_id id)
{
game_entity::remove(id);
}
bool
read_file(std::filesystem::path path, std::unique_ptr<u8[]>&data, u64& size)
{
if (!std::filesystem::exists(path)) return false;
size = std::filesystem::file_size(path);
assert(size);
if (!size) return false;
data = std::make_unique<u8[]>(size);
std::ifstream file{ path, std::ios::in | std::ios::binary };
if (!file || !file.read((char*)data.get(), size))
{
file.close();
return false;
}
file.close();
return true;
}
bool
test_initialize()
{
while (!compile_shaders())
{
if (MessageBox(nullptr, L"Failed to compile engine shaders.", L"sahder Compilation Error", MB_RETRYCANCEL) != IDRETRY)
return false;
}
if (!graphics::initialize(graphics::graphics_platform::direct3d12))
return false;
platform::window_init_info info[]
{
{&win_proc, nullptr, L"Test Window 1", 200, 100,400,400},
{&win_proc, nullptr, L"Test Window 2", 700, 100,400,400},
{&win_proc, nullptr, L"Test Window 3", 1200,100,400,400},/*
{&win_proc, nullptr, L"Test Window 4", 200, 600,400,400},
{&win_proc, nullptr, L"Test Window 5", 700, 600,400,400},
{&win_proc, nullptr, L"Test Window 6", 1200,600,400,400},*/
};
static_assert(_countof(info) == _countof(_surfaces));
for (u32 i{ 0 }; i < _countof(_surfaces); ++i)
create_camera_surface(_surfaces[i], info[i]);
//load test model
std::unique_ptr<u8[]> model;
u64 size{ 0 };
if (!read_file("..\\..\\enginetest\\model.model", model, size)) return false;
model_id = content::create_resource(model.get(), content::asset_type::mesh);
if (!id::is_valid(model_id))return false;
init_test_workers(buffer_test_worker);
item_id = create_render_item(create_one_game_entity({ }, { }, nullptr).get_id());
generate_lights();
is_restarting = false;
return true;
}
void
test_shutdown()
{
remove_lights();
destroy_render_item(item_id);
joint_test_workers();
if (id::is_valid(model_id))
{
content::destroy_resource(model_id, content::asset_type::mesh);
}
for (u32 i{ 0 }; i < _countof(_surfaces); ++i)
destroy_camera_surface(_surfaces[i]);
graphics::shutdown();
}
bool
engine_test::initialize()
{
return test_initialize();
}
void
engine_test::run()
{
timer.begin();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
script::update(timer.dt_avg());
for (u32 i{ 0 }; i < _countof(_surfaces); ++i)
{
if (_surfaces[i].surface.surface.is_valid())
{
f32 threshold{ 10 };
graphics::frame_info info{};
info.render_item_ids = &item_id;
info.render_item_count = 1;
info.thresholds = &threshold;
info.light_set_key = 0;
info.average_frame_time = timer.dt_avg();
info.camera_id = _surfaces[i].camera.get_id();
_surfaces[i].surface.surface.render(info);
}
}
timer.end();
}
bool
engine_test::shutdown()
{
test_shutdown();
return true;
}
#endif

154
EngineTest/TestShader.hlsl Normal file
View File

@@ -0,0 +1,154 @@
//#include "../Engine/Graphics/Direct3D12/Shaders/Common.hlsli"
#include "D:\AllWX\AllC\XEngine\Engine\Graphics\Direct3D12\Shaders\Common.hlsli"
struct VertexOut
{
float4 HomogeneousPosition : SV_Position;
float3 WorldPosition : POSITIONT;
float3 WorldNormal : NORMAL;
float3 WorldTangent : TANGENT;
float2 uv : TEXTURE;
};
struct PixelOut
{
float4 Color : SV_TARGET0;
};
#define ElementsTypeStaticNormal 0x01
#define ElementsTypeStaticNormalTexture 0x03
#define ElementsTypeStaticColor 0x04
#define ElementsTypeSkeletal 0x08
#define ElementsTypeSkeletalColor ElementsTypeSkeletal | ElementsTypeStaticColor
#define ElementsTypeSkeletalNormal ElementsTypeSkeletal | ElementsTypeStaticNormal
#define ElementsTypeSkeletalNormalColor ElementsTypeSkeletalNormal | ElementsTypeSkeletalColor
#define ElementsTypeSkeletalNormalTexture ElementsTypeSkeletal | ElementsTypeStaticNormalTexture
#define ElementsTypeSkeletalNormalTextureColor ElementsTypeSkeletalNormalTexture | ElementsTypeStaticColor
struct VertexElement
{
#if ELEMENTS_TYPE == ElementsTypeStaticNormal
uint ColorTSign;
uint16_t2 Normal;
#elif ELEMENTS_TYPE == ElementsTypeStaticNormalTexture
uint ColorTSign;
uint16_t2 Normal;
uint16_t2 Tangent;
float2 UV;
#elif ELEMENTS_TYPE == ElementsTypeStaticColor
#elif ELEMENTS_TYPE == ElementsTypeSkeletal
#elif ELEMENTS_TYPE == ElementsTypeSkeletalColor
#elif ELEMENTS_TYPE == ElementsTypeSkeletalNormal
#elif ELEMENTS_TYPE == ElementsTypeSkeletalNormalColor
#elif ELEMENTS_TYPE == ElementsTypeSkeletalNormalTexture
#elif ELEMENTS_TYPE == ElementsTypeSkeletalNormalTextureColor
#endif
};
const static float InvIntervals = 2.f / ((1 << 16) - 1);
ConstantBuffer<GlobalShaderData> GlobalData : register(b0, space0);
ConstantBuffer<PerObjectData> PerObjectBuffer : register(b1, space0);
StructuredBuffer<float3> VertexPositions : register(t0, space0);
StructuredBuffer<VertexElement> Elements : register(t1, space0);
StructuredBuffer<DirectionalLightParameters> DirectionLights : register(t3, space0);
VertexOut TestShaderVS(in uint VertexIdx : SV_VertexID)
{
VertexOut vsOut;
float4x4 IdentityMatrix =
{
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f
};
float4 position = float4(VertexPositions[VertexIdx], 1.f);
float4 worldPosition = mul(PerObjectBuffer.World, position);
#if ELEMENTS_TYPE == ElementsTypeStaticNormal
VertexElement element = Elements[VertexIdx];
float2 nXY = element.Normal * InvIntervals - 1.f;
uint signs = (element.ColorTSign >> 24) & 0xff;
float nSign = float(signs & 0x02) - 1;
float3 normal = float3(nXY.x, nXY.y, sqrt(saturate(1.f - dot(normal.xy, normal.xy))) * nSign);
vsOut.HomogeneousPosition = mul(PerObjectBuffer.WorldViewProjection, position);
vsOut.WorldPosition = worldPosition.xyz;
vsOut.WorldNormal = 0;
vsOut.WorldTangent = 0.f;
vsOut.uv = 0.f;
#elif ELEMENTS_TYPE == ElementsTypeStaticNormalTexture
VertexElement element = Elements[VertexIdx];
float2 nXY = element.Normal * InvIntervals - 1.f;
uint signs = (element.ColorTSign >> 24) & 0xff;
float nSign = float(signs & 0x02) - 1;
float3 normal = float3(nXY.x, nXY.y, sqrt(saturate(1.f - dot(normal.xy, normal.xy))) * nSign);
vsOut.HomogeneousPosition = mul(PerObjectBuffer.WorldViewProjection, position);
vsOut.WorldPosition = worldPosition.xyz;
vsOut.WorldNormal = mul(float4(normal, 0.f), PerObjectBuffer.InvWorld).xyz;
vsOut.WorldNormal = mul(PerObjectBuffer.World, float4(normal, 0.f)).xyz;
//vsOut.WorldNormal = sqrt(saturate(1.f - dot(normal.xy, normal.xy))) * nSign;
vsOut.WorldTangent = 0.f;
vsOut.uv = 0.f;
#else
#undef ELEMENTS_TYPE
vsOut.HomogeneousPosition = mul(PerObjectBuffer.WorldViewProjection, position);
vsOut.WorldPosition = worldPosition.xyz;
vsOut.WorldNormal = 0.f;
vsOut.WorldTangent = 0.f;
vsOut.uv = 0.f;
#endif
return vsOut;
}
[earlydepthstencil]
PixelOut TestShaderPS(in VertexOut psIn)
{
PixelOut psOut;
psOut.Color = float4(psIn.WorldNormal, 1);
float4x4 IdentityMatrix =
{
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f
};
float3 normal = normalize(psIn.WorldNormal);
float3 viewDir = normalize(GlobalData.CameraPosition - psIn.WorldPosition);
float3 color = 0;
for (uint i = 0; i < GlobalData.NumIdrectionalLights; ++i)
{
DirectionalLightParameters light = DirectionLights[i];
float3 lr = normalize(light.Direction);
float3 lightDirection = mul(IdentityMatrix, float4(lr, 0)).xyz;
//float3 lightDirection = normalize(float3(0, 1, 0));
float diffuse = max(dot(normal, -lightDirection), 0.f);
float3 h = (viewDir + lightDirection);
float specular = pow(max(dot(normal, h), 0.f), 16) * 0.5f;
//float3 lightColor = float3(1, 1, 1);
float3 lightColor = light.Color * light.Intensity;
//color = dot(normal, -lr);
color = normal;
}
float3 ambient = 10 / 255.f;
psOut.Color = saturate(float4(color + ambient, 1.f));
return psOut;
}

78
EngineTest/TestWindow.h Normal file
View File

@@ -0,0 +1,78 @@
/**
* @file TestWindow.h
* @brief 窗口系统测试用例实现。
*/
#pragma once
#include "Test.h"
#include "..\Platform\Platform.h"
#include "..\Platform\PlatformTypes.h"
using namespace XEngine;
platform::window _windows[4];
LRESULT win_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
switch (msg)
{
case WM_DESTROY:
{
bool all_closed{ true };
for (u32 i{ 0 }; i < _countof(_windows); ++i)
{
if (!_windows[i].is_closed())
{
all_closed = false;
}
}
if (all_closed)
{
PostQuitMessage(0);
return 0;
}
}
case WM_SYSCHAR:
if (wparam == VK_RETURN && (HIWORD(lparam) & KF_ALTDOWN))
{
platform::window win{ platform::window_id{(id::id_type)GetWindowLongPtr(hwnd, GWLP_USERDATA)} };
win.set_fullscreen(!win.is_fullscreen());
return 0;
}
default:
break;
}
return DefWindowProc(hwnd, msg, wparam, lparam);
}
class engine_test :public Test
{
public:
bool initialize() override
{
platform::window_init_info info[]
{
{&win_proc, nullptr, L"Test Window 1", 100,100,400,800},
{&win_proc, nullptr, L"Test Window 2", 150,200,400,700},
{&win_proc, nullptr, L"Test Window 3", 200,300,400,600},
{&win_proc, nullptr, L"Test Window 4", 150,400,400,500},
};
static_assert(_countof(info) == _countof(_windows));
for (u32 i{ 0 }; i < _countof(_windows); ++i)
_windows[i] = platform::create_window(&info[i]);
return true;
}
void run() override
{
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
bool shutdown() override
{
for (u32 i{ 0 }; i < _countof(_windows); ++i)
platform::remove_window(_windows[i].get_id());
return true;
}
};

BIN
EngineTest/model.model Normal file

Binary file not shown.

38
FeatureExtractDemo.sln Normal file
View File

@@ -0,0 +1,38 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.12.35527.113
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Engine", "Engine\Engine.vcxproj", "{838F1EAE-48A4-40BA-9B8F-332D37EA66CD}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EngineTest", "EngineTest\EngineTest.vcxproj", "{D1FB8BBD-6836-431B-A43D-F6EA248B8965}"
ProjectSection(ProjectDependencies) = postProject
{838F1EAE-48A4-40BA-9B8F-332D37EA66CD} = {838F1EAE-48A4-40BA-9B8F-332D37EA66CD}
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
DebugEditor|x64 = DebugEditor|x64
Release|x64 = Release|x64
ReleaseEditor|x64 = ReleaseEditor|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{838F1EAE-48A4-40BA-9B8F-332D37EA66CD}.Debug|x64.ActiveCfg = Debug|x64
{838F1EAE-48A4-40BA-9B8F-332D37EA66CD}.Debug|x64.Build.0 = Debug|x64
{838F1EAE-48A4-40BA-9B8F-332D37EA66CD}.DebugEditor|x64.ActiveCfg = DebugEditor|x64
{838F1EAE-48A4-40BA-9B8F-332D37EA66CD}.DebugEditor|x64.Build.0 = DebugEditor|x64
{838F1EAE-48A4-40BA-9B8F-332D37EA66CD}.Release|x64.ActiveCfg = Release|x64
{838F1EAE-48A4-40BA-9B8F-332D37EA66CD}.Release|x64.Build.0 = Release|x64
{838F1EAE-48A4-40BA-9B8F-332D37EA66CD}.ReleaseEditor|x64.ActiveCfg = ReleaseEditor|x64
{838F1EAE-48A4-40BA-9B8F-332D37EA66CD}.ReleaseEditor|x64.Build.0 = ReleaseEditor|x64
{D1FB8BBD-6836-431B-A43D-F6EA248B8965}.Debug|x64.ActiveCfg = Debug|x64
{D1FB8BBD-6836-431B-A43D-F6EA248B8965}.Debug|x64.Build.0 = Debug|x64
{D1FB8BBD-6836-431B-A43D-F6EA248B8965}.DebugEditor|x64.ActiveCfg = DebugEditor|x64
{D1FB8BBD-6836-431B-A43D-F6EA248B8965}.Release|x64.ActiveCfg = Release|x64
{D1FB8BBD-6836-431B-A43D-F6EA248B8965}.Release|x64.Build.0 = Release|x64
{D1FB8BBD-6836-431B-A43D-F6EA248B8965}.ReleaseEditor|x64.ActiveCfg = ReleaseEditor|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

110
README.md Normal file
View File

@@ -0,0 +1,110 @@
# FeatureExtractDemoDX12 基础框架)
FeatureExtractDemo 是一个面向 Windows 平台的 C++17 引擎学习工程,采用 **Engine 静态库 + EngineTest 可执行程序** 的结构,用于搭建和演进 DirectX 12 图形程序的基础工程能力。
---
## 1. 项目目标
- 提供清晰的引擎分层结构,便于学习与迭代
- 用最小可运行框架承载窗口、组件、内容与测试系统
- 为后续 DX12 功能扩展提供稳定入口
---
## 2. 目录与工程结构
- `FeatureExtractDemo.sln`Visual Studio 解决方案
- `Engine/`:核心引擎静态库工程
- `EngineTest/`:测试程序与主循环
- `ContentTools/`:内容工具工程(模型/几何处理相关)
- `packages/dxCompiler/`DXC 依赖文件
- `docs/`:架构分析文档
---
## 3. 模块说明
### Engine
- `Common/`:公共类型、基础定义与通用头
- `Components/`Entity / Transform / Script 组件系统
- `Content/`:内容加载与运行时转换
- `Platform/`Win32 窗口与平台层封装
- `Graphics/`:图形接口入口
- `Utilities/`数学、容器、IO 等工具模块
- `Core/`:引擎核心流程代码
### EngineTest
- `Main.cpp`:应用入口与 Win32 消息循环
- `Test.h`:测试基类与基础计时工具
- `Lights.cpp``RenderItem.cpp``ShaderComponents.*`:测试样例逻辑
- `Test*.h``Test*.hlsl`:测试配置与着色器样例
### ContentTools
- 几何生成、模型导入相关工具模块
---
## 4. 构建关系
- `Engine` 为静态库项目(`ConfigurationType = StaticLibrary`
- `EngineTest` 依赖 `Engine` 并链接 `engine.lib`
- 主要配置:`Debug|x64``Release|x64``DebugEditor|x64``ReleaseEditor|x64`
---
## 5. 开发环境
- Windows 10/11 x64
- Visual Studio 2022MSVC v143
- C++17
- Windows SDK 10.x
---
## 6. 快速开始
1. 使用 Visual Studio 2022 打开 `FeatureExtractDemo.sln`
2. 选择 `x64 + Debug` 配置
3. 构建解决方案
4.`EngineTest` 设为启动项目并运行
运行时说明:
- `EngineTest` 的预构建事件会将 `packages/dxCompiler/dxcompiler.dll` 复制到输出目录
- 程序启动时会自动切换工作目录到可执行文件路径
---
## 7. 测试开关
`EngineTest/Test.h` 中可通过宏切换测试入口:
- `TEST_ENTITY_COMPONENTS`
- `TEST_WINDOW_COMPONENTS`
- `TEST_RENDERER`
---
## 8. 代码约定
- 平台相关实现集中在 `Platform/`
- 业务逻辑尽量通过组件与 API 层解耦
- 图形接口通过 `Graphics` 入口统一抽象
- 建议按模块拆分提交,便于回溯与调试
---
## 9. 后续扩展建议
1. 完善 `Graphics` 接口与资源生命周期管理
2. 逐步补齐渲染通道、资源系统与 shader 管线
3. 增加渲染诊断与调试辅助能力
4. 细化测试场景并整理测试入口
---
这个仓库定位为 DX12 学习和工程化演进的基础骨架,适合按功能模块逐步扩展。

View File

@@ -0,0 +1,58 @@
# Entity 组件分析
## 组件职责
`Entity.*` 负责:
- 分配与回收 `entity_id`
- 维护实体到 `transform/script` 句柄的绑定;
- 提供 `is_alive` 存活判定。
## 核心存储
位于 `Entity.cpp` 的静态容器:
- `transforms`:实体索引 -> 变换组件句柄
- `scripts`:实体索引 -> 脚本组件句柄
- `generations`:实体索引 -> 当前代数
- `free_ids`:可复用实体 ID 队列
## 创建流程
入口:`game_entity::create(entity_info info)`
主要步骤:
1. 校验 `info.transform`Transform 为必需组件;
2. 分配 `entity_id`
- 回收路径:从 `free_ids` 取旧 id`new_generation`
- 新建路径:追加新索引并扩展并行数组;
3. 创建 Transform 组件(强制);
4. 若传入脚本工厂,则创建 Script 组件(可选);
5. 返回 `entity` 轻句柄。
## 删除流程
入口:`game_entity::remove(entity_id id)`
步骤:
1. 校验实体存活;
2. 若存在脚本组件,先删脚本并清空句柄槽;
3. 删除 Transform 并清空句柄槽;
4. 把 id 放入 `free_ids`,等待复用。
## 存活判定规则
`is_alive(id)` 依赖两项:
- `generations[index] == id::generation(id)`
- `transforms[index].is_valid()`
这让“旧代数句柄”天然失效,避免悬挂句柄误访问。
## 设计特点
- Entity 层是组件索引空间的“主时钟”;
- Transform 与实体强 1:1 绑定;
- Script 按需挂载,存在即可访问,不存在则为空句柄。

View File

@@ -0,0 +1,18 @@
# Id.h 与 Components 运行机制设计索引
本文档改为总览索引。按你的要求,每个组件拆分到独立页面。
## 总览
- ID 机制分析:`Engine/Common/Id.h`
- Entity 组件分析:`Engine/Components/Entity.cpp/.h`
- Transform 组件分析:`Engine/Components/Transform.cpp/.h`
- Script 组件分析:`Engine/Components/Script.cpp/.h`
## 分页文档
- [Id机制分析.md](./Id机制分析.md)
- [Entity组件分析.md](./Entity组件分析.md)
- [Transform组件分析.md](./Transform组件分析.md)
- [Script组件分析.md](./Script组件分析.md)
- [项目约定规范.md](./项目约定规范.md)

View File

@@ -0,0 +1,46 @@
# Id 机制分析
## 作用定位
`Engine/Common/Id.h` 是整个运行时句柄系统的基础。
核心目标是用一个 `u32` 同时表达:
- 资源在数组中的索引;
- 该索引的生命周期代数generation
## 位布局
- `id_type = u32`
- 高位generation`generation_bits = 8`
- 低位index`index_bit = 24`
对应关键常量与工具:
- `index_mask`:提取低位索引
- `generation_mask`:提取高位代数
- `invalid_id`:无效句柄哨兵值
## 核心函数语义
- `is_valid(id)`:判断是否不是无效值
- `index(id)`:提取索引位
- `generation(id)`:提取代数位
- `new_generation(id)`:索引不变,代数递增
## 运行时价值
它主要解决“删除后旧句柄仍被外部持有”的问题:
1. 对象删除后,槽位可复用;
2. 复用时代数 +1
3. 旧句柄的 generation 与当前 generation 不一致;
4. 存活校验失败,避免误访问新对象。
## 强类型策略
`DEFINE_TYPED_ID(name)`
- Debug生成包装类型增强类型隔离
- Release退化为 `id::id_type`,零额外开销。
这是“开发期安全 + 发布期性能”的组合设计。

View File

@@ -0,0 +1,119 @@
# Script 组件分析
## 组件职责
`Script.*` 负责:
- 脚本类型注册与脚本实例创建;
- `script_id` 生命周期管理;
- 脚本逐帧更新;
- 脚本对 Transform 的延迟写回。
## 核心存储
`Script.cpp` 的关键结构:
- `entity_scripts`:活跃脚本实例池
- `id_mapping``script_id` 索引到实例池下标
- `generations + free_ids`:脚本 ID 代数与复用队列
- `transform_cache`:脚本写变换的缓存数组
- `cache_map`Transform ID 到缓存下标映射
## 注册机制
脚本工厂注册表:
- 类型:`unordered_map<size_t, script_creator>`
- `register_script(tag, func)` 注册工厂
- `get_script_creator(tag)` 查询工厂
通常由 `REGISTER_SCRIPT` 宏在静态初始化阶段完成注册。
## 创建流程
`script::create(init_info, entity)`
1. 校验实体有效与工厂函数有效;
2. 分配 `script_id`(复用或新建);
3. 调用工厂创建脚本实例并压入 `entity_scripts`
4. 写入 `id_mapping`
5. 返回 `script::component` 句柄。
## 删除流程
`script::remove(component c)`
- 校验组件存在;
- 用无序删除移除脚本实例;
- 修正被交换元素的 `id_mapping`
- 将被删 id 的映射标记为无效。
## 更新与缓存提交流程
`script::update(dt)` 包含两段:
1. 遍历 `entity_scripts` 执行每个脚本的 `update(dt)`
2.`transform_cache` 非空,统一调用 `transform::update(...)` 提交后清空。
这使脚本阶段不直接写 Transform 主数组,减少帧内重复写。
## 脚本写 Transform 的方式
`entity_script::set_rotation/position/...` 的行为是:
- 先通过 `get_cache_ptr(entity)` 找到该实体缓存槽;
- 设置对应 `flags`
- 写入新值到 cache
- 等待帧末统一提交。
## 设计特点
- 句柄安全:`script_id` 采用 generation 机制;
- 批处理友好Transform 写入集中提交;
- 解耦:脚本逻辑与 Transform 存储细节隔离。
## 三条主线时序图(注册、生命周期、缓存提交)
```mermaid
sequenceDiagram
autonumber
participant Macro as REGISTER_SCRIPT
participant Detail as script::detail
participant Registry as script_registry
participant Entity as game_entity::entity
participant ScriptSys as script::system
participant Obj as entity_script
participant Cache as transform_cache
participant Trans as transform::system
rect rgb(236, 248, 255)
Note over Macro,Registry: 主线一:脚本类型注册
Macro->>Detail: register_script(tag, creator)
Detail->>Registry: insert(tag, creator)
Registry-->>Detail: creator 已登记
end
rect rgb(239, 252, 240)
Note over Entity,ScriptSys: 主线二:脚本实例生命周期
Entity->>ScriptSys: create(init_info, entity)
ScriptSys->>ScriptSys: 分配或复用 script_id
ScriptSys->>Registry: get_script_creator(tag)
Registry-->>ScriptSys: 返回 creator
ScriptSys->>Obj: creator(entity)
Obj-->>ScriptSys: 生成脚本实例
ScriptSys->>ScriptSys: 写入 entity_scripts 与 id_mapping
ScriptSys-->>Entity: 返回 script::component
Entity->>ScriptSys: remove(component)
ScriptSys->>ScriptSys: erase_unordered + 修正 id_mapping
end
rect rgb(255, 246, 236)
Note over Obj,Trans: 主线三:脚本写变换缓存并帧末提交
ScriptSys->>Obj: update(dt)
Obj->>Cache: set_position/rotation/scale...
Cache-->>Obj: flags + value 已缓存
ScriptSys->>Trans: transform::update(cache, count)
Trans-->>ScriptSys: 写入完成
ScriptSys->>Cache: clear()
end
```

View File

@@ -0,0 +1,48 @@
# Transform 组件分析
## 组件职责
`Transform.*` 负责实体空间变换数据:
- 位置、旋转、朝向、缩放存储;
- 世界矩阵与逆矩阵计算;
- 脏标记记录与批量更新入口。
## 数据布局
`Transform.cpp` 采用并行数组SoA
- `positions / rotations / orientations / scales`
- `to_world / inv_world`
- `has_transform`:矩阵缓存有效位
- `changes_from_previous_frame`:逐帧脏标记
## 创建与绑定
`transform::create(init_info, entity)` 使用 `entity_id` 的索引位定位槽位:
- 若槽位已存在,覆写其数据并标记脏;
- 若槽位不存在,扩容所有并行数组;
- 返回 `transform::component{ transform_id{ entity.get_id() } }`
这说明 Transform 不维护独立 free list而是复用实体索引空间。
## 读写策略
- 写接口(`set_rotation/position/scale`)只改数据与脏标记;
- `get_transform_matrices` 在读取时按需计算矩阵;
- 通过 `has_transform` 避免重复矩阵重算。
## 批量更新机制
`transform::update(cache, count)` 接收 `component_cache` 数组:
- 每条 cache 带 `flags` 指示写哪些字段;
- 逐条落地到内部并行数组;
- 适配脚本系统“先缓存,后统一提交”的帧内流程。
## 设计特点
- 读取快:索引直达;
- 可批处理cache + flags
- 低耦合:外部通过句柄和 cache 交互,不直接暴露内部数组。

View File

@@ -0,0 +1,132 @@
# 项目约定规范(接口层 / 实现层)
what every programmer should know about memory
本文档用于统一本项目在 `EngineAPI``Components``Platform``Utilities` 等目录下的设计与编码约定,重点规范“接口层与实现层分离”。
## 1. 分层职责约定
### 1.1 EngineAPI 层(对外契约层)
- 只暴露稳定接口:强类型 ID、句柄类、对外可调用的 API。
- 不暴露内部容器与存储细节,不暴露运行时池结构。
- 允许声明行为,不实现复杂运行逻辑。
- 头文件应可被上层模块直接 include尽量减少内部依赖。
示例:
- `Engine/EngineAPI/ScriptComponent.h`:仅定义 `script_id``script::component` 句柄。
### 1.2 Components 层(运行时实现层)
- 实现组件生命周期:`create/remove/update`
- 管理内部存储:数组、映射、空闲队列、代数校验。
- 对外只通过 API 层句柄交互,不向外泄露内部布局。
- 允许高性能实现细节(如 `erase_unordered`、缓存表、延迟提交)。
示例:
- `Engine/Components/Script.h/.cpp`:实现脚本创建、删除、逐帧更新和注册表逻辑。
### 1.3 Platform 层(平台适配层)
- 只处理操作系统相关对象与流程(如 Win32 `HWND`、消息循环、窗口样式)。
- 上层通过抽象类型访问,不直接依赖平台原生类型。
- 平台特有分支必须受平台宏保护(如 `_WIN64`)。
### 1.4 Utilities 层(通用基础层)
- 提供与业务无关的数学、容器、IO、工具函数。
- 不耦合具体业务组件,不反向依赖高层模块。
## 2. 文件组织约定
### 2.1 命名与位置
- 对外契约放 `EngineAPI`,运行时实现放 `Components`
- 头文件与实现文件成对组织:`Xxx.h` / `Xxx.cpp`
- 公共依赖入口统一放在模块公共头(如 `ComponentsCommon.h`)。
### 2.2 include 方向
- 允许 `Components -> EngineAPI` 依赖。
- 禁止 `EngineAPI -> Components` 反向依赖实现细节。
- `Utilities` 不应依赖 `Components``Platform`
### 2.3 前向声明优先
- 能前向声明时不直接 include 重头文件。
- 对外头文件避免引入不必要实现依赖,控制编译扇出。
## 3. 类型与句柄约定
### 3.1 强类型 ID
- 所有核心对象 ID 使用强类型封装(`DEFINE_TYPED_ID`)。
- 禁止在模块边界裸传 `u32` 作为对象标识。
- 必须通过 `is_valid` / `generation` 做有效性校验。
### 3.2 句柄对象
- 对外使用轻量句柄(如 `script::component``game_entity::entity`)。
- 句柄仅保存 ID不保存大对象数据。
- 句柄方法可转发到实现层查询真实数据。
## 4. 生命周期与存储约定
### 4.1 创建与删除
- `create` 负责分配 ID、构造实例、建立映射。
- `remove` 必须清理映射、回收槽位、更新空闲队列。
- 必须保证删除后旧句柄不可通过代数校验。
### 4.2 映射一致性
- 使用 `id_mapping` 时必须保证:
- `id -> 索引` 映射存在且越界受保护;
- 交换删除后映射及时回填;
- 无效 ID 显式写回 `invalid_id`
### 4.3 批处理更新
- 高频写入优先使用缓存并批量提交(如脚本修改 Transform 后统一提交)。
- 帧内缓存必须在提交后清空,避免跨帧污染。
## 5. 脚本系统专项约定
### 5.1 注册机制
- 脚本类型通过统一宏注册(`REGISTER_SCRIPT(TYPE)`)。
- 注册信息至少包含:类型名哈希、工厂函数指针。
- 编辑器模式下可额外导出脚本名列表。
### 5.2 工厂机制
- 统一使用 `script_creator` 创建脚本实例。
- 禁止外部直接 new 并绕过组件创建流程。
- 工厂函数签名保持稳定,避免跨模块 ABI 漂移。
## 6. 注释与文档约定
### 6.1 文件头注释
- 使用 `@file` + `@brief` + `@details`
- `@details` 需说明职责边界、数据流、关键约束。
### 6.2 函数注释
- 对核心函数标注参数、返回值、副作用。
- 对平台参数表、结构体字段允许保留逐行行尾注释。
- 禁止乱码注释;统一 UTF-8 编码保存。
## 7. 可维护性与扩展性约定
- 先保证接口稳定,再演进实现细节。
- 扩展新组件时,优先复用现有 ID/句柄/生命周期模式。
- 变更跨层关系时必须先更新本规范文档与对应模块分析文档。
## 8. 变更检查清单(提交前)
- 是否保持 `EngineAPI``Components` 的边界不反转。
- 是否新增了不必要 include 或扩大了依赖扇出。
- 是否破坏了 ID 代数校验与映射一致性。
- 是否补齐了注释并确保无乱码。
- 是否与现有目录结构与命名风格一致。

Binary file not shown.

Binary file not shown.