Compare commits
No commits in common. "main" and "vulkan_api_wrapper" have entirely different histories.
main
...
vulkan_api
104 changed files with 4044 additions and 4393 deletions
|
|
@ -2,8 +2,6 @@
|
|||
kind: pipeline
|
||||
type: exec
|
||||
name: amd64 — msvc
|
||||
node:
|
||||
environment: lina
|
||||
trigger:
|
||||
branch:
|
||||
- main
|
||||
|
|
|
|||
|
|
@ -1,51 +1,47 @@
|
|||
add_module(NAME preliminary INTERFACES module.cppm fundumental_types.cppm assertions.cppm build_constants.cppm)
|
||||
add_module(NAME logger INTERFACES logger.cppm TESTS logger.test.cpp DEPENDENCIES preliminary)
|
||||
add_module(NAME tracer INTERFACES tracer.cppm DEPENDENCIES preliminary logger)
|
||||
|
||||
add_module(NAME bitwise INTERFACES operations.cppm DEPENDENCIES preliminary)
|
||||
add_module(NAME memory INTERFACES null_on_move.cppm reference.cppm scope.cppm
|
||||
DEPENDENCIES
|
||||
preliminary
|
||||
logger
|
||||
)
|
||||
add_module(NAME time INTERFACES timer.cppm TESTS timer.test.cpp DEPENDENCIES preliminary)
|
||||
add_module(NAME logger INTERFACES logger.cppm TESTS logger.test.cpp)
|
||||
add_module(NAME bitwise INTERFACES operations.cppm)
|
||||
add_module(NAME env INTERFACES constants.cppm)
|
||||
add_module(NAME memory INTERFACES null_on_move.cppm reference.cppm scope.cppm)
|
||||
add_module(NAME time INTERFACES timer.cppm TESTS timer.test.cpp)
|
||||
|
||||
add_module(
|
||||
NAME
|
||||
test
|
||||
INTERFACES
|
||||
module.cppm
|
||||
test.cppm
|
||||
expects.cppm
|
||||
registry.cppm
|
||||
SOURCES
|
||||
entrypoint.cpp
|
||||
DEPENDENCIES
|
||||
preliminary
|
||||
logger
|
||||
TESTS
|
||||
test.test.cpp
|
||||
)
|
||||
|
||||
add_module(
|
||||
NAME
|
||||
lt_debug
|
||||
ROOT_DIR
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/debug
|
||||
INTERFACES
|
||||
instrumentor.cppm
|
||||
assertions.cppm
|
||||
DEPENDENCIES
|
||||
logger
|
||||
)
|
||||
|
||||
add_module(
|
||||
NAME
|
||||
math
|
||||
INTERFACES
|
||||
algebra.cppm
|
||||
mat4.cppm
|
||||
trig.cppm
|
||||
vec2.cppm
|
||||
vec3.cppm
|
||||
vec4.cppm
|
||||
mat4.cppm
|
||||
components.cppm
|
||||
DEPENDENCIES
|
||||
preliminary
|
||||
TESTS
|
||||
trig.test.cpp
|
||||
vec2.test.cpp
|
||||
vec3.test.cpp
|
||||
vec4.test.cpp
|
||||
mat4.test.cpp
|
||||
)
|
||||
|
||||
add_module(
|
||||
|
|
@ -55,8 +51,8 @@ add_module(
|
|||
shader.cppm
|
||||
metadata.cppm
|
||||
DEPENDENCIES
|
||||
preliminary
|
||||
logger
|
||||
lt_debug
|
||||
TESTS
|
||||
shader.test.cpp
|
||||
)
|
||||
|
|
@ -71,15 +67,17 @@ add_module(
|
|||
ENTRYPOINT
|
||||
entrypoint.cpp
|
||||
DEPENDENCIES
|
||||
preliminary
|
||||
assets
|
||||
logger
|
||||
lt_debug
|
||||
TESTS
|
||||
bakers.test.cpp
|
||||
)
|
||||
|
||||
# add_executable(asset_baker entrypoint.cpp) target_link_libraries(asset_baker
|
||||
# PRIVATE libasset_baker)
|
||||
|
||||
add_module(NAME camera INTERFACES components.cppm DEPENDENCIES preliminary math)
|
||||
add_module(NAME camera INTERFACES components.cppm DEPENDENCIES math)
|
||||
|
||||
add_module(
|
||||
NAME
|
||||
|
|
@ -88,9 +86,9 @@ add_module(
|
|||
application.cppm
|
||||
system.cppm
|
||||
DEPENDENCIES
|
||||
preliminary
|
||||
memory
|
||||
PRIVATE_DEPENDENCIES
|
||||
lt_debug
|
||||
)
|
||||
|
||||
add_module(
|
||||
|
|
@ -102,13 +100,14 @@ add_module(
|
|||
entity.cppm
|
||||
DEPENDENCIES
|
||||
logger
|
||||
lt_debug
|
||||
memory
|
||||
TESTS
|
||||
registry.test.cpp
|
||||
sparse_set.test.cpp
|
||||
)
|
||||
|
||||
add_module(NAME input_codes INTERFACES input_codes.cppm DEPENDENCIES preliminary)
|
||||
add_module(NAME input_codes INTERFACES input_codes.cppm)
|
||||
|
||||
if(WIN32)
|
||||
add_module(
|
||||
|
|
@ -120,8 +119,9 @@ if(WIN32)
|
|||
requests.cppm
|
||||
events.cppm
|
||||
components.cppm
|
||||
SOURCES
|
||||
platform_windows.cpp
|
||||
DEPENDENCIES
|
||||
preliminary
|
||||
ecs
|
||||
app
|
||||
math
|
||||
|
|
@ -129,9 +129,8 @@ if(WIN32)
|
|||
input_codes
|
||||
PRIVATE_DEPENDENCIES
|
||||
logger
|
||||
lt_debug
|
||||
time
|
||||
TESTS
|
||||
system.test.cpp
|
||||
)
|
||||
|
||||
elseif(UNIX)
|
||||
|
|
@ -144,34 +143,23 @@ elseif(UNIX)
|
|||
requests.cppm
|
||||
events.cppm
|
||||
components.cppm
|
||||
SOURCES
|
||||
platform_linux.cpp
|
||||
DEPENDENCIES
|
||||
preliminary
|
||||
ecs
|
||||
app
|
||||
math
|
||||
memory
|
||||
input_codes
|
||||
wayland-client
|
||||
PRIVATE_DEPENDENCIES
|
||||
X11
|
||||
logger
|
||||
lt_debug
|
||||
time
|
||||
TESTS
|
||||
system.test.cpp
|
||||
)
|
||||
|
||||
function(add_wayland_protocol_target TARGET_NAME SPEC NAME)
|
||||
add_custom_target(wayland_${TARGET_NAME}_header COMMAND wayland-scanner client-header /usr/share/wayland-protocols${SPEC} ${CMAKE_CURRENT_SOURCE_DIR}/surface/wayland-protocols/${NAME}.h)
|
||||
add_dependencies(surface wayland_${TARGET_NAME}_header)
|
||||
add_custom_target(wayland_${TARGET_NAME}_source COMMAND wayland-scanner private-code /usr/share/wayland-protocols${SPEC} ${CMAKE_CURRENT_SOURCE_DIR}/surface/wayland-protocols/${NAME}.c)
|
||||
add_dependencies(surface wayland_${TARGET_NAME}_source)
|
||||
|
||||
target_sources(surface PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/surface/wayland-protocols/${NAME}.c)
|
||||
endfunction()
|
||||
|
||||
target_include_directories(surface PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/surface/wayland-protocols/)
|
||||
add_wayland_protocol_target(xdg_shell "/stable/xdg-shell/xdg-shell.xml" xdg-shell)
|
||||
|
||||
else()
|
||||
message(FATAL "Failed to generate cmake: unsupported platform")
|
||||
|
||||
|
|
@ -185,7 +173,6 @@ add_module(
|
|||
components.cppm
|
||||
events.cppm
|
||||
DEPENDENCIES
|
||||
preliminary
|
||||
input_codes
|
||||
surface
|
||||
math
|
||||
|
|
@ -216,7 +203,6 @@ add_module(
|
|||
vk/renderer.cppm
|
||||
vk/debugger.cppm
|
||||
DEPENDENCIES
|
||||
preliminary
|
||||
app
|
||||
ecs
|
||||
memory
|
||||
|
|
@ -255,8 +241,21 @@ add_module(
|
|||
surface
|
||||
renderer
|
||||
camera
|
||||
# TESTS system.test.cpp
|
||||
)
|
||||
|
||||
if(ENABLE_SANDBOX)
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/sandbox/)
|
||||
endif()
|
||||
add_executable(exectest ${CMAKE_CURRENT_SOURCE_DIR}/mirror/entrypoint.cpp)
|
||||
|
||||
target_link_libraries(
|
||||
exectest
|
||||
PRIVATE mirror
|
||||
app
|
||||
time
|
||||
input
|
||||
surface
|
||||
renderer
|
||||
camera
|
||||
)
|
||||
|
||||
# add_executable_module(mirror entrypoint/mirror.cpp)
|
||||
# target_link_libraries(mirror PRIVATE libmirror input)
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
export module app;
|
||||
|
||||
import preliminary;
|
||||
import app.system;
|
||||
import memory.reference;
|
||||
import memory.scope;
|
||||
import std;
|
||||
|
||||
export namespace lt::app {
|
||||
namespace lt::app {
|
||||
|
||||
/** The main application class.
|
||||
* Think of this like an aggregate of systems, you register systems through this interface.
|
||||
* Then they'll tick every "application frame".
|
||||
*/
|
||||
class Application
|
||||
export class Application
|
||||
{
|
||||
public:
|
||||
Application(const Application &) = delete;
|
||||
|
|
@ -55,13 +54,11 @@ void Application::game_loop()
|
|||
const auto &last_tick = system->get_last_tick_result();
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
|
||||
system->tick(
|
||||
TickInfo {
|
||||
system->tick(TickInfo {
|
||||
.delta_time = now - last_tick.end_time,
|
||||
.budget = std::chrono::milliseconds { 10 },
|
||||
.start_time = now,
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
for (auto &system : m_systems_to_be_registered)
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
export module app.system;
|
||||
|
||||
import preliminary;
|
||||
import logger;
|
||||
import std;
|
||||
|
||||
export namespace lt::app {
|
||||
namespace lt::app {
|
||||
|
||||
/** Information required to tick a system.
|
||||
* @note May be used across an entire application-frame (consisting of multiple systems ticking)
|
||||
*/
|
||||
struct TickInfo
|
||||
export struct TickInfo
|
||||
{
|
||||
using Timepoint_T = std::chrono::time_point<std::chrono::steady_clock>;
|
||||
|
||||
using Duration_T = std::chrono::duration<f64>;
|
||||
using Duration_T = std::chrono::duration<double>;
|
||||
|
||||
/** Duration since previous tick's end_time to current tick's start_time. */
|
||||
Duration_T delta_time {};
|
||||
|
|
@ -31,11 +30,11 @@ struct TickInfo
|
|||
};
|
||||
|
||||
/** Information about how a system's tick performed */
|
||||
struct TickResult
|
||||
export struct TickResult
|
||||
{
|
||||
using Timepoint_T = std::chrono::time_point<std::chrono::steady_clock>;
|
||||
|
||||
using Duration_T = std::chrono::duration<f64>;
|
||||
using Duration_T = std::chrono::duration<double>;
|
||||
|
||||
/** The info supplied to the system for ticking. */
|
||||
TickInfo info;
|
||||
|
|
@ -47,9 +46,9 @@ struct TickResult
|
|||
Timepoint_T end_time;
|
||||
};
|
||||
|
||||
struct SystemDiagnosis
|
||||
export struct SystemDiagnosis
|
||||
{
|
||||
enum class Severity : u8
|
||||
enum class Severity : std::uint8_t
|
||||
{
|
||||
verbose,
|
||||
info,
|
||||
|
|
@ -65,7 +64,7 @@ struct SystemDiagnosis
|
|||
Severity severity;
|
||||
};
|
||||
|
||||
class SystemStats
|
||||
export class SystemStats
|
||||
{
|
||||
public:
|
||||
void push_diagnosis(SystemDiagnosis &&diagnosis)
|
||||
|
|
@ -84,7 +83,7 @@ private:
|
|||
std::vector<SystemDiagnosis> m_diagnosis;
|
||||
};
|
||||
|
||||
class ISystem
|
||||
export class ISystem
|
||||
{
|
||||
public:
|
||||
ISystem() = default;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
export module bakers;
|
||||
|
||||
import preliminary;
|
||||
import debug.assertions;
|
||||
import assets.metadata;
|
||||
import assets.shader;
|
||||
import logger;
|
||||
import std;
|
||||
|
||||
export void bake_shader(
|
||||
const std::filesystem::path &in_path,
|
||||
|
|
@ -25,18 +26,16 @@ export void bake_shader(
|
|||
|
||||
// Don't bother linking to shaderc, just invoke the command with a system call.
|
||||
// NOLINTNEXTLINE(concurrency-mt-unsafe)
|
||||
std::system(
|
||||
std::format(
|
||||
std::system(std::format(
|
||||
"glslc --target-env=vulkan1.4 -std=450core -fshader-stage={} {} -o {}",
|
||||
type == vertex ? "vert" : "frag",
|
||||
glsl_path,
|
||||
spv_path
|
||||
)
|
||||
.c_str()
|
||||
);
|
||||
.c_str());
|
||||
|
||||
auto stream = std::ifstream(spv_path, std::ios::binary);
|
||||
ensure(
|
||||
lt::debug::ensure(
|
||||
stream.is_open(),
|
||||
"Failed to open compiled {} shader at: {}",
|
||||
type == vertex ? "vert" : "frag",
|
||||
|
|
@ -46,9 +45,10 @@ export void bake_shader(
|
|||
stream.seekg(0, std::ios::end);
|
||||
const auto size = stream.tellg();
|
||||
|
||||
auto bytes = std::vector<byte>(size);
|
||||
auto bytes = std::vector<std::byte>(size);
|
||||
stream.seekg(0, std::ios::beg);
|
||||
stream.read((char *)bytes.data(), size); // NOLINT
|
||||
lt::log::debug("BYTES: {}", bytes.size());
|
||||
stream.close();
|
||||
std::filesystem::remove(spv_path);
|
||||
|
||||
|
|
|
|||
0
modules/asset_baker/bakers.test.cpp
Normal file
0
modules/asset_baker/bakers.test.cpp
Normal file
|
|
@ -1,9 +1,10 @@
|
|||
import preliminary;
|
||||
import assets.shader;
|
||||
import logger;
|
||||
import bakers;
|
||||
import std;
|
||||
|
||||
auto main(i32 argc, char *argv[]) -> i32
|
||||
|
||||
auto main(int argc, char *argv[]) -> std::int32_t
|
||||
try
|
||||
{
|
||||
if (argc != 2)
|
||||
|
|
@ -20,8 +21,7 @@ try
|
|||
}
|
||||
|
||||
const auto &in_path = directory_iterator.path();
|
||||
const std::string in_path_str = in_path.generic_string();
|
||||
const auto out_path = std::format("{}.asset", in_path_str);
|
||||
const auto out_path = std::format("{}.asset", in_path.c_str());
|
||||
|
||||
if (in_path.extension() == ".vert")
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,20 +1,19 @@
|
|||
export module assets.metadata;
|
||||
|
||||
import preliminary;
|
||||
import std;
|
||||
|
||||
export namespace lt::assets {
|
||||
|
||||
using Type_T = std::array<const char, 16>;
|
||||
|
||||
using Tag_T = u8;
|
||||
using Tag_T = std::uint8_t;
|
||||
|
||||
using Version = u8;
|
||||
using Version = std::uint8_t;
|
||||
|
||||
using Blob = std::vector<byte>;
|
||||
using Blob = std::vector<std::byte>;
|
||||
|
||||
constexpr auto current_version = Version { 1u };
|
||||
|
||||
enum class CompressionType : u8
|
||||
enum class CompressionType : std::uint8_t
|
||||
{
|
||||
none,
|
||||
lz4,
|
||||
|
|
@ -32,13 +31,13 @@ struct BlobMetadata
|
|||
{
|
||||
Tag_T tag;
|
||||
|
||||
size_t offset;
|
||||
std::size_t offset;
|
||||
|
||||
CompressionType compression_type;
|
||||
|
||||
size_t compressed_size;
|
||||
std::size_t compressed_size;
|
||||
|
||||
size_t uncompressed_size;
|
||||
std::size_t uncompressed_size;
|
||||
};
|
||||
|
||||
} // namespace lt::assets
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
export module assets.shader;
|
||||
|
||||
import preliminary;
|
||||
import assets.metadata;
|
||||
import logger;
|
||||
import debug.assertions;
|
||||
|
||||
import std;
|
||||
|
||||
export namespace lt::assets {
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ public:
|
|||
code,
|
||||
};
|
||||
|
||||
enum class Type : u8
|
||||
enum class Type : std::uint8_t
|
||||
{
|
||||
vertex,
|
||||
fragment,
|
||||
|
|
@ -38,7 +38,7 @@ public:
|
|||
|
||||
ShaderAsset(const std::filesystem::path &path);
|
||||
|
||||
void unpack_to(BlobTag tag, std::span<byte> destination) const;
|
||||
void unpack_to(BlobTag tag, std::span<std::byte> destination) const;
|
||||
|
||||
[[nodiscard]] auto unpack(BlobTag tag) const -> Blob;
|
||||
|
||||
|
|
@ -54,7 +54,7 @@ public:
|
|||
|
||||
[[nodiscard]] auto get_blob_metadata(BlobTag tag) const -> const BlobMetadata &
|
||||
{
|
||||
ensure(
|
||||
debug::ensure(
|
||||
tag == BlobTag::code,
|
||||
"Invalid blob tag for shader asset: {}",
|
||||
std::to_underlying(tag)
|
||||
|
|
@ -88,17 +88,16 @@ constexpr auto total_metadata_size = //
|
|||
+ sizeof(BlobMetadata::compressed_size) //
|
||||
+ sizeof(BlobMetadata::uncompressed_size);
|
||||
|
||||
ShaderAsset::ShaderAsset(const std::filesystem::path &path)
|
||||
: m_stream(path, std::ios::beg | std::ios::binary)
|
||||
ShaderAsset::ShaderAsset(const std::filesystem::path &path): m_stream(path)
|
||||
{
|
||||
ensure(m_stream.is_open(), "Failed to open shader asset at: {}", path.string());
|
||||
debug::ensure(m_stream.is_open(), "Failed to open shader asset at: {}", path.string());
|
||||
const auto read = [this](auto &field) {
|
||||
m_stream.read(std::bit_cast<char *>(&field), sizeof(field));
|
||||
};
|
||||
|
||||
m_stream.seekg(0, std::ifstream::end);
|
||||
const auto file_size = static_cast<size_t>(m_stream.tellg());
|
||||
ensure(
|
||||
const auto file_size = static_cast<std::size_t>(m_stream.tellg());
|
||||
debug::ensure(
|
||||
file_size > total_metadata_size,
|
||||
"Failed to open shader asset at: {}, file smaller than metadata: {} < {}",
|
||||
path.string(),
|
||||
|
|
@ -110,14 +109,13 @@ ShaderAsset::ShaderAsset(const std::filesystem::path &path)
|
|||
read(m_asset_metadata.type);
|
||||
read(m_asset_metadata.version);
|
||||
read(m_metadata.type);
|
||||
|
||||
read(m_code_blob_metadata.tag);
|
||||
read(m_code_blob_metadata.offset);
|
||||
read(m_code_blob_metadata.compression_type);
|
||||
read(m_code_blob_metadata.compressed_size);
|
||||
read(m_code_blob_metadata.uncompressed_size);
|
||||
|
||||
ensure(
|
||||
debug::ensure(
|
||||
m_asset_metadata.type == asset_type_identifier,
|
||||
"Failed to open shader asset at: {}, incorrect asset type: {} != {}",
|
||||
path.string(),
|
||||
|
|
@ -125,7 +123,7 @@ ShaderAsset::ShaderAsset(const std::filesystem::path &path)
|
|||
asset_type_identifier
|
||||
);
|
||||
|
||||
ensure(
|
||||
debug::ensure(
|
||||
m_asset_metadata.version == current_version,
|
||||
"Failed to open shader asset at: {}, version mismatch: {} != {}",
|
||||
path.string(),
|
||||
|
|
@ -133,21 +131,21 @@ ShaderAsset::ShaderAsset(const std::filesystem::path &path)
|
|||
current_version
|
||||
);
|
||||
|
||||
ensure(
|
||||
debug::ensure(
|
||||
std::to_underlying(m_metadata.type) <= std::to_underlying(Type::compute),
|
||||
"Failed to open shader asset at: {}, invalid shader type: {}",
|
||||
path.string(),
|
||||
std::to_underlying(m_metadata.type)
|
||||
);
|
||||
|
||||
ensure(
|
||||
debug::ensure(
|
||||
m_code_blob_metadata.tag == std::to_underlying(BlobTag::code),
|
||||
"Failed to open shader asset at: {}, invalid blob tag: {}",
|
||||
path.string(),
|
||||
m_code_blob_metadata.tag
|
||||
);
|
||||
|
||||
ensure(
|
||||
debug::ensure(
|
||||
m_code_blob_metadata.offset + m_code_blob_metadata.compressed_size <= file_size,
|
||||
"Failed to open shader asset at: {}, file smaller than blob: {} > {} + {}",
|
||||
path.string(),
|
||||
|
|
@ -177,7 +175,7 @@ ShaderAsset::ShaderAsset(const std::filesystem::path &path)
|
|||
.uncompressed_size = code_blob.size(),
|
||||
};
|
||||
|
||||
ensure(stream.is_open(), "Failed to pack shader asset to {}", destination.string());
|
||||
debug::ensure(stream.is_open(), "Failed to pack shader asset to {}", destination.string());
|
||||
const auto write = [&stream](auto &field) {
|
||||
stream.write(std::bit_cast<char *>(&field), sizeof(field));
|
||||
};
|
||||
|
|
@ -192,30 +190,38 @@ ShaderAsset::ShaderAsset(const std::filesystem::path &path)
|
|||
stream.write(std::bit_cast<char *>(code_blob.data()), static_cast<long long>(code_blob.size()));
|
||||
}
|
||||
|
||||
void ShaderAsset::unpack_to(BlobTag tag, std::span<byte> destination) const
|
||||
void ShaderAsset::unpack_to(BlobTag tag, std::span<std::byte> destination) const
|
||||
{
|
||||
ensure(tag == BlobTag::code, "Invalid blob tag for shader asset: {}", std::to_underlying(tag));
|
||||
debug::ensure(
|
||||
tag == BlobTag::code,
|
||||
"Invalid blob tag for shader asset: {}",
|
||||
std::to_underlying(tag)
|
||||
);
|
||||
|
||||
ensure(
|
||||
debug::ensure(
|
||||
destination.size() >= m_code_blob_metadata.uncompressed_size,
|
||||
"Failed to unpack shader blob {} to destination ({}) of size {} since it's smaller "
|
||||
"than the blobl's uncompressed size: {}",
|
||||
std::to_underlying(tag),
|
||||
std::bit_cast<size_t>(destination.data()),
|
||||
std::bit_cast<std::size_t>(destination.data()),
|
||||
destination.size(),
|
||||
m_code_blob_metadata.uncompressed_size
|
||||
);
|
||||
|
||||
m_stream.seekg(static_cast<long long>(m_code_blob_metadata.offset), std::ifstream::beg);
|
||||
m_stream.seekg(static_cast<long long>(m_code_blob_metadata.offset));
|
||||
m_stream.read(
|
||||
std::bit_cast<char *>(destination.data()),
|
||||
m_code_blob_metadata.uncompressed_size
|
||||
static_cast<long long>(m_code_blob_metadata.uncompressed_size)
|
||||
);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto ShaderAsset::unpack(BlobTag tag) const -> Blob
|
||||
{
|
||||
ensure(tag == BlobTag::code, "Invalid blob tag for shader asset: {}", std::to_underlying(tag));
|
||||
debug::ensure(
|
||||
tag == BlobTag::code,
|
||||
"Invalid blob tag for shader asset: {}",
|
||||
std::to_underlying(tag)
|
||||
);
|
||||
|
||||
auto blob = Blob(m_code_blob_metadata.uncompressed_size);
|
||||
unpack_to(tag, blob);
|
||||
|
|
|
|||
|
|
@ -1,65 +1,47 @@
|
|||
import test;
|
||||
import assets.metadata;
|
||||
import assets.shader;
|
||||
import test.test;
|
||||
import test.expects;
|
||||
import std;
|
||||
|
||||
using ::lt::assets::AssetMetadata;
|
||||
using ::lt::assets::Blob;
|
||||
using ::lt::assets::BlobMetadata;
|
||||
using ::lt::assets::ShaderAsset;
|
||||
using ::lt::test::Case;
|
||||
using ::lt::test::expect_eq;
|
||||
using ::lt::test::expect_throw;
|
||||
using ::lt::test::expect_true;
|
||||
using ::lt::test::Suite;
|
||||
using ::lt::test::operator""_suite;
|
||||
|
||||
const auto test_data_path = std::filesystem::path { "./data/test_assets" };
|
||||
const auto tmp_path = std::filesystem::path { "/tmp/lt_assets_tests/" };
|
||||
|
||||
[[nodiscard]] auto generate_blob(size_t size) -> Blob
|
||||
{
|
||||
auto blob = Blob {};
|
||||
for (auto idx : std::views::iota(0u, size))
|
||||
{
|
||||
blob.emplace_back(static_cast<byte>(idx));
|
||||
}
|
||||
|
||||
return blob;
|
||||
}
|
||||
|
||||
Suite raii = "shader_raii"_suite = [] {
|
||||
std::filesystem::current_path(test_data_path);
|
||||
std::filesystem::create_directories(tmp_path);
|
||||
|
||||
Case { "happy paths" } = [] {
|
||||
auto shader_asset = ShaderAsset { "triangle.frag.asset" };
|
||||
Case { "happy path won't throw" } = [] {
|
||||
|
||||
};
|
||||
|
||||
Case { "unhappy paths" } = [] {
|
||||
// non-existent file
|
||||
expect_throw([] { ShaderAsset { "path" }; });
|
||||
|
||||
// incompatible type
|
||||
expect_throw([] { ShaderAsset { "dummytext" }; });
|
||||
|
||||
// some random stressing
|
||||
expect_throw([] {
|
||||
for (auto idx : std::views::iota(0u, 1'000u))
|
||||
{
|
||||
auto shader_asset = ShaderAsset { std::to_string(idx) };
|
||||
}
|
||||
});
|
||||
Case { "many won't freeze/throw" } = [] {
|
||||
};
|
||||
|
||||
Case { "many" } = [] {
|
||||
for (auto idx : std::views::iota(0u, 1'000u))
|
||||
{
|
||||
ignore = idx;
|
||||
auto shader_asset = ShaderAsset { "triangle.frag.asset" };
|
||||
}
|
||||
Case { "unhappy path throws" } = [] {
|
||||
expect_throw([] { ShaderAsset { "random_path" }; });
|
||||
};
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-interfaces-global-init)
|
||||
Suite packing = "shader_pack"_suite = [] {
|
||||
Case { "Unpacking packed data returns the same data" } = [] {
|
||||
Case { "" } = [] {
|
||||
const auto out_path = tmp_path / "shader_packing";
|
||||
constexpr auto blob_size = size_t { 255u };
|
||||
|
||||
auto blob = generate_blob(blob_size);
|
||||
auto dummy_blob = lt::assets::Blob {};
|
||||
for (auto idx : std::views::iota(0, 255))
|
||||
{
|
||||
dummy_blob.emplace_back(static_cast<std::byte>(idx));
|
||||
}
|
||||
|
||||
const auto expected_size = //
|
||||
sizeof(AssetMetadata::type) //
|
||||
|
|
@ -70,7 +52,7 @@ Suite packing = "shader_pack"_suite = [] {
|
|||
+ sizeof(BlobMetadata::compression_type) //
|
||||
+ sizeof(BlobMetadata::compressed_size) //
|
||||
+ sizeof(BlobMetadata::uncompressed_size) //
|
||||
+ blob.size();
|
||||
+ dummy_blob.size();
|
||||
|
||||
ShaderAsset::pack(
|
||||
out_path,
|
||||
|
|
@ -81,7 +63,7 @@ Suite packing = "shader_pack"_suite = [] {
|
|||
ShaderAsset::Metadata {
|
||||
.type = ShaderAsset::Type::vertex,
|
||||
},
|
||||
std::move(blob)
|
||||
std::move(dummy_blob)
|
||||
);
|
||||
|
||||
auto stream = std::ifstream {
|
||||
|
|
@ -90,8 +72,8 @@ Suite packing = "shader_pack"_suite = [] {
|
|||
};
|
||||
expect_true(stream.is_open());
|
||||
|
||||
stream.seekg(0u, std::ios::end);
|
||||
const auto file_size = static_cast<size_t>(stream.tellg());
|
||||
stream.seekg(0, std::ios::end);
|
||||
const auto file_size = static_cast<std::size_t>(stream.tellg());
|
||||
expect_eq(file_size, expected_size);
|
||||
stream.close();
|
||||
|
||||
|
|
@ -104,12 +86,12 @@ Suite packing = "shader_pack"_suite = [] {
|
|||
const auto &metadata = shader_asset.get_metadata();
|
||||
expect_eq(metadata.type, ShaderAsset::Type::vertex);
|
||||
|
||||
auto unpakced_blob = shader_asset.unpack(ShaderAsset::BlobTag::code);
|
||||
expect_eq(unpakced_blob.size(), blob_size);
|
||||
auto blob = shader_asset.unpack(ShaderAsset::BlobTag::code);
|
||||
expect_eq(blob.size(), 255);
|
||||
|
||||
for (auto idx : std::views::iota(0u, blob_size))
|
||||
for (auto idx : std::views::iota(0, 255))
|
||||
{
|
||||
expect_eq(unpakced_blob[idx], static_cast<byte>(idx));
|
||||
expect_eq(blob[idx], static_cast<std::byte>(idx));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
export module bitwise;
|
||||
import preliminary;
|
||||
import std;
|
||||
|
||||
namespace lt::bitwise {
|
||||
|
||||
/* bit-wise */
|
||||
export constexpr auto bit(u32 x) -> u32
|
||||
export constexpr auto bit(std::uint32_t x) -> std::uint32_t
|
||||
{
|
||||
return u32 { 1u } << x;
|
||||
return 1u << x;
|
||||
}
|
||||
|
||||
} // namespace lt::bitwise
|
||||
|
|
|
|||
|
|
@ -1,19 +1,17 @@
|
|||
export module camera.components;
|
||||
|
||||
import preliminary;
|
||||
import math.vec4;
|
||||
|
||||
export namespace lt::camera::components {
|
||||
namespace lt::camera::components {
|
||||
|
||||
struct PerspectiveCamera
|
||||
export struct PerspectiveCamera
|
||||
{
|
||||
f32 vertical_fov {};
|
||||
float vertical_fov {};
|
||||
|
||||
f32 near_plane {};
|
||||
float near_plane {};
|
||||
|
||||
f32 far_plane {};
|
||||
float far_plane {};
|
||||
|
||||
f32 aspect_ratio {};
|
||||
float aspect_ratio {};
|
||||
|
||||
math::vec4 background_color;
|
||||
|
||||
|
|
|
|||
47
modules/debug/assertions.cppm
Normal file
47
modules/debug/assertions.cppm
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
export module debug.assertions;
|
||||
|
||||
import std;
|
||||
|
||||
namespace lt::debug {
|
||||
|
||||
///////////////////////////////////////
|
||||
// ----------* INTERFACE *--------- //
|
||||
/////////////////////////////////////
|
||||
export template<typename Expression_T, typename... Args_T>
|
||||
struct ensure
|
||||
{
|
||||
ensure(
|
||||
const Expression_T &expression,
|
||||
std::format_string<Args_T...> fmt,
|
||||
Args_T &&...args,
|
||||
const std::source_location &location = std::source_location::current()
|
||||
);
|
||||
};
|
||||
|
||||
export template<typename Expression_T, typename... Args_T>
|
||||
ensure(Expression_T, std::format_string<Args_T...>, Args_T &&...)
|
||||
-> ensure<Expression_T, Args_T...>;
|
||||
|
||||
///////////////////////////////////////
|
||||
// * IMPLEMENTATION -- TEMPLATES * //
|
||||
/////////////////////////////////////
|
||||
template<typename Expression_T, typename... Args_T>
|
||||
ensure<Expression_T, Args_T...>::ensure(
|
||||
const Expression_T &expression,
|
||||
std::format_string<Args_T...> fmt,
|
||||
Args_T &&...args,
|
||||
const std::source_location &location
|
||||
)
|
||||
{
|
||||
if (!static_cast<bool>(expression))
|
||||
{
|
||||
throw std::runtime_error { std::format(
|
||||
"exception: {}\nlocation: {}:{}",
|
||||
std::format(fmt, std::forward<Args_T>(args)...),
|
||||
location.file_name(),
|
||||
location.line()
|
||||
) };
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lt::debug
|
||||
|
|
@ -1,25 +1,23 @@
|
|||
// @todo(Light): Implement...
|
||||
|
||||
export module debug.instrumentor;
|
||||
|
||||
import preliminary;
|
||||
import std;
|
||||
import logger;
|
||||
|
||||
namespace lt::tracer {
|
||||
namespace lt::debug {
|
||||
|
||||
struct ScopeTraceResult
|
||||
struct ScopeProfileResult
|
||||
{
|
||||
std::string name;
|
||||
u64 start, duration;
|
||||
u32 threadID;
|
||||
long long start, duration;
|
||||
std::uint32_t threadID;
|
||||
};
|
||||
|
||||
class Tracer
|
||||
class Instrumentor
|
||||
{
|
||||
public:
|
||||
static auto instance() -> Tracer &
|
||||
static auto instance() -> Instrumentor &
|
||||
{
|
||||
static auto instance = Tracer {};
|
||||
static auto instance = Instrumentor {};
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
|
@ -32,7 +30,7 @@ public:
|
|||
instance().end_session_impl();
|
||||
}
|
||||
|
||||
static void submit_scope_profile(const ScopeTraceResult &profileResult)
|
||||
static void submit_scope_profile(const ScopeProfileResult &profileResult)
|
||||
{
|
||||
instance().submit_scope_profile_impl(profileResult);
|
||||
}
|
||||
|
|
@ -42,46 +40,46 @@ private:
|
|||
|
||||
unsigned int m_current_session_count { 0u };
|
||||
|
||||
Tracer() = default;
|
||||
Instrumentor() = default;
|
||||
|
||||
void begin_session_impl(const std::string &outputPath);
|
||||
|
||||
void end_session_impl();
|
||||
|
||||
void submit_scope_profile_impl(const ScopeTraceResult &profileResult);
|
||||
void submit_scope_profile_impl(const ScopeProfileResult &profileResult);
|
||||
};
|
||||
|
||||
class TracerTimer
|
||||
class InstrumentorTimer
|
||||
{
|
||||
public:
|
||||
TracerTimer(const std::string &scopeName);
|
||||
InstrumentorTimer(const std::string &scopeName);
|
||||
|
||||
~TracerTimer();
|
||||
~InstrumentorTimer();
|
||||
|
||||
private:
|
||||
ScopeTraceResult m_result;
|
||||
ScopeProfileResult m_result;
|
||||
|
||||
std::chrono::time_point<std::chrono::steady_clock> m_start;
|
||||
};
|
||||
|
||||
} // namespace lt::tracer
|
||||
} // namespace lt::debug
|
||||
|
||||
/* scope */
|
||||
#define lt_trace_scope(name) lt_profile_scope_no_redifinition(name, __LINE__)
|
||||
#define lt_trace_scope_no_redifinition(name, line) lt_profile_scope_no_redifinition2(name, line)
|
||||
#define lt_trace_scope_no_redifinition2(name, line) InstrumentorTimer timer##line(name)
|
||||
#define lt_profile_scope(name) lt_profile_scope_no_redifinition(name, __LINE__)
|
||||
#define lt_profile_scope_no_redifinition(name, line) lt_profile_scope_no_redifinition2(name, line)
|
||||
#define lt_profile_scope_no_redifinition2(name, line) InstrumentorTimer timer##line(name)
|
||||
|
||||
/* function */
|
||||
#define lt_trace_function lt_profile_scope(__FUNCSIG__)
|
||||
#define LT_PROFILE_FUNCTION lt_profile_scope(__FUNCSIG__)
|
||||
|
||||
/* session */
|
||||
#define lt_trace_begin_session(outputPath) ::lt::Instrumentor::begin_session(outputPath)
|
||||
#define lt_trace_end_session() ::lt::Instrumentor::end_session()
|
||||
#define lt_profile_begin_session(outputPath) ::lt::Instrumentor::begin_session(outputPath)
|
||||
#define lt_profile_end_session() ::lt::Instrumentor::end_session()
|
||||
|
||||
module :private;
|
||||
namespace lt::tracer {
|
||||
namespace lt::debug {
|
||||
|
||||
void Tracer::begin_session_impl(const std::string &outputPath)
|
||||
void Instrumentor::begin_session_impl(const std::string &outputPath)
|
||||
{
|
||||
std::filesystem::create_directory(outputPath.substr(0, outputPath.find_last_of('/') + 1));
|
||||
|
||||
|
|
@ -89,7 +87,7 @@ void Tracer::begin_session_impl(const std::string &outputPath)
|
|||
m_output_file_stream << "{\"traceEvents\":[";
|
||||
}
|
||||
|
||||
void Tracer::end_session_impl()
|
||||
void Instrumentor::end_session_impl()
|
||||
{
|
||||
if (m_current_session_count == 0u)
|
||||
{
|
||||
|
|
@ -103,7 +101,7 @@ void Tracer::end_session_impl()
|
|||
m_output_file_stream.close();
|
||||
}
|
||||
|
||||
void Tracer::submit_scope_profile_impl(const ScopeTraceResult &profileResult)
|
||||
void Instrumentor::submit_scope_profile_impl(const ScopeProfileResult &profileResult)
|
||||
{
|
||||
if (m_current_session_count++ == 0u)
|
||||
{
|
||||
|
|
@ -124,13 +122,13 @@ void Tracer::submit_scope_profile_impl(const ScopeTraceResult &profileResult)
|
|||
m_output_file_stream << "}";
|
||||
}
|
||||
|
||||
TracerTimer::TracerTimer(const std::string &scopeName)
|
||||
InstrumentorTimer::InstrumentorTimer(const std::string &scopeName)
|
||||
: m_result({ .name = scopeName, .start = 0, .duration = 0, .threadID = 0 })
|
||||
, m_start(std::chrono::steady_clock::now())
|
||||
{
|
||||
}
|
||||
|
||||
TracerTimer::~TracerTimer()
|
||||
InstrumentorTimer::~InstrumentorTimer()
|
||||
{
|
||||
auto end = std::chrono::steady_clock::now();
|
||||
|
||||
|
|
@ -143,6 +141,6 @@ TracerTimer::~TracerTimer()
|
|||
.count()
|
||||
- m_result.start;
|
||||
|
||||
Tracer::submit_scope_profile(m_result);
|
||||
Instrumentor::submit_scope_profile(m_result);
|
||||
}
|
||||
} // namespace lt::tracer
|
||||
} // namespace lt::debug
|
||||
|
|
@ -1,20 +1,20 @@
|
|||
export module ecs.entity;
|
||||
|
||||
import preliminary;
|
||||
import debug.assertions;
|
||||
import memory.reference;
|
||||
import ecs.registry;
|
||||
import std;
|
||||
|
||||
export namespace lt::ecs {
|
||||
namespace lt::ecs {
|
||||
|
||||
/** High-level entity convenience wrapper */
|
||||
class Entity
|
||||
export class Entity
|
||||
{
|
||||
public:
|
||||
Entity(memory::Ref<Registry> registry, EntityId identifier)
|
||||
: m_registry(std::move(registry))
|
||||
, m_identifier(identifier)
|
||||
{
|
||||
ensure(m_registry, "Failed to create Entity ({}): null registry", m_identifier);
|
||||
debug::ensure(m_registry, "Failed to create Entity ({}): null registry", m_identifier);
|
||||
}
|
||||
|
||||
template<typename Component_T>
|
||||
|
|
@ -51,4 +51,5 @@ private:
|
|||
EntityId m_identifier;
|
||||
};
|
||||
|
||||
|
||||
} // namespace lt::ecs
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
export module ecs.registry;
|
||||
|
||||
import preliminary;
|
||||
import debug.assertions;
|
||||
import ecs.sparse_set;
|
||||
import memory.scope;
|
||||
import std;
|
||||
|
||||
export namespace lt::ecs {
|
||||
namespace lt::ecs {
|
||||
|
||||
using EntityId = u32;
|
||||
export using EntityId = std::uint32_t;
|
||||
|
||||
constexpr auto null_entity = std::numeric_limits<EntityId>::max();
|
||||
export constexpr auto null_entity = std::numeric_limits<EntityId>::max();
|
||||
|
||||
/** A registry of components, the heart of an ECS architecture.
|
||||
*
|
||||
|
|
@ -23,7 +23,7 @@ constexpr auto null_entity = std::numeric_limits<EntityId>::max();
|
|||
* @ref https://github.com/skypjack/entt
|
||||
* @ref https://github.com/SanderMertens/flecs
|
||||
*/
|
||||
class Registry
|
||||
export class Registry
|
||||
{
|
||||
public:
|
||||
using UnderlyingSparseSet_T = TypeErasedSparseSet<EntityId>;
|
||||
|
|
@ -190,25 +190,25 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
[[nodiscard]] auto get_entity_count() const -> size_t
|
||||
[[nodiscard]] auto get_entity_count() const -> std::size_t
|
||||
{
|
||||
return static_cast<size_t>(m_entity_count);
|
||||
return static_cast<std::size_t>(m_entity_count);
|
||||
}
|
||||
|
||||
private:
|
||||
using TypeId = size_t;
|
||||
using TypeId = std::size_t;
|
||||
|
||||
static consteval auto hash_cstr(const char *str) -> TypeId
|
||||
{
|
||||
constexpr auto fnv_offset_basis = size_t { 14695981039346656037ull };
|
||||
constexpr auto fnv_prime = size_t { 1099511628211ull };
|
||||
constexpr auto fnv_offset_basis = std::size_t { 14695981039346656037ull };
|
||||
constexpr auto fnv_prime = std::size_t { 1099511628211ull };
|
||||
|
||||
auto hash = fnv_offset_basis;
|
||||
|
||||
for (const auto &ch : std::string_view { str })
|
||||
{
|
||||
hash *= fnv_prime;
|
||||
hash ^= static_cast<u8>(ch);
|
||||
hash ^= static_cast<std::uint8_t>(ch);
|
||||
}
|
||||
|
||||
return hash;
|
||||
|
|
@ -242,7 +242,7 @@ private:
|
|||
|
||||
auto *base_set = m_sparsed_sets[type_id].get();
|
||||
auto *derived_set = dynamic_cast<SparseSet<T, EntityId> *>(base_set);
|
||||
ensure(derived_set, "Failed to downcast to derived set");
|
||||
debug::ensure(derived_set, "Failed to downcast to derived set");
|
||||
|
||||
return *derived_set;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,21 @@
|
|||
import test;
|
||||
import ecs.registry;
|
||||
import test.test;
|
||||
import test.expects;
|
||||
import std;
|
||||
|
||||
using ::lt::ecs::EntityId;
|
||||
using ::lt::ecs::Registry;
|
||||
using ::lt::test::Case;
|
||||
using ::lt::test::expect_eq;
|
||||
using ::lt::test::expect_false;
|
||||
using ::lt::test::expect_true;
|
||||
using ::lt::test::expect_unreachable;
|
||||
using ::lt::test::Suite;
|
||||
using ::lt::test::operator""_suite;
|
||||
|
||||
struct Component
|
||||
{
|
||||
i32 m_int {};
|
||||
int m_int {};
|
||||
std::string m_string;
|
||||
|
||||
[[nodiscard]] friend auto operator==(const Component &lhs, const Component &rhs) -> bool
|
||||
|
|
@ -30,7 +39,7 @@ struct std::formatter<Component>
|
|||
|
||||
struct Component_B
|
||||
{
|
||||
f32 m_float {};
|
||||
float m_float {};
|
||||
|
||||
[[nodiscard]] friend auto operator==(const Component_B lhs, const Component_B &rhs) -> bool
|
||||
{
|
||||
|
|
@ -52,21 +61,20 @@ struct std::formatter<Component_B>
|
|||
};
|
||||
|
||||
Suite raii = "raii"_suite = [] {
|
||||
Case { "happy paths" } = [] {
|
||||
ignore = Registry {};
|
||||
Case { "happy path won't throw" } = [] {
|
||||
std::ignore = Registry {};
|
||||
};
|
||||
|
||||
Case { "unhappy paths" } = [] {
|
||||
};
|
||||
|
||||
Case { "many" } = [] {
|
||||
Case { "many won't freeze/throw" } = [] {
|
||||
for (auto idx : std::views::iota(0, 100'000))
|
||||
{
|
||||
ignore = idx;
|
||||
ignore = Registry {};
|
||||
std::ignore = Registry {};
|
||||
}
|
||||
};
|
||||
|
||||
Case { "unhappy path throws" } = [] {
|
||||
};
|
||||
|
||||
Case { "post construct has correct state" } = [] {
|
||||
auto registry = Registry {};
|
||||
expect_eq(registry.get_entity_count(), 0);
|
||||
|
|
@ -150,7 +158,6 @@ Suite callbacks = "callbacks"_suite = [] {
|
|||
|
||||
Case { "on_construct/on_destruct won't get called on unrelated component" } = [] {
|
||||
auto registry = Registry {};
|
||||
|
||||
registry.connect_on_construct<Component>([&](Registry &, EntityId) {
|
||||
expect_unreachable();
|
||||
});
|
||||
|
|
@ -160,7 +167,6 @@ Suite callbacks = "callbacks"_suite = [] {
|
|||
|
||||
for (auto idx : std::views::iota(0, 100'000))
|
||||
{
|
||||
ignore = idx;
|
||||
registry.add<Component_B>(registry.create_entity(), {});
|
||||
}
|
||||
};
|
||||
|
|
@ -182,8 +188,6 @@ Suite callbacks = "callbacks"_suite = [] {
|
|||
expect_true(on_destruct_called.empty());
|
||||
for (auto idx : std::views::iota(0, 100'000))
|
||||
{
|
||||
ignore = idx;
|
||||
|
||||
auto entity = all_entities.emplace_back(registry.create_entity());
|
||||
registry.add<Component>(entity, {});
|
||||
}
|
||||
|
|
@ -218,7 +222,7 @@ Suite each = "each"_suite = [] {
|
|||
component_map_a[entity] = component;
|
||||
}
|
||||
|
||||
auto component_map_b = std::unordered_map<EntityId, Component_B> {};
|
||||
auto component_map_b = std::unordered_map<lt::ecs::EntityId, Component_B> {};
|
||||
for (auto idx : std::views::iota(0, 10'000))
|
||||
{
|
||||
auto entity = EntityId {};
|
||||
|
|
@ -233,7 +237,7 @@ Suite each = "each"_suite = [] {
|
|||
}
|
||||
auto &component = registry.add<Component_B>(
|
||||
entity,
|
||||
{ .m_float = static_cast<f32>(idx) / 2.0f }
|
||||
{ .m_float = static_cast<float>(idx) / 2.0f }
|
||||
);
|
||||
|
||||
component_map_b[entity] = component;
|
||||
|
|
@ -304,7 +308,7 @@ Suite views = "views"_suite = [] {
|
|||
}
|
||||
auto &component = registry.add<Component_B>(
|
||||
entity,
|
||||
{ .m_float = static_cast<f32>(idx) / 2.0f }
|
||||
{ .m_float = static_cast<float>(idx) / 2.0f }
|
||||
);
|
||||
|
||||
component_map_b[entity] = component;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
export module ecs.sparse_set;
|
||||
import debug.assertions;
|
||||
import std;
|
||||
|
||||
import preliminary;
|
||||
|
||||
export namespace lt::ecs {
|
||||
namespace lt::ecs {
|
||||
|
||||
/**
|
||||
* @ref https://programmingpraxis.com/2012/03/09/sparse-sets/
|
||||
*/
|
||||
template<typename Identifier_T = u32>
|
||||
export template<typename Identifier_T = std::uint32_t>
|
||||
class TypeErasedSparseSet
|
||||
{
|
||||
public:
|
||||
|
|
@ -26,19 +26,19 @@ public:
|
|||
virtual void remove(Identifier_T identifier) = 0;
|
||||
};
|
||||
|
||||
template<typename Value_T, typename Identifier_T = u32>
|
||||
export template<typename Value_T, typename Identifier_T = std::uint32_t>
|
||||
class SparseSet: public TypeErasedSparseSet<Identifier_T>
|
||||
{
|
||||
public:
|
||||
using Dense_T = std::pair<Identifier_T, Value_T>;
|
||||
|
||||
static constexpr auto max_capacity = size_t { 1'000'000 };
|
||||
static constexpr auto max_capacity = std::size_t { 1'000'000 };
|
||||
|
||||
static constexpr auto null_identifier = std::numeric_limits<Identifier_T>().max();
|
||||
|
||||
explicit SparseSet(size_t initial_capacity = 1)
|
||||
explicit SparseSet(std::size_t initial_capacity = 1)
|
||||
{
|
||||
ensure(
|
||||
debug::ensure(
|
||||
initial_capacity <= max_capacity,
|
||||
"Failed to create SparseSet: capacity too large ({} > {})",
|
||||
initial_capacity,
|
||||
|
|
@ -51,18 +51,24 @@ public:
|
|||
|
||||
auto insert(Identifier_T identifier, Value_T value) -> Dense_T &
|
||||
{
|
||||
ensure(identifier < max_capacity, "SparseSet::insert: identifier < max_capacity");
|
||||
|
||||
if (m_sparse.size() < identifier + 1)
|
||||
{
|
||||
auto new_capacity = std::max(static_cast<size_t>(identifier + 1), m_sparse.size() * 2);
|
||||
auto new_capacity = std::max(
|
||||
static_cast<std::size_t>(identifier + 1),
|
||||
m_sparse.size() * 2
|
||||
);
|
||||
new_capacity = std::min(new_capacity, max_capacity);
|
||||
|
||||
// log::debug("Increasing sparse vector size:", m_dead_count);
|
||||
// log::debug("\tdead_count: {}", m_dead_count);
|
||||
// log::debug("\talive_count: {}", m_alive_count);
|
||||
// log::debug("\tsparse.size: {} -> {}", m_sparse.size(), new_capacity);
|
||||
|
||||
m_sparse.resize(new_capacity, null_identifier);
|
||||
}
|
||||
|
||||
++m_alive_count;
|
||||
m_sparse[identifier] = static_cast<Identifier_T>(m_dense.size());
|
||||
m_sparse[identifier] = m_dense.size();
|
||||
return m_dense.emplace_back(identifier, std::move(value));
|
||||
}
|
||||
|
||||
|
|
@ -72,27 +78,7 @@ public:
|
|||
*/
|
||||
void remove(Identifier_T identifier) override
|
||||
{
|
||||
ensure(
|
||||
identifier < m_sparse.size(),
|
||||
"Failed to ensure: identifier < m_sparse.size() [{} < {}]",
|
||||
identifier,
|
||||
m_sparse.size()
|
||||
);
|
||||
|
||||
auto &idx = m_sparse[identifier];
|
||||
ensure(
|
||||
idx != null_identifier,
|
||||
"Failed to ensure: idx != null_identifier [{} != {}]",
|
||||
idx,
|
||||
null_identifier
|
||||
);
|
||||
ensure(
|
||||
idx < m_dense.size(),
|
||||
"Failed to ensure: idx < m_dense.size() [{} < {}]",
|
||||
idx,
|
||||
m_dense.size()
|
||||
);
|
||||
|
||||
auto &[entity, component] = m_dense[idx];
|
||||
|
||||
auto &[last_entity, last_component] = m_dense.back();
|
||||
|
|
@ -163,12 +149,12 @@ public:
|
|||
return std::forward<Self_T>(self).m_dense[std::forward<Self_T>(self).m_sparse[identifier]];
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_size() const noexcept -> size_t
|
||||
[[nodiscard]] auto get_size() const noexcept -> std::size_t
|
||||
{
|
||||
return m_alive_count;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_capacity() const noexcept -> size_t
|
||||
[[nodiscard]] auto get_capacity() const noexcept -> std::size_t
|
||||
{
|
||||
return m_sparse.capacity();
|
||||
}
|
||||
|
|
@ -183,9 +169,9 @@ private:
|
|||
|
||||
std::vector<Identifier_T> m_sparse;
|
||||
|
||||
size_t m_alive_count {};
|
||||
std::size_t m_alive_count {};
|
||||
|
||||
size_t m_dead_count {};
|
||||
std::size_t m_dead_count {};
|
||||
};
|
||||
|
||||
} // namespace lt::ecs
|
||||
|
|
|
|||
|
|
@ -1,26 +1,28 @@
|
|||
import test;
|
||||
import ecs.sparse_set;
|
||||
import test.test;
|
||||
import test.expects;
|
||||
import std;
|
||||
|
||||
using Value_T = i32;
|
||||
using Set = lt::ecs::SparseSet<Value_T>;
|
||||
using ::lt::test::Case;
|
||||
using ::lt::test::expect_eq;
|
||||
using ::lt::test::expect_false;
|
||||
using ::lt::test::expect_ne;
|
||||
using ::lt::test::expect_throw;
|
||||
using ::lt::test::expect_true;
|
||||
using ::lt::test::Suite;
|
||||
using ::lt::test::operator""_suite;
|
||||
|
||||
using Set = lt::ecs::SparseSet<int>;
|
||||
constexpr auto capacity = 100;
|
||||
|
||||
Suite raii = "raii"_suite = [] {
|
||||
Case { "happy paths" } = [] {
|
||||
ignore = Set {};
|
||||
ignore = Set { Set::max_capacity };
|
||||
Case { "happy path won't throw" } = [] {
|
||||
std::ignore = Set {};
|
||||
std::ignore = Set { Set::max_capacity };
|
||||
};
|
||||
|
||||
Case { "unhappy paths" } = [] {
|
||||
expect_throw([] { ignore = Set { Set::max_capacity + 1 }; });
|
||||
};
|
||||
|
||||
Case { "many" } = [] {
|
||||
for (auto idx : std::views::iota(0, 1'000))
|
||||
{
|
||||
ignore = Set { static_cast<size_t>(idx) };
|
||||
}
|
||||
Case { "unhappy path throws" } = [] {
|
||||
expect_throw([] { std::ignore = Set { Set::max_capacity + 1 }; });
|
||||
};
|
||||
|
||||
Case { "post construct has correct state" } = [&] {
|
||||
|
|
@ -31,29 +33,7 @@ Suite raii = "raii"_suite = [] {
|
|||
};
|
||||
|
||||
Suite element_raii = "element_raii"_suite = [] {
|
||||
Case { "happy paths" } = [] {
|
||||
auto set = Set { capacity };
|
||||
set.insert(0, {});
|
||||
set.remove(0);
|
||||
};
|
||||
|
||||
Case { "unhappy paths" } = [] {
|
||||
expect_throw([] {
|
||||
auto set = Set { capacity };
|
||||
set.insert(Set::max_capacity + 1, {});
|
||||
});
|
||||
|
||||
expect_throw([] {
|
||||
auto set = Set { capacity };
|
||||
set.insert(0, {});
|
||||
set.insert(1, {});
|
||||
set.insert(2, {});
|
||||
|
||||
set.remove(3);
|
||||
});
|
||||
};
|
||||
|
||||
Case { "many" } = [] {
|
||||
Case { "many inserts/removes won't freeze/throw" } = [] {
|
||||
auto set = Set {};
|
||||
for (auto idx : std::views::iota(0, 10'000))
|
||||
{
|
||||
|
|
@ -97,7 +77,7 @@ Suite element_raii = "element_raii"_suite = [] {
|
|||
|
||||
expect_eq(set.get_size(), 10'000 - (idx + 1));
|
||||
expect_false(set.contains(idx));
|
||||
expect_throw([&] { ignore = set.at(idx); });
|
||||
expect_throw([&] { std::ignore = set.at(idx); });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -117,7 +97,7 @@ Suite element_raii = "element_raii"_suite = [] {
|
|||
|
||||
for (auto &[identifier, value] : set)
|
||||
{
|
||||
expect_eq(static_cast<Value_T>(identifier), value);
|
||||
expect_eq(identifier, value);
|
||||
expect_ne(value, 0);
|
||||
expect_ne(value, 32);
|
||||
expect_ne(value, 69);
|
||||
|
|
@ -149,7 +129,7 @@ Suite getters = "getters"_suite = [] {
|
|||
|
||||
expect_eq(set.get_capacity(), 10'000);
|
||||
|
||||
set.insert(static_cast<Value_T>(set.get_size()), {});
|
||||
set.insert(set.get_size(), {});
|
||||
expect_ne(set.get_capacity(), 10'000);
|
||||
};
|
||||
|
||||
|
|
@ -160,12 +140,12 @@ Suite getters = "getters"_suite = [] {
|
|||
{
|
||||
expect_throw([&] {
|
||||
set.insert(idx, {});
|
||||
ignore = set.at(50);
|
||||
std::ignore = set.at(50);
|
||||
});
|
||||
}
|
||||
|
||||
set.insert(50, {});
|
||||
ignore = set.at(50); // should not throw
|
||||
std::ignore = set.at(50); // should not throw
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -179,10 +159,5 @@ Suite clear = "clear"_suite = [] {
|
|||
|
||||
set.clear();
|
||||
expect_eq(set.get_size(), 0);
|
||||
|
||||
for (auto idx : std::views::iota(0, 10'000))
|
||||
{
|
||||
expect_throw([&] { ignore = set.at(idx); });
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
export module preliminary.build_constants;
|
||||
export module env;
|
||||
|
||||
import preliminary.fundumental_types;
|
||||
import std;
|
||||
|
||||
export namespace build_constants {
|
||||
namespace lt {
|
||||
|
||||
enum class Platform : u8
|
||||
enum class Platform : std::uint8_t
|
||||
{
|
||||
/** The GNU/Linux platform.
|
||||
* Tested on the following distros: arch-x86_64
|
||||
|
|
@ -27,7 +26,7 @@ enum class Platform : u8
|
|||
};
|
||||
|
||||
/** The compiler that was used for compiling the project. */
|
||||
enum class Compiler : u8
|
||||
enum class Compiler : std::uint8_t
|
||||
{
|
||||
clang,
|
||||
gcc,
|
||||
|
|
@ -35,51 +34,37 @@ enum class Compiler : u8
|
|||
apple_clang,
|
||||
};
|
||||
|
||||
enum class BuildType
|
||||
{
|
||||
debug,
|
||||
release,
|
||||
distribution
|
||||
};
|
||||
namespace constants {
|
||||
|
||||
#if defined(LIGHT_PLATFORM_WINDOWS)
|
||||
#define lt_win(x)
|
||||
constexpr auto platform = Platform::windows;
|
||||
constexpr auto platform_name = "windows";
|
||||
constexpr auto platform_identifier = platform_name; // TODO(Light)
|
||||
|
||||
#undef LIGHT_PLATFORM_WINDOWS
|
||||
|
||||
#elif defined(LIGHT_PLATFORM_LINUX)
|
||||
constexpr auto platform = Platform::gnu_linux;
|
||||
constexpr auto platform_name = "gnu_linux";
|
||||
constexpr auto platform_identifier = platform_name; // TODO(Light)
|
||||
|
||||
#elif defined(LIGHT_PLATFORM_MAC)
|
||||
#define lt_mac(x) x
|
||||
constexpr auto platform = Platform::mac;
|
||||
constexpr auto platform_name = "mac";
|
||||
constexpr auto platform_identifier = platform_name; // TODO(Light)
|
||||
|
||||
#else
|
||||
#error "Unsupported platform: Unknown"
|
||||
|
||||
#endif
|
||||
|
||||
/** @TODO(Light): Handle other compilers... */
|
||||
#ifdef __clang__
|
||||
constexpr auto compiler = Compiler::clang;
|
||||
constexpr auto compiler_name = "clang";
|
||||
|
||||
/** @TODO(Light): insert the full identifier, including version information and such */
|
||||
constexpr auto compiler_identifier = "clang";
|
||||
/** @todo(Light): insert the full identifier, including version information and such */
|
||||
constexpr auto full_compiler_identifier = "clang";
|
||||
#endif
|
||||
|
||||
// @TODO(Light): inject build info through CMake using LIGHT_... constant macros
|
||||
#if defined(_DEBUG)
|
||||
constexpr auto build_type = BuildType::debug;
|
||||
#else
|
||||
constexpr auto build_type = BuildType::release;
|
||||
#endif
|
||||
} // namespace constants
|
||||
|
||||
} // namespace build_constants
|
||||
} // namespace lt
|
||||
|
|
@ -1,18 +1,17 @@
|
|||
export module input.system:components;
|
||||
|
||||
import preliminary;
|
||||
import input.codes;
|
||||
import std;
|
||||
|
||||
export namespace lt::input {
|
||||
namespace lt::input {
|
||||
|
||||
struct Trigger
|
||||
export struct Trigger
|
||||
{
|
||||
Key mapped_keycode;
|
||||
};
|
||||
|
||||
struct InputAction
|
||||
export struct InputAction
|
||||
{
|
||||
enum class State : u8
|
||||
enum class State : std::uint8_t
|
||||
{
|
||||
inactive,
|
||||
active,
|
||||
|
|
@ -27,18 +26,18 @@ struct InputAction
|
|||
Trigger trigger;
|
||||
};
|
||||
|
||||
class InputComponent
|
||||
export class InputComponent
|
||||
{
|
||||
public:
|
||||
InputComponent() = default;
|
||||
|
||||
auto add_action(InputAction action) -> size_t
|
||||
auto add_action(InputAction action) -> std::size_t
|
||||
{
|
||||
m_actions.emplace_back(std::move(action));
|
||||
return m_actions.size() - 1;
|
||||
}
|
||||
|
||||
auto get_action(size_t idx) -> const InputAction &
|
||||
auto get_action(std::size_t idx) -> const InputAction &
|
||||
{
|
||||
return m_actions[idx];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ namespace lt::input {
|
|||
export class AnalogEvent
|
||||
{
|
||||
public:
|
||||
AnalogEvent(Key key, math::vec2_u32 pointer_position)
|
||||
AnalogEvent(Key key, math::uvec2 pointer_position)
|
||||
: m_key(key)
|
||||
, m_pointer_position(pointer_position)
|
||||
{
|
||||
|
|
@ -21,7 +21,7 @@ public:
|
|||
return m_key;
|
||||
};
|
||||
|
||||
[[nodiscard]] auto get_pointer_position() const -> math::vec2_u32
|
||||
[[nodiscard]] auto get_pointer_position() const -> math::uvec2
|
||||
{
|
||||
return m_pointer_position;
|
||||
}
|
||||
|
|
@ -35,7 +35,7 @@ public:
|
|||
private:
|
||||
Key m_key;
|
||||
|
||||
math::vec2_u32 m_pointer_position;
|
||||
math::uvec2 m_pointer_position;
|
||||
};
|
||||
|
||||
} // namespace lt::input
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ export module input.system;
|
|||
export import :components;
|
||||
import logger;
|
||||
import app.system;
|
||||
import debug.assertions;
|
||||
import ecs.registry;
|
||||
import memory.reference;
|
||||
import surface.system;
|
||||
|
|
@ -68,7 +69,7 @@ struct overloads: Ts...
|
|||
|
||||
System::System(memory::Ref<ecs::Registry> registry): m_registry(std::move(registry))
|
||||
{
|
||||
ensure(m_registry, "Failed to initialize input system: null registry");
|
||||
debug::ensure(m_registry, "Failed to initialize input system: null registry");
|
||||
}
|
||||
|
||||
void System::tick(app::TickInfo tick)
|
||||
|
|
@ -157,10 +158,8 @@ void System::on_key_press(const lt::surface::KeyPressedEvent &event)
|
|||
{
|
||||
if (std::to_underlying(event.get_key()) > m_keys.size())
|
||||
{
|
||||
log::warn(
|
||||
"Key code larger than key container size, implement platform-dependant "
|
||||
"key-code-mapping!"
|
||||
);
|
||||
log::debug("Key code larger than key container size, implement platform-dependant "
|
||||
"key-code-mapping!");
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
@ -172,10 +171,8 @@ void System::on_key_release(const lt::surface::KeyReleasedEvent &event)
|
|||
{
|
||||
if (std::to_underlying(event.get_key()) > m_keys.size())
|
||||
{
|
||||
log::warn(
|
||||
"Key code larger than key container size, implement platform-dependant "
|
||||
"key-code-mapping!"
|
||||
);
|
||||
log::debug("Key code larger than key container size, implement platform-dependant "
|
||||
"key-code-mapping!");
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import test;
|
||||
import std;
|
||||
import input.system;
|
||||
import input.codes;
|
||||
import std;
|
||||
import test.test;
|
||||
import test.expects;
|
||||
import surface.events;
|
||||
import memory.scope;
|
||||
import memory.reference;
|
||||
|
|
@ -9,10 +12,23 @@ import ecs.entity;
|
|||
import ecs.registry;
|
||||
import surface.system;
|
||||
|
||||
using ::lt::input::InputComponent;
|
||||
using ::lt::input::System;
|
||||
|
||||
[[nodiscard]] auto tick_info() -> lt::app::TickInfo
|
||||
// NOLINTBEGIN
|
||||
using namespace lt;
|
||||
using input::InputComponent;
|
||||
using input::System;
|
||||
using std::ignore;
|
||||
using test::Case;
|
||||
using test::expect_eq;
|
||||
using test::expect_false;
|
||||
using test::expect_ne;
|
||||
using test::expect_not_nullptr;
|
||||
using test::operator""_suite;
|
||||
using test::expect_throw;
|
||||
using test::Suite;
|
||||
// NOLINTEND
|
||||
|
||||
[[nodiscard]] auto tick_info() -> app::TickInfo
|
||||
{
|
||||
return {
|
||||
.delta_time = std::chrono::milliseconds { 16 },
|
||||
|
|
@ -24,12 +40,12 @@ using ::lt::input::System;
|
|||
class Fixture
|
||||
{
|
||||
public:
|
||||
[[nodiscard]] auto registry() -> lt::memory::Ref<lt::ecs::Registry>
|
||||
[[nodiscard]] auto registry() -> memory::Ref<ecs::Registry>
|
||||
{
|
||||
return m_registry;
|
||||
}
|
||||
|
||||
auto add_input_component() -> lt::ecs::EntityId
|
||||
auto add_input_component() -> ecs::EntityId
|
||||
{
|
||||
auto entity = m_registry->create_entity();
|
||||
m_registry->add<InputComponent>(entity, {});
|
||||
|
|
@ -37,7 +53,7 @@ public:
|
|||
return entity;
|
||||
}
|
||||
|
||||
auto add_surface_component() -> lt::ecs::EntityId
|
||||
auto add_surface_component() -> ecs::EntityId
|
||||
{
|
||||
auto entity = m_registry->create_entity();
|
||||
m_surface_system.create_surface_component(
|
||||
|
|
@ -49,28 +65,27 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
lt::memory::Ref<lt::ecs::Registry> m_registry = lt::memory::create_ref<lt::ecs::Registry>();
|
||||
memory::Ref<ecs::Registry> m_registry = memory::create_ref<ecs::Registry>();
|
||||
|
||||
lt::surface::System m_surface_system = lt::surface::System { m_registry };
|
||||
surface::System m_surface_system = surface::System { m_registry };
|
||||
};
|
||||
|
||||
Suite raii = "raii"_suite = "raii"_suite = [] {
|
||||
Case { "happy paths" } = [&] {
|
||||
Case { "happy path won't throw" } = [&] {
|
||||
System { Fixture {}.registry() };
|
||||
};
|
||||
|
||||
Case { "unhappy paths" } = [] {
|
||||
expect_throw([] { ignore = System { {} }; });
|
||||
};
|
||||
|
||||
Case { "many" } = [&] {
|
||||
Case { "many won't freeze/throw" } = [&] {
|
||||
auto fixture = Fixture {};
|
||||
for (auto idx : std::views::iota(0, 10'000))
|
||||
{
|
||||
ignore = idx;
|
||||
ignore = System { fixture.registry() };
|
||||
}
|
||||
};
|
||||
|
||||
Case { "unhappy path throws" } = [] {
|
||||
expect_throw([] { ignore = System { {} }; });
|
||||
};
|
||||
};
|
||||
|
||||
Suite system_events = "system_events"_suite = [] {
|
||||
|
|
@ -100,14 +115,14 @@ Suite registry_events = "registry_events"_suite = [] {
|
|||
auto registry = fixture.registry();
|
||||
auto system = System { registry };
|
||||
|
||||
fixture.add_input_component();
|
||||
const auto &entity = fixture.add_input_component();
|
||||
expect_eq(registry->view<InputComponent>().get_size(), 1);
|
||||
};
|
||||
|
||||
Case { "on_destrroy<InputComponent>" } = [] {
|
||||
auto fixture = Fixture {};
|
||||
auto registry = fixture.registry();
|
||||
auto system = lt::memory::create_scope<System>(registry);
|
||||
auto system = memory::create_scope<System>(registry);
|
||||
|
||||
auto entity_a = fixture.add_input_component();
|
||||
auto entity_b = fixture.add_input_component();
|
||||
|
|
@ -139,7 +154,7 @@ Suite tick = "tick"_suite = [] {
|
|||
auto system = System { fixture.registry() };
|
||||
|
||||
auto surface_entity = fixture.add_surface_component();
|
||||
auto &surface = registry->get<lt::surface::SurfaceComponent>(surface_entity);
|
||||
auto &surface = registry->get<surface::SurfaceComponent>(surface_entity);
|
||||
|
||||
auto input_entity = fixture.add_input_component();
|
||||
auto &input = registry->get<InputComponent>(input_entity);
|
||||
|
|
@ -147,29 +162,48 @@ Suite tick = "tick"_suite = [] {
|
|||
auto action_key = input.add_action(
|
||||
{
|
||||
.name { "test" },
|
||||
.trigger = { .mapped_keycode = Key::a },
|
||||
.trigger = { .mapped_keycode = Key::A },
|
||||
}
|
||||
);
|
||||
|
||||
using enum ::lt::input::InputAction::State;
|
||||
expect_eq(input.get_action(action_key).state, inactive);
|
||||
expect_eq(input.get_action(action_key).state, input::InputAction::State::inactive);
|
||||
system.tick(tick_info());
|
||||
expect_eq(input.get_action(action_key).state, inactive);
|
||||
expect_eq(input.get_action(action_key).state, input::InputAction::State::inactive);
|
||||
|
||||
surface.push_event(lt::surface::KeyPressedEvent(Key::a));
|
||||
surface.push_event(surface::KeyPressedEvent(Key::A));
|
||||
system.tick(tick_info());
|
||||
expect_eq(input.get_action(action_key).state, triggered);
|
||||
expect_eq(input.get_action(action_key).state, input::InputAction::State::triggered);
|
||||
|
||||
system.tick(tick_info());
|
||||
expect_eq(input.get_action(action_key).state, active);
|
||||
expect_eq(input.get_action(action_key).state, input::InputAction::State::active);
|
||||
|
||||
system.tick(tick_info());
|
||||
system.tick(tick_info());
|
||||
system.tick(tick_info());
|
||||
expect_eq(input.get_action(action_key).state, active);
|
||||
expect_eq(input.get_action(action_key).state, input::InputAction::State::active);
|
||||
|
||||
surface.push_event(lt::surface::KeyReleasedEvent(Key::a));
|
||||
surface.push_event(surface::KeyReleasedEvent(Key::A));
|
||||
system.tick(tick_info());
|
||||
expect_eq(input.get_action(action_key).state, inactive);
|
||||
expect_eq(input.get_action(action_key).state, input::InputAction::State::inactive);
|
||||
};
|
||||
|
||||
Case { "Tick triggers" } = [] {
|
||||
auto fixture = Fixture {};
|
||||
auto registry = fixture.registry();
|
||||
auto system = System { fixture.registry() };
|
||||
|
||||
auto surface_entity = fixture.add_surface_component();
|
||||
auto &surface = registry->get<surface::SurfaceComponent>(surface_entity);
|
||||
|
||||
auto input_entity = fixture.add_input_component();
|
||||
auto &input = registry->get<InputComponent>(input_entity);
|
||||
|
||||
|
||||
auto action_key = input.add_action(
|
||||
{
|
||||
.name { "test" },
|
||||
.trigger = { .mapped_keycode = Key::A },
|
||||
}
|
||||
);
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,292 +1,173 @@
|
|||
/**
|
||||
* @note: The reason this is a separate module, rather than being in the `Input` module is that
|
||||
* the input is received from the hardware through the `Surface` module, and it is further parsed
|
||||
* inside the `Input` module, USING the `Surface` module's events.
|
||||
*
|
||||
* Hence, both `Surface` and `Input` needs to agree to the same input codes, while `Input` depends
|
||||
* on `Surface`. The simplest solution is to keep the codes in a 3rd module and make both depend on
|
||||
* it. (I did not want to give `Surface` the responsibility of defining input codes...)
|
||||
*/
|
||||
export module input.codes;
|
||||
import std;
|
||||
|
||||
import preliminary;
|
||||
export enum class Key: std::uint16_t {
|
||||
/* digits */
|
||||
D0 = 48,
|
||||
D1 = 49,
|
||||
D2 = 50,
|
||||
D3 = 51,
|
||||
D4 = 52,
|
||||
D5 = 53,
|
||||
D6 = 54,
|
||||
D7 = 55,
|
||||
D8 = 56,
|
||||
D9 = 57,
|
||||
Semicolon = 59, // ;
|
||||
Equal = 61, // =
|
||||
|
||||
export enum class Key: u16 {
|
||||
none = 0,
|
||||
/* letters */
|
||||
A = 65,
|
||||
B = 66,
|
||||
C = 67,
|
||||
D = 68,
|
||||
E = 69,
|
||||
F = 70,
|
||||
G = 71,
|
||||
H = 72,
|
||||
I = 73,
|
||||
J = 74,
|
||||
K = 75,
|
||||
L = 76,
|
||||
M = 77,
|
||||
N = 78,
|
||||
O = 79,
|
||||
P = 80,
|
||||
Q = 81,
|
||||
R = 82,
|
||||
S = 83,
|
||||
t = 84,
|
||||
U = 85,
|
||||
V = 86,
|
||||
W = 87,
|
||||
X = 88,
|
||||
Y = 89,
|
||||
Z = 90,
|
||||
|
||||
left_button,
|
||||
l_button = left_button,
|
||||
/* brackets */
|
||||
LeftBracket = 91, // [
|
||||
LBracket = LeftBracket, // [
|
||||
RightBracket = 93, // ]
|
||||
RBracket = RightBracket, // ]
|
||||
|
||||
right_button,
|
||||
r_button = right_button,
|
||||
/* arrow */
|
||||
Right = 262,
|
||||
RightArrow = Right,
|
||||
RArrow = Right,
|
||||
Left = 263,
|
||||
LeftArrow = Left,
|
||||
LArrow = Left,
|
||||
Down = 264,
|
||||
DownArrow = Down,
|
||||
DArrow = Down,
|
||||
Up = 265,
|
||||
UpArrow = Up,
|
||||
UArrow = Up,
|
||||
|
||||
middle_button,
|
||||
m_button = middle_button,
|
||||
/* page */
|
||||
PageUp = 266,
|
||||
PageDown = 267,
|
||||
|
||||
// the buttons on the sidse of some mouses
|
||||
x_button_1,
|
||||
x_button_2,
|
||||
/* home/end */
|
||||
Home = 268,
|
||||
end = 269,
|
||||
|
||||
backspace,
|
||||
tab,
|
||||
capslock,
|
||||
enter,
|
||||
space,
|
||||
delete_,
|
||||
/* toggles */
|
||||
CapsLock = 280,
|
||||
ScrollLock = 281,
|
||||
NumLock = 282,
|
||||
NumberLock = NumLock,
|
||||
|
||||
shift,
|
||||
left_shit = shift,
|
||||
l_shift = shift,
|
||||
/* function */
|
||||
F1 = 290,
|
||||
F2 = 291,
|
||||
F3 = 292,
|
||||
F4 = 293,
|
||||
F5 = 294,
|
||||
F6 = 295,
|
||||
F7 = 296,
|
||||
F8 = 297,
|
||||
F9 = 298,
|
||||
F10 = 299,
|
||||
F11 = 300,
|
||||
F12 = 301,
|
||||
F13 = 302,
|
||||
F14 = 303,
|
||||
F15 = 304,
|
||||
F16 = 305,
|
||||
F17 = 306,
|
||||
F18 = 307,
|
||||
F19 = 308,
|
||||
F20 = 309,
|
||||
F21 = 310,
|
||||
F22 = 311,
|
||||
F23 = 312,
|
||||
F24 = 313,
|
||||
F25 = 314,
|
||||
|
||||
right_shift,
|
||||
r_shift = right_shift,
|
||||
/* keypad */
|
||||
Kp0 = 320,
|
||||
Kp1 = 321,
|
||||
Kp2 = 322,
|
||||
Kp3 = 323,
|
||||
Kp4 = 324,
|
||||
Kp5 = 325,
|
||||
Kp6 = 326,
|
||||
Kp7 = 327,
|
||||
Kp8 = 328,
|
||||
Kp9 = 329,
|
||||
KpDecimal = 330,
|
||||
KpDivide = 331,
|
||||
KpMultiply = 332,
|
||||
KpSubstract = 333,
|
||||
KpAdd = 334,
|
||||
KpEnter = 335,
|
||||
KpEqual = 336,
|
||||
|
||||
control,
|
||||
left_control = control,
|
||||
l_control = control,
|
||||
ctrl = control,
|
||||
left_ctrl = control,
|
||||
l_ctrl = control,
|
||||
/* modifiers */
|
||||
LeftShift = 340,
|
||||
LShift = LeftShift,
|
||||
LeftControl = 341,
|
||||
LControl = LeftControl,
|
||||
LeftAlt = 342,
|
||||
LAlt = LeftAlt,
|
||||
LeftSuper = 343,
|
||||
LSuper = LeftSuper,
|
||||
RightShift = 344,
|
||||
RShift = 344,
|
||||
RightControl = 345,
|
||||
RControl = 345,
|
||||
RightAlt = 346,
|
||||
RAlt = 346,
|
||||
RightSuper = 347,
|
||||
RSuper = 347,
|
||||
|
||||
right_control,
|
||||
r_control = right_control,
|
||||
right_ctrl = right_control,
|
||||
r_ctrl = right_control,
|
||||
/* misc */
|
||||
Space = 32,
|
||||
Apostrophe = 39, // '
|
||||
Quote = Apostrophe,
|
||||
|
||||
alt,
|
||||
left_alt = alt,
|
||||
l_alt = alt,
|
||||
Comma = 44, // ,
|
||||
Minus = 45, // -
|
||||
Period = 46, // .
|
||||
Slash = 47, // /
|
||||
ForwardSlash = Slash, // /
|
||||
BackSlash = 92, // \
|
||||
|
||||
right_alt,
|
||||
r_alt = right_alt,
|
||||
GraveAccent = 96, // `
|
||||
Console = GraveAccent,
|
||||
World1 = 161, // non-US #1
|
||||
World2 = 162, // non-US #2
|
||||
Escape = 256,
|
||||
Esc = Escape,
|
||||
Enter = 257,
|
||||
Tab = 258,
|
||||
BackSpace = 259,
|
||||
Insert = 260,
|
||||
Delete = 261,
|
||||
|
||||
pageup,
|
||||
pagedown,
|
||||
home,
|
||||
end,
|
||||
PrintScreen = 283,
|
||||
Pause = 284,
|
||||
|
||||
left_arrow,
|
||||
l_arrow = left_arrow,
|
||||
|
||||
up_arrow,
|
||||
u_arrow = up_arrow,
|
||||
|
||||
right_arrow,
|
||||
r_arrow = right_arrow,
|
||||
|
||||
down_arrow,
|
||||
d_arrow = down_arrow,
|
||||
|
||||
cancel,
|
||||
pause,
|
||||
select,
|
||||
print,
|
||||
snapshot, // aka. print-screen
|
||||
insert,
|
||||
help,
|
||||
sleep,
|
||||
eep = sleep,
|
||||
|
||||
digit_0,
|
||||
digit_1,
|
||||
digit_2,
|
||||
digit_3,
|
||||
digit_4,
|
||||
digit_5,
|
||||
digit_6,
|
||||
digit_7,
|
||||
digit_8,
|
||||
digit_9,
|
||||
|
||||
a,
|
||||
b,
|
||||
c,
|
||||
d,
|
||||
e,
|
||||
f,
|
||||
g,
|
||||
h,
|
||||
i,
|
||||
j,
|
||||
k,
|
||||
l,
|
||||
m,
|
||||
n,
|
||||
o,
|
||||
p,
|
||||
q,
|
||||
r,
|
||||
s,
|
||||
t,
|
||||
u,
|
||||
v,
|
||||
w,
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
|
||||
super,
|
||||
left_super = super,
|
||||
l_super = super,
|
||||
|
||||
right_super,
|
||||
r_super = right_super,
|
||||
|
||||
kp_0,
|
||||
kp_1,
|
||||
kp_2,
|
||||
kp_3,
|
||||
kp_4,
|
||||
kp_5,
|
||||
kp_6,
|
||||
kp_7,
|
||||
kp_8,
|
||||
kp_9,
|
||||
kp_decimal,
|
||||
kp_divide,
|
||||
kp_multiply,
|
||||
kp_subtract,
|
||||
kp_add,
|
||||
kp_enter,
|
||||
kp_equal,
|
||||
|
||||
f1,
|
||||
f2,
|
||||
f3,
|
||||
f4,
|
||||
f5,
|
||||
f6,
|
||||
f7,
|
||||
f8,
|
||||
f9,
|
||||
f10,
|
||||
f11,
|
||||
f12,
|
||||
|
||||
/** Input was received but was none of the above. */
|
||||
unknown,
|
||||
Menu = 348,
|
||||
};
|
||||
|
||||
export [[nodiscard]] constexpr auto to_string(Key key) -> std::string
|
||||
{
|
||||
using enum Key;
|
||||
switch (key)
|
||||
{
|
||||
case none: return "<none>";
|
||||
|
||||
case left_button: return "left_button";
|
||||
case right_button: return "right_button";
|
||||
case middle_button: return "middle_button";
|
||||
|
||||
case x_button_1: return "x_button_1";
|
||||
case x_button_2: return "x_button_2";
|
||||
|
||||
case backspace: return "backspace";
|
||||
case tab: return "tab";
|
||||
case capslock: return "capslock";
|
||||
case enter: return "enter";
|
||||
case space: return "space";
|
||||
case delete_: return "delete";
|
||||
|
||||
case shift: return "shift";
|
||||
case control: return "control";
|
||||
case right_control: return "right_control";
|
||||
case alt: return "alt";
|
||||
case right_alt: return "right_alt";
|
||||
|
||||
case pageup: return "pageup";
|
||||
case pagedown: return "pagedown";
|
||||
case home: return "home";
|
||||
case end: return "end";
|
||||
|
||||
case left_arrow: return "left_arrow";
|
||||
case up_arrow: return "up_arrow";
|
||||
case right_arrow: return "right_arrow";
|
||||
case down_arrow: return "down_arrow";
|
||||
|
||||
case cancel: return "cancel";
|
||||
case pause: return "pause";
|
||||
case select: return "select";
|
||||
case print: return "print";
|
||||
case snapshot: return "snapshot";
|
||||
case insert: return "insert";
|
||||
case help: return "help";
|
||||
case sleep: return "sleep";
|
||||
|
||||
case digit_0: return "0";
|
||||
case digit_1: return "1";
|
||||
case digit_2: return "2";
|
||||
case digit_3: return "3";
|
||||
case digit_4: return "4";
|
||||
case digit_5: return "5";
|
||||
case digit_6: return "6";
|
||||
case digit_7: return "7";
|
||||
case digit_8: return "8";
|
||||
case digit_9: return "9";
|
||||
|
||||
case a: return "a";
|
||||
case b: return "b";
|
||||
case c: return "c";
|
||||
case d: return "d";
|
||||
case e: return "e";
|
||||
case f: return "f";
|
||||
case g: return "g";
|
||||
case h: return "h";
|
||||
case i: return "i";
|
||||
case j: return "j";
|
||||
case k: return "k";
|
||||
case l: return "l";
|
||||
case m: return "m";
|
||||
case n: return "n";
|
||||
case o: return "o";
|
||||
case p: return "p";
|
||||
case q: return "q";
|
||||
case r: return "r";
|
||||
case s: return "s";
|
||||
case t: return "t";
|
||||
case u: return "u";
|
||||
case v: return "v";
|
||||
case w: return "w";
|
||||
case x: return "x";
|
||||
case y: return "y";
|
||||
case z: return "z";
|
||||
|
||||
case super: return "super";
|
||||
case right_super: return "right_super";
|
||||
|
||||
case kp_0: return "kp_0";
|
||||
case kp_1: return "kp_1";
|
||||
case kp_2: return "kp_2";
|
||||
case kp_3: return "kp_3";
|
||||
case kp_4: return "kp_4";
|
||||
case kp_5: return "kp_5";
|
||||
case kp_6: return "kp_6";
|
||||
case kp_7: return "kp_7";
|
||||
case kp_8: return "kp_8";
|
||||
case kp_9: return "kp_9";
|
||||
case kp_decimal: return "kp_decimal";
|
||||
case kp_divide: return "kp_divide";
|
||||
case kp_multiply: return "kp_multiply";
|
||||
case kp_subtract: return "kp_subtract";
|
||||
case kp_add: return "kp_add";
|
||||
case kp_enter: return "kp_enter";
|
||||
case kp_equal: return "kp_equal";
|
||||
|
||||
case f1: return "f1";
|
||||
case f2: return "f2";
|
||||
case f3: return "f3";
|
||||
case f4: return "f4";
|
||||
case f5: return "f5";
|
||||
case f6: return "f6";
|
||||
case f7: return "f7";
|
||||
case f8: return "f8";
|
||||
case f9: return "f9";
|
||||
case f10: return "f10";
|
||||
case f11: return "f11";
|
||||
case f12: return "f12";
|
||||
|
||||
case unknown: return "<unknown>";
|
||||
}
|
||||
|
||||
return "<invalid>";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
export module logger;
|
||||
|
||||
import preliminary;
|
||||
import std;
|
||||
|
||||
export namespace lt::log {
|
||||
namespace lt::log {
|
||||
|
||||
/** Severity of a log message. */
|
||||
enum class Level : u8
|
||||
enum class Level : std::uint8_t
|
||||
{
|
||||
/** Lowest and most vebose log level, for tracing execution paths and events */
|
||||
trace = 0,
|
||||
|
|
@ -25,25 +25,19 @@ enum class Level : u8
|
|||
/** Unrecoverable errors */
|
||||
critical = 5,
|
||||
|
||||
/**
|
||||
* Logs from the testing-framework.
|
||||
* Highest so we still get them while turning off all logs from the code under test.
|
||||
*
|
||||
* @note: log::test does NOT include source_location
|
||||
*/
|
||||
test = 6,
|
||||
|
||||
/** No logging */
|
||||
off = 7,
|
||||
off = 6,
|
||||
};
|
||||
|
||||
auto min_severity = Level::trace;
|
||||
namespace details {
|
||||
|
||||
auto set_min_severity(Level severity)
|
||||
inline auto thread_hash_id() noexcept -> std::uint64_t
|
||||
{
|
||||
min_severity = severity;
|
||||
return static_cast<std::uint64_t>(std::hash<std::thread::id> {}(std::this_thread::get_id()));
|
||||
}
|
||||
|
||||
} // namespace details
|
||||
|
||||
template<typename... Args>
|
||||
struct [[maybe_unused]] print
|
||||
{
|
||||
|
|
@ -54,12 +48,7 @@ struct [[maybe_unused]] print
|
|||
Args &&...arguments
|
||||
) noexcept
|
||||
{
|
||||
if (std::to_underlying(level) < std::to_underlying(min_severity))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr auto to_string = [](Level level) {
|
||||
constexpr auto to_string = [](Level level, auto location) {
|
||||
// clang-format off
|
||||
switch (level)
|
||||
{
|
||||
|
|
@ -81,50 +70,18 @@ struct [[maybe_unused]] print
|
|||
|
||||
std::println(
|
||||
"{} {} ==> {}",
|
||||
to_string(level),
|
||||
to_string(level, location),
|
||||
std::format("{}:{}", path.filename().string(), location.line()),
|
||||
std::format(format, std::forward<Args>(arguments)...)
|
||||
);
|
||||
}
|
||||
|
||||
[[maybe_unused]] print(
|
||||
Level level,
|
||||
std::format_string<Args...> format,
|
||||
Args &&...arguments
|
||||
) noexcept
|
||||
{
|
||||
constexpr auto to_string = [](Level level) {
|
||||
// clang-format off
|
||||
switch (level)
|
||||
{
|
||||
using enum ::lt::log::Level;
|
||||
case trace : return "\033[1;37m| trc |\033[0m";
|
||||
case debug : return "\033[1;36m| dbg |\033[0m";
|
||||
case info : return "\033[1;32m| inf |\033[0m";
|
||||
case warn : return "\033[1;33m| wrn |\033[0m";
|
||||
case error : return "\033[1;31m| err |\033[0m";
|
||||
case critical: return "\033[1;41m| crt |\033[0m";
|
||||
case test : return "\033[1;33m| test |\033[0m";
|
||||
case off : return "";
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
std::unreachable();
|
||||
};
|
||||
|
||||
std::println(
|
||||
"{} {}",
|
||||
to_string(level),
|
||||
std::format(format, std::forward<Args>(arguments)...)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Args>
|
||||
print(Level, const std::source_location &, std::format_string<Args...>, Args &&...) noexcept
|
||||
-> print<Args...>;
|
||||
|
||||
template<typename... Args>
|
||||
export template<typename... Args>
|
||||
struct [[maybe_unused]] trace
|
||||
{
|
||||
[[maybe_unused]] trace(
|
||||
|
|
@ -137,10 +94,10 @@ struct [[maybe_unused]] trace
|
|||
}
|
||||
};
|
||||
|
||||
template<typename... Args>
|
||||
export template<typename... Args>
|
||||
trace(std::format_string<Args...>, Args &&...) noexcept -> trace<Args...>;
|
||||
|
||||
template<typename... Args>
|
||||
export template<typename... Args>
|
||||
struct [[maybe_unused]] debug
|
||||
{
|
||||
[[maybe_unused]] debug(
|
||||
|
|
@ -153,11 +110,10 @@ struct [[maybe_unused]] debug
|
|||
}
|
||||
};
|
||||
|
||||
template<typename... Args>
|
||||
export template<typename... Args>
|
||||
debug(std::format_string<Args...>, Args &&...) noexcept -> debug<Args...>;
|
||||
|
||||
|
||||
template<typename... Args>
|
||||
export template<typename... Args>
|
||||
struct [[maybe_unused]] info
|
||||
{
|
||||
[[maybe_unused]] info(
|
||||
|
|
@ -170,10 +126,10 @@ struct [[maybe_unused]] info
|
|||
}
|
||||
};
|
||||
|
||||
template<typename... Args>
|
||||
export template<typename... Args>
|
||||
info(std::format_string<Args...>, Args &&...) noexcept -> info<Args...>;
|
||||
|
||||
template<typename... Args>
|
||||
export template<typename... Args>
|
||||
struct [[maybe_unused]] warn
|
||||
{
|
||||
[[maybe_unused]] warn(
|
||||
|
|
@ -186,10 +142,10 @@ struct [[maybe_unused]] warn
|
|||
}
|
||||
};
|
||||
|
||||
template<typename... Args>
|
||||
export template<typename... Args>
|
||||
warn(std::format_string<Args...>, Args &&...) noexcept -> warn<Args...>;
|
||||
|
||||
template<typename... Args>
|
||||
export template<typename... Args>
|
||||
struct [[maybe_unused]] error
|
||||
{
|
||||
[[maybe_unused]] error(
|
||||
|
|
@ -202,10 +158,10 @@ struct [[maybe_unused]] error
|
|||
}
|
||||
};
|
||||
|
||||
template<typename... Args>
|
||||
export template<typename... Args>
|
||||
error(std::format_string<Args...>, Args &&...) noexcept -> error<Args...>;
|
||||
|
||||
template<typename... Args>
|
||||
export template<typename... Args>
|
||||
struct [[maybe_unused]] critical
|
||||
{
|
||||
[[maybe_unused]] critical(
|
||||
|
|
@ -218,13 +174,7 @@ struct [[maybe_unused]] critical
|
|||
}
|
||||
};
|
||||
|
||||
template<typename... Args>
|
||||
export template<typename... Args>
|
||||
critical(std::format_string<Args...>, Args &&...) noexcept -> critical<Args...>;
|
||||
|
||||
template<typename... Args>
|
||||
void test(std::format_string<Args...> format, Args &&...arguments) noexcept
|
||||
{
|
||||
print(Level::test, format, std::forward<Args>(arguments)...);
|
||||
}
|
||||
|
||||
} // namespace lt::log
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
import test;
|
||||
import logger;
|
||||
import test.test;
|
||||
|
||||
using ::lt::test::Case;
|
||||
using ::lt::test::Suite;
|
||||
|
||||
Suite suite = [] {
|
||||
Case { "formatless" } = [] {
|
||||
Case { "no format" } = [] {
|
||||
lt::log::trace("trace");
|
||||
lt::log::debug("debug");
|
||||
lt::log::info("info");
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
export module math.algebra;
|
||||
|
||||
import preliminary;
|
||||
import math.mat4;
|
||||
import std;
|
||||
|
||||
export namespace lt::math {
|
||||
|
||||
|
|
@ -32,12 +31,11 @@ export namespace lt::math {
|
|||
*
|
||||
* the 1 at [z][3] is to save the Z axis into the resulting W for perspective division.
|
||||
*
|
||||
* @ref Thanks to @pikuma for explaining the math behind this:
|
||||
* @ref Thanks to pikuma for explaining the math behind this:
|
||||
* https://www.youtube.com/watch?v=EqNcqBdrNyI
|
||||
*/
|
||||
template<typename T>
|
||||
requires(std::is_arithmetic_v<T>)
|
||||
constexpr auto perspective(T field_of_view, T aspect_ratio, T z_near, T z_far) -> mat4_impl<T>
|
||||
constexpr auto perspective(T field_of_view, T aspect_ratio, T z_near, T z_far)
|
||||
{
|
||||
const T half_fov_tan = std::tan(field_of_view / static_cast<T>(2));
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
export module math.components;
|
||||
|
||||
import preliminary;
|
||||
import math.vec3;
|
||||
|
||||
namespace lt::math::components {
|
||||
|
|
|
|||
|
|
@ -1,26 +1,16 @@
|
|||
export module math.mat4;
|
||||
|
||||
import preliminary;
|
||||
import math.vec2;
|
||||
import math.vec3;
|
||||
import math.vec4;
|
||||
import std;
|
||||
|
||||
export namespace lt::math {
|
||||
namespace lt::math {
|
||||
|
||||
/** A 4 by 4 matrix, column major order
|
||||
*
|
||||
* @todo(Light): Use std::simd when it's implemented. */
|
||||
template<typename T = f32>
|
||||
requires(std::is_arithmetic_v<T>)
|
||||
export template<typename T = float>
|
||||
struct mat4_impl
|
||||
{
|
||||
using Column_T = vec4_impl<T>;
|
||||
|
||||
using Underlying_T = Column_T::Underlying_T;
|
||||
|
||||
static constexpr auto num_elements = 4u * 4u;
|
||||
|
||||
constexpr explicit mat4_impl(T scalar = T {})
|
||||
constexpr explicit mat4_impl(T scalar = 0)
|
||||
: values(
|
||||
{
|
||||
Column_T { scalar },
|
||||
|
|
@ -32,12 +22,13 @@ struct mat4_impl
|
|||
{
|
||||
}
|
||||
|
||||
constexpr mat4_impl(
|
||||
// clang-format off
|
||||
const T& x0, const T& x1, const T& x2, const T& x3,
|
||||
const T& y0, const T& y1, const T& y2, const T& y3,
|
||||
const T& z0, const T& z1, const T& z2, const T& z3,
|
||||
const T& w0, const T& w1, const T& w2, const T& w3)
|
||||
constexpr mat4_impl(
|
||||
const T& x0, const T& y0, const T& z0, const T& w0,
|
||||
const T& x1, const T& y1, const T& z1, const T& w1,
|
||||
const T& x2, const T& y2, const T& z2, const T& w2,
|
||||
const T& x3, const T& y3, const T& z3, const T& w3
|
||||
)
|
||||
// clang-format on
|
||||
: values({ { x0, x1, x2, x3 }, { y0, y1, y2, y3 }, { z0, z1, z2, z3 }, { w0, w1, w2, w3 } })
|
||||
{
|
||||
|
|
@ -63,123 +54,57 @@ struct mat4_impl
|
|||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator[](std::size_t idx) -> Column_T &
|
||||
{
|
||||
return values[idx];
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator[](std::size_t idx) const -> const Column_T &
|
||||
{
|
||||
return values[idx];
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator*(const mat4_impl<T> &other) const -> mat4_impl<T>
|
||||
{
|
||||
const auto &[a_x, a_y, a_z, a_w] = values;
|
||||
const auto &[b_x, b_y, b_z, b_w] = other.values;
|
||||
|
||||
return mat4_impl<T>(
|
||||
// X column
|
||||
a_x.x * b_x.x + a_y.x * b_x.y + a_z.x * b_x.z + a_w.x * b_x.w,
|
||||
a_x.y * b_x.x + a_y.y * b_x.y + a_z.y * b_x.z + a_w.y * b_x.w,
|
||||
a_x.z * b_x.x + a_y.z * b_x.y + a_z.z * b_x.z + a_w.z * b_x.w,
|
||||
a_x.w * b_x.x + a_y.w * b_x.y + a_z.w * b_x.z + a_w.w * b_x.w,
|
||||
|
||||
// Y column
|
||||
a_x.x * b_y.x + a_y.x * b_y.y + a_z.x * b_y.z + a_w.x * b_y.w,
|
||||
a_x.y * b_y.x + a_y.y * b_y.y + a_z.y * b_y.z + a_w.y * b_y.w,
|
||||
a_x.z * b_y.x + a_y.z * b_y.y + a_z.z * b_y.z + a_w.z * b_y.w,
|
||||
a_x.w * b_y.x + a_y.w * b_y.y + a_z.w * b_y.z + a_w.w * b_y.w,
|
||||
|
||||
// Z column
|
||||
a_x.x * b_z.x + a_y.x * b_z.y + a_z.x * b_z.z + a_w.x * b_z.w,
|
||||
a_x.y * b_z.x + a_y.y * b_z.y + a_z.y * b_z.z + a_w.y * b_z.w,
|
||||
a_x.z * b_z.x + a_y.z * b_z.y + a_z.z * b_z.z + a_w.z * b_z.w,
|
||||
a_x.w * b_z.x + a_y.w * b_z.y + a_z.w * b_z.z + a_w.w * b_z.w,
|
||||
|
||||
// W column
|
||||
a_x.x * b_w.x + a_y.x * b_w.y + a_z.x * b_w.z + a_w.x * b_w.w,
|
||||
a_x.y * b_w.x + a_y.y * b_w.y + a_z.y * b_w.z + a_w.y * b_w.w,
|
||||
a_x.z * b_w.x + a_y.z * b_w.y + a_z.z * b_w.z + a_w.z * b_w.w,
|
||||
a_x.w * b_w.x + a_y.w * b_w.y + a_z.w * b_w.z + a_w.w * b_w.w
|
||||
);
|
||||
return mat4_impl<T> {};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator[](size_t idx) -> Column_T &
|
||||
[[nodiscard]] constexpr auto operator*(const vec4_impl<T> &other) const -> vec4_impl<T>
|
||||
{
|
||||
debug_check(idx < num_elements, "mat4 out of bound access: {}", idx);
|
||||
return values[idx];
|
||||
return vec4_impl<T> {};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator[](size_t idx) const -> const Column_T &
|
||||
{
|
||||
return values[idx];
|
||||
}
|
||||
|
||||
[[nodiscard]] static constexpr auto transpose(const mat4_impl<T> &mat) -> mat4_impl<T>
|
||||
{
|
||||
const auto &[x, y, z, w] = mat.values;
|
||||
return mat4_impl<T> {
|
||||
x.x, y.x, z.x, w.x, x.y, y.y, z.y, w.y, x.z, y.z, z.z, w.z, x.w, y.w, z.w, w.w,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] static constexpr auto translate(const vec3_impl<T> &vec) -> mat4_impl<T>
|
||||
{
|
||||
return mat4_impl<T>(
|
||||
T { 1 },
|
||||
T { 0 },
|
||||
T { 0 },
|
||||
T { 0 },
|
||||
|
||||
T { 0 },
|
||||
T { 1 },
|
||||
T { 0 },
|
||||
T { 0 },
|
||||
|
||||
T { 0 },
|
||||
T { 0 },
|
||||
T { 1 },
|
||||
T { 0 },
|
||||
|
||||
vec.x,
|
||||
vec.y,
|
||||
vec.z,
|
||||
T { 1 }
|
||||
);
|
||||
}
|
||||
|
||||
[[nodiscard]] static constexpr auto scale(const vec3_impl<T> &vec) -> mat4_impl<T>
|
||||
{
|
||||
return mat4_impl<T>(
|
||||
vec.x,
|
||||
T { 0 },
|
||||
T { 0 },
|
||||
T { 0 },
|
||||
|
||||
T { 0 },
|
||||
vec.y,
|
||||
T { 0 },
|
||||
T { 0 },
|
||||
|
||||
T { 0 },
|
||||
T { 0 },
|
||||
vec.z,
|
||||
T { 0 },
|
||||
|
||||
T { 0 },
|
||||
T { 0 },
|
||||
T { 0 },
|
||||
T { 1 }
|
||||
);
|
||||
}
|
||||
|
||||
std::array<Column_T, 4u> values;
|
||||
std::array<Column_T, 4> values; // NOLINT
|
||||
};
|
||||
|
||||
using mat4 = mat4_impl<f32>;
|
||||
export template<typename T>
|
||||
[[nodiscard]] auto translate(const vec3_impl<T> &value) -> mat4_impl<T>
|
||||
{
|
||||
return mat4_impl<T> {};
|
||||
}
|
||||
|
||||
using mat4_f32 = mat4;
|
||||
using mat4_f64 = mat4_impl<f64>;
|
||||
export template<typename T>
|
||||
[[nodiscard]] auto rotate(float value, const vec3_impl<T> &xyz) -> mat4_impl<T>
|
||||
{
|
||||
return mat4_impl<T> {};
|
||||
}
|
||||
|
||||
using mat4_i8 = mat4_impl<i8>;
|
||||
using mat4_i16 = mat4_impl<i16>;
|
||||
using mat4_i32 = mat4_impl<i32>;
|
||||
using mat4_i64 = mat4_impl<i64>;
|
||||
export template<typename T>
|
||||
[[nodiscard]] auto scale(const vec3_impl<T> &value) -> mat4_impl<T>
|
||||
{
|
||||
return mat4_impl<T> {};
|
||||
}
|
||||
|
||||
using mat4_u8 = mat4_impl<u8>;
|
||||
using mat4_u16 = mat4_impl<u16>;
|
||||
using mat4_u32 = mat4_impl<u32>;
|
||||
using mat4_u64 = mat4_impl<u64>;
|
||||
export template<typename T>
|
||||
[[nodiscard]] auto inverse(const mat4_impl<T> &value) -> mat4_impl<T>
|
||||
{
|
||||
return mat4_impl<T> {};
|
||||
}
|
||||
|
||||
export using mat4 = mat4_impl<float>;
|
||||
|
||||
export using imat4 = mat4_impl<std::int32_t>;
|
||||
|
||||
export using umat4 = mat4_impl<std::uint32_t>;
|
||||
|
||||
} // namespace lt::math
|
||||
|
|
|
|||
|
|
@ -1,413 +0,0 @@
|
|||
import test;
|
||||
import math.vec3;
|
||||
import math.mat4;
|
||||
|
||||
using vec3 = ::lt::math::vec3;
|
||||
using mat4 = ::lt::math::mat4;
|
||||
|
||||
Suite static_tests = "mat4_static_checks"_suite = [] {
|
||||
constexpr auto num_elements = lt::math::mat4::num_elements;
|
||||
|
||||
static_assert(num_elements == 4u * 4u);
|
||||
static_assert(std::is_same_v<lt::math::mat4, lt::math::mat4_f32>);
|
||||
|
||||
static_assert(sizeof(lt::math::mat4_f32) == sizeof(f32) * num_elements);
|
||||
static_assert(sizeof(lt::math::mat4_f64) == sizeof(f64) * num_elements);
|
||||
|
||||
static_assert(sizeof(lt::math::mat4_i8) == sizeof(i8) * num_elements);
|
||||
static_assert(sizeof(lt::math::mat4_i16) == sizeof(i16) * num_elements);
|
||||
static_assert(sizeof(lt::math::mat4_i32) == sizeof(i32) * num_elements);
|
||||
static_assert(sizeof(lt::math::mat4_i64) == sizeof(i64) * num_elements);
|
||||
|
||||
static_assert(sizeof(lt::math::mat4_u8) == sizeof(u8) * num_elements);
|
||||
static_assert(sizeof(lt::math::mat4_u16) == sizeof(u16) * num_elements);
|
||||
static_assert(sizeof(lt::math::mat4_u32) == sizeof(u32) * num_elements);
|
||||
static_assert(sizeof(lt::math::mat4_u64) == sizeof(u64) * num_elements);
|
||||
};
|
||||
|
||||
Suite raii = "mat4_raii"_suite = [] {
|
||||
Case { "happy paths" } = [] {
|
||||
ignore = mat4 {};
|
||||
ignore = mat4 { 1.0 };
|
||||
ignore = mat4 {
|
||||
1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0,
|
||||
};
|
||||
ignore = mat4 {
|
||||
mat4::Column_T { 1.0, 2.0, 3.0, 4.0 },
|
||||
mat4::Column_T { 5.0, 6.0, 7.0, 8.0 },
|
||||
mat4::Column_T { 9.0, 10.0, 11.0, 12.0 },
|
||||
mat4::Column_T { 13.0, 14.0, 15.0, 16.0 },
|
||||
};
|
||||
};
|
||||
|
||||
Case { "unhappy paths" } = [] {
|
||||
};
|
||||
|
||||
Case { "many" } = [] {
|
||||
for (auto idx : std::views::iota(0, 1'000'000))
|
||||
{
|
||||
ignore = idx;
|
||||
ignore = mat4 {};
|
||||
ignore = mat4 { 1.0 };
|
||||
ignore = mat4 {
|
||||
1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0,
|
||||
9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0,
|
||||
};
|
||||
ignore = mat4 {
|
||||
mat4::Column_T { 1.0, 2.0, 3.0, 4.0 },
|
||||
mat4::Column_T { 5.0, 6.0, 7.0, 8.0 },
|
||||
mat4::Column_T { 9.0, 10.0, 11.0, 12.0 },
|
||||
mat4::Column_T { 13.0, 14.0, 15.0, 16.0 },
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
Case { "post default construct has correct state" } = [] {
|
||||
const auto [x, y, z, w] = mat4 {}.values;
|
||||
|
||||
expect_eq(x[0], mat4::Underlying_T {});
|
||||
expect_eq(x[1], mat4::Underlying_T {});
|
||||
expect_eq(x[2], mat4::Underlying_T {});
|
||||
expect_eq(x[3], mat4::Underlying_T {});
|
||||
|
||||
expect_eq(y[0], mat4::Underlying_T {});
|
||||
expect_eq(y[1], mat4::Underlying_T {});
|
||||
expect_eq(y[2], mat4::Underlying_T {});
|
||||
expect_eq(y[3], mat4::Underlying_T {});
|
||||
|
||||
expect_eq(z[0], mat4::Underlying_T {});
|
||||
expect_eq(z[1], mat4::Underlying_T {});
|
||||
expect_eq(z[2], mat4::Underlying_T {});
|
||||
expect_eq(z[3], mat4::Underlying_T {});
|
||||
|
||||
expect_eq(w[0], mat4::Underlying_T {});
|
||||
expect_eq(w[1], mat4::Underlying_T {});
|
||||
expect_eq(w[2], mat4::Underlying_T {});
|
||||
expect_eq(w[3], mat4::Underlying_T {});
|
||||
};
|
||||
|
||||
Case { "post scalar construct has correct state" } = [] {
|
||||
const auto [x, y, z, w] = mat4 { 69.0 }.values;
|
||||
|
||||
expect_eq(x[0], mat4::Underlying_T { 69.0 });
|
||||
expect_eq(x[1], mat4::Underlying_T { 69.0 });
|
||||
expect_eq(x[2], mat4::Underlying_T { 69.0 });
|
||||
expect_eq(x[3], mat4::Underlying_T { 69.0 });
|
||||
|
||||
expect_eq(y[0], mat4::Underlying_T { 69.0 });
|
||||
expect_eq(y[1], mat4::Underlying_T { 69.0 });
|
||||
expect_eq(y[2], mat4::Underlying_T { 69.0 });
|
||||
expect_eq(y[3], mat4::Underlying_T { 69.0 });
|
||||
|
||||
expect_eq(z[0], mat4::Underlying_T { 69.0 });
|
||||
expect_eq(z[1], mat4::Underlying_T { 69.0 });
|
||||
expect_eq(z[2], mat4::Underlying_T { 69.0 });
|
||||
expect_eq(z[3], mat4::Underlying_T { 69.0 });
|
||||
|
||||
expect_eq(w[0], mat4::Underlying_T { 69.0 });
|
||||
expect_eq(w[1], mat4::Underlying_T { 69.0 });
|
||||
expect_eq(w[2], mat4::Underlying_T { 69.0 });
|
||||
expect_eq(w[3], mat4::Underlying_T { 69.0 });
|
||||
};
|
||||
|
||||
Case { "post construct with all values has correct state" } = [] {
|
||||
const auto [x, y, z, w] = mat4 {
|
||||
1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0,
|
||||
}.values;
|
||||
|
||||
expect_eq(x[0], mat4::Underlying_T { 1.0 });
|
||||
expect_eq(x[1], mat4::Underlying_T { 2.0 });
|
||||
expect_eq(x[2], mat4::Underlying_T { 3.0 });
|
||||
expect_eq(x[3], mat4::Underlying_T { 4.0 });
|
||||
|
||||
expect_eq(y[0], mat4::Underlying_T { 5.0 });
|
||||
expect_eq(y[1], mat4::Underlying_T { 6.0 });
|
||||
expect_eq(y[2], mat4::Underlying_T { 7.0 });
|
||||
expect_eq(y[3], mat4::Underlying_T { 8.0 });
|
||||
|
||||
expect_eq(z[0], mat4::Underlying_T { 9.0 });
|
||||
expect_eq(z[1], mat4::Underlying_T { 10.0 });
|
||||
expect_eq(z[2], mat4::Underlying_T { 11.0 });
|
||||
expect_eq(z[3], mat4::Underlying_T { 12.0 });
|
||||
|
||||
expect_eq(w[0], mat4::Underlying_T { 13.0 });
|
||||
expect_eq(w[1], mat4::Underlying_T { 14.0 });
|
||||
expect_eq(w[2], mat4::Underlying_T { 15.0 });
|
||||
expect_eq(w[3], mat4::Underlying_T { 16.0 });
|
||||
};
|
||||
|
||||
Case { "post construct with columns has correct state" } = [] {
|
||||
const auto [x, y, z, w] = mat4 {
|
||||
mat4::Column_T { 1.0, 2.0, 3.0, 4.0 },
|
||||
mat4::Column_T { 5.0, 6.0, 7.0, 8.0 },
|
||||
mat4::Column_T { 9.0, 10.0, 11.0, 12.0 },
|
||||
mat4::Column_T { 13.0, 14.0, 15.0, 16.0 },
|
||||
}.values;
|
||||
|
||||
expect_eq(x[0], mat4::Underlying_T { 1.0 });
|
||||
expect_eq(x[1], mat4::Underlying_T { 2.0 });
|
||||
expect_eq(x[2], mat4::Underlying_T { 3.0 });
|
||||
expect_eq(x[3], mat4::Underlying_T { 4.0 });
|
||||
|
||||
expect_eq(y[0], mat4::Underlying_T { 5.0 });
|
||||
expect_eq(y[1], mat4::Underlying_T { 6.0 });
|
||||
expect_eq(y[2], mat4::Underlying_T { 7.0 });
|
||||
expect_eq(y[3], mat4::Underlying_T { 8.0 });
|
||||
|
||||
expect_eq(z[0], mat4::Underlying_T { 9.0 });
|
||||
expect_eq(z[1], mat4::Underlying_T { 10.0 });
|
||||
expect_eq(z[2], mat4::Underlying_T { 11.0 });
|
||||
expect_eq(z[3], mat4::Underlying_T { 12.0 });
|
||||
|
||||
expect_eq(w[0], mat4::Underlying_T { 13.0 });
|
||||
expect_eq(w[1], mat4::Underlying_T { 14.0 });
|
||||
expect_eq(w[2], mat4::Underlying_T { 15.0 });
|
||||
expect_eq(w[3], mat4::Underlying_T { 16.0 });
|
||||
};
|
||||
|
||||
Case { "post construct identity matrix has correct state" } = [] {
|
||||
const auto [x, y, z, w] = mat4::identity().values;
|
||||
|
||||
expect_eq(x[0], mat4::Underlying_T { 1 });
|
||||
expect_eq(x[1], mat4::Underlying_T {});
|
||||
expect_eq(x[2], mat4::Underlying_T {});
|
||||
expect_eq(x[3], mat4::Underlying_T {});
|
||||
|
||||
expect_eq(y[0], mat4::Underlying_T {});
|
||||
expect_eq(y[1], mat4::Underlying_T { 1 });
|
||||
expect_eq(y[2], mat4::Underlying_T {});
|
||||
expect_eq(y[3], mat4::Underlying_T {});
|
||||
|
||||
expect_eq(z[0], mat4::Underlying_T {});
|
||||
expect_eq(z[1], mat4::Underlying_T {});
|
||||
expect_eq(z[2], mat4::Underlying_T { 1 });
|
||||
expect_eq(z[3], mat4::Underlying_T {});
|
||||
|
||||
expect_eq(w[0], mat4::Underlying_T {});
|
||||
expect_eq(w[1], mat4::Underlying_T {});
|
||||
expect_eq(w[2], mat4::Underlying_T {});
|
||||
expect_eq(w[3], mat4::Underlying_T { 1 });
|
||||
};
|
||||
};
|
||||
|
||||
Suite arithmetic_operators = "mat4_arithmetic_operators"_suite = [] {
|
||||
Case { "operator *" } = [] {
|
||||
const auto lhs = mat4 {
|
||||
mat4::Column_T { 1.0, 2.0, 3.0, 4.0 },
|
||||
mat4::Column_T { 5.0, 6.0, 7.0, 8.0 },
|
||||
mat4::Column_T { 9.0, 10.0, 11.0, 12.0 },
|
||||
mat4::Column_T { 13.0, 14.0, 15.0, 16.0 },
|
||||
};
|
||||
|
||||
const auto rhs = mat4 {
|
||||
mat4::Column_T { 17.0, 18.0, 19.0, 20.0 },
|
||||
mat4::Column_T { 21.0, 22.0, 23.0, 24.0 },
|
||||
mat4::Column_T { 25.0, 26.0, 27.0, 28.0 },
|
||||
mat4::Column_T { 29.0, 30.0, 31.0, 32.0 },
|
||||
};
|
||||
|
||||
const auto [x, y, z, w] = (lhs * rhs).values;
|
||||
|
||||
expect_eq(x[0], 538.0);
|
||||
expect_eq(x[1], 612.0);
|
||||
expect_eq(x[2], 686.0);
|
||||
expect_eq(x[3], 760.0);
|
||||
|
||||
expect_eq(y[0], 650.0);
|
||||
expect_eq(y[1], 740.0);
|
||||
expect_eq(y[2], 830.0);
|
||||
expect_eq(y[3], 920.0);
|
||||
|
||||
expect_eq(z[0], 762.0);
|
||||
expect_eq(z[1], 868.0);
|
||||
expect_eq(z[2], 974.0);
|
||||
expect_eq(z[3], 1080.0);
|
||||
|
||||
expect_eq(w[0], 874.0);
|
||||
expect_eq(w[1], 996.0);
|
||||
expect_eq(w[2], 1118.0);
|
||||
expect_eq(w[3], 1240.0);
|
||||
};
|
||||
};
|
||||
|
||||
Suite access_operators = "mat4_access_operators"_suite = [] {
|
||||
Case { "operator []" } = [] {
|
||||
auto mat = mat4 {
|
||||
1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0,
|
||||
};
|
||||
|
||||
expect_eq(mat[0][0], 1.0);
|
||||
expect_eq(mat[0][1], 2.0);
|
||||
expect_eq(mat[0][2], 3.0);
|
||||
expect_eq(mat[0][3], 4.0);
|
||||
|
||||
expect_eq(mat[1][0], 5.0);
|
||||
expect_eq(mat[1][1], 6.0);
|
||||
expect_eq(mat[1][2], 7.0);
|
||||
expect_eq(mat[1][3], 8.0);
|
||||
|
||||
expect_eq(mat[2][0], 9.0);
|
||||
expect_eq(mat[2][1], 10.0);
|
||||
expect_eq(mat[2][2], 11.0);
|
||||
expect_eq(mat[2][3], 12.0);
|
||||
|
||||
expect_eq(mat[3][0], 13.0);
|
||||
expect_eq(mat[3][1], 14.0);
|
||||
expect_eq(mat[3][2], 15.0);
|
||||
expect_eq(mat[3][3], 16.0);
|
||||
};
|
||||
|
||||
Case { "operator [] const" } = [] {
|
||||
const auto mat = mat4 {
|
||||
1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0,
|
||||
};
|
||||
|
||||
expect_eq(mat[0][0], 1.0);
|
||||
expect_eq(mat[0][1], 2.0);
|
||||
expect_eq(mat[0][2], 3.0);
|
||||
expect_eq(mat[0][3], 4.0);
|
||||
|
||||
expect_eq(mat[1][0], 5.0);
|
||||
expect_eq(mat[1][1], 6.0);
|
||||
expect_eq(mat[1][2], 7.0);
|
||||
expect_eq(mat[1][3], 8.0);
|
||||
|
||||
expect_eq(mat[2][0], 9.0);
|
||||
expect_eq(mat[2][1], 10.0);
|
||||
expect_eq(mat[2][2], 11.0);
|
||||
expect_eq(mat[2][3], 12.0);
|
||||
|
||||
expect_eq(mat[3][0], 13.0);
|
||||
expect_eq(mat[3][1], 14.0);
|
||||
expect_eq(mat[3][2], 15.0);
|
||||
expect_eq(mat[3][3], 16.0);
|
||||
};
|
||||
};
|
||||
|
||||
Suite transformations = "mat4_transformations"_suite = [] {
|
||||
Case { "translate" } = [] {
|
||||
const auto &[x, y, z, w] = mat4::translate(vec3 { 1, 2, 3 }).values;
|
||||
|
||||
// identity basis
|
||||
expect_eq(x[0], 1);
|
||||
expect_eq(x[1], 0);
|
||||
expect_eq(x[2], 0);
|
||||
expect_eq(x[3], 0);
|
||||
expect_eq(y[0], 0);
|
||||
expect_eq(y[1], 1);
|
||||
expect_eq(y[2], 0);
|
||||
expect_eq(y[3], 0);
|
||||
expect_eq(z[0], 0);
|
||||
expect_eq(z[1], 0);
|
||||
expect_eq(z[2], 1);
|
||||
expect_eq(z[3], 0);
|
||||
|
||||
// translation column
|
||||
expect_eq(w[0], 1);
|
||||
expect_eq(w[1], 2);
|
||||
expect_eq(w[2], 3);
|
||||
expect_eq(w[3], 1);
|
||||
};
|
||||
|
||||
Case { "scale" } = [] {
|
||||
const auto [x, y, z, w] = mat4::scale(vec3 { 2, 3, 4 }).values;
|
||||
|
||||
expect_eq(x[0], 2);
|
||||
expect_eq(x[1], 0);
|
||||
expect_eq(x[2], 0);
|
||||
expect_eq(x[3], 0);
|
||||
expect_eq(y[0], 0);
|
||||
expect_eq(y[1], 3);
|
||||
expect_eq(y[2], 0);
|
||||
expect_eq(y[3], 0);
|
||||
expect_eq(z[0], 0);
|
||||
expect_eq(z[1], 0);
|
||||
expect_eq(z[2], 4);
|
||||
expect_eq(z[3], 0);
|
||||
expect_eq(w[0], 0);
|
||||
expect_eq(w[1], 0);
|
||||
expect_eq(w[2], 0);
|
||||
expect_eq(w[3], 1);
|
||||
};
|
||||
|
||||
Case { "scale -> translate" } = [] {
|
||||
const auto scale = mat4::scale(vec3 { 2, 2, 2 });
|
||||
const auto translate = mat4::translate(vec3 { 1, 2, 3 });
|
||||
const auto [x, y, z, w] = (scale * translate).values;
|
||||
|
||||
// scaled basis
|
||||
expect_eq(x[0], 2);
|
||||
expect_eq(x[1], 0);
|
||||
expect_eq(x[2], 0);
|
||||
expect_eq(x[3], 0);
|
||||
expect_eq(y[0], 0);
|
||||
expect_eq(y[1], 2);
|
||||
expect_eq(y[2], 0);
|
||||
expect_eq(y[3], 0);
|
||||
expect_eq(z[0], 0);
|
||||
expect_eq(z[1], 0);
|
||||
expect_eq(z[2], 2);
|
||||
expect_eq(z[3], 0);
|
||||
|
||||
// translation is scaled (local-space translation)
|
||||
expect_eq(w[0], 2); // 1 * 2
|
||||
expect_eq(w[1], 4); // 2 * 2
|
||||
expect_eq(w[2], 6); // 3 * 2
|
||||
expect_eq(w[3], 1);
|
||||
};
|
||||
|
||||
Case { "transpose" } = [] {
|
||||
const auto mat = mat4 {
|
||||
mat4::Column_T { 1, 2, 3, 4 },
|
||||
mat4::Column_T { 5, 6, 7, 8 },
|
||||
mat4::Column_T { 9, 10, 11, 12 },
|
||||
mat4::Column_T { 13, 14, 15, 16 },
|
||||
};
|
||||
const auto [x, y, z, w] = mat4::transpose(mat).values;
|
||||
|
||||
// rows become columns
|
||||
expect_eq(x[0], 1);
|
||||
expect_eq(x[1], 5);
|
||||
expect_eq(x[2], 9);
|
||||
expect_eq(x[3], 13);
|
||||
expect_eq(y[0], 2);
|
||||
expect_eq(y[1], 6);
|
||||
expect_eq(y[2], 10);
|
||||
expect_eq(y[3], 14);
|
||||
expect_eq(z[0], 3);
|
||||
expect_eq(z[1], 7);
|
||||
expect_eq(z[2], 11);
|
||||
expect_eq(z[3], 15);
|
||||
expect_eq(w[0], 4);
|
||||
expect_eq(w[1], 8);
|
||||
expect_eq(w[2], 12);
|
||||
expect_eq(w[3], 16);
|
||||
};
|
||||
|
||||
Case { "transpose twice" } = [] {
|
||||
const auto mat = mat4 {
|
||||
mat4::Column_T { 1, 2, 3, 4 },
|
||||
mat4::Column_T { 5, 6, 7, 8 },
|
||||
mat4::Column_T { 9, 10, 11, 12 },
|
||||
mat4::Column_T { 13, 14, 15, 16 },
|
||||
};
|
||||
const auto [x, y, z, w] = mat4::transpose(mat4::transpose(mat)).values;
|
||||
|
||||
expect_eq(x[0], 1);
|
||||
expect_eq(x[1], 2);
|
||||
expect_eq(x[2], 3);
|
||||
expect_eq(x[3], 4);
|
||||
expect_eq(y[0], 5);
|
||||
expect_eq(y[1], 6);
|
||||
expect_eq(y[2], 7);
|
||||
expect_eq(y[3], 8);
|
||||
expect_eq(z[0], 9);
|
||||
expect_eq(z[1], 10);
|
||||
expect_eq(z[2], 11);
|
||||
expect_eq(z[3], 12);
|
||||
expect_eq(w[0], 13);
|
||||
expect_eq(w[1], 14);
|
||||
expect_eq(w[2], 15);
|
||||
expect_eq(w[3], 16);
|
||||
};
|
||||
};
|
||||
|
|
@ -1,27 +1,26 @@
|
|||
export module math.trig;
|
||||
|
||||
import preliminary;
|
||||
|
||||
export namespace lt::math {
|
||||
|
||||
[[nodiscard]] constexpr auto to_radians(f32 degrees) -> f32
|
||||
[[nodiscard]] constexpr auto radians(float degrees) -> float
|
||||
{
|
||||
return degrees * 0.01745329251994329576923690768489f;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto to_radians(f64 degrees) -> f64
|
||||
[[nodiscard]] constexpr auto radians(double degrees) -> double
|
||||
{
|
||||
return degrees * 0.01745329251994329576923690768489;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto to_degrees(f32 radians) -> f32
|
||||
[[nodiscard]] constexpr auto degrees(float radians) -> float
|
||||
{
|
||||
return radians * 57.295779513082320876798154814105f;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto to_degrees(f64 radians) -> f64
|
||||
[[nodiscard]] constexpr auto degrees(double radians) -> double
|
||||
{
|
||||
return radians * 57.295779513082320876798154814105;
|
||||
}
|
||||
|
||||
|
||||
} // namespace lt::math
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
import test;
|
||||
import math.trig;
|
||||
|
||||
Suite conversions = "trig_conversions"_suite = [] {
|
||||
using ::lt::math::to_degrees;
|
||||
using ::lt::math::to_radians;
|
||||
|
||||
Case { "to_radians <f32>" } = [] {
|
||||
expect_eq(to_radians(f32 { 0.0f }), f32 { 0.0f });
|
||||
expect_eq(to_radians(f32 { 90.0f }), f32 { 1.5707963267948966f });
|
||||
expect_eq(to_radians(f32 { 180.0f }), f32 { 3.1415926535897932f });
|
||||
expect_eq(to_radians(f32 { 360.0f }), f32 { 6.2831853071795864f });
|
||||
};
|
||||
|
||||
Case { "to_radians <f64>" } = [] {
|
||||
expect_eq(to_radians(f64 { 0.0 }), f64 { 0.0 });
|
||||
expect_eq(to_radians(f64 { 90.0 }), f64 { 1.5707963267948966 });
|
||||
expect_eq(to_radians(f64 { 180.0 }), f64 { 3.1415926535897932 });
|
||||
expect_eq(to_radians(f64 { 360.0 }), f64 { 6.2831853071795864 });
|
||||
};
|
||||
|
||||
Case { "to_degrees <f32>" } = [] {
|
||||
expect_eq(to_degrees(f32 { 0.0f }), f32 { 0.0f });
|
||||
expect_eq(to_degrees(f32 { 1.5707963267948966f }), f32 { 90.0f });
|
||||
expect_eq(to_degrees(f32 { 3.1415926535897932f }), f32 { 180.0f });
|
||||
expect_eq(to_degrees(f32 { 6.2831853071795864f }), f32 { 360.0f });
|
||||
};
|
||||
|
||||
Case { "to_degrees <f64>" } = [] {
|
||||
expect_eq(to_degrees(f64 { 0.0 }), f64 { 0.0 });
|
||||
expect_eq(to_degrees(f64 { 1.5707963267948966 }), f64 { 90.0 });
|
||||
expect_eq(to_degrees(f64 { 3.1415926535897932 }), f64 { 180.0 });
|
||||
expect_eq(to_degrees(f64 { 6.2831853071795864 }), f64 { 360.0 });
|
||||
};
|
||||
|
||||
Case { "to_degrees -> to_radians -> to_degrees <f32>" } = [] {
|
||||
expect_eq(to_degrees(to_radians(f32 { 45.0f })), f32 { 45.0f });
|
||||
};
|
||||
|
||||
Case { "to_degrees -> to_radians -> to_degrees <f64>" } = [] {
|
||||
expect_eq(to_degrees(to_radians(f64 { 45.0 })), f64 { 45.0 });
|
||||
};
|
||||
};
|
||||
|
|
@ -1,17 +1,12 @@
|
|||
export module math.vec2;
|
||||
|
||||
import preliminary;
|
||||
import std;
|
||||
|
||||
export namespace lt::math {
|
||||
namespace lt::math {
|
||||
|
||||
template<typename T = f32>
|
||||
requires(std::is_arithmetic_v<T>)
|
||||
export template<typename T = float>
|
||||
struct vec2_impl
|
||||
{
|
||||
using Underlying_T = T;
|
||||
|
||||
static constexpr auto num_elements = 2u;
|
||||
|
||||
constexpr vec2_impl(): x(), y()
|
||||
{
|
||||
}
|
||||
|
|
@ -34,23 +29,7 @@ struct vec2_impl
|
|||
return !(*this == other);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator+(const vec2_impl<T> &other) const -> vec2_impl
|
||||
{
|
||||
return {
|
||||
x + other.x,
|
||||
y + other.y,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator-(const vec2_impl<T> &other) const -> vec2_impl
|
||||
{
|
||||
return {
|
||||
x - other.x,
|
||||
y - other.y,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator*(const vec2_impl<T> &other) const -> vec2_impl
|
||||
[[nodiscard]] auto operator*(const vec2_impl<T> &other) const -> vec2_impl
|
||||
{
|
||||
return {
|
||||
x * other.x,
|
||||
|
|
@ -58,45 +37,33 @@ struct vec2_impl
|
|||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator/(const vec2_impl<T> &other) const -> vec2_impl
|
||||
[[nodiscard]] auto operator-(const vec2_impl<T> &other) const -> vec2_impl
|
||||
{
|
||||
return {
|
||||
x / other.x,
|
||||
y / other.y,
|
||||
x - other.x,
|
||||
y - other.y,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator[](u8 idx) -> T &
|
||||
[[nodiscard]] auto operator*(float scalar) const -> vec2_impl
|
||||
{
|
||||
debug_check(idx < num_elements, "vec2 out of bound access: {}", idx);
|
||||
return ((T *)this)[idx];
|
||||
return {
|
||||
x * scalar,
|
||||
y * scalar,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator[](u8 idx) const -> const T &
|
||||
{
|
||||
debug_check(idx < num_elements, "vec2 out of bound access: {}", idx);
|
||||
return ((T *)this)[idx];
|
||||
}
|
||||
T x; // NOLINT
|
||||
|
||||
T x;
|
||||
|
||||
T y;
|
||||
T y; // NOLINT
|
||||
};
|
||||
|
||||
using vec2 = vec2_impl<f32>;
|
||||
|
||||
using vec2_f32 = vec2;
|
||||
using vec2_f64 = vec2_impl<f64>;
|
||||
export using vec2 = vec2_impl<float>;
|
||||
|
||||
using vec2_i8 = vec2_impl<i8>;
|
||||
using vec2_i16 = vec2_impl<i16>;
|
||||
using vec2_i32 = vec2_impl<i32>;
|
||||
using vec2_i64 = vec2_impl<i64>;
|
||||
export using ivec2 = vec2_impl<std::int32_t>;
|
||||
|
||||
using vec2_u8 = vec2_impl<u8>;
|
||||
using vec2_u16 = vec2_impl<u16>;
|
||||
using vec2_u32 = vec2_impl<u32>;
|
||||
using vec2_u64 = vec2_impl<u64>;
|
||||
export using uvec2 = vec2_impl<std::uint32_t>;
|
||||
|
||||
} // namespace lt::math
|
||||
|
||||
|
|
|
|||
|
|
@ -1,130 +0,0 @@
|
|||
import test;
|
||||
import math.vec2;
|
||||
|
||||
using vec2 = ::lt::math::vec2;
|
||||
using ivec2 = ::lt::math::vec2_i32;
|
||||
|
||||
Suite static_tests = "vec3_static_checks"_suite = [] {
|
||||
constexpr auto num_elements = lt::math::vec2::num_elements;
|
||||
|
||||
static_assert(num_elements == 2u);
|
||||
static_assert(std::is_same_v<lt::math::vec2, lt::math::vec2_f32>);
|
||||
|
||||
static_assert(sizeof(lt::math::vec2_f32) == sizeof(f32) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec2_f64) == sizeof(f64) * num_elements);
|
||||
|
||||
static_assert(sizeof(lt::math::vec2_i8) == sizeof(i8) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec2_i16) == sizeof(i16) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec2_i32) == sizeof(i32) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec2_i64) == sizeof(i64) * num_elements);
|
||||
|
||||
static_assert(sizeof(lt::math::vec2_u8) == sizeof(u8) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec2_u16) == sizeof(u16) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec2_u32) == sizeof(u32) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec2_u64) == sizeof(u64) * num_elements);
|
||||
};
|
||||
|
||||
Suite raii = "vec2_raii"_suite = [] {
|
||||
Case { "happy paths" } = [] {
|
||||
ignore = vec2 {};
|
||||
ignore = vec2 { 2.0 };
|
||||
ignore = vec2 { 2.0, 4.0 };
|
||||
};
|
||||
|
||||
Case { "unhappy paths" } = [] {
|
||||
};
|
||||
|
||||
Case { "many" } = [] {
|
||||
for (auto idx : std::views::iota(0, 1'000'000))
|
||||
{
|
||||
ignore = idx;
|
||||
ignore = vec2 {};
|
||||
ignore = vec2 { 2.0 };
|
||||
ignore = vec2 { 2.0, 4.0 };
|
||||
}
|
||||
};
|
||||
|
||||
Case { "post default construct has correct state" } = [] {
|
||||
const auto vec = vec2 {};
|
||||
expect_eq(vec.x, 0.0);
|
||||
expect_eq(vec.y, 0.0);
|
||||
};
|
||||
|
||||
Case { "post scalar construct has correct state" } = [] {
|
||||
const auto vec = vec2 { 2.0 };
|
||||
expect_eq(vec.x, 2.0);
|
||||
expect_eq(vec.y, 2.0);
|
||||
};
|
||||
|
||||
Case { "post construct with x,y has correct state" } = [] {
|
||||
const auto vec = vec2 { 2.0, 3.0 };
|
||||
expect_eq(vec.x, 2.0);
|
||||
expect_eq(vec.y, 3.0);
|
||||
};
|
||||
};
|
||||
|
||||
Suite arithmetic_operators = "vec2_operators"_suite = [] {
|
||||
Case { "operator ==" } = [] {
|
||||
const auto lhs = vec2 { 1.0, 2.0 };
|
||||
|
||||
expect_false(lhs == vec2 { {}, 2.0 });
|
||||
expect_false(lhs == vec2 { 1.0, {} });
|
||||
expect_true(lhs == vec2 { 1.0, 2.0 });
|
||||
};
|
||||
|
||||
Case { "operator !=" } = [] {
|
||||
const auto lhs = vec2 { 1.0, 2.0 };
|
||||
|
||||
expect_true(lhs != vec2 { {}, 2.0 });
|
||||
expect_true(lhs != vec2 { 1.0, {} });
|
||||
expect_false(lhs != vec2 { 1.0, 2.0 });
|
||||
};
|
||||
|
||||
Case { "operator +" } = [] {
|
||||
const auto lhs = vec2 { 2.0, 3.0 };
|
||||
const auto rhs = vec2 { 4.0, 5.0 };
|
||||
expect_eq(lhs + rhs, vec2 { 6.0, 8.0 });
|
||||
};
|
||||
|
||||
Case { "operator -" } = [] {
|
||||
const auto lhs = vec2 { 2.0, 3.0 };
|
||||
const auto rhs = vec2 { 4.0, 6.0 };
|
||||
expect_eq(lhs - rhs, vec2 { -2.0, -3.0 });
|
||||
};
|
||||
|
||||
Case { "operator *" } = [] {
|
||||
const auto lhs = vec2 { 2.0, 3.0 };
|
||||
const auto rhs = vec2 { 10.0, 20.0 };
|
||||
expect_eq(lhs * rhs, vec2 { 20.0, 60.0 });
|
||||
};
|
||||
|
||||
Case { "operator /" } = [] {
|
||||
const auto lhs = vec2 { 10.0, 20.0 };
|
||||
const auto rhs = vec2 { 2.0, 20.0 };
|
||||
expect_eq(lhs / rhs, vec2 { 5.0, 1.0 });
|
||||
};
|
||||
|
||||
Case { "operator []" } = [] {
|
||||
auto vec = vec2 { 0.0, 1.0 };
|
||||
expect_eq(vec[0], 0.0);
|
||||
expect_eq(vec[1], 1.0);
|
||||
};
|
||||
|
||||
Case { "operator [] const" } = [] {
|
||||
const auto vec = vec2 { 0.0, 1.0 };
|
||||
expect_eq(vec[0], 0.0);
|
||||
expect_eq(vec[1], 1.0);
|
||||
};
|
||||
};
|
||||
|
||||
Suite utilities = "vec2_utilities"_suite = [] {
|
||||
Case { "std::format float" } = [] {
|
||||
auto str = std::format("{}", vec2 { 10.0000f, 30.0005f });
|
||||
expect_eq(str, "10, 30.0005");
|
||||
};
|
||||
|
||||
Case { "std::format int" } = [] {
|
||||
auto str = std::format("{}", ivec2 { 10, 30 });
|
||||
expect_eq(str, "10, 30");
|
||||
};
|
||||
};
|
||||
|
|
@ -1,18 +1,13 @@
|
|||
export module math.vec3;
|
||||
|
||||
import preliminary;
|
||||
import math.vec2;
|
||||
import std;
|
||||
|
||||
export namespace lt::math {
|
||||
namespace lt::math {
|
||||
|
||||
template<typename T = f32>
|
||||
requires(std::is_arithmetic_v<T>)
|
||||
export template<typename T = float>
|
||||
struct vec3_impl
|
||||
{
|
||||
using Underlying_T = T;
|
||||
|
||||
static constexpr auto num_elements = 3u;
|
||||
|
||||
constexpr vec3_impl(): x(), y(), z()
|
||||
{
|
||||
}
|
||||
|
|
@ -25,14 +20,6 @@ struct vec3_impl
|
|||
{
|
||||
}
|
||||
|
||||
constexpr vec3_impl(vec2_impl<T> xy, T z): x(xy.x), y(xy.y), z(z)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr vec3_impl(T x, vec2_impl<T> yz): x(x), y(yz.x), z(yz.y)
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] auto operator==(const vec3_impl<T> &other) const -> bool
|
||||
{
|
||||
return x == other.x && y == other.y && z == other.z;
|
||||
|
|
@ -43,15 +30,6 @@ struct vec3_impl
|
|||
return !(*this == other);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator+(const vec3_impl<T> &other) const -> vec3_impl
|
||||
{
|
||||
return {
|
||||
x + other.x,
|
||||
y + other.y,
|
||||
z + other.z,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator-(const vec3_impl<T> &other) const -> vec3_impl
|
||||
{
|
||||
return {
|
||||
|
|
@ -70,58 +48,28 @@ struct vec3_impl
|
|||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator/(const vec3_impl<T> &other) const -> vec3_impl
|
||||
{
|
||||
return {
|
||||
x / other.x,
|
||||
y / other.y,
|
||||
z / other.z,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator[](u8 idx) -> T &
|
||||
{
|
||||
debug_check(idx < num_elements, "vec3 out of bound access: {}", idx);
|
||||
return ((T *)this)[idx];
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator[](u8 idx) const -> const T &
|
||||
{
|
||||
debug_check(idx < num_elements, "vec3 out of bound access: {}", idx);
|
||||
return ((T *)this)[idx];
|
||||
}
|
||||
|
||||
friend auto operator<<(std::ostream &stream, vec3_impl<T> value) -> std::ostream &
|
||||
{
|
||||
stream << value.x << ", " << value.y << ", " << value.z;
|
||||
return stream;
|
||||
}
|
||||
|
||||
T x;
|
||||
T x; // NOLINT
|
||||
|
||||
T y;
|
||||
T y; // NOLINT
|
||||
|
||||
T z;
|
||||
T z; // NOLINT
|
||||
};
|
||||
|
||||
using vec3 = vec3_impl<f32>;
|
||||
export using vec3 = vec3_impl<float>;
|
||||
|
||||
using vec3_f32 = vec3;
|
||||
using vec3_f64 = vec3_impl<f64>;
|
||||
export using ivec3 = vec3_impl<std::int32_t>;
|
||||
|
||||
using vec3_i8 = vec3_impl<i8>;
|
||||
using vec3_i16 = vec3_impl<i16>;
|
||||
using vec3_i32 = vec3_impl<i32>;
|
||||
using vec3_i64 = vec3_impl<i64>;
|
||||
|
||||
using vec3_u8 = vec3_impl<u8>;
|
||||
using vec3_u16 = vec3_impl<u16>;
|
||||
using vec3_u32 = vec3_impl<u32>;
|
||||
using vec3_u64 = vec3_impl<u64>;
|
||||
export using uvec3 = vec3_impl<std::uint32_t>;
|
||||
|
||||
} // namespace lt::math
|
||||
|
||||
export template<typename T>
|
||||
template<typename T>
|
||||
struct std::formatter<lt::math::vec3_impl<T>>
|
||||
{
|
||||
constexpr auto parse(std::format_parse_context &context)
|
||||
|
|
|
|||
|
|
@ -1,157 +0,0 @@
|
|||
import test;
|
||||
import math.vec2;
|
||||
import math.vec3;
|
||||
|
||||
using vec2 = ::lt::math::vec2;
|
||||
using vec3 = ::lt::math::vec3;
|
||||
using ivec3 = ::lt::math::vec3_i32;
|
||||
|
||||
Suite static_tests = "vec3_static_checks"_suite = [] {
|
||||
constexpr auto num_elements = lt::math::vec3::num_elements;
|
||||
|
||||
static_assert(num_elements == 3u);
|
||||
static_assert(std::is_same_v<lt::math::vec3, lt::math::vec3_f32>);
|
||||
|
||||
static_assert(sizeof(lt::math::vec3_f32) == sizeof(f32) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec3_f64) == sizeof(f64) * num_elements);
|
||||
|
||||
static_assert(sizeof(lt::math::vec3_i8) == sizeof(i8) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec3_i16) == sizeof(i16) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec3_i32) == sizeof(i32) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec3_i64) == sizeof(i64) * num_elements);
|
||||
|
||||
static_assert(sizeof(lt::math::vec3_u8) == sizeof(u8) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec3_u16) == sizeof(u16) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec3_u32) == sizeof(u32) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec3_u64) == sizeof(u64) * num_elements);
|
||||
};
|
||||
|
||||
Suite raii = "vec3_raii"_suite = [] {
|
||||
Case { "happy paths" } = [] {
|
||||
ignore = vec3 {};
|
||||
ignore = vec3 { 2.0 };
|
||||
ignore = vec3 { 2.0, 4.0, 6.0 };
|
||||
ignore = vec3 { vec2 { 2.0, 4.0 }, 6.0 };
|
||||
ignore = vec3 { 2.0, vec2 { 4.0, 6.0 } };
|
||||
};
|
||||
|
||||
Case { "unhappy paths" } = [] {
|
||||
};
|
||||
|
||||
Case { "many" } = [] {
|
||||
for (auto idx : std::views::iota(0, 1'000'000))
|
||||
{
|
||||
ignore = idx;
|
||||
ignore = vec3 {};
|
||||
ignore = vec3 { 2.0 };
|
||||
ignore = vec3 { 2.0, 4.0, 6.0 };
|
||||
ignore = vec3 { vec2 { 2.0, 4.0 }, 6.0 };
|
||||
ignore = vec3 { 2.0, vec2 { 4.0, 6.0 } };
|
||||
}
|
||||
};
|
||||
|
||||
Case { "post default construct has correct state" } = [] {
|
||||
const auto vec = vec3 {};
|
||||
expect_eq(vec.x, 0.0);
|
||||
expect_eq(vec.y, 0.0);
|
||||
expect_eq(vec.z, 0.0);
|
||||
};
|
||||
|
||||
Case { "post scalar construct has correct state" } = [] {
|
||||
const auto vec = vec3 { 2.0 };
|
||||
expect_eq(vec.x, 2.0);
|
||||
expect_eq(vec.y, 2.0);
|
||||
expect_eq(vec.z, 2.0);
|
||||
};
|
||||
|
||||
Case { "post construct with x,y,z has correct state" } = [] {
|
||||
const auto vec = vec3 { 1.0, 2.0, 3.0 };
|
||||
expect_eq(vec.x, 1.0);
|
||||
expect_eq(vec.y, 2.0);
|
||||
expect_eq(vec.y, 3.0);
|
||||
};
|
||||
|
||||
Case { "post construct with xy,z has correct state" } = [] {
|
||||
const auto vec = vec3 { vec2 { 1.0, 2.0 }, 3.0 };
|
||||
expect_eq(vec.x, 1.0);
|
||||
expect_eq(vec.y, 2.0);
|
||||
expect_eq(vec.z, 3.0);
|
||||
};
|
||||
|
||||
Case { "post construct with x,yz has correct state" } = [] {
|
||||
const auto vec = vec3 { 1.0, vec2 { 2.0, 3.0 } };
|
||||
expect_eq(vec.x, 1.0);
|
||||
expect_eq(vec.y, 2.0);
|
||||
expect_eq(vec.z, 3.0);
|
||||
};
|
||||
};
|
||||
|
||||
Suite arithmetic_operators = "vec3_operators"_suite = [] {
|
||||
Case { "operator ==" } = [] {
|
||||
const auto lhs = vec3 { 1.0, 2.0, 3.0 };
|
||||
|
||||
expect_false(lhs == vec3 { {}, 2.0, 3.0 });
|
||||
expect_false(lhs == vec3 { 1.0, {}, 3.0 });
|
||||
expect_false(lhs == vec3 { 1.0, 2.0, {} });
|
||||
expect_true(lhs == vec3 { 1.0, 2.0, 3.0 });
|
||||
};
|
||||
|
||||
Case { "operator !=" } = [] {
|
||||
const auto lhs = vec3 { 1.0, 2.0, 3.0 };
|
||||
|
||||
expect_true(lhs != vec3 { {}, 2.0, 3.0 });
|
||||
expect_true(lhs != vec3 { 1.0, {}, 3.0 });
|
||||
expect_true(lhs != vec3 { 1.0, 2.0, {} });
|
||||
expect_false(lhs != vec3 { 1.0, 2.0, 3.0 });
|
||||
};
|
||||
|
||||
Case { "operator +" } = [] {
|
||||
const auto lhs = vec3 { 1.0, 2.0, 3.0 };
|
||||
const auto rhs = vec3 { 4.0, 5.0, 6.0 };
|
||||
expect_eq(lhs + rhs, vec3 { 5.0, 7.0, 9.0 });
|
||||
};
|
||||
|
||||
Case { "operator -" } = [] {
|
||||
const auto lhs = vec3 { 1.0, 2.0, 3.0 };
|
||||
const auto rhs = vec3 { 4.0, 5.0, 7.0 };
|
||||
expect_eq(lhs - rhs, vec3 { -2.0, -3.0, -4.0 });
|
||||
};
|
||||
|
||||
Case { "operator *" } = [] {
|
||||
const auto lhs = vec3 { 1.0, 2.0, 3.0 };
|
||||
const auto rhs = vec3 { 4.0, 5.0, 6.0 };
|
||||
expect_eq(lhs * rhs, vec3 { 4.0, 10.0, 18.0 });
|
||||
};
|
||||
|
||||
Case { "operator /" } = [] {
|
||||
const auto lhs = vec3 { 4.0, 10.0, 30.0 };
|
||||
const auto rhs = vec3 { 1.0, 2.0, 5.0 };
|
||||
expect_eq(lhs / rhs, vec3 { 4.0, 5.0, 6.0 });
|
||||
};
|
||||
|
||||
Case { "operator []" } = [] {
|
||||
auto vec = vec3 { 0.0, 1.0, 2.0 };
|
||||
expect_eq(vec[0], 0.0);
|
||||
expect_eq(vec[1], 1.0);
|
||||
expect_eq(vec[2], 2.0);
|
||||
};
|
||||
|
||||
Case { "operator [] const" } = [] {
|
||||
const auto vec = vec3 { 0.0, 1.0, 2.0 };
|
||||
expect_eq(vec[0], 0.0);
|
||||
expect_eq(vec[1], 1.0);
|
||||
expect_eq(vec[2], 2.0);
|
||||
};
|
||||
};
|
||||
|
||||
Suite utilities = "vec3_utilities"_suite = [] {
|
||||
Case { "std::format float" } = [] {
|
||||
auto str = std::format("{}", vec3 { 10.0000f, 30.0005f, 40.00005f });
|
||||
expect_eq(str, "10, 30.0005, 40.00005");
|
||||
};
|
||||
|
||||
Case { "std::format int" } = [] {
|
||||
auto str = std::format("{}", ivec3 { 10, 30, 3'000'000 });
|
||||
expect_eq(str, "10, 30, 3000000");
|
||||
};
|
||||
};
|
||||
|
|
@ -1,19 +1,13 @@
|
|||
export module math.vec4;
|
||||
|
||||
import preliminary;
|
||||
import math.vec2;
|
||||
import math.vec3;
|
||||
import std;
|
||||
|
||||
export namespace lt::math {
|
||||
namespace lt::math {
|
||||
|
||||
template<typename T = f32>
|
||||
requires(std::is_arithmetic_v<T>)
|
||||
export template<typename T = float>
|
||||
struct vec4_impl
|
||||
{
|
||||
using Underlying_T = T;
|
||||
|
||||
static constexpr auto num_elements = 4u;
|
||||
|
||||
constexpr vec4_impl(): x(), y(), z(), w()
|
||||
{
|
||||
}
|
||||
|
|
@ -26,30 +20,6 @@ struct vec4_impl
|
|||
{
|
||||
}
|
||||
|
||||
constexpr vec4_impl(vec2_impl<T> xy, T z, T w): x(xy.x), y(xy.y), z(z), w(w)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr vec4_impl(T x, vec2_impl<T> yz, T w): x(x), y(yz.x), z(yz.y), w(w)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr vec4_impl(T x, T y, vec2_impl<T> zw): x(x), y(y), z(zw.x), w(zw.y)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr vec4_impl(vec2_impl<T> xy, vec2_impl<T> zw): x(xy.x), y(xy.y), z(zw.x), w(zw.y)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr vec4_impl(vec3_impl<T> xyz, T w): x(xyz.x), y(xyz.y), z(xyz.z), w(w)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr vec4_impl(T x, vec3_impl<T> yzw): x(x), y(yzw.x), z(yzw.y), w(yzw.z)
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] auto operator==(const vec4_impl<T> &other) const -> bool
|
||||
{
|
||||
return x == other.x && y == other.y && z == other.z && w == other.w;
|
||||
|
|
@ -60,16 +30,6 @@ struct vec4_impl
|
|||
return !(*this == other);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator+(const vec4_impl<T> &other) const -> vec4_impl
|
||||
{
|
||||
return {
|
||||
x + other.x,
|
||||
y + other.y,
|
||||
z + other.z,
|
||||
w + other.w,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator-(const vec4_impl<T> &other) const -> vec4_impl
|
||||
{
|
||||
return {
|
||||
|
|
@ -80,36 +40,14 @@ struct vec4_impl
|
|||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator*(const vec4_impl<T> &other) const -> vec4_impl
|
||||
[[nodiscard]] constexpr auto operator[](std::size_t idx) -> T &
|
||||
{
|
||||
return {
|
||||
x * other.x,
|
||||
y * other.y,
|
||||
z * other.z,
|
||||
w * other.w,
|
||||
};
|
||||
return values[idx];
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator/(const vec4_impl<T> &other) const -> vec4_impl
|
||||
[[nodiscard]] constexpr auto operator[](std::size_t idx) const -> const T &
|
||||
{
|
||||
return {
|
||||
x / other.x,
|
||||
y / other.y,
|
||||
z / other.z,
|
||||
w / other.w,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator[](u8 idx) -> T &
|
||||
{
|
||||
debug_check(idx < num_elements, "vec4 out of bound access: {}", idx);
|
||||
return ((T *)this)[idx];
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto operator[](u8 idx) const -> const T &
|
||||
{
|
||||
debug_check(idx < num_elements, "vec4 out of bound access: {}", idx);
|
||||
return ((T *)this)[idx];
|
||||
return values[idx];
|
||||
}
|
||||
|
||||
friend auto operator<<(std::ostream &stream, vec4_impl<T> value) -> std::ostream &
|
||||
|
|
@ -118,6 +56,11 @@ struct vec4_impl
|
|||
return stream;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
T x;
|
||||
|
||||
T y;
|
||||
|
|
@ -125,22 +68,29 @@ struct vec4_impl
|
|||
T z;
|
||||
|
||||
T w;
|
||||
};
|
||||
struct
|
||||
{
|
||||
T r;
|
||||
|
||||
T g;
|
||||
|
||||
T b;
|
||||
|
||||
T a;
|
||||
};
|
||||
struct
|
||||
{
|
||||
std::array<T, 4> values;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
using vec4 = vec4_impl<f32>;
|
||||
export using vec4 = vec4_impl<float>;
|
||||
|
||||
using vec4_f32 = vec4;
|
||||
using vec4_f64 = vec4_impl<f64>;
|
||||
export using ivec4 = vec4_impl<std::int32_t>;
|
||||
|
||||
using vec4_i8 = vec4_impl<i8>;
|
||||
using vec4_i16 = vec4_impl<i16>;
|
||||
using vec4_i32 = vec4_impl<i32>;
|
||||
using vec4_i64 = vec4_impl<i64>;
|
||||
|
||||
using vec4_u8 = vec4_impl<u8>;
|
||||
using vec4_u16 = vec4_impl<u16>;
|
||||
using vec4_u32 = vec4_impl<u32>;
|
||||
using vec4_u64 = vec4_impl<u64>;
|
||||
export using uvec4 = vec4_impl<std::uint32_t>;
|
||||
|
||||
} // namespace lt::math
|
||||
|
||||
|
|
|
|||
|
|
@ -1,215 +0,0 @@
|
|||
import test;
|
||||
import math.vec2;
|
||||
import math.vec3;
|
||||
import math.vec4;
|
||||
import logger;
|
||||
|
||||
using vec2 = ::lt::math::vec2;
|
||||
using vec3 = ::lt::math::vec3;
|
||||
using vec4 = ::lt::math::vec4;
|
||||
using ivec4 = ::lt::math::vec4_i32;
|
||||
|
||||
Suite static_tests = "vec4_static_checks"_suite = [] {
|
||||
constexpr auto num_elements = lt::math::vec4::num_elements;
|
||||
|
||||
static_assert(num_elements == 4u);
|
||||
static_assert(std::is_same_v<lt::math::vec4, lt::math::vec4_f32>);
|
||||
|
||||
static_assert(sizeof(lt::math::vec4_f32) == sizeof(f32) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec4_f64) == sizeof(f64) * num_elements);
|
||||
|
||||
static_assert(sizeof(lt::math::vec4_i8) == sizeof(i8) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec4_i16) == sizeof(i16) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec4_i32) == sizeof(i32) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec4_i64) == sizeof(i64) * num_elements);
|
||||
|
||||
static_assert(sizeof(lt::math::vec4_u8) == sizeof(u8) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec4_u16) == sizeof(u16) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec4_u32) == sizeof(u32) * num_elements);
|
||||
static_assert(sizeof(lt::math::vec4_u64) == sizeof(u64) * num_elements);
|
||||
};
|
||||
|
||||
Suite raii = "vec4_raii"_suite = [] {
|
||||
Case { "happy paths" } = [] {
|
||||
ignore = vec4 {};
|
||||
ignore = vec4 { 2.0 };
|
||||
ignore = vec4 { 2.0, 4.0, 6.0, 8.0 };
|
||||
ignore = vec4 { vec2 { 2.0, 4.0 }, 6.0, 8.0 };
|
||||
ignore = vec4 { 2.0, 4.0, vec2 { 6.0, 8.0 } };
|
||||
ignore = vec4 { vec2 { 2.0, 4.0 }, vec2 { 6.0, 8.0 } };
|
||||
ignore = vec4 { vec3 { 2.0, 4.0, 6.0 }, 8.0 };
|
||||
ignore = vec4 { 2.0, vec3 { 4.0, 6.0, 8.0 } };
|
||||
};
|
||||
|
||||
Case { "unhappy paths" } = [] {
|
||||
};
|
||||
|
||||
Case { "many" } = [] {
|
||||
for (auto idx : std::views::iota(0, 1'000'000))
|
||||
{
|
||||
ignore = idx;
|
||||
ignore = vec4 {};
|
||||
ignore = vec4 { 2.0 };
|
||||
ignore = vec4 { 2.0, 4.0, 6.0, 8.0 };
|
||||
ignore = vec4 { vec2 { 2.0, 4.0 }, 6.0, 8.0 };
|
||||
ignore = vec4 { 2.0, 4.0, vec2 { 6.0, 8.0 } };
|
||||
ignore = vec4 { vec2 { 2.0, 4.0 }, vec2 { 6.0, 8.0 } };
|
||||
ignore = vec4 { vec3 { 2.0, 4.0, 6.0 }, 8.0 };
|
||||
ignore = vec4 { 2.0, vec3 { 4.0, 6.0, 8.0 } };
|
||||
}
|
||||
};
|
||||
|
||||
Case { "post default construct has correct state" } = [] {
|
||||
const auto vec = vec4 {};
|
||||
expect_eq(vec.x, 0.0);
|
||||
expect_eq(vec.y, 0.0);
|
||||
expect_eq(vec.z, 0.0);
|
||||
expect_eq(vec.w, 0.0);
|
||||
};
|
||||
|
||||
Case { "post scalar construct has correct state" } = [] {
|
||||
const auto vec = vec4 { 2.0 };
|
||||
expect_eq(vec.x, 2.0);
|
||||
expect_eq(vec.y, 2.0);
|
||||
expect_eq(vec.z, 2.0);
|
||||
expect_eq(vec.w, 2.0);
|
||||
};
|
||||
|
||||
Case { "post construct with x,y,z,w has correct state" } = [] {
|
||||
const auto vec = vec4 { 1.0, 2.0, 3.0, 4.0 };
|
||||
expect_eq(vec.x, 1.0);
|
||||
expect_eq(vec.y, 2.0);
|
||||
expect_eq(vec.y, 3.0);
|
||||
expect_eq(vec.z, 4.0);
|
||||
};
|
||||
|
||||
Case { "post construct with xy,z,w has correct state" } = [] {
|
||||
const auto vec = vec4 { vec2 { 1.0, 2.0 }, 3.0, 4.0 };
|
||||
expect_eq(vec.x, 1.0);
|
||||
expect_eq(vec.y, 2.0);
|
||||
expect_eq(vec.z, 3.0);
|
||||
expect_eq(vec.w, 4.0);
|
||||
};
|
||||
|
||||
Case { "post construct with x,y,zw has correct state" } = [] {
|
||||
const auto vec = vec4 { 1.0, 2.0, vec2 { 3.0, 4.0 } };
|
||||
expect_eq(vec.x, 1.0);
|
||||
expect_eq(vec.y, 2.0);
|
||||
expect_eq(vec.z, 3.0);
|
||||
expect_eq(vec.w, 4.0);
|
||||
};
|
||||
|
||||
Case { "post construct with x,yz,w has correct state" } = [] {
|
||||
const auto vec = vec4 { 1.0, vec2 { 2.0, 3.0 }, 4.0 };
|
||||
expect_eq(vec.x, 1.0);
|
||||
expect_eq(vec.y, 2.0);
|
||||
expect_eq(vec.z, 3.0);
|
||||
expect_eq(vec.w, 4.0);
|
||||
};
|
||||
|
||||
Case { "post construct with x,y,zw has correct state" } = [] {
|
||||
const auto vec = vec4 { 1.0, 2.0, vec2 { 3.0, 4.0 } };
|
||||
expect_eq(vec.x, 1.0);
|
||||
expect_eq(vec.y, 2.0);
|
||||
expect_eq(vec.z, 3.0);
|
||||
expect_eq(vec.w, 4.0);
|
||||
};
|
||||
|
||||
Case { "post construct with xy,zw has correct state" } = [] {
|
||||
const auto vec = vec4 { vec2 { 1.0, 2.0 }, vec2 { 3.0, 4.0 } };
|
||||
expect_eq(vec.x, 1.0);
|
||||
expect_eq(vec.y, 2.0);
|
||||
expect_eq(vec.z, 3.0);
|
||||
expect_eq(vec.w, 4.0);
|
||||
};
|
||||
|
||||
Case { "post construct with xyz,w has correct state" } = [] {
|
||||
const auto vec = vec4 { vec3 { 1.0, 2.0, 3.0 }, 4.0 };
|
||||
expect_eq(vec.x, 1.0);
|
||||
expect_eq(vec.y, 2.0);
|
||||
expect_eq(vec.z, 3.0);
|
||||
expect_eq(vec.w, 4.0);
|
||||
};
|
||||
|
||||
Case { "post construct with x,yzw has correct state" } = [] {
|
||||
const auto vec = vec4 { 1.0, vec3 { 2.0, 3.0, 4.0 } };
|
||||
expect_eq(vec.x, 1.0);
|
||||
expect_eq(vec.y, 2.0);
|
||||
expect_eq(vec.z, 3.0);
|
||||
expect_eq(vec.w, 4.0);
|
||||
};
|
||||
};
|
||||
|
||||
Suite arithmetic_operators = "vec4_operators"_suite = [] {
|
||||
Case { "operator ==" } = [] {
|
||||
const auto lhs = vec4 { 1.0, 2.0, 3.0, 4.0 };
|
||||
|
||||
expect_false(lhs == vec4 { {}, 2.0, 3.0, 4.0 });
|
||||
expect_false(lhs == vec4 { 1.0, {}, 3.0, 4.0 });
|
||||
expect_false(lhs == vec4 { 1.0, 2.0, {}, 4.0 });
|
||||
expect_false(lhs == vec4 { 1.0, 2.0, 3.0, {} });
|
||||
expect_true(lhs == vec4 { 1.0, 2.0, 3.0, 4.0 });
|
||||
};
|
||||
|
||||
Case { "operator !=" } = [] {
|
||||
const auto lhs = vec4 { 1.0, 2.0, 3.0, 4.0 };
|
||||
|
||||
expect_true(lhs != vec4 { {}, 2.0, 3.0, 4.0 });
|
||||
expect_true(lhs != vec4 { 1.0, {}, 3.0, 4.0 });
|
||||
expect_true(lhs != vec4 { 1.0, 2.0, {}, 4.0 });
|
||||
expect_true(lhs != vec4 { 1.0, 2.0, 3.0, {} });
|
||||
expect_false(lhs != vec4 { 1.0, 2.0, 3.0, 4.0 });
|
||||
};
|
||||
|
||||
Case { "operator +" } = [] {
|
||||
const auto lhs = vec4 { 1.0, 2.0, 3.0, 4.0 };
|
||||
const auto rhs = vec4 { 5.0, 6.0, 7.0, 8.0 };
|
||||
expect_eq(lhs + rhs, vec4 { 6.0, 8.0, 10.0, 12.0 });
|
||||
};
|
||||
|
||||
Case { "operator -" } = [] {
|
||||
const auto lhs = vec4 { 1.0, 2.0, 3.0, 4.0 };
|
||||
const auto rhs = vec4 { 5.0, 10.0, 15.0, 20.0 };
|
||||
expect_eq(lhs - rhs, vec4 { -4.0, -8.0, -12.0, -16.0 });
|
||||
};
|
||||
|
||||
Case { "operator *" } = [] {
|
||||
const auto lhs = vec4 { 1.0, 2.0, 3.0, 4.0 };
|
||||
const auto rhs = vec4 { 5.0, 6.0, 7.0, 8.0 };
|
||||
expect_eq(lhs * rhs, vec4 { 5.0, 12.0, 21.0, 32.0 });
|
||||
};
|
||||
|
||||
Case { "operator /" } = [] {
|
||||
const auto lhs = vec4 { 5.0, 6.0, 30.0, 8.0 };
|
||||
const auto rhs = vec4 { 1.0, 2.0, 3.0, 4.0 };
|
||||
expect_eq(lhs / rhs, vec4 { 5.0, 3.0, 10.0, 2.0 });
|
||||
};
|
||||
|
||||
Case { "operator []" } = [] {
|
||||
auto vec = vec4 { 0.0, 1.0, 2.0, 3.0 };
|
||||
expect_eq(vec[0], 0.0);
|
||||
expect_eq(vec[1], 1.0);
|
||||
expect_eq(vec[2], 2.0);
|
||||
expect_eq(vec[3], 3.0);
|
||||
};
|
||||
|
||||
Case { "operator [] const" } = [] {
|
||||
const auto vec = vec4 { 0.0, 1.0, 2.0, 3.0 };
|
||||
expect_eq(vec[0], 0.0);
|
||||
expect_eq(vec[1], 1.0);
|
||||
expect_eq(vec[2], 2.0);
|
||||
expect_eq(vec[3], 3.0);
|
||||
};
|
||||
};
|
||||
|
||||
Suite utilities = "vec4_utilities"_suite = [] {
|
||||
Case { "std::format float" } = [] {
|
||||
auto str = std::format("{}", vec4 { 10.0000f, 30.0005f, 40.00005f, 0.0 });
|
||||
expect_eq(str, "10, 30.0005, 40.00005, 0");
|
||||
};
|
||||
|
||||
Case { "std::format int" } = [] {
|
||||
auto str = std::format("{}", ivec4 { 10, 30, 3'000'000, 13 });
|
||||
expect_eq(str, "10, 30, 3000000, 13");
|
||||
};
|
||||
};
|
||||
|
|
@ -1,15 +1,13 @@
|
|||
export module memory.null_on_move;
|
||||
|
||||
import logger;
|
||||
|
||||
import preliminary;
|
||||
import std;
|
||||
|
||||
namespace lt::memory {
|
||||
|
||||
/** Holds an `Underlying_T`, assigns it to `null_value` when this object is moved.
|
||||
*
|
||||
* @note For avoiding the need to explicitly implement the move constructor for objects that hold
|
||||
* non-raii-handles (eg. Vulkan, Wayland).
|
||||
* Vulkan handles. But may serve other purposes, hence why I kept the implementation generic.
|
||||
*/
|
||||
export template<typename Underlying_T, Underlying_T null_value = nullptr>
|
||||
class NullOnMove
|
||||
|
|
@ -81,6 +79,11 @@ public:
|
|||
return m_value;
|
||||
}
|
||||
|
||||
operator std::uint64_t() const
|
||||
{
|
||||
return (std::uint64_t)m_value;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get() -> Underlying_T &
|
||||
{
|
||||
return m_value;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
export module memory.reference;
|
||||
|
||||
import preliminary;
|
||||
import std;
|
||||
|
||||
namespace lt::memory {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
export module memory.scope;
|
||||
|
||||
import preliminary;
|
||||
import std;
|
||||
|
||||
namespace lt::memory {
|
||||
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ import mirror.system;
|
|||
import renderer.factory;
|
||||
|
||||
/** The ultimate entrypoint. */
|
||||
auto main(i32 argc, char *argv[]) -> i32
|
||||
auto main(int argc, char *argv[]) -> std::int32_t
|
||||
{
|
||||
try
|
||||
{
|
||||
ignore = argc;
|
||||
ignore = argv;
|
||||
std::ignore = argc;
|
||||
std::ignore = argv;
|
||||
|
||||
auto application = lt::memory::create_scope<lt::Mirror>();
|
||||
if (!application)
|
||||
|
|
|
|||
227
modules/mirror/layers/editor_layer.cppm
Normal file
227
modules/mirror/layers/editor_layer.cppm
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
#pragma once
|
||||
|
||||
#include <app/layer.hpp>
|
||||
#include <imgui.h>
|
||||
#include <math/vec2.hpp>
|
||||
#include <memory/reference.hpp>
|
||||
#include <mirror/panels/asset_browser.hpp>
|
||||
#include <mirror/panels/properties.hpp>
|
||||
#include <mirror/panels/scene_hierarchy.hpp>
|
||||
#include <renderer/texture.hpp>
|
||||
|
||||
namespace lt {
|
||||
|
||||
class Scene;
|
||||
|
||||
class EditorLayer: public Layer
|
||||
{
|
||||
public:
|
||||
EditorLayer(const std::string &name);
|
||||
|
||||
~EditorLayer() override;
|
||||
|
||||
EditorLayer(EditorLayer &&) = delete;
|
||||
|
||||
EditorLayer(const EditorLayer &) = delete;
|
||||
|
||||
auto operator=(EditorLayer &&) const -> EditorLayer & = delete;
|
||||
|
||||
auto operator=(const EditorLayer &) const -> EditorLayer & = delete;
|
||||
|
||||
void on_update(float delta_time) override;
|
||||
|
||||
void on_render() override;
|
||||
|
||||
void on_user_interface_update() override;
|
||||
|
||||
private:
|
||||
std::string m_scene_dir;
|
||||
|
||||
math::vec2 m_direction;
|
||||
|
||||
float m_speed = 1000.0f;
|
||||
|
||||
memory::Ref<Scene> m_scene;
|
||||
|
||||
memory::Ref<SceneHierarchyPanel> m_sceneHierarchyPanel;
|
||||
|
||||
memory::Ref<PropertiesPanel> m_properties_panel;
|
||||
|
||||
memory::Ref<AssetBrowserPanel> m_content_browser_panel;
|
||||
|
||||
memory::Ref<Framebuffer> m_framebuffer;
|
||||
|
||||
Entity m_camera_entity;
|
||||
|
||||
ImVec2 m_available_content_region_prev;
|
||||
};
|
||||
|
||||
} // namespace lt
|
||||
|
||||
|
||||
#include <app/application.hpp>
|
||||
#include <asset_manager/asset_manager.hpp>
|
||||
#include <camera/component.hpp>
|
||||
#include <ecs/components.hpp>
|
||||
#include <ecs/registry.hpp>
|
||||
#include <ecs/serializer.hpp>
|
||||
#include <input/input.hpp>
|
||||
#include <input/key_codes.hpp>
|
||||
#include <math/vec4.hpp>
|
||||
#include <memory/reference.hpp>
|
||||
#include <mirror/layers/editor_layer.hpp>
|
||||
#include <renderer/framebuffer.hpp>
|
||||
#include <renderer/texture.hpp>
|
||||
#include <ui/ui.hpp>
|
||||
|
||||
namespace lt {
|
||||
|
||||
EditorLayer::EditorLayer(const std::string &name)
|
||||
: Layer(name)
|
||||
, m_scene_dir("")
|
||||
, m_direction { 0.0, 0.0 }
|
||||
{
|
||||
m_scene = memory::create_ref<Scene>();
|
||||
|
||||
m_properties_panel = memory::create_ref<PropertiesPanel>();
|
||||
m_sceneHierarchyPanel = memory::create_ref<SceneHierarchyPanel>(m_scene, m_properties_panel);
|
||||
m_content_browser_panel = memory::create_ref<AssetBrowserPanel>(m_scene);
|
||||
|
||||
m_framebuffer = Framebuffer::create(
|
||||
{
|
||||
.width = 1,
|
||||
.height = 1,
|
||||
.samples = 1,
|
||||
},
|
||||
GraphicsContext::get_shared_context()
|
||||
);
|
||||
|
||||
if (m_scene_dir.empty())
|
||||
{
|
||||
m_camera_entity = m_scene->create_entity("Camera");
|
||||
m_camera_entity.add_component<CameraComponent>(SceneCamera(), true);
|
||||
|
||||
AssetManager::load_texture("Awesomeface", "data/assets/textures/awesomeface.asset");
|
||||
|
||||
auto entity = Entity { m_scene->create_entity("Awesomeface", {}) };
|
||||
entity.add_component<SpriteRendererComponent>(
|
||||
AssetManager::get_texture("Awesomeface"),
|
||||
math::vec4 { 0.0f, 1.0f, 1.0f, 1.0f }
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto serializer = SceneSerializer { m_scene };
|
||||
ensure(serializer.deserialize(m_scene_dir), "Failed to de-serialize: {}", m_scene_dir);
|
||||
|
||||
// m_camera_entity = m_scene->GetEntityByTag("Game Camera");
|
||||
}
|
||||
}
|
||||
|
||||
EditorLayer::~EditorLayer()
|
||||
{
|
||||
if (!m_scene_dir.empty())
|
||||
{
|
||||
auto serializer = SceneSerializer { m_scene };
|
||||
serializer.serialize(m_scene_dir);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorLayer::on_update(float delta_time)
|
||||
{
|
||||
m_scene->on_update(delta_time);
|
||||
|
||||
if (Input::get_keyboard_key(Key::A))
|
||||
{
|
||||
m_direction.x = -1.0;
|
||||
}
|
||||
else if (Input::get_keyboard_key(Key::D))
|
||||
{
|
||||
m_direction.x = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_direction.x = 0.0;
|
||||
}
|
||||
|
||||
if (Input::get_keyboard_key(Key::S))
|
||||
{
|
||||
m_direction.y = -1.0;
|
||||
}
|
||||
else if (Input::get_keyboard_key(Key::W))
|
||||
{
|
||||
m_direction.y = 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_direction.y = 0.0;
|
||||
}
|
||||
|
||||
auto &translation = m_camera_entity.get_component<TransformComponent>().translation;
|
||||
auto velocity = m_direction * m_speed * delta_time;
|
||||
translation = translation * math::vec3 { velocity.x, velocity.y, 0.0f };
|
||||
|
||||
if (Input::get_keyboard_key(Key::Escape))
|
||||
{
|
||||
Application::quit();
|
||||
}
|
||||
}
|
||||
|
||||
void EditorLayer::on_render()
|
||||
{
|
||||
m_scene->on_render(m_framebuffer);
|
||||
}
|
||||
|
||||
void EditorLayer::on_user_interface_update()
|
||||
{
|
||||
UserInterface::dockspace_begin();
|
||||
ImGui::ShowDemoWindow();
|
||||
|
||||
if (ImGui::Begin("Game"))
|
||||
{
|
||||
Input::receive_game_events(ImGui::IsWindowFocused());
|
||||
auto available_region = ImGui::GetContentRegionAvail();
|
||||
|
||||
if (m_available_content_region_prev.x != available_region.x
|
||||
|| m_available_content_region_prev.y != available_region.y)
|
||||
{
|
||||
m_framebuffer->resize(
|
||||
math::uvec2 {
|
||||
static_cast<uint32_t>(available_region.x),
|
||||
static_cast<uint32_t>(available_region.y),
|
||||
}
|
||||
);
|
||||
auto &camera = m_camera_entity.get_component<CameraComponent>().camera;
|
||||
camera.set_viewport_size(
|
||||
static_cast<uint32_t>(available_region.x),
|
||||
static_cast<uint32_t>(available_region.y)
|
||||
);
|
||||
|
||||
m_available_content_region_prev = available_region;
|
||||
}
|
||||
|
||||
if (GraphicsContext::get_graphics_api() == GraphicsAPI::DirectX)
|
||||
{
|
||||
ImGui::Image(m_framebuffer->get_color_attachment(), available_region);
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui::Image(
|
||||
m_framebuffer->get_color_attachment(),
|
||||
available_region,
|
||||
ImVec2(0, 1),
|
||||
ImVec2(1, 0)
|
||||
);
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
// Panels
|
||||
m_sceneHierarchyPanel->on_user_interface_update();
|
||||
m_properties_panel->on_user_interface_update();
|
||||
m_content_browser_panel->on_user_interface_update();
|
||||
|
||||
UserInterface::dockspace_end();
|
||||
}
|
||||
|
||||
} // namespace lt
|
||||
0
modules/mirror/layers/editor_layer.test.cpp
Normal file
0
modules/mirror/layers/editor_layer.test.cpp
Normal file
200
modules/mirror/panels/asset_browser.cppm
Normal file
200
modules/mirror/panels/asset_browser.cppm
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory/reference.hpp>
|
||||
#include <mirror/panels/panel.hpp>
|
||||
#include <renderer/texture.hpp>
|
||||
|
||||
namespace lt {
|
||||
|
||||
class Scene;
|
||||
|
||||
class AssetBrowserPanel: public Panel
|
||||
{
|
||||
public:
|
||||
AssetBrowserPanel(memory::Ref<Scene> active_scene);
|
||||
|
||||
void on_user_interface_update();
|
||||
|
||||
private:
|
||||
enum class AssetType
|
||||
{
|
||||
none = 0,
|
||||
scene,
|
||||
directory,
|
||||
text,
|
||||
image,
|
||||
};
|
||||
|
||||
std::filesystem::path m_current_directory;
|
||||
|
||||
const std::filesystem::path m_assets_path;
|
||||
|
||||
float m_file_size = 128.0f;
|
||||
|
||||
float m_file_padding = 8.0f;
|
||||
|
||||
memory::Ref<Scene> m_active_scene;
|
||||
|
||||
memory::Ref<Texture> m_directory_texture;
|
||||
|
||||
memory::Ref<Texture> m_scene_texture;
|
||||
|
||||
memory::Ref<Texture> m_image_texture;
|
||||
|
||||
memory::Ref<Texture> m_text_texture;
|
||||
};
|
||||
|
||||
} // namespace lt
|
||||
#include <asset_manager/asset_manager.hpp>
|
||||
#include <ecs/registry.hpp>
|
||||
#include <ecs/serializer.hpp>
|
||||
#include <imgui.h>
|
||||
#include <memory/reference.hpp>
|
||||
#include <mirror/panels/asset_browser.hpp>
|
||||
#include <renderer/texture.hpp>
|
||||
|
||||
namespace lt {
|
||||
|
||||
AssetBrowserPanel::AssetBrowserPanel(memory::Ref<Scene> active_scene)
|
||||
: m_current_directory("./data/assets")
|
||||
, m_assets_path("./data/assets")
|
||||
, m_active_scene(std::move(active_scene))
|
||||
{
|
||||
AssetManager::load_texture("_Assets_Directory", "data/engine/icons/asset/dir.asset");
|
||||
AssetManager::load_texture("_Assets_Scene", "data/engine/icons/asset/scene.asset");
|
||||
AssetManager::load_texture("_Assets_Image", "data/engine/icons/asset/img.asset");
|
||||
AssetManager::load_texture("_Assets_Text", "data/engine/icons/asset/txt.asset");
|
||||
|
||||
m_directory_texture = AssetManager::get_texture("_Assets_Directory");
|
||||
m_scene_texture = AssetManager::get_texture("_Assets_Scene");
|
||||
m_image_texture = AssetManager::get_texture("_Assets_Image");
|
||||
m_text_texture = AssetManager::get_texture("_Assets_Text");
|
||||
}
|
||||
|
||||
void AssetBrowserPanel::on_user_interface_update()
|
||||
{
|
||||
ImGui::Begin("Content Browser");
|
||||
|
||||
// Parent directory button
|
||||
if (m_current_directory != std::filesystem::path("data/assets"))
|
||||
{
|
||||
if (ImGui::Button(" <-- "))
|
||||
{
|
||||
m_current_directory = m_current_directory.parent_path();
|
||||
}
|
||||
}
|
||||
|
||||
const auto available_region = ImGui::GetContentRegionAvail();
|
||||
const auto cell_size = m_file_size + m_file_padding;
|
||||
const auto column_count = std::clamp(
|
||||
static_cast<uint32_t>(std::floor(available_region.x / cell_size)),
|
||||
1u,
|
||||
64u
|
||||
);
|
||||
|
||||
if (ImGui::BeginTable("ContentBrowser", static_cast<int>(column_count)))
|
||||
{
|
||||
m_directory_texture->bind(0u);
|
||||
for (const auto &directory_entry : std::filesystem::directory_iterator(m_current_directory))
|
||||
{
|
||||
const auto &path = directory_entry.path();
|
||||
auto extension = directory_entry.path().extension().string();
|
||||
|
||||
auto asset_type = AssetType {};
|
||||
|
||||
if (extension.empty())
|
||||
{
|
||||
asset_type = AssetType::directory;
|
||||
}
|
||||
else if (extension == ".txt" || extension == ".glsl")
|
||||
{
|
||||
asset_type = AssetType::text;
|
||||
}
|
||||
else if (extension == ".png")
|
||||
{
|
||||
asset_type = AssetType::image;
|
||||
}
|
||||
else if (extension == ".scene")
|
||||
{
|
||||
asset_type = AssetType::scene;
|
||||
}
|
||||
else
|
||||
{
|
||||
asset_type = AssetType::none;
|
||||
}
|
||||
|
||||
// Extension not supported
|
||||
if (asset_type == AssetType::none)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Button
|
||||
const auto path_str = path.string();
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::PushID(path_str.c_str());
|
||||
switch (asset_type)
|
||||
{
|
||||
// Directory
|
||||
case AssetType::directory:
|
||||
if (ImGui::ImageButton(
|
||||
path_str.c_str(),
|
||||
m_directory_texture->get_texture(),
|
||||
ImVec2(m_file_size, m_file_size)
|
||||
))
|
||||
{
|
||||
m_current_directory /= path.filename();
|
||||
}
|
||||
break;
|
||||
|
||||
// Scene
|
||||
case AssetType::scene:
|
||||
if (ImGui::ImageButton(
|
||||
path_str.c_str(),
|
||||
m_scene_texture->get_texture(),
|
||||
ImVec2(m_file_size, m_file_size)
|
||||
))
|
||||
{
|
||||
auto serializer = SceneSerializer { m_active_scene };
|
||||
log::info("Attempting to deserialize: {}", path.string());
|
||||
serializer.deserialize(path.string());
|
||||
}
|
||||
break;
|
||||
|
||||
// Image
|
||||
case AssetType::image:
|
||||
if (ImGui::ImageButton(
|
||||
path_str.c_str(),
|
||||
m_image_texture->get_texture(),
|
||||
ImVec2(m_file_size, m_file_size)
|
||||
))
|
||||
{
|
||||
}
|
||||
break;
|
||||
|
||||
// Text
|
||||
case AssetType::text:
|
||||
if (ImGui::ImageButton(
|
||||
path_str.c_str(),
|
||||
m_text_texture->get_texture(),
|
||||
ImVec2(m_file_size, m_file_size)
|
||||
))
|
||||
{
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
default: break;
|
||||
}
|
||||
// Label
|
||||
ImGui::TextUnformatted(std::format("{}", path.filename().string()).c_str());
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
} // namespace lt
|
||||
0
modules/mirror/panels/asset_browser.test.cpp
Normal file
0
modules/mirror/panels/asset_browser.test.cpp
Normal file
13
modules/mirror/panels/panel.cppm
Normal file
13
modules/mirror/panels/panel.cppm
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
namespace lt {
|
||||
|
||||
class Panel
|
||||
{
|
||||
public:
|
||||
Panel() = default;
|
||||
|
||||
virtual ~Panel() = default;
|
||||
};
|
||||
|
||||
} // namespace lt
|
||||
345
modules/mirror/panels/properties.cppm
Normal file
345
modules/mirror/panels/properties.cppm
Normal file
|
|
@ -0,0 +1,345 @@
|
|||
#pragma once
|
||||
|
||||
#include <ecs/entity.hpp>
|
||||
#include <math/vec3.hpp>
|
||||
#include <mirror/panels/panel.hpp>
|
||||
|
||||
namespace lt {
|
||||
|
||||
class PropertiesPanel: public Panel
|
||||
{
|
||||
public:
|
||||
PropertiesPanel() = default;
|
||||
|
||||
void on_user_interface_update();
|
||||
|
||||
void set_entity_context(const Entity &entity);
|
||||
|
||||
private:
|
||||
void draw_vec3_control(
|
||||
const std::string &label,
|
||||
math::vec3 &values,
|
||||
float reset_value = 0.0f,
|
||||
float column_width = 100.0f
|
||||
);
|
||||
|
||||
template<typename ComponentType, typename UIFunction>
|
||||
void draw_component(const std::string &name, Entity entity, UIFunction function);
|
||||
|
||||
Entity m_entity_context;
|
||||
};
|
||||
|
||||
|
||||
} // namespace lt
|
||||
#include <asset_manager/asset_manager.hpp>
|
||||
#include <camera/component.hpp>
|
||||
#include <ecs/components.hpp>
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <math/trig.hpp>
|
||||
#include <mirror/panels/properties.hpp>
|
||||
|
||||
namespace lt {
|
||||
|
||||
void PropertiesPanel::on_user_interface_update()
|
||||
{
|
||||
ImGui::Begin("Properties");
|
||||
|
||||
if (m_entity_context.is_valid())
|
||||
{
|
||||
if (m_entity_context.has_component<TagComponent>())
|
||||
{
|
||||
auto &tagComponent = m_entity_context.get_component<TagComponent>();
|
||||
|
||||
auto buffer = std::array<char, 256> {};
|
||||
memset(buffer.data(), 0, buffer.size());
|
||||
strncpy(buffer.data(), tagComponent.tag.c_str(), buffer.size());
|
||||
|
||||
if (ImGui::InputText("##Tag", buffer.data(), buffer.size()))
|
||||
{
|
||||
tagComponent.tag = buffer.data();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::PushItemWidth(-1);
|
||||
|
||||
if (ImGui::Button("Add component"))
|
||||
{
|
||||
ImGui::OpenPopup("Components");
|
||||
}
|
||||
|
||||
if (ImGui::BeginPopup("Components"))
|
||||
{
|
||||
if (ImGui::Selectable(
|
||||
"SpriteRenderer",
|
||||
false,
|
||||
m_entity_context.has_component<SpriteRendererComponent>() ?
|
||||
ImGuiSelectableFlags_Disabled :
|
||||
ImGuiSelectableFlags {}
|
||||
))
|
||||
{
|
||||
m_entity_context.add_component<SpriteRendererComponent>(
|
||||
lt::AssetManager::get_texture("awesomeface")
|
||||
);
|
||||
}
|
||||
|
||||
if (ImGui::Selectable(
|
||||
"Camera",
|
||||
false,
|
||||
m_entity_context.has_component<CameraComponent>() ?
|
||||
ImGuiSelectableFlags_Disabled :
|
||||
ImGuiSelectableFlags {}
|
||||
))
|
||||
{
|
||||
m_entity_context.add_component<CameraComponent>();
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
|
||||
draw_component<TransformComponent>(
|
||||
"Transform Component",
|
||||
m_entity_context,
|
||||
[&](auto &transformComponent) {
|
||||
draw_vec3_control("Translation", transformComponent.translation);
|
||||
}
|
||||
);
|
||||
|
||||
draw_component<SpriteRendererComponent>(
|
||||
"SpriteRenderer Component",
|
||||
m_entity_context,
|
||||
[&](auto &spriteRendererComponent) {
|
||||
ImGui::ColorEdit4("Color", &spriteRendererComponent.tint[0]);
|
||||
}
|
||||
);
|
||||
|
||||
draw_component<CameraComponent>(
|
||||
"Camera Component",
|
||||
m_entity_context,
|
||||
[&](auto &cameraComponent) {
|
||||
auto &camera = cameraComponent.camera;
|
||||
|
||||
auto projection_type = camera.get_projection_type();
|
||||
auto projection_types_str = std::array<const char *, 2> {
|
||||
"Orthographic",
|
||||
"Perspective",
|
||||
};
|
||||
|
||||
if (ImGui::BeginCombo("ProjectionType", projection_types_str[(int)projection_type]))
|
||||
{
|
||||
for (auto idx = 0; idx < 2; idx++)
|
||||
{
|
||||
const auto is_selected = static_cast<int>(projection_type) == idx;
|
||||
if (ImGui::Selectable(projection_types_str[idx], is_selected))
|
||||
{
|
||||
projection_type = static_cast<SceneCamera::ProjectionType>(idx);
|
||||
camera.set_projection_type(projection_type);
|
||||
}
|
||||
|
||||
if (is_selected)
|
||||
{
|
||||
ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
|
||||
if (projection_type == SceneCamera::ProjectionType::Orthographic)
|
||||
{
|
||||
auto ortho_size = float {};
|
||||
auto near_plane = float {};
|
||||
auto far_plane = float {};
|
||||
|
||||
ortho_size = camera.get_orthographic_size();
|
||||
near_plane = camera.get_orthographic_near_plane();
|
||||
far_plane = camera.get_orthographic_far_plane();
|
||||
|
||||
if (ImGui::DragFloat("Orthographic Size", &ortho_size))
|
||||
{
|
||||
camera.set_orthographic_size(ortho_size);
|
||||
}
|
||||
|
||||
if (ImGui::DragFloat("Near Plane", &near_plane))
|
||||
{
|
||||
camera.set_orthographic_near_plane(near_plane);
|
||||
}
|
||||
|
||||
if (ImGui::DragFloat("Far Plane", &far_plane))
|
||||
{
|
||||
camera.set_orthographic_far_plane(far_plane);
|
||||
}
|
||||
}
|
||||
|
||||
else // perspective
|
||||
{
|
||||
auto vertical_fov = float {};
|
||||
auto near_plane = float {};
|
||||
auto far_plane = float {};
|
||||
|
||||
vertical_fov = math::degrees(camera.get_perspective_vertical_fov());
|
||||
near_plane = camera.get_perspective_near_plane();
|
||||
far_plane = camera.get_perspective_far_plane();
|
||||
|
||||
if (ImGui::DragFloat("Vertical FOV", &vertical_fov))
|
||||
{
|
||||
camera.set_perspective_vertical_fov(math::radians(vertical_fov));
|
||||
}
|
||||
|
||||
if (ImGui::DragFloat("Near Plane", &near_plane))
|
||||
{
|
||||
camera.set_perspective_near_plane(near_plane);
|
||||
}
|
||||
|
||||
if (ImGui::DragFloat("Far Plane", &far_plane))
|
||||
{
|
||||
camera.set_perspective_far_plane(far_plane);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void PropertiesPanel::set_entity_context(const Entity &entity)
|
||||
{
|
||||
m_entity_context = entity;
|
||||
}
|
||||
|
||||
void PropertiesPanel::draw_vec3_control(
|
||||
const std::string &label,
|
||||
math::vec3 &values,
|
||||
float reset_value,
|
||||
float column_width
|
||||
)
|
||||
{
|
||||
auto &io = ImGui::GetIO();
|
||||
|
||||
auto *bold_font = io.Fonts->Fonts[0];
|
||||
|
||||
ImGui::Columns(2);
|
||||
ImGui::SetColumnWidth(0, column_width);
|
||||
ImGui::TextUnformatted(label.c_str());
|
||||
ImGui::NextColumn();
|
||||
|
||||
ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth());
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2 { 0, 0 });
|
||||
|
||||
auto line_height = GImGui->Font->LegacySize + GImGui->Style.FramePadding.y * 2.0f;
|
||||
auto button_size = ImVec2 { line_height + 3.0f, line_height };
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.8f, 0.1f, 0.15f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.9f, 0.2f, 0.2f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.8f, 0.1f, 0.15f, 1.0f));
|
||||
ImGui::PushFont(bold_font);
|
||||
if (ImGui::Button("X", button_size))
|
||||
{
|
||||
values.x = reset_value;
|
||||
}
|
||||
ImGui::PopFont();
|
||||
ImGui::PopStyleColor(3);
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::DragFloat("##X", &values.x, 0.1f);
|
||||
ImGui::PopItemWidth();
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.7f, 0.2f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.3f, 0.8f, 0.3f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.2f, 0.7f, 0.2f, 1.0f));
|
||||
ImGui::PushFont(bold_font);
|
||||
if (ImGui::Button("Y", button_size))
|
||||
{
|
||||
values.y = reset_value;
|
||||
}
|
||||
ImGui::PopFont();
|
||||
ImGui::PopStyleColor(3);
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::DragFloat("##Y", &values.y, 0.1f);
|
||||
ImGui::PopItemWidth();
|
||||
ImGui::SameLine();
|
||||
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.1f, 0.25f, 0.8f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.2f, 0.35f, 0.9f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.1f, 0.25f, 0.8f, 1.0f));
|
||||
ImGui::PushFont(bold_font);
|
||||
if (ImGui::Button("Z", button_size))
|
||||
{
|
||||
values.z = reset_value;
|
||||
}
|
||||
ImGui::PopFont();
|
||||
ImGui::PopStyleColor(3);
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::DragFloat("##Z", &values.z, 0.1f);
|
||||
ImGui::PopItemWidth();
|
||||
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::Columns(1);
|
||||
}
|
||||
|
||||
|
||||
template<typename ComponentType, typename UIFunction>
|
||||
void PropertiesPanel::draw_component(
|
||||
const std::string &name,
|
||||
Entity entity,
|
||||
UIFunction user_interface_function
|
||||
)
|
||||
{
|
||||
if (!entity.has_component<ComponentType>())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto &component = entity.get_component<ComponentType>();
|
||||
|
||||
auto available_region = ImGui::GetContentRegionAvail();
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
auto flags = ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_SpanAvailWidth
|
||||
| ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_AllowItemOverlap
|
||||
| ImGuiTreeNodeFlags_FramePadding;
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, { 4, 4 });
|
||||
auto lineHeight = GImGui->Font->LegacySize + GImGui->Style.FramePadding.y * 2.0f;
|
||||
ImGui::Separator();
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
if (ImGui::TreeNodeEx((void *)typeid(ComponentType).hash_code(), flags, name.c_str()))
|
||||
{
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
ImGui::SameLine(available_region.x - lineHeight * .5f);
|
||||
if (ImGui::Button("+", { lineHeight, lineHeight }))
|
||||
{
|
||||
ImGui::OpenPopup("ComponentSettings");
|
||||
}
|
||||
|
||||
if (ImGui::BeginPopup("ComponentSettings"))
|
||||
{
|
||||
if (ImGui::Selectable("Remove component"))
|
||||
{
|
||||
entity.remove_component<ComponentType>();
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
user_interface_function(component);
|
||||
ImGui::TreePop();
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lt
|
||||
0
modules/mirror/panels/properties.test.cpp
Normal file
0
modules/mirror/panels/properties.test.cpp
Normal file
124
modules/mirror/panels/scene_hierarchy.cpp
Normal file
124
modules/mirror/panels/scene_hierarchy.cpp
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
#pragma once
|
||||
|
||||
#include <ecs/entity.hpp>
|
||||
#include <ecs/registry.hpp>
|
||||
#include <memory/reference.hpp>
|
||||
#include <mirror/panels/panel.hpp>
|
||||
|
||||
namespace lt {
|
||||
|
||||
class PropertiesPanel;
|
||||
|
||||
class SceneHierarchyPanel: public Panel
|
||||
{
|
||||
public:
|
||||
SceneHierarchyPanel();
|
||||
|
||||
SceneHierarchyPanel(
|
||||
memory::Ref<Scene> context,
|
||||
memory::Ref<PropertiesPanel> properties_panel = nullptr
|
||||
);
|
||||
|
||||
void on_user_interface_update();
|
||||
|
||||
void set_context(
|
||||
memory::Ref<Scene> context,
|
||||
memory::Ref<PropertiesPanel> properties_panel = nullptr
|
||||
);
|
||||
|
||||
private:
|
||||
void draw_node(Entity entity, const std::string &label);
|
||||
|
||||
memory::Ref<Scene> m_context;
|
||||
|
||||
memory::Ref<PropertiesPanel> m_properties_panel_context;
|
||||
|
||||
Entity m_selection_context;
|
||||
};
|
||||
|
||||
} // namespace lt
|
||||
#include <ecs/components.hpp>
|
||||
#include <imgui.h>
|
||||
#include <memory/reference.hpp>
|
||||
#include <mirror/panels/properties.hpp>
|
||||
#include <mirror/panels/scene_hierarchy.hpp>
|
||||
|
||||
namespace lt {
|
||||
|
||||
SceneHierarchyPanel::SceneHierarchyPanel(): m_context(nullptr), m_properties_panel_context(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
SceneHierarchyPanel::SceneHierarchyPanel(
|
||||
memory::Ref<Scene> context,
|
||||
memory::Ref<PropertiesPanel> properties_panel
|
||||
)
|
||||
: m_context(std::move(context))
|
||||
, m_properties_panel_context(std::move(properties_panel))
|
||||
{
|
||||
}
|
||||
|
||||
void SceneHierarchyPanel::on_user_interface_update()
|
||||
{
|
||||
if (m_context)
|
||||
{
|
||||
ImGui::Begin("Hierarchy");
|
||||
|
||||
for (auto entityID : m_context->m_registry.view<TagComponent>())
|
||||
{
|
||||
auto entity = Entity {
|
||||
static_cast<entt::entity>(entityID),
|
||||
m_context.get(),
|
||||
};
|
||||
|
||||
const auto &tag = entity.get_component<TagComponent>();
|
||||
draw_node(entity, tag);
|
||||
};
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void SceneHierarchyPanel::set_context(
|
||||
memory::Ref<Scene> context,
|
||||
memory::Ref<PropertiesPanel> properties_panel
|
||||
)
|
||||
{
|
||||
if (properties_panel)
|
||||
{
|
||||
m_properties_panel_context = std::move(properties_panel);
|
||||
}
|
||||
|
||||
m_context = std::move(context);
|
||||
}
|
||||
|
||||
void SceneHierarchyPanel::draw_node(Entity entity, const std::string &label)
|
||||
{
|
||||
auto flags = ImGuiTreeNodeFlags {
|
||||
// NOLINTNEXTLINE
|
||||
(m_selection_context == entity ? ImGuiTreeNodeFlags_Selected : ImGuiTreeNodeFlags {})
|
||||
| ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_SpanFullWidth
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
const auto expanded = ImGui::TreeNodeEx(
|
||||
std::bit_cast<void *>(static_cast<uint64_t>(entity)),
|
||||
flags,
|
||||
"%s",
|
||||
label.c_str()
|
||||
);
|
||||
|
||||
if (ImGui::IsItemClicked())
|
||||
{
|
||||
m_selection_context = entity;
|
||||
m_properties_panel_context->set_entity_context(entity);
|
||||
}
|
||||
|
||||
if (expanded)
|
||||
{
|
||||
ImGui::TextUnformatted("TEST_OPENED_TREE!");
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lt
|
||||
0
modules/mirror/panels/scene_hierarchy.test.cpp
Normal file
0
modules/mirror/panels/scene_hierarchy.test.cpp
Normal file
|
|
@ -1,6 +1,4 @@
|
|||
export module mirror.system;
|
||||
|
||||
import preliminary;
|
||||
import math.vec3;
|
||||
import camera.components;
|
||||
import surface.requests;
|
||||
|
|
@ -24,6 +22,7 @@ import app;
|
|||
import app.system;
|
||||
import ecs.entity;
|
||||
import ecs.registry;
|
||||
import std;
|
||||
|
||||
namespace lt {
|
||||
|
||||
|
|
@ -34,11 +33,11 @@ void renderer_callback(
|
|||
std::any &user_data
|
||||
)
|
||||
{
|
||||
ignore = message_severity;
|
||||
ignore = message_type;
|
||||
ignore = user_data;
|
||||
std::ignore = message_severity;
|
||||
std::ignore = message_type;
|
||||
std::ignore = user_data;
|
||||
|
||||
log::trace("< Renderer > ==> {}", std::string { data.message });
|
||||
log::debug("RENDERER CALLBACK: {}", std::string { data.message });
|
||||
}
|
||||
|
||||
class MirrorSystem: public lt::app::ISystem
|
||||
|
|
@ -46,8 +45,8 @@ class MirrorSystem: public lt::app::ISystem
|
|||
public:
|
||||
MirrorSystem(
|
||||
memory::Ref<ecs::Registry> registry,
|
||||
size_t quit_action_key,
|
||||
std::array<size_t, 4ul> debug_action_keys
|
||||
std::size_t quit_action_key,
|
||||
std::array<std::size_t, 4ul> debug_action_keys
|
||||
)
|
||||
: m_registry(std::move(registry))
|
||||
, m_quit_action_key(quit_action_key)
|
||||
|
|
@ -85,7 +84,7 @@ public:
|
|||
for (auto &[id, camera] :
|
||||
m_registry->view<lt::camera::components::PerspectiveCamera>())
|
||||
{
|
||||
camera.vertical_fov += (static_cast<f32>(tick.delta_time.count()) * 40.0f);
|
||||
camera.vertical_fov += (static_cast<float>(tick.delta_time.count()) * 40.0f);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -129,9 +128,9 @@ public:
|
|||
private:
|
||||
memory::Ref<ecs::Registry> m_registry;
|
||||
|
||||
size_t m_quit_action_key;
|
||||
std::size_t m_quit_action_key;
|
||||
|
||||
std::array<size_t, 4ul> m_debug_action_keys {};
|
||||
std::array<std::size_t, 4ul> m_debug_action_keys {};
|
||||
|
||||
app::TickResult m_last_tick_result {};
|
||||
};
|
||||
|
|
@ -177,41 +176,31 @@ public:
|
|||
);
|
||||
|
||||
auto &input = m_editor_registry->add<InputComponent>(m_window, {});
|
||||
auto quit_action_key = input.add_action(
|
||||
input::InputAction {
|
||||
auto quit_action_key = input.add_action(input::InputAction {
|
||||
.name = "quit",
|
||||
.trigger = input::Trigger { .mapped_keycode = Key::q },
|
||||
}
|
||||
);
|
||||
.trigger = input::Trigger { .mapped_keycode = Key::Q },
|
||||
});
|
||||
|
||||
auto debug_action_keys = std::array<size_t, 4ul> {};
|
||||
debug_action_keys[0] = input.add_action(
|
||||
input::InputAction {
|
||||
auto debug_action_keys = std::array<std::size_t, 4ul> {};
|
||||
debug_action_keys[0] = input.add_action(input::InputAction {
|
||||
.name = "debug_1",
|
||||
.trigger = input::Trigger { .mapped_keycode = Key::digit_1 },
|
||||
}
|
||||
);
|
||||
.trigger = input::Trigger { .mapped_keycode = Key::D1 },
|
||||
});
|
||||
|
||||
debug_action_keys[1] = input.add_action(
|
||||
input::InputAction {
|
||||
debug_action_keys[1] = input.add_action(input::InputAction {
|
||||
.name = "debug_2",
|
||||
.trigger = input::Trigger { .mapped_keycode = Key::digit_2 },
|
||||
}
|
||||
);
|
||||
.trigger = input::Trigger { .mapped_keycode = Key::D2 },
|
||||
});
|
||||
|
||||
debug_action_keys[2] = input.add_action(
|
||||
input::InputAction {
|
||||
debug_action_keys[2] = input.add_action(input::InputAction {
|
||||
.name = "debug_3",
|
||||
.trigger = input::Trigger { .mapped_keycode = Key::digit_3 },
|
||||
}
|
||||
);
|
||||
.trigger = input::Trigger { .mapped_keycode = Key::D3 },
|
||||
});
|
||||
|
||||
debug_action_keys[3] = input.add_action(
|
||||
input::InputAction {
|
||||
debug_action_keys[3] = input.add_action(input::InputAction {
|
||||
.name = "debug_4",
|
||||
.trigger = input::Trigger { .mapped_keycode = Key::digit_4 },
|
||||
}
|
||||
);
|
||||
.trigger = input::Trigger { .mapped_keycode = Key::D4 },
|
||||
});
|
||||
|
||||
m_input_system = memory::create_ref<input::System>(m_editor_registry);
|
||||
m_mirror_system = memory::create_ref<MirrorSystem>(
|
||||
|
|
@ -255,7 +244,7 @@ public:
|
|||
m_editor_registry->add(
|
||||
m_camera_id,
|
||||
camera::components::PerspectiveCamera {
|
||||
.vertical_fov = math::to_radians(90.0f),
|
||||
.vertical_fov = math::radians(90.0f),
|
||||
.near_plane = 0.1f,
|
||||
.far_plane = 30.0,
|
||||
.aspect_ratio = 1.0f,
|
||||
|
|
|
|||
0
modules/mirror/system.test.cpp
Normal file
0
modules/mirror/system.test.cpp
Normal file
|
|
@ -1,85 +0,0 @@
|
|||
export module preliminary.assertions;
|
||||
|
||||
import preliminary.build_constants;
|
||||
import std;
|
||||
|
||||
///////////////////////////////////////
|
||||
// ----------* INTERFACE *--------- //
|
||||
/////////////////////////////////////
|
||||
/** To be used for ensuring a condition holds true, throws otherwise. */
|
||||
export template<typename Expression_T, typename... Args_T>
|
||||
struct ensure
|
||||
{
|
||||
ensure(
|
||||
const Expression_T &expression,
|
||||
std::format_string<Args_T...> fmt,
|
||||
Args_T &&...args,
|
||||
const std::source_location &location = std::source_location::current()
|
||||
);
|
||||
};
|
||||
|
||||
/** To be used for costly checks that should be stripped in release builds. */
|
||||
export template<typename Expression_T, typename... Args_T>
|
||||
struct debug_check
|
||||
{
|
||||
debug_check(
|
||||
const Expression_T &expression,
|
||||
std::format_string<Args_T...> fmt,
|
||||
Args_T &&...args,
|
||||
const std::source_location &location = std::source_location::current()
|
||||
);
|
||||
};
|
||||
|
||||
export template<typename Expression_T, typename... Args_T>
|
||||
ensure(Expression_T, std::format_string<Args_T...>, Args_T &&...)
|
||||
-> ensure<Expression_T, Args_T...>;
|
||||
|
||||
export template<typename Expression_T, typename... Args_T>
|
||||
debug_check(Expression_T, std::format_string<Args_T...>, Args_T &&...)
|
||||
-> debug_check<Expression_T, Args_T...>;
|
||||
|
||||
///////////////////////////////////////
|
||||
// * IMPLEMENTATION -- TEMPLATES * //
|
||||
/////////////////////////////////////
|
||||
template<typename Expression_T, typename... Args_T>
|
||||
ensure<Expression_T, Args_T...>::ensure(
|
||||
const Expression_T &expression,
|
||||
std::format_string<Args_T...> fmt,
|
||||
Args_T &&...args,
|
||||
const std::source_location &location
|
||||
)
|
||||
{
|
||||
if (!static_cast<bool>(expression))
|
||||
{
|
||||
throw std::runtime_error { std::format(
|
||||
"exception: {}\nlocation: {}:{}",
|
||||
std::format(fmt, std::forward<Args_T>(args)...),
|
||||
location.file_name(),
|
||||
location.line()
|
||||
) };
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Expression_T, typename... Args_T>
|
||||
debug_check<Expression_T, Args_T...>::debug_check(
|
||||
const Expression_T &expression,
|
||||
std::format_string<Args_T...> fmt,
|
||||
Args_T &&...args,
|
||||
const std::source_location &location
|
||||
)
|
||||
{
|
||||
if constexpr (build_constants::build_type != build_constants::BuildType::debug)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!static_cast<bool>(expression))
|
||||
{
|
||||
throw std::runtime_error { std::format(
|
||||
"exception: {}\nlocation: {}:{}",
|
||||
std::format(fmt, std::forward<Args_T>(args)...),
|
||||
location.file_name(),
|
||||
location.line()
|
||||
) };
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
export module preliminary.fundumental_types;
|
||||
|
||||
import std;
|
||||
|
||||
export using byte = ::std::byte;
|
||||
|
||||
export using u8 = ::std::uint8_t;
|
||||
export using u16 = ::std::uint16_t;
|
||||
export using u32 = ::std::uint32_t;
|
||||
export using u64 = ::std::uint64_t;
|
||||
|
||||
export using i8 = ::std::int8_t;
|
||||
export using i16 = ::std::int16_t;
|
||||
export using i32 = ::std::int32_t;
|
||||
export using i64 = ::std::int64_t;
|
||||
|
||||
export using f32 = float;
|
||||
export using f64 = double;
|
||||
|
||||
export using size_t = ::std::size_t;
|
||||
|
||||
export using ::std::ignore;
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
export module preliminary;
|
||||
export import preliminary.fundumental_types;
|
||||
export import preliminary.assertions;
|
||||
export import preliminary.build_constants;
|
||||
|
||||
// std should always be available...
|
||||
export import std;
|
||||
|
|
@ -3,22 +3,118 @@ import renderer.test_utils;
|
|||
|
||||
using enum ::lt::renderer::IDebugger::MessageSeverity;
|
||||
using enum ::lt::renderer::IBuffer::Usage;
|
||||
using ::std::this_thread::sleep_for;
|
||||
|
||||
// TODO(Light): finish these (and many other) tests...
|
||||
Suite raii = "buffer_raii"_suite = [] {
|
||||
Case { "happy paths" } = [] {
|
||||
Case { "happy path won't throw" } = [] {
|
||||
auto fixture = FixtureDeviceSwapchain {};
|
||||
};
|
||||
sleep_for(std::chrono::milliseconds { 500u });
|
||||
|
||||
Case { "unhappy paths" } = [] {
|
||||
for (auto idx = 0; idx <= std::to_underlying(staging); ++idx)
|
||||
{
|
||||
ignore = lt::renderer::create_buffer(
|
||||
lt::renderer::Api::vulkan,
|
||||
fixture.device(),
|
||||
fixture.gpu(),
|
||||
lt::renderer::IBuffer::CreateInfo {
|
||||
.usage = static_cast<lt::renderer::IBuffer::Usage>(idx),
|
||||
.size = 1000u,
|
||||
.debug_name = "",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
expect_false(fixture.has_any_messages_of(error));
|
||||
expect_false(fixture.has_any_messages_of(warning));
|
||||
};
|
||||
|
||||
Case { "unhappy path throws" } = [] {
|
||||
auto fixture = FixtureDeviceSwapchain {};
|
||||
};
|
||||
sleep_for(std::chrono::milliseconds { 500u });
|
||||
|
||||
auto info = lt::renderer::IBuffer::CreateInfo {
|
||||
.usage = vertex,
|
||||
.size = 10000u,
|
||||
.debug_name = "",
|
||||
};
|
||||
|
||||
expect_throw([&] {
|
||||
ignore = lt::renderer::create_buffer(
|
||||
lt::renderer::Api::vulkan,
|
||||
nullptr,
|
||||
fixture.gpu(),
|
||||
info
|
||||
);
|
||||
});
|
||||
|
||||
expect_throw([&] {
|
||||
ignore = lt::renderer::create_buffer(lt::renderer::Api::vulkan, fixture.device(), nullptr, info);
|
||||
});
|
||||
|
||||
expect_throw([&, info] mutable {
|
||||
info.size = 0;
|
||||
ignore = lt::renderer::create_buffer(
|
||||
lt::renderer::Api::vulkan,
|
||||
fixture.device(),
|
||||
fixture.gpu(),
|
||||
info
|
||||
);
|
||||
});
|
||||
|
||||
expect_throw([&] {
|
||||
ignore = lt::renderer::create_buffer(
|
||||
lt::renderer::Api::direct_x,
|
||||
fixture.device(),
|
||||
fixture.gpu(),
|
||||
info
|
||||
);
|
||||
});
|
||||
|
||||
expect_throw([&] {
|
||||
ignore = lt::renderer::create_buffer(
|
||||
lt::renderer::Api::metal,
|
||||
fixture.device(),
|
||||
fixture.gpu(),
|
||||
info
|
||||
);
|
||||
});
|
||||
|
||||
expect_throw([&] {
|
||||
ignore = lt::renderer::create_buffer(
|
||||
lt::renderer::Api::none,
|
||||
fixture.device(),
|
||||
fixture.gpu(),
|
||||
info
|
||||
);
|
||||
});
|
||||
|
||||
/** Make sure the default-case was OK */
|
||||
ignore = lt::renderer::create_buffer(lt::renderer::Api::vulkan, fixture.device(), fixture.gpu(), info);
|
||||
|
||||
expect_false(fixture.has_any_messages_of(error));
|
||||
expect_false(fixture.has_any_messages_of(warning));
|
||||
};
|
||||
};
|
||||
|
||||
Suite mapping = "buffer_mapping"_suite = [] {
|
||||
Case { "mapping" } = [] {
|
||||
auto fixture = FixtureDeviceSwapchain {};
|
||||
|
||||
constexpr auto size = 1000u;
|
||||
|
||||
auto buffer = lt::renderer::create_buffer(
|
||||
lt::renderer::Api::vulkan,
|
||||
fixture.device(),
|
||||
fixture.gpu(),
|
||||
lt::renderer::IBuffer::CreateInfo {
|
||||
.usage = staging,
|
||||
.size = size,
|
||||
.debug_name = "",
|
||||
}
|
||||
);
|
||||
|
||||
auto map = buffer->map();
|
||||
expect_eq(map.size(), size);
|
||||
expect_not_nullptr(map.data());
|
||||
|
||||
expect_false(fixture.has_any_messages_of(error));
|
||||
expect_false(fixture.has_any_messages_of(warning));
|
||||
};
|
||||
sleep_for(std::chrono::milliseconds { 500u });
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,9 +1,18 @@
|
|||
import renderer.frontend;
|
||||
import renderer.test_utils;
|
||||
|
||||
void noop_callback(
|
||||
lt::renderer::IDebugger::MessageSeverity message_severity,
|
||||
lt::renderer::IDebugger::MessageType message_type,
|
||||
const lt::renderer::IDebugger::MessageData &data,
|
||||
std::any &user_data
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
Suite raii = "debugger_raii"_suite = [] {
|
||||
Case { "happy paths" } = [] {
|
||||
ignore = lt::renderer::create_debugger(
|
||||
Case { "happy path won't throw" } = [] {
|
||||
std::ignore = lt::renderer::create_debugger(
|
||||
lt::renderer::Api::vulkan,
|
||||
lt::renderer::get_instance(lt::renderer::Api::vulkan),
|
||||
lt::renderer::IDebugger::CreateInfo {
|
||||
|
|
@ -14,9 +23,9 @@ Suite raii = "debugger_raii"_suite = [] {
|
|||
);
|
||||
};
|
||||
|
||||
Case { "unhappy paths" } = [] {
|
||||
Case { "unhappy path throws" } = [] {
|
||||
expect_throw([] {
|
||||
ignore = lt::renderer::create_debugger(
|
||||
std::ignore = lt::renderer::create_debugger(
|
||||
lt::renderer::Api::vulkan,
|
||||
lt::renderer::get_instance(lt::renderer::Api::vulkan),
|
||||
lt::renderer::IDebugger::CreateInfo {
|
||||
|
|
@ -28,7 +37,7 @@ Suite raii = "debugger_raii"_suite = [] {
|
|||
});
|
||||
|
||||
expect_throw([] {
|
||||
ignore = lt::renderer::create_debugger(
|
||||
std::ignore = lt::renderer::create_debugger(
|
||||
lt::renderer::Api::vulkan,
|
||||
lt::renderer::get_instance(lt::renderer::Api::vulkan),
|
||||
lt::renderer::IDebugger::CreateInfo {
|
||||
|
|
@ -40,7 +49,7 @@ Suite raii = "debugger_raii"_suite = [] {
|
|||
});
|
||||
|
||||
expect_throw([] {
|
||||
ignore = lt::renderer::create_debugger(
|
||||
std::ignore = lt::renderer::create_debugger(
|
||||
lt::renderer::Api::vulkan,
|
||||
lt::renderer::get_instance(lt::renderer::Api::vulkan),
|
||||
lt::renderer::IDebugger::CreateInfo {
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@ import renderer.frontend;
|
|||
import renderer.test_utils;
|
||||
|
||||
Suite raii = "device_raii"_suite = [] {
|
||||
Case { "happy paths" } = [] {
|
||||
Case { "happy path won't throw" } = [] {
|
||||
auto fixture = Fixture_SurfaceGpu {};
|
||||
ignore = lt::renderer::create_device(constants::api, fixture.gpu(), fixture.surface());
|
||||
std::ignore = lt::renderer::create_device(constants::api, fixture.gpu(), fixture.surface());
|
||||
};
|
||||
|
||||
Case { "unhappy paths" } = [] {
|
||||
Case { "unhappy path throws" } = [] {
|
||||
auto fixture = Fixture_SurfaceGpu {};
|
||||
|
||||
expect_throw([&] {
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ import renderer.frontend;
|
|||
import renderer.test_utils;
|
||||
|
||||
Suite raii = "pass_raii"_suite = [] {
|
||||
Case { "happy paths" } = [] {
|
||||
Case { "happy path won't throw" } = [] {
|
||||
auto fixture = FixtureDeviceSwapchain {};
|
||||
ignore = lt::renderer::create_pass(
|
||||
std::ignore = lt::renderer::create_pass(
|
||||
constants::api,
|
||||
fixture.device(),
|
||||
lt::assets::ShaderAsset { "./data/test_assets/triangle.vert.asset" },
|
||||
|
|
@ -17,10 +17,10 @@ Suite raii = "pass_raii"_suite = [] {
|
|||
);
|
||||
};
|
||||
|
||||
Case { "unhappy paths" } = [] {
|
||||
Case { "unhappy path throws" } = [] {
|
||||
auto fixture = FixtureDeviceSwapchain {};
|
||||
expect_throw([&] {
|
||||
ignore = lt::renderer::create_pass(
|
||||
std::ignore = lt::renderer::create_pass(
|
||||
constants::api,
|
||||
nullptr,
|
||||
lt::assets::ShaderAsset { "./data/test_assets/triangle.vert.asset" },
|
||||
|
|
@ -29,7 +29,7 @@ Suite raii = "pass_raii"_suite = [] {
|
|||
});
|
||||
|
||||
expect_throw([&] {
|
||||
ignore = lt::renderer::create_pass(
|
||||
std::ignore = lt::renderer::create_pass(
|
||||
lt::renderer::Api::none,
|
||||
fixture.device(),
|
||||
lt::assets::ShaderAsset { "./data/test_assets/triangle.vert.asset" },
|
||||
|
|
@ -38,7 +38,7 @@ Suite raii = "pass_raii"_suite = [] {
|
|||
});
|
||||
|
||||
expect_throw([&] {
|
||||
ignore = lt::renderer::create_pass(
|
||||
std::ignore = lt::renderer::create_pass(
|
||||
lt::renderer::Api::direct_x,
|
||||
fixture.device(),
|
||||
lt::assets::ShaderAsset { "./data/test_assets/triangle.vert.asset" },
|
||||
|
|
@ -47,7 +47,7 @@ Suite raii = "pass_raii"_suite = [] {
|
|||
});
|
||||
|
||||
expect_throw([&] {
|
||||
ignore = lt::renderer::create_pass(
|
||||
std::ignore = lt::renderer::create_pass(
|
||||
lt::renderer::Api::metal,
|
||||
fixture.device(),
|
||||
lt::assets::ShaderAsset { "./data/test_assets/triangle.vert.asset" },
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import renderer.frontend;
|
|||
import renderer.test_utils;
|
||||
|
||||
Suite raii = "renderer_raii"_suite = [] {
|
||||
Case { "happy paths" } = [] {
|
||||
Case { "happy path won't throw" } = [] {
|
||||
auto fixture = FixtureDeviceSwapchain {};
|
||||
ignore = lt::renderer::create_renderer(
|
||||
constants::api,
|
||||
|
|
@ -18,7 +18,7 @@ Suite raii = "renderer_raii"_suite = [] {
|
|||
);
|
||||
};
|
||||
|
||||
Case { "unhappy paths" } = [] {
|
||||
Case { "unhappy path throws" } = [] {
|
||||
auto fixture = FixtureDeviceSwapchain {};
|
||||
|
||||
expect_throw([&] {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import renderer.frontend;
|
|||
import renderer.test_utils;
|
||||
|
||||
Suite raii = "surface"_suite = [] {
|
||||
Case { "happy paths" } = [&] {
|
||||
Case { "happy path won't throw" } = [&] {
|
||||
auto fixture = Fixture_SurfaceSystem {};
|
||||
|
||||
const auto surface = lt::renderer::create_surface(
|
||||
|
|
@ -16,13 +16,13 @@ Suite raii = "surface"_suite = [] {
|
|||
expect_eq(y, constants::resolution.y);
|
||||
};
|
||||
|
||||
Case { "unhappy paths" } = [&] {
|
||||
Case { "unhappy path throws" } = [&] {
|
||||
auto registry = lt::memory::create_ref<lt::ecs::Registry>();
|
||||
auto entity = lt::ecs::Entity { registry, registry->create_entity() };
|
||||
auto system = lt::surface::System(registry);
|
||||
|
||||
expect_throw([&] {
|
||||
ignore = lt::renderer::create_surface(
|
||||
std::ignore = lt::renderer::create_surface(
|
||||
constants::api,
|
||||
lt::renderer::get_instance(constants::api),
|
||||
entity
|
||||
|
|
@ -38,11 +38,11 @@ Suite raii = "surface"_suite = [] {
|
|||
);
|
||||
|
||||
expect_throw([&] {
|
||||
ignore = lt::renderer::create_surface(constants::api, nullptr, entity);
|
||||
std::ignore = lt::renderer::create_surface(constants::api, nullptr, entity);
|
||||
});
|
||||
|
||||
expect_throw([&] {
|
||||
ignore = lt::renderer::create_surface(
|
||||
std::ignore = lt::renderer::create_surface(
|
||||
lt::renderer::Api::none,
|
||||
lt::renderer::get_instance(constants::api),
|
||||
entity
|
||||
|
|
@ -50,7 +50,7 @@ Suite raii = "surface"_suite = [] {
|
|||
});
|
||||
|
||||
expect_throw([&] {
|
||||
ignore = lt::renderer::create_surface(
|
||||
std::ignore = lt::renderer::create_surface(
|
||||
lt::renderer::Api::direct_x,
|
||||
lt::renderer::get_instance(constants::api),
|
||||
entity
|
||||
|
|
@ -58,7 +58,7 @@ Suite raii = "surface"_suite = [] {
|
|||
});
|
||||
|
||||
expect_throw([&] {
|
||||
ignore = lt::renderer::create_surface(
|
||||
std::ignore = lt::renderer::create_surface(
|
||||
lt::renderer::Api::metal,
|
||||
lt::renderer::get_instance(constants::api),
|
||||
entity
|
||||
|
|
@ -66,7 +66,7 @@ Suite raii = "surface"_suite = [] {
|
|||
});
|
||||
|
||||
// Ensure base creation info is non-throwing
|
||||
ignore = lt::renderer::create_surface(
|
||||
std::ignore = lt::renderer::create_surface(
|
||||
constants::api,
|
||||
lt::renderer::get_instance(constants::api),
|
||||
entity
|
||||
|
|
|
|||
|
|
@ -1,24 +1,25 @@
|
|||
import preliminary;
|
||||
import time;
|
||||
import renderer.frontend;
|
||||
import renderer.test_utils;
|
||||
|
||||
struct SurfaceContext
|
||||
{
|
||||
lt::surface::System system;
|
||||
|
||||
lt::ecs::Entity entity;
|
||||
};
|
||||
|
||||
struct RendererContext
|
||||
{
|
||||
lt::memory::Ref<lt::ecs::Registry> registry;
|
||||
|
||||
lt::renderer::System system;
|
||||
};
|
||||
|
||||
|
||||
Suite raii = "system_raii"_suite = [] {
|
||||
Case { "happy paths" } = [] {
|
||||
Case { "happy path won't throw" } = [] {
|
||||
ignore = Fixture_RendererSystem {};
|
||||
};
|
||||
|
||||
Case { "happy path has no errors" } = [] {
|
||||
auto fixture = Fixture_RendererSystem {};
|
||||
expect_false(fixture.has_any_messages_of(lt::renderer::IDebugger::MessageSeverity::error));
|
||||
expect_false(
|
||||
|
|
@ -26,7 +27,7 @@ Suite raii = "system_raii"_suite = [] {
|
|||
);
|
||||
};
|
||||
|
||||
Case { "unhappy paths" } = [] {
|
||||
Case { "unhappy path throws" } = [] {
|
||||
auto fixture = Fixture_SurfaceSystem {};
|
||||
auto empty_entity = lt::ecs::Entity { fixture.registry(),
|
||||
fixture.registry()->create_entity() };
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
export module renderer.test_utils;
|
||||
|
||||
export import test;
|
||||
export import logger;
|
||||
export import surface.system;
|
||||
export import ecs.registry;
|
||||
export import renderer.factory;
|
||||
export import test.test;
|
||||
export import test.expects;
|
||||
export import memory.reference;
|
||||
export import renderer.frontend;
|
||||
export import renderer.system;
|
||||
|
|
@ -11,21 +13,32 @@ export import math.vec2;
|
|||
export import math.vec3;
|
||||
export import math.vec4;
|
||||
export import math.mat4;
|
||||
export import std;
|
||||
|
||||
export using ::lt::test::Case;
|
||||
export using ::lt::test::expect_eq;
|
||||
export using ::lt::test::expect_false;
|
||||
export using ::lt::test::expect_not_nullptr;
|
||||
export using ::lt::test::expect_throw;
|
||||
export using ::lt::test::operator""_suite;
|
||||
export using ::lt::test::expect_true;
|
||||
export using ::lt::test::Suite;
|
||||
export using ::std::ignore;
|
||||
|
||||
export namespace constants {
|
||||
|
||||
constexpr auto api = lt::renderer::Api::vulkan;
|
||||
constexpr auto resolution = lt::math::vec2_u32 { 800u, 600u };
|
||||
constexpr auto frames_in_flight = u32 { 3u };
|
||||
constexpr auto resolution = lt::math::uvec2 { 800u, 600u };
|
||||
constexpr auto frames_in_flight = std::uint32_t { 3u };
|
||||
|
||||
} // namespace constants
|
||||
|
||||
|
||||
export void noop_callback(
|
||||
lt::renderer::IDebugger::MessageSeverity,
|
||||
lt::renderer::IDebugger::MessageType,
|
||||
const lt::renderer::IDebugger::MessageData &,
|
||||
std::any &
|
||||
void noop_messenger_callback(
|
||||
lt::renderer::IDebugger::MessageSeverity severity,
|
||||
lt::renderer::IDebugger::MessageType type,
|
||||
const lt::renderer::IDebugger::MessageData &data,
|
||||
std::any &user_data
|
||||
)
|
||||
{
|
||||
}
|
||||
|
|
@ -56,7 +69,7 @@ public:
|
|||
.debug_callback_info = {
|
||||
.severities = lt::renderer::IDebugger::MessageSeverity::all,
|
||||
.types= lt::renderer::IDebugger::MessageType::all,
|
||||
.callback = noop_callback,
|
||||
.callback = noop_messenger_callback,
|
||||
.user_data = {},
|
||||
}
|
||||
} ;
|
||||
|
|
@ -144,7 +157,7 @@ public:
|
|||
}
|
||||
|
||||
[[nodiscard]] auto has_any_messages_of(lt::renderer::IDebugger ::MessageSeverity severity) const
|
||||
-> u32
|
||||
-> std::uint32_t
|
||||
{
|
||||
return m_user_data->m_severity_counter.contains(severity);
|
||||
}
|
||||
|
|
@ -159,9 +172,9 @@ private:
|
|||
{
|
||||
// I know this makes the tests too verbose...
|
||||
// but makes it easier to figure out what the problem is when things fail on ci
|
||||
lt::log::trace("vulkan: {}", std::string { data.message });
|
||||
ignore = data;
|
||||
ignore = type;
|
||||
lt::log::debug("vulkan: {}", data.message);
|
||||
std::ignore = data;
|
||||
std::ignore = type;
|
||||
|
||||
auto *fixture = std::any_cast<UserData *>(user_data);
|
||||
fixture->m_has_any_messages = true;
|
||||
|
|
@ -170,7 +183,8 @@ private:
|
|||
|
||||
struct UserData
|
||||
{
|
||||
std::unordered_map<lt::renderer::IDebugger::MessageSeverity, u32> m_severity_counter;
|
||||
std::unordered_map<lt::renderer::IDebugger::MessageSeverity, std::uint32_t>
|
||||
m_severity_counter;
|
||||
|
||||
bool m_has_any_messages {};
|
||||
};
|
||||
|
|
@ -207,13 +221,17 @@ public:
|
|||
return m_system;
|
||||
}
|
||||
|
||||
auto device() -> lt::renderer::IDevice &
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] auto has_any_messages() const -> bool
|
||||
{
|
||||
return m_user_data->m_has_any_messages;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto has_any_messages_of(lt::renderer::IDebugger ::MessageSeverity severity) const
|
||||
-> u32
|
||||
-> std::uint32_t
|
||||
{
|
||||
return m_user_data->m_severity_counter.contains(severity);
|
||||
}
|
||||
|
|
@ -228,10 +246,10 @@ private:
|
|||
{
|
||||
// I know this makes the tests too verbose...
|
||||
// but makes it easier to figure out what the problem is when things fail on ci
|
||||
lt::log::trace("vulkan: {}", std::string { data.message });
|
||||
lt::log::trace("vulkan: {}", data.message);
|
||||
|
||||
ignore = data;
|
||||
ignore = type;
|
||||
std::ignore = data;
|
||||
std::ignore = type;
|
||||
|
||||
auto *fixture = std::any_cast<UserData *>(user_data);
|
||||
fixture->m_has_any_messages = true;
|
||||
|
|
@ -240,7 +258,8 @@ private:
|
|||
|
||||
struct UserData
|
||||
{
|
||||
std::unordered_map<lt::renderer::IDebugger::MessageSeverity, u32> m_severity_counter;
|
||||
std::unordered_map<lt::renderer::IDebugger::MessageSeverity, std::uint32_t>
|
||||
m_severity_counter;
|
||||
|
||||
bool m_has_any_messages {};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,20 +1,19 @@
|
|||
export module renderer.components;
|
||||
|
||||
import preliminary;
|
||||
import assets.shader;
|
||||
import math.vec3;
|
||||
import memory.reference;
|
||||
import std;
|
||||
|
||||
export namespace lt::renderer::components {
|
||||
|
||||
enum class VertexFormat : u8
|
||||
enum class VertexFormat : std::uint8_t
|
||||
{
|
||||
r32_g32_b32_sfloat,
|
||||
|
||||
r32_g32_sfloat,
|
||||
};
|
||||
|
||||
enum class VertexInputRate : u8
|
||||
enum class VertexInputRate : std::uint8_t
|
||||
{
|
||||
per_vertex,
|
||||
|
||||
|
|
@ -23,20 +22,20 @@ enum class VertexInputRate : u8
|
|||
|
||||
struct VertexInputAttributeDescriptipn
|
||||
{
|
||||
u32 location;
|
||||
std::uint32_t location;
|
||||
|
||||
u32 binding;
|
||||
std::uint32_t binding;
|
||||
|
||||
u32 offset;
|
||||
std::uint32_t offset;
|
||||
|
||||
VertexFormat format;
|
||||
};
|
||||
|
||||
struct VertexInputBindingDescription
|
||||
{
|
||||
u32 binding;
|
||||
std::uint32_t binding;
|
||||
|
||||
u32 stride;
|
||||
std::uint32_t stride;
|
||||
};
|
||||
|
||||
/** Requires a math::components::Transform component on the same entity to be functional. */
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@ export module renderer.data;
|
|||
|
||||
import math.mat4;
|
||||
|
||||
export namespace lt::renderer {
|
||||
|
||||
struct FrameConstants
|
||||
namespace lt::renderer {
|
||||
|
||||
export struct FrameConstants
|
||||
{
|
||||
math::mat4 view_projection;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ export import renderer.vk.gpu;
|
|||
export import renderer.vk.debugger;
|
||||
export import renderer.vk.surface;
|
||||
export import memory.scope;
|
||||
export import debug.assertions;
|
||||
export import ecs.entity;
|
||||
|
||||
import preliminary;
|
||||
export import std;
|
||||
|
||||
export namespace lt::renderer {
|
||||
|
||||
|
|
@ -48,7 +48,7 @@ export namespace lt::renderer {
|
|||
IGpu *gpu,
|
||||
IDevice *device,
|
||||
ISwapchain *swapchain,
|
||||
u32 max_frames_in_flight
|
||||
std::uint32_t max_frames_in_flight
|
||||
) -> memory::Scope<IRenderer>;
|
||||
|
||||
[[nodiscard]] auto create_buffer(
|
||||
|
|
@ -82,7 +82,7 @@ namespace lt::renderer {
|
|||
const lt::ecs::Entity &surface_entity
|
||||
) -> memory::Scope<ISurface>
|
||||
{
|
||||
ensure(instance, "Failed to create renderer::ISurface: null instance");
|
||||
debug::ensure(instance, "Failed to create renderer::ISurface: null instance");
|
||||
|
||||
switch (target_api)
|
||||
{
|
||||
|
|
@ -111,8 +111,8 @@ namespace lt::renderer {
|
|||
[[nodiscard]] auto create_device(Api target_api, IGpu *gpu, ISurface *surface)
|
||||
-> memory::Scope<IDevice>
|
||||
{
|
||||
ensure(gpu, "Failed to create renderer::IDevice: null gpu");
|
||||
ensure(surface, "Failed to create renderer::IDevice: null surface");
|
||||
debug::ensure(gpu, "Failed to create renderer::IDevice: null gpu");
|
||||
debug::ensure(surface, "Failed to create renderer::IDevice: null surface");
|
||||
|
||||
switch (target_api)
|
||||
{
|
||||
|
|
@ -146,9 +146,9 @@ namespace lt::renderer {
|
|||
const IBuffer::CreateInfo &info
|
||||
) -> memory::Scope<IBuffer>
|
||||
{
|
||||
ensure(device, "Failed to create renderer::IBuffer: null device");
|
||||
ensure(gpu, "Failed to create renderer::IBuffer: null gpu");
|
||||
ensure(info.size > 0, "Failed to create renderer::IBuffer: null size");
|
||||
debug::ensure(device, "Failed to create renderer::IBuffer: null device");
|
||||
debug::ensure(gpu, "Failed to create renderer::IBuffer: null gpu");
|
||||
debug::ensure(info.size > 0, "Failed to create renderer::IBuffer: null size");
|
||||
|
||||
switch (target_api)
|
||||
{
|
||||
|
|
@ -169,7 +169,7 @@ namespace lt::renderer {
|
|||
const lt::assets::ShaderAsset &fragment_shader
|
||||
) -> memory::Scope<IPass>
|
||||
{
|
||||
ensure(device, "Failed to create renderer::IPass: null device");
|
||||
debug::ensure(device, "Failed to create renderer::IPass: null device");
|
||||
|
||||
switch (target_api)
|
||||
{
|
||||
|
|
@ -188,13 +188,13 @@ namespace lt::renderer {
|
|||
IGpu *gpu,
|
||||
IDevice *device,
|
||||
ISwapchain *swapchain,
|
||||
u32 max_frames_in_flight
|
||||
std::uint32_t max_frames_in_flight
|
||||
) -> memory::Scope<IRenderer>
|
||||
{
|
||||
ensure(gpu, "Failed to create renderer::IRenderer: null gpu");
|
||||
ensure(device, "Failed to create renderer::IRenderer: null device");
|
||||
ensure(swapchain, "Failed to create renderer::IRenderer: null swapchain");
|
||||
ensure(
|
||||
debug::ensure(gpu, "Failed to create renderer::IRenderer: null gpu");
|
||||
debug::ensure(device, "Failed to create renderer::IRenderer: null device");
|
||||
debug::ensure(swapchain, "Failed to create renderer::IRenderer: null swapchain");
|
||||
debug::ensure(
|
||||
std::clamp(
|
||||
max_frames_in_flight,
|
||||
IRenderer::frames_in_flight_lower_limit,
|
||||
|
|
@ -223,17 +223,17 @@ namespace lt::renderer {
|
|||
[[nodiscard]] auto create_debugger(Api target_api, IInstance *instance, IDebugger::CreateInfo info)
|
||||
-> memory::Scope<IDebugger>
|
||||
{
|
||||
ensure(
|
||||
debug::ensure(
|
||||
info.severities != IDebugger::MessageSeverity::none,
|
||||
"Failed to create renderer::IDebugger: severities == none"
|
||||
);
|
||||
|
||||
ensure(
|
||||
debug::ensure(
|
||||
info.types != IDebugger::MessageType::none,
|
||||
"Failed to create renderer::IDebugger: types == none"
|
||||
);
|
||||
|
||||
ensure(info.callback, "Failed to create vk::Messenger: null callback");
|
||||
debug::ensure(info.callback, "Failed to create vk::Messenger: null callback");
|
||||
|
||||
switch (target_api)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
export module renderer.frontend;
|
||||
|
||||
import preliminary;
|
||||
import renderer.data;
|
||||
import renderer.components;
|
||||
import bitwise;
|
||||
|
|
@ -9,10 +7,11 @@ import assets.shader;
|
|||
import ecs.entity;
|
||||
import math.vec2;
|
||||
import memory.scope;
|
||||
import std;
|
||||
|
||||
export namespace lt::renderer {
|
||||
|
||||
enum class Api : u8
|
||||
enum class Api : std::uint8_t
|
||||
{
|
||||
none = 0u,
|
||||
|
||||
|
|
@ -52,7 +51,7 @@ public:
|
|||
|
||||
virtual ~ISurface() = default;
|
||||
|
||||
[[nodiscard]] virtual auto get_framebuffer_size() const -> math::vec2_u32 = 0;
|
||||
[[nodiscard]] virtual auto get_framebuffer_size() const -> math::uvec2 = 0;
|
||||
};
|
||||
|
||||
class ISwapchain
|
||||
|
|
@ -66,7 +65,7 @@ public:
|
|||
class IBuffer
|
||||
{
|
||||
public:
|
||||
enum class Usage : u8
|
||||
enum class Usage : std::uint8_t
|
||||
{
|
||||
vertex,
|
||||
|
||||
|
|
@ -81,27 +80,27 @@ public:
|
|||
{
|
||||
Usage usage;
|
||||
|
||||
size_t size;
|
||||
std::size_t size;
|
||||
|
||||
std::string debug_name;
|
||||
};
|
||||
|
||||
struct CopyInfo
|
||||
{
|
||||
size_t offset;
|
||||
std::size_t offset;
|
||||
|
||||
size_t size;
|
||||
std::size_t size;
|
||||
};
|
||||
|
||||
IBuffer() = default;
|
||||
|
||||
virtual ~IBuffer() = default;
|
||||
|
||||
[[nodiscard]] virtual auto map() -> std::span<byte> = 0;
|
||||
[[nodiscard]] virtual auto map() -> std::span<std::byte> = 0;
|
||||
|
||||
virtual void unmap() = 0;
|
||||
|
||||
[[nodiscard]] virtual auto get_size() const -> size_t = 0;
|
||||
[[nodiscard]] virtual auto get_size() const -> std::size_t = 0;
|
||||
|
||||
private:
|
||||
};
|
||||
|
|
@ -121,7 +120,7 @@ public:
|
|||
|
||||
static constexpr auto frames_in_flight_lower_limit = 1u;
|
||||
|
||||
enum class Result : u8
|
||||
enum class Result : std::uint8_t
|
||||
{
|
||||
success = 0,
|
||||
invalid_swapchain,
|
||||
|
|
@ -132,7 +131,7 @@ public:
|
|||
|
||||
virtual ~IRenderer() = default;
|
||||
|
||||
virtual auto frame(u32 frame_idx, std::function<void()> submit_scene) -> Result = 0;
|
||||
virtual auto frame(std::uint32_t frame_idx, std::function<void()> submit_scene) -> Result = 0;
|
||||
|
||||
virtual void replace_swapchain(class ISwapchain *swapchain) = 0;
|
||||
|
||||
|
|
@ -147,7 +146,7 @@ public:
|
|||
class IDebugger
|
||||
{
|
||||
public:
|
||||
enum class MessageSeverity : u8
|
||||
enum class MessageSeverity : std::uint8_t
|
||||
{
|
||||
none = 0u,
|
||||
|
||||
|
|
@ -159,7 +158,7 @@ public:
|
|||
all = verbose | info | warning | error,
|
||||
};
|
||||
|
||||
enum class MessageType : u8
|
||||
enum class MessageType : std::uint8_t
|
||||
{
|
||||
none = 0u,
|
||||
general = bitwise::bit(0u),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
export module renderer.system;
|
||||
|
||||
import preliminary;
|
||||
import logger;
|
||||
import debug.assertions;
|
||||
import math.mat4;
|
||||
import renderer.factory;
|
||||
import app.system;
|
||||
|
|
@ -17,8 +16,9 @@ import renderer.components;
|
|||
import math.components;
|
||||
import math.algebra;
|
||||
import math.trig;
|
||||
import std;
|
||||
|
||||
export namespace lt::renderer {
|
||||
namespace lt::renderer {
|
||||
|
||||
/** The main rendering engine.
|
||||
*
|
||||
|
|
@ -27,11 +27,9 @@ export namespace lt::renderer {
|
|||
* - Connecting the context to the physical devices (select gpu, create surface, logical device)
|
||||
* - Rendering the scene represented in registry via lt::renderer::components.
|
||||
*/
|
||||
class System: public app::ISystem
|
||||
export class System: public app::ISystem
|
||||
{
|
||||
public:
|
||||
// TODO(Light): this is some horrible design... fix it :(
|
||||
|
||||
/** config.max_frames_in_flight should not be higher than this value. */
|
||||
static constexpr auto frames_in_flight_upper_limit = 5u;
|
||||
|
||||
|
|
@ -42,7 +40,7 @@ public:
|
|||
{
|
||||
Api target_api;
|
||||
|
||||
u32 max_frames_in_flight;
|
||||
std::uint32_t max_frames_in_flight;
|
||||
};
|
||||
|
||||
struct CreateInfo
|
||||
|
|
@ -58,7 +56,7 @@ public:
|
|||
|
||||
System(CreateInfo info);
|
||||
|
||||
~System() override = default;
|
||||
~System() override;
|
||||
|
||||
System(System &&) = default;
|
||||
|
||||
|
|
@ -108,9 +106,9 @@ private:
|
|||
|
||||
app::TickResult m_last_tick_result {};
|
||||
|
||||
u32 m_frame_idx {};
|
||||
std::uint32_t m_frame_idx {};
|
||||
|
||||
u32 m_max_frames_in_flight {};
|
||||
std::uint32_t m_max_frames_in_flight {};
|
||||
};
|
||||
|
||||
} // namespace lt::renderer
|
||||
|
|
@ -125,8 +123,8 @@ System::System(CreateInfo info)
|
|||
, m_instance(get_instance(m_api))
|
||||
, m_max_frames_in_flight(info.config.max_frames_in_flight)
|
||||
{
|
||||
ensure(m_registry, "Failed to initialize renderer::System: null registry");
|
||||
ensure(
|
||||
debug::ensure(m_registry, "Failed to initialize renderer::System: null registry");
|
||||
debug::ensure(
|
||||
std::clamp(
|
||||
info.config.max_frames_in_flight,
|
||||
frames_in_flight_lower_limit,
|
||||
|
|
@ -154,6 +152,8 @@ System::System(CreateInfo info)
|
|||
) };
|
||||
}
|
||||
|
||||
System::~System() = default;
|
||||
|
||||
void System::on_register()
|
||||
{
|
||||
}
|
||||
|
|
@ -164,7 +164,7 @@ void System::on_unregister()
|
|||
|
||||
void System::tick(app::TickInfo tick)
|
||||
{
|
||||
ignore = tick;
|
||||
std::ignore = tick;
|
||||
|
||||
handle_surface_resized_events();
|
||||
auto frame_result = m_renderer->frame(m_frame_idx, [this] { submit_scene(); });
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,14 +1,13 @@
|
|||
export module renderer.vk.buffer;
|
||||
|
||||
import preliminary;
|
||||
import renderer.vk.device;
|
||||
import renderer.vk.gpu;
|
||||
import renderer.vk.api_wrapper;
|
||||
import renderer.frontend;
|
||||
import std;
|
||||
|
||||
export namespace lt::renderer::vkb {
|
||||
namespace lt::renderer::vkb {
|
||||
|
||||
class Buffer: public IBuffer
|
||||
export class Buffer: public IBuffer
|
||||
{
|
||||
public:
|
||||
Buffer(class IDevice *device, class IGpu *gpu, const CreateInfo &info);
|
||||
|
|
@ -17,7 +16,7 @@ public:
|
|||
|
||||
void unmap() override;
|
||||
|
||||
[[nodiscard]] auto get_size() const -> size_t override
|
||||
[[nodiscard]] auto get_size() const -> std::size_t override
|
||||
{
|
||||
return m_size;
|
||||
}
|
||||
|
|
@ -35,11 +34,12 @@ private:
|
|||
[[nodiscard]] auto to_native_memory_properties(Usage usage) const -> vk::Memory::PropertyFlags;
|
||||
|
||||
|
||||
[[nodiscard]] auto has_correct_memory_type_bit(u32 type_bits, u32 type_idx) const -> bool;
|
||||
[[nodiscard]] auto has_correct_memory_type_bit(std::uint32_t type_bits, std::uint32_t type_idx)
|
||||
const -> bool;
|
||||
|
||||
[[nodiscard]] auto has_required_memory_properties(
|
||||
u32 required_properties,
|
||||
u32 property_flags
|
||||
std::uint32_t required_properties,
|
||||
std::uint32_t property_flags
|
||||
) const -> bool;
|
||||
|
||||
Device *m_device {};
|
||||
|
|
@ -51,13 +51,14 @@ private:
|
|||
vk::Memory m_memory;
|
||||
|
||||
// TODO(Light): should this reflect the allocation size instead?
|
||||
size_t m_size {};
|
||||
std::size_t m_size {};
|
||||
};
|
||||
|
||||
} // namespace lt::renderer::vkb
|
||||
|
||||
module :private;
|
||||
namespace lt::renderer::vkb {
|
||||
using namespace ::lt::renderer;
|
||||
using namespace ::lt::renderer::vkb;
|
||||
|
||||
Buffer::Buffer(IDevice *device, IGpu *gpu, const CreateInfo &info)
|
||||
: m_device(static_cast<Device *>(device))
|
||||
|
|
@ -144,17 +145,18 @@ void Buffer::unmap() /* override */
|
|||
std::unreachable();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto Buffer::has_correct_memory_type_bit(u32 type_bits, u32 type_idx) const -> bool
|
||||
[[nodiscard]] auto Buffer::has_correct_memory_type_bit(
|
||||
std::uint32_t type_bits,
|
||||
std::uint32_t type_idx
|
||||
) const -> bool
|
||||
{
|
||||
return type_bits & (1 << type_idx);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto Buffer::has_required_memory_properties(
|
||||
u32 required_properties,
|
||||
u32 property_flags
|
||||
std::uint32_t required_properties,
|
||||
std::uint32_t property_flags
|
||||
) const -> bool
|
||||
{
|
||||
return (property_flags & required_properties) == required_properties;
|
||||
}
|
||||
|
||||
} // namespace lt::renderer::vkb
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
export module renderer.vk.debugger;
|
||||
|
||||
import preliminary;
|
||||
import renderer.vk.instance;
|
||||
import renderer.frontend;
|
||||
import renderer.vk.api_wrapper;
|
||||
import memory.null_on_move;
|
||||
import debug.assertions;
|
||||
import logger;
|
||||
import std;
|
||||
|
||||
export namespace lt::renderer::vkb {
|
||||
namespace lt::renderer::vkb {
|
||||
|
||||
class Debugger: public IDebugger
|
||||
export class Debugger: public IDebugger
|
||||
{
|
||||
public:
|
||||
Debugger(IInstance *instance, CreateInfo info);
|
||||
|
|
@ -155,7 +155,7 @@ void Debugger::native_callback(
|
|||
{
|
||||
try
|
||||
{
|
||||
ensure(user_data, "Null vulkan_user_data received in messenger callback");
|
||||
debug::ensure(user_data, "Null vulkan_user_data received in messenger callback");
|
||||
|
||||
auto *messenger = std::bit_cast<Debugger *>(user_data);
|
||||
messenger->m_user_callback(
|
||||
|
|
|
|||
9
modules/renderer/vk/descriptors.cppm
Normal file
9
modules/renderer/vk/descriptors.cppm
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
namespace lt::renderer::vk {
|
||||
|
||||
class Descriptors
|
||||
{
|
||||
};
|
||||
|
||||
} // namespace lt::renderer::vk
|
||||
|
|
@ -1,17 +1,17 @@
|
|||
export module renderer.vk.device;
|
||||
|
||||
import preliminary;
|
||||
import memory.null_on_move;
|
||||
import logger;
|
||||
import debug.assertions;
|
||||
import renderer.vk.instance;
|
||||
import renderer.frontend;
|
||||
import renderer.vk.api_wrapper;
|
||||
import renderer.vk.gpu;
|
||||
import renderer.vk.surface;
|
||||
import std;
|
||||
|
||||
export namespace lt::renderer::vkb {
|
||||
namespace lt::renderer::vkb {
|
||||
|
||||
class Device: public IDevice
|
||||
export class Device: public IDevice
|
||||
{
|
||||
public:
|
||||
Device(IGpu *gpu, ISurface *surface);
|
||||
|
|
@ -21,7 +21,7 @@ public:
|
|||
return m_device;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_family_indices() const -> std::vector<u32>
|
||||
[[nodiscard]] auto get_family_indices() const -> std::vector<std::uint32_t>
|
||||
{
|
||||
return { m_graphics_queue_family_index, m_present_queue_family_index };
|
||||
}
|
||||
|
|
@ -43,7 +43,7 @@ private:
|
|||
|
||||
void initialize_queue_indices();
|
||||
|
||||
[[nodiscard]] auto find_suitable_queue_family() const -> u32;
|
||||
[[nodiscard]] auto find_suitable_queue_family() const -> std::uint32_t;
|
||||
|
||||
vkb::Gpu *m_gpu {};
|
||||
|
||||
|
|
@ -55,9 +55,9 @@ private:
|
|||
|
||||
vk::Queue m_present_queue {};
|
||||
|
||||
u32 m_graphics_queue_family_index = vk::constants::queue_family_ignored;
|
||||
std::uint32_t m_graphics_queue_family_index = vk::constants::queue_family_ignored;
|
||||
|
||||
u32 m_present_queue_family_index = vk::constants::queue_family_ignored;
|
||||
std::uint32_t m_present_queue_family_index = vk::constants::queue_family_ignored;
|
||||
};
|
||||
|
||||
} // namespace lt::renderer::vkb
|
||||
|
|
@ -70,7 +70,7 @@ Device::Device(IGpu *gpu, ISurface *surface)
|
|||
: m_gpu(static_cast<Gpu *>(gpu))
|
||||
, m_surface(static_cast<Surface *>(surface))
|
||||
{
|
||||
ensure(m_surface->vk(), "Failed to initialize vk::Device: null vulkan surface");
|
||||
debug::ensure(m_surface->vk(), "Failed to initialize vk::Device: null vulkan surface");
|
||||
|
||||
initialize_queue_indices();
|
||||
initialize_logical_device();
|
||||
|
|
@ -105,9 +105,9 @@ void Device::initialize_logical_device()
|
|||
|
||||
.features = {
|
||||
.geometry_shader = true,
|
||||
.sampler_anisotropy = true,
|
||||
.multi_draw_indirect = true,
|
||||
.draw_indirect_first_instance = true,
|
||||
.sampler_anisotropy = true,
|
||||
},
|
||||
|
||||
.dynamic_rendering_features = m_gpu->vk().get_supported_dynamic_rendering_features(),
|
||||
|
|
@ -124,7 +124,7 @@ void Device::initialize_logical_device()
|
|||
void Device::initialize_queue_indices()
|
||||
{
|
||||
auto properties = m_gpu->vk().get_queue_family_properties();
|
||||
for (auto idx = u32 { 0u }; const auto &property : properties)
|
||||
for (auto idx = std::uint32_t { 0u }; const auto &property : properties)
|
||||
{
|
||||
if (property.queue_flags & vk::QueueFlags::graphics_bit)
|
||||
{
|
||||
|
|
@ -145,12 +145,12 @@ void Device::initialize_queue_indices()
|
|||
++idx;
|
||||
}
|
||||
|
||||
ensure(
|
||||
debug::ensure(
|
||||
m_graphics_queue_family_index != vk::constants::queue_family_ignored,
|
||||
"Failed to find graphics queue family"
|
||||
);
|
||||
|
||||
ensure(
|
||||
debug::ensure(
|
||||
m_present_queue_family_index != vk::constants::queue_family_ignored,
|
||||
"Failed to find presentation queue family"
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,19 +1,51 @@
|
|||
export module renderer.vk.gpu;
|
||||
|
||||
import preliminary;
|
||||
import renderer.vk.api_wrapper;
|
||||
import logger;
|
||||
import debug.assertions;
|
||||
import renderer.frontend;
|
||||
import renderer.vk.instance;
|
||||
import memory.null_on_move;
|
||||
import std;
|
||||
|
||||
export namespace lt::renderer::vkb {
|
||||
namespace lt::renderer::vkb {
|
||||
|
||||
class Gpu: public IGpu
|
||||
export class Gpu: public IGpu
|
||||
{
|
||||
public:
|
||||
Gpu(IInstance *instance);
|
||||
|
||||
// [[nodiscard]] auto queue_family_supports_presentation(
|
||||
// VkSurfaceKHR surface,
|
||||
// uint32_t queue_family_idx
|
||||
// ) -> bool;
|
||||
//
|
||||
// [[nodiscard]] auto get_surface_capabilities(VkSurfaceKHR surface) const
|
||||
// -> VkSurfaceCapabilitiesKHR;
|
||||
//
|
||||
// [[nodiscard]] auto get_surface_formats(VkSurfaceKHR surface) const
|
||||
// -> std::vector<VkSurfaceFormatKHR>;
|
||||
//
|
||||
// [[nodiscard]] auto get_properties() const -> VkPhysicalDeviceProperties
|
||||
// {
|
||||
// return m_properties;
|
||||
// }
|
||||
//
|
||||
// [[nodiscard]] auto get_descriptor_indexing_features() const
|
||||
// -> VkPhysicalDeviceDescriptorIndexingFeatures
|
||||
// {
|
||||
// return m_descriptor_indexing_features;
|
||||
// }
|
||||
//
|
||||
// [[nodiscard]] auto get_memory_properties() const -> VkPhysicalDeviceMemoryProperties
|
||||
// {
|
||||
// return m_memory_properties;
|
||||
// }
|
||||
//
|
||||
// [[nodiscard]] auto get_queue_family_properties() const ->
|
||||
// std::vector<VkQueueFamilyProperties>
|
||||
// {
|
||||
// return m_queue_family_properties;
|
||||
// }
|
||||
|
||||
[[nodiscard]] auto vk() -> vk::Gpu &;
|
||||
|
||||
private:
|
||||
|
|
@ -21,6 +53,8 @@ private:
|
|||
|
||||
vk::Gpu::Properties m_properties {};
|
||||
|
||||
// VkPhysicalDeviceDescriptorIndexingFeatures m_descriptor_indexing_features;
|
||||
|
||||
vk::Gpu::MemoryProperties m_memory_properties {};
|
||||
|
||||
std::vector<vk::Gpu::QueueFamilyProperties> m_queue_family_properties;
|
||||
|
|
@ -41,31 +75,13 @@ Gpu::Gpu(IInstance *instance)
|
|||
auto properties = gpu.get_properties();
|
||||
auto features = gpu.get_features();
|
||||
|
||||
// GPU is dedicated, a great success!
|
||||
if (properties.device_type == vk::Gpu::Type::discrete_gpu && features.geometry_shader)
|
||||
{
|
||||
m_gpu = gpu;
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_gpu)
|
||||
{
|
||||
for (auto &gpu : gpus)
|
||||
{
|
||||
auto properties = gpu.get_properties();
|
||||
auto features = gpu.get_features();
|
||||
|
||||
// GPU is integrated, fall back to anything with geometry shader support...
|
||||
if (features.geometry_shader)
|
||||
{
|
||||
m_gpu = gpu;
|
||||
log::warn("Using integrated GPU");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No suitable GPU is fonud...
|
||||
ensure(m_gpu, "Failed to find any suitable Vulkan physical device");
|
||||
debug::ensure(m_gpu, "Failed to find any suitable Vulkan physical device");
|
||||
|
||||
m_memory_properties = m_gpu.get_memory_properties();
|
||||
m_queue_family_properties = m_gpu.get_queue_family_properties();
|
||||
|
|
@ -76,4 +92,42 @@ Gpu::Gpu(IInstance *instance)
|
|||
return m_gpu;
|
||||
}
|
||||
|
||||
// [[nodiscard]] auto Gpu::queue_family_supports_presentation(
|
||||
// VkSurfaceKHR surface,
|
||||
// uint32_t queue_family_idx
|
||||
// ) -> bool
|
||||
// {
|
||||
// auto supported = VkBool32 { false };
|
||||
// vkc(vk_get_physical_device_surface_support(m_gpu, queue_family_idx, surface, &supported));
|
||||
//
|
||||
// return supported;
|
||||
// }
|
||||
//
|
||||
// [[nodiscard]] auto Gpu::get_surface_capabilities(VkSurfaceKHR surface) const
|
||||
// -> VkSurfaceCapabilitiesKHR
|
||||
// {
|
||||
// auto capabilities = VkSurfaceCapabilitiesKHR {};
|
||||
// vkc(vk_get_physical_device_surface_capabilities(m_gpu, surface, &capabilities));
|
||||
// return capabilities;
|
||||
// }
|
||||
//
|
||||
// [[nodiscard]] auto Gpu::get_surface_formats(VkSurfaceKHR surface) const
|
||||
// -> std::vector<VkSurfaceFormatKHR>
|
||||
// {
|
||||
// auto count = uint32_t { 0u };
|
||||
// vkc(vk_get_physical_device_surface_formats(m_gpu, surface, &count, nullptr));
|
||||
//
|
||||
// auto formats = std::vector<VkSurfaceFormatKHR>(count);
|
||||
// vkc(vk_get_physical_device_surface_formats(m_gpu, surface, &count, formats.data()));
|
||||
//
|
||||
// return formats;
|
||||
// }
|
||||
|
||||
// [[nodiscard]] auto Gpu::create_device(VkDeviceCreateInfo info) const -> VkDevice
|
||||
// {
|
||||
// auto *device = VkDevice {};
|
||||
// vkc(vk_create_device(m_gpu, &info, nullptr, &device));
|
||||
// return device;
|
||||
// }
|
||||
|
||||
} // namespace lt::renderer::vkb
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
export module renderer.vk.instance;
|
||||
|
||||
import preliminary;
|
||||
import debug.assertions;
|
||||
import renderer.frontend;
|
||||
import renderer.vk.api_wrapper;
|
||||
import std;
|
||||
|
||||
export namespace lt::renderer::vkb {
|
||||
namespace lt::renderer::vkb {
|
||||
|
||||
/**
|
||||
* Responsible for dynamically loading Vulkan library/functions.
|
||||
|
|
@ -15,7 +15,7 @@ export namespace lt::renderer::vkb {
|
|||
* https://www.xfree86.org/4.7.0/DRI11.html
|
||||
* https://github.com/KhronosGroup/Vulkan-LoaderAndValidationLayers/issues/1894
|
||||
*/
|
||||
class Instance: public IInstance
|
||||
export class Instance: public IInstance
|
||||
{
|
||||
public:
|
||||
static auto get() -> IInstance *
|
||||
|
|
@ -28,6 +28,20 @@ public:
|
|||
return m_instance;
|
||||
}
|
||||
|
||||
// /* create functions */
|
||||
// [[nodiscard]] auto create_xlib_surface(VkXlibSurfaceCreateInfoKHR info) const ->
|
||||
// VkSurfaceKHR;
|
||||
//
|
||||
// [[nodiscard]] auto create_messenger(VkDebugUtilsMessengerCreateInfoEXT info) const
|
||||
// -> VkDebugUtilsMessengerEXT;
|
||||
//
|
||||
// /* destroy functions */
|
||||
// void destroy_surface(VkSurfaceKHR surface) const;
|
||||
//
|
||||
// void destroy_messenger(VkDebugUtilsMessengerEXT messenger) const;
|
||||
//
|
||||
// [[nodiscard]] auto enumerate_gpus() const -> std::vector<VkPhysicalDevice>;
|
||||
|
||||
private:
|
||||
static auto instance() -> IInstance &
|
||||
{
|
||||
|
|
@ -67,7 +81,7 @@ Instance::Instance()
|
|||
Setting { .name = "enable_message_limit", .values = true },
|
||||
Setting {
|
||||
.name = "duplicate_message_limit",
|
||||
.values = std::numeric_limits<u32>::max(),
|
||||
.values = std::numeric_limits<std::uint32_t>::max(),
|
||||
},
|
||||
Setting {
|
||||
.name = "report_flags",
|
||||
|
|
@ -75,6 +89,7 @@ Instance::Instance()
|
|||
},
|
||||
};
|
||||
|
||||
|
||||
using Layer = vk::Instance::Layer;
|
||||
m_instance = vk::Instance(
|
||||
vk::Instance::CreateInfo {
|
||||
|
|
@ -95,4 +110,41 @@ Instance::Instance()
|
|||
m_instance.load_functions();
|
||||
}
|
||||
|
||||
// auto Instance::enumerate_gpus() const -> std::vector<VkPhysicalDevice>
|
||||
// {
|
||||
// auto count = 0u;
|
||||
// vkc(vk_enumerate_physical_devices(m_instance, &count, nullptr));
|
||||
// debug::ensure(count != 0u, "Failed to find any gpus with Vulkan support");
|
||||
//
|
||||
// auto gpus = std::vector<VkPhysicalDevice>(count);
|
||||
// vkc(vk_enumerate_physical_devices(m_instance, &count, gpus.data()));
|
||||
// return gpus;
|
||||
// }
|
||||
//
|
||||
// auto Instance::create_xlib_surface(VkXlibSurfaceCreateInfoKHR info) const -> VkSurfaceKHR
|
||||
// {
|
||||
// auto *value = VkSurfaceKHR {};
|
||||
// vk_create_xlib_surface_khr(m_instance, &info, m_allocator, &value);
|
||||
//
|
||||
// return value;
|
||||
// }
|
||||
//
|
||||
// [[nodiscard]] auto Instance::create_messenger(VkDebugUtilsMessengerCreateInfoEXT info) const
|
||||
// -> VkDebugUtilsMessengerEXT
|
||||
// {
|
||||
// auto *messenger = VkDebugUtilsMessengerEXT {};
|
||||
// vkc(vk_create_debug_messenger(m_instance, &info, m_allocator, &messenger));
|
||||
// return messenger;
|
||||
// }
|
||||
//
|
||||
// void Instance::destroy_surface(VkSurfaceKHR surface) const
|
||||
// {
|
||||
// vk_destroy_surface_khr(m_instance, surface, m_allocator);
|
||||
// }
|
||||
//
|
||||
// void Instance::destroy_messenger(VkDebugUtilsMessengerEXT messenger) const
|
||||
// {
|
||||
// vk_destroy_debug_messenger(m_instance, messenger, m_allocator);
|
||||
// }
|
||||
|
||||
} // namespace lt::renderer::vkb
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
export module renderer.vk.pass;
|
||||
|
||||
import preliminary;
|
||||
import renderer.data;
|
||||
import renderer.vk.api_wrapper;
|
||||
import renderer.vk.device;
|
||||
|
|
@ -9,10 +7,11 @@ import assets.shader;
|
|||
import assets.metadata;
|
||||
import memory.null_on_move;
|
||||
import renderer.frontend;
|
||||
import std;
|
||||
|
||||
export namespace lt::renderer::vkb {
|
||||
namespace lt::renderer::vkb {
|
||||
|
||||
class Pass: public IPass
|
||||
export class Pass: public IPass
|
||||
{
|
||||
public:
|
||||
Pass(
|
||||
|
|
@ -52,8 +51,10 @@ private:
|
|||
|
||||
} // namespace lt::renderer::vkb
|
||||
|
||||
|
||||
module :private;
|
||||
namespace lt::renderer::vkb {
|
||||
using namespace ::lt::renderer::vkb;
|
||||
using namespace ::lt::renderer;
|
||||
|
||||
using enum vk::DescriptorSetLayout::Binding::FlagBits;
|
||||
|
||||
|
|
@ -79,6 +80,36 @@ Pass::Pass(
|
|||
.push_constant_ranges = { { vk::ShaderStageFlags::vertex_bit, 0u, sizeof(FrameConstants) } }
|
||||
}))
|
||||
{
|
||||
// auto pool_size = VkDescriptorPoolSize {
|
||||
// .type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
||||
// .descriptorCount = descriptor_count,
|
||||
// };
|
||||
//
|
||||
// m_descriptor_pool = m_device->create_desscriptor_pool(
|
||||
// {
|
||||
// .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
|
||||
// .poolSizeCount = 1u,
|
||||
// .pPoolSizes = &pool_size,
|
||||
// }
|
||||
// );
|
||||
//
|
||||
// auto descriptor_set_variable_descriptor_count_info
|
||||
// = VkDescriptorSetVariableDescriptorCountAllocateInfo {
|
||||
// .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO,
|
||||
// .descriptorSetCount = 1u,
|
||||
// .pDescriptorCounts = &descriptor_count,
|
||||
// };
|
||||
//
|
||||
// m_vertices_descriptor_set = m_device->allocate_descriptor_set(
|
||||
// VkDescriptorSetAllocateInfo {
|
||||
// .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
|
||||
// .pNext = &descriptor_set_variable_descriptor_count_info,
|
||||
// .descriptorPool = m_descriptor_pool,
|
||||
// .descriptorSetCount = 1u,
|
||||
// .pSetLayouts = &m_vertices_descriptor_set_layout,
|
||||
// }
|
||||
// );
|
||||
|
||||
auto shaders = std::vector<std::pair<vk::ShaderModule, vk::ShaderStageFlags::T>> {};
|
||||
shaders.emplace_back(
|
||||
vk::ShaderModule(
|
||||
|
|
@ -121,5 +152,3 @@ Pass::Pass(
|
|||
}
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace lt::renderer::vkb
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
export module renderer.vk.renderer;
|
||||
|
||||
import preliminary;
|
||||
import logger;
|
||||
import assets.shader;
|
||||
import debug.assertions;
|
||||
import renderer.vk.api_wrapper;
|
||||
import memory.reference;
|
||||
import memory.null_on_move;
|
||||
|
|
@ -15,17 +14,19 @@ import renderer.vk.buffer;
|
|||
import renderer.vk.pass;
|
||||
import renderer.data;
|
||||
import renderer.frontend;
|
||||
import std;
|
||||
|
||||
export namespace lt::renderer::vkb {
|
||||
namespace lt::renderer::vkb {
|
||||
|
||||
class Renderer: public IRenderer
|
||||
// NOLINTNEXTLINE
|
||||
export class Renderer: public IRenderer
|
||||
{
|
||||
public:
|
||||
Renderer(
|
||||
class IGpu *gpu,
|
||||
class IDevice *device,
|
||||
class ISwapchain *swapchain,
|
||||
u32 max_frames_in_flight
|
||||
std::uint32_t max_frames_in_flight
|
||||
);
|
||||
|
||||
~Renderer() override
|
||||
|
|
@ -34,14 +35,14 @@ public:
|
|||
{
|
||||
m_device->vk().wait_idle();
|
||||
}
|
||||
catch (const std::exception &exp)
|
||||
catch (std::exception &exp)
|
||||
{
|
||||
log::error("Failed to wait idle on device in renderer destructor:");
|
||||
log::error("\twhat: {}", exp.what());
|
||||
log::error("Failed to wait idle on device in renderer destructor");
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] auto frame(u32 frame_idx, std::function<void()> submit_scene) -> Result override;
|
||||
[[nodiscard]] auto frame(std::uint32_t frame_idx, std::function<void()> submit_scene)
|
||||
-> Result override;
|
||||
|
||||
void replace_swapchain(ISwapchain *swapchain) override;
|
||||
|
||||
|
|
@ -56,11 +57,11 @@ public:
|
|||
) override;
|
||||
|
||||
private:
|
||||
void record_cmd(vk::CommandBuffer &cmd, u32 image_idx);
|
||||
void record_cmd(vk::CommandBuffer &cmd, std::uint32_t image_idx);
|
||||
|
||||
void map_buffers(u32 frame_idx);
|
||||
void map_buffers(std::uint32_t frame_idx);
|
||||
|
||||
u32 m_max_frames_in_flight {};
|
||||
std::uint32_t m_max_frames_in_flight {};
|
||||
|
||||
Device *m_device {};
|
||||
|
||||
|
|
@ -78,7 +79,7 @@ private:
|
|||
|
||||
std::vector<vk::Semaphore> m_submit_semaphores;
|
||||
|
||||
math::vec2_u32 m_resolution;
|
||||
math::uvec2 m_resolution;
|
||||
|
||||
FrameConstants m_frame_constants;
|
||||
|
||||
|
|
@ -86,13 +87,13 @@ private:
|
|||
|
||||
Buffer m_staging_buffer;
|
||||
|
||||
size_t m_staging_offset;
|
||||
std::size_t m_staging_offset;
|
||||
|
||||
std::span<byte> m_staging_map;
|
||||
std::span<std::byte> m_staging_map;
|
||||
|
||||
std::span<components::Sprite::Vertex> m_sprite_vertex_map;
|
||||
|
||||
size_t m_current_sprite_idx;
|
||||
std::size_t m_current_sprite_idx;
|
||||
|
||||
vk::DescriptorPool m_global_set_pool;
|
||||
|
||||
|
|
@ -104,7 +105,12 @@ private:
|
|||
module :private;
|
||||
namespace lt::renderer::vkb {
|
||||
|
||||
Renderer::Renderer(IGpu *gpu, IDevice *device, ISwapchain *swapchain, u32 max_frames_in_flight)
|
||||
Renderer::Renderer(
|
||||
IGpu *gpu,
|
||||
IDevice *device,
|
||||
ISwapchain *swapchain,
|
||||
std::uint32_t max_frames_in_flight
|
||||
)
|
||||
: m_device(static_cast<Device *>(device))
|
||||
, m_swapchain(static_cast<Swapchain *>(swapchain))
|
||||
, m_resolution(m_swapchain->get_resolution())
|
||||
|
|
@ -168,9 +174,10 @@ Renderer::Renderer(IGpu *gpu, IDevice *device, ISwapchain *swapchain, u32 max_fr
|
|||
}
|
||||
};
|
||||
|
||||
[[nodiscard]] auto Renderer::frame(u32 frame_idx, std::function<void()> submit_scene) -> Result
|
||||
[[nodiscard]] auto Renderer::frame(std::uint32_t frame_idx, std::function<void()> submit_scene)
|
||||
-> Result
|
||||
{
|
||||
ensure(
|
||||
debug::ensure(
|
||||
frame_idx < m_max_frames_in_flight,
|
||||
"Failed to draw: frame_idx >= max_frames_in_flight ({} >= {})",
|
||||
frame_idx,
|
||||
|
|
@ -186,10 +193,7 @@ Renderer::Renderer(IGpu *gpu, IDevice *device, ISwapchain *swapchain, u32 max_fr
|
|||
frame_fence.reset();
|
||||
|
||||
map_buffers(frame_idx);
|
||||
|
||||
// WIP(Light): submit the scene!
|
||||
ignore = submit_scene;
|
||||
// submit_scene();
|
||||
submit_scene();
|
||||
record_cmd(cmd, image_idx);
|
||||
|
||||
auto &submit_semaphore = m_submit_semaphores[image_idx];
|
||||
|
|
@ -221,7 +225,7 @@ void Renderer::replace_swapchain(ISwapchain *swapchain)
|
|||
m_resolution = m_swapchain->get_resolution();
|
||||
}
|
||||
|
||||
void Renderer::map_buffers(u32 frame_idx)
|
||||
void Renderer::map_buffers(std::uint32_t frame_idx)
|
||||
{
|
||||
using components::Sprite;
|
||||
|
||||
|
|
@ -238,7 +242,7 @@ void Renderer::map_buffers(u32 frame_idx)
|
|||
);
|
||||
}
|
||||
|
||||
void Renderer::record_cmd(vk::CommandBuffer &cmd, u32 image_idx)
|
||||
void Renderer::record_cmd(vk::CommandBuffer &cmd, std::uint32_t image_idx)
|
||||
{
|
||||
m_staging_map = {};
|
||||
m_sprite_vertex_map = {};
|
||||
|
|
@ -246,35 +250,35 @@ void Renderer::record_cmd(vk::CommandBuffer &cmd, u32 image_idx)
|
|||
|
||||
m_staging_buffer.unmap();
|
||||
|
||||
// if (m_current_sprite_idx)
|
||||
// {
|
||||
// cmd.copy(
|
||||
// {
|
||||
// .src_buffer = &m_staging_buffer.vk(),
|
||||
// .dst_buffer = &m_vertex_buffer.vk(),
|
||||
// .src_offset = m_staging_offset,
|
||||
// .dst_offset = m_staging_offset,
|
||||
// .size = m_current_sprite_idx * sizeof(components::Sprite::Vertex),
|
||||
// }
|
||||
// );
|
||||
// }
|
||||
if (m_current_sprite_idx)
|
||||
{
|
||||
cmd.copy(
|
||||
{
|
||||
.src_buffer = &m_staging_buffer.vk(),
|
||||
.dst_buffer = &m_vertex_buffer.vk(),
|
||||
.src_offset = m_staging_offset,
|
||||
.dst_offset = m_staging_offset,
|
||||
.size = m_current_sprite_idx * sizeof(components::Sprite::Vertex),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// cmd.push_constants(
|
||||
// {
|
||||
// .layout = &m_pass->get_pipeline_layout(),
|
||||
// .shader_stages = vk::ShaderStageFlags::vertex_bit,
|
||||
// .offset = 0u,
|
||||
// .size = sizeof(FrameConstants),
|
||||
// .data = &m_frame_constants,
|
||||
// }
|
||||
// );
|
||||
//
|
||||
// cmd.bind_descriptor_set(
|
||||
// m_global_set,
|
||||
// vk::Pipeline::BindPoint::graphics,
|
||||
// m_pass->get_pipeline_layout(),
|
||||
// 0
|
||||
// );
|
||||
cmd.push_constants(
|
||||
{
|
||||
.layout = &m_pass->get_pipeline_layout(),
|
||||
.shader_stages = vk::ShaderStageFlags::vertex_bit,
|
||||
.offset = 0u,
|
||||
.size = sizeof(FrameConstants),
|
||||
.data = &m_frame_constants,
|
||||
}
|
||||
);
|
||||
|
||||
cmd.bind_descriptor_set(
|
||||
m_global_set,
|
||||
vk::Pipeline::BindPoint::graphics,
|
||||
m_pass->get_pipeline_layout(),
|
||||
0
|
||||
);
|
||||
|
||||
using AccessFlagBits = vk::CommandBuffer::ImageBarrierInfo::AccessFlagBits;
|
||||
cmd.image_barrier(
|
||||
|
|
@ -306,25 +310,25 @@ void Renderer::record_cmd(vk::CommandBuffer &cmd, u32 image_idx)
|
|||
}
|
||||
}
|
||||
);
|
||||
// cmd.bind_pipeline(m_pass->get_pipeline(), vk::Pipeline::BindPoint::graphics);
|
||||
// cmd.set_viewport(
|
||||
// {
|
||||
// .origin = {},
|
||||
// .extent = { static_cast<f32>(m_resolution.x), static_cast<f32>(m_resolution.y) },
|
||||
// .min_depth = 0.0f,
|
||||
// .max_depth = 1.0f,
|
||||
// }
|
||||
// );
|
||||
// cmd.set_scissor({ .offset = {}, .extent = m_resolution });
|
||||
// cmd.draw(
|
||||
// {
|
||||
// .vertex_count = static_cast<u32>(m_current_sprite_idx),
|
||||
// .instance_count = 1u,
|
||||
// .first_vertex = 0u,
|
||||
// .first_instance = 0u,
|
||||
// }
|
||||
// );
|
||||
//
|
||||
cmd.bind_pipeline(m_pass->get_pipeline(), vk::Pipeline::BindPoint::graphics);
|
||||
cmd.set_viewport(
|
||||
{
|
||||
.origin = {},
|
||||
.extent = { static_cast<float>(m_resolution.x), static_cast<float>(m_resolution.y) },
|
||||
.min_depth = 0.0f,
|
||||
.max_depth = 1.0f,
|
||||
}
|
||||
);
|
||||
cmd.set_scissor({ .offset = {}, .extent = m_resolution });
|
||||
cmd.draw(
|
||||
{
|
||||
.vertex_count = static_cast<std::uint32_t>(m_current_sprite_idx),
|
||||
.instance_count = 1u,
|
||||
.first_vertex = 0u,
|
||||
.first_instance = 0u,
|
||||
}
|
||||
);
|
||||
|
||||
cmd.end_rendering();
|
||||
cmd.image_barrier(
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
export module renderer.vk.surface;
|
||||
|
||||
import preliminary;
|
||||
import debug.assertions;
|
||||
import ecs.entity;
|
||||
import ecs.registry;
|
||||
import memory.null_on_move;
|
||||
|
|
@ -10,9 +9,9 @@ import renderer.frontend;
|
|||
import renderer.vk.instance;
|
||||
import renderer.vk.api_wrapper;
|
||||
|
||||
export namespace lt::renderer::vkb {
|
||||
namespace lt::renderer::vkb {
|
||||
|
||||
class Surface: public ISurface
|
||||
export class Surface: public ISurface
|
||||
{
|
||||
public:
|
||||
Surface(IInstance *instance, const ecs::Entity &surface_entity);
|
||||
|
|
@ -22,7 +21,7 @@ public:
|
|||
return m_surface;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_framebuffer_size() const -> math::vec2_u32 override;
|
||||
[[nodiscard]] auto get_framebuffer_size() const -> math::uvec2 override;
|
||||
|
||||
private:
|
||||
vk::Surface m_surface;
|
||||
|
|
@ -42,25 +41,25 @@ Surface::Surface(IInstance *instance, const ecs::Entity &surface_entity)
|
|||
|
||||
|
||||
#if defined(LIGHT_PLATFORM_LINUX)
|
||||
ensure(
|
||||
debug::ensure(
|
||||
component.get_native_data().display,
|
||||
"Failed to initialize vk::Surface: null Wayland display"
|
||||
"Failed to initialize vk::Surface: null x-display"
|
||||
);
|
||||
ensure(
|
||||
component.get_native_data().surface,
|
||||
"Failed to initialize vk::Surface: null Wayland surface"
|
||||
debug::ensure(
|
||||
component.get_native_data().window,
|
||||
"Failed to initialize vk::Surface: null x-window"
|
||||
);
|
||||
|
||||
m_surface = vk::Surface(
|
||||
static_cast<Instance *>(instance)->vk(),
|
||||
vk::Surface::CreateInfo {
|
||||
.display = component.get_native_data().display,
|
||||
.surface = component.get_native_data().surface,
|
||||
.window = component.get_native_data().window,
|
||||
}
|
||||
);
|
||||
|
||||
#elif defined(LIGHT_PLATFORM_WINDOWS)
|
||||
ensure(
|
||||
debug::ensure(
|
||||
component.get_native_data().window,
|
||||
"Failed to initialize vk::Surface: null win32 window handle"
|
||||
);
|
||||
|
|
@ -78,7 +77,7 @@ Surface::Surface(IInstance *instance, const ecs::Entity &surface_entity)
|
|||
#endif
|
||||
}
|
||||
|
||||
[[nodiscard]] auto Surface::get_framebuffer_size() const -> math::vec2_u32
|
||||
[[nodiscard]] auto Surface::get_framebuffer_size() const -> math::uvec2
|
||||
{
|
||||
return m_surface_entity.get<surface::SurfaceComponent>().get_resolution();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
export module renderer.vk.swapchain;
|
||||
|
||||
import preliminary;
|
||||
import renderer.vk.api_wrapper;
|
||||
import renderer.vk.surface;
|
||||
import renderer.vk.device;
|
||||
|
|
@ -10,10 +8,11 @@ import renderer.frontend;
|
|||
import math.vec2;
|
||||
import memory.null_on_move;
|
||||
import logger;
|
||||
import std;
|
||||
|
||||
export namespace lt::renderer::vkb {
|
||||
namespace lt::renderer::vkb {
|
||||
|
||||
class Swapchain: public ISwapchain
|
||||
export class Swapchain: public ISwapchain
|
||||
{
|
||||
public:
|
||||
Swapchain(ISurface *surface, IGpu *gpu, IDevice *device);
|
||||
|
|
@ -23,7 +22,7 @@ public:
|
|||
return m_swapchain;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_resolution() const -> math::vec2_u32
|
||||
[[nodiscard]] auto get_resolution() const -> math::uvec2
|
||||
{
|
||||
return m_resolution;
|
||||
}
|
||||
|
|
@ -33,17 +32,17 @@ public:
|
|||
return m_format;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_image_count() const -> size_t
|
||||
[[nodiscard]] auto get_image_count() const -> std::size_t
|
||||
{
|
||||
return m_images.size();
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_image_view(u32 idx) -> vk::ImageView &
|
||||
[[nodiscard]] auto get_image_view(std::uint32_t idx) -> vk::ImageView &
|
||||
{
|
||||
return m_image_views[idx];
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_image(u32 idx) -> vk::Image &
|
||||
[[nodiscard]] auto get_image(std::uint32_t idx) -> vk::Image &
|
||||
{
|
||||
return m_images[idx];
|
||||
}
|
||||
|
|
@ -51,8 +50,8 @@ public:
|
|||
private:
|
||||
[[nodiscard]] auto get_optimal_image_count(
|
||||
vk::Surface::Capabilities capabilities,
|
||||
u32 desired_image_count
|
||||
) const -> u32;
|
||||
std::uint32_t desired_image_count
|
||||
) const -> std::uint32_t;
|
||||
|
||||
Gpu *m_gpu;
|
||||
|
||||
|
|
@ -66,13 +65,14 @@ private:
|
|||
|
||||
std::vector<vk::ImageView> m_image_views;
|
||||
|
||||
math::vec2_u32 m_resolution {};
|
||||
math::uvec2 m_resolution {};
|
||||
|
||||
vk::Format m_format {};
|
||||
};
|
||||
|
||||
} // namespace lt::renderer::vkb
|
||||
|
||||
|
||||
module :private;
|
||||
namespace lt::renderer::vkb {
|
||||
|
||||
|
|
@ -81,28 +81,16 @@ Swapchain::Swapchain(ISurface *surface, IGpu *gpu, IDevice *device)
|
|||
, m_gpu(static_cast<Gpu *>(gpu))
|
||||
, m_device(static_cast<Device *>(device))
|
||||
{
|
||||
auto capabilities = m_gpu->vk().get_surface_capabilities(m_surface->vk());
|
||||
static auto idx = 0u;
|
||||
|
||||
const auto capabilities = m_gpu->vk().get_surface_capabilities(m_surface->vk());
|
||||
const auto formats = m_gpu->vk().get_surface_formats(m_surface->vk());
|
||||
|
||||
// TODO(Light): parameterize
|
||||
constexpr auto desired_image_count = u32 { 3u };
|
||||
constexpr auto desired_image_count = std::uint32_t { 3u };
|
||||
const auto surface_format = formats.front();
|
||||
m_format = surface_format.format;
|
||||
|
||||
if (capabilities.current_extent.x == std::numeric_limits<u32>::max())
|
||||
{
|
||||
log::info(
|
||||
"Vulkan surface capabilities current extent is uint32 max... This indicates that the "
|
||||
"surface size will be determined by the extent of a swapchain targeting the surface."
|
||||
);
|
||||
|
||||
|
||||
// TODO(Light): Take surface extent as swapchain creation argument...
|
||||
capabilities.current_extent.x = 800u;
|
||||
capabilities.current_extent.y = 600u;
|
||||
}
|
||||
|
||||
static auto swapchain_idx = 0u;
|
||||
m_swapchain = vk::Swapchain(
|
||||
m_device->vk(),
|
||||
m_surface->vk(),
|
||||
|
|
@ -113,9 +101,9 @@ Swapchain::Swapchain(ISurface *surface, IGpu *gpu, IDevice *device)
|
|||
.extent = capabilities.current_extent,
|
||||
.min_image_count = get_optimal_image_count(capabilities, desired_image_count),
|
||||
.queue_family_indices = m_device->get_family_indices(),
|
||||
.present_mode = vk::Swapchain::PresentMode::mailbox,
|
||||
.present_mode = vk::Swapchain::PresentMode::immediate,
|
||||
.pre_transform = capabilities.current_transform,
|
||||
.name = std::format("swapchain {}", swapchain_idx++),
|
||||
.name = std::format("swapchain {}", idx++),
|
||||
}
|
||||
);
|
||||
m_resolution = capabilities.current_extent;
|
||||
|
|
@ -155,8 +143,8 @@ Swapchain::Swapchain(ISurface *surface, IGpu *gpu, IDevice *device)
|
|||
|
||||
[[nodiscard]] auto Swapchain::get_optimal_image_count(
|
||||
vk::Surface::Capabilities capabilities,
|
||||
u32 desired_image_count
|
||||
) const -> u32
|
||||
std::uint32_t desired_image_count
|
||||
) const -> std::uint32_t
|
||||
{
|
||||
const auto min_image_count = capabilities.min_image_count;
|
||||
const auto max_image_count = capabilities.max_image_count;
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
add_executable(sandbox sandbox.cpp)
|
||||
target_link_libraries(sandbox PRIVATE preliminary logger bitwise memory time test math assets app ecs surface renderer input mirror)
|
||||
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
import preliminary;
|
||||
import time;
|
||||
import logger;
|
||||
import surface.system;
|
||||
import surface.events;
|
||||
import surface.requests;
|
||||
import ecs.registry;
|
||||
import memory.scope;
|
||||
import memory.reference;
|
||||
import logger;
|
||||
import math.vec2;
|
||||
import app.system;
|
||||
|
||||
constexpr auto title = "TestWindow";
|
||||
constexpr auto width = 800u;
|
||||
constexpr auto height = 600u;
|
||||
constexpr auto vsync = true;
|
||||
constexpr auto visible = false;
|
||||
|
||||
auto main() -> i32
|
||||
try
|
||||
{
|
||||
for (auto idx = 0; idx < 100; ++idx)
|
||||
{
|
||||
std::println("{}: \033[1;{}m| color |\033[0m", idx, idx);
|
||||
}
|
||||
auto registry = lt::memory::create_ref<lt::ecs::Registry>();
|
||||
auto system = lt::surface::System { registry };
|
||||
|
||||
auto entity = registry->create_entity();
|
||||
|
||||
system.create_surface_component(
|
||||
entity,
|
||||
lt::surface::SurfaceComponent::CreateInfo {
|
||||
.title = title,
|
||||
.resolution = { width, height },
|
||||
.vsync = vsync,
|
||||
.visible = visible,
|
||||
}
|
||||
);
|
||||
|
||||
auto timer = lt::time::Timer {};
|
||||
lt::log::trace("Ticking for 3 seconds...");
|
||||
while (timer.elapsed_time() < std::chrono::seconds { 30 })
|
||||
{
|
||||
system.tick({});
|
||||
}
|
||||
lt::log::trace("Three seconds passed, quitting...");
|
||||
}
|
||||
catch (const std::exception &exp)
|
||||
{
|
||||
lt::log::critical("Aborting due to uncaught std::exception:");
|
||||
lt::log::critical("\twhat: {}", exp.what());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
lt::log::critical("Aborting due to uncaught non std::exception!");
|
||||
}
|
||||
|
|
@ -1,25 +1,23 @@
|
|||
module;
|
||||
#if defined(LIGHT_PLATFORM_LINUX)
|
||||
struct wl_display;
|
||||
struct wl_surface;
|
||||
typedef struct _XDisplay Display;
|
||||
#else defined(LIGHT_PLATFORM_WINDOWS)
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
export module surface.system:components;
|
||||
|
||||
import preliminary;
|
||||
import std;
|
||||
import math.vec2;
|
||||
import surface.events;
|
||||
import surface.requests;
|
||||
|
||||
export namespace lt::surface {
|
||||
namespace lt::surface {
|
||||
|
||||
/** Represents a platform's surface (eg. a Window).
|
||||
*
|
||||
* @note This is a "system component"
|
||||
*/
|
||||
class SurfaceComponent
|
||||
export class SurfaceComponent
|
||||
{
|
||||
public:
|
||||
friend class System;
|
||||
|
|
@ -45,9 +43,9 @@ public:
|
|||
#if defined(LIGHT_PLATFORM_LINUX)
|
||||
struct NativeData
|
||||
{
|
||||
wl_display *display;
|
||||
|
||||
wl_surface *surface;
|
||||
Display *display;
|
||||
std::uint32_t window;
|
||||
unsigned long wm_delete_message;
|
||||
};
|
||||
#elif defined(LIGHT_PLATFORM_WINDOWS)
|
||||
struct NativeData
|
||||
|
|
@ -60,13 +58,11 @@ public:
|
|||
|
||||
static constexpr auto max_title_length = 256;
|
||||
|
||||
// TODO(Light): add `center_to_screen` flag
|
||||
// TODO(Light): add `screen_mode` flag (windowed/full_screen/windowed_full_screen)
|
||||
struct CreateInfo
|
||||
{
|
||||
std::string_view title;
|
||||
|
||||
math::vec2_u32 resolution;
|
||||
math::uvec2 resolution;
|
||||
|
||||
bool vsync;
|
||||
|
||||
|
|
@ -78,12 +74,12 @@ public:
|
|||
return m_title;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_resolution() const -> const math::vec2_u32 &
|
||||
[[nodiscard]] auto get_resolution() const -> const math::uvec2 &
|
||||
{
|
||||
return m_resolution;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_position() const -> const math::vec2_i32 &
|
||||
[[nodiscard]] auto get_position() const -> const math::ivec2 &
|
||||
{
|
||||
return m_position;
|
||||
}
|
||||
|
|
@ -136,9 +132,9 @@ private:
|
|||
|
||||
std::string m_title;
|
||||
|
||||
math::vec2_u32 m_resolution;
|
||||
math::uvec2 m_resolution;
|
||||
|
||||
math::vec2_i32 m_position;
|
||||
math::ivec2 m_position;
|
||||
|
||||
bool m_vsync;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
export module surface.events;
|
||||
|
||||
import preliminary;
|
||||
import input.codes;
|
||||
import math.vec2;
|
||||
import std;
|
||||
|
||||
export namespace lt::surface {
|
||||
|
||||
|
|
@ -93,7 +92,7 @@ private:
|
|||
class MouseMovedEvent
|
||||
{
|
||||
public:
|
||||
MouseMovedEvent(f32 x, f32 y): m_position(x, y)
|
||||
MouseMovedEvent(float x, float y): m_position(x, y)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -102,12 +101,12 @@ public:
|
|||
return m_position;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_x() const -> f32
|
||||
[[nodiscard]] auto get_x() const -> float
|
||||
{
|
||||
return m_position.x;
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_y() const -> f32
|
||||
[[nodiscard]] auto get_y() const -> float
|
||||
{
|
||||
return m_position.y;
|
||||
}
|
||||
|
|
@ -124,11 +123,11 @@ private:
|
|||
class WheelScrolledEvent
|
||||
{
|
||||
public:
|
||||
WheelScrolledEvent(f32 offset): m_offset(offset)
|
||||
WheelScrolledEvent(float offset): m_offset(offset)
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_offset() const -> f32
|
||||
[[nodiscard]] auto get_offset() const -> float
|
||||
{
|
||||
return m_offset;
|
||||
}
|
||||
|
|
@ -141,7 +140,7 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
f32 m_offset;
|
||||
float m_offset;
|
||||
};
|
||||
|
||||
class ButtonPressedEvent
|
||||
|
|
@ -198,11 +197,11 @@ public:
|
|||
class MovedEvent
|
||||
{
|
||||
public:
|
||||
MovedEvent(i32 x, i32 y): m_position(x, y)
|
||||
MovedEvent(std::int32_t x, std::int32_t y): m_position(x, y)
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_position() const -> const math::vec2_i32 &
|
||||
[[nodiscard]] auto get_position() const -> const math::ivec2 &
|
||||
{
|
||||
return m_position;
|
||||
}
|
||||
|
|
@ -213,17 +212,17 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
math::vec2_i32 m_position;
|
||||
math::ivec2 m_position;
|
||||
};
|
||||
|
||||
class ResizedEvent
|
||||
{
|
||||
public:
|
||||
ResizedEvent(u32 width, u32 height): m_size(width, height)
|
||||
ResizedEvent(std::uint32_t width, std::uint32_t height): m_size(width, height)
|
||||
{
|
||||
}
|
||||
|
||||
[[nodiscard]] auto get_size() const -> const math::vec2_u32 &
|
||||
[[nodiscard]] auto get_size() const -> const math::uvec2 &
|
||||
{
|
||||
return m_size;
|
||||
}
|
||||
|
|
@ -234,7 +233,7 @@ public:
|
|||
}
|
||||
|
||||
private:
|
||||
math::vec2_u32 m_size;
|
||||
math::uvec2 m_size;
|
||||
};
|
||||
|
||||
class LostFocusEvent
|
||||
|
|
|
|||
506
modules/surface/platform_linux.cpp
Normal file
506
modules/surface/platform_linux.cpp
Normal file
|
|
@ -0,0 +1,506 @@
|
|||
module;
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/keysym.h>
|
||||
#include <X11/keysymdef.h>
|
||||
|
||||
module surface.system;
|
||||
import debug.assertions;
|
||||
import memory.reference;
|
||||
import surface.requests;
|
||||
import surface.events;
|
||||
import logger;
|
||||
import ecs.registry;
|
||||
import time;
|
||||
import std;
|
||||
import input.codes;
|
||||
|
||||
namespace lt::surface {
|
||||
|
||||
template<int EventType>
|
||||
auto XEventTypeEquals(Display *display, XEvent *event, char *winptr) -> int
|
||||
{
|
||||
std::ignore = display;
|
||||
return (
|
||||
event->type == EventType
|
||||
&& *(std::bit_cast<const Window *>(winptr))
|
||||
== std::bit_cast<const XAnyEvent *>(event)->window
|
||||
);
|
||||
}
|
||||
|
||||
template<class... Ts>
|
||||
struct overloads: Ts...
|
||||
{
|
||||
using Ts::operator()...;
|
||||
};
|
||||
|
||||
void ensure_component_sanity(const SurfaceComponent &component);
|
||||
|
||||
constexpr auto all_events_mask = KeyPressMask | //
|
||||
KeyReleaseMask | //
|
||||
ButtonPressMask | //
|
||||
ButtonReleaseMask | //
|
||||
EnterWindowMask | //
|
||||
LeaveWindowMask | //
|
||||
PointerMotionMask | //
|
||||
KeymapStateMask | //
|
||||
ExposureMask | //
|
||||
VisibilityChangeMask | //
|
||||
StructureNotifyMask | //
|
||||
FocusChangeMask | //
|
||||
ColormapChangeMask | //
|
||||
OwnerGrabButtonMask;
|
||||
|
||||
System::System(memory::Ref<ecs::Registry> registry): m_registry(std::move(registry))
|
||||
{
|
||||
debug::ensure(m_registry, "Failed to initialize surface system: null registry");
|
||||
|
||||
debug::ensure(
|
||||
m_registry->view<SurfaceComponent>().get_size() == 0,
|
||||
"Failed to initialize surface system: registry has surface component(s)"
|
||||
);
|
||||
|
||||
m_registry->connect_on_destruct<SurfaceComponent>(
|
||||
[this](ecs::Registry ®istry, ecs::EntityId entity) {
|
||||
on_surface_destruct(registry, entity);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
System::~System()
|
||||
{
|
||||
if (!m_registry)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// TODO(Light): make registry.remove not invalidate iterators
|
||||
auto entities_to_remove = std::vector<ecs::EntityId> {};
|
||||
for (auto &[entity, surface] : m_registry->view<SurfaceComponent>())
|
||||
{
|
||||
entities_to_remove.emplace_back(entity);
|
||||
}
|
||||
|
||||
for (auto entity : entities_to_remove)
|
||||
{
|
||||
m_registry->remove<SurfaceComponent>(entity);
|
||||
}
|
||||
|
||||
m_registry->disconnect_on_construct<SurfaceComponent>();
|
||||
m_registry->disconnect_on_destruct<SurfaceComponent>();
|
||||
}
|
||||
catch (const std::exception &exp)
|
||||
{
|
||||
log::error("Uncaught exception in surface::~System:");
|
||||
log::error("\twhat: {}", exp.what());
|
||||
}
|
||||
}
|
||||
|
||||
void System::on_register()
|
||||
{
|
||||
}
|
||||
|
||||
void System::on_unregister()
|
||||
{
|
||||
}
|
||||
|
||||
void System::create_surface_component(ecs::EntityId entity, SurfaceComponent::CreateInfo info)
|
||||
try
|
||||
{
|
||||
auto &component = m_registry->add<SurfaceComponent>(entity, info);
|
||||
auto &surface = m_registry->get<SurfaceComponent>(entity);
|
||||
const auto &resolution = surface.get_resolution();
|
||||
const auto &position = surface.get_position();
|
||||
ensure_component_sanity(surface);
|
||||
|
||||
// TODO(Light): refactor "environment" into standalone module
|
||||
// NOLINTNEXTLINE(concurrency-mt-unsafe)
|
||||
auto *display_env = std::getenv("DISPLAY");
|
||||
debug::ensure(display_env != nullptr, "DISPLAY env var not found!");
|
||||
|
||||
auto *display = XOpenDisplay(display_env);
|
||||
debug::ensure(display, "Failed to open XDisplay with DISPLAY: {}", display_env);
|
||||
|
||||
auto root_window = XDefaultRootWindow(display);
|
||||
|
||||
auto border_width = 0;
|
||||
auto depth = std::int32_t { CopyFromParent };
|
||||
auto window_class = CopyFromParent;
|
||||
auto *visual = (Visual *)CopyFromParent;
|
||||
|
||||
auto attribute_value_mask = CWBackPixel | CWEventMask;
|
||||
auto attributes = XSetWindowAttributes {
|
||||
.background_pixel = 0xffafe9af,
|
||||
.event_mask = all_events_mask,
|
||||
};
|
||||
|
||||
typedef struct Hints
|
||||
{
|
||||
unsigned long flags;
|
||||
unsigned long functions;
|
||||
unsigned long decorations;
|
||||
long inputMode;
|
||||
unsigned long status;
|
||||
} Hints;
|
||||
|
||||
auto main_window = XCreateWindow(
|
||||
display,
|
||||
root_window,
|
||||
position.x,
|
||||
position.y,
|
||||
resolution.x,
|
||||
resolution.y,
|
||||
border_width,
|
||||
depth,
|
||||
window_class,
|
||||
visual,
|
||||
attribute_value_mask,
|
||||
&attributes
|
||||
);
|
||||
surface.m_native_data.display = display;
|
||||
surface.m_native_data.window = main_window;
|
||||
|
||||
surface.m_native_data.wm_delete_message = XInternAtom(display, "WM_DELETE_WINDOW", False);
|
||||
XSetWMProtocols(display, main_window, &surface.m_native_data.wm_delete_message, 1);
|
||||
|
||||
// code to remove decoration
|
||||
auto hints = std::array<const unsigned char, 5> { 2, 0, 0, 0, 0 };
|
||||
const auto motif_hints = XInternAtom(display, "_MOTIF_WM_HINTS", False);
|
||||
|
||||
XChangeProperty(
|
||||
display,
|
||||
surface.m_native_data.window,
|
||||
motif_hints,
|
||||
motif_hints,
|
||||
32,
|
||||
PropModeReplace,
|
||||
hints.data(),
|
||||
5
|
||||
);
|
||||
|
||||
XMapWindow(display, main_window);
|
||||
XStoreName(display, main_window, surface.m_title.c_str());
|
||||
XFlush(display);
|
||||
|
||||
if (!surface.is_visible())
|
||||
{
|
||||
XUnmapWindow(display, main_window);
|
||||
}
|
||||
}
|
||||
catch (const std::exception &exp)
|
||||
{
|
||||
log::error("Exception thrown when on_constructing surface component");
|
||||
log::error("\tentity: {}", entity);
|
||||
log::error("\twhat: {}", exp.what());
|
||||
m_registry->remove<SurfaceComponent>(entity);
|
||||
}
|
||||
|
||||
void System::on_surface_destruct(ecs::Registry ®istry, ecs::EntityId entity)
|
||||
{
|
||||
const auto &[display, window, _] = registry.get<SurfaceComponent>(entity).get_native_data();
|
||||
if (!display)
|
||||
{
|
||||
log::warn("Surface component destroyed with null display");
|
||||
return;
|
||||
}
|
||||
|
||||
XDestroyWindow(display, window);
|
||||
XCloseDisplay(display);
|
||||
}
|
||||
|
||||
void System::handle_events(SurfaceComponent &surface)
|
||||
{
|
||||
auto &queue = surface.m_event_queue;
|
||||
queue.clear();
|
||||
|
||||
auto event = XEvent {};
|
||||
auto &[display, window, wm_delete_message] = surface.m_native_data;
|
||||
|
||||
XFlush(display);
|
||||
while (XEventsQueued(display, QueuedAlready) != 0)
|
||||
{
|
||||
XNextEvent(surface.m_native_data.display, &event);
|
||||
|
||||
switch (event.type)
|
||||
{
|
||||
case KeyPress:
|
||||
{
|
||||
queue.emplace_back<KeyPressedEvent>(static_cast<Key>(XLookupKeysym(&event.xkey, 0)));
|
||||
break;
|
||||
}
|
||||
case KeyRelease:
|
||||
{
|
||||
queue.emplace_back<KeyReleasedEvent>(static_cast<Key>(XLookupKeysym(&event.xkey, 0)));
|
||||
break;
|
||||
}
|
||||
case ButtonPress:
|
||||
{
|
||||
queue.emplace_back<ButtonPressedEvent>(static_cast<Key>(event.xbutton.button));
|
||||
break;
|
||||
}
|
||||
case ButtonRelease:
|
||||
{
|
||||
queue.emplace_back<ButtonReleasedEvent>(static_cast<Key>(event.xbutton.button));
|
||||
break;
|
||||
}
|
||||
case FocusIn:
|
||||
{
|
||||
queue.emplace_back<GainFocusEvent>({});
|
||||
break;
|
||||
}
|
||||
case FocusOut:
|
||||
{
|
||||
queue.emplace_back<LostFocusEvent>({});
|
||||
break;
|
||||
}
|
||||
case ClientMessage:
|
||||
{
|
||||
if (event.xclient.data.l[0] == wm_delete_message)
|
||||
{
|
||||
queue.emplace_back<ClosedEvent>({});
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case MotionNotify:
|
||||
{
|
||||
queue.emplace_back<MouseMovedEvent>(MouseMovedEvent {
|
||||
static_cast<float>(event.xmotion.x),
|
||||
static_cast<float>(event.xmotion.y),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case ConfigureNotify:
|
||||
{
|
||||
const auto [prev_width, prev_height] = surface.get_resolution();
|
||||
const auto new_width = event.xconfigure.width;
|
||||
const auto new_height = event.xconfigure.height;
|
||||
if (prev_width != new_width || prev_height != new_height)
|
||||
{
|
||||
surface.m_resolution.x = new_width;
|
||||
surface.m_resolution.y = new_height;
|
||||
queue.emplace_back<ResizedEvent>(ResizedEvent {
|
||||
static_cast<std::uint32_t>(new_width),
|
||||
static_cast<std::uint32_t>(new_height),
|
||||
});
|
||||
}
|
||||
|
||||
const auto [prev_x, prev_y] = surface.get_position();
|
||||
const auto new_x = event.xconfigure.x;
|
||||
const auto new_y = event.xconfigure.y;
|
||||
if (prev_x != new_x || prev_y != new_y)
|
||||
{
|
||||
surface.m_position.x = new_x;
|
||||
surface.m_position.y = new_y;
|
||||
queue.emplace_back<MovedEvent>(MovedEvent {
|
||||
new_x,
|
||||
new_y,
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: break; /* pass */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void System::handle_requests(SurfaceComponent &surface)
|
||||
{
|
||||
const auto visitor = overloads {
|
||||
[&](const ModifyTitleRequest &request) { modify_title(surface, request); },
|
||||
[&](const ModifyResolutionRequest &request) { modify_resolution(surface, request); },
|
||||
[&](const ModifyPositionRequest &request) { modify_position(surface, request); },
|
||||
[&](const ModifyVisibilityRequest &request) { modify_visiblity(surface, request); },
|
||||
[&](const auto &) { log::error("Unknown surface request"); },
|
||||
};
|
||||
|
||||
for (const auto &request : surface.peek_requests())
|
||||
{
|
||||
std::visit(visitor, request);
|
||||
}
|
||||
|
||||
surface.m_requests.clear();
|
||||
}
|
||||
|
||||
void System::modify_title(SurfaceComponent &surface, const ModifyTitleRequest &request)
|
||||
{
|
||||
surface.m_title = request.title;
|
||||
|
||||
const auto &[display, window, _] = surface.get_native_data();
|
||||
XStoreName(display, window, request.title.c_str());
|
||||
}
|
||||
|
||||
void System::modify_resolution(SurfaceComponent &surface, const ModifyResolutionRequest &request)
|
||||
{
|
||||
// surface.m_resolution = request.resolution;
|
||||
|
||||
auto &[display, window, _] = surface.m_native_data;
|
||||
const auto &[width, height] = request.resolution;
|
||||
// XResizeWindow(display, window, width, height);
|
||||
|
||||
// get baseline serial number for X requests generated from XResizeWindow
|
||||
auto serial = NextRequest(display);
|
||||
|
||||
// request a new window size from the X server
|
||||
XResizeWindow(
|
||||
display,
|
||||
window,
|
||||
static_cast<std::uint32_t>(width),
|
||||
static_cast<std::uint32_t>(height)
|
||||
);
|
||||
|
||||
// flush output queue and wait for X server to processes the request
|
||||
XSync(display, False);
|
||||
// The documentation for XResizeWindow includes this important note:
|
||||
//
|
||||
// If the override-redirect flag of the window is False and some
|
||||
// other client has selected SubstructureRedirectMask on the parent,
|
||||
// the X server generates a ConfigureRequest event, and no further
|
||||
// processing is performed.
|
||||
//
|
||||
// What this means, essentially, is that if this window is a top-level
|
||||
// window, then it's the window manager (the "other client") that is
|
||||
// responsible for changing this window's size. So when we call
|
||||
// XResizeWindow() on a top-level window, then instead of resizing
|
||||
// the window immediately, the X server informs the window manager,
|
||||
// and then the window manager sets our new size (usually it will be
|
||||
// the size we asked for). We receive a ConfigureNotify event when
|
||||
// our new size has been set.
|
||||
constexpr auto lifespan = std::chrono::milliseconds { 10 };
|
||||
auto timer = time::Timer {};
|
||||
auto event = XEvent {};
|
||||
while (!XCheckIfEvent(
|
||||
display,
|
||||
&event,
|
||||
XEventTypeEquals<ConfigureNotify>,
|
||||
reinterpret_cast<XPointer>(&window) // NOLINT
|
||||
)
|
||||
|| event.xconfigure.serial < serial)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::microseconds { 100 });
|
||||
if (timer.elapsed_time() > lifespan)
|
||||
{
|
||||
log::error("Timed out waiting for XResizeWindow's event");
|
||||
return;
|
||||
}
|
||||
}
|
||||
// We don't need to update the component's state and handle the event in this funcion.
|
||||
// Since handle_requests is called before handle_events.
|
||||
// So we just put the event back into the queue and move on.
|
||||
XPutBackEvent(display, &event);
|
||||
XSync(display, False);
|
||||
XFlush(display);
|
||||
}
|
||||
|
||||
void System::modify_position(SurfaceComponent &surface, const ModifyPositionRequest &request)
|
||||
{
|
||||
// surface.m_position = request.position;
|
||||
|
||||
auto &[display, window, _] = surface.m_native_data;
|
||||
const auto &[x, y] = request.position;
|
||||
|
||||
// get baseline serial number for X requests generated from XResizeWindow
|
||||
auto serial = NextRequest(display);
|
||||
XMoveWindow(display, window, static_cast<int>(x), static_cast<int>(y));
|
||||
|
||||
// flush output queue and wait for X server to processes the request
|
||||
XSync(display, False);
|
||||
constexpr auto lifespan = std::chrono::milliseconds { 10 };
|
||||
auto timer = time::Timer {};
|
||||
auto event = XEvent {};
|
||||
while (!XCheckIfEvent(
|
||||
display,
|
||||
&event,
|
||||
XEventTypeEquals<ConfigureNotify>,
|
||||
reinterpret_cast<XPointer>(&window) // NOLINT
|
||||
)
|
||||
|| event.xconfigure.serial < serial)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::microseconds { 100 });
|
||||
if (timer.elapsed_time() > lifespan)
|
||||
{
|
||||
log::error("Timed out waiting for XMoveWindow's event");
|
||||
return;
|
||||
}
|
||||
}
|
||||
// We don't need to update the component's state and handle the event in this funcion.
|
||||
// Since handle_requests is called before handle_events.
|
||||
// So we just put the event back into the queue and move on.
|
||||
XPutBackEvent(display, &event);
|
||||
XSync(display, False);
|
||||
XFlush(display);
|
||||
}
|
||||
|
||||
void System::modify_visiblity(SurfaceComponent &surface, const ModifyVisibilityRequest &request)
|
||||
{
|
||||
const auto &[display, window, _] = surface.get_native_data();
|
||||
surface.m_visible = request.visible;
|
||||
|
||||
if (request.visible)
|
||||
{
|
||||
XMapWindow(display, window);
|
||||
}
|
||||
else
|
||||
{
|
||||
XUnmapWindow(display, window);
|
||||
}
|
||||
}
|
||||
|
||||
void System::tick(app::TickInfo tick)
|
||||
{
|
||||
for (auto &[id, surface] : m_registry->view<SurfaceComponent>())
|
||||
{
|
||||
handle_requests(surface);
|
||||
handle_events(surface);
|
||||
}
|
||||
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
m_last_tick_result = app::TickResult {
|
||||
.info = tick,
|
||||
.duration = now - tick.start_time,
|
||||
.end_time = now,
|
||||
};
|
||||
}
|
||||
|
||||
void ensure_component_sanity(const SurfaceComponent &component)
|
||||
{
|
||||
auto [width, height] = component.get_resolution();
|
||||
|
||||
debug::ensure(width != 0u, "Received bad values for surface component: width({}) == 0", width);
|
||||
|
||||
debug::ensure(
|
||||
height != 0u,
|
||||
"Received bad values for surface component: height({}) == 0",
|
||||
height
|
||||
);
|
||||
|
||||
debug::ensure(
|
||||
width < SurfaceComponent::max_dimension,
|
||||
"Received bad values for surface component: width({}) > max_dimension({})",
|
||||
width,
|
||||
SurfaceComponent::max_dimension
|
||||
);
|
||||
|
||||
debug::ensure(
|
||||
height < SurfaceComponent::max_dimension,
|
||||
"Received bad values for surface component: height({}) > max_dimension({})",
|
||||
height,
|
||||
SurfaceComponent::max_dimension
|
||||
);
|
||||
|
||||
debug::ensure(
|
||||
component.get_title().size() < SurfaceComponent::max_title_length,
|
||||
"Received bad values for surface component: title.size({}) > max_title_length({})",
|
||||
component.get_title().size(),
|
||||
SurfaceComponent::max_title_length
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace lt::surface
|
||||
|
||||
namespace lt {
|
||||
|
||||
} // namespace lt
|
||||
527
modules/surface/platform_windows.cpp
Normal file
527
modules/surface/platform_windows.cpp
Normal file
|
|
@ -0,0 +1,527 @@
|
|||
module;
|
||||
#include <Windows.h>
|
||||
module surface.system;
|
||||
import surface.constants;
|
||||
import debug.assertions;
|
||||
import memory.reference;
|
||||
import surface.requests;
|
||||
import surface.events;
|
||||
import logger;
|
||||
import ecs.registry;
|
||||
import ecs.entity;
|
||||
import time;
|
||||
import std;
|
||||
|
||||
namespace lt::surface {
|
||||
|
||||
template<class... Ts>
|
||||
struct overloads: Ts...
|
||||
{
|
||||
using Ts::operator()...;
|
||||
};
|
||||
|
||||
void ensure_component_sanity(const SurfaceComponent &component);
|
||||
|
||||
auto CALLBACK native_window_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -> LRESULT;
|
||||
|
||||
System::System(memory::Ref<ecs::Registry> registry): m_registry(std::move(registry))
|
||||
{
|
||||
debug::ensure(m_registry, "Failed to initialize surface system: null registry");
|
||||
|
||||
debug::ensure(
|
||||
m_registry->view<SurfaceComponent>().get_size() == 0,
|
||||
"Failed to initialize surface system: registry has surface component(s)"
|
||||
);
|
||||
|
||||
m_registry->connect_on_destruct<SurfaceComponent>(
|
||||
[this](ecs::Registry ®istry, ecs::EntityId entity) {
|
||||
on_surface_destruct(registry, entity);
|
||||
}
|
||||
);
|
||||
|
||||
auto window_class = WNDCLASS {
|
||||
.lpfnWndProc = native_window_proc,
|
||||
.hInstance = GetModuleHandle(nullptr),
|
||||
.lpszClassName = constants::class_name,
|
||||
};
|
||||
RegisterClass(&window_class);
|
||||
}
|
||||
|
||||
System::~System()
|
||||
{
|
||||
if (!m_registry)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// TODO(Light): make registry.remove not invalidate iterators
|
||||
auto entities_to_remove = std::vector<ecs::EntityId> {};
|
||||
for (auto &[entity, surface] : m_registry->view<SurfaceComponent>())
|
||||
{
|
||||
entities_to_remove.emplace_back(entity);
|
||||
}
|
||||
|
||||
for (auto entity : entities_to_remove)
|
||||
{
|
||||
m_registry->remove<SurfaceComponent>(entity);
|
||||
}
|
||||
|
||||
m_registry->disconnect_on_construct<SurfaceComponent>();
|
||||
m_registry->disconnect_on_destruct<SurfaceComponent>();
|
||||
}
|
||||
catch (const std::exception &exp)
|
||||
{
|
||||
log::error("Uncaught exception in surface::~System:");
|
||||
log::error("\twhat: {}", exp.what());
|
||||
}
|
||||
}
|
||||
|
||||
void System::on_register()
|
||||
{
|
||||
}
|
||||
|
||||
void System::on_unregister()
|
||||
{
|
||||
}
|
||||
|
||||
void System::create_surface_component(ecs::EntityId entity, SurfaceComponent::CreateInfo info)
|
||||
try
|
||||
{
|
||||
auto &component = m_registry->add<SurfaceComponent>(entity, info);
|
||||
auto &surface = m_registry->get<SurfaceComponent>(entity);
|
||||
const auto &resolution = surface.get_resolution();
|
||||
const auto &position = surface.get_position();
|
||||
ensure_component_sanity(surface);
|
||||
|
||||
surface.m_native_data.window = CreateWindowEx(
|
||||
0,
|
||||
constants::class_name,
|
||||
info.title.data(),
|
||||
WS_OVERLAPPEDWINDOW,
|
||||
CW_USEDEFAULT,
|
||||
CW_USEDEFAULT,
|
||||
CW_USEDEFAULT,
|
||||
CW_USEDEFAULT,
|
||||
nullptr,
|
||||
nullptr,
|
||||
GetModuleHandle(nullptr),
|
||||
nullptr
|
||||
);
|
||||
debug::ensure(surface.m_native_data.window, "Failed to create Windows surface component");
|
||||
|
||||
ShowWindow(surface.m_native_data.window, SW_NORMAL);
|
||||
|
||||
// TODO(Light): refactor "environment" into standalone module
|
||||
// NOLINTNEXTLINE(concurrency-mt-unsafe)
|
||||
// auto *display_env = std::getenv("DISPLAY");
|
||||
// debug::ensure(display_env != nullptr, "DISPLAY env var not found!");
|
||||
//
|
||||
// auto *display = XOpenDisplay(display_env);
|
||||
// debug::ensure(display, "Failed to open XDisplay with DISPLAY: {}", display_env);
|
||||
//
|
||||
// auto root_window = XDefaultRootWindow(display);
|
||||
//
|
||||
// auto border_width = 0;
|
||||
// auto depth = std::int32_t { CopyFromParent };
|
||||
// auto window_class = CopyFromParent;
|
||||
// auto *visual = (Visual *)CopyFromParent;
|
||||
//
|
||||
// auto attribute_value_mask = CWBackPixel | CWEventMask;
|
||||
// auto attributes = XSetWindowAttributes {
|
||||
// .background_pixel = 0xffafe9af,
|
||||
// .event_mask = all_events_mask,
|
||||
// };
|
||||
//
|
||||
// typedef struct Hints
|
||||
// {
|
||||
// unsigned long flags;
|
||||
// unsigned long functions;
|
||||
// unsigned long decorations;
|
||||
// long inputMode;
|
||||
// unsigned long status;
|
||||
// } Hints;
|
||||
//
|
||||
// auto main_window = XCreateWindow(
|
||||
// display,
|
||||
// root_window,
|
||||
// position.x,
|
||||
// position.y,
|
||||
// resolution.x,
|
||||
// resolution.y,
|
||||
// border_width,
|
||||
// depth,
|
||||
// window_class,
|
||||
// visual,
|
||||
// attribute_value_mask,
|
||||
// &attributes
|
||||
// );
|
||||
// surface.m_native_data.display = display;
|
||||
// surface.m_native_data.window = main_window;
|
||||
//
|
||||
// surface.m_native_data.wm_delete_message = XInternAtom(display, "WM_DELETE_WINDOW", False);
|
||||
// XSetWMProtocols(display, main_window, &surface.m_native_data.wm_delete_message, 1);
|
||||
//
|
||||
// // code to remove decoration
|
||||
// auto hints = std::array<const unsigned char, 5> { 2, 0, 0, 0, 0 };
|
||||
// const auto motif_hints = XInternAtom(display, "_MOTIF_WM_HINTS", False);
|
||||
//
|
||||
// XChangeProperty(
|
||||
// display,
|
||||
// surface.m_native_data.window,
|
||||
// motif_hints,
|
||||
// motif_hints,
|
||||
// 32,
|
||||
// PropModeReplace,
|
||||
// hints.data(),
|
||||
// 5
|
||||
// );
|
||||
//
|
||||
// XMapWindow(display, main_window);
|
||||
// XStoreName(display, main_window, surface.m_title.c_str());
|
||||
// XFlush(display);
|
||||
//
|
||||
// if (!surface.is_visible())
|
||||
// {
|
||||
// XUnmapWindow(display, main_window);
|
||||
// }
|
||||
}
|
||||
catch (const std::exception &exp)
|
||||
{
|
||||
log::error("Exception thrown when on_constructing surface component");
|
||||
log::error("\tentity: {}", std::uint32_t { entity });
|
||||
log::error("\twhat: {}", exp.what());
|
||||
m_registry->remove<SurfaceComponent>(entity);
|
||||
}
|
||||
|
||||
void System::on_surface_destruct(ecs::Registry ®istry, ecs::EntityId entity)
|
||||
{
|
||||
auto *window = registry.get<SurfaceComponent>(entity).get_native_data().window;
|
||||
if (!window)
|
||||
{
|
||||
log::warn("Surface component destroyed with null window handle");
|
||||
return;
|
||||
}
|
||||
|
||||
DestroyWindow(window);
|
||||
}
|
||||
|
||||
void System::handle_events(SurfaceComponent &surface)
|
||||
{
|
||||
auto &queue = surface.m_event_queue;
|
||||
queue.clear();
|
||||
|
||||
auto message = MSG {};
|
||||
while (PeekMessage(&message, 0, {}, {}, PM_REMOVE))
|
||||
{
|
||||
switch (message.message)
|
||||
{
|
||||
}
|
||||
|
||||
log::debug("Window message type: {}", std::uint32_t { message.message });
|
||||
}
|
||||
|
||||
// auto event = XEvent {};
|
||||
// auto &[display, window, wm_delete_message] = surface.m_native_data;
|
||||
//
|
||||
// XFlush(display);
|
||||
// while (XEventsQueued(display, QueuedAlready) != 0)
|
||||
// {
|
||||
// XNextEvent(surface.m_native_data.display, &event);
|
||||
//
|
||||
// switch (event.type)
|
||||
// {
|
||||
// case KeyPress:
|
||||
// {
|
||||
// queue.emplace_back<KeyPressedEvent>(
|
||||
// static_cast<std::uint32_t>(XLookupKeysym(&event.xkey, 0))
|
||||
// );
|
||||
// break;
|
||||
// }
|
||||
// case KeyRelease:
|
||||
// {
|
||||
// queue.emplace_back<KeyReleasedEvent>(
|
||||
// static_cast<std::uint32_t>(XLookupKeysym(&event.xkey, 0))
|
||||
// );
|
||||
// break;
|
||||
// }
|
||||
// case ButtonPress:
|
||||
// {
|
||||
// queue.emplace_back<ButtonPressedEvent>(static_cast<int>(event.xbutton.button));
|
||||
// break;
|
||||
// }
|
||||
// case ButtonRelease:
|
||||
// {
|
||||
// queue.emplace_back<ButtonReleasedEvent>(static_cast<int>(event.xbutton.button));
|
||||
// break;
|
||||
// }
|
||||
// case FocusIn:
|
||||
// {
|
||||
// queue.emplace_back<GainFocusEvent>({});
|
||||
// break;
|
||||
// }
|
||||
// case FocusOut:
|
||||
// {
|
||||
// queue.emplace_back<LostFocusEvent>({});
|
||||
// break;
|
||||
// }
|
||||
// case ClientMessage:
|
||||
// {
|
||||
// if (event.xclient.data.l[0] == wm_delete_message)
|
||||
// {
|
||||
// queue.emplace_back<ClosedEvent>({});
|
||||
// }
|
||||
//
|
||||
// break;
|
||||
// }
|
||||
// case MotionNotify:
|
||||
// {
|
||||
// queue.emplace_back<MouseMovedEvent>(MouseMovedEvent {
|
||||
// static_cast<float>(event.xmotion.x),
|
||||
// static_cast<float>(event.xmotion.y),
|
||||
// });
|
||||
// break;
|
||||
// }
|
||||
// case ConfigureNotify:
|
||||
// {
|
||||
// const auto [prev_width, prev_height] = surface.get_resolution();
|
||||
// const auto new_width = event.xconfigure.width;
|
||||
// const auto new_height = event.xconfigure.height;
|
||||
// if (prev_width != new_width || prev_height != new_height)
|
||||
// {
|
||||
// surface.m_resolution.x = new_width;
|
||||
// surface.m_resolution.y = new_height;
|
||||
// queue.emplace_back<ResizedEvent>(ResizedEvent {
|
||||
// static_cast<std::uint32_t>(new_width),
|
||||
// static_cast<std::uint32_t>(new_height),
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// const auto [prev_x, prev_y] = surface.get_position();
|
||||
// const auto new_x = event.xconfigure.x;
|
||||
// const auto new_y = event.xconfigure.y;
|
||||
// if (prev_x != new_x || prev_y != new_y)
|
||||
// {
|
||||
// surface.m_position.x = new_x;
|
||||
// surface.m_position.y = new_y;
|
||||
// queue.emplace_back<MovedEvent>(MovedEvent {
|
||||
// new_x,
|
||||
// new_y,
|
||||
// });
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// default: break; /* pass */
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
void System::handle_requests(SurfaceComponent &surface)
|
||||
{
|
||||
const auto visitor = overloads {
|
||||
[&](const ModifyTitleRequest &request) { modify_title(surface, request); },
|
||||
[&](const ModifyResolutionRequest &request) { modify_resolution(surface, request); },
|
||||
[&](const ModifyPositionRequest &request) { modify_position(surface, request); },
|
||||
[&](const ModifyVisibilityRequest &request) { modify_visiblity(surface, request); },
|
||||
[&](const auto &) { log::error("Unknown surface request"); },
|
||||
};
|
||||
|
||||
for (const auto &request : surface.peek_requests())
|
||||
{
|
||||
std::visit(visitor, request);
|
||||
}
|
||||
|
||||
surface.m_requests.clear();
|
||||
}
|
||||
|
||||
void System::modify_title(SurfaceComponent &surface, const ModifyTitleRequest &request)
|
||||
{
|
||||
surface.m_title = request.title;
|
||||
|
||||
// const auto &[display, window, _] = surface.get_native_data();
|
||||
// XStoreName(display, window, request.title.c_str());
|
||||
}
|
||||
|
||||
void System::modify_resolution(SurfaceComponent &surface, const ModifyResolutionRequest &request)
|
||||
{
|
||||
// surface.m_resolution = request.resolution;
|
||||
|
||||
// auto &[display, window, _] = surface.m_native_data;
|
||||
// const auto &[width, height] = request.resolution;
|
||||
// // XResizeWindow(display, window, width, height);
|
||||
//
|
||||
// // get baseline serial number for X requests generated from XResizeWindow
|
||||
// auto serial = NextRequest(display);
|
||||
//
|
||||
// // request a new window size from the X server
|
||||
// XResizeWindow(
|
||||
// display,
|
||||
// window,
|
||||
// static_cast<std::uint32_t>(width),
|
||||
// static_cast<std::uint32_t>(height)
|
||||
// );
|
||||
//
|
||||
// // flush output queue and wait for X server to processes the request
|
||||
// XSync(display, False);
|
||||
// // The documentation for XResizeWindow includes this important note:
|
||||
// //
|
||||
// // If the override-redirect flag of the window is False and some
|
||||
// // other client has selected SubstructureRedirectMask on the parent,
|
||||
// // the X server generates a ConfigureRequest event, and no further
|
||||
// // processing is performed.
|
||||
// //
|
||||
// // What this means, essentially, is that if this window is a top-level
|
||||
// // window, then it's the window manager (the "other client") that is
|
||||
// // responsible for changing this window's size. So when we call
|
||||
// // XResizeWindow() on a top-level window, then instead of resizing
|
||||
// // the window immediately, the X server informs the window manager,
|
||||
// // and then the window manager sets our new size (usually it will be
|
||||
// // the size we asked for). We receive a ConfigureNotify event when
|
||||
// // our new size has been set.
|
||||
// constexpr auto lifespan = std::chrono::milliseconds { 10 };
|
||||
// auto timer = time::Timer {};
|
||||
// auto event = XEvent {};
|
||||
// while (!XCheckIfEvent(
|
||||
// display,
|
||||
// &event,
|
||||
// XEventTypeEquals<ConfigureNotify>,
|
||||
// reinterpret_cast<XPointer>(&window) // NOLINT
|
||||
// )
|
||||
// || event.xconfigure.serial < serial)
|
||||
// {
|
||||
// std::this_thread::sleep_for(std::chrono::microseconds { 100 });
|
||||
// if (timer.elapsed_time() > lifespan)
|
||||
// {
|
||||
// log::error("Timed out waiting for XResizeWindow's event");
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// // We don't need to update the component's state and handle the event in this funcion.
|
||||
// // Since handle_requests is called before handle_events.
|
||||
// // So we just put the event back into the queue and move on.
|
||||
// XPutBackEvent(display, &event);
|
||||
// XSync(display, False);
|
||||
// XFlush(display);
|
||||
}
|
||||
|
||||
void System::modify_position(SurfaceComponent &surface, const ModifyPositionRequest &request)
|
||||
{
|
||||
// surface.m_position = request.position;
|
||||
|
||||
// auto &[display, window, _] = surface.m_native_data;
|
||||
// const auto &[x, y] = request.position;
|
||||
//
|
||||
// // get baseline serial number for X requests generated from XResizeWindow
|
||||
// auto serial = NextRequest(display);
|
||||
// XMoveWindow(display, window, static_cast<int>(x), static_cast<int>(y));
|
||||
//
|
||||
// // flush output queue and wait for X server to processes the request
|
||||
// XSync(display, False);
|
||||
// constexpr auto lifespan = std::chrono::milliseconds { 10 };
|
||||
// auto timer = time::Timer {};
|
||||
// auto event = XEvent {};
|
||||
// while (!XCheckIfEvent(
|
||||
// display,
|
||||
// &event,
|
||||
// XEventTypeEquals<ConfigureNotify>,
|
||||
// reinterpret_cast<XPointer>(&window) // NOLINT
|
||||
// )
|
||||
// || event.xconfigure.serial < serial)
|
||||
// {
|
||||
// std::this_thread::sleep_for(std::chrono::microseconds { 100 });
|
||||
// if (timer.elapsed_time() > lifespan)
|
||||
// {
|
||||
// log::error("Timed out waiting for XMoveWindow's event");
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// // We don't need to update the component's state and handle the event in this funcion.
|
||||
// // Since handle_requests is called before handle_events.
|
||||
// // So we just put the event back into the queue and move on.
|
||||
// XPutBackEvent(display, &event);
|
||||
// XSync(display, False);
|
||||
// XFlush(display);
|
||||
}
|
||||
|
||||
void System::modify_visiblity(SurfaceComponent &surface, const ModifyVisibilityRequest &request)
|
||||
{
|
||||
// const auto &[display, window, _] = surface.get_native_data();
|
||||
// surface.m_visible = request.visible;
|
||||
|
||||
// if (request.visible)
|
||||
// {
|
||||
// XMapWindow(display, window);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// XUnmapWindow(display, window);
|
||||
// }
|
||||
}
|
||||
|
||||
void System::tick(app::TickInfo tick)
|
||||
{
|
||||
for (auto &[id, surface] : m_registry->view<SurfaceComponent>())
|
||||
{
|
||||
handle_requests(surface);
|
||||
handle_events(surface);
|
||||
}
|
||||
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
m_last_tick_result = app::TickResult {
|
||||
.info = tick,
|
||||
.duration = now - tick.start_time,
|
||||
.end_time = now,
|
||||
};
|
||||
}
|
||||
|
||||
void ensure_component_sanity(const SurfaceComponent &component)
|
||||
{
|
||||
auto [width, height] = component.get_resolution();
|
||||
|
||||
debug::ensure(width != 0u, "Received bad values for surface component: width({}) == 0", width);
|
||||
|
||||
debug::ensure(
|
||||
height != 0u,
|
||||
"Received bad values for surface component: height({}) == 0",
|
||||
height
|
||||
);
|
||||
|
||||
debug::ensure(
|
||||
width < SurfaceComponent::max_dimension,
|
||||
"Received bad values for surface component: width({}) > max_dimension({})",
|
||||
width,
|
||||
SurfaceComponent::max_dimension
|
||||
);
|
||||
|
||||
debug::ensure(
|
||||
height < SurfaceComponent::max_dimension,
|
||||
"Received bad values for surface component: height({}) > max_dimension({})",
|
||||
height,
|
||||
SurfaceComponent::max_dimension
|
||||
);
|
||||
|
||||
debug::ensure(
|
||||
component.get_title().size() < SurfaceComponent::max_title_length,
|
||||
"Received bad values for surface component: title.size({}) > max_title_length({})",
|
||||
component.get_title().size(),
|
||||
SurfaceComponent::max_title_length
|
||||
);
|
||||
}
|
||||
|
||||
auto CALLBACK native_window_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -> LRESULT
|
||||
{
|
||||
switch (uMsg)
|
||||
{
|
||||
case WM_DESTROY:
|
||||
{
|
||||
PostQuitMessage(0);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return DefWindowProcA(hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
|
||||
} // namespace lt::surface
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
export module surface.requests;
|
||||
|
||||
import preliminary;
|
||||
import math.vec2;
|
||||
import std;
|
||||
|
||||
export namespace lt::surface {
|
||||
|
||||
|
|
@ -12,12 +11,12 @@ struct ModifyTitleRequest
|
|||
|
||||
struct ModifyResolutionRequest
|
||||
{
|
||||
math::vec2_u32 resolution;
|
||||
math::uvec2 resolution;
|
||||
};
|
||||
|
||||
struct ModifyPositionRequest
|
||||
{
|
||||
math::vec2_i32 position;
|
||||
math::ivec2 position;
|
||||
};
|
||||
|
||||
struct ModifyVisibilityRequest
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
155
modules/surface/system.fuzz.cpp
Normal file
155
modules/surface/system.fuzz.cpp
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
#include <ecs/entity.hpp>
|
||||
#include <ecs/registry.hpp>
|
||||
#include <memory/reference.hpp>
|
||||
#include <surface/components.hpp>
|
||||
#include <surface/system.hpp>
|
||||
#include <test/fuzz.hpp>
|
||||
#include <test/test.hpp>
|
||||
|
||||
namespace lt::surface {
|
||||
|
||||
enum class FuzzAction : uint8_t
|
||||
{
|
||||
create_entity,
|
||||
|
||||
create_surface_component,
|
||||
|
||||
destroy_surface_component,
|
||||
|
||||
push_request,
|
||||
|
||||
push_event,
|
||||
|
||||
tick_system,
|
||||
|
||||
count,
|
||||
};
|
||||
|
||||
enum class EventType : uint8_t
|
||||
{
|
||||
Closed,
|
||||
Moved,
|
||||
Resized,
|
||||
LostFocus,
|
||||
GainFocus,
|
||||
};
|
||||
|
||||
void create_surface_component(test::FuzzDataProvider &provider, ecs::Registry ®istry)
|
||||
{
|
||||
const auto length = std::min(provider.consume<uint32_t>().value_or(16), 255u);
|
||||
const auto title = provider.consume_string(length).value_or("");
|
||||
|
||||
const auto resolution = math::uvec2 {
|
||||
provider.consume<uint32_t>().value_or(32u),
|
||||
provider.consume<uint32_t>().value_or(64u),
|
||||
};
|
||||
const auto visible = provider.consume<bool>().value_or(false);
|
||||
const auto vsync = provider.consume<bool>().value_or(false);
|
||||
|
||||
try
|
||||
{
|
||||
auto entity = registry.create_entity();
|
||||
registry.add<surface::SurfaceComponent>(
|
||||
entity,
|
||||
surface::SurfaceComponent::CreateInfo {
|
||||
.title = std::move(title),
|
||||
.resolution = resolution,
|
||||
.vsync = vsync,
|
||||
.visible = visible,
|
||||
}
|
||||
);
|
||||
}
|
||||
catch (const std::exception &exp)
|
||||
{
|
||||
std::ignore = exp;
|
||||
}
|
||||
}
|
||||
|
||||
void remove_surface_component(ecs::Registry ®istry)
|
||||
{
|
||||
const auto view = registry.view<SurfaceComponent>();
|
||||
|
||||
if (!view.is_empty())
|
||||
{
|
||||
registry.remove<SurfaceComponent>(view[0].first);
|
||||
}
|
||||
}
|
||||
|
||||
void push_request(ecs::Registry ®istry)
|
||||
{
|
||||
}
|
||||
|
||||
void push_event(ecs::Registry ®istry)
|
||||
{
|
||||
}
|
||||
|
||||
void check_invariants()
|
||||
{
|
||||
}
|
||||
|
||||
test::FuzzHarness harness = [](const uint8_t *data, size_t size) {
|
||||
auto provider = test::FuzzDataProvider { data, size };
|
||||
|
||||
auto registry = memory::create_ref<ecs::Registry>();
|
||||
auto system = surface::System { registry };
|
||||
|
||||
while (auto action = provider.consume<uint8_t>())
|
||||
{
|
||||
if (*action > std::to_underlying(FuzzAction::count))
|
||||
{
|
||||
*action = *action % std::to_underlying(FuzzAction::count);
|
||||
}
|
||||
|
||||
switch (static_cast<FuzzAction>(action.value()))
|
||||
{
|
||||
case FuzzAction::create_entity:
|
||||
{
|
||||
const auto length = std::min(provider.consume<uint32_t>().value_or(16), 255u);
|
||||
registry->create_entity();
|
||||
|
||||
break;
|
||||
}
|
||||
case FuzzAction::create_surface_component:
|
||||
{
|
||||
create_surface_component(provider, *registry);
|
||||
break;
|
||||
}
|
||||
case FuzzAction::destroy_surface_component:
|
||||
{
|
||||
remove_surface_component(*registry);
|
||||
break;
|
||||
}
|
||||
case FuzzAction::push_event:
|
||||
{
|
||||
auto view = registry->view<SurfaceComponent>();
|
||||
|
||||
if (!view.is_empty())
|
||||
{
|
||||
for (auto &[entity, component] : view)
|
||||
{
|
||||
provider.consume<uint8_t>().value_or(0);
|
||||
// @TODO(Light): push some event
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case FuzzAction::push_request:
|
||||
{
|
||||
break;
|
||||
}
|
||||
case FuzzAction::count:
|
||||
case FuzzAction::tick_system:
|
||||
{
|
||||
system.tick();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
check_invariants();
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
} // namespace lt::surface
|
||||
|
|
@ -1,16 +1,26 @@
|
|||
import test;
|
||||
import time;
|
||||
import test.test;
|
||||
import test.expects;
|
||||
import surface.system;
|
||||
import surface.events;
|
||||
import surface.requests;
|
||||
import ecs.registry;
|
||||
import memory.scope;
|
||||
import memory.reference;
|
||||
import logger;
|
||||
import math.vec2;
|
||||
import app.system;
|
||||
import std;
|
||||
|
||||
using ::lt::surface::SurfaceComponent;
|
||||
using ::lt::surface::System;
|
||||
using ::lt::test::Case;
|
||||
using ::lt::test::expect_eq;
|
||||
using ::lt::test::expect_ne;
|
||||
using ::lt::test::expect_not_nullptr;
|
||||
using ::lt::test::expect_throw;
|
||||
using ::lt::test::Suite;
|
||||
using ::std::ignore;
|
||||
using ::lt::test::operator""_suite;
|
||||
|
||||
[[nodiscard]] auto tick_info() -> lt::app::TickInfo
|
||||
{
|
||||
|
|
@ -60,7 +70,7 @@ public:
|
|||
{
|
||||
#ifdef LIGHT_PLATFORM_LINUX
|
||||
expect_not_nullptr(component->get_native_data().display);
|
||||
expect_not_nullptr(component->get_native_data().surface);
|
||||
expect_ne(component->get_native_data().window, 0);
|
||||
#endif
|
||||
|
||||
expect_eq(component->get_resolution().x, width);
|
||||
|
|
@ -76,25 +86,25 @@ private:
|
|||
System m_system { m_registry };
|
||||
};
|
||||
|
||||
|
||||
Suite raii = "raii"_suite = [] {
|
||||
Case { "happy paths" } = [] {
|
||||
Case { "happy path won't throw" } = [] {
|
||||
auto fixture = Fixture {};
|
||||
auto system = System { fixture.registry() };
|
||||
ignore = System { fixture.registry() };
|
||||
};
|
||||
|
||||
Case { "unhappy paths" } = [] {
|
||||
expect_throw([] { ignore = System { {} }; });
|
||||
};
|
||||
|
||||
Case { "many" } = [] {
|
||||
Case { "many won't freeze/throw" } = [] {
|
||||
auto fixture = Fixture {};
|
||||
for (auto idx : std::views::iota(0, 250))
|
||||
{
|
||||
ignore = idx;
|
||||
ignore = System { fixture.registry() };
|
||||
}
|
||||
};
|
||||
|
||||
Case { "unhappy path throws" } = [] {
|
||||
expect_throw([] { ignore = System { {} }; });
|
||||
};
|
||||
|
||||
Case { "post construct has correct state" } = [] {
|
||||
auto fixture = Fixture {};
|
||||
auto system = System { fixture.registry() };
|
||||
|
|
@ -133,7 +143,7 @@ Suite system_events = "system_events"_suite = [] {
|
|||
};
|
||||
|
||||
Suite registry_events = "registry_events"_suite = [] {
|
||||
Case { "on_construct initializes component" } = [] {
|
||||
Case { "on_construct<SurfaceComponent> initializes component" } = [] {
|
||||
auto fixture = Fixture {};
|
||||
|
||||
const auto &component = fixture.create_component();
|
||||
|
|
@ -141,7 +151,7 @@ Suite registry_events = "registry_events"_suite = [] {
|
|||
fixture.check_values(*component);
|
||||
};
|
||||
|
||||
Case { "unhappy on_construct throws" } = [] {
|
||||
Case { "unhappy on_construct<SurfaceComponent> throws" } = [] {
|
||||
auto fixture = Fixture {};
|
||||
auto system = System { fixture.registry() };
|
||||
|
||||
|
|
@ -168,7 +178,7 @@ Suite registry_events = "registry_events"_suite = [] {
|
|||
});
|
||||
};
|
||||
|
||||
Case { "unhappy on_construct removes component" } = [] {
|
||||
Case { "unhappy on_construct<SurfaceComponent> removes component" } = [] {
|
||||
auto fixture = Fixture {};
|
||||
auto system = System { fixture.registry() };
|
||||
|
||||
|
|
@ -176,7 +186,7 @@ Suite registry_events = "registry_events"_suite = [] {
|
|||
expect_eq(fixture.registry()->view<SurfaceComponent>().get_size(), 0);
|
||||
};
|
||||
|
||||
Case { "on_destroy cleans up component" } = [] {
|
||||
Case { "on_destrroy<SurfaceComponent> cleans up component" } = [] {
|
||||
auto fixture = Fixture {};
|
||||
auto system = lt::memory::create_scope<System>(fixture.registry());
|
||||
|
||||
|
|
@ -189,21 +199,23 @@ Suite registry_events = "registry_events"_suite = [] {
|
|||
};
|
||||
};
|
||||
|
||||
Suite tick = "ticking"_suite = [] {
|
||||
Case { "on empty registry won't throw" } = [] {
|
||||
Suite tick = "tick"_suite = [] {
|
||||
Case { "ticking on empty registry won't throw" } = [] {
|
||||
auto fixture = Fixture {};
|
||||
System { fixture.registry() }.tick(tick_info());
|
||||
};
|
||||
|
||||
Case { "on non-empty registry won't throw" } = [] {
|
||||
Case { "ticking on non-empty registry won't throw" } = [] {
|
||||
auto fixture = Fixture {};
|
||||
auto system = System { fixture.registry() };
|
||||
|
||||
fixture.create_component();
|
||||
system.tick(tick_info());
|
||||
};
|
||||
};
|
||||
|
||||
Case { "clears previous tick's events" } = [] {
|
||||
Suite tick_handles_events = "tick_handles_events"_suite = [] {
|
||||
Case { "ticking clears previous tick's events" } = [] {
|
||||
auto fixture = Fixture {};
|
||||
auto system = System { fixture.registry() };
|
||||
auto &surface = **fixture.create_component();
|
||||
|
|
@ -221,14 +233,17 @@ Suite tick = "ticking"_suite = [] {
|
|||
system.tick(tick_info());
|
||||
expect_eq(surface.peek_events().size(), 0);
|
||||
};
|
||||
};
|
||||
|
||||
Case { "clears requests" } = [] {
|
||||
Suite tick_handles_requests = "tick_handles_requests"_suite = [] {
|
||||
Case { "ticking clears requests" } = [] {
|
||||
auto fixture = Fixture {};
|
||||
auto system = System { fixture.registry() };
|
||||
auto &surface = **fixture.create_component();
|
||||
|
||||
constexpr auto position = lt::math::vec2_i32 { 50, 50 };
|
||||
constexpr auto resolution = lt::math::vec2_u32 { width, height };
|
||||
constexpr auto title = "ABC";
|
||||
constexpr auto position = lt::math::ivec2 { 50, 50 };
|
||||
constexpr auto resolution = lt::math::uvec2 { 50, 50 };
|
||||
|
||||
expect_eq(surface.peek_requests().size(), 0);
|
||||
|
||||
|
|
@ -255,5 +270,15 @@ Suite tick = "ticking"_suite = [] {
|
|||
expect_eq(surface.get_title(), title);
|
||||
expect_eq(surface.get_position(), position);
|
||||
expect_eq(surface.get_resolution(), resolution);
|
||||
|
||||
lt::log::debug("EVENT COUNT: {}", surface.peek_events().size());
|
||||
for (const auto &event : surface.peek_events())
|
||||
{
|
||||
const auto visitor = overloads {
|
||||
[&](auto event) { lt::log::debug("event: {}", event.to_string()); },
|
||||
};
|
||||
|
||||
std::visit(visitor, event);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
|||
1
modules/surface/wayland-protocols/.gitignore
vendored
1
modules/surface/wayland-protocols/.gitignore
vendored
|
|
@ -1 +0,0 @@
|
|||
*
|
||||
|
|
@ -1,7 +1,12 @@
|
|||
import test;
|
||||
import logger;
|
||||
import test.test;
|
||||
import test.registry;
|
||||
|
||||
void parse_option(std::string_view argument, lt::test::Registry::Options &options)
|
||||
import std;
|
||||
|
||||
using namespace ::lt::test;
|
||||
|
||||
void parse_option(std::string_view argument, Registry::Options &options)
|
||||
{
|
||||
constexpr auto case_str = std::string_view { "--case=" };
|
||||
constexpr auto suite_str = std::string_view { "--suite=" };
|
||||
|
|
@ -14,7 +19,7 @@ void parse_option(std::string_view argument, lt::test::Registry::Options &option
|
|||
|
||||
if (argument.starts_with("--mode=") && argument.substr(7ul) == "stats")
|
||||
{
|
||||
options.execution_policy = lt::test::Registry::ExecutionPolicy::stats;
|
||||
options.execution_policy = Registry::ExecutionPolicy::stats;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -46,13 +51,12 @@ void print_help()
|
|||
std::println("--help | -h --> ~You just used it! :D");
|
||||
}
|
||||
|
||||
auto main(i32 argc, char **argv) -> i32
|
||||
auto main(std::int32_t argc, char **argv) -> std::int32_t
|
||||
try
|
||||
{
|
||||
lt::log::set_min_severity(lt::log::Level::test);
|
||||
auto raw_arguments = std::span<char *>(argv, argc);
|
||||
|
||||
auto options = lt::test::Registry::Options {};
|
||||
auto options = Registry::Options {};
|
||||
for (auto idx = 0; auto &raw_argument : raw_arguments)
|
||||
{
|
||||
// First argument is the "cwd'
|
||||
|
|
@ -79,7 +83,7 @@ try
|
|||
}
|
||||
}
|
||||
|
||||
return static_cast<i32>(lt::test::Registry::run_all(options));
|
||||
return static_cast<std::int32_t>(Registry::run_all(options));
|
||||
}
|
||||
catch (const std::exception &exp)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
export module test.expects;
|
||||
|
||||
|
||||
import preliminary;
|
||||
import std;
|
||||
|
||||
namespace lt::test {
|
||||
|
||||
|
|
@ -24,14 +23,14 @@ export void expect_unreachable(
|
|||
{
|
||||
throw std::runtime_error {
|
||||
std::format(
|
||||
"unreachable reached: {}:{}",
|
||||
"Failed unreachable expectation:\n"
|
||||
"\tlocation: {}:{}",
|
||||
source_location.file_name(),
|
||||
source_location.line()
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
/** @todo(Light7734): Check exception type. */
|
||||
export constexpr void expect_throw(
|
||||
std::invocable auto invocable,
|
||||
std::source_location source_location = std::source_location::current()
|
||||
|
|
@ -41,13 +40,18 @@ export constexpr void expect_throw(
|
|||
{
|
||||
invocable();
|
||||
}
|
||||
catch (const std::exception &)
|
||||
catch (const std::exception &exp)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
throw std::runtime_error {
|
||||
std::format("did not throw: {}:{}", source_location.file_name(), source_location.line()),
|
||||
std::format(
|
||||
"Failed throwing expectation:\n"
|
||||
"\tlocation: {}:{}",
|
||||
source_location.file_name(),
|
||||
source_location.line()
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -63,7 +67,10 @@ export constexpr void expect_eq(
|
|||
{
|
||||
throw std::runtime_error {
|
||||
std::format(
|
||||
"expect_eq: {} == {} @ {}:{}",
|
||||
"Failed equality expectation:\n"
|
||||
"\tactual: {}\n"
|
||||
"\texpected: {}\n"
|
||||
"\tlocation: {}:{}",
|
||||
std::to_underlying<decltype(lhs)>(lhs),
|
||||
std::to_underlying<decltype(rhs)>(rhs),
|
||||
source_location.file_name(),
|
||||
|
|
@ -76,7 +83,10 @@ export constexpr void expect_eq(
|
|||
{
|
||||
throw std::runtime_error {
|
||||
std::format(
|
||||
"expect_eq: {} == {} @ {}:{}",
|
||||
"Failed equality expectation:\n"
|
||||
"\tactual: {}\n"
|
||||
"\texpected: {}\n"
|
||||
"\tlocation: {}:{}",
|
||||
lhs,
|
||||
rhs,
|
||||
source_location.file_name(),
|
||||
|
|
@ -96,47 +106,10 @@ export constexpr void expect_ne(
|
|||
{
|
||||
throw std::runtime_error {
|
||||
std::format(
|
||||
"expect_ne: {} != {} @ {}:{}",
|
||||
lhs,
|
||||
rhs,
|
||||
source_location.file_name(),
|
||||
source_location.line()
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export constexpr void expect_le(
|
||||
Testable auto lhs,
|
||||
Testable auto rhs,
|
||||
std::source_location source_location = std::source_location::current()
|
||||
)
|
||||
{
|
||||
if (lhs > rhs)
|
||||
{
|
||||
throw std::runtime_error {
|
||||
std::format(
|
||||
"expect_le: {} <= {} @ {}:{}",
|
||||
lhs,
|
||||
rhs,
|
||||
source_location.file_name(),
|
||||
source_location.line()
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export constexpr void expect_ge(
|
||||
Testable auto lhs,
|
||||
Testable auto rhs,
|
||||
std::source_location source_location = std::source_location::current()
|
||||
)
|
||||
{
|
||||
if (lhs < rhs)
|
||||
{
|
||||
throw std::runtime_error {
|
||||
std::format(
|
||||
"expect_ge: {} >= {} @ {}:{}",
|
||||
"Failed un-equality expectation:\n"
|
||||
"\tactual: {}\n"
|
||||
"\texpected: {}\n"
|
||||
"\tlocation: {}:{}",
|
||||
lhs,
|
||||
rhs,
|
||||
source_location.file_name(),
|
||||
|
|
@ -155,7 +128,10 @@ export constexpr void expect_true(
|
|||
{
|
||||
throw std::runtime_error {
|
||||
std::format(
|
||||
"expect_true: {} @ {}:{}",
|
||||
"Failed true expectation:\n"
|
||||
"\tactual: {}\n"
|
||||
"\texpected: true\n"
|
||||
"\tlocation: {}:{}",
|
||||
expression,
|
||||
source_location.file_name(),
|
||||
source_location.line()
|
||||
|
|
@ -173,7 +149,10 @@ export constexpr void expect_false(
|
|||
{
|
||||
throw std::runtime_error {
|
||||
std::format(
|
||||
"expect_false: {} @ {}:{}",
|
||||
"Failed false expectation:\n"
|
||||
"\tactual: {}\n"
|
||||
"\texpected: true\n"
|
||||
"\tlocation: {}:{}",
|
||||
expression,
|
||||
source_location.file_name(),
|
||||
source_location.line()
|
||||
|
|
@ -191,7 +170,34 @@ export constexpr void expect_not_nullptr(
|
|||
{
|
||||
throw std::runtime_error {
|
||||
std::format(
|
||||
"expect_not_nullptr: @ {}:{}",
|
||||
"Failed true expectation:\n"
|
||||
"\tactual: nullptr\n"
|
||||
"\texpected: not nullptr\n"
|
||||
"\tlocation: {}:{}",
|
||||
source_location.file_name(),
|
||||
source_location.line()
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export constexpr void expect_le(
|
||||
Testable auto lhs,
|
||||
Testable auto rhs,
|
||||
std::source_location source_location = std::source_location::current()
|
||||
)
|
||||
{
|
||||
if (lhs > rhs)
|
||||
{
|
||||
throw std::runtime_error {
|
||||
std::format(
|
||||
"Failed false expectation:\n"
|
||||
"\tactual: {}\n"
|
||||
"\texpected: >= {}\n"
|
||||
"\tlocation: {}:{}",
|
||||
lhs,
|
||||
rhs,
|
||||
source_location.file_name(),
|
||||
source_location.line()
|
||||
),
|
||||
|
|
|
|||
86
modules/test/fuzz.cppm
Normal file
86
modules/test/fuzz.cppm
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
|
||||
#include <cstring>
|
||||
#include <test/test.hpp>
|
||||
|
||||
namespace lt::test {
|
||||
|
||||
class FuzzDataProvider
|
||||
{
|
||||
public:
|
||||
FuzzDataProvider(const uint8_t *data, size_t size): m_data(data, size)
|
||||
{
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
requires(
|
||||
std::is_trivially_constructible_v<T> //
|
||||
&& std::is_trivially_copy_constructible_v<T> //
|
||||
&& std::is_trivially_copy_assignable_v<T>
|
||||
)
|
||||
|
||||
auto consume() -> std::optional<T>
|
||||
{
|
||||
if (m_data.size() < sizeof(T))
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
T value;
|
||||
std::memcpy(&value, m_data.data(), sizeof(T));
|
||||
|
||||
m_data = m_data.subspan(sizeof(T));
|
||||
return value;
|
||||
}
|
||||
|
||||
auto consume_string(size_t size) -> std::optional<std::string>
|
||||
{
|
||||
if (m_data.size() < size)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
auto value = std::string { (const char *)m_data.data(), size };
|
||||
m_data = m_data.subspan(size);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
auto consume_remaining_as_string() -> std::string
|
||||
{
|
||||
if (m_data.empty())
|
||||
{
|
||||
return std::string {};
|
||||
}
|
||||
|
||||
return { m_data.begin(), m_data.end() };
|
||||
};
|
||||
|
||||
private:
|
||||
std::span<const uint8_t> m_data;
|
||||
};
|
||||
|
||||
} // namespace lt::test
|
||||
|
||||
namespace lt::test {
|
||||
|
||||
auto process_fuzz_input(const uint8_t *data, size_t size) -> int32_t
|
||||
try
|
||||
{
|
||||
return details::Registry::process_fuzz_input(data, size);
|
||||
}
|
||||
catch (const std::exception &exp)
|
||||
{
|
||||
std::println("Fuzz input resulted in uncaught exception:");
|
||||
std::println("\twhat: {}", exp.what());
|
||||
std::println("\tinput size: {}", size);
|
||||
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
}; // namespace lt::test
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
|
||||
{
|
||||
return lt::test::process_fuzz_input(data, size);
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
export module test;
|
||||
|
||||
export import preliminary;
|
||||
export import test.test;
|
||||
export import test.expects;
|
||||
export import test.expects;
|
||||
export import logger;
|
||||
|
||||
export using ::lt::test::Suite;
|
||||
export using ::lt::test::Case;
|
||||
|
||||
export using ::lt::test::expect_eq;
|
||||
export using ::lt::test::expect_ne;
|
||||
export using ::lt::test::expect_le;
|
||||
|
||||
export using ::lt::test::expect_true;
|
||||
export using ::lt::test::expect_false;
|
||||
|
||||
export using ::lt::test::expect_throw;
|
||||
export using ::lt::test::expect_not_nullptr;
|
||||
export using ::lt::test::expect_unreachable;
|
||||
|
||||
export using ::lt::test::operator""_suite;
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
export module test.registry;
|
||||
|
||||
import logger;
|
||||
import preliminary;
|
||||
import std;
|
||||
import test.expects;
|
||||
|
||||
///////////////////////////////////////
|
||||
|
|
@ -12,7 +11,7 @@ namespace lt::test {
|
|||
export class Registry
|
||||
{
|
||||
public:
|
||||
enum class ExecutionPolicy : u8
|
||||
enum class ExecutionPolicy : std::uint8_t
|
||||
{
|
||||
normal,
|
||||
stats,
|
||||
|
|
@ -29,7 +28,7 @@ public:
|
|||
std::string case_regex;
|
||||
};
|
||||
|
||||
using FuzzFunction = i32 (*)(const u8 *, size_t);
|
||||
using FuzzFunction = std::int32_t (*)(const std::uint8_t *, std::size_t);
|
||||
|
||||
using SuiteFunction = void (*)();
|
||||
|
||||
|
|
@ -37,9 +36,9 @@ public:
|
|||
|
||||
static void register_fuzz_harness(FuzzFunction suite);
|
||||
|
||||
static auto run_all(Options options) -> i32;
|
||||
static auto run_all(Options options) -> std::int32_t;
|
||||
|
||||
static auto process_fuzz_input(const u8 *data, size_t size) -> i32;
|
||||
static auto process_fuzz_input(const std::uint8_t *data, std::size_t size) -> std::int32_t;
|
||||
|
||||
static void set_last_suite_name(const char *name);
|
||||
|
||||
|
|
@ -74,7 +73,7 @@ private:
|
|||
|
||||
[[nodiscard]] static auto instance() -> Registry &;
|
||||
|
||||
auto run_all_impl() -> i32;
|
||||
auto run_all_impl() -> std::int32_t;
|
||||
|
||||
void print_options();
|
||||
|
||||
|
|
@ -84,25 +83,25 @@ private:
|
|||
|
||||
FuzzFunction m_fuzz_harness {};
|
||||
|
||||
i32 m_total_case_count {};
|
||||
std::int32_t m_total_case_count {};
|
||||
|
||||
i32 m_passed_case_count {};
|
||||
std::int32_t m_passed_case_count {};
|
||||
|
||||
i32 m_failed_case_count {};
|
||||
std::int32_t m_failed_case_count {};
|
||||
|
||||
i32 m_matched_case_count {};
|
||||
std::int32_t m_matched_case_count {};
|
||||
|
||||
i32 m_skipped_case_count {};
|
||||
std::int32_t m_skipped_case_count {};
|
||||
|
||||
i32 m_total_suite_count {};
|
||||
std::int32_t m_total_suite_count {};
|
||||
|
||||
i32 m_passed_suite_count {};
|
||||
std::int32_t m_passed_suite_count {};
|
||||
|
||||
i32 m_failed_suite_count {};
|
||||
std::int32_t m_failed_suite_count {};
|
||||
|
||||
i32 m_matched_suite_count {};
|
||||
std::int32_t m_matched_suite_count {};
|
||||
|
||||
i32 m_skipped_suite_count {};
|
||||
std::int32_t m_skipped_suite_count {};
|
||||
|
||||
std::regex m_case_regex;
|
||||
};
|
||||
|
|
@ -132,13 +131,14 @@ namespace lt::test {
|
|||
instance().m_fuzz_harness = suite;
|
||||
}
|
||||
|
||||
/* static */ auto Registry::run_all(Options options) -> i32
|
||||
/* static */ auto Registry::run_all(Options options) -> std::int32_t
|
||||
{
|
||||
instance().m_options = std::move(options);
|
||||
return instance().run_all_impl();
|
||||
}
|
||||
|
||||
/* static */ auto Registry::process_fuzz_input(const u8 *data, size_t size) -> i32
|
||||
/* static */ auto Registry::process_fuzz_input(const std::uint8_t *data, std::size_t size)
|
||||
-> std::int32_t
|
||||
{
|
||||
if (!instance().m_fuzz_harness)
|
||||
{
|
||||
|
|
@ -205,22 +205,22 @@ namespace lt::test {
|
|||
++instance().m_failed_case_count;
|
||||
}
|
||||
|
||||
/* static */ [[nodiscard]] auto Registry::should_return_on_failure() -> bool
|
||||
[[nodiscard]] /* static */ auto Registry::should_return_on_failure() -> bool
|
||||
{
|
||||
return instance().m_options.stop_on_fail;
|
||||
}
|
||||
|
||||
/* static */ [[nodiscard]] auto Registry::get_options() -> const Options &
|
||||
[[nodiscard]] /* static */ auto Registry::get_options() -> const Options &
|
||||
{
|
||||
return instance().m_options;
|
||||
}
|
||||
|
||||
/* static */ [[nodiscard]] auto Registry::get_case_regex() -> const std::regex &
|
||||
[[nodiscard]] /* static */ auto Registry::get_case_regex() -> const std::regex &
|
||||
{
|
||||
return instance().m_case_regex;
|
||||
}
|
||||
|
||||
auto Registry::run_all_impl() -> i32
|
||||
auto Registry::run_all_impl() -> std::int32_t
|
||||
{
|
||||
print_options();
|
||||
m_case_regex = std::regex(m_options.case_regex);
|
||||
|
|
@ -232,30 +232,6 @@ auto Registry::run_all_impl() -> i32
|
|||
{
|
||||
if (std::regex_search(name, regex))
|
||||
{
|
||||
auto padding_left = std::string {};
|
||||
padding_left.resize((79 - std::strlen(name)) / 2u - 1u);
|
||||
for (auto &ch : padding_left)
|
||||
{
|
||||
ch = '-';
|
||||
}
|
||||
|
||||
auto padding_right = std::string {};
|
||||
padding_right.resize((79 - std::strlen(name)) / 2u);
|
||||
if (std::strlen(name) % 2 == 0)
|
||||
{
|
||||
padding_right.resize(padding_right.size() + 1);
|
||||
}
|
||||
for (auto &ch : padding_right)
|
||||
{
|
||||
ch = '-';
|
||||
}
|
||||
|
||||
log::test(
|
||||
"\033[1;33m*{}{}{}-*\033[0m",
|
||||
std::string { padding_left },
|
||||
std::string_view { name },
|
||||
std::string { padding_right }
|
||||
);
|
||||
suite();
|
||||
increment_matched_suite_count();
|
||||
}
|
||||
|
|
@ -270,12 +246,12 @@ auto Registry::run_all_impl() -> i32
|
|||
{
|
||||
if (m_options.stop_on_fail)
|
||||
{
|
||||
log::info("Quitting due to options.stop_on_fail == true");
|
||||
std::println("Quitting due to options.stop_on_fail == true");
|
||||
break;
|
||||
}
|
||||
|
||||
log::test("Uncaught exception when running suite:");
|
||||
log::test("\twhat: {}", exp.what());
|
||||
std::println("Uncaught exception when running suite:");
|
||||
std::println("\twhat: {}", exp.what());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -284,32 +260,32 @@ auto Registry::run_all_impl() -> i32
|
|||
{
|
||||
case ExecutionPolicy::normal:
|
||||
{
|
||||
// log::test("[-------STATS------]");
|
||||
//
|
||||
// log::test("suites:");
|
||||
// log::test("\ttotal: {}", (i32)m_total_suite_count);
|
||||
// log::test("\tpassed: {}", (i32)m_passed_suite_count);
|
||||
// log::test("\tfailed: {}", (i32)m_failed_suite_count);
|
||||
// log::test("\tmatched: {}", (i32)m_matched_suite_count);
|
||||
// log::test("\tskipped: {}", (i32)m_skipped_suite_count);
|
||||
//
|
||||
// log::test("tests:");
|
||||
// log::test("\ttotal: {}", (i32)m_total_case_count);
|
||||
// log::test("\tpassed: {}", (i32)m_passed_case_count);
|
||||
// log::test("\tfailed: {}", (i32)m_failed_case_count);
|
||||
// log::test("\tmatched: {}", (i32)m_matched_case_count);
|
||||
// log::test("\tskipped: {}", (i32)m_skipped_case_count);
|
||||
std::println("[-------STATS------]");
|
||||
|
||||
// log::test("________________________________________________________________");
|
||||
std::println("suites:");
|
||||
std::println("\ttotal: {}", m_total_suite_count);
|
||||
std::println("\tpassed: {}", m_passed_suite_count);
|
||||
std::println("\tfailed: {}", m_failed_suite_count);
|
||||
std::println("\tmatched: {}", m_matched_suite_count);
|
||||
std::println("\tskipped: {}", m_skipped_suite_count);
|
||||
|
||||
std::println("tests:");
|
||||
std::println("\ttotal: {}", m_total_case_count);
|
||||
std::println("\tpassed: {}", m_passed_case_count);
|
||||
std::println("\tfailed: {}", m_failed_case_count);
|
||||
std::println("\tmatched: {}", m_matched_case_count);
|
||||
std::println("\tskipped: {}", m_skipped_case_count);
|
||||
|
||||
std::println("________________________________________________________________");
|
||||
|
||||
return m_failed_case_count;
|
||||
}
|
||||
case ExecutionPolicy::stats:
|
||||
{
|
||||
log::test("[-------STATS------]");
|
||||
log::test("Total suite count: {}", (i32)m_total_suite_count);
|
||||
log::test("Total test count: {}", (i32)m_total_case_count);
|
||||
log::test("________________________________________________________________");
|
||||
std::println("[-------STATS------]");
|
||||
std::println("Total suite count: {}", m_total_suite_count);
|
||||
std::println("Total test count: {}", m_total_case_count);
|
||||
std::println("________________________________________________________________");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -320,12 +296,12 @@ auto Registry::run_all_impl() -> i32
|
|||
|
||||
void Registry::print_options()
|
||||
{
|
||||
// log::info("stop-on-failure: {}", static_cast<bool>(m_options.stop_on_fail));
|
||||
std::println("stop-on-failure: {}", m_options.stop_on_fail);
|
||||
}
|
||||
|
||||
Registry::Registry()
|
||||
{
|
||||
// log::info("________________________________________________________________");
|
||||
std::println("________________________________________________________________");
|
||||
}
|
||||
|
||||
[[nodiscard]] /* static */ auto Registry::instance() -> Registry &
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
export module test.test;
|
||||
|
||||
import std;
|
||||
import test.expects;
|
||||
import test.registry;
|
||||
import preliminary;
|
||||
import logger;
|
||||
|
||||
|
||||
///////////////////////////////////////
|
||||
// ----------* INTERFACE *--------- //
|
||||
|
|
@ -14,7 +12,7 @@ namespace lt::test {
|
|||
class TestCase
|
||||
{
|
||||
public:
|
||||
TestCase(std::string name);
|
||||
TestCase(std::string_view name);
|
||||
|
||||
// NOLINTNEXTLINE(misc-unconventional-assign-operator)
|
||||
auto operator=(std::invocable auto test) const -> void;
|
||||
|
|
@ -23,7 +21,7 @@ private:
|
|||
void run_normal(std::invocable auto test) const;
|
||||
|
||||
private:
|
||||
std::string m_name;
|
||||
std::string_view m_name;
|
||||
};
|
||||
|
||||
struct TestSuite
|
||||
|
|
@ -40,7 +38,7 @@ struct TestFuzzHarness
|
|||
export using Case = const TestCase;
|
||||
export using Suite = const TestSuite;
|
||||
export using FuzzHarness = const TestFuzzHarness;
|
||||
export auto operator""_suite(const char *name, size_t size) -> TestSuite;
|
||||
export auto operator""_suite(const char *name, std::size_t size) -> TestSuite;
|
||||
|
||||
///////////////////////////////////////
|
||||
// * IMPLEMENTATION -- TEMPLATES * //
|
||||
|
|
@ -70,26 +68,16 @@ void TestCase::run_normal(std::invocable auto test) const
|
|||
}
|
||||
Registry::increment_matched_case_count();
|
||||
|
||||
auto padding = std::string {};
|
||||
padding.resize(79 - m_name.size());
|
||||
for (auto &ch : padding)
|
||||
{
|
||||
ch = ' ';
|
||||
}
|
||||
|
||||
std::println("[Running-----------] --> ");
|
||||
std::println("{}", m_name);
|
||||
try
|
||||
{
|
||||
test();
|
||||
}
|
||||
catch (const std::exception &exp)
|
||||
{
|
||||
log::test(
|
||||
"\033[1;31m{}{} | {}\033[0m",
|
||||
std::string_view { m_name },
|
||||
std::string { padding },
|
||||
std::string { exp.what() }
|
||||
);
|
||||
|
||||
std::println("{}", exp.what());
|
||||
std::println("[-----------FAIL !!]");
|
||||
Registry::increment_failed_case_count();
|
||||
|
||||
if (Registry::should_return_on_failure())
|
||||
|
|
@ -101,9 +89,7 @@ void TestCase::run_normal(std::invocable auto test) const
|
|||
}
|
||||
|
||||
Registry::increment_passed_case_count();
|
||||
|
||||
|
||||
log::test("{}{} | \033[1;32mpass\033[0m", std::string_view { m_name }, std::string { padding });
|
||||
std::println("[--------SUCCESS :D]");
|
||||
}
|
||||
|
||||
TestSuite::TestSuite(auto body)
|
||||
|
|
@ -120,11 +106,8 @@ constexpr TestFuzzHarness::TestFuzzHarness(auto body)
|
|||
#endif
|
||||
};
|
||||
|
||||
auto operator""_suite(const char *name, size_t size) -> TestSuite
|
||||
auto operator""_suite(const char *name, std::size_t size) -> TestSuite
|
||||
{
|
||||
// TODO(Light): do we need the size parameter?
|
||||
ignore = size;
|
||||
|
||||
Registry::set_last_suite_name(name);
|
||||
return {};
|
||||
}
|
||||
|
|
@ -137,13 +120,8 @@ auto operator""_suite(const char *name, size_t size) -> TestSuite
|
|||
module :private;
|
||||
namespace lt::test {
|
||||
|
||||
TestCase::TestCase(std::string name): m_name(name)
|
||||
TestCase::TestCase(std::string_view name): m_name(name)
|
||||
{
|
||||
if (m_name.size() > 79u)
|
||||
{
|
||||
m_name.resize(79u - 3);
|
||||
m_name.append("...");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lt::test
|
||||
|
|
|
|||
|
|
@ -1,14 +1,22 @@
|
|||
import test;
|
||||
import test.test;
|
||||
import test.expects;
|
||||
|
||||
import std;
|
||||
|
||||
using lt::test::Case;
|
||||
using lt::test::Suite;
|
||||
using lt::test::operator""_suite;
|
||||
|
||||
Suite expects = "expects"_suite = []() {
|
||||
// should be truncated...
|
||||
Case { "berryyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy "
|
||||
"long name" }
|
||||
= [] {
|
||||
};
|
||||
using lt::test::expect_unreachable;
|
||||
using lt::test::expect_true;
|
||||
using lt::test::expect_false;
|
||||
using lt::test::expect_eq;
|
||||
using lt::test::expect_ne;
|
||||
using lt::test::expect_le;
|
||||
using lt::test::expect_throw;
|
||||
|
||||
Case { "this emptiness machine" } = [] {
|
||||
expect_le(9, 6);
|
||||
Case { "" } = [] {
|
||||
};
|
||||
|
||||
Case { "expect_unreachable" } = [] {
|
||||
|
|
@ -16,7 +24,7 @@ Suite expects = "expects"_suite = []() {
|
|||
|
||||
// clang-format off
|
||||
try { expect_unreachable(); }
|
||||
catch (const std::exception &) { unhappy = true; }
|
||||
catch (const std::exception &exp) { unhappy = true; }
|
||||
// clang-format on
|
||||
|
||||
if (!unhappy)
|
||||
|
|
@ -40,16 +48,16 @@ Suite expects = "expects"_suite = []() {
|
|||
|
||||
// clang-format off
|
||||
try { expect_true(where_oongaboonga_ptr); }
|
||||
catch (const std::exception&) { ++unhappy_counter; }
|
||||
catch (const std::exception& exp) { ++unhappy_counter; }
|
||||
|
||||
try { expect_true(!true); }
|
||||
catch (const std::exception&) { ++unhappy_counter; }
|
||||
catch (const std::exception& exp) { ++unhappy_counter; }
|
||||
|
||||
try { expect_true(false); }
|
||||
catch (const std::exception&) { ++unhappy_counter; }
|
||||
catch (const std::exception& exp) { ++unhappy_counter; }
|
||||
|
||||
try { expect_true(0); } // NOLINT
|
||||
catch (const std::exception&) { ++unhappy_counter; }
|
||||
catch (const std::exception& exp) { ++unhappy_counter; }
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
|
|
@ -58,31 +66,27 @@ Suite expects = "expects"_suite = []() {
|
|||
|
||||
expect_false(oongaboonga_is_slacking);
|
||||
expect_false(false);
|
||||
expect_false(0); // NOLINT
|
||||
};
|
||||
|
||||
Case { "expect_false - unhappy" } = [] {
|
||||
auto *oonga_oonga_can_rest_now = (u32 *)nullptr;
|
||||
auto oongaboonga = int {};
|
||||
auto *oonga_oonga_can_rest_now = (int *)nullptr;
|
||||
auto unhappy_counter = 0u;
|
||||
oonga_oonga_can_rest_now = &unhappy_counter;
|
||||
|
||||
// clang-format off
|
||||
try { expect_false(oonga_oonga_can_rest_now); }
|
||||
catch (const std::exception&) { ++unhappy_counter; }
|
||||
catch (const std::exception& exp) { ++unhappy_counter; }
|
||||
|
||||
try { expect_false(true); }
|
||||
catch (const std::exception&) { ++unhappy_counter; }
|
||||
catch (const std::exception& exp) { ++unhappy_counter; }
|
||||
|
||||
try { expect_false(!false); }
|
||||
catch (const std::exception&) { ++unhappy_counter; }
|
||||
catch (const std::exception& exp) { ++unhappy_counter; }
|
||||
|
||||
try { expect_false(!!1); }
|
||||
catch (const std::exception&) { ++unhappy_counter; }
|
||||
try { expect_false(1); } // NOLINT
|
||||
catch (const std::exception& exp) { ++unhappy_counter; }
|
||||
// clang-format on
|
||||
|
||||
if (unhappy_counter != 4)
|
||||
{
|
||||
throw std::runtime_error { "expect_false - unhappy" };
|
||||
}
|
||||
};
|
||||
|
||||
Case { "expect_true - unhappy" } = [] {
|
||||
|
|
@ -91,29 +95,23 @@ Suite expects = "expects"_suite = []() {
|
|||
|
||||
// clang-format off
|
||||
try { expect_true(where_oongaboonga_ptr); }
|
||||
catch (const std::exception&) { ++unhappy_counter; }
|
||||
catch (const std::exception& exp) { ++unhappy_counter; }
|
||||
|
||||
try { expect_true(!true); }
|
||||
catch (const std::exception&) { ++unhappy_counter; }
|
||||
catch (const std::exception& exp) { ++unhappy_counter; }
|
||||
|
||||
try { expect_true(false); }
|
||||
catch (const std::exception&) { ++unhappy_counter; }
|
||||
catch (const std::exception& exp) { ++unhappy_counter; }
|
||||
|
||||
try { expect_true(!!0); }
|
||||
catch (const std::exception&) { ++unhappy_counter; }
|
||||
try { expect_true(0); } // NOLINT
|
||||
catch (const std::exception& exp) { ++unhappy_counter; }
|
||||
// clang-format on
|
||||
|
||||
if (unhappy_counter != 4)
|
||||
{
|
||||
throw std::runtime_error { "expect_true - unhappy" };
|
||||
}
|
||||
};
|
||||
|
||||
Case { "expect_eq - happy" } = [] {
|
||||
expect_eq(5, 5);
|
||||
expect_eq(20.0, 20.0);
|
||||
expect_eq(true, true);
|
||||
expect_eq(false, false);
|
||||
expect_eq(true, 1);
|
||||
};
|
||||
|
||||
Case { "expect_eq - unhappy" } = [] {
|
||||
|
|
@ -121,7 +119,7 @@ Suite expects = "expects"_suite = []() {
|
|||
|
||||
// clang-format off
|
||||
try { expect_eq(true, false); }
|
||||
catch (const std::exception &) { unhappy = true; }
|
||||
catch (const std::exception &exp) { unhappy = true; }
|
||||
// clang-format on
|
||||
|
||||
if (!unhappy)
|
||||
|
|
@ -133,8 +131,7 @@ Suite expects = "expects"_suite = []() {
|
|||
Case { "expect_ne - happy " } = [] {
|
||||
expect_ne(5, 5.0000001);
|
||||
expect_ne(20.0, 69.0);
|
||||
expect_ne(true, false);
|
||||
expect_ne(false, true);
|
||||
expect_ne(true, 0);
|
||||
};
|
||||
|
||||
Case { "expect_ne - unhappy" } = [] {
|
||||
|
|
@ -142,19 +139,16 @@ Suite expects = "expects"_suite = []() {
|
|||
|
||||
// clang-format off
|
||||
try { expect_ne(5, 5); }
|
||||
catch (const std::exception &) { ++unhappy_counter; }
|
||||
catch (const std::exception &exp) { ++unhappy_counter; }
|
||||
|
||||
try { expect_ne(20.0, 20.0); }
|
||||
catch (const std::exception &) { ++unhappy_counter; }
|
||||
catch (const std::exception &exp) { ++unhappy_counter; }
|
||||
|
||||
try { expect_ne(true, true); }
|
||||
catch (const std::exception &) { ++unhappy_counter; }
|
||||
|
||||
try { expect_ne(false, false); }
|
||||
catch (const std::exception &) { ++unhappy_counter; }
|
||||
try { expect_ne(true, 1); }
|
||||
catch (const std::exception &exp) { ++unhappy_counter; }
|
||||
// clang-format on
|
||||
|
||||
if (unhappy_counter != 4)
|
||||
if (unhappy_counter != 3)
|
||||
{
|
||||
throw std::runtime_error { "expect_ne unhappy" };
|
||||
}
|
||||
|
|
@ -169,7 +163,7 @@ Suite expects = "expects"_suite = []() {
|
|||
|
||||
// clang-format off
|
||||
try { expect_throw([] {}); }
|
||||
catch (const std::exception &) { unhappy = true; }
|
||||
catch (const std::exception &exp) { unhappy = true; }
|
||||
// clang-format on
|
||||
|
||||
if (!unhappy)
|
||||
|
|
@ -181,7 +175,7 @@ Suite expects = "expects"_suite = []() {
|
|||
Case { "expect_le - happy" } = [] {
|
||||
expect_le(69, 420);
|
||||
expect_le(19.694206942069420, 20.0);
|
||||
expect_le(false, !!1);
|
||||
expect_le(false, 1);
|
||||
};
|
||||
|
||||
Case { "expect_le - unhappy" } = [] {
|
||||
|
|
@ -189,16 +183,16 @@ Suite expects = "expects"_suite = []() {
|
|||
|
||||
// clang-format off
|
||||
try { expect_le(20020619 + 23, 20020619 ); }
|
||||
catch (const std::exception &) { ++unhappy_counter; }
|
||||
catch (const std::exception &exp) { ++unhappy_counter; }
|
||||
|
||||
try { expect_le(420, 69); }
|
||||
catch (const std::exception &) { ++unhappy_counter; }
|
||||
catch (const std::exception &exp) { ++unhappy_counter; }
|
||||
|
||||
try { expect_le(20.0, 19.694206942069420); }
|
||||
catch (const std::exception &) { ++unhappy_counter; }
|
||||
catch (const std::exception &exp) { ++unhappy_counter; }
|
||||
|
||||
try { expect_le(true, false); }
|
||||
catch (const std::exception &) { ++unhappy_counter; }
|
||||
try { expect_le(1, false); }
|
||||
catch (const std::exception &exp) { ++unhappy_counter; }
|
||||
// clang-format on
|
||||
|
||||
if (unhappy_counter != 4)
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
export module time;
|
||||
import std;
|
||||
|
||||
import preliminary;
|
||||
|
||||
export namespace lt::time {
|
||||
namespace lt::time {
|
||||
|
||||
/** Simple timer class to keep track of the elapsed time. */
|
||||
class Timer
|
||||
export class Timer
|
||||
{
|
||||
public:
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
using Duration = std::chrono::duration<f64>;
|
||||
using Duration = std::chrono::duration<double>;
|
||||
|
||||
using Timepoint = std::chrono::time_point<std::chrono::steady_clock>;
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue