commit 60f73b525def48e528df0cae3f9b8a8129ee3ad1 Author: SpecialX <47072643+wangxiner55@users.noreply.github.com> Date: Thu Mar 19 18:27:49 2026 +0800 feat: initial DX12 foundation framework diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2702aac --- /dev/null +++ b/.gitignore @@ -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/ diff --git a/ContentTools/ContentTools/ContentTools.vcxproj b/ContentTools/ContentTools/ContentTools.vcxproj new file mode 100644 index 0000000..b42393a --- /dev/null +++ b/ContentTools/ContentTools/ContentTools.vcxproj @@ -0,0 +1,102 @@ + + + + + DebugEditor + x64 + + + ReleaseEditor + x64 + + + + + + + + + + + + + + + 17.0 + Win32Proj + {d98da990-9d7c-4064-ab98-6f6c405c10f0} + ContentTools + 10.0 + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + Level4 + true + _DEBUG;CONTENTTOOLS_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + + + stdcpp17 + $(SolutionDir)Engine;$(SolutionDir)Engine\Common;C:\Program Files\Autodesk\FBX\FBX SDK\2020.3.2\include + /Ignore:4099 %(AdditionalOptions) + + + Windows + true + false + + + + + Level4 + true + true + true + NDEBUG;CONTENTTOOLS_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + NotUsing + + + stdcpp17 + $(SolutionDir)Engine;$(SolutionDir)Engine\Common; + /Ignore:4099 %(AdditionalOptions) + + + Windows + true + true + true + false + + + + + + \ No newline at end of file diff --git a/ContentTools/ContentTools/ContentTools.vcxproj.filters b/ContentTools/ContentTools/ContentTools.vcxproj.filters new file mode 100644 index 0000000..bc34f03 --- /dev/null +++ b/ContentTools/ContentTools/ContentTools.vcxproj.filters @@ -0,0 +1,42 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + 头文件 + + + 头文件 + + + 头文件 + + + 头文件 + + + + + 源文件 + + + 源文件 + + + 源文件 + + + \ No newline at end of file diff --git a/ContentTools/ContentTools/FbxImporter.cpp b/ContentTools/ContentTools/FbxImporter.cpp new file mode 100644 index 0000000..77ae6bb --- /dev/null +++ b/ContentTools/ContentTools/FbxImporter.cpp @@ -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& 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& 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* 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 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* 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 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); +} + +} \ No newline at end of file diff --git a/ContentTools/ContentTools/FbxImporter.h b/ContentTools/ContentTools/FbxImporter.h new file mode 100644 index 0000000..2f9527d --- /dev/null +++ b/ContentTools/ContentTools/FbxImporter.h @@ -0,0 +1,54 @@ +#pragma once +#include "ToolsCommon.h" +#include + + +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& meshes, u32 lod_id, f32 lod_threshold); + void get_mesh(FbxNodeAttribute* attribute, utl::vector& 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 }; + +}; +} diff --git a/ContentTools/ContentTools/Geometry.cpp b/ContentTools/ContentTools/Geometry.cpp new file mode 100644 index 0000000..50c8b26 --- /dev/null +++ b/ContentTools/ContentTools/Geometry.cpp @@ -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_anglemeshķߡ + * ƽǶȽӽ 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Ӧ4pointοhoudini + utl::vector> 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ѾnormalizeijΪ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 old_vertices; + old_vertices.swap(m.vertices); + utl::vector 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> 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 t_signs(num_vertices); + utl::vector normals(num_vertices); + utl::vector tangents(num_vertices); + utl::vector 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 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 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 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()); +} + +} \ No newline at end of file diff --git a/ContentTools/ContentTools/Geometry.h b/ContentTools/ContentTools/Geometry.h new file mode 100644 index 0000000..8424612 --- /dev/null +++ b/ContentTools/ContentTools/Geometry.h @@ -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 positions; + utl::vector normals; + utl::vector colors; + utl::vector tangents; + utl::vector> uv_sets; + utl::vector material_indices; + utl::vector material_used; + + utl::vector raw_indices; + + + // Intermediate data + utl::vector vertices; + utl::vector indices; + + // Output data + std::string name; + elements::element_type::type elements_type; + utl::vector position_buffer; + utl::vector element_buffer; + f32 lod_threshold{ -1.f }; + u32 lod_id{ u32_invalid_id }; +}; + +struct lod_group +{ + std::string name; + utl::vector meshes; +}; + +struct scene +{ + std::string name; + utl::vector 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); +} \ No newline at end of file diff --git a/ContentTools/ContentTools/MeshPrimitives.cpp b/ContentTools/ContentTools/MeshPrimitives.cpp new file mode 100644 index 0000000..276084b --- /dev/null +++ b/ContentTools/ContentTools/MeshPrimitives.cpp @@ -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 ƽijʼλƫƣĬΪ {-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) }; + + // ˮƽʹֱIJ + const f32 horizontal_step{ 1.f / horizontal_count }; + const f32 vertical_step{ 1.f / vertical_count }; + + // UV IJ + 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 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 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); +} + +} \ No newline at end of file diff --git a/ContentTools/ContentTools/MeshPrimitives.h b/ContentTools/ContentTools/MeshPrimitives.h new file mode 100644 index 0000000..67709e5 --- /dev/null +++ b/ContentTools/ContentTools/MeshPrimitives.h @@ -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 }; +}; + +} \ No newline at end of file diff --git a/ContentTools/ContentTools/ToolsCommon.h b/ContentTools/ContentTools/ToolsCommon.h new file mode 100644 index 0000000..de19940 --- /dev/null +++ b/ContentTools/ContentTools/ToolsCommon.h @@ -0,0 +1,11 @@ +#pragma once +#include "CommonHeader.h" +#include + +#ifndef EDITOR_INTERFACE +#define EDITOR_INTERFACE extern "C" __declspec(dllexport) +#endif + + + + diff --git a/ContentTools/Geometry.h b/ContentTools/Geometry.h new file mode 100644 index 0000000..8424612 --- /dev/null +++ b/ContentTools/Geometry.h @@ -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 positions; + utl::vector normals; + utl::vector colors; + utl::vector tangents; + utl::vector> uv_sets; + utl::vector material_indices; + utl::vector material_used; + + utl::vector raw_indices; + + + // Intermediate data + utl::vector vertices; + utl::vector indices; + + // Output data + std::string name; + elements::element_type::type elements_type; + utl::vector position_buffer; + utl::vector element_buffer; + f32 lod_threshold{ -1.f }; + u32 lod_id{ u32_invalid_id }; +}; + +struct lod_group +{ + std::string name; + utl::vector meshes; +}; + +struct scene +{ + std::string name; + utl::vector 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); +} \ No newline at end of file diff --git a/ContentTools/ToolsCommon.h b/ContentTools/ToolsCommon.h new file mode 100644 index 0000000..de19940 --- /dev/null +++ b/ContentTools/ToolsCommon.h @@ -0,0 +1,11 @@ +#pragma once +#include "CommonHeader.h" +#include + +#ifndef EDITOR_INTERFACE +#define EDITOR_INTERFACE extern "C" __declspec(dllexport) +#endif + + + + diff --git a/Engine/Common/CommonHeader.h b/Engine/Common/CommonHeader.h new file mode 100644 index 0000000..e76b044 --- /dev/null +++ b/Engine/Common/CommonHeader.h @@ -0,0 +1,55 @@ +#pragma once + +#ifdef _WIN64 +#pragma warning(disable: 4530) +#endif // _WIN64 + + +#include +#include +#include +#include +#include +#include +#include +#include + +//#define USESTDFUNC +#if defined USESTDFUNC +#include +#endif + + +#if defined(_WIN64) +#include +#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" + diff --git a/Engine/Common/Id.h b/Engine/Common/Id.h new file mode 100644 index 0000000..d743d8e --- /dev/null +++ b/Engine/Common/Id.h @@ -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, 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 + + +} diff --git a/Engine/Common/XEnginType.h b/Engine/Common/XEnginType.h new file mode 100644 index 0000000..6e768a2 --- /dev/null +++ b/Engine/Common/XEnginType.h @@ -0,0 +1,23 @@ +#pragma once +#include + +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; \ No newline at end of file diff --git a/Engine/Components/ComponentsCommon.h b/Engine/Components/ComponentsCommon.h new file mode 100644 index 0000000..b5257a1 --- /dev/null +++ b/Engine/Components/ComponentsCommon.h @@ -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 { + +} diff --git a/Engine/Components/Entity.cpp b/Engine/Components/Entity.cpp new file mode 100644 index 0000000..dac945b --- /dev/null +++ b/Engine/Components/Entity.cpp @@ -0,0 +1,108 @@ +#include "Entity.h" +#include "Script.h" +#include "Transform.h" + +namespace XEngine::game_entity { + +namespace { + +utl::vector transforms; +utl::vector scripts; + + +utl::vector generations; +utl::deque 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]; +} + +} \ No newline at end of file diff --git a/Engine/Components/Entity.h b/Engine/Components/Entity.h new file mode 100644 index 0000000..74fac4a --- /dev/null +++ b/Engine/Components/Entity.h @@ -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); +} +} diff --git a/Engine/Components/Script.cpp b/Engine/Components/Script.cpp new file mode 100644 index 0000000..bab9a93 --- /dev/null +++ b/Engine/Components/Script.cpp @@ -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 entity_scripts; +/** + * @brief script_id 索引到实例数组下标的映射。 + */ +utl::vector id_mapping; + +/** + * @brief script_id 代数数组。 + */ +utl::vector generations; +/** + * @brief 可复用 script_id 队列。 + */ +utl::deque free_ids; + +/** + * @brief 脚本对 Transform 的延迟修改缓存。 + */ +utl::vector transform_cache; + +#if USE_TRANSFORM_CACHE_MAP +std::unordered_map cache_map; +#endif + + +using script_registry = std::unordered_map; + + +/** + * @brief 获取全局脚本工厂注册表。 + * @return 注册表引用。 + */ +script_registry& +registery() +{ + static script_registry reg; + return reg; +} +#ifdef USE_WITH_EDITOR +/** + * @brief 获取编辑器脚本名集合。 + * @return 脚本名数组引用。 + */ +utl::vector& +script_names() +{ + static utl::vector 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 + +/** + * @brief 导出脚本名列表给编辑器端。 + * @details + * 返回值为 LPSAFEARRAY(SAFEARRAY*),元素类型为 BSTR。 + * 内部流程: + * - 从 script_names() 读取脚本名数量; + * - 为空时返回 nullptr; + * - 使用 CComSafeArray 分配数组; + * - 逐项把 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 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 diff --git a/Engine/Components/Script.h b/Engine/Components/Script.h new file mode 100644 index 0000000..8e8c134 --- /dev/null +++ b/Engine/Components/Script.h @@ -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); +} diff --git a/Engine/Components/Transform.cpp b/Engine/Components/Transform.cpp new file mode 100644 index 0000000..6b86ec7 --- /dev/null +++ b/Engine/Components/Transform.cpp @@ -0,0 +1,213 @@ +#include "Transform.h" +#include "Entity.h" + +namespace XEngine::transform { + +namespace { + +utl::vector to_world; +utl::vector inv_world; +utl::vector positions; +utl::vector orientations; +utl::vector rotations; +utl::vector scales; +utl::vector has_transform; +utl::vector 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)]; + +} + +} \ No newline at end of file diff --git a/Engine/Components/Transform.h b/Engine/Components/Transform.h new file mode 100644 index 0000000..44508a8 --- /dev/null +++ b/Engine/Components/Transform.h @@ -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); + +} diff --git a/Engine/Content/ContentLoader.cpp b/Engine/Content/ContentLoader.cpp new file mode 100644 index 0000000..03d3f5d --- /dev/null +++ b/Engine/Content/ContentLoader.cpp @@ -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 +#include +#include + +#if !defined(SHIPPING) && defined(_WIN64) + +namespace XEngine::content { +namespace { + +/** + * @brief 关卡文件中的组件类型标签。 + */ +enum component_type +{ + transform, + script, + + count +}; + +/** + * @brief 已创建的实体缓存,用于卸载阶段统一回收。 + */ +utl::vector 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&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(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 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&shaders, u64& size) +//{ +// auto path = graphics::get_engine_shaders_path(); +// return read_file(path, shaders, size); +//} + +} +#endif //!defined(SHIPPING) diff --git a/Engine/Content/ContentLoader.h b/Engine/Content/ContentLoader.h new file mode 100644 index 0000000..48cd16f --- /dev/null +++ b/Engine/Content/ContentLoader.h @@ -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&shader, u64 size); +} + +#endif //!defined(SHIPPING) diff --git a/Engine/Content/ContentToEngine.cpp b/Engine/Content/ContentToEngine.cpp new file mode 100644 index 0000000..a2c0f17 --- /dev/null +++ b/Engine/Content/ContentToEngine.cpp @@ -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> 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 geometry_hierarchies; +/** + * @brief 几何资源互斥锁。 + */ +std::mutex geometry_mutex; + +/** + * @brief 着色器组资源池。 + */ +utl::free_list 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() }; + 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(); + // skip submesh data and goto next LOD + blob.skip(blob.reader()); + } + + 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() }; +// 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(); +// const u32 id_count{ blob.reader() }; +// 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() }; + assert(lod_count); + if (lod_count > 1) return false; + + + blob.skip(sizeof(f32)); // skip threhold + const u32 submesh_count{ blob.reader() }; + 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 shader{ std::make_unique(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& 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]); + } + } +} +} diff --git a/Engine/Content/ContentToEngine.h b/Engine/Content/ContentToEngine.h new file mode 100644 index 0000000..67e0bae --- /dev/null +++ b/Engine/Content/ContentToEngine.h @@ -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& offsets); +} diff --git a/Engine/Core/EngineWin32.cpp b/Engine/Core/EngineWin32.cpp new file mode 100644 index 0000000..cc79884 --- /dev/null +++ b/Engine/Core/EngineWin32.cpp @@ -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 + +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) diff --git a/Engine/Core/MainWin32.cpp b/Engine/Core/MainWin32.cpp new file mode 100644 index 0000000..3cff462 --- /dev/null +++ b/Engine/Core/MainWin32.cpp @@ -0,0 +1,77 @@ +/** + * @file MainWin32.cpp + * @brief Win32 可执行程序入口与消息泵实现。 + * @details + * 负责设置工作目录、初始化调试内存检测,并驱动主循环: + * - 轮询并分发 Windows 消息; + * - 在消息空闲阶段调用 engine_update; + * - 退出时由 engine_shutdown 完成资源收尾。 + */ +#ifdef _WIN64 +#include "CommonHeader.h" +#include +#ifndef WIN32_MEAN_AND_LEAN +#define WIN32_MEAN_AND_LEAN +#endif + +#include +#include + +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 diff --git a/Engine/Engine.vcxproj b/Engine/Engine.vcxproj new file mode 100644 index 0000000..fb3374d --- /dev/null +++ b/Engine/Engine.vcxproj @@ -0,0 +1,227 @@ + + + + + DebugEditor + x64 + + + Debug + x64 + + + ReleaseEditor + x64 + + + Release + x64 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 17.0 + Win32Proj + {838f1eae-48a4-40ba-9b8f-332d37ea66cd} + Engine + 10.0 + + + + StaticLibrary + true + v143 + Unicode + + + StaticLibrary + true + v143 + Unicode + + + StaticLibrary + false + v143 + true + Unicode + + + StaticLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + Level4 + + + _DEBUG;_LIB;%(PreprocessorDefinitions) + true + NotUsing + + + true + false + Fast + false + stdcpp17 + FastCall + false + $(ProjectDir);$(ProjectDir)/Common + + + + + true + + + + + Level4 + + + _DEBUG;_LIB;USE_WITH_EDITOR;%(PreprocessorDefinitions) + true + NotUsing + + + true + false + Fast + false + stdcpp17 + FastCall + false + $(ProjectDir);$(ProjectDir)/Common + + + + + true + + + + + Level4 + true + true + + + NDEBUG;_LIB;%(PreprocessorDefinitions) + true + NotUsing + + + true + false + false + false + true + Fast + false + stdcpp17 + FastCall + $(ProjectDir);$(ProjectDir)/Common + + + + + true + true + true + + + + + Level4 + true + true + + + NDEBUG;_LIB;USE_WITH_EDITOR;%(PreprocessorDefinitions) + true + NotUsing + + + true + false + false + false + true + Fast + false + stdcpp17 + FastCall + $(ProjectDir);$(ProjectDir)/Common + + + + + true + true + true + + + + + + diff --git a/Engine/Engine.vcxproj.filters b/Engine/Engine.vcxproj.filters new file mode 100644 index 0000000..b5b1e52 --- /dev/null +++ b/Engine/Engine.vcxproj.filters @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Engine/EngineAPI/Camera.h b/Engine/EngineAPI/Camera.h new file mode 100644 index 0000000..3e568bf --- /dev/null +++ b/Engine/EngineAPI/Camera.h @@ -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 }; +}; + + +} diff --git a/Engine/EngineAPI/GameEntity.h b/Engine/EngineAPI/GameEntity.h new file mode 100644 index 0000000..72d68c2 --- /dev/null +++ b/Engine/EngineAPI/GameEntity.h @@ -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; +#ifdef USESTDFUNC +/** + * @brief 基于 std::function 的脚本工厂签名。 + */ +using script_creator = std::function; +#else +/** + * @brief 基于函数指针的脚本工厂签名。 + */ +using script_creator = script_ptr(*)(game_entity::entity entity); +#endif // USESTDFUNC +/** + * @brief 脚本名哈希器类型。 + */ +using string_hash = std::hash; + +/** + * @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 +script_ptr create_script(game_entity::entity entity) +{ + assert(entity.is_valid()); + return std::make_unique(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) }; \ + 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) }; \ + } + +#endif // USE_WITH_EDITOR + + +}//namespace detail +}//namespace script + +} diff --git a/Engine/EngineAPI/Light.h b/Engine/EngineAPI/Light.h new file mode 100644 index 0000000..67ec2e0 --- /dev/null +++ b/Engine/EngineAPI/Light.h @@ -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 }; +}; + + +} diff --git a/Engine/EngineAPI/ScriptComponent.h b/Engine/EngineAPI/ScriptComponent.h new file mode 100644 index 0000000..ca37331 --- /dev/null +++ b/Engine/EngineAPI/ScriptComponent.h @@ -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; + +}; +} diff --git a/Engine/EngineAPI/TransformComponent.h b/Engine/EngineAPI/TransformComponent.h new file mode 100644 index 0000000..ac89399 --- /dev/null +++ b/Engine/EngineAPI/TransformComponent.h @@ -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; + +}; +} diff --git a/Engine/Platform/IncludeWindowCpp.h b/Engine/Platform/IncludeWindowCpp.h new file mode 100644 index 0000000..5ea336b --- /dev/null +++ b/Engine/Platform/IncludeWindowCpp.h @@ -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 diff --git a/Engine/Platform/Platform.h b/Engine/Platform/Platform.h new file mode 100644 index 0000000..afc1fe1 --- /dev/null +++ b/Engine/Platform/Platform.h @@ -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); + + + +} diff --git a/Engine/Platform/PlatformTypes.h b/Engine/Platform/PlatformTypes.h new file mode 100644 index 0000000..0875099 --- /dev/null +++ b/Engine/Platform/PlatformTypes.h @@ -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 + +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 diff --git a/Engine/Platform/PlatformWin32.cpp b/Engine/Platform/PlatformWin32.cpp new file mode 100644 index 0000000..701cb73 --- /dev/null +++ b/Engine/Platform/PlatformWin32.cpp @@ -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 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); +} + +/// + + +/** + * @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; +} + +/// + +/** + * @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 diff --git a/Engine/Platform/Window.cpp b/Engine/Platform/Window.cpp new file mode 100644 index 0000000..1f9f1ae --- /dev/null +++ b/Engine/Platform/Window.cpp @@ -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 diff --git a/Engine/Platform/Window.h b/Engine/Platform/Window.h new file mode 100644 index 0000000..cf3ad0c --- /dev/null +++ b/Engine/Platform/Window.h @@ -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 }; + +}; +} diff --git a/Engine/Utilities/FreeList.h b/Engine/Utilities/FreeList.h new file mode 100644 index 0000000..0a934c6 --- /dev/null +++ b/Engine/Utilities/FreeList.h @@ -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 +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 + 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(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(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 _array; +#else + utl::vector _array; +#endif + u32 _next_free_index{ u32_invalid_id }; + u32 _size{ 0 }; +}; + +} diff --git a/Engine/Utilities/IOStream.h b/Engine/Utilities/IOStream.h new file mode 100644 index 0000000..9c07923 --- /dev/null +++ b/Engine/Utilities/IOStream.h @@ -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 + [[nodiscard]] T reader() + { + static_assert(std::is_arithmetic_v, "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 + void write(T value) + { + static_assert(std::is_arithmetic_v, "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; +}; +} diff --git a/Engine/Utilities/Math.h b/Engine/Utilities/Math.h new file mode 100644 index 0000000..cdd69b6 --- /dev/null +++ b/Engine/Utilities/Math.h @@ -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 +[[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 +[[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 +[[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(distance); +} + + + +/** + * @brief 将量化整数还原为 [0,1] 浮点值。 + * @tparam bits 量化位宽。 + * @param i 量化输入值。 + * @return 反量化结果。 + */ +template +[[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 +[[nodiscard]] constexpr f32 +unpack_float(f32 i, f32 min, f32 max) +{ + assert(min < max); + return unpack_unit_float(i) * (max - min) + min; +} + +/** + * @brief 按固定对齐值向上对齐。 + * @tparam alignment 对齐粒度(2 的幂)。 + * @param size 原始大小。 + * @return 向上对齐后的大小。 + */ +template +[[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 +[[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(size) }; + while (at < end) + { + crc = _mm_crc32_u64(crc, *((const u64*)at)); + at += sizeof(u64); + } + + return crc; +} + +} diff --git a/Engine/Utilities/MathTypes.h b/Engine/Utilities/MathTypes.h new file mode 100644 index 0000000..3853d6e --- /dev/null +++ b/Engine/Utilities/MathTypes.h @@ -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) + + +} diff --git a/Engine/Utilities/Utilities.h b/Engine/Utilities/Utilities.h new file mode 100644 index 0000000..16ab728 --- /dev/null +++ b/Engine/Utilities/Utilities.h @@ -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 +#include +namespace XEngine::utl { +template +using vector = std::vector; + + +/** + * @brief 通过交换尾元素方式无序删除。 + * @tparam T 容器类型。 + * @param v 目标容器。 + * @param index 删除下标。 + */ +template +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 +void erase_unordered(T& v, size_t index) +{ + v.erase_unordered(index); +} + +} + +#endif + + +#if USE_STL_DRQUE +#include +namespace XEngine::utl { +template +using deque = std::deque; +} +#endif + + + + +namespace XEngine::utl { +} + +#include "FreeList.h" diff --git a/Engine/Utilities/Vector.h b/Engine/Utilities/Vector.h new file mode 100644 index 0000000..0a2598d --- /dev/null +++ b/Engine/Utilities/Vector.h @@ -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 +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 + 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(p)...) }; + ++_size; + return *item; + } + + /** + * @brief 调整元素数量,新增元素默认构造。 + * @param new_size 新大小。 + */ + constexpr void resize(u64 new_size) + { + static_assert(std::is_default_constructible::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::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(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 }; +}; + + +} + diff --git a/EngineTest/EngineTest.vcxproj b/EngineTest/EngineTest.vcxproj new file mode 100644 index 0000000..bf5a935 --- /dev/null +++ b/EngineTest/EngineTest.vcxproj @@ -0,0 +1,194 @@ + + + + + DebugEditor + x64 + + + ReleaseEditor + x64 + + + Debug + x64 + + + Release + x64 + + + + 17.0 + Win32Proj + {d1fb8bbd-6836-431b-a43d-f6ea248b8965} + EngineTest + 10.0 + + + + Application + true + v143 + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + false + + + false + + + false + + + false + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + $(SolutionDir)Engine\Common;$(SolutionDir)Engine + stdcpp17 + + + Windows + true + $(OutDir) + + + xcopy /Y /D "$(SolutionDir)packages\dxCompiler\dxcompiler.dll" "$(OutDir)" + + + + + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + $(SolutionDir)Engine\Common;$(SolutionDir)Engine + stdcpp17 + + + Windows + true + $(OutDir) + + + xcopy /Y /D "$(SolutionDir)packages\dxCompiler\dxcompiler.dll" "$(OutDir)" + + + + + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + $(SolutionDir)Engine\Common;$(SolutionDir)Engine + stdcpp17 + + + Windows + true + true + true + $(OutDir) + + + xcopy /Y /D "$(SolutionDir)packages\dxCompiler\dxcompiler.dll" "$(OutDir)" + + + + + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + $(SolutionDir)Engine\Common;$(SolutionDir)Engine + stdcpp17 + + + Windows + true + true + true + $(OutDir) + + + xcopy /Y /D "$(SolutionDir)packages\dxCompiler\dxcompiler.dll" "$(OutDir)" + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/EngineTest/EngineTest.vcxproj.filters b/EngineTest/EngineTest.vcxproj.filters new file mode 100644 index 0000000..848a439 --- /dev/null +++ b/EngineTest/EngineTest.vcxproj.filters @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/EngineTest/Lights.cpp b/EngineTest/Lights.cpp new file mode 100644 index 0000000..c9cc6b4 --- /dev/null +++ b/EngineTest/Lights.cpp @@ -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 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 + + diff --git a/EngineTest/Main.cpp b/EngineTest/Main.cpp new file mode 100644 index 0000000..cf84d47 --- /dev/null +++ b/EngineTest/Main.cpp @@ -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 +#include + +// 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 diff --git a/EngineTest/RenderItem.cpp b/EngineTest/RenderItem.cpp new file mode 100644 index 0000000..211f7dc --- /dev/null +++ b/EngineTest/RenderItem.cpp @@ -0,0 +1,141 @@ +/** + * @file RenderItem.cpp + * @brief 渲染项创建与销毁测试逻辑。 + */ +#include "CommonHeader.h" +#include +#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&, 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 render_item_entity_map; + +void +load_model() +{ + std::unique_ptr 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 keys; + keys.emplace_back(tools::elements::element_type::static_normal); + keys.emplace_back(tools::elements::element_type::static_normal_texture); + + utl::vector extra_args{}; + utl::vector> vertex_shaders; + utl::vector 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 diff --git a/EngineTest/ShaderComponents.cpp b/EngineTest/ShaderComponents.cpp new file mode 100644 index 0000000..119e9d9 --- /dev/null +++ b/EngineTest/ShaderComponents.cpp @@ -0,0 +1,326 @@ +/** + * @file ShaderComponents.cpp + * @brief 测试工程着色器编译与缓存实现。 + */ +#include +#include +#include +#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 + + +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 byte_code; + ComPtr 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& extra_args) + { + assert(_compiler && _utils && _include_handler); + HRESULT hr{ S_OK }; + + ComPtr 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 compiler_args) + { + DxcBuffer buffer{}; + buffer.Encoding = DXC_CP_ACP; + buffer.Ptr = source_blob->GetBufferPointer(); + buffer.Size = source_blob->GetBufferSize(); + + utl::vector args; + for (const auto& arg : compiler_args) + { + args.emplace_back(arg.c_str()); + } + + HRESULT hr{ S_OK }; + ComPtr results{ nullptr }; + DXCall(hr = _compiler->Compile(&buffer, args.data(), args.size(), _include_handler.Get(), IID_PPV_ARGS(&results))); + if (FAILED(hr)) return {}; + + ComPtr 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 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 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 disasm_results{ nullptr }; + DXCall(hr = _compiler->Disassemble(&buffer, IID_PPV_ARGS(&disasm_results))); + + ComPtr 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 + get_args(const shader_file_info& info, utl::vector& extra_args) + { + + utl::vector 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 _compiler{ nullptr }; + ComPtr _utils{ nullptr }; + ComPtr _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& 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 +compile_shader(shader_file_info info, const char* file_path, utl::vector& 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 buffer{ std::make_unique(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 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 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); +} diff --git a/EngineTest/ShaderComponents.h b/EngineTest/ShaderComponents.h new file mode 100644 index 0000000..6199af7 --- /dev/null +++ b/EngineTest/ShaderComponents.h @@ -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 compile_shader(shader_file_info info, const char* file_path, XEngine::utl::vector& extra_args); +bool compile_shaders(); diff --git a/EngineTest/Test.h b/EngineTest/Test.h new file mode 100644 index 0000000..e33bb0e --- /dev/null +++ b/EngineTest/Test.h @@ -0,0 +1,72 @@ +/** + * @file Test.h + * @brief 测试基类与计时工具定义。 + */ +#pragma once +#include +#include +#include +#include +#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 +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(dt).count() - _msg_avg) / (float)_counter; + ++_counter; + _dt_avg = _msg_avg; + + if (std::chrono::duration_cast(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 diff --git a/EngineTest/TestEntityComponent.h b/EngineTest/TestEntityComponent.h new file mode 100644 index 0000000..093e033 --- /dev/null +++ b/EngineTest/TestEntityComponent.h @@ -0,0 +1,98 @@ +/** + * @file TestEntityComponent.h + * @brief 实体与组件系统测试用例定义。 + */ +#pragma once +#include "Test.h" +#include "..\Engine\Components\Entity.h" +#include "..\Engine\Components\Transform.h" + +#include +#include + +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 _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; + } + } + +}; diff --git a/EngineTest/TestRenderer_Beta.cpp b/EngineTest/TestRenderer_Beta.cpp new file mode 100644 index 0000000..f9fcccf --- /dev/null +++ b/EngineTest/TestRenderer_Beta.cpp @@ -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 +#include + + +#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 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 +void init_test_workers(FnPtr&& fnPtr, Args&&... args) +{ +#if ENABLE_TEST_WORKERS + wshutdown = false; + for (auto& w : workers) + w = std::thread(std::forward(fnPtr), std::forward(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&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(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 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 diff --git a/EngineTest/TestShader.hlsl b/EngineTest/TestShader.hlsl new file mode 100644 index 0000000..b773e9a --- /dev/null +++ b/EngineTest/TestShader.hlsl @@ -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 GlobalData : register(b0, space0); +ConstantBuffer PerObjectBuffer : register(b1, space0); +StructuredBuffer VertexPositions : register(t0, space0); +StructuredBuffer Elements : register(t1, space0); + +StructuredBuffer 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; +} \ No newline at end of file diff --git a/EngineTest/TestWindow.h b/EngineTest/TestWindow.h new file mode 100644 index 0000000..07e6539 --- /dev/null +++ b/EngineTest/TestWindow.h @@ -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; + } + + + +}; diff --git a/EngineTest/model.model b/EngineTest/model.model new file mode 100644 index 0000000..f5985db Binary files /dev/null and b/EngineTest/model.model differ diff --git a/FeatureExtractDemo.sln b/FeatureExtractDemo.sln new file mode 100644 index 0000000..f780efc --- /dev/null +++ b/FeatureExtractDemo.sln @@ -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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..a46d44d --- /dev/null +++ b/README.md @@ -0,0 +1,110 @@ +# FeatureExtractDemo(DX12 基础框架) + +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 2022(MSVC 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 学习和工程化演进的基础骨架,适合按功能模块逐步扩展。 diff --git a/docs/架构分析/Entity组件分析.md b/docs/架构分析/Entity组件分析.md new file mode 100644 index 0000000..2768405 --- /dev/null +++ b/docs/架构分析/Entity组件分析.md @@ -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 按需挂载,存在即可访问,不存在则为空句柄。 diff --git a/docs/架构分析/Id与Components运行机制设计分析.md b/docs/架构分析/Id与Components运行机制设计分析.md new file mode 100644 index 0000000..fafc2c7 --- /dev/null +++ b/docs/架构分析/Id与Components运行机制设计分析.md @@ -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) diff --git a/docs/架构分析/Id机制分析.md b/docs/架构分析/Id机制分析.md new file mode 100644 index 0000000..c26f701 --- /dev/null +++ b/docs/架构分析/Id机制分析.md @@ -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`,零额外开销。 + +这是“开发期安全 + 发布期性能”的组合设计。 diff --git a/docs/架构分析/Script组件分析.md b/docs/架构分析/Script组件分析.md new file mode 100644 index 0000000..65ccc4d --- /dev/null +++ b/docs/架构分析/Script组件分析.md @@ -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` +- `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 +``` diff --git a/docs/架构分析/Transform组件分析.md b/docs/架构分析/Transform组件分析.md new file mode 100644 index 0000000..609a4b3 --- /dev/null +++ b/docs/架构分析/Transform组件分析.md @@ -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 交互,不直接暴露内部数组。 diff --git a/docs/架构分析/项目约定规范.md b/docs/架构分析/项目约定规范.md new file mode 100644 index 0000000..e42c6de --- /dev/null +++ b/docs/架构分析/项目约定规范.md @@ -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 代数校验与映射一致性。 +- 是否补齐了注释并确保无乱码。 +- 是否与现有目录结构与命名风格一致。 diff --git a/packages/dxCompiler/dxcompiler.dll b/packages/dxCompiler/dxcompiler.dll new file mode 100644 index 0000000..fd95641 Binary files /dev/null and b/packages/dxCompiler/dxcompiler.dll differ diff --git a/packages/dxCompiler/dxil.dll b/packages/dxCompiler/dxil.dll new file mode 100644 index 0000000..e99de3e Binary files /dev/null and b/packages/dxCompiler/dxil.dll differ