Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 17fe9de756 | |||
| 2f5268fef6 | |||
| 09a6c0283a | |||
| 3784d0fdec | |||
| bfc96bac2b | |||
| 0f0b0d1434 | |||
| 777e407d67 | |||
| e32e5f911f | |||
| 5b2d50a925 | |||
| f6f4f8b7c9 | |||
| b6098d4dc6 | |||
| b31c120691 |
+1
-1
@@ -5,7 +5,7 @@ set(NAME sophia)
|
|||||||
project(${NAME} VERSION 0.0.0)
|
project(${NAME} VERSION 0.0.0)
|
||||||
|
|
||||||
enable_language(CXX)
|
enable_language(CXX)
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ target_link_libraries(${NAME}_deps INTERFACE
|
|||||||
imguizmo
|
imguizmo
|
||||||
)
|
)
|
||||||
|
|
||||||
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17)
|
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_23)
|
||||||
set_property(TARGET ${PROJECT_NAME} PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/build")
|
set_property(TARGET ${PROJECT_NAME} PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/build")
|
||||||
|
|
||||||
add_executable(editor ${SOURCES})
|
add_executable(editor ${SOURCES})
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <format>
|
||||||
#include <sophia/util/logger.hpp>
|
#include <sophia/util/logger.hpp>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
void log(Args... args) {
|
void log(std::format_string<Args...> fmt, Args&&... args) {
|
||||||
sophia::log::print("\033[1;38;2;170;90;255m[editor]\033[0m", args...);
|
if constexpr (sophia::log::enabled) {
|
||||||
|
sophia::log::print("\033[1;38;2;170;90;255m[editor]\033[0m {}",
|
||||||
|
std::format(fmt, std::forward<Args>(args)...));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace sophia {
|
|||||||
|
|
||||||
struct TagComponent {
|
struct TagComponent {
|
||||||
std::string tag; // entt::hashed_string ?
|
std::string tag; // entt::hashed_string ?
|
||||||
uint32_t mask = 0;
|
u32 mask = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TransformComponent {
|
struct TransformComponent {
|
||||||
@@ -100,7 +100,7 @@ struct CameraComponent {
|
|||||||
float farPlane = 100.f;
|
float farPlane = 100.f;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class Projection { Orthographic, Perspective };
|
enum class Projection : u8 { Orthographic, Perspective };
|
||||||
|
|
||||||
Projection projection = Projection::Orthographic;
|
Projection projection = Projection::Orthographic;
|
||||||
Orthographic ortho;
|
Orthographic ortho;
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ class EventSystem {
|
|||||||
this->eventDelegates[eventType] =
|
this->eventDelegates[eventType] =
|
||||||
std::make_unique<std::any>(EventDelegate<E>());
|
std::make_unique<std::any>(EventDelegate<E>());
|
||||||
|
|
||||||
log::trace("EventSystem creating new event type: ", eventType);
|
log::trace("EventSystem creating new event type: {}", eventType);
|
||||||
}
|
}
|
||||||
|
|
||||||
EventDelegate<E>& delegate =
|
EventDelegate<E>& delegate =
|
||||||
@@ -102,7 +102,7 @@ class EventSystem {
|
|||||||
this->eventDelegates[eventType] =
|
this->eventDelegates[eventType] =
|
||||||
std::make_unique<std::any>(EventDelegate<E>());
|
std::make_unique<std::any>(EventDelegate<E>());
|
||||||
|
|
||||||
log::trace("EventSystem creating new event type: ", eventType);
|
log::trace("EventSystem creating new event type: {}", eventType);
|
||||||
}
|
}
|
||||||
|
|
||||||
EventDelegate<E>& delegate =
|
EventDelegate<E>& delegate =
|
||||||
@@ -124,12 +124,14 @@ class EventSystem {
|
|||||||
std::any_cast<EventDelegate<E>&>(*this->eventDelegates.at(eventType));
|
std::any_cast<EventDelegate<E>&>(*this->eventDelegates.at(eventType));
|
||||||
delegate.invoke(event);
|
delegate.invoke(event);
|
||||||
} catch (const std::out_of_range& error) {
|
} catch (const std::out_of_range& error) {
|
||||||
log::error("event dispatch failed: ", event.toString());
|
log::error("event dispatch failed: {}", event.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
EventSystem() { log::info("EventSystem Initialized: ", this); }
|
EventSystem() {
|
||||||
|
log::info("EventSystem Initialized: {}", static_cast<const void*>(this));
|
||||||
|
}
|
||||||
~EventSystem() { log::info("EventSystem Destroyed"); }
|
~EventSystem() { log::info("EventSystem Destroyed"); }
|
||||||
|
|
||||||
std::unordered_map<EventType, std::unique_ptr<std::any>> eventDelegates;
|
std::unordered_map<EventType, std::unique_ptr<std::any>> eventDelegates;
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class PanelRegistry {
|
|||||||
const T& get(PanelHandle handle) const {
|
const T& get(PanelHandle handle) const {
|
||||||
const Panel& panel = get(handle);
|
const Panel& panel = get(handle);
|
||||||
SOPHIA_ASSERT(dynamic_cast<const T*>(&panel) != nullptr,
|
SOPHIA_ASSERT(dynamic_cast<const T*>(&panel) != nullptr,
|
||||||
"PanelHandle does not refer to a", typeid(T).name());
|
"PanelHandle does not refer to a {}", typeid(T).name());
|
||||||
return static_cast<const T&>(panel);
|
return static_cast<const T&>(panel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
/**
|
/**
|
||||||
* Usage:
|
* Usage:
|
||||||
* SOPHIA_ASSERT(pipelineLayout != nullptr);
|
* SOPHIA_ASSERT(pipelineLayout != nullptr);
|
||||||
* SOPHIA_ASSERT(index < count, "index", index, "out of bounds:", count);
|
* SOPHIA_ASSERT(index < count, "index {} out of bounds: {}", index, count);
|
||||||
|
*
|
||||||
|
* The optional message is a std::format string, checked at compile time.
|
||||||
*
|
*
|
||||||
* Compiled out entirely in release (NDEBUG) builds, matching <cassert>'s
|
* Compiled out entirely in release (NDEBUG) builds, matching <cassert>'s
|
||||||
* assert() semantics: don't rely on side effects inside the condition.
|
* assert() semantics: don't rely on side effects inside the condition.
|
||||||
@@ -10,14 +12,16 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <format>
|
||||||
#include <sophia/util/logger.hpp>
|
#include <sophia/util/logger.hpp>
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
#define SOPHIA_ASSERT(condition, ...) \
|
#define SOPHIA_ASSERT(condition, ...) \
|
||||||
do { \
|
do { \
|
||||||
if (!(condition)) { \
|
if (!(condition)) { \
|
||||||
::sophia::log::fatal("Assertion failed:", #condition, "at", __FILE__, \
|
::sophia::log::fatal( \
|
||||||
":", __LINE__ __VA_OPT__(, ) __VA_ARGS__); \
|
"Assertion failed: {} at {}:{}" __VA_OPT__(" - {}"), #condition, \
|
||||||
|
__FILE__, __LINE__ __VA_OPT__(, ::std::format(__VA_ARGS__))); \
|
||||||
std::abort(); \
|
std::abort(); \
|
||||||
} \
|
} \
|
||||||
} while (false)
|
} while (false)
|
||||||
|
|||||||
@@ -2,13 +2,12 @@
|
|||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <sophia/util/logger.hpp>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <typeinfo>
|
#include <typeinfo>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <sophia/util/logger.hpp>
|
|
||||||
|
|
||||||
#define DEFAULT_DELEGATE_FUNCTOR_ALLOC 5
|
#define DEFAULT_DELEGATE_FUNCTOR_ALLOC 5
|
||||||
|
|
||||||
namespace sophia {
|
namespace sophia {
|
||||||
@@ -39,7 +38,9 @@ class expired_weak_object : public std::runtime_error {
|
|||||||
template <class O, typename... A>
|
template <class O, typename... A>
|
||||||
std::function<void(A...)> createWeakFunction(void (O::*method)(A...),
|
std::function<void(A...)> createWeakFunction(void (O::*method)(A...),
|
||||||
std::shared_ptr<O> obj) {
|
std::shared_ptr<O> obj) {
|
||||||
if (!obj) { throw std::invalid_argument("method and obj must not be null"); }
|
if (!obj) {
|
||||||
|
throw std::invalid_argument("method and obj must not be null");
|
||||||
|
}
|
||||||
|
|
||||||
std::weak_ptr<O> weakObj = obj;
|
std::weak_ptr<O> weakObj = obj;
|
||||||
|
|
||||||
@@ -82,7 +83,7 @@ class Delegate {
|
|||||||
try {
|
try {
|
||||||
this->functors[i](std::forward<A>(args)...);
|
this->functors[i](std::forward<A>(args)...);
|
||||||
} catch (const expired_weak_object& e) {
|
} catch (const expired_weak_object& e) {
|
||||||
log::error("delegate invoke error: ", e.what());
|
log::error("delegate invoke error: {}", e.what());
|
||||||
deadFunctorIndexs.push_back(i);
|
deadFunctorIndexs.push_back(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,7 +98,9 @@ class Delegate {
|
|||||||
void operator=(Functor c) {
|
void operator=(Functor c) {
|
||||||
clear();
|
clear();
|
||||||
|
|
||||||
if (c != nullptr) { add(c); }
|
if (c != nullptr) {
|
||||||
|
add(c);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator+=(Functor c) { add(c); }
|
void operator+=(Functor c) { add(c); }
|
||||||
|
|||||||
@@ -1,23 +1,36 @@
|
|||||||
/**
|
/**
|
||||||
* Lots of work still needs to be done in here.
|
* std::format-style logging. Format strings are checked at compile time.
|
||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
* using namespace sophia;
|
* using namespace sophia;
|
||||||
* log::fatal("fatal message")
|
* log::fatal("failed to load {}", path)
|
||||||
* log::info("Infomation message")
|
* log::info("loaded {} meshes in {:.2f}ms", count, ms)
|
||||||
|
* log::debug("value: {}", x) // prefixed with file:line
|
||||||
* log::print("no prefix, just the message")
|
* log::print("no prefix, just the message")
|
||||||
*
|
*
|
||||||
|
* Everything is compiled out in release (NDEBUG) builds.
|
||||||
|
*
|
||||||
* TODO Impl some form of log error handling
|
* TODO Impl some form of log error handling
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <iostream>
|
#include <cstdio>
|
||||||
#include <sophia/types.hpp>
|
#include <format>
|
||||||
|
#include <print>
|
||||||
|
#include <source_location>
|
||||||
|
#include <string_view>
|
||||||
|
#include <type_traits>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
namespace sophia::log {
|
namespace sophia::log {
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
inline constexpr bool enabled = true;
|
||||||
|
#else
|
||||||
|
inline constexpr bool enabled = false;
|
||||||
|
#endif // !NDEBUG
|
||||||
|
|
||||||
enum class LEVEL {
|
enum class LEVEL {
|
||||||
NONE,
|
NONE,
|
||||||
FATAL,
|
FATAL,
|
||||||
@@ -29,118 +42,113 @@ enum class LEVEL {
|
|||||||
TRACE,
|
TRACE,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper print functions
|
namespace detail {
|
||||||
#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>
|
constexpr std::string_view prefix(LEVEL level) {
|
||||||
void log(LEVEL level = LEVEL::NONE, Args... args) {
|
|
||||||
#ifndef NDEBUG
|
|
||||||
switch (level) {
|
switch (level) {
|
||||||
case LEVEL::FATAL:
|
case LEVEL::FATAL:
|
||||||
LOG_PREFIX_FATAL;
|
return "\033[1;91m[FATAL]\033[0m ";
|
||||||
break;
|
|
||||||
case LEVEL::ERROR:
|
case LEVEL::ERROR:
|
||||||
LOG_PREFIX_ERROR;
|
return "\033[1;31m[ERROR]\033[0m ";
|
||||||
break;
|
|
||||||
case LEVEL::WARNING:
|
case LEVEL::WARNING:
|
||||||
LOG_PREFIX_WARNING;
|
return "\033[1;33m[WARNING]\033[0m ";
|
||||||
break;
|
|
||||||
case LEVEL::INFO:
|
case LEVEL::INFO:
|
||||||
LOG_PREFIX_INFO;
|
return "\033[32m[INFO]\033[0m ";
|
||||||
break;
|
|
||||||
case LEVEL::VERBOSE:
|
case LEVEL::VERBOSE:
|
||||||
LOG_PREFIX_VERBOSE;
|
return "\033[36m[VERBOSE]\033[0m ";
|
||||||
break;
|
case LEVEL::DEBUGGING:
|
||||||
// case LEVEL::DEBUGGING:
|
return "\033[1;34m[DEBUG]\033[0m ";
|
||||||
// LOG_PREFIX_DEBUG;
|
|
||||||
// break;
|
|
||||||
case LEVEL::TRACE:
|
case LEVEL::TRACE:
|
||||||
LOG_PREFIX_TRACE;
|
return "\033[1;90m[TRACE]\033[0m ";
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
std::cout << "\033[1;90m[LOG]\033[0m ";
|
return "\033[1;90m[LOG]\033[0m ";
|
||||||
break;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
((std::cout << ' ' << std::forward<Args>(args)), ...);
|
inline void write(std::string_view prefix, std::string_view message) {
|
||||||
std::cout << std::endl;
|
std::println(stdout, "{}{}", prefix, message);
|
||||||
#else
|
std::fflush(stdout);
|
||||||
(void)level;
|
}
|
||||||
(void)std::initializer_list<int>{((void)args, 0)...};
|
|
||||||
#endif // !NDEBUG
|
// Captures the caller's location alongside the format string, since a
|
||||||
|
// defaulted std::source_location parameter can't follow a parameter pack.
|
||||||
|
template <typename... Args>
|
||||||
|
struct FormatWithLocation {
|
||||||
|
std::format_string<Args...> fmt;
|
||||||
|
std::source_location location;
|
||||||
|
|
||||||
|
template <typename S>
|
||||||
|
requires std::is_convertible_v<const S&, std::string_view>
|
||||||
|
consteval FormatWithLocation(
|
||||||
|
const S& fmt,
|
||||||
|
std::source_location location = std::source_location::current())
|
||||||
|
: fmt(fmt), location(location) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
void log(LEVEL level, std::format_string<Args...> fmt, Args&&... args) {
|
||||||
|
if constexpr (enabled) {
|
||||||
|
detail::write(detail::prefix(level),
|
||||||
|
std::format(fmt, std::forward<Args>(args)...));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
void fatal(Args... args) {
|
void fatal(std::format_string<Args...> fmt, Args&&... args) {
|
||||||
log(LEVEL::FATAL, args...);
|
log(LEVEL::FATAL, fmt, std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
void error(Args... args) {
|
void error(std::format_string<Args...> fmt, Args&&... args) {
|
||||||
log(LEVEL::ERROR, args...);
|
log(LEVEL::ERROR, fmt, std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
void warning(Args... args) {
|
void warning(std::format_string<Args...> fmt, Args&&... args) {
|
||||||
log(LEVEL::WARNING, args...);
|
log(LEVEL::WARNING, fmt, std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
void info(Args... args) {
|
void info(std::format_string<Args...> fmt, Args&&... args) {
|
||||||
log(LEVEL::INFO, args...);
|
log(LEVEL::INFO, fmt, std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
void verbose(Args... args) {
|
void verbose(std::format_string<Args...> fmt, Args&&... args) {
|
||||||
log(LEVEL::VERBOSE, args...);
|
log(LEVEL::VERBOSE, fmt, std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
void debug(const char* file, u16 line, Args... args) {
|
void debug(detail::FormatWithLocation<std::type_identity_t<Args>...> fmt,
|
||||||
#ifndef NDEBUG
|
Args&&... args) {
|
||||||
LOG_PREFIX_DEBUG;
|
if constexpr (enabled) {
|
||||||
std::cout << file << "::" << line;
|
detail::write(
|
||||||
((std::cout << ' ' << std::forward<Args>(args)), ...);
|
detail::prefix(LEVEL::DEBUGGING),
|
||||||
std::cout << std::endl;
|
std::format("{}:{} {}", fmt.location.file_name(), fmt.location.line(),
|
||||||
#else
|
std::format(fmt.fmt, std::forward<Args>(args)...)));
|
||||||
(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>
|
template <typename... Args>
|
||||||
void trace(Args... args) {
|
void trace(std::format_string<Args...> fmt, Args&&... args) {
|
||||||
log(LEVEL::TRACE, args...);
|
log(LEVEL::TRACE, fmt, std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Like the functions above, but with no "[LEVEL]" prefix: just the arguments,
|
// Like the functions above, but with no "[LEVEL]" prefix. With no arguments,
|
||||||
// separated by spaces, then a newline. With none, an empty line.
|
// an empty line.
|
||||||
inline void print() {
|
inline void print() {
|
||||||
#ifndef NDEBUG
|
if constexpr (enabled) {
|
||||||
std::cout << std::endl;
|
detail::write("", "");
|
||||||
#endif // !NDEBUG
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename First, typename... Rest>
|
template <typename... Args>
|
||||||
void print(First first, Rest... rest) {
|
void print(std::format_string<Args...> fmt, Args&&... args) {
|
||||||
#ifndef NDEBUG
|
if constexpr (enabled) {
|
||||||
std::cout << first;
|
detail::write("", std::format(fmt, std::forward<Args>(args)...));
|
||||||
((std::cout << ' ' << rest), ...);
|
}
|
||||||
std::cout << std::endl;
|
|
||||||
#else
|
|
||||||
(void)first;
|
|
||||||
(void)std::initializer_list<int>{((void)rest, 0)...};
|
|
||||||
#endif // !NDEBUG
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace sophia::log
|
} // namespace sophia::log
|
||||||
|
|||||||
@@ -111,15 +111,12 @@ class Pool {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const Slot& validate(Handle<T> handle) const {
|
const Slot& validate(Handle<T> handle) const {
|
||||||
SOPHIA_ASSERT(handle.isValid(), "invalid handle:", name);
|
SOPHIA_ASSERT(handle.isValid(), "invalid handle: {}", name);
|
||||||
SOPHIA_ASSERT(handle.index < slots.size(), name,
|
SOPHIA_ASSERT(handle.index < slots.size(), "{} handle index out of range: {}", name, static_cast<u32>(handle.index));
|
||||||
"handle index out of range:", handle.index);
|
|
||||||
|
|
||||||
const Slot& slot = slots[handle.index];
|
const Slot& slot = slots[handle.index];
|
||||||
SOPHIA_ASSERT(slot.generation == handle.generation, "stale", name,
|
SOPHIA_ASSERT(slot.generation == handle.generation, "stale {} handle (generation mismatch): {}", name, static_cast<u32>(handle.index));
|
||||||
"handle (generation mismatch):", handle.index);
|
SOPHIA_ASSERT(slot.item != nullptr, "{} handle refers to a released item: {}", name, static_cast<u32>(handle.index));
|
||||||
SOPHIA_ASSERT(slot.item != nullptr, name,
|
|
||||||
"handle refers to a released item:", handle.index);
|
|
||||||
return slot;
|
return slot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,17 +35,6 @@ namespace sophia {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
struct Vertex {
|
|
||||||
glm::vec2 position;
|
|
||||||
glm::vec3 color;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PushConstants {
|
|
||||||
glm::mat4 transform;
|
|
||||||
glm::vec4 color;
|
|
||||||
glm::vec4 data;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<std::string> commandLineArgs;
|
std::vector<std::string> commandLineArgs;
|
||||||
|
|
||||||
constexpr ClearColor CLEAR_COLOR = {0.01f, 0.01f, 0.02f, 1.0f};
|
constexpr ClearColor CLEAR_COLOR = {0.01f, 0.01f, 0.02f, 1.0f};
|
||||||
@@ -287,7 +276,7 @@ class Application::Impl {
|
|||||||
auto endTime = std::chrono::high_resolution_clock::now();
|
auto endTime = std::chrono::high_resolution_clock::now();
|
||||||
double totalRuntime =
|
double totalRuntime =
|
||||||
std::chrono::duration<double>(endTime - startTime).count();
|
std::chrono::duration<double>(endTime - startTime).count();
|
||||||
log::info("Application ran for", totalRuntime, "s");
|
log::info("Application ran for {:.2f}s", totalRuntime);
|
||||||
}
|
}
|
||||||
|
|
||||||
void pushMesh(std::vector<CanvasVertex> vertices, std::vector<u32> indices) {
|
void pushMesh(std::vector<CanvasVertex> vertices, std::vector<u32> indices) {
|
||||||
|
|||||||
@@ -43,8 +43,9 @@ void MeshRenderSystem::render(vk::CommandBuffer commandBuffer,
|
|||||||
|
|
||||||
u32 available = MAX_INSTANCES_PER_FRAME - this->instanceCursor;
|
u32 available = MAX_INSTANCES_PER_FRAME - this->instanceCursor;
|
||||||
if (instances.size() > available) {
|
if (instances.size() > available) {
|
||||||
log::warning("MeshRenderSystem: frame instance buffer full, dropping",
|
log::warning(
|
||||||
instances.size() - available, "meshes");
|
"MeshRenderSystem: frame instance buffer full, dropping {} meshes",
|
||||||
|
instances.size() - available);
|
||||||
}
|
}
|
||||||
|
|
||||||
Buffer& buffer = *this->instanceBuffers[this->frameIndex];
|
Buffer& buffer = *this->instanceBuffers[this->frameIndex];
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ vk::CommandBuffer RenderContext::beginFrame() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fenceResult != vk::Result::eSuccess) {
|
if (fenceResult != vk::Result::eSuccess) {
|
||||||
log::error("failed to wait for fence: " + vk::to_string(fenceResult));
|
log::error("failed to wait for fence: {}", vk::to_string(fenceResult));
|
||||||
}
|
}
|
||||||
|
|
||||||
vk::Semaphore imageAvailable = frame.imageAvailable.get();
|
vk::Semaphore imageAvailable = frame.imageAvailable.get();
|
||||||
@@ -62,7 +62,7 @@ vk::CommandBuffer RenderContext::beginFrame() {
|
|||||||
imageInFlight, vk::True, std::numeric_limits<u64>::max());
|
imageInFlight, vk::True, std::numeric_limits<u64>::max());
|
||||||
|
|
||||||
if (fenceResult != vk::Result::eSuccess) {
|
if (fenceResult != vk::Result::eSuccess) {
|
||||||
log::error("failed to wait for fence: " + vk::to_string(fenceResult));
|
log::error("failed to wait for fence: {}", vk::to_string(fenceResult));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -106,8 +106,8 @@ void RenderGraph::recordRendering(vk::CommandBuffer commandBuffer, Pass& pass) {
|
|||||||
const Image& image = *this->images[color.image.index].image;
|
const Image& image = *this->images[color.image.index].image;
|
||||||
SOPHIA_ASSERT(image.getExtent().width == extent.width &&
|
SOPHIA_ASSERT(image.getExtent().width == extent.width &&
|
||||||
image.getExtent().height == extent.height,
|
image.getExtent().height == extent.height,
|
||||||
"Pass", pass.name,
|
"Pass {} has color attachments of different sizes",
|
||||||
"has color attachments of different sizes");
|
pass.name);
|
||||||
|
|
||||||
vk::RenderingAttachmentInfo attachment{};
|
vk::RenderingAttachmentInfo attachment{};
|
||||||
attachment.imageView = image.getView();
|
attachment.imageView = image.getView();
|
||||||
@@ -129,8 +129,8 @@ void RenderGraph::recordRendering(vk::CommandBuffer commandBuffer, Pass& pass) {
|
|||||||
const Image& image = *this->images[pass.depthAttachment->image.index].image;
|
const Image& image = *this->images[pass.depthAttachment->image.index].image;
|
||||||
SOPHIA_ASSERT(image.getExtent().width == extent.width &&
|
SOPHIA_ASSERT(image.getExtent().width == extent.width &&
|
||||||
image.getExtent().height == extent.height,
|
image.getExtent().height == extent.height,
|
||||||
"Pass", pass.name,
|
"Pass {} has a depth attachment of a different size",
|
||||||
"has a depth attachment of a different size");
|
pass.name);
|
||||||
|
|
||||||
depthAttachment.imageView = image.getView();
|
depthAttachment.imageView = image.getView();
|
||||||
depthAttachment.imageLayout =
|
depthAttachment.imageLayout =
|
||||||
@@ -168,8 +168,8 @@ RenderGraph::PassBuilder& RenderGraph::PassBuilder::writeColor(
|
|||||||
|
|
||||||
RenderGraph::PassBuilder& RenderGraph::PassBuilder::writeDepth(
|
RenderGraph::PassBuilder& RenderGraph::PassBuilder::writeDepth(
|
||||||
ImageRef image, vk::AttachmentLoadOp loadOp, float clearDepth) {
|
ImageRef image, vk::AttachmentLoadOp loadOp, float clearDepth) {
|
||||||
SOPHIA_ASSERT(!this->pass.depthAttachment, "Pass", this->pass.name,
|
SOPHIA_ASSERT(!this->pass.depthAttachment,
|
||||||
"already has a depth attachment");
|
"Pass {} already has a depth attachment", this->pass.name);
|
||||||
this->use(image, Access::DepthWrite);
|
this->use(image, Access::DepthWrite);
|
||||||
this->pass.depthAttachment = DepthAttachment{image, loadOp, clearDepth};
|
this->pass.depthAttachment = DepthAttachment{image, loadOp, clearDepth};
|
||||||
return *this;
|
return *this;
|
||||||
@@ -184,8 +184,8 @@ RenderGraph::PassBuilder& RenderGraph::PassBuilder::use(ImageRef image,
|
|||||||
SOPHIA_ASSERT(image.isValid() && image.index < this->graph.images.size(),
|
SOPHIA_ASSERT(image.isValid() && image.index < this->graph.images.size(),
|
||||||
"ImageRef doesn't belong to this graph");
|
"ImageRef doesn't belong to this graph");
|
||||||
for (const ImageUse& existing : this->pass.uses) {
|
for (const ImageUse& existing : this->pass.uses) {
|
||||||
SOPHIA_ASSERT(existing.image.index != image.index, "Pass", this->pass.name,
|
SOPHIA_ASSERT(existing.image.index != image.index,
|
||||||
"uses the same image twice");
|
"Pass {} uses the same image twice", this->pass.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->pass.uses.push_back({image, access});
|
this->pass.uses.push_back({image, access});
|
||||||
|
|||||||
@@ -45,8 +45,9 @@ void SpriteRenderSystem::render(vk::CommandBuffer commandBuffer,
|
|||||||
|
|
||||||
u32 available = MAX_INSTANCES_PER_FRAME - this->instanceCursor;
|
u32 available = MAX_INSTANCES_PER_FRAME - this->instanceCursor;
|
||||||
if (instances.size() > available) {
|
if (instances.size() > available) {
|
||||||
log::warning("SpriteRenderSystem: frame instance buffer full, dropping",
|
log::warning(
|
||||||
instances.size() - available, "sprites");
|
"SpriteRenderSystem: frame instance buffer full, dropping {} sprites",
|
||||||
|
instances.size() - available);
|
||||||
}
|
}
|
||||||
|
|
||||||
Buffer& buffer = *this->instanceBuffers[this->frameIndex];
|
Buffer& buffer = *this->instanceBuffers[this->frameIndex];
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ TextureHandle ResourceRegistryRef::load<Texture>(const std::string& path) {
|
|||||||
// Always decode to RGBA8, whatever the file has.
|
// Always decode to RGBA8, whatever the file has.
|
||||||
stbi_uc* pixels = stbi_load(path.c_str(), &width, &height, &channels, 4);
|
stbi_uc* pixels = stbi_load(path.c_str(), &width, &height, &channels, 4);
|
||||||
if (pixels == nullptr) {
|
if (pixels == nullptr) {
|
||||||
log::fatal("failed to load texture", path, "-", stbi_failure_reason());
|
log::fatal("failed to load texture {} - {}", path, stbi_failure_reason());
|
||||||
throw std::runtime_error("failed to load texture: " + path);
|
throw std::runtime_error("failed to load texture: " + path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ AccessState getStateForLayout(vk::ImageLayout layout) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log::fatal("Unsupported image layout:", vk::to_string(layout));
|
log::fatal("Unsupported image layout: {}", vk::to_string(layout));
|
||||||
throw std::runtime_error("Unsupported image layout.");
|
throw std::runtime_error("Unsupported image layout.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ Buffer::Buffer(Device& device, vk::DeviceSize instanceSize, u32 instanceCount,
|
|||||||
memoryPropertyFlags{memoryPropertyFlags} {
|
memoryPropertyFlags{memoryPropertyFlags} {
|
||||||
alignmentSize = getAlignment(instanceSize, minOffsetAlignment);
|
alignmentSize = getAlignment(instanceSize, minOffsetAlignment);
|
||||||
bufferSize = alignmentSize * instanceCount;
|
bufferSize = alignmentSize * instanceCount;
|
||||||
device.createBuffer(bufferSize, usageFlags, memoryPropertyFlags, buffer,
|
device.createBuffer(bufferSize, this->usageFlags, this->memoryPropertyFlags,
|
||||||
allocation);
|
this->buffer, this->allocation, this->info);
|
||||||
}
|
}
|
||||||
|
|
||||||
Buffer::~Buffer() {
|
Buffer::~Buffer() {
|
||||||
@@ -36,17 +36,14 @@ Buffer::~Buffer() {
|
|||||||
vmaDestroyBuffer(this->device.getAllocator(), this->buffer, this->allocation);
|
vmaDestroyBuffer(this->device.getAllocator(), this->buffer, this->allocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
// VMA always maps the whole allocation, so `size` only exists to keep the
|
vk::Result Buffer::map() {
|
||||||
// interface; `offset` is applied to the returned pointer.
|
|
||||||
vk::Result Buffer::map(vk::DeviceSize size, vk::DeviceSize offset) {
|
|
||||||
SOPHIA_ASSERT(buffer && allocation, "Called map on buffer before create");
|
SOPHIA_ASSERT(buffer && allocation, "Called map on buffer before create");
|
||||||
(void)size;
|
|
||||||
|
|
||||||
void* base = nullptr;
|
void* base = nullptr;
|
||||||
vk::Result result = static_cast<vk::Result>(
|
vk::Result result = static_cast<vk::Result>(
|
||||||
vmaMapMemory(this->device.getAllocator(), this->allocation, &base));
|
vmaMapMemory(this->device.getAllocator(), this->allocation, &base));
|
||||||
if (result == vk::Result::eSuccess) {
|
if (result == vk::Result::eSuccess) {
|
||||||
this->mapped = static_cast<char*>(base) + offset;
|
this->mapped = base;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -65,6 +62,9 @@ void Buffer::writeToBuffer(void* data, vk::DeviceSize size,
|
|||||||
if (size == VK_WHOLE_SIZE) {
|
if (size == VK_WHOLE_SIZE) {
|
||||||
memcpy(mapped, data, bufferSize);
|
memcpy(mapped, data, bufferSize);
|
||||||
} else {
|
} else {
|
||||||
|
SOPHIA_ASSERT(offset + size <= bufferSize,
|
||||||
|
"write of {} bytes at offset {} overflows buffer of {} bytes",
|
||||||
|
size, offset, bufferSize);
|
||||||
char* memOffset = (char*)mapped;
|
char* memOffset = (char*)mapped;
|
||||||
memOffset += offset;
|
memOffset += offset;
|
||||||
memcpy(memOffset, data, size);
|
memcpy(memOffset, data, size);
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ class Buffer {
|
|||||||
vk::DeviceSize minOffsetAlignment = 1);
|
vk::DeviceSize minOffsetAlignment = 1);
|
||||||
~Buffer();
|
~Buffer();
|
||||||
|
|
||||||
vk::Result map(vk::DeviceSize size = VK_WHOLE_SIZE,
|
vk::Result map();
|
||||||
vk::DeviceSize offset = 0);
|
|
||||||
void unmap();
|
void unmap();
|
||||||
|
|
||||||
void writeToBuffer(void* data, vk::DeviceSize size = VK_WHOLE_SIZE,
|
void writeToBuffer(void* data, vk::DeviceSize size = VK_WHOLE_SIZE,
|
||||||
@@ -37,17 +36,22 @@ class Buffer {
|
|||||||
vk::DescriptorBufferInfo descriptorInfoForIndex(int index);
|
vk::DescriptorBufferInfo descriptorInfoForIndex(int index);
|
||||||
vk::Result invalidateIndex(int index);
|
vk::Result invalidateIndex(int index);
|
||||||
|
|
||||||
vk::Buffer getBuffer() const { return buffer; }
|
void* getMappedMemory() const { return this->mapped; }
|
||||||
void* getMappedMemory() const { return mapped; }
|
vk::Buffer getBuffer() const { return this->buffer; }
|
||||||
uint32_t getInstanceCount() const { return instanceCount; }
|
const VmaAllocationInfo& getAllocationInfo() const { return this->info; }
|
||||||
|
u32 getInstanceCount() const { return instanceCount; }
|
||||||
vk::DeviceSize getInstanceSize() const { return instanceSize; }
|
vk::DeviceSize getInstanceSize() const { return instanceSize; }
|
||||||
vk::DeviceSize getAlignmentSize() const { return instanceSize; }
|
vk::DeviceSize getAlignmentSize() const { return alignmentSize; }
|
||||||
vk::BufferUsageFlags getUsageFlags() const { return usageFlags; }
|
vk::BufferUsageFlags getUsageFlags() const { return usageFlags; }
|
||||||
vk::MemoryPropertyFlags getMemoryPropertyFlags() const {
|
vk::MemoryPropertyFlags getMemoryPropertyFlags() const {
|
||||||
return memoryPropertyFlags;
|
return memoryPropertyFlags;
|
||||||
}
|
}
|
||||||
vk::DeviceSize getBufferSize() const { return bufferSize; }
|
vk::DeviceSize getBufferSize() const { return bufferSize; }
|
||||||
|
|
||||||
|
vk::DeviceAddress getDeviceAddress() {
|
||||||
|
return this->device.get()->getBufferAddress(this->buffer);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static vk::DeviceSize getAlignment(vk::DeviceSize instanceSize,
|
static vk::DeviceSize getAlignment(vk::DeviceSize instanceSize,
|
||||||
vk::DeviceSize minOffsetAlignment);
|
vk::DeviceSize minOffsetAlignment);
|
||||||
@@ -56,9 +60,10 @@ class Buffer {
|
|||||||
void* mapped = nullptr;
|
void* mapped = nullptr;
|
||||||
vk::Buffer buffer = VK_NULL_HANDLE;
|
vk::Buffer buffer = VK_NULL_HANDLE;
|
||||||
VmaAllocation allocation = VK_NULL_HANDLE;
|
VmaAllocation allocation = VK_NULL_HANDLE;
|
||||||
|
VmaAllocationInfo info;
|
||||||
|
|
||||||
vk::DeviceSize bufferSize;
|
vk::DeviceSize bufferSize;
|
||||||
uint32_t instanceCount;
|
u32 instanceCount;
|
||||||
vk::DeviceSize instanceSize;
|
vk::DeviceSize instanceSize;
|
||||||
vk::DeviceSize alignmentSize;
|
vk::DeviceSize alignmentSize;
|
||||||
vk::BufferUsageFlags usageFlags;
|
vk::BufferUsageFlags usageFlags;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <sophia/util/logger.hpp>
|
#include <sophia/util/logger.hpp>
|
||||||
|
|
||||||
@@ -19,16 +20,16 @@ debugCallback(vk::DebugUtilsMessageSeverityFlagBitsEXT m_severity,
|
|||||||
(void)pUser_data;
|
(void)pUser_data;
|
||||||
|
|
||||||
if (m_severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eError) {
|
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::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) {
|
} else if (m_severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning) {
|
||||||
log::warning(pCallback_data->pMessage);
|
log::warning("{}", pCallback_data->pMessage);
|
||||||
} else if (m_severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo) {
|
} else if (m_severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo) {
|
||||||
log::info(pCallback_data->pMessage);
|
log::info("{}", pCallback_data->pMessage);
|
||||||
} else if (m_severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose) {
|
} else if (m_severity == vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose) {
|
||||||
log::verbose(pCallback_data->pMessage);
|
log::verbose("{}", pCallback_data->pMessage);
|
||||||
} else {
|
} else {
|
||||||
log::info(pCallback_data->pMessage);
|
log::info("{}", pCallback_data->pMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
return VK_FALSE;
|
return VK_FALSE;
|
||||||
@@ -107,8 +108,8 @@ static void forEachRequiredFeature(DeviceFeatureChain& chain, Fn&& fn) {
|
|||||||
static bool checkDeviceFeatureSupport(const vk::PhysicalDevice& device) {
|
static bool checkDeviceFeatureSupport(const vk::PhysicalDevice& device) {
|
||||||
vk::PhysicalDeviceProperties properties = device.getProperties();
|
vk::PhysicalDeviceProperties properties = device.getProperties();
|
||||||
if (properties.apiVersion < SOPHIA_VULKAN_API_VERSION) {
|
if (properties.apiVersion < SOPHIA_VULKAN_API_VERSION) {
|
||||||
log::verbose("Skipping ", properties.deviceName,
|
log::verbose("Skipping {}: Vulkan 1.3 not supported",
|
||||||
": Vulkan 1.3 not supported");
|
properties.deviceName.data());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,8 +120,8 @@ static bool checkDeviceFeatureSupport(const vk::PhysicalDevice& device) {
|
|||||||
bool allSupported = true;
|
bool allSupported = true;
|
||||||
forEachRequiredFeature(supported, [&](vk::Bool32 has, const char* name) {
|
forEachRequiredFeature(supported, [&](vk::Bool32 has, const char* name) {
|
||||||
if (!has) {
|
if (!has) {
|
||||||
log::verbose("Skipping ", properties.deviceName,
|
log::verbose("Skipping {}: missing device feature {}",
|
||||||
": missing device feature ", name);
|
properties.deviceName.data(), name);
|
||||||
allSupported = false;
|
allSupported = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -183,7 +184,8 @@ vk::Format Device::findSupportedFormat(
|
|||||||
|
|
||||||
void Device::createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage,
|
void Device::createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage,
|
||||||
vk::MemoryPropertyFlags properties,
|
vk::MemoryPropertyFlags properties,
|
||||||
vk::Buffer& buffer, VmaAllocation& allocation) {
|
vk::Buffer& buffer, VmaAllocation& allocation,
|
||||||
|
VmaAllocationInfo& info) {
|
||||||
vk::BufferCreateInfo bufferInfo = {};
|
vk::BufferCreateInfo bufferInfo = {};
|
||||||
bufferInfo.size = size;
|
bufferInfo.size = size;
|
||||||
bufferInfo.usage = usage;
|
bufferInfo.usage = usage;
|
||||||
@@ -195,9 +197,9 @@ void Device::createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage,
|
|||||||
VkBuffer rawBuffer = VK_NULL_HANDLE;
|
VkBuffer rawBuffer = VK_NULL_HANDLE;
|
||||||
VkResult result = vmaCreateBuffer(
|
VkResult result = vmaCreateBuffer(
|
||||||
this->allocator, &static_cast<const VkBufferCreateInfo&>(bufferInfo),
|
this->allocator, &static_cast<const VkBufferCreateInfo&>(bufferInfo),
|
||||||
&allocInfo, &rawBuffer, &allocation, nullptr);
|
&allocInfo, &rawBuffer, &allocation, &info);
|
||||||
if (result != VK_SUCCESS) {
|
if (result != VK_SUCCESS) {
|
||||||
log::fatal("failed to create buffer. Error: ",
|
log::fatal("failed to create buffer. Error: {}",
|
||||||
vk::to_string(static_cast<vk::Result>(result)));
|
vk::to_string(static_cast<vk::Result>(result)));
|
||||||
throw std::runtime_error("failed to create buffer");
|
throw std::runtime_error("failed to create buffer");
|
||||||
}
|
}
|
||||||
@@ -220,7 +222,7 @@ vk::CommandBuffer Device::beginSingleTimeCommands() {
|
|||||||
throw vk::SystemError(result);
|
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());
|
||||||
throw std::runtime_error("failed to allocate single time commandBuffer");
|
throw std::runtime_error("failed to allocate single time commandBuffer");
|
||||||
}
|
}
|
||||||
@@ -234,7 +236,7 @@ vk::CommandBuffer Device::beginSingleTimeCommands() {
|
|||||||
throw vk::SystemError(result);
|
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());
|
||||||
throw std::runtime_error("failed to begin single time commandBuffer");
|
throw std::runtime_error("failed to begin single time commandBuffer");
|
||||||
}
|
}
|
||||||
@@ -278,7 +280,7 @@ void Device::createImageWithInfo(const vk::ImageCreateInfo& imageInfo,
|
|||||||
this->allocator, &static_cast<const VkImageCreateInfo&>(imageInfo),
|
this->allocator, &static_cast<const VkImageCreateInfo&>(imageInfo),
|
||||||
&allocInfo, &rawImage, &allocation, nullptr);
|
&allocInfo, &rawImage, &allocation, nullptr);
|
||||||
if (result != VK_SUCCESS) {
|
if (result != VK_SUCCESS) {
|
||||||
log::fatal("failed to create image. Error: ",
|
log::fatal("failed to create image. Error: {}",
|
||||||
vk::to_string(static_cast<vk::Result>(result)));
|
vk::to_string(static_cast<vk::Result>(result)));
|
||||||
throw std::runtime_error("failed to create image");
|
throw std::runtime_error("failed to create image");
|
||||||
}
|
}
|
||||||
@@ -374,7 +376,7 @@ std::vector<const char*> Device::getRequiredExtensions() {
|
|||||||
throw std::exception();
|
throw std::exception();
|
||||||
}
|
}
|
||||||
|
|
||||||
log::verbose("Number of available extensions: ", available_extension_count);
|
log::verbose("Number of available extensions: {}", available_extension_count);
|
||||||
|
|
||||||
log::verbose("Available extensions:");
|
log::verbose("Available extensions:");
|
||||||
for (const auto& e : available_extensions) {
|
for (const auto& e : available_extensions) {
|
||||||
@@ -482,7 +484,7 @@ void Device::pickPhysicalDevice() {
|
|||||||
log::fatal("Failed to find any physical devices.");
|
log::fatal("Failed to find any physical devices.");
|
||||||
throw std::exception();
|
throw std::exception();
|
||||||
}
|
}
|
||||||
log::verbose("Physical Devices count: ", physicalDevices.size());
|
log::verbose("Physical Devices count: {}", physicalDevices.size());
|
||||||
|
|
||||||
// Select first physical device that is suitable
|
// Select first physical device that is suitable
|
||||||
for (const auto& device : physicalDevices) {
|
for (const auto& device : physicalDevices) {
|
||||||
@@ -498,7 +500,7 @@ void Device::pickPhysicalDevice() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this->properties = this->physicalDevice.getProperties();
|
this->properties = this->physicalDevice.getProperties();
|
||||||
log::verbose("Physical Device: ", this->properties.deviceName);
|
log::verbose("Physical Device: {}", this->properties.deviceName.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Device::createLogicalDevice() {
|
void Device::createLogicalDevice() {
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ class Device {
|
|||||||
|
|
||||||
void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage,
|
void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage,
|
||||||
vk::MemoryPropertyFlags properties, vk::Buffer& buffer,
|
vk::MemoryPropertyFlags properties, vk::Buffer& buffer,
|
||||||
VmaAllocation& allocation);
|
VmaAllocation& allocation, VmaAllocationInfo& info);
|
||||||
|
|
||||||
vk::CommandBuffer beginSingleTimeCommands();
|
vk::CommandBuffer beginSingleTimeCommands();
|
||||||
|
|
||||||
|
|||||||
@@ -226,11 +226,12 @@ std::unique_ptr<Image> Image::Builder::build() {
|
|||||||
|
|
||||||
vk::Buffer stagingBuffer;
|
vk::Buffer stagingBuffer;
|
||||||
VmaAllocation stagingAllocation;
|
VmaAllocation stagingAllocation;
|
||||||
this->device.createBuffer(this->initialDataSize,
|
VmaAllocationInfo stagingAllocationInfo;
|
||||||
vk::BufferUsageFlagBits::eTransferSrc,
|
this->device.createBuffer(
|
||||||
|
this->initialDataSize, vk::BufferUsageFlagBits::eTransferSrc,
|
||||||
vk::MemoryPropertyFlagBits::eHostVisible |
|
vk::MemoryPropertyFlagBits::eHostVisible |
|
||||||
vk::MemoryPropertyFlagBits::eHostCoherent,
|
vk::MemoryPropertyFlagBits::eHostCoherent,
|
||||||
stagingBuffer, stagingAllocation);
|
stagingBuffer, stagingAllocation, stagingAllocationInfo);
|
||||||
|
|
||||||
void* mapped = nullptr;
|
void* mapped = nullptr;
|
||||||
vmaMapMemory(allocator, stagingAllocation, &mapped);
|
vmaMapMemory(allocator, stagingAllocation, &mapped);
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ std::vector<char> readFile(const std::string& path) {
|
|||||||
std::ifstream file(path, std::ios::ate | std::ios::binary);
|
std::ifstream file(path, std::ios::ate | std::ios::binary);
|
||||||
|
|
||||||
if (!file.is_open()) {
|
if (!file.is_open()) {
|
||||||
log::error("failed to open shader: " + path);
|
log::error("failed to open shader: {}", path);
|
||||||
throw std::runtime_error("failed to open: " + path);
|
throw std::runtime_error("failed to open: " + path);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,22 +46,23 @@ void Shader::compile(const std::string& path, ShaderStage shaderStage) {
|
|||||||
|
|
||||||
std::vector<char> source = readFile(path);
|
std::vector<char> source = readFile(path);
|
||||||
|
|
||||||
log::info("Compiling shader: " + path);
|
log::info("Compiling shader: {}", path);
|
||||||
|
|
||||||
shaderc::Compiler compiler;
|
shaderc::Compiler compiler;
|
||||||
shaderc::SpvCompilationResult result = compiler.CompileGlslToSpv(
|
shaderc::SpvCompilationResult result = compiler.CompileGlslToSpv(
|
||||||
source.data(), source.size(), toShadercKind(shaderStage), path.c_str());
|
source.data(), source.size(), toShadercKind(shaderStage), path.c_str());
|
||||||
|
|
||||||
if (result.GetCompilationStatus() != shaderc_compilation_status_success) {
|
if (result.GetCompilationStatus() != shaderc_compilation_status_success) {
|
||||||
log::fatal("failed to compile shader:", path, result.GetErrorMessage());
|
log::fatal("failed to compile shader: {}: {}", path,
|
||||||
|
result.GetErrorMessage());
|
||||||
throw std::runtime_error("failed to compile shader: " + path + ": " +
|
throw std::runtime_error("failed to compile shader: " + path + ": " +
|
||||||
result.GetErrorMessage());
|
result.GetErrorMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
this->spirv.assign(result.cbegin(), result.cend());
|
this->spirv.assign(result.cbegin(), result.cend());
|
||||||
|
|
||||||
log::info("Successfully compiled shader: " + path + " (" +
|
log::info("Successfully compiled shader: {} ({} bytes)", path,
|
||||||
std::to_string(this->spirv.size() * 4) + " bytes)");
|
this->spirv.size() * 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shader::load(const std::string& path, ShaderStage shaderStage) {
|
void Shader::load(const std::string& path, ShaderStage shaderStage) {
|
||||||
@@ -70,15 +71,14 @@ void Shader::load(const std::string& path, ShaderStage shaderStage) {
|
|||||||
std::vector<char> bytes = readFile(path);
|
std::vector<char> bytes = readFile(path);
|
||||||
|
|
||||||
if (bytes.size() % sizeof(u32) != 0) {
|
if (bytes.size() % sizeof(u32) != 0) {
|
||||||
log::fatal("invalid SPIR-V binary (size not a multiple of 4):", path);
|
log::fatal("invalid SPIR-V binary (size not a multiple of 4): {}", path);
|
||||||
throw std::runtime_error("invalid SPIR-V binary: " + path);
|
throw std::runtime_error("invalid SPIR-V binary: " + path);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->spirv.assign(reinterpret_cast<const u32*>(bytes.data()),
|
this->spirv.assign(reinterpret_cast<const u32*>(bytes.data()),
|
||||||
reinterpret_cast<const u32*>(bytes.data() + bytes.size()));
|
reinterpret_cast<const u32*>(bytes.data() + bytes.size()));
|
||||||
|
|
||||||
log::info("Loaded precompiled shader: " + path + " (" +
|
log::info("Loaded precompiled shader: {} ({} bytes)", path, bytes.size());
|
||||||
std::to_string(bytes.size()) + " bytes)");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Shader::Builder& Shader::Builder::fromGLSL(const std::string& path) {
|
Shader::Builder& Shader::Builder::fromGLSL(const std::string& path) {
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ void Swapchain::createSwapchain() {
|
|||||||
this->device.get()->createSwapchainKHR(this->swapchainCreateInfo);
|
this->device.get()->createSwapchainKHR(this->swapchainCreateInfo);
|
||||||
// log::trace("created vk::SwapchainKHR");
|
// log::trace("created vk::SwapchainKHR");
|
||||||
} catch (const vk::SystemError& err) {
|
} catch (const vk::SystemError& err) {
|
||||||
log::fatal("failed to create swapchain. Error: ", err.what());
|
log::fatal("failed to create swapchain. Error: {}", err.what());
|
||||||
throw std::runtime_error("failed to create swapchain");
|
throw std::runtime_error("failed to create swapchain");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <source_location>
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
|
inline void vk_check(vk::Result result, std::source_location loc =
|
||||||
|
std::source_location::current()) {
|
||||||
|
if (result != vk::Result::eSuccess) {
|
||||||
|
std::fprintf(stderr, "%s:%u: %s: %s\n", loc.file_name(), loc.line(),
|
||||||
|
loc.function_name(), vk::to_string(result).c_str());
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void vk_check(VkResult result, std::source_location loc =
|
||||||
|
std::source_location::current()) {
|
||||||
|
vk_check(static_cast<vk::Result>(result), loc);
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
add_executable(conway_game_of_life main.cpp)
|
||||||
|
|
||||||
|
target_link_libraries(conway_game_of_life PRIVATE ${PROJECT_NAME})
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
void main(){}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include <sophia/application.hpp>
|
||||||
|
|
||||||
|
class ConwayGameOfLife : public sophia::Application {
|
||||||
|
public:
|
||||||
|
ConwayGameOfLife()
|
||||||
|
: Application{{.width = 800, .height = 600, .name = "ConwayGameOfLife"}} {
|
||||||
|
}
|
||||||
|
|
||||||
|
void onCreate() override {}
|
||||||
|
};
|
||||||
|
|
||||||
|
SOPHIA_MAIN(ConwayGameOfLife)
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# sandbox
|
||||||
|
|
||||||
|
A playground for working out the implementation and architecture of new engine features. It can include the engine's internal backend headers (`engine/src`) as well as the public API, so you can use classes such as `Device`, `RenderContext`, `RenderGraph` and `SceneRenderer` directly.
|
||||||
|
|
||||||
|
Prototype a feature here first. Once its design settles, move it into the engine and expose it through the public API, which [testbed](../testbed/README.md) exercises.
|
||||||
|
|
||||||
|
Any `.cpp` file in this directory is compiled in automatically.
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dev sandbox [args...]
|
||||||
|
```
|
||||||
|
|
||||||
|
This builds the `debug` preset and runs `build/debug/bin/sandbox`. Shaders are loaded from `shaders/` (`SANDBOX_SHADER_DIR`).
|
||||||
+211
-18
@@ -1,28 +1,221 @@
|
|||||||
#define GLFW_INCLUDE_VULKAN
|
#define GLFW_INCLUDE_VULKAN
|
||||||
#include <GLFW/glfw3.h>
|
#include <GLFW/glfw3.h>
|
||||||
|
#include <spirv_reflect.h>
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <array>
|
|
||||||
#include <cmath>
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdio>
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <glm/mat4x4.hpp>
|
#include <iostream>
|
||||||
#include <glm/vec4.hpp>
|
#include <sophia/components.hpp>
|
||||||
#include <vector>
|
#include <sophia/scene_registry.hpp>
|
||||||
|
#include <sophia/scene_view.hpp>
|
||||||
|
#include <tracy/Tracy.hpp>
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
#define VK_CHECK(expr) \
|
#include "renderer/render_context.hpp"
|
||||||
do { \
|
#include "renderer/render_graph.hpp"
|
||||||
vk::Result result_ = (expr); \
|
#include "renderer/scene_renderer.hpp"
|
||||||
if (result_ != vk::Result::eSuccess) { \
|
#include "resources/resource_registry.hpp"
|
||||||
std::fprintf(stderr, "%s:%d: %s -> %d\n", __FILE__, __LINE__, #expr, \
|
#include "vulkan/buffer.hpp"
|
||||||
result_); \
|
#include "vulkan/device.hpp"
|
||||||
std::abort(); \
|
#include "vulkan/shader.hpp"
|
||||||
} \
|
#include "window.hpp"
|
||||||
} while (0)
|
|
||||||
|
// local includes
|
||||||
|
#include "material.hpp"
|
||||||
|
|
||||||
constexpr uint32_t WIDTH = 1280;
|
constexpr uint32_t WIDTH = 1280;
|
||||||
constexpr uint32_t HEIGHT = 720;
|
constexpr uint32_t HEIGHT = 720;
|
||||||
constexpr const char* NAME = "";
|
constexpr const char* NAME = "sandbox";
|
||||||
|
|
||||||
int main() { return EXIT_SUCCESS; }
|
void immediate_submit(sophia::Device& device,
|
||||||
|
std::function<void(VkCommandBuffer cmd)>&& function) {
|
||||||
|
auto command = device.beginSingleTimeCommands();
|
||||||
|
function(command);
|
||||||
|
device.endSingleTimeCommands(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
void triangle_test() {
|
||||||
|
sophia::Window window{WIDTH, HEIGHT, NAME};
|
||||||
|
sophia::Device device{&window};
|
||||||
|
sophia::RenderContext renderContext{window, device};
|
||||||
|
sophia::ResourceRegistry resourceRegistry{device,
|
||||||
|
renderContext.getImageFormat()};
|
||||||
|
|
||||||
|
sophia::SceneRegistry sceneRegistry;
|
||||||
|
sophia::SceneRenderer sceneRenderer{sceneRegistry, resourceRegistry, device,
|
||||||
|
renderContext.getImageFormat()};
|
||||||
|
|
||||||
|
sophia::SceneHandle sceneHandle = sceneRegistry.create();
|
||||||
|
sophia::Scene& scene = sceneRegistry.get(sceneHandle);
|
||||||
|
sophia::Entity camera = scene.createCamera();
|
||||||
|
|
||||||
|
// {
|
||||||
|
// auto& transform = camera.getComponent<sophia::TransformComponent>();
|
||||||
|
// transform.position.z += 60;
|
||||||
|
// }
|
||||||
|
|
||||||
|
sophia::SceneView view;
|
||||||
|
view.scene = sceneHandle;
|
||||||
|
view.clearColor = {0.01f, 0.01f, 0.02f, 1.0f};
|
||||||
|
view.camera = camera;
|
||||||
|
|
||||||
|
auto vertexShader = sophia::Shader::Builder()
|
||||||
|
.fromGLSL(SANDBOX_SHADER_DIR "/triangle.vert")
|
||||||
|
.setStage(sophia::ShaderStage::VERTEX)
|
||||||
|
.build();
|
||||||
|
auto fragmentShader = sophia::Shader::Builder()
|
||||||
|
.fromGLSL(SANDBOX_SHADER_DIR "/triangle.frag")
|
||||||
|
.setStage(sophia::ShaderStage::FRAGMENT)
|
||||||
|
.build();
|
||||||
|
vk::Format depthFormat = resourceRegistry.renderTargetPool.getDepthFormat();
|
||||||
|
sophia::Material material{device, std::move(vertexShader),
|
||||||
|
std::move(fragmentShader),
|
||||||
|
renderContext.getImageFormat(), depthFormat};
|
||||||
|
|
||||||
|
struct TriangleVertex {
|
||||||
|
sophia::vec4 vertex;
|
||||||
|
sophia::vec4 color;
|
||||||
|
};
|
||||||
|
|
||||||
|
TriangleVertex verticies[] = {
|
||||||
|
{{-1.f, 1.f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f, 0.0f}},
|
||||||
|
{{1.f, 1.f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f, 0.0f}},
|
||||||
|
{{0.f, -1.f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f, 0.0f}}};
|
||||||
|
sophia::u32 triangleVerticiesCount = 3;
|
||||||
|
|
||||||
|
sophia::Buffer triangleBuffer{device, sizeof(TriangleVertex),
|
||||||
|
triangleVerticiesCount,
|
||||||
|
vk::BufferUsageFlagBits::eShaderDeviceAddress,
|
||||||
|
vk::MemoryPropertyFlagBits::eHostVisible |
|
||||||
|
vk::MemoryPropertyFlagBits::eHostCoherent};
|
||||||
|
triangleBuffer.map();
|
||||||
|
|
||||||
|
triangleBuffer.writeToBuffer(verticies);
|
||||||
|
|
||||||
|
vk::DeviceAddress address = triangleBuffer.getDeviceAddress();
|
||||||
|
|
||||||
|
while (!window.shouldClose()) {
|
||||||
|
glfwPollEvents();
|
||||||
|
|
||||||
|
vk::CommandBuffer commandBuffer = renderContext.beginFrame();
|
||||||
|
if (!commandBuffer) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
sceneRenderer.beginFrame(renderContext.getFrameIndex());
|
||||||
|
|
||||||
|
sophia::RenderGraph graph{renderContext.getTracyContext()};
|
||||||
|
sophia::ImageRef color = graph.importImage(renderContext.getCurrentImage(),
|
||||||
|
sophia::Access::Present);
|
||||||
|
sophia::ImageRef depth = graph.importImage(
|
||||||
|
renderContext.getCurrentDepthImage(), sophia::Access::DepthWrite);
|
||||||
|
|
||||||
|
graph.addPass(
|
||||||
|
"sandbox",
|
||||||
|
[&](sophia::RenderGraph::PassBuilder& pass) {
|
||||||
|
const sophia::vec4& c = view.clearColor;
|
||||||
|
pass.writeColor(color, vk::AttachmentLoadOp::eClear,
|
||||||
|
{c.r, c.g, c.b, c.a});
|
||||||
|
pass.writeDepth(depth, vk::AttachmentLoadOp::eClear);
|
||||||
|
},
|
||||||
|
[&](vk::CommandBuffer commandBuffer) {
|
||||||
|
ZoneScopedN("main sandbox pass");
|
||||||
|
material.bind(commandBuffer);
|
||||||
|
sophia::Material::PushConstants push;
|
||||||
|
push.data0 = address;
|
||||||
|
|
||||||
|
commandBuffer.pushConstants(
|
||||||
|
material.getLayout(), vk::ShaderStageFlagBits::eVertex, 0,
|
||||||
|
sizeof(sophia::Material::PushConstants), &push);
|
||||||
|
commandBuffer.draw(3, 1, 0, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
graph.addPass(
|
||||||
|
"scene",
|
||||||
|
[&](sophia::RenderGraph::PassBuilder& pass) {
|
||||||
|
const sophia::vec4& c = view.clearColor;
|
||||||
|
pass.writeColor(color, vk::AttachmentLoadOp::eClear,
|
||||||
|
{c.r, c.g, c.b, c.a});
|
||||||
|
pass.writeDepth(depth, vk::AttachmentLoadOp::eClear);
|
||||||
|
},
|
||||||
|
[&](vk::CommandBuffer commandBuffer) {
|
||||||
|
ZoneScopedN("scene pass");
|
||||||
|
sceneRenderer.render(commandBuffer, view, view.camera,
|
||||||
|
renderContext.getExtent());
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
|
graph.execute(commandBuffer);
|
||||||
|
|
||||||
|
renderContext.endFrame();
|
||||||
|
FrameMark;
|
||||||
|
}
|
||||||
|
|
||||||
|
device.waitIdle();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
{
|
||||||
|
using namespace sophia;
|
||||||
|
|
||||||
|
auto shader = Shader::Builder()
|
||||||
|
.fromGLSL(SANDBOX_SHADER_DIR "/shader.comp")
|
||||||
|
.setStage(ShaderStage::VERTEX)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
std::vector<u32> spirv = shader.get()->getSpirv();
|
||||||
|
size_t spirv_size = spirv.size() * sizeof(u32);
|
||||||
|
void* spirv_code = spirv.data();
|
||||||
|
|
||||||
|
SpvReflectShaderModule module;
|
||||||
|
SpvReflectResult result =
|
||||||
|
spvReflectCreateShaderModule(spirv_size, spirv_code, &module);
|
||||||
|
assert(result == SPV_REFLECT_RESULT_SUCCESS);
|
||||||
|
|
||||||
|
{
|
||||||
|
log::print();
|
||||||
|
log::info("Reflecting Input Variables");
|
||||||
|
|
||||||
|
uint32_t count = 0;
|
||||||
|
result = spvReflectEnumerateInputVariables(&module, &count, NULL);
|
||||||
|
assert(result == SPV_REFLECT_RESULT_SUCCESS);
|
||||||
|
SpvReflectInterfaceVariable** vars =
|
||||||
|
(SpvReflectInterfaceVariable**)malloc(
|
||||||
|
count * sizeof(SpvReflectInterfaceVariable*));
|
||||||
|
result = spvReflectEnumerateInputVariables(&module, &count, vars);
|
||||||
|
assert(result == SPV_REFLECT_RESULT_SUCCESS);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < count; i++) {
|
||||||
|
SpvReflectInterfaceVariable* var = vars[i];
|
||||||
|
assert(var != nullptr);
|
||||||
|
|
||||||
|
std::cout << "{" << std::endl;
|
||||||
|
log::print("\tname: {},", var->name);
|
||||||
|
std::cout << "}" << std::endl;
|
||||||
|
std::cout << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
log::print();
|
||||||
|
log::info("Reflecting Output Variables");
|
||||||
|
|
||||||
|
u32 count = 0;
|
||||||
|
result = spvReflectEnumerateOutputVariables(&module, &count, NULL);
|
||||||
|
assert(result == SPV_REFLECT_RESULT_SUCCESS);
|
||||||
|
|
||||||
|
SpvReflectInterfaceVariable** vars =
|
||||||
|
(SpvReflectInterfaceVariable**)malloc(
|
||||||
|
count * sizeof(SpvReflectInterfaceVariable*));
|
||||||
|
|
||||||
|
result = spvReflectEnumerateOutputVariables(&module, &count, vars);
|
||||||
|
assert(result == SPV_REFLECT_RESULT_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
spvReflectDestroyShaderModule(&module);
|
||||||
|
}
|
||||||
|
|
||||||
|
// triangle_test();
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#include "material.hpp"
|
||||||
|
|
||||||
|
#include <sophia/util/logger.hpp>
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
Material::Material(Device& device, std::unique_ptr<Shader> vertexShader,
|
||||||
|
std::unique_ptr<Shader> fragmentShader,
|
||||||
|
vk::Format colorFormat, vk::Format depthFormat) {
|
||||||
|
createPipelineLayout(device);
|
||||||
|
this->pipeline = GraphicsPipeline::Builder(device)
|
||||||
|
.setVertexShader(*vertexShader)
|
||||||
|
.setFragmentShader(*fragmentShader)
|
||||||
|
.setPipelineLayout(this->pipelineLayout.get())
|
||||||
|
.setColorAttachmentFormats({colorFormat})
|
||||||
|
.setDepthAttachmentFormat(depthFormat)
|
||||||
|
.setDepthTest(true, true)
|
||||||
|
.setCullMode(vk::CullModeFlagBits::eBack)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
Material::~Material() { log::trace("destroyed sophia::Material"); }
|
||||||
|
|
||||||
|
void Material::createPipelineLayout(Device& device) {
|
||||||
|
vk::PushConstantRange pushConstantRange{};
|
||||||
|
pushConstantRange.stageFlags = vk::ShaderStageFlagBits::eVertex;
|
||||||
|
pushConstantRange.offset = 0;
|
||||||
|
pushConstantRange.size = sizeof(PushConstants);
|
||||||
|
|
||||||
|
vk::PipelineLayoutCreateInfo pipelineLayoutInfo = {};
|
||||||
|
pipelineLayoutInfo.setLayoutCount = 0;
|
||||||
|
pipelineLayoutInfo.pushConstantRangeCount = 1;
|
||||||
|
pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this->pipelineLayout =
|
||||||
|
device.get()->createPipelineLayoutUnique(pipelineLayoutInfo);
|
||||||
|
log::trace("created vk::PipelineLayout");
|
||||||
|
} catch (const vk::SystemError& err) {
|
||||||
|
log::fatal("failed to create vk::PipelineLayout");
|
||||||
|
throw std::runtime_error("failed to create vk::PipelineLayout");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <sophia/math.hpp>
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
|
#include "vulkan/device.hpp"
|
||||||
|
#include "vulkan/pipelines/graphics_pipeline.hpp"
|
||||||
|
#include "vulkan/shader.hpp"
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
class Material {
|
||||||
|
public:
|
||||||
|
struct PushConstants {
|
||||||
|
vk::DeviceAddress data0;
|
||||||
|
u64 padding;
|
||||||
|
vec4 data1;
|
||||||
|
vec4 data2;
|
||||||
|
vec4 data3;
|
||||||
|
};
|
||||||
|
|
||||||
|
Material(const Material&) = delete;
|
||||||
|
Material& operator=(const Material&) = delete;
|
||||||
|
|
||||||
|
Material(Device& device, std::unique_ptr<Shader> vertexShader,
|
||||||
|
std::unique_ptr<Shader> fragmentShader, vk::Format colorFormat,
|
||||||
|
vk::Format depthFormat);
|
||||||
|
~Material();
|
||||||
|
|
||||||
|
void bind(vk::CommandBuffer commandBuffer) const {
|
||||||
|
pipeline->bind(commandBuffer);
|
||||||
|
}
|
||||||
|
vk::PipelineLayout getLayout() const { return pipelineLayout.get(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void createPipelineLayout(Device& device);
|
||||||
|
|
||||||
|
vk::UniquePipelineLayout pipelineLayout;
|
||||||
|
std::unique_ptr<GraphicsPipeline> pipeline;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MaterialInstance {
|
||||||
|
public:
|
||||||
|
void setBaseColor();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Material* material;
|
||||||
|
|
||||||
|
vec3 base_color = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
// !NOTE: eventually add ecs support for ecs
|
||||||
|
// struct MaterialComponent {
|
||||||
|
// MaterialInstanceHandle handle;
|
||||||
|
// };
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
#include "renderer.hpp"
|
||||||
|
|
||||||
|
namespace sophia {} // namespace sophia
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <sophia/math.hpp>
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
|
#include "material.hpp"
|
||||||
|
|
||||||
|
namespace sophia {
|
||||||
|
|
||||||
|
struct RenderObject {
|
||||||
|
uint32_t indexCount;
|
||||||
|
uint32_t firstIndex;
|
||||||
|
vk::Buffer indexBuffer;
|
||||||
|
|
||||||
|
MaterialInstance* material;
|
||||||
|
|
||||||
|
mat4 transform;
|
||||||
|
VkDeviceAddress vertexBufferAddress;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Renderer {
|
||||||
|
public:
|
||||||
|
private:
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace sophia
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#version 450
|
||||||
|
#extension GL_EXT_buffer_reference : require
|
||||||
|
|
||||||
|
struct TriangleVertex {
|
||||||
|
vec4 vertex;
|
||||||
|
vec4 color;
|
||||||
|
};
|
||||||
|
|
||||||
|
layout(buffer_reference, std430) readonly buffer TriangleVertexBuffer {
|
||||||
|
TriangleVertex vertices[];
|
||||||
|
};
|
||||||
|
|
||||||
|
layout( push_constant ) uniform constants {
|
||||||
|
TriangleVertexBuffer triangleBuffer;
|
||||||
|
vec2 padding;
|
||||||
|
vec4 data1;
|
||||||
|
vec4 data2;
|
||||||
|
vec4 data3;
|
||||||
|
} PushConstants;
|
||||||
|
|
||||||
|
layout (location = 0) out vec3 outColor;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
//const array of positions for the triangle
|
||||||
|
const vec3 positions[3] = vec3[3](
|
||||||
|
vec3(-1.f,1.f, 0.0f),
|
||||||
|
vec3(1.f,1.f, 0.0f),
|
||||||
|
vec3(0.f,-1.f, 0.0f)
|
||||||
|
);
|
||||||
|
|
||||||
|
//const array of colors for the triangle
|
||||||
|
const vec3 colors[3] = vec3[3](
|
||||||
|
vec3(1.0f, 0.0f, 0.0f), //red
|
||||||
|
vec3(0.0f, 1.0f, 0.0f), //green
|
||||||
|
vec3(00.f, 0.0f, 1.0f) //blue
|
||||||
|
);
|
||||||
|
|
||||||
|
// Load vertex data from device address
|
||||||
|
TriangleVertex v = PushConstants.triangleBuffer.vertices[gl_VertexIndex];
|
||||||
|
|
||||||
|
// output the position of each vertex
|
||||||
|
gl_Position = vec4(v.vertex.xyz, 1.0);
|
||||||
|
outColor = vec3(v.color.xyz);
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
//shader input
|
||||||
|
layout (location = 0) in vec3 inColor;
|
||||||
|
|
||||||
|
//output write
|
||||||
|
layout (location = 0) out vec4 outFragColor;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
//return red
|
||||||
|
outFragColor = vec4(inColor,1.0f);
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#version 450
|
||||||
|
#extension GL_EXT_buffer_reference : require
|
||||||
|
|
||||||
|
struct TriangleVertex {
|
||||||
|
vec4 vertex;
|
||||||
|
vec4 color;
|
||||||
|
};
|
||||||
|
|
||||||
|
layout(buffer_reference, std430) readonly buffer TriangleVertexBuffer {
|
||||||
|
TriangleVertex vertices[];
|
||||||
|
};
|
||||||
|
|
||||||
|
layout( push_constant ) uniform constants {
|
||||||
|
TriangleVertexBuffer triangleBuffer;
|
||||||
|
vec2 padding;
|
||||||
|
vec4 data1;
|
||||||
|
vec4 data2;
|
||||||
|
vec4 data3;
|
||||||
|
} PushConstants;
|
||||||
|
|
||||||
|
layout (location = 0) out vec3 outColor;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
//const array of positions for the triangle
|
||||||
|
const vec3 positions[3] = vec3[3](
|
||||||
|
vec3(-1.f,1.f, 0.0f),
|
||||||
|
vec3(1.f,1.f, 0.0f),
|
||||||
|
vec3(0.f,-1.f, 0.0f)
|
||||||
|
);
|
||||||
|
|
||||||
|
//const array of colors for the triangle
|
||||||
|
const vec3 colors[3] = vec3[3](
|
||||||
|
vec3(1.0f, 0.0f, 0.0f), //red
|
||||||
|
vec3(0.0f, 1.0f, 0.0f), //green
|
||||||
|
vec3(00.f, 0.0f, 1.0f) //blue
|
||||||
|
);
|
||||||
|
|
||||||
|
// Load vertex data from device address
|
||||||
|
TriangleVertex v = PushConstants.triangleBuffer.vertices[gl_VertexIndex];
|
||||||
|
|
||||||
|
// output the position of each vertex
|
||||||
|
gl_Position = vec4(v.vertex.xyz, 1.0);
|
||||||
|
outColor = vec3(v.color.xyz);
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#version 460
|
||||||
|
|
||||||
|
//size of a workgroup for compute
|
||||||
|
layout (local_size_x = 16, local_size_y = 16) in;
|
||||||
|
|
||||||
|
//descriptor bindings for the pipeline
|
||||||
|
layout(rgba16f,set = 0, binding = 0) uniform image2D image;
|
||||||
|
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
ivec2 texelCoord = ivec2(gl_GlobalInvocationID.xy);
|
||||||
|
ivec2 size = imageSize(image);
|
||||||
|
|
||||||
|
if(texelCoord.x < size.x && texelCoord.y < size.y)
|
||||||
|
{
|
||||||
|
vec4 color = vec4(0.0, 0.0, 0.0, 1.0);
|
||||||
|
|
||||||
|
if(gl_LocalInvocationID.x != 0 && gl_LocalInvocationID.y != 0)
|
||||||
|
{
|
||||||
|
color.x = float(texelCoord.x)/(size.x);
|
||||||
|
color.y = float(texelCoord.y)/(size.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
imageStore(image, texelCoord, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ target_link_libraries(${NAME}_deps INTERFACE
|
|||||||
${ENGINE_NAME}
|
${ENGINE_NAME}
|
||||||
)
|
)
|
||||||
|
|
||||||
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17)
|
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_23)
|
||||||
set_property(TARGET ${PROJECT_NAME} PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/build")
|
set_property(TARGET ${PROJECT_NAME} PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/build")
|
||||||
|
|
||||||
add_executable(testbed main.cpp)
|
add_executable(testbed main.cpp)
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# testbed
|
||||||
|
|
||||||
|
Exercises the engine's frontend API. It includes only the public headers in `engine/include/sophia/`, so it uses the engine the way a game would.
|
||||||
|
|
||||||
|
Use it to check that public API changes work and feel right from the caller's side. It cannot include engine internals (`engine/src`). If something needs them, it belongs in [sandbox](../sandbox/README.md) instead.
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dev testbed [args...]
|
||||||
|
```
|
||||||
|
|
||||||
|
This builds the `debug` preset and runs `build/debug/bin/testbed`. Shaders are loaded from `shaders/` (`TESTBED_SHADER_DIR`).
|
||||||
Reference in New Issue
Block a user