Init squashed
This commit is contained in:
commit
4aac4b824f
95 changed files with 7582 additions and 0 deletions
205
.clang-format
Normal file
205
.clang-format
Normal file
|
@ -0,0 +1,205 @@
|
|||
---
|
||||
BasedOnStyle: Microsoft
|
||||
AccessModifierOffset: -2
|
||||
AlignAfterOpenBracket: BlockIndent
|
||||
AlignArrayOfStructures: Right
|
||||
AlignConsecutiveAssignments:
|
||||
Enabled: true
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: true
|
||||
AlignConsecutiveBitFields:
|
||||
Enabled: true
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveDeclarations:
|
||||
Enabled: true
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveMacros:
|
||||
Enabled: true
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
PadOperators: false
|
||||
AlignConsecutiveShortCaseStatements:
|
||||
Enabled: true
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCaseColons: false
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: Align
|
||||
AlignTrailingComments:
|
||||
Kind: Always
|
||||
OverEmptyLines: 0
|
||||
AllowAllArgumentsOnNextLine: false
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortBlocksOnASingleLine: Empty
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortEnumsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
AttributeMacros:
|
||||
- __capability
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BitFieldColonSpacing: Both
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: true
|
||||
AfterClass: true
|
||||
AfterControlStatement: Always
|
||||
AfterEnum: true
|
||||
AfterFunction: true
|
||||
AfterNamespace: true
|
||||
AfterObjCDeclaration: true
|
||||
AfterStruct: true
|
||||
AfterUnion: false
|
||||
AfterExternBlock: true
|
||||
BeforeCatch: true
|
||||
BeforeElse: true
|
||||
BeforeLambdaBody: false
|
||||
BeforeWhile: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: true
|
||||
SplitEmptyRecord: true
|
||||
SplitEmptyNamespace: true
|
||||
BreakAfterAttributes: Never
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakArrays: true
|
||||
BreakBeforeBinaryOperators: NonAssignment
|
||||
BreakBeforeBraces: Custom
|
||||
BreakBeforeConceptDeclarations: Always
|
||||
BreakBeforeInlineASMColon: OnlyMultiline
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializers: AfterColon
|
||||
BreakInheritanceList: AfterColon
|
||||
BreakStringLiterals: true
|
||||
ColumnLimit: 100
|
||||
CommentPragmas: "^ IWYU pragma:"
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
EmptyLineAfterAccessModifier: Never
|
||||
EmptyLineBeforeAccessModifier: Always
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: true
|
||||
IncludeBlocks: Preserve
|
||||
IncludeIsMainRegex: (Test)?$
|
||||
IncludeIsMainSourceRegex: ""
|
||||
IndentAccessModifiers: false
|
||||
IndentCaseBlocks: false
|
||||
IndentCaseLabels: true
|
||||
IndentExternBlock: AfterExternBlock
|
||||
IndentGotoLabels: true
|
||||
IndentPPDirectives: None
|
||||
IndentRequiresClause: false
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: false
|
||||
InsertBraces: true
|
||||
InsertNewlineAtEOF: true
|
||||
InsertTrailingCommas: Wrapped
|
||||
IntegerLiteralSeparator:
|
||||
Binary: 0
|
||||
BinaryMinDigits: 0
|
||||
Decimal: 0
|
||||
DecimalMinDigits: 0
|
||||
Hex: 0
|
||||
HexMinDigits: 0
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLinesAtEOF: false
|
||||
KeepEmptyLinesAtTheStartOfBlocks: true
|
||||
LambdaBodyIndentation: Signature
|
||||
Language: Cpp
|
||||
LineEnding: LF
|
||||
MacroBlockBegin: ""
|
||||
MacroBlockEnd: ""
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
ObjCBinPackProtocolList: Auto
|
||||
ObjCBlockIndentWidth: 2
|
||||
ObjCBreakBeforeNestedBlockParam: true
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PPIndentWidth: -1
|
||||
PackConstructorInitializers: CurrentLine
|
||||
PenaltyBreakAssignment: 2
|
||||
PenaltyBreakBeforeFirstCallParameter: 19
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakOpenParenthesis: 0
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyBreakTemplateDeclaration: 10
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyIndentedWhitespace: 0
|
||||
PenaltyReturnTypeOnItsOwnLine: 1000
|
||||
PointerAlignment: Left
|
||||
QualifierAlignment: Left
|
||||
ReferenceAlignment: Pointer
|
||||
ReflowComments: true
|
||||
RemoveBracesLLVM: false
|
||||
RemoveParentheses: Leave
|
||||
RemoveSemicolon: false
|
||||
RequiresClausePosition: OwnLine
|
||||
RequiresExpressionIndentation: OuterScope
|
||||
SeparateDefinitionBlocks: Always
|
||||
ShortNamespaceLines: 1
|
||||
SortIncludes: CaseInsensitive
|
||||
SortJavaStaticImport: Before
|
||||
SortUsingDeclarations: Lexicographic
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: false
|
||||
SpaceAroundPointerQualifiers: Default
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceBeforeCpp11BracedList: true
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeJsonColon: false
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeParensOptions:
|
||||
AfterControlStatements: true
|
||||
AfterForeachMacros: true
|
||||
AfterFunctionDeclarationName: false
|
||||
AfterFunctionDefinitionName: false
|
||||
AfterIfMacros: true
|
||||
AfterOverloadedOperator: false
|
||||
AfterRequiresInClause: false
|
||||
AfterRequiresInExpression: false
|
||||
BeforeNonEmptyParentheses: false
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceBeforeSquareBrackets: false
|
||||
SpaceInEmptyBlock: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: Never
|
||||
SpacesInContainerLiterals: false
|
||||
SpacesInLineCommentPrefix:
|
||||
Minimum: 1
|
||||
Maximum: -1
|
||||
SpacesInParens: Never
|
||||
SpacesInParensOptions:
|
||||
InConditionalStatements: false
|
||||
InCStyleCasts: false
|
||||
InEmptyParentheses: false
|
||||
Other: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Latest
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
VerilogBreakBetweenInstancePorts: true
|
||||
|
3
.clangd
Normal file
3
.clangd
Normal file
|
@ -0,0 +1,3 @@
|
|||
CompileFlags:
|
||||
CompilationDatabase: ./build/
|
||||
|
0
.gitattributes
vendored
Normal file
0
.gitattributes
vendored
Normal file
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
subprojects/**/*
|
||||
!subprojects/*.wrap
|
||||
|
||||
.cache/
|
||||
|
||||
lib/
|
||||
|
||||
include/steamworks
|
||||
|
||||
imgui.ini
|
||||
|
15
.gitmodules
vendored
Normal file
15
.gitmodules
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
[submodule "include/imgui"]
|
||||
path = include/imgui
|
||||
url = https://github.com/ocornut/imgui.git
|
||||
[submodule "include/glm"]
|
||||
path = include/glm
|
||||
url = https://github.com/g-truc/glm.git
|
||||
[submodule "include/VulkanMemoryAllocator"]
|
||||
path = include/VulkanMemoryAllocator
|
||||
url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git
|
||||
[submodule "include/PerlinNoise"]
|
||||
path = include/PerlinNoise
|
||||
url = https://github.com/Reputeless/PerlinNoise.git
|
||||
[submodule "include/JoltPhysics"]
|
||||
path = include/JoltPhysics
|
||||
url = https://github.com/jrouwe/JoltPhysics.git
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2024 nullprop
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
30
README.md
Normal file
30
README.md
Normal file
|
@ -0,0 +1,30 @@
|
|||
# LEGS
|
||||
|
||||
Very WIP framework for 3D games.
|
||||
|
||||
**legs** help you run. (\*ba-dum-tss\*)
|
||||
|
||||
## Running
|
||||
|
||||
### Linux
|
||||
|
||||
Dependencies: Meson, gcc
|
||||
|
||||
```sh
|
||||
git clone https://github.com/nullprop/legs.git --recursive
|
||||
cd legs
|
||||
./scripts/compile_jolt.sh distribution
|
||||
./scripts/setup_release.sh
|
||||
ninja -C build
|
||||
./build/examples/02_systems/02_systems
|
||||
```
|
||||
|
||||
## Third-party code
|
||||
|
||||
- [glm](https://github.com/g-truc/glm): MIT / The Happy Bunny License
|
||||
- [imgui](https://github.com/ocornut/imgui): MIT License
|
||||
- [JoltPhysics](https://github.com/jrouwe/JoltPhysics): MIT License
|
||||
- [VulkanMemoryAllocator](https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator): MIT License
|
||||
- [SDL](https://github.com/libsdl-org/SDL) (system/wrapdb): Zlib License
|
||||
- [Vulkan-Headers](https://github.com/KhronosGroup/Vulkan-Headers) (system/wrapdb): Apache-2.0 / MIT License
|
||||
|
19
examples/01_hello_world/main.cpp
Normal file
19
examples/01_hello_world/main.cpp
Normal file
|
@ -0,0 +1,19 @@
|
|||
#include <legs/entry.hpp>
|
||||
#include <legs/log.hpp>
|
||||
|
||||
using namespace legs;
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
Log::SetLogLevel(LogLevel::Debug);
|
||||
|
||||
auto code = LEGS_Init(argc, argv);
|
||||
if (code < 0)
|
||||
{
|
||||
return code;
|
||||
}
|
||||
|
||||
g_engine->GetWindow()->SetTitle("01_hello_world");
|
||||
|
||||
return LEGS_Run();
|
||||
}
|
1
examples/01_hello_world/meson.build
Normal file
1
examples/01_hello_world/meson.build
Normal file
|
@ -0,0 +1 @@
|
|||
executable('01_hello_world', files('main.cpp'), dependencies: [legs_dep])
|
163
examples/02_systems/main.cpp
Normal file
163
examples/02_systems/main.cpp
Normal file
|
@ -0,0 +1,163 @@
|
|||
#include <memory>
|
||||
|
||||
#include <legs/entry.hpp>
|
||||
|
||||
#include <legs/entity/mesh_entity.hpp>
|
||||
#include <legs/entity/sky.hpp>
|
||||
#include <legs/geometry/icosphere.hpp>
|
||||
#include <legs/geometry/plane.hpp>
|
||||
#include <legs/isystem.hpp>
|
||||
#include <legs/log.hpp>
|
||||
#include <legs/time.hpp>
|
||||
|
||||
using namespace legs;
|
||||
|
||||
class MySystem : public ISystem
|
||||
{
|
||||
public:
|
||||
MySystem()
|
||||
{
|
||||
int width;
|
||||
int height;
|
||||
g_engine->GetWindow()->GetFramebufferSize(&width, &height);
|
||||
m_camera = std::make_shared<NoclipCamera>(width, height);
|
||||
m_camera->SetPosition({0.0f, -10.0f, 5.0f});
|
||||
g_engine->SetCamera(m_camera);
|
||||
|
||||
auto renderer = g_engine->GetRenderer();
|
||||
auto world = g_engine->GetWorld();
|
||||
|
||||
// Create a sky
|
||||
auto sky = std::make_shared<Sky>(renderer);
|
||||
world->SetSky(sky);
|
||||
|
||||
// Create some test spheres
|
||||
for (unsigned int x = 0; x < 3; x++)
|
||||
{
|
||||
for (unsigned int y = 0; y < 3; y++)
|
||||
{
|
||||
auto testSphere = SIcosphere(glm::vec3(x, y, 5), 0.5f, 1);
|
||||
std::shared_ptr<Buffer> sphereVertexBuffer;
|
||||
std::shared_ptr<Buffer> sphereIndexBuffer;
|
||||
|
||||
std::vector<Vertex_P_N_C> sphereVertices;
|
||||
sphereVertices.reserve(testSphere.positions.size());
|
||||
for (unsigned int i = 0; i < testSphere.positions.size(); i++)
|
||||
{
|
||||
sphereVertices.push_back(
|
||||
{testSphere.positions[i], testSphere.normals[i], glm::vec3(0.5, 0.5, 0.5)}
|
||||
);
|
||||
}
|
||||
renderer->CreateBuffer(
|
||||
sphereVertexBuffer,
|
||||
VertexBuffer,
|
||||
sphereVertices.data(),
|
||||
sizeof(Vertex_P_N_C),
|
||||
static_cast<uint32_t>(sphereVertices.size())
|
||||
);
|
||||
renderer->CreateBuffer(
|
||||
sphereIndexBuffer,
|
||||
IndexBuffer,
|
||||
testSphere.indices.data(),
|
||||
sizeof(Index),
|
||||
static_cast<uint32_t>(testSphere.indices.size())
|
||||
);
|
||||
|
||||
auto sphere = std::make_shared<MeshEntity>();
|
||||
sphere->SetBuffers(sphereVertexBuffer, sphereIndexBuffer);
|
||||
sphere->SetPipeline(RenderPipeline::GEO_P_N_C);
|
||||
world->AddEntity(sphere);
|
||||
m_spheres.push_back(sphere);
|
||||
}
|
||||
}
|
||||
|
||||
// Create a test plane
|
||||
std::shared_ptr<Buffer> planeVertexBuffer;
|
||||
std::shared_ptr<Buffer> planeIndexBuffer;
|
||||
|
||||
auto testPlane = SPlane({0.0f, 0.0f, 0.0f}, 20.0f);
|
||||
auto planeVertices = std::array<Vertex_P_C, 4>();
|
||||
for (unsigned int i = 0; i < 4; i++)
|
||||
{
|
||||
auto color = glm::vec3 {
|
||||
i == 0 ? 1.0f : 0.0f,
|
||||
i == 1 ? 1.0f : 0.0f,
|
||||
i == 2 ? 1.0f : 0.0f,
|
||||
};
|
||||
planeVertices[i] = {testPlane.vertices[i], color};
|
||||
}
|
||||
renderer->CreateBuffer(
|
||||
planeVertexBuffer,
|
||||
VertexBuffer,
|
||||
planeVertices.data(),
|
||||
sizeof(Vertex_P_C),
|
||||
static_cast<uint32_t>(planeVertices.size())
|
||||
);
|
||||
renderer->CreateBuffer(
|
||||
planeIndexBuffer,
|
||||
IndexBuffer,
|
||||
testPlane.indices.data(),
|
||||
sizeof(Index),
|
||||
static_cast<uint32_t>(testPlane.indices.size())
|
||||
);
|
||||
|
||||
auto plane = std::make_shared<MeshEntity>();
|
||||
plane->SetBuffers(planeVertexBuffer, planeIndexBuffer);
|
||||
plane->SetPipeline(RenderPipeline::GEO_P_C);
|
||||
world->AddEntity(plane);
|
||||
}
|
||||
|
||||
~MySystem()
|
||||
{
|
||||
}
|
||||
|
||||
void OnFrame() override
|
||||
{
|
||||
m_camera->HandleInput(g_engine->GetFrameInput());
|
||||
|
||||
// TODO: model matrix buffer
|
||||
// Move the spheres around
|
||||
/*
|
||||
for (unsigned int i = 0; i < m_spheres.size(); i++)
|
||||
{
|
||||
auto pos = m_spheres[i]->GetPosition();
|
||||
pos.z = static_cast<float>(std::sin(legs::Time::Uptime() * i));
|
||||
m_spheres[i]->SetPosition(pos);
|
||||
}
|
||||
*/
|
||||
|
||||
// Move the sun across the sky
|
||||
const auto degreesPerSecond = 5.0;
|
||||
auto sunRotation = glm::angleAxis(
|
||||
glm::radians(degreesPerSecond * legs::Time::Uptime()),
|
||||
glm::normalize(glm::dvec3 {1.0, 0.3, 0.2})
|
||||
);
|
||||
g_engine->GetWorld()->GetSky()->SunDirection =
|
||||
glm::normalize(sunRotation * glm::dvec3(0.1, 0.2, 1.0));
|
||||
}
|
||||
|
||||
void OnTick() override
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<NoclipCamera> m_camera;
|
||||
std::vector<std::shared_ptr<MeshEntity>> m_spheres;
|
||||
};
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
Log::SetLogLevel(LogLevel::Debug);
|
||||
|
||||
auto code = LEGS_Init(argc, argv);
|
||||
if (code < 0)
|
||||
{
|
||||
return code;
|
||||
}
|
||||
|
||||
g_engine->GetWindow()->SetTitle("02_systems");
|
||||
|
||||
g_engine->AddSystem(std::make_shared<MySystem>());
|
||||
|
||||
return LEGS_Run();
|
||||
}
|
1
examples/02_systems/meson.build
Normal file
1
examples/02_systems/meson.build
Normal file
|
@ -0,0 +1 @@
|
|||
executable('02_systems', files('main.cpp'), dependencies: [legs_dep])
|
140
examples/03_physics/main.cpp
Normal file
140
examples/03_physics/main.cpp
Normal file
|
@ -0,0 +1,140 @@
|
|||
#include <memory>
|
||||
|
||||
#include <legs/entry.hpp>
|
||||
|
||||
#include <legs/entity/physics_entity.hpp>
|
||||
#include <legs/geometry/icosphere.hpp>
|
||||
#include <legs/geometry/plane.hpp>
|
||||
#include <legs/isystem.hpp>
|
||||
#include <legs/log.hpp>
|
||||
#include <legs/time.hpp>
|
||||
|
||||
using namespace legs;
|
||||
|
||||
class MySystem : public ISystem
|
||||
{
|
||||
public:
|
||||
MySystem()
|
||||
{
|
||||
int width;
|
||||
int height;
|
||||
g_engine->GetWindow()->GetFramebufferSize(&width, &height);
|
||||
m_camera = std::make_shared<NoclipCamera>(width, height);
|
||||
m_camera->SetPosition({0.0f, -10.0f, 5.0f});
|
||||
g_engine->SetCamera(m_camera);
|
||||
|
||||
auto renderer = g_engine->GetRenderer();
|
||||
auto world = g_engine->GetWorld();
|
||||
|
||||
// Create a plane
|
||||
std::shared_ptr<Buffer> planeVertexBuffer;
|
||||
std::shared_ptr<Buffer> planeIndexBuffer;
|
||||
|
||||
auto testPlane = SPlane({0.0f, 0.0f, 0.0f}, 20.0f);
|
||||
auto planeVertices = std::array<Vertex_P_C, 4>();
|
||||
for (unsigned int i = 0; i < 4; i++)
|
||||
{
|
||||
auto color = glm::vec3 {
|
||||
i == 0 ? 1.0f : 0.0f,
|
||||
i == 1 ? 1.0f : 0.0f,
|
||||
i == 2 ? 1.0f : 0.0f,
|
||||
};
|
||||
planeVertices[i] = {testPlane.vertices[i], color};
|
||||
}
|
||||
renderer->CreateBuffer(
|
||||
planeVertexBuffer,
|
||||
VertexBuffer,
|
||||
planeVertices.data(),
|
||||
sizeof(Vertex_P_C),
|
||||
static_cast<uint32_t>(planeVertices.size())
|
||||
);
|
||||
renderer->CreateBuffer(
|
||||
planeIndexBuffer,
|
||||
IndexBuffer,
|
||||
testPlane.indices.data(),
|
||||
sizeof(Index),
|
||||
static_cast<uint32_t>(testPlane.indices.size())
|
||||
);
|
||||
|
||||
auto plane = std::make_shared<PhysicsEntity>();
|
||||
plane->SetBuffers(planeVertexBuffer, planeIndexBuffer);
|
||||
plane->SetPipeline(RenderPipeline::GEO_P_C);
|
||||
|
||||
auto planeCollider = BoxCollider(
|
||||
JPH::EMotionType::Static,
|
||||
Layers::NON_MOVING,
|
||||
plane->GetTransform(),
|
||||
{20.0f, 20.0f, 0.1f}
|
||||
);
|
||||
plane->SetCollider(planeCollider);
|
||||
|
||||
world->AddEntity(plane);
|
||||
|
||||
// Create a sphere
|
||||
auto testSphere = SIcosphere(glm::vec3(0.0f, 0.0f, 10.0f), 0.5f, 1);
|
||||
std::shared_ptr<Buffer> sphereVertexBuffer;
|
||||
std::shared_ptr<Buffer> sphereIndexBuffer;
|
||||
|
||||
std::vector<Vertex_P_N_C> sphereVertices;
|
||||
sphereVertices.reserve(testSphere.positions.size());
|
||||
for (unsigned int i = 0; i < testSphere.positions.size(); i++)
|
||||
{
|
||||
sphereVertices.push_back(
|
||||
{testSphere.positions[i], testSphere.normals[i], glm::vec3(0.5, 0.5, 0.5)}
|
||||
);
|
||||
}
|
||||
renderer->CreateBuffer(
|
||||
sphereVertexBuffer,
|
||||
VertexBuffer,
|
||||
sphereVertices.data(),
|
||||
sizeof(Vertex_P_N_C),
|
||||
static_cast<uint32_t>(sphereVertices.size())
|
||||
);
|
||||
renderer->CreateBuffer(
|
||||
sphereIndexBuffer,
|
||||
IndexBuffer,
|
||||
testSphere.indices.data(),
|
||||
sizeof(Index),
|
||||
static_cast<uint32_t>(testSphere.indices.size())
|
||||
);
|
||||
|
||||
auto sphere = std::make_shared<PhysicsEntity>();
|
||||
sphere->SetBuffers(sphereVertexBuffer, sphereIndexBuffer);
|
||||
sphere->SetPipeline(RenderPipeline::GEO_P_N_C);
|
||||
|
||||
auto sphereCollider =
|
||||
SphereCollider(JPH::EMotionType::Dynamic, Layers::MOVING, sphere->GetTransform(), 0.5f);
|
||||
sphere->SetCollider(sphereCollider);
|
||||
|
||||
world->AddEntity(sphere);
|
||||
}
|
||||
|
||||
~MySystem()
|
||||
{
|
||||
}
|
||||
|
||||
void OnFrame() override
|
||||
{
|
||||
m_camera->HandleInput(g_engine->GetFrameInput());
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<NoclipCamera> m_camera;
|
||||
};
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
Log::SetLogLevel(LogLevel::Debug);
|
||||
|
||||
auto code = LEGS_Init(argc, argv);
|
||||
if (code < 0)
|
||||
{
|
||||
return code;
|
||||
}
|
||||
|
||||
g_engine->GetWindow()->SetTitle("03_systems");
|
||||
|
||||
g_engine->AddSystem(std::make_shared<MySystem>());
|
||||
|
||||
return LEGS_Run();
|
||||
}
|
1
examples/03_physics/meson.build
Normal file
1
examples/03_physics/meson.build
Normal file
|
@ -0,0 +1 @@
|
|||
executable('03_physics', files('main.cpp'), dependencies: [legs_dep])
|
3
examples/meson.build
Normal file
3
examples/meson.build
Normal file
|
@ -0,0 +1,3 @@
|
|||
subdir('01_hello_world')
|
||||
subdir('02_systems')
|
||||
subdir('03_physics')
|
1
include/JoltPhysics
Submodule
1
include/JoltPhysics
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit cede24d2733a4a473c6d486650ca9b6d0481681a
|
1
include/VulkanMemoryAllocator
Submodule
1
include/VulkanMemoryAllocator
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 1c35ba99ce775f8342d87a83a3f0f696f99c2a39
|
1
include/glm
Submodule
1
include/glm
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 33b4a621a697a305bc3a7610d290677b96beb181
|
1
include/imgui
Submodule
1
include/imgui
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 71714eab5367d2fa119e36be30148278e0d5974f
|
119
meson.build
Normal file
119
meson.build
Normal file
|
@ -0,0 +1,119 @@
|
|||
project(
|
||||
'legs',
|
||||
'cpp',
|
||||
default_options: [
|
||||
'cpp_std=c++23',
|
||||
'b_ndebug=if-release',
|
||||
'warning_level=3',
|
||||
],
|
||||
meson_version: '>=1.1',
|
||||
)
|
||||
|
||||
cpp = meson.get_compiler('cpp')
|
||||
buildtype = get_option('buildtype')
|
||||
|
||||
compiler_args = [
|
||||
'-Wcast-align',
|
||||
'-Wconversion',
|
||||
'-Wdouble-promotion',
|
||||
'-Wduplicated-branches',
|
||||
'-Wduplicated-cond',
|
||||
'-Wformat=2',
|
||||
'-Wimplicit-fallthrough',
|
||||
'-Wlogical-op',
|
||||
'-Wmisleading-indentation',
|
||||
'-Wnon-virtual-dtor',
|
||||
'-Wnrvo',
|
||||
'-Wnull-dereference',
|
||||
'-Wold-style-cast',
|
||||
'-Woverloaded-virtual',
|
||||
'-Wshadow',
|
||||
'-Wsign-conversion',
|
||||
'-Wsuggest-override',
|
||||
'-Wunused',
|
||||
'-Wuseless-cast',
|
||||
|
||||
'-DGLM_FORCE_DEPTH_ZERO_TO_ONE',
|
||||
|
||||
'-m64',
|
||||
|
||||
'-DLINUX',
|
||||
'-D_LINUX',
|
||||
'-DPOSIX',
|
||||
|
||||
'-DJPH_OBJECT_STREAM',
|
||||
'-DCPP_RTTI_ENABLED',
|
||||
]
|
||||
|
||||
linker_args = [
|
||||
'-m64',
|
||||
]
|
||||
|
||||
if buildtype == 'debug'
|
||||
compiler_args += [
|
||||
'-DJPH_SHARED_LIBRARY',
|
||||
'-DJPH_PROFILE_ENABLED',
|
||||
'-DJPH_DEBUG_RENDERER',
|
||||
]
|
||||
endif
|
||||
|
||||
add_project_arguments(cpp.get_supported_arguments(compiler_args), language: 'cpp')
|
||||
add_project_link_arguments(cpp.get_supported_link_arguments(linker_args), language: 'cpp')
|
||||
|
||||
# https://github.com/mesonbuild/meson/issues/13601
|
||||
# jolt_opt = cmake.subproject_options()
|
||||
# jolt_dep = cmake.subproject('Jolt', options: jolt_opt)
|
||||
jolt_dep = cpp.find_library('Jolt', dirs: [meson.current_source_dir() + '/lib'])
|
||||
|
||||
# TODO: is there a way to disable warnings for these?
|
||||
imgui_src = files(
|
||||
|
||||
'include/imgui/backends/imgui_impl_sdl2.cpp',
|
||||
'include/imgui/backends/imgui_impl_vulkan.cpp',
|
||||
'include/imgui/imgui.cpp',
|
||||
'include/imgui/imgui_demo.cpp',
|
||||
'include/imgui/imgui_draw.cpp',
|
||||
'include/imgui/imgui_tables.cpp',
|
||||
'include/imgui/imgui_widgets.cpp',
|
||||
)
|
||||
|
||||
glsl_compiler = find_program('glslang', 'glslangValidator')
|
||||
glsl_args = [
|
||||
'--target-env', 'vulkan1.3',
|
||||
'--vn', '@BASENAME@',
|
||||
'--depfile', '@DEPFILE@',
|
||||
'@INPUT@',
|
||||
'-o', '@OUTPUT@',
|
||||
]
|
||||
glsl_generator = generator(
|
||||
glsl_compiler,
|
||||
output: ['@BASENAME@.h'],
|
||||
depfile: '@BASENAME@.h.d',
|
||||
arguments: glsl_args,
|
||||
)
|
||||
|
||||
legs_includes = include_directories(
|
||||
'include/VulkanMemoryAllocator/include',
|
||||
|
||||
'include/glm',
|
||||
|
||||
'include/JoltPhysics',
|
||||
|
||||
'include/imgui',
|
||||
'include/imgui/backends',
|
||||
|
||||
is_system: true,
|
||||
)
|
||||
|
||||
legs_version = vcs_tag(
|
||||
command: ['git', 'describe', '--always', '--dirty=+'],
|
||||
input: 'version.h.in',
|
||||
output: 'version.h',
|
||||
)
|
||||
|
||||
subdir('src')
|
||||
|
||||
examples = get_option('examples')
|
||||
if examples == true
|
||||
subdir('examples')
|
||||
endif
|
6
meson.options
Normal file
6
meson.options
Normal file
|
@ -0,0 +1,6 @@
|
|||
option(
|
||||
'examples',
|
||||
description: 'Should the examples be built?',
|
||||
type: 'boolean',
|
||||
value: false
|
||||
)
|
27
scripts/compile_jolt.sh
Executable file
27
scripts/compile_jolt.sh
Executable file
|
@ -0,0 +1,27 @@
|
|||
#!/bin/bash -x
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
mode=${1:-"Debug"}
|
||||
|
||||
CWD="$(pwd)"
|
||||
|
||||
mkdir -p lib
|
||||
cd include/JoltPhysics/Build
|
||||
|
||||
rm -rf Linux_Debug
|
||||
rm -rf Linux_Release
|
||||
rm -rf Linux_Distribution
|
||||
|
||||
./cmake_linux_clang_gcc.sh "$mode" clang++ \
|
||||
-DBUILD_SHARED_LIBS=ON \
|
||||
-DCPP_RTTI_ENABLED=ON
|
||||
|
||||
cd "Linux_$mode"
|
||||
|
||||
make -j "$(nproc)"
|
||||
|
||||
./UnitTests
|
||||
|
||||
cp libJolt.so "$CWD/lib/libJolt.so"
|
||||
|
11
scripts/get_steamworks_sdk.sh
Executable file
11
scripts/get_steamworks_sdk.sh
Executable file
|
@ -0,0 +1,11 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SDK_VER=160
|
||||
|
||||
rm -rf include/steamworks
|
||||
curl "https://partner.steamgames.com/downloads/steamworks_sdk_$SDK_VER.zip" -o /tmp/steamworks.zip
|
||||
unzip /tmp/steamworks.zip -d include/steamworks
|
||||
head -n 32 include/steamworks/sdk/Readme.txt
|
||||
|
8
scripts/run.sh
Executable file
8
scripts/run.sh
Executable file
|
@ -0,0 +1,8 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd build
|
||||
meson compile
|
||||
LSAN_OPTIONS="suppressions=../suppr.txt" ./examples/02_systems/02_systems
|
||||
|
15
scripts/setup_debug.sh
Executable file
15
scripts/setup_debug.sh
Executable file
|
@ -0,0 +1,15 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# TODO: gcc busted
|
||||
export CXX=clang++
|
||||
|
||||
# TODO: ubsan doesn't link with jolt
|
||||
meson setup --reconfigure \
|
||||
-D examples=true \
|
||||
-D buildtype=debug \
|
||||
-D b_sanitize=address \
|
||||
-D b_lundef=false \
|
||||
build
|
||||
|
9
scripts/setup_release.sh
Executable file
9
scripts/setup_release.sh
Executable file
|
@ -0,0 +1,9 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
meson setup --reconfigure \
|
||||
-D examples=true \
|
||||
-D buildtype=release \
|
||||
build
|
||||
|
290
src/engine.cpp
Normal file
290
src/engine.cpp
Normal file
|
@ -0,0 +1,290 @@
|
|||
#include <functional>
|
||||
#include <memory>
|
||||
#include <stop_token>
|
||||
#include <thread>
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include "physics.hpp"
|
||||
|
||||
#include "legs/engine.hpp"
|
||||
#include "legs/log.hpp"
|
||||
#include "legs/time.hpp"
|
||||
#include "legs/window/window.hpp"
|
||||
|
||||
namespace legs
|
||||
{
|
||||
Engine::Engine()
|
||||
{
|
||||
LOG_INFO("Creating Engine");
|
||||
|
||||
m_inputSettings = std::make_shared<InputSettings>();
|
||||
m_window = std::make_shared<Window>(m_inputSettings);
|
||||
m_renderer = std::make_shared<Renderer>(m_window);
|
||||
|
||||
int width;
|
||||
int height;
|
||||
m_window->GetFramebufferSize(&width, &height);
|
||||
m_camera = std::make_shared<Camera>(width, height);
|
||||
|
||||
m_ui = std::make_unique<UI>(m_window, m_renderer);
|
||||
|
||||
Physics::Register();
|
||||
m_world = std::make_shared<World>(m_renderer);
|
||||
|
||||
m_window->SetMouseGrab(true);
|
||||
|
||||
auto fps = m_window->GetRefreshRate();
|
||||
LOG_DEBUG("Setting framerate to {}", fps);
|
||||
Time::SetFrameRate(fps);
|
||||
|
||||
Time::SetStart();
|
||||
|
||||
m_frameInput.Clear();
|
||||
m_tickInput.Clear();
|
||||
|
||||
m_tickThread = std::jthread {std::bind_front(&Engine::TickThread, this)};
|
||||
m_renderThread = std::jthread {std::bind_front(&Engine::RenderThread, this)};
|
||||
}
|
||||
|
||||
Engine::~Engine()
|
||||
{
|
||||
LOG_INFO("Destroying Engine");
|
||||
|
||||
LOG_DEBUG("Requesting TickThread stop");
|
||||
m_tickThread.request_stop();
|
||||
m_threadTickSemaphore.release();
|
||||
m_tickThread.join();
|
||||
|
||||
LOG_DEBUG("Requesting RenderThread stop");
|
||||
m_renderThread.request_stop();
|
||||
m_threadFrameSemaphore.release();
|
||||
m_renderThread.join();
|
||||
|
||||
LOG_DEBUG("Waiting for renderer idle");
|
||||
m_renderer->WaitForIdle();
|
||||
}
|
||||
|
||||
int Engine::Run()
|
||||
{
|
||||
m_mainFrameSemaphore.release();
|
||||
m_mainTickSemaphore.release();
|
||||
|
||||
Time::UpdateTickDelta();
|
||||
Time::UpdateFrameDelta();
|
||||
|
||||
const double sleepThreshold = 0.0001;
|
||||
while (true)
|
||||
{
|
||||
auto toTick = Time::TimeToEngineTick();
|
||||
if (toTick <= 0)
|
||||
{
|
||||
if (!Tick())
|
||||
{
|
||||
LOG_INFO("Engine::Tick exit");
|
||||
break;
|
||||
}
|
||||
toTick = Time::TickInterval;
|
||||
}
|
||||
|
||||
auto toFrame = Time::TimeToEngineFrame();
|
||||
if (toFrame <= 0)
|
||||
{
|
||||
Frame();
|
||||
toFrame = Time::FrameInterval;
|
||||
}
|
||||
|
||||
const auto lowest = std::min(toTick, toFrame);
|
||||
if (lowest < sleepThreshold)
|
||||
{
|
||||
std::this_thread::yield();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::this_thread::sleep_for(Time::Duration(lowest - sleepThreshold));
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Engine::Frame()
|
||||
{
|
||||
// Renderer still busy?
|
||||
auto acquired = m_mainFrameSemaphore.try_acquire();
|
||||
if (!acquired)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Time::UpdateFrameDelta();
|
||||
|
||||
UpdateInput();
|
||||
|
||||
for (auto system : m_systems)
|
||||
{
|
||||
system->OnFrame();
|
||||
}
|
||||
|
||||
if (m_frameInput.wantsResize)
|
||||
{
|
||||
int width;
|
||||
int height;
|
||||
m_window->GetFramebufferSize(&width, &height);
|
||||
m_camera->UpdateViewport(width, height);
|
||||
m_renderer->Resize();
|
||||
}
|
||||
|
||||
if (m_world != nullptr)
|
||||
{
|
||||
|
||||
m_world->Frame();
|
||||
}
|
||||
|
||||
if (m_frameInput.HasKey(Key::KEY_MOUSE_GRAB))
|
||||
{
|
||||
m_window->SetMouseGrab(!m_window->IsMouseGrabbed());
|
||||
m_frameInput.Clear(true);
|
||||
}
|
||||
|
||||
if (m_frameInput.HasKey(Key::KEY_WINDOW_DEBUG))
|
||||
{
|
||||
m_ui->ToggleWindow(UIWindow::DEBUG);
|
||||
m_frameInput.KeyUp(Key::KEY_WINDOW_DEBUG);
|
||||
}
|
||||
|
||||
if (m_frameInput.HasKey(Key::KEY_WINDOW_DEMO))
|
||||
{
|
||||
m_window->SetMouseGrab(false);
|
||||
m_frameInput.Clear();
|
||||
m_ui->ToggleWindow(UIWindow::DEMO);
|
||||
}
|
||||
|
||||
m_frameInput.Clear();
|
||||
|
||||
// Allow render thread to run.
|
||||
m_threadFrameSemaphore.release();
|
||||
}
|
||||
|
||||
bool Engine::Tick()
|
||||
{
|
||||
if (m_tickInput.wantsQuit)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Tick thread still busy?
|
||||
auto acquired = m_mainTickSemaphore.try_acquire();
|
||||
if (!acquired)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto delta = Time::TimeSinceEngineTick();
|
||||
const auto slowThreshold = Time::TickInterval * 2.0;
|
||||
if (delta > slowThreshold)
|
||||
{
|
||||
LOG_WARN("Tick thread ran slow: {:.2f}ms", 1000 * Time::TimeSinceEngineTick());
|
||||
}
|
||||
|
||||
Time::UpdateTickDelta();
|
||||
|
||||
// Allow tick thread to run.
|
||||
m_threadTickSemaphore.release();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Engine::UpdateInput()
|
||||
{
|
||||
m_window->AggregateInput(m_frameInput);
|
||||
m_tickInput.Aggregate(m_frameInput);
|
||||
}
|
||||
|
||||
void Engine::TickThread(const std::stop_token token)
|
||||
{
|
||||
LOG_INFO("Enter TickThread");
|
||||
|
||||
while (!token.stop_requested())
|
||||
{
|
||||
// Wait for main thread.
|
||||
m_threadTickSemaphore.acquire();
|
||||
|
||||
for (auto system : m_systems)
|
||||
{
|
||||
system->OnTick();
|
||||
}
|
||||
|
||||
if (m_world != nullptr)
|
||||
{
|
||||
m_world->Tick();
|
||||
}
|
||||
|
||||
m_tickInput.Clear();
|
||||
|
||||
// Let main thread know we are done.
|
||||
m_mainTickSemaphore.release();
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::RenderThread(const std::stop_token token)
|
||||
{
|
||||
LOG_INFO("Enter RenderThread");
|
||||
|
||||
while (!token.stop_requested())
|
||||
{
|
||||
// Wait for main thread.
|
||||
m_threadFrameSemaphore.acquire();
|
||||
|
||||
Time::StartRender();
|
||||
|
||||
if (m_window->IsMinimized())
|
||||
{
|
||||
Time::StopRender();
|
||||
m_mainFrameSemaphore.release();
|
||||
continue;
|
||||
}
|
||||
|
||||
m_renderer->Begin();
|
||||
|
||||
auto ubo = m_renderer->GetUBO();
|
||||
|
||||
ubo->sunColor = {};
|
||||
ubo->sunDir = {};
|
||||
|
||||
if (m_world != nullptr)
|
||||
{
|
||||
if (auto sky = m_world->GetSky())
|
||||
{
|
||||
ubo->sunDir = sky->SunDirection;
|
||||
ubo->sunColor = sky->SunColor;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_camera != nullptr)
|
||||
{
|
||||
ubo->SetCamera(m_camera);
|
||||
}
|
||||
|
||||
m_renderer->UpdateUBO();
|
||||
|
||||
if (m_world != nullptr)
|
||||
{
|
||||
m_world->Render();
|
||||
}
|
||||
|
||||
m_ui->Render();
|
||||
|
||||
m_renderer->Submit();
|
||||
|
||||
m_renderer->Present();
|
||||
|
||||
Time::StopRender();
|
||||
|
||||
// Let main thread know we are done.
|
||||
m_mainFrameSemaphore.release();
|
||||
}
|
||||
|
||||
LOG_INFO("Exit RenderThread");
|
||||
}
|
||||
} // namespace legs
|
8
src/entry.cpp
Normal file
8
src/entry.cpp
Normal file
|
@ -0,0 +1,8 @@
|
|||
#include <memory>
|
||||
|
||||
#include <legs/entry.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
std::shared_ptr<Engine> g_engine;
|
||||
}
|
337
src/job_system_thread_pool.cpp
Normal file
337
src/job_system_thread_pool.cpp
Normal file
|
@ -0,0 +1,337 @@
|
|||
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
|
||||
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <Jolt/Jolt.h>
|
||||
|
||||
#include <Jolt/Core/FPException.h>
|
||||
#include <Jolt/Core/Profiler.h>
|
||||
|
||||
#include "job_system_thread_pool.hpp"
|
||||
|
||||
#ifdef JPH_PLATFORM_LINUX
|
||||
#include <sys/prctl.h>
|
||||
#endif
|
||||
|
||||
using namespace JPH;
|
||||
|
||||
namespace legs
|
||||
{
|
||||
|
||||
void JobSystemThreadPool::Init(uint inMaxJobs, uint inMaxBarriers, int inNumThreads)
|
||||
{
|
||||
JobSystemWithBarrier::Init(inMaxBarriers);
|
||||
|
||||
// Init freelist of jobs
|
||||
mJobs.Init(inMaxJobs, inMaxJobs);
|
||||
|
||||
// Init queue
|
||||
for (std::atomic<Job*>& j : mQueue)
|
||||
{
|
||||
j = nullptr;
|
||||
}
|
||||
|
||||
// Start the worker threads
|
||||
StartThreads(inNumThreads);
|
||||
}
|
||||
|
||||
JobSystemThreadPool::JobSystemThreadPool(uint inMaxJobs, uint inMaxBarriers, int inNumThreads)
|
||||
{
|
||||
Init(inMaxJobs, inMaxBarriers, inNumThreads);
|
||||
}
|
||||
|
||||
void JobSystemThreadPool::StartThreads([[maybe_unused]] int inNumThreads)
|
||||
{
|
||||
#if !defined(JPH_CPU_WASM) \
|
||||
|| defined(__EMSCRIPTEN_PTHREADS__) // If we're running without threads support we cannot create
|
||||
// threads and we ignore the inNumThreads parameter
|
||||
// Auto detect number of threads
|
||||
if (inNumThreads < 0)
|
||||
{
|
||||
inNumThreads = std::thread::hardware_concurrency() - 1;
|
||||
}
|
||||
|
||||
// If no threads are requested we're done
|
||||
if (inNumThreads == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't quit the threads
|
||||
mQuit = false;
|
||||
|
||||
// Allocate heads
|
||||
mHeads =
|
||||
reinterpret_cast<std::atomic<uint>*>(JPH::Allocate(sizeof(std::atomic<uint>) * inNumThreads)
|
||||
);
|
||||
for (int i = 0; i < inNumThreads; ++i)
|
||||
{
|
||||
mHeads[i] = 0;
|
||||
}
|
||||
|
||||
// Start running threads
|
||||
JPH_ASSERT(mThreads.empty());
|
||||
mThreads.reserve(inNumThreads);
|
||||
for (int i = 0; i < inNumThreads; ++i)
|
||||
{
|
||||
mThreads.emplace_back([this, i] { ThreadMain(i); });
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
JobSystemThreadPool::~JobSystemThreadPool()
|
||||
{
|
||||
// Stop all worker threads
|
||||
StopThreads();
|
||||
}
|
||||
|
||||
void JobSystemThreadPool::StopThreads()
|
||||
{
|
||||
if (mThreads.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Signal threads that we want to stop and wake them up
|
||||
mQuit = true;
|
||||
mSemaphore.Release((uint)mThreads.size());
|
||||
|
||||
// Wait for all threads to finish
|
||||
for (std::thread& t : mThreads)
|
||||
{
|
||||
if (t.joinable())
|
||||
{
|
||||
t.join();
|
||||
}
|
||||
}
|
||||
|
||||
// Delete all threads
|
||||
mThreads.clear();
|
||||
|
||||
// Ensure that there are no lingering jobs in the queue
|
||||
for (uint head = 0; head != mTail; ++head)
|
||||
{
|
||||
// Fetch job
|
||||
Job* job_ptr = mQueue[head & (cQueueLength - 1)].exchange(nullptr);
|
||||
if (job_ptr != nullptr)
|
||||
{
|
||||
// And execute it
|
||||
job_ptr->Execute();
|
||||
job_ptr->Release();
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy heads and reset tail
|
||||
JPH::Free(mHeads);
|
||||
mHeads = nullptr;
|
||||
mTail = 0;
|
||||
}
|
||||
|
||||
JPH::JobHandle JobSystemThreadPool::CreateJob(
|
||||
const char* inJobName,
|
||||
JPH::ColorArg inColor,
|
||||
const JobFunction& inJobFunction,
|
||||
uint32_t inNumDependencies
|
||||
)
|
||||
{
|
||||
JPH_PROFILE_FUNCTION();
|
||||
|
||||
// Loop until we can get a job from the free list
|
||||
uint32_t index;
|
||||
for (;;)
|
||||
{
|
||||
index = mJobs.ConstructObject(inJobName, inColor, this, inJobFunction, inNumDependencies);
|
||||
if (index != AvailableJobs::cInvalidObjectIndex)
|
||||
{
|
||||
break;
|
||||
}
|
||||
JPH_ASSERT(false, "No jobs available!");
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(100));
|
||||
}
|
||||
Job* job = &mJobs.Get(index);
|
||||
|
||||
// Construct handle to keep a reference, the job is queued below and may immediately complete
|
||||
JobHandle handle(job);
|
||||
|
||||
// If there are no dependencies, queue the job now
|
||||
if (inNumDependencies == 0)
|
||||
{
|
||||
QueueJob(job);
|
||||
}
|
||||
|
||||
// Return the handle
|
||||
return handle;
|
||||
}
|
||||
|
||||
void JobSystemThreadPool::FreeJob(Job* inJob)
|
||||
{
|
||||
mJobs.DestructObject(inJob);
|
||||
}
|
||||
|
||||
uint JobSystemThreadPool::GetHead() const
|
||||
{
|
||||
// Find the minimal value across all threads
|
||||
uint head = mTail;
|
||||
for (size_t i = 0; i < mThreads.size(); ++i)
|
||||
{
|
||||
head = std::min(head, mHeads[i].load());
|
||||
}
|
||||
return head;
|
||||
}
|
||||
|
||||
void JobSystemThreadPool::QueueJobInternal(Job* inJob)
|
||||
{
|
||||
// Add reference to job because we're adding the job to the queue
|
||||
inJob->AddRef();
|
||||
|
||||
// Need to read head first because otherwise the tail can already have passed the head
|
||||
// We read the head outside of the loop since it involves iterating over all threads and we only
|
||||
// need to update it if there's not enough space in the queue.
|
||||
uint head = GetHead();
|
||||
|
||||
for (;;)
|
||||
{
|
||||
// Check if there's space in the queue
|
||||
uint old_value = mTail;
|
||||
if (old_value - head >= cQueueLength)
|
||||
{
|
||||
// We calculated the head outside of the loop, update head (and we also need to update
|
||||
// tail to prevent it from passing head)
|
||||
head = GetHead();
|
||||
old_value = mTail;
|
||||
|
||||
// Second check if there's space in the queue
|
||||
if (old_value - head >= cQueueLength)
|
||||
{
|
||||
// Wake up all threads in order to ensure that they can clear any nullptrs they may
|
||||
// not have processed yet
|
||||
mSemaphore.Release((uint)mThreads.size());
|
||||
|
||||
// Sleep a little (we have to wait for other threads to update their head pointer in
|
||||
// order for us to be able to continue)
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(100));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Write the job pointer if the slot is empty
|
||||
Job* expected_job = nullptr;
|
||||
bool success =
|
||||
mQueue[old_value & (cQueueLength - 1)].compare_exchange_strong(expected_job, inJob);
|
||||
|
||||
// Regardless of who wrote the slot, we will update the tail (if the successful thread got
|
||||
// scheduled out after writing the pointer we still want to be able to continue)
|
||||
mTail.compare_exchange_strong(old_value, old_value + 1);
|
||||
|
||||
// If we successfully added our job we're done
|
||||
if (success)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void JobSystemThreadPool::QueueJob(Job* inJob)
|
||||
{
|
||||
JPH_PROFILE_FUNCTION();
|
||||
|
||||
// If we have no worker threads, we can't queue the job either. We assume in this case that the
|
||||
// job will be added to a barrier and that the barrier will execute the job when it's Wait()
|
||||
// function is called.
|
||||
if (mThreads.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Queue the job
|
||||
QueueJobInternal(inJob);
|
||||
|
||||
// Wake up thread
|
||||
mSemaphore.Release();
|
||||
}
|
||||
|
||||
void JobSystemThreadPool::QueueJobs(Job** inJobs, uint inNumJobs)
|
||||
{
|
||||
JPH_PROFILE_FUNCTION();
|
||||
|
||||
JPH_ASSERT(inNumJobs > 0);
|
||||
|
||||
// If we have no worker threads, we can't queue the job either. We assume in this case that the
|
||||
// job will be added to a barrier and that the barrier will execute the job when it's Wait()
|
||||
// function is called.
|
||||
if (mThreads.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Queue all jobs
|
||||
for (Job **job = inJobs, **job_end = inJobs + inNumJobs; job < job_end; ++job)
|
||||
{
|
||||
QueueJobInternal(*job);
|
||||
}
|
||||
|
||||
// Wake up threads
|
||||
mSemaphore.Release(std::min(inNumJobs, (uint)mThreads.size()));
|
||||
}
|
||||
|
||||
static void SetThreadName(const char* inName)
|
||||
{
|
||||
JPH_ASSERT(strlen(inName) < 16); // String will be truncated if it is longer
|
||||
prctl(PR_SET_NAME, inName, 0, 0, 0);
|
||||
}
|
||||
|
||||
void JobSystemThreadPool::ThreadMain(int inThreadIndex)
|
||||
{
|
||||
// Name the thread
|
||||
char name[64];
|
||||
snprintf(name, sizeof(name), "Worker %d", int(inThreadIndex + 1));
|
||||
|
||||
#if defined(JPH_PLATFORM_WINDOWS) || defined(JPH_PLATFORM_LINUX)
|
||||
SetThreadName(name);
|
||||
#endif // JPH_PLATFORM_WINDOWS && !JPH_COMPILER_MINGW
|
||||
|
||||
// Enable floating point exceptions
|
||||
JPH::FPExceptionsEnable enable_exceptions;
|
||||
JPH_UNUSED(enable_exceptions);
|
||||
|
||||
JPH_PROFILE_THREAD_START(name);
|
||||
|
||||
// Call the thread init function
|
||||
mThreadInitFunction(inThreadIndex);
|
||||
|
||||
std::atomic<uint>& head = mHeads[inThreadIndex];
|
||||
|
||||
while (!mQuit)
|
||||
{
|
||||
// Wait for jobs
|
||||
mSemaphore.Acquire();
|
||||
|
||||
{
|
||||
JPH_PROFILE("Executing Jobs");
|
||||
|
||||
// Loop over the queue
|
||||
while (head != mTail)
|
||||
{
|
||||
// Exchange any job pointer we find with a nullptr
|
||||
std::atomic<Job*>& job = mQueue[head & (cQueueLength - 1)];
|
||||
if (job.load() != nullptr)
|
||||
{
|
||||
Job* job_ptr = job.exchange(nullptr);
|
||||
if (job_ptr != nullptr)
|
||||
{
|
||||
// And execute it
|
||||
job_ptr->Execute();
|
||||
job_ptr->Release();
|
||||
}
|
||||
}
|
||||
head++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call the thread exit function
|
||||
mThreadExitFunction(inThreadIndex);
|
||||
|
||||
JPH_PROFILE_THREAD_END();
|
||||
}
|
||||
}; // namespace legs
|
119
src/job_system_thread_pool.hpp
Normal file
119
src/job_system_thread_pool.hpp
Normal file
|
@ -0,0 +1,119 @@
|
|||
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
|
||||
// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include <legs/jolt_pch.hpp>
|
||||
|
||||
#include "job_system_with_barrier.hpp"
|
||||
|
||||
namespace legs
|
||||
{
|
||||
|
||||
/// Implementation of a JobSystem using a thread pool
|
||||
///
|
||||
/// Note that this is considered an example implementation. It is expected that when you integrate
|
||||
/// the physics engine into your own project that you'll provide your own implementation of the
|
||||
/// JobSystem built on top of whatever job system your project uses.
|
||||
class JobSystemThreadPool final : public JobSystemWithBarrier
|
||||
{
|
||||
public:
|
||||
/// Creates a thread pool.
|
||||
/// @see JobSystemThreadPool::Init
|
||||
JobSystemThreadPool(uint inMaxJobs, uint inMaxBarriers, int inNumThreads = -1);
|
||||
JobSystemThreadPool() = default;
|
||||
virtual ~JobSystemThreadPool() override;
|
||||
|
||||
/// Functions to call when a thread is initialized or exits, must be set before calling Init()
|
||||
using InitExitFunction = JPH::function<void(int)>;
|
||||
|
||||
void SetThreadInitFunction(const InitExitFunction& inInitFunction)
|
||||
{
|
||||
mThreadInitFunction = inInitFunction;
|
||||
}
|
||||
|
||||
void SetThreadExitFunction(const InitExitFunction& inExitFunction)
|
||||
{
|
||||
mThreadExitFunction = inExitFunction;
|
||||
}
|
||||
|
||||
/// Initialize the thread pool
|
||||
/// @param inMaxJobs Max number of jobs that can be allocated at any time
|
||||
/// @param inMaxBarriers Max number of barriers that can be allocated at any time
|
||||
/// @param inNumThreads Number of threads to start (the number of concurrent jobs is 1 more
|
||||
/// because the main thread will also run jobs while waiting for a barrier to complete). Use -1
|
||||
/// to auto detect the amount of CPU's.
|
||||
void Init(uint inMaxJobs, uint inMaxBarriers, int inNumThreads = -1);
|
||||
|
||||
// See JobSystem
|
||||
virtual int GetMaxConcurrency() const override
|
||||
{
|
||||
return int(mThreads.size()) + 1;
|
||||
}
|
||||
|
||||
virtual JobHandle CreateJob(
|
||||
const char* inName,
|
||||
JPH::ColorArg inColor,
|
||||
const JobFunction& inJobFunction,
|
||||
uint32_t inNumDependencies = 0
|
||||
) override;
|
||||
|
||||
/// Change the max concurrency after initialization
|
||||
void SetNumThreads(int inNumThreads)
|
||||
{
|
||||
StopThreads();
|
||||
StartThreads(inNumThreads);
|
||||
}
|
||||
|
||||
protected:
|
||||
// See JobSystem
|
||||
virtual void QueueJob(Job* inJob) override;
|
||||
virtual void QueueJobs(Job** inJobs, uint inNumJobs) override;
|
||||
virtual void FreeJob(Job* inJob) override;
|
||||
|
||||
private:
|
||||
/// Start/stop the worker threads
|
||||
void StartThreads(int inNumThreads);
|
||||
void StopThreads();
|
||||
|
||||
/// Entry point for a thread
|
||||
void ThreadMain(int inThreadIndex);
|
||||
|
||||
/// Get the head of the thread that has processed the least amount of jobs
|
||||
inline uint GetHead() const;
|
||||
|
||||
/// Internal helper function to queue a job
|
||||
inline void QueueJobInternal(Job* inJob);
|
||||
|
||||
/// Functions to call when initializing or exiting a thread
|
||||
InitExitFunction mThreadInitFunction = [](int) {};
|
||||
InitExitFunction mThreadExitFunction = [](int) {};
|
||||
|
||||
/// Array of jobs (fixed size)
|
||||
using AvailableJobs = JPH::FixedSizeFreeList<Job>;
|
||||
AvailableJobs mJobs;
|
||||
|
||||
/// Threads running jobs
|
||||
JPH::Array<std::thread> mThreads;
|
||||
|
||||
// The job queue
|
||||
static constexpr uint32_t cQueueLength = 1024;
|
||||
static_assert(JPH::IsPowerOf2(cQueueLength)
|
||||
); // We do bit operations and require queue length to be a power of 2
|
||||
std::atomic<Job*> mQueue[cQueueLength];
|
||||
|
||||
// Head and tail of the queue, do this value modulo cQueueLength - 1 to get the element in the
|
||||
// mQueue array
|
||||
std::atomic<uint>* mHeads = nullptr; ///< Per executing thread the head of the current queue
|
||||
alignas(JPH_CACHE_LINE_SIZE) std::atomic<uint> mTail = 0; ///< Tail (write end) of the queue
|
||||
|
||||
// Semaphore used to signal worker threads that there is new work
|
||||
JPH::Semaphore mSemaphore;
|
||||
|
||||
/// Boolean to indicate that we want to stop the job system
|
||||
std::atomic<bool> mQuit = false;
|
||||
};
|
||||
}; // namespace legs
|
253
src/job_system_with_barrier.cpp
Normal file
253
src/job_system_with_barrier.cpp
Normal file
|
@ -0,0 +1,253 @@
|
|||
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
|
||||
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include <Jolt/Jolt.h>
|
||||
|
||||
#include <Jolt/Core/JobSystemWithBarrier.h>
|
||||
#include <Jolt/Core/Profiler.h>
|
||||
|
||||
#include "job_system_with_barrier.hpp"
|
||||
|
||||
using namespace JPH;
|
||||
|
||||
namespace legs
|
||||
{
|
||||
|
||||
JobSystemWithBarrier::BarrierImpl::BarrierImpl()
|
||||
{
|
||||
for (std::atomic<Job*>& j : mJobs)
|
||||
{
|
||||
j = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
JobSystemWithBarrier::BarrierImpl::~BarrierImpl()
|
||||
{
|
||||
JPH_ASSERT(IsEmpty());
|
||||
}
|
||||
|
||||
void JobSystemWithBarrier::BarrierImpl::AddJob(const JobHandle& inJob)
|
||||
{
|
||||
JPH_PROFILE_FUNCTION();
|
||||
|
||||
bool release_semaphore = false;
|
||||
|
||||
// Set the barrier on the job, this returns true if the barrier was successfully set (otherwise
|
||||
// the job is already done and we don't need to add it to our list)
|
||||
Job* job = inJob.GetPtr();
|
||||
if (job->SetBarrier(this))
|
||||
{
|
||||
// If the job can be executed we want to release the semaphore an extra time to allow the
|
||||
// waiting thread to start executing it
|
||||
mNumToAcquire++;
|
||||
if (job->CanBeExecuted())
|
||||
{
|
||||
release_semaphore = true;
|
||||
mNumToAcquire++;
|
||||
}
|
||||
|
||||
// Add the job to our job list
|
||||
job->AddRef();
|
||||
uint write_index = mJobWriteIndex++;
|
||||
while (write_index - mJobReadIndex >= cMaxJobs)
|
||||
{
|
||||
JPH_ASSERT(false, "Barrier full, stalling!");
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(100));
|
||||
}
|
||||
mJobs[write_index & (cMaxJobs - 1)] = job;
|
||||
}
|
||||
|
||||
// Notify waiting thread that a new executable job is available
|
||||
if (release_semaphore)
|
||||
{
|
||||
mSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
void JobSystemWithBarrier::BarrierImpl::AddJobs(const JobHandle* inHandles, uint inNumHandles)
|
||||
{
|
||||
JPH_PROFILE_FUNCTION();
|
||||
|
||||
bool release_semaphore = false;
|
||||
|
||||
for (const JobHandle *handle = inHandles, *handles_end = inHandles + inNumHandles;
|
||||
handle < handles_end;
|
||||
++handle)
|
||||
{
|
||||
// Set the barrier on the job, this returns true if the barrier was successfully set
|
||||
// (otherwise the job is already done and we don't need to add it to our list)
|
||||
Job* job = handle->GetPtr();
|
||||
if (job->SetBarrier(this))
|
||||
{
|
||||
// If the job can be executed we want to release the semaphore an extra time to allow
|
||||
// the waiting thread to start executing it
|
||||
mNumToAcquire++;
|
||||
if (!release_semaphore && job->CanBeExecuted())
|
||||
{
|
||||
release_semaphore = true;
|
||||
mNumToAcquire++;
|
||||
}
|
||||
|
||||
// Add the job to our job list
|
||||
job->AddRef();
|
||||
uint write_index = mJobWriteIndex++;
|
||||
while (write_index - mJobReadIndex >= cMaxJobs)
|
||||
{
|
||||
JPH_ASSERT(false, "Barrier full, stalling!");
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(100));
|
||||
}
|
||||
mJobs[write_index & (cMaxJobs - 1)] = job;
|
||||
}
|
||||
}
|
||||
|
||||
// Notify waiting thread that a new executable job is available
|
||||
if (release_semaphore)
|
||||
{
|
||||
mSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
void JobSystemWithBarrier::BarrierImpl::OnJobFinished(Job* inJob)
|
||||
{
|
||||
JPH_PROFILE_FUNCTION();
|
||||
|
||||
mSemaphore.Release();
|
||||
}
|
||||
|
||||
void JobSystemWithBarrier::BarrierImpl::Wait()
|
||||
{
|
||||
while (mNumToAcquire > 0)
|
||||
{
|
||||
{
|
||||
JPH_PROFILE("Execute Jobs");
|
||||
|
||||
// Go through all jobs
|
||||
bool has_executed;
|
||||
do
|
||||
{
|
||||
has_executed = false;
|
||||
|
||||
// Loop through the jobs and erase jobs from the beginning of the list that are done
|
||||
while (mJobReadIndex < mJobWriteIndex)
|
||||
{
|
||||
std::atomic<Job*>& job = mJobs[mJobReadIndex & (cMaxJobs - 1)];
|
||||
Job* job_ptr = job.load();
|
||||
if (job_ptr == nullptr || !job_ptr->IsDone())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Job is finished, release it
|
||||
job_ptr->Release();
|
||||
job = nullptr;
|
||||
++mJobReadIndex;
|
||||
}
|
||||
|
||||
// Loop through the jobs and execute the first executable job
|
||||
for (uint index = mJobReadIndex; index < mJobWriteIndex; ++index)
|
||||
{
|
||||
const std::atomic<Job*>& job = mJobs[index & (cMaxJobs - 1)];
|
||||
Job* job_ptr = job.load();
|
||||
if (job_ptr != nullptr && job_ptr->CanBeExecuted())
|
||||
{
|
||||
// This will only execute the job if it has not already executed
|
||||
job_ptr->Execute();
|
||||
has_executed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} while (has_executed);
|
||||
}
|
||||
|
||||
// Wait for another thread to wake us when either there is more work to do or when all jobs
|
||||
// have completed
|
||||
int num_to_acquire = std::max(
|
||||
1,
|
||||
mSemaphore.GetValue()
|
||||
); // When there have been multiple releases, we acquire them all at the same time to
|
||||
// avoid needlessly spinning on executing jobs
|
||||
mSemaphore.Acquire(num_to_acquire);
|
||||
mNumToAcquire -= num_to_acquire;
|
||||
}
|
||||
|
||||
// All jobs should be done now, release them
|
||||
while (mJobReadIndex < mJobWriteIndex)
|
||||
{
|
||||
std::atomic<Job*>& job = mJobs[mJobReadIndex & (cMaxJobs - 1)];
|
||||
Job* job_ptr = job.load();
|
||||
JPH_ASSERT(job_ptr != nullptr && job_ptr->IsDone());
|
||||
job_ptr->Release();
|
||||
job = nullptr;
|
||||
++mJobReadIndex;
|
||||
}
|
||||
}
|
||||
|
||||
void JobSystemWithBarrier::Init(uint inMaxBarriers)
|
||||
{
|
||||
JPH_ASSERT(mBarriers == nullptr); // Already initialized?
|
||||
|
||||
// Init freelist of barriers
|
||||
mMaxBarriers = inMaxBarriers;
|
||||
mBarriers = new BarrierImpl[inMaxBarriers];
|
||||
}
|
||||
|
||||
JobSystemWithBarrier::JobSystemWithBarrier(uint inMaxBarriers)
|
||||
{
|
||||
Init(inMaxBarriers);
|
||||
}
|
||||
|
||||
JobSystemWithBarrier::~JobSystemWithBarrier()
|
||||
{
|
||||
// Ensure that none of the barriers are used
|
||||
#ifdef JPH_ENABLE_ASSERTS
|
||||
for (const BarrierImpl *b = mBarriers, *b_end = mBarriers + mMaxBarriers; b < b_end; ++b)
|
||||
{
|
||||
JPH_ASSERT(!b->mInUse);
|
||||
}
|
||||
#endif // JPH_ENABLE_ASSERTS
|
||||
delete[] mBarriers;
|
||||
}
|
||||
|
||||
JPH::JobSystem::Barrier* JobSystemWithBarrier::CreateBarrier()
|
||||
{
|
||||
JPH_PROFILE_FUNCTION();
|
||||
|
||||
// Find the first unused barrier
|
||||
for (uint32_t index = 0; index < mMaxBarriers; ++index)
|
||||
{
|
||||
bool expected = false;
|
||||
if (mBarriers[index].mInUse.compare_exchange_strong(expected, true))
|
||||
{
|
||||
return &mBarriers[index];
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void JobSystemWithBarrier::DestroyBarrier(Barrier* inBarrier)
|
||||
{
|
||||
JPH_PROFILE_FUNCTION();
|
||||
|
||||
// Check that no jobs are in the barrier
|
||||
JPH_ASSERT(static_cast<BarrierImpl*>(inBarrier)->IsEmpty());
|
||||
|
||||
// Flag the barrier as unused
|
||||
bool expected = true;
|
||||
static_cast<BarrierImpl*>(inBarrier)->mInUse.compare_exchange_strong(expected, false);
|
||||
JPH_ASSERT(expected);
|
||||
}
|
||||
|
||||
void JobSystemWithBarrier::WaitForJobs(Barrier* inBarrier)
|
||||
{
|
||||
JPH_PROFILE_FUNCTION();
|
||||
|
||||
// Let our barrier implementation wait for the jobs
|
||||
static_cast<BarrierImpl*>(inBarrier)->Wait();
|
||||
}
|
||||
|
||||
}; // namespace legs
|
95
src/job_system_with_barrier.hpp
Normal file
95
src/job_system_with_barrier.hpp
Normal file
|
@ -0,0 +1,95 @@
|
|||
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
|
||||
// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <legs/jolt_pch.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
|
||||
/// Implementation of the Barrier class for a JobSystem
|
||||
///
|
||||
/// This class can be used to make it easier to create a new JobSystem implementation that
|
||||
/// integrates with your own job system. It will implement all functionality relating to barriers,
|
||||
/// so the only functions that are left to be implemented are:
|
||||
///
|
||||
/// * JobSystem::GetMaxConcurrency
|
||||
/// * JobSystem::CreateJob
|
||||
/// * JobSystem::FreeJob
|
||||
/// * JobSystem::QueueJob/QueueJobs
|
||||
///
|
||||
/// See instructions in JobSystem for more information on how to implement these.
|
||||
class JobSystemWithBarrier : public JPH::JobSystem
|
||||
{
|
||||
public:
|
||||
/// Constructs barriers
|
||||
/// @see JobSystemWithBarrier::Init
|
||||
explicit JobSystemWithBarrier(uint inMaxBarriers);
|
||||
JobSystemWithBarrier() = default;
|
||||
virtual ~JobSystemWithBarrier() override;
|
||||
|
||||
/// Initialize the barriers
|
||||
/// @param inMaxBarriers Max number of barriers that can be allocated at any time
|
||||
void Init(uint inMaxBarriers);
|
||||
|
||||
// See JobSystem
|
||||
virtual Barrier* CreateBarrier() override;
|
||||
virtual void DestroyBarrier(Barrier* inBarrier) override;
|
||||
virtual void WaitForJobs(Barrier* inBarrier) override;
|
||||
|
||||
private:
|
||||
class BarrierImpl : public Barrier
|
||||
{
|
||||
public:
|
||||
/// Constructor
|
||||
BarrierImpl();
|
||||
virtual ~BarrierImpl() override;
|
||||
|
||||
// See Barrier
|
||||
virtual void AddJob(const JobHandle& inJob) override;
|
||||
virtual void AddJobs(const JobHandle* inHandles, uint inNumHandles) override;
|
||||
|
||||
/// Check if there are any jobs in the job barrier
|
||||
inline bool IsEmpty() const
|
||||
{
|
||||
return mJobReadIndex == mJobWriteIndex;
|
||||
}
|
||||
|
||||
/// Wait for all jobs in this job barrier, while waiting, execute jobs that are part of this
|
||||
/// barrier on the current thread
|
||||
void Wait();
|
||||
|
||||
/// Flag to indicate if a barrier has been handed out
|
||||
std::atomic<bool> mInUse {false};
|
||||
|
||||
protected:
|
||||
/// Called by a Job to mark that it is finished
|
||||
virtual void OnJobFinished(Job* inJob) override;
|
||||
|
||||
/// Jobs queue for the barrier
|
||||
static constexpr uint cMaxJobs = 2048;
|
||||
static_assert(JPH::IsPowerOf2(cMaxJobs)
|
||||
); // We do bit operations and require max jobs to be a power of 2
|
||||
std::atomic<Job*> mJobs[cMaxJobs]; ///< List of jobs that are part of this barrier, nullptrs
|
||||
///< for empty slots
|
||||
alignas(JPH_CACHE_LINE_SIZE) std::atomic<uint> mJobReadIndex {0
|
||||
}; ///< First job that could be valid (modulo cMaxJobs), can be nullptr if other thread is
|
||||
///< still working on adding the job
|
||||
alignas(JPH_CACHE_LINE_SIZE) std::atomic<uint> mJobWriteIndex {0
|
||||
}; ///< First job that can be written (modulo cMaxJobs)
|
||||
std::atomic<int> mNumToAcquire {0
|
||||
}; ///< Number of times the semaphore has been released, the barrier should acquire the
|
||||
///< semaphore this many times (written at the same time as mJobWriteIndex so ok to put
|
||||
///< in same cache line)
|
||||
JPH::Semaphore mSemaphore; ///< Semaphore used by finishing jobs to signal the barrier that
|
||||
///< they're done
|
||||
};
|
||||
|
||||
/// Array of barriers (we keep them constructed all the time since constructing a
|
||||
/// semaphore/mutex is not cheap)
|
||||
uint mMaxBarriers = 0; ///< Max amount of barriers
|
||||
BarrierImpl* mBarriers = nullptr; ///< List of the actual barriers
|
||||
};
|
||||
}; // namespace legs
|
57
src/meson.build
Normal file
57
src/meson.build
Normal file
|
@ -0,0 +1,57 @@
|
|||
sdl_dep = dependency('sdl2', include_type: 'system')
|
||||
vk_dep = dependency('vulkan', include_type: 'system')
|
||||
|
||||
legs_src = files(
|
||||
'renderer/buffer.cpp',
|
||||
'renderer/descriptor_set.cpp',
|
||||
'renderer/device.cpp',
|
||||
'renderer/instance.cpp',
|
||||
'renderer/renderer.cpp',
|
||||
'renderer/vma_usage.cpp',
|
||||
|
||||
'ui/ui.cpp',
|
||||
|
||||
'window/window.cpp',
|
||||
|
||||
'world/world.cpp',
|
||||
|
||||
'engine.cpp',
|
||||
'entry.cpp',
|
||||
'job_system_thread_pool.cpp',
|
||||
'job_system_with_barrier.cpp',
|
||||
'physics.cpp',
|
||||
)
|
||||
|
||||
legs_phc = [
|
||||
'public/legs/jolt_pch.hpp',
|
||||
]
|
||||
|
||||
legs_shaders = files(
|
||||
'public/legs/shaders/fullscreen_frag.frag',
|
||||
'public/legs/shaders/fullscreen_vert.vert',
|
||||
'public/legs/shaders/lit_pnc_frag.frag',
|
||||
'public/legs/shaders/lit_pnc_vert.vert',
|
||||
'public/legs/shaders/sky_frag.frag',
|
||||
'public/legs/shaders/sky_vert.vert',
|
||||
'public/legs/shaders/unlit_pc_frag.frag',
|
||||
'public/legs/shaders/unlit_pc_vert.vert',
|
||||
)
|
||||
|
||||
legs_public_includes = include_directories(['public/'])
|
||||
|
||||
legs_lib = library(
|
||||
'legs',
|
||||
legs_src + imgui_src,
|
||||
glsl_generator.process(legs_shaders),
|
||||
legs_version,
|
||||
link_with: [],
|
||||
dependencies: [jolt_dep, sdl_dep, vk_dep],
|
||||
include_directories: [legs_includes, legs_public_includes],
|
||||
cpp_pch: legs_phc,
|
||||
)
|
||||
|
||||
legs_dep = declare_dependency(
|
||||
link_with: [legs_lib],
|
||||
dependencies: [jolt_dep, sdl_dep, vk_dep],
|
||||
include_directories: [legs_includes, legs_public_includes],
|
||||
)
|
224
src/physics.cpp
Normal file
224
src/physics.cpp
Normal file
|
@ -0,0 +1,224 @@
|
|||
#include <cmath>
|
||||
#include <cstdarg>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
|
||||
#include <legs/time.hpp>
|
||||
|
||||
#include "physics.hpp"
|
||||
|
||||
namespace legs
|
||||
{
|
||||
|
||||
// Callback for traces, connect this to your own trace function if you have one
|
||||
static void TraceImpl(const char* fmt, ...)
|
||||
{
|
||||
va_list list;
|
||||
va_start(list, fmt);
|
||||
char buffer[1024];
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, list);
|
||||
va_end(list);
|
||||
|
||||
LOG_ERROR("JOLT TRACE: {}", buffer);
|
||||
}
|
||||
|
||||
#ifdef JPH_ENABLE_ASSERTS
|
||||
|
||||
// Callback for asserts, connect this to your own assert handler if you have one
|
||||
static bool AssertFailedImpl(
|
||||
const char* inExpression,
|
||||
const char* inMessage,
|
||||
const char* inFile,
|
||||
uint inLine
|
||||
)
|
||||
{
|
||||
Log::Print(inFile, inLine, inExpression, LogLevel::Error, inMessage);
|
||||
|
||||
// Breakpoint
|
||||
return true;
|
||||
};
|
||||
|
||||
#endif // JPH_ENABLE_ASSERTS
|
||||
|
||||
void Physics::Register()
|
||||
{
|
||||
// Register allocation hook. In this example we'll just let Jolt use malloc / free but you can
|
||||
// override these if you want (see Memory.h). This needs to be done before any other Jolt
|
||||
// function is called.
|
||||
JPH::RegisterDefaultAllocator();
|
||||
|
||||
// Install trace and assert callbacks
|
||||
JPH::Trace = TraceImpl;
|
||||
JPH_IF_ENABLE_ASSERTS(JPH::AssertFailed = AssertFailedImpl;)
|
||||
|
||||
// Create a factory, this class is responsible for creating instances of classes based on their
|
||||
// name or hash and is mainly used for deserialization of saved data. It is not directly used in
|
||||
// this example but still required.
|
||||
JPH::Factory::sInstance = new JPH::Factory();
|
||||
|
||||
// Register all physics types with the factory and install their collision handlers with the
|
||||
// CollisionDispatch class. If you have your own custom shape types you probably need to
|
||||
// register their handlers with the CollisionDispatch before calling this function. If you
|
||||
// implement your own default material (PhysicsMaterial::sDefault) make sure to initialize it
|
||||
// before this function or else this function will create one for you.
|
||||
JPH::RegisterTypes();
|
||||
}
|
||||
|
||||
Physics::Physics() :
|
||||
m_tempAllocator(10 * 1024 * 1024),
|
||||
m_jobSystem(
|
||||
JPH::cMaxPhysicsJobs,
|
||||
JPH::cMaxPhysicsBarriers,
|
||||
static_cast<int>(std::thread::hardware_concurrency()) - 1
|
||||
),
|
||||
m_maxDeltaTime(1.0f / 60.0f)
|
||||
{
|
||||
// This is the max amount of rigid bodies that you can add to the physics system. If you try to
|
||||
// add more you'll get an error. Note: This value is low because this is a simple test. For a
|
||||
// real project use something in the order of 65536.
|
||||
const uint cMaxBodies = 1024;
|
||||
|
||||
// This determines how many mutexes to allocate to protect rigid bodies from concurrent access.
|
||||
// Set it to 0 for the default settings.
|
||||
const uint cNumBodyMutexes = 0;
|
||||
|
||||
// This is the max amount of body pairs that can be queued at any time (the broad phase will
|
||||
// detect overlapping body pairs based on their bounding boxes and will insert them into a queue
|
||||
// for the narrowphase). If you make this buffer too small the queue will fill up and the broad
|
||||
// phase jobs will start to do narrow phase work. This is slightly less efficient. Note: This
|
||||
// value is low because this is a simple test. For a real project use something in the order of
|
||||
// 65536.
|
||||
const uint cMaxBodyPairs = 1024;
|
||||
|
||||
// This is the maximum size of the contact constraint buffer. If more contacts (collisions
|
||||
// between bodies) are detected than this number then these contacts will be ignored and bodies
|
||||
// will start interpenetrating / fall through the world. Note: This value is low because this is
|
||||
// a simple test. For a real project use something in the order of 10240.
|
||||
const uint cMaxContactConstraints = 1024;
|
||||
|
||||
// Now we can create the actual physics system.
|
||||
m_physicsSystem.Init(
|
||||
cMaxBodies,
|
||||
cNumBodyMutexes,
|
||||
cMaxBodyPairs,
|
||||
cMaxContactConstraints,
|
||||
m_broadPhaseLayerInterface,
|
||||
m_objectVsBroadphaseLayerFilter,
|
||||
m_objectVsObjectLayerFilter
|
||||
);
|
||||
|
||||
// A body activation listener gets notified when bodies activate and go to sleep
|
||||
// Note that this is called from a job so whatever you do here needs to be thread safe.
|
||||
// Registering one is entirely optional.
|
||||
m_physicsSystem.SetBodyActivationListener(&m_bodyActivationListener);
|
||||
|
||||
// A contact listener gets notified when bodies (are about to) collide, and when they separate
|
||||
// again. Note that this is called from a job so whatever you do here needs to be thread safe.
|
||||
// Registering one is entirely optional.
|
||||
m_physicsSystem.SetContactListener(&m_contactListener);
|
||||
}
|
||||
|
||||
Physics::~Physics()
|
||||
{
|
||||
// Unregisters all types with the factory and cleans up the default material
|
||||
JPH::UnregisterTypes();
|
||||
|
||||
// Destroy the factory
|
||||
if (JPH::Factory::sInstance != nullptr)
|
||||
{
|
||||
delete JPH::Factory::sInstance;
|
||||
JPH::Factory::sInstance = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Physics::Optimize()
|
||||
{
|
||||
m_physicsSystem.OptimizeBroadPhase();
|
||||
}
|
||||
|
||||
void Physics::Update()
|
||||
{
|
||||
const unsigned int steps = std::ceil(Time::DeltaTick / m_maxDeltaTime);
|
||||
m_physicsSystem.Update(Time::DeltaTick, steps, &m_tempAllocator, &m_jobSystem);
|
||||
}
|
||||
|
||||
JPH::BodyID Physics::CreateBody(JPH::BodyCreationSettings settings)
|
||||
{
|
||||
auto body = m_physicsSystem.GetBodyInterface().CreateBody(settings);
|
||||
if (body != nullptr)
|
||||
{
|
||||
return body->GetID();
|
||||
}
|
||||
return JPH::BodyID(JPH::BodyID::cInvalidBodyID);
|
||||
}
|
||||
|
||||
void Physics::AddBody(JPH::BodyID id)
|
||||
{
|
||||
m_physicsSystem.GetBodyInterface().AddBody(id, JPH::EActivation::Activate);
|
||||
}
|
||||
|
||||
void Physics::RemoveBody(JPH::BodyID id)
|
||||
{
|
||||
m_physicsSystem.GetBodyInterface().RemoveBody(id);
|
||||
}
|
||||
|
||||
void Physics::DestroyBody(JPH::BodyID id)
|
||||
{
|
||||
m_physicsSystem.GetBodyInterface().DestroyBody(id);
|
||||
}
|
||||
|
||||
void Physics::GetBodyTransform(JPH::BodyID id, std::shared_ptr<STransform> trans)
|
||||
{
|
||||
JPH::RVec3 joltPos;
|
||||
JPH::Quat joltRot;
|
||||
|
||||
m_physicsSystem.GetBodyInterface().GetPositionAndRotation(id, joltPos, joltRot);
|
||||
|
||||
trans->position.x = joltPos.GetX();
|
||||
trans->position.y = joltPos.GetY();
|
||||
trans->position.z = joltPos.GetZ();
|
||||
|
||||
trans->rotation.quaternion.x = joltRot.GetX();
|
||||
trans->rotation.quaternion.y = joltRot.GetY();
|
||||
trans->rotation.quaternion.z = joltRot.GetZ();
|
||||
trans->rotation.quaternion.w = joltRot.GetW();
|
||||
}
|
||||
|
||||
void Physics::SetBodyTransform(JPH::BodyID id, std::shared_ptr<STransform> trans)
|
||||
{
|
||||
JPH::RVec3 joltPos = {trans->position.x, trans->position.y, trans->position.z};
|
||||
JPH::Quat joltRot = {
|
||||
trans->rotation.quaternion.x,
|
||||
trans->rotation.quaternion.y,
|
||||
trans->rotation.quaternion.w,
|
||||
trans->rotation.quaternion.z
|
||||
};
|
||||
|
||||
m_physicsSystem.GetBodyInterface()
|
||||
.SetPositionAndRotation(id, joltPos, joltRot, JPH::EActivation::Activate);
|
||||
}
|
||||
|
||||
void Physics::SetBodyPosition(JPH::BodyID id, glm::vec3 pos)
|
||||
{
|
||||
JPH::RVec3 joltPos = {pos.x, pos.y, pos.z};
|
||||
m_physicsSystem.GetBodyInterface().SetPosition(id, joltPos, JPH::EActivation::Activate);
|
||||
}
|
||||
|
||||
void Physics::SetBodyRotation(JPH::BodyID id, glm::quat rot)
|
||||
{
|
||||
JPH::Quat joltRot = {rot.x, rot.y, rot.w, rot.z};
|
||||
m_physicsSystem.GetBodyInterface().SetRotation(id, joltRot, JPH::EActivation::Activate);
|
||||
}
|
||||
|
||||
void Physics::SetBodyVelocity(JPH::BodyID id, glm::vec3 vel)
|
||||
{
|
||||
JPH::RVec3 joltVel = {vel.x, vel.y, vel.z};
|
||||
m_physicsSystem.GetBodyInterface().SetPosition(id, joltVel, JPH::EActivation::Activate);
|
||||
}
|
||||
|
||||
void Physics::SetBodyAngularVelocity(JPH::BodyID id, glm::vec3 vel)
|
||||
{
|
||||
JPH::RVec3 joltVel = {vel.x, vel.y, vel.z};
|
||||
m_physicsSystem.GetBodyInterface().SetPosition(id, joltVel, JPH::EActivation::Activate);
|
||||
}
|
||||
}; // namespace legs
|
187
src/physics.hpp
Normal file
187
src/physics.hpp
Normal file
|
@ -0,0 +1,187 @@
|
|||
#pragma once
|
||||
|
||||
#include <legs/collider.hpp>
|
||||
#include <legs/iphysics.hpp>
|
||||
#include <legs/log.hpp>
|
||||
|
||||
#include "job_system_thread_pool.hpp"
|
||||
|
||||
namespace legs
|
||||
{
|
||||
// Each broadphase layer results in a separate bounding volume tree in the broad phase. You at least
|
||||
// want to have a layer for non-moving and moving objects to avoid having to update a tree full of
|
||||
// static objects every frame. You can have a 1-on-1 mapping between object layers and broadphase
|
||||
// layers (like in this case) but if you have many object layers you'll be creating many broad phase
|
||||
// trees, which is not efficient. If you want to fine tune your broadphase layers define
|
||||
// JPH_TRACK_BROADPHASE_STATS and look at the stats reported on the TTY.
|
||||
namespace BroadPhaseLayers
|
||||
{
|
||||
static constexpr JPH::BroadPhaseLayer NON_MOVING(0);
|
||||
static constexpr JPH::BroadPhaseLayer MOVING(1);
|
||||
static constexpr uint NUM_LAYERS(2);
|
||||
}; // namespace BroadPhaseLayers
|
||||
|
||||
// BroadPhaseLayerInterface implementation
|
||||
// This defines a mapping between object and broadphase layers.
|
||||
class BPLayerInterfaceImpl final : public JPH::BroadPhaseLayerInterface
|
||||
{
|
||||
public:
|
||||
BPLayerInterfaceImpl()
|
||||
{
|
||||
// Create a mapping table from object to broad phase layer
|
||||
mObjectToBroadPhase[Layers::NON_MOVING] = BroadPhaseLayers::NON_MOVING;
|
||||
mObjectToBroadPhase[Layers::MOVING] = BroadPhaseLayers::MOVING;
|
||||
}
|
||||
|
||||
virtual uint GetNumBroadPhaseLayers() const override
|
||||
{
|
||||
return BroadPhaseLayers::NUM_LAYERS;
|
||||
}
|
||||
|
||||
virtual JPH::BroadPhaseLayer GetBroadPhaseLayer(JPH::ObjectLayer inLayer) const override
|
||||
{
|
||||
JPH_ASSERT(inLayer < Layers::NUM_LAYERS);
|
||||
return mObjectToBroadPhase[inLayer];
|
||||
}
|
||||
|
||||
#if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
|
||||
virtual const char* GetBroadPhaseLayerName(JPH::BroadPhaseLayer inLayer) const override
|
||||
{
|
||||
switch ((JPH::BroadPhaseLayer::Type)inLayer)
|
||||
{
|
||||
case (JPH::BroadPhaseLayer::Type)BroadPhaseLayers::NON_MOVING:
|
||||
return "NON_MOVING";
|
||||
case (JPH::BroadPhaseLayer::Type)BroadPhaseLayers::MOVING:
|
||||
return "MOVING";
|
||||
default:
|
||||
JPH_ASSERT(false);
|
||||
return "INVALID";
|
||||
}
|
||||
}
|
||||
#endif // JPH_EXTERNAL_PROFILE || JPH_PROFILE_ENABLED
|
||||
|
||||
private:
|
||||
JPH::BroadPhaseLayer mObjectToBroadPhase[Layers::NUM_LAYERS];
|
||||
};
|
||||
|
||||
/// Class that determines if an object layer can collide with a broadphase layer
|
||||
class ObjectVsBroadPhaseLayerFilterImpl : public JPH::ObjectVsBroadPhaseLayerFilter
|
||||
{
|
||||
public:
|
||||
virtual bool ShouldCollide(JPH::ObjectLayer inLayer1, JPH::BroadPhaseLayer inLayer2)
|
||||
const override
|
||||
{
|
||||
switch (inLayer1)
|
||||
{
|
||||
case Layers::NON_MOVING:
|
||||
return inLayer2 == BroadPhaseLayers::MOVING;
|
||||
case Layers::MOVING:
|
||||
return true;
|
||||
default:
|
||||
JPH_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// An example contact listener
|
||||
class MyContactListener : public JPH::ContactListener
|
||||
{
|
||||
public:
|
||||
// See: ContactListener
|
||||
virtual JPH::ValidateResult OnContactValidate(
|
||||
const JPH::Body& inBody1,
|
||||
const JPH::Body& inBody2,
|
||||
JPH::RVec3Arg inBaseOffset,
|
||||
const JPH::CollideShapeResult& inCollisionResult
|
||||
) override
|
||||
{
|
||||
// std::cout << "Contact validate callback" << std::endl;
|
||||
|
||||
// Allows you to ignore a contact before it is created (using layers to not make objects
|
||||
// collide is cheaper!)
|
||||
return JPH::ValidateResult::AcceptAllContactsForThisBodyPair;
|
||||
}
|
||||
|
||||
virtual void OnContactAdded(
|
||||
const JPH::Body& inBody1,
|
||||
const JPH::Body& inBody2,
|
||||
const JPH::ContactManifold& inManifold,
|
||||
JPH::ContactSettings& ioSettings
|
||||
) override
|
||||
{
|
||||
// std::cout << "A contact was added" << std::endl;
|
||||
}
|
||||
|
||||
virtual void OnContactPersisted(
|
||||
const JPH::Body& inBody1,
|
||||
const JPH::Body& inBody2,
|
||||
const JPH::ContactManifold& inManifold,
|
||||
JPH::ContactSettings& ioSettings
|
||||
) override
|
||||
{
|
||||
// std::cout << "A contact was persisted" << std::endl;
|
||||
}
|
||||
|
||||
virtual void OnContactRemoved(const JPH::SubShapeIDPair& inSubShapePair) override
|
||||
{
|
||||
// std::cout << "A contact was removed" << std::endl;
|
||||
}
|
||||
};
|
||||
|
||||
// An example activation listener
|
||||
class MyBodyActivationListener : public JPH::BodyActivationListener
|
||||
{
|
||||
public:
|
||||
virtual void OnBodyActivated(const JPH::BodyID& inBodyID, uint64_t inBodyUserData) override
|
||||
{
|
||||
// std::cout << "A body got activated" << std::endl;
|
||||
}
|
||||
|
||||
virtual void OnBodyDeactivated(const JPH::BodyID& inBodyID, uint64_t inBodyUserData) override
|
||||
{
|
||||
// std::cout << "A body went to sleep" << std::endl;
|
||||
}
|
||||
};
|
||||
|
||||
class Physics final : public IPhysics
|
||||
{
|
||||
public:
|
||||
static void Register();
|
||||
|
||||
Physics();
|
||||
~Physics();
|
||||
|
||||
Physics(const Physics&) = delete;
|
||||
Physics(Physics&&) = delete;
|
||||
Physics& operator=(const Physics&) = delete;
|
||||
Physics& operator=(Physics&&) = delete;
|
||||
|
||||
void Optimize() override;
|
||||
void Update() override;
|
||||
|
||||
JPH::BodyID CreateBody(JPH::BodyCreationSettings settings) override;
|
||||
void AddBody(JPH::BodyID id) override;
|
||||
void RemoveBody(JPH::BodyID id) override;
|
||||
void DestroyBody(JPH::BodyID id) override;
|
||||
|
||||
void GetBodyTransform(JPH::BodyID id, std::shared_ptr<STransform> trans) override;
|
||||
void SetBodyTransform(JPH::BodyID id, std::shared_ptr<STransform> trans) override;
|
||||
|
||||
void SetBodyPosition(JPH::BodyID id, glm::vec3 pos) override;
|
||||
void SetBodyRotation(JPH::BodyID id, glm::quat rot) override;
|
||||
void SetBodyVelocity(JPH::BodyID id, glm::vec3 vel) override;
|
||||
void SetBodyAngularVelocity(JPH::BodyID id, glm::vec3 vel) override;
|
||||
|
||||
private:
|
||||
JPH::PhysicsSystem m_physicsSystem;
|
||||
JPH::TempAllocatorImpl m_tempAllocator;
|
||||
JobSystemThreadPool m_jobSystem;
|
||||
BPLayerInterfaceImpl m_broadPhaseLayerInterface;
|
||||
ObjectVsBroadPhaseLayerFilterImpl m_objectVsBroadphaseLayerFilter;
|
||||
ObjectLayerPairFilterImpl m_objectVsObjectLayerFilter;
|
||||
MyContactListener m_contactListener;
|
||||
MyBodyActivationListener m_bodyActivationListener;
|
||||
float m_maxDeltaTime;
|
||||
};
|
||||
}; // namespace legs
|
120
src/public/legs/collider.hpp
Normal file
120
src/public/legs/collider.hpp
Normal file
|
@ -0,0 +1,120 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <legs/jolt_pch.hpp>
|
||||
|
||||
#include <legs/components/transform.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
namespace Layers
|
||||
{
|
||||
static constexpr JPH::ObjectLayer NON_MOVING = 0;
|
||||
static constexpr JPH::ObjectLayer MOVING = 1;
|
||||
static constexpr JPH::ObjectLayer NUM_LAYERS = 2;
|
||||
}; // namespace Layers
|
||||
|
||||
class ObjectLayerPairFilterImpl : public JPH::ObjectLayerPairFilter
|
||||
{
|
||||
public:
|
||||
virtual bool ShouldCollide(JPH::ObjectLayer inObject1, JPH::ObjectLayer inObject2)
|
||||
const override
|
||||
{
|
||||
switch (inObject1)
|
||||
{
|
||||
case Layers::NON_MOVING:
|
||||
return inObject2 == Layers::MOVING;
|
||||
case Layers::MOVING:
|
||||
return true;
|
||||
default:
|
||||
JPH_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class ICollider
|
||||
{
|
||||
public:
|
||||
ICollider() = default;
|
||||
|
||||
virtual ~ICollider() = default;
|
||||
|
||||
ICollider(const ICollider&) = default;
|
||||
ICollider(ICollider&&) = default;
|
||||
ICollider& operator=(const ICollider&) = default;
|
||||
ICollider& operator=(ICollider&&) = default;
|
||||
|
||||
void CreateBody(std::shared_ptr<STransform> trans)
|
||||
{
|
||||
JPH::ShapeSettings::ShapeResult shapeResult = ShapeSettings->Create();
|
||||
if (shapeResult.HasError())
|
||||
{
|
||||
throw std::runtime_error(std::format("Jolt: {}", shapeResult.GetError()));
|
||||
}
|
||||
JPH::ShapeRefC shape = shapeResult.Get();
|
||||
|
||||
CreationSettings = JPH::BodyCreationSettings(
|
||||
shape,
|
||||
JPH::RVec3(trans->position.x, trans->position.y, trans->position.z),
|
||||
JPH::Quat(
|
||||
trans->rotation.quaternion.x,
|
||||
trans->rotation.quaternion.y,
|
||||
trans->rotation.quaternion.z,
|
||||
trans->rotation.quaternion.w
|
||||
),
|
||||
MotionType,
|
||||
Layer
|
||||
);
|
||||
}
|
||||
|
||||
JPH::EMotionType MotionType;
|
||||
JPH::ObjectLayer Layer;
|
||||
JPH::BodyCreationSettings CreationSettings;
|
||||
std::shared_ptr<JPH::ShapeSettings> ShapeSettings;
|
||||
};
|
||||
|
||||
class BoxCollider final : public ICollider
|
||||
{
|
||||
public:
|
||||
BoxCollider(
|
||||
JPH::EMotionType motionType,
|
||||
JPH::ObjectLayer layer,
|
||||
std::shared_ptr<STransform> trans,
|
||||
glm::vec3 size
|
||||
)
|
||||
{
|
||||
MotionType = motionType;
|
||||
Layer = layer;
|
||||
ShapeSettings = std::make_shared<JPH::BoxShapeSettings>(JPH::Vec3(size.x, size.y, size.z));
|
||||
CreateBody(trans);
|
||||
}
|
||||
|
||||
~BoxCollider()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class SphereCollider final : public ICollider
|
||||
{
|
||||
public:
|
||||
SphereCollider(
|
||||
JPH::EMotionType motionType,
|
||||
JPH::ObjectLayer layer,
|
||||
std::shared_ptr<STransform> trans,
|
||||
float radius
|
||||
)
|
||||
{
|
||||
MotionType = motionType;
|
||||
Layer = layer;
|
||||
ShapeSettings = std::make_shared<JPH::SphereShapeSettings>(radius);
|
||||
CreateBody(trans);
|
||||
}
|
||||
|
||||
~SphereCollider()
|
||||
{
|
||||
}
|
||||
};
|
||||
}; // namespace legs
|
12
src/public/legs/components/rect.hpp
Normal file
12
src/public/legs/components/rect.hpp
Normal file
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <glm/vec2.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
struct SRect
|
||||
{
|
||||
glm::ivec2 size;
|
||||
glm::ivec2 offset;
|
||||
};
|
||||
} // namespace legs
|
26
src/public/legs/components/rotation.hpp
Normal file
26
src/public/legs/components/rotation.hpp
Normal file
|
@ -0,0 +1,26 @@
|
|||
#pragma once
|
||||
|
||||
#include <glm/ext/matrix_transform.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
#include <glm/vec3.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
struct SRotation
|
||||
{
|
||||
SRotation()
|
||||
{
|
||||
euler = {};
|
||||
quaternion = glm::identity<glm::quat>();
|
||||
}
|
||||
|
||||
void UpdateQuaternion()
|
||||
{
|
||||
quaternion = glm::angleAxis(glm::radians(euler.z), glm::vec3 {0.0f, 0.0f, 1.0f})
|
||||
* glm::angleAxis(glm::radians(euler.x), glm::vec3 {1.0f, 0.0f, 0.0f});
|
||||
}
|
||||
|
||||
glm::vec3 euler;
|
||||
glm::quat quaternion;
|
||||
};
|
||||
} // namespace legs
|
43
src/public/legs/components/transform.hpp
Normal file
43
src/public/legs/components/transform.hpp
Normal file
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
#include <glm/ext/matrix_float4x4.hpp>
|
||||
#include <glm/ext/matrix_transform.hpp>
|
||||
#include <glm/vec3.hpp>
|
||||
|
||||
#include <legs/components/rotation.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
struct STransform
|
||||
{
|
||||
glm::vec3 position;
|
||||
SRotation rotation;
|
||||
glm::vec3 velocity;
|
||||
glm::vec3 angularVelocity;
|
||||
|
||||
glm::mat4x4 GetModelMatrix()
|
||||
{
|
||||
auto model = glm::identity<glm::mat4x4>();
|
||||
model = glm::translate(model, position);
|
||||
model = glm::rotate(model, glm::radians(rotation.euler.x), glm::vec3 {1, 0, 0});
|
||||
model = glm::rotate(model, glm::radians(rotation.euler.y), glm::vec3 {0, 1, 0});
|
||||
model = glm::rotate(model, glm::radians(rotation.euler.z), glm::vec3 {0, 0, 1});
|
||||
return model;
|
||||
}
|
||||
|
||||
glm::vec3 Forward()
|
||||
{
|
||||
return rotation.quaternion * glm::vec3(0.0f, 1.0f, 0.0f);
|
||||
}
|
||||
|
||||
glm::vec3 Right()
|
||||
{
|
||||
return rotation.quaternion * glm::vec3(1.0f, 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
glm::vec3 Up()
|
||||
{
|
||||
return rotation.quaternion * glm::vec3(0.0f, 0.0f, 1.0f);
|
||||
}
|
||||
};
|
||||
} // namespace legs
|
99
src/public/legs/engine.hpp
Normal file
99
src/public/legs/engine.hpp
Normal file
|
@ -0,0 +1,99 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <semaphore>
|
||||
#include <stop_token>
|
||||
#include <thread>
|
||||
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
#include <glm/vec3.hpp>
|
||||
|
||||
#include <legs/world/world.hpp>
|
||||
|
||||
#include <legs/isystem.hpp>
|
||||
#include <legs/renderer/renderer.hpp>
|
||||
#include <legs/ui/ui.hpp>
|
||||
#include <legs/window/input.hpp>
|
||||
#include <legs/window/window.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
class Engine
|
||||
{
|
||||
public:
|
||||
Engine();
|
||||
~Engine();
|
||||
|
||||
int Run();
|
||||
|
||||
std::shared_ptr<Window> GetWindow() const
|
||||
{
|
||||
return m_window;
|
||||
}
|
||||
|
||||
void AddSystem(std::shared_ptr<ISystem> system)
|
||||
{
|
||||
m_systems.push_back(system);
|
||||
}
|
||||
|
||||
std::shared_ptr<Renderer> GetRenderer()
|
||||
{
|
||||
return m_renderer;
|
||||
}
|
||||
|
||||
std::shared_ptr<World> GetWorld()
|
||||
{
|
||||
return m_world;
|
||||
}
|
||||
|
||||
std::shared_ptr<Camera> GetCamera()
|
||||
{
|
||||
return m_camera;
|
||||
}
|
||||
|
||||
void SetCamera(std::shared_ptr<Camera> camera)
|
||||
{
|
||||
m_camera = camera;
|
||||
}
|
||||
|
||||
WindowInput GetFrameInput()
|
||||
{
|
||||
return m_frameInput;
|
||||
}
|
||||
|
||||
WindowInput GetTickInput()
|
||||
{
|
||||
return m_tickInput;
|
||||
}
|
||||
|
||||
private:
|
||||
void Frame();
|
||||
bool Tick();
|
||||
|
||||
void UpdateInput();
|
||||
|
||||
void TickThread(const std::stop_token token);
|
||||
void RenderThread(const std::stop_token token);
|
||||
|
||||
std::shared_ptr<InputSettings> m_inputSettings;
|
||||
std::shared_ptr<Window> m_window;
|
||||
std::shared_ptr<Camera> m_camera;
|
||||
std::shared_ptr<Renderer> m_renderer;
|
||||
std::shared_ptr<World> m_world;
|
||||
std::unique_ptr<UI> m_ui;
|
||||
|
||||
WindowInput m_frameInput;
|
||||
WindowInput m_tickInput;
|
||||
|
||||
std::jthread m_tickThread;
|
||||
std::jthread m_renderThread;
|
||||
|
||||
std::binary_semaphore m_mainTickSemaphore {0};
|
||||
std::binary_semaphore m_threadTickSemaphore {0};
|
||||
|
||||
std::binary_semaphore m_mainFrameSemaphore {0};
|
||||
std::binary_semaphore m_threadFrameSemaphore {0};
|
||||
|
||||
std::vector<std::shared_ptr<ISystem>> m_systems;
|
||||
};
|
||||
} // namespace legs
|
139
src/public/legs/entity/camera.hpp
Normal file
139
src/public/legs/entity/camera.hpp
Normal file
|
@ -0,0 +1,139 @@
|
|||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <glm/ext/matrix_transform.hpp>
|
||||
#include <glm/ext/quaternion_float.hpp>
|
||||
#include <glm/ext/quaternion_transform.hpp>
|
||||
#include <glm/gtc/constants.hpp>
|
||||
|
||||
#include <legs/components/transform.hpp>
|
||||
#include <legs/entity/entity.hpp>
|
||||
#include <legs/time.hpp>
|
||||
#include <legs/window/input.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
#define CAM_NEAR 0.1f
|
||||
#define CAM_FAR 1000.0f
|
||||
|
||||
class Camera : public Entity
|
||||
{
|
||||
public:
|
||||
Camera(int width, int height) : Entity()
|
||||
{
|
||||
fov = 60.0f;
|
||||
near = CAM_NEAR;
|
||||
far = CAM_FAR;
|
||||
UpdateViewport(width, height);
|
||||
UpdateMatrices();
|
||||
}
|
||||
|
||||
virtual ~Camera() {};
|
||||
|
||||
void UpdateMatrices()
|
||||
{
|
||||
Transform->rotation.UpdateQuaternion();
|
||||
view = glm::lookAt(
|
||||
Transform->position,
|
||||
Transform->position + Transform->Forward(),
|
||||
glm::vec3(0.0f, 0.0f, 1.0f)
|
||||
);
|
||||
proj = glm::perspective(glm::radians(fov), aspect, near, far);
|
||||
proj[1][1] *= -1; // OpenGL Y flip
|
||||
}
|
||||
|
||||
void UpdateViewport(int width, int height)
|
||||
{
|
||||
aspect = static_cast<float>(width) / static_cast<float>(height);
|
||||
viewport.x = static_cast<float>(width);
|
||||
viewport.y = static_cast<float>(height);
|
||||
viewport.z = CAM_NEAR;
|
||||
viewport.w = CAM_FAR;
|
||||
}
|
||||
|
||||
float fov;
|
||||
float aspect;
|
||||
float near;
|
||||
float far;
|
||||
glm::vec4 viewport;
|
||||
glm::mat4 view;
|
||||
glm::mat4 proj;
|
||||
};
|
||||
|
||||
class NoclipCamera : public Camera
|
||||
{
|
||||
public:
|
||||
NoclipCamera(int width, int height) : Camera(width, height)
|
||||
{
|
||||
MoveSpeed = 1.0f;
|
||||
Sensitivity = 2.0f;
|
||||
}
|
||||
|
||||
void HandleInput(WindowInput input)
|
||||
{
|
||||
if (input.mouse.x != 0)
|
||||
{
|
||||
Transform->rotation.euler.z -= 0.022f * 3.14f * static_cast<float>(input.mouse.x);
|
||||
while (Transform->rotation.euler.z < -180.0f)
|
||||
{
|
||||
Transform->rotation.euler.z += 360.0f;
|
||||
}
|
||||
while (Transform->rotation.euler.z > 180.0f)
|
||||
{
|
||||
Transform->rotation.euler.z -= 360.0f;
|
||||
}
|
||||
}
|
||||
if (input.mouse.y != 0)
|
||||
{
|
||||
Transform->rotation.euler.x -= 0.022f * 3.14f * static_cast<float>(input.mouse.y);
|
||||
Transform->rotation.euler.x = std::clamp(Transform->rotation.euler.x, -89.0f, 89.0f);
|
||||
}
|
||||
|
||||
if (input.scroll.y > 0)
|
||||
{
|
||||
MoveSpeed *= 2;
|
||||
}
|
||||
else if (input.scroll.y < 0)
|
||||
{
|
||||
MoveSpeed /= 2;
|
||||
}
|
||||
|
||||
if (input.HasKey(Key::KEY_MOVE_FORWARD))
|
||||
{
|
||||
Transform->position +=
|
||||
Transform->Forward() * MoveSpeed * static_cast<float>(Time::DeltaFrame);
|
||||
}
|
||||
if (input.HasKey(Key::KEY_MOVE_BACK))
|
||||
{
|
||||
Transform->position -=
|
||||
Transform->Forward() * MoveSpeed * static_cast<float>(Time::DeltaFrame);
|
||||
}
|
||||
if (input.HasKey(Key::KEY_MOVE_RIGHT))
|
||||
{
|
||||
Transform->position +=
|
||||
Transform->Right() * MoveSpeed * static_cast<float>(Time::DeltaFrame);
|
||||
}
|
||||
if (input.HasKey(Key::KEY_MOVE_LEFT))
|
||||
{
|
||||
Transform->position -=
|
||||
Transform->Right() * MoveSpeed * static_cast<float>(Time::DeltaFrame);
|
||||
}
|
||||
if (input.HasKey(Key::KEY_MOVE_UP))
|
||||
{
|
||||
Transform->position +=
|
||||
glm::vec3(0, 0, 1) * MoveSpeed * static_cast<float>(Time::DeltaFrame);
|
||||
}
|
||||
if (input.HasKey(Key::KEY_MOVE_DOWN))
|
||||
{
|
||||
Transform->position -=
|
||||
glm::vec3(0, 0, 1) * MoveSpeed * static_cast<float>(Time::DeltaFrame);
|
||||
}
|
||||
|
||||
UpdateMatrices();
|
||||
}
|
||||
|
||||
float MoveSpeed;
|
||||
float Sensitivity;
|
||||
};
|
||||
} // namespace legs
|
75
src/public/legs/entity/entity.hpp
Normal file
75
src/public/legs/entity/entity.hpp
Normal file
|
@ -0,0 +1,75 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <legs/components/transform.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
class Entity
|
||||
{
|
||||
public:
|
||||
Entity() : Name(""), Transform(std::make_shared<STransform>()) {};
|
||||
virtual ~Entity() = default;
|
||||
|
||||
Entity(const Entity&) = delete;
|
||||
Entity(Entity&&) = delete;
|
||||
Entity& operator=(const Entity&) = delete;
|
||||
Entity& operator=(Entity&&) = delete;
|
||||
|
||||
virtual void OnSpawn() {};
|
||||
virtual void OnDestroy() {};
|
||||
virtual void OnFrame() {};
|
||||
virtual void OnTick() {};
|
||||
|
||||
virtual void SetPosition(glm::vec3 pos)
|
||||
{
|
||||
Transform->position = pos;
|
||||
}
|
||||
|
||||
virtual void SetRotation(glm::quat rot)
|
||||
{
|
||||
Transform->rotation.quaternion = rot;
|
||||
}
|
||||
|
||||
virtual void SetVelocity(glm::vec3 vel)
|
||||
{
|
||||
Transform->velocity = vel;
|
||||
}
|
||||
|
||||
virtual void SetAngularVelocity(glm::vec3 vel)
|
||||
{
|
||||
Transform->angularVelocity = vel;
|
||||
}
|
||||
|
||||
virtual std::shared_ptr<STransform> GetTransform()
|
||||
{
|
||||
return Transform;
|
||||
}
|
||||
|
||||
virtual glm::vec3 GetPosition()
|
||||
{
|
||||
return Transform->position;
|
||||
}
|
||||
|
||||
virtual glm::quat GetRotation()
|
||||
{
|
||||
return Transform->rotation.quaternion;
|
||||
}
|
||||
|
||||
virtual glm::vec3 GetVelocity()
|
||||
{
|
||||
return Transform->velocity;
|
||||
}
|
||||
|
||||
virtual glm::vec3 GetAngularVelocity()
|
||||
{
|
||||
return Transform->angularVelocity;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::string Name;
|
||||
std::shared_ptr<STransform> Transform;
|
||||
};
|
||||
}; // namespace legs
|
77
src/public/legs/entity/mesh_entity.hpp
Normal file
77
src/public/legs/entity/mesh_entity.hpp
Normal file
|
@ -0,0 +1,77 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <legs/entity/entity.hpp>
|
||||
#include <legs/renderer/buffer.hpp>
|
||||
#include <legs/renderer/renderer.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
class MeshEntity : public Entity
|
||||
{
|
||||
public:
|
||||
MeshEntity() : Entity(), m_pipeline(RenderPipeline::INVALID)
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~MeshEntity() = default;
|
||||
|
||||
MeshEntity(const MeshEntity&) = delete;
|
||||
MeshEntity(MeshEntity&&) = delete;
|
||||
MeshEntity& operator=(const MeshEntity&) = delete;
|
||||
MeshEntity& operator=(MeshEntity&&) = delete;
|
||||
|
||||
virtual void OnSpawn() override
|
||||
{
|
||||
Entity::OnSpawn();
|
||||
}
|
||||
|
||||
virtual void OnDestroy() override
|
||||
{
|
||||
Entity::OnDestroy();
|
||||
}
|
||||
|
||||
virtual void OnFrame() override
|
||||
{
|
||||
Entity::OnFrame();
|
||||
}
|
||||
|
||||
virtual void OnTick() override
|
||||
{
|
||||
Entity::OnTick();
|
||||
}
|
||||
|
||||
virtual void SetBuffers(
|
||||
std::shared_ptr<Buffer> vertexBuffer,
|
||||
std::shared_ptr<Buffer> indexBuffer
|
||||
)
|
||||
{
|
||||
m_vertexBuffer = vertexBuffer;
|
||||
m_indexBuffer = indexBuffer;
|
||||
}
|
||||
|
||||
virtual void Render(std::shared_ptr<Renderer> renderer)
|
||||
{
|
||||
if (m_pipeline == RenderPipeline::INVALID)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Transform matrices
|
||||
renderer->BindPipeline(m_pipeline);
|
||||
renderer->DrawWithBuffers(m_vertexBuffer, m_indexBuffer);
|
||||
}
|
||||
|
||||
virtual void SetPipeline(RenderPipeline pipeline)
|
||||
{
|
||||
m_pipeline = pipeline;
|
||||
}
|
||||
|
||||
protected:
|
||||
RenderPipeline m_pipeline;
|
||||
std::shared_ptr<Buffer> m_vertexBuffer;
|
||||
std::shared_ptr<Buffer> m_indexBuffer;
|
||||
};
|
||||
}; // namespace legs
|
85
src/public/legs/entity/physics_entity.hpp
Normal file
85
src/public/legs/entity/physics_entity.hpp
Normal file
|
@ -0,0 +1,85 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <legs/collider.hpp>
|
||||
#include <legs/iphysics.hpp>
|
||||
|
||||
#include <legs/entity/mesh_entity.hpp>
|
||||
#include <legs/entry.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
class PhysicsEntity : public MeshEntity
|
||||
{
|
||||
public:
|
||||
PhysicsEntity() : MeshEntity()
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~PhysicsEntity() = default;
|
||||
|
||||
PhysicsEntity(const PhysicsEntity&) = delete;
|
||||
PhysicsEntity(PhysicsEntity&&) = delete;
|
||||
PhysicsEntity& operator=(const PhysicsEntity&) = delete;
|
||||
PhysicsEntity& operator=(PhysicsEntity&&) = delete;
|
||||
|
||||
virtual void OnSpawn() override
|
||||
{
|
||||
MeshEntity::OnSpawn();
|
||||
m_joltBody = g_engine->GetWorld()->GetPhysics()->CreateBody(m_collider.CreationSettings);
|
||||
g_engine->GetWorld()->GetPhysics()->AddBody(m_joltBody);
|
||||
}
|
||||
|
||||
virtual void OnDestroy() override
|
||||
{
|
||||
MeshEntity::OnDestroy();
|
||||
g_engine->GetWorld()->GetPhysics()->RemoveBody(m_joltBody);
|
||||
g_engine->GetWorld()->GetPhysics()->DestroyBody(m_joltBody);
|
||||
}
|
||||
|
||||
virtual void OnFrame() override
|
||||
{
|
||||
MeshEntity::OnFrame();
|
||||
}
|
||||
|
||||
virtual void OnTick() override
|
||||
{
|
||||
MeshEntity::OnTick();
|
||||
g_engine->GetWorld()->GetPhysics()->GetBodyTransform(m_joltBody, Transform);
|
||||
}
|
||||
|
||||
virtual void SetPosition(glm::vec3 pos) override
|
||||
{
|
||||
MeshEntity::SetPosition(pos);
|
||||
g_engine->GetWorld()->GetPhysics()->SetBodyPosition(m_joltBody, pos);
|
||||
}
|
||||
|
||||
virtual void SetRotation(glm::quat rot) override
|
||||
{
|
||||
MeshEntity::SetRotation(rot);
|
||||
g_engine->GetWorld()->GetPhysics()->SetBodyRotation(m_joltBody, rot);
|
||||
}
|
||||
|
||||
virtual void SetVelocity(glm::vec3 vel) override
|
||||
{
|
||||
MeshEntity::SetVelocity(vel);
|
||||
g_engine->GetWorld()->GetPhysics()->SetBodyVelocity(m_joltBody, vel);
|
||||
}
|
||||
|
||||
virtual void SetAngularVelocity(glm::vec3 vel) override
|
||||
{
|
||||
MeshEntity::SetAngularVelocity(vel);
|
||||
g_engine->GetWorld()->GetPhysics()->SetBodyAngularVelocity(m_joltBody, vel);
|
||||
}
|
||||
|
||||
virtual void SetCollider(ICollider collider)
|
||||
{
|
||||
m_collider = collider;
|
||||
}
|
||||
|
||||
protected:
|
||||
JPH::BodyID m_joltBody;
|
||||
ICollider m_collider;
|
||||
};
|
||||
}; // namespace legs
|
59
src/public/legs/entity/sky.hpp
Normal file
59
src/public/legs/entity/sky.hpp
Normal file
|
@ -0,0 +1,59 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <legs/entity/camera.hpp>
|
||||
#include <legs/entity/mesh_entity.hpp>
|
||||
#include <legs/geometry/icosphere.hpp>
|
||||
#include <legs/renderer/buffer.hpp>
|
||||
#include <legs/renderer/mesh_data.hpp>
|
||||
#include <legs/renderer/renderer.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
class Sky : public MeshEntity
|
||||
{
|
||||
public:
|
||||
Sky(std::shared_ptr<Renderer> renderer) : MeshEntity()
|
||||
{
|
||||
auto icosphere = SIcosphere({}, CAM_FAR * 0.95f, 1, true);
|
||||
|
||||
std::vector<Vertex_P> vertices;
|
||||
vertices.reserve(icosphere.positions.size());
|
||||
for (unsigned int i = 0; i < icosphere.positions.size(); i++)
|
||||
{
|
||||
vertices.push_back({icosphere.positions[i]});
|
||||
}
|
||||
|
||||
renderer->CreateBuffer(
|
||||
m_vertexBuffer,
|
||||
VertexBuffer,
|
||||
vertices.data(),
|
||||
sizeof(Vertex_P),
|
||||
static_cast<uint32_t>(vertices.size())
|
||||
);
|
||||
renderer->CreateBuffer(
|
||||
m_indexBuffer,
|
||||
IndexBuffer,
|
||||
icosphere.indices.data(),
|
||||
sizeof(Index),
|
||||
static_cast<uint32_t>(icosphere.indices.size())
|
||||
);
|
||||
|
||||
m_pipeline = RenderPipeline::SKY;
|
||||
|
||||
SunDirection = glm::vec3(0, 0, 1.0f);
|
||||
SunColor = glm::vec3(0.5f, 0.5f, 0.5f);
|
||||
}
|
||||
|
||||
void Render(std::shared_ptr<Renderer> renderer) override
|
||||
{
|
||||
renderer->GetUBO()->sunDir = SunDirection;
|
||||
renderer->GetUBO()->sunColor = SunColor;
|
||||
MeshEntity::Render(renderer);
|
||||
}
|
||||
|
||||
glm::vec3 SunDirection;
|
||||
glm::vec3 SunColor;
|
||||
};
|
||||
} // namespace legs
|
74
src/public/legs/entry.hpp
Normal file
74
src/public/legs/entry.hpp
Normal file
|
@ -0,0 +1,74 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
|
||||
#include <legs/engine.hpp>
|
||||
#include <legs/log.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
extern std::shared_ptr<Engine> g_engine;
|
||||
|
||||
static bool HasLaunchArg(const char* name, const char* value, int argc, char** argv)
|
||||
{
|
||||
for (int i = 0; i < argc; i++)
|
||||
{
|
||||
if (std::strcmp(name, argv[i]) == 0)
|
||||
{
|
||||
if (value == nullptr)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (i < argc - 1 && std::strcmp(value, argv[i + 1]) == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static int LEGS_Init(int /*argc*/, char** /*argv*/)
|
||||
{
|
||||
try
|
||||
{
|
||||
g_engine = std::make_shared<legs::Engine>();
|
||||
return 0;
|
||||
}
|
||||
catch (std::exception& ex)
|
||||
{
|
||||
LOG_FATAL("Unhandled exception: {}", ex.what());
|
||||
legs::Log::Flush();
|
||||
|
||||
if (g_engine != nullptr)
|
||||
{
|
||||
g_engine.reset();
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static int LEGS_Run()
|
||||
{
|
||||
try
|
||||
{
|
||||
auto code = g_engine->Run();
|
||||
g_engine.reset();
|
||||
return code;
|
||||
}
|
||||
catch (std::exception& ex)
|
||||
{
|
||||
LOG_FATAL("Unhandled exception: {}", ex.what());
|
||||
legs::Log::Flush();
|
||||
|
||||
if (g_engine != nullptr)
|
||||
{
|
||||
g_engine.reset();
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}; // namespace legs
|
195
src/public/legs/geometry/icosphere.hpp
Normal file
195
src/public/legs/geometry/icosphere.hpp
Normal file
|
@ -0,0 +1,195 @@
|
|||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <glm/geometric.hpp>
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <glm/vec2.hpp>
|
||||
#include <glm/vec3.hpp>
|
||||
|
||||
#include <legs/log.hpp>
|
||||
#include <legs/renderer/mesh_data.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
static const glm::vec2 baseSize = {0.525731112119133606f, 0.850650808352039932f};
|
||||
static const std::array<glm::vec3, 12> basePositions = {
|
||||
glm::vec3 {-baseSize.x, 0.0f, baseSize.y},
|
||||
glm::vec3 { baseSize.x, 0.0f, baseSize.y},
|
||||
glm::vec3 {-baseSize.x, 0.0f, -baseSize.y},
|
||||
glm::vec3 { baseSize.x, 0.0f, -baseSize.y},
|
||||
|
||||
glm::vec3 { 0.0f, baseSize.y, baseSize.x},
|
||||
glm::vec3 { 0.0f, baseSize.y, -baseSize.x},
|
||||
glm::vec3 { 0.0f, -baseSize.y, baseSize.x},
|
||||
glm::vec3 { 0.0f, -baseSize.y, -baseSize.x},
|
||||
|
||||
glm::vec3 { baseSize.y, baseSize.x, 0.0f},
|
||||
glm::vec3 {-baseSize.y, baseSize.x, 0.0f},
|
||||
glm::vec3 { baseSize.y, -baseSize.x, 0.0f},
|
||||
glm::vec3 {-baseSize.y, -baseSize.x, 0.0f},
|
||||
};
|
||||
static const std::array<unsigned int, 60> baseIndices = {
|
||||
// clang-format off
|
||||
0, 1, 4,
|
||||
0, 4, 9,
|
||||
9, 4, 5,
|
||||
4, 8, 5,
|
||||
4, 1, 8,
|
||||
|
||||
8, 1, 10,
|
||||
8, 10, 3,
|
||||
5, 8, 3,
|
||||
5, 3, 2,
|
||||
2, 3, 7,
|
||||
|
||||
7, 3, 10,
|
||||
7, 10, 6,
|
||||
7, 6, 11,
|
||||
11, 6, 0,
|
||||
0, 6, 1,
|
||||
|
||||
6, 10, 1,
|
||||
9, 11, 0,
|
||||
9, 2, 11,
|
||||
9, 5, 2,
|
||||
7, 11, 2
|
||||
// clang-format on
|
||||
};
|
||||
using EdgeMap = std::map<std::pair<Index, Index>, unsigned int>;
|
||||
|
||||
struct SIcosphere
|
||||
{
|
||||
std::vector<glm::vec3> positions;
|
||||
std::vector<glm::vec3> normals;
|
||||
std::vector<Index> indices;
|
||||
|
||||
SIcosphere(glm::vec3 origin, float radius, unsigned int subdivisions, bool invert = false)
|
||||
{
|
||||
// Don't be silly...
|
||||
// 10k verts should be enough for anything
|
||||
const unsigned int maxDiv = 5;
|
||||
if (subdivisions > maxDiv)
|
||||
{
|
||||
LOG_WARN("Tried to create icosphere with subdiv {}, clamping", subdivisions);
|
||||
subdivisions = maxDiv;
|
||||
}
|
||||
|
||||
auto numIndices = static_cast<size_t>(60 * std::pow(4, subdivisions));
|
||||
auto numVerts = static_cast<size_t>(12 + 10 * (std::pow(4, subdivisions) - 1));
|
||||
|
||||
positions.reserve(numVerts);
|
||||
indices.reserve(60);
|
||||
|
||||
for (const auto& p : basePositions)
|
||||
{
|
||||
positions.push_back(p);
|
||||
}
|
||||
for (const auto& i : baseIndices)
|
||||
{
|
||||
indices.push_back(i);
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < subdivisions; i++)
|
||||
{
|
||||
Subdivide();
|
||||
}
|
||||
|
||||
if (numVerts != positions.size())
|
||||
{
|
||||
LOG_ERROR(
|
||||
"Generated {} vertices for icosphere {}, expected {}",
|
||||
positions.size(),
|
||||
subdivisions,
|
||||
numVerts
|
||||
);
|
||||
numVerts = positions.size();
|
||||
}
|
||||
|
||||
if (numIndices != indices.size())
|
||||
{
|
||||
LOG_ERROR(
|
||||
"Generated {} indices for icosphere, expected {}",
|
||||
indices.size(),
|
||||
numIndices
|
||||
);
|
||||
numIndices = indices.size();
|
||||
}
|
||||
|
||||
normals.resize(numVerts);
|
||||
for (unsigned int i = 0; i < numVerts; i++)
|
||||
{
|
||||
normals[i] = invert ? -positions[i] : positions[i];
|
||||
positions[i] = positions[i] * radius + origin;
|
||||
}
|
||||
|
||||
if (invert)
|
||||
{
|
||||
for (unsigned int i = 0; i < numIndices; i += 3)
|
||||
{
|
||||
std::swap(indices[i], indices[i + 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Subdivide()
|
||||
{
|
||||
EdgeMap edgeMap;
|
||||
|
||||
std::vector<Index> newIndices;
|
||||
newIndices.reserve(indices.size() * 4);
|
||||
|
||||
for (unsigned int i = 0; i < indices.size(); i += 3)
|
||||
{
|
||||
unsigned int mid[3];
|
||||
for (unsigned int edge = 0; edge < 3; edge++)
|
||||
{
|
||||
const auto first = i + edge;
|
||||
const auto second = i + ((edge + 1) % 3);
|
||||
mid[edge] = EdgeVertex(edgeMap, indices[first], indices[second]);
|
||||
}
|
||||
|
||||
newIndices.push_back(indices[i]);
|
||||
newIndices.push_back(mid[0]);
|
||||
newIndices.push_back(mid[2]);
|
||||
|
||||
newIndices.push_back(indices[i + 1]);
|
||||
newIndices.push_back(mid[1]);
|
||||
newIndices.push_back(mid[0]);
|
||||
|
||||
newIndices.push_back(indices[i + 2]);
|
||||
newIndices.push_back(mid[2]);
|
||||
newIndices.push_back(mid[1]);
|
||||
|
||||
newIndices.push_back(mid[0]);
|
||||
newIndices.push_back(mid[1]);
|
||||
newIndices.push_back(mid[2]);
|
||||
}
|
||||
|
||||
indices = newIndices;
|
||||
}
|
||||
|
||||
unsigned int EdgeVertex(EdgeMap& edgeMap, Index first, Index second)
|
||||
{
|
||||
EdgeMap::key_type key(first, second);
|
||||
if (key.first > key.second)
|
||||
{
|
||||
std::swap(key.first, key.second);
|
||||
}
|
||||
|
||||
const auto inserted = edgeMap.insert({key, positions.size()});
|
||||
if (inserted.second)
|
||||
{
|
||||
auto& edge0 = positions[first];
|
||||
auto& edge1 = positions[second];
|
||||
auto pos = glm::normalize(edge0 + edge1);
|
||||
positions.push_back(pos);
|
||||
}
|
||||
|
||||
return inserted.first->second;
|
||||
}
|
||||
};
|
||||
}; // namespace legs
|
31
src/public/legs/geometry/plane.hpp
Normal file
31
src/public/legs/geometry/plane.hpp
Normal file
|
@ -0,0 +1,31 @@
|
|||
#pragma once
|
||||
|
||||
#include <glm/vec3.hpp>
|
||||
|
||||
#include <legs/renderer/mesh_data.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
static const std::array<glm::vec3, 4> planeBaseVertices = {
|
||||
glm::vec3 {-0.5f, -0.5f, 0.0f},
|
||||
glm::vec3 { 0.5f, -0.5f, 0.0f},
|
||||
glm::vec3 { 0.5f, 0.5f, 0.0f},
|
||||
glm::vec3 {-0.5f, 0.5f, 0.0f},
|
||||
};
|
||||
static const std::array<Index, 6> planeBaseIndices = {0, 1, 2, 2, 3, 0};
|
||||
|
||||
struct SPlane
|
||||
{
|
||||
std::array<glm::vec3, 4> vertices;
|
||||
std::array<Index, 6> indices;
|
||||
|
||||
SPlane(glm::vec3 position, float scale = 1.0f)
|
||||
{
|
||||
indices = planeBaseIndices;
|
||||
for (unsigned int i = 0; i < 4; i++)
|
||||
{
|
||||
vertices[i] = position + planeBaseVertices[i] * scale;
|
||||
}
|
||||
}
|
||||
};
|
||||
}; // namespace legs
|
38
src/public/legs/iphysics.hpp
Normal file
38
src/public/legs/iphysics.hpp
Normal file
|
@ -0,0 +1,38 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <legs/jolt_pch.hpp>
|
||||
|
||||
#include <legs/components/transform.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
class IPhysics
|
||||
{
|
||||
public:
|
||||
IPhysics() = default;
|
||||
virtual ~IPhysics() = default;
|
||||
|
||||
IPhysics(const IPhysics&) = delete;
|
||||
IPhysics(IPhysics&&) = delete;
|
||||
IPhysics& operator=(const IPhysics&) = delete;
|
||||
IPhysics& operator=(IPhysics&&) = delete;
|
||||
|
||||
virtual void Update() = 0;
|
||||
virtual void Optimize() = 0;
|
||||
|
||||
virtual JPH::BodyID CreateBody(JPH::BodyCreationSettings settings) = 0;
|
||||
virtual void AddBody(JPH::BodyID id) = 0;
|
||||
virtual void RemoveBody(JPH::BodyID id) = 0;
|
||||
virtual void DestroyBody(JPH::BodyID id) = 0;
|
||||
|
||||
virtual void GetBodyTransform(JPH::BodyID id, std::shared_ptr<STransform> trans) = 0;
|
||||
virtual void SetBodyTransform(JPH::BodyID id, std::shared_ptr<STransform> trans) = 0;
|
||||
|
||||
virtual void SetBodyPosition(JPH::BodyID id, glm::vec3 pos) = 0;
|
||||
virtual void SetBodyRotation(JPH::BodyID id, glm::quat rot) = 0;
|
||||
virtual void SetBodyVelocity(JPH::BodyID id, glm::vec3 vel) = 0;
|
||||
virtual void SetBodyAngularVelocity(JPH::BodyID id, glm::vec3 vel) = 0;
|
||||
};
|
||||
}; // namespace legs
|
20
src/public/legs/isystem.hpp
Normal file
20
src/public/legs/isystem.hpp
Normal file
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
namespace legs
|
||||
{
|
||||
class ISystem
|
||||
{
|
||||
public:
|
||||
ISystem() = default;
|
||||
virtual ~ISystem() = default;
|
||||
|
||||
ISystem(const ISystem&) = delete;
|
||||
ISystem(ISystem&&) = delete;
|
||||
ISystem& operator=(const ISystem&) = delete;
|
||||
ISystem& operator=(ISystem&&) = delete;
|
||||
|
||||
virtual void OnLevelLoad() {};
|
||||
virtual void OnFrame() {};
|
||||
virtual void OnTick() {};
|
||||
};
|
||||
}; // namespace legs
|
18
src/public/legs/jolt_pch.hpp
Normal file
18
src/public/legs/jolt_pch.hpp
Normal file
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include <Jolt/Jolt.h>
|
||||
|
||||
#include <Jolt/Core/Factory.h>
|
||||
#include <Jolt/Core/FixedSizeFreeList.h>
|
||||
#include <Jolt/Core/JobSystem.h>
|
||||
#include <Jolt/Core/Semaphore.h>
|
||||
#include <Jolt/Core/TempAllocator.h>
|
||||
#include <Jolt/Physics/Body/Body.h>
|
||||
#include <Jolt/Physics/Body/BodyActivationListener.h>
|
||||
#include <Jolt/Physics/Body/BodyCreationSettings.h>
|
||||
#include <Jolt/Physics/Collision/ObjectLayer.h>
|
||||
#include <Jolt/Physics/Collision/Shape/BoxShape.h>
|
||||
#include <Jolt/Physics/Collision/Shape/SphereShape.h>
|
||||
#include <Jolt/Physics/PhysicsSettings.h>
|
||||
#include <Jolt/Physics/PhysicsSystem.h>
|
||||
#include <Jolt/RegisterTypes.h>
|
100
src/public/legs/log.hpp
Normal file
100
src/public/legs/log.hpp
Normal file
|
@ -0,0 +1,100 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <format>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <ostream>
|
||||
|
||||
#include <legs/time.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
enum LogLevel
|
||||
{
|
||||
Debug,
|
||||
Info,
|
||||
Warn,
|
||||
Error,
|
||||
Fatal,
|
||||
MAX,
|
||||
};
|
||||
|
||||
class Log
|
||||
{
|
||||
public:
|
||||
static void SetLogLevel(const LogLevel level)
|
||||
{
|
||||
m_logLevel = level;
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
static void Print(
|
||||
const char* file,
|
||||
const unsigned int line,
|
||||
const char* func,
|
||||
const LogLevel level,
|
||||
const char* fmt,
|
||||
Args&&... args
|
||||
)
|
||||
{
|
||||
if (level < m_logLevel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto vargs = std::vformat(fmt, std::make_format_args(args...));
|
||||
|
||||
auto message = std::format(
|
||||
"[{:.3f}]" // Time
|
||||
"[{}]" // Severity
|
||||
"[{}:{}@{}()] " // Location
|
||||
"{}", // Message
|
||||
Time::Now(),
|
||||
m_severityStrings[static_cast<int>(level)],
|
||||
file,
|
||||
line,
|
||||
func,
|
||||
vargs
|
||||
);
|
||||
|
||||
const std::scoped_lock lock {m_logMutex};
|
||||
|
||||
std::cout << message << '\n';
|
||||
|
||||
#if !NDEBUG
|
||||
Flush();
|
||||
#endif
|
||||
}
|
||||
|
||||
static void Flush()
|
||||
{
|
||||
std::flush(std::cout);
|
||||
}
|
||||
|
||||
private:
|
||||
static inline LogLevel m_logLevel;
|
||||
|
||||
static constexpr const char* m_severityStrings[static_cast<int>(LogLevel::MAX)] = {
|
||||
"DEBUG",
|
||||
"INFO",
|
||||
"WARN",
|
||||
"ERROR",
|
||||
"FATAL",
|
||||
};
|
||||
|
||||
static inline std::mutex m_logMutex;
|
||||
};
|
||||
|
||||
#define __FILENAME__ (strrchr("/" __FILE__, '/') + 1)
|
||||
|
||||
#define _LOG(L, F, ...) legs::Log::Print(__FILENAME__, __LINE__, __func__, L, F, ##__VA_ARGS__)
|
||||
|
||||
#define LOG_DEBUG(F, ...) _LOG(legs::LogLevel::Debug, F, ##__VA_ARGS__)
|
||||
#define LOG_INFO(F, ...) _LOG(legs::LogLevel::Info, F, ##__VA_ARGS__)
|
||||
#define LOG_WARN(F, ...) _LOG(legs::LogLevel::Warn, F, ##__VA_ARGS__)
|
||||
#define LOG_ERROR(F, ...) _LOG(legs::LogLevel::Error, F, ##__VA_ARGS__)
|
||||
#define LOG_FATAL(F, ...) _LOG(legs::LogLevel::Fatal, F, ##__VA_ARGS__)
|
||||
|
||||
} // namespace legs
|
23
src/public/legs/memory.hpp
Normal file
23
src/public/legs/memory.hpp
Normal file
|
@ -0,0 +1,23 @@
|
|||
#pragma once
|
||||
|
||||
#include <sys/resource.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
class Memory
|
||||
{
|
||||
public:
|
||||
// Get resident memory usage in KB.
|
||||
static long int GetUsage()
|
||||
{
|
||||
rusage ru = {};
|
||||
if (getrusage(RUSAGE_SELF, &ru) == 0)
|
||||
{
|
||||
return ru.ru_maxrss;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
}; // namespace legs
|
120
src/public/legs/renderer/buffer.hpp
Normal file
120
src/public/legs/renderer/buffer.hpp
Normal file
|
@ -0,0 +1,120 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
#include <legs/renderer/vma_usage.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
enum BufferType
|
||||
{
|
||||
VertexBuffer,
|
||||
IndexBuffer,
|
||||
UniformBuffer,
|
||||
};
|
||||
|
||||
enum BufferLocation
|
||||
{
|
||||
HostBuffer,
|
||||
DeviceBuffer,
|
||||
};
|
||||
|
||||
class Buffer
|
||||
{
|
||||
public:
|
||||
Buffer() = delete;
|
||||
Buffer(
|
||||
BufferType bufferType,
|
||||
BufferLocation bufferLocation,
|
||||
uint32_t elementSize,
|
||||
uint32_t elementCount
|
||||
);
|
||||
|
||||
~Buffer();
|
||||
|
||||
Buffer(const Buffer&) = delete;
|
||||
Buffer(Buffer&&) = delete;
|
||||
Buffer& operator=(const Buffer&) = delete;
|
||||
Buffer& operator=(Buffer&&) = delete;
|
||||
|
||||
template<class IT>
|
||||
requires std::contiguous_iterator<IT>
|
||||
void Write(const IT it, uint32_t length)
|
||||
{
|
||||
if (m_bufferLocation != HostBuffer)
|
||||
{
|
||||
throw std::runtime_error("Tried mapping a non-host buffer");
|
||||
}
|
||||
|
||||
size_t size = length * m_elementSize;
|
||||
if (size > m_size)
|
||||
{
|
||||
throw std::runtime_error("Tried to write more data than allocated");
|
||||
}
|
||||
|
||||
Write(static_cast<void*>(it), size);
|
||||
m_elementCount = length;
|
||||
}
|
||||
|
||||
void Write(void* data, size_t size);
|
||||
|
||||
void CopyToDevice(void* commandBuffer, std::shared_ptr<Buffer> deviceBuffer);
|
||||
|
||||
void Clear();
|
||||
|
||||
void Bind(void* commandBuffer);
|
||||
|
||||
void Draw(void* commandBuffer);
|
||||
void Draw(void* commandBuffer, uint32_t indexOffset, uint32_t indexCount, int32_t vertexOffset);
|
||||
|
||||
void Map(void** data);
|
||||
void Unmap();
|
||||
|
||||
VkBuffer GetVkBuffer() const
|
||||
{
|
||||
return m_vkBuffer;
|
||||
}
|
||||
|
||||
void SetElementCount(uint32_t count)
|
||||
{
|
||||
m_elementCount = count;
|
||||
}
|
||||
|
||||
uint32_t GetElementCount() const
|
||||
{
|
||||
return m_elementCount;
|
||||
}
|
||||
|
||||
constexpr BufferType GetType() const
|
||||
{
|
||||
return m_bufferType;
|
||||
}
|
||||
|
||||
BufferLocation GetLocation() const
|
||||
{
|
||||
return m_bufferLocation;
|
||||
}
|
||||
|
||||
size_t GetSize() const
|
||||
{
|
||||
return m_size;
|
||||
}
|
||||
|
||||
size_t GetElementSize()
|
||||
{
|
||||
return m_elementSize;
|
||||
}
|
||||
|
||||
private:
|
||||
VkBuffer m_vkBuffer;
|
||||
VmaAllocation m_vmaAllocation;
|
||||
|
||||
BufferType m_bufferType;
|
||||
BufferLocation m_bufferLocation;
|
||||
uint32_t m_elementSize;
|
||||
uint32_t m_elementCount;
|
||||
size_t m_size;
|
||||
bool m_isMapped = false;
|
||||
};
|
||||
} // namespace legs
|
159
src/public/legs/renderer/common.hpp
Normal file
159
src/public/legs/renderer/common.hpp
Normal file
|
@ -0,0 +1,159 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <imgui_impl_vulkan.h>
|
||||
#include <vulkan/vulkan_core.h>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
#define VK_CHECK(RESULT, MSG) \
|
||||
if (RESULT != VK_SUCCESS) \
|
||||
{ \
|
||||
throw std::runtime_error(MSG); \
|
||||
}
|
||||
|
||||
constexpr static void ImGuiVkCheck(VkResult result)
|
||||
{
|
||||
VK_CHECK(result, "ImGuiVkCheck failed");
|
||||
}
|
||||
|
||||
// Hold color format so it doesn't get dropped from stack
|
||||
struct ImGuiCreationInfo
|
||||
{
|
||||
VkFormat colorFormat;
|
||||
VkPipelineRenderingCreateInfoKHR pipelineCreateInfo;
|
||||
ImGui_ImplVulkan_InitInfo imGuiInfo;
|
||||
};
|
||||
|
||||
constexpr static VkPipelineStageFlags GetPipelineStageFlags(const VkImageLayout imageLayout)
|
||||
{
|
||||
switch (imageLayout)
|
||||
{
|
||||
case VK_IMAGE_LAYOUT_UNDEFINED:
|
||||
return VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
||||
case VK_IMAGE_LAYOUT_PREINITIALIZED:
|
||||
return VK_PIPELINE_STAGE_HOST_BIT;
|
||||
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
|
||||
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
|
||||
return VK_PIPELINE_STAGE_TRANSFER_BIT;
|
||||
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
|
||||
return VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||
case VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL:
|
||||
return VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT
|
||||
| VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
||||
case VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR:
|
||||
return VK_PIPELINE_STAGE_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR;
|
||||
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
|
||||
return VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
||||
case VK_IMAGE_LAYOUT_PRESENT_SRC_KHR:
|
||||
return VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
||||
default:
|
||||
throw std::runtime_error("Unhandled VkImageLayout to VkPipelineStageFlags conversion");
|
||||
}
|
||||
}
|
||||
|
||||
constexpr static VkAccessFlags GetAccessFlags(const VkImageLayout imageLayout)
|
||||
{
|
||||
switch (imageLayout)
|
||||
{
|
||||
case VK_IMAGE_LAYOUT_UNDEFINED:
|
||||
case VK_IMAGE_LAYOUT_PRESENT_SRC_KHR:
|
||||
return 0;
|
||||
case VK_IMAGE_LAYOUT_PREINITIALIZED:
|
||||
return VK_ACCESS_HOST_WRITE_BIT;
|
||||
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
|
||||
return VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
case VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL:
|
||||
return VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT
|
||||
| VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||||
case VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR:
|
||||
return VK_ACCESS_FRAGMENT_SHADING_RATE_ATTACHMENT_READ_BIT_KHR;
|
||||
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
|
||||
return VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_INPUT_ATTACHMENT_READ_BIT;
|
||||
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
|
||||
return VK_ACCESS_TRANSFER_READ_BIT;
|
||||
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
|
||||
return VK_ACCESS_TRANSFER_WRITE_BIT;
|
||||
default:
|
||||
throw std::runtime_error("Unhandled VkImageLayout to VkAccessFlags conversion");
|
||||
}
|
||||
}
|
||||
|
||||
constexpr static void TransitionImageLayout(
|
||||
VkCommandBuffer commandBuffer,
|
||||
VkImage image,
|
||||
VkImageLayout oldLayout,
|
||||
VkImageLayout newLayout,
|
||||
const VkImageSubresourceRange& subresourceRange,
|
||||
const VkPipelineStageFlags srcStageMask,
|
||||
const VkPipelineStageFlags dstStageMask,
|
||||
const VkAccessFlags srcAccessMask,
|
||||
const VkAccessFlags dstAccessMask
|
||||
)
|
||||
{
|
||||
VkImageMemoryBarrier barrier {};
|
||||
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
barrier.srcAccessMask = srcAccessMask;
|
||||
barrier.dstAccessMask = dstAccessMask;
|
||||
barrier.oldLayout = oldLayout;
|
||||
barrier.newLayout = newLayout;
|
||||
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.image = image;
|
||||
barrier.subresourceRange = subresourceRange;
|
||||
vkCmdPipelineBarrier(
|
||||
commandBuffer,
|
||||
srcStageMask,
|
||||
dstStageMask,
|
||||
0,
|
||||
0,
|
||||
nullptr,
|
||||
0,
|
||||
nullptr,
|
||||
1,
|
||||
&barrier
|
||||
);
|
||||
}
|
||||
|
||||
constexpr static void TransitionImageLayout(
|
||||
VkCommandBuffer commandBuffer,
|
||||
VkImage image,
|
||||
VkImageLayout oldLayout,
|
||||
VkImageLayout newLayout,
|
||||
const VkImageSubresourceRange& subresourceRange
|
||||
)
|
||||
{
|
||||
const VkPipelineStageFlags srcStageMask = GetPipelineStageFlags(oldLayout);
|
||||
const VkPipelineStageFlags dstStageMask = GetPipelineStageFlags(newLayout);
|
||||
const VkAccessFlags srcAccessMask = GetAccessFlags(oldLayout);
|
||||
const VkAccessFlags dstAccessMask = GetAccessFlags(newLayout);
|
||||
TransitionImageLayout(
|
||||
commandBuffer,
|
||||
image,
|
||||
oldLayout,
|
||||
newLayout,
|
||||
subresourceRange,
|
||||
srcStageMask,
|
||||
dstStageMask,
|
||||
srcAccessMask,
|
||||
dstAccessMask
|
||||
);
|
||||
}
|
||||
|
||||
constexpr static void TransitionImageLayout(
|
||||
VkCommandBuffer commandBuffer,
|
||||
VkImage image,
|
||||
VkImageLayout oldLayout,
|
||||
VkImageLayout newLayout
|
||||
)
|
||||
{
|
||||
VkImageSubresourceRange subRange {};
|
||||
subRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
subRange.baseMipLevel = 0;
|
||||
subRange.levelCount = 1;
|
||||
subRange.baseArrayLayer = 0;
|
||||
subRange.layerCount = 1;
|
||||
TransitionImageLayout(commandBuffer, image, oldLayout, newLayout, subRange);
|
||||
}
|
||||
} // namespace legs
|
45
src/public/legs/renderer/descriptor_set.hpp
Normal file
45
src/public/legs/renderer/descriptor_set.hpp
Normal file
|
@ -0,0 +1,45 @@
|
|||
#pragma once
|
||||
|
||||
#include <vulkan/vulkan_core.h>
|
||||
|
||||
#include <legs/renderer/buffer.hpp>
|
||||
#include <legs/renderer/device.hpp>
|
||||
#include <legs/renderer/mesh_data.hpp>
|
||||
#include <legs/renderer/ubo.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
class DescriptorSet
|
||||
{
|
||||
public:
|
||||
DescriptorSet() = delete;
|
||||
DescriptorSet(const Device& device, std::vector<std::shared_ptr<Buffer>> uboBuffers);
|
||||
~DescriptorSet();
|
||||
|
||||
DescriptorSet(const DescriptorSet&) = delete;
|
||||
DescriptorSet(DescriptorSet&&) = default;
|
||||
DescriptorSet& operator=(const DescriptorSet&) = delete;
|
||||
DescriptorSet& operator=(DescriptorSet&&) = delete;
|
||||
|
||||
void UpdateUBO(uint32_t frameIndex, const std::shared_ptr<UniformBufferObject> ubo);
|
||||
|
||||
void Bind(
|
||||
VkCommandBuffer commandBuffer,
|
||||
VkPipelineBindPoint bindPoint,
|
||||
VkPipelineLayout pipelineLayout,
|
||||
uint32_t frameIndex
|
||||
);
|
||||
|
||||
std::vector<VkDescriptorSetLayout> GetLayouts() const
|
||||
{
|
||||
return m_vkLayouts;
|
||||
}
|
||||
|
||||
private:
|
||||
const Device& m_device;
|
||||
std::vector<VkDescriptorSetLayout> m_vkLayouts;
|
||||
std::vector<VkDescriptorSet> m_vkSets;
|
||||
std::vector<std::shared_ptr<Buffer>> m_uniformBuffers;
|
||||
std::vector<void*> m_ubosMappedMemory;
|
||||
};
|
||||
} // namespace legs
|
203
src/public/legs/renderer/device.hpp
Normal file
203
src/public/legs/renderer/device.hpp
Normal file
|
@ -0,0 +1,203 @@
|
|||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <legs/components/rect.hpp>
|
||||
#include <legs/renderer/instance.hpp>
|
||||
#include <legs/renderer/vma_usage.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
struct QueueFamilyIndices
|
||||
{
|
||||
std::optional<uint32_t> graphicsFamily;
|
||||
std::optional<uint32_t> presentFamily;
|
||||
|
||||
bool IsComplete() const
|
||||
{
|
||||
return graphicsFamily.has_value() && presentFamily.has_value();
|
||||
}
|
||||
};
|
||||
|
||||
struct SwapchainSupportDetails
|
||||
{
|
||||
VkSurfaceCapabilitiesKHR capabilities;
|
||||
std::vector<VkSurfaceFormatKHR> formats;
|
||||
std::vector<VkPresentModeKHR> presentModes;
|
||||
};
|
||||
|
||||
class Device
|
||||
{
|
||||
public:
|
||||
Device(const Instance& instance, uint32_t maxFramesInFlight);
|
||||
~Device();
|
||||
|
||||
Device(const Device&) = delete;
|
||||
Device(Device&&) = delete;
|
||||
Device& operator=(const Device&) = delete;
|
||||
Device& operator=(Device&&) = delete;
|
||||
|
||||
void Begin();
|
||||
void ResetViewport();
|
||||
void SetViewport(SRect rect);
|
||||
void Submit();
|
||||
void Present();
|
||||
void WaitForGraphicsIdle();
|
||||
|
||||
VkCommandBuffer GetTemporaryCommandBuffer();
|
||||
void SubmitTemporaryCommandBuffer(VkCommandBuffer commandBuffer);
|
||||
|
||||
VkCommandBuffer GetCommandBuffer() const
|
||||
{
|
||||
return m_vkCommandBuffers[m_currentFrame];
|
||||
}
|
||||
|
||||
uint32_t GetCurrentFrame() const
|
||||
{
|
||||
return m_currentFrame;
|
||||
}
|
||||
|
||||
void ResizeFramebuffer()
|
||||
{
|
||||
m_frameBufferResized = true;
|
||||
}
|
||||
|
||||
VkPhysicalDevice GetVkPhysicalDevice() const
|
||||
{
|
||||
return m_vkPhysicalDevice;
|
||||
}
|
||||
|
||||
VkDevice GetVkDevice() const
|
||||
{
|
||||
return m_vkDevice;
|
||||
}
|
||||
|
||||
constexpr VkExtent2D GetSwapchainExtent() const
|
||||
{
|
||||
return m_vkSwapchainExtent;
|
||||
}
|
||||
|
||||
constexpr float GetSwapchainAspect() const
|
||||
{
|
||||
const auto extent = GetSwapchainExtent();
|
||||
return static_cast<float>(extent.width) / static_cast<float>(extent.height);
|
||||
}
|
||||
|
||||
VkFormat GetSwapchainImageFormat() const
|
||||
{
|
||||
return m_vkSwapchainImageFormat;
|
||||
}
|
||||
|
||||
VkFormat GetDepthFormat() const
|
||||
{
|
||||
return VK_FORMAT_D32_SFLOAT;
|
||||
}
|
||||
|
||||
VkDescriptorPool GetUboDescriptorPool() const
|
||||
{
|
||||
return m_vkUboDescriptorPool;
|
||||
}
|
||||
|
||||
VkDescriptorPool GetImGuiDescriptorPool() const
|
||||
{
|
||||
return m_vkImGuiDescriptorPool;
|
||||
}
|
||||
|
||||
uint32_t GetGraphicsQueueIndex() const
|
||||
{
|
||||
return m_vkGraphicsQueueIndex;
|
||||
}
|
||||
|
||||
VkQueue GetGraphicsQueue() const
|
||||
{
|
||||
return m_vkGraphicsQueue;
|
||||
}
|
||||
|
||||
private:
|
||||
void RecreateSwapchain();
|
||||
void DestroySwapchain();
|
||||
|
||||
void PickPhysicalDevice();
|
||||
bool IsDeviceSuitable(const VkPhysicalDevice device);
|
||||
QueueFamilyIndices FindQueueFamilies(const VkPhysicalDevice device);
|
||||
bool CheckDeviceExtensionSupport(const VkPhysicalDevice device);
|
||||
|
||||
SwapchainSupportDetails QuerySwapchainSupport(const VkPhysicalDevice device);
|
||||
constexpr VkSurfaceFormatKHR ChooseSwapSurfaceFormat(
|
||||
const std::vector<VkSurfaceFormatKHR>& available
|
||||
);
|
||||
constexpr VkPresentModeKHR ChooseSwapPresentMode(const std::vector<VkPresentModeKHR>& available
|
||||
);
|
||||
constexpr VkExtent2D ChooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities);
|
||||
void CreateSwapchain();
|
||||
|
||||
void CreateImage(
|
||||
VkImage* image,
|
||||
VmaAllocation* imageAllocation,
|
||||
VkImageType imageType,
|
||||
VkFormat format,
|
||||
VkImageUsageFlags usage,
|
||||
uint32_t width,
|
||||
uint32_t height
|
||||
);
|
||||
void CreateImageView(
|
||||
VkImage image,
|
||||
VkImageView* imageView,
|
||||
VkImageViewType viewType,
|
||||
VkFormat format,
|
||||
VkImageAspectFlags aspect
|
||||
);
|
||||
void CreateImageViews();
|
||||
|
||||
void CreateLogicalDevice();
|
||||
|
||||
void CreateCommandPool();
|
||||
void CreateCommandBuffers();
|
||||
|
||||
void CreateSyncObjects();
|
||||
|
||||
void CreateDescriptorPools();
|
||||
|
||||
const Instance& m_instance;
|
||||
|
||||
VkPhysicalDevice m_vkPhysicalDevice;
|
||||
VkDevice m_vkDevice;
|
||||
|
||||
uint32_t m_vkGraphicsQueueIndex;
|
||||
uint32_t m_vkPresentQueueIndex;
|
||||
|
||||
VkQueue m_vkGraphicsQueue;
|
||||
VkQueue m_vkPresentQueue;
|
||||
|
||||
VkSwapchainKHR m_vkSwapchain;
|
||||
std::vector<VkImage> m_vkSwapchainImages;
|
||||
VkFormat m_vkSwapchainImageFormat;
|
||||
VkExtent2D m_vkSwapchainExtent;
|
||||
std::vector<VkImageView> m_vkSwapchainImageViews;
|
||||
|
||||
VkImage m_vkDepthImage;
|
||||
VmaAllocation m_vmaDepthAllocation;
|
||||
VkImageView m_vkDepthImageView;
|
||||
|
||||
VkCommandPool m_vkCommandPool;
|
||||
std::vector<VkCommandBuffer> m_vkCommandBuffers;
|
||||
|
||||
std::vector<VkSemaphore> m_vkImageSemaphores;
|
||||
std::vector<VkSemaphore> m_vkRenderSemaphores;
|
||||
std::vector<VkFence> m_vkInFlightFences;
|
||||
|
||||
VkDescriptorPool m_vkUboDescriptorPool;
|
||||
VkDescriptorPool m_vkImGuiDescriptorPool;
|
||||
|
||||
uint32_t m_currentImageIndex;
|
||||
uint32_t m_currentFrame = 0;
|
||||
const uint32_t m_maxFramesInFlight;
|
||||
|
||||
bool m_frameBufferResized = false;
|
||||
|
||||
const std::vector<const char*> m_requiredExtensions = {
|
||||
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
|
||||
VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME,
|
||||
};
|
||||
};
|
||||
} // namespace legs
|
59
src/public/legs/renderer/instance.hpp
Normal file
59
src/public/legs/renderer/instance.hpp
Normal file
|
@ -0,0 +1,59 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <legs/window/window.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
class Instance
|
||||
{
|
||||
public:
|
||||
Instance(std::shared_ptr<Window> window);
|
||||
~Instance();
|
||||
|
||||
Instance(const Instance&) = delete;
|
||||
Instance(Instance&&) = delete;
|
||||
Instance& operator=(const Instance&) = delete;
|
||||
Instance& operator=(Instance&&) = delete;
|
||||
|
||||
VkInstance GetVkInstance() const
|
||||
{
|
||||
return m_vkInstance;
|
||||
}
|
||||
|
||||
VkSurfaceKHR GetSurface() const
|
||||
{
|
||||
return m_vkSurface;
|
||||
}
|
||||
|
||||
void GetFramebufferSize(int* width, int* height) const
|
||||
{
|
||||
m_window->GetFramebufferSize(width, height);
|
||||
}
|
||||
|
||||
void SetWindow(std::shared_ptr<Window> window);
|
||||
|
||||
private:
|
||||
bool ValidationLayersSupported();
|
||||
std::vector<const char*> GetRequiredExtensions();
|
||||
void constexpr PopulateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo);
|
||||
void SetupDebugMessenger();
|
||||
void CreateSurface();
|
||||
|
||||
std::shared_ptr<Window> m_window;
|
||||
|
||||
VkInstance m_vkInstance;
|
||||
VkDebugUtilsMessengerEXT m_vkDebugMessenger;
|
||||
VkSurfaceKHR m_vkSurface;
|
||||
|
||||
const std::vector<const char*> m_validationLayers = {"VK_LAYER_KHRONOS_validation"};
|
||||
|
||||
#ifdef NDEBUG
|
||||
constexpr static bool m_enableValidationLayers = false;
|
||||
#else
|
||||
constexpr static bool m_enableValidationLayers = true;
|
||||
#endif
|
||||
};
|
||||
} // namespace legs
|
110
src/public/legs/renderer/mesh_data.hpp
Normal file
110
src/public/legs/renderer/mesh_data.hpp
Normal file
|
@ -0,0 +1,110 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
#include <glm/vec3.hpp>
|
||||
|
||||
#include <legs/renderer/common.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
struct Vertex_P
|
||||
{
|
||||
glm::vec3 position;
|
||||
};
|
||||
|
||||
struct Vertex_P_C
|
||||
{
|
||||
glm::vec3 position;
|
||||
glm::vec3 color;
|
||||
};
|
||||
|
||||
struct Vertex_P_N_C
|
||||
{
|
||||
glm::vec3 position;
|
||||
glm::vec3 normal;
|
||||
glm::vec3 color;
|
||||
};
|
||||
|
||||
// For fullscreen triangle
|
||||
struct VertexEmpty
|
||||
{
|
||||
};
|
||||
|
||||
typedef uint32_t Index;
|
||||
|
||||
template<typename T>
|
||||
consteval VkVertexInputBindingDescription GetBindingDescription()
|
||||
{
|
||||
VkVertexInputBindingDescription desc {};
|
||||
desc.binding = 0;
|
||||
desc.stride = sizeof(T);
|
||||
desc.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
|
||||
return desc;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
constexpr std::vector<VkVertexInputAttributeDescription> GetAttributeDescriptions()
|
||||
{
|
||||
std::vector<VkVertexInputAttributeDescription> desc {};
|
||||
|
||||
// TODO: reflection
|
||||
|
||||
if constexpr (std::is_same_v<T, Vertex_P>)
|
||||
{
|
||||
desc.push_back({
|
||||
.location = 0,
|
||||
.binding = 0,
|
||||
.format = VK_FORMAT_R32G32B32_SFLOAT,
|
||||
.offset = offsetof(T, position),
|
||||
});
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, Vertex_P_C>)
|
||||
{
|
||||
desc.push_back({
|
||||
.location = 0,
|
||||
.binding = 0,
|
||||
.format = VK_FORMAT_R32G32B32_SFLOAT,
|
||||
.offset = offsetof(T, position),
|
||||
});
|
||||
desc.push_back({
|
||||
.location = 1,
|
||||
.binding = 0,
|
||||
.format = VK_FORMAT_R32G32B32_SFLOAT,
|
||||
.offset = offsetof(T, color),
|
||||
});
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, Vertex_P_N_C>)
|
||||
{
|
||||
desc.push_back({
|
||||
.location = 0,
|
||||
.binding = 0,
|
||||
.format = VK_FORMAT_R32G32B32_SFLOAT,
|
||||
.offset = offsetof(T, position),
|
||||
});
|
||||
desc.push_back({
|
||||
.location = 1,
|
||||
.binding = 0,
|
||||
.format = VK_FORMAT_R32G32B32_SFLOAT,
|
||||
.offset = offsetof(T, normal),
|
||||
});
|
||||
desc.push_back({
|
||||
.location = 2,
|
||||
.binding = 0,
|
||||
.format = VK_FORMAT_R32G32B32_SFLOAT,
|
||||
.offset = offsetof(T, color),
|
||||
});
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, VertexEmpty>)
|
||||
{
|
||||
// nada
|
||||
}
|
||||
else
|
||||
{
|
||||
static_assert(false, "Unhandled type for attribute descriptions");
|
||||
}
|
||||
|
||||
return desc;
|
||||
}
|
||||
} // namespace legs
|
227
src/public/legs/renderer/pipeline.hpp
Normal file
227
src/public/legs/renderer/pipeline.hpp
Normal file
|
@ -0,0 +1,227 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "vulkan/vulkan_core.h"
|
||||
|
||||
#include <legs/log.hpp>
|
||||
#include <legs/renderer/descriptor_set.hpp>
|
||||
#include <legs/renderer/device.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
template<class V>
|
||||
class Pipeline
|
||||
{
|
||||
public:
|
||||
Pipeline() = delete;
|
||||
Pipeline(
|
||||
const Device& device,
|
||||
std::shared_ptr<DescriptorSet> descriptorSet,
|
||||
std::vector<VkPipelineShaderStageCreateInfo> shaderStages,
|
||||
bool enableCulling = true,
|
||||
bool enableDepth = true
|
||||
);
|
||||
~Pipeline();
|
||||
|
||||
Pipeline(const Pipeline&) = delete;
|
||||
Pipeline(Pipeline&&) = default;
|
||||
Pipeline& operator=(const Pipeline&) = delete;
|
||||
Pipeline& operator=(Pipeline&&) = delete;
|
||||
|
||||
void Bind(VkCommandBuffer commandBuffer, VkPipelineBindPoint bindPoint, uint32_t frameIndex);
|
||||
|
||||
VkPipeline GetVkPipeline() const
|
||||
{
|
||||
return m_vkPipeline;
|
||||
}
|
||||
|
||||
private:
|
||||
const Device& m_device;
|
||||
std::shared_ptr<DescriptorSet> m_descriptorSet;
|
||||
|
||||
VkPipelineLayout m_vkPipelineLayout;
|
||||
VkPipeline m_vkPipeline;
|
||||
};
|
||||
|
||||
template<typename V>
|
||||
Pipeline<V>::Pipeline(
|
||||
const Device& device,
|
||||
std::shared_ptr<DescriptorSet> descriptorSet,
|
||||
std::vector<VkPipelineShaderStageCreateInfo> shaderStages,
|
||||
bool enableCulling,
|
||||
bool enableDepth
|
||||
) :
|
||||
m_device(device),
|
||||
m_descriptorSet(descriptorSet)
|
||||
{
|
||||
LOG_DEBUG("Creating Pipeline");
|
||||
|
||||
const VkExtent2D swapchainExtent = m_device.GetSwapchainExtent();
|
||||
|
||||
const std::vector dynamicStates = {
|
||||
VK_DYNAMIC_STATE_VIEWPORT,
|
||||
VK_DYNAMIC_STATE_SCISSOR,
|
||||
};
|
||||
|
||||
VkPipelineDynamicStateCreateInfo dynamicState {};
|
||||
dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
|
||||
dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size());
|
||||
dynamicState.pDynamicStates = dynamicStates.data();
|
||||
|
||||
const auto vertexBindingDescription = GetBindingDescription<V>();
|
||||
const auto vertexAttributeDescriptions = GetAttributeDescriptions<V>();
|
||||
|
||||
VkPipelineVertexInputStateCreateInfo vertexInputState {};
|
||||
vertexInputState.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
|
||||
vertexInputState.vertexBindingDescriptionCount = 1;
|
||||
vertexInputState.vertexAttributeDescriptionCount =
|
||||
static_cast<uint32_t>(vertexAttributeDescriptions.size());
|
||||
vertexInputState.pVertexBindingDescriptions = &vertexBindingDescription;
|
||||
vertexInputState.pVertexAttributeDescriptions = vertexAttributeDescriptions.data();
|
||||
|
||||
VkPipelineInputAssemblyStateCreateInfo inputAssemblyState {};
|
||||
inputAssemblyState.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
|
||||
inputAssemblyState.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||
inputAssemblyState.primitiveRestartEnable = VK_FALSE;
|
||||
|
||||
VkViewport viewport {};
|
||||
viewport.x = 0.0f;
|
||||
viewport.y = 0.0f;
|
||||
viewport.width = static_cast<float>(swapchainExtent.width);
|
||||
viewport.height = static_cast<float>(swapchainExtent.height);
|
||||
viewport.minDepth = 0.0f;
|
||||
viewport.maxDepth = 1.0f;
|
||||
|
||||
VkRect2D scissor {};
|
||||
scissor.extent = swapchainExtent;
|
||||
scissor.offset = {0, 0};
|
||||
|
||||
VkPipelineViewportStateCreateInfo viewportState {};
|
||||
viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
|
||||
viewportState.viewportCount = 1;
|
||||
viewportState.pViewports = &viewport;
|
||||
viewportState.scissorCount = 1;
|
||||
viewportState.pScissors = &scissor;
|
||||
|
||||
VkPipelineRasterizationStateCreateInfo rasterizationState {};
|
||||
rasterizationState.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
|
||||
rasterizationState.depthClampEnable = VK_FALSE;
|
||||
rasterizationState.rasterizerDiscardEnable = VK_FALSE;
|
||||
rasterizationState.polygonMode = VK_POLYGON_MODE_FILL;
|
||||
rasterizationState.lineWidth = 1.0f;
|
||||
rasterizationState.cullMode = enableCulling ? VK_CULL_MODE_BACK_BIT : VK_CULL_MODE_NONE;
|
||||
rasterizationState.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
|
||||
rasterizationState.depthBiasEnable = VK_FALSE;
|
||||
|
||||
VkPipelineMultisampleStateCreateInfo multisampleState {};
|
||||
multisampleState.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
|
||||
multisampleState.sampleShadingEnable = VK_FALSE;
|
||||
multisampleState.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
|
||||
|
||||
VkPipelineColorBlendAttachmentState colorBlendAttachment {};
|
||||
colorBlendAttachment.blendEnable = VK_TRUE;
|
||||
colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT
|
||||
| VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
|
||||
colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
|
||||
colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
|
||||
colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
|
||||
colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
|
||||
colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
|
||||
colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
|
||||
|
||||
VkPipelineColorBlendStateCreateInfo colorBlendState {};
|
||||
colorBlendState.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
|
||||
colorBlendState.logicOpEnable = VK_FALSE;
|
||||
colorBlendState.attachmentCount = 1;
|
||||
colorBlendState.pAttachments = &colorBlendAttachment;
|
||||
|
||||
auto descriptorSetLayouts = descriptorSet->GetLayouts();
|
||||
|
||||
VkPipelineLayoutCreateInfo pipelineLayoutInfo {};
|
||||
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
|
||||
pipelineLayoutInfo.setLayoutCount = static_cast<uint32_t>(descriptorSetLayouts.size());
|
||||
pipelineLayoutInfo.pSetLayouts = descriptorSetLayouts.data();
|
||||
|
||||
VK_CHECK(
|
||||
vkCreatePipelineLayout(
|
||||
m_device.GetVkDevice(),
|
||||
&pipelineLayoutInfo,
|
||||
nullptr,
|
||||
&m_vkPipelineLayout
|
||||
),
|
||||
"Failed to create pipeline layout"
|
||||
);
|
||||
|
||||
VkPipelineDepthStencilStateCreateInfo depthStencilState {};
|
||||
depthStencilState.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
|
||||
depthStencilState.depthTestEnable = enableDepth ? VK_TRUE : VK_FALSE;
|
||||
depthStencilState.depthWriteEnable = enableDepth ? VK_TRUE : VK_FALSE;
|
||||
depthStencilState.depthCompareOp = VK_COMPARE_OP_LESS;
|
||||
depthStencilState.depthBoundsTestEnable = VK_FALSE;
|
||||
depthStencilState.minDepthBounds = 0.0f;
|
||||
depthStencilState.maxDepthBounds = 1.0f;
|
||||
depthStencilState.stencilTestEnable = VK_FALSE;
|
||||
depthStencilState.front = {};
|
||||
depthStencilState.back = {};
|
||||
|
||||
VkFormat colorFormat = m_device.GetSwapchainImageFormat();
|
||||
VkFormat depthFormat = m_device.GetDepthFormat();
|
||||
|
||||
VkPipelineRenderingCreateInfoKHR pipelineCreateInfo {};
|
||||
pipelineCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR;
|
||||
pipelineCreateInfo.pNext = VK_NULL_HANDLE;
|
||||
pipelineCreateInfo.colorAttachmentCount = 1;
|
||||
pipelineCreateInfo.pColorAttachmentFormats = &colorFormat;
|
||||
pipelineCreateInfo.depthAttachmentFormat = depthFormat;
|
||||
pipelineCreateInfo.stencilAttachmentFormat = VK_FORMAT_UNDEFINED;
|
||||
|
||||
VkGraphicsPipelineCreateInfo graphicsCreateInfo {};
|
||||
graphicsCreateInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
|
||||
graphicsCreateInfo.pNext = &pipelineCreateInfo;
|
||||
graphicsCreateInfo.renderPass = VK_NULL_HANDLE;
|
||||
graphicsCreateInfo.pVertexInputState = &vertexInputState;
|
||||
graphicsCreateInfo.pInputAssemblyState = &inputAssemblyState;
|
||||
graphicsCreateInfo.pViewportState = &viewportState;
|
||||
graphicsCreateInfo.pRasterizationState = &rasterizationState;
|
||||
graphicsCreateInfo.pMultisampleState = &multisampleState;
|
||||
graphicsCreateInfo.pDepthStencilState = &depthStencilState;
|
||||
graphicsCreateInfo.pColorBlendState = &colorBlendState;
|
||||
graphicsCreateInfo.pDynamicState = &dynamicState;
|
||||
graphicsCreateInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
|
||||
graphicsCreateInfo.pStages = shaderStages.data();
|
||||
graphicsCreateInfo.layout = m_vkPipelineLayout;
|
||||
|
||||
VK_CHECK(
|
||||
vkCreateGraphicsPipelines(
|
||||
m_device.GetVkDevice(),
|
||||
VK_NULL_HANDLE,
|
||||
1,
|
||||
&graphicsCreateInfo,
|
||||
nullptr,
|
||||
&m_vkPipeline
|
||||
),
|
||||
"Failed to create graphics pipeline"
|
||||
);
|
||||
}
|
||||
|
||||
template<typename V>
|
||||
Pipeline<V>::~Pipeline()
|
||||
{
|
||||
LOG_DEBUG("Destroying Pipeline");
|
||||
|
||||
vkDestroyPipeline(m_device.GetVkDevice(), m_vkPipeline, nullptr);
|
||||
vkDestroyPipelineLayout(m_device.GetVkDevice(), m_vkPipelineLayout, nullptr);
|
||||
}
|
||||
|
||||
template<typename V>
|
||||
void Pipeline<V>::Bind(
|
||||
VkCommandBuffer commandBuffer,
|
||||
VkPipelineBindPoint bindPoint,
|
||||
uint32_t frameIndex
|
||||
)
|
||||
{
|
||||
vkCmdBindPipeline(commandBuffer, bindPoint, m_vkPipeline);
|
||||
m_descriptorSet->Bind(commandBuffer, bindPoint, m_vkPipelineLayout, frameIndex);
|
||||
}
|
||||
} // namespace legs
|
183
src/public/legs/renderer/renderer.hpp
Normal file
183
src/public/legs/renderer/renderer.hpp
Normal file
|
@ -0,0 +1,183 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <imgui_impl_vulkan.h>
|
||||
|
||||
#include <legs/entity/camera.hpp>
|
||||
#include <legs/renderer/buffer.hpp>
|
||||
#include <legs/renderer/common.hpp>
|
||||
#include <legs/renderer/descriptor_set.hpp>
|
||||
#include <legs/renderer/device.hpp>
|
||||
#include <legs/renderer/instance.hpp>
|
||||
#include <legs/renderer/pipeline.hpp>
|
||||
#include <legs/renderer/ubo.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
enum RenderPipeline
|
||||
{
|
||||
INVALID,
|
||||
GEO_P_C,
|
||||
GEO_P_N_C,
|
||||
FULLSCREEN,
|
||||
SKY,
|
||||
};
|
||||
|
||||
class Renderer
|
||||
{
|
||||
public:
|
||||
Renderer(std::shared_ptr<Window> window);
|
||||
~Renderer();
|
||||
|
||||
Renderer(const Renderer&) = delete;
|
||||
Renderer(Renderer&&) = delete;
|
||||
Renderer& operator=(const Renderer&) = delete;
|
||||
Renderer& operator=(Renderer&&) = delete;
|
||||
|
||||
void SetWindow(std::shared_ptr<Window> window);
|
||||
|
||||
void ResetViewport()
|
||||
{
|
||||
m_device.ResetViewport();
|
||||
}
|
||||
|
||||
void SetViewport(SRect rect)
|
||||
{
|
||||
m_device.SetViewport(rect);
|
||||
}
|
||||
|
||||
void ClearViewport();
|
||||
void Resize();
|
||||
float GetAspect();
|
||||
void Begin();
|
||||
void Submit();
|
||||
void Present();
|
||||
void UpdateUBO();
|
||||
void WaitForIdle();
|
||||
|
||||
std::shared_ptr<UniformBufferObject> GetUBO()
|
||||
{
|
||||
return m_ubo;
|
||||
}
|
||||
|
||||
void* GetCommandBuffer()
|
||||
{
|
||||
return GetVkCommandBuffer();
|
||||
};
|
||||
|
||||
void GetImGuiInfo(ImGuiCreationInfo& info);
|
||||
|
||||
VkCommandBuffer GetVkCommandBuffer() const
|
||||
{
|
||||
return m_device.GetCommandBuffer();
|
||||
}
|
||||
|
||||
VkCommandBuffer GetTemporaryCommandBuffer()
|
||||
{
|
||||
return m_device.GetTemporaryCommandBuffer();
|
||||
}
|
||||
|
||||
void SubmitTemporaryCommandBuffer(VkCommandBuffer buffer)
|
||||
{
|
||||
m_device.SubmitTemporaryCommandBuffer(buffer);
|
||||
}
|
||||
|
||||
void CreateBuffer(
|
||||
std::shared_ptr<Buffer>& buffer,
|
||||
BufferType bufferType,
|
||||
void* data,
|
||||
uint32_t elementSize,
|
||||
uint32_t elementCount
|
||||
)
|
||||
{
|
||||
auto hostBuffer =
|
||||
std::make_shared<Buffer>(bufferType, HostBuffer, elementSize, elementCount);
|
||||
auto deviceBuffer =
|
||||
std::make_shared<Buffer>(bufferType, DeviceBuffer, elementSize, elementCount);
|
||||
hostBuffer->Write(data, elementSize * elementCount);
|
||||
auto tempBuffer = GetTemporaryCommandBuffer();
|
||||
hostBuffer->CopyToDevice(tempBuffer, static_pointer_cast<Buffer>(deviceBuffer));
|
||||
SubmitTemporaryCommandBuffer(tempBuffer);
|
||||
buffer = static_pointer_cast<Buffer>(deviceBuffer);
|
||||
}
|
||||
|
||||
void DrawWithBuffers(std::shared_ptr<Buffer> vertexBuffer, std::shared_ptr<Buffer> indexBuffer)
|
||||
{
|
||||
auto commandBuffer = GetCommandBuffer();
|
||||
if (commandBuffer != nullptr)
|
||||
{
|
||||
vertexBuffer->Bind(commandBuffer);
|
||||
indexBuffer->Bind(commandBuffer);
|
||||
indexBuffer->Draw(commandBuffer);
|
||||
m_frameBuffers.push_back(vertexBuffer);
|
||||
m_frameBuffers.push_back(indexBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
void BindPipeline(RenderPipeline pipe)
|
||||
{
|
||||
auto commandBuffer = m_device.GetCommandBuffer();
|
||||
auto currentFrame = m_device.GetCurrentFrame();
|
||||
|
||||
switch (pipe)
|
||||
{
|
||||
case GEO_P_C:
|
||||
{
|
||||
m_testPipeline->Bind(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, currentFrame);
|
||||
break;
|
||||
}
|
||||
|
||||
case GEO_P_N_C:
|
||||
{
|
||||
m_geoPNCPipeline
|
||||
->Bind(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, currentFrame);
|
||||
break;
|
||||
}
|
||||
|
||||
case FULLSCREEN:
|
||||
{
|
||||
m_fullscreenPipeline
|
||||
->Bind(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, currentFrame);
|
||||
break;
|
||||
}
|
||||
|
||||
case SKY:
|
||||
{
|
||||
m_skyPipeline->Bind(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, currentFrame);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
throw std::runtime_error("unknown pipeline");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
VkShaderModule& CreateShaderModule(VkShaderModuleCreateInfo createInfo);
|
||||
constexpr VkPipelineShaderStageCreateInfo FillShaderStageCreateInfo(
|
||||
VkShaderModule& module,
|
||||
VkShaderStageFlagBits stage
|
||||
);
|
||||
|
||||
Instance m_instance;
|
||||
Device m_device;
|
||||
|
||||
std::shared_ptr<DescriptorSet> m_descriptorSet;
|
||||
std::vector<VkShaderModule> m_vkShaderModules;
|
||||
|
||||
std::shared_ptr<Pipeline<Vertex_P_C>> m_testPipeline;
|
||||
std::shared_ptr<Pipeline<Vertex_P_N_C>> m_geoPNCPipeline;
|
||||
std::shared_ptr<Pipeline<VertexEmpty>> m_fullscreenPipeline;
|
||||
std::shared_ptr<Pipeline<Vertex_P>> m_skyPipeline;
|
||||
|
||||
std::shared_ptr<UniformBufferObject> m_ubo;
|
||||
|
||||
// Hold so we don't call Buffer destructor
|
||||
// while still in use by command buffer.
|
||||
std::vector<std::shared_ptr<Buffer>> m_frameBuffers;
|
||||
};
|
||||
} // namespace legs
|
41
src/public/legs/renderer/shader.hpp
Normal file
41
src/public/legs/renderer/shader.hpp
Normal file
|
@ -0,0 +1,41 @@
|
|||
#pragma once
|
||||
|
||||
#include <vulkan/vulkan_core.h>
|
||||
|
||||
// Generated files by glslang
|
||||
#include <fullscreen_frag.h>
|
||||
#include <fullscreen_vert.h>
|
||||
#include <lit_pnc_vert.h>
|
||||
#include <lit_pnc_frag.h>
|
||||
#include <unlit_pc_frag.h>
|
||||
#include <unlit_pc_vert.h>
|
||||
#include <sky_frag.h>
|
||||
#include <sky_vert.h>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
class Shader
|
||||
{
|
||||
public:
|
||||
Shader() = delete;
|
||||
~Shader() = delete;
|
||||
Shader(const Shader&) = delete;
|
||||
Shader(Shader&&) = delete;
|
||||
Shader& operator=(const Shader&) = delete;
|
||||
Shader& operator=(Shader&&) = delete;
|
||||
|
||||
constexpr static VkShaderModuleCreateInfo GetCreateInfo(const uint32_t spv[], const size_t size)
|
||||
{
|
||||
VkShaderModuleCreateInfo createInfo {};
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
||||
createInfo.codeSize = size;
|
||||
createInfo.pCode = static_cast<const uint32_t*>(spv);
|
||||
return createInfo;
|
||||
}
|
||||
};
|
||||
|
||||
// Usage:
|
||||
// auto shader = LOAD_VULKAN_SPV(simple_frag);
|
||||
// To load Shaders/simple_frag.frag (simple_frag.h)
|
||||
#define LOAD_VULKAN_SPV(NAME) legs::Shader::GetCreateInfo(NAME, sizeof(NAME))
|
||||
} // namespace legs
|
50
src/public/legs/renderer/ubo.hpp
Normal file
50
src/public/legs/renderer/ubo.hpp
Normal file
|
@ -0,0 +1,50 @@
|
|||
#pragma once
|
||||
|
||||
#include <glm/ext/matrix_clip_space.hpp>
|
||||
#include <glm/ext/matrix_transform.hpp>
|
||||
#include <glm/ext/quaternion_common.hpp>
|
||||
#include <glm/ext/quaternion_float.hpp>
|
||||
#include <glm/ext/quaternion_transform.hpp>
|
||||
#include <glm/gtc/constants.hpp>
|
||||
#include <glm/mat4x4.hpp>
|
||||
#include <glm/matrix.hpp>
|
||||
#include <glm/vec3.hpp>
|
||||
|
||||
#include <legs/entity/camera.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
struct UniformBufferObject
|
||||
{
|
||||
alignas(16) glm::mat4 model;
|
||||
alignas(16) glm::mat4 view;
|
||||
alignas(16) glm::mat4 proj;
|
||||
alignas(16) glm::mat4 mvp;
|
||||
alignas(16) glm::mat4 invModel;
|
||||
alignas(16) glm::mat4 invView;
|
||||
alignas(16) glm::mat4 invProj;
|
||||
alignas(16) glm::mat4 clipToWorld;
|
||||
alignas(16) glm::vec3 eye;
|
||||
|
||||
alignas(16) glm::vec4 viewport;
|
||||
|
||||
alignas(16) glm::vec3 sunDir;
|
||||
alignas(16) glm::vec3 sunColor;
|
||||
|
||||
void SetCamera(const std::shared_ptr<Camera> cam)
|
||||
{
|
||||
model = glm::identity<glm::mat4>();
|
||||
view = cam->view;
|
||||
proj = cam->proj;
|
||||
mvp = proj * view * model;
|
||||
invModel = glm::inverse(model);
|
||||
invView = glm::inverse(view);
|
||||
invProj = glm::inverse(proj);
|
||||
clipToWorld = glm::inverse(proj * view);
|
||||
eye = cam->GetPosition();
|
||||
|
||||
viewport = cam->viewport;
|
||||
}
|
||||
};
|
||||
|
||||
}; // namespace legs
|
14
src/public/legs/renderer/vma_usage.hpp
Normal file
14
src/public/legs/renderer/vma_usage.hpp
Normal file
|
@ -0,0 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
#include <vk_mem_alloc.h>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
extern void CreateAllocator(VkInstance instance, VkPhysicalDevice physicalDevice, VkDevice device);
|
||||
|
||||
extern void DestroyAllocator();
|
||||
|
||||
extern VmaTotalStatistics GetAllocatorTotalStatistics();
|
||||
|
||||
extern VmaAllocator g_vma;
|
||||
} // namespace legs
|
12
src/public/legs/shaders/fullscreen_frag.frag
Normal file
12
src/public/legs/shaders/fullscreen_frag.frag
Normal file
|
@ -0,0 +1,12 @@
|
|||
#version 450
|
||||
|
||||
#extension GL_GOOGLE_include_directive : enable
|
||||
|
||||
layout(location = 0) in vec2 outUV;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
outColor = vec4(0.0, 0.0, 0.0, 1.0);
|
||||
}
|
12
src/public/legs/shaders/fullscreen_vert.vert
Normal file
12
src/public/legs/shaders/fullscreen_vert.vert
Normal file
|
@ -0,0 +1,12 @@
|
|||
// https://www.saschawillems.de/blog/2016/08/13/vulkan-tutorial-on-rendering-a-fullscreen-quad-without-buffers/
|
||||
#version 450
|
||||
|
||||
#extension GL_GOOGLE_include_directive : enable
|
||||
|
||||
layout(location = 0) out vec2 outUV;
|
||||
|
||||
void main()
|
||||
{
|
||||
outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2);
|
||||
gl_Position = vec4(outUV * 2.0f + -1.0f, 0.0f, 1.0f);
|
||||
}
|
21
src/public/legs/shaders/include/lighting.glsl
Normal file
21
src/public/legs/shaders/include/lighting.glsl
Normal file
|
@ -0,0 +1,21 @@
|
|||
vec3 BlinnPhong(vec3 pos, vec3 normal, vec3 eyePos, float specularity)
|
||||
{
|
||||
vec3 viewDir = normalize(eyePos - pos);
|
||||
vec3 halfDir = normalize(ubo.sunDir + viewDir);
|
||||
|
||||
float sunMult = smoothstep(-0.6, 0.1, ubo.sunDir.z);
|
||||
vec3 sunColor = ubo.sunColor * sunMult;
|
||||
|
||||
float diff = max(dot(normal, ubo.sunDir), 0.0);
|
||||
vec3 diffuse = diff * sunColor;
|
||||
|
||||
float spec = pow(max(dot(normal, halfDir), 0.0), 16.0);
|
||||
vec3 specular = specularity * spec * sunColor;
|
||||
|
||||
vec3 ambient = vec3(0.01);
|
||||
float horizonScatter = 1.0 - abs(ubo.sunDir.z);
|
||||
ambient += 0.08 * horizonScatter;
|
||||
ambient.r *= (1.0 + horizonScatter);
|
||||
|
||||
return ambient + diffuse + specular;
|
||||
}
|
17
src/public/legs/shaders/include/ubo.glsl
Normal file
17
src/public/legs/shaders/include/ubo.glsl
Normal file
|
@ -0,0 +1,17 @@
|
|||
layout(binding = 0) uniform UniformBufferObject
|
||||
{
|
||||
mat4 model;
|
||||
mat4 view;
|
||||
mat4 proj;
|
||||
mat4 mvp;
|
||||
mat4 invModel;
|
||||
mat4 invView;
|
||||
mat4 invProj;
|
||||
mat4 clipToWorld;
|
||||
vec3 eye;
|
||||
|
||||
vec4 viewport;
|
||||
|
||||
vec3 sunDir;
|
||||
vec3 sunColor;
|
||||
} ubo;
|
14
src/public/legs/shaders/include/util.glsl
Normal file
14
src/public/legs/shaders/include/util.glsl
Normal file
|
@ -0,0 +1,14 @@
|
|||
vec4 FragClipPos()
|
||||
{
|
||||
vec4 ndc;
|
||||
ndc.xy = (2.0 * gl_FragCoord.xy / ubo.viewport.xy) - 1.0;
|
||||
ndc.z = (2.0 * gl_FragCoord.z) - 1.0;
|
||||
ndc.w = 1.0;
|
||||
vec4 clip = ndc / gl_FragCoord.w;
|
||||
return clip;
|
||||
}
|
||||
|
||||
vec3 FragWorldPos()
|
||||
{
|
||||
return (ubo.clipToWorld * FragClipPos()).xyz;
|
||||
}
|
1
src/public/legs/shaders/include/vertex_p.glsl
Normal file
1
src/public/legs/shaders/include/vertex_p.glsl
Normal file
|
@ -0,0 +1 @@
|
|||
layout(location = 0) in vec3 inPosition;
|
2
src/public/legs/shaders/include/vertex_pc.glsl
Normal file
2
src/public/legs/shaders/include/vertex_pc.glsl
Normal file
|
@ -0,0 +1,2 @@
|
|||
layout(location = 0) in vec3 inPosition;
|
||||
layout(location = 1) in vec3 inColor;
|
3
src/public/legs/shaders/include/vertex_pnc.glsl
Normal file
3
src/public/legs/shaders/include/vertex_pnc.glsl
Normal file
|
@ -0,0 +1,3 @@
|
|||
layout(location = 0) in vec3 inPosition;
|
||||
layout(location = 1) in vec3 inNormal;
|
||||
layout(location = 2) in vec3 inColor;
|
15
src/public/legs/shaders/lit_pnc_frag.frag
Normal file
15
src/public/legs/shaders/lit_pnc_frag.frag
Normal file
|
@ -0,0 +1,15 @@
|
|||
#version 450
|
||||
|
||||
#extension GL_GOOGLE_include_directive : enable
|
||||
|
||||
#include "include/ubo.glsl"
|
||||
#include "include/lighting.glsl"
|
||||
|
||||
layout(location = 0) in vec3 fragColor;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
outColor = vec4(fragColor, 1.0);
|
||||
}
|
19
src/public/legs/shaders/lit_pnc_vert.vert
Normal file
19
src/public/legs/shaders/lit_pnc_vert.vert
Normal file
|
@ -0,0 +1,19 @@
|
|||
#version 450
|
||||
|
||||
#extension GL_GOOGLE_include_directive : enable
|
||||
|
||||
#include "include/vertex_pnc.glsl"
|
||||
#include "include/ubo.glsl"
|
||||
#include "include/lighting.glsl"
|
||||
|
||||
layout(location = 0) out vec3 fragColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = ubo.mvp * vec4(inPosition, 1.0);
|
||||
|
||||
vec3 position = (ubo.model * vec4(inPosition, 1.0)).xyz;
|
||||
vec3 normal = (ubo.model * vec4(inNormal, 1.0)).xyz;
|
||||
vec3 light = BlinnPhong(gl_Position.xyz, normal, ubo.eye, 1.0);
|
||||
fragColor = inColor * light;
|
||||
}
|
40
src/public/legs/shaders/sky_frag.frag
Normal file
40
src/public/legs/shaders/sky_frag.frag
Normal file
|
@ -0,0 +1,40 @@
|
|||
#version 450
|
||||
|
||||
#extension GL_GOOGLE_include_directive : enable
|
||||
|
||||
#include "include/ubo.glsl"
|
||||
#include "include/util.glsl"
|
||||
|
||||
layout(location = 0) in vec3 fragDir;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
float sunDot = dot(fragDir, ubo.sunDir);
|
||||
|
||||
vec3 darkColor = vec3(0.05, 0.05, 0.05);
|
||||
vec3 baseColor = vec3(0.1, 0.2, 0.7);
|
||||
vec3 brightColor = vec3(1.0, 1.0, 1.0);
|
||||
|
||||
float dayAmount = 0.01 + smoothstep(-1.0, 1.0, ubo.sunDir.z);
|
||||
darkColor.rgb *= dayAmount;
|
||||
baseColor.rgb *= dayAmount;
|
||||
brightColor.rgb *= dayAmount;
|
||||
|
||||
float horizonAmount = 3.0 * smoothstep(0.9, -0.4, ubo.sunDir.z);
|
||||
vec3 horizonColor = 1.0 + vec3(horizonAmount, 0.0, -0.4 * horizonAmount);
|
||||
darkColor.rgb *= horizonColor;
|
||||
baseColor.rgb *= horizonColor;
|
||||
brightColor.rgb *= horizonColor;
|
||||
|
||||
float darkMix = smoothstep(0.0, -1.0, sunDot);
|
||||
|
||||
float brightMix = sign(sunDot) * pow(sunDot, 4);
|
||||
brightMix = smoothstep(0.0, 1.0, brightMix);
|
||||
|
||||
vec3 gradient = mix(baseColor, darkColor, darkMix)
|
||||
+ mix(baseColor, brightColor, brightMix);
|
||||
|
||||
outColor = vec4(gradient, 1.0);
|
||||
}
|
14
src/public/legs/shaders/sky_vert.vert
Normal file
14
src/public/legs/shaders/sky_vert.vert
Normal file
|
@ -0,0 +1,14 @@
|
|||
#version 450
|
||||
|
||||
#extension GL_GOOGLE_include_directive : enable
|
||||
|
||||
#include "include/vertex_p.glsl"
|
||||
#include "include/ubo.glsl"
|
||||
|
||||
layout(location = 0) out vec3 fragDir;
|
||||
|
||||
void main()
|
||||
{
|
||||
fragDir = normalize((transpose(ubo.view) * vec4(inPosition, 1.0)).xyz);
|
||||
gl_Position = ubo.proj * vec4(inPosition, 1.0);
|
||||
}
|
12
src/public/legs/shaders/unlit_pc_frag.frag
Normal file
12
src/public/legs/shaders/unlit_pc_frag.frag
Normal file
|
@ -0,0 +1,12 @@
|
|||
#version 450
|
||||
|
||||
#extension GL_GOOGLE_include_directive : enable
|
||||
|
||||
layout(location = 0) in vec3 fragColor;
|
||||
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
outColor = vec4(fragColor, 1.0);
|
||||
}
|
14
src/public/legs/shaders/unlit_pc_vert.vert
Normal file
14
src/public/legs/shaders/unlit_pc_vert.vert
Normal file
|
@ -0,0 +1,14 @@
|
|||
#version 450
|
||||
|
||||
#extension GL_GOOGLE_include_directive : enable
|
||||
|
||||
#include "include/vertex_pc.glsl"
|
||||
#include "include/ubo.glsl"
|
||||
|
||||
layout(location = 0) out vec3 fragColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0);
|
||||
fragColor = inColor;
|
||||
}
|
108
src/public/legs/time.hpp
Normal file
108
src/public/legs/time.hpp
Normal file
|
@ -0,0 +1,108 @@
|
|||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
using clock_duration = std::chrono::duration<
|
||||
double,
|
||||
std::ratio<
|
||||
std::chrono::high_resolution_clock::period::num,
|
||||
std::chrono::high_resolution_clock::period::den>>;
|
||||
|
||||
class Time
|
||||
{
|
||||
public:
|
||||
static double Now()
|
||||
{
|
||||
const auto now = std::chrono::high_resolution_clock::now();
|
||||
return static_cast<double>(now.time_since_epoch().count())
|
||||
* std::chrono::high_resolution_clock::period::num
|
||||
/ std::chrono::high_resolution_clock::period::den;
|
||||
}
|
||||
|
||||
static double Uptime()
|
||||
{
|
||||
return Now() - startTime;
|
||||
}
|
||||
|
||||
constexpr static clock_duration Duration(const double seconds)
|
||||
{
|
||||
return clock_duration {
|
||||
seconds * std::chrono::high_resolution_clock::period::den
|
||||
/ std::chrono::high_resolution_clock::period::num
|
||||
};
|
||||
}
|
||||
|
||||
static void UpdateFrameDelta()
|
||||
{
|
||||
const auto now = Now();
|
||||
DeltaFrame = now - prevFrame;
|
||||
prevFrame = now;
|
||||
}
|
||||
|
||||
static void UpdateTickDelta()
|
||||
{
|
||||
const auto now = Now();
|
||||
DeltaTick = now - prevTick;
|
||||
prevTick = now;
|
||||
}
|
||||
|
||||
static double TimeSinceEngineFrame()
|
||||
{
|
||||
return Now() - prevFrame;
|
||||
}
|
||||
|
||||
static double TimeSinceEngineTick()
|
||||
{
|
||||
return Now() - prevTick;
|
||||
}
|
||||
|
||||
static double TimeToEngineFrame()
|
||||
{
|
||||
return FrameInterval - TimeSinceEngineFrame();
|
||||
}
|
||||
|
||||
static double TimeToEngineTick()
|
||||
{
|
||||
return TickInterval - TimeSinceEngineTick();
|
||||
}
|
||||
|
||||
static void SetStart()
|
||||
{
|
||||
startTime = Now();
|
||||
}
|
||||
|
||||
static void SetFrameRate(unsigned int fps)
|
||||
{
|
||||
FrameRate = fps;
|
||||
FrameInterval = 1.0 / fps;
|
||||
}
|
||||
|
||||
static void StartRender()
|
||||
{
|
||||
renderStart = Now();
|
||||
}
|
||||
|
||||
static void StopRender()
|
||||
{
|
||||
DeltaRender = Now() - renderStart;
|
||||
}
|
||||
|
||||
static inline double DeltaFrame;
|
||||
static inline double DeltaTick;
|
||||
static inline double DeltaRender;
|
||||
|
||||
static inline unsigned int FrameRate = 60;
|
||||
static inline double FrameInterval = 1.0 / FrameRate;
|
||||
|
||||
static constexpr const unsigned int TickRate = 60;
|
||||
static constexpr const double TickInterval = 1.0 / TickRate;
|
||||
|
||||
private:
|
||||
static inline double prevFrame;
|
||||
static inline double prevTick;
|
||||
static inline double renderStart;
|
||||
static inline double startTime;
|
||||
};
|
||||
} // namespace legs
|
54
src/public/legs/ui/ui.hpp
Normal file
54
src/public/legs/ui/ui.hpp
Normal file
|
@ -0,0 +1,54 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <glm/vec2.hpp>
|
||||
|
||||
#include <legs/renderer/renderer.hpp>
|
||||
#include <legs/window/window.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
|
||||
enum class UIWindow
|
||||
{
|
||||
DEBUG,
|
||||
DEMO,
|
||||
MAX
|
||||
};
|
||||
|
||||
struct UIState
|
||||
{
|
||||
bool showWindow[static_cast<unsigned int>(UIWindow::MAX)];
|
||||
};
|
||||
|
||||
class UI
|
||||
{
|
||||
public:
|
||||
UI() = delete;
|
||||
UI(const UI&) = delete;
|
||||
UI(UI&&) = delete;
|
||||
UI& operator=(const UI&) = delete;
|
||||
UI& operator=(UI&&) = delete;
|
||||
|
||||
UI(std::shared_ptr<Window> window, std::shared_ptr<Renderer> renderer);
|
||||
~UI();
|
||||
|
||||
void ToggleWindow(UIWindow window)
|
||||
{
|
||||
auto index = static_cast<unsigned int>(window);
|
||||
m_state.showWindow[index] = !m_state.showWindow[index];
|
||||
}
|
||||
|
||||
void Render();
|
||||
|
||||
private:
|
||||
void DebugWindow();
|
||||
void DemoWindow();
|
||||
|
||||
std::shared_ptr<Window> m_window;
|
||||
std::shared_ptr<Renderer> m_renderer;
|
||||
ImGuiCreationInfo m_info;
|
||||
UIState m_state;
|
||||
};
|
||||
}; // namespace legs
|
130
src/public/legs/window/input.hpp
Normal file
130
src/public/legs/window/input.hpp
Normal file
|
@ -0,0 +1,130 @@
|
|||
#pragma once
|
||||
|
||||
#include <glm/vec2.hpp>
|
||||
#include <SDL_scancode.h>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
|
||||
enum Key : unsigned int
|
||||
{
|
||||
KEY_NONE,
|
||||
|
||||
KEY_MOVE_FORWARD,
|
||||
KEY_MOVE_BACK,
|
||||
KEY_MOVE_RIGHT,
|
||||
KEY_MOVE_LEFT,
|
||||
KEY_MOVE_UP,
|
||||
KEY_MOVE_DOWN,
|
||||
|
||||
KEY_MOUSE_GRAB,
|
||||
|
||||
KEY_WINDOW_DEBUG,
|
||||
KEY_WINDOW_DEMO,
|
||||
|
||||
KEY_MAX,
|
||||
};
|
||||
|
||||
static constexpr unsigned long long KeyToFlag(Key k)
|
||||
{
|
||||
return 1 << static_cast<unsigned int>(k);
|
||||
}
|
||||
|
||||
class InputSettings
|
||||
{
|
||||
public:
|
||||
InputSettings()
|
||||
{
|
||||
for (unsigned int i = 0; i < SDL_NUM_SCANCODES; i++)
|
||||
{
|
||||
m_sdlKeyMap[i] = Key::KEY_NONE;
|
||||
}
|
||||
|
||||
ApplyDefaults();
|
||||
}
|
||||
|
||||
void ApplyDefaults()
|
||||
{
|
||||
m_sdlKeyMap[static_cast<unsigned int>(SDL_SCANCODE_W)] = Key::KEY_MOVE_FORWARD;
|
||||
m_sdlKeyMap[static_cast<unsigned int>(SDL_SCANCODE_A)] = Key::KEY_MOVE_LEFT;
|
||||
m_sdlKeyMap[static_cast<unsigned int>(SDL_SCANCODE_S)] = Key::KEY_MOVE_BACK;
|
||||
m_sdlKeyMap[static_cast<unsigned int>(SDL_SCANCODE_D)] = Key::KEY_MOVE_RIGHT;
|
||||
m_sdlKeyMap[static_cast<unsigned int>(SDL_SCANCODE_SPACE)] = Key::KEY_MOVE_UP;
|
||||
m_sdlKeyMap[static_cast<unsigned int>(SDL_SCANCODE_LCTRL)] = Key::KEY_MOVE_DOWN;
|
||||
|
||||
m_sdlKeyMap[static_cast<unsigned int>(SDL_SCANCODE_F1)] = Key::KEY_MOUSE_GRAB;
|
||||
|
||||
m_sdlKeyMap[static_cast<unsigned int>(SDL_SCANCODE_F2)] = Key::KEY_WINDOW_DEBUG;
|
||||
m_sdlKeyMap[static_cast<unsigned int>(SDL_SCANCODE_F3)] = Key::KEY_WINDOW_DEMO;
|
||||
}
|
||||
|
||||
Key GetKeyFromSDL(unsigned int scan)
|
||||
{
|
||||
return m_sdlKeyMap[scan];
|
||||
}
|
||||
|
||||
private:
|
||||
Key m_sdlKeyMap[SDL_NUM_SCANCODES];
|
||||
};
|
||||
|
||||
struct WindowInput
|
||||
{
|
||||
|
||||
bool wantsQuit;
|
||||
bool wantsResize;
|
||||
|
||||
unsigned long long keyFlags;
|
||||
|
||||
glm::ivec2 mouse;
|
||||
glm::ivec2 scroll;
|
||||
|
||||
WindowInput()
|
||||
{
|
||||
Clear(true);
|
||||
}
|
||||
|
||||
void Clear(bool clearKeys = false)
|
||||
{
|
||||
wantsQuit = false;
|
||||
wantsResize = false;
|
||||
|
||||
// Generally don't want to do this,
|
||||
// since window only tracks key up / down events.
|
||||
if (clearKeys)
|
||||
{
|
||||
keyFlags = 0;
|
||||
}
|
||||
|
||||
mouse.x = 0;
|
||||
mouse.y = 0;
|
||||
scroll.x = 0;
|
||||
scroll.y = 0;
|
||||
}
|
||||
|
||||
void Aggregate(const WindowInput& other)
|
||||
{
|
||||
wantsQuit |= other.wantsQuit;
|
||||
wantsResize |= other.wantsResize;
|
||||
|
||||
keyFlags |= other.keyFlags;
|
||||
|
||||
mouse += other.mouse;
|
||||
scroll += other.scroll;
|
||||
}
|
||||
|
||||
bool HasKey(Key key)
|
||||
{
|
||||
return (keyFlags & KeyToFlag(key)) == KeyToFlag(key);
|
||||
}
|
||||
|
||||
void KeyDown(Key key)
|
||||
{
|
||||
keyFlags |= KeyToFlag(key);
|
||||
}
|
||||
|
||||
void KeyUp(Key key)
|
||||
{
|
||||
keyFlags &= ~KeyToFlag(key);
|
||||
}
|
||||
};
|
||||
}; // namespace legs
|
43
src/public/legs/window/window.hpp
Normal file
43
src/public/legs/window/window.hpp
Normal file
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <SDL_syswm.h>
|
||||
#include <vulkan/vulkan_core.h>
|
||||
|
||||
#include <legs/window/input.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
class Window
|
||||
{
|
||||
public:
|
||||
Window(std::shared_ptr<InputSettings> inputSettings);
|
||||
~Window();
|
||||
|
||||
void SetTitle(const char* title);
|
||||
void SetMouseGrab(bool grab);
|
||||
bool IsMouseGrabbed();
|
||||
|
||||
void AggregateInput(WindowInput& input);
|
||||
|
||||
void GetFramebufferSize(int* width, int* height);
|
||||
|
||||
bool CreateSurface(VkInstance instance, VkSurfaceKHR* surface);
|
||||
|
||||
bool GetExtensions(unsigned int* pCount, const char** pNames);
|
||||
|
||||
bool IsMinimized();
|
||||
|
||||
unsigned int GetRefreshRate();
|
||||
|
||||
SDL_Window* GetSDLWindow() const
|
||||
{
|
||||
return m_window;
|
||||
}
|
||||
|
||||
private:
|
||||
SDL_Window* m_window;
|
||||
std::shared_ptr<InputSettings> m_inputSettings;
|
||||
};
|
||||
} // namespace legs
|
58
src/public/legs/world/world.hpp
Normal file
58
src/public/legs/world/world.hpp
Normal file
|
@ -0,0 +1,58 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#include <legs/iphysics.hpp>
|
||||
|
||||
#include <legs/entity/mesh_entity.hpp>
|
||||
#include <legs/entity/sky.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
class World
|
||||
{
|
||||
public:
|
||||
World() = delete;
|
||||
World(std::shared_ptr<Renderer> renderer);
|
||||
~World();
|
||||
|
||||
World(const World&) = delete;
|
||||
World(World&&) = delete;
|
||||
World& operator=(const World&) = delete;
|
||||
World& operator=(World&&) = delete;
|
||||
|
||||
void Frame();
|
||||
void Tick();
|
||||
void Render();
|
||||
|
||||
void AddEntity(std::shared_ptr<Entity> entity);
|
||||
void RemoveEntity(std::shared_ptr<Entity> entity);
|
||||
|
||||
void SetSky(std::shared_ptr<Sky> sky)
|
||||
{
|
||||
m_sky = sky;
|
||||
}
|
||||
|
||||
std::shared_ptr<Sky> GetSky()
|
||||
{
|
||||
return m_sky;
|
||||
}
|
||||
|
||||
std::shared_ptr<IPhysics> GetPhysics()
|
||||
{
|
||||
return m_physics;
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex m_worldMutex;
|
||||
|
||||
std::shared_ptr<Renderer> m_renderer;
|
||||
|
||||
std::vector<std::shared_ptr<Entity>> m_entities;
|
||||
|
||||
std::shared_ptr<Sky> m_sky;
|
||||
|
||||
std::shared_ptr<IPhysics> m_physics;
|
||||
};
|
||||
} // namespace legs
|
253
src/renderer/buffer.cpp
Normal file
253
src/renderer/buffer.cpp
Normal file
|
@ -0,0 +1,253 @@
|
|||
#include <legs/renderer/buffer.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
Buffer::Buffer(
|
||||
BufferType bufferType,
|
||||
BufferLocation bufferLocation,
|
||||
uint32_t elementSize,
|
||||
uint32_t elementCount
|
||||
) :
|
||||
m_bufferType(bufferType),
|
||||
m_bufferLocation(bufferLocation),
|
||||
m_elementSize(elementSize),
|
||||
m_elementCount(elementCount),
|
||||
m_size(m_elementSize * m_elementCount)
|
||||
{
|
||||
VkBufferCreateInfo bufferInfo {};
|
||||
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||
bufferInfo.size = m_size;
|
||||
bufferInfo.usage = 0;
|
||||
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
|
||||
VmaAllocationCreateInfo allocInfo {};
|
||||
|
||||
switch (m_bufferType)
|
||||
{
|
||||
case VertexBuffer:
|
||||
{
|
||||
bufferInfo.usage |= VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
|
||||
break;
|
||||
}
|
||||
|
||||
case IndexBuffer:
|
||||
{
|
||||
bufferInfo.usage |= VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
|
||||
break;
|
||||
}
|
||||
|
||||
case UniformBuffer:
|
||||
{
|
||||
bufferInfo.usage |= VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
|
||||
allocInfo.requiredFlags |= VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
std::runtime_error("Unhandled buffer type");
|
||||
}
|
||||
}
|
||||
|
||||
switch (m_bufferLocation)
|
||||
{
|
||||
case HostBuffer:
|
||||
{
|
||||
bufferInfo.usage |= VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
|
||||
|
||||
allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
|
||||
allocInfo.flags |= VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
|
||||
allocInfo.requiredFlags |= VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;
|
||||
allocInfo.preferredFlags |= VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case DeviceBuffer:
|
||||
{
|
||||
bufferInfo.usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
||||
|
||||
allocInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
|
||||
allocInfo.preferredFlags |= VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
throw std::runtime_error("Unhandled buffer destination");
|
||||
}
|
||||
}
|
||||
|
||||
vmaCreateBuffer(g_vma, &bufferInfo, &allocInfo, &m_vkBuffer, &m_vmaAllocation, nullptr);
|
||||
}
|
||||
|
||||
Buffer::~Buffer()
|
||||
{
|
||||
if (m_isMapped)
|
||||
{
|
||||
Unmap();
|
||||
}
|
||||
vmaDestroyBuffer(g_vma, m_vkBuffer, m_vmaAllocation);
|
||||
}
|
||||
|
||||
void Buffer::Write(void* data, size_t size)
|
||||
{
|
||||
vmaCopyMemoryToAllocation(g_vma, data, m_vmaAllocation, 0, size);
|
||||
}
|
||||
|
||||
void Buffer::CopyToDevice(void* commandBuffer, std::shared_ptr<Buffer> deviceBuffer)
|
||||
{
|
||||
if (m_bufferLocation != HostBuffer)
|
||||
{
|
||||
throw std::runtime_error("Tried copying from a non-host buffer");
|
||||
}
|
||||
|
||||
if (deviceBuffer->GetLocation() != DeviceBuffer)
|
||||
{
|
||||
throw std::runtime_error("Tried copying to a non-device buffer");
|
||||
}
|
||||
|
||||
VkDeviceSize requiredSize = GetElementCount() * GetElementSize();
|
||||
if (deviceBuffer->GetSize() < requiredSize)
|
||||
{
|
||||
throw std::runtime_error("Tried copying to a buffer that is too small");
|
||||
}
|
||||
|
||||
auto vkCommandBuffer = static_cast<VkCommandBuffer>(commandBuffer);
|
||||
|
||||
VkBufferCopy copyRegion {};
|
||||
copyRegion.size = requiredSize;
|
||||
copyRegion.srcOffset = 0;
|
||||
copyRegion.dstOffset = 0;
|
||||
|
||||
auto vkDeviceBuffer = static_pointer_cast<Buffer>(deviceBuffer);
|
||||
vkCmdCopyBuffer(vkCommandBuffer, m_vkBuffer, vkDeviceBuffer->GetVkBuffer(), 1, ©Region);
|
||||
|
||||
deviceBuffer->SetElementCount(m_elementCount);
|
||||
}
|
||||
|
||||
void Buffer::Clear()
|
||||
{
|
||||
if (m_bufferLocation != HostBuffer)
|
||||
{
|
||||
throw std::runtime_error("Tried mapping a non-host buffer");
|
||||
}
|
||||
|
||||
VmaAllocationInfo allocInfo {};
|
||||
vmaGetAllocationInfo(g_vma, m_vmaAllocation, &allocInfo);
|
||||
|
||||
void* mappedData;
|
||||
vmaMapMemory(g_vma, m_vmaAllocation, &mappedData);
|
||||
std::memset(mappedData, 0, allocInfo.size);
|
||||
vmaUnmapMemory(g_vma, m_vmaAllocation);
|
||||
|
||||
if (!(allocInfo.memoryType & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT))
|
||||
{
|
||||
vmaFlushAllocation(g_vma, m_vmaAllocation, 0, allocInfo.size);
|
||||
}
|
||||
}
|
||||
|
||||
void Buffer::Bind(void* commandBuffer)
|
||||
{
|
||||
if (m_bufferLocation != DeviceBuffer)
|
||||
{
|
||||
throw std::runtime_error("Tried to bind a non-device buffer");
|
||||
}
|
||||
|
||||
auto vkCommandBuffer = static_cast<VkCommandBuffer>(commandBuffer);
|
||||
|
||||
switch (m_bufferType)
|
||||
{
|
||||
case VertexBuffer:
|
||||
{
|
||||
VkBuffer buffers[] = {m_vkBuffer};
|
||||
VkDeviceSize offsets[] = {0};
|
||||
vkCmdBindVertexBuffers(vkCommandBuffer, 0, 1, buffers, offsets);
|
||||
break;
|
||||
}
|
||||
|
||||
case IndexBuffer:
|
||||
{
|
||||
vkCmdBindIndexBuffer(vkCommandBuffer, m_vkBuffer, 0, VK_INDEX_TYPE_UINT32);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
throw std::runtime_error("Unhandled buffer type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Buffer::Draw(void* commandBuffer)
|
||||
{
|
||||
auto vkCommandBuffer = static_cast<VkCommandBuffer>(commandBuffer);
|
||||
|
||||
switch (m_bufferType)
|
||||
{
|
||||
case VertexBuffer:
|
||||
{
|
||||
vkCmdDraw(vkCommandBuffer, m_elementCount, 1, 0, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
case IndexBuffer:
|
||||
{
|
||||
vkCmdDrawIndexed(vkCommandBuffer, m_elementCount, 1, 0, 0, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
throw std::runtime_error("Unhandled buffer type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Buffer::Draw(
|
||||
void* commandBuffer,
|
||||
uint32_t indexOffset,
|
||||
uint32_t indexCount,
|
||||
int32_t vertexOffset
|
||||
)
|
||||
{
|
||||
auto vkCommandBuffer = static_cast<VkCommandBuffer>(commandBuffer);
|
||||
|
||||
switch (m_bufferType)
|
||||
{
|
||||
case IndexBuffer:
|
||||
{
|
||||
vkCmdDrawIndexed(vkCommandBuffer, indexCount, 1, indexOffset, vertexOffset, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
throw std::runtime_error("Unhandled buffer type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Buffer::Map(void** data)
|
||||
{
|
||||
if (m_isMapped)
|
||||
{
|
||||
throw std::runtime_error("Tried to map a buffer that is already mapped");
|
||||
}
|
||||
|
||||
vmaMapMemory(g_vma, m_vmaAllocation, data);
|
||||
m_isMapped = true;
|
||||
}
|
||||
|
||||
void Buffer::Unmap()
|
||||
{
|
||||
if (!m_isMapped)
|
||||
{
|
||||
throw std::runtime_error("Tried to unmap buffer that isn't mapped");
|
||||
}
|
||||
|
||||
vmaUnmapMemory(g_vma, m_vmaAllocation);
|
||||
m_isMapped = false;
|
||||
}
|
||||
}; // namespace legs
|
116
src/renderer/descriptor_set.cpp
Normal file
116
src/renderer/descriptor_set.cpp
Normal file
|
@ -0,0 +1,116 @@
|
|||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#include <legs/renderer/common.hpp>
|
||||
#include <legs/renderer/descriptor_set.hpp>
|
||||
#include <legs/renderer/mesh_data.hpp>
|
||||
#include <legs/renderer/ubo.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
DescriptorSet::DescriptorSet(
|
||||
const Device& device,
|
||||
std::vector<std::shared_ptr<Buffer>> uboBuffers
|
||||
) :
|
||||
m_device(device),
|
||||
m_uniformBuffers(uboBuffers)
|
||||
{
|
||||
VkDescriptorSetLayoutBinding uboBinding {};
|
||||
uboBinding.binding = 0;
|
||||
uboBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
||||
uboBinding.descriptorCount = 1;
|
||||
uboBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
|
||||
uboBinding.pImmutableSamplers = nullptr;
|
||||
|
||||
VkDescriptorSetLayoutCreateInfo layoutInfo {};
|
||||
layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
|
||||
layoutInfo.bindingCount = 1;
|
||||
layoutInfo.pBindings = &uboBinding;
|
||||
|
||||
const auto maxFrames = static_cast<uint32_t>(m_uniformBuffers.size());
|
||||
m_vkLayouts.resize(maxFrames);
|
||||
|
||||
for (uint32_t i = 0; i < maxFrames; i++)
|
||||
{
|
||||
VK_CHECK(
|
||||
vkCreateDescriptorSetLayout(
|
||||
device.GetVkDevice(),
|
||||
&layoutInfo,
|
||||
nullptr,
|
||||
&m_vkLayouts[i]
|
||||
),
|
||||
"Failed to create descriptor set layout"
|
||||
);
|
||||
}
|
||||
|
||||
m_vkSets.resize(maxFrames);
|
||||
|
||||
VkDescriptorSetAllocateInfo allocInfo {};
|
||||
allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
|
||||
allocInfo.descriptorPool = device.GetUboDescriptorPool();
|
||||
allocInfo.descriptorSetCount = maxFrames;
|
||||
allocInfo.pSetLayouts = m_vkLayouts.data();
|
||||
|
||||
VK_CHECK(
|
||||
vkAllocateDescriptorSets(device.GetVkDevice(), &allocInfo, m_vkSets.data()),
|
||||
"Failed to allocate descriptor sets"
|
||||
);
|
||||
|
||||
m_ubosMappedMemory.resize(maxFrames);
|
||||
for (uint32_t i = 0; i < maxFrames; i++)
|
||||
{
|
||||
auto uniformBuffer = uboBuffers[i];
|
||||
uniformBuffer->Map(&m_ubosMappedMemory[i]);
|
||||
|
||||
VkDescriptorBufferInfo bufferInfo {};
|
||||
bufferInfo.buffer = uniformBuffer->GetVkBuffer();
|
||||
bufferInfo.offset = 0;
|
||||
bufferInfo.range = VK_WHOLE_SIZE;
|
||||
|
||||
VkWriteDescriptorSet descriptorWrite {};
|
||||
descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
descriptorWrite.dstSet = m_vkSets[i];
|
||||
descriptorWrite.dstBinding = 0;
|
||||
descriptorWrite.dstArrayElement = 0;
|
||||
descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
||||
descriptorWrite.descriptorCount = 1;
|
||||
descriptorWrite.pBufferInfo = &bufferInfo;
|
||||
|
||||
vkUpdateDescriptorSets(m_device.GetVkDevice(), 1, &descriptorWrite, 0, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
DescriptorSet::~DescriptorSet()
|
||||
{
|
||||
m_uniformBuffers.clear();
|
||||
|
||||
for (auto& layout : m_vkLayouts)
|
||||
{
|
||||
vkDestroyDescriptorSetLayout(m_device.GetVkDevice(), layout, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void DescriptorSet::UpdateUBO(uint32_t frameIndex, const std::shared_ptr<UniformBufferObject> ubo)
|
||||
{
|
||||
std::memcpy(m_ubosMappedMemory[frameIndex], ubo.get(), sizeof(UniformBufferObject));
|
||||
}
|
||||
|
||||
void DescriptorSet::Bind(
|
||||
VkCommandBuffer commandBuffer,
|
||||
VkPipelineBindPoint bindPoint,
|
||||
VkPipelineLayout pipelineLayout,
|
||||
uint32_t frameIndex
|
||||
)
|
||||
{
|
||||
vkCmdBindDescriptorSets(
|
||||
commandBuffer,
|
||||
bindPoint,
|
||||
pipelineLayout,
|
||||
0,
|
||||
1,
|
||||
&m_vkSets[frameIndex],
|
||||
0,
|
||||
nullptr
|
||||
);
|
||||
}
|
||||
} // namespace legs
|
891
src/renderer/device.cpp
Normal file
891
src/renderer/device.cpp
Normal file
|
@ -0,0 +1,891 @@
|
|||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
#include <vulkan/vulkan_core.h>
|
||||
|
||||
#include <legs/log.hpp>
|
||||
#include <legs/renderer/common.hpp>
|
||||
#include <legs/renderer/device.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
constexpr static void _vkCmdBeginRenderingKHR(
|
||||
VkInstance instance,
|
||||
VkCommandBuffer commandBuffer,
|
||||
const VkRenderingInfo* renderingInfo
|
||||
)
|
||||
{
|
||||
auto func =
|
||||
(PFN_vkCmdBeginRenderingKHR)vkGetInstanceProcAddr(instance, "vkCmdBeginRenderingKHR");
|
||||
if (func != nullptr)
|
||||
{
|
||||
func(commandBuffer, renderingInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("Failed to find vkCmdBeginRenderingKHR address");
|
||||
}
|
||||
}
|
||||
|
||||
constexpr static void _vkCmdEndRenderingKHR(VkInstance instance, VkCommandBuffer commandBuffer)
|
||||
{
|
||||
auto func = (PFN_vkCmdEndRenderingKHR)vkGetInstanceProcAddr(instance, "vkCmdEndRenderingKHR");
|
||||
if (func != nullptr)
|
||||
{
|
||||
func(commandBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::runtime_error("Failed to find vkCmdEndRenderingKHR address");
|
||||
}
|
||||
}
|
||||
|
||||
Device::Device(const Instance& instance, uint32_t maxFramesInFlight) :
|
||||
m_instance(instance),
|
||||
m_maxFramesInFlight(maxFramesInFlight)
|
||||
{
|
||||
LOG_INFO("Creating Device");
|
||||
|
||||
PickPhysicalDevice();
|
||||
CreateLogicalDevice();
|
||||
|
||||
CreateAllocator(m_instance.GetVkInstance(), m_vkPhysicalDevice, m_vkDevice);
|
||||
|
||||
CreateSwapchain();
|
||||
CreateImageViews();
|
||||
CreateCommandPool();
|
||||
CreateCommandBuffers();
|
||||
CreateSyncObjects();
|
||||
CreateDescriptorPools();
|
||||
}
|
||||
|
||||
Device::~Device()
|
||||
{
|
||||
LOG_INFO("Destroying Device");
|
||||
|
||||
vkDeviceWaitIdle(m_vkDevice);
|
||||
|
||||
vkDestroyDescriptorPool(m_vkDevice, m_vkUboDescriptorPool, nullptr);
|
||||
vkDestroyDescriptorPool(m_vkDevice, m_vkImGuiDescriptorPool, nullptr);
|
||||
|
||||
for (auto& fence : m_vkInFlightFences)
|
||||
{
|
||||
vkDestroyFence(m_vkDevice, fence, nullptr);
|
||||
}
|
||||
for (auto& semaphore : m_vkImageSemaphores)
|
||||
{
|
||||
vkDestroySemaphore(m_vkDevice, semaphore, nullptr);
|
||||
}
|
||||
for (auto& semaphore : m_vkRenderSemaphores)
|
||||
{
|
||||
vkDestroySemaphore(m_vkDevice, semaphore, nullptr);
|
||||
}
|
||||
|
||||
vkDestroyCommandPool(m_vkDevice, m_vkCommandPool, nullptr);
|
||||
|
||||
DestroySwapchain();
|
||||
|
||||
DestroyAllocator();
|
||||
|
||||
vkDestroyDevice(m_vkDevice, nullptr);
|
||||
}
|
||||
|
||||
void Device::Begin()
|
||||
{
|
||||
VK_CHECK(
|
||||
vkWaitForFences(m_vkDevice, 1, &m_vkInFlightFences[m_currentFrame], VK_TRUE, UINT64_MAX),
|
||||
"Failed waiting for in flight fence"
|
||||
);
|
||||
|
||||
auto imageResult = vkAcquireNextImageKHR(
|
||||
m_vkDevice,
|
||||
m_vkSwapchain,
|
||||
UINT64_MAX,
|
||||
m_vkImageSemaphores[m_currentFrame],
|
||||
VK_NULL_HANDLE,
|
||||
&m_currentImageIndex
|
||||
);
|
||||
if (imageResult == VK_ERROR_OUT_OF_DATE_KHR)
|
||||
{
|
||||
RecreateSwapchain();
|
||||
return;
|
||||
}
|
||||
else if (imageResult != VK_SUCCESS && imageResult != VK_SUBOPTIMAL_KHR)
|
||||
{
|
||||
|
||||
throw std::runtime_error("Failed to acquire next swapchain image");
|
||||
}
|
||||
|
||||
VK_CHECK(
|
||||
vkResetFences(m_vkDevice, 1, &m_vkInFlightFences[m_currentFrame]),
|
||||
"Failed to reset in flight fence"
|
||||
);
|
||||
|
||||
VK_CHECK(
|
||||
vkResetCommandBuffer(m_vkCommandBuffers[m_currentFrame], 0),
|
||||
"Failed to reset command buffer"
|
||||
);
|
||||
|
||||
VkCommandBufferBeginInfo beginInfo {};
|
||||
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||
|
||||
VK_CHECK(
|
||||
vkBeginCommandBuffer(m_vkCommandBuffers[m_currentFrame], &beginInfo),
|
||||
"Failed to begin command buffer"
|
||||
);
|
||||
|
||||
TransitionImageLayout(
|
||||
m_vkCommandBuffers[m_currentFrame],
|
||||
m_vkSwapchainImages[m_currentImageIndex],
|
||||
VK_IMAGE_LAYOUT_UNDEFINED,
|
||||
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
|
||||
);
|
||||
|
||||
VkClearValue clearColor {};
|
||||
clearColor.color = {
|
||||
{0.0f, 0.0f, 0.0f, 1.0f}
|
||||
};
|
||||
VkClearValue clearDepth {};
|
||||
clearDepth.depthStencil = {1.0f, 0};
|
||||
|
||||
VkRenderingAttachmentInfoKHR colorAttachment {};
|
||||
colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
|
||||
colorAttachment.clearValue = clearColor;
|
||||
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
colorAttachment.imageView = m_vkSwapchainImageViews[m_currentImageIndex];
|
||||
colorAttachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
colorAttachment.resolveMode = VK_RESOLVE_MODE_NONE;
|
||||
|
||||
VkRenderingAttachmentInfoKHR depthAttachment {};
|
||||
depthAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
|
||||
depthAttachment.clearValue = clearDepth;
|
||||
depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
depthAttachment.imageView = m_vkDepthImageView;
|
||||
depthAttachment.imageLayout = VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL;
|
||||
depthAttachment.resolveMode = VK_RESOLVE_MODE_NONE;
|
||||
|
||||
VkRect2D renderArea {};
|
||||
renderArea.extent = m_vkSwapchainExtent;
|
||||
renderArea.offset = {0, 0};
|
||||
|
||||
VkRenderingInfo renderingInfo {};
|
||||
renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
|
||||
renderingInfo.renderArea = renderArea;
|
||||
renderingInfo.viewMask = 0;
|
||||
renderingInfo.layerCount = 1;
|
||||
renderingInfo.colorAttachmentCount = 1;
|
||||
renderingInfo.pColorAttachments = &colorAttachment;
|
||||
renderingInfo.pDepthAttachment = &depthAttachment;
|
||||
renderingInfo.pStencilAttachment = nullptr;
|
||||
renderingInfo.flags = 0;
|
||||
|
||||
_vkCmdBeginRenderingKHR(
|
||||
m_instance.GetVkInstance(),
|
||||
m_vkCommandBuffers[m_currentFrame],
|
||||
&renderingInfo
|
||||
);
|
||||
|
||||
ResetViewport();
|
||||
}
|
||||
|
||||
void Device::ResetViewport()
|
||||
{
|
||||
VkViewport viewport {};
|
||||
viewport.x = 0.0f;
|
||||
viewport.y = 0.0f;
|
||||
viewport.width = static_cast<float>(m_vkSwapchainExtent.width);
|
||||
viewport.height = static_cast<float>(m_vkSwapchainExtent.height);
|
||||
viewport.minDepth = 0.0f;
|
||||
viewport.maxDepth = 1.0f;
|
||||
vkCmdSetViewport(m_vkCommandBuffers[m_currentFrame], 0, 1, &viewport);
|
||||
|
||||
VkRect2D scissor {};
|
||||
scissor.offset = {0, 0};
|
||||
scissor.extent = m_vkSwapchainExtent;
|
||||
vkCmdSetScissor(m_vkCommandBuffers[m_currentFrame], 0, 1, &scissor);
|
||||
}
|
||||
|
||||
void Device::SetViewport(SRect rect)
|
||||
{
|
||||
VkViewport viewport {};
|
||||
viewport.x = static_cast<float>(rect.offset.x);
|
||||
viewport.y = static_cast<float>(rect.offset.y);
|
||||
viewport.width = static_cast<float>(rect.size.x);
|
||||
viewport.height = static_cast<float>(rect.size.y);
|
||||
viewport.minDepth = 0.0f;
|
||||
viewport.maxDepth = 1.0f;
|
||||
vkCmdSetViewport(m_vkCommandBuffers[m_currentFrame], 0, 1, &viewport);
|
||||
|
||||
VkRect2D scissor {};
|
||||
scissor.offset = {rect.offset.x, rect.offset.y};
|
||||
scissor.extent = {static_cast<uint32_t>(rect.size.x), static_cast<uint32_t>(rect.size.y)};
|
||||
vkCmdSetScissor(m_vkCommandBuffers[m_currentFrame], 0, 1, &scissor);
|
||||
}
|
||||
|
||||
void Device::Submit()
|
||||
{
|
||||
_vkCmdEndRenderingKHR(m_instance.GetVkInstance(), m_vkCommandBuffers[m_currentFrame]);
|
||||
|
||||
TransitionImageLayout(
|
||||
m_vkCommandBuffers[m_currentFrame],
|
||||
m_vkSwapchainImages[m_currentImageIndex],
|
||||
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
|
||||
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
|
||||
);
|
||||
|
||||
VK_CHECK(
|
||||
vkEndCommandBuffer(m_vkCommandBuffers[m_currentFrame]),
|
||||
"Failed to end command buffer"
|
||||
);
|
||||
|
||||
VkSemaphore waitSemaphores[] = {m_vkImageSemaphores[m_currentFrame]};
|
||||
VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
|
||||
VkSemaphore signalSemaphores[] = {m_vkRenderSemaphores[m_currentFrame]};
|
||||
|
||||
VkSubmitInfo submitInfo {};
|
||||
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||
submitInfo.commandBufferCount = 1;
|
||||
submitInfo.pCommandBuffers = &m_vkCommandBuffers[m_currentFrame];
|
||||
submitInfo.waitSemaphoreCount = 1;
|
||||
submitInfo.pWaitSemaphores = waitSemaphores;
|
||||
submitInfo.pWaitDstStageMask = waitStages;
|
||||
submitInfo.signalSemaphoreCount = 1;
|
||||
submitInfo.pSignalSemaphores = signalSemaphores;
|
||||
|
||||
VK_CHECK(
|
||||
vkQueueSubmit(m_vkGraphicsQueue, 1, &submitInfo, m_vkInFlightFences[m_currentFrame]),
|
||||
"Failed to submit queue"
|
||||
);
|
||||
}
|
||||
|
||||
void Device::Present()
|
||||
{
|
||||
VkSemaphore signalSemaphores[] = {m_vkRenderSemaphores[m_currentFrame]};
|
||||
VkSwapchainKHR swapchains[] = {m_vkSwapchain};
|
||||
|
||||
VkPresentInfoKHR presentInfo {};
|
||||
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
|
||||
presentInfo.waitSemaphoreCount = 1;
|
||||
presentInfo.pWaitSemaphores = signalSemaphores;
|
||||
presentInfo.swapchainCount = 1;
|
||||
presentInfo.pSwapchains = swapchains;
|
||||
presentInfo.pImageIndices = &m_currentImageIndex;
|
||||
|
||||
auto presentResult = vkQueuePresentKHR(m_vkPresentQueue, &presentInfo);
|
||||
if (presentResult == VK_ERROR_OUT_OF_DATE_KHR || presentResult == VK_SUBOPTIMAL_KHR
|
||||
|| m_frameBufferResized)
|
||||
{
|
||||
m_frameBufferResized = false;
|
||||
RecreateSwapchain();
|
||||
}
|
||||
else if (presentResult != VK_SUCCESS)
|
||||
{
|
||||
throw std::runtime_error("Failed to present queue");
|
||||
}
|
||||
|
||||
m_currentFrame = (m_currentFrame + 1) % m_maxFramesInFlight;
|
||||
}
|
||||
|
||||
void Device::WaitForGraphicsIdle()
|
||||
{
|
||||
vkQueueWaitIdle(m_vkGraphicsQueue);
|
||||
}
|
||||
|
||||
VkCommandBuffer Device::GetTemporaryCommandBuffer()
|
||||
{
|
||||
VkCommandBufferAllocateInfo allocInfo {};
|
||||
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||
// TODO: use separate pool
|
||||
allocInfo.commandPool = m_vkCommandPool;
|
||||
allocInfo.commandBufferCount = 1;
|
||||
|
||||
VkCommandBuffer commandBuffer {};
|
||||
VK_CHECK(
|
||||
vkAllocateCommandBuffers(m_vkDevice, &allocInfo, &commandBuffer),
|
||||
"Failed to allocate temporary command buffer"
|
||||
);
|
||||
|
||||
VkCommandBufferBeginInfo beginInfo {};
|
||||
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
||||
|
||||
VK_CHECK(
|
||||
vkBeginCommandBuffer(commandBuffer, &beginInfo),
|
||||
"Failed to begin temporary command buffer"
|
||||
);
|
||||
|
||||
return commandBuffer;
|
||||
}
|
||||
|
||||
void Device::SubmitTemporaryCommandBuffer(VkCommandBuffer commandBuffer)
|
||||
{
|
||||
VK_CHECK(vkEndCommandBuffer(commandBuffer), "Failed to end temporary command buffer");
|
||||
|
||||
VkSubmitInfo submitInfo {};
|
||||
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||
submitInfo.commandBufferCount = 1;
|
||||
submitInfo.pCommandBuffers = &commandBuffer;
|
||||
|
||||
VK_CHECK(
|
||||
vkQueueSubmit(m_vkGraphicsQueue, 1, &submitInfo, VK_NULL_HANDLE),
|
||||
"Failed to submit temporary command buffer"
|
||||
);
|
||||
// TODO: wait all copies with fences?
|
||||
WaitForGraphicsIdle();
|
||||
|
||||
vkFreeCommandBuffers(m_vkDevice, m_vkCommandPool, 1, &commandBuffer);
|
||||
}
|
||||
|
||||
void Device::RecreateSwapchain()
|
||||
{
|
||||
vkDeviceWaitIdle(m_vkDevice);
|
||||
|
||||
DestroySwapchain();
|
||||
|
||||
CreateSwapchain();
|
||||
CreateImageViews();
|
||||
}
|
||||
|
||||
void Device::DestroySwapchain()
|
||||
{
|
||||
for (auto view : m_vkSwapchainImageViews)
|
||||
{
|
||||
vkDestroyImageView(m_vkDevice, view, nullptr);
|
||||
}
|
||||
|
||||
vkDestroySwapchainKHR(m_vkDevice, m_vkSwapchain, nullptr);
|
||||
|
||||
vkDestroyImageView(m_vkDevice, m_vkDepthImageView, nullptr);
|
||||
vmaDestroyImage(g_vma, m_vkDepthImage, m_vmaDepthAllocation);
|
||||
}
|
||||
|
||||
void Device::PickPhysicalDevice()
|
||||
{
|
||||
LOG_DEBUG("Picking physical device");
|
||||
|
||||
m_vkPhysicalDevice = VK_NULL_HANDLE;
|
||||
uint32_t deviceCount = 0;
|
||||
vkEnumeratePhysicalDevices(m_instance.GetVkInstance(), &deviceCount, nullptr);
|
||||
|
||||
if (deviceCount == 0)
|
||||
{
|
||||
throw std::runtime_error("No GPUs found with support");
|
||||
}
|
||||
|
||||
std::vector<VkPhysicalDevice> devices(deviceCount);
|
||||
vkEnumeratePhysicalDevices(m_instance.GetVkInstance(), &deviceCount, devices.data());
|
||||
|
||||
for (const auto& device : devices)
|
||||
{
|
||||
if (IsDeviceSuitable(device))
|
||||
{
|
||||
m_vkPhysicalDevice = device;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_vkPhysicalDevice == VK_NULL_HANDLE)
|
||||
{
|
||||
throw std::runtime_error("Failed to find a suitable GPU for vulkan");
|
||||
}
|
||||
}
|
||||
|
||||
bool Device::IsDeviceSuitable(const VkPhysicalDevice device)
|
||||
{
|
||||
VkPhysicalDeviceProperties deviceProperties {};
|
||||
vkGetPhysicalDeviceProperties(device, &deviceProperties);
|
||||
|
||||
VkPhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeature {};
|
||||
dynamicRenderingFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES;
|
||||
dynamicRenderingFeature.pNext = nullptr;
|
||||
|
||||
VkPhysicalDeviceFeatures2 deviceFeatures {};
|
||||
deviceFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
|
||||
deviceFeatures.pNext = &dynamicRenderingFeature;
|
||||
|
||||
vkGetPhysicalDeviceFeatures2(device, &deviceFeatures);
|
||||
|
||||
auto dynamicRenderingSupported = dynamicRenderingFeature.dynamicRendering == VK_TRUE;
|
||||
|
||||
auto featuresSupported = dynamicRenderingSupported;
|
||||
|
||||
auto familyIndices = FindQueueFamilies(device);
|
||||
auto extensionsSupported = CheckDeviceExtensionSupport(device);
|
||||
|
||||
auto swapchainAdequate = false;
|
||||
if (extensionsSupported)
|
||||
{
|
||||
auto swapchainSupport = QuerySwapchainSupport(device);
|
||||
swapchainAdequate =
|
||||
!swapchainSupport.formats.empty() && !swapchainSupport.presentModes.empty();
|
||||
}
|
||||
|
||||
return deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU
|
||||
&& familyIndices.IsComplete() && featuresSupported && extensionsSupported
|
||||
&& swapchainAdequate;
|
||||
}
|
||||
|
||||
QueueFamilyIndices Device::FindQueueFamilies(const VkPhysicalDevice device)
|
||||
{
|
||||
QueueFamilyIndices familyIndices;
|
||||
|
||||
uint32_t queueFamilyCount = 0;
|
||||
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);
|
||||
|
||||
std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
|
||||
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data());
|
||||
|
||||
uint32_t i = 0;
|
||||
for (const auto& family : queueFamilies)
|
||||
{
|
||||
if (family.queueFlags & VK_QUEUE_GRAPHICS_BIT)
|
||||
{
|
||||
familyIndices.graphicsFamily = i;
|
||||
}
|
||||
|
||||
VkBool32 presentSupport = false;
|
||||
vkGetPhysicalDeviceSurfaceSupportKHR(device, i, m_instance.GetSurface(), &presentSupport);
|
||||
|
||||
if (presentSupport)
|
||||
{
|
||||
familyIndices.presentFamily = i;
|
||||
}
|
||||
|
||||
if (familyIndices.IsComplete())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return familyIndices;
|
||||
}
|
||||
|
||||
bool Device::CheckDeviceExtensionSupport(const VkPhysicalDevice device)
|
||||
{
|
||||
uint32_t extensionCount;
|
||||
vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr);
|
||||
std::vector<VkExtensionProperties> availableExtensions(extensionCount);
|
||||
vkEnumerateDeviceExtensionProperties(
|
||||
device,
|
||||
nullptr,
|
||||
&extensionCount,
|
||||
availableExtensions.data()
|
||||
);
|
||||
|
||||
std::set<std::string> uniqueRequired(m_requiredExtensions.begin(), m_requiredExtensions.end());
|
||||
for (const auto& extension : availableExtensions)
|
||||
{
|
||||
uniqueRequired.erase(extension.extensionName);
|
||||
}
|
||||
|
||||
return uniqueRequired.empty();
|
||||
}
|
||||
|
||||
SwapchainSupportDetails Device::QuerySwapchainSupport(const VkPhysicalDevice device)
|
||||
{
|
||||
SwapchainSupportDetails details {};
|
||||
|
||||
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(
|
||||
device,
|
||||
m_instance.GetSurface(),
|
||||
&details.capabilities
|
||||
);
|
||||
|
||||
uint32_t formatCount;
|
||||
vkGetPhysicalDeviceSurfaceFormatsKHR(device, m_instance.GetSurface(), &formatCount, nullptr);
|
||||
if (formatCount != 0)
|
||||
{
|
||||
details.formats.resize(formatCount);
|
||||
vkGetPhysicalDeviceSurfaceFormatsKHR(
|
||||
device,
|
||||
m_instance.GetSurface(),
|
||||
&formatCount,
|
||||
details.formats.data()
|
||||
);
|
||||
}
|
||||
|
||||
uint32_t presentModeCount;
|
||||
vkGetPhysicalDeviceSurfacePresentModesKHR(
|
||||
device,
|
||||
m_instance.GetSurface(),
|
||||
&presentModeCount,
|
||||
nullptr
|
||||
);
|
||||
if (presentModeCount != 0)
|
||||
{
|
||||
details.presentModes.resize(presentModeCount);
|
||||
vkGetPhysicalDeviceSurfacePresentModesKHR(
|
||||
device,
|
||||
m_instance.GetSurface(),
|
||||
&presentModeCount,
|
||||
details.presentModes.data()
|
||||
);
|
||||
}
|
||||
|
||||
return details;
|
||||
}
|
||||
|
||||
constexpr VkSurfaceFormatKHR Device::ChooseSwapSurfaceFormat(
|
||||
const std::vector<VkSurfaceFormatKHR>& available
|
||||
)
|
||||
{
|
||||
for (const auto& fmt : available)
|
||||
{
|
||||
if (fmt.format == VK_FORMAT_B8G8R8A8_SRGB
|
||||
&& fmt.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
|
||||
{
|
||||
return fmt;
|
||||
}
|
||||
}
|
||||
|
||||
return available[0];
|
||||
}
|
||||
|
||||
constexpr VkPresentModeKHR Device::ChooseSwapPresentMode(
|
||||
const std::vector<VkPresentModeKHR>& available
|
||||
)
|
||||
{
|
||||
for (const auto& mode : available)
|
||||
{
|
||||
if (mode == VK_PRESENT_MODE_MAILBOX_KHR)
|
||||
{
|
||||
return mode;
|
||||
}
|
||||
}
|
||||
|
||||
return VK_PRESENT_MODE_FIFO_KHR;
|
||||
}
|
||||
|
||||
constexpr VkExtent2D Device::ChooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities)
|
||||
{
|
||||
if (capabilities.currentExtent.width != std::numeric_limits<uint32_t>::max())
|
||||
{
|
||||
return capabilities.currentExtent;
|
||||
}
|
||||
|
||||
int width;
|
||||
int height;
|
||||
m_instance.GetFramebufferSize(&width, &height);
|
||||
|
||||
VkExtent2D extent = {
|
||||
static_cast<uint32_t>(width),
|
||||
static_cast<uint32_t>(height),
|
||||
};
|
||||
extent.width = std::clamp(
|
||||
extent.width,
|
||||
capabilities.minImageExtent.width,
|
||||
capabilities.maxImageExtent.width
|
||||
);
|
||||
extent.height = std::clamp(
|
||||
extent.height,
|
||||
capabilities.minImageExtent.height,
|
||||
capabilities.maxImageExtent.height
|
||||
);
|
||||
|
||||
return extent;
|
||||
}
|
||||
|
||||
void Device::CreateSwapchain()
|
||||
{
|
||||
auto support = QuerySwapchainSupport(m_vkPhysicalDevice);
|
||||
auto surfaceFormat = ChooseSwapSurfaceFormat(support.formats);
|
||||
auto presentMode = ChooseSwapPresentMode(support.presentModes);
|
||||
auto extent = ChooseSwapExtent(support.capabilities);
|
||||
auto imageCount = support.capabilities.minImageCount + 1;
|
||||
if (support.capabilities.maxImageCount > 0 && imageCount > support.capabilities.maxImageCount)
|
||||
{
|
||||
imageCount = support.capabilities.maxImageCount;
|
||||
}
|
||||
|
||||
VkSwapchainCreateInfoKHR createInfo {};
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
|
||||
createInfo.surface = m_instance.GetSurface();
|
||||
createInfo.minImageCount = imageCount;
|
||||
createInfo.imageFormat = surfaceFormat.format;
|
||||
createInfo.imageColorSpace = surfaceFormat.colorSpace;
|
||||
createInfo.imageExtent = extent;
|
||||
createInfo.imageArrayLayers = 1;
|
||||
createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
||||
createInfo.preTransform = support.capabilities.currentTransform;
|
||||
createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
|
||||
createInfo.presentMode = presentMode;
|
||||
createInfo.clipped = VK_TRUE;
|
||||
createInfo.oldSwapchain = VK_NULL_HANDLE;
|
||||
|
||||
auto families = FindQueueFamilies(m_vkPhysicalDevice);
|
||||
uint32_t familyIndices[] = {families.graphicsFamily.value(), families.presentFamily.value()};
|
||||
|
||||
if (families.graphicsFamily != families.presentFamily)
|
||||
{
|
||||
createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
|
||||
createInfo.queueFamilyIndexCount = 2;
|
||||
createInfo.pQueueFamilyIndices = familyIndices;
|
||||
}
|
||||
else
|
||||
{
|
||||
createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
}
|
||||
|
||||
VK_CHECK(
|
||||
vkCreateSwapchainKHR(m_vkDevice, &createInfo, nullptr, &m_vkSwapchain),
|
||||
"Failed to create swapchain"
|
||||
);
|
||||
|
||||
vkGetSwapchainImagesKHR(m_vkDevice, m_vkSwapchain, &imageCount, nullptr);
|
||||
m_vkSwapchainImages.resize(imageCount);
|
||||
vkGetSwapchainImagesKHR(m_vkDevice, m_vkSwapchain, &imageCount, m_vkSwapchainImages.data());
|
||||
|
||||
m_vkSwapchainImageFormat = surfaceFormat.format;
|
||||
m_vkSwapchainExtent = extent;
|
||||
|
||||
CreateImage(
|
||||
&m_vkDepthImage,
|
||||
&m_vmaDepthAllocation,
|
||||
VK_IMAGE_TYPE_2D,
|
||||
GetDepthFormat(),
|
||||
VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
|
||||
extent.width,
|
||||
extent.height
|
||||
);
|
||||
}
|
||||
|
||||
void Device::CreateImage(
|
||||
VkImage* image,
|
||||
VmaAllocation* imageAllocation,
|
||||
VkImageType imageType,
|
||||
VkFormat format,
|
||||
VkImageUsageFlags usage,
|
||||
uint32_t width,
|
||||
uint32_t height
|
||||
)
|
||||
{
|
||||
VkImageCreateInfo createInfo {};
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
createInfo.imageType = imageType;
|
||||
createInfo.format = format;
|
||||
createInfo.extent.width = width;
|
||||
createInfo.extent.height = height;
|
||||
createInfo.extent.depth = 1;
|
||||
createInfo.mipLevels = 1;
|
||||
createInfo.arrayLayers = 1;
|
||||
createInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
createInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
createInfo.usage = usage;
|
||||
createInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
createInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
createInfo.flags = 0;
|
||||
|
||||
VmaAllocationCreateInfo allocInfo {};
|
||||
allocInfo.usage = VMA_MEMORY_USAGE_AUTO;
|
||||
|
||||
vmaCreateImage(g_vma, &createInfo, &allocInfo, image, imageAllocation, nullptr);
|
||||
}
|
||||
|
||||
void Device::CreateImageView(
|
||||
VkImage image,
|
||||
VkImageView* imageView,
|
||||
VkImageViewType viewType,
|
||||
VkFormat format,
|
||||
VkImageAspectFlags aspect
|
||||
)
|
||||
{
|
||||
VkImageViewCreateInfo createInfo {};
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
createInfo.image = image;
|
||||
createInfo.viewType = viewType;
|
||||
createInfo.format = format;
|
||||
createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||||
createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||||
createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||||
createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||||
createInfo.subresourceRange.aspectMask = aspect;
|
||||
createInfo.subresourceRange.baseMipLevel = 0;
|
||||
createInfo.subresourceRange.levelCount = 1;
|
||||
createInfo.subresourceRange.baseArrayLayer = 0;
|
||||
createInfo.subresourceRange.layerCount = 1;
|
||||
|
||||
VK_CHECK(
|
||||
vkCreateImageView(m_vkDevice, &createInfo, nullptr, imageView),
|
||||
"Failed to create image view"
|
||||
);
|
||||
}
|
||||
|
||||
void Device::CreateImageViews()
|
||||
{
|
||||
m_vkSwapchainImageViews.resize(m_vkSwapchainImages.size());
|
||||
for (size_t i = 0; i < m_vkSwapchainImages.size(); i++)
|
||||
{
|
||||
CreateImageView(
|
||||
m_vkSwapchainImages[i],
|
||||
&m_vkSwapchainImageViews[i],
|
||||
VK_IMAGE_VIEW_TYPE_2D,
|
||||
m_vkSwapchainImageFormat,
|
||||
VK_IMAGE_ASPECT_COLOR_BIT
|
||||
);
|
||||
}
|
||||
|
||||
CreateImageView(
|
||||
m_vkDepthImage,
|
||||
&m_vkDepthImageView,
|
||||
VK_IMAGE_VIEW_TYPE_2D,
|
||||
GetDepthFormat(),
|
||||
VK_IMAGE_ASPECT_DEPTH_BIT
|
||||
);
|
||||
}
|
||||
|
||||
void Device::CreateLogicalDevice()
|
||||
{
|
||||
LOG_DEBUG("Creating logical device");
|
||||
|
||||
QueueFamilyIndices familyIndices = FindQueueFamilies(m_vkPhysicalDevice);
|
||||
|
||||
std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
|
||||
std::set<uint32_t> uniqueQueueFamilies = {
|
||||
familyIndices.graphicsFamily.value(),
|
||||
familyIndices.presentFamily.value()
|
||||
};
|
||||
|
||||
auto queuePriority = 1.0f;
|
||||
for (uint32_t familyIndex : uniqueQueueFamilies)
|
||||
{
|
||||
VkDeviceQueueCreateInfo queueCreateInfo {};
|
||||
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
|
||||
queueCreateInfo.queueFamilyIndex = familyIndex;
|
||||
queueCreateInfo.queueCount = 1;
|
||||
queueCreateInfo.pQueuePriorities = &queuePriority;
|
||||
queueCreateInfos.push_back(queueCreateInfo);
|
||||
}
|
||||
|
||||
VkPhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeature {};
|
||||
dynamicRenderingFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES;
|
||||
dynamicRenderingFeature.dynamicRendering = VK_TRUE;
|
||||
dynamicRenderingFeature.pNext = nullptr;
|
||||
|
||||
VkPhysicalDeviceFeatures2 deviceFeatures {};
|
||||
deviceFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
|
||||
deviceFeatures.pNext = &dynamicRenderingFeature;
|
||||
|
||||
VkDeviceCreateInfo deviceCreateInfo {};
|
||||
deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
|
||||
deviceCreateInfo.pQueueCreateInfos = queueCreateInfos.data();
|
||||
deviceCreateInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());
|
||||
deviceCreateInfo.pEnabledFeatures = nullptr; // handled in pNext
|
||||
deviceCreateInfo.pNext = &deviceFeatures;
|
||||
deviceCreateInfo.enabledExtensionCount = static_cast<uint32_t>(m_requiredExtensions.size());
|
||||
deviceCreateInfo.ppEnabledExtensionNames = m_requiredExtensions.data();
|
||||
|
||||
VK_CHECK(
|
||||
vkCreateDevice(m_vkPhysicalDevice, &deviceCreateInfo, nullptr, &m_vkDevice),
|
||||
"Failed to create logical device"
|
||||
);
|
||||
|
||||
m_vkGraphicsQueueIndex = familyIndices.graphicsFamily.value();
|
||||
m_vkPresentQueueIndex = familyIndices.presentFamily.value();
|
||||
vkGetDeviceQueue(m_vkDevice, m_vkGraphicsQueueIndex, 0, &m_vkGraphicsQueue);
|
||||
vkGetDeviceQueue(m_vkDevice, m_vkPresentQueueIndex, 0, &m_vkPresentQueue);
|
||||
}
|
||||
|
||||
void Device::CreateCommandPool()
|
||||
{
|
||||
auto familyIndices = FindQueueFamilies(m_vkPhysicalDevice);
|
||||
|
||||
VkCommandPoolCreateInfo poolInfo {};
|
||||
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
||||
poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
||||
poolInfo.queueFamilyIndex = familyIndices.graphicsFamily.value();
|
||||
|
||||
VK_CHECK(
|
||||
vkCreateCommandPool(m_vkDevice, &poolInfo, nullptr, &m_vkCommandPool),
|
||||
"Failed to create command pool"
|
||||
);
|
||||
}
|
||||
|
||||
void Device::CreateCommandBuffers()
|
||||
{
|
||||
m_vkCommandBuffers.resize(m_maxFramesInFlight);
|
||||
|
||||
VkCommandBufferAllocateInfo allocInfo {};
|
||||
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||
allocInfo.commandPool = m_vkCommandPool;
|
||||
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||
allocInfo.commandBufferCount = m_maxFramesInFlight;
|
||||
|
||||
VK_CHECK(
|
||||
vkAllocateCommandBuffers(m_vkDevice, &allocInfo, m_vkCommandBuffers.data()),
|
||||
"Failed to allocate command buffers"
|
||||
);
|
||||
}
|
||||
|
||||
void Device::CreateSyncObjects()
|
||||
{
|
||||
m_vkImageSemaphores.resize(m_maxFramesInFlight);
|
||||
m_vkRenderSemaphores.resize(m_maxFramesInFlight);
|
||||
m_vkInFlightFences.resize(m_maxFramesInFlight);
|
||||
|
||||
VkSemaphoreCreateInfo semaphoreInfo {};
|
||||
semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
|
||||
|
||||
VkFenceCreateInfo fenceInfo {};
|
||||
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
|
||||
fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
|
||||
|
||||
for (uint32_t i = 0; i < m_maxFramesInFlight; i++)
|
||||
{
|
||||
VK_CHECK(
|
||||
vkCreateSemaphore(m_vkDevice, &semaphoreInfo, nullptr, &m_vkImageSemaphores[i]),
|
||||
"Failed to create image semaphore"
|
||||
);
|
||||
VK_CHECK(
|
||||
vkCreateSemaphore(m_vkDevice, &semaphoreInfo, nullptr, &m_vkRenderSemaphores[i]),
|
||||
"Failed to create render semaphore"
|
||||
);
|
||||
VK_CHECK(
|
||||
vkCreateFence(m_vkDevice, &fenceInfo, nullptr, &m_vkInFlightFences[i]),
|
||||
"Failed to create in flight fence"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void Device::CreateDescriptorPools()
|
||||
{
|
||||
VkDescriptorPoolSize uboPoolSize {};
|
||||
uboPoolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
||||
uboPoolSize.descriptorCount = m_maxFramesInFlight;
|
||||
|
||||
VkDescriptorPoolCreateInfo uboPoolInfo {};
|
||||
uboPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
||||
uboPoolInfo.poolSizeCount = 1;
|
||||
uboPoolInfo.pPoolSizes = &uboPoolSize;
|
||||
uboPoolInfo.maxSets = m_maxFramesInFlight;
|
||||
|
||||
VK_CHECK(
|
||||
vkCreateDescriptorPool(m_vkDevice, &uboPoolInfo, nullptr, &m_vkUboDescriptorPool),
|
||||
"Failed to create ubo descriptor pool"
|
||||
);
|
||||
|
||||
VkDescriptorPoolSize imGuiPoolSize {};
|
||||
imGuiPoolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
imGuiPoolSize.descriptorCount = m_maxFramesInFlight;
|
||||
|
||||
VkDescriptorPoolCreateInfo imGuiPoolInfo {};
|
||||
imGuiPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
||||
imGuiPoolInfo.poolSizeCount = 1;
|
||||
imGuiPoolInfo.pPoolSizes = &imGuiPoolSize;
|
||||
imGuiPoolInfo.maxSets = m_maxFramesInFlight;
|
||||
imGuiPoolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
|
||||
|
||||
VK_CHECK(
|
||||
vkCreateDescriptorPool(m_vkDevice, &imGuiPoolInfo, nullptr, &m_vkImGuiDescriptorPool),
|
||||
"Failed to create ImGui descriptor pool"
|
||||
);
|
||||
}
|
||||
} // namespace legs
|
233
src/renderer/instance.cpp
Normal file
233
src/renderer/instance.cpp
Normal file
|
@ -0,0 +1,233 @@
|
|||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <legs/log.hpp>
|
||||
#include <legs/renderer/common.hpp>
|
||||
#include <legs/renderer/instance.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
constexpr static VKAPI_ATTR VkBool32 VKAPI_CALL _VkDebugMessengerCallback(
|
||||
VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
|
||||
VkDebugUtilsMessageTypeFlagsEXT /*messageType*/,
|
||||
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
|
||||
void* /*pUserData*/
|
||||
)
|
||||
{
|
||||
auto level = LogLevel::Debug;
|
||||
switch (messageSeverity)
|
||||
{
|
||||
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT:
|
||||
level = LogLevel::Debug;
|
||||
break;
|
||||
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT:
|
||||
level = LogLevel::Info;
|
||||
break;
|
||||
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT:
|
||||
level = LogLevel::Warn;
|
||||
break;
|
||||
default:
|
||||
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT:
|
||||
level = LogLevel::Error;
|
||||
break;
|
||||
}
|
||||
|
||||
_LOG(level, "validation layer: {}", pCallbackData->pMessage);
|
||||
|
||||
return VK_FALSE;
|
||||
}
|
||||
|
||||
constexpr static VkResult _CreateDebugUtilsMessengerEXT(
|
||||
VkInstance instance,
|
||||
const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo,
|
||||
const VkAllocationCallbacks* pAllocator,
|
||||
VkDebugUtilsMessengerEXT* pDebugMessenger
|
||||
)
|
||||
{
|
||||
auto func = (PFN_vkCreateDebugUtilsMessengerEXT
|
||||
)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT");
|
||||
if (func != nullptr)
|
||||
{
|
||||
return func(instance, pCreateInfo, pAllocator, pDebugMessenger);
|
||||
}
|
||||
else
|
||||
{
|
||||
return VK_ERROR_EXTENSION_NOT_PRESENT;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr static void _DestroyDebugUtilsMessengerEXT(
|
||||
VkInstance instance,
|
||||
VkDebugUtilsMessengerEXT debugMessenger,
|
||||
const VkAllocationCallbacks* pAllocator
|
||||
)
|
||||
{
|
||||
auto func = (PFN_vkDestroyDebugUtilsMessengerEXT
|
||||
)vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT");
|
||||
if (func != nullptr)
|
||||
{
|
||||
func(instance, debugMessenger, pAllocator);
|
||||
}
|
||||
}
|
||||
|
||||
Instance::Instance(std::shared_ptr<Window> window) : m_window(window)
|
||||
{
|
||||
LOG_DEBUG("Creating Instance");
|
||||
|
||||
VkApplicationInfo appInfo {};
|
||||
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
|
||||
appInfo.pApplicationName = "legs";
|
||||
appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
|
||||
appInfo.pEngineName = "legs";
|
||||
appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
|
||||
appInfo.apiVersion = VK_API_VERSION_1_3;
|
||||
|
||||
if (m_enableValidationLayers && !ValidationLayersSupported())
|
||||
{
|
||||
throw std::runtime_error("Validation layers not available");
|
||||
}
|
||||
|
||||
auto extensions = GetRequiredExtensions();
|
||||
|
||||
VkInstanceCreateInfo createInfo {};
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
|
||||
createInfo.pApplicationInfo = &appInfo;
|
||||
createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
|
||||
createInfo.ppEnabledExtensionNames = extensions.data();
|
||||
|
||||
VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo;
|
||||
if (m_enableValidationLayers)
|
||||
{
|
||||
createInfo.enabledLayerCount = static_cast<uint32_t>(m_validationLayers.size());
|
||||
createInfo.ppEnabledLayerNames = m_validationLayers.data();
|
||||
|
||||
PopulateDebugMessengerCreateInfo(debugCreateInfo);
|
||||
createInfo.pNext = &debugCreateInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
createInfo.enabledLayerCount = 0;
|
||||
createInfo.pNext = nullptr;
|
||||
}
|
||||
|
||||
VK_CHECK(vkCreateInstance(&createInfo, nullptr, &m_vkInstance), "Failed to create instance");
|
||||
|
||||
if (m_enableValidationLayers)
|
||||
{
|
||||
SetupDebugMessenger();
|
||||
}
|
||||
|
||||
CreateSurface();
|
||||
}
|
||||
|
||||
Instance::~Instance()
|
||||
{
|
||||
LOG_DEBUG("Destroying Instance");
|
||||
|
||||
vkDestroySurfaceKHR(m_vkInstance, m_vkSurface, nullptr);
|
||||
|
||||
if (m_enableValidationLayers)
|
||||
{
|
||||
_DestroyDebugUtilsMessengerEXT(m_vkInstance, m_vkDebugMessenger, nullptr);
|
||||
}
|
||||
|
||||
vkDestroyInstance(m_vkInstance, nullptr);
|
||||
}
|
||||
|
||||
void Instance::SetWindow(std::shared_ptr<Window> window)
|
||||
{
|
||||
m_window = window;
|
||||
vkDestroySurfaceKHR(m_vkInstance, m_vkSurface, nullptr);
|
||||
CreateSurface();
|
||||
}
|
||||
|
||||
std::vector<const char*> Instance::GetRequiredExtensions()
|
||||
{
|
||||
unsigned int extensionCount = 0;
|
||||
if (!m_window->GetExtensions(&extensionCount, nullptr))
|
||||
{
|
||||
throw std::runtime_error("Failed to get vulkan extensions from window");
|
||||
}
|
||||
std::vector<const char*> extensions(extensionCount);
|
||||
if (!m_window->GetExtensions(&extensionCount, extensions.data()))
|
||||
{
|
||||
throw std::runtime_error("Failed to get vulkan extensions from window");
|
||||
}
|
||||
|
||||
if (m_enableValidationLayers)
|
||||
{
|
||||
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
||||
}
|
||||
|
||||
return extensions;
|
||||
}
|
||||
|
||||
bool Instance::ValidationLayersSupported()
|
||||
{
|
||||
uint32_t layerCount;
|
||||
vkEnumerateInstanceLayerProperties(&layerCount, nullptr);
|
||||
|
||||
std::vector<VkLayerProperties> availableLayers(layerCount);
|
||||
vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data());
|
||||
|
||||
for (const auto* layerName : m_validationLayers)
|
||||
{
|
||||
auto layerFound = false;
|
||||
|
||||
for (const auto& layerProperties : availableLayers)
|
||||
{
|
||||
if (strcmp(layerName, layerProperties.layerName) == 0)
|
||||
{
|
||||
layerFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!layerFound)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void constexpr Instance::PopulateDebugMessengerCreateInfo(
|
||||
VkDebugUtilsMessengerCreateInfoEXT& createInfo
|
||||
)
|
||||
{
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
|
||||
createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT
|
||||
| VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT
|
||||
| VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT
|
||||
| VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
|
||||
createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT
|
||||
| VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT
|
||||
| VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
|
||||
createInfo.pfnUserCallback = _VkDebugMessengerCallback;
|
||||
createInfo.pUserData = nullptr;
|
||||
createInfo.flags = 0;
|
||||
createInfo.pNext = nullptr;
|
||||
}
|
||||
|
||||
void Instance::SetupDebugMessenger()
|
||||
{
|
||||
VkDebugUtilsMessengerCreateInfoEXT createInfo {};
|
||||
PopulateDebugMessengerCreateInfo(createInfo);
|
||||
|
||||
VK_CHECK(
|
||||
_CreateDebugUtilsMessengerEXT(m_vkInstance, &createInfo, nullptr, &m_vkDebugMessenger),
|
||||
"Failed to set up debug messenger"
|
||||
);
|
||||
}
|
||||
|
||||
void Instance::CreateSurface()
|
||||
{
|
||||
auto result = m_window->CreateSurface(m_vkInstance, &m_vkSurface);
|
||||
if (!result)
|
||||
{
|
||||
throw std::runtime_error("Failed to create vulkan surface");
|
||||
}
|
||||
}
|
||||
} // namespace legs
|
205
src/renderer/renderer.cpp
Normal file
205
src/renderer/renderer.cpp
Normal file
|
@ -0,0 +1,205 @@
|
|||
#include <imgui_impl_vulkan.h>
|
||||
#include <memory>
|
||||
|
||||
#include <legs/log.hpp>
|
||||
#include <legs/renderer/mesh_data.hpp>
|
||||
#include <legs/renderer/renderer.hpp>
|
||||
#include <legs/renderer/shader.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
|
||||
#define MAX_FRAMES_IN_FLIGHT 2
|
||||
|
||||
Renderer::Renderer(std::shared_ptr<Window> window) :
|
||||
m_instance(window),
|
||||
m_device(m_instance, MAX_FRAMES_IN_FLIGHT)
|
||||
{
|
||||
LOG_INFO("Creating Renderer");
|
||||
|
||||
auto uboBuffers = std::vector<std::shared_ptr<Buffer>>();
|
||||
for (unsigned int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++)
|
||||
{
|
||||
uboBuffers.push_back(
|
||||
std::make_shared<Buffer>(UniformBuffer, HostBuffer, sizeof(UniformBufferObject), 1)
|
||||
);
|
||||
}
|
||||
m_descriptorSet = std::make_shared<DescriptorSet>(m_device, uboBuffers);
|
||||
|
||||
// TODO: abstract away all the shader + pipeline setup
|
||||
auto moduleSimpleFrag = CreateShaderModule(LOAD_VULKAN_SPV(unlit_pc_frag));
|
||||
auto moduleSimpleVert = CreateShaderModule(LOAD_VULKAN_SPV(unlit_pc_vert));
|
||||
auto stageSimpleFrag =
|
||||
FillShaderStageCreateInfo(moduleSimpleFrag, VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
auto stageSimpleVert = FillShaderStageCreateInfo(moduleSimpleVert, VK_SHADER_STAGE_VERTEX_BIT);
|
||||
std::vector simpleStages {stageSimpleFrag, stageSimpleVert};
|
||||
|
||||
m_testPipeline =
|
||||
std::make_shared<Pipeline<Vertex_P_C>>(m_device, m_descriptorSet, simpleStages);
|
||||
|
||||
auto moduleGeoPNCFrag = CreateShaderModule(LOAD_VULKAN_SPV(lit_pnc_frag));
|
||||
auto moduleGeoPNCVert = CreateShaderModule(LOAD_VULKAN_SPV(lit_pnc_vert));
|
||||
auto stageGeoPNCFrag =
|
||||
FillShaderStageCreateInfo(moduleGeoPNCFrag, VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
auto stageGeoPNCVert = FillShaderStageCreateInfo(moduleGeoPNCVert, VK_SHADER_STAGE_VERTEX_BIT);
|
||||
std::vector geoPNCStages {stageGeoPNCFrag, stageGeoPNCVert};
|
||||
|
||||
m_geoPNCPipeline =
|
||||
std::make_shared<Pipeline<Vertex_P_N_C>>(m_device, m_descriptorSet, geoPNCStages);
|
||||
|
||||
auto moduleFullscreenFrag = CreateShaderModule(LOAD_VULKAN_SPV(fullscreen_frag));
|
||||
auto moduleFullscreenVert = CreateShaderModule(LOAD_VULKAN_SPV(fullscreen_vert));
|
||||
auto stageFullscreenFrag =
|
||||
FillShaderStageCreateInfo(moduleFullscreenFrag, VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
auto stageFullscreenVert =
|
||||
FillShaderStageCreateInfo(moduleFullscreenVert, VK_SHADER_STAGE_VERTEX_BIT);
|
||||
std::vector fullscreenStages {stageFullscreenFrag, stageFullscreenVert};
|
||||
|
||||
m_fullscreenPipeline = std::make_shared<Pipeline<VertexEmpty>>(
|
||||
m_device,
|
||||
m_descriptorSet,
|
||||
fullscreenStages,
|
||||
false,
|
||||
false
|
||||
);
|
||||
|
||||
auto moduleSkyFrag = CreateShaderModule(LOAD_VULKAN_SPV(sky_frag));
|
||||
auto moduleSkyVert = CreateShaderModule(LOAD_VULKAN_SPV(sky_vert));
|
||||
auto stageSkyFrag = FillShaderStageCreateInfo(moduleSkyFrag, VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||
auto stageSkyVert = FillShaderStageCreateInfo(moduleSkyVert, VK_SHADER_STAGE_VERTEX_BIT);
|
||||
std::vector skyStages {stageSkyFrag, stageSkyVert};
|
||||
|
||||
m_skyPipeline = std::make_shared<Pipeline<Vertex_P>>(m_device, m_descriptorSet, skyStages);
|
||||
|
||||
m_ubo = std::make_shared<UniformBufferObject>();
|
||||
}
|
||||
|
||||
Renderer::~Renderer()
|
||||
{
|
||||
LOG_INFO("Destroying Renderer");
|
||||
|
||||
vkDeviceWaitIdle(m_device.GetVkDevice());
|
||||
|
||||
m_testPipeline.reset();
|
||||
m_geoPNCPipeline.reset();
|
||||
m_fullscreenPipeline.reset();
|
||||
m_skyPipeline.reset();
|
||||
|
||||
m_descriptorSet.reset();
|
||||
|
||||
for (auto& module : m_vkShaderModules)
|
||||
{
|
||||
vkDestroyShaderModule(m_device.GetVkDevice(), module, nullptr);
|
||||
}
|
||||
|
||||
m_frameBuffers.clear();
|
||||
}
|
||||
|
||||
void Renderer::SetWindow(std::shared_ptr<Window> window)
|
||||
{
|
||||
LOG_INFO("Setting window");
|
||||
m_instance.SetWindow(window);
|
||||
Resize();
|
||||
}
|
||||
|
||||
void Renderer::ClearViewport()
|
||||
{
|
||||
auto commandBuffer = m_device.GetCommandBuffer();
|
||||
BindPipeline(RenderPipeline::FULLSCREEN);
|
||||
vkCmdDraw(commandBuffer, 3, 1, 0, 0);
|
||||
}
|
||||
|
||||
void Renderer::Resize()
|
||||
{
|
||||
LOG_INFO("Resizing");
|
||||
m_device.ResizeFramebuffer();
|
||||
}
|
||||
|
||||
float Renderer::GetAspect()
|
||||
{
|
||||
return m_device.GetSwapchainAspect();
|
||||
}
|
||||
|
||||
void Renderer::Begin()
|
||||
{
|
||||
m_device.Begin();
|
||||
}
|
||||
|
||||
void Renderer::Submit()
|
||||
{
|
||||
m_device.Submit();
|
||||
|
||||
// Not ideal but guarantees chunk buffers aren't freed too early.
|
||||
m_device.WaitForGraphicsIdle();
|
||||
m_frameBuffers.clear();
|
||||
}
|
||||
|
||||
void Renderer::Present()
|
||||
{
|
||||
m_device.Present();
|
||||
}
|
||||
|
||||
void Renderer::UpdateUBO()
|
||||
{
|
||||
auto currentFrame = m_device.GetCurrentFrame();
|
||||
m_descriptorSet->UpdateUBO(currentFrame, m_ubo);
|
||||
}
|
||||
|
||||
void Renderer::WaitForIdle()
|
||||
{
|
||||
vkDeviceWaitIdle(m_device.GetVkDevice());
|
||||
}
|
||||
|
||||
void Renderer::GetImGuiInfo(ImGuiCreationInfo& info)
|
||||
{
|
||||
info.colorFormat = m_device.GetSwapchainImageFormat();
|
||||
VkFormat depthFormat = m_device.GetDepthFormat();
|
||||
|
||||
info.pipelineCreateInfo = {};
|
||||
info.pipelineCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR;
|
||||
info.pipelineCreateInfo.pNext = VK_NULL_HANDLE;
|
||||
info.pipelineCreateInfo.colorAttachmentCount = 1;
|
||||
info.pipelineCreateInfo.pColorAttachmentFormats = &info.colorFormat;
|
||||
info.pipelineCreateInfo.depthAttachmentFormat = depthFormat;
|
||||
info.pipelineCreateInfo.stencilAttachmentFormat = VK_FORMAT_UNDEFINED;
|
||||
|
||||
info.imGuiInfo = {};
|
||||
info.imGuiInfo.Instance = m_instance.GetVkInstance();
|
||||
info.imGuiInfo.PhysicalDevice = m_device.GetVkPhysicalDevice();
|
||||
info.imGuiInfo.Device = m_device.GetVkDevice();
|
||||
info.imGuiInfo.QueueFamily = m_device.GetGraphicsQueueIndex();
|
||||
info.imGuiInfo.Queue = m_device.GetGraphicsQueue();
|
||||
info.imGuiInfo.PipelineCache = VK_NULL_HANDLE; // TODO
|
||||
info.imGuiInfo.DescriptorPool = m_device.GetImGuiDescriptorPool();
|
||||
info.imGuiInfo.UseDynamicRendering = true;
|
||||
info.imGuiInfo.PipelineRenderingCreateInfo = info.pipelineCreateInfo;
|
||||
info.imGuiInfo.MinImageCount = MAX_FRAMES_IN_FLIGHT;
|
||||
info.imGuiInfo.ImageCount = MAX_FRAMES_IN_FLIGHT;
|
||||
info.imGuiInfo.MSAASamples = VK_SAMPLE_COUNT_1_BIT;
|
||||
info.imGuiInfo.Allocator = nullptr; // TODO vma?
|
||||
info.imGuiInfo.CheckVkResultFn = ImGuiVkCheck;
|
||||
}
|
||||
|
||||
VkShaderModule& Renderer::CreateShaderModule(VkShaderModuleCreateInfo createInfo)
|
||||
{
|
||||
VkShaderModule module;
|
||||
VK_CHECK(
|
||||
vkCreateShaderModule(m_device.GetVkDevice(), &createInfo, nullptr, &module),
|
||||
"Failed to create shader module"
|
||||
);
|
||||
return m_vkShaderModules.emplace_back(module);
|
||||
}
|
||||
|
||||
constexpr VkPipelineShaderStageCreateInfo Renderer::FillShaderStageCreateInfo(
|
||||
VkShaderModule& module,
|
||||
VkShaderStageFlagBits stage
|
||||
)
|
||||
{
|
||||
VkPipelineShaderStageCreateInfo createInfo {};
|
||||
createInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
||||
createInfo.stage = stage;
|
||||
createInfo.module = module;
|
||||
createInfo.pName = "main";
|
||||
return createInfo;
|
||||
}
|
||||
} // namespace legs
|
33
src/renderer/vma_usage.cpp
Normal file
33
src/renderer/vma_usage.cpp
Normal file
|
@ -0,0 +1,33 @@
|
|||
#include "vk_mem_alloc.h"
|
||||
#define VMA_IMPLEMENTATION
|
||||
#define VMA_VULKAN_VERSION 1003000 // 1.3
|
||||
|
||||
#include <legs/renderer/common.hpp>
|
||||
#include <legs/renderer/vma_usage.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
VmaAllocator g_vma;
|
||||
|
||||
void CreateAllocator(VkInstance instance, VkPhysicalDevice physicalDevice, VkDevice device)
|
||||
{
|
||||
VmaAllocatorCreateInfo createInfo {};
|
||||
createInfo.instance = instance;
|
||||
createInfo.physicalDevice = physicalDevice;
|
||||
createInfo.device = device;
|
||||
createInfo.vulkanApiVersion = VK_API_VERSION_1_3;
|
||||
VK_CHECK(vmaCreateAllocator(&createInfo, &g_vma), "Failed to crate vulkan allocator");
|
||||
}
|
||||
|
||||
void DestroyAllocator()
|
||||
{
|
||||
vmaDestroyAllocator(g_vma);
|
||||
}
|
||||
|
||||
VmaTotalStatistics GetAllocatorTotalStatistics()
|
||||
{
|
||||
VmaTotalStatistics stats {};
|
||||
vmaCalculateStatistics(g_vma, &stats);
|
||||
return stats;
|
||||
}
|
||||
} // namespace legs
|
108
src/ui/ui.cpp
Normal file
108
src/ui/ui.cpp
Normal file
|
@ -0,0 +1,108 @@
|
|||
#include <imgui.h>
|
||||
#include <imgui_impl_sdl2.h>
|
||||
#include <imgui_impl_vulkan.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <SDL_events.h>
|
||||
|
||||
#include <legs/log.hpp>
|
||||
#include <legs/memory.hpp>
|
||||
#include <legs/ui/ui.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
|
||||
UI::UI(std::shared_ptr<Window> window, std::shared_ptr<Renderer> renderer) :
|
||||
m_window(window),
|
||||
m_renderer(renderer),
|
||||
m_state({})
|
||||
{
|
||||
LOG_INFO("Creating UI");
|
||||
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||
io.ConfigDebugIsDebuggerPresent = true;
|
||||
|
||||
ImGui_ImplSDL2_InitForVulkan(m_window->GetSDLWindow());
|
||||
m_renderer->GetImGuiInfo(m_info);
|
||||
ImGui_ImplVulkan_Init(&m_info.imGuiInfo);
|
||||
|
||||
m_state.showWindow[static_cast<unsigned int>(UIWindow::DEBUG)] = true;
|
||||
}
|
||||
|
||||
UI::~UI()
|
||||
{
|
||||
LOG_INFO("Destroying UI");
|
||||
|
||||
ImGui_ImplVulkan_Shutdown();
|
||||
|
||||
ImGui_ImplSDL2_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
}
|
||||
|
||||
void UI::Render()
|
||||
{
|
||||
// Start new frame
|
||||
ImGui_ImplVulkan_NewFrame();
|
||||
ImGui_ImplSDL2_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
|
||||
DebugWindow();
|
||||
DemoWindow();
|
||||
|
||||
// Prep data for renderer implementation
|
||||
ImGui::Render();
|
||||
|
||||
// Pass the data to renderer
|
||||
auto data = ImGui::GetDrawData();
|
||||
ImGui_ImplVulkan_RenderDrawData(data, m_renderer->GetVkCommandBuffer());
|
||||
}
|
||||
|
||||
void UI::DebugWindow()
|
||||
{
|
||||
if (!m_state.showWindow[static_cast<unsigned int>(UIWindow::DEBUG)])
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui::SetNextWindowBgAlpha(0.5f);
|
||||
if (ImGui::Begin(
|
||||
"Debug info",
|
||||
&m_state.showWindow[static_cast<unsigned int>(UIWindow::DEBUG)],
|
||||
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoNav
|
||||
| ImGuiWindowFlags_NoDecoration
|
||||
))
|
||||
{
|
||||
auto fps = std::format(
|
||||
"FPS: {:.0f} ({:.2f} ms)",
|
||||
1.0 / Time::DeltaFrame,
|
||||
Time::DeltaFrame * 1000.0
|
||||
);
|
||||
ImGui::Text("%s", fps.c_str());
|
||||
|
||||
auto ren = std::format(" Render: {:.2f} ms", Time::DeltaRender * 1000.0);
|
||||
ImGui::Text("%s", ren.c_str());
|
||||
|
||||
auto tps =
|
||||
std::format("TPS: {:.0f} ({:.2f} ms)", 1.0 / Time::DeltaTick, Time::DeltaTick * 1000.0);
|
||||
ImGui::Text("%s", tps.c_str());
|
||||
|
||||
auto mem = std::format("MEM: {:d} MB", Memory::GetUsage() / 1024);
|
||||
ImGui::Text("%s", mem.c_str());
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
|
||||
void UI::DemoWindow()
|
||||
{
|
||||
if (!m_state.showWindow[static_cast<unsigned int>(UIWindow::DEMO)])
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui::ShowDemoWindow(&m_state.showWindow[static_cast<unsigned int>(UIWindow::DEMO)]);
|
||||
}
|
||||
}; // namespace legs
|
206
src/window/window.cpp
Normal file
206
src/window/window.cpp
Normal file
|
@ -0,0 +1,206 @@
|
|||
#include <imgui.h>
|
||||
#include <imgui_impl_sdl2.h>
|
||||
#include <SDL.h>
|
||||
#include <SDL_events.h>
|
||||
#include <SDL_mouse.h>
|
||||
#include <SDL_scancode.h>
|
||||
#include <SDL_stdinc.h>
|
||||
#include <SDL_video.h>
|
||||
#include <SDL_vulkan.h>
|
||||
|
||||
#include <legs/log.hpp>
|
||||
#include "legs/window/window.hpp"
|
||||
|
||||
namespace legs
|
||||
{
|
||||
Window::Window(std::shared_ptr<InputSettings> inputSettings) : m_inputSettings(inputSettings)
|
||||
{
|
||||
LOG_INFO(
|
||||
"Creating window, SDL version: {}.{}.{}",
|
||||
SDL_MAJOR_VERSION,
|
||||
SDL_MINOR_VERSION,
|
||||
SDL_PATCHLEVEL
|
||||
);
|
||||
|
||||
if (SDL_Init(SDL_INIT_VIDEO) < 0)
|
||||
{
|
||||
LOG_ERROR("SDL could not initialize. SDL_Error: {}", SDL_GetError());
|
||||
throw("Failed to create Engine");
|
||||
}
|
||||
|
||||
m_window = SDL_CreateWindow(
|
||||
"legs",
|
||||
SDL_WINDOWPOS_UNDEFINED,
|
||||
SDL_WINDOWPOS_UNDEFINED,
|
||||
1280,
|
||||
720,
|
||||
SDL_WINDOW_VULKAN | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_MAXIMIZED
|
||||
);
|
||||
|
||||
if (m_window == nullptr)
|
||||
{
|
||||
LOG_ERROR("Window could not be created. SDL_Error: {}", SDL_GetError());
|
||||
throw("Failed to create window");
|
||||
}
|
||||
|
||||
SDL_SysWMinfo wmi;
|
||||
SDL_VERSION(&wmi.version);
|
||||
if (!SDL_GetWindowWMInfo(m_window, &wmi))
|
||||
{
|
||||
LOG_ERROR("SDL_SysWMinfo could not be retrieved. SDL_Error: {}", SDL_GetError());
|
||||
throw("Failed to create window");
|
||||
}
|
||||
|
||||
LOG_DEBUG("Window created");
|
||||
}
|
||||
|
||||
Window::~Window()
|
||||
{
|
||||
LOG_INFO("Destroying window");
|
||||
if (m_window != nullptr)
|
||||
{
|
||||
SDL_DestroyWindow(m_window);
|
||||
}
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
void Window::SetTitle(const char* title)
|
||||
{
|
||||
SDL_SetWindowTitle(m_window, title);
|
||||
}
|
||||
|
||||
void Window::SetMouseGrab(bool grab)
|
||||
{
|
||||
SDL_SetRelativeMouseMode(grab ? SDL_TRUE : SDL_FALSE);
|
||||
}
|
||||
|
||||
bool Window::IsMouseGrabbed()
|
||||
{
|
||||
return SDL_GetRelativeMouseMode() == SDL_TRUE;
|
||||
}
|
||||
|
||||
void Window::AggregateInput(WindowInput& input)
|
||||
{
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
bool handleKeyboard = true;
|
||||
bool handleMouse = true;
|
||||
|
||||
for (SDL_Event event; SDL_PollEvent(&event) != 0;)
|
||||
{
|
||||
if (event.type == SDL_QUIT)
|
||||
{
|
||||
input.wantsQuit = true;
|
||||
break;
|
||||
}
|
||||
|
||||
ImGui_ImplSDL2_ProcessEvent(&event);
|
||||
handleKeyboard = !io.WantCaptureKeyboard;
|
||||
handleMouse = !io.WantCaptureMouse;
|
||||
|
||||
if (!handleMouse)
|
||||
{
|
||||
SetMouseGrab(false);
|
||||
}
|
||||
if (!IsMouseGrabbed())
|
||||
{
|
||||
handleKeyboard = false;
|
||||
}
|
||||
|
||||
if (event.type == SDL_WINDOWEVENT)
|
||||
{
|
||||
switch (event.window.event)
|
||||
{
|
||||
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
||||
input.wantsResize = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (handleKeyboard)
|
||||
{
|
||||
if (event.type == SDL_KEYDOWN)
|
||||
{
|
||||
auto key = m_inputSettings->GetKeyFromSDL(event.key.keysym.scancode);
|
||||
input.KeyDown(key);
|
||||
}
|
||||
|
||||
if (event.type == SDL_KEYUP)
|
||||
{
|
||||
auto key = m_inputSettings->GetKeyFromSDL(event.key.keysym.scancode);
|
||||
input.KeyUp(key);
|
||||
}
|
||||
}
|
||||
|
||||
if (handleMouse)
|
||||
{
|
||||
if (event.type == SDL_MOUSEBUTTONDOWN)
|
||||
{
|
||||
switch (event.button.button)
|
||||
{
|
||||
case 1:
|
||||
SetMouseGrab(true);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (IsMouseGrabbed())
|
||||
{
|
||||
if (event.type == SDL_MOUSEMOTION)
|
||||
{
|
||||
input.mouse.x += event.motion.xrel;
|
||||
input.mouse.y += event.motion.yrel;
|
||||
}
|
||||
if (event.type == SDL_MOUSEWHEEL)
|
||||
{
|
||||
input.scroll.x += event.motion.xrel;
|
||||
input.scroll.y += event.motion.yrel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Window::GetFramebufferSize(int* width, int* height)
|
||||
{
|
||||
SDL_Vulkan_GetDrawableSize(m_window, width, height);
|
||||
}
|
||||
|
||||
bool Window::CreateSurface(VkInstance instance, VkSurfaceKHR* surface)
|
||||
{
|
||||
auto result = SDL_Vulkan_CreateSurface(m_window, instance, surface);
|
||||
return result == SDL_TRUE;
|
||||
}
|
||||
|
||||
bool Window::GetExtensions(unsigned int* pCount, const char** pNames)
|
||||
{
|
||||
auto result = SDL_Vulkan_GetInstanceExtensions(m_window, pCount, pNames);
|
||||
return result == SDL_TRUE;
|
||||
}
|
||||
|
||||
bool Window::IsMinimized()
|
||||
{
|
||||
return SDL_GetWindowFlags(m_window) & SDL_WINDOW_MINIMIZED;
|
||||
}
|
||||
|
||||
unsigned int Window::GetRefreshRate()
|
||||
{
|
||||
auto display = SDL_GetWindowDisplayIndex(m_window);
|
||||
if (display >= 0)
|
||||
{
|
||||
SDL_DisplayMode mode = {};
|
||||
if (SDL_GetDesktopDisplayMode(0, &mode) == 0)
|
||||
{
|
||||
return static_cast<unsigned int>(mode.refresh_rate);
|
||||
}
|
||||
}
|
||||
|
||||
LOG_ERROR("Failed to get display mode, {}", SDL_GetError());
|
||||
return 60;
|
||||
}
|
||||
} // namespace legs
|
85
src/world/world.cpp
Normal file
85
src/world/world.cpp
Normal file
|
@ -0,0 +1,85 @@
|
|||
#include <memory>
|
||||
|
||||
#include <glm/ext/matrix_transform.hpp>
|
||||
|
||||
#include "../physics.hpp"
|
||||
|
||||
#include <legs/entity/sky.hpp>
|
||||
#include <legs/geometry/icosphere.hpp>
|
||||
#include <legs/log.hpp>
|
||||
#include <legs/renderer/renderer.hpp>
|
||||
#include <legs/world/world.hpp>
|
||||
|
||||
namespace legs
|
||||
{
|
||||
|
||||
World::World(std::shared_ptr<Renderer> renderer) :
|
||||
m_renderer(renderer),
|
||||
m_physics(std::make_shared<Physics>())
|
||||
{
|
||||
LOG_DEBUG("Creating World");
|
||||
}
|
||||
|
||||
World::~World()
|
||||
{
|
||||
LOG_DEBUG("Destroying World");
|
||||
m_physics.reset();
|
||||
}
|
||||
|
||||
void World::Frame()
|
||||
{
|
||||
for (auto ent : m_entities)
|
||||
{
|
||||
ent->OnFrame();
|
||||
}
|
||||
}
|
||||
|
||||
void World::Tick()
|
||||
{
|
||||
{
|
||||
m_physics->Update();
|
||||
|
||||
std::scoped_lock worldLock {m_worldMutex};
|
||||
for (auto ent : m_entities)
|
||||
{
|
||||
ent->OnTick();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void World::Render()
|
||||
{
|
||||
if (m_sky != nullptr)
|
||||
{
|
||||
m_sky->Render(m_renderer);
|
||||
}
|
||||
|
||||
for (auto ent : m_entities)
|
||||
{
|
||||
if (auto meshEnt = std::static_pointer_cast<MeshEntity>(ent))
|
||||
{
|
||||
meshEnt->Render(m_renderer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void World::AddEntity(std::shared_ptr<Entity> entity)
|
||||
{
|
||||
m_entities.push_back(entity);
|
||||
entity->OnSpawn();
|
||||
}
|
||||
|
||||
void World::RemoveEntity(std::shared_ptr<Entity> entity)
|
||||
{
|
||||
for (auto it = m_entities.begin(); it != m_entities.end();)
|
||||
{
|
||||
if (*it == entity)
|
||||
{
|
||||
m_entities.erase(it);
|
||||
entity->OnDestroy();
|
||||
break;
|
||||
}
|
||||
it++;
|
||||
}
|
||||
}
|
||||
} // namespace legs
|
15
subprojects/sdl2.wrap
Normal file
15
subprojects/sdl2.wrap
Normal file
|
@ -0,0 +1,15 @@
|
|||
[wrap-file]
|
||||
directory = SDL2-2.30.6
|
||||
source_url = https://github.com/libsdl-org/SDL/releases/download/release-2.30.6/SDL2-2.30.6.tar.gz
|
||||
source_filename = SDL2-2.30.6.tar.gz
|
||||
source_hash = c6ef64ca18a19d13df6eb22df9aff19fb0db65610a74cc81dae33a82235cacd4
|
||||
patch_filename = sdl2_2.30.6-2_patch.zip
|
||||
patch_url = https://wrapdb.mesonbuild.com/v2/sdl2_2.30.6-2/get_patch
|
||||
patch_hash = aa9f6a4947b07510c2ea84fb457e965bebe5a5deeb9f5059fbcf10dfe6b76d1f
|
||||
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/sdl2_2.30.6-2/SDL2-2.30.6.tar.gz
|
||||
wrapdb_version = 2.30.6-2
|
||||
|
||||
[provide]
|
||||
sdl2 = sdl2_dep
|
||||
sdl2main = sdl2main_dep
|
||||
sdl2_test = sdl2_test_dep
|
13
subprojects/vulkan-headers.wrap
Normal file
13
subprojects/vulkan-headers.wrap
Normal file
|
@ -0,0 +1,13 @@
|
|||
[wrap-file]
|
||||
directory = Vulkan-Headers-1.3.283
|
||||
source_url = https://github.com/KhronosGroup/Vulkan-Headers/archive/v1.3.283.tar.gz
|
||||
source_filename = vulkan-headers-1.3.283.tar.gz
|
||||
source_hash = a76ff77815012c76abc9811215c2167128a73a697bcc23948e858d1f7dd54a85
|
||||
patch_filename = vulkan-headers_1.3.283-1_patch.zip
|
||||
patch_url = https://wrapdb.mesonbuild.com/v2/vulkan-headers_1.3.283-1/get_patch
|
||||
patch_hash = 00e30d35117ae90a19b5b8878746fceaf31b41778b817ca9e6b3ae6063be8233
|
||||
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/vulkan-headers_1.3.283-1/vulkan-headers-1.3.283.tar.gz
|
||||
wrapdb_version = 1.3.283-1
|
||||
|
||||
[provide]
|
||||
vulkanheaders = vulkan_headers_dep
|
4
suppr.txt
Normal file
4
suppr.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
# probably fine...
|
||||
leak:libSDL2
|
||||
leak:libubsan
|
||||
|
4
version.h.in
Normal file
4
version.h.in
Normal file
|
@ -0,0 +1,4 @@
|
|||
#pragma once
|
||||
|
||||
#define LEGS_VERSION "@VCS_TAG@"
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue