Compare commits
7 Commits
263b6da528
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 99e06bbbb1 | |||
| d09f01402d | |||
| ce075403d5 | |||
| 518c664741 | |||
| c9fa1b8299 | |||
| 0cb0628101 | |||
| 7243e13df5 |
Vendored
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Linux",
|
||||||
|
"includePath": ["${workspaceFolder}/**", "${env:VULKAN_SDK}/include"],
|
||||||
|
"defines": [],
|
||||||
|
"compileCommands": "${workspaceFolder}/build/compile_commands.json",
|
||||||
|
"intelliSenseMode": "linux-clang-x64"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 4
|
||||||
|
}
|
||||||
Vendored
+59
@@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"C_Cpp.formatting": "clangFormat",
|
||||||
|
"C_Cpp.clang_format_style": "file",
|
||||||
|
"C_Cpp.clang_format_fallbackStyle": "Google",
|
||||||
|
"C_Cpp.clang_format_path": "/usr/bin/clang-format",
|
||||||
|
"C_Cpp.vcFormat.indent.namespaceContents": false,
|
||||||
|
|
||||||
|
"[cpp]": {
|
||||||
|
"editor.defaultFormatter": "ms-vscode.cpptools",
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.formatOnType": true,
|
||||||
|
"editor.rulers": [80],
|
||||||
|
"editor.tabSize": 2,
|
||||||
|
"editor.insertSpaces": true
|
||||||
|
},
|
||||||
|
"[c]": {
|
||||||
|
"editor.defaultFormatter": "ms-vscode.cpptools",
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.rulers": [80],
|
||||||
|
"editor.tabSize": 2,
|
||||||
|
"editor.insertSpaces": true
|
||||||
|
},
|
||||||
|
"C_Cpp.codeAnalysis.clangTidy.enabled": true,
|
||||||
|
"C_Cpp.codeAnalysis.clangTidy.path": "/usr/bin/clang-tidy",
|
||||||
|
"C_Cpp.codeAnalysis.clangTidy.useBuildPath": true,
|
||||||
|
"C_Cpp.codeAnalysis.runAutomatically": true,
|
||||||
|
"C_Cpp.codeAnalysis.exclude": {
|
||||||
|
"**/third_party/**": true,
|
||||||
|
"**/build/**": true
|
||||||
|
},
|
||||||
|
"C_Cpp.default.compileCommands": "${workspaceFolder}/build/compile_commands.json",
|
||||||
|
"C_Cpp.default.cppStandard": "c++20",
|
||||||
|
"C_Cpp.default.cStandard": "c17",
|
||||||
|
"C_Cpp.errorSquiggles": "enabled",
|
||||||
|
"cmake.configureEnvironment": {
|
||||||
|
"VULKAN_SDK": "/home/caleb/.local/share/vulkan-sdk/1.4.357.1/x86_64",
|
||||||
|
"CMAKE_PREFIX_PATH": "/home/caleb/.local/share/vulkan-sdk/1.4.357.1/x86_64:/home/caleb/.local/share/vulkan-sdk/1.4.357.1/x86_64/lib/VulkanLoader"
|
||||||
|
},
|
||||||
|
"cmake.environment": {
|
||||||
|
"VULKAN_SDK": "/home/caleb/.local/share/vulkan-sdk/1.4.357.1/x86_64",
|
||||||
|
"LD_LIBRARY_PATH": "/home/caleb/.local/share/vulkan-sdk/1.4.357.1/x86_64/lib/VulkanLoader/lib:/home/caleb/.local/share/vulkan-sdk/1.4.357.1/x86_64/lib",
|
||||||
|
"VK_ADD_LAYER_PATH": "/home/caleb/.local/share/vulkan-sdk/1.4.357.1/x86_64/share/vulkan/explicit_layer.d"
|
||||||
|
},
|
||||||
|
"cmake.buildDirectory": "${workspaceFolder}/build",
|
||||||
|
"cmake.generator": "Ninja",
|
||||||
|
"cmake.configureOnOpen": true,
|
||||||
|
"files.associations": {
|
||||||
|
".clang-format": "yaml",
|
||||||
|
".clang-tidy": "yaml",
|
||||||
|
"*.hpp": "cpp",
|
||||||
|
"*.inl": "cpp"
|
||||||
|
},
|
||||||
|
"files.insertFinalNewline": true,
|
||||||
|
"files.trimTrailingWhitespace": true,
|
||||||
|
"search.exclude": {
|
||||||
|
"**/build": true,
|
||||||
|
"**/third_party": true
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-1
@@ -39,4 +39,5 @@ endif()
|
|||||||
|
|
||||||
add_subdirectory(third_party)
|
add_subdirectory(third_party)
|
||||||
add_subdirectory(engine)
|
add_subdirectory(engine)
|
||||||
add_subdirectory(testbed)
|
add_subdirectory(testbed)
|
||||||
|
add_subdirectory(examples)
|
||||||
+15
-8
@@ -2,17 +2,24 @@ set(ENGINE_NAME ${PROJECT_NAME})
|
|||||||
|
|
||||||
add_library(${ENGINE_NAME} STATIC)
|
add_library(${ENGINE_NAME} STATIC)
|
||||||
|
|
||||||
file(GLOB_RECURSE SOURCES src/*.cpp)
|
file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS src/*.cpp)
|
||||||
file(GLOB_RECURSE HEADERS include/*.hpp)
|
file(GLOB_RECURSE HEADERS CONFIGURE_DEPENDS include/*.hpp)
|
||||||
|
|
||||||
target_sources(${ENGINE_NAME} PRIVATE
|
target_sources(${ENGINE_NAME} PRIVATE
|
||||||
${SOURCES}
|
${SOURCES}
|
||||||
# ${HEADERS}
|
)
|
||||||
|
|
||||||
|
target_include_directories(${ENGINE_NAME} PRIVATE
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src"
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(${ENGINE_NAME} PUBLIC
|
target_include_directories(${ENGINE_NAME} PUBLIC
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/src"
|
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/include"
|
"${CMAKE_CURRENT_SOURCE_DIR}/include"
|
||||||
"${CMAKE_SOURCE_DIR}/third_party/imgui"
|
)
|
||||||
"${CMAKE_SOURCE_DIR}/third_party/imgui/backends"
|
|
||||||
|
target_link_libraries(${ENGINE_NAME} PUBLIC
|
||||||
|
vulkan
|
||||||
|
glfw
|
||||||
|
glm
|
||||||
|
imgui
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
class Application {
|
||||||
|
public:
|
||||||
|
struct Config {
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
std::string name;
|
||||||
|
bool headless = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
Application(const Application&) = delete;
|
||||||
|
Application& operator=(const Application&) = delete;
|
||||||
|
|
||||||
|
Application(const Config& config);
|
||||||
|
~Application();
|
||||||
|
|
||||||
|
void run();
|
||||||
|
|
||||||
|
private:
|
||||||
|
class Impl;
|
||||||
|
std::unique_ptr<Impl> impl;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
// Minimal end-to-end compute dispatch: compiles the GLSL compute shader at
|
||||||
|
// `computeShaderPath`, builds the descriptor/pipeline layer around a small
|
||||||
|
// storage buffer, dispatches it, reads the buffer back, and verifies the
|
||||||
|
// result against what the shader is expected to compute. Logs the outcome
|
||||||
|
// and throws std::runtime_error if the GPU result doesn't match.
|
||||||
|
void runComputeSmokeTest(const std::string& computeShaderPath);
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <any>
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include <sophia/types.hpp>
|
||||||
|
#include <sophia/util/delegate.hpp>
|
||||||
|
#include <sophia/util/logger.hpp>
|
||||||
|
|
||||||
|
#define BIT(x) (1 << (x))
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
// https://github.com/TheCherno/Hazel/blob/1feb70572fa87fa1c4ba784a2cfeada5b4a500db/Hazel/src/Hazel/Events/Event.h
|
||||||
|
|
||||||
|
enum EventCategoryFlags : u32 {
|
||||||
|
None = 0,
|
||||||
|
EventCategoryApplication = BIT(0),
|
||||||
|
EventCategoryInput = BIT(1),
|
||||||
|
EventCategoryKeyboard = BIT(2),
|
||||||
|
EventCategoryMouse = BIT(3),
|
||||||
|
EventCategoryMouseButton = BIT(4)
|
||||||
|
};
|
||||||
|
|
||||||
|
class Event {
|
||||||
|
public:
|
||||||
|
virtual ~Event() = default;
|
||||||
|
|
||||||
|
virtual const char* getName() const = 0;
|
||||||
|
virtual std::string toString() const { return getName(); }
|
||||||
|
|
||||||
|
virtual int getCategoryFlags() const { return EventCategoryFlags::None; };
|
||||||
|
|
||||||
|
inline bool isInCategory(EventCategoryFlags category) {
|
||||||
|
return getCategoryFlags() & category;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename DerivedEvent>
|
||||||
|
class EventDelegate : public Delegate<DerivedEvent&> {
|
||||||
|
public:
|
||||||
|
EventDelegate() {
|
||||||
|
static_assert(std::is_base_of<Event, DerivedEvent>::value,
|
||||||
|
"DerivedEvent must derive from Event");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
inline std::ostream& operator<<(std::ostream& os, const Event& e) {
|
||||||
|
return os << e.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Singleton class for managing event subscription and dispatching.
|
||||||
|
*
|
||||||
|
* TODO heavy documentation is needed
|
||||||
|
*
|
||||||
|
* @todo impl removing functors from delegate
|
||||||
|
* @warning this class is in heavy development, use at your own risk
|
||||||
|
* @warning NOT THREAD SAFE
|
||||||
|
*/
|
||||||
|
class EventSystem {
|
||||||
|
public:
|
||||||
|
using EventType = decltype(std::declval<std::type_info>().hash_code());
|
||||||
|
|
||||||
|
#define GET_EVENT_TYPE(E) typeid(E).hash_code()
|
||||||
|
|
||||||
|
EventSystem(const EventSystem&) = delete;
|
||||||
|
EventSystem& operator=(const EventSystem&) = delete;
|
||||||
|
|
||||||
|
static EventSystem& get() {
|
||||||
|
static EventSystem instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class E>
|
||||||
|
void addListener(std::function<void(E&)> f) {
|
||||||
|
static_assert(std::is_base_of<Event, E>::value, "E must derive from Event");
|
||||||
|
|
||||||
|
EventType eventType = GET_EVENT_TYPE(E);
|
||||||
|
|
||||||
|
// Ensures pointer to EventDelegate<E> is not null in unordered map
|
||||||
|
if (!this->eventDelegates.count(eventType)) {
|
||||||
|
this->eventDelegates[eventType] =
|
||||||
|
std::make_unique<std::any>(EventDelegate<E>());
|
||||||
|
|
||||||
|
log::trace("EventSystem creating new event type: ", eventType);
|
||||||
|
}
|
||||||
|
|
||||||
|
EventDelegate<E>& delegate =
|
||||||
|
std::any_cast<EventDelegate<E>&>(*eventDelegates[eventType]);
|
||||||
|
delegate.add(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class E, class O>
|
||||||
|
void addMethodListener(void (O::*memberFn)(E&), std::shared_ptr<O> obj) {
|
||||||
|
static_assert(std::is_base_of<Event, E>::value, "E must derive from Event");
|
||||||
|
|
||||||
|
EventType eventType = GET_EVENT_TYPE(E);
|
||||||
|
|
||||||
|
// Ensures pointer to EventDelegate<E> is not null in unordered map
|
||||||
|
if (!this->eventDelegates.count(eventType)) {
|
||||||
|
this->eventDelegates[eventType] =
|
||||||
|
std::make_unique<std::any>(EventDelegate<E>());
|
||||||
|
|
||||||
|
log::trace("EventSystem creating new event type: ", eventType);
|
||||||
|
}
|
||||||
|
|
||||||
|
EventDelegate<E>& delegate =
|
||||||
|
std::any_cast<EventDelegate<E>&>(*eventDelegates[eventType]);
|
||||||
|
delegate.addMethod(memberFn, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class E>
|
||||||
|
void dispatch(E& event) {
|
||||||
|
static_assert(std::is_base_of<Event, E>::value, "E must derive from Event");
|
||||||
|
EventType eventType = GET_EVENT_TYPE(E);
|
||||||
|
|
||||||
|
if (!this->eventDelegates.count(eventType)) { return; }
|
||||||
|
|
||||||
|
try {
|
||||||
|
EventDelegate<E>& delegate =
|
||||||
|
std::any_cast<EventDelegate<E>&>(*this->eventDelegates.at(eventType));
|
||||||
|
delegate.invoke(event);
|
||||||
|
} catch (const std::out_of_range& error) {
|
||||||
|
log::error("event dispatch failed: ", event.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
EventSystem() { log::info("EventSystem Initialized: ", this); }
|
||||||
|
~EventSystem() { log::info("EventSystem Destroyed"); }
|
||||||
|
|
||||||
|
std::unordered_map<EventType, std::unique_ptr<std::any>> eventDelegates;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <sophia/types.hpp>
|
||||||
|
|
||||||
|
#define GLM_FORCE_RADIANS
|
||||||
|
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
struct FrameInfo {
|
||||||
|
u32 frameIndex;
|
||||||
|
double elapsedTime;
|
||||||
|
double deltaTime;
|
||||||
|
glm::vec2 currentFramebufferExtent;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define GLFW_INCLUDE_VULKAN
|
||||||
|
#include <GLFW/glfw3.h>
|
||||||
|
|
||||||
|
#include <sophia/types.hpp>
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
using KeyCode = u32;
|
||||||
|
|
||||||
|
namespace Key {
|
||||||
|
|
||||||
|
enum : KeyCode {
|
||||||
|
Space = GLFW_KEY_SPACE,
|
||||||
|
Apostrophe = GLFW_KEY_APOSTROPHE, /* ' */
|
||||||
|
Comma = GLFW_KEY_COMMA, /* , */
|
||||||
|
Minus = GLFW_KEY_MINUS, /* - */
|
||||||
|
Period = GLFW_KEY_PERIOD, /* . */
|
||||||
|
Slash = GLFW_KEY_SLASH, /* / */
|
||||||
|
D0 = GLFW_KEY_0,
|
||||||
|
D1 = GLFW_KEY_1,
|
||||||
|
D2 = GLFW_KEY_2,
|
||||||
|
D3 = GLFW_KEY_3,
|
||||||
|
D4 = GLFW_KEY_4,
|
||||||
|
D5 = GLFW_KEY_5,
|
||||||
|
D6 = GLFW_KEY_6,
|
||||||
|
D7 = GLFW_KEY_7,
|
||||||
|
D8 = GLFW_KEY_8,
|
||||||
|
D9 = GLFW_KEY_9,
|
||||||
|
Semicolon = GLFW_KEY_SEMICOLON, /* ; */
|
||||||
|
Equal = GLFW_KEY_EQUAL, /* = */
|
||||||
|
A = GLFW_KEY_A,
|
||||||
|
B = GLFW_KEY_B,
|
||||||
|
C = GLFW_KEY_C,
|
||||||
|
D = GLFW_KEY_D,
|
||||||
|
E = GLFW_KEY_E,
|
||||||
|
F = GLFW_KEY_F,
|
||||||
|
G = GLFW_KEY_G,
|
||||||
|
H = GLFW_KEY_H,
|
||||||
|
I = GLFW_KEY_I,
|
||||||
|
J = GLFW_KEY_J,
|
||||||
|
K = GLFW_KEY_K,
|
||||||
|
L = GLFW_KEY_L,
|
||||||
|
M = GLFW_KEY_M,
|
||||||
|
N = GLFW_KEY_N,
|
||||||
|
O = GLFW_KEY_O,
|
||||||
|
P = GLFW_KEY_P,
|
||||||
|
Q = GLFW_KEY_Q,
|
||||||
|
R = GLFW_KEY_R,
|
||||||
|
S = GLFW_KEY_S,
|
||||||
|
T = GLFW_KEY_T,
|
||||||
|
U = GLFW_KEY_U,
|
||||||
|
V = GLFW_KEY_V,
|
||||||
|
W = GLFW_KEY_W,
|
||||||
|
X = GLFW_KEY_X,
|
||||||
|
Y = GLFW_KEY_Y,
|
||||||
|
Z = GLFW_KEY_Z,
|
||||||
|
LeftBracket = GLFW_KEY_LEFT_BRACKET, /* [ */
|
||||||
|
Backslash = GLFW_KEY_BACKSLASH, /* \ */
|
||||||
|
RightBracket = GLFW_KEY_RIGHT_BRACKET, /* ] */
|
||||||
|
GraveAccent = GLFW_KEY_GRAVE_ACCENT, /* ` */
|
||||||
|
World1 = GLFW_KEY_WORLD_1, /* non-US #1 */
|
||||||
|
World2 = GLFW_KEY_WORLD_2, /* non-US #2 */
|
||||||
|
|
||||||
|
/* Function keys */
|
||||||
|
Escape = GLFW_KEY_ESCAPE,
|
||||||
|
Enter = GLFW_KEY_ENTER,
|
||||||
|
Tab = GLFW_KEY_TAB,
|
||||||
|
Backspace = GLFW_KEY_BACKSPACE,
|
||||||
|
Insert = GLFW_KEY_INSERT,
|
||||||
|
Delete = GLFW_KEY_DELETE,
|
||||||
|
Right = GLFW_KEY_RIGHT,
|
||||||
|
Left = GLFW_KEY_LEFT,
|
||||||
|
Down = GLFW_KEY_DOWN,
|
||||||
|
Up = GLFW_KEY_UP,
|
||||||
|
PageUp = GLFW_KEY_PAGE_UP,
|
||||||
|
PageDown = GLFW_KEY_PAGE_DOWN,
|
||||||
|
Home = GLFW_KEY_HOME,
|
||||||
|
End = GLFW_KEY_END,
|
||||||
|
CapsLock = GLFW_KEY_CAPS_LOCK,
|
||||||
|
ScrollLock = GLFW_KEY_SCROLL_LOCK,
|
||||||
|
NumLock = GLFW_KEY_NUM_LOCK,
|
||||||
|
PrintScreen = GLFW_KEY_PRINT_SCREEN,
|
||||||
|
Pause = GLFW_KEY_PAUSE,
|
||||||
|
F1 = GLFW_KEY_F1,
|
||||||
|
F2 = GLFW_KEY_F2,
|
||||||
|
F3 = GLFW_KEY_F3,
|
||||||
|
F4 = GLFW_KEY_F4,
|
||||||
|
F5 = GLFW_KEY_F5,
|
||||||
|
F6 = GLFW_KEY_F6,
|
||||||
|
F7 = GLFW_KEY_F7,
|
||||||
|
F8 = GLFW_KEY_F8,
|
||||||
|
F9 = GLFW_KEY_F9,
|
||||||
|
F10 = GLFW_KEY_F10,
|
||||||
|
F11 = GLFW_KEY_F11,
|
||||||
|
F12 = GLFW_KEY_F12,
|
||||||
|
F13 = GLFW_KEY_F13,
|
||||||
|
F14 = GLFW_KEY_F14,
|
||||||
|
F15 = GLFW_KEY_F15,
|
||||||
|
F16 = GLFW_KEY_F16,
|
||||||
|
F17 = GLFW_KEY_F17,
|
||||||
|
F18 = GLFW_KEY_F18,
|
||||||
|
F19 = GLFW_KEY_F19,
|
||||||
|
F20 = GLFW_KEY_F20,
|
||||||
|
F21 = GLFW_KEY_F21,
|
||||||
|
F22 = GLFW_KEY_F22,
|
||||||
|
F23 = GLFW_KEY_F23,
|
||||||
|
F24 = GLFW_KEY_F24,
|
||||||
|
F25 = GLFW_KEY_F25,
|
||||||
|
KP0 = GLFW_KEY_KP_0,
|
||||||
|
KP1 = GLFW_KEY_KP_1,
|
||||||
|
KP2 = GLFW_KEY_KP_2,
|
||||||
|
KP3 = GLFW_KEY_KP_3,
|
||||||
|
KP4 = GLFW_KEY_KP_4,
|
||||||
|
KP5 = GLFW_KEY_KP_5,
|
||||||
|
KP6 = GLFW_KEY_KP_6,
|
||||||
|
KP7 = GLFW_KEY_KP_7,
|
||||||
|
KP8 = GLFW_KEY_KP_8,
|
||||||
|
KP9 = GLFW_KEY_KP_9,
|
||||||
|
KPDecimal = GLFW_KEY_KP_DECIMAL,
|
||||||
|
KPDivide = GLFW_KEY_KP_DIVIDE,
|
||||||
|
KPMultiply = GLFW_KEY_KP_MULTIPLY,
|
||||||
|
KPSubtract = GLFW_KEY_KP_SUBTRACT,
|
||||||
|
KPAdd = GLFW_KEY_KP_ADD,
|
||||||
|
KPEnter = GLFW_KEY_KP_ENTER,
|
||||||
|
KPEqual = GLFW_KEY_KP_EQUAL,
|
||||||
|
LeftShift = GLFW_KEY_LEFT_SHIFT,
|
||||||
|
LeftControl = GLFW_KEY_LEFT_CONTROL,
|
||||||
|
LeftAlt = GLFW_KEY_LEFT_ALT,
|
||||||
|
LeftSuper = GLFW_KEY_LEFT_SUPER,
|
||||||
|
RightShift = GLFW_KEY_RIGHT_SHIFT,
|
||||||
|
RightControl = GLFW_KEY_RIGHT_CONTROL,
|
||||||
|
RightAlt = GLFW_KEY_RIGHT_ALT,
|
||||||
|
RightSuper = GLFW_KEY_RIGHT_SUPER,
|
||||||
|
Menu = GLFW_KEY_MENU,
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Key
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include <sophia/event.hpp>
|
||||||
|
#include <sophia/key_codes.hpp>
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
class KeyEvent : public Event {
|
||||||
|
public:
|
||||||
|
KeyCode getKeyCode() const { return this->keycode; }
|
||||||
|
|
||||||
|
virtual int getCategoryFlags() const override {
|
||||||
|
return EventCategoryKeyboard | EventCategoryInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
KeyEvent(const KeyCode keycode) : keycode{keycode} {}
|
||||||
|
|
||||||
|
KeyCode keycode;
|
||||||
|
};
|
||||||
|
|
||||||
|
class KeyPressedEvent : public KeyEvent {
|
||||||
|
public:
|
||||||
|
KeyPressedEvent(const KeyCode keycode, bool repeated = false)
|
||||||
|
: KeyEvent{keycode}, repeated{repeated} {}
|
||||||
|
|
||||||
|
bool isRepeat() const { return this->repeated; }
|
||||||
|
|
||||||
|
std::string toString() const override {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "KeyPressedEvent: " << this->keycode
|
||||||
|
<< " (repeat = " << this->repeated << ")";
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual const char* getName() const override { return "KeyPressed"; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool repeated;
|
||||||
|
};
|
||||||
|
|
||||||
|
class KeyReleasedEvent : public KeyEvent {
|
||||||
|
public:
|
||||||
|
KeyReleasedEvent(const KeyCode keycode) : KeyEvent{keycode} {}
|
||||||
|
|
||||||
|
std::string toString() const override {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "KeyReleasedEvent: " << this->keycode;
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual const char* getName() const override { return "KeyReleased"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class KeyTypedEvent : public KeyEvent {
|
||||||
|
public:
|
||||||
|
KeyTypedEvent(const KeyCode keycode) : KeyEvent{keycode} {}
|
||||||
|
|
||||||
|
std::string toString() const override {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "KeyTypedEvent: " << this->keycode;
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual const char* getName() const override { return "KeyTyped"; }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
using u8 = uint8_t;
|
||||||
|
using u16 = uint16_t;
|
||||||
|
using u32 = uint32_t;
|
||||||
|
using u64 = uint64_t;
|
||||||
|
|
||||||
|
using s8 = int8_t;
|
||||||
|
using s16 = int16_t;
|
||||||
|
using s32 = int32_t;
|
||||||
|
using s64 = int64_t;
|
||||||
|
|
||||||
|
using f32 = float;
|
||||||
|
using f64 = double;
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -7,11 +7,11 @@
|
|||||||
#include <typeinfo>
|
#include <typeinfo>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "util/logger.hpp"
|
#include <sophia/util/logger.hpp>
|
||||||
|
|
||||||
#define DEFAULT_DELEGATE_FUNCTOR_ALLOC 5
|
#define DEFAULT_DELEGATE_FUNCTOR_ALLOC 5
|
||||||
|
|
||||||
namespace hep {
|
namespace sophia {
|
||||||
|
|
||||||
class expired_weak_object : public std::runtime_error {
|
class expired_weak_object : public std::runtime_error {
|
||||||
public:
|
public:
|
||||||
@@ -108,4 +108,4 @@ class Delegate {
|
|||||||
std::vector<Functor> functors;
|
std::vector<Functor> functors;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace hep
|
} // namespace sophia
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
/**
|
||||||
|
* Lots of work still needs to be done in here.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* using namespace sophia;
|
||||||
|
* log::fatal("fatal message")
|
||||||
|
* log::info("Infomation message")
|
||||||
|
*
|
||||||
|
* TODO Impl some form of log error handling
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <sophia/types.hpp>
|
||||||
|
|
||||||
|
namespace sophia::log {
|
||||||
|
|
||||||
|
enum class LEVEL {
|
||||||
|
NONE,
|
||||||
|
FATAL,
|
||||||
|
ERROR,
|
||||||
|
WARNING,
|
||||||
|
INFO,
|
||||||
|
VERBOSE,
|
||||||
|
DEBUGGING,
|
||||||
|
TRACE,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper print functions
|
||||||
|
#define LOG_PRINT_HELPER(MSG) std::cout << MSG
|
||||||
|
#define LOG_PREFIX_FATAL LOG_PRINT_HELPER("\033[1;91m[FATAL]\033[0m ")
|
||||||
|
#define LOG_PREFIX_ERROR LOG_PRINT_HELPER("\033[1;31m[ERROR]\033[0m ")
|
||||||
|
#define LOG_PREFIX_WARNING LOG_PRINT_HELPER("\033[1;33m[WARNING]\033[0m ")
|
||||||
|
#define LOG_PREFIX_INFO LOG_PRINT_HELPER("\033[32m[INFO]\033[0m ")
|
||||||
|
#define LOG_PREFIX_VERBOSE LOG_PRINT_HELPER("\033[36m[VERBOSE]\033[0m ")
|
||||||
|
#define LOG_PREFIX_DEBUG LOG_PRINT_HELPER("\033[1;34m[DEBUG]\033[0m ")
|
||||||
|
#define LOG_PREFIX_TRACE LOG_PRINT_HELPER("\033[1;90m[TRACE]\033[0m ")
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
void log(LEVEL level = LEVEL::NONE, Args... args) {
|
||||||
|
#ifndef NDEBUG
|
||||||
|
switch (level) {
|
||||||
|
case LEVEL::FATAL:
|
||||||
|
LOG_PREFIX_FATAL;
|
||||||
|
break;
|
||||||
|
case LEVEL::ERROR:
|
||||||
|
LOG_PREFIX_ERROR;
|
||||||
|
break;
|
||||||
|
case LEVEL::WARNING:
|
||||||
|
LOG_PREFIX_WARNING;
|
||||||
|
break;
|
||||||
|
case LEVEL::INFO:
|
||||||
|
LOG_PREFIX_INFO;
|
||||||
|
break;
|
||||||
|
case LEVEL::VERBOSE:
|
||||||
|
LOG_PREFIX_VERBOSE;
|
||||||
|
break;
|
||||||
|
// case LEVEL::DEBUGGING:
|
||||||
|
// LOG_PREFIX_DEBUG;
|
||||||
|
// break;
|
||||||
|
case LEVEL::TRACE:
|
||||||
|
LOG_PREFIX_TRACE;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
std::cout << "\033[1;90m[LOG]\033[0m ";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
((std::cout << ' ' << std::forward<Args>(args)), ...);
|
||||||
|
std::cout << std::endl;
|
||||||
|
#else
|
||||||
|
(void)level;
|
||||||
|
(void)std::initializer_list<int>{((void)args, 0)...};
|
||||||
|
#endif // !NDEBUG
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
void fatal(Args... args) {
|
||||||
|
log(LEVEL::FATAL, args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
void error(Args... args) {
|
||||||
|
log(LEVEL::ERROR, args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
void warning(Args... args) {
|
||||||
|
log(LEVEL::WARNING, args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
void info(Args... args) {
|
||||||
|
log(LEVEL::INFO, args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
void verbose(Args... args) {
|
||||||
|
log(LEVEL::VERBOSE, args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
void debug(const char* file, u16 line, Args... args) {
|
||||||
|
#ifndef NDEBUG
|
||||||
|
LOG_PREFIX_DEBUG;
|
||||||
|
std::cout << file << "::" << line;
|
||||||
|
((std::cout << ' ' << std::forward<Args>(args)), ...);
|
||||||
|
std::cout << std::endl;
|
||||||
|
#else
|
||||||
|
(void)file;
|
||||||
|
(void)line;
|
||||||
|
(void)std::initializer_list<int>{((void)args, 0)...};
|
||||||
|
#endif // !NDEBUG
|
||||||
|
}
|
||||||
|
// trick
|
||||||
|
#define debug(...) debug(__FILE__, __LINE__, __VA_ARGS__)
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
void trace(Args... args) {
|
||||||
|
log(LEVEL::TRACE, args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sophia::log
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
namespace hep
|
|
||||||
{
|
|
||||||
|
|
||||||
using u8 = uint8_t;
|
|
||||||
using u16 = uint16_t;
|
|
||||||
using u32 = uint32_t;
|
|
||||||
using u64 = uint64_t;
|
|
||||||
|
|
||||||
using s8 = int8_t;
|
|
||||||
using s16 = int16_t;
|
|
||||||
using s32 = int32_t;
|
|
||||||
using s64 = int64_t;
|
|
||||||
|
|
||||||
using f32 = float;
|
|
||||||
using f64 = double;
|
|
||||||
|
|
||||||
} // namespace hep
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
/**
|
|
||||||
* Lots of work still needs to be done in here.
|
|
||||||
*
|
|
||||||
* Usage:
|
|
||||||
* using namespace hep;
|
|
||||||
* log::fatal("fatal message")
|
|
||||||
* log::info("Infomation message")
|
|
||||||
*
|
|
||||||
* TODO Impl some form of log error handling
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
#include "types.hpp"
|
|
||||||
|
|
||||||
namespace hep
|
|
||||||
{
|
|
||||||
namespace log
|
|
||||||
{
|
|
||||||
|
|
||||||
enum class LEVEL
|
|
||||||
{
|
|
||||||
NONE,
|
|
||||||
FATAL,
|
|
||||||
ERROR,
|
|
||||||
WARNING,
|
|
||||||
INFO,
|
|
||||||
VERBOSE,
|
|
||||||
DEBUGGING,
|
|
||||||
TRACE,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Helper print functions
|
|
||||||
#define LOG_PRINT_HELPER(MSG) std::cout << MSG
|
|
||||||
#define LOG_PREFIX_FATAL LOG_PRINT_HELPER("\033[1;91m[FATAL]\033[0m ")
|
|
||||||
#define LOG_PREFIX_ERROR LOG_PRINT_HELPER("\033[1;31m[ERROR]\033[0m ")
|
|
||||||
#define LOG_PREFIX_WARNING LOG_PRINT_HELPER("\033[1;33m[WARNING]\033[0m ")
|
|
||||||
#define LOG_PREFIX_INFO LOG_PRINT_HELPER("\033[32m[INFO]\033[0m ")
|
|
||||||
#define LOG_PREFIX_VERBOSE LOG_PRINT_HELPER("\033[36m[VERBOSE]\033[0m ")
|
|
||||||
#define LOG_PREFIX_DEBUG LOG_PRINT_HELPER("\033[1;34m[DEBUG]\033[0m ")
|
|
||||||
#define LOG_PREFIX_TRACE LOG_PRINT_HELPER("\033[1;90m[TRACE]\033[0m ")
|
|
||||||
|
|
||||||
template <typename... Args>
|
|
||||||
void log(LEVEL level = LEVEL::NONE, Args... args)
|
|
||||||
{
|
|
||||||
#ifndef NDEBUG
|
|
||||||
switch (level)
|
|
||||||
{
|
|
||||||
case LEVEL::FATAL:
|
|
||||||
LOG_PREFIX_FATAL;
|
|
||||||
break;
|
|
||||||
case LEVEL::ERROR:
|
|
||||||
LOG_PREFIX_ERROR;
|
|
||||||
break;
|
|
||||||
case LEVEL::WARNING:
|
|
||||||
LOG_PREFIX_WARNING;
|
|
||||||
break;
|
|
||||||
case LEVEL::INFO:
|
|
||||||
LOG_PREFIX_INFO;
|
|
||||||
break;
|
|
||||||
case LEVEL::VERBOSE:
|
|
||||||
LOG_PREFIX_VERBOSE;
|
|
||||||
break;
|
|
||||||
// case LEVEL::DEBUGGING:
|
|
||||||
// LOG_PREFIX_DEBUG;
|
|
||||||
// break;
|
|
||||||
case LEVEL::TRACE:
|
|
||||||
LOG_PREFIX_TRACE;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
std::cout << "\033[1;90m[LOG]\033[0m ";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
((std::cout << ' ' << std::forward<Args>(args)), ...);
|
|
||||||
std::cout << std::endl;
|
|
||||||
#else
|
|
||||||
(void)level;
|
|
||||||
(void)std::initializer_list<int>{((void)args, 0)...};
|
|
||||||
#endif // !NDEBUG
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename... Args>
|
|
||||||
void fatal(Args... args)
|
|
||||||
{
|
|
||||||
log(LEVEL::FATAL, args...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename... Args>
|
|
||||||
void error(Args... args)
|
|
||||||
{
|
|
||||||
log(LEVEL::ERROR, args...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename... Args>
|
|
||||||
void warning(Args... args)
|
|
||||||
{
|
|
||||||
log(LEVEL::WARNING, args...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename... Args>
|
|
||||||
void info(Args... args)
|
|
||||||
{
|
|
||||||
log(LEVEL::INFO, args...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename... Args>
|
|
||||||
void verbose(Args... args)
|
|
||||||
{
|
|
||||||
log(LEVEL::VERBOSE, args...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename... Args>
|
|
||||||
void debug(const char *file, u16 line, Args... args)
|
|
||||||
{
|
|
||||||
#ifndef NDEBUG
|
|
||||||
LOG_PREFIX_DEBUG;
|
|
||||||
std::cout << file << "::" << line;
|
|
||||||
((std::cout << ' ' << std::forward<Args>(args)), ...);
|
|
||||||
std::cout << std::endl;
|
|
||||||
#else
|
|
||||||
(void)file;
|
|
||||||
(void)line;
|
|
||||||
(void)std::initializer_list<int>{((void)args, 0)...};
|
|
||||||
#endif // !NDEBUG
|
|
||||||
}
|
|
||||||
// trick
|
|
||||||
#define debug(...) debug(__FILE__, __LINE__, __VA_ARGS__)
|
|
||||||
|
|
||||||
template <typename... Args>
|
|
||||||
void trace(Args... args)
|
|
||||||
{
|
|
||||||
log(LEVEL::TRACE, args...);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace log
|
|
||||||
} // namespace hep
|
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#include <sophia/application.hpp>
|
||||||
|
|
||||||
|
#include "engine.hpp"
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
class Application::Impl {
|
||||||
|
public:
|
||||||
|
Impl(const Application::Config& config)
|
||||||
|
: engine{{config.width, config.height, config.name, config.headless}} {}
|
||||||
|
|
||||||
|
void run() { engine.run(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Engine engine;
|
||||||
|
};
|
||||||
|
|
||||||
|
Application::Application(const Config& config)
|
||||||
|
: impl{std::make_unique<Impl>(config)} {}
|
||||||
|
|
||||||
|
Application::~Application() = default;
|
||||||
|
|
||||||
|
void Application::run() { impl->run(); }
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
/**
|
||||||
|
* Based on
|
||||||
|
* https://github.com/blurrypiano/littleVulkanEngine/blob/main/src/lve_buffer.cpp
|
||||||
|
*/
|
||||||
|
#include "buffer.hpp"
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
VkDeviceSize Buffer::getAlignment(VkDeviceSize instanceSize,
|
||||||
|
VkDeviceSize minOffsetAlignment) {
|
||||||
|
if (minOffsetAlignment > 0) {
|
||||||
|
return (instanceSize + minOffsetAlignment - 1) & ~(minOffsetAlignment - 1);
|
||||||
|
}
|
||||||
|
return instanceSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer::Buffer(Device& device, vk::DeviceSize instanceSize, u32 instanceCount,
|
||||||
|
vk::BufferUsageFlags usageFlags,
|
||||||
|
vk::MemoryPropertyFlags memoryPropertyFlags,
|
||||||
|
vk::DeviceSize minOffsetAlignment)
|
||||||
|
: device{device},
|
||||||
|
instanceSize{instanceSize},
|
||||||
|
instanceCount{instanceCount},
|
||||||
|
usageFlags{usageFlags},
|
||||||
|
memoryPropertyFlags{memoryPropertyFlags} {
|
||||||
|
alignmentSize = getAlignment(instanceSize, minOffsetAlignment);
|
||||||
|
bufferSize = alignmentSize * instanceCount;
|
||||||
|
device.createBuffer(bufferSize, usageFlags, memoryPropertyFlags, buffer,
|
||||||
|
memory);
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer::~Buffer() {
|
||||||
|
unmap();
|
||||||
|
this->device.get()->destroyBuffer(this->buffer);
|
||||||
|
this->device.get()->freeMemory(this->memory);
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::Result Buffer::map(vk::DeviceSize size, vk::DeviceSize offset) {
|
||||||
|
assert(buffer && memory && "Called map on buffer before create");
|
||||||
|
|
||||||
|
vk::MemoryMapFlags emptyFlag{};
|
||||||
|
return this->device.get()->mapMemory(this->memory, offset, size, emptyFlag,
|
||||||
|
&this->mapped);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Buffer::unmap() {
|
||||||
|
if (mapped) {
|
||||||
|
this->device.get()->unmapMemory(this->memory);
|
||||||
|
mapped = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Buffer::writeToBuffer(void* data, vk::DeviceSize size,
|
||||||
|
vk::DeviceSize offset) {
|
||||||
|
assert(mapped && "Cannot copy to unmapped buffer");
|
||||||
|
|
||||||
|
if (size == VK_WHOLE_SIZE) {
|
||||||
|
memcpy(mapped, data, bufferSize);
|
||||||
|
} else {
|
||||||
|
char* memOffset = (char*)mapped;
|
||||||
|
memOffset += offset;
|
||||||
|
memcpy(memOffset, data, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::Result Buffer::flush(vk::DeviceSize size, vk::DeviceSize offset) {
|
||||||
|
vk::MappedMemoryRange mappedRange = {};
|
||||||
|
mappedRange.memory = memory;
|
||||||
|
mappedRange.offset = offset;
|
||||||
|
mappedRange.size = size;
|
||||||
|
return this->device.get()->flushMappedMemoryRanges(1, &mappedRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::Result Buffer::invalidate(vk::DeviceSize size, vk::DeviceSize offset) {
|
||||||
|
vk::MappedMemoryRange mappedRange = {};
|
||||||
|
mappedRange.memory = memory;
|
||||||
|
mappedRange.offset = offset;
|
||||||
|
mappedRange.size = size;
|
||||||
|
return this->device.get()->invalidateMappedMemoryRanges(1, &mappedRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::DescriptorBufferInfo Buffer::descriptorInfo(vk::DeviceSize size,
|
||||||
|
vk::DeviceSize offset) {
|
||||||
|
return vk::DescriptorBufferInfo{
|
||||||
|
this->buffer,
|
||||||
|
offset,
|
||||||
|
size,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Buffer::writeToIndex(void* data, int index) {
|
||||||
|
writeToBuffer(data, instanceSize, index * alignmentSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::Result Buffer::flushIndex(int index) {
|
||||||
|
return flush(alignmentSize, index * alignmentSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::DescriptorBufferInfo Buffer::descriptorInfoForIndex(int index) {
|
||||||
|
return descriptorInfo(alignmentSize, index * alignmentSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::Result Buffer::invalidateIndex(int index) {
|
||||||
|
return invalidate(alignmentSize, index * alignmentSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
/**
|
||||||
|
* Based on
|
||||||
|
* https://github.com/blurrypiano/littleVulkanEngine/blob/main/src/lve_buffer.hpp
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "device.hpp"
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
class Buffer {
|
||||||
|
public:
|
||||||
|
Buffer(const Buffer&) = delete;
|
||||||
|
Buffer& operator=(const Buffer&) = delete;
|
||||||
|
|
||||||
|
Buffer(Device& device, vk::DeviceSize instanceSize, u32 instanceCount,
|
||||||
|
vk::BufferUsageFlags usageFlags,
|
||||||
|
vk::MemoryPropertyFlags memoryPropertyFlags,
|
||||||
|
vk::DeviceSize minOffsetAlignment = 1);
|
||||||
|
~Buffer();
|
||||||
|
|
||||||
|
vk::Result map(vk::DeviceSize size = VK_WHOLE_SIZE,
|
||||||
|
vk::DeviceSize offset = 0);
|
||||||
|
void unmap();
|
||||||
|
|
||||||
|
void writeToBuffer(void* data, vk::DeviceSize size = VK_WHOLE_SIZE,
|
||||||
|
vk::DeviceSize offset = 0);
|
||||||
|
vk::Result flush(vk::DeviceSize size = VK_WHOLE_SIZE,
|
||||||
|
vk::DeviceSize offset = 0);
|
||||||
|
vk::DescriptorBufferInfo descriptorInfo(vk::DeviceSize size = VK_WHOLE_SIZE,
|
||||||
|
vk::DeviceSize offset = 0);
|
||||||
|
vk::Result invalidate(vk::DeviceSize size = VK_WHOLE_SIZE,
|
||||||
|
vk::DeviceSize offset = 0);
|
||||||
|
|
||||||
|
void writeToIndex(void* data, int index);
|
||||||
|
vk::Result flushIndex(int index);
|
||||||
|
vk::DescriptorBufferInfo descriptorInfoForIndex(int index);
|
||||||
|
vk::Result invalidateIndex(int index);
|
||||||
|
|
||||||
|
vk::Buffer getBuffer() const { return buffer; }
|
||||||
|
void* getMappedMemory() const { return mapped; }
|
||||||
|
uint32_t getInstanceCount() const { return instanceCount; }
|
||||||
|
vk::DeviceSize getInstanceSize() const { return instanceSize; }
|
||||||
|
vk::DeviceSize getAlignmentSize() const { return instanceSize; }
|
||||||
|
vk::BufferUsageFlags getUsageFlags() const { return usageFlags; }
|
||||||
|
vk::MemoryPropertyFlags getMemoryPropertyFlags() const {
|
||||||
|
return memoryPropertyFlags;
|
||||||
|
}
|
||||||
|
vk::DeviceSize getBufferSize() const { return bufferSize; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
static vk::DeviceSize getAlignment(vk::DeviceSize instanceSize,
|
||||||
|
vk::DeviceSize minOffsetAlignment);
|
||||||
|
|
||||||
|
Device& device;
|
||||||
|
void* mapped = nullptr;
|
||||||
|
vk::Buffer buffer = VK_NULL_HANDLE;
|
||||||
|
vk::DeviceMemory memory = VK_NULL_HANDLE;
|
||||||
|
|
||||||
|
vk::DeviceSize bufferSize;
|
||||||
|
uint32_t instanceCount;
|
||||||
|
vk::DeviceSize instanceSize;
|
||||||
|
vk::DeviceSize alignmentSize;
|
||||||
|
vk::BufferUsageFlags usageFlags;
|
||||||
|
vk::MemoryPropertyFlags memoryPropertyFlags;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
#include <cmath>
|
||||||
|
#include <cstring>
|
||||||
|
#include <sophia/compute_smoke_test.hpp>
|
||||||
|
#include <sophia/util/logger.hpp>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "buffer.hpp"
|
||||||
|
#include "descriptors/descriptor_pool.hpp"
|
||||||
|
#include "descriptors/descriptor_set_layout.hpp"
|
||||||
|
#include "descriptors/descriptor_writer.hpp"
|
||||||
|
#include "device.hpp"
|
||||||
|
#include "pipelines/compute_pipeline.hpp"
|
||||||
|
#include "shader.hpp"
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr u32 kElementCount = 256;
|
||||||
|
constexpr u32 kWorkgroupSize = 64;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void runComputeSmokeTest(const std::string& computeShaderPath) {
|
||||||
|
auto device = Device::Builder().headless().build();
|
||||||
|
|
||||||
|
Buffer buffer{*device, sizeof(f32), kElementCount,
|
||||||
|
vk::BufferUsageFlagBits::eStorageBuffer,
|
||||||
|
vk::MemoryPropertyFlagBits::eHostVisible |
|
||||||
|
vk::MemoryPropertyFlagBits::eHostCoherent};
|
||||||
|
|
||||||
|
std::vector<f32> input(kElementCount);
|
||||||
|
for (u32 i = 0; i < kElementCount; i++) {
|
||||||
|
input[i] = static_cast<f32>(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.map();
|
||||||
|
buffer.writeToBuffer(input.data(), sizeof(f32) * kElementCount);
|
||||||
|
buffer.unmap();
|
||||||
|
|
||||||
|
auto shader = Shader::Builder()
|
||||||
|
.fromGLSL(computeShaderPath)
|
||||||
|
.setStage(ShaderStage::COMPUTE)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
auto setLayout = DescriptorSetLayout::Builder(*device)
|
||||||
|
.addBinding(0, vk::DescriptorType::eStorageBuffer,
|
||||||
|
vk::ShaderStageFlagBits::eCompute)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
auto pool = DescriptorPool::Builder(*device)
|
||||||
|
.addPoolSize(vk::DescriptorType::eStorageBuffer, 1)
|
||||||
|
.setMaxSets(1)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
vk::DescriptorBufferInfo bufferInfo = buffer.descriptorInfo();
|
||||||
|
vk::DescriptorSet descriptorSet;
|
||||||
|
if (!DescriptorWriter(*setLayout, *pool)
|
||||||
|
.writeBuffer(0, &bufferInfo)
|
||||||
|
.build(descriptorSet)) {
|
||||||
|
log::fatal("compute smoke test: failed to allocate descriptor set");
|
||||||
|
throw std::runtime_error(
|
||||||
|
"compute smoke test: failed to allocate descriptor set");
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::DescriptorSetLayout rawSetLayout = setLayout->getDescriptorSetLayout();
|
||||||
|
vk::PipelineLayoutCreateInfo pipelineLayoutInfo{};
|
||||||
|
pipelineLayoutInfo.setLayoutCount = 1;
|
||||||
|
pipelineLayoutInfo.pSetLayouts = &rawSetLayout;
|
||||||
|
|
||||||
|
vk::UniquePipelineLayout pipelineLayout =
|
||||||
|
device->get()->createPipelineLayoutUnique(pipelineLayoutInfo);
|
||||||
|
|
||||||
|
std::unique_ptr<sophia::ComputePipeline> computePipeline =
|
||||||
|
ComputePipeline::Builder(*device)
|
||||||
|
.setShader(*shader)
|
||||||
|
.setPipelineLayout(pipelineLayout.get())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
vk::CommandBuffer commandBuffer = device->beginSingleTimeCommands();
|
||||||
|
computePipeline->bind(commandBuffer);
|
||||||
|
commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eCompute,
|
||||||
|
pipelineLayout.get(), 0, descriptorSet, {});
|
||||||
|
commandBuffer.dispatch((kElementCount + kWorkgroupSize - 1) / kWorkgroupSize,
|
||||||
|
1, 1);
|
||||||
|
device->endSingleTimeCommands(commandBuffer);
|
||||||
|
|
||||||
|
std::vector<f32> output(kElementCount);
|
||||||
|
buffer.map();
|
||||||
|
std::memcpy(output.data(), buffer.getMappedMemory(),
|
||||||
|
sizeof(f32) * kElementCount);
|
||||||
|
buffer.unmap();
|
||||||
|
|
||||||
|
u32 mismatches = 0;
|
||||||
|
for (u32 i = 0; i < kElementCount; i++) {
|
||||||
|
f32 expected = input[i] * 2.0f;
|
||||||
|
if (std::fabs(output[i] - expected) > 1e-4f) {
|
||||||
|
mismatches++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mismatches > 0) {
|
||||||
|
log::fatal("compute smoke test FAILED:", mismatches, "/", kElementCount,
|
||||||
|
"elements mismatched");
|
||||||
|
throw std::runtime_error("compute smoke test: GPU result did not match");
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info("compute smoke test PASSED:", kElementCount, "elements verified");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
#include "descriptor_pool.hpp"
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
DescriptorPool::Builder& DescriptorPool::Builder::addPoolSize(
|
||||||
|
vk::DescriptorType descriptorType, u32 count) {
|
||||||
|
this->poolSizes.push_back({descriptorType, count});
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
DescriptorPool::Builder& DescriptorPool::Builder::setPoolFlags(
|
||||||
|
vk::DescriptorPoolCreateFlags flags) {
|
||||||
|
this->poolFlags = flags;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
DescriptorPool::Builder& DescriptorPool::Builder::setMaxSets(u32 count) {
|
||||||
|
this->maxSets = count;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<DescriptorPool> DescriptorPool::Builder::build() const {
|
||||||
|
return std::make_unique<DescriptorPool>(this->device, this->maxSets,
|
||||||
|
this->poolFlags, this->poolSizes);
|
||||||
|
}
|
||||||
|
|
||||||
|
DescriptorPool::DescriptorPool(
|
||||||
|
Device& device, u32 maxSets, vk::DescriptorPoolCreateFlags poolFlags,
|
||||||
|
const std::vector<vk::DescriptorPoolSize>& poolSizes)
|
||||||
|
: device{device} {
|
||||||
|
vk::DescriptorPoolCreateInfo descriptorPoolInfo{};
|
||||||
|
descriptorPoolInfo.poolSizeCount = static_cast<uint32_t>(poolSizes.size());
|
||||||
|
descriptorPoolInfo.pPoolSizes = poolSizes.data();
|
||||||
|
descriptorPoolInfo.maxSets = maxSets;
|
||||||
|
descriptorPoolInfo.flags = poolFlags;
|
||||||
|
|
||||||
|
vk::Result result = this->device.get()->createDescriptorPool(
|
||||||
|
&descriptorPoolInfo, nullptr, &this->descriptorPool);
|
||||||
|
|
||||||
|
if (result != vk::Result::eSuccess) {
|
||||||
|
log::fatal("failed to create descriptor pool");
|
||||||
|
throw std::runtime_error("failed to create descriptor pool");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DescriptorPool::~DescriptorPool() {
|
||||||
|
this->device.get()->destroyDescriptorPool(this->descriptorPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DescriptorPool::allocateDescriptorSet(
|
||||||
|
const vk::DescriptorSetLayout descriptorSetLayout,
|
||||||
|
vk::DescriptorSet& descriptor) const {
|
||||||
|
vk::DescriptorSetAllocateInfo allocInfo{};
|
||||||
|
allocInfo.descriptorPool = descriptorPool;
|
||||||
|
allocInfo.pSetLayouts = &descriptorSetLayout;
|
||||||
|
allocInfo.descriptorSetCount = 1;
|
||||||
|
|
||||||
|
// Might want to create a "DescriptorPoolManager" class that handles this
|
||||||
|
// case, and builds a new pool whenever an old pool fills up. But this is
|
||||||
|
// beyond our current scope
|
||||||
|
vk::Result result =
|
||||||
|
this->device.get()->allocateDescriptorSets(&allocInfo, &descriptor);
|
||||||
|
|
||||||
|
if (result == vk::Result::eSuccess) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DescriptorPool::freeDescriptors(
|
||||||
|
std::vector<vk::DescriptorSet>& descriptors) const {
|
||||||
|
vk::Result result = this->device.get()->freeDescriptorSets(
|
||||||
|
this->descriptorPool, static_cast<u32>(descriptors.size()),
|
||||||
|
descriptors.data());
|
||||||
|
|
||||||
|
if (result != vk::Result::eSuccess) {
|
||||||
|
log::fatal("failed to free descriptor sets");
|
||||||
|
throw std::runtime_error("failed to free descriptor sets");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DescriptorPool::resetPool() {
|
||||||
|
this->device.get()->resetDescriptorPool(this->descriptorPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "device.hpp"
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
class DescriptorPool {
|
||||||
|
public:
|
||||||
|
DescriptorPool(const DescriptorPool&) = delete;
|
||||||
|
DescriptorPool& operator=(const DescriptorPool&) = delete;
|
||||||
|
|
||||||
|
class Builder {
|
||||||
|
public:
|
||||||
|
Builder(Device& device) : device{device} {}
|
||||||
|
|
||||||
|
Builder& addPoolSize(vk::DescriptorType descriptorType, u32 count);
|
||||||
|
Builder& setPoolFlags(vk::DescriptorPoolCreateFlags flags);
|
||||||
|
Builder& setMaxSets(u32 count);
|
||||||
|
std::unique_ptr<DescriptorPool> build() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Device& device;
|
||||||
|
std::vector<vk::DescriptorPoolSize> poolSizes{};
|
||||||
|
u32 maxSets = 1000;
|
||||||
|
vk::DescriptorPoolCreateFlags poolFlags{};
|
||||||
|
};
|
||||||
|
|
||||||
|
DescriptorPool(Device& device, u32 maxSets,
|
||||||
|
vk::DescriptorPoolCreateFlags poolFlags,
|
||||||
|
const std::vector<vk::DescriptorPoolSize>& poolSizes);
|
||||||
|
~DescriptorPool();
|
||||||
|
|
||||||
|
bool allocateDescriptorSet(const vk::DescriptorSetLayout descriptorSetLayout,
|
||||||
|
vk::DescriptorSet& descriptor) const;
|
||||||
|
|
||||||
|
void freeDescriptors(std::vector<vk::DescriptorSet>& descriptors) const;
|
||||||
|
|
||||||
|
void resetPool();
|
||||||
|
|
||||||
|
const vk::DescriptorPool& get() { return this->descriptorPool; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Device& device;
|
||||||
|
vk::DescriptorPool descriptorPool;
|
||||||
|
|
||||||
|
friend class DescriptorWriter;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
#include "descriptor_set_layout.hpp"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
DescriptorSetLayout::Builder& DescriptorSetLayout::Builder::addBinding(
|
||||||
|
u32 binding, vk::DescriptorType descriptorType,
|
||||||
|
vk::ShaderStageFlags stageFlags, u32 count) {
|
||||||
|
assert(this->bindings.count(binding) == 0 && "binding already in use");
|
||||||
|
|
||||||
|
vk::DescriptorSetLayoutBinding layoutBinding{binding, descriptorType, count,
|
||||||
|
stageFlags};
|
||||||
|
this->bindings[binding] = layoutBinding;
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<DescriptorSetLayout> DescriptorSetLayout::Builder::build()
|
||||||
|
const {
|
||||||
|
return std::make_unique<DescriptorSetLayout>(this->device, this->bindings);
|
||||||
|
}
|
||||||
|
|
||||||
|
DescriptorSetLayout::DescriptorSetLayout(
|
||||||
|
Device& device,
|
||||||
|
std::unordered_map<u32, vk::DescriptorSetLayoutBinding> bindings)
|
||||||
|
: device{device}, bindings{bindings} {
|
||||||
|
std::vector<vk::DescriptorSetLayoutBinding> setLayoutBindings{};
|
||||||
|
|
||||||
|
for (auto binding : bindings) {
|
||||||
|
setLayoutBindings.push_back(binding.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::DescriptorSetLayoutCreateInfo layoutCreateInfo{};
|
||||||
|
layoutCreateInfo.bindingCount =
|
||||||
|
static_cast<uint32_t>(setLayoutBindings.size());
|
||||||
|
layoutCreateInfo.pBindings = setLayoutBindings.data();
|
||||||
|
|
||||||
|
vk::Result result = this->device.get()->createDescriptorSetLayout(
|
||||||
|
&layoutCreateInfo, nullptr, &this->layout);
|
||||||
|
|
||||||
|
if (result != vk::Result::eSuccess) {
|
||||||
|
log::fatal("failed to create descriptor pool");
|
||||||
|
throw std::runtime_error("failed to create descriptor set layout");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DescriptorSetLayout::~DescriptorSetLayout() {
|
||||||
|
this->device.get()->destroyDescriptorSetLayout(this->layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "device.hpp"
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
class DescriptorSetLayout {
|
||||||
|
public:
|
||||||
|
DescriptorSetLayout(const DescriptorSetLayout&) = delete;
|
||||||
|
DescriptorSetLayout& operator=(const DescriptorSetLayout&) = delete;
|
||||||
|
|
||||||
|
class Builder {
|
||||||
|
public:
|
||||||
|
Builder(Device& device) : device{device} {}
|
||||||
|
|
||||||
|
Builder& addBinding(u32 binding, vk::DescriptorType descriptorType,
|
||||||
|
vk::ShaderStageFlags stageFlags, u32 count = 1);
|
||||||
|
|
||||||
|
std::unique_ptr<DescriptorSetLayout> build() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Device& device;
|
||||||
|
std::unordered_map<u32, vk::DescriptorSetLayoutBinding> bindings{};
|
||||||
|
};
|
||||||
|
|
||||||
|
DescriptorSetLayout(
|
||||||
|
Device& device,
|
||||||
|
std::unordered_map<u32, vk::DescriptorSetLayoutBinding> bindings);
|
||||||
|
~DescriptorSetLayout();
|
||||||
|
|
||||||
|
vk::DescriptorSetLayout getDescriptorSetLayout() const {
|
||||||
|
return this->layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Device& device;
|
||||||
|
vk::DescriptorSetLayout layout;
|
||||||
|
std::unordered_map<u32, vk::DescriptorSetLayoutBinding> bindings;
|
||||||
|
|
||||||
|
friend class DescriptorWriter;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
#include "descriptor_writer.hpp"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
DescriptorWriter::DescriptorWriter(DescriptorSetLayout& setLayout,
|
||||||
|
DescriptorPool& pool)
|
||||||
|
: setLayout{setLayout}, pool{pool} {}
|
||||||
|
|
||||||
|
DescriptorWriter& DescriptorWriter::writeBuffer(
|
||||||
|
u32 binding, vk::DescriptorBufferInfo* bufferInfo) {
|
||||||
|
assert(this->setLayout.bindings.count(binding) == 1 &&
|
||||||
|
"setLayout does not contain specified binding");
|
||||||
|
|
||||||
|
auto& bindingDescription = this->setLayout.bindings[binding];
|
||||||
|
|
||||||
|
assert(bindingDescription.descriptorCount == 1 &&
|
||||||
|
"binding single descriptor info, but binding expects multiple");
|
||||||
|
|
||||||
|
vk::WriteDescriptorSet write{};
|
||||||
|
write.descriptorType = bindingDescription.descriptorType;
|
||||||
|
write.dstBinding = binding;
|
||||||
|
write.pBufferInfo = bufferInfo;
|
||||||
|
write.descriptorCount = 1;
|
||||||
|
|
||||||
|
writes.push_back(write);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
DescriptorWriter& DescriptorWriter::writeImage(
|
||||||
|
u32 binding, vk::DescriptorImageInfo* imageInfo) {
|
||||||
|
assert(this->setLayout.bindings.count(binding) == 1 &&
|
||||||
|
"setLayout does not contain specified binding");
|
||||||
|
|
||||||
|
auto& bindingDescription = this->setLayout.bindings[binding];
|
||||||
|
|
||||||
|
assert(bindingDescription.descriptorCount == 1 &&
|
||||||
|
"binding single descriptor info, but binding expects multiple");
|
||||||
|
|
||||||
|
vk::WriteDescriptorSet write{};
|
||||||
|
write.descriptorType = bindingDescription.descriptorType;
|
||||||
|
write.dstBinding = binding;
|
||||||
|
write.pImageInfo = imageInfo;
|
||||||
|
write.descriptorCount = 1;
|
||||||
|
|
||||||
|
writes.push_back(write);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DescriptorWriter::build(vk::DescriptorSet& set) {
|
||||||
|
bool success =
|
||||||
|
this->pool.allocateDescriptorSet(setLayout.getDescriptorSetLayout(), set);
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
overwrite(set);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DescriptorWriter::overwrite(vk::DescriptorSet& set) {
|
||||||
|
for (auto& write : writes) {
|
||||||
|
write.dstSet = set;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->pool.device.get()->updateDescriptorSets(writes.size(), writes.data(), 0,
|
||||||
|
nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "descriptor_pool.hpp"
|
||||||
|
#include "descriptor_set_layout.hpp"
|
||||||
|
#include "device.hpp"
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
class DescriptorWriter {
|
||||||
|
public:
|
||||||
|
DescriptorWriter(DescriptorSetLayout& setLayout, DescriptorPool& pool);
|
||||||
|
|
||||||
|
DescriptorWriter& writeBuffer(u32 binding,
|
||||||
|
vk::DescriptorBufferInfo* bufferInfo);
|
||||||
|
DescriptorWriter& writeImage(u32 binding, vk::DescriptorImageInfo* imageInfo);
|
||||||
|
|
||||||
|
bool build(vk::DescriptorSet& set);
|
||||||
|
void overwrite(vk::DescriptorSet& set);
|
||||||
|
|
||||||
|
private:
|
||||||
|
DescriptorSetLayout& setLayout;
|
||||||
|
DescriptorPool& pool;
|
||||||
|
std::vector<vk::WriteDescriptorSet> writes;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
+119
-52
@@ -3,25 +3,28 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include <set>
|
#include <set>
|
||||||
|
#include <sophia/util/logger.hpp>
|
||||||
|
|
||||||
#include "util/logger.hpp"
|
namespace sophia {
|
||||||
|
|
||||||
namespace hep {
|
static VKAPI_ATTR vk::Bool32 VKAPI_CALL
|
||||||
|
debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT m_severity,
|
||||||
static VKAPI_ATTR VkBool32 VKAPI_CALL
|
vk::DebugUtilsMessageTypeFlagsEXT m_type,
|
||||||
debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT m_severity,
|
const vk::DebugUtilsMessengerCallbackDataEXT* pCallback_data,
|
||||||
VkDebugUtilsMessageTypeFlagsEXT m_type,
|
|
||||||
const VkDebugUtilsMessengerCallbackDataEXT* pCallback_data,
|
|
||||||
void* pUser_data) {
|
void* pUser_data) {
|
||||||
(void)m_type;
|
(void)m_type;
|
||||||
(void)pCallback_data;
|
(void)pCallback_data;
|
||||||
(void)pUser_data;
|
(void)pUser_data;
|
||||||
|
|
||||||
if (m_severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
|
if (m_severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError) {
|
||||||
log::error(pCallback_data->pMessage);
|
log::error(pCallback_data->pMessage);
|
||||||
return VK_SUCCESS;
|
return VK_SUCCESS;
|
||||||
} else if (m_severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) {
|
} else if (m_severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) {
|
||||||
log::warning(pCallback_data->pMessage);
|
log::warning(pCallback_data->pMessage);
|
||||||
|
} else if (m_severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo) {
|
||||||
|
log::info(pCallback_data->pMessage);
|
||||||
|
} else if (m_severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose) {
|
||||||
|
log::verbose(pCallback_data->pMessage);
|
||||||
} else {
|
} else {
|
||||||
log::info(pCallback_data->pMessage);
|
log::info(pCallback_data->pMessage);
|
||||||
}
|
}
|
||||||
@@ -30,8 +33,7 @@ debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT m_severity,
|
|||||||
}
|
}
|
||||||
|
|
||||||
VkResult createDebugUtilsMessengerEXT(
|
VkResult createDebugUtilsMessengerEXT(
|
||||||
VkInstance instance,
|
VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo,
|
||||||
const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo,
|
|
||||||
const VkAllocationCallbacks* pAllocator,
|
const VkAllocationCallbacks* pAllocator,
|
||||||
VkDebugUtilsMessengerEXT* pCallback) {
|
VkDebugUtilsMessengerEXT* pCallback) {
|
||||||
auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(
|
auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(
|
||||||
@@ -48,13 +50,21 @@ void destroyDebugUtilsMessengerEXT(VkInstance instance,
|
|||||||
const VkAllocationCallbacks* pAllocator) {
|
const VkAllocationCallbacks* pAllocator) {
|
||||||
auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(
|
auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(
|
||||||
instance, "vkDestroyDebugUtilsMessengerEXT");
|
instance, "vkDestroyDebugUtilsMessengerEXT");
|
||||||
if (func != nullptr) { func(instance, callback, pAllocator); }
|
if (func != nullptr) {
|
||||||
|
func(instance, callback, pAllocator);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Device::Device(Window& window) : window{window} {
|
Device::Device(Window* window)
|
||||||
|
: headless{window == nullptr},
|
||||||
|
enabledExtensions{headless ? std::vector<const char*>{}
|
||||||
|
: std::vector<const char*>{
|
||||||
|
VK_KHR_SWAPCHAIN_EXTENSION_NAME}} {
|
||||||
createVulkanInstance();
|
createVulkanInstance();
|
||||||
setupDebugMessenger();
|
setupDebugMessenger();
|
||||||
this->window.createSurface(*instance, surface);
|
if (!this->headless) {
|
||||||
|
window->createSurface(*instance, surface);
|
||||||
|
}
|
||||||
pickPhysicalDevice();
|
pickPhysicalDevice();
|
||||||
createLogicalDevice();
|
createLogicalDevice();
|
||||||
createCommandPool();
|
createCommandPool();
|
||||||
@@ -70,8 +80,42 @@ Device::~Device() {
|
|||||||
this->device->destroyCommandPool(commandPool);
|
this->device->destroyCommandPool(commandPool);
|
||||||
log::trace("destroyed vk::CommandPool");
|
log::trace("destroyed vk::CommandPool");
|
||||||
|
|
||||||
this->instance->destroySurfaceKHR(surface);
|
if (!this->headless) {
|
||||||
log::trace("destroyed vk::SurfaceKHR");
|
this->instance->destroySurfaceKHR(surface);
|
||||||
|
log::trace("destroyed vk::SurfaceKHR");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Device::Builder& Device::Builder::withWindow(Window& window) {
|
||||||
|
this->window = &window;
|
||||||
|
this->headlessRequested = false;
|
||||||
|
this->modeSet = true;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Device::Builder& Device::Builder::headless() {
|
||||||
|
this->window = nullptr;
|
||||||
|
this->headlessRequested = true;
|
||||||
|
this->modeSet = true;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Device> Device::Builder::build() const {
|
||||||
|
if (!this->modeSet) {
|
||||||
|
log::fatal(
|
||||||
|
"failed to build device: neither withWindow() nor headless() was "
|
||||||
|
"called");
|
||||||
|
throw std::runtime_error(
|
||||||
|
"failed to build device: no window/headless mode specified");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->headlessRequested) {
|
||||||
|
log::verbose("building headless Device");
|
||||||
|
return std::unique_ptr<Device>(new Device(nullptr));
|
||||||
|
} else {
|
||||||
|
log::verbose("building windowed Device");
|
||||||
|
return std::unique_ptr<Device>(new Device(this->window));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 Device::findMemoryType(u32 typeFilter, vk::MemoryPropertyFlags properties) {
|
u32 Device::findMemoryType(u32 typeFilter, vk::MemoryPropertyFlags properties) {
|
||||||
@@ -91,8 +135,7 @@ u32 Device::findMemoryType(u32 typeFilter, vk::MemoryPropertyFlags properties) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vk::Format Device::findSupportedFormat(
|
vk::Format Device::findSupportedFormat(
|
||||||
const std::vector<vk::Format>& candidates,
|
const std::vector<vk::Format>& candidates, vk::ImageTiling tiling,
|
||||||
vk::ImageTiling tiling,
|
|
||||||
vk::FormatFeatureFlags features) {
|
vk::FormatFeatureFlags features) {
|
||||||
for (vk::Format format : candidates) {
|
for (vk::Format format : candidates) {
|
||||||
vk::FormatProperties properties =
|
vk::FormatProperties properties =
|
||||||
@@ -110,11 +153,9 @@ vk::Format Device::findSupportedFormat(
|
|||||||
throw std::runtime_error("failed to find supported format");
|
throw std::runtime_error("failed to find supported format");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Device::createBuffer(vk::DeviceSize size,
|
void Device::createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage,
|
||||||
vk::BufferUsageFlags usage,
|
|
||||||
vk::MemoryPropertyFlags properties,
|
vk::MemoryPropertyFlags properties,
|
||||||
vk::Buffer& buffer,
|
vk::Buffer& buffer, vk::DeviceMemory& bufferMemory) {
|
||||||
vk::DeviceMemory& bufferMemory) {
|
|
||||||
vk::BufferCreateInfo bufferInfo = {};
|
vk::BufferCreateInfo bufferInfo = {};
|
||||||
bufferInfo.size = size;
|
bufferInfo.size = size;
|
||||||
bufferInfo.usage = usage;
|
bufferInfo.usage = usage;
|
||||||
@@ -156,7 +197,9 @@ vk::CommandBuffer Device::beginSingleTimeCommands() {
|
|||||||
try {
|
try {
|
||||||
vk::Result result =
|
vk::Result result =
|
||||||
this->device->allocateCommandBuffers(&allocInfo, &commandBuffer);
|
this->device->allocateCommandBuffers(&allocInfo, &commandBuffer);
|
||||||
if (result != vk::Result::eSuccess) { throw vk::SystemError(result); }
|
if (result != vk::Result::eSuccess) {
|
||||||
|
throw vk::SystemError(result);
|
||||||
|
}
|
||||||
} catch (const vk::SystemError& error) {
|
} catch (const vk::SystemError& error) {
|
||||||
log::error("failed to allocate single time commandBuffer. Error: ",
|
log::error("failed to allocate single time commandBuffer. Error: ",
|
||||||
error.what());
|
error.what());
|
||||||
@@ -168,7 +211,9 @@ vk::CommandBuffer Device::beginSingleTimeCommands() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
vk::Result result = commandBuffer.begin(&beginInfo);
|
vk::Result result = commandBuffer.begin(&beginInfo);
|
||||||
if (result != vk::Result::eSuccess) { throw vk::SystemError(result); }
|
if (result != vk::Result::eSuccess) {
|
||||||
|
throw vk::SystemError(result);
|
||||||
|
}
|
||||||
} catch (const vk::SystemError& error) {
|
} catch (const vk::SystemError& error) {
|
||||||
log::error("failed to begin single time commandBuffer. Error: ",
|
log::error("failed to begin single time commandBuffer. Error: ",
|
||||||
error.what());
|
error.what());
|
||||||
@@ -191,8 +236,7 @@ void Device::endSingleTimeCommands(vk::CommandBuffer commandBuffer) {
|
|||||||
this->device->freeCommandBuffers(commandPool, commandBuffer);
|
this->device->freeCommandBuffers(commandPool, commandBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Device::copyBuffer(vk::Buffer sourceBuffer,
|
void Device::copyBuffer(vk::Buffer sourceBuffer, vk::Buffer destinationBuffer,
|
||||||
vk::Buffer destinationBuffer,
|
|
||||||
vk::DeviceSize size) {
|
vk::DeviceSize size) {
|
||||||
vk::CommandBuffer commandBuffer = beginSingleTimeCommands();
|
vk::CommandBuffer commandBuffer = beginSingleTimeCommands();
|
||||||
|
|
||||||
@@ -210,7 +254,9 @@ void Device::createImageWithInfo(const vk::ImageCreateInfo& imageInfo,
|
|||||||
vk::DeviceMemory& imageMemory) {
|
vk::DeviceMemory& imageMemory) {
|
||||||
try {
|
try {
|
||||||
vk::Result result = this->device->createImage(&imageInfo, nullptr, &image);
|
vk::Result result = this->device->createImage(&imageInfo, nullptr, &image);
|
||||||
if (result != vk::Result::eSuccess) { throw vk::SystemError(result); }
|
if (result != vk::Result::eSuccess) {
|
||||||
|
throw vk::SystemError(result);
|
||||||
|
}
|
||||||
} catch (const vk::SystemError& error) {
|
} catch (const vk::SystemError& error) {
|
||||||
log::fatal("failed to create image. Error: ", error.what());
|
log::fatal("failed to create image. Error: ", error.what());
|
||||||
throw std::runtime_error("failed to create image");
|
throw std::runtime_error("failed to create image");
|
||||||
@@ -227,7 +273,9 @@ void Device::createImageWithInfo(const vk::ImageCreateInfo& imageInfo,
|
|||||||
try {
|
try {
|
||||||
vk::Result result =
|
vk::Result result =
|
||||||
this->device->allocateMemory(&allocateInfo, nullptr, &imageMemory);
|
this->device->allocateMemory(&allocateInfo, nullptr, &imageMemory);
|
||||||
if (result != vk::Result::eSuccess) { throw vk::SystemError(result); }
|
if (result != vk::Result::eSuccess) {
|
||||||
|
throw vk::SystemError(result);
|
||||||
|
}
|
||||||
} catch (const vk::SystemError& error) {
|
} catch (const vk::SystemError& error) {
|
||||||
log::fatal("failed to allocate image memory. Error: ", error.what());
|
log::fatal("failed to allocate image memory. Error: ", error.what());
|
||||||
throw std::runtime_error("failed to allocate image memory");
|
throw std::runtime_error("failed to allocate image memory");
|
||||||
@@ -243,7 +291,7 @@ void Device::createImageWithInfo(const vk::ImageCreateInfo& imageInfo,
|
|||||||
|
|
||||||
void Device::populateImGuiInitInfo(ImGui_ImplVulkan_InitInfo& initInfo) {
|
void Device::populateImGuiInitInfo(ImGui_ImplVulkan_InitInfo& initInfo) {
|
||||||
initInfo.Instance = this->instance.get();
|
initInfo.Instance = this->instance.get();
|
||||||
initInfo.ApiVersion = HEP_VULKAN_API_VERSION;
|
initInfo.ApiVersion = SOPHIA_VULKAN_API_VERSION;
|
||||||
initInfo.PhysicalDevice = this->physicalDevice;
|
initInfo.PhysicalDevice = this->physicalDevice;
|
||||||
initInfo.Device = this->device.get();
|
initInfo.Device = this->device.get();
|
||||||
|
|
||||||
@@ -286,19 +334,23 @@ bool Device::checkValidationLayerSupport() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!layerFound) { return false; }
|
if (!layerFound) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<const char*> Device::getRequiredExtensions() {
|
std::vector<const char*> Device::getRequiredExtensions() {
|
||||||
u32 glfw_extension_count = 0;
|
std::vector<const char*> extensions;
|
||||||
const char** glfw_extensions;
|
|
||||||
glfw_extensions = glfwGetRequiredInstanceExtensions(&glfw_extension_count);
|
|
||||||
|
|
||||||
std::vector<const char*> extensions(glfw_extensions,
|
if (!this->headless) {
|
||||||
glfw_extensions + glfw_extension_count);
|
u32 glfw_extension_count = 0;
|
||||||
|
const char** glfw_extensions =
|
||||||
|
glfwGetRequiredInstanceExtensions(&glfw_extension_count);
|
||||||
|
extensions.assign(glfw_extensions, glfw_extensions + glfw_extension_count);
|
||||||
|
}
|
||||||
|
|
||||||
if (enableValidationLayers) {
|
if (enableValidationLayers) {
|
||||||
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
||||||
@@ -309,7 +361,9 @@ std::vector<const char*> Device::getRequiredExtensions() {
|
|||||||
vk::Result result = vk::enumerateInstanceExtensionProperties(
|
vk::Result result = vk::enumerateInstanceExtensionProperties(
|
||||||
nullptr, &available_extension_count, nullptr);
|
nullptr, &available_extension_count, nullptr);
|
||||||
|
|
||||||
if (result != vk::Result::eSuccess) { throw std::exception(); }
|
if (result != vk::Result::eSuccess) {
|
||||||
|
throw std::exception();
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<vk::ExtensionProperties> available_extensions(
|
std::vector<vk::ExtensionProperties> available_extensions(
|
||||||
available_extension_count);
|
available_extension_count);
|
||||||
@@ -317,7 +371,9 @@ std::vector<const char*> Device::getRequiredExtensions() {
|
|||||||
result = vk::enumerateInstanceExtensionProperties(
|
result = vk::enumerateInstanceExtensionProperties(
|
||||||
nullptr, &available_extension_count, available_extensions.data());
|
nullptr, &available_extension_count, available_extensions.data());
|
||||||
|
|
||||||
if (result != vk::Result::eSuccess) { throw std::exception(); }
|
if (result != vk::Result::eSuccess) {
|
||||||
|
throw std::exception();
|
||||||
|
}
|
||||||
|
|
||||||
log::verbose("Number of available extensions: ", available_extension_count);
|
log::verbose("Number of available extensions: ", available_extension_count);
|
||||||
|
|
||||||
@@ -326,7 +382,9 @@ std::vector<const char*> Device::getRequiredExtensions() {
|
|||||||
std::cout << '\t' << e.extensionName << '\n';
|
std::cout << '\t' << e.extensionName << '\n';
|
||||||
}
|
}
|
||||||
std::cout << "Required extensions:\n";
|
std::cout << "Required extensions:\n";
|
||||||
for (const auto& e : extensions) { std::cout << "\t" << e << '\n'; }
|
for (const auto& e : extensions) {
|
||||||
|
std::cout << "\t" << e << '\n';
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return extensions;
|
return extensions;
|
||||||
@@ -339,7 +397,7 @@ void Device::createVulkanInstance() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vk::ApplicationInfo app_info = vk::ApplicationInfo(
|
vk::ApplicationInfo app_info = vk::ApplicationInfo(
|
||||||
"Hephaestus", VK_MAKE_VERSION(1, 0, 0), "Hephaestus Vulkan Engine",
|
"Sophia", VK_MAKE_VERSION(1, 0, 0), "Sophia Vulkan Engine",
|
||||||
VK_MAKE_VERSION(1, 0, 0), VK_API_VERSION_1_3);
|
VK_MAKE_VERSION(1, 0, 0), VK_API_VERSION_1_3);
|
||||||
|
|
||||||
auto extensions = getRequiredExtensions();
|
auto extensions = getRequiredExtensions();
|
||||||
@@ -369,11 +427,14 @@ QueueFamilyIndices Device::findQueueFamilies(vk::PhysicalDevice device) {
|
|||||||
indices.graphicsFamily = i;
|
indices.graphicsFamily = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (queueFamily.queueCount > 0 && device.getSurfaceSupportKHR(i, surface)) {
|
if (!this->headless && queueFamily.queueCount > 0 &&
|
||||||
|
device.getSurfaceSupportKHR(i, surface)) {
|
||||||
indices.presentFamily = i;
|
indices.presentFamily = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (indices.isComplete()) { break; }
|
if (indices.isComplete()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -396,6 +457,10 @@ bool Device::isPhysicalDeviceSuitable(const vk::PhysicalDevice& device) {
|
|||||||
|
|
||||||
bool extensionsSupported = checkDeviceExtensionSupport(device);
|
bool extensionsSupported = checkDeviceExtensionSupport(device);
|
||||||
|
|
||||||
|
if (this->headless) {
|
||||||
|
return indices.graphicsFamily.has_value() && extensionsSupported;
|
||||||
|
}
|
||||||
|
|
||||||
bool swapchainAdequate = false;
|
bool swapchainAdequate = false;
|
||||||
if (extensionsSupported) {
|
if (extensionsSupported) {
|
||||||
SwapchainSupportDetails swapchainSupport = querySwapchainSupport(device);
|
SwapchainSupportDetails swapchainSupport = querySwapchainSupport(device);
|
||||||
@@ -436,10 +501,12 @@ void Device::pickPhysicalDevice() {
|
|||||||
void Device::createLogicalDevice() {
|
void Device::createLogicalDevice() {
|
||||||
QueueFamilyIndices indices = findQueueFamilies(this->physicalDevice);
|
QueueFamilyIndices indices = findQueueFamilies(this->physicalDevice);
|
||||||
|
|
||||||
std::vector<vk::DeviceQueueCreateInfo> queueCreateInfos;
|
std::set<uint32_t> uniqueQueueFamilies = {indices.graphicsFamily.value()};
|
||||||
std::set<uint32_t> uniqueQueueFamilies = {indices.graphicsFamily.value(),
|
if (!this->headless) {
|
||||||
indices.presentFamily.value()};
|
uniqueQueueFamilies.insert(indices.presentFamily.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<vk::DeviceQueueCreateInfo> queueCreateInfos;
|
||||||
float queuePriority = 1.0f;
|
float queuePriority = 1.0f;
|
||||||
|
|
||||||
for (uint32_t queueFamily : uniqueQueueFamilies) {
|
for (uint32_t queueFamily : uniqueQueueFamilies) {
|
||||||
@@ -447,22 +514,20 @@ void Device::createLogicalDevice() {
|
|||||||
{vk::DeviceQueueCreateFlags(), queueFamily, 1, &queuePriority});
|
{vk::DeviceQueueCreateFlags(), queueFamily, 1, &queuePriority});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vk::PhysicalDeviceVulkan13Features vulkan13Features{};
|
||||||
|
vulkan13Features.dynamicRendering = vk::True;
|
||||||
|
|
||||||
auto deviceFeatures = vk::PhysicalDeviceFeatures();
|
auto deviceFeatures = vk::PhysicalDeviceFeatures();
|
||||||
auto createInfo = vk::DeviceCreateInfo(
|
auto createInfo = vk::DeviceCreateInfo(
|
||||||
vk::DeviceCreateFlags(), static_cast<uint32_t>(queueCreateInfos.size()),
|
vk::DeviceCreateFlags(), static_cast<uint32_t>(queueCreateInfos.size()),
|
||||||
queueCreateInfos.data());
|
queueCreateInfos.data());
|
||||||
createInfo.pEnabledFeatures = &deviceFeatures;
|
createInfo.pEnabledFeatures = &deviceFeatures;
|
||||||
|
createInfo.pNext = &vulkan13Features;
|
||||||
|
|
||||||
createInfo.enabledExtensionCount =
|
createInfo.enabledExtensionCount =
|
||||||
static_cast<uint32_t>(this->enabledExtensions.size());
|
static_cast<uint32_t>(this->enabledExtensions.size());
|
||||||
createInfo.ppEnabledExtensionNames = this->enabledExtensions.data();
|
createInfo.ppEnabledExtensionNames = this->enabledExtensions.data();
|
||||||
|
|
||||||
if (this->enableValidationLayers) {
|
|
||||||
createInfo.enabledLayerCount =
|
|
||||||
static_cast<uint32_t>(this->enabledLayers.size());
|
|
||||||
createInfo.ppEnabledLayerNames = this->enabledLayers.data();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this->device = this->physicalDevice.createDeviceUnique(createInfo);
|
this->device = this->physicalDevice.createDeviceUnique(createInfo);
|
||||||
log::trace("created vk::Device.");
|
log::trace("created vk::Device.");
|
||||||
@@ -472,7 +537,9 @@ void Device::createLogicalDevice() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this->graphicsQueue = device->getQueue(indices.graphicsFamily.value(), 0);
|
this->graphicsQueue = device->getQueue(indices.graphicsFamily.value(), 0);
|
||||||
this->presentQueue = device->getQueue(indices.presentFamily.value(), 0);
|
if (!this->headless) {
|
||||||
|
this->presentQueue = device->getQueue(indices.presentFamily.value(), 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SwapchainSupportDetails Device::querySwapchainSupport(
|
SwapchainSupportDetails Device::querySwapchainSupport(
|
||||||
@@ -502,4 +569,4 @@ void Device::createCommandPool() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace hep
|
} // namespace sophia
|
||||||
|
|||||||
@@ -3,16 +3,17 @@
|
|||||||
#include <imgui_impl_vulkan.h>
|
#include <imgui_impl_vulkan.h>
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <sophia/types.hpp>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <vulkan/vulkan.hpp>
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
#include "types.hpp"
|
|
||||||
#include "window.hpp"
|
#include "window.hpp"
|
||||||
|
|
||||||
#define HEP_VULKAN_API_VERSION VK_API_VERSION_1_3
|
#define SOPHIA_VULKAN_API_VERSION VK_API_VERSION_1_3
|
||||||
|
|
||||||
namespace hep {
|
namespace sophia {
|
||||||
|
|
||||||
struct QueueFamilyIndices {
|
struct QueueFamilyIndices {
|
||||||
std::optional<uint32_t> graphicsFamily;
|
std::optional<uint32_t> graphicsFamily;
|
||||||
@@ -31,12 +32,13 @@ struct SwapchainSupportDetails {
|
|||||||
|
|
||||||
class Device {
|
class Device {
|
||||||
public:
|
public:
|
||||||
|
class Builder;
|
||||||
|
|
||||||
Device(const Device&) = delete;
|
Device(const Device&) = delete;
|
||||||
Device& operator=(const Device&) = delete;
|
Device& operator=(const Device&) = delete;
|
||||||
Device(Device&&) = delete;
|
Device(Device&&) = delete;
|
||||||
Device& operator=(Device&&) = delete;
|
Device& operator=(Device&&) = delete;
|
||||||
|
|
||||||
Device(Window& window);
|
|
||||||
~Device();
|
~Device();
|
||||||
|
|
||||||
const vk::Device* get() const {
|
const vk::Device* get() const {
|
||||||
@@ -65,23 +67,19 @@ class Device {
|
|||||||
vk::ImageTiling tiling,
|
vk::ImageTiling tiling,
|
||||||
vk::FormatFeatureFlags features);
|
vk::FormatFeatureFlags features);
|
||||||
|
|
||||||
void createBuffer(vk::DeviceSize size,
|
void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage,
|
||||||
vk::BufferUsageFlags usage,
|
vk::MemoryPropertyFlags properties, vk::Buffer& buffer,
|
||||||
vk::MemoryPropertyFlags properties,
|
|
||||||
vk::Buffer& buffer,
|
|
||||||
vk::DeviceMemory& bufferMemory);
|
vk::DeviceMemory& bufferMemory);
|
||||||
|
|
||||||
vk::CommandBuffer beginSingleTimeCommands();
|
vk::CommandBuffer beginSingleTimeCommands();
|
||||||
|
|
||||||
void endSingleTimeCommands(vk::CommandBuffer commandBuffer);
|
void endSingleTimeCommands(vk::CommandBuffer commandBuffer);
|
||||||
|
|
||||||
void copyBuffer(vk::Buffer sourceBuffer,
|
void copyBuffer(vk::Buffer sourceBuffer, vk::Buffer destinationBuffer,
|
||||||
vk::Buffer destinationBuffer,
|
|
||||||
vk::DeviceSize size);
|
vk::DeviceSize size);
|
||||||
|
|
||||||
void createImageWithInfo(const vk::ImageCreateInfo& imageInfo,
|
void createImageWithInfo(const vk::ImageCreateInfo& imageInfo,
|
||||||
vk::MemoryPropertyFlags properties,
|
vk::MemoryPropertyFlags properties, vk::Image& image,
|
||||||
vk::Image& image,
|
|
||||||
vk::DeviceMemory& imageMemory);
|
vk::DeviceMemory& imageMemory);
|
||||||
|
|
||||||
void populateImGuiInitInfo(ImGui_ImplVulkan_InitInfo& initInfo);
|
void populateImGuiInitInfo(ImGui_ImplVulkan_InitInfo& initInfo);
|
||||||
@@ -89,6 +87,8 @@ class Device {
|
|||||||
vk::PhysicalDeviceProperties properties;
|
vk::PhysicalDeviceProperties properties;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
Device(Window* window);
|
||||||
|
|
||||||
void setupDebugMessenger();
|
void setupDebugMessenger();
|
||||||
|
|
||||||
bool checkValidationLayerSupport();
|
bool checkValidationLayerSupport();
|
||||||
@@ -108,7 +108,7 @@ class Device {
|
|||||||
vk::UniqueInstance instance;
|
vk::UniqueInstance instance;
|
||||||
VkDebugUtilsMessengerEXT debugMessenger;
|
VkDebugUtilsMessengerEXT debugMessenger;
|
||||||
|
|
||||||
Window& window;
|
const bool headless;
|
||||||
vk::SurfaceKHR surface;
|
vk::SurfaceKHR surface;
|
||||||
|
|
||||||
vk::PhysicalDevice physicalDevice = VK_NULL_HANDLE;
|
vk::PhysicalDevice physicalDevice = VK_NULL_HANDLE;
|
||||||
@@ -127,8 +127,22 @@ class Device {
|
|||||||
const std::vector<const char*> enabledLayers = {
|
const std::vector<const char*> enabledLayers = {
|
||||||
"VK_LAYER_KHRONOS_validation"};
|
"VK_LAYER_KHRONOS_validation"};
|
||||||
#endif
|
#endif
|
||||||
const std::vector<const char*> enabledExtensions = {
|
const std::vector<const char*> enabledExtensions;
|
||||||
VK_KHR_SWAPCHAIN_EXTENSION_NAME};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace hep
|
class Device::Builder {
|
||||||
|
public:
|
||||||
|
Builder() = default;
|
||||||
|
|
||||||
|
Builder& withWindow(Window& window);
|
||||||
|
Builder& headless();
|
||||||
|
|
||||||
|
std::unique_ptr<Device> build() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Window* window = nullptr;
|
||||||
|
bool headlessRequested = false;
|
||||||
|
bool modeSet = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
#include "engine.hpp"
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <sophia/frame_info.hpp>
|
||||||
|
#include <sophia/util/logger.hpp>
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
Engine::Engine(const Config& config) : config{config} {
|
||||||
|
if (config.headless) {
|
||||||
|
this->device = Device::Builder().headless().build();
|
||||||
|
} else {
|
||||||
|
this->window =
|
||||||
|
std::make_unique<Window>(config.width, config.height, config.name);
|
||||||
|
this->device = Device::Builder().withWindow(*this->window).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// this->imguiDescriptorPool =
|
||||||
|
// DescriptorPool::Builder(this->device)
|
||||||
|
// .addPoolSize(vk::DescriptorType::eCombinedImageSampler,
|
||||||
|
// IMGUI_IMPL_VULKAN_MINIMUM_IMAGE_SAMPLER_POOL_SIZE
|
||||||
|
// + 5)
|
||||||
|
// .setPoolFlags(vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet)
|
||||||
|
// .setMaxSets(IMGUI_IMPL_VULKAN_MINIMUM_IMAGE_SAMPLER_POOL_SIZE
|
||||||
|
// + 5)
|
||||||
|
// .build();
|
||||||
|
|
||||||
|
// this->uiManager =
|
||||||
|
// UIManager::Builder(this->window, this->device, this->renderer,
|
||||||
|
// imguiDescriptorPool->get())
|
||||||
|
// .darkTheme()
|
||||||
|
// .setDocking(true)
|
||||||
|
// .setKeyboard(true)
|
||||||
|
// .build();
|
||||||
|
};
|
||||||
|
|
||||||
|
Engine::~Engine() { this->device->waitIdle(); };
|
||||||
|
|
||||||
|
void Engine::run() {
|
||||||
|
auto startTime = std::chrono::high_resolution_clock::now();
|
||||||
|
auto currentTime = startTime;
|
||||||
|
|
||||||
|
// EventSystem::get().addListener<KeyReleasedEvent>(
|
||||||
|
// std::bind(&Application::onEvent, this, std::placeholders::_1));
|
||||||
|
|
||||||
|
while (this->isRunning) {
|
||||||
|
if (this->window) {
|
||||||
|
glfwPollEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto newTime = std::chrono::high_resolution_clock::now();
|
||||||
|
double deltaTime =
|
||||||
|
std::chrono::duration<double>(newTime - currentTime).count();
|
||||||
|
currentTime = newTime;
|
||||||
|
|
||||||
|
// // Attempt to start a new frame
|
||||||
|
// vk::CommandBuffer commandBuffer = this->renderer.beginFrame();
|
||||||
|
// if (commandBuffer != nullptr) {
|
||||||
|
// double elapsedTime =
|
||||||
|
// std::chrono::duration<double>(currentTime - startTime).count();
|
||||||
|
|
||||||
|
// vk::Extent2D extent = this->renderer.getCurrentFramebufferExtent();
|
||||||
|
// glm::vec2 extentVec2(static_cast<float>(extent.width),
|
||||||
|
// static_cast<float>(extent.height));
|
||||||
|
|
||||||
|
// FrameInfo frameInfo{this->renderer.getFrameIndex(), elapsedTime,
|
||||||
|
// deltaTime, extentVec2};
|
||||||
|
// }
|
||||||
|
|
||||||
|
this->isRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->device->waitIdle();
|
||||||
|
|
||||||
|
auto endTime = std::chrono::high_resolution_clock::now();
|
||||||
|
double totalRuntime =
|
||||||
|
std::chrono::duration<double>(endTime - startTime).count();
|
||||||
|
log::info("Application ran for", totalRuntime, "s");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "device.hpp"
|
||||||
|
#include "window.hpp"
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
class Engine {
|
||||||
|
public:
|
||||||
|
struct Config {
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
std::string name;
|
||||||
|
bool headless = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
Engine(const Engine&) = delete;
|
||||||
|
Engine& operator=(const Engine&) = delete;
|
||||||
|
|
||||||
|
Engine(const Config& config);
|
||||||
|
~Engine();
|
||||||
|
|
||||||
|
void run();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Config config;
|
||||||
|
|
||||||
|
std::unique_ptr<Window> window;
|
||||||
|
std::unique_ptr<Device> device;
|
||||||
|
|
||||||
|
bool isRunning = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,324 @@
|
|||||||
|
#include "image.hpp"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <sophia/util/logger.hpp>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include "device.hpp"
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
void Image::transitionLayout(vk::CommandBuffer commandBuffer,
|
||||||
|
vk::ImageLayout newLayout) {
|
||||||
|
vk::ImageMemoryBarrier barrier{};
|
||||||
|
barrier.oldLayout = this->layout;
|
||||||
|
barrier.newLayout = newLayout;
|
||||||
|
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||||
|
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||||
|
barrier.image = this->get();
|
||||||
|
barrier.subresourceRange.aspectMask = this->aspectMask;
|
||||||
|
barrier.subresourceRange.baseMipLevel = 0;
|
||||||
|
barrier.subresourceRange.levelCount = this->mipLevels;
|
||||||
|
barrier.subresourceRange.baseArrayLayer = 0;
|
||||||
|
barrier.subresourceRange.layerCount = this->arrayLayers;
|
||||||
|
|
||||||
|
vk::PipelineStageFlags srcStage;
|
||||||
|
vk::PipelineStageFlags dstStage;
|
||||||
|
|
||||||
|
if (this->layout == vk::ImageLayout::eUndefined &&
|
||||||
|
newLayout == vk::ImageLayout::eTransferDstOptimal) {
|
||||||
|
barrier.srcAccessMask = {};
|
||||||
|
barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite;
|
||||||
|
srcStage = vk::PipelineStageFlagBits::eTopOfPipe;
|
||||||
|
dstStage = vk::PipelineStageFlagBits::eTransfer;
|
||||||
|
} else if (this->layout == vk::ImageLayout::eTransferDstOptimal &&
|
||||||
|
newLayout == vk::ImageLayout::eShaderReadOnlyOptimal) {
|
||||||
|
barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
|
||||||
|
barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
|
||||||
|
srcStage = vk::PipelineStageFlagBits::eTransfer;
|
||||||
|
dstStage = vk::PipelineStageFlagBits::eFragmentShader;
|
||||||
|
} else if (this->layout == vk::ImageLayout::eUndefined &&
|
||||||
|
newLayout == vk::ImageLayout::eDepthStencilAttachmentOptimal) {
|
||||||
|
barrier.srcAccessMask = {};
|
||||||
|
barrier.dstAccessMask = vk::AccessFlagBits::eDepthStencilAttachmentRead |
|
||||||
|
vk::AccessFlagBits::eDepthStencilAttachmentWrite;
|
||||||
|
srcStage = vk::PipelineStageFlagBits::eTopOfPipe;
|
||||||
|
dstStage = vk::PipelineStageFlagBits::eEarlyFragmentTests;
|
||||||
|
} else if (this->layout == vk::ImageLayout::eUndefined &&
|
||||||
|
newLayout == vk::ImageLayout::eColorAttachmentOptimal) {
|
||||||
|
barrier.srcAccessMask = {};
|
||||||
|
barrier.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
|
||||||
|
srcStage = vk::PipelineStageFlagBits::eTopOfPipe;
|
||||||
|
dstStage = vk::PipelineStageFlagBits::eColorAttachmentOutput;
|
||||||
|
} else if (this->layout == vk::ImageLayout::eUndefined &&
|
||||||
|
newLayout == vk::ImageLayout::eGeneral) {
|
||||||
|
barrier.srcAccessMask = {};
|
||||||
|
barrier.dstAccessMask =
|
||||||
|
vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
|
||||||
|
srcStage = vk::PipelineStageFlagBits::eTopOfPipe;
|
||||||
|
dstStage = vk::PipelineStageFlagBits::eComputeShader;
|
||||||
|
} else if (this->layout == vk::ImageLayout::eColorAttachmentOptimal &&
|
||||||
|
newLayout == vk::ImageLayout::ePresentSrcKHR) {
|
||||||
|
barrier.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
|
||||||
|
barrier.dstAccessMask = {};
|
||||||
|
srcStage = vk::PipelineStageFlagBits::eColorAttachmentOutput;
|
||||||
|
dstStage = vk::PipelineStageFlagBits::eBottomOfPipe;
|
||||||
|
} else {
|
||||||
|
log::fatal("Unsupported image layout transition.");
|
||||||
|
throw std::runtime_error("Unsupported image layout transition.");
|
||||||
|
}
|
||||||
|
|
||||||
|
commandBuffer.pipelineBarrier(srcStage, dstStage, {}, nullptr, nullptr,
|
||||||
|
barrier);
|
||||||
|
|
||||||
|
this->layout = newLayout;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Image::transitionLayoutImmediate(vk::ImageLayout newLayout) {
|
||||||
|
vk::CommandBuffer commandBuffer = this->device.beginSingleTimeCommands();
|
||||||
|
this->transitionLayout(commandBuffer, newLayout);
|
||||||
|
this->device.endSingleTimeCommands(commandBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
Image::Builder& Image::Builder::setExtent(u32 width, u32 height, u32 depth) {
|
||||||
|
this->extent = vk::Extent3D{width, height, depth};
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Image::Builder& Image::Builder::setFormat(vk::Format format) {
|
||||||
|
this->format = format;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Image::Builder& Image::Builder::setUsage(vk::ImageUsageFlags usage) {
|
||||||
|
this->usage = usage;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Image::Builder& Image::Builder::setTiling(vk::ImageTiling tiling) {
|
||||||
|
this->tiling = tiling;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Image::Builder& Image::Builder::setMemoryProperties(
|
||||||
|
vk::MemoryPropertyFlags properties) {
|
||||||
|
this->memoryProperties = properties;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Image::Builder& Image::Builder::setSamples(vk::SampleCountFlagBits samples) {
|
||||||
|
this->samples = samples;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Image::Builder& Image::Builder::setImageType(vk::ImageType imageType) {
|
||||||
|
this->imageType = imageType;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Image::Builder& Image::Builder::setMipLevels(u32 mipLevels) {
|
||||||
|
this->mipLevels = mipLevels;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Image::Builder& Image::Builder::setArrayLayers(u32 arrayLayers) {
|
||||||
|
this->arrayLayers = arrayLayers;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Image::Builder& Image::Builder::setAspectMask(vk::ImageAspectFlags aspectMask) {
|
||||||
|
this->aspectMask = aspectMask;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Image::Builder& Image::Builder::asComputeStorage() {
|
||||||
|
// Storage images commonly can't use sRGB formats without an extension, so
|
||||||
|
// default to a plain UNORM format; override with setFormat() if needed.
|
||||||
|
this->format = vk::Format::eR8G8B8A8Unorm;
|
||||||
|
this->usage = vk::ImageUsageFlagBits::eStorage;
|
||||||
|
this->aspectMask = vk::ImageAspectFlagBits::eColor;
|
||||||
|
this->tiling = vk::ImageTiling::eOptimal;
|
||||||
|
this->memoryProperties = vk::MemoryPropertyFlagBits::eDeviceLocal;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Image::Builder& Image::Builder::asColorAttachment() {
|
||||||
|
this->usage = vk::ImageUsageFlagBits::eColorAttachment |
|
||||||
|
vk::ImageUsageFlagBits::eSampled;
|
||||||
|
this->aspectMask = vk::ImageAspectFlagBits::eColor;
|
||||||
|
this->tiling = vk::ImageTiling::eOptimal;
|
||||||
|
this->memoryProperties = vk::MemoryPropertyFlagBits::eDeviceLocal;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Image::Builder& Image::Builder::asDepthAttachment() {
|
||||||
|
this->format = vk::Format::eD32Sfloat;
|
||||||
|
this->usage = vk::ImageUsageFlagBits::eDepthStencilAttachment;
|
||||||
|
this->aspectMask = vk::ImageAspectFlagBits::eDepth;
|
||||||
|
this->tiling = vk::ImageTiling::eOptimal;
|
||||||
|
this->memoryProperties = vk::MemoryPropertyFlagBits::eDeviceLocal;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Image::Builder& Image::Builder::wrapExisting(vk::Image image) {
|
||||||
|
this->isExternalImage = true;
|
||||||
|
this->externalImage = image;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Image::Builder& Image::Builder::setInitialData(const void* data,
|
||||||
|
vk::DeviceSize size) {
|
||||||
|
this->initialData = data;
|
||||||
|
this->initialDataSize = size;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Image::Builder& Image::Builder::withView(bool enabled) {
|
||||||
|
this->createView = enabled;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Image::Builder& Image::Builder::withSampler(
|
||||||
|
vk::Filter filter, vk::SamplerAddressMode addressMode) {
|
||||||
|
this->createSampler = true;
|
||||||
|
this->samplerFilter = filter;
|
||||||
|
this->samplerAddressMode = addressMode;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Image> Image::Builder::build() {
|
||||||
|
vk::ImageUsageFlags usage = this->usage;
|
||||||
|
|
||||||
|
auto result = std::unique_ptr<Image>(new Image(this->device));
|
||||||
|
result->format = this->format;
|
||||||
|
result->extent = this->extent;
|
||||||
|
result->aspectMask = this->aspectMask;
|
||||||
|
result->mipLevels = this->mipLevels;
|
||||||
|
result->arrayLayers = this->arrayLayers;
|
||||||
|
|
||||||
|
if (this->isExternalImage) {
|
||||||
|
if (this->initialData != nullptr) {
|
||||||
|
log::warning(
|
||||||
|
"Image::Builder::setInitialData is ignored when wrapExisting is "
|
||||||
|
"used.");
|
||||||
|
}
|
||||||
|
result->externalImage = this->externalImage;
|
||||||
|
// Wrapped images (e.g. swapchain images) are handed to us already in a
|
||||||
|
// known layout controlled by their owner; we can't observe it here, so
|
||||||
|
// leave `layout` at eUndefined and let the caller call
|
||||||
|
// transitionLayout() to bring it in sync before relying on it.
|
||||||
|
} else {
|
||||||
|
if (this->initialData != nullptr) {
|
||||||
|
usage |= vk::ImageUsageFlagBits::eTransferDst;
|
||||||
|
}
|
||||||
|
if (this->createView || this->createSampler) {
|
||||||
|
usage |= vk::ImageUsageFlagBits::eSampled;
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::ImageCreateInfo imageInfo{};
|
||||||
|
imageInfo.imageType = this->imageType;
|
||||||
|
imageInfo.extent = this->extent;
|
||||||
|
imageInfo.mipLevels = this->mipLevels;
|
||||||
|
imageInfo.arrayLayers = this->arrayLayers;
|
||||||
|
imageInfo.format = this->format;
|
||||||
|
imageInfo.tiling = this->tiling;
|
||||||
|
imageInfo.initialLayout = vk::ImageLayout::eUndefined;
|
||||||
|
imageInfo.usage = usage;
|
||||||
|
imageInfo.sharingMode = vk::SharingMode::eExclusive;
|
||||||
|
imageInfo.samples = this->samples;
|
||||||
|
|
||||||
|
result->image = this->device.get()->createImageUnique(imageInfo);
|
||||||
|
|
||||||
|
vk::MemoryRequirements memoryRequirements =
|
||||||
|
this->device.get()->getImageMemoryRequirements(result->image.get());
|
||||||
|
|
||||||
|
vk::MemoryAllocateInfo allocInfo{};
|
||||||
|
allocInfo.allocationSize = memoryRequirements.size;
|
||||||
|
allocInfo.memoryTypeIndex = this->device.findMemoryType(
|
||||||
|
memoryRequirements.memoryTypeBits, this->memoryProperties);
|
||||||
|
|
||||||
|
result->memory = this->device.get()->allocateMemoryUnique(allocInfo);
|
||||||
|
this->device.get()->bindImageMemory(result->image.get(),
|
||||||
|
result->memory.get(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this->isExternalImage && this->initialData != nullptr) {
|
||||||
|
vk::Buffer stagingBuffer;
|
||||||
|
vk::DeviceMemory stagingMemory;
|
||||||
|
this->device.createBuffer(this->initialDataSize,
|
||||||
|
vk::BufferUsageFlagBits::eTransferSrc,
|
||||||
|
vk::MemoryPropertyFlagBits::eHostVisible |
|
||||||
|
vk::MemoryPropertyFlagBits::eHostCoherent,
|
||||||
|
stagingBuffer, stagingMemory);
|
||||||
|
|
||||||
|
void* mapped =
|
||||||
|
this->device.get()->mapMemory(stagingMemory, 0, this->initialDataSize);
|
||||||
|
memcpy(mapped, this->initialData,
|
||||||
|
static_cast<size_t>(this->initialDataSize));
|
||||||
|
this->device.get()->unmapMemory(stagingMemory);
|
||||||
|
|
||||||
|
vk::BufferImageCopy region{};
|
||||||
|
region.bufferOffset = 0;
|
||||||
|
region.bufferRowLength = 0;
|
||||||
|
region.bufferImageHeight = 0;
|
||||||
|
region.imageSubresource.aspectMask = this->aspectMask;
|
||||||
|
region.imageSubresource.mipLevel = 0;
|
||||||
|
region.imageSubresource.baseArrayLayer = 0;
|
||||||
|
region.imageSubresource.layerCount = this->arrayLayers;
|
||||||
|
region.imageOffset = vk::Offset3D{0, 0, 0};
|
||||||
|
region.imageExtent = this->extent;
|
||||||
|
|
||||||
|
vk::CommandBuffer commandBuffer = this->device.beginSingleTimeCommands();
|
||||||
|
result->transitionLayout(commandBuffer, vk::ImageLayout::eTransferDstOptimal);
|
||||||
|
commandBuffer.copyBufferToImage(stagingBuffer, result->get(),
|
||||||
|
vk::ImageLayout::eTransferDstOptimal,
|
||||||
|
region);
|
||||||
|
if (usage & vk::ImageUsageFlagBits::eSampled) {
|
||||||
|
result->transitionLayout(commandBuffer,
|
||||||
|
vk::ImageLayout::eShaderReadOnlyOptimal);
|
||||||
|
}
|
||||||
|
this->device.endSingleTimeCommands(commandBuffer);
|
||||||
|
|
||||||
|
this->device.get()->destroyBuffer(stagingBuffer);
|
||||||
|
this->device.get()->freeMemory(stagingMemory);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->createView) {
|
||||||
|
vk::ImageViewCreateInfo viewInfo{};
|
||||||
|
viewInfo.image = result->get();
|
||||||
|
viewInfo.viewType = vk::ImageViewType::e2D;
|
||||||
|
viewInfo.format = this->format;
|
||||||
|
viewInfo.subresourceRange.aspectMask = this->aspectMask;
|
||||||
|
viewInfo.subresourceRange.baseMipLevel = 0;
|
||||||
|
viewInfo.subresourceRange.levelCount = this->mipLevels;
|
||||||
|
viewInfo.subresourceRange.baseArrayLayer = 0;
|
||||||
|
viewInfo.subresourceRange.layerCount = this->arrayLayers;
|
||||||
|
|
||||||
|
result->view = this->device.get()->createImageViewUnique(viewInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->createSampler) {
|
||||||
|
vk::SamplerCreateInfo samplerInfo{};
|
||||||
|
samplerInfo.magFilter = this->samplerFilter;
|
||||||
|
samplerInfo.minFilter = this->samplerFilter;
|
||||||
|
samplerInfo.addressModeU = this->samplerAddressMode;
|
||||||
|
samplerInfo.addressModeV = this->samplerAddressMode;
|
||||||
|
samplerInfo.addressModeW = this->samplerAddressMode;
|
||||||
|
samplerInfo.anisotropyEnable = VK_FALSE;
|
||||||
|
samplerInfo.maxAnisotropy = 1.0f;
|
||||||
|
samplerInfo.borderColor = vk::BorderColor::eIntOpaqueBlack;
|
||||||
|
samplerInfo.unnormalizedCoordinates = VK_FALSE;
|
||||||
|
samplerInfo.compareEnable = VK_FALSE;
|
||||||
|
samplerInfo.compareOp = vk::CompareOp::eAlways;
|
||||||
|
samplerInfo.mipmapMode = vk::SamplerMipmapMode::eLinear;
|
||||||
|
samplerInfo.minLod = 0.0f;
|
||||||
|
samplerInfo.maxLod = static_cast<float>(this->mipLevels);
|
||||||
|
|
||||||
|
result->sampler = this->device.get()->createSamplerUnique(samplerInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
log::trace("created vk::Image.");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <sophia/types.hpp>
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
class Device;
|
||||||
|
|
||||||
|
class Image {
|
||||||
|
public:
|
||||||
|
class Builder;
|
||||||
|
|
||||||
|
Image(const Image&) = delete;
|
||||||
|
Image& operator=(const Image&) = delete;
|
||||||
|
Image(Image&&) = default;
|
||||||
|
Image& operator=(Image&&) = delete;
|
||||||
|
|
||||||
|
vk::Image get() const {
|
||||||
|
return this->image ? this->image.get() : this->externalImage;
|
||||||
|
}
|
||||||
|
vk::ImageView getView() const { return this->view.get(); }
|
||||||
|
vk::Sampler getSampler() const { return this->sampler.get(); }
|
||||||
|
|
||||||
|
bool isExternal() const { return !this->image; }
|
||||||
|
|
||||||
|
vk::Format getFormat() const { return this->format; }
|
||||||
|
vk::Extent3D getExtent() const { return this->extent; }
|
||||||
|
vk::ImageLayout getLayout() const { return this->layout; }
|
||||||
|
|
||||||
|
void transitionLayout(vk::CommandBuffer commandBuffer,
|
||||||
|
vk::ImageLayout newLayout);
|
||||||
|
|
||||||
|
void transitionLayoutImmediate(vk::ImageLayout newLayout);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Image(Device& device) : device{device} {}
|
||||||
|
|
||||||
|
Device& device;
|
||||||
|
|
||||||
|
vk::Image externalImage;
|
||||||
|
vk::UniqueImage image;
|
||||||
|
vk::UniqueDeviceMemory memory;
|
||||||
|
vk::UniqueImageView view;
|
||||||
|
vk::UniqueSampler sampler;
|
||||||
|
|
||||||
|
vk::Format format = vk::Format::eUndefined;
|
||||||
|
vk::Extent3D extent{};
|
||||||
|
vk::ImageAspectFlags aspectMask = vk::ImageAspectFlagBits::eColor;
|
||||||
|
u32 mipLevels = 1;
|
||||||
|
u32 arrayLayers = 1;
|
||||||
|
vk::ImageLayout layout = vk::ImageLayout::eUndefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Image::Builder {
|
||||||
|
public:
|
||||||
|
Builder(Device& device) : device{device} {}
|
||||||
|
|
||||||
|
Builder& setExtent(u32 width, u32 height, u32 depth = 1);
|
||||||
|
Builder& setFormat(vk::Format format);
|
||||||
|
Builder& setUsage(vk::ImageUsageFlags usage);
|
||||||
|
Builder& setTiling(vk::ImageTiling tiling);
|
||||||
|
Builder& setMemoryProperties(vk::MemoryPropertyFlags properties);
|
||||||
|
Builder& setSamples(vk::SampleCountFlagBits samples);
|
||||||
|
Builder& setImageType(vk::ImageType imageType);
|
||||||
|
Builder& setMipLevels(u32 mipLevels);
|
||||||
|
Builder& setArrayLayers(u32 arrayLayers);
|
||||||
|
Builder& setAspectMask(vk::ImageAspectFlags aspectMask);
|
||||||
|
|
||||||
|
// High-level usage presets for GPGPU + Graphics. Each sets usage/aspect/
|
||||||
|
// tiling/memory-property defaults for the common case; call the
|
||||||
|
// individual setters afterward to override anything preset-specific.
|
||||||
|
Builder& asComputeStorage();
|
||||||
|
Builder& asColorAttachment();
|
||||||
|
Builder& asDepthAttachment();
|
||||||
|
|
||||||
|
// Wraps an image this Builder does not own instead of creating one —
|
||||||
|
// e.g. a swapchain image. build() will skip image/memory creation and
|
||||||
|
// teardown entirely; setUsage/setTiling/setMemoryProperties/setSamples/
|
||||||
|
// setInitialData are ignored, but setFormat/setExtent/setAspectMask still
|
||||||
|
// matter, since view/sampler creation relies on them.
|
||||||
|
Builder& wrapExisting(vk::Image image);
|
||||||
|
|
||||||
|
// Uploads `size` bytes to the image via a staging buffer once it is
|
||||||
|
// created, transitioning it to eTransferDstOptimal (and on to
|
||||||
|
// eShaderReadOnlyOptimal if the image is sampled) in the process.
|
||||||
|
Builder& setInitialData(const void* data, vk::DeviceSize size);
|
||||||
|
|
||||||
|
Builder& withView(bool enabled = true);
|
||||||
|
Builder& withSampler(
|
||||||
|
vk::Filter filter = vk::Filter::eLinear,
|
||||||
|
vk::SamplerAddressMode addressMode = vk::SamplerAddressMode::eRepeat);
|
||||||
|
|
||||||
|
std::unique_ptr<Image> build();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Device& device;
|
||||||
|
|
||||||
|
vk::Extent3D extent{1, 1, 1};
|
||||||
|
vk::Format format = vk::Format::eR8G8B8A8Srgb;
|
||||||
|
vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eSampled;
|
||||||
|
vk::ImageTiling tiling = vk::ImageTiling::eOptimal;
|
||||||
|
vk::MemoryPropertyFlags memoryProperties =
|
||||||
|
vk::MemoryPropertyFlagBits::eDeviceLocal;
|
||||||
|
vk::SampleCountFlagBits samples = vk::SampleCountFlagBits::e1;
|
||||||
|
vk::ImageType imageType = vk::ImageType::e2D;
|
||||||
|
vk::ImageAspectFlags aspectMask = vk::ImageAspectFlagBits::eColor;
|
||||||
|
u32 mipLevels = 1;
|
||||||
|
u32 arrayLayers = 1;
|
||||||
|
|
||||||
|
const void* initialData = nullptr;
|
||||||
|
vk::DeviceSize initialDataSize = 0;
|
||||||
|
|
||||||
|
bool isExternalImage = false;
|
||||||
|
vk::Image externalImage;
|
||||||
|
|
||||||
|
bool createView = false;
|
||||||
|
bool createSampler = false;
|
||||||
|
vk::Filter samplerFilter = vk::Filter::eLinear;
|
||||||
|
vk::SamplerAddressMode samplerAddressMode = vk::SamplerAddressMode::eRepeat;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
#include "compute_pipeline.hpp"
|
||||||
|
|
||||||
|
#include <sophia/util/logger.hpp>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
ComputePipeline::Builder& ComputePipeline::Builder::setShader(Shader& shader) {
|
||||||
|
if (shader.getStage() != ShaderStage::COMPUTE) {
|
||||||
|
log::fatal(
|
||||||
|
"failed to configure compute pipeline: shader is not a compute "
|
||||||
|
"shader");
|
||||||
|
throw std::runtime_error(
|
||||||
|
"failed to configure compute pipeline: shader is not a compute "
|
||||||
|
"shader");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->shader != nullptr) {
|
||||||
|
log::warning("compute pipeline builder: shader already set, overwriting");
|
||||||
|
}
|
||||||
|
|
||||||
|
this->shader = &shader;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
ComputePipeline::Builder& ComputePipeline::Builder::setPipelineLayout(
|
||||||
|
vk::PipelineLayout pipelineLayout) {
|
||||||
|
this->pipelineLayout = pipelineLayout;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<ComputePipeline> ComputePipeline::Builder::build() const {
|
||||||
|
if (this->shader == nullptr) {
|
||||||
|
log::fatal("failed to build compute pipeline: setShader() was not called");
|
||||||
|
throw std::runtime_error(
|
||||||
|
"failed to build compute pipeline: setShader() was not called");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->pipelineLayout == VK_NULL_HANDLE) {
|
||||||
|
log::fatal(
|
||||||
|
"failed to build compute pipeline: setPipelineLayout() was not "
|
||||||
|
"called");
|
||||||
|
throw std::runtime_error(
|
||||||
|
"failed to build compute pipeline: setPipelineLayout() was not "
|
||||||
|
"called");
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::unique_ptr<ComputePipeline>(
|
||||||
|
new ComputePipeline(this->device, *this->shader, this->pipelineLayout));
|
||||||
|
}
|
||||||
|
|
||||||
|
ComputePipeline::ComputePipeline(Device& device, Shader& shader,
|
||||||
|
vk::PipelineLayout pipelineLayout)
|
||||||
|
: Pipeline(device, vk::PipelineBindPoint::eCompute) {
|
||||||
|
vk::UniqueShaderModule computeShaderModule =
|
||||||
|
createShaderModule(shader.getSpirv());
|
||||||
|
log::trace("created compute shader module");
|
||||||
|
|
||||||
|
vk::PipelineShaderStageCreateInfo shaderStage{
|
||||||
|
vk::PipelineShaderStageCreateFlags(), vk::ShaderStageFlagBits::eCompute,
|
||||||
|
computeShaderModule.get(), "main"};
|
||||||
|
|
||||||
|
vk::ComputePipelineCreateInfo pipelineInfo{};
|
||||||
|
pipelineInfo.stage = shaderStage;
|
||||||
|
pipelineInfo.layout = pipelineLayout;
|
||||||
|
pipelineInfo.basePipelineHandle = nullptr;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this->pipeline = this->device.get()
|
||||||
|
->createComputePipelineUnique(nullptr, pipelineInfo)
|
||||||
|
.value;
|
||||||
|
log::trace("created vk::Pipeline (compute)");
|
||||||
|
} catch (const vk::SystemError& err) {
|
||||||
|
log::fatal("failed to create vk::Pipeline (compute)");
|
||||||
|
throw std::runtime_error("failed to create vk::Pipeline (compute)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ComputePipeline::~ComputePipeline() {
|
||||||
|
log::trace("destoryed vk::Pipeline (compute)");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
|
#include "device.hpp"
|
||||||
|
#include "pipeline.hpp"
|
||||||
|
#include "shader.hpp"
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
class ComputePipeline : public Pipeline {
|
||||||
|
public:
|
||||||
|
class Builder;
|
||||||
|
|
||||||
|
~ComputePipeline() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
ComputePipeline(Device& device, Shader& shader,
|
||||||
|
vk::PipelineLayout pipelineLayout);
|
||||||
|
};
|
||||||
|
|
||||||
|
class ComputePipeline::Builder {
|
||||||
|
public:
|
||||||
|
Builder(Device& device) : device{device} {}
|
||||||
|
|
||||||
|
Builder& setShader(Shader& shader);
|
||||||
|
Builder& setPipelineLayout(vk::PipelineLayout pipelineLayout);
|
||||||
|
|
||||||
|
std::unique_ptr<ComputePipeline> build() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Device& device;
|
||||||
|
|
||||||
|
Shader* shader = nullptr;
|
||||||
|
vk::PipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,282 @@
|
|||||||
|
#include "graphics_pipeline.hpp"
|
||||||
|
|
||||||
|
#include <sophia/util/logger.hpp>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
GraphicsPipeline::Builder& GraphicsPipeline::Builder::setVertexShader(
|
||||||
|
Shader& vertexShader) {
|
||||||
|
if (vertexShader.getStage() != ShaderStage::VERTEX) {
|
||||||
|
log::fatal(
|
||||||
|
"failed to configure graphics pipeline: shader is not a vertex "
|
||||||
|
"shader");
|
||||||
|
throw std::runtime_error(
|
||||||
|
"failed to configure graphics pipeline: shader is not a vertex "
|
||||||
|
"shader");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->vertexShader != nullptr) {
|
||||||
|
log::warning(
|
||||||
|
"graphics pipeline builder: vertex shader already set, overwriting");
|
||||||
|
}
|
||||||
|
|
||||||
|
this->vertexShader = &vertexShader;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipeline::Builder& GraphicsPipeline::Builder::setFragmentShader(
|
||||||
|
Shader& fragmentShader) {
|
||||||
|
if (fragmentShader.getStage() != ShaderStage::FRAGMENT) {
|
||||||
|
log::fatal(
|
||||||
|
"failed to configure graphics pipeline: shader is not a fragment "
|
||||||
|
"shader");
|
||||||
|
throw std::runtime_error(
|
||||||
|
"failed to configure graphics pipeline: shader is not a fragment "
|
||||||
|
"shader");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->fragmentShader != nullptr) {
|
||||||
|
log::warning(
|
||||||
|
"graphics pipeline builder: fragment shader already set, "
|
||||||
|
"overwriting");
|
||||||
|
}
|
||||||
|
|
||||||
|
this->fragmentShader = &fragmentShader;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipeline::Builder& GraphicsPipeline::Builder::addShader(
|
||||||
|
Shader& shader) {
|
||||||
|
switch (shader.getStage()) {
|
||||||
|
case ShaderStage::VERTEX:
|
||||||
|
return setVertexShader(shader);
|
||||||
|
case ShaderStage::FRAGMENT:
|
||||||
|
return setFragmentShader(shader);
|
||||||
|
default:
|
||||||
|
log::fatal(
|
||||||
|
"failed to configure graphics pipeline: unsupported shader stage");
|
||||||
|
throw std::runtime_error(
|
||||||
|
"failed to configure graphics pipeline: unsupported shader stage");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipeline::Builder& GraphicsPipeline::Builder::setPipelineLayout(
|
||||||
|
vk::PipelineLayout pipelineLayout) {
|
||||||
|
this->pipelineLayout = pipelineLayout;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipeline::Builder& GraphicsPipeline::Builder::setColorAttachmentFormats(
|
||||||
|
std::vector<vk::Format> colorAttachmentFormats) {
|
||||||
|
this->colorAttachmentFormats = std::move(colorAttachmentFormats);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipeline::Builder& GraphicsPipeline::Builder::setDepthAttachmentFormat(
|
||||||
|
vk::Format depthAttachmentFormat) {
|
||||||
|
this->depthAttachmentFormat = depthAttachmentFormat;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipeline::Builder&
|
||||||
|
GraphicsPipeline::Builder::setStencilAttachmentFormat(
|
||||||
|
vk::Format stencilAttachmentFormat) {
|
||||||
|
this->stencilAttachmentFormat = stencilAttachmentFormat;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipeline::Builder& GraphicsPipeline::Builder::setVertexInput(
|
||||||
|
std::vector<vk::VertexInputBindingDescription> bindingDescriptions,
|
||||||
|
std::vector<vk::VertexInputAttributeDescription> attributeDescriptions) {
|
||||||
|
this->bindingDescriptions = std::move(bindingDescriptions);
|
||||||
|
this->attributeDescriptions = std::move(attributeDescriptions);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<GraphicsPipeline> GraphicsPipeline::Builder::build() const {
|
||||||
|
if (this->vertexShader == nullptr || this->fragmentShader == nullptr) {
|
||||||
|
log::fatal(
|
||||||
|
"failed to build graphics pipeline: setVertexShader()/"
|
||||||
|
"setFragmentShader() was not called");
|
||||||
|
throw std::runtime_error(
|
||||||
|
"failed to build graphics pipeline: setVertexShader()/"
|
||||||
|
"setFragmentShader() was not called");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->pipelineLayout == VK_NULL_HANDLE) {
|
||||||
|
log::fatal(
|
||||||
|
"failed to build graphics pipeline: setPipelineLayout() was not "
|
||||||
|
"called");
|
||||||
|
throw std::runtime_error(
|
||||||
|
"failed to build graphics pipeline: setPipelineLayout() was not "
|
||||||
|
"called");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->colorAttachmentFormats.empty() &&
|
||||||
|
this->depthAttachmentFormat == vk::Format::eUndefined) {
|
||||||
|
log::fatal(
|
||||||
|
"failed to build graphics pipeline: no color or depth attachment "
|
||||||
|
"format provided");
|
||||||
|
throw std::runtime_error(
|
||||||
|
"failed to build graphics pipeline: setColorAttachmentFormats() or "
|
||||||
|
"setDepthAttachmentFormat() was not called");
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::unique_ptr<GraphicsPipeline>(new GraphicsPipeline(
|
||||||
|
this->device, *this->vertexShader, *this->fragmentShader,
|
||||||
|
this->pipelineLayout, this->colorAttachmentFormats,
|
||||||
|
this->depthAttachmentFormat, this->stencilAttachmentFormat,
|
||||||
|
this->bindingDescriptions, this->attributeDescriptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipeline::GraphicsPipeline(
|
||||||
|
Device& device, Shader& vertexShader, Shader& fragmentShader,
|
||||||
|
vk::PipelineLayout pipelineLayout,
|
||||||
|
std::vector<vk::Format> colorAttachmentFormats,
|
||||||
|
vk::Format depthAttachmentFormat, vk::Format stencilAttachmentFormat,
|
||||||
|
std::vector<vk::VertexInputBindingDescription> bindingDescriptions,
|
||||||
|
std::vector<vk::VertexInputAttributeDescription> attributeDescriptions)
|
||||||
|
: Pipeline(device, vk::PipelineBindPoint::eGraphics) {
|
||||||
|
setDefaultGraphicsPipelineConfig();
|
||||||
|
|
||||||
|
this->config.bindingDescriptions = std::move(bindingDescriptions);
|
||||||
|
this->config.attributeDescriptions = std::move(attributeDescriptions);
|
||||||
|
this->config.pipelineLayout = pipelineLayout;
|
||||||
|
this->config.colorAttachmentFormats = std::move(colorAttachmentFormats);
|
||||||
|
this->config.depthAttachmentFormat = depthAttachmentFormat;
|
||||||
|
this->config.stencilAttachmentFormat = stencilAttachmentFormat;
|
||||||
|
|
||||||
|
vk::UniqueShaderModule vertexShaderModule =
|
||||||
|
createShaderModule(vertexShader.getSpirv());
|
||||||
|
vk::UniqueShaderModule fragmentShaderModule =
|
||||||
|
createShaderModule(fragmentShader.getSpirv());
|
||||||
|
log::trace("created graphics shader modules");
|
||||||
|
|
||||||
|
vk::PipelineShaderStageCreateInfo shaderStages[] = {
|
||||||
|
{vk::PipelineShaderStageCreateFlags(), vk::ShaderStageFlagBits::eVertex,
|
||||||
|
vertexShaderModule.get(), "main"},
|
||||||
|
{vk::PipelineShaderStageCreateFlags(), vk::ShaderStageFlagBits::eFragment,
|
||||||
|
fragmentShaderModule.get(), "main"}};
|
||||||
|
|
||||||
|
vk::PipelineVertexInputStateCreateInfo vertexInputInfo{};
|
||||||
|
vertexInputInfo.vertexBindingDescriptionCount =
|
||||||
|
static_cast<u32>(this->config.bindingDescriptions.size());
|
||||||
|
vertexInputInfo.pVertexBindingDescriptions =
|
||||||
|
this->config.bindingDescriptions.data();
|
||||||
|
vertexInputInfo.vertexAttributeDescriptionCount =
|
||||||
|
static_cast<u32>(this->config.attributeDescriptions.size());
|
||||||
|
vertexInputInfo.pVertexAttributeDescriptions =
|
||||||
|
this->config.attributeDescriptions.data();
|
||||||
|
|
||||||
|
vk::PipelineViewportStateCreateInfo viewportInfo{};
|
||||||
|
viewportInfo.viewportCount = 1;
|
||||||
|
viewportInfo.pViewports = nullptr;
|
||||||
|
viewportInfo.scissorCount = 1;
|
||||||
|
viewportInfo.pScissors = nullptr;
|
||||||
|
|
||||||
|
vk::PipelineRenderingCreateInfo pipelineRenderingInfo{};
|
||||||
|
pipelineRenderingInfo.colorAttachmentCount =
|
||||||
|
static_cast<u32>(this->config.colorAttachmentFormats.size());
|
||||||
|
pipelineRenderingInfo.pColorAttachmentFormats =
|
||||||
|
this->config.colorAttachmentFormats.data();
|
||||||
|
pipelineRenderingInfo.depthAttachmentFormat =
|
||||||
|
this->config.depthAttachmentFormat;
|
||||||
|
pipelineRenderingInfo.stencilAttachmentFormat =
|
||||||
|
this->config.stencilAttachmentFormat;
|
||||||
|
|
||||||
|
vk::GraphicsPipelineCreateInfo pipelineInfo{};
|
||||||
|
pipelineInfo.pNext = &pipelineRenderingInfo;
|
||||||
|
pipelineInfo.stageCount = 2;
|
||||||
|
pipelineInfo.pStages = shaderStages;
|
||||||
|
pipelineInfo.pVertexInputState = &vertexInputInfo;
|
||||||
|
pipelineInfo.pInputAssemblyState = &this->config.inputAssemblyInfo;
|
||||||
|
pipelineInfo.pViewportState = &viewportInfo;
|
||||||
|
pipelineInfo.pRasterizationState = &this->config.rasterizationInfo;
|
||||||
|
pipelineInfo.pMultisampleState = &this->config.multisampleInfo;
|
||||||
|
pipelineInfo.pDepthStencilState = &this->config.depthStencilInfo;
|
||||||
|
pipelineInfo.pColorBlendState = &this->config.colorBlendInfo;
|
||||||
|
pipelineInfo.pDynamicState = &this->config.dynamicStateInfo;
|
||||||
|
|
||||||
|
pipelineInfo.layout = this->config.pipelineLayout;
|
||||||
|
pipelineInfo.basePipelineHandle = nullptr;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this->pipeline = this->device.get()
|
||||||
|
->createGraphicsPipelineUnique(nullptr, pipelineInfo)
|
||||||
|
.value;
|
||||||
|
log::trace("created vk::Pipeline (graphics)");
|
||||||
|
} catch (const vk::SystemError& err) {
|
||||||
|
log::fatal("failed to create vk::Pipeline (graphics)");
|
||||||
|
throw std::runtime_error("failed to create vk::Pipeline (graphics)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsPipeline::~GraphicsPipeline() {
|
||||||
|
log::trace("destroyed vk::Pipeline (graphics)");
|
||||||
|
}
|
||||||
|
|
||||||
|
void GraphicsPipeline::setDefaultGraphicsPipelineConfig() {
|
||||||
|
this->config.inputAssemblyInfo.topology =
|
||||||
|
vk::PrimitiveTopology::eTriangleList;
|
||||||
|
this->config.inputAssemblyInfo.primitiveRestartEnable = vk::False;
|
||||||
|
|
||||||
|
this->config.rasterizationInfo.depthClampEnable = vk::False;
|
||||||
|
this->config.rasterizationInfo.rasterizerDiscardEnable = vk::False;
|
||||||
|
this->config.rasterizationInfo.polygonMode = vk::PolygonMode::eFill;
|
||||||
|
this->config.rasterizationInfo.lineWidth = 1.0f;
|
||||||
|
this->config.rasterizationInfo.cullMode = vk::CullModeFlagBits::eNone;
|
||||||
|
this->config.rasterizationInfo.frontFace = vk::FrontFace::eClockwise;
|
||||||
|
this->config.rasterizationInfo.depthBiasEnable = vk::False;
|
||||||
|
this->config.rasterizationInfo.depthBiasConstantFactor = 0.0f;
|
||||||
|
this->config.rasterizationInfo.depthBiasClamp = 0.0f;
|
||||||
|
this->config.rasterizationInfo.depthBiasSlopeFactor = 0.0f;
|
||||||
|
|
||||||
|
this->config.multisampleInfo.sampleShadingEnable = vk::False;
|
||||||
|
this->config.multisampleInfo.rasterizationSamples =
|
||||||
|
vk::SampleCountFlagBits::e1;
|
||||||
|
this->config.multisampleInfo.minSampleShading = 1.0f;
|
||||||
|
this->config.multisampleInfo.pSampleMask = nullptr;
|
||||||
|
this->config.multisampleInfo.alphaToCoverageEnable = vk::False;
|
||||||
|
this->config.multisampleInfo.alphaToOneEnable = vk::False;
|
||||||
|
|
||||||
|
this->config.colorBlendAttachment.colorWriteMask =
|
||||||
|
vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
|
||||||
|
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA;
|
||||||
|
this->config.colorBlendAttachment.blendEnable = vk::False;
|
||||||
|
this->config.colorBlendAttachment.srcColorBlendFactor = vk::BlendFactor::eOne;
|
||||||
|
this->config.colorBlendAttachment.dstColorBlendFactor =
|
||||||
|
vk::BlendFactor::eZero;
|
||||||
|
this->config.colorBlendAttachment.colorBlendOp = vk::BlendOp::eAdd;
|
||||||
|
this->config.colorBlendAttachment.srcAlphaBlendFactor = vk::BlendFactor::eOne;
|
||||||
|
this->config.colorBlendAttachment.dstAlphaBlendFactor =
|
||||||
|
vk::BlendFactor::eZero;
|
||||||
|
this->config.colorBlendAttachment.alphaBlendOp = vk::BlendOp::eAdd;
|
||||||
|
|
||||||
|
this->config.colorBlendInfo.logicOpEnable = vk::False;
|
||||||
|
this->config.colorBlendInfo.logicOp = vk::LogicOp::eCopy;
|
||||||
|
this->config.colorBlendInfo.attachmentCount = 1;
|
||||||
|
this->config.colorBlendInfo.pAttachments = &this->config.colorBlendAttachment;
|
||||||
|
this->config.colorBlendInfo.blendConstants[0] = 0.0f;
|
||||||
|
this->config.colorBlendInfo.blendConstants[1] = 0.0f;
|
||||||
|
this->config.colorBlendInfo.blendConstants[2] = 0.0f;
|
||||||
|
this->config.colorBlendInfo.blendConstants[3] = 0.0f;
|
||||||
|
|
||||||
|
this->config.depthStencilInfo.depthTestEnable = vk::True;
|
||||||
|
this->config.depthStencilInfo.depthWriteEnable = vk::True;
|
||||||
|
this->config.depthStencilInfo.depthCompareOp = vk::CompareOp::eLess;
|
||||||
|
this->config.depthStencilInfo.depthBoundsTestEnable = vk::False;
|
||||||
|
this->config.depthStencilInfo.minDepthBounds = 0.0f;
|
||||||
|
this->config.depthStencilInfo.maxDepthBounds = 1.0f;
|
||||||
|
this->config.depthStencilInfo.stencilTestEnable = vk::False;
|
||||||
|
|
||||||
|
this->config.dynamicStateEnables = {vk::DynamicState::eViewport,
|
||||||
|
vk::DynamicState::eScissor};
|
||||||
|
this->config.dynamicStateInfo.pDynamicStates =
|
||||||
|
this->config.dynamicStateEnables.data();
|
||||||
|
this->config.dynamicStateInfo.dynamicStateCount =
|
||||||
|
static_cast<u32>(this->config.dynamicStateEnables.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <sophia/types.hpp>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
|
#include "device.hpp"
|
||||||
|
#include "pipeline.hpp"
|
||||||
|
#include "shader.hpp"
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
struct GraphicsPipelineConfig {
|
||||||
|
GraphicsPipelineConfig() = default;
|
||||||
|
GraphicsPipelineConfig(const GraphicsPipelineConfig&) = delete;
|
||||||
|
GraphicsPipelineConfig& operator=(const GraphicsPipelineConfig&) = delete;
|
||||||
|
|
||||||
|
std::vector<vk::VertexInputBindingDescription> bindingDescriptions{};
|
||||||
|
std::vector<vk::VertexInputAttributeDescription> attributeDescriptions{};
|
||||||
|
vk::PipelineInputAssemblyStateCreateInfo inputAssemblyInfo;
|
||||||
|
vk::PipelineRasterizationStateCreateInfo rasterizationInfo;
|
||||||
|
vk::PipelineMultisampleStateCreateInfo multisampleInfo;
|
||||||
|
vk::PipelineColorBlendAttachmentState colorBlendAttachment;
|
||||||
|
vk::PipelineColorBlendStateCreateInfo colorBlendInfo;
|
||||||
|
vk::PipelineDepthStencilStateCreateInfo depthStencilInfo;
|
||||||
|
std::vector<vk::DynamicState> dynamicStateEnables;
|
||||||
|
vk::PipelineDynamicStateCreateInfo dynamicStateInfo;
|
||||||
|
|
||||||
|
vk::PipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
||||||
|
std::vector<vk::Format> colorAttachmentFormats{};
|
||||||
|
vk::Format depthAttachmentFormat = vk::Format::eUndefined;
|
||||||
|
vk::Format stencilAttachmentFormat = vk::Format::eUndefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
class GraphicsPipeline : public Pipeline {
|
||||||
|
public:
|
||||||
|
class Builder;
|
||||||
|
|
||||||
|
~GraphicsPipeline() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
GraphicsPipeline(
|
||||||
|
Device& device, Shader& vertexShader, Shader& fragmentShader,
|
||||||
|
vk::PipelineLayout pipelineLayout,
|
||||||
|
std::vector<vk::Format> colorAttachmentFormats,
|
||||||
|
vk::Format depthAttachmentFormat, vk::Format stencilAttachmentFormat,
|
||||||
|
std::vector<vk::VertexInputBindingDescription> bindingDescriptions,
|
||||||
|
std::vector<vk::VertexInputAttributeDescription> attributeDescriptions);
|
||||||
|
|
||||||
|
void setDefaultGraphicsPipelineConfig();
|
||||||
|
|
||||||
|
GraphicsPipelineConfig config;
|
||||||
|
};
|
||||||
|
|
||||||
|
class GraphicsPipeline::Builder {
|
||||||
|
public:
|
||||||
|
Builder(Device& device) : device{device} {}
|
||||||
|
|
||||||
|
Builder& setVertexShader(Shader& vertexShader);
|
||||||
|
Builder& setFragmentShader(Shader& fragmentShader);
|
||||||
|
Builder& addShader(Shader& shader);
|
||||||
|
Builder& setPipelineLayout(vk::PipelineLayout pipelineLayout);
|
||||||
|
Builder& setColorAttachmentFormats(
|
||||||
|
std::vector<vk::Format> colorAttachmentFormats);
|
||||||
|
Builder& setDepthAttachmentFormat(vk::Format depthAttachmentFormat);
|
||||||
|
Builder& setStencilAttachmentFormat(vk::Format stencilAttachmentFormat);
|
||||||
|
Builder& setVertexInput(
|
||||||
|
std::vector<vk::VertexInputBindingDescription> bindingDescriptions,
|
||||||
|
std::vector<vk::VertexInputAttributeDescription> attributeDescriptions);
|
||||||
|
|
||||||
|
std::unique_ptr<GraphicsPipeline> build() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Device& device;
|
||||||
|
|
||||||
|
Shader* vertexShader = nullptr;
|
||||||
|
Shader* fragmentShader = nullptr;
|
||||||
|
vk::PipelineLayout pipelineLayout = VK_NULL_HANDLE;
|
||||||
|
std::vector<vk::Format> colorAttachmentFormats{};
|
||||||
|
vk::Format depthAttachmentFormat = vk::Format::eUndefined;
|
||||||
|
vk::Format stencilAttachmentFormat = vk::Format::eUndefined;
|
||||||
|
std::vector<vk::VertexInputBindingDescription> bindingDescriptions{};
|
||||||
|
std::vector<vk::VertexInputAttributeDescription> attributeDescriptions{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#include "pipeline.hpp"
|
||||||
|
|
||||||
|
#include <sophia/util/logger.hpp>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
Pipeline::Pipeline(Device& device, vk::PipelineBindPoint bindPoint)
|
||||||
|
: device{device}, bindPoint{bindPoint} {}
|
||||||
|
|
||||||
|
void Pipeline::bind(vk::CommandBuffer commandBuffer) {
|
||||||
|
commandBuffer.bindPipeline(this->bindPoint, this->pipeline.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::UniqueShaderModule Pipeline::createShaderModule(
|
||||||
|
const std::vector<u32>& spirv) {
|
||||||
|
try {
|
||||||
|
return device.get()->createShaderModuleUnique(
|
||||||
|
{vk::ShaderModuleCreateFlags(), spirv.size() * sizeof(u32),
|
||||||
|
spirv.data()});
|
||||||
|
} catch (const vk::SystemError& err) {
|
||||||
|
log::fatal("failed to create shader module");
|
||||||
|
throw std::runtime_error("failed to create shader module");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <sophia/types.hpp>
|
||||||
|
#include <vector>
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
|
#include "device.hpp"
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
class Pipeline {
|
||||||
|
public:
|
||||||
|
Pipeline(const Pipeline&) = delete;
|
||||||
|
Pipeline& operator=(const Pipeline&) = delete;
|
||||||
|
|
||||||
|
virtual ~Pipeline() = default;
|
||||||
|
|
||||||
|
void bind(vk::CommandBuffer commandBuffer);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Pipeline(Device& device, vk::PipelineBindPoint bindPoint);
|
||||||
|
|
||||||
|
vk::UniqueShaderModule createShaderModule(const std::vector<u32>& spirv);
|
||||||
|
|
||||||
|
Device& device;
|
||||||
|
vk::UniquePipeline pipeline;
|
||||||
|
|
||||||
|
private:
|
||||||
|
vk::PipelineBindPoint bindPoint;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace sophia {} // namespace sophia
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
#include "shader.hpp"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <shaderc/shaderc.hpp>
|
||||||
|
#include <sophia/util/logger.hpp>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
shaderc_shader_kind toShadercKind(ShaderStage shaderStage) {
|
||||||
|
switch (shaderStage) {
|
||||||
|
case ShaderStage::VERTEX:
|
||||||
|
return shaderc_vertex_shader;
|
||||||
|
case ShaderStage::FRAGMENT:
|
||||||
|
return shaderc_fragment_shader;
|
||||||
|
case ShaderStage::COMPUTE:
|
||||||
|
return shaderc_compute_shader;
|
||||||
|
}
|
||||||
|
throw std::invalid_argument("unsupported shader stage");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<char> readFile(const std::string& path) {
|
||||||
|
std::ifstream file(path, std::ios::ate | std::ios::binary);
|
||||||
|
|
||||||
|
if (!file.is_open()) {
|
||||||
|
log::error("failed to open shader: " + path);
|
||||||
|
throw std::runtime_error("failed to open: " + path);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t fileSize = static_cast<size_t>(file.tellg());
|
||||||
|
std::vector<char> buffer(fileSize);
|
||||||
|
|
||||||
|
file.seekg(0);
|
||||||
|
file.read(buffer.data(), fileSize);
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void Shader::compile(const std::string& path, ShaderStage shaderStage) {
|
||||||
|
this->stage = shaderStage;
|
||||||
|
|
||||||
|
std::vector<char> source = readFile(path);
|
||||||
|
|
||||||
|
log::info("Compiling shader: " + path);
|
||||||
|
|
||||||
|
shaderc::Compiler compiler;
|
||||||
|
shaderc::SpvCompilationResult result = compiler.CompileGlslToSpv(
|
||||||
|
source.data(), source.size(), toShadercKind(shaderStage), path.c_str());
|
||||||
|
|
||||||
|
if (result.GetCompilationStatus() != shaderc_compilation_status_success) {
|
||||||
|
log::fatal("failed to compile shader:", path, result.GetErrorMessage());
|
||||||
|
throw std::runtime_error("failed to compile shader: " + path + ": " +
|
||||||
|
result.GetErrorMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
this->spirv.assign(result.cbegin(), result.cend());
|
||||||
|
|
||||||
|
log::info("Successfully compiled shader: " + path + " (" +
|
||||||
|
std::to_string(this->spirv.size() * 4) + " bytes)");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shader::load(const std::string& path, ShaderStage shaderStage) {
|
||||||
|
this->stage = shaderStage;
|
||||||
|
|
||||||
|
std::vector<char> bytes = readFile(path);
|
||||||
|
|
||||||
|
if (bytes.size() % sizeof(u32) != 0) {
|
||||||
|
log::fatal("invalid SPIR-V binary (size not a multiple of 4):", path);
|
||||||
|
throw std::runtime_error("invalid SPIR-V binary: " + path);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->spirv.assign(
|
||||||
|
reinterpret_cast<const u32*>(bytes.data()),
|
||||||
|
reinterpret_cast<const u32*>(bytes.data() + bytes.size()));
|
||||||
|
|
||||||
|
log::info("Loaded precompiled shader: " + path + " (" +
|
||||||
|
std::to_string(bytes.size()) + " bytes)");
|
||||||
|
}
|
||||||
|
|
||||||
|
Shader::Builder& Shader::Builder::fromGLSL(const std::string& path) {
|
||||||
|
this->path = path;
|
||||||
|
this->precompiled = false;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Shader::Builder& Shader::Builder::fromSPIRV(const std::string& path) {
|
||||||
|
this->path = path;
|
||||||
|
this->precompiled = true;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Shader::Builder& Shader::Builder::setStage(ShaderStage shaderStage) {
|
||||||
|
this->stage = shaderStage;
|
||||||
|
this->stageSet = true;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Shader> Shader::Builder::build() {
|
||||||
|
if (this->path.empty()) {
|
||||||
|
log::fatal("failed to build shader: no source path provided");
|
||||||
|
throw std::runtime_error("failed to build shader: no source path provided");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this->stageSet) {
|
||||||
|
log::fatal("failed to build shader: no ShaderStage provided");
|
||||||
|
throw std::runtime_error("failed to build shader: no ShaderStage provided");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto shader = std::unique_ptr<Shader>(new Shader());
|
||||||
|
|
||||||
|
if (this->precompiled) {
|
||||||
|
shader->load(this->path, this->stage);
|
||||||
|
} else {
|
||||||
|
shader->compile(this->path, this->stage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return shader;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <sophia/types.hpp>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
enum class ShaderStage { VERTEX, FRAGMENT, COMPUTE };
|
||||||
|
|
||||||
|
class Shader {
|
||||||
|
public:
|
||||||
|
class Builder;
|
||||||
|
|
||||||
|
Shader(const Shader&) = delete;
|
||||||
|
Shader& operator=(const Shader&) = delete;
|
||||||
|
|
||||||
|
~Shader() = default;
|
||||||
|
|
||||||
|
const std::vector<u32>& getSpirv() const { return this->spirv; }
|
||||||
|
ShaderStage getStage() const { return this->stage; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Shader() = default;
|
||||||
|
|
||||||
|
void compile(const std::string& path, ShaderStage shaderStage);
|
||||||
|
void load(const std::string& path, ShaderStage shaderStage);
|
||||||
|
|
||||||
|
ShaderStage stage;
|
||||||
|
std::vector<u32> spirv;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Shader::Builder {
|
||||||
|
public:
|
||||||
|
Builder() = default;
|
||||||
|
|
||||||
|
Builder& fromGLSL(const std::string& path);
|
||||||
|
Builder& fromSPIRV(const std::string& path);
|
||||||
|
Builder& setStage(ShaderStage shaderStage);
|
||||||
|
|
||||||
|
std::unique_ptr<Shader> build();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string path;
|
||||||
|
ShaderStage stage;
|
||||||
|
bool stageSet = false;
|
||||||
|
bool precompiled = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,220 @@
|
|||||||
|
#include "swapchain.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <limits>
|
||||||
|
#include <sophia/util/logger.hpp>
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
Swapchain::Swapchain(Device& device, vk::Extent2D extent)
|
||||||
|
: device{device}, extent{extent} {
|
||||||
|
initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
Swapchain::Swapchain(Device& device, vk::Extent2D extent,
|
||||||
|
std::shared_ptr<Swapchain> previous)
|
||||||
|
: device{device}, extent{extent}, oldSwapchain{previous} {
|
||||||
|
initialize();
|
||||||
|
this->oldSwapchain = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Swapchain::~Swapchain() {
|
||||||
|
this->images.clear();
|
||||||
|
this->depthImages.clear();
|
||||||
|
|
||||||
|
this->device.get()->destroySwapchainKHR(this->swapchain);
|
||||||
|
// log::trace("destroyed vk::SwapchainKHR");
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::Result Swapchain::acquireNextImage(vk::Semaphore signalSemaphore,
|
||||||
|
u32& imageIndex) {
|
||||||
|
return this->device.get()->acquireNextImageKHR(
|
||||||
|
this->swapchain, std::numeric_limits<u64>::max(), signalSemaphore,
|
||||||
|
VK_NULL_HANDLE, &imageIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::Result Swapchain::present(vk::Queue presentQueue, u32 imageIndex,
|
||||||
|
vk::Semaphore waitSemaphore) {
|
||||||
|
vk::PresentInfoKHR presentInfo = {};
|
||||||
|
presentInfo.waitSemaphoreCount = 1;
|
||||||
|
presentInfo.pWaitSemaphores = &waitSemaphore;
|
||||||
|
|
||||||
|
vk::SwapchainKHR swapChains[] = {this->swapchain};
|
||||||
|
presentInfo.swapchainCount = 1;
|
||||||
|
presentInfo.pSwapchains = swapChains;
|
||||||
|
presentInfo.pImageIndices = &imageIndex;
|
||||||
|
|
||||||
|
return presentQueue.presentKHR(&presentInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Swapchain::initialize() {
|
||||||
|
setDefaultCreateInfo();
|
||||||
|
createSwapchain();
|
||||||
|
createImages();
|
||||||
|
createDepthImages();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Swapchain::setDefaultCreateInfo() {
|
||||||
|
SwapchainSupportDetails swapchainSupport = this->device.getSwapchainSupport();
|
||||||
|
|
||||||
|
vk::SurfaceFormatKHR surfaceFormat =
|
||||||
|
chooseSurfaceFormat(swapchainSupport.formats);
|
||||||
|
vk::PresentModeKHR presentMode =
|
||||||
|
choosePresentMode(swapchainSupport.presentModes);
|
||||||
|
this->extent = chooseExtent(swapchainSupport.capabilities);
|
||||||
|
|
||||||
|
u32 imageCount = swapchainSupport.capabilities.minImageCount + 1;
|
||||||
|
if (swapchainSupport.capabilities.maxImageCount > 0 &&
|
||||||
|
imageCount > swapchainSupport.capabilities.maxImageCount) {
|
||||||
|
imageCount = swapchainSupport.capabilities.maxImageCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->swapchainCreateInfo = {vk::SwapchainCreateFlagsKHR(),
|
||||||
|
this->device.getSurface(),
|
||||||
|
imageCount,
|
||||||
|
surfaceFormat.format,
|
||||||
|
surfaceFormat.colorSpace,
|
||||||
|
this->extent,
|
||||||
|
1,
|
||||||
|
vk::ImageUsageFlagBits::eColorAttachment};
|
||||||
|
|
||||||
|
QueueFamilyIndices indices = this->device.getQueueIndices();
|
||||||
|
u32 queueFamilyIndices[] = {indices.graphicsFamily.value(),
|
||||||
|
indices.presentFamily.value()};
|
||||||
|
|
||||||
|
if (indices.graphicsFamily != indices.presentFamily) {
|
||||||
|
swapchainCreateInfo.imageSharingMode = vk::SharingMode::eConcurrent;
|
||||||
|
swapchainCreateInfo.queueFamilyIndexCount = 2;
|
||||||
|
swapchainCreateInfo.pQueueFamilyIndices = queueFamilyIndices;
|
||||||
|
} else {
|
||||||
|
swapchainCreateInfo.imageSharingMode = vk::SharingMode::eExclusive;
|
||||||
|
}
|
||||||
|
|
||||||
|
swapchainCreateInfo.preTransform =
|
||||||
|
swapchainSupport.capabilities.currentTransform;
|
||||||
|
swapchainCreateInfo.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque;
|
||||||
|
swapchainCreateInfo.presentMode = presentMode;
|
||||||
|
swapchainCreateInfo.clipped = VK_TRUE;
|
||||||
|
|
||||||
|
swapchainCreateInfo.oldSwapchain = vk::SwapchainKHR(nullptr);
|
||||||
|
swapchainCreateInfo.oldSwapchain = this->oldSwapchain == nullptr
|
||||||
|
? VK_NULL_HANDLE
|
||||||
|
: this->oldSwapchain->swapchain;
|
||||||
|
|
||||||
|
this->imageFormat = surfaceFormat.format;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Swapchain::createSwapchain() {
|
||||||
|
try {
|
||||||
|
this->swapchain =
|
||||||
|
this->device.get()->createSwapchainKHR(this->swapchainCreateInfo);
|
||||||
|
// log::trace("created vk::SwapchainKHR");
|
||||||
|
} catch (const vk::SystemError& err) {
|
||||||
|
log::fatal("failed to create swapchain. Error: ", err.what());
|
||||||
|
throw std::runtime_error("failed to create swapchain");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Swapchain::createImages() {
|
||||||
|
std::vector<vk::Image> swapchainImages =
|
||||||
|
this->device.get()->getSwapchainImagesKHR(this->swapchain);
|
||||||
|
|
||||||
|
this->images.clear();
|
||||||
|
this->images.reserve(swapchainImages.size());
|
||||||
|
|
||||||
|
for (vk::Image image : swapchainImages) {
|
||||||
|
this->images.push_back(Image::Builder(this->device)
|
||||||
|
.wrapExisting(image)
|
||||||
|
.setFormat(this->imageFormat)
|
||||||
|
.setExtent(width(), height())
|
||||||
|
.withView()
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
// log::trace("created all swapchain vk::ImageView");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Swapchain::createDepthImages() {
|
||||||
|
this->depthFormat = findDepthFormat();
|
||||||
|
|
||||||
|
this->depthImages.clear();
|
||||||
|
this->depthImages.reserve(imageCount());
|
||||||
|
|
||||||
|
for (size_t i = 0; i < imageCount(); i++) {
|
||||||
|
this->depthImages.push_back(Image::Builder(this->device)
|
||||||
|
.asDepthAttachment()
|
||||||
|
.setFormat(this->depthFormat)
|
||||||
|
.setExtent(width(), height())
|
||||||
|
.withView()
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
// log::trace("created all depth resources");
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::SurfaceFormatKHR Swapchain::chooseSurfaceFormat(
|
||||||
|
const std::vector<vk::SurfaceFormatKHR>& availableFormats) {
|
||||||
|
if (availableFormats.size() == 1 &&
|
||||||
|
availableFormats[0].format == vk::Format::eUndefined) {
|
||||||
|
return {vk::Format::eB8G8R8A8Unorm, vk::ColorSpaceKHR::eSrgbNonlinear};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& availableFormat : availableFormats) {
|
||||||
|
if (availableFormat.format == vk::Format::eB8G8R8A8Unorm &&
|
||||||
|
availableFormat.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear) {
|
||||||
|
return availableFormat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info("No available surface formats, choosing default.");
|
||||||
|
return availableFormats[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::PresentModeKHR Swapchain::choosePresentMode(
|
||||||
|
const std::vector<vk::PresentModeKHR> availablePresentModes) {
|
||||||
|
vk::PresentModeKHR bestMode = vk::PresentModeKHR::eFifo;
|
||||||
|
|
||||||
|
for (const auto& availablePresentMode : availablePresentModes) {
|
||||||
|
if (availablePresentMode == vk::PresentModeKHR::eMailbox) {
|
||||||
|
log::info("Selected present mode: MAILBOX (triple buffering)");
|
||||||
|
return availablePresentMode;
|
||||||
|
} else if (availablePresentMode == vk::PresentModeKHR::eImmediate) {
|
||||||
|
bestMode = availablePresentMode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log::warning("Present mode MAILBOX not available.");
|
||||||
|
|
||||||
|
if (bestMode == vk::PresentModeKHR::eImmediate) {
|
||||||
|
log::info("Selected present mode: IMMEDIATE (no vsync)");
|
||||||
|
} else {
|
||||||
|
log::info("Selected present mode: FIFO (vsync)");
|
||||||
|
}
|
||||||
|
|
||||||
|
return bestMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::Extent2D Swapchain::chooseExtent(
|
||||||
|
const vk::SurfaceCapabilitiesKHR& capabilities) {
|
||||||
|
if (capabilities.currentExtent.width != std::numeric_limits<u32>::max()) {
|
||||||
|
return capabilities.currentExtent;
|
||||||
|
} else {
|
||||||
|
vk::Extent2D choosenExtent = {};
|
||||||
|
choosenExtent.width =
|
||||||
|
std::clamp(this->extent.width, capabilities.minImageExtent.width,
|
||||||
|
capabilities.maxImageExtent.width);
|
||||||
|
choosenExtent.height =
|
||||||
|
std::clamp(this->extent.height, capabilities.minImageExtent.height,
|
||||||
|
capabilities.maxImageExtent.height);
|
||||||
|
return choosenExtent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::Format Swapchain::findDepthFormat() {
|
||||||
|
return this->device.findSupportedFormat(
|
||||||
|
{vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint,
|
||||||
|
vk::Format::eD24UnormS8Uint},
|
||||||
|
vk::ImageTiling::eOptimal,
|
||||||
|
vk::FormatFeatureFlagBits::eDepthStencilAttachment);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <sophia/types.hpp>
|
||||||
|
#include <vector>
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
|
#include "device.hpp"
|
||||||
|
#include "image.hpp"
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
class Swapchain {
|
||||||
|
public:
|
||||||
|
Swapchain(const Swapchain&) = delete;
|
||||||
|
Swapchain& operator=(const Swapchain&) = delete;
|
||||||
|
|
||||||
|
Swapchain(Device& device, vk::Extent2D extent);
|
||||||
|
Swapchain(Device& device, vk::Extent2D extent,
|
||||||
|
std::shared_ptr<Swapchain> previous);
|
||||||
|
~Swapchain();
|
||||||
|
|
||||||
|
Image& getImage(int index) const { return *this->images.at(index); }
|
||||||
|
Image& getDepthImage(int index) const { return *this->depthImages.at(index); }
|
||||||
|
|
||||||
|
vk::ImageView getImageView(int index) const {
|
||||||
|
return this->images.at(index)->getView();
|
||||||
|
}
|
||||||
|
vk::ImageView getDepthImageView(int index) const {
|
||||||
|
return this->depthImages.at(index)->getView();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t imageCount() { return this->images.size(); }
|
||||||
|
vk::Format getImageFormat() { return this->imageFormat; }
|
||||||
|
vk::Extent2D getExtent() { return this->extent; }
|
||||||
|
u32 width() { return this->extent.width; }
|
||||||
|
u32 height() { return this->extent.height; }
|
||||||
|
|
||||||
|
float extentAspectRatio() {
|
||||||
|
return static_cast<float>(this->extent.width) /
|
||||||
|
static_cast<float>(this->extent.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::Result acquireNextImage(vk::Semaphore signalSemaphore, u32& imageIndex);
|
||||||
|
|
||||||
|
vk::Result present(vk::Queue presentQueue, u32 imageIndex,
|
||||||
|
vk::Semaphore waitSemaphore);
|
||||||
|
|
||||||
|
bool compareSwapchainFormats(const Swapchain& swapchain) const {
|
||||||
|
return swapchain.depthFormat == this->depthFormat &&
|
||||||
|
swapchain.imageFormat == this->imageFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void initialize();
|
||||||
|
void setDefaultCreateInfo();
|
||||||
|
void createSwapchain();
|
||||||
|
void createImages();
|
||||||
|
void createDepthImages();
|
||||||
|
|
||||||
|
vk::SurfaceFormatKHR chooseSurfaceFormat(
|
||||||
|
const std::vector<vk::SurfaceFormatKHR>& availableFormats);
|
||||||
|
vk::PresentModeKHR choosePresentMode(
|
||||||
|
const std::vector<vk::PresentModeKHR> availablePresentModes);
|
||||||
|
vk::Extent2D chooseExtent(const vk::SurfaceCapabilitiesKHR& capabilities);
|
||||||
|
|
||||||
|
vk::Format findDepthFormat();
|
||||||
|
|
||||||
|
Device& device;
|
||||||
|
vk::Extent2D extent;
|
||||||
|
|
||||||
|
vk::SwapchainKHR swapchain;
|
||||||
|
vk::SwapchainCreateInfoKHR swapchainCreateInfo;
|
||||||
|
std::shared_ptr<Swapchain> oldSwapchain;
|
||||||
|
|
||||||
|
vk::Format imageFormat;
|
||||||
|
std::vector<std::unique_ptr<Image>> images;
|
||||||
|
|
||||||
|
vk::Format depthFormat;
|
||||||
|
std::vector<std::unique_ptr<Image>> depthImages;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
#include "window.hpp"
|
#include "window.hpp"
|
||||||
|
|
||||||
#include "key_event.hpp"
|
#include <sophia/key_event.hpp>
|
||||||
#include "util/logger.hpp"
|
#include <sophia/util/logger.hpp>
|
||||||
|
|
||||||
namespace hep {
|
namespace sophia {
|
||||||
|
|
||||||
Window::Window(int width, int height, const std::string& name)
|
Window::Window(int width, int height, const std::string& name)
|
||||||
: width{width}, height{height}, name{name} {
|
: width{width}, height{height}, name{name} {
|
||||||
@@ -28,11 +28,8 @@ void Window::createSurface(const vk::Instance& instance,
|
|||||||
log::trace("Created vk::SurfaceKHR.");
|
log::trace("Created vk::SurfaceKHR.");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Window::keyEventCallback(GLFWwindow* window,
|
void Window::keyEventCallback(GLFWwindow* window, int key, int scancode,
|
||||||
int key,
|
int action, int mods) {
|
||||||
int scancode,
|
|
||||||
int action,
|
|
||||||
int mods) {
|
|
||||||
if (action == GLFW_PRESS) {
|
if (action == GLFW_PRESS) {
|
||||||
KeyPressedEvent event{static_cast<KeyCode>(key)};
|
KeyPressedEvent event{static_cast<KeyCode>(key)};
|
||||||
EventSystem::get().dispatch(event);
|
EventSystem::get().dispatch(event);
|
||||||
@@ -61,4 +58,4 @@ void Window::initialize() {
|
|||||||
glfwSetKeyCallback(this->window, keyEventCallback);
|
glfwSetKeyCallback(this->window, keyEventCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace hep
|
} // namespace sophia
|
||||||
|
|||||||
@@ -6,17 +6,17 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vulkan/vulkan.hpp>
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
#include "event.hpp"
|
#include <sophia/event.hpp>
|
||||||
#include "types.hpp"
|
#include <sophia/types.hpp>
|
||||||
|
|
||||||
namespace hep {
|
namespace sophia {
|
||||||
|
|
||||||
class Window {
|
class Window {
|
||||||
public:
|
public:
|
||||||
Window(const Window&) = delete;
|
Window(const Window&) = delete;
|
||||||
Window& operator=(const Window&) = delete;
|
Window& operator=(const Window&) = delete;
|
||||||
|
|
||||||
Window(int width, int height, const std::string& name = "hephaestus");
|
Window(int width, int height, const std::string& name = "sophiahaestus");
|
||||||
~Window();
|
~Window();
|
||||||
|
|
||||||
bool shouldClose() { return glfwWindowShouldClose(window); }
|
bool shouldClose() { return glfwWindowShouldClose(window); }
|
||||||
@@ -48,4 +48,4 @@ class Window {
|
|||||||
GLFWwindow* window;
|
GLFWwindow* window;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace hep
|
} // namespace sophia
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
file(GLOB EXAMPLE_DIRS CONFIGURE_DEPENDS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/*)
|
||||||
|
|
||||||
|
foreach(dir ${EXAMPLE_DIRS})
|
||||||
|
if(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${dir} AND EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${dir}/CMakeLists.txt)
|
||||||
|
add_subdirectory(${dir})
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
add_executable(triangle main.cpp)
|
||||||
|
|
||||||
|
target_link_libraries(triangle PRIVATE ${PROJECT_NAME})
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
#include <sophia/application.hpp>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
sophia::Application app{{.width = 800, .height = 600, .name = "Triangle"}};
|
||||||
|
|
||||||
|
app.run();
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
set(NAME testbed)
|
set(NAME testbed)
|
||||||
set(ENGINE_NAME ${PROJECT_NAME})
|
set(ENGINE_NAME ${PROJECT_NAME})
|
||||||
|
|
||||||
file(GLOB_RECURSE SOURCES *.cpp src/*.cpp)
|
file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS *.cpp src/*.cpp)
|
||||||
|
|
||||||
add_library(deps INTERFACE)
|
add_library(deps INTERFACE)
|
||||||
|
|
||||||
@@ -21,7 +21,11 @@ add_executable(testbed main.cpp)
|
|||||||
|
|
||||||
target_link_libraries(testbed PRIVATE deps)
|
target_link_libraries(testbed PRIVATE deps)
|
||||||
|
|
||||||
target_include_directories(testbed PRIVATE
|
target_include_directories(testbed PRIVATE
|
||||||
"${CMAKE_SOURCE_DIR}/engine/include"
|
"${CMAKE_SOURCE_DIR}/engine/include"
|
||||||
"${CMAKE_SOURCE_DIR}/external/imgui"
|
"${CMAKE_SOURCE_DIR}/external/imgui"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
target_compile_definitions(testbed PRIVATE
|
||||||
|
TESTBED_SHADER_DIR="${CMAKE_CURRENT_SOURCE_DIR}/shaders"
|
||||||
|
)
|
||||||
|
|||||||
+15
-2
@@ -1,11 +1,24 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
// #include <sophia/application.hpp>
|
||||||
|
#include <sophia/compute_smoke_test.hpp>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
|
||||||
int main(int argc, const char **argv)
|
int main(int argc, const char** argv) {
|
||||||
{
|
|
||||||
(void)argc;
|
(void)argc;
|
||||||
(void)argv;
|
(void)argv;
|
||||||
std::cout << __FILE__ << "::" << __LINE__ << '\n';
|
std::cout << __FILE__ << "::" << __LINE__ << '\n';
|
||||||
|
|
||||||
|
try {
|
||||||
|
sophia::runComputeSmokeTest(TESTBED_SHADER_DIR "/smoke_test.comp");
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
std::cerr << "Compute smoke test failed: " << e.what() << '\n';
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sophia::Application app{
|
||||||
|
// {.width = 750, .height = 1000, .name = "Testbed", .headless = true}};
|
||||||
|
|
||||||
|
// app.run();
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
layout(local_size_x = 64) in;
|
||||||
|
|
||||||
|
layout(std430, binding = 0) buffer Data {
|
||||||
|
float values[];
|
||||||
|
};
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
uint idx = gl_GlobalInvocationID.x;
|
||||||
|
values[idx] = values[idx] * 2.0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user