From 685b3892d10ef72794c7383d2474f77882bb09a4 Mon Sep 17 00:00:00 2001
From: Shell <104874910+Shell4026@users.noreply.github.com>
Date: Sat, 28 Mar 2026 15:17:59 +0900
Subject: [PATCH 01/82] docs: Update README.md
Update README.md
---
Editor.md | 35 +++++++++++++++++++++++++++++++----
README.md | 4 ++--
2 files changed, 33 insertions(+), 6 deletions(-)
diff --git a/Editor.md b/Editor.md
index 13465e6e..927bb2c2 100644
--- a/Editor.md
+++ b/Editor.md
@@ -1,9 +1,36 @@
-# 개요
-효율적인 게임 제작을 위해 필요한 요소인 에디터를 구현했습니다.
-에디터는 Unity 엔진의 UI와 비슷하며 조작법은 Blender와 유사 합니다.
-더욱 많은 기능을 개발중입니다.
+# 에디터 구조
+ShellEngine의 에디터는 게임 바깥의 외부 툴이 아니라, 엔진이 에디터 모드로 부팅된 상태입니다.
+그래서 월드, 카메라, 렌더러, 입력, GC, 스레드 모델을 대부분 공유하면서도 에디터 전용 타입만 별도로 끼워 넣을 수 있습니다.
+
+- ```Project```
+ - 새 프로젝트 생성, 프로젝트 열기, 월드 저장/불러오기, 빌드, 모듈 리로드
+- ```EditorWorld```
+ - 일반 game::World를 상속한 편집용 월드
+ - 선택 상태, 에디터 카메라, 피킹 카메라, 뷰포트 렌더 텍스처, 그리드/축, EditorUI 컴포넌트를 붙인 에디터용 게임오브젝트를 관리
+ - 에디터 전용 편집 상태를 분리해서 기록하고, 정지 시에는 world point를 되돌려 플레이 이전 상태를 복원
+- ```EditorRenderer```
+ - 일반 렌더러를 그대로 쓰지 않고 에디터 전용 패스를 조합한 ```ScriptableRenderer```
+ - 피킹, 아웃라인 pre/post pass, 일반 Opaque/Transparent, UI pass, ImGui pass를 카메라별로 필터링
+- ```EditorUI```
+ - Viewport, Hierarchy, Inspector, Project 패널과 각종 툴 UI를 생성하고, 단축키와 메뉴를 라우팅
+- ```AssetDatabase```
+ - 파일 경로와 UUID 매핑 유지
+ - 원본 파일, .meta, 캐시 .asset 사이 동기화
+ - 에셋 타입별 로더 선택 및 의존 순서 조절
# 엔진 생명주기
+에디터 실행은 src/main.cpp에서 ```EngineInit::Start()```를 호출하는 순간 시작됩니다. 그 후 에디터와 게임 모드의 실행 분기점은 ```#if SH_EDITOR``` 블록에서 나눠집니다.
+
+그 다음으로 이루어지는 과정은 다음과 같습니다.
+
+1. ```AssetResolverRegistry```에 에디터용 resolver를 등록
+ - resolver: 메모리에 안 올라온 에셋을 불러올 때 어떻게 처리 할 것인지 정의
+3. ```Project``` 인스턴스를 생성
+4. ```EditorWorld``` 팩토리를 등록
+5. 기본 ```EditorWorld```를 하나 만들고 ```GameManager```에 메인 월드로 로드
+6. ```EditorResource```와 에디터 렌더링 리소스를 초기화
+7. ```GameManager```에서 아래 이미지의 루프를 실행
+
OnEnable과 OnDisable은 객체가 활성화/비활성화 되는 즉시 실행 됩니다.
diff --git a/README.md b/README.md
index bbf93308..9a340937 100644
--- a/README.md
+++ b/README.md
@@ -34,14 +34,14 @@ C++에서 메모리 관리를 단순화하기 위해 마크 앤 스윕 방식의
- 멀티스레딩 구조의 Sync 타이밍에 동작하여 렌더 중 해제로 인한 문제를 원천 차단
- ```GCObject```를 상속하여 사용자 정의 구조체 내부의 ```SObject```도 추적 가능
---
-### 에디터 [상세 문서](Editor.md)
+### 에디터 [상세 문서](Editor.md) | [구현 과정 (velog)](https://velog.io/@shell4026/%EC%97%90%EB%94%94%ED%84%B0-%EA%B0%9C%EB%B0%9C-%EA%B3%BC%EC%A0%95)
- 런타임 리플렉션을 활용한 Hierarchy(계층 구조) 및 Inspector(속성 편집) 자동 생성
- 사용자 정의 Inspector 지원 | [상세 문서](https://github.com/Shell4026/ShellEngine/blob/main/CustomInspector.md)
- 유저 코드 핫-리로드: 엔진 재시작 없이 런타임 중 컴포넌트 코드 추가, 수정 가능 | [구현 과정 (velog)](https://velog.io/@shell4026/ShellEngine-%ED%95%AB-%EB%A6%AC%EB%A1%9C%EB%93%9C-%EA%B5%AC%ED%98%84-%EA%B3%BC%EC%A0%95)
- Unity 스타일의 편집 환경 / Blender 스타일의 오브젝트 조작 방식
- 빌드 시스템
---
-### 커스텀 셰이더 언어 | [상세 문서](Shader.md)
+### 커스텀 셰이더 언어 | [상세 문서](Shader.md) | [구현 과정 (velog)](https://velog.io/@shell4026/%EC%85%B0%EC%9D%B4%EB%8D%94-%ED%8C%8C%EC%84%9C-%EA%B0%9C%EB%B0%9C-%EA%B3%BC%EC%A0%95)
Unity ShaderLab과 유사한 문법의 셰이더 언어와 하향식 파서(Top-Down Parser) 를 직접 구현했습니다.
파이프라인 설정(블렌딩, 컬링 등)을 셰이더 코드 안에서 선언적으로 작성할 수 있습니다.
From 822323af09a3a612b73bad172b4c1184090721e5 Mon Sep 17 00:00:00 2001
From: Shell <104874910+Shell4026@users.noreply.github.com>
Date: Mon, 13 Apr 2026 17:35:57 +0900
Subject: [PATCH 02/82] Update Editor.md
---
Editor.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Editor.md b/Editor.md
index 927bb2c2..e31ae898 100644
--- a/Editor.md
+++ b/Editor.md
@@ -1,6 +1,6 @@
# 에디터 구조
-ShellEngine의 에디터는 게임 바깥의 외부 툴이 아니라, 엔진이 에디터 모드로 부팅된 상태입니다.
-그래서 월드, 카메라, 렌더러, 입력, GC, 스레드 모델을 대부분 공유하면서도 에디터 전용 타입만 별도로 끼워 넣을 수 있습니다.
+ShellEngine의 에디터는 엔진이 에디터 모드로 부팅된 상태입니다.
+이런 구조로 인해 월드, 카메라, 렌더러, 입력, GC, 스레드 모델을 대부분 공유하면서도 에디터 전용 타입만 별도로 끼워 넣을 수 있습니다.
- ```Project```
- 새 프로젝트 생성, 프로젝트 열기, 월드 저장/불러오기, 빌드, 모듈 리로드
From d2aaf896a5fba3a46db80e6aa295fd53a4c18ffd Mon Sep 17 00:00:00 2001
From: Shell <104874910+Shell4026@users.noreply.github.com>
Date: Sat, 18 Apr 2026 08:26:33 +0900
Subject: [PATCH 03/82] feat(STypeInfo): Add ConvertFromTypeInfo()
---
include/Core/Reflection/STypeInfo.hpp | 10 ++++++++++
src/Core/Reflection/STypeInfo.cpp | 10 ++++++++++
2 files changed, 20 insertions(+)
diff --git a/include/Core/Reflection/STypeInfo.hpp b/include/Core/Reflection/STypeInfo.hpp
index 8011515c..8ef6f6fd 100644
--- a/include/Core/Reflection/STypeInfo.hpp
+++ b/include/Core/Reflection/STypeInfo.hpp
@@ -93,6 +93,7 @@ namespace sh::core::reflection
hash(data.hash), super(data.super),
name(data.name), type(GetType())
{
+ typeInfoMap.insert({ type.hash, this });
}
STypeInfo(const STypeInfo& other) = delete;
SH_CORE_API ~STypeInfo();
@@ -116,6 +117,13 @@ namespace sh::core::reflection
SH_CORE_API auto GetFunction(const core::Name& name) const -> Function*;
SH_CORE_API auto GetFunction(std::string_view name) const -> Function*;
SH_CORE_API auto GetFunctions() const -> const std::vector>& { return functions; }
+
+ /// @brief TypeInfo를 STypeInfo로 변환하는 함수.
+ ///
+ /// TypeInfo가 가르키는 타입이 포인터였어도 STypeInfo는 원본 클래스의 타입을 가르킨다.
+ /// @param typeInfo 일반 타입
+ /// @return 변환 할 수 없으면 nullptr
+ SH_CORE_API static auto ConvertFromTypeInfo(const core::reflection::TypeInfo& typeInfo) -> const STypeInfo*;
public:
const core::Name name;
const TypeInfo& type;
@@ -127,6 +135,8 @@ namespace sh::core::reflection
std::vector sobjPtrs;
std::vector sobjPtrContainers;
std::vector> functions;
+
+ SH_CORE_API static std::unordered_map typeInfoMap; // key = typeHash
};//STypeInfo
/// @brief 리플렉션 데이터의 DLL간 공유를 위한 구조체
diff --git a/src/Core/Reflection/STypeInfo.cpp b/src/Core/Reflection/STypeInfo.cpp
index cecdc4ca..3cc1cb5c 100644
--- a/src/Core/Reflection/STypeInfo.cpp
+++ b/src/Core/Reflection/STypeInfo.cpp
@@ -4,6 +4,7 @@
namespace sh::core::reflection
{
std::unordered_map STypes::types{};
+ std::unordered_map STypeInfo::typeInfoMap;
STypeInfo::~STypeInfo()
{
@@ -12,6 +13,7 @@ namespace sh::core::reflection
info.size = type.size;
// DLL 핫스왑용
STypes::types.erase(info);
+ typeInfoMap.erase(type.hash);
}
SH_CORE_API auto STypeInfo::AddProperty(std::unique_ptr&& prop) -> Property*
@@ -124,4 +126,12 @@ namespace sh::core::reflection
core::Name key{ name };
return GetFunction(key);
}
+
+ SH_CORE_API auto STypeInfo::ConvertFromTypeInfo(const core::reflection::TypeInfo& typeInfo) -> const STypeInfo*
+ {
+ auto it = typeInfoMap.find(typeInfo.hash);
+ if (it == typeInfoMap.end())
+ return nullptr;
+ return it->second;
+ }
}//namespace
\ No newline at end of file
From 0eb8fdd376e4e566f10e3ccd1c15cfe912848209 Mon Sep 17 00:00:00 2001
From: Shell <104874910+Shell4026@users.noreply.github.com>
Date: Sat, 18 Apr 2026 08:30:49 +0900
Subject: [PATCH 04/82] feat: Add DragDropHelper to simplify using
ImGui::AcceptDragDropPayload
---
include/Editor/DragDropHelper.hpp | 46 ++++++++
include/Editor/UI/CustomHierarchy.h | 2 +-
include/Editor/UI/DefaultHierarchy.h | 4 +-
include/Editor/UI/Hierarchy.h | 4 +-
src/Core/SObject.cpp | 2 +-
src/Editor/Project.cpp | 5 +-
src/Editor/UI/DefaultHierarchy.cpp | 14 +--
src/Editor/UI/DefaultInspector.cpp | 14 +--
src/Editor/UI/Hierarchy.cpp | 89 +++++++--------
src/Editor/UI/Inspector.cpp | 160 ++++++---------------------
src/Editor/UI/ProjectExplorer.cpp | 55 +--------
src/Editor/UI/ProjectSettingUI.cpp | 12 +-
12 files changed, 150 insertions(+), 257 deletions(-)
create mode 100644 include/Editor/DragDropHelper.hpp
diff --git a/include/Editor/DragDropHelper.hpp b/include/Editor/DragDropHelper.hpp
new file mode 100644
index 00000000..47dbbfe3
--- /dev/null
+++ b/include/Editor/DragDropHelper.hpp
@@ -0,0 +1,46 @@
+#pragma once
+#include "Core/SObject.h"
+#include "Core/Reflection/STypeInfo.hpp"
+
+#include "Game/ImGUImpl.h"
+
+namespace sh::editor::dragdrop
+{
+ template
+ auto AcceptAsset() -> T*
+ {
+ const ImGuiPayload* current = ImGui::GetDragDropPayload();
+ if (current == nullptr || !current->IsDataType("SObject"))
+ return nullptr;
+
+ auto* obj = *reinterpret_cast(current->Data);
+ if (!obj->GetType().IsChildOf(T::GetStaticType()))
+ return nullptr;
+
+ if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("SObject"))
+ return static_cast(*reinterpret_cast(payload->Data));
+ return nullptr;
+ }
+
+ inline auto AcceptAsset(const core::reflection::STypeInfo& type) -> core::SObject*
+ {
+ const ImGuiPayload* current = ImGui::GetDragDropPayload();
+ if (current == nullptr || !current->IsDataType("SObject"))
+ return nullptr;
+
+ auto* obj = *reinterpret_cast(current->Data);
+ if (!obj->GetType().IsChildOf(type))
+ return nullptr;
+
+ if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("SObject"))
+ return *reinterpret_cast(payload->Data);
+ return nullptr;
+ }
+
+ inline auto AcceptAnyAsset() -> core::SObject*
+ {
+ if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("SObject"))
+ return static_cast(*reinterpret_cast(payload->Data));
+ return nullptr;
+ }
+}//namespace
\ No newline at end of file
diff --git a/include/Editor/UI/CustomHierarchy.h b/include/Editor/UI/CustomHierarchy.h
index 978dd57e..b0bdcf9f 100644
--- a/include/Editor/UI/CustomHierarchy.h
+++ b/include/Editor/UI/CustomHierarchy.h
@@ -32,7 +32,7 @@ namespace sh::editor
virtual ~ICustomHierarchy() = default;
virtual void OnHierarchyInstantiated(EditorWorld& world) {};
- virtual void OnHierarchyDraged(EditorWorld& world, const ImGuiPayload& payload) {};
+ virtual void OnHierarchyDraged(EditorWorld& world, core::SObject& payload) {};
};
class CustomHierarchyManager : public core::Singleton
diff --git a/include/Editor/UI/DefaultHierarchy.h b/include/Editor/UI/DefaultHierarchy.h
index 99270773..9aef59a6 100644
--- a/include/Editor/UI/DefaultHierarchy.h
+++ b/include/Editor/UI/DefaultHierarchy.h
@@ -12,12 +12,12 @@ namespace sh::editor
{
HIERARCHY(ModelHierarchy, render::Model)
public:
- SH_EDITOR_API void OnHierarchyDraged(EditorWorld& world, const ImGuiPayload& payload) override;
+ SH_EDITOR_API void OnHierarchyDraged(EditorWorld& world, core::SObject& payload) override;
};
class PrefabHierarchy : public ICustomHierarchy
{
HIERARCHY(PrefabHierarchy, game::Prefab)
public:
- SH_EDITOR_API void OnHierarchyDraged(EditorWorld& world, const ImGuiPayload& payload) override;
+ SH_EDITOR_API void OnHierarchyDraged(EditorWorld& world, core::SObject& payload) override;
};
}//namespace
\ No newline at end of file
diff --git a/include/Editor/UI/Hierarchy.h b/include/Editor/UI/Hierarchy.h
index ea374cc5..1783f1d8 100644
--- a/include/Editor/UI/Hierarchy.h
+++ b/include/Editor/UI/Hierarchy.h
@@ -27,7 +27,7 @@ namespace sh::editor
/// @brief 뒤늦게 추가 된 기능으로 덮어 씌워진다.
/// @param dragItem 드래그 드롭 아이템 이름
/// @param func 작동 함수
- SH_EDITOR_API void RegisterDragItemFunction(const std::string& dragItem, const std::function& func);
+ SH_EDITOR_API void RegisterDragItemFunction(const std::string& dragItem, const std::function& func);
SH_EDITOR_API void AddOtherWorld(game::World& world);
SH_EDITOR_API void RegisterPopupMenu(std::string_view name, const std::function& fn);
@@ -54,7 +54,7 @@ namespace sh::editor
core::EventSubscriber gameObjectEventSubscriber;
core::EventSubscriber gameObjectEventSubscriberOther;
- std::vector>> dragFunc;
+ std::vector>> dragFunc;
struct PopupMenu
{
diff --git a/src/Core/SObject.cpp b/src/Core/SObject.cpp
index 4506127e..12718c86 100644
--- a/src/Core/SObject.cpp
+++ b/src/Core/SObject.cpp
@@ -245,7 +245,7 @@ namespace sh::core
if (objPtr != nullptr && objPtr->IsPendingKill())
objPtr->SetUUID(core::UUID::Generate());
bool success = SetUUID(std::move(newUUID));
- assert(success);
+ // assert(success);
}
}
if (json.contains("name"))
diff --git a/src/Editor/Project.cpp b/src/Editor/Project.cpp
index d8eec3da..22f4da4f 100644
--- a/src/Editor/Project.cpp
+++ b/src/Editor/Project.cpp
@@ -3,6 +3,7 @@
#include "AssetDatabase.h"
#include "BuildSystem.h"
#include "UI/ProjectSettingUI.h"
+#include "DragDropHelper.hpp"
#include "Core/FileSystem.h"
#include "Core/GarbageCollection.h"
@@ -99,10 +100,8 @@ namespace sh::editor
// 프리팹 드래그
if (ImGui::BeginDragDropTarget())
{
- const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(std::string{ core::reflection::GetType().name }.c_str());
- if (payload != nullptr && payload->Data != nullptr)
+ if (game::GameObject* objPtr = dragdrop::AcceptAsset())
{
- game::GameObject* objPtr = *reinterpret_cast(payload->Data);
if (core::IsValid(objPtr))
{
auto prefab = game::Prefab::CreatePrefab(*objPtr);
diff --git a/src/Editor/UI/DefaultHierarchy.cpp b/src/Editor/UI/DefaultHierarchy.cpp
index a987840d..78894a07 100644
--- a/src/Editor/UI/DefaultHierarchy.cpp
+++ b/src/Editor/UI/DefaultHierarchy.cpp
@@ -10,13 +10,13 @@
namespace sh::editor
{
- SH_EDITOR_API void ModelHierarchy::OnHierarchyDraged(EditorWorld& world, const ImGuiPayload& payload)
+ SH_EDITOR_API void ModelHierarchy::OnHierarchyDraged(EditorWorld& world, core::SObject& payload)
{
- render::Model* model = *reinterpret_cast(payload.Data);
+ render::Model& model = static_cast(payload);
- auto obj = world.AddGameObject(model->GetName().ToString());
+ auto obj = world.AddGameObject(model.GetName().ToString());
- auto rootNode = model->GetRootNode();
+ auto rootNode = model.GetRootNode();
std::queue> nodeQ;
for (auto& child : rootNode->children)
nodeQ.push({ child.get(), obj });
@@ -39,9 +39,9 @@ namespace sh::editor
nodeQ.push({ child.get(), meshObj });
}
}
- SH_EDITOR_API void PrefabHierarchy::OnHierarchyDraged(EditorWorld& world, const ImGuiPayload& payload)
+ SH_EDITOR_API void PrefabHierarchy::OnHierarchyDraged(EditorWorld& world, core::SObject& payload)
{
- game::Prefab* prefabPtr = *reinterpret_cast(payload.Data);
- prefabPtr->AddToWorld(world);
+ game::Prefab& prefabPtr = static_cast(payload);
+ prefabPtr.AddToWorld(world);
}
}//namespace
\ No newline at end of file
diff --git a/src/Editor/UI/DefaultInspector.cpp b/src/Editor/UI/DefaultInspector.cpp
index 75f13eb6..f5257c81 100644
--- a/src/Editor/UI/DefaultInspector.cpp
+++ b/src/Editor/UI/DefaultInspector.cpp
@@ -1,6 +1,7 @@
#include "UI/DefaultInspector.h"
#include "UI/Inspector.h"
#include "AssetDatabase.h"
+#include "DragDropHelper.hpp"
#include "Game/World.h"
@@ -174,11 +175,8 @@ namespace sh::editor
if (ImGui::BeginDragDropTarget())
{
- auto p = ImGui::GetCurrentContext()->DragDropPayload;
- const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(std::string{ core::reflection::TypeTraits::GetTypeName() }.c_str());
- if (payload)
+ if (render::Shader* shaderPtr = dragdrop::AcceptAsset())
{
- render::Shader* shader = *reinterpret_cast(payload->Data);
for (auto matPtr : mats)
if (matPtr != nullptr)
matPtr->SetShader(shader);
@@ -253,17 +251,13 @@ namespace sh::editor
}
if (ImGui::BeginDragDropTarget())
{
- auto p = ImGui::GetCurrentContext()->DragDropPayload;
- const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(std::string{ core::reflection::TypeTraits::GetTypeName() }.c_str());
- if (payload != nullptr)
+ if (render::Texture* texturePtr = dragdrop::AcceptAsset())
{
- const render::Texture* texture = *reinterpret_cast(payload->Data);
for (auto matPtr : mats)
if (matPtr != nullptr)
- matPtr->SetProperty(name, texture);
+ matPtr->SetProperty(name, texturePtr);
bChanged = true;
}
-
ImGui::EndDragDropTarget();
}
}
diff --git a/src/Editor/UI/Hierarchy.cpp b/src/Editor/UI/Hierarchy.cpp
index 64996844..25bfa73b 100644
--- a/src/Editor/UI/Hierarchy.cpp
+++ b/src/Editor/UI/Hierarchy.cpp
@@ -2,6 +2,7 @@
#include "UI/CustomHierarchy.h"
#include "EditorWorld.h"
#include "EditorResource.h"
+#include "DragDropHelper.hpp"
#include "Game/ImGUImpl.h"
#include "Game/GameObject.h"
@@ -39,22 +40,23 @@ namespace sh::editor
);
world.SubscribeEvent(gameObjectEventSubscriber);
- for (auto obj : world.GetGameObjects())
+ for (game::GameObject* objPtr : world.GetGameObjects())
{
- objList.push_back(obj);
+ objList.push_back(objPtr);
}
RegisterDragItemFunction(std::string{ core::reflection::GetType().name },
- [&](const ImGuiPayload& payload)
+ [&](core::SObject& payload)
{
- IM_ASSERT(payload.DataSize == sizeof(game::GameObject*));
- game::GameObject* draggedObj = *(game::GameObject**)payload.Data;
+ if (!payload.GetType().IsChildOf(game::GameObject::GetStaticType()))
+ return;
+ game::GameObject& draggedObj = static_cast(payload);
- if (draggedObj->transform->GetParent() != nullptr)
- draggedObj->transform->SetParent(nullptr);
+ if (draggedObj.transform->GetParent() != nullptr)
+ draggedObj.transform->SetParent(nullptr);
- objList.erase(std::find(objList.begin(), objList.end(), draggedObj));
- objList.push_back(draggedObj);
+ objList.erase(std::find(objList.begin(), objList.end(), &draggedObj));
+ objList.push_back(&draggedObj);
}
);
}
@@ -98,17 +100,16 @@ namespace sh::editor
ImGui::InvisibleButton("HierarchyEmptySpace", ImGui::GetContentRegionAvail());
if (ImGui::BeginDragDropTarget())
{
- const auto customHierarchyManager = CustomHierarchyManager::GetInstance();
- for (const auto& [stype, customHierarchy] : customHierarchyManager->map)
+ const CustomHierarchyManager& customHierarchyManager = *CustomHierarchyManager::GetInstance();
+ for (const auto& [stype, customHierarchy] : customHierarchyManager.map)
{
- if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(std::string{ stype->type.name }.c_str()))
- customHierarchy->OnHierarchyDraged(world, *payload);
+ if (core::SObject* objPtr = dragdrop::AcceptAsset(*stype))
+ customHierarchy->OnHierarchyDraged(world, *objPtr);
}
for (auto& [name, func] : dragFunc)
{
- const std::string typeName{ core::reflection::GetType().name };
- if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(typeName.c_str()))
- func(*payload);
+ if (game::GameObject* gameObjPtr = dragdrop::AcceptAsset())
+ func(*gameObjPtr);
}
ImGui::EndDragDropTarget();
@@ -120,7 +121,7 @@ namespace sh::editor
ImGui::End();
}
- SH_EDITOR_API void Hierarchy::RegisterDragItemFunction(const std::string& dragItem, const std::function& func)
+ SH_EDITOR_API void Hierarchy::RegisterDragItemFunction(const std::string& dragItem, const std::function& func)
{
for (auto& [name, _func] : dragFunc)
{
@@ -172,45 +173,41 @@ namespace sh::editor
if (ImGui::BeginDragDropTarget())
{
- const std::string typeName{ core::reflection::GetType().name };
- if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(typeName.c_str()))
+ if (game::GameObject* draggedObjPtr = dragdrop::AcceptAsset())
{
- IM_ASSERT(payload->DataSize == sizeof(game::GameObject*));
- game::GameObject* draggedObj = *(game::GameObject**)payload->Data;
-
// 순서 변경
if (obj->transform->GetParent() == nullptr)
{
- objList.erase(std::find(objList.begin(), objList.end(), draggedObj));
+ objList.erase(std::find(objList.begin(), objList.end(), draggedObjPtr));
auto it = std::find(objList.begin(), objList.end(), obj);
if (it != objList.begin())
- objList.insert(it, draggedObj);
+ objList.insert(it, draggedObjPtr);
else
- objList.push_front(draggedObj);
+ objList.push_front(draggedObjPtr);
}
else
{
- if (obj->transform->GetParent() == draggedObj->transform->GetParent())
+ if (obj->transform->GetParent() == draggedObjPtr->transform->GetParent())
{
auto objParent = obj->transform->GetParent();
auto objIt = std::find(objParent->GetChildren().begin(), objParent->GetChildren().end(), obj->transform);
- auto draggedIt = std::find(objParent->GetChildren().begin(), objParent->GetChildren().end(), draggedObj->transform);
+ auto draggedIt = std::find(objParent->GetChildren().begin(), objParent->GetChildren().end(), draggedObjPtr->transform);
uint32_t dif = draggedIt - objIt;
for (std::size_t i = 0; i < dif; ++i)
{
- objParent->ReorderChildAbove(draggedObj->transform);
+ objParent->ReorderChildAbove(draggedObjPtr->transform);
}
}
else
{
auto objParent = obj->transform->GetParent();
- draggedObj->transform->SetParent(objParent);
+ draggedObjPtr->transform->SetParent(objParent);
auto objIt = std::find(objParent->GetChildren().begin(), objParent->GetChildren().end(), obj->transform);
- auto draggedIt = std::find(objParent->GetChildren().begin(), objParent->GetChildren().end(), draggedObj->transform);
+ auto draggedIt = std::find(objParent->GetChildren().begin(), objParent->GetChildren().end(), draggedObjPtr->transform);
uint32_t dif = draggedIt - objIt;
for (std::size_t i = 0; i < dif; ++i)
{
- objParent->ReorderChildAbove(draggedObj->transform);
+ objParent->ReorderChildAbove(draggedObjPtr->transform);
}
}
}
@@ -288,32 +285,32 @@ namespace sh::editor
// 드래그 되는 대상의 시점
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_::ImGuiDragDropFlags_None))
{
- ImGui::SetDragDropPayload(typeName.c_str(), &obj, sizeof(game::GameObject*));
+ ImGui::SetDragDropPayload("SObject", &obj, sizeof(core::SObject*));
ImGui::Text("%s", obj->GetName().ToString().c_str());
ImGui::EndDragDropSource();
}
// 드래그 받는 대상의 시점
if (ImGui::BeginDragDropTarget())
{
- if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(typeName.c_str()))
+ if (game::GameObject* draggedObjPtr = dragdrop::AcceptAsset())
{
- IM_ASSERT(payload->DataSize == sizeof(game::GameObject*));
- game::GameObject* draggedObj = *reinterpret_cast(payload->Data);
-
- // 순환 참조 체크, 부모 설정
- bool circulation = false;
- game::Transform* parent = obj->transform->GetParent();
- while (parent)
+ if (core::IsValid(draggedObjPtr))
{
- if (parent == draggedObj->transform)
+ // 순환 참조 체크, 부모 설정
+ bool circulation = false;
+ game::Transform* parent = obj->transform->GetParent();
+ while (parent)
{
- circulation = true;
- break;
+ if (parent == draggedObjPtr->transform)
+ {
+ circulation = true;
+ break;
+ }
+ parent = parent->GetParent();
}
- parent = parent->GetParent();
+ if (!circulation)
+ draggedObjPtr->transform->SetParent(obj->transform);
}
- if (!circulation)
- draggedObj->transform->SetParent(obj->transform);
}
ImGui::EndDragDropTarget();
}
diff --git a/src/Editor/UI/Inspector.cpp b/src/Editor/UI/Inspector.cpp
index 27ea5abe..49732074 100644
--- a/src/Editor/UI/Inspector.cpp
+++ b/src/Editor/UI/Inspector.cpp
@@ -6,6 +6,7 @@
#include "AssetDatabase.h"
#include "LambdaEditorCommand.h"
#include "Project.h"
+#include "DragDropHelper.hpp"
#include "Core/Logger.h"
#include "Core/SObject.h"
@@ -288,6 +289,7 @@ namespace sh::editor
SH_EDITOR_API void Inspector::RenderSObjectPtrProperty(const core::reflection::Property& prop, core::SObject& propertyOwner, const std::string& name,
core::SObject** objPtr, const core::reflection::TypeInfo* type)
{
+ const core::reflection::STypeInfo& propertySTypeInfo = *core::reflection::STypeInfo::ConvertFromTypeInfo(prop.type);
const std::string propertyTypeName{ type == nullptr ? prop.pureTypeName : type->name };
if (type == nullptr)
type = &prop.type;
@@ -322,12 +324,9 @@ namespace sh::editor
}
if (ImGui::BeginDragDropTarget())
{
- // 드래그로 받는 객체의 타입 이름 == 드래그 중인 객체의 타입 이름이면 받음
- const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(propertyTypeName.c_str());
- auto currentPayload = ImGui::GetDragDropPayload();
- if (payload)
+ if (core::SObject* objPtr = dragdrop::AcceptAsset(propertySTypeInfo))
{
- *parameter = *reinterpret_cast(payload->Data);
+ *parameter = objPtr;
propertyOwner.OnPropertyChanged(prop);
AssetDatabase::GetInstance()->SetDirty(&propertyOwner);
AssetDatabase::GetInstance()->SaveAllAssets();
@@ -335,77 +334,27 @@ namespace sh::editor
else
{
// 게임 오브젝트라면 컴포넌트 검사
- std::string gameObjTypeName{ core::reflection::GetType().name };
- if (gameObjTypeName == ImGui::GetDragDropPayload()->DataType)
+ core::SObject* payloadDataPtr = *reinterpret_cast(ImGui::GetDragDropPayload()->Data);
+ if (payloadDataPtr->GetType().IsChildOf(game::GameObject::GetStaticType()))
{
- game::GameObject* obj = *reinterpret_cast(ImGui::GetDragDropPayload()->Data);
-
- if (obj->transform->GetType().type.name == propertyTypeName)
+ game::GameObject* gameObjPtr = static_cast(payloadDataPtr);
+ if (gameObjPtr->transform->GetType().IsChildOf(propertySTypeInfo))
{
- ImGui::SetDragDropPayload(std::string{ obj->transform->GetType().type.name }.c_str(), &obj->transform, sizeof(game::Transform*));
+ ImGui::SetDragDropPayload("SObject", &gameObjPtr->transform, sizeof(core::SObject*));
ImGui::EndDragDropTarget();
return;
}
-
- std::vector list{};
- for (game::Component* const& payloadComponent : obj->GetComponents())
+ for (game::Component* const& payloadComponent : gameObjPtr->GetComponents())
{
if (!core::IsValid(payloadComponent))
continue;
const core::reflection::STypeInfo* componentType = &payloadComponent->GetType();
- while (componentType)
- {
- if (componentType->type.name == propertyTypeName)
- {
- // 부모가 해당 컴포넌트인 오브젝트는 여럿일 수 있으므로 후보 리스트에 넣는다.
- list.push_back(&payloadComponent);
- break;
- }
- componentType = componentType->super;
- }
- }
- bool bFind = false;
- for (game::Component* const* component : list)
- {
- const core::reflection::STypeInfo& componentType = (*component)->GetType();
- // 정확히 같은 컴포넌트라면
- if (componentType.type.name == propertyTypeName)
- {
- bFind = true;
- // 페이로드 재설정
- ImGui::SetDragDropPayload(std::string{ componentType.type.name }.c_str(), component, sizeof(game::Component*));
- break;
- }
- }
- // 정확히 같은 컴포넌트는 못 찾았으므로 제일 첫번째로 찾은 컴포넌트를 페이로드로 설정
- if (!bFind && list.size() > 0)
- {
- const core::reflection::STypeInfo& componentType = (*list[0])->GetType();
- ImGui::SetDragDropPayload(std::string{ componentType.type.name }.c_str(), list[0], sizeof(game::Component*));
- }
- }
- else
- {
- // 부모 타입하고는 일치 하는지 검사
- core::SObject** dataPtr = reinterpret_cast(currentPayload->Data);
- if (dataPtr != nullptr)
- {
- auto type = &(*dataPtr)->GetType();
- while (type != nullptr)
+ if (componentType->IsChildOf(propertySTypeInfo))
{
- if (type->type.name == propertyTypeName) // 맞으면 페이로드 허용
- {
- const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(currentPayload->DataType);
- if (payload != nullptr)
- {
- *parameter = *reinterpret_cast(payload->Data);
- propertyOwner.OnPropertyChanged(prop);
- AssetDatabase::GetInstance()->SetDirty(&propertyOwner);
- AssetDatabase::GetInstance()->SaveAllAssets();
- }
- }
- type = type->super;
+ ImGui::SetDragDropPayload("SObject", &payloadComponent, sizeof(core::SObject*));
+ ImGui::EndDragDropTarget();
+ return;
}
}
}
@@ -436,19 +385,17 @@ namespace sh::editor
const char* name = "None";
if (core::IsValid(*obj))
name = (*obj)->GetName().ToString().c_str();
- auto& itType = it.GetType();
+ const core::reflection::TypeInfo& itType = it.GetType();
+ const core::reflection::STypeInfo& propertySTypeInfo = *core::reflection::STypeInfo::ConvertFromTypeInfo(itType);
const std::string propTypeName{ it.GetType().name };
if (ImGui::Button(name, ImVec2{ buttonWidth, iconSize }))
{
}
if (ImGui::BeginDragDropTarget())
{
- // 드래그로 받는 객체의 타입 == 드래그 중인 객체의 타입이면 받음
- const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(propTypeName.c_str());
- auto currentPayload = ImGui::GetDragDropPayload();
- if (payload)
+ if (core::SObject* objPtr = dragdrop::AcceptAsset(propertySTypeInfo))
{
- *obj = *reinterpret_cast(payload->Data);
+ *obj = objPtr;
propertyOwner.OnPropertyChanged(prop);
AssetDatabase::GetInstance()->SetDirty(&propertyOwner);
AssetDatabase::GetInstance()->SaveAllAssets();
@@ -456,70 +403,27 @@ namespace sh::editor
else
{
// 게임 오브젝트라면 컴포넌트 검사
- std::string gameObjTypeName{ core::reflection::GetType().name };
- if (gameObjTypeName == ImGui::GetDragDropPayload()->DataType)
+ core::SObject* payloadDataPtr = *reinterpret_cast(ImGui::GetDragDropPayload()->Data);
+ if (payloadDataPtr->GetType().IsChildOf(game::GameObject::GetStaticType()))
{
- game::GameObject* obj = *reinterpret_cast(ImGui::GetDragDropPayload()->Data);
-
- std::vector list{};
- for (game::Component* const& payloadComponent : obj->GetComponents())
+ game::GameObject* gameObjPtr = static_cast(payloadDataPtr);
+ if (gameObjPtr->transform->GetType().IsChildOf(propertySTypeInfo))
+ {
+ ImGui::SetDragDropPayload("SObject", &gameObjPtr->transform, sizeof(core::SObject*));
+ ImGui::EndDragDropTarget();
+ return;
+ }
+ for (game::Component* const& payloadComponent : gameObjPtr->GetComponents())
{
if (!core::IsValid(payloadComponent))
continue;
const core::reflection::STypeInfo* componentType = &payloadComponent->GetType();
- while (componentType)
- {
- if (componentType->type.name == propTypeName)
- {
- // 부모가 해당 컴포넌트인 오브젝트는 여럿일 수 있으므로 후보 리스트에 넣는다.
- list.push_back(&payloadComponent);
- break;
- }
- componentType = componentType->super;
- }
- }
- bool bFind = false;
- for (game::Component*const* component : list)
- {
- const core::reflection::STypeInfo& componentType = (*component)->GetType();
- // 정확히 같은 컴포넌트라면
- if (componentType.type.name == propTypeName)
- {
- bFind = true;
- // 페이로드 재설정
- ImGui::SetDragDropPayload(std::string{ componentType.type.name }.c_str(), component, sizeof(game::Component*));
- break;
- }
- }
- // 정확히 같은 컴포넌트는 못 찾았으므로 제일 첫번째로 찾은 컴포넌트를 페이로드로 설정
- if (!bFind && list.size() > 0)
- {
- const core::reflection::STypeInfo& componentType = (*list[0])->GetType();
- ImGui::SetDragDropPayload(std::string{ componentType.type.name }.c_str(), list[0], sizeof(game::Component*));
- }
- }
- else
- {
- // 부모 타입하고는 일치 하는지 검사
- core::SObject** dataPtr = reinterpret_cast(currentPayload->Data);
- if (dataPtr != nullptr)
- {
- auto type = &(*dataPtr)->GetType();
- while (type != nullptr)
+ if (componentType->IsChildOf(propertySTypeInfo))
{
- if (type->type.name == propTypeName) // 맞으면 페이로드 허용
- {
- const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(currentPayload->DataType);
- if (payload != nullptr)
- {
- *obj = *reinterpret_cast(payload->Data);
- propertyOwner.OnPropertyChanged(prop);
- AssetDatabase::GetInstance()->SetDirty(&propertyOwner);
- AssetDatabase::GetInstance()->SaveAllAssets();
- }
- }
- type = type->super;
+ ImGui::SetDragDropPayload("SObject", &payloadComponent, sizeof(core::SObject*));
+ ImGui::EndDragDropTarget();
+ return;
}
}
}
diff --git a/src/Editor/UI/ProjectExplorer.cpp b/src/Editor/UI/ProjectExplorer.cpp
index 9b9d0d8f..bfeecc4f 100644
--- a/src/Editor/UI/ProjectExplorer.cpp
+++ b/src/Editor/UI/ProjectExplorer.cpp
@@ -4,6 +4,7 @@
#include "AssetExtensions.h"
#include "AssetDatabase.h"
#include "EditorWorld.h"
+#include "DragDropHelper.hpp"
#include "Core/FileSystem.h"
@@ -314,9 +315,6 @@ namespace sh::editor
void ProjectExplorer::SetItemDragTarget(const std::filesystem::path& path)
{
void* item = nullptr;
- std::string pathStr = path.u8string();
- std::string extension = path.extension().u8string();
- std::string payloadName = "asset";
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_::ImGuiDragDropFlags_None))
{
@@ -334,9 +332,7 @@ namespace sh::editor
auto sobjPtr = reinterpret_cast(item);
- payloadName = sobjPtr->GetType().type.name;
-
- ImGui::SetDragDropPayload(payloadName.c_str(), &item, sizeof(void*));
+ ImGui::SetDragDropPayload("SObject", &item, sizeof(core::SObject*));
ImGui::Text("%s", path.filename().u8string().c_str());
ImGui::EndDragDropSource();
@@ -346,51 +342,10 @@ namespace sh::editor
{
if (ImGui::BeginDragDropTarget())
{
- const ImGuiPayload* payload = nullptr;
- const std::string_view assetTypes[] =
- {
- core::reflection::GetType().name,
- core::reflection::GetType().name,
- core::reflection::GetType().name,
- core::reflection::GetType().name,
- core::reflection::GetType().name,
- core::reflection::GetType().name,
- core::reflection::GetType().name,
- core::reflection::GetType().name,
- core::reflection::GetType().name,
- core::reflection::GetType().name,
- core::reflection::GetType().name,
- core::reflection::GetType().name
- };
- for (const std::string_view& assetType : assetTypes)
+ if (core::SObject* obj = dragdrop::AcceptAnyAsset())
{
- payload = ImGui::AcceptDragDropPayload(std::string{ assetType }.c_str());
- if (payload != nullptr && payload->Data != nullptr)
- {
- core::SObject* objPtr = *reinterpret_cast(payload->Data);
- AssetDatabase::GetInstance()->MoveAssetToDirectory(objPtr->GetUUID(), folderPath);
- Refresh();
- break;
- }
- else if (payload == nullptr && assetType == core::reflection::GetType().name)
- {
- auto curPayloadPtr = ImGui::GetDragDropPayload();
- if (curPayloadPtr != nullptr)
- {
- core::SObject* const objPtr = *reinterpret_cast(curPayloadPtr->Data);
- if (objPtr->GetType().IsChildOf(game::ScriptableObject::GetStaticType()))
- {
- payload = ImGui::AcceptDragDropPayload(std::string{ objPtr->GetType().type.name }.c_str());
- if (payload != nullptr && payload->Data != nullptr)
- {
- core::SObject* objPtr = *reinterpret_cast(payload->Data);
- AssetDatabase::GetInstance()->MoveAssetToDirectory(objPtr->GetUUID(), folderPath);
- Refresh();
- break;
- }
- }
- }
- }
+ AssetDatabase::GetInstance()->MoveAssetToDirectory(obj->GetUUID(), folderPath);
+ Refresh();
}
ImGui::EndDragDropTarget();
}
diff --git a/src/Editor/UI/ProjectSettingUI.cpp b/src/Editor/UI/ProjectSettingUI.cpp
index 9bded6fb..184d424a 100644
--- a/src/Editor/UI/ProjectSettingUI.cpp
+++ b/src/Editor/UI/ProjectSettingUI.cpp
@@ -1,6 +1,7 @@
#include "UI/ProjectSettingUI.h"
#include "ProjectSetting.h"
#include "AssetDatabase.h"
+#include "DragDropHelper.hpp"
namespace sh::editor
{
@@ -38,17 +39,14 @@ namespace sh::editor
}
else
{
- const ImGuiPayload* currentPayload = ImGui::GetDragDropPayload();
- core::SObject* sobjPtr = *reinterpret_cast(currentPayload->Data);
- if (sobjPtr->GetType().IsChildOf(game::World::GetStaticType()))
+ if (game::World* worldPtr = dragdrop::AcceptAsset())
{
- payload = ImGui::AcceptDragDropPayload(currentPayload->DataType);
- if (payload != nullptr)
+ if (core::IsValid(worldPtr))
{
- auto assetInfoPtr = assetDatabase.GetAssetPath(sobjPtr->GetUUID());
+ auto assetInfoPtr = assetDatabase.GetAssetPath(worldPtr->GetUUID());
if (assetInfoPtr != nullptr)
{
- setting.startingWorldUUID = sobjPtr->GetUUID();
+ setting.startingWorldUUID = worldPtr->GetUUID();
setting.Save(rootPath / "ProjectSetting.json");
}
}
From 74e62aceb7cb2c30dc9dfe5f639d3c2201a9e5ff Mon Sep 17 00:00:00 2001
From: Shell <104874910+Shell4026@users.noreply.github.com>
Date: Mon, 20 Apr 2026 05:25:34 +0900
Subject: [PATCH 05/82] fix(MaterialInspector): Fix issue where shader could
not be set
---
src/Editor/UI/DefaultInspector.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/Editor/UI/DefaultInspector.cpp b/src/Editor/UI/DefaultInspector.cpp
index f5257c81..89d7d13f 100644
--- a/src/Editor/UI/DefaultInspector.cpp
+++ b/src/Editor/UI/DefaultInspector.cpp
@@ -175,11 +175,11 @@ namespace sh::editor
if (ImGui::BeginDragDropTarget())
{
- if (render::Shader* shaderPtr = dragdrop::AcceptAsset())
+ if (render::Shader* const shaderPtr = dragdrop::AcceptAsset())
{
for (auto matPtr : mats)
if (matPtr != nullptr)
- matPtr->SetShader(shader);
+ matPtr->SetShader(shaderPtr);
bChanged = true;
}
ImGui::EndDragDropTarget();
From 8794f91c68d3755779ca40c10c7d74708452a12c Mon Sep 17 00:00:00 2001
From: Shell <104874910+Shell4026@users.noreply.github.com>
Date: Sat, 18 Apr 2026 05:21:55 +0900
Subject: [PATCH 06/82] fix(UniformStructLayout): Incorrect member size
---
include/Render/UniformStructLayout.h | 2 +-
src/Render/UniformStructLayout.cpp | 2 ++
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/include/Render/UniformStructLayout.h b/include/Render/UniformStructLayout.h
index 5ef04421..32a182e6 100644
--- a/include/Render/UniformStructLayout.h
+++ b/include/Render/UniformStructLayout.h
@@ -133,7 +133,7 @@ namespace sh::render
uint32_t arrayBaseAlignment = (count > 1) ?
std::max(info.baseAlignment, 16u) : info.baseAlignment; // 배열이면 최소 16정렬
uint32_t arrayStride = (count > 1) ?
- arrayBaseAlignment : info.std140Size;
+ core::Util::AlignTo(info.std140Size, arrayBaseAlignment) : info.std140Size;
uint32_t totalSize = arrayStride * static_cast(count);
UniformMember member{ name, core::reflection::GetType().hash, 0, totalSize, count, (count > 1), false };
diff --git a/src/Render/UniformStructLayout.cpp b/src/Render/UniformStructLayout.cpp
index a21a0467..d2e24f07 100644
--- a/src/Render/UniformStructLayout.cpp
+++ b/src/Render/UniformStructLayout.cpp
@@ -27,6 +27,8 @@ namespace sh::render
}
SH_RENDER_API auto UniformStructLayout::GetSize() const -> std::size_t
{
+ if (members.empty())
+ return 0;
if (members.back().offset == 0)
return members.back().layoutSize;
return members.back().offset + members.back().layoutSize;
From d5113f5d2d4ead93091e2f67e1b50795c975d935 Mon Sep 17 00:00:00 2001
From: Shell <104874910+Shell4026@users.noreply.github.com>
Date: Sat, 18 Apr 2026 05:37:13 +0900
Subject: [PATCH 07/82] refact(Render/Mesh): inline simple getters/setters, fix
self-assignment in VulkanVertexBuffer
- Move trivial getters/setters to header as inline
- Expose SetVertexBuffer and CreateFace as protected for subclass use
- Add self-assignment guard in VulkanVertexBuffer copy/move assignment
---
include/Render/Mesh.h | 32 ++++---
src/Render/Mesh.cpp | 89 +++++---------------
src/Render/VulkanImpl/VulkanVertexBuffer.cpp | 4 +
3 files changed, 40 insertions(+), 85 deletions(-)
diff --git a/include/Render/Mesh.h b/include/Render/Mesh.h
index 418e465a..f7817602 100644
--- a/include/Render/Mesh.h
+++ b/include/Render/Mesh.h
@@ -54,31 +54,29 @@ namespace sh::render
SH_RENDER_API auto operator=(const Mesh& other) -> Mesh&;
SH_RENDER_API auto operator=(Mesh&& other) noexcept -> Mesh&;
- SH_RENDER_API void SetVertex(const std::vector& verts);
- SH_RENDER_API void SetVertex(std::vector&& verts) noexcept;
+ SH_RENDER_API void SetVertex(std::vector verts) noexcept { this->verts = std::move(verts); }
SH_RENDER_API void SetVertex(const std::initializer_list& verts);
-
- SH_RENDER_API auto GetVertex() const -> const std::vector&;
- SH_RENDER_API auto GetVertexCount() const -> size_t;
- SH_RENDER_API void SetIndices(const std::vector&indices);
- SH_RENDER_API void SetIndices(std::vector&& indices);
+ SH_RENDER_API void SetIndices(std::vector indices) noexcept { this->indices = std::move(indices); }
SH_RENDER_API void SetIndices(const std::initializer_list& indices);
- SH_RENDER_API auto GetIndices() const -> const std::vector&;
-
- SH_RENDER_API auto GetFaces() const -> const std::vector&;
SH_RENDER_API void Build(const IRenderContext& context) override;
- SH_RENDER_API auto GetVertexBuffer() const ->IVertexBuffer*;
-
- SH_RENDER_API void SetTopology(Topology topology);
- SH_RENDER_API auto GetTopology() const -> Topology;
-
- SH_RENDER_API auto GetBoundingBox() const -> const AABB&;
- SH_RENDER_API auto GetBoundingBox() -> AABB&;
+ SH_RENDER_API void SetTopology(Topology topology) { this->topology = topology; }
SH_RENDER_API void CalculateTangents();
+
+ SH_RENDER_API auto GetVertex() const -> const std::vector& { return verts; }
+ SH_RENDER_API auto GetVertexCount() const -> size_t { return verts.size(); }
+ SH_RENDER_API auto GetFaces() const -> const std::vector& { return faces; }
+ SH_RENDER_API auto GetIndices() const -> const std::vector& { return indices; }
+ SH_RENDER_API auto GetVertexBuffer() const -> IVertexBuffer* { return buffer.get(); }
+ SH_RENDER_API auto GetTopology() const -> Topology { return topology; }
+ SH_RENDER_API auto GetBoundingBox() const -> const AABB& { return bounding; }
+ SH_RENDER_API auto GetBoundingBox() -> AABB& { return bounding; }
+ protected:
+ SH_RENDER_API void SetVertexBuffer(std::unique_ptr buf);
+ SH_RENDER_API void CreateFace();
public:
float lineWidth = 1.f;
private:
diff --git a/src/Render/Mesh.cpp b/src/Render/Mesh.cpp
index b5063df7..63961116 100644
--- a/src/Render/Mesh.cpp
+++ b/src/Render/Mesh.cpp
@@ -54,14 +54,6 @@ namespace sh::render
return *this;
}
- SH_RENDER_API void Mesh::SetVertex(const std::vector& verts)
- {
- this->verts = verts;
- }
- SH_RENDER_API void Mesh::SetVertex(std::vector&& verts) noexcept
- {
- this->verts = std::move(verts);
- }
SH_RENDER_API void Mesh::SetVertex(const std::initializer_list& verts)
{
this->verts.clear();
@@ -69,23 +61,7 @@ namespace sh::render
for (auto& vert : verts)
this->verts.push_back(vert);
}
- SH_RENDER_API auto Mesh::GetVertex() const -> const std::vector&
- {
- return verts;
- }
- SH_RENDER_API auto Mesh::GetVertexCount() const -> size_t
- {
- return verts.size();
- }
- SH_RENDER_API void Mesh::SetIndices(const std::vector& indices)
- {
- this->indices = indices;
- }
- SH_RENDER_API void Mesh::SetIndices(std::vector&& indices)
- {
- this->indices = std::move(indices);
- }
SH_RENDER_API void Mesh::SetIndices(const std::initializer_list& indices)
{
this->indices.resize(indices.size());
@@ -94,56 +70,14 @@ namespace sh::render
this->indices[i] = *(indices.begin() + i);
}
}
- SH_RENDER_API auto Mesh::GetIndices() const -> const std::vector&
- {
- return indices;
- }
-
- SH_RENDER_API auto Mesh::GetFaces() const -> const std::vector&
- {
- return faces;
- }
SH_RENDER_API void Mesh::Build(const IRenderContext& context)
{
- if (topology == Topology::Face)
- {
- for (int i = 0; i < indices.size(); i += 3)
- {
- Face face{};
- face.vertexIdx[0] = indices[i + 0];
- face.vertexIdx[1] = indices[i + 1];
- face.vertexIdx[2] = indices[i + 2];
- faces.push_back(face);
- }
- }
+ CreateFace();
buffer = VertexBufferFactory::Create(context, *this);
}
- SH_RENDER_API auto Mesh::GetVertexBuffer() const -> IVertexBuffer*
- {
- return buffer.get();
- }
-
- SH_RENDER_API void Mesh::SetTopology(Topology topology)
- {
- this->topology = topology;
- }
-
- SH_RENDER_API auto Mesh::GetTopology() const -> Topology
- {
- return topology;
- }
-
- SH_RENDER_API auto Mesh::GetBoundingBox() const -> const AABB&
- {
- return bounding;
- }
- SH_RENDER_API auto Mesh::GetBoundingBox() -> AABB&
- {
- return bounding;
- }
SH_RENDER_API void Mesh::CalculateTangents()
{
for (size_t i = 0; i < indices.size(); i += 3)
@@ -179,4 +113,23 @@ namespace sh::render
for (auto& v : verts)
v.tangent = glm::normalize(v.tangent);
}
-}
\ No newline at end of file
+
+ SH_RENDER_API void Mesh::SetVertexBuffer(std::unique_ptr buf)
+ {
+ buffer = std::move(buf);
+ }
+ SH_RENDER_API void Mesh::CreateFace()
+ {
+ if (topology == Topology::Face)
+ {
+ for (int i = 0; i < indices.size(); i += 3)
+ {
+ Face face{};
+ face.vertexIdx[0] = indices[i + 0];
+ face.vertexIdx[1] = indices[i + 1];
+ face.vertexIdx[2] = indices[i + 2];
+ faces.push_back(face);
+ }
+ }
+ }
+}//namespace
\ No newline at end of file
diff --git a/src/Render/VulkanImpl/VulkanVertexBuffer.cpp b/src/Render/VulkanImpl/VulkanVertexBuffer.cpp
index 5d125828..fee3ecf9 100644
--- a/src/Render/VulkanImpl/VulkanVertexBuffer.cpp
+++ b/src/Render/VulkanImpl/VulkanVertexBuffer.cpp
@@ -37,6 +37,8 @@ namespace sh::render::vk
}
SH_RENDER_API auto VulkanVertexBuffer::operator=(const VulkanVertexBuffer& other) -> VulkanVertexBuffer&
{
+ if (&other == this)
+ return *this;
vertexBuffer = other.vertexBuffer;
indexBuffer = other.indexBuffer;
@@ -44,6 +46,8 @@ namespace sh::render::vk
}
SH_RENDER_API auto VulkanVertexBuffer::operator=(VulkanVertexBuffer&& other) noexcept ->VulkanVertexBuffer&
{
+ if (&other == this)
+ return *this;
vertexBuffer = std::move(other.vertexBuffer);
indexBuffer = std::move(other.indexBuffer);
return *this;
From 2c70e169d348d41f9b859261e2526f1042c4fbf0 Mon Sep 17 00:00:00 2001
From: Shell <104874910+Shell4026@users.noreply.github.com>
Date: Sat, 18 Apr 2026 05:37:28 +0900
Subject: [PATCH 08/82] refact(Render/Model): replace node tree with flat
array, add Skeleton struct
- Replace recursive unique_ptr children with childrenIdx index array
- Replace rootNode pointer with std::vector flat array
- Remove AddMeshes/GetRootNode; constructor takes node array and collects meshes automatically
- Add skeletonIdx field to Node
- Add Skeleton struct (joints: nodeIdx + inverseBindMat)
---
include/Render/Model.h | 30 ++++++++++++++---------------
include/Render/Skeleton.h | 23 ++++++++++++++++++++++
src/Render/Model.cpp | 40 +++++++++++++--------------------------
3 files changed, 51 insertions(+), 42 deletions(-)
create mode 100644 include/Render/Skeleton.h
diff --git a/include/Render/Model.h b/include/Render/Model.h
index 19043b89..9c37f6fd 100644
--- a/include/Render/Model.h
+++ b/include/Render/Model.h
@@ -1,12 +1,15 @@
#pragma once
#include "Export.h"
#include "Mesh.h"
+#include "Skeleton.h"
#include "Core/SObject.h"
#include "Core/SContainer.hpp"
+#include
#include
#include
+#include
namespace sh::render
{
/// @brief 메쉬의 집합
@@ -17,31 +20,28 @@ namespace sh::render
struct Node
{
std::string name;
- glm::mat4 modelMatrix = glm::mat4{ 1.0f };
+ glm::mat4 modelMatrix{ 1.0f };
Mesh* mesh = nullptr;
- std::vector> children;
+ int skeletonIdx = -1;
+ std::vector childrenIdx;
};
private:
- std::unique_ptr rootNode;
-
+ std::vector nodes;
core::SVector meshes;
+ std::vector skeletons;
public:
- SH_RENDER_API Model();
+ SH_RENDER_API Model(std::vector nodes);
SH_RENDER_API ~Model();
SH_RENDER_API void Destroy() override;
- /// @brief 노드에 있는 모든 메쉬를 추가한다.
- /// @param node 이동할 노드
- SH_RENDER_API void AddMeshes(std::unique_ptr&& node);
- /// @brief 해당 모델의 모든 메쉬를 가져오는 함수.
- /// @return 메쉬 벡터
- SH_RENDER_API auto GetMeshes() const -> const core::SVector&;
- /// @brief 루트 노드를 가져오는 함수.
- /// @return 루트 노드 포인터
- SH_RENDER_API auto GetRootNode() const -> const Node*;
+ SH_RENDER_API void SetSkeletons(std::vector skeletons) { this->skeletons = std::move(skeletons); }
+
+ SH_RENDER_API auto GetMeshes() const -> const core::SVector& { return meshes; }
+ SH_RENDER_API auto GetNodes() const -> const std::vector& { return nodes; }
+ SH_RENDER_API auto GetSkeletons() const -> const std::vector& { return skeletons; }
SH_RENDER_API auto Serialize() const -> core::Json override;
SH_RENDER_API void Deserialize(const core::Json& json) override;
};
-}//namespace
\ No newline at end of file
+}//namespace
diff --git a/include/Render/Skeleton.h b/include/Render/Skeleton.h
new file mode 100644
index 00000000..82838c0b
--- /dev/null
+++ b/include/Render/Skeleton.h
@@ -0,0 +1,23 @@
+#pragma once
+#include "Export.h"
+
+#include
+#include
+#include
+
+namespace sh::render
+{
+ /// @brief SkinnedMeshRenderer가 런타임에 사용하는 스킨 바인드 포즈 데이터.
+ /// 조인트 트랜스폼은 실제 씬의 bone GameObject(Transform)에서 읽는다.
+ struct Skeleton
+ {
+ struct Joint
+ {
+ int nodeIdx = -1;
+ glm::mat4 inverseBindMat{ 1.f };
+ };
+ std::vector joints;
+
+ auto GetJointCount() const -> std::size_t { return joints.size(); }
+ };
+}//namespace
diff --git a/src/Render/Model.cpp b/src/Render/Model.cpp
index f9b5eb7c..4f51287e 100644
--- a/src/Render/Model.cpp
+++ b/src/Render/Model.cpp
@@ -4,9 +4,18 @@
#include
namespace sh::render
{
- Model::Model()
+ Model::Model(std::vector nodes)
{
- rootNode = std::make_unique();
+ if (!nodes.empty())
+ SetName(nodes.front().name);
+
+ this->nodes = std::move(nodes);
+
+ for (Node& node : this->nodes)
+ {
+ if (node.mesh != nullptr)
+ meshes.push_back(node.mesh);
+ }
}
Model::~Model()
{
@@ -22,33 +31,10 @@ namespace sh::render
std::vector a;
a.push_back(0);
}
- SH_RENDER_API void Model::AddMeshes(std::unique_ptr&& node)
- {
- rootNode = std::move(node);
- std::queue q;
- q.push(rootNode.get());
- while (!q.empty())
- {
- Node* curNode = q.front();
- q.pop();
- if (curNode->mesh != nullptr)
- {
- meshes.push_back(curNode->mesh);
- }
- for (auto& child : curNode->children)
- q.push(child.get());
- }
- }
- SH_RENDER_API auto sh::render::Model::GetMeshes() const -> const core::SVector&
- {
- return meshes;
- }
- SH_RENDER_API auto Model::GetRootNode() const -> const Node*
- {
- return rootNode.get();
- }
+
SH_RENDER_API auto Model::Serialize() const -> core::Json
{
+ // 노트 데이터는 직렬화 안 함 (ModelAsset에서 따로 저장)
core::Json mainJson = Super::Serialize();
for (auto mesh : meshes)
{
From e556d5d0f78c6939af7027016c5e7711fb25de9e Mon Sep 17 00:00:00 2001
From: Shell <104874910+Shell4026@users.noreply.github.com>
Date: Sat, 18 Apr 2026 05:37:34 +0900
Subject: [PATCH 09/82] feat(Render/SkinnedMesh): add SkinnedMesh,
VulkanSkinnedVertexBuffer, VertexBufferFactory::CreateSkinned
- SkinnedMesh extends Mesh with per-vertex BoneVertex (boneIndices/boneWeights)
- VulkanSkinnedVertexBuffer uploads binding 0 (Mesh::Vertex) and binding 1 (BoneVertex)
- VertexBufferFactory::CreateSkinned creates VulkanSkinnedVertexBuffer
---
include/Render/SkinnedMesh.h | 41 ++++
include/Render/VertexBufferFactory.h | 2 +
.../VulkanImpl/VulkanSkinnedVertexBuffer.h | 52 +++++
src/Render/SkinnedMesh.cpp | 26 +++
src/Render/VertexBufferFactory.cpp | 13 ++
.../VulkanImpl/VulkanSkinnedVertexBuffer.cpp | 214 ++++++++++++++++++
6 files changed, 348 insertions(+)
create mode 100644 include/Render/SkinnedMesh.h
create mode 100644 include/Render/VulkanImpl/VulkanSkinnedVertexBuffer.h
create mode 100644 src/Render/SkinnedMesh.cpp
create mode 100644 src/Render/VulkanImpl/VulkanSkinnedVertexBuffer.cpp
diff --git a/include/Render/SkinnedMesh.h b/include/Render/SkinnedMesh.h
new file mode 100644
index 00000000..86daa30b
--- /dev/null
+++ b/include/Render/SkinnedMesh.h
@@ -0,0 +1,41 @@
+#pragma once
+#include "Export.h"
+#include "Mesh.h"
+
+#include
+#include
+
+namespace sh::render
+{
+ /// @brief 스켈레탈 애니메이션용 메쉬.
+ /// @brief Mesh::Vertex 데이터에 더해 bone indices/weights를 별도 벡터로 보관한다.
+ /// @brief VulkanSkinnedVertexBuffer가 두 번째 binding으로 업로드한다.
+ class SkinnedMesh : public Mesh
+ {
+ SCLASS(SkinnedMesh)
+ public:
+ struct BoneVertex
+ {
+ glm::ivec4 boneIndices{ 0 };
+ glm::vec4 boneWeights{ 0.0f };
+ };
+
+ static constexpr uint8_t BONE_INDEX_ID = 4;
+ static constexpr uint8_t BONE_WEIGHT_ID = 5;
+ static constexpr uint32_t MAX_BONES = 128;
+ public:
+ SH_RENDER_API SkinnedMesh();
+ SH_RENDER_API SkinnedMesh(const SkinnedMesh& other);
+ SH_RENDER_API SkinnedMesh(SkinnedMesh&& other) noexcept;
+ SH_RENDER_API ~SkinnedMesh();
+
+ /// @brief GPU 버퍼 생성. VulkanSkinnedVertexBuffer를 생성한다.
+ SH_RENDER_API void Build(const IRenderContext& context) override;
+
+ SH_RENDER_API void SetBoneVertices(std::vector bv) { boneVerts = std::move(bv); }
+
+ SH_RENDER_API auto GetBoneVertices() const -> const std::vector& { return boneVerts; }
+ private:
+ std::vector boneVerts;
+ };
+}//namespace
diff --git a/include/Render/VertexBufferFactory.h b/include/Render/VertexBufferFactory.h
index 1ba98e82..dfc91210 100644
--- a/include/Render/VertexBufferFactory.h
+++ b/include/Render/VertexBufferFactory.h
@@ -8,11 +8,13 @@ namespace sh::render
{
class IRenderContext;
class Mesh;
+ class SkinnedMesh;
/// @brief 렌더러 API에 맞는 버텍스 버퍼를 생성하는 클래스
class VertexBufferFactory
{
public:
SH_RENDER_API static auto Create(const IRenderContext& v, const Mesh& mesh) -> std::unique_ptr;
+ SH_RENDER_API static auto CreateSkinned(const IRenderContext& v, const SkinnedMesh& mesh) -> std::unique_ptr;
};
}//namespace
\ No newline at end of file
diff --git a/include/Render/VulkanImpl/VulkanSkinnedVertexBuffer.h b/include/Render/VulkanImpl/VulkanSkinnedVertexBuffer.h
new file mode 100644
index 00000000..6be25094
--- /dev/null
+++ b/include/Render/VulkanImpl/VulkanSkinnedVertexBuffer.h
@@ -0,0 +1,52 @@
+#pragma once
+#include "../Export.h"
+#include "../IVertexBuffer.h"
+#include "VulkanBuffer.h"
+
+#include
+#include
+
+namespace sh::render::vk
+{
+ class VulkanContext;
+
+ /// @brief SkinnedMesh용 버텍스 버퍼.
+ /// binding 0: Mesh::Vertex (location 0-3)
+ /// binding 1: SkinnedMesh::BoneVertex (location 4-5)
+ class VulkanSkinnedVertexBuffer : public IVertexBuffer
+ {
+ public:
+ SH_RENDER_API VulkanSkinnedVertexBuffer(const VulkanContext& context);
+ SH_RENDER_API VulkanSkinnedVertexBuffer(const VulkanSkinnedVertexBuffer& other);
+ SH_RENDER_API VulkanSkinnedVertexBuffer(VulkanSkinnedVertexBuffer&& other) noexcept;
+ SH_RENDER_API ~VulkanSkinnedVertexBuffer();
+
+ SH_RENDER_API auto operator=(const VulkanSkinnedVertexBuffer& other) -> VulkanSkinnedVertexBuffer&;
+ SH_RENDER_API auto operator=(VulkanSkinnedVertexBuffer&& other) noexcept -> VulkanSkinnedVertexBuffer&;
+
+ /// @brief SkinnedMesh로 다운캐스트 후 두 버퍼를 생성한다.
+ SH_RENDER_API void Create(const Mesh& mesh) override;
+ SH_RENDER_API void Clear() override;
+
+ SH_RENDER_API auto Clone() const -> std::unique_ptr override;
+
+ SH_RENDER_API auto GetVertexBuffer() const -> const VulkanBuffer& { return vertexBuffer; }
+ SH_RENDER_API auto GetBoneBuffer() const -> const VulkanBuffer& { return boneBuffer; }
+ SH_RENDER_API auto GetIndexBuffer() const -> const VulkanBuffer& { return indexBuffer; }
+
+ /// @brief binding 0 (Mesh::Vertex), binding 1 (BoneVertex) 두 개를 반환한다.
+ SH_RENDER_API static auto GetBindingDescriptions() -> std::array;
+ /// @brief location 0-5 (위치,UV,노말,탄젠트,boneIdx,boneWeight) 여섯 개를 반환한다.
+ SH_RENDER_API static auto GetAttributeDescriptions() -> std::vector;
+ private:
+ void CreateBuffers(const Mesh& mesh);
+ private:
+ const VulkanContext& context;
+
+ VulkanBuffer vertexBuffer; // binding 0
+ VulkanBuffer boneBuffer; // binding 1
+ VulkanBuffer indexBuffer;
+
+ SH_RENDER_API static inline std::vector attribDescriptions;
+ };
+}//namespace
diff --git a/src/Render/SkinnedMesh.cpp b/src/Render/SkinnedMesh.cpp
new file mode 100644
index 00000000..6102e77d
--- /dev/null
+++ b/src/Render/SkinnedMesh.cpp
@@ -0,0 +1,26 @@
+#include "SkinnedMesh.h"
+#include "VertexBufferFactory.h"
+
+namespace sh::render
+{
+ SkinnedMesh::SkinnedMesh() = default;
+
+ SkinnedMesh::SkinnedMesh(const SkinnedMesh& other) :
+ Mesh(other),
+ boneVerts(other.boneVerts)
+ {
+ }
+ SkinnedMesh::SkinnedMesh(SkinnedMesh&& other) noexcept :
+ Mesh(std::move(other)),
+ boneVerts(std::move(other.boneVerts))
+ {
+ }
+ SkinnedMesh::~SkinnedMesh() = default;
+
+ SH_RENDER_API void SkinnedMesh::Build(const IRenderContext& context)
+ {
+ CreateFace();
+
+ SetVertexBuffer(VertexBufferFactory::CreateSkinned(context, *this));
+ }
+}//namespace
\ No newline at end of file
diff --git a/src/Render/VertexBufferFactory.cpp b/src/Render/VertexBufferFactory.cpp
index 3b062515..7d847b37 100644
--- a/src/Render/VertexBufferFactory.cpp
+++ b/src/Render/VertexBufferFactory.cpp
@@ -2,6 +2,8 @@
#include "VulkanContext.h"
#include "VulkanImpl/VulkanVertexBuffer.h"
+#include "VulkanImpl/VulkanSkinnedVertexBuffer.h"
+#include "SkinnedMesh.h"
#include
namespace sh::render
@@ -17,4 +19,15 @@ namespace sh::render
}
return nullptr;
}
+ auto sh::render::VertexBufferFactory::CreateSkinned(const IRenderContext& context, const SkinnedMesh& mesh) -> std::unique_ptr
+ {
+ assert(context.GetRenderAPIType() == RenderAPI::Vulkan);
+ if (context.GetRenderAPIType() == RenderAPI::Vulkan)
+ {
+ auto buffer = std::make_unique(static_cast(context));
+ buffer->Create(mesh);
+ return buffer;
+ }
+ return nullptr;
+ }
}//namespace
\ No newline at end of file
diff --git a/src/Render/VulkanImpl/VulkanSkinnedVertexBuffer.cpp b/src/Render/VulkanImpl/VulkanSkinnedVertexBuffer.cpp
new file mode 100644
index 00000000..467840df
--- /dev/null
+++ b/src/Render/VulkanImpl/VulkanSkinnedVertexBuffer.cpp
@@ -0,0 +1,214 @@
+#include "VulkanSkinnedVertexBuffer.h"
+#include "VulkanContext.h"
+#include "VulkanQueueManager.h"
+#include "VulkanCommandBuffer.h"
+#include "VulkanCommandBufferPool.h"
+#include "Mesh.h"
+#include "SkinnedMesh.h"
+
+#include
+#include
+#include
+#include
+
+namespace sh::render::vk
+{
+ VulkanSkinnedVertexBuffer::VulkanSkinnedVertexBuffer(const VulkanContext& context) :
+ context(context),
+ vertexBuffer(context),
+ boneBuffer(context),
+ indexBuffer(context)
+ {
+ }
+ VulkanSkinnedVertexBuffer::VulkanSkinnedVertexBuffer(const VulkanSkinnedVertexBuffer& other) :
+ context(other.context),
+ vertexBuffer(other.vertexBuffer),
+ boneBuffer(other.boneBuffer),
+ indexBuffer(other.indexBuffer)
+ {
+ }
+ VulkanSkinnedVertexBuffer::VulkanSkinnedVertexBuffer(VulkanSkinnedVertexBuffer&& other) noexcept :
+ context(other.context),
+ vertexBuffer(std::move(other.vertexBuffer)),
+ boneBuffer(std::move(other.boneBuffer)),
+ indexBuffer(std::move(other.indexBuffer))
+ {
+ }
+ VulkanSkinnedVertexBuffer::~VulkanSkinnedVertexBuffer()
+ {
+ Clear();
+ }
+ SH_RENDER_API auto VulkanSkinnedVertexBuffer::operator=(const VulkanSkinnedVertexBuffer& other) -> VulkanSkinnedVertexBuffer&
+ {
+ if (&other == this)
+ return *this;
+ vertexBuffer = other.vertexBuffer;
+ boneBuffer = other.boneBuffer;
+ indexBuffer = other.indexBuffer;
+ return *this;
+ }
+ SH_RENDER_API auto VulkanSkinnedVertexBuffer::operator=(VulkanSkinnedVertexBuffer&& other) noexcept -> VulkanSkinnedVertexBuffer&
+ {
+ if (&other == this)
+ return *this;
+ vertexBuffer = std::move(other.vertexBuffer);
+ boneBuffer = std::move(other.boneBuffer);
+ indexBuffer = std::move(other.indexBuffer);
+ return *this;
+ }
+ SH_RENDER_API void VulkanSkinnedVertexBuffer::Clear()
+ {
+ vertexBuffer.Clean();
+ boneBuffer.Clean();
+ indexBuffer.Clean();
+ }
+ SH_RENDER_API void VulkanSkinnedVertexBuffer::Create(const Mesh& mesh)
+ {
+ Clear();
+ CreateBuffers(mesh);
+ }
+ SH_RENDER_API auto VulkanSkinnedVertexBuffer::Clone() const -> std::unique_ptr
+ {
+ return std::make_unique(*this);
+ }
+ SH_RENDER_API auto VulkanSkinnedVertexBuffer::GetBindingDescriptions() -> std::array
+ {
+ std::array bindings{};
+ // binding 0: Mesh::Vertex
+ bindings[0].binding = 0;
+ bindings[0].stride = sizeof(Mesh::Vertex);
+ bindings[0].inputRate = VkVertexInputRate::VK_VERTEX_INPUT_RATE_VERTEX;
+ // binding 1: SkinnedMesh::BoneVertex
+ bindings[1].binding = 1;
+ bindings[1].stride = sizeof(SkinnedMesh::BoneVertex);
+ bindings[1].inputRate = VkVertexInputRate::VK_VERTEX_INPUT_RATE_VERTEX;
+ return bindings;
+ }
+ SH_RENDER_API auto VulkanSkinnedVertexBuffer::GetAttributeDescriptions() -> std::vector
+ {
+ if (attribDescriptions.empty())
+ {
+ VkVertexInputAttributeDescription attrDesc{};
+
+ // binding 0: Mesh::Vertex 속성 (location 0-3)
+ attrDesc.binding = 0;
+ attrDesc.location = Mesh::VERTEX_ID; // 0
+ attrDesc.format = VkFormat::VK_FORMAT_R32G32B32_SFLOAT;
+ attrDesc.offset = offsetof(Mesh::Vertex, vertex);
+ attribDescriptions.push_back(attrDesc);
+
+ attrDesc.location = Mesh::UV_ID; // 1
+ attrDesc.format = VkFormat::VK_FORMAT_R32G32_SFLOAT;
+ attrDesc.offset = offsetof(Mesh::Vertex, uv);
+ attribDescriptions.push_back(attrDesc);
+
+ attrDesc.location = Mesh::NORMAL_ID; // 2
+ attrDesc.format = VkFormat::VK_FORMAT_R32G32B32_SFLOAT;
+ attrDesc.offset = offsetof(Mesh::Vertex, normal);
+ attribDescriptions.push_back(attrDesc);
+
+ attrDesc.location = Mesh::TANGENT_ID; // 3
+ attrDesc.format = VkFormat::VK_FORMAT_R32G32B32_SFLOAT;
+ attrDesc.offset = offsetof(Mesh::Vertex, tangent);
+ attribDescriptions.push_back(attrDesc);
+
+ // binding 1: BoneVertex 속성 (location 4-5)
+ attrDesc.binding = 1;
+ attrDesc.location = SkinnedMesh::BONE_INDEX_ID; // 4
+ attrDesc.format = VkFormat::VK_FORMAT_R32G32B32A32_SINT;
+ attrDesc.offset = offsetof(SkinnedMesh::BoneVertex, boneIndices);
+ attribDescriptions.push_back(attrDesc);
+
+ attrDesc.location = SkinnedMesh::BONE_WEIGHT_ID; // 5
+ attrDesc.format = VkFormat::VK_FORMAT_R32G32B32A32_SFLOAT;
+ attrDesc.offset = offsetof(SkinnedMesh::BoneVertex, boneWeights);
+ attribDescriptions.push_back(attrDesc);
+ }
+ return attribDescriptions;
+ }
+
+ void VulkanSkinnedVertexBuffer::CreateBuffers(const Mesh& mesh)
+ {
+ if (mesh.GetVertexCount() == 0)
+ return;
+
+ const SkinnedMesh* skinnedMesh = static_cast(&mesh);
+
+ VulkanCommandBuffer* cmd = context.GetCommandBufferPool().AllocateCommandBuffer(
+ std::this_thread::get_id(), VkQueueFlagBits::VK_QUEUE_TRANSFER_BIT);
+ assert(cmd != nullptr);
+
+ // Mesh::Vertex 버퍼
+ size_t vertexBufferSize = sizeof(Mesh::Vertex) * mesh.GetVertexCount();
+ VulkanBuffer stagingVert{ context };
+ stagingVert.Create(vertexBufferSize,
+ VkBufferUsageFlagBits::VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+ VkSharingMode::VK_SHARING_MODE_EXCLUSIVE,
+ VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+ stagingVert.SetData(mesh.GetVertex().data());
+
+ vertexBuffer.Create(vertexBufferSize,
+ VkBufferUsageFlagBits::VK_BUFFER_USAGE_TRANSFER_DST_BIT | VkBufferUsageFlagBits::VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
+ VkSharingMode::VK_SHARING_MODE_EXCLUSIVE,
+ VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+
+ // BoneVertex 버퍼
+ const auto& boneVerts = skinnedMesh->GetBoneVertices();
+ size_t boneBufferSize = sizeof(SkinnedMesh::BoneVertex) * boneVerts.size();
+ VulkanBuffer stagingBone{ context };
+ if (boneBufferSize > 0)
+ {
+ stagingBone.Create(boneBufferSize,
+ VkBufferUsageFlagBits::VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+ VkSharingMode::VK_SHARING_MODE_EXCLUSIVE,
+ VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+ stagingBone.SetData(boneVerts.data());
+
+ boneBuffer.Create(boneBufferSize,
+ VkBufferUsageFlagBits::VK_BUFFER_USAGE_TRANSFER_DST_BIT | VkBufferUsageFlagBits::VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
+ VkSharingMode::VK_SHARING_MODE_EXCLUSIVE,
+ VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+ }
+
+ // Index 버퍼
+ size_t indicesSize = sizeof(uint32_t) * mesh.GetIndices().size();
+ VulkanBuffer stagingIdx{ context };
+ stagingIdx.Create(indicesSize,
+ VkBufferUsageFlagBits::VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+ VkSharingMode::VK_SHARING_MODE_EXCLUSIVE,
+ VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+ stagingIdx.SetData(mesh.GetIndices().data());
+
+ indexBuffer.Create(indicesSize,
+ VkBufferUsageFlagBits::VK_BUFFER_USAGE_TRANSFER_DST_BIT | VkBufferUsageFlagBits::VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
+ VkSharingMode::VK_SHARING_MODE_EXCLUSIVE,
+ VkMemoryPropertyFlagBits::VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+
+ // 단일 커맨드로 모두 전송
+ cmd->Build(
+ [&]()
+ {
+ VkBufferCopy cpy{};
+ cpy.size = vertexBufferSize;
+ vkCmdCopyBuffer(cmd->GetCommandBuffer(), stagingVert.GetBuffer(), vertexBuffer.GetBuffer(), 1, &cpy);
+
+ if (boneBufferSize > 0)
+ {
+ cpy.size = boneBufferSize;
+ vkCmdCopyBuffer(cmd->GetCommandBuffer(), stagingBone.GetBuffer(), boneBuffer.GetBuffer(), 1, &cpy);
+ }
+
+ cpy.size = indicesSize;
+ vkCmdCopyBuffer(cmd->GetCommandBuffer(), stagingIdx.GetBuffer(), indexBuffer.GetBuffer(), 1, &cpy);
+ },
+ true
+ );
+
+ VkFence fence = cmd->GetOrCreateFence();
+ context.GetQueueManager().Submit(VulkanQueueManager::Role::Transfer, *cmd, fence);
+ vkWaitForFences(context.GetDevice(), 1, &fence, true, std::numeric_limits::max());
+ vkResetFences(context.GetDevice(), 1, &fence);
+
+ context.GetCommandBufferPool().DeallocateCommandBuffer(*cmd);
+ }
+}//namespace
From b5f3d00f31118fe18ef248c7f9b0a46df38a975e Mon Sep 17 00:00:00 2001
From: Shell <104874910+Shell4026@users.noreply.github.com>
Date: Sat, 18 Apr 2026 05:37:57 +0900
Subject: [PATCH 10/82] refact(Render/Shader): add bone tokens, replace
bUseLighting with lightingBinding
- Add IVec4 type to ShaderAST
- Add BONE_WEIGHTS and BONE_INDICES tokens to ShaderLexer
- Parse bone input attributes in ShaderParser; record lightingBinding index
- Replace bUseLighting(bool) with lightingBinding(int, -1 = unused) in ShaderPass
- Inline ShaderPass getters; fix operator= return type
- Handle skinned mesh pipeline variant in VulkanPipelineManager and VulkanRenderImpl
---
include/Render/ShaderAST.h | 3 +-
include/Render/ShaderLexer.h | 2 +-
include/Render/ShaderPass.h | 35 +--
.../Render/VulkanImpl/VulkanPipelineManager.h | 13 +-
src/Render/MaterialData.cpp | 2 +-
src/Render/Shader.cpp | 4 +-
src/Render/ShaderAST.cpp | 4 +-
src/Render/ShaderLexer.cpp | 2 +
src/Render/ShaderParser.cpp | 181 +++++--------
src/Render/ShaderPass.cpp | 252 ++++++++----------
.../VulkanImpl/VulkanPipelineManager.cpp | 30 ++-
src/Render/VulkanImpl/VulkanRenderImpl.cpp | 54 +++-
12 files changed, 273 insertions(+), 309 deletions(-)
diff --git a/include/Render/ShaderAST.h b/include/Render/ShaderAST.h
index ede26039..54ee444e 100644
--- a/include/Render/ShaderAST.h
+++ b/include/Render/ShaderAST.h
@@ -27,6 +27,7 @@ namespace sh::render
Vec4,
Vec3,
Vec2,
+ IVec4,
Float,
Int,
Sampler,
@@ -98,7 +99,7 @@ namespace sh::render
std::vector declaration;
std::vector functions;
std::string code;
- bool bUseLighting = false;
+ int lightingBinding = -1;
SH_RENDER_API auto Serialize() const -> core::Json override;
SH_RENDER_API void Deserialize(const core::Json& json) override;
diff --git a/include/Render/ShaderLexer.h b/include/Render/ShaderLexer.h
index 831bffb2..0b38e6cb 100644
--- a/include/Render/ShaderLexer.h
+++ b/include/Render/ShaderLexer.h
@@ -16,7 +16,7 @@ namespace sh::render
Vertex, Fragment,
Layout, Uniform, In, Out, Sampler2D, Constexpr,
Const,
- VERTEX, UV, NORMAL, TANGENT, MVP, LIGHT,
+ VERTEX, UV, NORMAL, TANGENT, MVP, LIGHT, BONE_WEIGHTS, BONE_INDICES,
MATRIX_MODEL, MATRIX_VIEW, MATRIX_PROJ,
LBracket, // (
RBracket, // )
diff --git a/include/Render/ShaderPass.h b/include/Render/ShaderPass.h
index 928cb9c2..8f3158ad 100644
--- a/include/Render/ShaderPass.h
+++ b/include/Render/ShaderPass.h
@@ -45,30 +45,31 @@ namespace sh::render
};
public:
SH_RENDER_API virtual ~ShaderPass();
- SH_RENDER_API void operator=(ShaderPass&& other) noexcept;
+ SH_RENDER_API auto operator=(ShaderPass&& other) noexcept -> ShaderPass&;
SH_RENDER_API virtual void Clear() = 0;
SH_RENDER_API virtual void Build() = 0;
SH_RENDER_API auto HasUniformMember(const std::string& name, ShaderStage stage) const -> const UniformStructLayout*;
- SH_RENDER_API auto GetStencilState() const -> const StencilState&;
- SH_RENDER_API auto GetCullMode() const->CullMode;
- SH_RENDER_API auto GetZWrite() const -> bool;
+ SH_RENDER_API auto GetStencilState() const -> const StencilState& { return stencilState; }
+ SH_RENDER_API auto GetCullMode() const -> CullMode { return cull; }
+ SH_RENDER_API auto GetZWrite() const -> bool { return bZWrite; }
SH_RENDER_API auto GetZTest() const -> bool { return bZTest; }
/// @brief RGBA전부 쓰기면 0b1111, R만 쓰기면 0b0001, G만 쓴다면 0b0010, B만 쓴다면 0b0100, A만 쓴다면 0b1000
/// @return 컬러 마스크 값
- SH_RENDER_API auto GetColorMask() const -> uint8_t;
- SH_RENDER_API auto GetLightingPassName() const -> const core::Name&;
- SH_RENDER_API auto GetId() const -> int;
- SH_RENDER_API auto GetShaderType() const->ShaderType;
- SH_RENDER_API auto GetAttributes() const -> const std::vector&;
- SH_RENDER_API auto GetVertexUniforms() const -> const std::vector&;
- SH_RENDER_API auto GetFragmentUniforms() const -> const std::vector&;
- SH_RENDER_API auto GetSamplerUniforms() const -> const std::vector&;
- SH_RENDER_API auto HasConstantUniform() const -> bool;
- SH_RENDER_API auto IsUsingLight() const -> bool;
- SH_RENDER_API auto GetConstants() const -> const std::unordered_map&;
+ SH_RENDER_API auto GetColorMask() const -> uint8_t { return colorMask; }
+ SH_RENDER_API auto GetLightingPassName() const -> const core::Name& { return lightingPassName; }
+ SH_RENDER_API auto GetShaderType() const -> ShaderType { return type; }
+ SH_RENDER_API auto GetAttributes() const -> const std::vector& { return attrs; }
+ SH_RENDER_API auto GetVertexUniforms() const -> const std::vector& { return vertexUniforms; }
+ SH_RENDER_API auto GetFragmentUniforms() const -> const std::vector& { return fragmentUniforms; }
+ SH_RENDER_API auto GetSamplerUniforms() const -> const std::vector& { return samplerUniforms; }
+ SH_RENDER_API auto HasConstantUniform() const -> bool { return bHasConstant; }
+ /// @brief 라이트 유니폼의 바인딩 번호를 리턴한다.
+ /// @return 라이팅을 안 쓸 시 -1
+ SH_RENDER_API auto GetLightingBinding() const -> int { return lightingBinding; }
+ SH_RENDER_API auto GetConstants() const -> const std::unordered_map& { return constantNameMap; }
SH_RENDER_API auto GetConstantsInfo(const std::string& name) const -> const ConstantInfo*;
SH_RENDER_API auto GetConstantSize() const -> std::size_t;
@@ -115,10 +116,10 @@ namespace sh::render
core::Name lightingPassName;
uint8_t colorMask = 7; //0b111
std::size_t constantSize = 0;
+ int lightingBinding = -1;
bool bZWrite = true;
bool bZTest = true;
bool bHasConstant = false;
- bool bUseLighting = false;
};
template
@@ -143,4 +144,4 @@ namespace sh::render
return true;
}
-}
\ No newline at end of file
+}//namespace
\ No newline at end of file
diff --git a/include/Render/VulkanImpl/VulkanPipelineManager.h b/include/Render/VulkanImpl/VulkanPipelineManager.h
index d5af5fa4..ca91840e 100644
--- a/include/Render/VulkanImpl/VulkanPipelineManager.h
+++ b/include/Render/VulkanImpl/VulkanPipelineManager.h
@@ -43,15 +43,17 @@ namespace sh::render::vk
SH_RENDER_API VulkanPipelineManager(const VulkanContext& context);
SH_RENDER_API VulkanPipelineManager(VulkanPipelineManager&& other) = delete;
/// @brief 파이프라인을 생성하거나 가져온다. 스레드 안전하다.
- /// @param pass 렌더 패스
/// @param shader 셰이더
+ /// @param renderTargetLayout 렌더 타겟
/// @param topology 메쉬 토폴로지
+ /// @param bSkinned 스키닝 메쉬인지
/// @param constPtr 상수 데이터 포인터
/// @return 파이프라인 핸들
SH_RENDER_API auto GetOrCreatePipelineHandle(
const VulkanShaderPass& shader,
- const RenderTargetLayout& renderTargetLayout,
- Mesh::Topology topology,
+ const RenderTargetLayout& renderTargetLayout,
+ Mesh::Topology topology,
+ bool bSkinned = false,
const std::vector* constDataPtr = nullptr) -> PipelineHandle;
/// @brief vkCmdBindPipeline()의 래퍼 함수. 스레드 안전하다.
@@ -63,6 +65,7 @@ namespace sh::render::vk
const VulkanShaderPass& shader,
const RenderTargetLayout& renderTargetLayout,
Mesh::Topology topology,
+ bool bSkinned,
const std::vector* constDataPtr) -> std::unique_ptr;
auto ConvertStencilState(const StencilState& stencilState) const->VkStencilOpState;
@@ -73,10 +76,11 @@ namespace sh::render::vk
RenderTargetLayout renderTargetLayout;
Mesh::Topology topology;
std::size_t constantHash = 0;
+ bool bSkinned = false;
bool operator==(const PipelineInfo& other) const
{
- return renderTargetLayout == other.renderTargetLayout && shader == other.shader && topology == other.topology && constantHash == other.constantHash;
+ return renderTargetLayout == other.renderTargetLayout && shader == other.shader && topology == other.topology && constantHash == other.constantHash && bSkinned == other.bSkinned;
}
};
struct PipelineInfoHasher
@@ -93,6 +97,7 @@ namespace sh::render::vk
hash = Util::CombineHash(hash, hasher(info.shader));
hash = Util::CombineHash(hash, intHasher(static_cast(info.topology)));
hash = Util::CombineHash(hash, sizeHasher(info.constantHash));
+ hash = Util::CombineHash(hash, std::hash{}(info.bSkinned));
return hash;
}
diff --git a/src/Render/MaterialData.cpp b/src/Render/MaterialData.cpp
index 3d478e35..97c6bc8b 100644
--- a/src/Render/MaterialData.cpp
+++ b/src/Render/MaterialData.cpp
@@ -60,7 +60,7 @@ namespace sh::render
for (const auto& syncData : syncDatas)
{
- if (syncData.data.index() == 0) // 버퍼
+ if (std::holds_alternative(syncData.data)) // 버퍼
{
auto& bufferData = std::get<0>(syncData.data);
SetUniformDataAtSync(bufferData);
diff --git a/src/Render/Shader.cpp b/src/Render/Shader.cpp
index 46f4c12c..6e22c7c2 100644
--- a/src/Render/Shader.cpp
+++ b/src/Render/Shader.cpp
@@ -125,7 +125,7 @@ namespace sh::render
this->passes = std::move(passes);
for (ShaderPass* pass : this->passes)
{
- if (pass->IsUsingLight())
+ if (pass->GetLightingBinding() != -1)
bUsingLight = true;
const core::Name& lightingPassName = this->passes.back()->GetLightingPassName();
@@ -145,7 +145,7 @@ namespace sh::render
}
void Shader::AddShaderPass(ShaderPass* pass)
{
- if (pass->IsUsingLight())
+ if (pass->GetLightingBinding() != -1)
bUsingLight = true;
passes.push_back(pass);
diff --git a/src/Render/ShaderAST.cpp b/src/Render/ShaderAST.cpp
index fab49c37..2013940c 100644
--- a/src/Render/ShaderAST.cpp
+++ b/src/Render/ShaderAST.cpp
@@ -76,7 +76,7 @@ namespace sh::render
{
core::Json json;
json["type"] = static_cast(type);
- json["bUseLighting"] = bUseLighting;
+ json["lightingBinding"] = lightingBinding;
json["code"] = code;
// in
@@ -109,7 +109,7 @@ namespace sh::render
void ShaderAST::StageNode::Deserialize(const core::Json& json)
{
type = static_cast(json.at("type").get());
- bUseLighting = json.at("bUseLighting").get();
+ lightingBinding = json.value("lightingBinding", -1);
code = json.at("code").get();
// in
diff --git a/src/Render/ShaderLexer.cpp b/src/Render/ShaderLexer.cpp
index 423ef2e7..4f5540fb 100644
--- a/src/Render/ShaderLexer.cpp
+++ b/src/Render/ShaderLexer.cpp
@@ -308,6 +308,8 @@ namespace sh::render
{"MATRIX_MODEL", TokenType::MATRIX_MODEL},
{"MATRIX_VIEW", TokenType::MATRIX_VIEW},
{"MATRIX_PROJ", TokenType::MATRIX_PROJ},
+ {"BONE_WEIGHTS", TokenType::BONE_WEIGHTS},
+ {"BONE_INDICES", TokenType::BONE_INDICES},
};
auto it = keywordMap.find(ident);
if (it == keywordMap.end())
diff --git a/src/Render/ShaderParser.cpp b/src/Render/ShaderParser.cpp
index 2b85c2c3..977b693c 100644
--- a/src/Render/ShaderParser.cpp
+++ b/src/Render/ShaderParser.cpp
@@ -2,6 +2,7 @@
#include "StencilState.h"
#include "UniformStructLayout.h"
#include "Mesh.h"
+#include "SkinnedMesh.h"
#include
@@ -92,6 +93,8 @@ namespace sh::render
return "vec3";
case ShaderAST::VariableType::Vec2:
return "vec2";
+ case ShaderAST::VariableType::IVec4:
+ return "ivec4";
case ShaderAST::VariableType::Float:
return "float";
case ShaderAST::VariableType::Int:
@@ -584,10 +587,41 @@ namespace sh::render
auto ShaderParser::ParseFunctionBody(ShaderAST::StageNode& stageNode) -> std::string
{
- auto uboit = std::find_if(stageNode.uniforms.begin(), stageNode.uniforms.end(), [&](const ShaderAST::UBONode& ubo)
+ // uniforms 벡터 수정 후 UBO 이터레이터를 갱신하는 헬퍼
+ auto refreshUboIt = [&]()
{
- return ubo.name == "UBO";
- });
+ return std::find_if(stageNode.uniforms.begin(), stageNode.uniforms.end(),
+ [](const ShaderAST::UBONode& ubo)
+ {
+ return ubo.name == "UBO";
+ }
+ );
+ };
+
+ // 버텍스 입력 어트리뷰트를 한 번만 등록하는 헬퍼
+ auto ensureInput = [&](bool& bRegistered, const char* name, int binding, ShaderAST::VariableType type)
+ {
+ if (bRegistered)
+ return;
+ auto it = std::find_if(stageNode.in.begin(), stageNode.in.end(),
+ [name](const ShaderAST::LayoutNode& l)
+ {
+ return l.var.name == name;
+ }
+ );
+ if (it == stageNode.in.end())
+ {
+ ShaderAST::LayoutNode layoutNode{};
+ layoutNode.binding = binding;
+ layoutNode.var.name = name;
+ layoutNode.var.type = type;
+ layoutNode.var.size = 1;
+ stageNode.in.push_back(std::move(layoutNode));
+ }
+ bRegistered = true;
+ };
+
+ auto uboit = refreshUboIt();
std::string code{};
int nested = 1;
@@ -595,16 +629,18 @@ namespace sh::render
bool usingNormal = false;
bool usingUV = false;
bool usingTangent = false;
+ bool usingBoneWeights = false;
+ bool usingBoneIndices = false;
bool usingMatrixModel = false;
bool usingCamera = false;
bool usingLIGHT = false;
+
while (nested != 0 || PeekToken().type != ShaderLexer::TokenType::EndOfFile)
{
auto& token = PeekToken();
if (token.type == ShaderLexer::TokenType::RBrace)
{
- --nested;
- if (nested == 0)
+ if (--nested == 0)
break;
}
else if (token.type == ShaderLexer::TokenType::LBrace)
@@ -615,101 +651,23 @@ namespace sh::render
code.pop_back();
}
else if (CheckToken(ShaderLexer::TokenType::VERTEX))
- {
- if (!usingVertex)
- {
- auto it = std::find_if(stageNode.in.begin(), stageNode.in.end(), [&](const ShaderAST::LayoutNode& layoutNode)
- {
- return layoutNode.var.name == "VERTEX";
- });
- if (it == stageNode.in.end())
- {
- ShaderAST::LayoutNode layoutNode{};
- layoutNode.binding = Mesh::VERTEX_ID;
- layoutNode.var.name = "VERTEX";
- layoutNode.var.type = ShaderAST::VariableType::Vec3;
- layoutNode.var.size = 1;
- stageNode.in.push_back(std::move(layoutNode));
- usingVertex = true;
- }
- else
- usingVertex = true;
- }
- }
+ ensureInput(usingVertex, "VERTEX", Mesh::VERTEX_ID, ShaderAST::VariableType::Vec3);
else if (CheckToken(ShaderLexer::TokenType::UV))
- {
- if (!usingUV)
- {
- auto it = std::find_if(stageNode.in.begin(), stageNode.in.end(), [&](const ShaderAST::LayoutNode& layoutNode)
- {
- return layoutNode.var.name == "UV";
- });
- if (it == stageNode.in.end())
- {
- ShaderAST::LayoutNode layoutNode{};
- layoutNode.binding = Mesh::UV_ID;
- layoutNode.var.name = "UV";
- layoutNode.var.type = ShaderAST::VariableType::Vec2;
- layoutNode.var.size = 1;
- stageNode.in.push_back(std::move(layoutNode));
- usingUV = true;
- }
- else
- usingUV = true;
- }
- }
+ ensureInput(usingUV, "UV", Mesh::UV_ID, ShaderAST::VariableType::Vec2);
else if (CheckToken(ShaderLexer::TokenType::NORMAL))
- {
- if (!usingNormal)
- {
- auto it = std::find_if(stageNode.in.begin(), stageNode.in.end(), [&](const ShaderAST::LayoutNode& layoutNode)
- {
- return layoutNode.var.name == "NORMAL";
- });
- if (it == stageNode.in.end())
- {
- ShaderAST::LayoutNode layoutNode{};
- layoutNode.binding = Mesh::NORMAL_ID;
- layoutNode.var.name = "NORMAL";
- layoutNode.var.type = ShaderAST::VariableType::Vec3;
- layoutNode.var.size = 1;
- stageNode.in.push_back(std::move(layoutNode));
- usingNormal = true;
- }
- else
- usingNormal = true;
- }
- }
+ ensureInput(usingNormal, "NORMAL", Mesh::NORMAL_ID, ShaderAST::VariableType::Vec3);
else if (CheckToken(ShaderLexer::TokenType::TANGENT))
- {
- if (!usingTangent)
- {
- auto it = std::find_if(stageNode.in.begin(), stageNode.in.end(), [&](const ShaderAST::LayoutNode& layoutNode)
- {
- return layoutNode.var.name == "TANGENT";
- });
- if (it == stageNode.in.end())
- {
- ShaderAST::LayoutNode layoutNode{};
- layoutNode.binding = Mesh::TANGENT_ID;
- layoutNode.var.name = "TANGENT";
- layoutNode.var.type = ShaderAST::VariableType::Vec3;
- layoutNode.var.size = 1;
- stageNode.in.push_back(std::move(layoutNode));
- usingTangent = true;
- }
- else
- usingTangent = true;
- }
- }
+ ensureInput(usingTangent, "TANGENT", Mesh::TANGENT_ID, ShaderAST::VariableType::Vec3);
+ else if (CheckToken(ShaderLexer::TokenType::BONE_WEIGHTS))
+ ensureInput(usingBoneWeights, "BONE_WEIGHTS", SkinnedMesh::BONE_WEIGHT_ID, ShaderAST::VariableType::Vec4);
+ else if (CheckToken(ShaderLexer::TokenType::BONE_INDICES))
+ ensureInput(usingBoneIndices, "BONE_INDICES", SkinnedMesh::BONE_INDEX_ID, ShaderAST::VariableType::IVec4);
else if (CheckToken(ShaderLexer::TokenType::MATRIX_VIEW) || CheckToken(ShaderLexer::TokenType::MATRIX_PROJ))
{
if (!usingCamera)
{
- auto it = std::find_if(stageNode.uniforms.begin(), stageNode.uniforms.end(), [&](const ShaderAST::UBONode& ubo)
- {
- return ubo.name == "CAMERA";
- });
+ auto it = std::find_if(stageNode.uniforms.begin(), stageNode.uniforms.end(),
+ [](const ShaderAST::UBONode& ubo) { return ubo.name == "CAMERA"; });
if (it == stageNode.uniforms.end())
{
ShaderAST::UBONode uboNode{};
@@ -720,10 +678,7 @@ namespace sh::render
uboNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Mat4, 1, "view" });
uboNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Mat4, 1, "proj" });
stageNode.uniforms.push_back(std::move(uboNode));
- uboit = std::find_if(stageNode.uniforms.begin(), stageNode.uniforms.end(), [&](const ShaderAST::UBONode& ubo)
- {
- return ubo.name == "UBO";
- });
+ uboit = refreshUboIt();
}
usingCamera = true;
}
@@ -732,10 +687,8 @@ namespace sh::render
{
if (!usingMatrixModel)
{
- auto it = std::find_if(stageNode.uniforms.begin(), stageNode.uniforms.end(), [&](const ShaderAST::UBONode& ubo)
- {
- return ubo.name == "CONSTANTS";
- });
+ auto it = std::find_if(stageNode.uniforms.begin(), stageNode.uniforms.end(),
+ [](const ShaderAST::UBONode& ubo) { return ubo.name == "CONSTANTS"; });
if (it == stageNode.uniforms.end())
{
ShaderAST::UBONode uboNode{};
@@ -746,10 +699,7 @@ namespace sh::render
uboNode.bSampler = false;
uboNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Mat4, 1, "model" });
stageNode.uniforms.push_back(std::move(uboNode));
- uboit = std::find_if(stageNode.uniforms.begin(), stageNode.uniforms.end(), [&](const ShaderAST::UBONode& ubo)
- {
- return ubo.name == "UBO";
- });
+ uboit = refreshUboIt();
}
usingMatrixModel = true;
}
@@ -758,10 +708,8 @@ namespace sh::render
{
if (!usingLIGHT)
{
- auto it = std::find_if(stageNode.uniforms.begin(), stageNode.uniforms.end(), [&](const ShaderAST::UBONode& ubo)
- {
- return ubo.name == "LIGHT";
- });
+ auto it = std::find_if(stageNode.uniforms.begin(), stageNode.uniforms.end(),
+ [](const ShaderAST::UBONode& ubo) { return ubo.name == "LIGHT"; });
if (it == stageNode.uniforms.end())
{
ShaderAST::UBONode uboNode{};
@@ -773,23 +721,18 @@ namespace sh::render
uboNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Vec4, 10, "pos" });
uboNode.vars.push_back(ShaderAST::VariableNode{ ShaderAST::VariableType::Vec4, 10, "other" });
stageNode.uniforms.push_back(std::move(uboNode));
- stageNode.bUseLighting = true;
- usingLIGHT = true;
- uboit = std::find_if(stageNode.uniforms.begin(), stageNode.uniforms.end(), [&](const ShaderAST::UBONode& ubo)
- {
- return ubo.name == "UBO";
- });
+ stageNode.lightingBinding = uboNode.binding;
+ uboit = refreshUboIt();
}
+ usingLIGHT = true;
}
}
else if (token.type == ShaderLexer::TokenType::Identifier)
{
if (uboit != stageNode.uniforms.end())
{
- auto varit = std::find_if(uboit->vars.begin(), uboit->vars.end(), [&](const ShaderAST::VariableNode& var)
- {
- return var.name == token.text;
- });
+ auto varit = std::find_if(uboit->vars.begin(), uboit->vars.end(),
+ [&](const ShaderAST::VariableNode& var) { return var.name == token.text; });
if (varit != uboit->vars.end())
code += "UBO.";
}
diff --git a/src/Render/ShaderPass.cpp b/src/Render/ShaderPass.cpp
index 3bbee39b..2afe9cf2 100644
--- a/src/Render/ShaderPass.cpp
+++ b/src/Render/ShaderPass.cpp
@@ -2,71 +2,6 @@
namespace sh::render
{
- void ShaderPass::FillAttributes(const render::ShaderAST::PassNode& passNode)
- {
- for (auto& stage : passNode.stages)
- {
- auto stageType = (stage.type == render::ShaderAST::StageType::Vertex) ?
- render::ShaderStage::Vertex :
- render::ShaderStage::Fragment;
-
- if (stage.type == render::ShaderAST::StageType::Vertex)
- {
- for (auto& in : stage.in)
- {
- switch (in.var.type)
- {
- case render::ShaderAST::VariableType::Vec4: AddAttribute(in.var.name, in.binding); break;
- case render::ShaderAST::VariableType::Vec3: AddAttribute(in.var.name, in.binding); break;
- case render::ShaderAST::VariableType::Vec2: AddAttribute(in.var.name, in.binding); break;
- case render::ShaderAST::VariableType::Mat4: AddAttribute(in.var.name, in.binding); break;
- case render::ShaderAST::VariableType::Mat3: AddAttribute(in.var.name, in.binding); break;
- case render::ShaderAST::VariableType::Float: AddAttribute< float>(in.var.name, in.binding); break;
- case render::ShaderAST::VariableType::Int: AddAttribute< int>(in.var.name, in.binding); break;
- }
- }
- }
- for (auto& uniform : stage.uniforms)
- {
- UniformStructLayout::Type uniformType = static_cast(uniform.set);
-
- render::UniformStructLayout uniformLayout{ uniform.name, uniform.binding, uniformType, stageType, uniform.bConstant };
-
- bHasConstant |= uniform.bConstant;
-
- if (!uniform.bSampler)
- {
- for (auto& var : uniform.vars)
- {
- switch (var.type)
- {
- case render::ShaderAST::VariableType::Vec4: uniformLayout.AddArrayMember(var.name, var.size); break;
- case render::ShaderAST::VariableType::Vec3: uniformLayout.AddArrayMember(var.name, var.size); break;
- case render::ShaderAST::VariableType::Vec2: uniformLayout.AddArrayMember(var.name, var.size); break;
- case render::ShaderAST::VariableType::Mat4: uniformLayout.AddArrayMember(var.name, var.size); break;
- case render::ShaderAST::VariableType::Mat3: uniformLayout.AddArrayMember(var.name, var.size); break;
- case render::ShaderAST::VariableType::Float: uniformLayout.AddArrayMember< float>(var.name, var.size); break;
- case render::ShaderAST::VariableType::Int: uniformLayout.AddArrayMember< int>(var.name, var.size); break;
- }
- }
- }
- else
- uniformLayout.AddMember(uniform.name);
-
- AddUniformLayout(stageType, std::move(uniformLayout));
- }
- }
- }
- auto ShaderPass::IsSamplerLayout(const UniformStructLayout& layout) const -> bool
- {
- for (auto& member : layout.GetMembers())
- {
- if (member.isSampler)
- return true;
- }
- return false;
- }
-
ShaderPass::ShaderPass(const ShaderAST::PassNode& passNode, ShaderType type) :
type(type), lightingPassName(passNode.lightingPass)
{
@@ -78,9 +13,9 @@ namespace sh::render
FillAttributes(passNode);
for (auto& stage : passNode.stages)
{
- if (stage.bUseLighting)
+ if (stage.lightingBinding != -1)
{
- bUseLighting = true;
+ lightingBinding = stage.lightingBinding;
break;
}
}
@@ -125,19 +60,18 @@ namespace sh::render
cull(other.cull), colorMask(other.colorMask),
bZWrite(other.bZWrite),
bHasConstant(other.bHasConstant),
- bUseLighting(other.bUseLighting)
+ lightingBinding(other.lightingBinding)
{
}
ShaderPass::~ShaderPass()
{
}
- auto ShaderPass::GetShaderType() const -> ShaderType
- {
- return type;
- }
- void ShaderPass::operator=(ShaderPass&& other) noexcept
+ auto ShaderPass::operator=(ShaderPass&& other) noexcept -> ShaderPass&
{
+ if (&other == this)
+ return *this;
+
type = other.type;
lightingPassName = other.lightingPassName;
attrs = std::move(other.attrs);
@@ -146,32 +80,15 @@ namespace sh::render
fragmentUniforms = std::move(other.fragmentUniforms);
samplerUniforms = std::move(other.samplerUniforms);
constantNameMap = std::move(other.constantNameMap);
- }
+ cull = other.cull;
+ colorMask = other.colorMask;
+ bZWrite = other.bZWrite;
+ bHasConstant = other.bHasConstant;
+ lightingBinding = other.lightingBinding;
- void ShaderPass::AddUniformLayout(ShaderStage stage, const UniformStructLayout& layout)
- {
- if (stage == ShaderStage::Vertex)
- vertexUniforms.push_back(layout);
- else if (stage == ShaderStage::Fragment)
- {
- if (IsSamplerLayout(layout) == false)
- fragmentUniforms.push_back(layout);
- else
- samplerUniforms.push_back(layout);
- }
- }
- void ShaderPass::AddUniformLayout(ShaderStage stage, UniformStructLayout&& layout)
- {
- if (stage == ShaderStage::Vertex)
- vertexUniforms.push_back(std::move(layout));
- else if (stage == ShaderStage::Fragment)
- {
- if (IsSamplerLayout(layout) == false)
- fragmentUniforms.push_back(std::move(layout));
- else
- samplerUniforms.push_back(std::move(layout));
- }
+ return *this;
}
+
SH_RENDER_API auto ShaderPass::HasUniformMember(const std::string& name, ShaderStage stage) const -> const UniformStructLayout*
{
if (stage == ShaderStage::Vertex)
@@ -203,27 +120,6 @@ namespace sh::render
this->stencilState = stencilState;
}
- SH_RENDER_API auto ShaderPass::GetStencilState() const -> const StencilState&
- {
- return stencilState;
- }
- SH_RENDER_API auto ShaderPass::GetCullMode() const -> CullMode
- {
- return cull;
- }
- SH_RENDER_API auto ShaderPass::GetZWrite() const -> bool
- {
- return bZWrite;
- }
- SH_RENDER_API auto ShaderPass::GetColorMask() const -> uint8_t
- {
- return colorMask;
- }
- SH_RENDER_API auto ShaderPass::GetLightingPassName() const -> const core::Name&
- {
- return lightingPassName;
- }
-
bool ShaderPass::HasAttribute(const std::string& name) const
{
auto it = attridx.find(name);
@@ -240,34 +136,7 @@ namespace sh::render
return attrs[it->second];
}
- auto ShaderPass::GetAttributes() const -> const std::vector&
- {
- return attrs;
- }
- auto ShaderPass::GetVertexUniforms() const -> const std::vector&
- {
- return vertexUniforms;
- }
- auto ShaderPass::GetFragmentUniforms() const -> const std::vector&
- {
- return fragmentUniforms;
- }
- auto ShaderPass::GetSamplerUniforms() const -> const std::vector&
- {
- return samplerUniforms;
- }
- SH_RENDER_API auto ShaderPass::HasConstantUniform() const -> bool
- {
- return bHasConstant;
- }
- SH_RENDER_API auto ShaderPass::IsUsingLight() const -> bool
- {
- return bUseLighting;
- }
- SH_RENDER_API auto ShaderPass::GetConstants() const -> const std::unordered_map&
- {
- return constantNameMap;
- }
+
SH_RENDER_API auto ShaderPass::GetConstantsInfo(const std::string& name) const -> const ConstantInfo*
{
auto it = constantNameMap.find(name);
@@ -308,4 +177,93 @@ namespace sh::render
if (shaderPassJson.contains("fragShaderData"))
shaderCode.frag = shaderPassJson["fragShaderData"].get>();
}
-}
\ No newline at end of file
+
+ void ShaderPass::AddUniformLayout(ShaderStage stage, const UniformStructLayout& layout)
+ {
+ if (stage == ShaderStage::Vertex)
+ vertexUniforms.push_back(layout);
+ else if (stage == ShaderStage::Fragment)
+ {
+ if (IsSamplerLayout(layout) == false)
+ fragmentUniforms.push_back(layout);
+ else
+ samplerUniforms.push_back(layout);
+ }
+ }
+ void ShaderPass::AddUniformLayout(ShaderStage stage, UniformStructLayout&& layout)
+ {
+ if (stage == ShaderStage::Vertex)
+ vertexUniforms.push_back(std::move(layout));
+ else if (stage == ShaderStage::Fragment)
+ {
+ if (IsSamplerLayout(layout) == false)
+ fragmentUniforms.push_back(std::move(layout));
+ else
+ samplerUniforms.push_back(std::move(layout));
+ }
+ }
+ void ShaderPass::FillAttributes(const render::ShaderAST::PassNode& passNode)
+ {
+ for (auto& stage : passNode.stages)
+ {
+ auto stageType = (stage.type == render::ShaderAST::StageType::Vertex) ?
+ render::ShaderStage::Vertex :
+ render::ShaderStage::Fragment;
+
+ if (stage.type == render::ShaderAST::StageType::Vertex)
+ {
+ for (auto& in : stage.in)
+ {
+ switch (in.var.type)
+ {
+ case render::ShaderAST::VariableType::Vec4: AddAttribute(in.var.name, in.binding); break;
+ case render::ShaderAST::VariableType::Vec3: AddAttribute(in.var.name, in.binding); break;
+ case render::ShaderAST::VariableType::Vec2: AddAttribute(in.var.name, in.binding); break;
+ case render::ShaderAST::VariableType::Mat4: AddAttribute(in.var.name, in.binding); break;
+ case render::ShaderAST::VariableType::Mat3: AddAttribute(in.var.name, in.binding); break;
+ case render::ShaderAST::VariableType::Float: AddAttribute< float>(in.var.name, in.binding); break;
+ case render::ShaderAST::VariableType::Int: AddAttribute< int>(in.var.name, in.binding); break;
+ }
+ }
+ }
+ for (auto& uniform : stage.uniforms)
+ {
+ UniformStructLayout::Type uniformType = static_cast(uniform.set);
+
+ render::UniformStructLayout uniformLayout{ uniform.name, uniform.binding, uniformType, stageType, uniform.bConstant };
+
+ bHasConstant |= uniform.bConstant;
+
+ if (!uniform.bSampler)
+ {
+ for (auto& var : uniform.vars)
+ {
+ switch (var.type)
+ {
+ case render::ShaderAST::VariableType::Vec4: uniformLayout.AddArrayMember(var.name, var.size); break;
+ case render::ShaderAST::VariableType::Vec3: uniformLayout.AddArrayMember(var.name, var.size); break;
+ case render::ShaderAST::VariableType::Vec2: uniformLayout.AddArrayMember(var.name, var.size); break;
+ case render::ShaderAST::VariableType::Mat4: uniformLayout.AddArrayMember(var.name, var.size); break;
+ case render::ShaderAST::VariableType::Mat3: uniformLayout.AddArrayMember(var.name, var.size); break;
+ case render::ShaderAST::VariableType::Float: uniformLayout.AddArrayMember< float>(var.name, var.size); break;
+ case render::ShaderAST::VariableType::Int: uniformLayout.AddArrayMember< int>(var.name, var.size); break;
+ }
+ }
+ }
+ else
+ uniformLayout.AddMember(uniform.name);
+
+ AddUniformLayout(stageType, std::move(uniformLayout));
+ }
+ }
+ }
+ auto ShaderPass::IsSamplerLayout(const UniformStructLayout& layout) const -> bool
+ {
+ for (auto& member : layout.GetMembers())
+ {
+ if (member.isSampler)
+ return true;
+ }
+ return false;
+ }
+}//namespace
\ No newline at end of file
diff --git a/src/Render/VulkanImpl/VulkanPipelineManager.cpp b/src/Render/VulkanImpl/VulkanPipelineManager.cpp
index 2589f186..747b8021 100644
--- a/src/Render/VulkanImpl/VulkanPipelineManager.cpp
+++ b/src/Render/VulkanImpl/VulkanPipelineManager.cpp
@@ -1,6 +1,7 @@
#include "VulkanPipelineManager.h"
#include "VulkanShaderPass.h"
#include "VulkanVertexBuffer.h"
+#include "VulkanSkinnedVertexBuffer.h"
#include "VulkanContext.h"
namespace sh::render::vk
@@ -44,6 +45,7 @@ namespace sh::render::vk
const VulkanShaderPass& shader,
const RenderTargetLayout& renderTargetLayout,
Mesh::Topology topology,
+ bool bSkinned,
const std::vector* constDataPtr) -> PipelineHandle
{
std::size_t constantHash = 0;
@@ -54,7 +56,8 @@ namespace sh::render::vk
constantHash = core::Util::CombineHash(constantHash, hasher(data));
}
- PipelineInfo info{ &shader, renderTargetLayout, topology, constantHash };
+ // 이미 파이프라인이 존재
+ PipelineInfo info{ &shader, renderTargetLayout, topology, constantHash, bSkinned };
{
std::shared_lock readLock{ mu };
auto it = infoIdx.find(info);
@@ -68,13 +71,14 @@ namespace sh::render::vk
std::unique_lock writeLock{ mu };
if (pipelines[it->second].pipelinePtr.get() == nullptr)
{
- pipelines[it->second].pipelinePtr = BuildPipeline(shader, renderTargetLayout, topology, constDataPtr);
+ pipelines[it->second].pipelinePtr = BuildPipeline(shader, renderTargetLayout, topology, bSkinned, constDataPtr);
gen = ++pipelines[it->second].generation;
}
}
return PipelineHandle{ it->second, gen };
}
}
+ // 없으니 새로 생성
{
std::unique_lock writeLock{ mu };
auto it = infoIdx.find(info);
@@ -87,7 +91,7 @@ namespace sh::render::vk
uint32_t gen = 0;
if (emptyIdx.empty())
{
- pipelines.push_back(Pipeline{ BuildPipeline(shader, renderTargetLayout, topology, constDataPtr), 0 });
+ pipelines.push_back(Pipeline{ BuildPipeline(shader, renderTargetLayout, topology, bSkinned, constDataPtr), 0 });
pipelinesInfo.push_back(info);
idx = static_cast(pipelines.size()) - 1;
@@ -97,7 +101,7 @@ namespace sh::render::vk
idx = emptyIdx.front();
emptyIdx.pop();
- pipelines[idx].pipelinePtr = BuildPipeline(shader, renderTargetLayout, topology, constDataPtr);
+ pipelines[idx].pipelinePtr = BuildPipeline(shader, renderTargetLayout, topology, bSkinned, constDataPtr);
gen = ++pipelines[idx].generation;
}
infoIdx.insert({ info, idx });
@@ -110,6 +114,7 @@ namespace sh::render::vk
return PipelineHandle{ idx, gen };
}
}
+
SH_RENDER_API bool VulkanPipelineManager::BindPipeline(VkCommandBuffer cmd, PipelineHandle handle)
{
VkPipeline vkPipeline = VK_NULL_HANDLE;
@@ -134,6 +139,7 @@ namespace sh::render::vk
const VulkanShaderPass& shader,
const RenderTargetLayout& renderTargetLayout,
Mesh::Topology topology,
+ bool bSkinned,
const std::vector* constDataPtr) -> std::unique_ptr
{
auto pipeline = std::make_unique(context.GetDevice(), renderTargetLayout);
@@ -165,9 +171,19 @@ namespace sh::render::vk
if (constDataPtr != nullptr)
pipeline->SetSpecializationConstant(constDataPtr->data());
- pipeline->AddBindingDescription(VulkanVertexBuffer::GetBindingDescription());
- for (auto& attrDesc : VulkanVertexBuffer::GetAttributeDescriptions())
- pipeline->AddAttributeDescription(attrDesc);
+ if (!bSkinned)
+ {
+ pipeline->AddBindingDescription(VulkanVertexBuffer::GetBindingDescription());
+ for (auto& attrDesc : VulkanVertexBuffer::GetAttributeDescriptions())
+ pipeline->AddAttributeDescription(attrDesc);
+ }
+ else
+ {
+ for (VkVertexInputBindingDescription& bindingDesc : VulkanSkinnedVertexBuffer::GetBindingDescriptions())
+ pipeline->AddBindingDescription(bindingDesc);
+ for (auto& attrDesc : VulkanSkinnedVertexBuffer::GetAttributeDescriptions())
+ pipeline->AddAttributeDescription(attrDesc);
+ }
pipeline->SetLineWidth(1.0f);
pipeline->SetStencilState(true, ConvertStencilState(shader.GetStencilState()));
diff --git a/src/Render/VulkanImpl/VulkanRenderImpl.cpp b/src/Render/VulkanImpl/VulkanRenderImpl.cpp
index af2417e4..7b87ef6f 100644
--- a/src/Render/VulkanImpl/VulkanRenderImpl.cpp
+++ b/src/Render/VulkanImpl/VulkanRenderImpl.cpp
@@ -3,11 +3,15 @@
#include "VulkanSwapChain.h"
#include "VulkanUniformBuffer.h"
#include "VulkanVertexBuffer.h"
+#include "VulkanSkinnedVertexBuffer.h"
#include "VulkanPipelineManager.h"
#include "VulkanShaderPass.h"
#include "../Material.h"
#include "../ShaderPass.h"
#include "../Drawable.h"
+#include "../SkinnedMesh.h"
+
+#include "Core/Reflection.hpp"
namespace sh::render::vk
{
@@ -244,14 +248,32 @@ namespace sh::render::vk
}
void VulkanRenderImpl::DrawMesh(VulkanCommandBuffer& cmd, const ShaderPass& pass, const Mesh& mesh) const
{
- const VulkanVertexBuffer* vkVertexBuffer = static_cast(mesh.GetVertexBuffer());
+ const SkinnedMesh* skinnedMesh = core::reflection::Cast(&mesh);
+ if (skinnedMesh)
+ {
+ const VulkanSkinnedVertexBuffer* vkSkinnedVB =
+ static_cast(skinnedMesh->GetVertexBuffer());
+
+ VkBuffer buffers[2] = {
+ vkSkinnedVB->GetVertexBuffer().GetBuffer(),
+ vkSkinnedVB->GetBoneBuffer().GetBuffer()
+ };
+ VkDeviceSize offsets[2] = { 0, 0 };
+ vkCmdBindVertexBuffers(cmd.GetCommandBuffer(), 0, 2, buffers, offsets);
+ vkCmdBindIndexBuffer(cmd.GetCommandBuffer(), vkSkinnedVB->GetIndexBuffer().GetBuffer(), 0, VK_INDEX_TYPE_UINT32);
+ vkCmdDrawIndexed(cmd.GetCommandBuffer(), static_cast(mesh.GetIndices().size()), 1, 0, 0, 0);
+ }
+ else
+ {
+ const VulkanVertexBuffer* vkVertexBuffer = static_cast(mesh.GetVertexBuffer());
- std::array buffers = { vkVertexBuffer->GetVertexBuffer().GetBuffer() };
+ std::array buffers = { vkVertexBuffer->GetVertexBuffer().GetBuffer() };
- VkDeviceSize offsets[1] = { 0 };
- vkCmdBindVertexBuffers(cmd.GetCommandBuffer(), 0, 1, buffers.data(), offsets);
- vkCmdBindIndexBuffer(cmd.GetCommandBuffer(), vkVertexBuffer->GetIndexBuffer().GetBuffer(), 0, VkIndexType::VK_INDEX_TYPE_UINT32);
- vkCmdDrawIndexed(cmd.GetCommandBuffer(), static_cast(mesh.GetIndices().size()), 1, 0, 0, 0);
+ VkDeviceSize offsets[1] = { 0 };
+ vkCmdBindVertexBuffers(cmd.GetCommandBuffer(), 0, 1, buffers.data(), offsets);
+ vkCmdBindIndexBuffer(cmd.GetCommandBuffer(), vkVertexBuffer->GetIndexBuffer().GetBuffer(), 0, VkIndexType::VK_INDEX_TYPE_UINT32);
+ vkCmdDrawIndexed(cmd.GetCommandBuffer(), static_cast(mesh.GetIndices().size()), 1, 0, 0, 0);
+ }
}
void VulkanRenderImpl::RenderDrawable(VulkanCommandBuffer& cmd, const core::Name& passName, const RenderTarget& renderData, const DrawList& drawList, const RenderTargetLayout& layout) const
{
@@ -270,6 +292,18 @@ namespace sh::render::vk
const auto passVectorPtr = renderGroup.material->GetShader()->GetShaderPasses(passName);
if (passVectorPtr == nullptr)
continue;
+
+ // 첫 번째 유효 drawable의 mesh로 스킨드 여부 판단
+ bool bGroupSkinned = false;
+ for (auto drawable : renderGroup.drawables)
+ {
+ if (drawable && drawable->GetMesh())
+ {
+ bGroupSkinned = (core::reflection::Cast(drawable->GetMesh()) != nullptr);
+ break;
+ }
+ }
+
for (const ShaderPass& pass : *passVectorPtr)
{
if (pass.IsPendingKill())
@@ -278,7 +312,7 @@ namespace sh::render::vk
const std::vector* constantData = mat->GetConstantData(pass);
auto pipelineHandle = ctx.GetPipelineManager().
- GetOrCreatePipelineHandle(static_cast(pass), layout, renderGroup.topology, constantData);
+ GetOrCreatePipelineHandle(static_cast(pass), layout, renderGroup.topology, bGroupSkinned, constantData);
if (lastPipelineHandle != pipelineHandle)
{
@@ -329,6 +363,10 @@ namespace sh::render::vk
if (passVectorPtr == nullptr)
continue;
+ bool bItemSkinned = false;
+ if (renderItem.drawable && renderItem.drawable->GetMesh())
+ bItemSkinned = (core::reflection::Cast(renderItem.drawable->GetMesh()) != nullptr);
+
for (const ShaderPass& pass : *passVectorPtr)
{
if (pass.IsPendingKill())
@@ -337,7 +375,7 @@ namespace sh::render::vk
const std::vector* constantData = mat->GetConstantData(pass);
auto pipelineHandle = ctx.GetPipelineManager().
- GetOrCreatePipelineHandle(static_cast(pass), layout, renderItem.topology, constantData);
+ GetOrCreatePipelineHandle(static_cast(pass), layout, renderItem.topology, bItemSkinned, constantData);
if (lastPipelineHandle != pipelineHandle)
{
From 58d4b954c89be96aaf37728153607163fb55ddd6 Mon Sep 17 00:00:00 2001
From: Shell <104874910+Shell4026@users.noreply.github.com>
Date: Sat, 18 Apr 2026 05:38:04 +0900
Subject: [PATCH 11/82] feat(Game/SkinnedMeshRenderer): add SkinnedMeshRenderer
component
- Extends MeshRenderer; SetSkinnedMesh/SetBones API
- ComputeBoneMatrices: compute finalBoneMatrix from bone Transforms and upload to GPU UBO
- MeshRenderer: use GetLightingBinding() instead of removed IsUsingLight()
---
.../Component/Render/SkinnedMeshRenderer.h | 53 +++++++
src/Game/Component/Render/MeshRenderer.cpp | 5 +-
.../Component/Render/SkinnedMeshRenderer.cpp | 140 ++++++++++++++++++
3 files changed, 196 insertions(+), 2 deletions(-)
create mode 100644 include/Game/Component/Render/SkinnedMeshRenderer.h
create mode 100644 src/Game/Component/Render/SkinnedMeshRenderer.cpp
diff --git a/include/Game/Component/Render/SkinnedMeshRenderer.h b/include/Game/Component/Render/SkinnedMeshRenderer.h
new file mode 100644
index 00000000..9d2edf98
--- /dev/null
+++ b/include/Game/Component/Render/SkinnedMeshRenderer.h
@@ -0,0 +1,53 @@
+#pragma once
+#include "Game/Export.h"
+#include "Game/Component/Render/MeshRenderer.h"
+
+#include "Render/SkinnedMesh.h"
+
+#include
+#include
+#include
+
+namespace sh::game
+{
+ class Transform;
+
+ /// @brief 스켈레탈 메쉬 렌더러.
+ ///
+ /// ### 설정 순서
+ /// 1. SetSkinnedMesh() — 스킨드 메쉬 설정
+ /// 2. SetBones() — 바인드 포즈 상태의 bone Transform 배열 설정 (IBM 자동 계산)
+ ///
+ /// ### 매 프레임 동작
+ /// ComputeBoneMatrices(): finalBoneMatrix[i] = bones[i]->localToWorldMatrix * inverseBindMatrices[i]
+ /// → GPU BONES_UBO (set=1, binding=0) 업로드
+ class SkinnedMeshRenderer : public MeshRenderer
+ {
+ COMPONENT(SkinnedMeshRenderer)
+ public:
+ SH_GAME_API SkinnedMeshRenderer(GameObject& owner);
+ SH_GAME_API ~SkinnedMeshRenderer();
+
+ SH_GAME_API void Awake() override;
+ SH_GAME_API void Start() override;
+
+ SH_GAME_API auto Serialize() const -> core::Json override;
+ SH_GAME_API void Deserialize(const core::Json& json) override;
+
+ SH_GAME_API void SetSkinnedMesh(render::SkinnedMesh* mesh);
+ SH_GAME_API void SetBones(std::vector bones);
+
+ SH_GAME_API auto GetBones() const -> const std::vector