diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..82258098 --- /dev/null +++ b/.clang-format @@ -0,0 +1,209 @@ +TabWidth: 4 +IndentWidth: 4 +ColumnLimit: 80 + +UseTab: AlignWithSpaces + +# Pointer and reference alignment style. Possible values: Left, Right, Middle. +PointerAlignment: Right + +# AccessModifierOffset (int) +# The extra indent or outdent of access modifiers, e.g. public:. +AccessModifierOffset: 0 + +AlignArrayOfStructures: Right +AlignAfterOpenBracket: true +AlignConsecutiveAssignments: Consecutive +AlignConsecutiveBitFields: Consecutive +AlignConsecutiveDeclarations: None +AlignConsecutiveMacros: Consecutive +AlignConsecutiveShortCaseStatements: + Enabled: true + AcrossEmptyLines: true + AcrossComments: true + AlignCaseColons: false + + +AlignEscapedNewlines: LeftWithLastLine + +AlignOperands: Align + +AlignTrailingComments: + Kind: Always + AlignPPAndNotPP: true + +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowBreakBeforeNoexceptSpecifier: Always + +AllowShortBlocksOnASingleLine: Always +AllowShortCaseExpressionOnASingleLine: true +AllowShortCaseLabelsOnASingleLine: true +AllowShortCompoundRequirementOnASingleLine: true +AllowShortEnumsOnASingleLine: false + +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: WithoutElse +AllowShortLambdasOnASingleLine: Inline +AllowShortLoopsOnASingleLine: true +AllowShortNamespacesOnASingleLine: false + +AlwaysBreakBeforeMultilineStrings: false + +BitFieldColonSpacing: Both + +# TODO Highly Opinionated will need to deliberate on this. I prefer K&R Style but some prefer Allman +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterStruct: false + AfterExternBlock: false + BeforeCatch: true # catch on separate line, not like we'll use catch + BeforeElse: true + BeforeLambdaBody: false + BeforeWhile: true + IndentBraces: false + +BreakAdjacentStringLiterals: false +BreakAfterAttributes: Leave + +BreakAfterOpenBracketBracedList: false +BreakAfterOpenBracketFunction: false +BreakAfterOpenBracketIf: false +BreakAfterOpenBracketLoop: false +BreakAfterOpenBracketSwitch: false +BreakAfterReturnType: Automatic + +BreakBeforeBinaryOperators: None +BreakBeforeCloseBracketBracedList: true +BreakBeforeCloseBracketFunction: true +BreakBeforeCloseBracketIf: true +BreakBeforeCloseBracketSwitch: true + +BreakBeforeConceptDeclarations: Allowed +BreakBeforeInlineASMColon: OnlyMultiline +BreakBeforeTemplateCloser: true +BreakBeforeTernaryOperators: true + +BreakConstructorInitializers: AfterColon +BreakFunctionDefinitionParameters: false + +BreakInheritanceList: AfterColon + +BreakStringLiterals: false + +BreakTemplateDeclarations: Leave + +CompactNamespaces: false + +Cpp11BracedListStyle: FunctionCall + +EmptyLineAfterAccessModifier: Leave +EmptyLineBeforeAccessModifier: LogicalBlock + +EnumTrailingComma: Leave + +FixNamespaceComments: true + +IndentAccessModifiers: false + +IndentCaseBlocks: false + +IndentExportBlock: false +IndentExternBlock: false +IndentGotoLabels: true + +IndentPPDirectives: BeforeHash +IndentRequiresClause: false +IndentWrappedFunctionNames: false + +# I personally don't like unbraced things but this should be heavily discussed +# For example it's a Pain in the ass to go back and add braces when someone +# didn't include them after you need to extend it +InsertBraces: true + +InsertNewlineAtEOF: true + +# This one will be controversial... +IntegerLiteralSeparator: + Binary: 4 + Decimal: 3 + Hex: 4 + BinaryMinDigitsInsert: 8 + DecimalMinDigitsInsert: 6 + HexMinDigitsInsert: 8 + +KeepEmptyLines: + AtEndOfFile: false + AtStartOfBlock: false + AtStartOfFile: false + +KeepFormFeed: false + +# No Microslop BS \r\n +LineEnding: LF + +NamespaceIndentation: None + +NumericLiteralCase: + ExponentLetter: Leave + HexDigit: Lower + Prefix: Lower + Suffix: Upper + +# TODO Use after upgrading to Clang 23 +# PackParameters: +# BinPack: OnePerLine + +BinPackParameters: OnePerLine + +QualifierAlignment: Right + +ReflowComments: Never + +RemoveBracesLLVM: false +RemoveEmptyLinesInUnwrappedLines: false +RemoveParentheses: Leave + +RemoveSemicolon: false + +RequiresExpressionIndentation: OuterScope + +SeparateDefinitionBlocks: Leave + +SkipMacroDefinitionBody: true + +SortIncludes: + Enabled: false + +SortUsingDeclarations: Never + +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterOperatorKeyword: false +SpaceAfterTemplateKeyword: false +SpaceAroundPointerQualifiers: Default + +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements + +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBraces: Always + +SpacesInAngles: Leave + +SpacesInContainerLiterals: false +SpacesInParens: Never +SpacesInSquareBrackets: false + + diff --git a/.clang-format-ignore b/.clang-format-ignore new file mode 100644 index 00000000..585844be --- /dev/null +++ b/.clang-format-ignore @@ -0,0 +1,2 @@ +build/* +engine/native/thirdparty/* diff --git a/engine/native/core/definitions/definitions.cppm b/engine/native/core/definitions/definitions.cppm index 822c9985..fbf06f84 100644 --- a/engine/native/core/definitions/definitions.cppm +++ b/engine/native/core/definitions/definitions.cppm @@ -6,24 +6,22 @@ export module core.defs; export import core.version; export import core.stdtypes; -static_assert(__cplusplus >= 202207L, "Minimum of C++23 required. Consider upgrading your compiler."); +static_assert(__cplusplus >= 202'207L, + "Minimum of C++23 required. Consider upgrading your compiler."); export namespace draco { - template - concept arithmetic = std::is_arithmetic_v; +template concept arithmetic = std::is_arithmetic_v; - template - concept trivial = std::is_trivial_v; +template concept trivial = std::is_trivial_v; - // Whether the default value of a type is just all-0 bytes. - // This can most commonly be exploited by using memset for these types instead of loop-construct. - // Must be explicitly specialized to mark a type as such. - template - struct is_zero_constructible : std::false_type {}; +// Whether the default value of a type is just all-0 bytes. +// This can most commonly be exploited by using memset for these types instead of loop-construct. +// Must be explicitly specialized to mark a type as such. +template +struct is_zero_constructible : std::false_type { }; - template - constexpr bool is_zero_constructible_v = is_zero_constructible::value; +template +constexpr bool is_zero_constructible_v = is_zero_constructible::value; - template - concept zero_constructible = is_zero_constructible_v; -} +template concept zero_constructible = is_zero_constructible_v; +} // namespace draco diff --git a/engine/native/core/definitions/stdtypes.cppm b/engine/native/core/definitions/stdtypes.cppm index fb1284b7..09ac04d2 100644 --- a/engine/native/core/definitions/stdtypes.cppm +++ b/engine/native/core/definitions/stdtypes.cppm @@ -6,16 +6,16 @@ module; export module core.stdtypes; export namespace draco { -using i8 = int8_t; +using i8 = int8_t; using i16 = int16_t; using i32 = int32_t; using i64 = int64_t; using uint = unsigned int; -using u8 = uint8_t; -using u16 = uint16_t; -using u32 = uint32_t; -using u64 = uint64_t; +using u8 = uint8_t; +using u16 = uint16_t; +using u32 = uint32_t; +using u64 = uint64_t; using f32 = float; using f64 = double; @@ -23,7 +23,7 @@ using f64 = double; using isize = int64_t; using usize = std::size_t; -using rawptr = void *; +using rawptr = void *; using uintptr = uintptr_t; using ptrdiff = ptrdiff_t; diff --git a/engine/native/core/definitions/version.cppm b/engine/native/core/definitions/version.cppm index fd1034df..dfbec3ae 100644 --- a/engine/native/core/definitions/version.cppm +++ b/engine/native/core/definitions/version.cppm @@ -8,22 +8,23 @@ import core.stdtypes; export namespace draco { struct Version { - u16 major; - u16 minor; - u16 patch; + u16 major; + u16 minor; + u16 patch; }; constexpr Version VERSION{.major = 2026, .minor = 0, .patch = 0}; } // namespace draco export namespace std { -template <> struct formatter { - constexpr auto parse(std::format_parse_context &ctx) { - return ctx.begin(); // Accept any format spec (or parse custom ones) - } - - auto format(const draco::Version &v, std::format_context &ctx) const { - return std::format_to(ctx.out(), "{}.{}.{}", v.major, v.minor, v.patch); - } +template<> struct formatter { + constexpr auto parse(std::format_parse_context &ctx) { + return ctx.begin(); // Accept any format spec (or parse custom ones) + } + + auto format(draco::Version const &v, std::format_context &ctx) const { + return std::format_to(ctx.out(), "{}.{}.{}", v.major, v.minor, v.patch); + } }; + } // namespace std diff --git a/engine/native/core/io/filesystem.cpp b/engine/native/core/io/filesystem.cpp index 36e98247..dc9982c7 100644 --- a/engine/native/core/io/filesystem.cpp +++ b/engine/native/core/io/filesystem.cpp @@ -9,37 +9,33 @@ module core.io.filesystem; import core.stdtypes; -namespace draco::core::io::filesystem -{ - std::vector load_binary(const std::string& path) - { - // Open at the end (ate) to get size and in binary mode - std::ifstream file(path, std::ios::binary | std::ios::ate); - - if (!file.is_open()) { - std::println("Error: Could not open file at: {}", path); - // Return an empty vector - return {}; - } - - std::streamsize size = file.tellg(); - if (size < 0) { - std::println("Error: File is empty or unreadable: {}", path); - return {}; - } - - if (size == 0) { - return {}; - } - - file.seekg(0, std::ios::beg); - - std::vector buffer(static_cast(size)); - if (file.read(reinterpret_cast(buffer.data()), size)) { - return buffer; - } - - std::println("Error: Failed to read file contents: {}", path); - return {}; - } -} \ No newline at end of file +namespace draco::core::io::filesystem { +std::vector load_binary(std::string const &path) { + // Open at the end (ate) to get size and in binary mode + std::ifstream file(path, std::ios::binary | std::ios::ate); + + if (!file.is_open()) { + std::println("Error: Could not open file at: {}", path); + // Return an empty vector + return {}; + } + + std::streamsize size = file.tellg(); + if (size < 0) { + std::println("Error: File is empty or unreadable: {}", path); + return {}; + } + + if (size == 0) { return {}; } + + file.seekg(0, std::ios::beg); + + std::vector buffer(static_cast(size)); + if (file.read(reinterpret_cast(buffer.data()), size)) { + return buffer; + } + + std::println("Error: Failed to read file contents: {}", path); + return {}; +} +} // namespace draco::core::io::filesystem diff --git a/engine/native/core/io/filesystem.cppm b/engine/native/core/io/filesystem.cppm index bdba6bdc..2094ac88 100644 --- a/engine/native/core/io/filesystem.cppm +++ b/engine/native/core/io/filesystem.cppm @@ -8,8 +8,7 @@ export module core.io.filesystem; import core.stdtypes; -export namespace draco::core::io::filesystem -{ - // Returns a buffer of the file data - std::vector load_binary(const std::string& path); -} +export namespace draco::core::io::filesystem { +// Returns a buffer of the file data +std::vector load_binary(std::string const &path); +} // namespace draco::core::io::filesystem diff --git a/engine/native/core/io/image_loader.cpp b/engine/native/core/io/image_loader.cpp index 98ec0b44..2b40d64b 100644 --- a/engine/native/core/io/image_loader.cpp +++ b/engine/native/core/io/image_loader.cpp @@ -14,50 +14,49 @@ import core.stdtypes; // TODO: I'm too lazy to write code so we need somethin' better -namespace draco::core::io::image_loader -{ - ImageData load_image(const std::filesystem::path& path) - { - ImageData result; - - std::error_code ec; - if (!std::filesystem::exists(path, ec) || ec) { - std::println("Error: Image path does not exist: {}", path.string()); - return result; - } - - int width, height, channels; - // STBI_rgb_alpha forces the output to be 4 bytes per pixel (RGBA) - unsigned char* data = stbi_load(path.string().c_str(), &width, &height, &channels, STBI_rgb_alpha); - - if (!data) { - std::println("Error: Failed to decode image: {}", path.string()); - return result; - } - - if (width <= 0 || height <= 0) { - stbi_image_free(data); - return result; - } - - const usize w = static_cast(width); - const usize h = static_cast(height); - if (w > (std::numeric_limits::max() / 4) / h) { - stbi_image_free(data); - return result; - } - - usize size = w * h * 4; - - result.pixels.assign(data, data + size); - result.width = static_cast(width); - result.height = static_cast(height); - result.channels = 4; - result.is_valid = true; - - // Free the memory allocated by stb - stbi_image_free(data); - - return result; - } -} \ No newline at end of file +namespace draco::core::io::image_loader { +ImageData load_image(std::filesystem::path const &path) { + ImageData result; + + std::error_code ec; + if (!std::filesystem::exists(path, ec) || ec) { + std::println("Error: Image path does not exist: {}", path.string()); + return result; + } + + int width, height, channels; + // STBI_rgb_alpha forces the output to be 4 bytes per pixel (RGBA) + unsigned char *data = stbi_load(path.string().c_str(), &width, &height, + &channels, STBI_rgb_alpha); + + if (!data) { + std::println("Error: Failed to decode image: {}", path.string()); + return result; + } + + if (width <= 0 || height <= 0) { + stbi_image_free(data); + return result; + } + + usize const w = static_cast(width); + usize const h = static_cast(height); + if (w > (std::numeric_limits::max() / 4) / h) { + stbi_image_free(data); + return result; + } + + usize size = w * h * 4; + + result.pixels.assign(data, data + size); + result.width = static_cast(width); + result.height = static_cast(height); + result.channels = 4; + result.is_valid = true; + + // Free the memory allocated by stb + stbi_image_free(data); + + return result; +} +} // namespace draco::core::io::image_loader diff --git a/engine/native/core/io/image_loader.cppm b/engine/native/core/io/image_loader.cppm index facced31..24d6572c 100644 --- a/engine/native/core/io/image_loader.cppm +++ b/engine/native/core/io/image_loader.cppm @@ -8,17 +8,15 @@ export module core.io.image_loader; import core.stdtypes; -export namespace draco::core::io::image_loader -{ - struct ImageData - { - std::vector pixels; - u32 width = 0; - u32 height = 0; - u8 channels = 0; - bool is_valid = false; - }; +export namespace draco::core::io::image_loader { +struct ImageData { + std::vector pixels; + u32 width = 0; + u32 height = 0; + u8 channels = 0; + bool is_valid = false; +}; - // Load an image file (PNG, JPG, etc) & decode it to raw RGBA8 - ImageData load_image(const std::filesystem::path& path); -} \ No newline at end of file +// Load an image file (PNG, JPG, etc) & decode it to raw RGBA8 +ImageData load_image(std::filesystem::path const &path); +} // namespace draco::core::io::image_loader diff --git a/engine/native/core/io/io.cppm b/engine/native/core/io/io.cppm index 64c775d8..9729c13e 100644 --- a/engine/native/core/io/io.cppm +++ b/engine/native/core/io/io.cppm @@ -1,4 +1,4 @@ export module core.io; export import core.io.filesystem; -export import core.io.image_loader; \ No newline at end of file +export import core.io.image_loader; diff --git a/engine/native/core/math/constants.cppm b/engine/native/core/math/constants.cppm index 210c35db..6aa87113 100644 --- a/engine/native/core/math/constants.cppm +++ b/engine/native/core/math/constants.cppm @@ -8,34 +8,37 @@ import core.defs; import core.stdtypes; export namespace draco::math { - // Limit the depth of recursive algorithms - constexpr int MAX_RECURSIONS = 100; +// Limit the depth of recursive algorithms +constexpr int MAX_RECURSIONS = 100; - constexpr f32 SQRT2 = std::numbers::sqrt2_v; - constexpr f32 SQRT3 = std::numbers::sqrt3_v; - constexpr f32 SQRT12 = 1. / SQRT2; - constexpr f32 SQRT13 = std::numbers::inv_sqrt3_v; - constexpr f32 LN2 = std::numbers::ln2_v; - constexpr f32 LN10 = std::numbers::ln10_v; - constexpr f32 PI = std::numbers::pi_v; - constexpr f32 PI2 = PI * .5; - constexpr f32 TAU = 2. * PI; - constexpr f32 E = std::numbers::e_v; - constexpr f32 INF = std::numeric_limits::infinity(); - constexpr f32 NaN = std::numeric_limits::quiet_NaN(); - constexpr f32 DB_CONVERSION_GAIN = 8.6858896380650365530225783783321; - constexpr f32 GAIN_CONVERSION_DB = 0.11512925464970228420089957273422; - constexpr u16 UINT16_MAX_VAL = std::numeric_limits::max(); - constexpr u32 UINT32_MAX_VAL = std::numeric_limits::max(); - // This is a reciprocal for normalization - // Used to map a u32 [0, MAX] range to a float [0, 1.0] range - constexpr f32 UINT32_INVERSE_MAX_F = static_cast(1.0 / static_cast(UINT32_MAX_VAL)); // Calculated via double precision to prevent rounding errors - constexpr f32 DECIMAL_LIMIT_F = 8388608.0f; +constexpr f32 SQRT2 = std::numbers::sqrt2_v; +constexpr f32 SQRT3 = std::numbers::sqrt3_v; +constexpr f32 SQRT12 = 1. / SQRT2; +constexpr f32 SQRT13 = std::numbers::inv_sqrt3_v; +constexpr f32 LN2 = std::numbers::ln2_v; +constexpr f32 LN10 = std::numbers::ln10_v; +constexpr f32 PI = std::numbers::pi_v; +constexpr f32 PI2 = PI * .5; +constexpr f32 TAU = 2. * PI; +constexpr f32 E = std::numbers::e_v; +constexpr f32 INF = std::numeric_limits::infinity(); +constexpr f32 NaN = std::numeric_limits::quiet_NaN(); +constexpr f32 DB_CONVERSION_GAIN = 8.6858896380650365530225783783321; +constexpr f32 GAIN_CONVERSION_DB = 0.11512925464970228420089957273422; +constexpr u16 UINT16_MAX_VAL = std::numeric_limits::max(); +constexpr u32 UINT32_MAX_VAL = std::numeric_limits::max(); +// This is a reciprocal for normalization +// Used to map a u32 [0, MAX] range to a float [0, 1.0] range +// Calculated via double precision to prevent rounding errors +constexpr f32 UINT32_INVERSE_MAX_F = + static_cast(1.0 / static_cast(UINT32_MAX_VAL)); +constexpr f32 DECIMAL_LIMIT_F = 8388608.0F; - constexpr f32 CMP_EPSILON = 0.000001f; - constexpr f32 CMP_EPSILON2 = CMP_EPSILON * CMP_EPSILON; +constexpr f32 CMP_EPSILON = 0.000001F; +constexpr f32 CMP_EPSILON2 = CMP_EPSILON * CMP_EPSILON; - constexpr f32 CMP_NORMALIZE_TOLERANCE = 0.000001f; - constexpr f32 CMP_NORMALIZE_TOLERANCE2 = CMP_NORMALIZE_TOLERANCE * CMP_NORMALIZE_TOLERANCE; - constexpr f32 CMP_POINT_IN_PLANE_EPSILON = 0.00001f; -} +constexpr f32 CMP_NORMALIZE_TOLERANCE = 0.000001F; +constexpr f32 CMP_NORMALIZE_TOLERANCE2 = + CMP_NORMALIZE_TOLERANCE * CMP_NORMALIZE_TOLERANCE; +constexpr f32 CMP_POINT_IN_PLANE_EPSILON = 0.00001F; +} // namespace draco::math diff --git a/engine/native/core/math/functions.cppm b/engine/native/core/math/functions.cppm index c3260098..948da0e3 100644 --- a/engine/native/core/math/functions.cppm +++ b/engine/native/core/math/functions.cppm @@ -11,186 +11,190 @@ import core.defs; import core.stdtypes; export namespace draco::math { - template - constexpr T sqr(T x) noexcept { return x*x; } - - template - [[nodiscard]] constexpr bool is_nan(T val) noexcept { - // Only NaN does not equal itself. - return val != val; - } - - template - [[nodiscard]] constexpr bool is_inf(T val) noexcept { - return std::isinf(val); - } - - template - [[nodiscard]] constexpr bool is_finite(T val) noexcept { - return std::isfinite(val); - } - - template - constexpr T abs(T value) noexcept { - // Manually compute abs for signed types. - // Also avoids potential i8 -> i32 issues. - if constexpr (std::floating_point) { - return value < T{0} ? -value : value; - } else if constexpr (std::signed_integral) { - if (value == std::numeric_limits::min()) { - return std::numeric_limits::max(); // define saturating behavior explicitly - } - return value < T{0} ? -value : value; - } else { - // unsigned is always positive! :^) - return value; - } - } - - template - constexpr T sign(T value) noexcept { - if constexpr (std::floating_point) { - if (value != value) { - return value; - } else if (value) { - return value < T{0} ? T{-1} : T{1}; - } - return T{0}; - } else if constexpr (std::signed_integral) { - if (value) { - return value < T{0} ? T{-1} : T{1}; - } - return T{0}; - } else { - return T{value != T{0}}; - } +template +constexpr T sqr(T x) noexcept { + return x * x; +} + +template +[[nodiscard]] constexpr bool is_nan(T val) noexcept { + // Only NaN does not equal itself. + return val != val; +} + +template +[[nodiscard]] constexpr bool is_inf(T val) noexcept { + return std::isinf(val); +} + +template +[[nodiscard]] constexpr bool is_finite(T val) noexcept { + return std::isfinite(val); +} + +template +constexpr T abs(T value) noexcept { + // Manually compute abs for signed types. + // Also avoids potential i8 -> i32 issues. + if constexpr (std::floating_point) { + return value < T{0} ? -value : value; } - - constexpr f32 floor(f32 value) noexcept { - if (value != value || abs(value) >= DECIMAL_LIMIT_F) { - return value; + else if constexpr (std::signed_integral) { + if (value == std::numeric_limits::min()) { + return std::numeric_limits:: + max(); // define saturating behavior explicitly } - const f32 truncated = static_cast(value); - return truncated - (value < truncated); + return value < T{0} ? -value : value; } - - constexpr f32 ceil(f32 value) noexcept { - return -floor(-value); + else { + // unsigned is always positive! :^) + return value; } - - constexpr f32 trunc(f32 value) noexcept { - if (value != value || abs(value) >= DECIMAL_LIMIT_F) { - return value; - } - return static_cast(value); +} + +template +constexpr T sign(T value) noexcept { + if constexpr (std::floating_point) { + if (value != value) { return value; } + else if (value) { return value < T{0} ? T{-1} : T{1}; } + return T{0}; } - - constexpr f32 round(f32 value) noexcept { - const f32 s = sign(value); - return s * floor(s * value + 0.5f); + else if constexpr (std::signed_integral) { + if (value) { return value < T{0} ? T{-1} : T{1}; } + return T{0}; } - - template - constexpr T deg_to_rad(T y) noexcept { - return y * (T{PI} / T{180.}); - } - - template - constexpr T rad_to_deg(T y) noexcept { - return y * (T{180.} / T{PI}); - } - - template - T pow(T x, T y) { - return static_cast(std::pow(x, y)); - } - - template - constexpr T lerp(T from, T to, T weight) noexcept { - return std::lerp(from, to, weight); - } - - template - constexpr T cubic_interpolate(T from, T to, T before, T after, T weight) noexcept { - // weight squared. - T w2 = weight * weight; - // weight cubed. - T w3 = weight * w2; - - // calculate coefficients. - T a = -before + to; - T b = T{2} * before - T{5} * from + T{4} * to - after; - T c = -before + T{3} * from - T{3} * to + after; - - // Catmull-Rom Interpolation: - // 0.5 * ((2 * p_from) + (a * w) + (b * w^2) + (c * w^3)) - - if consteval { - // compile time - return T{0.5} * (T{2.}*from + a*weight + b*w2 + c*w3); - } else { - // runtime - return T{0.5} * std::fma(c, w3, std::fma(b, w2, std::fma(a, weight, T{2} * from))); - } - } - - template - constexpr T cubic_interpolate_in_time( - T from, T to, - T before, T after, T weight, - T to_t, T before_t, T after_t) noexcept { - /* Barry-Goldman method */ - T t = lerp(T{0.}, to_t, weight); - - // At least try to make this easier to parse for others. - T pre_scale = before_t == T{0.} ? T{0.} : (t - before_t) / -before_t; - T to_scale = (to_t == T{0.}) ? T{.5} : t / to_t; - T post_range = after_t - to_t; - T post_scale = (post_range == T{0.}) ? T{1.} : (t - to_t) / post_range; - - // First layer. - T a1 = lerp(before, from, pre_scale); - T a2 = lerp(from, to, to_scale); - T a3 = lerp(to, after, post_scale); - - // More parsing. - T mid_range = to_t - before_t; - T from_to_scale = (mid_range == T{0.}) ? T{0.} : (t - before_t) / mid_range; - T to_post_scale = (after_t == T{0.}) ? T{1.} : t / after_t; - - // Second layer. - T b1 = lerp(a1, a2, from_to_scale); - T b2 = lerp(a2, a3, to_post_scale); - - // One more for the road. - T final_scale = (to_t == T{0.}) ? T{.5} : t / to_t; - - return lerp(b1, b2, final_scale); - } - - template - constexpr T bezier_interpolate(T start, T control_1, T control_2, T end, T t) noexcept { - /* Formula from Wikipedia article on Bezier curves. */ - // one minus t. - T omt = T{1.} - t; - T omt2 = omt * omt; - T omt3 = omt2 * omt; - T t2 = t * t; - T t3 = t2 * t; - - // B(t) = (1-t)^3 * P_0 + 3(1 - t)^2 * t * P_1 + 3(1 - t) * t^2 * P_2 + t^3 * P_3 - T d = start * omt3 + control_1 * omt2 * t * T{3.} + control_2 * omt * t2 * T{3.} + end * t3; - return d; - } - - template - constexpr T bezier_derivative(T start, T control_1, T control_2, T end, T t) noexcept { - /* Formula from Wikipedia article on Bezier curves. */ - T omt = T{1.} - t; - T omt2 = omt * omt; - T t2 = t * t; - - T d = (control_1 - start) * T{3.} * omt2 + (control_2 - control_1) * T{6.} * omt * t + (end - control_2) * T{3.} * t2; - return d; - } -} \ No newline at end of file + else { return T{value != T{0}}; } +} + +constexpr f32 floor(f32 value) noexcept { + if (value != value || abs(value) >= DECIMAL_LIMIT_F) { return value; } + f32 const truncated = static_cast(value); + return truncated - (value < truncated); +} + +constexpr f32 ceil(f32 value) noexcept { + return -floor(-value); +} + +constexpr f32 trunc(f32 value) noexcept { + if (value != value || abs(value) >= DECIMAL_LIMIT_F) { return value; } + return static_cast(value); +} + +constexpr f32 round(f32 value) noexcept { + f32 const s = sign(value); + return s * floor(s * value + 0.5F); +} + +template +constexpr T deg_to_rad(T y) noexcept { + return y * (T{PI} / T{180.}); +} + +template +constexpr T rad_to_deg(T y) noexcept { + return y * (T{180.} / T{PI}); +} + +template +T pow(T x, T y) { + return static_cast(std::pow(x, y)); +} + +template +constexpr T lerp(T from, T to, T weight) noexcept { + return std::lerp(from, to, weight); +} + +template +constexpr T +cubic_interpolate(T from, T to, T before, T after, T weight) noexcept { + // weight squared. + T w2 = weight * weight; + // weight cubed. + T w3 = weight * w2; + + // calculate coefficients. + T a = -before + to; + T b = T{2} * before - T{5} * from + T{4} * to - after; + T c = -before + T{3} * from - T{3} * to + after; + + // Catmull-Rom Interpolation: + // 0.5 * ((2 * p_from) + (a * w) + (b * w^2) + (c * w^3)) + + if consteval { + // compile time + return T{0.5} * (T{2.} * from + a * weight + b * w2 + c * w3); + } + else { + // runtime + return T{0.5} * + std::fma(c, w3, + std::fma(b, w2, std::fma(a, weight, T{2} * from))); + } +} + +template +constexpr T cubic_interpolate_in_time( + T from, T to, T before, T after, T weight, T to_t, T before_t, T after_t +) noexcept { + /* Barry-Goldman method */ + T t = lerp(T{0.}, to_t, weight); + + // At least try to make this easier to parse for others. + T pre_scale = before_t == T{0.} ? T{0.} : (t - before_t) / -before_t; + T to_scale = (to_t == T{0.}) ? T{.5} : t / to_t; + T post_range = after_t - to_t; + T post_scale = (post_range == T{0.}) ? T{1.} : (t - to_t) / post_range; + + // First layer. + T a1 = lerp(before, from, pre_scale); + T a2 = lerp(from, to, to_scale); + T a3 = lerp(to, after, post_scale); + + // More parsing. + T mid_range = to_t - before_t; + T from_to_scale = (mid_range == T{0.}) ? T{0.} : (t - before_t) / mid_range; + T to_post_scale = (after_t == T{0.}) ? T{1.} : t / after_t; + + // Second layer. + T b1 = lerp(a1, a2, from_to_scale); + T b2 = lerp(a2, a3, to_post_scale); + + // One more for the road. + T final_scale = (to_t == T{0.}) ? T{.5} : t / to_t; + + return lerp(b1, b2, final_scale); +} + +template +constexpr T +bezier_interpolate(T start, T control_1, T control_2, T end, T t) noexcept { + /* Formula from Wikipedia article on Bezier curves. */ + // one minus t. + T omt = T{1.} - t; + T omt2 = omt * omt; + T omt3 = omt2 * omt; + T t2 = t * t; + T t3 = t2 * t; + + // B(t) = (1-t)^3 * P_0 + 3(1 - t)^2 * t * P_1 + 3(1 - t) * t^2 * P_2 + t^3 * P_3 + T d = start * omt3 + control_1 * omt2 * t * T{3.} + + control_2 * omt * t2 * T{3.} + end * t3; + return d; +} + +template +constexpr T +bezier_derivative(T start, T control_1, T control_2, T end, T t) noexcept { + /* Formula from Wikipedia article on Bezier curves. */ + T omt = T{1.} - t; + T omt2 = omt * omt; + T t2 = t * t; + + T d = (control_1 - start) * T{3.} * omt2 + + (control_2 - control_1) * T{6.} * omt * t + + (end - control_2) * T{3.} * t2; + return d; +} +} // namespace draco::math diff --git a/engine/native/core/math/math.test.cpp b/engine/native/core/math/math.test.cpp index 2424f921..827cf32b 100644 --- a/engine/native/core/math/math.test.cpp +++ b/engine/native/core/math/math.test.cpp @@ -34,1417 +34,1051 @@ import core.math; using namespace draco; TEST_SUITE("math") { - TEST_CASE("pow") { - f32 result = draco::math::pow(2.0f, 0.5f); - constexpr f32 expected = draco::math::SQRT2; - CHECK_EQ(result, expected); - } - - TEST_CASE("abs") { - using draco::math::abs; - - RAC_CHECK_EQ(abs(-1.f), 1.f); - RAC_CHECK_EQ(abs(4.56f), 4.56f); - RAC_CHECK_EQ(abs(-1.), 1.); - RAC_CHECK_EQ(abs(4.56), 4.56); - RAC_CHECK_EQ(abs(-5), 5); - RAC_CHECK_EQ(abs(3L), 3L); - RAC_CHECK_EQ(abs(-32L), 32L); - RAC_CHECK_EQ(abs(5000ULL), 5000ULL); - } + TEST_CASE("pow") { + f32 result = draco::math::pow(2.0F, 0.5F); + constexpr f32 expected = draco::math::SQRT2; + CHECK_EQ(result, expected); + } + + TEST_CASE("abs") { + using draco::math::abs; + + RAC_CHECK_EQ(abs(-1.F), 1.F); + RAC_CHECK_EQ(abs(4.56F), 4.56F); + RAC_CHECK_EQ(abs(-1.), 1.); + RAC_CHECK_EQ(abs(4.56), 4.56); + RAC_CHECK_EQ(abs(-5), 5); + RAC_CHECK_EQ(abs(3L), 3L); + RAC_CHECK_EQ(abs(-32L), 32L); + RAC_CHECK_EQ(abs(5000ULL), 5000ULL); + } } TEST_SUITE("vector2") { - TEST_CASE("constructors") { - using draco::math::Vector2; - using draco::math::Vector3; - using draco::math::Vector4; - - static constexpr Vector3 a{1.0f, 2.0f, 3.0f}; - static constexpr Vector4 b{4.0f, 5.0f, 6.0f, 7.0f}; - - BASIC_RAC_SUBCASE("float", - ( Vector2(1.0f) ), - ( Vector2{1.0f, 1.0f} ) - ); - - BASIC_RAC_SUBCASE("vec3", - ( Vector2(a) ), - ( Vector2{1.0f, 2.0f} ) - ); - - BASIC_RAC_SUBCASE("vec4", - ( Vector2(b) ), - ( Vector2{4.0f, 5.0f} ) - ); - } - - TEST_CASE("access") { - using draco::math::Vector2; - - static constexpr Vector2 v(1.0f, 2.0f); - - RAC_CHECK_EQ(v[0], 1.0f); - RAC_CHECK_EQ(v[1], 2.0f); - } - - TEST_CASE("swizzle") { - using draco::math::Vector2; - using draco::math::Vector3; - using draco::math::Vector4; - - static constexpr Vector2 v{1.0f, 2.0f}; - - BASIC_RAC_SUBCASE("vec2", - ( v[1, 0] ), - ( Vector2{2.0f, 1.0f} ) - ); - - BASIC_RAC_SUBCASE("vec3", - ( v[1, 1, 0] ), - ( Vector3{2.0f, 2.0f, 1.0f} ) - ); - - BASIC_RAC_SUBCASE("vec4", - ( v[0, 1, 1, 0] ), - ( Vector4{1.0f, 2.0f, 2.0f, 1.0f} ) - ); - } - - TEST_CASE("swap") { - using draco::math::Vector2; - - Vector2 a{1.f, 2.f}; - Vector2 b{2.f, 1.f}; - - std::swap(a, b); - - CHECK_EQ(a, Vector2{2.f, 1.f}); - CHECK_EQ(b, Vector2{1.f, 2.f}); - } - - TEST_CASE("dot") { - using draco::math::Vector2; - using draco::math::dot; - - static constexpr Vector2 a{1.0f, 2.0f}; - static constexpr Vector2 b{3.0f, 4.0f}; - - BASIC_RAC_SUBCASE("basic", - ( dot(a, b) ), - ( 11.0f ) - ); - - BASIC_RAC_SUBCASE("self", - ( dot(a, a) ), - ( 5.0f ) - ); - - BASIC_RAC_SUBCASE("zero", - ( dot(a, Vector2()) ), - ( 0.0f ) - ); - } - - TEST_CASE("length") { - using draco::math::Vector2; - using draco::math::length; - using draco::math::length_sq; - - static constexpr Vector2 v{3.0f, 4.0f}; - - BASIC_R_SUBCASE("normal", - ( length(v) ), - ( 5.0f ) - ); - - BASIC_RAC_SUBCASE("squared", - ( length_sq(v) ), - ( 25.0f ) - ); - } - - TEST_CASE("distance") { - using draco::math::Vector2; - using draco::math::distance; - using draco::math::distance_sq; - - static constexpr Vector2 a{3.0f, 4.0f}; - static constexpr Vector2 b{-3.0f, 12.0f}; - - BASIC_R_SUBCASE("normal", - ( distance(a, b) ), - ( 10.0f ) - ); - - BASIC_RAC_SUBCASE("squared", - ( distance_sq(a, b) ), - ( 100.0f ) - ); - } - - TEST_CASE("normalize") { - using draco::math::Vector2; - using draco::math::length; - using draco::math::normalize; - using draco::math::normalize_fast; - - static constexpr Vector2 a{3.0f, 4.0f}; - static constexpr Vector2 b(1e-99); - - const Vector2 result = normalize(a); - const Vector2 result_fast = normalize_fast(a); - const Vector2 result_zero = normalize(b); - - CHECK_EQ(length(result), 1.0f); - CHECK_EQ(result, result_fast); - CHECK_EQ(result_zero, Vector2()); - } - - TEST_CASE("project") { - using draco::math::Vector2; - using draco::math::project; - - static constexpr Vector2 a{4.0f, 6.0f}; - static constexpr Vector2 b{2.0f, 2.0f}; - - RAC_CHECK_EQ( - ( project(a, b) ), - ( Vector2{5.0f, 5.0f} ) - ); - } - - TEST_CASE("reflect") { - using draco::math::Vector2; - using draco::math::reflect; - - static constexpr Vector2 a{1.0f, 2.0f}; - static constexpr Vector2 b{3.0f, 4.0f}; - - RAC_CHECK_EQ( - ( reflect(a, b) ), - ( Vector2{-65.0f, -86.0f} ) - ); - } - - TEST_CASE("angle") { - using draco::math::Vector2; - using draco::math::angle; - using draco::math::PI2; - - static constexpr Vector2 a{2.0f, 1.0f}; - static constexpr Vector2 b{-2.0f, 4.0f}; - - R_CHECK_EQ( - ( angle(a, b) ), - ( PI2 ) - ); - } - - TEST_CASE("lerp") { - using draco::math::Vector2; - using draco::math::lerp; - - static constexpr Vector2 a{1.0f, 2.0f}; - static constexpr Vector2 b{3.0f, 4.0f}; - - BASIC_RAC_SUBCASE("weight = -1", - ( lerp(a, b, -1.0f) ), - ( Vector2{-1.0f, 0.0f} ) - ); - - BASIC_RAC_SUBCASE("weight = 0", - ( lerp(a, b, 0.0f) ), - ( a ) - ); - - BASIC_RAC_SUBCASE("weight = 0.5", - ( lerp(a, b, 0.5f) ), - ( Vector2{2.0f, 3.0f} ) - ); - - BASIC_RAC_SUBCASE("weight = 1", - ( lerp(a, b, 1.0f) ), - ( b ) - ); - - BASIC_RAC_SUBCASE("weight = 2", - ( lerp(a, b, 2.0f) ), - ( Vector2{5.0f, 6.0f} ) - ); - } - - TEST_CASE("min") { - using draco::math::Vector2; - using draco::math::min; - - static constexpr Vector2 a{5.0f, 3.0f}; - static constexpr Vector2 b{1.0f, 7.0f}; - - BASIC_RAC_SUBCASE("vector", - ( min(a, b) ), - ( Vector2{1.0f, 3.0f} ) - ); - - static constexpr Vector2 expected{4.0f, 3.0f}; - - BASIC_RAC_SUBCASE_2("float", - ( min(a, 4.0f) ), - ( expected ), - ( min(4.0f, a) ), - ( expected ) - ); - } - - TEST_CASE("min_length") { - using draco::math::Vector2; - using draco::math::length; - using draco::math::min_length; - - static constexpr Vector2 a{3.0f, 4.0f}; // len: 5 - static constexpr Vector2 b{5.0f, 12.0f}; // len: 13 - - BASIC_RAC_SUBCASE("vector", - ( min_length(a, b) ), - ( a ) - ); - - SUBCASE("float") { - static constexpr f32 smaller_length = 1.0f; - static constexpr f32 larger_length = 10.0f; - - const Vector2 result_smaller = min_length(a, smaller_length); - const Vector2 result_swapped = min_length(smaller_length, a); - const Vector2 result_larger = min_length(larger_length, a); - - CHECK_EQ(length(result_smaller), smaller_length); - CHECK_EQ(result_smaller, result_swapped); - CHECK_EQ(result_larger, a); - } - } - - TEST_CASE("max") { - using draco::math::Vector2; - using draco::math::max; - - static constexpr Vector2 a{5.0f, 3.0f}; - static constexpr Vector2 b{1.0f, 7.0f}; - - BASIC_RAC_SUBCASE("vector", - ( max(a, b) ), - ( Vector2{5.0f, 7.0f} ) - ); - - static constexpr Vector2 expected{5.0f, 4.0f}; - - BASIC_RAC_SUBCASE_2("float", - ( max(a, 4.0f) ), - ( expected ), - ( max(4.0f, a) ), - ( expected ) - ); - } - - TEST_CASE("max_length") { - using draco::math::Vector2; - using draco::math::length; - using draco::math::max_length; - - static constexpr Vector2 a{3.0f, 4.0f}; // len: 5 - static constexpr Vector2 b{5.0f, 12.0f}; // len: 13 - - BASIC_RAC_SUBCASE("vector", - ( max_length(a, b) ), - ( b ) - ); - - SUBCASE("float") { - static constexpr f32 smaller_length = 1.0f; - static constexpr f32 larger_length = 10.0f; - - const Vector2 result_smaller = max_length(a, smaller_length); - const Vector2 result_swapped = max_length(larger_length, a); - const Vector2 result_larger = max_length(larger_length, a); - - CHECK_EQ(length(result_larger), larger_length); - CHECK_EQ(result_larger, result_swapped); - CHECK_EQ(result_smaller, a); - } - } - - TEST_CASE("clamp") { - using draco::math::Vector2; - using draco::math::clamp; - - static constexpr Vector2 a{5.0f, 3.0f}; - static constexpr Vector2 b{1.0f, 7.0f}; - static constexpr Vector2 c{3.0f, 8.0f}; - - BASIC_RAC_SUBCASE("vector", - ( clamp(a, b, c) ), - ( Vector2{3.0f, 7.0f} ) - ); - - BASIC_RAC_SUBCASE("float", - ( clamp(a, 4.0f, 5.0f) ), - ( Vector2{5.0f, 4.0f} ) - ); - } - - TEST_CASE("clamp_length") { - using draco::math::Vector2; - using draco::math::length; - using draco::math::clamp_length; - - static constexpr Vector2 v{3.0f, 4.0f}; // len: 5 - - BASIC_R_SUBCASE("length < min", - ( clamp_length(v, 10.0f, 15.0f) ), - ( Vector2{6.0f, 8.0f} ) - ); - - BASIC_R_SUBCASE("length == min", - ( clamp_length(v, 5.0f, 10.0f) ), - ( v ) - ); - - BASIC_R_SUBCASE("length == max", - ( clamp_length(v, 3.0f, 5.0f) ), - ( v ) - ); - - BASIC_R_SUBCASE("length > max", - ( clamp_length(v, 1.0f, 2.5f) ), - ( Vector2{1.5f, 2.0f} ) - ); - } - - TEST_CASE("abs") { - using draco::math::Vector2; - using draco::math::abs; - - RAC_CHECK_EQ( - ( abs(Vector2{1.0f, -2.0f}) ), - ( Vector2{1.0f, 2.0f} ) - ); - } - - TEST_CASE("rounding") { - using draco::math::Vector2; - using draco::math::floor; - using draco::math::ceil; - using draco::math::trunc; - using draco::math::round; - - static constexpr Vector2 a{0.5f, 1.4f}; - static constexpr Vector2 b{-1.0f, 1.0f}; - - BASIC_RAC_SUBCASE_2("floor", - ( floor(a) ), - ( Vector2{0.0f, 1.0f} ), - ( floor(b) ), - ( b ) - ); - - BASIC_RAC_SUBCASE_2("ceil", - ( ceil(a) ), - ( Vector2{1.0f, 2.0f} ), - ( ceil(b) ), - ( b ) - ); - - BASIC_RAC_SUBCASE_2("trunc", - ( trunc(a) ), - ( Vector2{0.0f, 1.0f} ), - ( trunc(b) ), - ( b ) - ); - - BASIC_RAC_SUBCASE_2("round", - ( round(a) ), - ( Vector2{1.0f, 1.0f} ), - ( round(b) ), - ( b ) - ); - } - - TEST_CASE("sign") { - using draco::math::Vector2; - using draco::math::sign; - - RAC_CHECK_EQ( - ( sign(Vector2{1.0f, -1.0f}) ), - ( Vector2{1.0f, -1.0f} ) - ); - } - - TEST_CASE("approx_eq") { - using draco::math::Vector2; - using draco::math::approx_eq; - using draco::math::CMP_EPSILON; - - static constexpr Vector2 v{1.0f, 2.0f}; - static constexpr Vector2 offset = Vector2::x_axis(CMP_EPSILON); - - BASIC_R_SUBCASE("distance < epsilon", - ( approx_eq(v, v + offset * 0.5f) ), - ( true ) - ); - - BASIC_R_SUBCASE("distance == epsilon", - ( approx_eq(v, v + offset) ), - ( true ) - ); - - BASIC_R_SUBCASE("distance > epsilon", - ( approx_eq(v, v + offset * 2.0f) ), - ( false ) - ); - } + TEST_CASE("constructors") { + using draco::math::Vector2; + using draco::math::Vector3; + using draco::math::Vector4; + + static constexpr Vector3 a{1.0F, 2.0F, 3.0F}; + static constexpr Vector4 b{4.0F, 5.0F, 6.0F, 7.0F}; + + BASIC_RAC_SUBCASE("float", (Vector2(1.0F)), (Vector2{1.0F, 1.0F})); + + BASIC_RAC_SUBCASE("vec3", (Vector2(a)), (Vector2{1.0F, 2.0F})); + + BASIC_RAC_SUBCASE("vec4", (Vector2(b)), (Vector2{4.0F, 5.0F})); + } + + TEST_CASE("access") { + using draco::math::Vector2; + + static constexpr Vector2 v(1.0F, 2.0F); + + RAC_CHECK_EQ(v[0], 1.0F); + RAC_CHECK_EQ(v[1], 2.0F); + } + + TEST_CASE("swizzle") { + using draco::math::Vector2; + using draco::math::Vector3; + using draco::math::Vector4; + + static constexpr Vector2 v{1.0F, 2.0F}; + + BASIC_RAC_SUBCASE("vec2", (v[1, 0]), (Vector2{2.0F, 1.0F})); + + BASIC_RAC_SUBCASE("vec3", (v[1, 1, 0]), (Vector3{2.0F, 2.0F, 1.0F})); + + BASIC_RAC_SUBCASE("vec4", (v[0, 1, 1, 0]), + (Vector4{1.0F, 2.0F, 2.0F, 1.0F})); + } + + TEST_CASE("swap") { + using draco::math::Vector2; + + Vector2 a{1.F, 2.F}; + Vector2 b{2.F, 1.F}; + + std::swap(a, b); + + CHECK_EQ(a, Vector2{2.F, 1.F}); + CHECK_EQ(b, Vector2{1.F, 2.F}); + } + + TEST_CASE("dot") { + using draco::math::Vector2; + using draco::math::dot; + + static constexpr Vector2 a{1.0F, 2.0F}; + static constexpr Vector2 b{3.0F, 4.0F}; + + BASIC_RAC_SUBCASE("basic", (dot(a, b)), (11.0F)); + + BASIC_RAC_SUBCASE("self", (dot(a, a)), (5.0F)); + + BASIC_RAC_SUBCASE("zero", (dot(a, Vector2())), (0.0F)); + } + + TEST_CASE("length") { + using draco::math::Vector2; + using draco::math::length; + using draco::math::length_sq; + + static constexpr Vector2 v{3.0F, 4.0F}; + + BASIC_R_SUBCASE("normal", (length(v)), (5.0F)); + + BASIC_RAC_SUBCASE("squared", (length_sq(v)), (25.0F)); + } + + TEST_CASE("distance") { + using draco::math::Vector2; + using draco::math::distance; + using draco::math::distance_sq; + + static constexpr Vector2 a{3.0F, 4.0F}; + static constexpr Vector2 b{-3.0F, 12.0F}; + + BASIC_R_SUBCASE("normal", (distance(a, b)), (10.0F)); + + BASIC_RAC_SUBCASE("squared", (distance_sq(a, b)), (100.0F)); + } + + TEST_CASE("normalize") { + using draco::math::Vector2; + using draco::math::length; + using draco::math::normalize; + using draco::math::normalize_fast; + + static constexpr Vector2 a{3.0F, 4.0F}; + static constexpr Vector2 b(1e-99); + + Vector2 const result = normalize(a); + Vector2 const result_fast = normalize_fast(a); + Vector2 const result_zero = normalize(b); + + CHECK_EQ(length(result), 1.0F); + CHECK_EQ(result, result_fast); + CHECK_EQ(result_zero, Vector2()); + } + + TEST_CASE("project") { + using draco::math::Vector2; + using draco::math::project; + + static constexpr Vector2 a{4.0F, 6.0F}; + static constexpr Vector2 b{2.0F, 2.0F}; + + RAC_CHECK_EQ((project(a, b)), (Vector2{5.0F, 5.0F})); + } + + TEST_CASE("reflect") { + using draco::math::Vector2; + using draco::math::reflect; + + static constexpr Vector2 a{1.0F, 2.0F}; + static constexpr Vector2 b{3.0F, 4.0F}; + + RAC_CHECK_EQ((reflect(a, b)), (Vector2{-65.0F, -86.0F})); + } + + TEST_CASE("angle") { + using draco::math::Vector2; + using draco::math::angle; + using draco::math::PI2; + + static constexpr Vector2 a{2.0F, 1.0F}; + static constexpr Vector2 b{-2.0F, 4.0F}; + + R_CHECK_EQ((angle(a, b)), (PI2)); + } + + TEST_CASE("lerp") { + using draco::math::Vector2; + using draco::math::lerp; + + static constexpr Vector2 a{1.0F, 2.0F}; + static constexpr Vector2 b{3.0F, 4.0F}; + + BASIC_RAC_SUBCASE("weight = -1", (lerp(a, b, -1.0F)), + (Vector2{-1.0F, 0.0F})); + + BASIC_RAC_SUBCASE("weight = 0", (lerp(a, b, 0.0F)), (a)); + + BASIC_RAC_SUBCASE("weight = 0.5", (lerp(a, b, 0.5F)), + (Vector2{2.0F, 3.0F})); + + BASIC_RAC_SUBCASE("weight = 1", (lerp(a, b, 1.0F)), (b)); + + BASIC_RAC_SUBCASE("weight = 2", (lerp(a, b, 2.0F)), + (Vector2{5.0F, 6.0F})); + } + + TEST_CASE("min") { + using draco::math::Vector2; + using draco::math::min; + + static constexpr Vector2 a{5.0F, 3.0F}; + static constexpr Vector2 b{1.0F, 7.0F}; + + BASIC_RAC_SUBCASE("vector", (min(a, b)), (Vector2{1.0F, 3.0F})); + + static constexpr Vector2 expected{4.0F, 3.0F}; + + BASIC_RAC_SUBCASE_2("float", (min(a, 4.0F)), (expected), (min(4.0F, a)), + (expected)); + } + + TEST_CASE("min_length") { + using draco::math::Vector2; + using draco::math::length; + using draco::math::min_length; + + static constexpr Vector2 a{3.0F, 4.0F}; // len: 5 + static constexpr Vector2 b{5.0F, 12.0F}; // len: 13 + + BASIC_RAC_SUBCASE("vector", (min_length(a, b)), (a)); + + SUBCASE("float") { + static constexpr f32 smaller_length = 1.0F; + static constexpr f32 larger_length = 10.0F; + + Vector2 const result_smaller = min_length(a, smaller_length); + Vector2 const result_swapped = min_length(smaller_length, a); + Vector2 const result_larger = min_length(larger_length, a); + + CHECK_EQ(length(result_smaller), smaller_length); + CHECK_EQ(result_smaller, result_swapped); + CHECK_EQ(result_larger, a); + } + } + + TEST_CASE("max") { + using draco::math::Vector2; + using draco::math::max; + + static constexpr Vector2 a{5.0F, 3.0F}; + static constexpr Vector2 b{1.0F, 7.0F}; + + BASIC_RAC_SUBCASE("vector", (max(a, b)), (Vector2{5.0F, 7.0F})); + + static constexpr Vector2 expected{5.0F, 4.0F}; + + BASIC_RAC_SUBCASE_2("float", (max(a, 4.0F)), (expected), (max(4.0F, a)), + (expected)); + } + + TEST_CASE("max_length") { + using draco::math::Vector2; + using draco::math::length; + using draco::math::max_length; + + static constexpr Vector2 a{3.0F, 4.0F}; // len: 5 + static constexpr Vector2 b{5.0F, 12.0F}; // len: 13 + + BASIC_RAC_SUBCASE("vector", (max_length(a, b)), (b)); + + SUBCASE("float") { + static constexpr f32 smaller_length = 1.0F; + static constexpr f32 larger_length = 10.0F; + + Vector2 const result_smaller = max_length(a, smaller_length); + Vector2 const result_swapped = max_length(larger_length, a); + Vector2 const result_larger = max_length(larger_length, a); + + CHECK_EQ(length(result_larger), larger_length); + CHECK_EQ(result_larger, result_swapped); + CHECK_EQ(result_smaller, a); + } + } + + TEST_CASE("clamp") { + using draco::math::Vector2; + using draco::math::clamp; + + static constexpr Vector2 a{5.0F, 3.0F}; + static constexpr Vector2 b{1.0F, 7.0F}; + static constexpr Vector2 c{3.0F, 8.0F}; + + BASIC_RAC_SUBCASE("vector", (clamp(a, b, c)), (Vector2{3.0F, 7.0F})); + + BASIC_RAC_SUBCASE("float", (clamp(a, 4.0F, 5.0F)), + (Vector2{5.0F, 4.0F})); + } + + TEST_CASE("clamp_length") { + using draco::math::Vector2; + using draco::math::length; + using draco::math::clamp_length; + + static constexpr Vector2 v{3.0F, 4.0F}; // len: 5 + + BASIC_R_SUBCASE("length < min", (clamp_length(v, 10.0F, 15.0F)), + (Vector2{6.0F, 8.0F})); + + BASIC_R_SUBCASE("length == min", (clamp_length(v, 5.0F, 10.0F)), (v)); + + BASIC_R_SUBCASE("length == max", (clamp_length(v, 3.0F, 5.0F)), (v)); + + BASIC_R_SUBCASE("length > max", (clamp_length(v, 1.0F, 2.5F)), + (Vector2{1.5F, 2.0F})); + } + + TEST_CASE("abs") { + using draco::math::Vector2; + using draco::math::abs; + + RAC_CHECK_EQ((abs(Vector2{1.0F, -2.0F})), (Vector2{1.0F, 2.0F})); + } + + TEST_CASE("rounding") { + using draco::math::Vector2; + using draco::math::floor; + using draco::math::ceil; + using draco::math::trunc; + using draco::math::round; + + static constexpr Vector2 a{0.5F, 1.4F}; + static constexpr Vector2 b{-1.0F, 1.0F}; + + BASIC_RAC_SUBCASE_2("floor", (floor(a)), (Vector2{0.0F, 1.0F}), + (floor(b)), (b)); + + BASIC_RAC_SUBCASE_2("ceil", (ceil(a)), (Vector2{1.0F, 2.0F}), (ceil(b)), + (b)); + + BASIC_RAC_SUBCASE_2("trunc", (trunc(a)), (Vector2{0.0F, 1.0F}), + (trunc(b)), (b)); + + BASIC_RAC_SUBCASE_2("round", (round(a)), (Vector2{1.0F, 1.0F}), + (round(b)), (b)); + } + + TEST_CASE("sign") { + using draco::math::Vector2; + using draco::math::sign; + + RAC_CHECK_EQ((sign(Vector2{1.0F, -1.0F})), (Vector2{1.0F, -1.0F})); + } + + TEST_CASE("approx_eq") { + using draco::math::Vector2; + using draco::math::approx_eq; + using draco::math::CMP_EPSILON; + + static constexpr Vector2 v{1.0F, 2.0F}; + static constexpr Vector2 offset = Vector2::x_axis(CMP_EPSILON); + + BASIC_R_SUBCASE("distance < epsilon", (approx_eq(v, v + offset * 0.5F)), + (true)); + + BASIC_R_SUBCASE("distance == epsilon", (approx_eq(v, v + offset)), + (true)); + + BASIC_R_SUBCASE("distance > epsilon", (approx_eq(v, v + offset * 2.0F)), + (false)); + } } TEST_SUITE("vector3") { - TEST_CASE("constructors") { - using draco::math::Vector2; - using draco::math::Vector3; - using draco::math::Vector4; - - static constexpr Vector2 a{1.0f, 2.0f}; - static constexpr Vector4 b{3.0f, 4.0f, 5.0f, 6.0f}; - - BASIC_RAC_SUBCASE("float", - ( Vector3(1.0f) ), - ( Vector3{1.0f, 1.0f, 1.0f} ) - ); - - BASIC_RAC_SUBCASE("vec2, float", - ( Vector3(a, 7.0f) ), - ( Vector3{1.0f, 2.0f, 7.0f} ) - ); - - BASIC_RAC_SUBCASE("float, vec2", - ( Vector3(7.0f, a) ), - ( Vector3{7.0f, 1.0f, 2.0f} ) - ); - - BASIC_RAC_SUBCASE("vec4", - ( Vector3(b) ), - ( Vector3{3.0f, 4.0f, 5.0f} ) - ); - } - - TEST_CASE("access") { - using draco::math::Vector3; - - static constexpr Vector3 v(1.0f, 2.0f, 3.0f); - - RAC_CHECK_EQ(v[0], 1.0f); - RAC_CHECK_EQ(v[1], 2.0f); - RAC_CHECK_EQ(v[2], 3.0f); - } - - TEST_CASE("swizzle") { - using draco::math::Vector2; - using draco::math::Vector3; - using draco::math::Vector4; - - static constexpr Vector3 v{1.0f, 2.0f, 3.0f}; - - BASIC_RAC_SUBCASE("vec2", - ( v[1, 0] ), - ( Vector2{2.0f, 1.0f} ) - ); - - BASIC_RAC_SUBCASE("vec3", - ( v[1, 2, 0] ), - ( Vector3{2.0f, 3.0f, 1.0f} ) - ); - - BASIC_RAC_SUBCASE("vec4", - ( v[0, 2, 1, 0] ), - ( Vector4{1.0f, 3.0f, 2.0f, 1.0f} ) - ); - } - - TEST_CASE("swap") { - using draco::math::Vector3; - - Vector3 a{1.f, 2.f, 3.f}; - Vector3 b{3.f, 2.f, 1.f}; - - std::swap(a, b); - - CHECK_EQ(a, Vector3{3.f, 2.f, 1.f}); - CHECK_EQ(b, Vector3{1.f, 2.f, 3.f}); - } - - TEST_CASE("dot") { - using draco::math::Vector3; - using draco::math::dot; - - static constexpr Vector3 a{1.0f, 2.0f, 3.0f}; - static constexpr Vector3 b{4.0f, 5.0f, 6.0f}; - - BASIC_RAC_SUBCASE("basic", - ( dot(a, b) ), - ( 32.0f ) - ); - - BASIC_RAC_SUBCASE("self", - ( dot(a, a) ), - ( 14.0f ) - ); - - BASIC_RAC_SUBCASE("zero", - ( dot(a, Vector3()) ), - ( 0.0f ) - ); - } - - TEST_CASE("length") { - using draco::math::Vector3; - using draco::math::length; - using draco::math::length_sq; - - static constexpr Vector3 v{2.0f, 4.0f, 4.0f}; - - BASIC_R_SUBCASE("normal", - ( length(v) ), - ( 6.0f ) - ); - - BASIC_RAC_SUBCASE("squared", - ( length_sq(v) ), - ( 36.0f ) - ); - } - - TEST_CASE("distance") { - using draco::math::Vector3; - using draco::math::distance; - using draco::math::distance_sq; - - static constexpr Vector3 a{2.0f, 4.0f, 4.0f}; - static constexpr Vector3 b{-1.0f, -2.0f, -2.0f}; - - BASIC_R_SUBCASE("normal", - ( distance(a, b) ), - ( 9.0f ) - ); - - BASIC_RAC_SUBCASE("squared", - ( distance_sq(a, b) ), - ( 81.0f ) - ); - } - - TEST_CASE("normalize") { - using draco::math::Vector3; - using draco::math::length; - using draco::math::normalize; - using draco::math::normalize_fast; - - static constexpr Vector3 a{0.0f, 6.4f, 4.8f}; - static constexpr Vector3 b(1e-99); - - const Vector3 result = normalize(a); - const Vector3 result_fast = normalize_fast(a); - const Vector3 result_zero = normalize(b); - - CHECK_EQ(length(result), 1.0f); - CHECK_EQ(result, result_fast); - CHECK_EQ(result_zero, Vector3()); - } - - TEST_CASE("project") { - using draco::math::Vector3; - using draco::math::project; - - static constexpr Vector3 a{2.0f, 8.0f, 4.0f}; - static constexpr Vector3 b{1.0f, 1.0f, 2.0f}; - - RAC_CHECK_EQ( - ( project(a, b) ), - ( Vector3{3.0f, 3.0f, 6.0f} ) - ); - } - - TEST_CASE("reflect") { - using draco::math::Vector3; - using draco::math::reflect; - - static constexpr Vector3 a{1.0f, 2.0f, 3.0f}; - static constexpr Vector3 b{4.0f, 5.0f, 6.0f}; - - RAC_CHECK_EQ( - ( reflect(a, b) ), - ( Vector3{-255.0f, -318.0f, -381.0f} ) - ); - } - - TEST_CASE("angle") { - using draco::math::Vector3; - using draco::math::angle; - using draco::math::PI; - - static constexpr Vector3 a{2.0f, 4.0f, 4.0f}; - static constexpr Vector3 b{-4.0f, -8.0f, -8.0f}; - - R_CHECK_EQ( - ( angle(a, b) ), - ( PI ) - ); - } - - TEST_CASE("lerp") { - using draco::math::Vector3; - using draco::math::lerp; - - static constexpr Vector3 a{1.0f, 2.0f, 3.0f}; - static constexpr Vector3 b{4.0f, 5.0f, 6.0f}; - - BASIC_RAC_SUBCASE("weight = -1", - ( lerp(a, b, -1.0f) ), - ( Vector3{-2.0f, -1.0f, -0.0f} ) - ); - - BASIC_RAC_SUBCASE("weight = 0", - ( lerp(a, b, 0.0f) ), - ( a ) - ); - - BASIC_RAC_SUBCASE("weight = 0.5", - ( lerp(a, b, 0.5f) ), - ( Vector3{2.5f, 3.5f, 4.5f} ) - ); - - BASIC_RAC_SUBCASE("weight = 1", - ( lerp(a, b, 1.0f) ), - ( b ) - ); - - BASIC_RAC_SUBCASE("weight = 2", - ( lerp(a, b, 2.0f) ), - ( Vector3{7.0f, 8.0f, 9.0f} ) - ); - } - - TEST_CASE("min") { - using draco::math::Vector3; - using draco::math::min; - - static constexpr Vector3 a{5.0f, 8.0f, 3.0f}; - static constexpr Vector3 b{1.0f, 6.0f, 7.0f}; - - BASIC_RAC_SUBCASE("vector", - ( min(a, b) ), - ( Vector3{1.0f, 6.0f, 3.0f} ) - ); - - static constexpr Vector3 expected{4.0f, 4.0f, 3.0f}; - - BASIC_RAC_SUBCASE_2("float", - ( min(a, 4.0f) ), - ( expected ), - ( min(4.0f, a) ), - ( expected ) - ); - } - - TEST_CASE("min_length") { - using draco::math::Vector3; - using draco::math::length; - using draco::math::min_length; - - static constexpr Vector3 a{2.0f, 4.0f, 4.0f}; // len: 6 - static constexpr Vector3 b{5.0f, 10.0f, 10.0f}; // len: 15 - - BASIC_RAC_SUBCASE("vector", - ( min_length(a, b) ), - ( a ) - ); - - SUBCASE("float") { - static constexpr f32 smaller_length = 1.0f; - static constexpr f32 larger_length = 10.0f; - - const Vector3 result_smaller = min_length(a, smaller_length); - const Vector3 result_swapped = min_length(smaller_length, a); - const Vector3 result_larger = min_length(larger_length, a); - - CHECK_EQ(length(result_smaller), smaller_length); - CHECK_EQ(result_smaller, result_swapped); - CHECK_EQ(result_larger, a); - } - } - - TEST_CASE("max") { - using draco::math::Vector3; - using draco::math::max; - - static constexpr Vector3 a{5.0f, 8.0f, 3.0f}; - static constexpr Vector3 b{1.0f, 6.0f, 7.0f}; - - BASIC_RAC_SUBCASE("vector", - ( max(a, b) ), - ( Vector3{5.0f, 8.0f, 7.0f} ) - ); - - static constexpr Vector3 expected{5.0f, 8.0f, 4.0f}; - - BASIC_RAC_SUBCASE_2("float", - ( max(a, 4.0f) ), - ( expected ), - ( max(4.0f, a) ), - ( expected ) - ); - } - - TEST_CASE("max_length") { - using draco::math::Vector3; - using draco::math::length; - using draco::math::max_length; - - static constexpr Vector3 a{2.0f, 4.0f, 4.0f}; // len: 6 - static constexpr Vector3 b{5.0f, 10.0f, 10.0f}; // len: 15 - - BASIC_RAC_SUBCASE("vector", - ( max_length(a, b) ), - ( b ) - ); - - SUBCASE("float") { - static constexpr f32 smaller_length = 1.0f; - static constexpr f32 larger_length = 10.0f; - - const Vector3 result_smaller = max_length(a, smaller_length); - const Vector3 result_swapped = max_length(larger_length, a); - const Vector3 result_larger = max_length(larger_length, a); - - CHECK_EQ(length(result_larger), larger_length); - CHECK_EQ(result_larger, result_swapped); - CHECK_EQ(result_smaller, a); - } - } - - TEST_CASE("clamp") { - using draco::math::Vector3; - using draco::math::clamp; - - static constexpr Vector3 a{5.0f, 8.0f, 3.0f}; - static constexpr Vector3 b{1.0f, 6.0f, 7.0f}; - static constexpr Vector3 c{3.0f, 9.0f, 8.0f}; - - BASIC_RAC_SUBCASE("vector", - ( clamp(a, b, c) ), - ( Vector3{3.0f, 8.0f, 7.0f} ) - ); - - BASIC_RAC_SUBCASE("float", - ( clamp(a, 4.0f, 5.0f) ), - ( Vector3{5.0f, 5.0f, 4.0f} ) - ); - } - - TEST_CASE("clamp_length") { - using draco::math::Vector3; - using draco::math::length; - using draco::math::clamp_length; - - static constexpr Vector3 v{2.0f, 4.0f, 4.0f}; // len: 6 - - BASIC_R_SUBCASE("length < min", - ( clamp_length(v, 12.0f, 14.0f) ), - ( Vector3{4.0f, 8.0f, 8.0f} ) - ); - - BASIC_R_SUBCASE("length == min", - ( clamp_length(v, 6.0f, 9.0f) ), - ( v ) - ); - - BASIC_R_SUBCASE("length == max", - ( clamp_length(v, 3.0f, 6.0f) ), - ( v ) - ); - - BASIC_R_SUBCASE("length > max", - ( clamp_length(v, 1.0f, 3.0f) ), - ( Vector3{1.0f, 2.0f, 2.0f} ) - ); - } - - TEST_CASE("abs") { - using draco::math::Vector3; - using draco::math::abs; - - RAC_CHECK_EQ( - ( abs(Vector3{1.0f, -2.0f, 0.0f}) ), - ( Vector3{1.0f, 2.0f, 0.0f} ) - ); - } - - TEST_CASE("rounding") { - using draco::math::Vector3; - using draco::math::floor; - using draco::math::ceil; - using draco::math::trunc; - using draco::math::round; - - static constexpr Vector3 a{0.5f, -0.5f, 1.4f}; - static constexpr Vector3 b{-1.0f, 0.0f, 1.0f}; - - BASIC_RAC_SUBCASE_2("floor", - ( floor(a) ), - ( Vector3{0.0f, -1.0f, 1.0f} ), - ( floor(b) ), - ( b ) - ); - - BASIC_RAC_SUBCASE_2("ceil", - ( ceil(a) ), - ( Vector3{1.0f, 0.0f, 2.0f} ), - ( ceil(b) ), - ( b ) - ); - - BASIC_RAC_SUBCASE_2("trunc", - ( trunc(a) ), - ( Vector3{0.0f, 0.0f, 1.0f} ), - ( trunc(b) ), - ( b ) - ); - - BASIC_RAC_SUBCASE_2("round", - ( round(a) ), - ( Vector3{1.0f, -1.0f, 1.0f} ), - ( round(b) ), - ( b ) - ); - } - - TEST_CASE("sign") { - using draco::math::Vector3; - using draco::math::sign; - - RAC_CHECK_EQ( - ( sign(Vector3{1.0f, -1.0f, 0.0f}) ), - ( Vector3{1.0f, -1.0f, 0.0f} ) - ); - } - - TEST_CASE("approx_eq") { - using draco::math::Vector3; - using draco::math::approx_eq; - using draco::math::CMP_EPSILON; - - static constexpr Vector3 v{1.0f, 2.0f, 3.0f}; - static constexpr Vector3 offset = Vector3::x_axis(CMP_EPSILON); - - BASIC_R_SUBCASE("distance < epsilon", - ( approx_eq(v, v + offset * 0.5f) ), - ( true ) - ); - - BASIC_R_SUBCASE("distance == epsilon", - ( approx_eq(v, v + offset) ), - ( true ) - ); - - BASIC_R_SUBCASE("distance > epsilon", - ( approx_eq(v, v + offset * 2.0f) ), - ( false ) - ); - } - - TEST_CASE("cross") { - using draco::math::Vector3; - using draco::math::cross; - - RAC_CHECK_EQ( - ( cross(Vector3::x_axis(), Vector3::y_axis()) ), - ( Vector3::z_axis() ) - ) - } + TEST_CASE("constructors") { + using draco::math::Vector2; + using draco::math::Vector3; + using draco::math::Vector4; + + static constexpr Vector2 a{1.0F, 2.0F}; + static constexpr Vector4 b{3.0F, 4.0F, 5.0F, 6.0F}; + + BASIC_RAC_SUBCASE("float", (Vector3(1.0F)), + (Vector3{1.0F, 1.0F, 1.0F})); + + BASIC_RAC_SUBCASE("vec2, float", (Vector3(a, 7.0F)), + (Vector3{1.0F, 2.0F, 7.0F})); + + BASIC_RAC_SUBCASE("float, vec2", (Vector3(7.0F, a)), + (Vector3{7.0F, 1.0F, 2.0F})); + + BASIC_RAC_SUBCASE("vec4", (Vector3(b)), (Vector3{3.0F, 4.0F, 5.0F})); + } + + TEST_CASE("access") { + using draco::math::Vector3; + + static constexpr Vector3 v(1.0F, 2.0F, 3.0F); + + RAC_CHECK_EQ(v[0], 1.0F); + RAC_CHECK_EQ(v[1], 2.0F); + RAC_CHECK_EQ(v[2], 3.0F); + } + + TEST_CASE("swizzle") { + using draco::math::Vector2; + using draco::math::Vector3; + using draco::math::Vector4; + + static constexpr Vector3 v{1.0F, 2.0F, 3.0F}; + + BASIC_RAC_SUBCASE("vec2", (v[1, 0]), (Vector2{2.0F, 1.0F})); + + BASIC_RAC_SUBCASE("vec3", (v[1, 2, 0]), (Vector3{2.0F, 3.0F, 1.0F})); + + BASIC_RAC_SUBCASE("vec4", (v[0, 2, 1, 0]), + (Vector4{1.0F, 3.0F, 2.0F, 1.0F})); + } + + TEST_CASE("swap") { + using draco::math::Vector3; + + Vector3 a{1.F, 2.F, 3.F}; + Vector3 b{3.F, 2.F, 1.F}; + + std::swap(a, b); + + CHECK_EQ(a, Vector3{3.F, 2.F, 1.F}); + CHECK_EQ(b, Vector3{1.F, 2.F, 3.F}); + } + + TEST_CASE("dot") { + using draco::math::Vector3; + using draco::math::dot; + + static constexpr Vector3 a{1.0F, 2.0F, 3.0F}; + static constexpr Vector3 b{4.0F, 5.0F, 6.0F}; + + BASIC_RAC_SUBCASE("basic", (dot(a, b)), (32.0F)); + + BASIC_RAC_SUBCASE("self", (dot(a, a)), (14.0F)); + + BASIC_RAC_SUBCASE("zero", (dot(a, Vector3())), (0.0F)); + } + + TEST_CASE("length") { + using draco::math::Vector3; + using draco::math::length; + using draco::math::length_sq; + + static constexpr Vector3 v{2.0F, 4.0F, 4.0F}; + + BASIC_R_SUBCASE("normal", (length(v)), (6.0F)); + + BASIC_RAC_SUBCASE("squared", (length_sq(v)), (36.0F)); + } + + TEST_CASE("distance") { + using draco::math::Vector3; + using draco::math::distance; + using draco::math::distance_sq; + + static constexpr Vector3 a{2.0F, 4.0F, 4.0F}; + static constexpr Vector3 b{-1.0F, -2.0F, -2.0F}; + + BASIC_R_SUBCASE("normal", (distance(a, b)), (9.0F)); + + BASIC_RAC_SUBCASE("squared", (distance_sq(a, b)), (81.0F)); + } + + TEST_CASE("normalize") { + using draco::math::Vector3; + using draco::math::length; + using draco::math::normalize; + using draco::math::normalize_fast; + + static constexpr Vector3 a{0.0F, 6.4F, 4.8F}; + static constexpr Vector3 b(1e-99); + + Vector3 const result = normalize(a); + Vector3 const result_fast = normalize_fast(a); + Vector3 const result_zero = normalize(b); + + CHECK_EQ(length(result), 1.0F); + CHECK_EQ(result, result_fast); + CHECK_EQ(result_zero, Vector3()); + } + + TEST_CASE("project") { + using draco::math::Vector3; + using draco::math::project; + + static constexpr Vector3 a{2.0F, 8.0F, 4.0F}; + static constexpr Vector3 b{1.0F, 1.0F, 2.0F}; + + RAC_CHECK_EQ((project(a, b)), (Vector3{3.0F, 3.0F, 6.0F})); + } + + TEST_CASE("reflect") { + using draco::math::Vector3; + using draco::math::reflect; + + static constexpr Vector3 a{1.0F, 2.0F, 3.0F}; + static constexpr Vector3 b{4.0F, 5.0F, 6.0F}; + + RAC_CHECK_EQ((reflect(a, b)), (Vector3{-255.0F, -318.0F, -381.0F})); + } + + TEST_CASE("angle") { + using draco::math::Vector3; + using draco::math::angle; + using draco::math::PI; + + static constexpr Vector3 a{2.0F, 4.0F, 4.0F}; + static constexpr Vector3 b{-4.0F, -8.0F, -8.0F}; + + R_CHECK_EQ((angle(a, b)), (PI)); + } + + TEST_CASE("lerp") { + using draco::math::Vector3; + using draco::math::lerp; + + static constexpr Vector3 a{1.0F, 2.0F, 3.0F}; + static constexpr Vector3 b{4.0F, 5.0F, 6.0F}; + + BASIC_RAC_SUBCASE("weight = -1", (lerp(a, b, -1.0F)), + (Vector3{-2.0F, -1.0F, -0.0F})); + + BASIC_RAC_SUBCASE("weight = 0", (lerp(a, b, 0.0F)), (a)); + + BASIC_RAC_SUBCASE("weight = 0.5", (lerp(a, b, 0.5F)), + (Vector3{2.5F, 3.5F, 4.5F})); + + BASIC_RAC_SUBCASE("weight = 1", (lerp(a, b, 1.0F)), (b)); + + BASIC_RAC_SUBCASE("weight = 2", (lerp(a, b, 2.0F)), + (Vector3{7.0F, 8.0F, 9.0F})); + } + + TEST_CASE("min") { + using draco::math::Vector3; + using draco::math::min; + + static constexpr Vector3 a{5.0F, 8.0F, 3.0F}; + static constexpr Vector3 b{1.0F, 6.0F, 7.0F}; + + BASIC_RAC_SUBCASE("vector", (min(a, b)), (Vector3{1.0F, 6.0F, 3.0F})); + + static constexpr Vector3 expected{4.0F, 4.0F, 3.0F}; + + BASIC_RAC_SUBCASE_2("float", (min(a, 4.0F)), (expected), (min(4.0F, a)), + (expected)); + } + + TEST_CASE("min_length") { + using draco::math::Vector3; + using draco::math::length; + using draco::math::min_length; + + static constexpr Vector3 a{2.0F, 4.0F, 4.0F}; // len: 6 + static constexpr Vector3 b{5.0F, 10.0F, 10.0F}; // len: 15 + + BASIC_RAC_SUBCASE("vector", (min_length(a, b)), (a)); + + SUBCASE("float") { + static constexpr f32 smaller_length = 1.0F; + static constexpr f32 larger_length = 10.0F; + + Vector3 const result_smaller = min_length(a, smaller_length); + Vector3 const result_swapped = min_length(smaller_length, a); + Vector3 const result_larger = min_length(larger_length, a); + + CHECK_EQ(length(result_smaller), smaller_length); + CHECK_EQ(result_smaller, result_swapped); + CHECK_EQ(result_larger, a); + } + } + + TEST_CASE("max") { + using draco::math::Vector3; + using draco::math::max; + + static constexpr Vector3 a{5.0F, 8.0F, 3.0F}; + static constexpr Vector3 b{1.0F, 6.0F, 7.0F}; + + BASIC_RAC_SUBCASE("vector", (max(a, b)), (Vector3{5.0F, 8.0F, 7.0F})); + + static constexpr Vector3 expected{5.0F, 8.0F, 4.0F}; + + BASIC_RAC_SUBCASE_2("float", (max(a, 4.0F)), (expected), (max(4.0F, a)), + (expected)); + } + + TEST_CASE("max_length") { + using draco::math::Vector3; + using draco::math::length; + using draco::math::max_length; + + static constexpr Vector3 a{2.0F, 4.0F, 4.0F}; // len: 6 + static constexpr Vector3 b{5.0F, 10.0F, 10.0F}; // len: 15 + + BASIC_RAC_SUBCASE("vector", (max_length(a, b)), (b)); + + SUBCASE("float") { + static constexpr f32 smaller_length = 1.0F; + static constexpr f32 larger_length = 10.0F; + + Vector3 const result_smaller = max_length(a, smaller_length); + Vector3 const result_swapped = max_length(larger_length, a); + Vector3 const result_larger = max_length(larger_length, a); + + CHECK_EQ(length(result_larger), larger_length); + CHECK_EQ(result_larger, result_swapped); + CHECK_EQ(result_smaller, a); + } + } + + TEST_CASE("clamp") { + using draco::math::Vector3; + using draco::math::clamp; + + static constexpr Vector3 a{5.0F, 8.0F, 3.0F}; + static constexpr Vector3 b{1.0F, 6.0F, 7.0F}; + static constexpr Vector3 c{3.0F, 9.0F, 8.0F}; + + BASIC_RAC_SUBCASE("vector", (clamp(a, b, c)), + (Vector3{3.0F, 8.0F, 7.0F})); + + BASIC_RAC_SUBCASE("float", (clamp(a, 4.0F, 5.0F)), + (Vector3{5.0F, 5.0F, 4.0F})); + } + + TEST_CASE("clamp_length") { + using draco::math::Vector3; + using draco::math::length; + using draco::math::clamp_length; + + static constexpr Vector3 v{2.0F, 4.0F, 4.0F}; // len: 6 + + BASIC_R_SUBCASE("length < min", (clamp_length(v, 12.0F, 14.0F)), + (Vector3{4.0F, 8.0F, 8.0F})); + + BASIC_R_SUBCASE("length == min", (clamp_length(v, 6.0F, 9.0F)), (v)); + + BASIC_R_SUBCASE("length == max", (clamp_length(v, 3.0F, 6.0F)), (v)); + + BASIC_R_SUBCASE("length > max", (clamp_length(v, 1.0F, 3.0F)), + (Vector3{1.0F, 2.0F, 2.0F})); + } + + TEST_CASE("abs") { + using draco::math::Vector3; + using draco::math::abs; + + RAC_CHECK_EQ((abs(Vector3{1.0F, -2.0F, 0.0F})), + (Vector3{1.0F, 2.0F, 0.0F})); + } + + TEST_CASE("rounding") { + using draco::math::Vector3; + using draco::math::floor; + using draco::math::ceil; + using draco::math::trunc; + using draco::math::round; + + static constexpr Vector3 a{0.5F, -0.5F, 1.4F}; + static constexpr Vector3 b{-1.0F, 0.0F, 1.0F}; + + BASIC_RAC_SUBCASE_2("floor", (floor(a)), (Vector3{0.0F, -1.0F, 1.0F}), + (floor(b)), (b)); + + BASIC_RAC_SUBCASE_2("ceil", (ceil(a)), (Vector3{1.0F, 0.0F, 2.0F}), + (ceil(b)), (b)); + + BASIC_RAC_SUBCASE_2("trunc", (trunc(a)), (Vector3{0.0F, 0.0F, 1.0F}), + (trunc(b)), (b)); + + BASIC_RAC_SUBCASE_2("round", (round(a)), (Vector3{1.0F, -1.0F, 1.0F}), + (round(b)), (b)); + } + + TEST_CASE("sign") { + using draco::math::Vector3; + using draco::math::sign; + + RAC_CHECK_EQ((sign(Vector3{1.0F, -1.0F, 0.0F})), + (Vector3{1.0F, -1.0F, 0.0F})); + } + + TEST_CASE("approx_eq") { + using draco::math::Vector3; + using draco::math::approx_eq; + using draco::math::CMP_EPSILON; + + static constexpr Vector3 v{1.0F, 2.0F, 3.0F}; + static constexpr Vector3 offset = Vector3::x_axis(CMP_EPSILON); + + BASIC_R_SUBCASE("distance < epsilon", (approx_eq(v, v + offset * 0.5F)), + (true)); + + BASIC_R_SUBCASE("distance == epsilon", (approx_eq(v, v + offset)), + (true)); + + BASIC_R_SUBCASE("distance > epsilon", (approx_eq(v, v + offset * 2.0F)), + (false)); + } + + TEST_CASE("cross") { + using draco::math::Vector3; + using draco::math::cross; + + RAC_CHECK_EQ((cross(Vector3::x_axis(), Vector3::y_axis())), + (Vector3::z_axis())) + } } TEST_SUITE("vector4") { - TEST_CASE("constructors") { - using draco::math::Vector2; - using draco::math::Vector3; - using draco::math::Vector4; - - static constexpr Vector2 a{1.0f, 2.0f}; - static constexpr Vector3 b{3.0f, 4.0f, 5.0f}; - - BASIC_RAC_SUBCASE("float", - ( Vector4(1.0f) ), - ( Vector4{1.0f, 1.0f, 1.0f, 1.0f} ) - ); - - BASIC_RAC_SUBCASE("vec2, float, float", - ( Vector4(a, 6.0f, 7.0f) ), - ( Vector4{1.0f, 2.0f, 6.0f, 7.0f} ) - ); - - BASIC_RAC_SUBCASE("float, vec2, float", - ( Vector4(6.0f, a, 7.0f) ), - ( Vector4{6.0f, 1.0f, 2.0f, 7.0f} ) - ); - - BASIC_RAC_SUBCASE("float, float, vec2", - ( Vector4(6.0f, 7.0f, a) ), - ( Vector4{6.0f, 7.0f, 1.0f, 2.0f} ) - ); - - BASIC_RAC_SUBCASE("vec2, vec2", - ( Vector4(a, a) ), - ( Vector4{1.0f, 2.0f, 1.0f, 2.0f} ) - ); - - BASIC_RAC_SUBCASE("vec3, float", - ( Vector4(b, 6.0f) ), - ( Vector4{3.0f, 4.0f, 5.0f, 6.0f} ) - ); - - BASIC_RAC_SUBCASE("float, vec3", - ( Vector4(6.0f, b) ), - ( Vector4{6.0f, 3.0f, 4.0f, 5.0f} ) - ); - - BASIC_RAC_SUBCASE("vec2", - ( Vector4(a) ), - ( Vector4{1.0f, 2.0f, 0.0f, 0.0f} ) - ); - - BASIC_RAC_SUBCASE("vec3", - ( Vector4(b) ), - ( Vector4{3.0f, 4.0f, 5.0f, 0.0f} ) - ); - } - - TEST_CASE("access") { - using draco::math::Vector4; - - static constexpr Vector4 v(1.0f, 2.0f, 3.0f, 4.0f); - - RAC_CHECK_EQ(v[0], 1.0f); - RAC_CHECK_EQ(v[1], 2.0f); - RAC_CHECK_EQ(v[2], 3.0f); - RAC_CHECK_EQ(v[3], 4.0f); - } - - TEST_CASE("swizzle") { - using draco::math::Vector2; - using draco::math::Vector3; - using draco::math::Vector4; - - static constexpr Vector4 v{1.0f, 2.0f, 3.0f, 4.0f}; - - BASIC_RAC_SUBCASE("vec2", - ( v[1, 0] ), - ( Vector2{2.0f, 1.0f} ) - ); - - BASIC_RAC_SUBCASE("vec3", - ( v[1, 2, 0] ), - ( Vector3{2.0f, 3.0f, 1.0f} ) - ); - - BASIC_RAC_SUBCASE("vec4", - ( v[0, 2, 1, 3] ), - ( Vector4{1.0f, 3.0f, 2.0f, 4.0f} ) - ); - } - - TEST_CASE("swap") { - using draco::math::Vector4; - - Vector4 a{1.f, 2.f, 3.f, 4.f}; - Vector4 b{4.f, 3.f, 2.f, 1.f}; - - std::swap(a, b); - - CHECK_EQ(a, Vector4{4.f, 3.f, 2.f, 1.f}); - CHECK_EQ(b, Vector4{1.f, 2.f, 3.f, 4.f}); - } - - TEST_CASE("dot") { - using draco::math::Vector4; - using draco::math::dot; - - static constexpr Vector4 a{1.0f, 2.0f, 3.0f, 4.0f}; - static constexpr Vector4 b{5.0f, 6.0f, 7.0f, 8.0f}; - - BASIC_RAC_SUBCASE("basic", - ( dot(a, b) ), - ( 70.0f ) - ); - - BASIC_RAC_SUBCASE("self", - ( dot(a, a) ), - ( 30.0f ) - ); - - BASIC_RAC_SUBCASE("zero", - ( dot(a, Vector4()) ), - ( 0.0f ) - ); - } - - TEST_CASE("length") { - using draco::math::Vector4; - using draco::math::length; - using draco::math::length_sq; - - static constexpr Vector4 v{1.0f, 2.0f, 2.0f, 4.0f}; - - BASIC_R_SUBCASE("normal", - ( length(v) ), - ( 5.0f ) - ); - - BASIC_RAC_SUBCASE("squared", - ( length_sq(v) ), - ( 25.0f ) - ); - } - - TEST_CASE("distance") { - using draco::math::Vector4; - using draco::math::distance; - using draco::math::distance_sq; - - static constexpr Vector4 a{1.0f, 2.0f, 2.0f, 4.0f}; - static constexpr Vector4 b{3.0f, 6.0f, 7.0f, 10.0f}; - - BASIC_R_SUBCASE("normal", - ( distance(a, b) ), - ( 9.0f ) - ); - - BASIC_RAC_SUBCASE("squared", - ( distance_sq(a, b) ), - ( 81.0f ) - ); - } - - TEST_CASE("normalize") { - using draco::math::Vector4; - using draco::math::length; - using draco::math::normalize; - using draco::math::normalize_fast; - - static constexpr Vector4 a{2.0f, 4.0f, 5.0f, 6.0f}; - static constexpr Vector4 b(1e-99); - - const Vector4 result = normalize(a); - const Vector4 result_fast = normalize_fast(a); - const Vector4 result_zero = normalize(b); - - CHECK_EQ(length(result), 1.0f); - CHECK_EQ(result, result_fast); - CHECK_EQ(result_zero, Vector4()); - } - - TEST_CASE("project") { - using draco::math::Vector4; - using draco::math::project; - - static constexpr Vector4 a{8.0f, 2.0f, 6.0f, 8.0f}; - static constexpr Vector4 b{12.0f, 14.0f, 8.0f, 6.0f}; - - RAC_CHECK_EQ( - ( project(a, b) ), - ( Vector4{6.0f, 7.0f, 4.0f, 3.0f} ) - ); - } - - TEST_CASE("reflect") { - using draco::math::Vector4; - using draco::math::reflect; - - static constexpr Vector4 a{1.0f, 2.0f, 3.0f, 4.0f}; - static constexpr Vector4 b{5.0f, 6.0f, 7.0f, 8.0f}; - - RAC_CHECK_EQ( - ( reflect(a, b) ), - ( Vector4{-699.0f, -838.0f, -977.0f, -1116.0f} ) - ); - } - - TEST_CASE("angle") { - using draco::math::Vector4; - using draco::math::angle; - using draco::math::PI2; - - static constexpr Vector4 a{1.0f, 5.0f, 1.0f, 3.0f}; - static constexpr Vector4 b{2.0f, -6.0f, -2.0f, 10.0f}; - - R_CHECK_EQ( - ( angle(a, b) ), - ( PI2 ) - ); - } - - TEST_CASE("lerp") { - using draco::math::Vector4; - using draco::math::lerp; - - static constexpr Vector4 a{1.0f, 2.0f, 3.0f, 4.0f}; - static constexpr Vector4 b{5.0f, 6.0f, 7.0f, 8.0f}; - - BASIC_RAC_SUBCASE("weight = -1", - ( lerp(a, b, -1.0f) ), - ( Vector4{-3.0f, -2.0f, -1.0f, 0.0f} ) - ); - - BASIC_RAC_SUBCASE("weight = 0", - ( lerp(a, b, 0.0f) ), - ( a ) - ); - - BASIC_RAC_SUBCASE("weight = 0.5", - ( lerp(a, b, 0.5f) ), - ( Vector4{3.0f, 4.0f, 5.0f, 6.0f} ) - ); - - BASIC_RAC_SUBCASE("weight = 1", - ( lerp(a, b, 1.0f) ), - ( b ) - ); - - BASIC_RAC_SUBCASE("weight = 2", - ( lerp(a, b, 2.0f) ), - ( Vector4{9.0f, 10.0f, 11.0f, 12.0f} ) - ); - } - - TEST_CASE("min") { - using draco::math::Vector4; - using draco::math::min; - - static constexpr Vector4 a{5.0f, 8.0f, 3.0f, 4.0f}; - static constexpr Vector4 b{1.0f, 6.0f, 7.0f, 2.0f}; - - BASIC_RAC_SUBCASE("vector", - ( min(a, b) ), - ( Vector4{1.0f, 6.0f, 3.0f, 2.0f} ) - ); - - static constexpr Vector4 expected{4.0f, 4.0f, 3.0f, 4.0f}; - - BASIC_RAC_SUBCASE_2("float", - ( min(a, 4.0f) ), - ( expected ), - ( min(4.0f, a) ), - ( expected ) - ); - } - - TEST_CASE("min_length") { - using draco::math::Vector4; - using draco::math::length; - using draco::math::min_length; - - static constexpr Vector4 a{1.0f, 2.0f, 2.0f, 4.0f}; // len: 5 - static constexpr Vector4 b{1.0f, -3.0f, -1.0f, 5.0f}; // len: 6 - - BASIC_RAC_SUBCASE("vector", - ( min_length(a, b) ), - ( a ) - ); - - SUBCASE("float") { - static constexpr f32 smaller_length = 1.0f; - static constexpr f32 larger_length = 10.0f; - - const Vector4 result_smaller = min_length(a, smaller_length); - const Vector4 result_swapped = min_length(smaller_length, a); - const Vector4 result_larger = min_length(larger_length, a); - - CHECK_EQ(length(result_smaller), smaller_length); - CHECK_EQ(result_smaller, result_swapped); - CHECK_EQ(result_larger, a); - } - } - - TEST_CASE("max") { - using draco::math::Vector4; - using draco::math::max; - - static constexpr Vector4 a{5.0f, 8.0f, 3.0f, 4.0f}; - static constexpr Vector4 b{1.0f, 6.0f, 7.0f, 2.0f}; - - BASIC_RAC_SUBCASE("vector", - ( max(a, b) ), - ( Vector4{5.0f, 8.0f, 7.0f, 4.0f} ) - ); - - static constexpr Vector4 expected{5.0f, 8.0f, 4.0f, 4.0f}; - - BASIC_RAC_SUBCASE_2("float", - ( max(a, 4.0f) ), - ( expected ), - ( max(4.0f, a) ), - ( expected ) - ); - } - - TEST_CASE("max_length") { - using draco::math::Vector4; - using draco::math::length; - using draco::math::max_length; - - static constexpr Vector4 a{1.0f, 2.0f, 2.0f, 4.0f}; // len: 5 - static constexpr Vector4 b{1.0f, -3.0f, -1.0f, 5.0f}; // len: 6 - - BASIC_RAC_SUBCASE("vector", - ( max_length(a, b) ), - ( b ) - ); - - SUBCASE("float") { - static constexpr f32 smaller_length = 1.0f; - static constexpr f32 larger_length = 10.0f; - - const Vector4 result_smaller = max_length(a, smaller_length); - const Vector4 result_swapped = max_length(larger_length, a); - const Vector4 result_larger = max_length(larger_length, a); - - CHECK_EQ(length(result_larger), larger_length); - CHECK_EQ(result_larger, result_swapped); - CHECK_EQ(result_smaller, a); - } - } - - TEST_CASE("clamp") { - using draco::math::Vector4; - using draco::math::clamp; - - static constexpr Vector4 a{5.0f, 8.0f, 3.0f, 4.0f}; - static constexpr Vector4 b{1.0f, 6.0f, 7.0f, 2.0f}; - static constexpr Vector4 c{3.0f, 9.0f, 8.0f, 4.0f}; - - BASIC_RAC_SUBCASE("vector", - ( clamp(a, b, c) ), - ( Vector4{3.0f, 8.0f, 7.0f, 4.0f} ) - ); - - BASIC_RAC_SUBCASE("float", - ( clamp(a, 4.0f, 5.0f) ), - ( Vector4{5.0f, 5.0f, 4.0f, 4.0f} ) - ); - } - - TEST_CASE("clamp_length") { - using draco::math::Vector4; - using draco::math::length; - using draco::math::clamp_length; - - static constexpr Vector4 v{1.0f, -3.0f, -1.0f, 5.0f}; // len: 6 - - BASIC_R_SUBCASE("length < min", - ( clamp_length(v, 12.0f, 14.0f) ), - ( Vector4{2.0f, -6.0f, -2.0f, 10.0f} ) - ); - - BASIC_R_SUBCASE("length == min", - ( clamp_length(v, 6.0f, 9.0f) ), - ( v ) - ); - - BASIC_R_SUBCASE("length == max", - ( clamp_length(v, 3.0f, 6.0f) ), - ( v ) - ); - - BASIC_R_SUBCASE("length > max", - ( clamp_length(v, 1.0f, 3.0f) ), - ( Vector4{0.5f, -1.5f, -0.5f, 2.5f} ) - ); - } - - TEST_CASE("abs") { - using draco::math::Vector4; - using draco::math::abs; - - RAC_CHECK_EQ( - ( abs(Vector4{1.0f, -2.0f, -3.0f, 0.0f}) ), - ( Vector4{1.0f, 2.0f, 3.0f, 0.0f} ) - ); - } - - TEST_CASE("rounding") { - using draco::math::Vector4; - using draco::math::floor; - using draco::math::ceil; - using draco::math::trunc; - using draco::math::round; - - static constexpr Vector4 a{0.5f, -0.5f, 1.4f, 1.6f}; - static constexpr Vector4 b{-1.0f, 0.0f, 1.0f, 2.0f}; - - BASIC_RAC_SUBCASE_2("floor", - ( floor(a) ), - ( Vector4{0.0f, -1.0f, 1.0f, 1.0f} ), - ( floor(b) ), - ( b ) - ); - - BASIC_RAC_SUBCASE_2("ceil", - ( ceil(a) ), - ( Vector4{1.0f, 0.0f, 2.0f, 2.0f} ), - ( ceil(b) ), - ( b ) - ); - - BASIC_RAC_SUBCASE_2("trunc", - ( trunc(a) ), - ( Vector4{0.0f, 0.0f, 1.0f, 1.0f} ), - ( trunc(b) ), - ( b ) - ); - - BASIC_RAC_SUBCASE_2("round", - ( round(a) ), - ( Vector4{1.0f, -1.0f, 1.0f, 2.0f} ), - ( round(b) ), - ( b ) - ); - } - - TEST_CASE("sign") { - using draco::math::Vector4; - using draco::math::sign; - - RAC_CHECK_EQ( - ( sign(Vector4{1.0f, -1.0f, 0.0f, -0.0f}) ), - ( Vector4{1.0f, -1.0f, 0.0f, 0.0f} ) - ); - } - - TEST_CASE("approx_eq") { - using draco::math::Vector4; - using draco::math::approx_eq; - using draco::math::CMP_EPSILON; - - static constexpr Vector4 v{1.0f, 2.0f, 3.0f, 4.0f}; - static constexpr Vector4 offset = Vector4::x_axis(CMP_EPSILON); - - BASIC_R_SUBCASE("distance < epsilon", - ( approx_eq(v, v + offset * 0.5f) ), - ( true ) - ); - - BASIC_R_SUBCASE("distance == epsilon", - ( approx_eq(v, v + offset) ), - ( true ) - ); - - BASIC_R_SUBCASE("distance > epsilon", - ( approx_eq(v, v + offset * 2.0f) ), - ( false ) - ); - } -} \ No newline at end of file + TEST_CASE("constructors") { + using draco::math::Vector2; + using draco::math::Vector3; + using draco::math::Vector4; + + static constexpr Vector2 a{1.0F, 2.0F}; + static constexpr Vector3 b{3.0F, 4.0F, 5.0F}; + + BASIC_RAC_SUBCASE("float", (Vector4(1.0F)), + (Vector4{1.0F, 1.0F, 1.0F, 1.0F})); + + BASIC_RAC_SUBCASE("vec2, float, float", (Vector4(a, 6.0F, 7.0F)), + (Vector4{1.0F, 2.0F, 6.0F, 7.0F})); + + BASIC_RAC_SUBCASE("float, vec2, float", (Vector4(6.0F, a, 7.0F)), + (Vector4{6.0F, 1.0F, 2.0F, 7.0F})); + + BASIC_RAC_SUBCASE("float, float, vec2", (Vector4(6.0F, 7.0F, a)), + (Vector4{6.0F, 7.0F, 1.0F, 2.0F})); + + BASIC_RAC_SUBCASE("vec2, vec2", (Vector4(a, a)), + (Vector4{1.0F, 2.0F, 1.0F, 2.0F})); + + BASIC_RAC_SUBCASE("vec3, float", (Vector4(b, 6.0F)), + (Vector4{3.0F, 4.0F, 5.0F, 6.0F})); + + BASIC_RAC_SUBCASE("float, vec3", (Vector4(6.0F, b)), + (Vector4{6.0F, 3.0F, 4.0F, 5.0F})); + + BASIC_RAC_SUBCASE("vec2", (Vector4(a)), + (Vector4{1.0F, 2.0F, 0.0F, 0.0F})); + + BASIC_RAC_SUBCASE("vec3", (Vector4(b)), + (Vector4{3.0F, 4.0F, 5.0F, 0.0F})); + } + + TEST_CASE("access") { + using draco::math::Vector4; + + static constexpr Vector4 v(1.0F, 2.0F, 3.0F, 4.0F); + + RAC_CHECK_EQ(v[0], 1.0F); + RAC_CHECK_EQ(v[1], 2.0F); + RAC_CHECK_EQ(v[2], 3.0F); + RAC_CHECK_EQ(v[3], 4.0F); + } + + TEST_CASE("swizzle") { + using draco::math::Vector2; + using draco::math::Vector3; + using draco::math::Vector4; + + static constexpr Vector4 v{1.0F, 2.0F, 3.0F, 4.0F}; + + BASIC_RAC_SUBCASE("vec2", (v[1, 0]), (Vector2{2.0F, 1.0F})); + + BASIC_RAC_SUBCASE("vec3", (v[1, 2, 0]), (Vector3{2.0F, 3.0F, 1.0F})); + + BASIC_RAC_SUBCASE("vec4", (v[0, 2, 1, 3]), + (Vector4{1.0F, 3.0F, 2.0F, 4.0F})); + } + + TEST_CASE("swap") { + using draco::math::Vector4; + + Vector4 a{1.F, 2.F, 3.F, 4.F}; + Vector4 b{4.F, 3.F, 2.F, 1.F}; + + std::swap(a, b); + + CHECK_EQ(a, Vector4{4.F, 3.F, 2.F, 1.F}); + CHECK_EQ(b, Vector4{1.F, 2.F, 3.F, 4.F}); + } + + TEST_CASE("dot") { + using draco::math::Vector4; + using draco::math::dot; + + static constexpr Vector4 a{1.0F, 2.0F, 3.0F, 4.0F}; + static constexpr Vector4 b{5.0F, 6.0F, 7.0F, 8.0F}; + + BASIC_RAC_SUBCASE("basic", (dot(a, b)), (70.0F)); + + BASIC_RAC_SUBCASE("self", (dot(a, a)), (30.0F)); + + BASIC_RAC_SUBCASE("zero", (dot(a, Vector4())), (0.0F)); + } + + TEST_CASE("length") { + using draco::math::Vector4; + using draco::math::length; + using draco::math::length_sq; + + static constexpr Vector4 v{1.0F, 2.0F, 2.0F, 4.0F}; + + BASIC_R_SUBCASE("normal", (length(v)), (5.0F)); + + BASIC_RAC_SUBCASE("squared", (length_sq(v)), (25.0F)); + } + + TEST_CASE("distance") { + using draco::math::Vector4; + using draco::math::distance; + using draco::math::distance_sq; + + static constexpr Vector4 a{1.0F, 2.0F, 2.0F, 4.0F}; + static constexpr Vector4 b{3.0F, 6.0F, 7.0F, 10.0F}; + + BASIC_R_SUBCASE("normal", (distance(a, b)), (9.0F)); + + BASIC_RAC_SUBCASE("squared", (distance_sq(a, b)), (81.0F)); + } + + TEST_CASE("normalize") { + using draco::math::Vector4; + using draco::math::length; + using draco::math::normalize; + using draco::math::normalize_fast; + + static constexpr Vector4 a{2.0F, 4.0F, 5.0F, 6.0F}; + static constexpr Vector4 b(1e-99); + + Vector4 const result = normalize(a); + Vector4 const result_fast = normalize_fast(a); + Vector4 const result_zero = normalize(b); + + CHECK_EQ(length(result), 1.0F); + CHECK_EQ(result, result_fast); + CHECK_EQ(result_zero, Vector4()); + } + + TEST_CASE("project") { + using draco::math::Vector4; + using draco::math::project; + + static constexpr Vector4 a{8.0F, 2.0F, 6.0F, 8.0F}; + static constexpr Vector4 b{12.0F, 14.0F, 8.0F, 6.0F}; + + RAC_CHECK_EQ((project(a, b)), (Vector4{6.0F, 7.0F, 4.0F, 3.0F})); + } + + TEST_CASE("reflect") { + using draco::math::Vector4; + using draco::math::reflect; + + static constexpr Vector4 a{1.0F, 2.0F, 3.0F, 4.0F}; + static constexpr Vector4 b{5.0F, 6.0F, 7.0F, 8.0F}; + + RAC_CHECK_EQ((reflect(a, b)), + (Vector4{-699.0F, -838.0F, -977.0F, -1116.0F})); + } + + TEST_CASE("angle") { + using draco::math::Vector4; + using draco::math::angle; + using draco::math::PI2; + + static constexpr Vector4 a{1.0F, 5.0F, 1.0F, 3.0F}; + static constexpr Vector4 b{2.0F, -6.0F, -2.0F, 10.0F}; + + R_CHECK_EQ((angle(a, b)), (PI2)); + } + + TEST_CASE("lerp") { + using draco::math::Vector4; + using draco::math::lerp; + + static constexpr Vector4 a{1.0F, 2.0F, 3.0F, 4.0F}; + static constexpr Vector4 b{5.0F, 6.0F, 7.0F, 8.0F}; + + BASIC_RAC_SUBCASE("weight = -1", (lerp(a, b, -1.0F)), + (Vector4{-3.0F, -2.0F, -1.0F, 0.0F})); + + BASIC_RAC_SUBCASE("weight = 0", (lerp(a, b, 0.0F)), (a)); + + BASIC_RAC_SUBCASE("weight = 0.5", (lerp(a, b, 0.5F)), + (Vector4{3.0F, 4.0F, 5.0F, 6.0F})); + + BASIC_RAC_SUBCASE("weight = 1", (lerp(a, b, 1.0F)), (b)); + + BASIC_RAC_SUBCASE("weight = 2", (lerp(a, b, 2.0F)), + (Vector4{9.0F, 10.0F, 11.0F, 12.0F})); + } + + TEST_CASE("min") { + using draco::math::Vector4; + using draco::math::min; + + static constexpr Vector4 a{5.0F, 8.0F, 3.0F, 4.0F}; + static constexpr Vector4 b{1.0F, 6.0F, 7.0F, 2.0F}; + + BASIC_RAC_SUBCASE("vector", (min(a, b)), + (Vector4{1.0F, 6.0F, 3.0F, 2.0F})); + + static constexpr Vector4 expected{4.0F, 4.0F, 3.0F, 4.0F}; + + BASIC_RAC_SUBCASE_2("float", (min(a, 4.0F)), (expected), (min(4.0F, a)), + (expected)); + } + + TEST_CASE("min_length") { + using draco::math::Vector4; + using draco::math::length; + using draco::math::min_length; + + static constexpr Vector4 a{1.0F, 2.0F, 2.0F, 4.0F}; // len: 5 + static constexpr Vector4 b{1.0F, -3.0F, -1.0F, 5.0F}; // len: 6 + + BASIC_RAC_SUBCASE("vector", (min_length(a, b)), (a)); + + SUBCASE("float") { + static constexpr f32 smaller_length = 1.0F; + static constexpr f32 larger_length = 10.0F; + + Vector4 const result_smaller = min_length(a, smaller_length); + Vector4 const result_swapped = min_length(smaller_length, a); + Vector4 const result_larger = min_length(larger_length, a); + + CHECK_EQ(length(result_smaller), smaller_length); + CHECK_EQ(result_smaller, result_swapped); + CHECK_EQ(result_larger, a); + } + } + + TEST_CASE("max") { + using draco::math::Vector4; + using draco::math::max; + + static constexpr Vector4 a{5.0F, 8.0F, 3.0F, 4.0F}; + static constexpr Vector4 b{1.0F, 6.0F, 7.0F, 2.0F}; + + BASIC_RAC_SUBCASE("vector", (max(a, b)), + (Vector4{5.0F, 8.0F, 7.0F, 4.0F})); + + static constexpr Vector4 expected{5.0F, 8.0F, 4.0F, 4.0F}; + + BASIC_RAC_SUBCASE_2("float", (max(a, 4.0F)), (expected), (max(4.0F, a)), + (expected)); + } + + TEST_CASE("max_length") { + using draco::math::Vector4; + using draco::math::length; + using draco::math::max_length; + + static constexpr Vector4 a{1.0F, 2.0F, 2.0F, 4.0F}; // len: 5 + static constexpr Vector4 b{1.0F, -3.0F, -1.0F, 5.0F}; // len: 6 + + BASIC_RAC_SUBCASE("vector", (max_length(a, b)), (b)); + + SUBCASE("float") { + static constexpr f32 smaller_length = 1.0F; + static constexpr f32 larger_length = 10.0F; + + Vector4 const result_smaller = max_length(a, smaller_length); + Vector4 const result_swapped = max_length(larger_length, a); + Vector4 const result_larger = max_length(larger_length, a); + + CHECK_EQ(length(result_larger), larger_length); + CHECK_EQ(result_larger, result_swapped); + CHECK_EQ(result_smaller, a); + } + } + + TEST_CASE("clamp") { + using draco::math::Vector4; + using draco::math::clamp; + + static constexpr Vector4 a{5.0F, 8.0F, 3.0F, 4.0F}; + static constexpr Vector4 b{1.0F, 6.0F, 7.0F, 2.0F}; + static constexpr Vector4 c{3.0F, 9.0F, 8.0F, 4.0F}; + + BASIC_RAC_SUBCASE("vector", (clamp(a, b, c)), + (Vector4{3.0F, 8.0F, 7.0F, 4.0F})); + + BASIC_RAC_SUBCASE("float", (clamp(a, 4.0F, 5.0F)), + (Vector4{5.0F, 5.0F, 4.0F, 4.0F})); + } + + TEST_CASE("clamp_length") { + using draco::math::Vector4; + using draco::math::length; + using draco::math::clamp_length; + + static constexpr Vector4 v{1.0F, -3.0F, -1.0F, 5.0F}; // len: 6 + + BASIC_R_SUBCASE("length < min", (clamp_length(v, 12.0F, 14.0F)), + (Vector4{2.0F, -6.0F, -2.0F, 10.0F})); + + BASIC_R_SUBCASE("length == min", (clamp_length(v, 6.0F, 9.0F)), (v)); + + BASIC_R_SUBCASE("length == max", (clamp_length(v, 3.0F, 6.0F)), (v)); + + BASIC_R_SUBCASE("length > max", (clamp_length(v, 1.0F, 3.0F)), + (Vector4{0.5F, -1.5F, -0.5F, 2.5F})); + } + + TEST_CASE("abs") { + using draco::math::Vector4; + using draco::math::abs; + + RAC_CHECK_EQ((abs(Vector4{1.0F, -2.0F, -3.0F, 0.0F})), + (Vector4{1.0F, 2.0F, 3.0F, 0.0F})); + } + + TEST_CASE("rounding") { + using draco::math::Vector4; + using draco::math::floor; + using draco::math::ceil; + using draco::math::trunc; + using draco::math::round; + + static constexpr Vector4 a{0.5F, -0.5F, 1.4F, 1.6F}; + static constexpr Vector4 b{-1.0F, 0.0F, 1.0F, 2.0F}; + + BASIC_RAC_SUBCASE_2("floor", (floor(a)), + (Vector4{0.0F, -1.0F, 1.0F, 1.0F}), (floor(b)), + (b)); + + BASIC_RAC_SUBCASE_2("ceil", (ceil(a)), + (Vector4{1.0F, 0.0F, 2.0F, 2.0F}), (ceil(b)), (b)); + + BASIC_RAC_SUBCASE_2("trunc", (trunc(a)), + (Vector4{0.0F, 0.0F, 1.0F, 1.0F}), (trunc(b)), (b)); + + BASIC_RAC_SUBCASE_2("round", (round(a)), + (Vector4{1.0F, -1.0F, 1.0F, 2.0F}), (round(b)), + (b)); + } + + TEST_CASE("sign") { + using draco::math::Vector4; + using draco::math::sign; + + RAC_CHECK_EQ((sign(Vector4{1.0F, -1.0F, 0.0F, -0.0F})), + (Vector4{1.0F, -1.0F, 0.0F, 0.0F})); + } + + TEST_CASE("approx_eq") { + using draco::math::Vector4; + using draco::math::approx_eq; + using draco::math::CMP_EPSILON; + + static constexpr Vector4 v{1.0F, 2.0F, 3.0F, 4.0F}; + static constexpr Vector4 offset = Vector4::x_axis(CMP_EPSILON); + + BASIC_R_SUBCASE("distance < epsilon", (approx_eq(v, v + offset * 0.5F)), + (true)); + + BASIC_R_SUBCASE("distance == epsilon", (approx_eq(v, v + offset)), + (true)); + + BASIC_R_SUBCASE("distance > epsilon", (approx_eq(v, v + offset * 2.0F)), + (false)); + } +} diff --git a/engine/native/core/math/transform.cpp b/engine/native/core/math/transform.cpp index 2538a51c..37814afc 100644 --- a/engine/native/core/math/transform.cpp +++ b/engine/native/core/math/transform.cpp @@ -8,71 +8,65 @@ module core.math.transform; import core.stdtypes; -namespace draco::math -{ - Transform make_transform() - { - Transform t; - - t.position[0] = 0.0f; - t.position[1] = 0.0f; - t.position[2] = 0.0f; - - t.rotation[0] = 0.0f; - t.rotation[1] = 0.0f; - t.rotation[2] = 0.0f; - - t.scale[0] = 1.0f; - t.scale[1] = 1.0f; - t.scale[2] = 1.0f; - - return t; - } - - void set_position(Transform& t, f32 x, f32 y, f32 z) - { - t.position[0] = x; - t.position[1] = y; - t.position[2] = z; - } - - void set_rotation(Transform& t, f32 x, f32 y, f32 z) - { - t.rotation[0] = x; - t.rotation[1] = y; - t.rotation[2] = z; - } - - void set_scale(Transform& t, f32 x, f32 y, f32 z) - { - t.scale[0] = x; - t.scale[1] = y; - t.scale[2] = z; - } - - void compute_matrix(const Transform& t, f32 out[16]) - { - f32 translation[16]; - f32 rx[16]; - f32 ry[16]; - f32 rz[16]; - f32 scale[16]; - f32 temp[16]; - - bx::mtxIdentity(out); - - bx::mtxScale(scale, t.scale[0], t.scale[1], t.scale[2]); - - bx::mtxRotateX(rx, t.rotation[0]); - bx::mtxRotateY(ry, t.rotation[1]); - bx::mtxRotateZ(rz, t.rotation[2]); - - bx::mtxTranslate(translation, t.position[0], t.position[1], t.position[2]); - - // scale * rotation * translation - bx::mtxMul(temp, scale, rx); - bx::mtxMul(temp, temp, ry); - bx::mtxMul(temp, temp, rz); - bx::mtxMul(out, temp, translation); - } +namespace draco::math { +Transform make_transform() { + Transform t; + + t.position[0] = 0.0F; + t.position[1] = 0.0F; + t.position[2] = 0.0F; + + t.rotation[0] = 0.0F; + t.rotation[1] = 0.0F; + t.rotation[2] = 0.0F; + + t.scale[0] = 1.0F; + t.scale[1] = 1.0F; + t.scale[2] = 1.0F; + + return t; +} + +void set_position(Transform &t, f32 x, f32 y, f32 z) { + t.position[0] = x; + t.position[1] = y; + t.position[2] = z; +} + +void set_rotation(Transform &t, f32 x, f32 y, f32 z) { + t.rotation[0] = x; + t.rotation[1] = y; + t.rotation[2] = z; +} + +void set_scale(Transform &t, f32 x, f32 y, f32 z) { + t.scale[0] = x; + t.scale[1] = y; + t.scale[2] = z; +} + +void compute_matrix(Transform const &t, f32 out[16]) { + f32 translation[16]; + f32 rx[16]; + f32 ry[16]; + f32 rz[16]; + f32 scale[16]; + f32 temp[16]; + + bx::mtxIdentity(out); + + bx::mtxScale(scale, t.scale[0], t.scale[1], t.scale[2]); + + bx::mtxRotateX(rx, t.rotation[0]); + bx::mtxRotateY(ry, t.rotation[1]); + bx::mtxRotateZ(rz, t.rotation[2]); + + bx::mtxTranslate(translation, t.position[0], t.position[1], t.position[2]); + + // scale * rotation * translation + bx::mtxMul(temp, scale, rx); + bx::mtxMul(temp, temp, ry); + bx::mtxMul(temp, temp, rz); + bx::mtxMul(out, temp, translation); } +} // namespace draco::math diff --git a/engine/native/core/math/transform.cppm b/engine/native/core/math/transform.cppm index fc1a5893..cde2ea16 100644 --- a/engine/native/core/math/transform.cppm +++ b/engine/native/core/math/transform.cppm @@ -8,25 +8,23 @@ export module core.math.transform; import core.stdtypes; -export namespace draco::math -{ - struct Transform - { - f32 position[3] = { 0.0f, 0.0f, 0.0f }; - f32 rotation[3] = { 0.0f, 0.0f, 0.0f }; // Euler (radians) - f32 scale[3] = { 1.0f, 1.0f, 1.0f }; - }; - - // TODO: Besides compute_matrix, we should make rest of the funcs constexpr funcs - - // Creates a default identity transform - Transform make_transform(); - - // Recompute matrix from transform (column-major) - void compute_matrix(const Transform& t, f32 out[16]); - - // Helpers - void set_position(Transform& t, f32 x, f32 y, f32 z); - void set_rotation(Transform& t, f32 x, f32 y, f32 z); - void set_scale(Transform& t, f32 x, f32 y, f32 z); -} +export namespace draco::math { +struct Transform { + f32 position[3] = {0.0F, 0.0F, 0.0F}; + f32 rotation[3] = {0.0F, 0.0F, 0.0F}; // Euler (radians) + f32 scale[3] = {1.0F, 1.0F, 1.0F}; +}; + +// TODO: Besides compute_matrix, we should make rest of the funcs constexpr funcs + +// Creates a default identity transform +Transform make_transform(); + +// Recompute matrix from transform (column-major) +void compute_matrix(Transform const &t, f32 out[16]); + +// Helpers +void set_position(Transform &t, f32 x, f32 y, f32 z); +void set_rotation(Transform &t, f32 x, f32 y, f32 z); +void set_scale(Transform &t, f32 x, f32 y, f32 z); +} // namespace draco::math diff --git a/engine/native/core/math/types.cppm b/engine/native/core/math/types.cppm index 0dbef119..c6f7027e 100644 --- a/engine/native/core/math/types.cppm +++ b/engine/native/core/math/types.cppm @@ -3,4 +3,4 @@ export module core.math.types; export import :common; export import :vector2; export import :vector3; -export import :vector4; \ No newline at end of file +export import :vector4; diff --git a/engine/native/core/math/types_common.cppm b/engine/native/core/math/types_common.cppm index 089faa2a..a14e9b62 100644 --- a/engine/native/core/math/types_common.cppm +++ b/engine/native/core/math/types_common.cppm @@ -3,169 +3,189 @@ import core.defs; import core.stdtypes; export namespace draco::math { - struct Vector2; - struct Vector3; - struct Vector4; - - struct alignas(8) Vector2 { - f32 x, y; - - // constructors - [[nodiscard]] constexpr Vector2() noexcept = default; - [[nodiscard]] constexpr explicit Vector2(f32 n) noexcept; - [[nodiscard]] constexpr Vector2(f32 x, f32 y) noexcept; - [[nodiscard]] constexpr explicit Vector2(const Vector3& xy) noexcept; - [[nodiscard]] constexpr explicit Vector2(const Vector4& xy) noexcept; - - // static - [[nodiscard]] static constexpr Vector2 x_axis(f32 x = 1.0f) noexcept; - [[nodiscard]] static constexpr Vector2 y_axis(f32 y = 1.0f) noexcept; - [[nodiscard]] static Vector2 polar(f32 angle, f32 radius = 1.0f) noexcept; - - // element access - [[nodiscard]] constexpr f32& operator[](i32 i) noexcept; - [[nodiscard]] constexpr const f32& operator[](i32 i) const noexcept; - - // swizzle - [[nodiscard]] constexpr Vector2 operator[](i32 i0, i32 i1) noexcept; - [[nodiscard]] constexpr Vector2 operator[](i32 i0, i32 i1) const noexcept; - [[nodiscard]] constexpr Vector3 operator[](i32 i0, i32 i1, i32 i2) noexcept; - [[nodiscard]] constexpr Vector3 operator[](i32 i0, i32 i1, i32 i2) const noexcept; - [[nodiscard]] constexpr Vector4 operator[](i32 i0, i32 i1, i32 i2, i32 i3) noexcept; - [[nodiscard]] constexpr Vector4 operator[](i32 i0, i32 i1, i32 i2, i32 i3) const noexcept; - - // operators - [[nodiscard]] constexpr Vector2 operator+() const noexcept; - [[nodiscard]] constexpr Vector2 operator-() const noexcept; - [[nodiscard]] constexpr bool operator==(const Vector2& other) const noexcept = default; - constexpr Vector2& operator+=(const Vector2& other) noexcept; - constexpr Vector2& operator+=(f32 other) noexcept; - constexpr Vector2& operator-=(const Vector2& other) noexcept; - constexpr Vector2& operator-=(f32 other) noexcept; - constexpr Vector2& operator*=(const Vector2& other) noexcept; - constexpr Vector2& operator*=(f32 other) noexcept; - constexpr Vector2& operator/=(const Vector2& other) noexcept; - constexpr Vector2& operator/=(f32 other) noexcept; - constexpr Vector2& operator=(f32 other) noexcept; - }; - - struct alignas(16) Vector3 { - f32 x, y, z; - - // constructors - [[nodiscard]] constexpr Vector3() noexcept = default; - [[nodiscard]] constexpr explicit Vector3(f32 n) noexcept; - [[nodiscard]] constexpr Vector3(f32 x, f32 y, f32 z) noexcept; - [[nodiscard]] constexpr explicit Vector3(const Vector2& xy, f32 z = 0.0f) noexcept; - [[nodiscard]] constexpr Vector3(f32 x, const Vector2& yz) noexcept; - [[nodiscard]] constexpr explicit Vector3(const Vector4& xyz) noexcept; - - // static - [[nodiscard]] static constexpr Vector3 x_axis(f32 x = 1.0f) noexcept; - [[nodiscard]] static constexpr Vector3 y_axis(f32 y = 1.0f) noexcept; - [[nodiscard]] static constexpr Vector3 z_axis(f32 z = 1.0f) noexcept; - [[nodiscard]] static Vector3 spherical(f32 azimuth, f32 inclination, f32 radius = 1.0f) noexcept; - [[nodiscard]] static Vector3 cylindrical(f32 angle, f32 radius = 1.0f, f32 height = 0.0f) noexcept; - - // element access - [[nodiscard]] constexpr f32& operator[](i32 i) noexcept; - [[nodiscard]] constexpr const f32& operator[](i32 i) const noexcept; - - // swizzle - [[nodiscard]] constexpr Vector2 operator[](i32 i0, i32 i1) noexcept; - [[nodiscard]] constexpr Vector2 operator[](i32 i0, i32 i1) const noexcept; - [[nodiscard]] constexpr Vector3 operator[](i32 i0, i32 i1, i32 i2) noexcept; - [[nodiscard]] constexpr Vector3 operator[](i32 i0, i32 i1, i32 i2) const noexcept; - [[nodiscard]] constexpr Vector4 operator[](i32 i0, i32 i1, i32 i2, i32 i3) noexcept; - [[nodiscard]] constexpr Vector4 operator[](i32 i0, i32 i1, i32 i2, i32 i3) const noexcept; - - // operators - [[nodiscard]] constexpr Vector3 operator+() const noexcept; - [[nodiscard]] constexpr Vector3 operator-() const noexcept; - [[nodiscard]] constexpr bool operator==(const Vector3& other) const noexcept = default; - constexpr Vector3& operator+=(const Vector3& other) noexcept; - constexpr Vector3& operator+=(f32 other) noexcept; - constexpr Vector3& operator-=(const Vector3& other) noexcept; - constexpr Vector3& operator-=(f32 other) noexcept; - constexpr Vector3& operator*=(const Vector3& other) noexcept; - constexpr Vector3& operator*=(f32 other) noexcept; - constexpr Vector3& operator/=(const Vector3& other) noexcept; - constexpr Vector3& operator/=(f32 other) noexcept; - constexpr Vector3& operator=(f32 other) noexcept; - }; - - struct alignas(16) Vector4 { - f32 x, y, z, w; - - // constructors - [[nodiscard]] constexpr Vector4() noexcept = default; - [[nodiscard]] constexpr explicit Vector4(f32 n) noexcept; - [[nodiscard]] constexpr Vector4(f32 x, f32 y, f32 z, f32 w) noexcept; - [[nodiscard]] constexpr explicit Vector4(const Vector2& xy) noexcept; - [[nodiscard]] constexpr Vector4(const Vector2& xy, f32 z, f32 w) noexcept; - [[nodiscard]] constexpr Vector4(f32 x, const Vector2& yz, f32 w) noexcept; - [[nodiscard]] constexpr Vector4(f32 x, f32 y, const Vector2& zw) noexcept; - [[nodiscard]] constexpr Vector4(const Vector2& xy, const Vector2& zw) noexcept; - [[nodiscard]] constexpr explicit Vector4(const Vector3& xyz, f32 w = 0.0f) noexcept; - [[nodiscard]] constexpr Vector4(f32 x, const Vector3& yzw) noexcept; - - // static - [[nodiscard]] static constexpr Vector4 x_axis(f32 x = 1.0f) noexcept; - [[nodiscard]] static constexpr Vector4 y_axis(f32 y = 1.0f) noexcept; - [[nodiscard]] static constexpr Vector4 z_axis(f32 z = 1.0f) noexcept; - [[nodiscard]] static constexpr Vector4 w_axis(f32 w = 1.0f) noexcept; - - // element access - [[nodiscard]] constexpr f32& operator[](i32 i) noexcept; - [[nodiscard]] constexpr const f32& operator[](i32 i) const noexcept; - - // swizzle - [[nodiscard]] constexpr Vector2 operator[](i32 i0, i32 i1) noexcept; - [[nodiscard]] constexpr Vector2 operator[](i32 i0, i32 i1) const noexcept; - [[nodiscard]] constexpr Vector3 operator[](i32 i0, i32 i1, i32 i2) noexcept; - [[nodiscard]] constexpr Vector3 operator[](i32 i0, i32 i1, i32 i2) const noexcept; - [[nodiscard]] constexpr Vector4 operator[](i32 i0, i32 i1, i32 i2, i32 i3) noexcept; - [[nodiscard]] constexpr Vector4 operator[](i32 i0, i32 i1, i32 i2, i32 i3) const noexcept; - - // member operators - [[nodiscard]] constexpr Vector4 operator+() const noexcept; - [[nodiscard]] constexpr Vector4 operator-() const noexcept; - [[nodiscard]] constexpr bool operator==(const Vector4& other) const noexcept = default; - constexpr Vector4& operator+=(const Vector4& other) noexcept; - constexpr Vector4& operator+=(f32 other) noexcept; - constexpr Vector4& operator-=(const Vector4& other) noexcept; - constexpr Vector4& operator-=(f32 other) noexcept; - constexpr Vector4& operator*=(const Vector4& other) noexcept; - constexpr Vector4& operator*=(f32 other) noexcept; - constexpr Vector4& operator/=(const Vector4& other) noexcept; - constexpr Vector4& operator/=(f32 other) noexcept; - constexpr Vector4& operator=(f32 other) noexcept; - }; +struct Vector2; +struct Vector3; +struct Vector4; + +struct alignas(8) Vector2 { + f32 x, y; + + // constructors + [[nodiscard]] constexpr Vector2() noexcept = default; + [[nodiscard]] constexpr explicit Vector2(f32 n) noexcept; + [[nodiscard]] constexpr Vector2(f32 x, f32 y) noexcept; + [[nodiscard]] constexpr explicit Vector2(Vector3 const &xy) noexcept; + [[nodiscard]] constexpr explicit Vector2(Vector4 const &xy) noexcept; + + // static + [[nodiscard]] static constexpr Vector2 x_axis(f32 x = 1.0F) noexcept; + [[nodiscard]] static constexpr Vector2 y_axis(f32 y = 1.0F) noexcept; + [[nodiscard]] static Vector2 polar(f32 angle, f32 radius = 1.0F) noexcept; + + // element access + [[nodiscard]] constexpr f32 &operator[](i32 i) noexcept; + [[nodiscard]] constexpr f32 const &operator[](i32 i) const noexcept; + + // swizzle + [[nodiscard]] constexpr Vector2 operator[](i32 i0, i32 i1) noexcept; + [[nodiscard]] constexpr Vector2 operator[](i32 i0, i32 i1) const noexcept; + [[nodiscard]] constexpr Vector3 operator[](i32 i0, i32 i1, i32 i2) noexcept; + [[nodiscard]] constexpr Vector3 + operator[](i32 i0, i32 i1, i32 i2) const noexcept; + [[nodiscard]] constexpr Vector4 + operator[](i32 i0, i32 i1, i32 i2, i32 i3) noexcept; + [[nodiscard]] constexpr Vector4 + operator[](i32 i0, i32 i1, i32 i2, i32 i3) const noexcept; + + // operators + [[nodiscard]] constexpr Vector2 operator+() const noexcept; + [[nodiscard]] constexpr Vector2 operator-() const noexcept; + [[nodiscard]] constexpr bool + operator==(Vector2 const &other) const noexcept = default; + constexpr Vector2 &operator+=(Vector2 const &other) noexcept; + constexpr Vector2 &operator+=(f32 other) noexcept; + constexpr Vector2 &operator-=(Vector2 const &other) noexcept; + constexpr Vector2 &operator-=(f32 other) noexcept; + constexpr Vector2 &operator*=(Vector2 const &other) noexcept; + constexpr Vector2 &operator*=(f32 other) noexcept; + constexpr Vector2 &operator/=(Vector2 const &other) noexcept; + constexpr Vector2 &operator/=(f32 other) noexcept; + constexpr Vector2 &operator=(f32 other) noexcept; +}; + +struct alignas(16) Vector3 { + f32 x, y, z; + + // constructors + [[nodiscard]] constexpr Vector3() noexcept = default; + [[nodiscard]] constexpr explicit Vector3(f32 n) noexcept; + [[nodiscard]] constexpr Vector3(f32 x, f32 y, f32 z) noexcept; + [[nodiscard]] constexpr explicit Vector3(Vector2 const &xy, + f32 z = 0.0F) noexcept; + [[nodiscard]] constexpr Vector3(f32 x, Vector2 const &yz) noexcept; + [[nodiscard]] constexpr explicit Vector3(Vector4 const &xyz) noexcept; + + // static + [[nodiscard]] static constexpr Vector3 x_axis(f32 x = 1.0F) noexcept; + [[nodiscard]] static constexpr Vector3 y_axis(f32 y = 1.0F) noexcept; + [[nodiscard]] static constexpr Vector3 z_axis(f32 z = 1.0F) noexcept; + [[nodiscard]] static Vector3 + spherical(f32 azimuth, f32 inclination, f32 radius = 1.0F) noexcept; + [[nodiscard]] static Vector3 + cylindrical(f32 angle, f32 radius = 1.0F, f32 height = 0.0F) noexcept; + + // element access + [[nodiscard]] constexpr f32 &operator[](i32 i) noexcept; + [[nodiscard]] constexpr f32 const &operator[](i32 i) const noexcept; + + // swizzle + [[nodiscard]] constexpr Vector2 operator[](i32 i0, i32 i1) noexcept; + [[nodiscard]] constexpr Vector2 operator[](i32 i0, i32 i1) const noexcept; + [[nodiscard]] constexpr Vector3 operator[](i32 i0, i32 i1, i32 i2) noexcept; + [[nodiscard]] constexpr Vector3 + operator[](i32 i0, i32 i1, i32 i2) const noexcept; + [[nodiscard]] constexpr Vector4 + operator[](i32 i0, i32 i1, i32 i2, i32 i3) noexcept; + [[nodiscard]] constexpr Vector4 + operator[](i32 i0, i32 i1, i32 i2, i32 i3) const noexcept; + + // operators + [[nodiscard]] constexpr Vector3 operator+() const noexcept; + [[nodiscard]] constexpr Vector3 operator-() const noexcept; + [[nodiscard]] constexpr bool + operator==(Vector3 const &other) const noexcept = default; + constexpr Vector3 &operator+=(Vector3 const &other) noexcept; + constexpr Vector3 &operator+=(f32 other) noexcept; + constexpr Vector3 &operator-=(Vector3 const &other) noexcept; + constexpr Vector3 &operator-=(f32 other) noexcept; + constexpr Vector3 &operator*=(Vector3 const &other) noexcept; + constexpr Vector3 &operator*=(f32 other) noexcept; + constexpr Vector3 &operator/=(Vector3 const &other) noexcept; + constexpr Vector3 &operator/=(f32 other) noexcept; + constexpr Vector3 &operator=(f32 other) noexcept; +}; + +struct alignas(16) Vector4 { + f32 x, y, z, w; + + // constructors + [[nodiscard]] constexpr Vector4() noexcept = default; + [[nodiscard]] constexpr explicit Vector4(f32 n) noexcept; + [[nodiscard]] constexpr Vector4(f32 x, f32 y, f32 z, f32 w) noexcept; + [[nodiscard]] constexpr explicit Vector4(Vector2 const &xy) noexcept; + [[nodiscard]] constexpr Vector4(Vector2 const &xy, f32 z, f32 w) noexcept; + [[nodiscard]] constexpr Vector4(f32 x, Vector2 const &yz, f32 w) noexcept; + [[nodiscard]] constexpr Vector4(f32 x, f32 y, Vector2 const &zw) noexcept; + [[nodiscard]] constexpr Vector4(Vector2 const &xy, + Vector2 const &zw) noexcept; + [[nodiscard]] constexpr explicit Vector4(Vector3 const &xyz, + f32 w = 0.0F) noexcept; + [[nodiscard]] constexpr Vector4(f32 x, Vector3 const &yzw) noexcept; + + // static + [[nodiscard]] static constexpr Vector4 x_axis(f32 x = 1.0F) noexcept; + [[nodiscard]] static constexpr Vector4 y_axis(f32 y = 1.0F) noexcept; + [[nodiscard]] static constexpr Vector4 z_axis(f32 z = 1.0F) noexcept; + [[nodiscard]] static constexpr Vector4 w_axis(f32 w = 1.0F) noexcept; + + // element access + [[nodiscard]] constexpr f32 &operator[](i32 i) noexcept; + [[nodiscard]] constexpr f32 const &operator[](i32 i) const noexcept; + + // swizzle + [[nodiscard]] constexpr Vector2 operator[](i32 i0, i32 i1) noexcept; + [[nodiscard]] constexpr Vector2 operator[](i32 i0, i32 i1) const noexcept; + [[nodiscard]] constexpr Vector3 operator[](i32 i0, i32 i1, i32 i2) noexcept; + [[nodiscard]] constexpr Vector3 + operator[](i32 i0, i32 i1, i32 i2) const noexcept; + [[nodiscard]] constexpr Vector4 + operator[](i32 i0, i32 i1, i32 i2, i32 i3) noexcept; + [[nodiscard]] constexpr Vector4 + operator[](i32 i0, i32 i1, i32 i2, i32 i3) const noexcept; + + // member operators + [[nodiscard]] constexpr Vector4 operator+() const noexcept; + [[nodiscard]] constexpr Vector4 operator-() const noexcept; + [[nodiscard]] constexpr bool + operator==(Vector4 const &other) const noexcept = default; + constexpr Vector4 &operator+=(Vector4 const &other) noexcept; + constexpr Vector4 &operator+=(f32 other) noexcept; + constexpr Vector4 &operator-=(Vector4 const &other) noexcept; + constexpr Vector4 &operator-=(f32 other) noexcept; + constexpr Vector4 &operator*=(Vector4 const &other) noexcept; + constexpr Vector4 &operator*=(f32 other) noexcept; + constexpr Vector4 &operator/=(Vector4 const &other) noexcept; + constexpr Vector4 &operator/=(f32 other) noexcept; + constexpr Vector4 &operator=(f32 other) noexcept; +}; +} // namespace draco::math + +template consteval T +select(draco::i32 const i, T const v1, T const v2) { + switch (i) { + case 0: return v1; + case 1: return v2; + default: throw "Index out of range"; + } } -template consteval T select(const draco::i32 i, const T v1, const T v2) { - switch (i) { - case 0: return v1; - case 1: return v2; - default: throw "Index out of range"; - } +template consteval T +select(draco::i32 const i, T const v1, T const v2, T const v3) { + switch (i) { + case 0: return v1; + case 1: return v2; + case 2: return v3; + default: throw "Index out of range"; + } } -template consteval T select(const draco::i32 i, const T v1, const T v2, const T v3) { - switch (i) { - case 0: return v1; - case 1: return v2; - case 2: return v3; - default: throw "Index out of range"; - } +template consteval T +select(draco::i32 const i, T const v1, T const v2, T const v3, T const v4) { + switch (i) { + case 0: return v1; + case 1: return v2; + case 2: return v3; + case 3: return v4; + default: throw "Index out of range"; + } } - -template consteval T select(const draco::i32 i, const T v1, const T v2, const T v3, const T v4) { - switch (i) { - case 0: return v1; - case 1: return v2; - case 2: return v3; - case 3: return v4; - default: throw "Index out of range"; - } -} \ No newline at end of file diff --git a/engine/native/core/math/vector2.cppm b/engine/native/core/math/vector2.cppm index a20b6e3a..288f14e3 100644 --- a/engine/native/core/math/vector2.cppm +++ b/engine/native/core/math/vector2.cppm @@ -6,9 +6,9 @@ module; #include "platform/simd.h" #if ARCH_X64 - #include + #include #elif ARCH_ARM64 - #include + #include #endif export module core.math.types:vector2; @@ -20,445 +20,421 @@ import core.defs; import core.stdtypes; export namespace draco::math { - // assertions - static_assert(sizeof(Vector2) == 8, "Vector2 must be 8 bytes"); - static_assert(alignof(Vector2) == 8, "Vector2 must be 8-byte aligned"); - static_assert(trivial, "Vector2 must be trivial"); - static_assert(std::is_standard_layout_v, "Vector2 must be standard layout"); - - // constructors - [[nodiscard]] constexpr Vector2::Vector2(const f32 n) noexcept - : x{n}, y{n} { } - - [[nodiscard]] constexpr Vector2::Vector2(const f32 x, const f32 y) noexcept - : x{x}, y{y} { } - - [[nodiscard]] constexpr Vector2::Vector2(const Vector3& xy) noexcept - : x{xy.x}, y{xy.y} { } - - [[nodiscard]] constexpr Vector2::Vector2(const Vector4& xy) noexcept - : x{xy.x}, y{xy.y} { } - - // static - [[nodiscard]] constexpr Vector2 Vector2::x_axis(const f32 x) noexcept { - return { x, 0.0f }; - } - - [[nodiscard]] constexpr Vector2 Vector2::y_axis(const f32 y) noexcept { - return { 0.0f, y }; - } - - [[nodiscard]] Vector2 Vector2::polar(const f32 angle, const f32 radius) noexcept { - return { radius * std::cos(angle), radius * std::sin(angle) }; - } - - // element access - [[nodiscard]] constexpr f32& Vector2::operator[](const i32 i) noexcept { - if consteval { - return i ? y : x; - } else { - return (&x)[i]; - } - } - - [[nodiscard]] constexpr const f32& Vector2::operator[](const i32 i) const noexcept { - if consteval { - return i ? y : x; - } else { - return (&x)[i]; - } - } - - // swizzle - [[nodiscard]] constexpr Vector2 Vector2::operator[](const i32 i0, const i32 i1) noexcept { - if consteval { - return { select(i0, x, y), select(i1, x, y) }; - } else { - return { (&x)[i0], (&x)[i1] }; - } - } - - [[nodiscard]] constexpr Vector2 Vector2::operator[](const i32 i0, const i32 i1) const noexcept { - if consteval { - return { select(i0, x, y), select(i1, x, y) }; - } else { - return { (&x)[i0], (&x)[i1] }; - } - } - - [[nodiscard]] constexpr Vector3 Vector2::operator[](const i32 i0, const i32 i1, const i32 i2) noexcept { - if consteval { - return { select(i0, x, y), select(i1, x, y), select(i2, x, y) }; - } else { - return { (&x)[i0], (&x)[i1], (&x)[i2] }; - } - } - - [[nodiscard]] constexpr Vector3 Vector2::operator[](const i32 i0, const i32 i1, const i32 i2) const noexcept { - if consteval { - return { select(i0, x, y), select(i1, x, y), select(i2, x, y) }; - } else { - return { (&x)[i0], (&x)[i1], (&x)[i2] }; - } - } - - [[nodiscard]] constexpr Vector4 Vector2::operator[](const i32 i0, const i32 i1, const i32 i2, const i32 i3) noexcept { - if consteval { - return { select(i0, x, y), select(i1, x, y), select(i2, x, y), select(i3, x, y) }; - } else { - return { (&x)[i0], (&x)[i1], (&x)[i2], (&x)[i3] }; - } - } - - [[nodiscard]] constexpr Vector4 Vector2::operator[](const i32 i0, const i32 i1, const i32 i2, const i32 i3) const noexcept { - if consteval { - return { select(i0, x, y), select(i1, x, y), select(i2, x, y), select(i3, x, y) }; - } else { - return { (&x)[i0], (&x)[i1], (&x)[i2], (&x)[i3] }; - } - } - - // operators - constexpr Vector2& Vector2::operator+=(const Vector2& other) noexcept { - x += other.x; - y += other.y; - return *this; - } - - constexpr Vector2& Vector2::operator+=(const f32 other) noexcept { - x += other; - y += other; - return *this; - } - - constexpr Vector2& Vector2::operator-=(const Vector2& other) noexcept { - x -= other.x; - y -= other.y; - return *this; - } - - constexpr Vector2& Vector2::operator-=(const f32 other) noexcept { - x -= other; - y -= other; - return *this; - } - - constexpr Vector2& Vector2::operator*=(const Vector2& other) noexcept { - x *= other.x; - y *= other.y; - return *this; - } - - constexpr Vector2& Vector2::operator*=(const f32 other) noexcept { - x *= other; - y *= other; - return *this; - } - - constexpr Vector2& Vector2::operator/=(const Vector2& other) noexcept { - x /= other.x; - y /= other.y; - return *this; - } - - constexpr Vector2& Vector2::operator/=(const f32 other) noexcept { - const f32 inv = 1.0f / other; - x *= inv; - y *= inv; - return *this; - } - - constexpr Vector2& Vector2::operator=(const f32 other) noexcept { - x = other; - y = other; - return *this; - } - - [[nodiscard]] constexpr Vector2 Vector2::operator+() const noexcept { - return { x, y }; - } - - [[nodiscard]] constexpr Vector2 Vector2::operator-() const noexcept { - return { -x, -y }; - } - - [[nodiscard]] constexpr Vector2 operator+(const Vector2& a, const Vector2& b) noexcept { - return { a.x+b.x, a.y+b.y }; - } - - [[nodiscard]] constexpr Vector2 operator+(const Vector2& a, const f32 b) noexcept { - return { a.x+b, a.y+b }; - } - - [[nodiscard]] constexpr Vector2 operator+(const f32 a, const Vector2& b) noexcept { - return b+a; - } - - [[nodiscard]] constexpr Vector2 operator-(const Vector2& a, const Vector2& b) noexcept { - return { a.x-b.x, a.y-b.y }; - } - - [[nodiscard]] constexpr Vector2 operator-(const Vector2& a, const f32 b) noexcept { - return { a.x-b, a.y-b }; - } - - [[nodiscard]] constexpr Vector2 operator-(const f32 a, const Vector2& b) noexcept { - return { a-b.x, a-b.y }; - } - - [[nodiscard]] constexpr Vector2 operator*(const Vector2& a, const Vector2& b) noexcept { - return { a.x*b.x, a.y*b.y }; - } - - [[nodiscard]] constexpr Vector2 operator*(const Vector2& a, const f32 b) noexcept { - return { a.x*b, a.y*b }; - } - - [[nodiscard]] constexpr Vector2 operator*(const f32 a, const Vector2& b) noexcept { - return b*a; - } - - [[nodiscard]] constexpr Vector2 operator/(const Vector2& a, const Vector2& b) noexcept { - return { a.x/b.x, a.y/b.y }; - } - - [[nodiscard]] constexpr Vector2 operator/(const Vector2& a, const f32 b) noexcept { - return a * (1.0f / b); - } - - [[nodiscard]] constexpr Vector2 operator/(const f32 a, const Vector2& b) noexcept { - return { a/b.x, a/b.y }; - } - - // functions - - // Returns dot product - [[nodiscard]] constexpr f32 dot(const Vector2& a, const Vector2& b) noexcept { - return a.x*b.x + a.y*b.y; - } - - // Returns squared magnitude - [[nodiscard]] constexpr f32 length_sq(const Vector2& v) noexcept { - return dot(v, v); - } - - // Returns magnitude - [[nodiscard]] f32 length(const Vector2& v) noexcept { - return std::sqrt(length_sq(v)); - } - - // Return squared distance between two vectors - [[nodiscard]] constexpr f32 distance_sq(const Vector2& a, const Vector2& b) noexcept { - return length_sq(a - b); - } - - // Returns distance between two vectors - [[nodiscard]] f32 distance(const Vector2& a, const Vector2& b) noexcept { - return length(a - b); - } - - // Safe normalize, checks length - [[nodiscard]] Vector2 normalize(const Vector2& v) noexcept { - const f32 len = length(v); - - return (len > CMP_NORMALIZE_TOLERANCE) ? v / len : Vector2(); - } - - // Faster normalize, it presupposes vector has non-zero length - // TODO: add check that v is non-zero on debug builds - [[nodiscard]] Vector2 normalize_fast(const Vector2& v) noexcept { - return v / length(v); - } - - // Returns vector projected onto normal - [[nodiscard]] constexpr Vector2 project(const Vector2& vector, const Vector2& normal) noexcept { - return normal * (dot(vector, normal) / length_sq(normal)); - } - - // Returns a vector reflected off a plane defined by its normal - [[nodiscard]] constexpr Vector2 reflect(const Vector2& incoming, const Vector2& normal) noexcept { - return incoming - 2.0f * dot(incoming, normal) * normal; - } - - // Returns the angle between two vectors - [[nodiscard]] f32 angle(const Vector2& a, const Vector2& b) noexcept { - return std::acos(dot(a, b) / (length(a) * length(b))); - } - - // Returns linear i32erpolation between two vectors - [[nodiscard]] constexpr Vector2 lerp(const Vector2& from, const Vector2& to, const f32 weight) noexcept { - return { - lerp(from.x, to.x, weight), - lerp(from.y, to.y, weight) - }; - } - - // Returns component-wise minimum - [[nodiscard]] constexpr Vector2 min(const Vector2& a, const Vector2& b) noexcept { - return { - std::min(a.x, b.x), - std::min(a.y, b.y) - }; - } - - [[nodiscard]] constexpr Vector2 min(const Vector2& a, const f32 b) noexcept { - return { - std::min(a.x, b), - std::min(a.y, b) - }; - } - - [[nodiscard]] constexpr Vector2 min(const f32 a, const Vector2& b) noexcept { - return min(b, a); - } - - // Returns the vector with the smaller length - [[nodiscard]] constexpr Vector2 min_length(const Vector2& a, const Vector2& b) noexcept { - return length_sq(a) < length_sq(b) ? a : b; - } - - // Returns a vector in the same direction whose length is bounded above by the given value. - [[nodiscard]] Vector2 min_length(const Vector2& a, const f32 b) noexcept { - const f32 len_sq = length_sq(a); - - if (len_sq > b * b) { - return a * (b / std::sqrt(len_sq)); - } else { - return a; - } - } - - [[nodiscard]] Vector2 min_length(const f32 a, const Vector2& b) noexcept { - return min_length(b, a); - } - - // Returns component-wise maximum - [[nodiscard]] constexpr Vector2 max(const Vector2& a, const Vector2& b) noexcept { - return { - std::max(a.x, b.x), - std::max(a.y, b.y) - }; - } - - [[nodiscard]] constexpr Vector2 max(const Vector2& a, const f32 b) noexcept { - return { - std::max(a.x, b), - std::max(a.y, b) - }; - } - - [[nodiscard]] constexpr Vector2 max(const f32 a, const Vector2& b) noexcept { - return max(b, a); - } - - // Returns the vector with the larger length - [[nodiscard]] constexpr Vector2 max_length(const Vector2& a, const Vector2& b) noexcept { - return length_sq(a) > length_sq(b) ? a : b; - } - - // Returns a vector in the same direction whose length is bounded below by the given value. Returns the 0 vector if the vector is too small to be normalized. - [[nodiscard]] Vector2 max_length(const Vector2& a, const f32 b) noexcept { - const f32 len_sq = length_sq(a); - - if (len_sq <= CMP_NORMALIZE_TOLERANCE2) { - return Vector2(); - } else if (len_sq < b * b) { - return a * (b / std::sqrt(len_sq)); - } else { - return a; - } - } - - [[nodiscard]] Vector2 max_length(const f32 a, const Vector2& b) noexcept { - return max_length(b, a); - } - - // Clamps each component of x to the range [x_min, x_max]. Presupposes x_min <= x_max. - [[nodiscard]] constexpr Vector2 clamp(const Vector2& x, const Vector2& x_min, const Vector2& x_max) noexcept { - return max(x_min, min(x, x_max)); - } - - [[nodiscard]] constexpr Vector2 clamp(const Vector2& x, const f32 x_min, const f32 x_max) noexcept { - return max(x_min, min(x, x_max)); - } - - // Clamps the length of the vector to the range [x_min, x_max]. Presupposes x_min <= x_max. Returns the 0 vector if the vector is too small to be normalized. - [[nodiscard]] Vector2 clamp_length(const Vector2& v, const f32 x_min, const f32 x_max) noexcept { - const f32 len_sq = length_sq(v); - - if (len_sq <= CMP_NORMALIZE_TOLERANCE2) { - return Vector2(); - } else if (len_sq < x_min * x_min) { - return v * (x_min / std::sqrt(len_sq)); - } else if (len_sq > x_max * x_max) { - return v * (x_max / std::sqrt(len_sq)); - } else { - return v; - } - } - - // Returns component-wise absolute value - [[nodiscard]] constexpr Vector2 abs(const Vector2& v) noexcept { - return { - abs(v.x), - abs(v.y) - }; - } - - // Returns component-wise floor - [[nodiscard]] constexpr Vector2 floor(const Vector2& v) noexcept { - return { - floor(v.x), - floor(v.y) - }; - } - - // Returns component-wise ceiling - [[nodiscard]] constexpr Vector2 ceil(const Vector2& v) noexcept { - return { - ceil(v.x), - ceil(v.y) - }; - } - - // Returns component-wise truncation - [[nodiscard]] constexpr Vector2 trunc(const Vector2& v) noexcept { - return { - trunc(v.x), - trunc(v.y) - }; - } - - // Returns component-wise round - [[nodiscard]] constexpr Vector2 round(const Vector2& v) noexcept { - return { - round(v.x), - round(v.y) - }; - } - - // Returns component-wise sign. Note that -0 still returns 0 - [[nodiscard]] constexpr Vector2 sign(const Vector2& v) noexcept { - return { - sign(v.x), - sign(v.y) - }; - } - - // Returns true if the vectors are approximately equal - [[nodiscard]] constexpr bool approx_eq(const Vector2& a, const Vector2& b) noexcept { - return distance_sq(a, b) < CMP_EPSILON2; - } +// assertions +static_assert(sizeof(Vector2) == 8, "Vector2 must be 8 bytes"); +static_assert(alignof(Vector2) == 8, "Vector2 must be 8-byte aligned"); +static_assert(trivial, "Vector2 must be trivial"); +static_assert(std::is_standard_layout_v, + "Vector2 must be standard layout"); + +// constructors +[[nodiscard]] constexpr Vector2::Vector2(f32 const n) noexcept : x{n}, y{n} { } + +[[nodiscard]] constexpr Vector2::Vector2(f32 const x, f32 const y) noexcept : + x{x}, y{y} { } + +[[nodiscard]] constexpr Vector2::Vector2(Vector3 const &xy) noexcept : + x{xy.x}, y{xy.y} { } + +[[nodiscard]] constexpr Vector2::Vector2(Vector4 const &xy) noexcept : + x{xy.x}, y{xy.y} { } + +// static +[[nodiscard]] constexpr Vector2 Vector2::x_axis(f32 const x) noexcept { + return {x, 0.0F}; +} + +[[nodiscard]] constexpr Vector2 Vector2::y_axis(f32 const y) noexcept { + return {0.0F, y}; +} + +[[nodiscard]] Vector2 Vector2::polar(f32 const angle, + f32 const radius) noexcept { + return {radius * std::cos(angle), radius * std::sin(angle)}; +} + +// element access +[[nodiscard]] constexpr f32 &Vector2::operator[](i32 const i) noexcept { + if consteval { return i ? y : x; } + else { return (&x)[i]; } +} + +[[nodiscard]] constexpr f32 const & +Vector2::operator[](i32 const i) const noexcept { + if consteval { return i ? y : x; } + else { return (&x)[i]; } +} + +// swizzle +[[nodiscard]] constexpr Vector2 Vector2::operator[](i32 const i0, + i32 const i1) noexcept { + if consteval { return {select(i0, x, y), select(i1, x, y)}; } + else { return {(&x)[i0], (&x)[i1]}; } +} + +[[nodiscard]] constexpr Vector2 +Vector2::operator[](i32 const i0, i32 const i1) const noexcept { + if consteval { return {select(i0, x, y), select(i1, x, y)}; } + else { return {(&x)[i0], (&x)[i1]}; } +} + +[[nodiscard]] constexpr Vector3 +Vector2::operator[](i32 const i0, i32 const i1, i32 const i2) noexcept { + if consteval { + return {select(i0, x, y), select(i1, x, y), select(i2, x, y)}; + } + else { return {(&x)[i0], (&x)[i1], (&x)[i2]}; } +} + +[[nodiscard]] constexpr Vector3 +Vector2::operator[](i32 const i0, i32 const i1, i32 const i2) const noexcept { + if consteval { + return {select(i0, x, y), select(i1, x, y), select(i2, x, y)}; + } + else { return {(&x)[i0], (&x)[i1], (&x)[i2]}; } +} + +[[nodiscard]] constexpr Vector4 Vector2::operator[](i32 const i0, + i32 const i1, + i32 const i2, + i32 const i3) noexcept { + if consteval { + return {select(i0, x, y), select(i1, x, y), select(i2, x, y), + select(i3, x, y)}; + } + else { return {(&x)[i0], (&x)[i1], (&x)[i2], (&x)[i3]}; } +} + +[[nodiscard]] constexpr Vector4 Vector2::operator[]( + i32 const i0, i32 const i1, i32 const i2, i32 const i3 +) const noexcept { + if consteval { + return {select(i0, x, y), select(i1, x, y), select(i2, x, y), + select(i3, x, y)}; + } + else { return {(&x)[i0], (&x)[i1], (&x)[i2], (&x)[i3]}; } +} + +// operators +constexpr Vector2 &Vector2::operator+=(Vector2 const &other) noexcept { + x += other.x; + y += other.y; + return *this; +} + +constexpr Vector2 &Vector2::operator+=(f32 const other) noexcept { + x += other; + y += other; + return *this; +} + +constexpr Vector2 &Vector2::operator-=(Vector2 const &other) noexcept { + x -= other.x; + y -= other.y; + return *this; +} + +constexpr Vector2 &Vector2::operator-=(f32 const other) noexcept { + x -= other; + y -= other; + return *this; +} + +constexpr Vector2 &Vector2::operator*=(Vector2 const &other) noexcept { + x *= other.x; + y *= other.y; + return *this; +} + +constexpr Vector2 &Vector2::operator*=(f32 const other) noexcept { + x *= other; + y *= other; + return *this; +} + +constexpr Vector2 &Vector2::operator/=(Vector2 const &other) noexcept { + x /= other.x; + y /= other.y; + return *this; +} + +constexpr Vector2 &Vector2::operator/=(f32 const other) noexcept { + f32 const inv = 1.0F / other; + x *= inv; + y *= inv; + return *this; +} + +constexpr Vector2 &Vector2::operator=(f32 const other) noexcept { + x = other; + y = other; + return *this; +} + +[[nodiscard]] constexpr Vector2 Vector2::operator+() const noexcept { + return {x, y}; +} + +[[nodiscard]] constexpr Vector2 Vector2::operator-() const noexcept { + return {-x, -y}; +} + +[[nodiscard]] constexpr Vector2 operator+(Vector2 const &a, + Vector2 const &b) noexcept { + return {a.x + b.x, a.y + b.y}; +} + +[[nodiscard]] constexpr Vector2 operator+(Vector2 const &a, + f32 const b) noexcept { + return {a.x + b, a.y + b}; +} + +[[nodiscard]] constexpr Vector2 operator+(f32 const a, + Vector2 const &b) noexcept { + return b + a; +} + +[[nodiscard]] constexpr Vector2 operator-(Vector2 const &a, + Vector2 const &b) noexcept { + return {a.x - b.x, a.y - b.y}; +} + +[[nodiscard]] constexpr Vector2 operator-(Vector2 const &a, + f32 const b) noexcept { + return {a.x - b, a.y - b}; +} + +[[nodiscard]] constexpr Vector2 operator-(f32 const a, + Vector2 const &b) noexcept { + return {a - b.x, a - b.y}; +} + +[[nodiscard]] constexpr Vector2 operator*(Vector2 const &a, + Vector2 const &b) noexcept { + return {a.x * b.x, a.y * b.y}; +} + +[[nodiscard]] constexpr Vector2 operator*(Vector2 const &a, + f32 const b) noexcept { + return {a.x * b, a.y * b}; +} + +[[nodiscard]] constexpr Vector2 operator*(f32 const a, + Vector2 const &b) noexcept { + return b * a; +} + +[[nodiscard]] constexpr Vector2 operator/(Vector2 const &a, + Vector2 const &b) noexcept { + return {a.x / b.x, a.y / b.y}; +} + +[[nodiscard]] constexpr Vector2 operator/(Vector2 const &a, + f32 const b) noexcept { + return a * (1.0F / b); +} + +[[nodiscard]] constexpr Vector2 operator/(f32 const a, + Vector2 const &b) noexcept { + return {a / b.x, a / b.y}; +} + +// functions + +// Returns dot product +[[nodiscard]] constexpr f32 dot(Vector2 const &a, Vector2 const &b) noexcept { + return a.x * b.x + a.y * b.y; +} + +// Returns squared magnitude +[[nodiscard]] constexpr f32 length_sq(Vector2 const &v) noexcept { + return dot(v, v); +} + +// Returns magnitude +[[nodiscard]] f32 length(Vector2 const &v) noexcept { + return std::sqrt(length_sq(v)); +} + +// Return squared distance between two vectors +[[nodiscard]] constexpr f32 distance_sq(Vector2 const &a, + Vector2 const &b) noexcept { + return length_sq(a - b); +} + +// Returns distance between two vectors +[[nodiscard]] f32 distance(Vector2 const &a, Vector2 const &b) noexcept { + return length(a - b); +} + +// Safe normalize, checks length +[[nodiscard]] Vector2 normalize(Vector2 const &v) noexcept { + f32 const len = length(v); + + return (len > CMP_NORMALIZE_TOLERANCE) ? v / len : Vector2(); +} + +// Faster normalize, it presupposes vector has non-zero length +// TODO: add check that v is non-zero on debug builds +[[nodiscard]] Vector2 normalize_fast(Vector2 const &v) noexcept { + return v / length(v); +} + +// Returns vector projected onto normal +[[nodiscard]] constexpr Vector2 project(Vector2 const &vector, + Vector2 const &normal) noexcept { + return normal * (dot(vector, normal) / length_sq(normal)); +} + +// Returns a vector reflected off a plane defined by its normal +[[nodiscard]] constexpr Vector2 reflect(Vector2 const &incoming, + Vector2 const &normal) noexcept { + return incoming - 2.0F * dot(incoming, normal) * normal; +} + +// Returns the angle between two vectors +[[nodiscard]] f32 angle(Vector2 const &a, Vector2 const &b) noexcept { + return std::acos(dot(a, b) / (length(a) * length(b))); +} + +// Returns linear i32erpolation between two vectors +[[nodiscard]] constexpr Vector2 +lerp(Vector2 const &from, Vector2 const &to, f32 const weight) noexcept { + return {lerp(from.x, to.x, weight), lerp(from.y, to.y, weight)}; +} + +// Returns component-wise minimum +[[nodiscard]] constexpr Vector2 min(Vector2 const &a, + Vector2 const &b) noexcept { + return {std::min(a.x, b.x), std::min(a.y, b.y)}; +} + +[[nodiscard]] constexpr Vector2 min(Vector2 const &a, f32 const b) noexcept { + return {std::min(a.x, b), std::min(a.y, b)}; +} + +[[nodiscard]] constexpr Vector2 min(f32 const a, Vector2 const &b) noexcept { + return min(b, a); +} + +// Returns the vector with the smaller length +[[nodiscard]] constexpr Vector2 min_length(Vector2 const &a, + Vector2 const &b) noexcept { + return length_sq(a) < length_sq(b) ? a : b; +} + +// Returns a vector in the same direction whose length is bounded above by the given value. +[[nodiscard]] Vector2 min_length(Vector2 const &a, f32 const b) noexcept { + f32 const len_sq = length_sq(a); + + if (len_sq > b * b) { return a * (b / std::sqrt(len_sq)); } + else { return a; } +} + +[[nodiscard]] Vector2 min_length(f32 const a, Vector2 const &b) noexcept { + return min_length(b, a); +} + +// Returns component-wise maximum +[[nodiscard]] constexpr Vector2 max(Vector2 const &a, + Vector2 const &b) noexcept { + return {std::max(a.x, b.x), std::max(a.y, b.y)}; +} + +[[nodiscard]] constexpr Vector2 max(Vector2 const &a, f32 const b) noexcept { + return {std::max(a.x, b), std::max(a.y, b)}; +} + +[[nodiscard]] constexpr Vector2 max(f32 const a, Vector2 const &b) noexcept { + return max(b, a); +} + +// Returns the vector with the larger length +[[nodiscard]] constexpr Vector2 max_length(Vector2 const &a, + Vector2 const &b) noexcept { + return length_sq(a) > length_sq(b) ? a : b; +} + +// Returns a vector in the same direction whose length is bounded below by the given value. Returns the 0 vector if the vector is too small to be normalized. +[[nodiscard]] Vector2 max_length(Vector2 const &a, f32 const b) noexcept { + f32 const len_sq = length_sq(a); + + if (len_sq <= CMP_NORMALIZE_TOLERANCE2) { return Vector2(); } + else if (len_sq < b * b) { return a * (b / std::sqrt(len_sq)); } + else { return a; } +} + +[[nodiscard]] Vector2 max_length(f32 const a, Vector2 const &b) noexcept { + return max_length(b, a); +} + +// Clamps each component of x to the range [x_min, x_max]. Presupposes x_min <= x_max. +[[nodiscard]] constexpr Vector2 +clamp(Vector2 const &x, Vector2 const &x_min, Vector2 const &x_max) noexcept { + return max(x_min, min(x, x_max)); +} + +[[nodiscard]] constexpr Vector2 +clamp(Vector2 const &x, f32 const x_min, f32 const x_max) noexcept { + return max(x_min, min(x, x_max)); +} + +// Clamps the length of the vector to the range [x_min, x_max]. Presupposes x_min <= x_max. Returns the 0 vector if the vector is too small to be normalized. +[[nodiscard]] Vector2 +clamp_length(Vector2 const &v, f32 const x_min, f32 const x_max) noexcept { + f32 const len_sq = length_sq(v); + + if (len_sq <= CMP_NORMALIZE_TOLERANCE2) { return Vector2(); } + else if (len_sq < x_min * x_min) { return v * (x_min / std::sqrt(len_sq)); } + else if (len_sq > x_max * x_max) { return v * (x_max / std::sqrt(len_sq)); } + else { return v; } +} + +// Returns component-wise absolute value +[[nodiscard]] constexpr Vector2 abs(Vector2 const &v) noexcept { + return {abs(v.x), abs(v.y)}; +} + +// Returns component-wise floor +[[nodiscard]] constexpr Vector2 floor(Vector2 const &v) noexcept { + return {floor(v.x), floor(v.y)}; +} + +// Returns component-wise ceiling +[[nodiscard]] constexpr Vector2 ceil(Vector2 const &v) noexcept { + return {ceil(v.x), ceil(v.y)}; +} + +// Returns component-wise truncation +[[nodiscard]] constexpr Vector2 trunc(Vector2 const &v) noexcept { + return {trunc(v.x), trunc(v.y)}; +} + +// Returns component-wise round +[[nodiscard]] constexpr Vector2 round(Vector2 const &v) noexcept { + return {round(v.x), round(v.y)}; +} + +// Returns component-wise sign. Note that -0 still returns 0 +[[nodiscard]] constexpr Vector2 sign(Vector2 const &v) noexcept { + return {sign(v.x), sign(v.y)}; +} + +// Returns true if the vectors are approximately equal +[[nodiscard]] constexpr bool approx_eq(Vector2 const &a, + Vector2 const &b) noexcept { + return distance_sq(a, b) < CMP_EPSILON2; } +} // namespace draco::math export namespace std { - template<> struct formatter : formatter { - auto format(const draco::math::Vector2& v, format_context& ctx) const { - ctx.advance_to(format_to(ctx.out(), "{{")); - ctx.advance_to(formatter::format(v.x, ctx)); - ctx.advance_to(format_to(ctx.out(), ", ")); - ctx.advance_to(formatter::format(v.y, ctx)); - return format_to(ctx.out(), "}}"); - } - }; -} \ No newline at end of file +template<> struct formatter : formatter { + auto format(draco::math::Vector2 const &v, format_context &ctx) const { + ctx.advance_to(format_to(ctx.out(), "{{")); + ctx.advance_to(formatter::format(v.x, ctx)); + ctx.advance_to(format_to(ctx.out(), ", ")); + ctx.advance_to(formatter::format(v.y, ctx)); + return format_to(ctx.out(), "}}"); + } +}; +} // namespace std diff --git a/engine/native/core/math/vector3.cppm b/engine/native/core/math/vector3.cppm index 05240c01..dc72ae3e 100644 --- a/engine/native/core/math/vector3.cppm +++ b/engine/native/core/math/vector3.cppm @@ -6,9 +6,9 @@ module; #include "platform/simd.h" #if ARCH_X64 - #include + #include #elif ARCH_ARM64 - #include + #include #endif export module core.math.types:vector3; @@ -20,491 +20,476 @@ import core.defs; import core.stdtypes; export namespace draco::math { - // assertions - static_assert(sizeof(Vector3) == 16, "Vector3 must be 16 bytes"); - static_assert(alignof(Vector3) == 16, "Vector3 must be 16-byte aligned"); - static_assert(trivial, "Vector3 must be trivial"); - static_assert(std::is_standard_layout_v, "Vector3 must be standard layout"); - - // constructors - [[nodiscard]] constexpr Vector3::Vector3(const f32 n) noexcept - : x{n}, y{n}, z{n} { } - - [[nodiscard]] constexpr Vector3::Vector3(const f32 x, const f32 y, const f32 z) noexcept - : x{x}, y{y}, z{z} { } - - [[nodiscard]] constexpr Vector3::Vector3(const Vector2& xy, const f32 z) noexcept - : x{xy.x}, y{xy.y}, z{z} { } - - [[nodiscard]] constexpr Vector3::Vector3(const f32 x, const Vector2& yz) noexcept - : x{x}, y{yz.x}, z{yz.y} { } - - [[nodiscard]] constexpr Vector3::Vector3(const Vector4& xyz) noexcept - : x{xyz.x}, y{xyz.y}, z{xyz.z} { } - - // static - [[nodiscard]] constexpr Vector3 Vector3::x_axis(const f32 x) noexcept { - return { x, 0.0f, 0.0f }; - } - - [[nodiscard]] constexpr Vector3 Vector3::y_axis(const f32 y) noexcept { - return { 0.0f, y, 0.0f }; - } - - [[nodiscard]] constexpr Vector3 Vector3::z_axis(const f32 z) noexcept { - return { 0.0f, 0.0f, z }; - } - - [[nodiscard]] Vector3 Vector3::spherical(const f32 azimuth, const f32 inclination, const f32 radius) noexcept { - const f32 sin_incl = radius * std::sin(inclination); - return { sin_incl * std::cos(azimuth), radius * std::cos(inclination), sin_incl * std::sin(azimuth) }; - } - - [[nodiscard]] Vector3 Vector3::cylindrical(const f32 angle, const f32 radius, const f32 height) noexcept { - return { radius * std::cos(angle), height, radius * std::sin(angle) }; - } - - // element access - [[nodiscard]] constexpr f32& Vector3::operator[](const i32 i) noexcept { - if consteval { - switch (i) { - case 0: return x; - case 1: return y; - default: - case 2: return z; - } - } else { return (&x)[i]; } - } - - [[nodiscard]] constexpr const f32& Vector3::operator[](const i32 i) const noexcept { - if consteval { - switch (i) { - case 0: return x; - case 1: return y; - default: - case 2: return z; - } - } else { return (&x)[i]; } - } - - // swizzle - [[nodiscard]] constexpr Vector2 Vector3::operator[](const i32 i0, const i32 i1) noexcept { - if consteval { - return { select(i0, x, y, z), select(i1, x, y, z) }; - } else { - return { (&x)[i0], (&x)[i1] }; - } - } - - [[nodiscard]] constexpr Vector2 Vector3::operator[](const i32 i0, const i32 i1) const noexcept { - if consteval { - return { select(i0, x, y, z), select(i1, x, y, z) }; - } else { - return { (&x)[i0], (&x)[i1] }; - } - } - - [[nodiscard]] constexpr Vector3 Vector3::operator[](const i32 i0, const i32 i1, const i32 i2) noexcept { - if consteval { - return { select(i0, x, y, z), select(i1, x, y, z), select(i2, x, y, z) }; - } else { - return { (&x)[i0], (&x)[i1], (&x)[i2] }; - } - } - - [[nodiscard]] constexpr Vector3 Vector3::operator[](const i32 i0, const i32 i1, const i32 i2) const noexcept { - if consteval { - return { select(i0, x, y, z), select(i1, x, y, z), select(i2, x, y, z) }; - } else { - return { (&x)[i0], (&x)[i1], (&x)[i2] }; - } - } - - [[nodiscard]] constexpr Vector4 Vector3::operator[](const i32 i0, const i32 i1, const i32 i2, const i32 i3) noexcept { - if consteval { - return { select(i0, x, y, z), select(i1, x, y, z), select(i2, x, y, z), select(i3, x, y, z) }; - } else { - return { (&x)[i0], (&x)[i1], (&x)[i2], (&x)[i3] }; - } - } - - [[nodiscard]] constexpr Vector4 Vector3::operator[](const i32 i0, const i32 i1, const i32 i2, const i32 i3) const noexcept { - if consteval { - return { select(i0, x, y, z), select(i1, x, y, z), select(i2, x, y, z), select(i3, x, y, z) }; - } else { - return { (&x)[i0], (&x)[i1], (&x)[i2], (&x)[i3] }; - } - } - - // operators - constexpr Vector3& Vector3::operator+=(const Vector3& other) noexcept { - x += other.x; - y += other.y; - z += other.z; - return *this; - } - - constexpr Vector3& Vector3::operator+=(const f32 other) noexcept { - x += other; - y += other; - z += other; - return *this; - } - - constexpr Vector3& Vector3::operator-=(const Vector3& other) noexcept { - x -= other.x; - y -= other.y; - z -= other.z; - return *this; - } - - constexpr Vector3& Vector3::operator-=(const f32 other) noexcept { - x -= other; - y -= other; - z -= other; - return *this; - } - - constexpr Vector3& Vector3::operator*=(const Vector3& other) noexcept { - x *= other.x; - y *= other.y; - z *= other.z; - return *this; - } - - constexpr Vector3& Vector3::operator*=(const f32 other) noexcept { - x *= other; - y *= other; - z *= other; - return *this; - } - - constexpr Vector3& Vector3::operator/=(const Vector3& other) noexcept { - x /= other.x; - y /= other.y; - z /= other.z; - return *this; - } - - constexpr Vector3& Vector3::operator/=(const f32 other) noexcept { - const f32 inv = 1.0f / other; - x *= inv; - y *= inv; - z *= inv; - return *this; - } - - constexpr Vector3& Vector3::operator=(const f32 other) noexcept { - x = other; - y = other; - z = other; - return *this; - } - - [[nodiscard]] constexpr Vector3 Vector3::operator+() const noexcept { - return { x, y, z }; - } - - [[nodiscard]] constexpr Vector3 Vector3::operator-() const noexcept { - return { -x, -y, -z }; - } - - [[nodiscard]] constexpr Vector3 operator+(const Vector3& a, const Vector3& b) noexcept { - return { a.x+b.x, a.y+b.y, a.z+b.z }; - } - - [[nodiscard]] constexpr Vector3 operator+(const Vector3& a, const f32 b) noexcept { - return { a.x+b, a.y+b, a.z+b }; - } - - [[nodiscard]] constexpr Vector3 operator+(const f32 a, const Vector3& b) noexcept { - return b+a; - } - - [[nodiscard]] constexpr Vector3 operator-(const Vector3& a, const Vector3& b) noexcept { - return { a.x-b.x, a.y-b.y, a.z-b.z }; - } - - [[nodiscard]] constexpr Vector3 operator-(const Vector3& a, const f32 b) noexcept { - return { a.x-b, a.y-b, a.z-b }; - } - - [[nodiscard]] constexpr Vector3 operator-(const f32 a, const Vector3& b) noexcept { - return { a-b.x, a-b.y, a-b.z }; - } - - [[nodiscard]] constexpr Vector3 operator*(const Vector3& a, const Vector3& b) noexcept { - return { a.x*b.x, a.y*b.y, a.z*b.z }; - } - - [[nodiscard]] constexpr Vector3 operator*(const Vector3& a, const f32 b) noexcept { - return { a.x*b, a.y*b, a.z*b }; - } - - [[nodiscard]] constexpr Vector3 operator*(const f32 a, const Vector3& b) noexcept { - return b*a; - } - - [[nodiscard]] constexpr Vector3 operator/(const Vector3& a, const Vector3& b) noexcept { - return { a.x/b.x, a.y/b.y, a.z/b.z }; - } - - [[nodiscard]] constexpr Vector3 operator/(const Vector3& a, const f32 b) noexcept { - return a * (1.0f / b); - } - - [[nodiscard]] constexpr Vector3 operator/(const f32 a, const Vector3& b) noexcept { - return { a/b.x, a/b.y, a/b.z }; - } - - // functions - - // Returns dot product - [[nodiscard]] constexpr f32 dot(const Vector3& a, const Vector3& b) noexcept { - return a.x*b.x + a.y*b.y + a.z*b.z; - } - - // Returns squared magnitude - [[nodiscard]] constexpr f32 length_sq(const Vector3& v) noexcept { - return dot(v, v); - } - - // Returns magnitude - [[nodiscard]] f32 length(const Vector3& v) noexcept { - return std::sqrt(length_sq(v)); - } - - // Return squared distance between two vectors - [[nodiscard]] constexpr f32 distance_sq(const Vector3& a, const Vector3& b) noexcept { - return length_sq(a - b); - } - - // Returns distance between two vectors - [[nodiscard]] f32 distance(const Vector3& a, const Vector3& b) noexcept { - return length(a - b); - } - - // Safe normalize, checks length - [[nodiscard]] Vector3 normalize(const Vector3& v) noexcept { - const f32 len = length(v); - - return (len > CMP_NORMALIZE_TOLERANCE) ? v / len : Vector3(); - } - - // Faster normalize, it presupposes vector has non-zero length - // TODO: add check that v is non-zero on debug builds - [[nodiscard]] Vector3 normalize_fast(const Vector3& v) noexcept { - return v / length(v); - } - - // Returns vector projected onto normal - [[nodiscard]] constexpr Vector3 project(const Vector3& vector, const Vector3& normal) noexcept { - return normal * (dot(vector, normal) / length_sq(normal)); - } - - // Returns a vector reflected off a plane defined by its normal - [[nodiscard]] constexpr Vector3 reflect(const Vector3& incoming, const Vector3& normal) noexcept { - return incoming - 2.0f * dot(incoming, normal) * normal; - } - - // Returns the angle between two vectors - [[nodiscard]] f32 angle(const Vector3& a, const Vector3& b) noexcept { - return std::acos(dot(a, b) / (length(a) * length(b))); - } - - // Returns linear interpolation between two vectors - [[nodiscard]] constexpr Vector3 lerp(const Vector3& from, const Vector3& to, const f32 weight) noexcept { - return { - lerp(from.x, to.x, weight), - lerp(from.y, to.y, weight), - lerp(from.z, to.z, weight) - }; - } - - // Returns component-wise minimum - [[nodiscard]] constexpr Vector3 min(const Vector3& a, const Vector3& b) noexcept { - return { - std::min(a.x, b.x), - std::min(a.y, b.y), - std::min(a.z, b.z) - }; - } - - [[nodiscard]] constexpr Vector3 min(const Vector3& a, const f32 b) noexcept { - return { - std::min(a.x, b), - std::min(a.y, b), - std::min(a.z, b) - }; - } - - [[nodiscard]] constexpr Vector3 min(const f32 a, const Vector3& b) noexcept { - return min(b, a); - } - - // Returns the vector with the smaller length - [[nodiscard]] constexpr Vector3 min_length(const Vector3& a, const Vector3& b) noexcept { - return length_sq(a) < length_sq(b) ? a : b; - } - - // Returns a vector in the same direction whose length is bounded above by the given value. - [[nodiscard]] Vector3 min_length(const Vector3& a, const f32 b) noexcept { - const f32 len_sq = length_sq(a); - - if (len_sq > b * b) { - return a * (b / std::sqrt(len_sq)); - } else { - return a; - } - } - - [[nodiscard]] Vector3 min_length(const f32 a, const Vector3& b) noexcept { - return min_length(b, a); - } - - // Returns component-wise maximum - [[nodiscard]] constexpr Vector3 max(const Vector3& a, const Vector3& b) noexcept { - return { - std::max(a.x, b.x), - std::max(a.y, b.y), - std::max(a.z, b.z) - }; - } - - [[nodiscard]] constexpr Vector3 max(const Vector3& a, const f32 b) noexcept { - return { - std::max(a.x, b), - std::max(a.y, b), - std::max(a.z, b) - }; - } - - [[nodiscard]] constexpr Vector3 max(const f32 a, const Vector3& b) noexcept { - return max(b, a); - } - - // Returns the vector with the larger length - [[nodiscard]] constexpr Vector3 max_length(const Vector3& a, const Vector3& b) noexcept { - return length_sq(a) > length_sq(b) ? a : b; - } - - // Returns a vector in the same direction whose length is bounded below by the given value. Returns the 0 vector if the vector is too small to be normalized. - [[nodiscard]] Vector3 max_length(const Vector3& a, const f32 b) noexcept { - const f32 len_sq = length_sq(a); - - if (len_sq <= CMP_NORMALIZE_TOLERANCE2) { - return Vector3(); - } else if (len_sq < b * b) { - return a * (b / std::sqrt(len_sq)); - } else { - return a; - } - } - - [[nodiscard]] Vector3 max_length(const f32 a, const Vector3& b) noexcept { - return max_length(b, a); - } - - // Clamps each component of x to the range [x_min, x_max]. Presupposes x_min <= x_max. - [[nodiscard]] constexpr Vector3 clamp(const Vector3& x, const Vector3& x_min, const Vector3& x_max) noexcept { - return max(x_min, min(x, x_max)); - } - - [[nodiscard]] constexpr Vector3 clamp(const Vector3& x, const f32 x_min, const f32 x_max) noexcept { - return max(x_min, min(x, x_max)); - } - - // Clamps the length of the vector to the range [x_min, x_max]. Presupposes x_min <= x_max. Returns the 0 vector if the vector is too small to be normalized. - [[nodiscard]] Vector3 clamp_length(const Vector3& v, const f32 x_min, const f32 x_max) noexcept { - const f32 len_sq = length_sq(v); - - if (len_sq <= CMP_NORMALIZE_TOLERANCE2) { - return Vector3(); - } else if (len_sq < x_min * x_min) { - return v * (x_min / std::sqrt(len_sq)); - } else if (len_sq > x_max * x_max) { - return v * (x_max / std::sqrt(len_sq)); - } else { - return v; - } - } - - // Returns component-wise absolute value - [[nodiscard]] constexpr Vector3 abs(const Vector3& v) noexcept { - return { - abs(v.x), - abs(v.y), - abs(v.z) - }; - } - - // Returns component-wise floor - [[nodiscard]] constexpr Vector3 floor(const Vector3& v) noexcept { - return { - floor(v.x), - floor(v.y), - floor(v.z) - }; - } - - // Returns component-wise ceiling - [[nodiscard]] constexpr Vector3 ceil(const Vector3& v) noexcept { - return { - ceil(v.x), - ceil(v.y), - ceil(v.z) - }; - } - - // Returns component-wise truncation - [[nodiscard]] constexpr Vector3 trunc(const Vector3& v) noexcept { - return { - trunc(v.x), - trunc(v.y), - trunc(v.z) - }; - } - - // Returns component-wise round - [[nodiscard]] constexpr Vector3 round(const Vector3& v) noexcept { - return { - round(v.x), - round(v.y), - round(v.z) - }; - } - - // Returns component-wise sign. Note that -0 still returns 0 - [[nodiscard]] constexpr Vector3 sign(const Vector3& v) noexcept { - return { - sign(v.x), - sign(v.y), - sign(v.z) - }; - } - - // Returns true if the vectors are approximately equal - [[nodiscard]] constexpr bool approx_eq(const Vector3& a, const Vector3& b) noexcept { - return distance_sq(a, b) < CMP_EPSILON2; - } - - // Returns cross product - [[nodiscard]] constexpr Vector3 cross(const Vector3& a, const Vector3& b) noexcept { - return { a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x }; - } +// assertions +static_assert(sizeof(Vector3) == 16, "Vector3 must be 16 bytes"); +static_assert(alignof(Vector3) == 16, "Vector3 must be 16-byte aligned"); +static_assert(trivial, "Vector3 must be trivial"); +static_assert(std::is_standard_layout_v, + "Vector3 must be standard layout"); + +// constructors +[[nodiscard]] constexpr Vector3::Vector3(f32 const n) noexcept : + x{n}, y{n}, z{n} { } + +[[nodiscard]] constexpr Vector3::Vector3(f32 const x, + f32 const y, + f32 const z) noexcept : + x{x}, y{y}, z{z} { } + +[[nodiscard]] constexpr Vector3::Vector3(Vector2 const &xy, + f32 const z) noexcept : + x{xy.x}, y{xy.y}, z{z} { } + +[[nodiscard]] constexpr Vector3::Vector3(f32 const x, + Vector2 const &yz) noexcept : + x{x}, y{yz.x}, z{yz.y} { } + +[[nodiscard]] constexpr Vector3::Vector3(Vector4 const &xyz) noexcept : + x{xyz.x}, y{xyz.y}, z{xyz.z} { } + +// static +[[nodiscard]] constexpr Vector3 Vector3::x_axis(f32 const x) noexcept { + return {x, 0.0F, 0.0F}; } +[[nodiscard]] constexpr Vector3 Vector3::y_axis(f32 const y) noexcept { + return {0.0F, y, 0.0F}; +} + +[[nodiscard]] constexpr Vector3 Vector3::z_axis(f32 const z) noexcept { + return {0.0F, 0.0F, z}; +} + +[[nodiscard]] Vector3 Vector3::spherical(f32 const azimuth, + f32 const inclination, + f32 const radius) noexcept { + f32 const sin_incl = radius * std::sin(inclination); + return {sin_incl * std::cos(azimuth), radius * std::cos(inclination), + sin_incl * std::sin(azimuth)}; +} + +[[nodiscard]] Vector3 Vector3::cylindrical(f32 const angle, + f32 const radius, + f32 const height) noexcept { + return {radius * std::cos(angle), height, radius * std::sin(angle)}; +} + +// element access +[[nodiscard]] constexpr f32 &Vector3::operator[](i32 const i) noexcept { + if consteval { + switch (i) { + case 0: return x; + case 1: return y; + default: + case 2: return z; + } + } + else { return (&x)[i]; } +} + +[[nodiscard]] constexpr f32 const & +Vector3::operator[](i32 const i) const noexcept { + if consteval { + switch (i) { + case 0: return x; + case 1: return y; + default: + case 2: return z; + } + } + else { return (&x)[i]; } +} + +// swizzle +[[nodiscard]] constexpr Vector2 Vector3::operator[](i32 const i0, + i32 const i1) noexcept { + if consteval { return {select(i0, x, y, z), select(i1, x, y, z)}; } + else { return {(&x)[i0], (&x)[i1]}; } +} + +[[nodiscard]] constexpr Vector2 +Vector3::operator[](i32 const i0, i32 const i1) const noexcept { + if consteval { return {select(i0, x, y, z), select(i1, x, y, z)}; } + else { return {(&x)[i0], (&x)[i1]}; } +} + +[[nodiscard]] constexpr Vector3 +Vector3::operator[](i32 const i0, i32 const i1, i32 const i2) noexcept { + if consteval { + return {select(i0, x, y, z), select(i1, x, y, z), select(i2, x, y, z)}; + } + else { return {(&x)[i0], (&x)[i1], (&x)[i2]}; } +} + +[[nodiscard]] constexpr Vector3 +Vector3::operator[](i32 const i0, i32 const i1, i32 const i2) const noexcept { + if consteval { + return {select(i0, x, y, z), select(i1, x, y, z), select(i2, x, y, z)}; + } + else { return {(&x)[i0], (&x)[i1], (&x)[i2]}; } +} + +[[nodiscard]] constexpr Vector4 Vector3::operator[](i32 const i0, + i32 const i1, + i32 const i2, + i32 const i3) noexcept { + if consteval { + return {select(i0, x, y, z), select(i1, x, y, z), select(i2, x, y, z), + select(i3, x, y, z)}; + } + else { return {(&x)[i0], (&x)[i1], (&x)[i2], (&x)[i3]}; } +} + +[[nodiscard]] constexpr Vector4 Vector3::operator[]( + i32 const i0, i32 const i1, i32 const i2, i32 const i3 +) const noexcept { + if consteval { + return {select(i0, x, y, z), select(i1, x, y, z), select(i2, x, y, z), + select(i3, x, y, z)}; + } + else { return {(&x)[i0], (&x)[i1], (&x)[i2], (&x)[i3]}; } +} + +// operators +constexpr Vector3 &Vector3::operator+=(Vector3 const &other) noexcept { + x += other.x; + y += other.y; + z += other.z; + return *this; +} + +constexpr Vector3 &Vector3::operator+=(f32 const other) noexcept { + x += other; + y += other; + z += other; + return *this; +} + +constexpr Vector3 &Vector3::operator-=(Vector3 const &other) noexcept { + x -= other.x; + y -= other.y; + z -= other.z; + return *this; +} + +constexpr Vector3 &Vector3::operator-=(f32 const other) noexcept { + x -= other; + y -= other; + z -= other; + return *this; +} + +constexpr Vector3 &Vector3::operator*=(Vector3 const &other) noexcept { + x *= other.x; + y *= other.y; + z *= other.z; + return *this; +} + +constexpr Vector3 &Vector3::operator*=(f32 const other) noexcept { + x *= other; + y *= other; + z *= other; + return *this; +} + +constexpr Vector3 &Vector3::operator/=(Vector3 const &other) noexcept { + x /= other.x; + y /= other.y; + z /= other.z; + return *this; +} + +constexpr Vector3 &Vector3::operator/=(f32 const other) noexcept { + f32 const inv = 1.0F / other; + x *= inv; + y *= inv; + z *= inv; + return *this; +} + +constexpr Vector3 &Vector3::operator=(f32 const other) noexcept { + x = other; + y = other; + z = other; + return *this; +} + +[[nodiscard]] constexpr Vector3 Vector3::operator+() const noexcept { + return {x, y, z}; +} + +[[nodiscard]] constexpr Vector3 Vector3::operator-() const noexcept { + return {-x, -y, -z}; +} + +[[nodiscard]] constexpr Vector3 operator+(Vector3 const &a, + Vector3 const &b) noexcept { + return {a.x + b.x, a.y + b.y, a.z + b.z}; +} + +[[nodiscard]] constexpr Vector3 operator+(Vector3 const &a, + f32 const b) noexcept { + return {a.x + b, a.y + b, a.z + b}; +} + +[[nodiscard]] constexpr Vector3 operator+(f32 const a, + Vector3 const &b) noexcept { + return b + a; +} + +[[nodiscard]] constexpr Vector3 operator-(Vector3 const &a, + Vector3 const &b) noexcept { + return {a.x - b.x, a.y - b.y, a.z - b.z}; +} + +[[nodiscard]] constexpr Vector3 operator-(Vector3 const &a, + f32 const b) noexcept { + return {a.x - b, a.y - b, a.z - b}; +} + +[[nodiscard]] constexpr Vector3 operator-(f32 const a, + Vector3 const &b) noexcept { + return {a - b.x, a - b.y, a - b.z}; +} + +[[nodiscard]] constexpr Vector3 operator*(Vector3 const &a, + Vector3 const &b) noexcept { + return {a.x * b.x, a.y * b.y, a.z * b.z}; +} + +[[nodiscard]] constexpr Vector3 operator*(Vector3 const &a, + f32 const b) noexcept { + return {a.x * b, a.y * b, a.z * b}; +} + +[[nodiscard]] constexpr Vector3 operator*(f32 const a, + Vector3 const &b) noexcept { + return b * a; +} + +[[nodiscard]] constexpr Vector3 operator/(Vector3 const &a, + Vector3 const &b) noexcept { + return {a.x / b.x, a.y / b.y, a.z / b.z}; +} + +[[nodiscard]] constexpr Vector3 operator/(Vector3 const &a, + f32 const b) noexcept { + return a * (1.0F / b); +} + +[[nodiscard]] constexpr Vector3 operator/(f32 const a, + Vector3 const &b) noexcept { + return {a / b.x, a / b.y, a / b.z}; +} + +// functions + +// Returns dot product +[[nodiscard]] constexpr f32 dot(Vector3 const &a, Vector3 const &b) noexcept { + return a.x * b.x + a.y * b.y + a.z * b.z; +} + +// Returns squared magnitude +[[nodiscard]] constexpr f32 length_sq(Vector3 const &v) noexcept { + return dot(v, v); +} + +// Returns magnitude +[[nodiscard]] f32 length(Vector3 const &v) noexcept { + return std::sqrt(length_sq(v)); +} + +// Return squared distance between two vectors +[[nodiscard]] constexpr f32 distance_sq(Vector3 const &a, + Vector3 const &b) noexcept { + return length_sq(a - b); +} + +// Returns distance between two vectors +[[nodiscard]] f32 distance(Vector3 const &a, Vector3 const &b) noexcept { + return length(a - b); +} + +// Safe normalize, checks length +[[nodiscard]] Vector3 normalize(Vector3 const &v) noexcept { + f32 const len = length(v); + + return (len > CMP_NORMALIZE_TOLERANCE) ? v / len : Vector3(); +} + +// Faster normalize, it presupposes vector has non-zero length +// TODO: add check that v is non-zero on debug builds +[[nodiscard]] Vector3 normalize_fast(Vector3 const &v) noexcept { + return v / length(v); +} + +// Returns vector projected onto normal +[[nodiscard]] constexpr Vector3 project(Vector3 const &vector, + Vector3 const &normal) noexcept { + return normal * (dot(vector, normal) / length_sq(normal)); +} + +// Returns a vector reflected off a plane defined by its normal +[[nodiscard]] constexpr Vector3 reflect(Vector3 const &incoming, + Vector3 const &normal) noexcept { + return incoming - 2.0F * dot(incoming, normal) * normal; +} + +// Returns the angle between two vectors +[[nodiscard]] f32 angle(Vector3 const &a, Vector3 const &b) noexcept { + return std::acos(dot(a, b) / (length(a) * length(b))); +} + +// Returns linear interpolation between two vectors +[[nodiscard]] constexpr Vector3 +lerp(Vector3 const &from, Vector3 const &to, f32 const weight) noexcept { + return {lerp(from.x, to.x, weight), lerp(from.y, to.y, weight), + lerp(from.z, to.z, weight)}; +} + +// Returns component-wise minimum +[[nodiscard]] constexpr Vector3 min(Vector3 const &a, + Vector3 const &b) noexcept { + return {std::min(a.x, b.x), std::min(a.y, b.y), std::min(a.z, b.z)}; +} + +[[nodiscard]] constexpr Vector3 min(Vector3 const &a, f32 const b) noexcept { + return {std::min(a.x, b), std::min(a.y, b), std::min(a.z, b)}; +} + +[[nodiscard]] constexpr Vector3 min(f32 const a, Vector3 const &b) noexcept { + return min(b, a); +} + +// Returns the vector with the smaller length +[[nodiscard]] constexpr Vector3 min_length(Vector3 const &a, + Vector3 const &b) noexcept { + return length_sq(a) < length_sq(b) ? a : b; +} + +// Returns a vector in the same direction whose length is bounded above by the given value. +[[nodiscard]] Vector3 min_length(Vector3 const &a, f32 const b) noexcept { + f32 const len_sq = length_sq(a); + + if (len_sq > b * b) { return a * (b / std::sqrt(len_sq)); } + else { return a; } +} + +[[nodiscard]] Vector3 min_length(f32 const a, Vector3 const &b) noexcept { + return min_length(b, a); +} + +// Returns component-wise maximum +[[nodiscard]] constexpr Vector3 max(Vector3 const &a, + Vector3 const &b) noexcept { + return {std::max(a.x, b.x), std::max(a.y, b.y), std::max(a.z, b.z)}; +} + +[[nodiscard]] constexpr Vector3 max(Vector3 const &a, f32 const b) noexcept { + return {std::max(a.x, b), std::max(a.y, b), std::max(a.z, b)}; +} + +[[nodiscard]] constexpr Vector3 max(f32 const a, Vector3 const &b) noexcept { + return max(b, a); +} + +// Returns the vector with the larger length +[[nodiscard]] constexpr Vector3 max_length(Vector3 const &a, + Vector3 const &b) noexcept { + return length_sq(a) > length_sq(b) ? a : b; +} + +// Returns a vector in the same direction whose length is bounded below by the given value. Returns the 0 vector if the vector is too small to be normalized. +[[nodiscard]] Vector3 max_length(Vector3 const &a, f32 const b) noexcept { + f32 const len_sq = length_sq(a); + + if (len_sq <= CMP_NORMALIZE_TOLERANCE2) { return Vector3(); } + else if (len_sq < b * b) { return a * (b / std::sqrt(len_sq)); } + else { return a; } +} + +[[nodiscard]] Vector3 max_length(f32 const a, Vector3 const &b) noexcept { + return max_length(b, a); +} + +// Clamps each component of x to the range [x_min, x_max]. Presupposes x_min <= x_max. +[[nodiscard]] constexpr Vector3 +clamp(Vector3 const &x, Vector3 const &x_min, Vector3 const &x_max) noexcept { + return max(x_min, min(x, x_max)); +} + +[[nodiscard]] constexpr Vector3 +clamp(Vector3 const &x, f32 const x_min, f32 const x_max) noexcept { + return max(x_min, min(x, x_max)); +} + +// Clamps the length of the vector to the range [x_min, x_max]. Presupposes x_min <= x_max. Returns the 0 vector if the vector is too small to be normalized. +[[nodiscard]] Vector3 +clamp_length(Vector3 const &v, f32 const x_min, f32 const x_max) noexcept { + f32 const len_sq = length_sq(v); + + if (len_sq <= CMP_NORMALIZE_TOLERANCE2) { return Vector3(); } + else if (len_sq < x_min * x_min) { return v * (x_min / std::sqrt(len_sq)); } + else if (len_sq > x_max * x_max) { return v * (x_max / std::sqrt(len_sq)); } + else { return v; } +} + +// Returns component-wise absolute value +[[nodiscard]] constexpr Vector3 abs(Vector3 const &v) noexcept { + return {abs(v.x), abs(v.y), abs(v.z)}; +} + +// Returns component-wise floor +[[nodiscard]] constexpr Vector3 floor(Vector3 const &v) noexcept { + return {floor(v.x), floor(v.y), floor(v.z)}; +} + +// Returns component-wise ceiling +[[nodiscard]] constexpr Vector3 ceil(Vector3 const &v) noexcept { + return {ceil(v.x), ceil(v.y), ceil(v.z)}; +} + +// Returns component-wise truncation +[[nodiscard]] constexpr Vector3 trunc(Vector3 const &v) noexcept { + return {trunc(v.x), trunc(v.y), trunc(v.z)}; +} + +// Returns component-wise round +[[nodiscard]] constexpr Vector3 round(Vector3 const &v) noexcept { + return {round(v.x), round(v.y), round(v.z)}; +} + +// Returns component-wise sign. Note that -0 still returns 0 +[[nodiscard]] constexpr Vector3 sign(Vector3 const &v) noexcept { + return {sign(v.x), sign(v.y), sign(v.z)}; +} + +// Returns true if the vectors are approximately equal +[[nodiscard]] constexpr bool approx_eq(Vector3 const &a, + Vector3 const &b) noexcept { + return distance_sq(a, b) < CMP_EPSILON2; +} + +// Returns cross product +[[nodiscard]] constexpr Vector3 cross(Vector3 const &a, + Vector3 const &b) noexcept { + return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, + a.x * b.y - a.y * b.x}; +} +} // namespace draco::math + export namespace std { - template<> struct formatter : formatter { - auto format(const draco::math::Vector3& v, format_context& ctx) const { - ctx.advance_to(format_to(ctx.out(), "{{")); - - for (draco::i32 i = 0; i < 3; ++i) { - if (i) ctx.advance_to(format_to(ctx.out(), ", ")); - ctx.advance_to(formatter::format(v[i], ctx)); - } - - return format_to(ctx.out(), "}}"); - } - }; -} \ No newline at end of file +template<> struct formatter : formatter { + auto format(draco::math::Vector3 const &v, format_context &ctx) const { + ctx.advance_to(format_to(ctx.out(), "{{")); + + for (draco::i32 i = 0; i < 3; ++i) { + if (i) { ctx.advance_to(format_to(ctx.out(), ", ")); } + ctx.advance_to(formatter::format(v[i], ctx)); + } + + return format_to(ctx.out(), "}}"); + } +}; +} // namespace std diff --git a/engine/native/core/math/vector4.cppm b/engine/native/core/math/vector4.cppm index d2ddcd40..331ac93f 100644 --- a/engine/native/core/math/vector4.cppm +++ b/engine/native/core/math/vector4.cppm @@ -6,9 +6,9 @@ module; #include "platform/simd.h" #if ARCH_X64 - #include + #include #elif ARCH_ARM64 - #include + #include #endif export module core.math.types:vector4; @@ -20,539 +20,520 @@ import core.defs; import core.stdtypes; export namespace draco::math { - // assertions - static_assert(sizeof(Vector4) == 16, "Vector4 must be 16 bytes"); - static_assert(alignof(Vector4) == 16, "Vector4 must be 16-byte aligned"); - static_assert(trivial, "Vector4 must be trivial"); - static_assert(std::is_standard_layout_v, "Vector4 must be standard layout"); - - // constructors - [[nodiscard]] constexpr Vector4::Vector4(const f32 n) noexcept - : x{n}, y{n}, z{n}, w{n} { } - - [[nodiscard]] constexpr Vector4::Vector4(const f32 x, const f32 y, const f32 z, const f32 w) noexcept - : x{x}, y{y}, z{z}, w{w} { } - - [[nodiscard]] constexpr Vector4::Vector4(const Vector2& xy) noexcept - : x{xy.x}, y{xy.y}, z{0.0f}, w{0.0f} { } - - [[nodiscard]] constexpr Vector4::Vector4(const Vector2& xy, const f32 z, const f32 w) noexcept - : x{xy.x}, y{xy.y}, z{z}, w{w} { } - - [[nodiscard]] constexpr Vector4::Vector4(const f32 x, const Vector2& yz, const f32 w) noexcept - : x{x}, y{yz.x}, z{yz.y}, w{w} { } - - [[nodiscard]] constexpr Vector4::Vector4(const f32 x, const f32 y, const Vector2& zw) noexcept - : x{x}, y{y}, z{zw.x}, w{zw.y} { } - - [[nodiscard]] constexpr Vector4::Vector4(const Vector2& xy, const Vector2& zw) noexcept - : x{xy.x}, y{xy.y}, z{zw.x}, w{zw.y} { } - - [[nodiscard]] constexpr Vector4::Vector4(const Vector3& xyz, const f32 w) noexcept - : x{xyz.x}, y{xyz.y}, z{xyz.z}, w{w} { } - - [[nodiscard]] constexpr Vector4::Vector4(const f32 x, const Vector3& yzw) noexcept - : x{x}, y{yzw.x}, z{yzw.y}, w{yzw.z} { } - - // static - [[nodiscard]] constexpr Vector4 Vector4::x_axis(const f32 x) noexcept { - return { x, 0.0f, 0.0f, 0.0f }; - } - - [[nodiscard]] constexpr Vector4 Vector4::y_axis(const f32 y) noexcept { - return { 0.0f, y, 0.0f, 0.0f }; - } - - [[nodiscard]] constexpr Vector4 Vector4::z_axis(const f32 z) noexcept { - return { 0.0f, 0.0f, z, 0.0f }; - } - - [[nodiscard]] constexpr Vector4 Vector4::w_axis(const f32 w) noexcept { - return { 0.0f, 0.0f, 0.0f, w }; - } - - // element access - [[nodiscard]] constexpr f32& Vector4::operator[](const i32 i) noexcept { - if consteval { - switch (i) { - case 0: return x; - case 1: return y; - case 2: return z; - default: - case 3: return w; - } - } else { return (&x)[i]; } - } - - [[nodiscard]] constexpr const f32& Vector4::operator[](const i32 i) const noexcept { - if consteval { - switch (i) { - case 0: return x; - case 1: return y; - case 2: return z; - default: - case 3: return w; - } - } else { return (&x)[i]; } - } - - // swizzle - [[nodiscard]] constexpr Vector2 Vector4::operator[](const i32 i0, const i32 i1) noexcept { - if consteval { - return { select(i0, x, y, z, w), select(i1, x, y, z, w) }; - } else { - return { (&x)[i0], (&x)[i1] }; - } - } - - [[nodiscard]] constexpr Vector2 Vector4::operator[](const i32 i0, const i32 i1) const noexcept { - if consteval { - return { select(i0, x, y, z, w), select(i1, x, y, z, w) }; - } else { - return { (&x)[i0], (&x)[i1] }; - } - } - - [[nodiscard]] constexpr Vector3 Vector4::operator[](const i32 i0, const i32 i1, const i32 i2) noexcept { - if consteval { - return { select(i0, x, y, z, w), select(i1, x, y, z, w), select(i2, x, y, z, w) }; - } else { - return { (&x)[i0], (&x)[i1], (&x)[i2] }; - } - } - - [[nodiscard]] constexpr Vector3 Vector4::operator[](const i32 i0, const i32 i1, const i32 i2) const noexcept { - if consteval { - return { select(i0, x, y, z, w), select(i1, x, y, z, w), select(i2, x, y, z, w) }; - } else { - return { (&x)[i0], (&x)[i1], (&x)[i2] }; - } - } - - [[nodiscard]] constexpr Vector4 Vector4::operator[](const i32 i0, const i32 i1, const i32 i2, const i32 i3) noexcept { - if consteval { - return { select(i0, x, y, z, w), select(i1, x, y, z, w), select(i2, x, y, z, w), select(i3, x, y, z, w) }; - } else { - return { (&x)[i0], (&x)[i1], (&x)[i2], (&x)[i3] }; - } - } - - [[nodiscard]] constexpr Vector4 Vector4::operator[](const i32 i0, const i32 i1, const i32 i2, const i32 i3) const noexcept { - if consteval { - return { select(i0, x, y, z, w), select(i1, x, y, z, w), select(i2, x, y, z, w), select(i3, x, y, z, w) }; - } else { - return { (&x)[i0], (&x)[i1], (&x)[i2], (&x)[i3] }; - } - } - - // operators - constexpr Vector4& Vector4::operator+=(const Vector4& other) noexcept { - x += other.x; - y += other.y; - z += other.z; - w += other.w; - return *this; - } - - constexpr Vector4& Vector4::operator+=(const f32 other) noexcept { - x += other; - y += other; - z += other; - w += other; - return *this; - } - - constexpr Vector4& Vector4::operator-=(const Vector4& other) noexcept { - x -= other.x; - y -= other.y; - z -= other.z; - w -= other.w; - return *this; - } - - constexpr Vector4& Vector4::operator-=(const f32 other) noexcept { - x -= other; - y -= other; - z -= other; - w -= other; - return *this; - } - - constexpr Vector4& Vector4::operator*=(const Vector4& other) noexcept { - x *= other.x; - y *= other.y; - z *= other.z; - w *= other.w; - return *this; - } - - constexpr Vector4& Vector4::operator*=(const f32 other) noexcept { - x *= other; - y *= other; - z *= other; - w *= other; - return *this; - } - - constexpr Vector4& Vector4::operator/=(const Vector4& other) noexcept { - x /= other.x; - y /= other.y; - z /= other.z; - w /= other.w; - return *this; - } - - constexpr Vector4& Vector4::operator/=(const f32 other) noexcept { - const f32 inv = 1.0f / other; - x *= inv; - y *= inv; - z *= inv; - w *= inv; - return *this; - } - - constexpr Vector4& Vector4::operator=(const f32 other) noexcept { - x = other; - y = other; - z = other; - w = other; - return *this; - } - - [[nodiscard]] constexpr Vector4 Vector4::operator+() const noexcept { - return { x, y, z, w }; - } - - [[nodiscard]] constexpr Vector4 Vector4::operator-() const noexcept { - return { -x, -y, -z, -w }; - } - - [[nodiscard]] constexpr Vector4 operator+(const Vector4& a, const Vector4& b) noexcept { - return { a.x+b.x, a.y+b.y, a.z+b.z, a.w+b.w }; - } - - [[nodiscard]] constexpr Vector4 operator+(const Vector4& a, const f32 b) noexcept { - return { a.x+b, a.y+b, a.z+b, a.w+b }; - } - - [[nodiscard]] constexpr Vector4 operator+(const f32 a, const Vector4& b) noexcept { - return b+a; - } - - [[nodiscard]] constexpr Vector4 operator-(const Vector4& a, const Vector4& b) noexcept { - return { a.x-b.x, a.y-b.y, a.z-b.z, a.w-b.w }; - } - - [[nodiscard]] constexpr Vector4 operator-(const Vector4& a, const f32 b) noexcept { - return { a.x-b, a.y-b, a.z-b, a.w-b }; - } - - [[nodiscard]] constexpr Vector4 operator-(const f32 a, const Vector4& b) noexcept { - return { a-b.x, a-b.y, a-b.z, a-b.w }; - } - - [[nodiscard]] constexpr Vector4 operator*(const Vector4& a, const Vector4& b) noexcept { - return { a.x*b.x, a.y*b.y, a.z*b.z, a.w*b.w }; - } - - [[nodiscard]] constexpr Vector4 operator*(const Vector4& a, const f32 b) noexcept { - return { a.x*b, a.y*b, a.z*b, a.w*b }; - } - - [[nodiscard]] constexpr Vector4 operator*(const f32 a, const Vector4& b) noexcept { - return b*a; - } - - [[nodiscard]] constexpr Vector4 operator/(const Vector4& a, const Vector4& b) noexcept { - return { a.x/b.x, a.y/b.y, a.z/b.z, a.w/b.w }; - } - - [[nodiscard]] constexpr Vector4 operator/(const Vector4& a, const f32 b) noexcept { - return a * (1.0f / b); - } - - [[nodiscard]] constexpr Vector4 operator/(const f32 a, const Vector4& b) noexcept { - return { a/b.x, a/b.y, a/b.z, a/b.w }; - } - - // functions - - // Returns dot product - [[nodiscard]] constexpr f32 dot(const Vector4& a, const Vector4& b) noexcept { - if !consteval { - #if ARCH_X64 - // There's only 4 floats, so SSE is what we will use. - // If there's a situation with multiple dot calls, we can setup a - // way to call 8 / 16 / 32 floats, but over-head could upset gains. - // Be sure it occurs commonly enough to matter here. - // Shuffle-first reduction worked best here. - __m128 va = _mm_load_ps(&a.x); - __m128 vb = _mm_load_ps(&b.x); - - __m128 m = _mm_mul_ps(va, vb); - - __m128 shuf = _mm_movehdup_ps(m); - __m128 sum = _mm_add_ps(m, shuf); - - shuf = _mm_movehl_ps(shuf, sum); - sum = _mm_add_ss(sum, shuf); - - return _mm_cvtss_f32(sum); - #elif ARCH_ARM64 - #error "ARM64 NEON support not yet implemented." - #endif - } - - return a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w; - } - - // Returns squared magnitude - [[nodiscard]] constexpr f32 length_sq(const Vector4& v) noexcept { - return dot(v, v); - } - - // Returns magnitude - [[nodiscard]] f32 length(const Vector4& v) noexcept { - return std::sqrt(length_sq(v)); - } - - // Return squared distance between two vectors - [[nodiscard]] constexpr f32 distance_sq(const Vector4& a, const Vector4& b) noexcept { - return length_sq(a - b); - } - - // Returns distance between two vectors - [[nodiscard]] f32 distance(const Vector4& a, const Vector4& b) noexcept { - return length(a - b); - } - - // Safe normalize, checks length - [[nodiscard]] Vector4 normalize(const Vector4& v) noexcept { - const f32 len = length(v); - - return (len > CMP_NORMALIZE_TOLERANCE) ? v / len : Vector4(); - } - - // Faster normalize, it presupposes vector has non-zero length - // TODO: add check that v is non-zero on debug builds - [[nodiscard]] Vector4 normalize_fast(const Vector4& v) noexcept { - return v / length(v); - } - - // Returns vector projected onto normal - [[nodiscard]] constexpr Vector4 project(const Vector4& vector, const Vector4& normal) noexcept { - return normal * (dot(vector, normal) / length_sq(normal)); - } - - // Returns a vector reflected off a plane defined by its normal - [[nodiscard]] constexpr Vector4 reflect(const Vector4& incoming, const Vector4& normal) noexcept { - return incoming - 2.0f * dot(incoming, normal) * normal; - } - - // Returns the angle between two vectors - [[nodiscard]] f32 angle(const Vector4& a, const Vector4& b) noexcept { - return std::acos(dot(a, b) / (length(a) * length(b))); - } - - // Returns linear interpolation between two vectors - [[nodiscard]] constexpr Vector4 lerp(const Vector4& from, const Vector4& to, const f32 weight) noexcept { - return { - lerp(from.x, to.x, weight), - lerp(from.y, to.y, weight), - lerp(from.z, to.z, weight), - lerp(from.w, to.w, weight) - }; - } - - // Returns component-wise minimum - [[nodiscard]] constexpr Vector4 min(const Vector4& a, const Vector4& b) noexcept { - return { - std::min(a.x, b.x), - std::min(a.y, b.y), - std::min(a.z, b.z), - std::min(a.w, b.w) - }; - } - - [[nodiscard]] constexpr Vector4 min(const Vector4& a, const f32 b) noexcept { - return { - std::min(a.x, b), - std::min(a.y, b), - std::min(a.z, b), - std::min(a.w, b) - }; - } - - [[nodiscard]] constexpr Vector4 min(const f32 a, const Vector4& b) noexcept { - return min(b, a); - } - - // Returns the vector with the smaller length - [[nodiscard]] constexpr Vector4 min_length(const Vector4& a, const Vector4& b) noexcept { - return length_sq(a) < length_sq(b) ? a : b; - } - - // Returns a vector in the same direction whose length is bounded above by the given value. - [[nodiscard]] Vector4 min_length(const Vector4& a, const f32 b) noexcept { - const f32 len_sq = length_sq(a); - - if (len_sq > b * b) { - return a * (b / std::sqrt(len_sq)); - } else { - return a; - } - } - - [[nodiscard]] Vector4 min_length(const f32 a, const Vector4& b) noexcept { - return min_length(b, a); - } - - // Returns component-wise maximum - [[nodiscard]] constexpr Vector4 max(const Vector4& a, const Vector4& b) noexcept { - return { - std::max(a.x, b.x), - std::max(a.y, b.y), - std::max(a.z, b.z), - std::max(a.w, b.w) - }; - } - - [[nodiscard]] constexpr Vector4 max(const Vector4& a, const f32 b) noexcept { - return { - std::max(a.x, b), - std::max(a.y, b), - std::max(a.z, b), - std::max(a.w, b) - }; - } - - [[nodiscard]] constexpr Vector4 max(const f32 a, const Vector4& b) noexcept { - return max(b, a); - } - - // Returns the vector with the larger length - [[nodiscard]] constexpr Vector4 max_length(const Vector4& a, const Vector4& b) noexcept { - return length_sq(a) > length_sq(b) ? a : b; - } - - // Returns a vector in the same direction whose length is bounded below by the given value. Returns the 0 vector if the vector is too small to be normalized. - [[nodiscard]] Vector4 max_length(const Vector4& a, const f32 b) noexcept { - const f32 len_sq = length_sq(a); - - if (len_sq <= CMP_NORMALIZE_TOLERANCE2) { - return Vector4(); - } else if (len_sq < b * b) { - return a * (b / std::sqrt(len_sq)); - } else { - return a; - } - } - - [[nodiscard]] Vector4 max_length(const f32 a, const Vector4& b) noexcept { - return max_length(b, a); - } - - // Clamps each component of x to the range [x_min, x_max]. Presupposes x_min <= x_max. - [[nodiscard]] constexpr Vector4 clamp(const Vector4& x, const Vector4& x_min, const Vector4& x_max) noexcept { - return max(x_min, min(x, x_max)); - } - - [[nodiscard]] constexpr Vector4 clamp(const Vector4& x, const f32 x_min, const f32 x_max) noexcept { - return max(x_min, min(x, x_max)); - } - - // Clamps the length of the vector to the range [x_min, x_max]. Presupposes x_min <= x_max. Returns the 0 vector if the vector is too small to be normalized. - [[nodiscard]] Vector4 clamp_length(const Vector4& v, const f32 x_min, const f32 x_max) noexcept { - const f32 len_sq = length_sq(v); - - if (len_sq <= CMP_NORMALIZE_TOLERANCE2) { - return Vector4(); - } else if (len_sq < x_min * x_min) { - return v * (x_min / std::sqrt(len_sq)); - } else if (len_sq > x_max * x_max) { - return v * (x_max / std::sqrt(len_sq)); - } else { - return v; - } - } - - // Returns component-wise absolute value - [[nodiscard]] constexpr Vector4 abs(const Vector4& v) noexcept { - return { - abs(v.x), - abs(v.y), - abs(v.z), - abs(v.w) - }; - } - - // Returns component-wise floor - [[nodiscard]] constexpr Vector4 floor(const Vector4& v) noexcept { - return { - floor(v.x), - floor(v.y), - floor(v.z), - floor(v.w) - }; - } - - // Returns component-wise ceiling - [[nodiscard]] constexpr Vector4 ceil(const Vector4& v) noexcept { - return { - ceil(v.x), - ceil(v.y), - ceil(v.z), - ceil(v.w) - }; - } - - // Returns component-wise truncation - [[nodiscard]] constexpr Vector4 trunc(const Vector4& v) noexcept { - return { - trunc(v.x), - trunc(v.y), - trunc(v.z), - trunc(v.w) - }; - } - - // Returns component-wise round - [[nodiscard]] constexpr Vector4 round(const Vector4& v) noexcept { - return { - round(v.x), - round(v.y), - round(v.z), - round(v.w) - }; - } - - // Returns component-wise sign. Note that -0 still returns 0 - [[nodiscard]] constexpr Vector4 sign(const Vector4& v) noexcept { - return { - sign(v.x), - sign(v.y), - sign(v.z), - sign(v.w) - }; - } - - // Returns true if the vectors are approximately equal - [[nodiscard]] constexpr bool approx_eq(const Vector4& a, const Vector4& b) noexcept { - return distance_sq(a, b) < CMP_EPSILON2; - } -} // namespace draco::math +// assertions +static_assert(sizeof(Vector4) == 16, "Vector4 must be 16 bytes"); +static_assert(alignof(Vector4) == 16, "Vector4 must be 16-byte aligned"); +static_assert(trivial, "Vector4 must be trivial"); +static_assert(std::is_standard_layout_v, + "Vector4 must be standard layout"); + +// constructors +[[nodiscard]] constexpr Vector4::Vector4(f32 const n) noexcept : + x{n}, y{n}, z{n}, w{n} { } + +[[nodiscard]] constexpr Vector4::Vector4(f32 const x, + f32 const y, + f32 const z, + f32 const w) noexcept : + x{x}, y{y}, z{z}, w{w} { } + +[[nodiscard]] constexpr Vector4::Vector4(Vector2 const &xy) noexcept : + x{xy.x}, y{xy.y}, z{0.0F}, w{0.0F} { } + +[[nodiscard]] constexpr Vector4::Vector4(Vector2 const &xy, + f32 const z, + f32 const w) noexcept : + x{xy.x}, y{xy.y}, z{z}, w{w} { } + +[[nodiscard]] constexpr Vector4::Vector4(f32 const x, + Vector2 const &yz, + f32 const w) noexcept : + x{x}, y{yz.x}, z{yz.y}, w{w} { } + +[[nodiscard]] constexpr Vector4::Vector4(f32 const x, + f32 const y, + Vector2 const &zw) noexcept : + x{x}, y{y}, z{zw.x}, w{zw.y} { } + +[[nodiscard]] constexpr Vector4::Vector4(Vector2 const &xy, + Vector2 const &zw) noexcept : + x{xy.x}, y{xy.y}, z{zw.x}, w{zw.y} { } + +[[nodiscard]] constexpr Vector4::Vector4(Vector3 const &xyz, + f32 const w) noexcept : + x{xyz.x}, y{xyz.y}, z{xyz.z}, w{w} { } + +[[nodiscard]] constexpr Vector4::Vector4(f32 const x, + Vector3 const &yzw) noexcept : + x{x}, y{yzw.x}, z{yzw.y}, w{yzw.z} { } + +// static +[[nodiscard]] constexpr Vector4 Vector4::x_axis(f32 const x) noexcept { + return {x, 0.0F, 0.0F, 0.0F}; +} -export namespace std { - template<> struct formatter : formatter { - auto format(const draco::math::Vector4& v, format_context& ctx) const { - ctx.advance_to(format_to(ctx.out(), "{{")); +[[nodiscard]] constexpr Vector4 Vector4::y_axis(f32 const y) noexcept { + return {0.0F, y, 0.0F, 0.0F}; +} + +[[nodiscard]] constexpr Vector4 Vector4::z_axis(f32 const z) noexcept { + return {0.0F, 0.0F, z, 0.0F}; +} + +[[nodiscard]] constexpr Vector4 Vector4::w_axis(f32 const w) noexcept { + return {0.0F, 0.0F, 0.0F, w}; +} + +// element access +[[nodiscard]] constexpr f32 &Vector4::operator[](i32 const i) noexcept { + if consteval { + switch (i) { + case 0: return x; + case 1: return y; + case 2: return z; + default: + case 3: return w; + } + } + else { return (&x)[i]; } +} + +[[nodiscard]] constexpr f32 const & +Vector4::operator[](i32 const i) const noexcept { + if consteval { + switch (i) { + case 0: return x; + case 1: return y; + case 2: return z; + default: + case 3: return w; + } + } + else { return (&x)[i]; } +} + +// swizzle +[[nodiscard]] constexpr Vector2 Vector4::operator[](i32 const i0, + i32 const i1) noexcept { + if consteval { return {select(i0, x, y, z, w), select(i1, x, y, z, w)}; } + else { return {(&x)[i0], (&x)[i1]}; } +} + +[[nodiscard]] constexpr Vector2 +Vector4::operator[](i32 const i0, i32 const i1) const noexcept { + if consteval { return {select(i0, x, y, z, w), select(i1, x, y, z, w)}; } + else { return {(&x)[i0], (&x)[i1]}; } +} + +[[nodiscard]] constexpr Vector3 +Vector4::operator[](i32 const i0, i32 const i1, i32 const i2) noexcept { + if consteval { + return {select(i0, x, y, z, w), select(i1, x, y, z, w), + select(i2, x, y, z, w)}; + } + else { return {(&x)[i0], (&x)[i1], (&x)[i2]}; } +} + +[[nodiscard]] constexpr Vector3 +Vector4::operator[](i32 const i0, i32 const i1, i32 const i2) const noexcept { + if consteval { + return {select(i0, x, y, z, w), select(i1, x, y, z, w), + select(i2, x, y, z, w)}; + } + else { return {(&x)[i0], (&x)[i1], (&x)[i2]}; } +} + +[[nodiscard]] constexpr Vector4 Vector4::operator[](i32 const i0, + i32 const i1, + i32 const i2, + i32 const i3) noexcept { + if consteval { + return {select(i0, x, y, z, w), select(i1, x, y, z, w), + select(i2, x, y, z, w), select(i3, x, y, z, w)}; + } + else { return {(&x)[i0], (&x)[i1], (&x)[i2], (&x)[i3]}; } +} + +[[nodiscard]] constexpr Vector4 Vector4::operator[]( + i32 const i0, i32 const i1, i32 const i2, i32 const i3 +) const noexcept { + if consteval { + return {select(i0, x, y, z, w), select(i1, x, y, z, w), + select(i2, x, y, z, w), select(i3, x, y, z, w)}; + } + else { return {(&x)[i0], (&x)[i1], (&x)[i2], (&x)[i3]}; } +} - for (draco::i32 i = 0; i < 4; ++i) { - if (i) ctx.advance_to(format_to(ctx.out(), ", ")); - ctx.advance_to(formatter::format(v[i], ctx)); - } +// operators +constexpr Vector4 &Vector4::operator+=(Vector4 const &other) noexcept { + x += other.x; + y += other.y; + z += other.z; + w += other.w; + return *this; +} + +constexpr Vector4 &Vector4::operator+=(f32 const other) noexcept { + x += other; + y += other; + z += other; + w += other; + return *this; +} + +constexpr Vector4 &Vector4::operator-=(Vector4 const &other) noexcept { + x -= other.x; + y -= other.y; + z -= other.z; + w -= other.w; + return *this; +} - return format_to(ctx.out(), "}}"); - } - }; +constexpr Vector4 &Vector4::operator-=(f32 const other) noexcept { + x -= other; + y -= other; + z -= other; + w -= other; + return *this; } + +constexpr Vector4 &Vector4::operator*=(Vector4 const &other) noexcept { + x *= other.x; + y *= other.y; + z *= other.z; + w *= other.w; + return *this; +} + +constexpr Vector4 &Vector4::operator*=(f32 const other) noexcept { + x *= other; + y *= other; + z *= other; + w *= other; + return *this; +} + +constexpr Vector4 &Vector4::operator/=(Vector4 const &other) noexcept { + x /= other.x; + y /= other.y; + z /= other.z; + w /= other.w; + return *this; +} + +constexpr Vector4 &Vector4::operator/=(f32 const other) noexcept { + f32 const inv = 1.0F / other; + x *= inv; + y *= inv; + z *= inv; + w *= inv; + return *this; +} + +constexpr Vector4 &Vector4::operator=(f32 const other) noexcept { + x = other; + y = other; + z = other; + w = other; + return *this; +} + +[[nodiscard]] constexpr Vector4 Vector4::operator+() const noexcept { + return {x, y, z, w}; +} + +[[nodiscard]] constexpr Vector4 Vector4::operator-() const noexcept { + return {-x, -y, -z, -w}; +} + +[[nodiscard]] constexpr Vector4 operator+(Vector4 const &a, + Vector4 const &b) noexcept { + return {a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w}; +} + +[[nodiscard]] constexpr Vector4 operator+(Vector4 const &a, + f32 const b) noexcept { + return {a.x + b, a.y + b, a.z + b, a.w + b}; +} + +[[nodiscard]] constexpr Vector4 operator+(f32 const a, + Vector4 const &b) noexcept { + return b + a; +} + +[[nodiscard]] constexpr Vector4 operator-(Vector4 const &a, + Vector4 const &b) noexcept { + return {a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w}; +} + +[[nodiscard]] constexpr Vector4 operator-(Vector4 const &a, + f32 const b) noexcept { + return {a.x - b, a.y - b, a.z - b, a.w - b}; +} + +[[nodiscard]] constexpr Vector4 operator-(f32 const a, + Vector4 const &b) noexcept { + return {a - b.x, a - b.y, a - b.z, a - b.w}; +} + +[[nodiscard]] constexpr Vector4 operator*(Vector4 const &a, + Vector4 const &b) noexcept { + return {a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w}; +} + +[[nodiscard]] constexpr Vector4 operator*(Vector4 const &a, + f32 const b) noexcept { + return {a.x * b, a.y * b, a.z * b, a.w * b}; +} + +[[nodiscard]] constexpr Vector4 operator*(f32 const a, + Vector4 const &b) noexcept { + return b * a; +} + +[[nodiscard]] constexpr Vector4 operator/(Vector4 const &a, + Vector4 const &b) noexcept { + return {a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w}; +} + +[[nodiscard]] constexpr Vector4 operator/(Vector4 const &a, + f32 const b) noexcept { + return a * (1.0F / b); +} + +[[nodiscard]] constexpr Vector4 operator/(f32 const a, + Vector4 const &b) noexcept { + return {a / b.x, a / b.y, a / b.z, a / b.w}; +} + +// functions + +// Returns dot product +[[nodiscard]] constexpr f32 dot(Vector4 const &a, Vector4 const &b) noexcept { + if !consteval { +#if ARCH_X64 + // There's only 4 floats, so SSE is what we will use. + // If there's a situation with multiple dot calls, we can setup a + // way to call 8 / 16 / 32 floats, but over-head could upset gains. + // Be sure it occurs commonly enough to matter here. + // Shuffle-first reduction worked best here. + __m128 va = _mm_load_ps(&a.x); + __m128 vb = _mm_load_ps(&b.x); + + __m128 m = _mm_mul_ps(va, vb); + + __m128 shuf = _mm_movehdup_ps(m); + __m128 sum = _mm_add_ps(m, shuf); + + shuf = _mm_movehl_ps(shuf, sum); + sum = _mm_add_ss(sum, shuf); + + return _mm_cvtss_f32(sum); +#elif ARCH_ARM64 + #error "ARM64 NEON support not yet implemented." +#endif + } + + return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; +} + +// Returns squared magnitude +[[nodiscard]] constexpr f32 length_sq(Vector4 const &v) noexcept { + return dot(v, v); +} + +// Returns magnitude +[[nodiscard]] f32 length(Vector4 const &v) noexcept { + return std::sqrt(length_sq(v)); +} + +// Return squared distance between two vectors +[[nodiscard]] constexpr f32 distance_sq(Vector4 const &a, + Vector4 const &b) noexcept { + return length_sq(a - b); +} + +// Returns distance between two vectors +[[nodiscard]] f32 distance(Vector4 const &a, Vector4 const &b) noexcept { + return length(a - b); +} + +// Safe normalize, checks length +[[nodiscard]] Vector4 normalize(Vector4 const &v) noexcept { + f32 const len = length(v); + + return (len > CMP_NORMALIZE_TOLERANCE) ? v / len : Vector4(); +} + +// Faster normalize, it presupposes vector has non-zero length +// TODO: add check that v is non-zero on debug builds +[[nodiscard]] Vector4 normalize_fast(Vector4 const &v) noexcept { + return v / length(v); +} + +// Returns vector projected onto normal +[[nodiscard]] constexpr Vector4 project(Vector4 const &vector, + Vector4 const &normal) noexcept { + return normal * (dot(vector, normal) / length_sq(normal)); +} + +// Returns a vector reflected off a plane defined by its normal +[[nodiscard]] constexpr Vector4 reflect(Vector4 const &incoming, + Vector4 const &normal) noexcept { + return incoming - 2.0F * dot(incoming, normal) * normal; +} + +// Returns the angle between two vectors +[[nodiscard]] f32 angle(Vector4 const &a, Vector4 const &b) noexcept { + return std::acos(dot(a, b) / (length(a) * length(b))); +} + +// Returns linear interpolation between two vectors +[[nodiscard]] constexpr Vector4 +lerp(Vector4 const &from, Vector4 const &to, f32 const weight) noexcept { + return {lerp(from.x, to.x, weight), lerp(from.y, to.y, weight), + lerp(from.z, to.z, weight), lerp(from.w, to.w, weight)}; +} + +// Returns component-wise minimum +[[nodiscard]] constexpr Vector4 min(Vector4 const &a, + Vector4 const &b) noexcept { + return {std::min(a.x, b.x), std::min(a.y, b.y), std::min(a.z, b.z), + std::min(a.w, b.w)}; +} + +[[nodiscard]] constexpr Vector4 min(Vector4 const &a, f32 const b) noexcept { + return {std::min(a.x, b), std::min(a.y, b), std::min(a.z, b), + std::min(a.w, b)}; +} + +[[nodiscard]] constexpr Vector4 min(f32 const a, Vector4 const &b) noexcept { + return min(b, a); +} + +// Returns the vector with the smaller length +[[nodiscard]] constexpr Vector4 min_length(Vector4 const &a, + Vector4 const &b) noexcept { + return length_sq(a) < length_sq(b) ? a : b; +} + +// Returns a vector in the same direction whose length is bounded above by the given value. +[[nodiscard]] Vector4 min_length(Vector4 const &a, f32 const b) noexcept { + f32 const len_sq = length_sq(a); + + if (len_sq > b * b) { return a * (b / std::sqrt(len_sq)); } + else { return a; } +} + +[[nodiscard]] Vector4 min_length(f32 const a, Vector4 const &b) noexcept { + return min_length(b, a); +} + +// Returns component-wise maximum +[[nodiscard]] constexpr Vector4 max(Vector4 const &a, + Vector4 const &b) noexcept { + return {std::max(a.x, b.x), std::max(a.y, b.y), std::max(a.z, b.z), + std::max(a.w, b.w)}; +} + +[[nodiscard]] constexpr Vector4 max(Vector4 const &a, f32 const b) noexcept { + return {std::max(a.x, b), std::max(a.y, b), std::max(a.z, b), + std::max(a.w, b)}; +} + +[[nodiscard]] constexpr Vector4 max(f32 const a, Vector4 const &b) noexcept { + return max(b, a); +} + +// Returns the vector with the larger length +[[nodiscard]] constexpr Vector4 max_length(Vector4 const &a, + Vector4 const &b) noexcept { + return length_sq(a) > length_sq(b) ? a : b; +} + +// Returns a vector in the same direction whose length is bounded below by the given value. Returns the 0 vector if the vector is too small to be normalized. +[[nodiscard]] Vector4 max_length(Vector4 const &a, f32 const b) noexcept { + f32 const len_sq = length_sq(a); + + if (len_sq <= CMP_NORMALIZE_TOLERANCE2) { return Vector4(); } + else if (len_sq < b * b) { return a * (b / std::sqrt(len_sq)); } + else { return a; } +} + +[[nodiscard]] Vector4 max_length(f32 const a, Vector4 const &b) noexcept { + return max_length(b, a); +} + +// Clamps each component of x to the range [x_min, x_max]. Presupposes x_min <= x_max. +[[nodiscard]] constexpr Vector4 +clamp(Vector4 const &x, Vector4 const &x_min, Vector4 const &x_max) noexcept { + return max(x_min, min(x, x_max)); +} + +[[nodiscard]] constexpr Vector4 +clamp(Vector4 const &x, f32 const x_min, f32 const x_max) noexcept { + return max(x_min, min(x, x_max)); +} + +// Clamps the length of the vector to the range [x_min, x_max]. Presupposes x_min <= x_max. Returns the 0 vector if the vector is too small to be normalized. +[[nodiscard]] Vector4 +clamp_length(Vector4 const &v, f32 const x_min, f32 const x_max) noexcept { + f32 const len_sq = length_sq(v); + + if (len_sq <= CMP_NORMALIZE_TOLERANCE2) { return Vector4(); } + else if (len_sq < x_min * x_min) { return v * (x_min / std::sqrt(len_sq)); } + else if (len_sq > x_max * x_max) { return v * (x_max / std::sqrt(len_sq)); } + else { return v; } +} + +// Returns component-wise absolute value +[[nodiscard]] constexpr Vector4 abs(Vector4 const &v) noexcept { + return {abs(v.x), abs(v.y), abs(v.z), abs(v.w)}; +} + +// Returns component-wise floor +[[nodiscard]] constexpr Vector4 floor(Vector4 const &v) noexcept { + return {floor(v.x), floor(v.y), floor(v.z), floor(v.w)}; +} + +// Returns component-wise ceiling +[[nodiscard]] constexpr Vector4 ceil(Vector4 const &v) noexcept { + return {ceil(v.x), ceil(v.y), ceil(v.z), ceil(v.w)}; +} + +// Returns component-wise truncation +[[nodiscard]] constexpr Vector4 trunc(Vector4 const &v) noexcept { + return {trunc(v.x), trunc(v.y), trunc(v.z), trunc(v.w)}; +} + +// Returns component-wise round +[[nodiscard]] constexpr Vector4 round(Vector4 const &v) noexcept { + return {round(v.x), round(v.y), round(v.z), round(v.w)}; +} + +// Returns component-wise sign. Note that -0 still returns 0 +[[nodiscard]] constexpr Vector4 sign(Vector4 const &v) noexcept { + return {sign(v.x), sign(v.y), sign(v.z), sign(v.w)}; +} + +// Returns true if the vectors are approximately equal +[[nodiscard]] constexpr bool approx_eq(Vector4 const &a, + Vector4 const &b) noexcept { + return distance_sq(a, b) < CMP_EPSILON2; +} +} // namespace draco::math + +export namespace std { +template<> struct formatter : formatter { + auto format(draco::math::Vector4 const &v, format_context &ctx) const { + ctx.advance_to(format_to(ctx.out(), "{{")); + + for (draco::i32 i = 0; i < 4; ++i) { + if (i) { ctx.advance_to(format_to(ctx.out(), ", ")); } + ctx.advance_to(formatter::format(v[i], ctx)); + } + + return format_to(ctx.out(), "}}"); + } +}; +} // namespace std diff --git a/engine/native/core/memory/allocator.cpp b/engine/native/core/memory/allocator.cpp index 6b7af67a..6999b4eb 100644 --- a/engine/native/core/memory/allocator.cpp +++ b/engine/native/core/memory/allocator.cpp @@ -5,34 +5,30 @@ module; module core.memory.allocator; import core.stdtypes; -namespace draco::memory -{ - Error nilAlloc( - Allocator alloc, - Slice *dst, - usize size, - usize align +namespace draco::memory { +Error nilAlloc( + Allocator alloc, + Slice *dst, + usize size, + usize align #ifdef DEBUG - , std::source_location loc + , + std::source_location loc #endif - ) - { - return Error::NotImplemented; - } +) { + return Error::NotImplemented; +} - Error nilFree(Allocator alloc, Slice block) - { - return Error::NotImplemented; - } +Error nilFree(Allocator alloc, Slice block) { + return Error::NotImplemented; +} - Error nilFreeAll(Allocator alloc) - { - return Error::NotImplemented; - } +Error nilFreeAll(Allocator alloc) { + return Error::NotImplemented; +} - void asAllocatorVoid(Allocator *dst, rawptr alloc, AllocatorVTbl *vtbl) - { - dst->allocatorData = (void*)alloc; - dst->vtbl = vtbl; - } +void asAllocatorVoid(Allocator *dst, rawptr alloc, AllocatorVTbl *vtbl) { + dst->allocatorData = (void *)alloc; + dst->vtbl = vtbl; } +} // namespace draco::memory diff --git a/engine/native/core/memory/allocator.cppm b/engine/native/core/memory/allocator.cppm index 4d70177e..1b27b8a4 100644 --- a/engine/native/core/memory/allocator.cppm +++ b/engine/native/core/memory/allocator.cppm @@ -6,96 +6,91 @@ export module core.memory.allocator; export import core.memory.slice; export import core.stdtypes; -export namespace draco::memory -{ - enum class Error - { - Okay, - OutOfMemory, - NotImplemented, - IllegalAddressRange, - Other, // This one shouldn't be needed. If you see it returned, make a - // new error. - }; +export namespace draco::memory { +enum class Error { + Okay, + OutOfMemory, + NotImplemented, + IllegalAddressRange, + Other, // This one shouldn't be needed. If you see it returned, make a + // new error. +}; - struct AllocatorVTbl; +struct AllocatorVTbl; - struct Allocator - { - AllocatorVTbl *vtbl; - rawptr allocatorData; - inline Error alloc( - Slice *dst, - usize size, - usize align -#ifdef DEBUG - , std::source_location loc = std::source_location::current() -#endif - ); - inline Error free(Slice block); - inline Error freeAll(); - }; - - struct AllocatorVTbl - { - using AllocFn = Error (*)( - Allocator alloc, - Slice *dst, - usize size, - usize align +struct Allocator { + AllocatorVTbl *vtbl; + rawptr allocatorData; + inline Error alloc( + Slice *dst, + usize size, + usize align #ifdef DEBUG - , std::source_location loc + , + std::source_location loc = std::source_location::current() #endif - ); - using FreeFn = Error (*)(Allocator alloc, Slice block); - using FreeAllFn = Error (*)(Allocator alloc); - AllocFn alloc; - FreeFn free; - FreeAllFn freeAll; - }; + ); + inline Error free(Slice block); + inline Error freeAll(); +}; - Error nilAlloc( +struct AllocatorVTbl { + using AllocFn = Error (*)( Allocator alloc, Slice *dst, usize size, usize align #ifdef DEBUG - , std::source_location loc + , + std::source_location loc #endif ); + using FreeFn = Error (*)(Allocator alloc, Slice block); + using FreeAllFn = Error (*)(Allocator alloc); + AllocFn alloc; + FreeFn free; + FreeAllFn freeAll; +}; - Error nilFree(Allocator alloc, Slice block); +Error nilAlloc( + Allocator alloc, + Slice *dst, + usize size, + usize align +#ifdef DEBUG + , + std::source_location loc +#endif +); + +Error nilFree(Allocator alloc, Slice block); - Error nilFreeAll(Allocator alloc); +Error nilFreeAll(Allocator alloc); - void asAllocatorVoid(Allocator *dst, rawptr alloc, AllocatorVTbl *vtbl); - inline Error Allocator::alloc( - Slice *dst, - usize size, - usize align +void asAllocatorVoid(Allocator *dst, rawptr alloc, AllocatorVTbl *vtbl); +inline Error Allocator::alloc( + Slice *dst, + usize size, + usize align #ifdef DEBUG - , std::source_location loc + , + std::source_location loc #endif - ) - { - return vtbl->alloc( - *this, - dst, - size, - align +) { + return vtbl->alloc( + *this, dst, size, align #ifdef DEBUG - , loc + , + loc #endif - ); - } + ); +} - inline Error Allocator::free(Slice block) - { - return vtbl->free(*this, block); - } +inline Error Allocator::free(Slice block) { + return vtbl->free(*this, block); +} - inline Error Allocator::freeAll() - { - return vtbl->freeAll(*this); - } +inline Error Allocator::freeAll() { + return vtbl->freeAll(*this); } +} // namespace draco::memory diff --git a/engine/native/core/memory/bumpAllocator.cpp b/engine/native/core/memory/bumpAllocator.cpp index f131ea76..ff538d1c 100644 --- a/engine/native/core/memory/bumpAllocator.cpp +++ b/engine/native/core/memory/bumpAllocator.cpp @@ -9,121 +9,102 @@ module; module core.memory.bumpAllocator; import core.stdtypes; -namespace draco::memory::bump -{ - void init( - BumpAllocator *alloc, - Allocator baseAlloc, - // one page by default on unix-like systems - usize minAllocRequest - ) - { - memset(alloc, 0, sizeof(BumpAllocator)); - alloc->base = baseAlloc; - alloc->minAllocRequest = minAllocRequest; - } +namespace draco::memory::bump { +void init(BumpAllocator *alloc, + Allocator baseAlloc, + // one page by default on unix-like systems + usize minAllocRequest) { + memset(alloc, 0, sizeof(BumpAllocator)); + alloc->base = baseAlloc; + alloc->minAllocRequest = minAllocRequest; +} - void deinit(BumpAllocator *alloc) - { - Node *lastNode; - Node *node = alloc->first; - while (node != nullptr) - { - lastNode = node; - node = node->next; - alloc->base.vtbl->free( - alloc->base, - { - .data = (void*)lastNode, - .size = lastNode->size + sizeof(Node), - } - ); - } +void deinit(BumpAllocator *alloc) { + Node *lastNode; + Node *node = alloc->first; + while (node != nullptr) { + lastNode = node; + node = node->next; + alloc->base.vtbl->free(alloc->base, + { + .data = (void *)lastNode, + .size = lastNode->size + sizeof(Node), + }); } +} - Error alloc( - Allocator alloc, - Slice *dst, - usize size, - usize align +Error alloc( + Allocator alloc, + Slice *dst, + usize size, + usize align #ifdef DEBUG - , std::source_location loc + , + std::source_location loc #endif - ) - { - Error err; - BumpAllocator *allocData = (BumpAllocator *)alloc.allocatorData; - uintptr alignMask = align - 1; - Node **lastNode; - Node **node = &(allocData->first); - usize pos = allocData->allocated; - usize oldPos = pos; - usize reqSize = size; - usize spillover = 0; - Slice newBlock; - uintptr currentPtr; - assert(std::popcount(align) == 1); +) { + Error err; + BumpAllocator *allocData = (BumpAllocator *)alloc.allocatorData; + uintptr alignMask = align - 1; + Node **lastNode; + Node **node = &(allocData->first); + usize pos = allocData->allocated; + usize oldPos = pos; + usize reqSize = size; + usize spillover = 0; + Slice newBlock; + uintptr currentPtr; + assert(std::popcount(align) == 1); + lastNode = node; + while (((*node) != nullptr) & (pos > 0)) { + oldPos = pos; + pos -= std::min((*node)->size, pos); lastNode = node; - while (((*node) != nullptr) & (pos > 0)) - { - oldPos = pos; - pos -= std::min((*node)->size, pos); - lastNode = node; - node = &((*node)->next); - } - assert(pos == 0); // fraudulent mark provided - currentPtr = ((uintptr)(*lastNode)) + sizeof(Node) + oldPos; - reqSize = size + ((align - (currentPtr & alignMask)) & alignMask); - if (!(*lastNode) || (reqSize > ((*lastNode)->size - oldPos))) - { - if (*lastNode) - { - spillover = ((*lastNode)->size - oldPos); - } - reqSize = (sizeof(Node) + size + alignMask) & ~alignMask; - err = allocData->base.vtbl->alloc( - allocData->base, - &newBlock, - std::max(allocData->minAllocRequest, reqSize), - std::max(alignof(Node), align) + node = &((*node)->next); + } + assert(pos == 0); // fraudulent mark provided + currentPtr = ((uintptr)(*lastNode)) + sizeof(Node) + oldPos; + reqSize = size + ((align - (currentPtr & alignMask)) & alignMask); + if (!(*lastNode) || (reqSize > ((*lastNode)->size - oldPos))) { + if (*lastNode) { spillover = ((*lastNode)->size - oldPos); } + reqSize = (sizeof(Node) + size + alignMask) & ~alignMask; + err = allocData->base.vtbl->alloc( + allocData->base, &newBlock, + std::max(allocData->minAllocRequest, reqSize), + std::max(alignof(Node), align) #ifdef DEBUG - , loc + , + loc #endif - ); - if (err != Error::Okay) - { - return Error::OutOfMemory; - } - (*node) = (Node *)newBlock.data; - (*node)->next = nullptr; - (*node)->size = newBlock.size - sizeof(Node); - pos = 0; - lastNode = node; - oldPos = 0; - } - currentPtr = ((uintptr)&((*lastNode)->data[oldPos])); - reqSize = size + ((align - (currentPtr & alignMask)) & alignMask); - currentPtr = (currentPtr + alignMask) & ~alignMask; - allocData->allocated += reqSize + spillover; - dst->data = (void*)currentPtr; - dst->size = size; - return Error::Okay; + ); + if (err != Error::Okay) { return Error::OutOfMemory; } + (*node) = (Node *)newBlock.data; + (*node)->next = nullptr; + (*node)->size = newBlock.size - sizeof(Node); + pos = 0; + lastNode = node; + oldPos = 0; } + currentPtr = ((uintptr) & ((*lastNode)->data[oldPos])); + reqSize = size + ((align - (currentPtr & alignMask)) & alignMask); + currentPtr = (currentPtr + alignMask) & ~alignMask; + allocData->allocated += reqSize + spillover; + dst->data = (void *)currentPtr; + dst->size = size; + return Error::Okay; +} - Error freeAll(Allocator alloc) - { - BumpAllocator *allocData = (BumpAllocator *)alloc.allocatorData; - allocData->allocated = 0; - return Error::Okay; - } +Error freeAll(Allocator alloc) { + BumpAllocator *allocData = (BumpAllocator *)alloc.allocatorData; + allocData->allocated = 0; + return Error::Okay; +} - usize saveMark(BumpAllocator *self) - { - return self->allocated; - } +usize saveMark(BumpAllocator *self) { + return self->allocated; +} - void resumeMark(BumpAllocator *self, usize mark) - { - self->allocated = mark; - } +void resumeMark(BumpAllocator *self, usize mark) { + self->allocated = mark; } +} // namespace draco::memory::bump diff --git a/engine/native/core/memory/bumpAllocator.cppm b/engine/native/core/memory/bumpAllocator.cppm index 630b9ece..21ce124c 100644 --- a/engine/native/core/memory/bumpAllocator.cppm +++ b/engine/native/core/memory/bumpAllocator.cppm @@ -8,59 +8,53 @@ export import core.memory.allocator; export import core.memory.slice; export import core.stdtypes; -export namespace draco::memory -{ - namespace bump - { - struct Node - { - Node *next; - usize size; - u8 data[]; - }; - - struct BumpAllocator - { - Allocator base; - Node *first; - usize minAllocRequest; - usize allocated; - }; - - void init( - BumpAllocator *alloc, - Allocator baseAlloc, - // one page by default on unix-like systems - usize minAllocRequest = (1 << 12) - ); - - void deinit(BumpAllocator *alloc); - - Error alloc( - Allocator alloc, - Slice *dst, - usize size, - usize align +export namespace draco::memory { +namespace bump { +struct Node { + Node *next; + usize size; + u8 data[]; +}; + +struct BumpAllocator { + Allocator base; + Node *first; + usize minAllocRequest; + usize allocated; +}; + +void init(BumpAllocator *alloc, + Allocator baseAlloc, + // one page by default on unix-like systems + usize minAllocRequest = (1 << 12)); + +void deinit(BumpAllocator *alloc); + +Error alloc( + Allocator alloc, + Slice *dst, + usize size, + usize align #ifdef DEBUG - , std::source_location loc + , + std::source_location loc #endif - ); +); - Error freeAll(Allocator alloc); +Error freeAll(Allocator alloc); - AllocatorVTbl bumpAllocatorVtbl = { - .alloc = alloc, - .free = nilFree, - .freeAll = freeAll, - }; +AllocatorVTbl bumpAllocatorVtbl = { + .alloc = alloc, + .free = nilFree, + .freeAll = freeAll, +}; - usize saveMark(BumpAllocator *self); +usize saveMark(BumpAllocator *self); - void resumeMark(BumpAllocator *self, usize mark); +void resumeMark(BumpAllocator *self, usize mark); - inline void asAllocator(Allocator *dst, BumpAllocator *alloc) - { - asAllocatorVoid(dst, (void*)alloc, &bumpAllocatorVtbl); - } - } +inline void asAllocator(Allocator *dst, BumpAllocator *alloc) { + asAllocatorVoid(dst, (void *)alloc, &bumpAllocatorVtbl); } +} // namespace bump +} // namespace draco::memory diff --git a/engine/native/core/memory/bumpAllocator.test.cpp b/engine/native/core/memory/bumpAllocator.test.cpp index fded289d..820d66e1 100644 --- a/engine/native/core/memory/bumpAllocator.test.cpp +++ b/engine/native/core/memory/bumpAllocator.test.cpp @@ -4,8 +4,7 @@ import core.memory; import core.stdtypes; -TEST_CASE("Bump allocator provides distinct pointers on allocation") -{ +TEST_CASE("Bump allocator provides distinct pointers on allocation") { using namespace draco::memory; bump::BumpAllocator bumpAlloc; Allocator alloc; @@ -25,8 +24,7 @@ TEST_CASE("Bump allocator provides distinct pointers on allocation") bump::deinit(&bumpAlloc); } -TEST_CASE("Bump allocator aligns pointers correctly") -{ +TEST_CASE("Bump allocator aligns pointers correctly") { using namespace draco::memory; bump::BumpAllocator bumpAlloc; Allocator alloc; @@ -46,10 +44,8 @@ TEST_CASE("Bump allocator aligns pointers correctly") bump::deinit(&bumpAlloc); } -TEST_CASE("Bump allocator data is well packed") -{ - struct Foo - { +TEST_CASE("Bump allocator data is well packed") { + struct Foo { draco::u32 a; draco::u64 b; }; @@ -70,17 +66,16 @@ TEST_CASE("Bump allocator data is well packed") REQUIRE(bumpAlloc.first->size > bumpAlloc.allocated); err = alloc.alloc(&bSlice, sizeof(draco::u64), alignof(Foo)); REQUIRE(err == Error::Okay); - a = (draco::u32*)aSlice.data; - b = (draco::u64*)bSlice.data; + a = (draco::u32 *)aSlice.data; + b = (draco::u64 *)bSlice.data; *a = 69; *b = 420; - REQUIRE(((Foo*)bumpAlloc.first->data)->a == 69); - REQUIRE(((Foo*)bumpAlloc.first->data)->b == 420); + REQUIRE(((Foo *)bumpAlloc.first->data)->a == 69); + REQUIRE(((Foo *)bumpAlloc.first->data)->b == 420); bump::deinit(&bumpAlloc); } -TEST_CASE("Bump allocator allocates second page when available") -{ +TEST_CASE("Bump allocator allocates second page when available") { using namespace draco::memory; bump::BumpAllocator bumpAlloc; Allocator alloc; @@ -100,8 +95,7 @@ TEST_CASE("Bump allocator allocates second page when available") bump::deinit(&bumpAlloc); } -TEST_CASE("Exact alignment") -{ +TEST_CASE("Exact alignment") { using namespace draco::memory; bump::BumpAllocator bumpAlloc; Allocator alloc; diff --git a/engine/native/core/memory/fixedAllocator.cpp b/engine/native/core/memory/fixedAllocator.cpp index 899f268a..a09d07ce 100644 --- a/engine/native/core/memory/fixedAllocator.cpp +++ b/engine/native/core/memory/fixedAllocator.cpp @@ -7,48 +7,42 @@ module; module core.memory.fixedAllocator; import core.stdtypes; -namespace draco::memory::fixed -{ - void init(FixedAllocator *alloc, Slice block) - { - alloc->buffer = (u8 *)block.data; - alloc->size = block.size; - alloc->allocated = false; - } +namespace draco::memory::fixed { +void init(FixedAllocator *alloc, Slice block) { + alloc->buffer = (u8 *)block.data; + alloc->size = block.size; + alloc->allocated = false; +} - Error alloc( - Allocator alloc, - Slice *dst, - usize size, - usize align +Error alloc( + Allocator alloc, + Slice *dst, + usize size, + usize align #ifdef DEBUG - , std::source_location loc + , + std::source_location loc #endif - ) - { - FixedAllocator *allocData = (FixedAllocator*)alloc.allocatorData; - usize alignMask = align - 1; - usize alignedSize = allocData->size - ( - (align - (((uintptr)allocData->buffer) & alignMask)) - & alignMask - ); - assert(std::popcount(align) == 1); - if (allocData->allocated | (alignedSize < size)) - { - return Error::OutOfMemory; - } - dst->data = (rawptr)( - ((uintptr)&(allocData->buffer[alignMask])) & ~alignMask - ); - dst->size = alignedSize; - allocData->allocated = true; - return Error::Okay; +) { + FixedAllocator *allocData = (FixedAllocator *)alloc.allocatorData; + usize alignMask = align - 1; + usize alignedSize = + allocData->size - + ((align - (((uintptr)allocData->buffer) & alignMask)) & alignMask); + assert(std::popcount(align) == 1); + if (allocData->allocated | (alignedSize < size)) { + return Error::OutOfMemory; } + dst->data = + (rawptr)(((uintptr) & (allocData->buffer[alignMask])) & ~alignMask); + dst->size = alignedSize; + allocData->allocated = true; + return Error::Okay; +} - Error freeAll(Allocator alloc) - { - FixedAllocator *allocData = (FixedAllocator*)alloc.allocatorData; - allocData->allocated = false; - return Error::Okay; - } +Error freeAll(Allocator alloc) { + FixedAllocator *allocData = (FixedAllocator *)alloc.allocatorData; + allocData->allocated = false; + return Error::Okay; } +} // namespace draco::memory::fixed diff --git a/engine/native/core/memory/fixedAllocator.cppm b/engine/native/core/memory/fixedAllocator.cppm index 63f5aebd..e1ba8cd6 100644 --- a/engine/native/core/memory/fixedAllocator.cppm +++ b/engine/native/core/memory/fixedAllocator.cppm @@ -8,40 +8,37 @@ export import core.memory.allocator; export import core.memory.slice; export import core.stdtypes; -export namespace draco::memory -{ - namespace fixed - { - struct FixedAllocator - { - u8 *buffer; - usize size; - bool allocated; - }; - - void init(FixedAllocator *alloc, Slice block); - - Error alloc( - Allocator alloc, - Slice *dst, - usize size, - usize align +export namespace draco::memory { +namespace fixed { +struct FixedAllocator { + u8 *buffer; + usize size; + bool allocated; +}; + +void init(FixedAllocator *alloc, Slice block); + +Error alloc( + Allocator alloc, + Slice *dst, + usize size, + usize align #ifdef DEBUG - , std::source_location loc + , + std::source_location loc #endif - ); +); - Error freeAll(Allocator alloc); +Error freeAll(Allocator alloc); - AllocatorVTbl fixedAllocatorVtbl = { - .alloc = alloc, - .free = nilFree, - .freeAll = freeAll, - }; +AllocatorVTbl fixedAllocatorVtbl = { + .alloc = alloc, + .free = nilFree, + .freeAll = freeAll, +}; - inline void asAllocator(Allocator *dst, FixedAllocator *alloc) - { - asAllocatorVoid(dst, (void*)alloc, &fixedAllocatorVtbl); - } - } +inline void asAllocator(Allocator *dst, FixedAllocator *alloc) { + asAllocatorVoid(dst, (void *)alloc, &fixedAllocatorVtbl); } +} // namespace fixed +} // namespace draco::memory diff --git a/engine/native/core/memory/fixedAllocator.test.cpp b/engine/native/core/memory/fixedAllocator.test.cpp index 83766123..d90078e3 100644 --- a/engine/native/core/memory/fixedAllocator.test.cpp +++ b/engine/native/core/memory/fixedAllocator.test.cpp @@ -4,14 +4,13 @@ import core.memory; import core.stdtypes; -TEST_CASE("Fixed allocator is correctly aligned") -{ +TEST_CASE("Fixed allocator is correctly aligned") { using namespace draco::memory; alignas(8) draco::u8 buffer[1024]; fixed::FixedAllocator fixedAlloc; Allocator alloc; Slice block; - fixed::init(&fixedAlloc, { .data = buffer, .size = 1024 }); + fixed::init(&fixedAlloc, {.data = buffer, .size = 1024}); fixed::asAllocator(&alloc, &fixedAlloc); alloc.alloc(&block, 512, 16); REQUIRE((((uintptr_t)block.data) & 15) == 0); diff --git a/engine/native/core/memory/handle.cppm b/engine/native/core/memory/handle.cppm index 00ff70d7..561c31e0 100644 --- a/engine/native/core/memory/handle.cppm +++ b/engine/native/core/memory/handle.cppm @@ -3,53 +3,45 @@ export module core.memory.handle; import core.stdtypes; import core.math.constants; -export namespace draco::core::memory -{ - // Packed generation handle: - // [ 16 bits generation | 16 bits index ] - template - struct Handle - { - u32 value = math::UINT32_MAX_VAL; - - static constexpr u32 INVALID = math::UINT32_MAX_VAL; - - constexpr Handle() = default; - constexpr explicit Handle(u32 v) : value(v) {} - - constexpr u16 index() const - { - return static_cast(value & 0xFFFF); - } - - constexpr u16 generation() const - { - return static_cast(value >> 16); - } - - constexpr explicit operator bool() const - { - return value != INVALID; - } - - constexpr bool operator==(const Handle& other) const - { - return value == other.value; - } - - constexpr bool operator!=(const Handle& other) const - { - return value != other.value; - } - - static constexpr Handle invalid() - { - return Handle{ INVALID }; - } - - static constexpr Handle make(u16 index, u16 generation) - { - return Handle{(static_cast(generation) << 16) | static_cast(index)}; - } - }; -} +export namespace draco::core::memory { +// Packed generation handle: +// [ 16 bits generation | 16 bits index ] +template +struct Handle { + u32 value = math::UINT32_MAX_VAL; + + static constexpr u32 INVALID = math::UINT32_MAX_VAL; + + constexpr Handle() = default; + constexpr explicit Handle(u32 v) : value(v) { } + + constexpr u16 index() const { + return static_cast(value & 0xffff); + } + + constexpr u16 generation() const { + return static_cast(value >> 16); + } + + constexpr explicit operator bool() const { + return value != INVALID; + } + + constexpr bool operator==(Handle const &other) const { + return value == other.value; + } + + constexpr bool operator!=(Handle const &other) const { + return value != other.value; + } + + static constexpr Handle invalid() { + return Handle{INVALID}; + } + + static constexpr Handle make(u16 index, u16 generation) { + return Handle{(static_cast(generation) << 16) | + static_cast(index)}; + } +}; +} // namespace draco::core::memory diff --git a/engine/native/core/memory/handle_registry.cppm b/engine/native/core/memory/handle_registry.cppm index 6545d427..a58c58d5 100644 --- a/engine/native/core/memory/handle_registry.cppm +++ b/engine/native/core/memory/handle_registry.cppm @@ -3,47 +3,39 @@ export module core.memory.handle_registry; import core.memory.slot_array; import core.memory.handle; -export namespace draco::core::memory -{ - // Manager layer so other subsystems don't touch raw storage logic - - template - class HandleRegistry - { - public: - using HandleType = Handle; - - HandleType create(const T& value) - { - return storage.create(value); - } - - bool valid(HandleType h) const - { - return storage.valid(h); - } - - T* get(HandleType h) - { - return storage.get(h); - } - - const T* get(HandleType h) const - { - return storage.get(h); - } - - void destroy(HandleType h) - { - storage.destroy(h); - } - - SlotArray& internal() - { - return storage; - } - - private: - SlotArray storage; - }; -} +export namespace draco::core::memory { +// Manager layer so other subsystems don't touch raw storage logic + +template +class HandleRegistry { + public: + using HandleType = Handle; + + HandleType create(T const &value) { + return storage.create(value); + } + + bool valid(HandleType h) const { + return storage.valid(h); + } + + T *get(HandleType h) { + return storage.get(h); + } + + T const *get(HandleType h) const { + return storage.get(h); + } + + void destroy(HandleType h) { + storage.destroy(h); + } + + SlotArray &internal() { + return storage; + } + + private: + SlotArray storage; +}; +} // namespace draco::core::memory diff --git a/engine/native/core/memory/pageAllocator.cpp b/engine/native/core/memory/pageAllocator.cpp index de82bede..760bceed 100644 --- a/engine/native/core/memory/pageAllocator.cpp +++ b/engine/native/core/memory/pageAllocator.cpp @@ -2,132 +2,105 @@ module; #include #ifdef __unix__ -#include -#include + #include + #include #endif #ifdef _WIN32 -#include -#include + #include + #include #endif module core.memory.pageAllocator; import core.stdtypes; -namespace draco::memory::page -{ +namespace draco::memory::page { #ifdef __unix__ - Error alloc( - Allocator alloc, - Slice *dst, - usize size, - usize align -#ifdef DEBUG - , std::source_location loc -#endif - ) - { - int pageSizeSub1 = getpagesize() - 1; - // Coderabbit, this is for a 64-bit machine with 48-bit addressing, - // if this overflows, the request was never going to fit into - // memory to begin with. - usize reqSize = (size + (pageSizeSub1)) & (~pageSizeSub1); - rawptr ptr = mmap( - nullptr, - reqSize, - PROT_READ | PROT_WRITE, - MAP_ANONYMOUS | MAP_PRIVATE, - -1, - 0 - ); - if (((ptrdiff)ptr) == -1) - { - return Error::OutOfMemory; - } - dst->data = ptr; - dst->size = reqSize; - return Error::Okay; - } +Error alloc( + Allocator alloc, + Slice *dst, + usize size, + usize align + #ifdef DEBUG + , + std::source_location loc + #endif +) { + int pageSizeSub1 = getpagesize() - 1; + // Coderabbit, this is for a 64-bit machine with 48-bit addressing, + // if this overflows, the request was never going to fit into + // memory to begin with. + usize reqSize = (size + (pageSizeSub1)) & (~pageSizeSub1); + rawptr ptr = mmap(nullptr, reqSize, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + if (((ptrdiff)ptr) == -1) { return Error::OutOfMemory; } + dst->data = ptr; + dst->size = reqSize; + return Error::Okay; +} - Error free(Allocator alloc, Slice block) - { - return munmap(block.data, block.size) ? - Error::IllegalAddressRange : - Error::Okay; - } +Error free(Allocator alloc, Slice block) { + return munmap(block.data, block.size) ? Error::IllegalAddressRange + : Error::Okay; +} #endif #ifdef _WIN32 - Error alloc( - Allocator alloc, - Slice *dst, - usize size, - usize align -#ifdef DEBUG - , std::source_location loc -#endif - ) - { - SYSTEM_INFO sysinfo; - usize pageSizeSub1; - usize reqSize; - rawptr ptr; - GetSystemInfo(&sysinfo); - pageSizeSub1 = (usize)(sysinfo.dwAllocationGranularity - 1); - // Coderabbit, this is for a 64-bit machine with 48-bit addressing, - // if this overflows, the request was never going to fit into - // memory to begin with. - reqSize = (size + (pageSizeSub1)) & (~pageSizeSub1); - ptr = VirtualAlloc( - nullptr, - reqSize, - MEM_COMMIT | MEM_RESERVE, - PAGE_READWRITE - ); - if (ptr == nullptr) - { - return Error::OutOfMemory; - } - dst->data = ptr; - dst->size = reqSize; - return Error::Okay; - } +Error alloc( + Allocator alloc, + Slice *dst, + usize size, + usize align + #ifdef DEBUG + , + std::source_location loc + #endif +) { + SYSTEM_INFO sysinfo; + usize pageSizeSub1; + usize reqSize; + rawptr ptr; + GetSystemInfo(&sysinfo); + pageSizeSub1 = (usize)(sysinfo.dwAllocationGranularity - 1); + // Coderabbit, this is for a 64-bit machine with 48-bit addressing, + // if this overflows, the request was never going to fit into + // memory to begin with. + reqSize = (size + (pageSizeSub1)) & (~pageSizeSub1); + ptr = VirtualAlloc(nullptr, reqSize, MEM_COMMIT | MEM_RESERVE, + PAGE_READWRITE); + if (ptr == nullptr) { return Error::OutOfMemory; } + dst->data = ptr; + dst->size = reqSize; + return Error::Okay; +} - Error allocLargePages( - Allocator alloc, - Slice *dst, - usize size, - usize align -#ifdef DEBUG - , std::source_location loc -#endif - ) - { - usize pageSize = GetLargePageMinimum(); - usize pageSizeSub1 = (pageSize ? pageSize : (4 * 1024)) - 1; - // Coderabbit, this is for a 64-bit machine with 48-bit addressing, - // if this overflows, the request was never going to fit into - // memory to begin with. - usize reqSize = (size + (pageSizeSub1)) & (~pageSizeSub1); - rawptr ptr; - ptr = VirtualAlloc( - nullptr, - reqSize, - MEM_COMMIT | MEM_RESERVE | MEM_LARGE_PAGES, - PAGE_READWRITE - ); - if (ptr == nullptr) - { - return Error::OutOfMemory; - } - dst->data = ptr; - dst->size = reqSize; - return Error::Okay; - } +Error allocLargePages( + Allocator alloc, + Slice *dst, + usize size, + usize align + #ifdef DEBUG + , + std::source_location loc + #endif +) { + usize pageSize = GetLargePageMinimum(); + usize pageSizeSub1 = (pageSize ? pageSize : (4 * 1024)) - 1; + // Coderabbit, this is for a 64-bit machine with 48-bit addressing, + // if this overflows, the request was never going to fit into + // memory to begin with. + usize reqSize = (size + (pageSizeSub1)) & (~pageSizeSub1); + rawptr ptr; + ptr = VirtualAlloc(nullptr, reqSize, + MEM_COMMIT | MEM_RESERVE | MEM_LARGE_PAGES, + PAGE_READWRITE); + if (ptr == nullptr) { return Error::OutOfMemory; } + dst->data = ptr; + dst->size = reqSize; + return Error::Okay; +} - Error free(Allocator alloc, Slice block) - { - return VirtualFree(block.data, 0, MEM_RELEASE) ? - Error::Okay : - Error::IllegalAddressRange; - } -#endif +Error free(Allocator alloc, Slice block) { + return VirtualFree(block.data, 0, MEM_RELEASE) ? Error::Okay + : Error::IllegalAddressRange; } +#endif +} // namespace draco::memory::page diff --git a/engine/native/core/memory/pageAllocator.cppm b/engine/native/core/memory/pageAllocator.cppm index 6a8ee2ea..d04a3ddf 100644 --- a/engine/native/core/memory/pageAllocator.cppm +++ b/engine/native/core/memory/pageAllocator.cppm @@ -7,30 +7,29 @@ export import core.memory.allocator; export import core.memory.slice; export import core.stdtypes; -export namespace draco::memory -{ - namespace page - { - Error alloc( - Allocator alloc, - Slice *dst, - usize size, - usize align +export namespace draco::memory { +namespace page { +Error alloc( + Allocator alloc, + Slice *dst, + usize size, + usize align #ifdef DEBUG - , std::source_location loc + , + std::source_location loc #endif - ); +); - Error free(Allocator alloc, Slice block); +Error free(Allocator alloc, Slice block); - AllocatorVTbl pageAllocatorVtbl = { - .alloc = alloc, - .free = free, - .freeAll = nilFreeAll, - }; - Allocator pageAllocator = { - .vtbl = &pageAllocatorVtbl, - .allocatorData = nullptr, - }; - } -} +AllocatorVTbl pageAllocatorVtbl = { + .alloc = alloc, + .free = free, + .freeAll = nilFreeAll, +}; +Allocator pageAllocator = { + .vtbl = &pageAllocatorVtbl, + .allocatorData = nullptr, +}; +} // namespace page +} // namespace draco::memory diff --git a/engine/native/core/memory/slice.cppm b/engine/native/core/memory/slice.cppm index 552e78fc..f5bd952e 100644 --- a/engine/native/core/memory/slice.cppm +++ b/engine/native/core/memory/slice.cppm @@ -3,11 +3,9 @@ module; export module core.memory.slice; export import core.stdtypes; -export namespace draco::memory -{ - struct Slice - { - rawptr data; - usize size; - }; -} +export namespace draco::memory { +struct Slice { + rawptr data; + usize size; +}; +} // namespace draco::memory diff --git a/engine/native/core/memory/slot_array.cppm b/engine/native/core/memory/slot_array.cppm index 22ee5209..af41a03c 100644 --- a/engine/native/core/memory/slot_array.cppm +++ b/engine/native/core/memory/slot_array.cppm @@ -7,89 +7,74 @@ export module core.memory.slot_array; import core.stdtypes; import core.memory.handle; -export namespace draco::core::memory -{ - template - struct Slot - { - T value{}; - u32 generation = 0; - bool alive = false; - }; - - template - class SlotArray - { - public: - using Handle = Handle; - - Handle create(const T& value) - { - u32 idx; - - if (!free_list.empty()) - { - idx = free_list.back(); - free_list.pop_back(); - } - else - { - idx = static_cast(slots.size()); - slots.push_back({}); - } - - Slot& slot = slots[idx]; - - slot.value = value; - slot.alive = true; - - return Handle::make(idx, slot.generation); - } - - bool valid(Handle h) const - { - u32 i = h.index(); - - return i < slots.size() - && slots[i].alive - && slots[i].generation == h.generation(); - } - - T* get(Handle h) - { - if (!valid(h)) - return nullptr; - - return &slots[h.index()].value; - } - - const T* get(Handle h) const - { - if (!valid(h)) - return nullptr; - - return &slots[h.index()].value; - } - - void destroy(Handle h) - { - if (!valid(h)) - return; - - auto& s = slots[h.index()]; - - s.alive = false; - s.generation++; // Invalidate all old handles - free_list.push_back(h.index()); - } - - const std::vector>& raw() const - { - return slots; - } - - private: - std::vector> slots; - std::vector free_list; - }; -} +export namespace draco::core::memory { +template +struct Slot { + T value{}; + u32 generation = 0; + bool alive = false; +}; + +template +class SlotArray { + public: + using Handle = Handle; + + Handle create(T const &value) { + u32 idx; + + if (!free_list.empty()) { + idx = free_list.back(); + free_list.pop_back(); + } + else { + idx = static_cast(slots.size()); + slots.push_back({}); + } + + Slot &slot = slots[idx]; + + slot.value = value; + slot.alive = true; + + return Handle::make(idx, slot.generation); + } + + bool valid(Handle h) const { + u32 i = h.index(); + + return i < slots.size() && slots[i].alive && + slots[i].generation == h.generation(); + } + + T *get(Handle h) { + if (!valid(h)) { return nullptr; } + + return &slots[h.index()].value; + } + + T const *get(Handle h) const { + if (!valid(h)) { return nullptr; } + + return &slots[h.index()].value; + } + + void destroy(Handle h) { + if (!valid(h)) { return; } + + auto &s = slots[h.index()]; + + s.alive = false; + s.generation++; // Invalidate all old handles + free_list.push_back(h.index()); + } + + std::vector> const &raw() const { + return slots; + } + + private: + std::vector> slots; + std::vector free_list; +}; +} // namespace draco::core::memory diff --git a/engine/native/core/memory/trackingAllocator.cpp b/engine/native/core/memory/trackingAllocator.cpp index 4240450b..6e0143fa 100644 --- a/engine/native/core/memory/trackingAllocator.cpp +++ b/engine/native/core/memory/trackingAllocator.cpp @@ -7,137 +7,112 @@ module; module core.memory.trackingAllocator; import core.memory.slice; -namespace draco::memory::tracking -{ - void init(TrackingAllocator *alloc, Allocator baseAlloc) - { - alloc->base = baseAlloc; - alloc->nodes = nullptr; - } +namespace draco::memory::tracking { +void init(TrackingAllocator *alloc, Allocator baseAlloc) { + alloc->base = baseAlloc; + alloc->nodes = nullptr; +} - Error alloc( - Allocator alloc, - Slice *dst, - usize size, - usize align +Error alloc( + Allocator alloc, + Slice *dst, + usize size, + usize align #ifdef DEBUG - , std::source_location loc + , + std::source_location loc #endif - ) - { - TrackingAllocator *allocData = (TrackingAllocator *)alloc.allocatorData; - usize reqAlign = std::max(align, alignof(Node)); - usize alignMask = reqAlign - 1; - usize reqSize = (size + sizeof(Node) + alignMask) & ~alignMask; - Slice tmpDst; - Node *node; - Error err = allocData->base.vtbl->alloc( - allocData->base, - &tmpDst, - reqSize, - reqAlign +) { + TrackingAllocator *allocData = (TrackingAllocator *)alloc.allocatorData; + usize reqAlign = std::max(align, alignof(Node)); + usize alignMask = reqAlign - 1; + usize reqSize = (size + sizeof(Node) + alignMask) & ~alignMask; + Slice tmpDst; + Node *node; + Error err = allocData->base.vtbl->alloc( + allocData->base, &tmpDst, reqSize, reqAlign #ifdef DEBUG - , loc + , + loc #endif - ); - if (err != Error::Okay) { return err; } - node = (Node *)&(((u8 *)tmpDst.data)[tmpDst.size - sizeof(Node)]); - memset(node, 0, sizeof(Node)); - if (allocData->nodes) - { - allocData->nodes->prev = node; - } - node->next = allocData->nodes; - allocData->nodes = node; - tmpDst.size -= sizeof(Node); - node->details.data = tmpDst; + ); + if (err != Error::Okay) { return err; } + node = (Node *)&(((u8 *)tmpDst.data)[tmpDst.size - sizeof(Node)]); + memset(node, 0, sizeof(Node)); + if (allocData->nodes) { allocData->nodes->prev = node; } + node->next = allocData->nodes; + allocData->nodes = node; + tmpDst.size -= sizeof(Node); + node->details.data = tmpDst; #ifdef DEBUG - node->details.loc = loc; + node->details.loc = loc; #endif - *dst = tmpDst; - return Error::Okay; - } + *dst = tmpDst; + return Error::Okay; +} - Error free(Allocator alloc, Slice block) - { - TrackingAllocator *allocData = (TrackingAllocator *)alloc.allocatorData; - Node *node = allocData->nodes; - Node *prev; - Node *next; - Error err; - while (node && (node->details.data.data != block.data)) - { - node = node->next; - } - block.size += sizeof(Node); - err = allocData->base.vtbl->free(allocData->base, block); - switch (err) - { - case Error::Okay: - if (node) - { - prev = node->prev; - next = node->next; - prev->next = next; - if (next) - { - next->prev = prev; - } - memset(node, 0, sizeof(Node)); - } - [[fallthrough]]; - default: - return err; +Error free(Allocator alloc, Slice block) { + TrackingAllocator *allocData = (TrackingAllocator *)alloc.allocatorData; + Node *node = allocData->nodes; + Node *prev; + Node *next; + Error err; + while (node && (node->details.data.data != block.data)) { + node = node->next; + } + block.size += sizeof(Node); + err = allocData->base.vtbl->free(allocData->base, block); + switch (err) { + case Error::Okay: + if (node) { + prev = node->prev; + next = node->next; + prev->next = next; + if (next) { next->prev = prev; } + memset(node, 0, sizeof(Node)); } + [[fallthrough]]; + default: return err; } +} - Error freeAll(Allocator alloc) - { - TrackingAllocator *allocData = (TrackingAllocator *)alloc.allocatorData; - Error err = allocData->base.vtbl->freeAll(allocData->base); - switch (err) - { - case Error::Okay: - allocData->nodes = nullptr; - [[fallthrough]]; - default: - return err; - } +Error freeAll(Allocator alloc) { + TrackingAllocator *allocData = (TrackingAllocator *)alloc.allocatorData; + Error err = allocData->base.vtbl->freeAll(allocData->base); + switch (err) { + case Error::Okay: allocData->nodes = nullptr; [[fallthrough]]; + default: return err; } +} - void getAnalytics(TrackingAllocator alloc, Analytics *analytics) - { - Node *node = alloc.nodes; - memset(analytics, 0, sizeof(Analytics)); - while (node != nullptr) - { - analytics->activeAllocationsCount += 1; - analytics->totalAllocatedBytes += node->details.data.size; - node = node->next; - } +void getAnalytics(TrackingAllocator alloc, Analytics *analytics) { + Node *node = alloc.nodes; + memset(analytics, 0, sizeof(Analytics)); + while (node != nullptr) { + analytics->activeAllocationsCount += 1; + analytics->totalAllocatedBytes += node->details.data.size; + node = node->next; } +} - size_t getActiveAllocations( - TrackingAllocator alloc, - size_t detailsCount, - AllocationDetails *details // nullable - ) - { - size_t ret = 0; - Node *node = alloc.nodes; - if (details) - { - while ((node != nullptr) & (ret < detailsCount)) - { - details[ret] = node->details; - node = node->next; - ret += 1; - } - } - while (node != nullptr) { - node = node->next; +size_t getActiveAllocations( + TrackingAllocator alloc, + size_t detailsCount, + AllocationDetails *details // nullable +) { + size_t ret = 0; + Node *node = alloc.nodes; + if (details) { + while ((node != nullptr) & (ret < detailsCount)) { + details[ret] = node->details; + node = node->next; ret += 1; } - return ret; } -} \ No newline at end of file + while (node != nullptr) { + node = node->next; + ret += 1; + } + return ret; +} +} // namespace draco::memory::tracking diff --git a/engine/native/core/memory/trackingAllocator.cppm b/engine/native/core/memory/trackingAllocator.cppm index 349e14e8..b39b8d9f 100644 --- a/engine/native/core/memory/trackingAllocator.cppm +++ b/engine/native/core/memory/trackingAllocator.cppm @@ -6,68 +6,60 @@ export module core.memory.trackingAllocator; export import core.memory.allocator; export import core.memory.slice; -export namespace draco::memory -{ - namespace tracking - { - struct Analytics - { - usize totalAllocatedBytes; - usize activeAllocationsCount; - }; +export namespace draco::memory { +namespace tracking { +struct Analytics { + usize totalAllocatedBytes; + usize activeAllocationsCount; +}; // TODO(Victor Sohier): Set this up to handle stack traces when clang finally // supports them - struct AllocationDetails - { - Slice data; +struct AllocationDetails { + Slice data; #ifdef DEBUG - std::source_location loc; + std::source_location loc; #endif - }; +}; - struct Node - { - Node *prev; - Node *next; - AllocationDetails details; - }; +struct Node { + Node *prev; + Node *next; + AllocationDetails details; +}; - struct TrackingAllocator - { - Allocator base; - Node *nodes; - }; +struct TrackingAllocator { + Allocator base; + Node *nodes; +}; - void init(TrackingAllocator *alloc, Allocator baseAlloc); +void init(TrackingAllocator *alloc, Allocator baseAlloc); - Error alloc( - Allocator alloc, - Slice *dst, - usize size, - usize align +Error alloc( + Allocator alloc, + Slice *dst, + usize size, + usize align #ifdef DEBUG - , std::source_location loc + , + std::source_location loc #endif - ); - Error free(Allocator alloc, Slice block); - Error freeAll(Allocator alloc); - void getAnalytics(TrackingAllocator alloc, Analytics *analytics); - usize getActiveAllocations( - TrackingAllocator alloc, - usize detailsCount, - AllocationDetails *details - ); +); +Error free(Allocator alloc, Slice block); +Error freeAll(Allocator alloc); +void getAnalytics(TrackingAllocator alloc, Analytics *analytics); +usize getActiveAllocations(TrackingAllocator alloc, + usize detailsCount, + AllocationDetails *details); - AllocatorVTbl trackingAllocatorVtbl = { - .alloc = alloc, - .free = free, - .freeAll = freeAll, - }; - - inline void asAllocator(Allocator *dst, TrackingAllocator *alloc) - { - asAllocatorVoid(dst, (void*)alloc, &trackingAllocatorVtbl); - } - } +AllocatorVTbl trackingAllocatorVtbl = { + .alloc = alloc, + .free = free, + .freeAll = freeAll, +}; + +inline void asAllocator(Allocator *dst, TrackingAllocator *alloc) { + asAllocatorVoid(dst, (void *)alloc, &trackingAllocatorVtbl); } +} // namespace tracking +} // namespace draco::memory diff --git a/engine/native/core/memory/trackingAllocator.test.cpp b/engine/native/core/memory/trackingAllocator.test.cpp index 2a1ece42..0ab65438 100644 --- a/engine/native/core/memory/trackingAllocator.test.cpp +++ b/engine/native/core/memory/trackingAllocator.test.cpp @@ -5,8 +5,7 @@ import core.memory; import core.memory.slice; import core.stdtypes; -TEST_CASE("Tracking allocator basic functions") -{ +TEST_CASE("Tracking allocator basic functions") { using namespace draco::memory; bump::BumpAllocator bumpAlloc; tracking::TrackingAllocator trackingAlloc; @@ -32,7 +31,8 @@ TEST_CASE("Tracking allocator basic functions") alloc.free(bSlice); REQUIRE(tracking::getActiveAllocations(trackingAlloc, 3, details) == 3); REQUIRE(details[2].data.data == aSlice.data); - REQUIRE(details[1].data.data == bSlice.data); // with a freeing allocator this line shouldn't exist + REQUIRE(details[1].data.data == + bSlice.data); // with a freeing allocator this line shouldn't exist REQUIRE(details[0].data.data == cSlice.data); tracking::getAnalytics(trackingAlloc, &analytics); REQUIRE(analytics.totalAllocatedBytes >= 9); diff --git a/engine/native/input/input.cpp b/engine/native/input/input.cpp index 3cf8bf83..ff1f10c1 100644 --- a/engine/native/input/input.cpp +++ b/engine/native/input/input.cpp @@ -6,94 +6,77 @@ module input; import core.stdtypes; -namespace draco::input -{ - // Static globals - static bool g_keys[256]{}; - static bool g_mouse_captured; - static f32 g_mouse_dx = 0; - static f32 g_mouse_dy = 0; - - static Key map_sdl_key(SDL_Keycode k) - { - switch (k) - { - case SDLK_W: return Key::W; - case SDLK_A: return Key::A; - case SDLK_S: return Key::S; - case SDLK_D: return Key::D; - case SDLK_SPACE: return Key::Space; - case SDLK_LCTRL: return Key::Ctrl; - case SDLK_LSHIFT: return Key::Shift; - case SDLK_ESCAPE: return Key::Escape; - default: return Key::Invalid; - } - } - - void begin_frame() - { - g_mouse_dx = 0; - g_mouse_dy = 0; - } - - void end_frame() - { - } - - void set_key(Key key, bool down) - { - if (key == Key::Invalid) return; - - g_keys[(u16)key] = down; - } - - bool is_down(Key key) - { - return g_keys[(u16)key]; - } - - void process_event(const SDL_Event& e) - { - switch (e.type) - { - case SDL_EVENT_KEY_DOWN: - set_key(map_sdl_key(e.key.key), true); - break; - - case SDL_EVENT_KEY_UP: - set_key(map_sdl_key(e.key.key), false); - break; - - case SDL_EVENT_MOUSE_MOTION: - set_mouse_delta((f32)e.motion.xrel, (f32)e.motion.yrel); - break; - } - } - - void set_mouse_captured(SDL_Window* window, bool enabled) - { - g_mouse_captured = enabled; - - SDL_SetWindowRelativeMouseMode(window, enabled); - SDL_SetWindowMouseGrab(window, enabled); - } - - bool is_mouse_captured() - { - return g_mouse_captured; - } - - void set_mouse_delta(f32 dx, f32 dy) - { - g_mouse_dx += dx; - g_mouse_dy += dy; - } - - f32 get_mouse_dx() { - return g_mouse_dx; - } - - f32 get_mouse_dy() { - return g_mouse_dy; - } -} \ No newline at end of file +namespace draco::input { +// Static globals +static bool g_keys[256]{}; +static bool g_mouse_captured; +static f32 g_mouse_dx = 0; +static f32 g_mouse_dy = 0; + +static Key map_sdl_key(SDL_Keycode k) { + switch (k) { + case SDLK_W: return Key::W; + case SDLK_A: return Key::A; + case SDLK_S: return Key::S; + case SDLK_D: return Key::D; + case SDLK_SPACE: return Key::Space; + case SDLK_LCTRL: return Key::Ctrl; + case SDLK_LSHIFT: return Key::Shift; + case SDLK_ESCAPE: return Key::Escape; + default: return Key::Invalid; + } +} + +void begin_frame() { + g_mouse_dx = 0; + g_mouse_dy = 0; +} + +void end_frame() { } + +void set_key(Key key, bool down) { + if (key == Key::Invalid) { return; } + + g_keys[(u16)key] = down; +} + +bool is_down(Key key) { + return g_keys[(u16)key]; +} + +void process_event(SDL_Event const &e) { + switch (e.type) { + case SDL_EVENT_KEY_DOWN: set_key(map_sdl_key(e.key.key), true); break; + + case SDL_EVENT_KEY_UP: set_key(map_sdl_key(e.key.key), false); break; + + case SDL_EVENT_MOUSE_MOTION: + set_mouse_delta((f32)e.motion.xrel, (f32)e.motion.yrel); + break; + } +} + +void set_mouse_captured(SDL_Window *window, bool enabled) { + g_mouse_captured = enabled; + + SDL_SetWindowRelativeMouseMode(window, enabled); + SDL_SetWindowMouseGrab(window, enabled); +} + +bool is_mouse_captured() { + return g_mouse_captured; +} + +void set_mouse_delta(f32 dx, f32 dy) { + g_mouse_dx += dx; + g_mouse_dy += dy; +} + +f32 get_mouse_dx() { + return g_mouse_dx; +} + +f32 get_mouse_dy() { + return g_mouse_dy; +} +} // namespace draco::input diff --git a/engine/native/input/input.cppm b/engine/native/input/input.cppm index 7ce81a75..c2de1e64 100644 --- a/engine/native/input/input.cppm +++ b/engine/native/input/input.cppm @@ -7,32 +7,34 @@ export module input; import core.stdtypes; -export namespace draco::input -{ - enum class Key : u16 - { - // TODO: A small set of keys should be okay for now but this needs to be updated later as per our needs - W, A, S, D, - Space, Ctrl, - Shift, - Escape, - Invalid - }; - - // Note: This isn't the same as RHI - void begin_frame(); - void end_frame(); - - void set_key(Key key, bool down); - bool is_down(Key key); - - void process_event(const SDL_Event& e); - - void set_mouse_captured(SDL_Window* window, bool enabled); - bool is_mouse_captured(); - - void set_mouse_delta(f32 dx, f32 dy); - - f32 get_mouse_dx(); - f32 get_mouse_dy(); -} \ No newline at end of file +export namespace draco::input { +enum class Key : u16 { + // TODO: A small set of keys should be okay for now but this needs to be updated later as per our needs + W, + A, + S, + D, + Space, + Ctrl, + Shift, + Escape, + Invalid +}; + +// Note: This isn't the same as RHI +void begin_frame(); +void end_frame(); + +void set_key(Key key, bool down); +bool is_down(Key key); + +void process_event(SDL_Event const &e); + +void set_mouse_captured(SDL_Window *window, bool enabled); +bool is_mouse_captured(); + +void set_mouse_delta(f32 dx, f32 dy); + +f32 get_mouse_dx(); +f32 get_mouse_dy(); +} // namespace draco::input diff --git a/engine/native/main/main.cpp b/engine/native/main/main.cpp index 85419af9..e6972f3e 100644 --- a/engine/native/main/main.cpp +++ b/engine/native/main/main.cpp @@ -12,203 +12,241 @@ import platform; import scene; import rendering; -int main(int argc, char* argv[]) -{ - if (!SDL_Init(SDL_INIT_VIDEO)) { - std::println("SDL init failed: {}", SDL_GetError()); - return -1; - } +int main(int argc, char *argv[]) { + if (!SDL_Init(SDL_INIT_VIDEO)) { + std::println("SDL init failed: {}", SDL_GetError()); + return -1; + } + + SDL_Window *window = + SDL_CreateWindow("Draconic Engine", 1280, 720, SDL_WINDOW_RESIZABLE); + + if (!window) { + std::println("Failed to create window: {}", SDL_GetError()); + SDL_Quit(); + return -1; + } + + draco::input::set_mouse_captured(window, true); + + auto handles = draco::platform::get_native_handles(window); + + if (!handles.valid) { + std::println("Failed to get native handles"); + SDL_DestroyWindow(window); + SDL_Quit(); + return -1; + } + + if (!draco::rendering::rhi::init(handles.ndt, handles.nwh, handles.type, + 1280, 720)) { + std::println("RHI init failed"); + SDL_DestroyWindow(window); + SDL_Quit(); + return -1; + } + + draco::rendering::renderer::init(1280, 720); + + auto cube_mesh = draco::rendering::mesh::create_cube(); + auto plane_mesh = draco::rendering::mesh::create_plane(5.0F); + auto sphere_mesh = draco::rendering::mesh::create_sphere(24, 16); + auto cylinder_mesh = draco::rendering::mesh::create_cylinder(24, 2.0F); + auto capsule_mesh = draco::rendering::mesh::create_capsule(24, 12, 2.0F); + + auto img = draco::core::io::image_loader::load_image("test.png"); + + draco::rendering::rhi::TextureHandle tex = + draco::rendering::rhi::InvalidTexture; + + if (img.is_valid) { + tex = draco::rendering::rhi::create_texture(img.pixels.data(), + img.width, img.height); + } + + auto s_texColor = draco::rendering::rhi::create_uniform( + "s_texColor", draco::rendering::rhi::UniformType::Sampler + ); + + auto vs = draco::core::io::filesystem::load_binary("vs.bin"); + auto fs = draco::core::io::filesystem::load_binary("fs.bin"); + + auto vs_quad = draco::core::io::filesystem::load_binary("vs_quad.bin"); + auto fs_quad = draco::core::io::filesystem::load_binary("fs_quad.bin"); + + if (vs.empty() || fs.empty() || vs_quad.empty() || fs_quad.empty()) { + std::println("Shader load failed"); + draco::rendering::rhi::shutdown(); + SDL_DestroyWindow(window); + SDL_Quit(); + return -1; + } + + auto vsh = + draco::rendering::rhi::create_shader(vs.data(), (draco::u32)vs.size()); + auto fsh = + draco::rendering::rhi::create_shader(fs.data(), (draco::u32)fs.size()); + + auto vsh_quad = + draco::rendering::rhi::create_shader(vs_quad.data(), + (draco::u32)vs_quad.size()); + auto fsh_quad = + draco::rendering::rhi::create_shader(fs_quad.data(), + (draco::u32)fs_quad.size()); + + auto pipeline = draco::rendering::rhi::create_pipeline( + {vsh, fsh, + draco::rendering::rhi::PipelineState::WriteRGB | + draco::rendering::rhi::PipelineState::WriteAlpha | + draco::rendering::rhi::PipelineState::MSAA, + draco::rendering::rhi::BlendMode::None, + draco::rendering::rhi::DepthTest::Less, + draco::rendering::rhi::CullMode::CCW, true} + ); + + auto pipeline_quad = draco::rendering::rhi::create_pipeline( + {vsh_quad, fsh_quad, + draco::rendering::rhi::PipelineState::WriteRGB | + draco::rendering::rhi::PipelineState::WriteAlpha | + draco::rendering::rhi::PipelineState::MSAA, + draco::rendering::rhi::BlendMode::Alpha, + draco::rendering::rhi::DepthTest::None, + draco::rendering::rhi::CullMode::None, true} + ); + + draco::rendering::quad_renderer::QuadRenderer quad_renderer; + quad_renderer.init(pipeline_quad); + + draco::scene::CameraController camera; + camera.init(); + + auto u_tint = draco::rendering::rhi::create_uniform( + "u_tint", draco::rendering::rhi::UniformType::Vec4 + ); + auto u_offset = draco::rendering::rhi::create_uniform( + "u_offset", draco::rendering::rhi::UniformType::Vec4 + ); + + draco::rendering::rhi::register_uniform( + draco::rendering::rhi::hash_uniform("u_tint"), u_tint + ); + + draco::rendering::rhi::register_uniform( + draco::rendering::rhi::hash_uniform("u_offset"), u_offset + ); + + draco::f32 tint[4] = {1, 1, 1, 1}; + draco::f32 offset[4] = {0, 0, 0, 0}; + + bool running = true; + bool mouse_captured = true; + + draco::rendering::material::Material mat{}; + mat.pipeline = pipeline; + mat.texture = tex; + mat.sampler = s_texColor; + + mat.uniforms.push_back({.name_hash = + draco::rendering::rhi::hash_uniform("u_tint"), + .data = tint, + .count = 1}); + + mat.uniforms.push_back({.name_hash = + draco::rendering::rhi::hash_uniform("u_offset"), + .data = offset, + .count = 1}); + + draco::scene::Scene scene; + + scene.renderables.push_back({cube_mesh, draco::math::make_transform(), + mat}); + scene.renderables.push_back({plane_mesh, draco::math::make_transform(), + mat}); + scene.renderables.push_back({sphere_mesh, draco::math::make_transform(), + mat}); + scene.renderables.push_back({cylinder_mesh, draco::math::make_transform(), + mat}); + scene.renderables.push_back({capsule_mesh, draco::math::make_transform(), + mat}); + + draco::math::set_position(scene.renderables[0].transform, -12.0F, 0.0F, + 0.0F); + draco::math::set_position(scene.renderables[1].transform, -6.0F, 0.0F, + 0.0F); + draco::math::set_position(scene.renderables[2].transform, 0.0F, 0.0F, 0.0F); + draco::math::set_position(scene.renderables[3].transform, 6.0F, 0.0F, 0.0F); + draco::math::set_position(scene.renderables[4].transform, 12.0F, 0.0F, + 0.0F); + + draco::math::set_rotation(scene.renderables[1].transform, -bx::kPiHalf, + 0.0F, 0.0F); + + while (running) { + static draco::u64 last = SDL_GetTicks(); + draco::u64 now = SDL_GetTicks(); + draco::f32 dt = (now - last) / 1000.0F; + last = now; + + SDL_Event e; + draco::input::begin_frame(); + + while (SDL_PollEvent(&e)) { + if (e.type == SDL_EVENT_QUIT) { running = false; } + + if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) { + mouse_captured = !mouse_captured; + draco::input::set_mouse_captured(window, mouse_captured); + } + + draco::input::process_event(e); + } + + int w, h; + SDL_GetWindowSize(window, &w, &h); + + if (w <= 0 || h <= 0) { continue; } + + draco::rendering::rhi::resize((draco::u16)w, (draco::u16)h); + draco::rendering::renderer::resize((draco::u16)w, (draco::u16)h); + + camera.update(dt); + auto cam = camera.get_camera(); - SDL_Window* window = SDL_CreateWindow( - "Draconic Engine", - 1280, 720, - SDL_WINDOW_RESIZABLE - ); + draco::rendering::renderer::begin_frame(cam); - if (!window) { - std::println("Failed to create window: {}", SDL_GetError()); - SDL_Quit(); - return -1; - } + for (auto const &renderable : scene.renderables) { + draco::rendering::renderer::submit_renderable(renderable.transform, + renderable.material, + renderable.mesh); + } - draco::input::set_mouse_captured(window, true); + quad_renderer.begin(); + + static draco::f32 quad_base_x = 400.0F; + static draco::f32 quad_base_y = 300.0F; - auto handles = draco::platform::get_native_handles(window); + for (int i = 0; i < 50; i++) { + draco::rendering::quad_renderer::QuadCommand q{}; - if (!handles.valid) { - std::println("Failed to get native handles"); - SDL_DestroyWindow(window); - SDL_Quit(); - return -1; - } + q.texture = tex; + q.color = 0xffff'ffff; + q.x = quad_base_x + std::sin(SDL_GetTicks() * 0.001F + i) * 50.0F; + q.y = quad_base_y + i * 6.0F; + q.width = 50.0F; + q.height = 50.0F; + q.rotation = SDL_GetTicks() * 0.001F; - if (!draco::rendering::rhi::init(handles.ndt, handles.nwh, handles.type, 1280, 720)) { - std::println("RHI init failed"); - SDL_DestroyWindow(window); - SDL_Quit(); - return -1; - } + quad_renderer.submit(q); + } - draco::rendering::renderer::init(1280, 720); + draco::rendering::renderer::submit_ui(quad_renderer); - auto cube_mesh = draco::rendering::mesh::create_cube(); - auto plane_mesh = draco::rendering::mesh::create_plane(5.0f); - auto sphere_mesh = draco::rendering::mesh::create_sphere(24, 16); - auto cylinder_mesh = draco::rendering::mesh::create_cylinder(24, 2.0f); - auto capsule_mesh = draco::rendering::mesh::create_capsule(24, 12, 2.0f); + draco::rendering::renderer::end_frame(); + } - auto img = draco::core::io::image_loader::load_image("test.png"); + draco::rendering::rhi::shutdown(); + SDL_DestroyWindow(window); + SDL_Quit(); - draco::rendering::rhi::TextureHandle tex = draco::rendering::rhi::InvalidTexture; - - if (img.is_valid) { - tex = draco::rendering::rhi::create_texture(img.pixels.data(), img.width, img.height); - } - - auto s_texColor = draco::rendering::rhi::create_uniform("s_texColor", draco::rendering::rhi::UniformType::Sampler); - - auto vs = draco::core::io::filesystem::load_binary("vs.bin"); - auto fs = draco::core::io::filesystem::load_binary("fs.bin"); - - auto vs_quad = draco::core::io::filesystem::load_binary("vs_quad.bin"); - auto fs_quad = draco::core::io::filesystem::load_binary("fs_quad.bin"); - - if (vs.empty() || fs.empty() || vs_quad.empty() || fs_quad.empty()) { - std::println("Shader load failed"); - draco::rendering::rhi::shutdown(); - SDL_DestroyWindow(window); - SDL_Quit(); - return -1; - } - - auto vsh = draco::rendering::rhi::create_shader(vs.data(), (draco::u32)vs.size()); - auto fsh = draco::rendering::rhi::create_shader(fs.data(), (draco::u32)fs.size()); - - auto vsh_quad = draco::rendering::rhi::create_shader(vs_quad.data(), (draco::u32)vs_quad.size()); - auto fsh_quad = draco::rendering::rhi::create_shader(fs_quad.data(), (draco::u32)fs_quad.size()); - - auto pipeline = draco::rendering::rhi::create_pipeline({vsh, fsh, draco::rendering::rhi::PipelineState::WriteRGB | draco::rendering::rhi::PipelineState::WriteAlpha | draco::rendering::rhi::PipelineState::MSAA, draco::rendering::rhi::BlendMode::None, draco::rendering::rhi::DepthTest::Less, draco::rendering::rhi::CullMode::CCW, true}); - - auto pipeline_quad = draco::rendering::rhi::create_pipeline({vsh_quad, fsh_quad, draco::rendering::rhi::PipelineState::WriteRGB | draco::rendering::rhi::PipelineState::WriteAlpha | draco::rendering::rhi::PipelineState::MSAA, draco::rendering::rhi::BlendMode::Alpha, draco::rendering::rhi::DepthTest::None, draco::rendering::rhi::CullMode::None, true}); - - draco::rendering::quad_renderer::QuadRenderer quad_renderer; - quad_renderer.init(pipeline_quad); - - draco::scene::CameraController camera; - camera.init(); - - auto u_tint = draco::rendering::rhi::create_uniform("u_tint", draco::rendering::rhi::UniformType::Vec4); - auto u_offset = draco::rendering::rhi::create_uniform("u_offset", draco::rendering::rhi::UniformType::Vec4); - - draco::rendering::rhi::register_uniform(draco::rendering::rhi::hash_uniform("u_tint"), u_tint); - - draco::rendering::rhi::register_uniform(draco::rendering::rhi::hash_uniform("u_offset"), u_offset); - - draco::f32 tint[4] = {1,1,1,1}; - draco::f32 offset[4] = {0,0,0,0}; - - bool running = true; - bool mouse_captured = true; - - draco::rendering::material::Material mat{}; - mat.pipeline = pipeline; - mat.texture = tex; - mat.sampler = s_texColor; - - mat.uniforms.push_back({.name_hash = draco::rendering::rhi::hash_uniform("u_tint"), .data = tint, .count = 1}); - - mat.uniforms.push_back({.name_hash = draco::rendering::rhi::hash_uniform("u_offset"), .data = offset, .count = 1}); - - draco::scene::Scene scene; - - scene.renderables.push_back({cube_mesh, draco::math::make_transform(), mat}); - scene.renderables.push_back({plane_mesh, draco::math::make_transform(), mat}); - scene.renderables.push_back({sphere_mesh, draco::math::make_transform(), mat}); - scene.renderables.push_back({cylinder_mesh, draco::math::make_transform(), mat}); - scene.renderables.push_back({capsule_mesh, draco::math::make_transform(), mat}); - - draco::math::set_position(scene.renderables[0].transform, -12.0f, 0.0f, 0.0f); - draco::math::set_position(scene.renderables[1].transform, -6.0f, 0.0f, 0.0f); - draco::math::set_position(scene.renderables[2].transform, 0.0f, 0.0f, 0.0f); - draco::math::set_position(scene.renderables[3].transform, 6.0f, 0.0f, 0.0f); - draco::math::set_position(scene.renderables[4].transform, 12.0f, 0.0f, 0.0f); - - draco::math::set_rotation(scene.renderables[1].transform, -bx::kPiHalf, 0.0f, 0.0f); - - while (running) - { - static draco::u64 last = SDL_GetTicks(); - draco::u64 now = SDL_GetTicks(); - draco::f32 dt = (now - last) / 1000.0f; - last = now; - - SDL_Event e; - draco::input::begin_frame(); - - while (SDL_PollEvent(&e)) - { - if (e.type == SDL_EVENT_QUIT) - running = false; - - if (e.type == SDL_EVENT_KEY_DOWN && - e.key.key == SDLK_ESCAPE) - { - mouse_captured = !mouse_captured; - draco::input::set_mouse_captured(window, mouse_captured); - } - - draco::input::process_event(e); - } - - int w, h; - SDL_GetWindowSize(window, &w, &h); - - if (w <= 0 || h <= 0) - { - continue; - } - - draco::rendering::rhi::resize((draco::u16)w, (draco::u16)h); - draco::rendering::renderer::resize((draco::u16)w, (draco::u16)h); - - camera.update(dt); - auto cam = camera.get_camera(); - - draco::rendering::renderer::begin_frame(cam); - - for (const auto& renderable : scene.renderables) - { - draco::rendering::renderer::submit_renderable(renderable.transform, renderable.material, renderable.mesh); - } - - quad_renderer.begin(); - - static draco::f32 quad_base_x = 400.0f; - static draco::f32 quad_base_y = 300.0f; - - for (int i = 0; i < 50; i++) - { - draco::rendering::quad_renderer::QuadCommand q{}; - - q.texture = tex; - q.color = 0xffffffff; - q.x = quad_base_x + std::sin(SDL_GetTicks() * 0.001f + i) * 50.0f; - q.y = quad_base_y + i * 6.0f; - q.width = 50.0f; - q.height = 50.0f; - q.rotation = SDL_GetTicks() * 0.001f; - - quad_renderer.submit(q); - } - - draco::rendering::renderer::submit_ui(quad_renderer); - - draco::rendering::renderer::end_frame(); - } - - draco::rendering::rhi::shutdown(); - SDL_DestroyWindow(window); - SDL_Quit(); - - return 0; + return 0; } diff --git a/engine/native/platform/cpu/cpu_info.h b/engine/native/platform/cpu/cpu_info.h index 4ce187b0..2d74efcd 100644 --- a/engine/native/platform/cpu/cpu_info.h +++ b/engine/native/platform/cpu/cpu_info.h @@ -2,12 +2,12 @@ #pragma once namespace draconic::platform::cpu { - enum class CpuFeature : unsigned char { - NONE, - AVX2, - AVX512F, - NEON, - }; +enum class CpuFeature : unsigned char { + NONE, + AVX2, + AVX512F, + NEON, +}; - void validate_cpu() noexcept; -} +void validate_cpu() noexcept; +} // namespace draconic::platform::cpu diff --git a/engine/native/platform/cpu/cpu_info_neon.cpp b/engine/native/platform/cpu/cpu_info_neon.cpp index e24dc8be..c4f89050 100644 --- a/engine/native/platform/cpu/cpu_info_neon.cpp +++ b/engine/native/platform/cpu/cpu_info_neon.cpp @@ -3,12 +3,12 @@ #include "platform/cpu/cpu_info.h" #if !defined(__aarch64__) - #error cpu_info_neon.cpp compiled on non-ARM64 platform + #error cpu_info_neon.cpp compiled on non-ARM64 platform #endif namespace draconic::platform::cpu { - void validate_cpu() noexcept { - // NEON is mandatory on AArch64. - // So, if this compiles - It's valid. - } +void validate_cpu() noexcept { + // NEON is mandatory on AArch64. + // So, if this compiles - It's valid. } +} // namespace draconic::platform::cpu diff --git a/engine/native/platform/cpu/cpu_info_x64.cpp b/engine/native/platform/cpu/cpu_info_x64.cpp index 443093c5..c728b37c 100644 --- a/engine/native/platform/cpu/cpu_info_x64.cpp +++ b/engine/native/platform/cpu/cpu_info_x64.cpp @@ -5,97 +5,97 @@ #include // std::abort #if defined(_MSC_VER) - #include + #include #else - #include // for mingw. - #include + #include // for mingw. + #include #endif #if !defined(__x86_64__) && !defined(_M_X64) - #error cpu_info_x64.cpp compiled on non-x86-64 platform + #error cpu_info_x64.cpp compiled on non-x86-64 platform #endif namespace draconic::platform::cpu { - namespace { - // Checks if OS has enabled save/restore YMM states. Required for AVX. - unsigned long long get_xcr0() noexcept { - #if defined(_MSC_VER) || defined(__GNUC__) || defined(__clang__) - return _xgetbv(0); - #else - return 0; - #endif - } - - // XCR0[1] is XMM, XCR0[2] is YMM. - bool os_has_ymm() noexcept { - return (get_xcr0() & 0x6ULL) == 0x6ULL; - } - - // full ZMM state is required for AVX512. - bool os_has_zmm() noexcept { - return (get_xcr0() & 0xE6ULL) == 0xE6ULL; - } - - void cpuid(unsigned int leaf, unsigned int subleaf, unsigned int& eax, unsigned int& ebx, unsigned int& ecx, unsigned int& edx) noexcept { - #if defined(_MSC_VER) - int regs[4]; - __cpuidex(regs, leaf, subleaf); - eax = regs[0]; ebx = regs[1]; ecx = regs[2]; edx = regs[3]; - #else - __cpuid_count(leaf, subleaf, eax, ebx, ecx, edx); - #endif - } - - CpuFeature detect_cpu_feature() noexcept { - unsigned int eax = 0; - unsigned int ebx = 0; - unsigned int ecx = 0; - unsigned int edx = 0; - - // leaf 1. - cpuid(1, 0, eax, ebx, ecx, edx); - - constexpr unsigned int OSXSAVE = 1u << 27; - constexpr unsigned int AVX = 1u << 28; - constexpr unsigned int FMA = 1u << 12; - - if ((ecx & (OSXSAVE | AVX | FMA)) != (OSXSAVE | AVX | FMA)) { - return CpuFeature::NONE; - } - - if (!os_has_ymm()) { - return CpuFeature::NONE; - } - - // leaf 7 - cpuid(7, 0, eax, ebx, ecx, edx); - - constexpr unsigned int AVX2 = 1u << 5; - constexpr unsigned int AVX512F = 1u << 16; - - if (!(ebx & AVX2)) { - return CpuFeature::NONE; - } - - if ((ebx & AVX512F) && os_has_zmm()) { - return CpuFeature::AVX512F; - } - - return CpuFeature::AVX2; - } - - } // anonymous namespace. - - void validate_cpu() noexcept { - #if defined(__x86_64__) || defined(_M_X64) - const CpuFeature level = detect_cpu_feature(); - - if(level == CpuFeature::NONE) { - std::abort(); - } - #else - #error Unsupported architecture. - #endif - } +namespace { +// Checks if OS has enabled save/restore YMM states. Required for AVX. +unsigned long long get_xcr0() noexcept { +#if defined(_MSC_VER) || defined(__GNUC__) || defined(__clang__) + return _xgetbv(0); +#else + return 0; +#endif +} + +// XCR0[1] is XMM, XCR0[2] is YMM. +bool os_has_ymm() noexcept { + return (get_xcr0() & 0x6ULL) == 0x6ULL; +} + +// full ZMM state is required for AVX512. +bool os_has_zmm() noexcept { + return (get_xcr0() & 0xe6ULL) == 0xe6ULL; +} + +void cpuid(unsigned int leaf, + unsigned int subleaf, + unsigned int &eax, + unsigned int &ebx, + unsigned int &ecx, + unsigned int &edx) noexcept { +#if defined(_MSC_VER) + int regs[4]; + __cpuidex(regs, leaf, subleaf); + eax = regs[0]; + ebx = regs[1]; + ecx = regs[2]; + edx = regs[3]; +#else + __cpuid_count(leaf, subleaf, eax, ebx, ecx, edx); +#endif +} + +CpuFeature detect_cpu_feature() noexcept { + unsigned int eax = 0; + unsigned int ebx = 0; + unsigned int ecx = 0; + unsigned int edx = 0; + + // leaf 1. + cpuid(1, 0, eax, ebx, ecx, edx); + + constexpr unsigned int OSXSAVE = 1U << 27; + constexpr unsigned int AVX = 1U << 28; + constexpr unsigned int FMA = 1U << 12; + + if ((ecx & (OSXSAVE | AVX | FMA)) != (OSXSAVE | AVX | FMA)) { + return CpuFeature::NONE; + } + + if (!os_has_ymm()) { return CpuFeature::NONE; } + + // leaf 7 + cpuid(7, 0, eax, ebx, ecx, edx); + + constexpr unsigned int AVX2 = 1U << 5; + constexpr unsigned int AVX512F = 1U << 16; + + if (!(ebx & AVX2)) { return CpuFeature::NONE; } + + if ((ebx & AVX512F) && os_has_zmm()) { return CpuFeature::AVX512F; } + + return CpuFeature::AVX2; +} + +} // anonymous namespace. + +void validate_cpu() noexcept { +#if defined(__x86_64__) || defined(_M_X64) + CpuFeature const level = detect_cpu_feature(); + + if (level == CpuFeature::NONE) { std::abort(); } +#else + #error Unsupported architecture. +#endif } +} // namespace draconic::platform::cpu diff --git a/engine/native/platform/linux/linux.cpp b/engine/native/platform/linux/linux.cpp index 3ca59d90..8c7f0daf 100644 --- a/engine/native/platform/linux/linux.cpp +++ b/engine/native/platform/linux/linux.cpp @@ -10,31 +10,41 @@ module platform; import core.stdtypes; namespace draco::platform { - NativeWindowFrame get_native_handles(void* sdl_window_ptr) { - SDL_Window* window = static_cast(sdl_window_ptr); - NativeWindowFrame frame; - - const char* driver = SDL_GetCurrentVideoDriver(); - SDL_PropertiesID props = SDL_GetWindowProperties(window); - - if (std::string_view(driver) == "x11") { - frame.ndt = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, nullptr); - frame.nwh = (void*)(uintptr)SDL_GetNumberProperty(props, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0); - frame.type = NativeWindowType::X11; - } else if (std::string_view(driver) == "wayland") { - frame.ndt = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, nullptr); - frame.nwh = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, nullptr); - frame.type = NativeWindowType::Wayland; - } else { - std::println("No video driver was found"); - SDL_Quit(); - frame.type = NativeWindowType::None; - } - - std::println("Video driver: {}", driver); - - SDL_GetWindowSize(window, &frame.width, &frame.height); - frame.valid = (frame.nwh != nullptr); - return frame; - } +NativeWindowFrame get_native_handles(void *sdl_window_ptr) { + SDL_Window *window = static_cast(sdl_window_ptr); + NativeWindowFrame frame; + + char const *driver = SDL_GetCurrentVideoDriver(); + SDL_PropertiesID props = SDL_GetWindowProperties(window); + + if (std::string_view(driver) == "x11") { + frame.ndt = + SDL_GetPointerProperty(props, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, + nullptr); + frame.nwh = (void *)(uintptr)SDL_GetNumberProperty( + props, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0 + ); + frame.type = NativeWindowType::X11; + } + else if (std::string_view(driver) == "wayland") { + frame.ndt = SDL_GetPointerProperty( + props, SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, nullptr + ); + frame.nwh = SDL_GetPointerProperty( + props, SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, nullptr + ); + frame.type = NativeWindowType::Wayland; + } + else { + std::println("No video driver was found"); + SDL_Quit(); + frame.type = NativeWindowType::None; + } + + std::println("Video driver: {}", driver); + + SDL_GetWindowSize(window, &frame.width, &frame.height); + frame.valid = (frame.nwh != nullptr); + return frame; } +} // namespace draco::platform diff --git a/engine/native/platform/platform.cppm b/engine/native/platform/platform.cppm index 88955014..ba09f137 100644 --- a/engine/native/platform/platform.cppm +++ b/engine/native/platform/platform.cppm @@ -5,28 +5,27 @@ module; export module platform; export namespace draco::platform { - enum class NativeWindowType - { - None, - Default, - Win32, - Wayland, - X11, - Cocoa - }; - - struct NativeWindowFrame - { - void* nwh = nullptr; // Native Window Handle - void* ndt = nullptr; // Native Display Type - - int width = 0; - int height = 0; - - bool valid = false; - - NativeWindowType type = NativeWindowType::None; // Track the type of the native window - }; - - NativeWindowFrame get_native_handles(void* sdl_window_ptr); -} +enum class NativeWindowType { + None, + Default, + Win32, + Wayland, + X11, + Cocoa +}; + +struct NativeWindowFrame { + void *nwh = nullptr; // Native Window Handle + void *ndt = nullptr; // Native Display Type + + int width = 0; + int height = 0; + + bool valid = false; + + NativeWindowType type = + NativeWindowType::None; // Track the type of the native window +}; + +NativeWindowFrame get_native_handles(void *sdl_window_ptr); +} // namespace draco::platform diff --git a/engine/native/platform/simd.h b/engine/native/platform/simd.h index 830b96d6..76fd6570 100644 --- a/engine/native/platform/simd.h +++ b/engine/native/platform/simd.h @@ -4,69 +4,69 @@ // Compiler detection. #if defined(__clang__) - #define USING_COMPILER_CLANG 1 + #define USING_COMPILER_CLANG 1 #elif defined(_MSC_VER) - #define USING_COMPILER_MSVC 1 + #define USING_COMPILER_MSVC 1 #elif defined(__GNUC__) - #define USING_COMPILER_GCC 1 + #define USING_COMPILER_GCC 1 #else - #error Unsupported compiler + #error Unsupported compiler #endif // Architecture detection #if defined(__x86_64__) || defined(_M_X64) - #define ARCH_X64 1 + #define ARCH_X64 1 #elif defined(__aarch64__) - #define ARCH_ARM64 1 + #define ARCH_ARM64 1 #else - #error Unsupported architecture + #error Unsupported architecture #endif // SIMD level #if ARCH_X64 - #define SIMD_AVX2 1 + #define SIMD_AVX2 1 - // We MAY remove this and have it auto-detect later. - #if defined(ENABLE_AVX512) - #define SIMD_AVX512 1 - #endif + // We MAY remove this and have it auto-detect later. + #if defined(ENABLE_AVX512) + #define SIMD_AVX512 1 + #endif #elif ARCH_ARM64 - #define SIMD_NEON 1 + #define SIMD_NEON 1 #endif // Force inline. #if USING_COMPILER_MSVC - #define FORCEINLINE __forceinline + #define FORCEINLINE __forceinline #else - #define FORCEINLINE inline __attribute__((always_inline)) + #define FORCEINLINE inline __attribute__((always_inline)) #endif // Restrict #if USING_COMPILER_MSVC - #define RESTRICT __restrict + #define RESTRICT __restrict #else - #define RESTRICT __restrict__ + #define RESTRICT __restrict__ #endif // Alignment helpers. #if USING_COMPILER_MSVC - #define ALIGN(N) __declspec(align(N)) + #define ALIGN(N) __declspec(align(N)) #else - #define ALIGN(N) __attribute__((aligned(N))) + #define ALIGN(N) __attribute__((aligned(N))) #endif // assume and unreachable. #ifdef DEBUG - #if USING_COMPILER_MSVC - #define ASSUME(x) do { if (!(x)) __debugbreak(); } while (0) - #define UNREACHABLE() __debugbreak() - #else - #define ASSUME(x) do { if (!(x)) __builtin_trap(); } while (0) - #define UNREACHABLE() __builtin_trap() - #endif + #if USING_COMPILER_MSVC + #define ASSUME(x) do { if (!(x)) __debugbreak(); } while (0) + #define UNREACHABLE() __debugbreak() + #else + #define ASSUME(x) do { if (!(x)) __builtin_trap(); } while (0) + #define UNREACHABLE() __builtin_trap() + #endif #else - // TODO: just use [[assume]] in the code - #define ASSUME(x) [[assume(x)]] // C++23 — GCC≥13, Clang≥19, MSVC≥17.3 - #define UNREACHABLE() ASSUME(false) + // TODO: just use [[assume]] in the code + #define ASSUME(x) [[assume(x)]] // C++23 — GCC≥13, Clang≥19, MSVC≥17.3 + #define UNREACHABLE() ASSUME(false) #endif diff --git a/engine/native/platform/win32/win32.cpp b/engine/native/platform/win32/win32.cpp index 1e34f9e1..0ec1fe63 100644 --- a/engine/native/platform/win32/win32.cpp +++ b/engine/native/platform/win32/win32.cpp @@ -7,18 +7,21 @@ module; module platform; namespace draco::platform { - NativeWindowFrame get_native_handles(void* sdl_window_ptr) { - SDL_Window* window = static_cast(sdl_window_ptr); - NativeWindowFrame frame; - - SDL_PropertiesID props = SDL_GetWindowProperties(window); +NativeWindowFrame get_native_handles(void *sdl_window_ptr) { + SDL_Window *window = static_cast(sdl_window_ptr); + NativeWindowFrame frame; - frame.nwh = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr); - frame.ndt = nullptr; // Note: On Windows, ndt isn't required by bgfx for the Vulkan & DX backends which is why we leave it as a nullptr - frame.type = NativeWindowType::Win32; + SDL_PropertiesID props = SDL_GetWindowProperties(window); - SDL_GetWindowSize(window, &frame.width, &frame.height); - frame.valid = (frame.nwh != nullptr); - return frame; - } + frame.nwh = + SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WIN32_HWND_POINTER, + nullptr); + frame.ndt = + nullptr; // Note: On Windows, ndt isn't required by bgfx for the Vulkan & DX backends which is why we leave it as a nullptr + frame.type = NativeWindowType::Win32; + + SDL_GetWindowSize(window, &frame.width, &frame.height); + frame.valid = (frame.nwh != nullptr); + return frame; } +} // namespace draco::platform diff --git a/engine/native/rendering/material/material.cppm b/engine/native/rendering/material/material.cppm index b531766a..49d3bb05 100644 --- a/engine/native/rendering/material/material.cppm +++ b/engine/native/rendering/material/material.cppm @@ -7,26 +7,23 @@ export module rendering.material; import core.stdtypes; import rendering.rhi; -export namespace draco::rendering::material -{ - struct Uniform - { - u32 name_hash = 0; - const void* data = nullptr; - u16 count = 1; - }; +export namespace draco::rendering::material { +struct Uniform { + u32 name_hash = 0; + void const *data = nullptr; + u16 count = 1; +}; - struct Material - { - u32 shader_id = 0; +struct Material { + u32 shader_id = 0; - rhi::PipelineHandle pipeline = rhi::InvalidPipeline; + rhi::PipelineHandle pipeline = rhi::InvalidPipeline; - rhi::TextureHandle texture = rhi::InvalidTexture; - rhi::UniformHandle sampler = rhi::InvalidUniform; + rhi::TextureHandle texture = rhi::InvalidTexture; + rhi::UniformHandle sampler = rhi::InvalidUniform; - u8 texture_unit = 0; + u8 texture_unit = 0; - std::vector uniforms; - }; -} + std::vector uniforms; +}; +} // namespace draco::rendering::material diff --git a/engine/native/rendering/mesh/mesh.cpp b/engine/native/rendering/mesh/mesh.cpp index 77f72423..cb7ff3f2 100644 --- a/engine/native/rendering/mesh/mesh.cpp +++ b/engine/native/rendering/mesh/mesh.cpp @@ -12,420 +12,398 @@ import core.memory; import rendering.rhi; import rendering.rhi.vertex; -namespace draco::rendering::mesh -{ - using namespace draco::rendering; +namespace draco::rendering::mesh { +using namespace draco::rendering; - static std::unordered_map g_mesh_cache; - static draco::core::memory::HandleRegistry g_meshes; - static rhi::LayoutHandle g_mesh_layout = rhi::InvalidLayout; +static std::unordered_map g_mesh_cache; +static draco::core::memory::HandleRegistry g_meshes; +static rhi::LayoutHandle g_mesh_layout = rhi::InvalidLayout; - static usize hash_combine(usize a, usize b) - { - return a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2)); - } +static usize hash_combine(usize a, usize b) { + return a ^ (b + 0x9e37'79b9 + (a << 6) + (a >> 2)); +} + +static usize hash_mesh_params(int a, int b = 0, f32 c = 0.0F) { + usize h1 = std::hash{}(a); + usize h2 = std::hash{}(b); + usize h3 = std::hash{}(c); + + return hash_combine(hash_combine(h1, h2), h3); +} + +static void ensure_mesh_layout() { + if (g_mesh_layout != rhi::InvalidLayout) { return; } + + rhi::VertexLayoutDesc desc; + desc.elements = { + { rhi::Attrib::Position, 3, rhi::AttribType::Float}, + { rhi::Attrib::Normal, 3, rhi::AttribType::Float}, + {rhi::Attrib::TexCoord0, 2, rhi::AttribType::Float} + }; + + g_mesh_layout = rhi::create_vertex_layout(desc); +} - static usize hash_mesh_params(int a, int b = 0, f32 c = 0.0f) - { - usize h1 = std::hash{}(a); - usize h2 = std::hash{}(b); - usize h3 = std::hash{}(c); +MeshHandle create(void const *vertex_data, + u32 vertex_size, + u32 vertex_count, + std::vector const &indices, + rhi::LayoutHandle layout) { + Mesh mesh{}; - return hash_combine(hash_combine(h1, h2), h3); - } + mesh.vbh = rhi::create_vertex_buffer(vertex_data, vertex_size, layout); + mesh.ibh = rhi::create_index_buffer( + indices.data(), static_cast(indices.size() * sizeof(u32)) + ); - static void ensure_mesh_layout() - { - if (g_mesh_layout != rhi::InvalidLayout) - return; - - rhi::VertexLayoutDesc desc; - desc.elements = - { - { rhi::Attrib::Position, 3, rhi::AttribType::Float }, - { rhi::Attrib::Normal, 3, rhi::AttribType::Float }, - { rhi::Attrib::TexCoord0,2, rhi::AttribType::Float } - }; + mesh.layout = layout; - g_mesh_layout = rhi::create_vertex_layout(desc); - } + mesh.vertex_count = vertex_count; + mesh.index_count = static_cast(indices.size()); - MeshHandle create(const void* vertex_data, u32 vertex_size, u32 vertex_count, const std::vector& indices, rhi::LayoutHandle layout) - { - Mesh mesh{}; + mesh.valid = + (mesh.vbh != rhi::InvalidBuffer) && (mesh.ibh != rhi::InvalidBuffer); - mesh.vbh = rhi::create_vertex_buffer(vertex_data, vertex_size, layout); - mesh.ibh = rhi::create_index_buffer(indices.data(), static_cast(indices.size() * sizeof(u32))); + if (!mesh.valid) { + if (mesh.vbh != rhi::InvalidBuffer) { rhi::destroy_buffer(mesh.vbh); } + if (mesh.ibh != rhi::InvalidBuffer) { rhi::destroy_buffer(mesh.ibh); } + return {}; + } + return g_meshes.create(mesh); +} - mesh.layout = layout; +MeshHandle create_cube() { + ensure_mesh_layout(); - mesh.vertex_count = vertex_count; - mesh.index_count = static_cast(indices.size()); + usize key = 1; - mesh.valid = (mesh.vbh != rhi::InvalidBuffer) && (mesh.ibh != rhi::InvalidBuffer); + if (auto it = g_mesh_cache.find(key); it != g_mesh_cache.end()) { + return it->second; + } - if (!mesh.valid) - { - if (mesh.vbh != rhi::InvalidBuffer) rhi::destroy_buffer(mesh.vbh); - if (mesh.ibh != rhi::InvalidBuffer) rhi::destroy_buffer(mesh.ibh); - return {}; - } - return g_meshes.create(mesh); - } + auto v = gen::cube_vertices(); + auto i = gen::cube_indices(); - MeshHandle create_cube() - { - ensure_mesh_layout(); + MeshHandle h = create(v.data(), v.size() * sizeof(Vertex), (u32)v.size(), i, + g_mesh_layout); - usize key = 1; + g_mesh_cache[key] = h; + return h; +} - if (auto it = g_mesh_cache.find(key); it != g_mesh_cache.end()) - return it->second; +MeshHandle create_plane(f32 size) { + ensure_mesh_layout(); - auto v = gen::cube_vertices(); - auto i = gen::cube_indices(); + usize key = hash_mesh_params(1000, 0, size); - MeshHandle h = create(v.data(), v.size()*sizeof(Vertex), (u32)v.size(), i, g_mesh_layout); + if (auto it = g_mesh_cache.find(key); it != g_mesh_cache.end()) { + return it->second; + } - g_mesh_cache[key] = h; - return h; - } + auto v = gen::plane_vertices(size); + auto i = gen::plane_indices(); - MeshHandle create_plane(f32 size) - { - ensure_mesh_layout(); + MeshHandle h = create(v.data(), v.size() * sizeof(Vertex), (u32)v.size(), i, + g_mesh_layout); - usize key = hash_mesh_params(1000, 0, size); + g_mesh_cache[key] = h; + return h; +} - if (auto it = g_mesh_cache.find(key); it != g_mesh_cache.end()) - return it->second; +MeshHandle create_sphere(int segments, int rings) { + if (segments < 3 || rings < 2) { return {}; } - auto v = gen::plane_vertices(size); - auto i = gen::plane_indices(); + ensure_mesh_layout(); - MeshHandle h = create(v.data(), v.size()*sizeof(Vertex), (u32)v.size(), i, g_mesh_layout); + usize key = + hash_combine(std::hash{}(segments), std::hash{}(rings)); - g_mesh_cache[key] = h; - return h; - } + if (auto it = g_mesh_cache.find(key); it != g_mesh_cache.end()) { + return it->second; + } - MeshHandle create_sphere(int segments, int rings) - { - if (segments < 3 || rings < 2) - return {}; + auto v = gen::sphere_vertices(segments, rings); + auto i = gen::sphere_indices(segments, rings); - ensure_mesh_layout(); + MeshHandle h = create(v.data(), v.size() * sizeof(Vertex), (u32)v.size(), i, + g_mesh_layout); - usize key = hash_combine(std::hash{}(segments), std::hash{}(rings)); + g_mesh_cache[key] = h; + return h; +} + +MeshHandle create_cylinder(int segments, f32 height) { + if (segments < 3 || height < 0.0F) { return {}; } - if (auto it = g_mesh_cache.find(key); it != g_mesh_cache.end()) - return it->second; + ensure_mesh_layout(); - auto v = gen::sphere_vertices(segments, rings); - auto i = gen::sphere_indices(segments, rings); + usize key = hash_mesh_params(2000, segments, height); - MeshHandle h = create(v.data(), v.size()*sizeof(Vertex), (u32)v.size(), i, g_mesh_layout); + if (auto it = g_mesh_cache.find(key); it != g_mesh_cache.end()) { + return it->second; + } - g_mesh_cache[key] = h; - return h; - } + auto v = gen::cylinder_vertices(segments, height); + auto i = gen::cylinder_indices(segments); - MeshHandle create_cylinder(int segments, f32 height) - { - if (segments < 3 || height < 0.0f) - return {}; - - ensure_mesh_layout(); + MeshHandle h = create(v.data(), v.size() * sizeof(Vertex), (u32)v.size(), i, + g_mesh_layout); - usize key = hash_mesh_params(2000, segments, height); + g_mesh_cache[key] = h; + return h; +} - if (auto it = g_mesh_cache.find(key); it != g_mesh_cache.end()) - return it->second; +MeshHandle create_capsule(int segments, int rings, f32 height) { + if (segments < 3 || rings < 2 || height < 0.0F) { return {}; } - auto v = gen::cylinder_vertices(segments, height); - auto i = gen::cylinder_indices(segments); + ensure_mesh_layout(); + usize key = hash_combine(hash_combine(std::hash{}(segments), + std::hash{}(rings)), + std::hash{}(height)); - MeshHandle h = create(v.data(), v.size()*sizeof(Vertex), (u32)v.size(), i, g_mesh_layout); + if (auto it = g_mesh_cache.find(key); it != g_mesh_cache.end()) { + return it->second; + } + + auto v = gen::capsule_vertices(segments, rings, height); + auto i = gen::capsule_indices(segments, rings); + + MeshHandle h = create(v.data(), v.size() * sizeof(Vertex), (u32)v.size(), i, + g_mesh_layout); + + g_mesh_cache[key] = h; + return h; +} + +void destroy(MeshHandle handle) { + auto *mesh = g_meshes.get(handle); + if (!mesh) { return; } + + rhi::destroy_buffer(mesh->vbh); + rhi::destroy_buffer(mesh->ibh); + + // Remove from cache + for (auto it = g_mesh_cache.begin(); it != g_mesh_cache.end();) { + if (it->second == handle) { it = g_mesh_cache.erase(it); } + else { ++it; } + } + + g_meshes.destroy(handle); +} + +Mesh const *get(MeshHandle handle) { + return g_meshes.get(handle); +} +} // namespace draco::rendering::mesh + +namespace draco::rendering::mesh::gen { +Vertex make(f32 px, f32 py, f32 pz, f32 nx, f32 ny, f32 nz, f32 u, f32 v) { + return {px, py, pz, nx, ny, nz, u, v}; +} - g_mesh_cache[key] = h; - return h; - } +std::vector cube_vertices() { + return { + make(-1, -1, 1, 0, 0, 1, 0, 0), make(1, -1, 1, 0, 0, 1, 1, 0), + make(1, 1, 1, 0, 0, 1, 1, 1), make(-1, 1, 1, 0, 0, 1, 0, 1), - MeshHandle create_capsule(int segments, int rings, f32 height) - { - if (segments < 3 || rings < 2 || height < 0.0f) - return {}; - - ensure_mesh_layout(); - usize key = hash_combine(hash_combine(std::hash{}(segments), std::hash{}(rings)), std::hash{}(height)); + make(1, -1, -1, 0, 0, -1, 0, 0), make(-1, -1, -1, 0, 0, -1, 1, 0), + make(-1, 1, -1, 0, 0, -1, 1, 1), make(1, 1, -1, 0, 0, -1, 0, 1), - if (auto it = g_mesh_cache.find(key); it != g_mesh_cache.end()) - return it->second; + make(-1, -1, -1, -1, 0, 0, 0, 0), make(-1, -1, 1, -1, 0, 0, 1, 0), + make(-1, 1, 1, -1, 0, 0, 1, 1), make(-1, 1, -1, -1, 0, 0, 0, 1), - auto v = gen::capsule_vertices(segments, rings, height); - auto i = gen::capsule_indices(segments, rings); + make(1, -1, 1, 1, 0, 0, 0, 0), make(1, -1, -1, 1, 0, 0, 1, 0), + make(1, 1, -1, 1, 0, 0, 1, 1), make(1, 1, 1, 1, 0, 0, 0, 1), - MeshHandle h = create(v.data(), v.size()*sizeof(Vertex), (u32)v.size(), i, g_mesh_layout); + make(-1, 1, 1, 0, 1, 0, 0, 0), make(1, 1, 1, 0, 1, 0, 1, 0), + make(1, 1, -1, 0, 1, 0, 1, 1), make(-1, 1, -1, 0, 1, 0, 0, 1), + + make(-1, -1, -1, 0, -1, 0, 0, 0), make(1, -1, -1, 0, -1, 0, 1, 0), + make(1, -1, 1, 0, -1, 0, 1, 1), make(-1, -1, 1, 0, -1, 0, 0, 1), + }; +} + +std::vector cube_indices() { + return {0, 1, 2, 2, 3, 0, 4, 5, 6, 6, 7, 4, + 8, 9, 10, 10, 11, 8, 12, 13, 14, 14, 15, 12, + 16, 17, 18, 18, 19, 16, 20, 21, 22, 22, 23, 20}; +} + +std::vector plane_vertices(f32 size) { + f32 s = size * 0.5F; + + return { + make(-s, 0, -s, 0, 1, 0, 0, 0), + make(s, 0, -s, 0, 1, 0, 1, 0), + make(s, 0, s, 0, 1, 0, 1, 1), + make(-s, 0, s, 0, 1, 0, 0, 1), + }; +} + +std::vector plane_indices() { + return {0, 1, 2, 2, 3, 0}; +} + +std::vector sphere_vertices(int segments, int rings) { + std::vector v; + + for (int y = 0; y <= rings; y++) { + f32 v01 = (f32)y / rings; + f32 theta = v01 * draco::math::PI; + + for (int x = 0; x <= segments; x++) { + f32 u01 = (f32)x / segments; + f32 phi = u01 * 2.0F * draco::math::PI; + + f32 px = sinf(theta) * cosf(phi); + f32 py = cosf(theta); + f32 pz = sinf(theta) * sinf(phi); + + v.push_back(make(px, py, pz, px, py, pz, u01, v01)); + } + } + + return v; +} + +std::vector sphere_indices(int segments, int rings) { + std::vector i; + + for (int y = 0; y < rings; y++) { + for (int x = 0; x < segments; x++) { + int a = y * (segments + 1) + x; + int b = a + segments + 1; + + i.push_back(a); + i.push_back(b); + i.push_back(a + 1); + + i.push_back(b); + i.push_back(b + 1); + i.push_back(a + 1); + } + } + + return i; +} + +std::vector cylinder_vertices(int segments, f32 height) { + std::vector v; + f32 half = height * 0.5F; + + // Side walls (Outward normals) + for (int y = 0; y <= 1; y++) { + f32 py = (y ? half : -half); + for (int x = 0; x <= segments; x++) { + f32 t = (f32)x / segments; + f32 a = t * 2.0F * draco::math::PI; + f32 cx = cosf(a); + f32 cz = sinf(a); + // Normal is strictly horizontal for side walls + v.push_back(make(cx, py, cz, cx, 0, cz, t, (f32)y)); + } + } + + // Top cap (Upward normals) + // Center vertex + v.push_back(make(0, half, 0, 0, 1, 0, 0.5F, 0.5F)); + for (int x = 0; x <= segments; x++) { + f32 t = (f32)x / segments; + f32 a = t * 2.0F * draco::math::PI; + v.push_back(make(cosf(a), half, sinf(a), 0, 1, 0, (cosf(a) + 1) * 0.5F, + (sinf(a) + 1) * 0.5F)); + } + + // Bottom cap (Downward Normals) + // Center vertex + v.push_back(make(0, -half, 0, 0, -1, 0, 0.5F, 0.5F)); + for (int x = 0; x <= segments; x++) { + f32 t = (f32)x / segments; + f32 a = t * 2.0F * draco::math::PI; + v.push_back(make(cosf(a), -half, sinf(a), 0, -1, 0, + (cosf(a) + 1) * 0.5F, (sinf(a) + 1) * 0.5F)); + } + + return v; +} + +std::vector cylinder_indices(int segments) { + std::vector i; + int side_start = 0; + int top_start = (segments + 1) * 2; + int bottom_start = top_start + (segments + 2); + + // Sides + for (int s = 0; s < segments; s++) { + int b0 = s; + int b1 = s + 1; + int t0 = b0 + segments + 1; + int t1 = b1 + segments + 1; + i.push_back(b0); + i.push_back(t0); + i.push_back(t1); + i.push_back(b0); + i.push_back(t1); + i.push_back(b1); + } + + // Top Cap (Triangle Fan style) + for (int s = 0; s < segments; s++) { + i.push_back(top_start); // Center + i.push_back(top_start + s + 2); + i.push_back(top_start + s + 1); + } + + // Bottom Cap + for (int s = 0; s < segments; s++) { + i.push_back(bottom_start); // Center + i.push_back(bottom_start + s + 1); + i.push_back(bottom_start + s + 2); + } + return i; +} - g_mesh_cache[key] = h; - return h; - } +std::vector capsule_vertices(int segments, int rings, f32 height) { + std::vector v; + f32 half = height * 0.5F; - void destroy(MeshHandle handle) - { - auto* mesh = g_meshes.get(handle); - if (!mesh) return; + // One continuous loop from bottom pole to top pole + // Total rings for a capsule = rings (bottom cap) + rings (top cap) + for (int r = 0; r <= rings; r++) { + f32 v_uv = (f32)r / rings; + f32 theta = v_uv * draco::math::PI; // 0 to draco::math::PI - rhi::destroy_buffer(mesh->vbh); - rhi::destroy_buffer(mesh->ibh); + // Adjust Y for the cylinder section + f32 y_offset = (theta < draco::math::PI * 0.5F) ? half : -half; - // Remove from cache - for (auto it = g_mesh_cache.begin(); it != g_mesh_cache.end(); ) - { - if (it->second == handle) - it = g_mesh_cache.erase(it); - else - ++it; - } + for (int s = 0; s <= segments; s++) { + f32 u_uv = (f32)s / segments; + f32 phi = u_uv * 2.0F * draco::math::PI; - g_meshes.destroy(handle); - } + f32 nx = sinf(theta) * cosf(phi); + f32 ny = cosf(theta); + f32 nz = sinf(theta) * sinf(phi); - const Mesh* get(MeshHandle handle) - { - return g_meshes.get(handle); - } + v.push_back(make(nx, ny + y_offset, nz, nx, ny, nz, u_uv, v_uv)); + } + } + return v; } -namespace draco::rendering::mesh::gen -{ - Vertex make(f32 px, f32 py, f32 pz, f32 nx, f32 ny, f32 nz, f32 u, f32 v) - { - return { px, py, pz, nx, ny, nz, u, v }; - } - - std::vector cube_vertices() - { - return { - make(-1,-1, 1, 0,0,1, 0,0), - make( 1,-1, 1, 0,0,1, 1,0), - make( 1, 1, 1, 0,0,1, 1,1), - make(-1, 1, 1, 0,0,1, 0,1), - - make( 1,-1,-1, 0,0,-1, 0,0), - make(-1,-1,-1, 0,0,-1, 1,0), - make(-1, 1,-1, 0,0,-1, 1,1), - make( 1, 1,-1, 0,0,-1, 0,1), - - make(-1,-1,-1,-1,0,0, 0,0), - make(-1,-1, 1,-1,0,0, 1,0), - make(-1, 1, 1,-1,0,0, 1,1), - make(-1, 1,-1,-1,0,0, 0,1), - - make( 1,-1, 1, 1,0,0, 0,0), - make( 1,-1,-1, 1,0,0, 1,0), - make( 1, 1,-1, 1,0,0, 1,1), - make( 1, 1, 1, 1,0,0, 0,1), - - make(-1, 1, 1, 0,1,0, 0,0), - make( 1, 1, 1, 0,1,0, 1,0), - make( 1, 1,-1, 0,1,0, 1,1), - make(-1, 1,-1, 0,1,0, 0,1), - - make(-1,-1,-1, 0,-1,0, 0,0), - make( 1,-1,-1, 0,-1,0, 1,0), - make( 1,-1, 1, 0,-1,0, 1,1), - make(-1,-1, 1, 0,-1,0, 0,1), - }; - } - - std::vector cube_indices() - { - return { - 0,1,2, 2,3,0, - 4,5,6, 6,7,4, - 8,9,10, 10,11,8, - 12,13,14, 14,15,12, - 16,17,18, 18,19,16, - 20,21,22, 22,23,20 - }; - } - - std::vector plane_vertices(f32 size) - { - f32 s = size * 0.5f; - - return { - make(-s,0,-s, 0,1,0, 0,0), - make( s,0,-s, 0,1,0, 1,0), - make( s,0, s, 0,1,0, 1,1), - make(-s,0, s, 0,1,0, 0,1), - }; - } - - std::vector plane_indices() - { - return { 0,1,2, 2,3,0 }; - } - - std::vector sphere_vertices(int segments, int rings) - { - std::vector v; - - for (int y = 0; y <= rings; y++) - { - f32 v01 = (f32)y / rings; - f32 theta = v01 * draco::math::PI; - - for (int x = 0; x <= segments; x++) - { - f32 u01 = (f32)x / segments; - f32 phi = u01 * 2.0f * draco::math::PI; - - f32 px = sinf(theta) * cosf(phi); - f32 py = cosf(theta); - f32 pz = sinf(theta) * sinf(phi); - - v.push_back(make(px,py,pz, px,py,pz, u01,v01)); - } - } - - return v; - } - - std::vector sphere_indices(int segments, int rings) - { - std::vector i; - - for (int y = 0; y < rings; y++) - { - for (int x = 0; x < segments; x++) - { - int a = y * (segments + 1) + x; - int b = a + segments + 1; - - i.push_back(a); - i.push_back(b); - i.push_back(a + 1); - - i.push_back(b); - i.push_back(b + 1); - i.push_back(a + 1); - } - } - - return i; - } - - std::vector cylinder_vertices(int segments, f32 height) - { - std::vector v; - f32 half = height * 0.5f; - - // Side walls (Outward normals) - for (int y = 0; y <= 1; y++) { - f32 py = (y ? half : -half); - for (int x = 0; x <= segments; x++) { - f32 t = (f32)x / segments; - f32 a = t * 2.0f * draco::math::PI; - f32 cx = cosf(a); - f32 cz = sinf(a); - // Normal is strictly horizontal for side walls - v.push_back(make(cx, py, cz, cx, 0, cz, t, (f32)y)); - } - } - - // Top cap (Upward normals) - // Center vertex - v.push_back(make(0, half, 0, 0, 1, 0, 0.5f, 0.5f)); - for (int x = 0; x <= segments; x++) { - f32 t = (f32)x / segments; - f32 a = t * 2.0f * draco::math::PI; - v.push_back(make(cosf(a), half, sinf(a), 0, 1, 0, (cosf(a)+1)*0.5f, (sinf(a)+1)*0.5f)); - } - - // Bottom cap (Downward Normals) - // Center vertex - v.push_back(make(0, -half, 0, 0, -1, 0, 0.5f, 0.5f)); - for (int x = 0; x <= segments; x++) { - f32 t = (f32)x / segments; - f32 a = t * 2.0f * draco::math::PI; - v.push_back(make(cosf(a), -half, sinf(a), 0, -1, 0, (cosf(a)+1)*0.5f, (sinf(a)+1)*0.5f)); - } - - return v; - } - - std::vector cylinder_indices(int segments) - { - std::vector i; - int side_start = 0; - int top_start = (segments + 1) * 2; - int bottom_start = top_start + (segments + 2); - - // Sides - for (int s = 0; s < segments; s++) { - int b0 = s; int b1 = s + 1; - int t0 = b0 + segments + 1; int t1 = b1 + segments + 1; - i.push_back(b0); i.push_back(t0); i.push_back(t1); - i.push_back(b0); i.push_back(t1); i.push_back(b1); - } - - // Top Cap (Triangle Fan style) - for (int s = 0; s < segments; s++) { - i.push_back(top_start); // Center - i.push_back(top_start + s + 2); - i.push_back(top_start + s + 1); - } - - // Bottom Cap - for (int s = 0; s < segments; s++) { - i.push_back(bottom_start); // Center - i.push_back(bottom_start + s + 1); - i.push_back(bottom_start + s + 2); - } - return i; - } - - std::vector capsule_vertices(int segments, int rings, f32 height) - { - std::vector v; - f32 half = height * 0.5f; - - // One continuous loop from bottom pole to top pole - // Total rings for a capsule = rings (bottom cap) + rings (top cap) - for (int r = 0; r <= rings; r++) { - f32 v_uv = (f32)r / rings; - f32 theta = v_uv * draco::math::PI; // 0 to draco::math::PI - - // Adjust Y for the cylinder section - f32 y_offset = (theta < draco::math::PI * 0.5f) ? half : -half; - - for (int s = 0; s <= segments; s++) { - f32 u_uv = (f32)s / segments; - f32 phi = u_uv * 2.0f * draco::math::PI; - - f32 nx = sinf(theta) * cosf(phi); - f32 ny = cosf(theta); - f32 nz = sinf(theta) * sinf(phi); - - v.push_back(make(nx, ny + y_offset, nz, nx, ny, nz, u_uv, v_uv)); - } - } - return v; - } - - std::vector capsule_indices(int segments, int rings) - { - std::vector i; - for (int r = 0; r < rings; r++) { - for (int s = 0; s < segments; s++) { - int a = r * (segments + 1) + s; - int b = a + segments + 1; - i.push_back(a); i.push_back(b); i.push_back(a + 1); - i.push_back(b); i.push_back(b + 1); i.push_back(a + 1); - } - } - return i; - } +std::vector capsule_indices(int segments, int rings) { + std::vector i; + for (int r = 0; r < rings; r++) { + for (int s = 0; s < segments; s++) { + int a = r * (segments + 1) + s; + int b = a + segments + 1; + i.push_back(a); + i.push_back(b); + i.push_back(a + 1); + i.push_back(b); + i.push_back(b + 1); + i.push_back(a + 1); + } + } + return i; } +} // namespace draco::rendering::mesh::gen diff --git a/engine/native/rendering/mesh/mesh.cppm b/engine/native/rendering/mesh/mesh.cppm index 6b10d932..b61f209c 100644 --- a/engine/native/rendering/mesh/mesh.cppm +++ b/engine/native/rendering/mesh/mesh.cppm @@ -8,64 +8,58 @@ import core.stdtypes; import core.memory; import rendering.rhi; -export namespace draco::rendering::mesh -{ - struct MeshTag {}; - - using MeshHandle = draco::core::memory::Handle; - - struct Vertex - { - f32 px, py, pz; - f32 nx, ny, nz; - f32 u, v; - }; - - struct Mesh - { - draco::rendering::rhi::BufferHandle vbh; - draco::rendering::rhi::BufferHandle ibh; - - draco::rendering::rhi::LayoutHandle layout; - - u32 vertex_count = 0; - u32 index_count = 0; - - bool valid = false; - }; - - MeshHandle create( - const void* vertex_data, - u32 vertex_size, - u32 vertex_count, - const std::vector& indices, - draco::rendering::rhi::LayoutHandle layout - ); - - MeshHandle create_cube(); - MeshHandle create_plane(float size); - MeshHandle create_sphere(int segments, int rings); - MeshHandle create_cylinder(int segments, float height); - MeshHandle create_capsule(int segments, int rings, float height); - - void destroy(MeshHandle mesh); - const Mesh* get(MeshHandle mesh); -} - -export namespace draco::rendering::mesh::gen -{ - std::vector cube_vertices(); - std::vector cube_indices(); - - std::vector plane_vertices(float size); - std::vector plane_indices(); - - std::vector sphere_vertices(int segments, int rings); - std::vector sphere_indices(int segments, int rings); - - std::vector cylinder_vertices(int segments, float height); - std::vector cylinder_indices(int segments); - - std::vector capsule_vertices(int segments, int rings, float height); - std::vector capsule_indices(int segments, int rings); -} +export namespace draco::rendering::mesh { +struct MeshTag { }; + +using MeshHandle = draco::core::memory::Handle; + +struct Vertex { + f32 px, py, pz; + f32 nx, ny, nz; + f32 u, v; +}; + +struct Mesh { + draco::rendering::rhi::BufferHandle vbh; + draco::rendering::rhi::BufferHandle ibh; + + draco::rendering::rhi::LayoutHandle layout; + + u32 vertex_count = 0; + u32 index_count = 0; + + bool valid = false; +}; + +MeshHandle create(void const *vertex_data, + u32 vertex_size, + u32 vertex_count, + std::vector const &indices, + draco::rendering::rhi::LayoutHandle layout); + +MeshHandle create_cube(); +MeshHandle create_plane(float size); +MeshHandle create_sphere(int segments, int rings); +MeshHandle create_cylinder(int segments, float height); +MeshHandle create_capsule(int segments, int rings, float height); + +void destroy(MeshHandle mesh); +Mesh const *get(MeshHandle mesh); +} // namespace draco::rendering::mesh + +export namespace draco::rendering::mesh::gen { +std::vector cube_vertices(); +std::vector cube_indices(); + +std::vector plane_vertices(float size); +std::vector plane_indices(); + +std::vector sphere_vertices(int segments, int rings); +std::vector sphere_indices(int segments, int rings); + +std::vector cylinder_vertices(int segments, float height); +std::vector cylinder_indices(int segments); + +std::vector capsule_vertices(int segments, int rings, float height); +std::vector capsule_indices(int segments, int rings); +} // namespace draco::rendering::mesh::gen diff --git a/engine/native/rendering/quad_renderer/quad_renderer.cpp b/engine/native/rendering/quad_renderer/quad_renderer.cpp index 16478c51..227511a4 100644 --- a/engine/native/rendering/quad_renderer/quad_renderer.cpp +++ b/engine/native/rendering/quad_renderer/quad_renderer.cpp @@ -15,172 +15,169 @@ import rendering.rendergraph; namespace draco::rendering::quad_renderer { - static constexpr f32 QuadUV[4][2] = { - {0.0f, 0.0f}, - {1.0f, 0.0f}, - {1.0f, 1.0f}, - {0.0f, 1.0f} - }; +static constexpr f32 QuadUV[4][2] = { + {0.0F, 0.0F}, + {1.0F, 0.0F}, + {1.0F, 1.0F}, + {0.0F, 1.0F} +}; - void QuadRenderer::init(draco::rendering::rhi::PipelineHandle pipeline) - { - using namespace draco::rendering::rhi; +void QuadRenderer::init(draco::rendering::rhi::PipelineHandle pipeline) { + using namespace draco::rendering::rhi; - VertexLayoutDesc layout{}; - layout.elements.push_back({Attrib::Position, 3, AttribType::Float}); - layout.elements.push_back({Attrib::TexCoord0, 2, AttribType::Float}); - layout.elements.push_back({Attrib::Color0, 4, AttribType::Uint8, true}); + VertexLayoutDesc layout{}; + layout.elements.push_back({Attrib::Position, 3, AttribType::Float}); + layout.elements.push_back({Attrib::TexCoord0, 2, AttribType::Float}); + layout.elements.push_back({Attrib::Color0, 4, AttribType::Uint8, true}); - m_pipeline = pipeline; - m_layout = create_vertex_layout(layout); + m_pipeline = pipeline; + m_layout = create_vertex_layout(layout); - // Allocating dynamic streaming buffers - m_vb = create_dynamic_vertex_buffer(sizeof(TexturedVertex) * MaxVertices, m_layout); - - // Pass BGFX_BUFFER_NONE implicitly to match tracking - m_ib = create_dynamic_index_buffer(MaxIndices * sizeof(u16), BGFX_BUFFER_NONE); + // Allocating dynamic streaming buffers + m_vb = create_dynamic_vertex_buffer(sizeof(TexturedVertex) * MaxVertices, + m_layout); - m_sampler = create_uniform("s_texColor", UniformType::Sampler); - } + // Pass BGFX_BUFFER_NONE implicitly to match tracking + m_ib = + create_dynamic_index_buffer(MaxIndices * sizeof(u16), BGFX_BUFFER_NONE); - void QuadRenderer::begin() - { - m_vertices.clear(); - m_indices.clear(); + m_sampler = create_uniform("s_texColor", UniformType::Sampler); +} - m_quad_count = 0; +void QuadRenderer::begin() { + m_vertices.clear(); + m_indices.clear(); - m_batch_key = {}; - } + m_quad_count = 0; - void QuadRenderer::submit(const QuadCommand& cmd) - { - if (m_quad_count >= MaxQuads) - return; + m_batch_key = {}; +} - BatchKey new_key{cmd.texture, m_pipeline, draco::rendering::rhi::InvalidSampler}; +void QuadRenderer::submit(QuadCommand const &cmd) { + if (m_quad_count >= MaxQuads) { return; } - if (m_batch_key.texture == draco::rendering::rhi::InvalidTexture) - { - m_batch_key = new_key; - } + BatchKey new_key{cmd.texture, m_pipeline, + draco::rendering::rhi::InvalidSampler}; - bool state_change = !(new_key == m_batch_key); + if (m_batch_key.texture == draco::rendering::rhi::InvalidTexture) { + m_batch_key = new_key; + } - if (state_change) - { - // TODO: Flush current batch automatically + bool state_change = !(new_key == m_batch_key); - return; - } + if (state_change) { + // TODO: Flush current batch automatically - push_quad(cmd); + return; + } - m_quad_count++; - } + push_quad(cmd); - void QuadRenderer::push_quad(const QuadCommand& cmd) - { - f32 hw = cmd.width * 0.5f; - f32 hh = cmd.height * 0.5f; + m_quad_count++; +} - f32 c = cosf(cmd.rotation); - f32 s = sinf(cmd.rotation); +void QuadRenderer::push_quad(QuadCommand const &cmd) { + f32 hw = cmd.width * 0.5F; + f32 hh = cmd.height * 0.5F; - f32 corners[4][2] = { - {-hw, -hh}, - { hw, -hh}, - { hw, hh}, - {-hw, hh} - }; + f32 c = cosf(cmd.rotation); + f32 s = sinf(cmd.rotation); - u16 start = static_cast(m_vertices.size()); + f32 corners[4][2] = { + {-hw, -hh}, + { hw, -hh}, + { hw, hh}, + {-hw, hh} + }; - for (int i = 0; i < 4; i++) - { - f32 rx = corners[i][0] * c - corners[i][1] * s; + u16 start = static_cast(m_vertices.size()); - f32 ry = corners[i][0] * s + corners[i][1] * c; + for (int i = 0; i < 4; i++) { + f32 rx = corners[i][0] * c - corners[i][1] * s; - draco::rendering::rhi::TexturedVertex v{}; + f32 ry = corners[i][0] * s + corners[i][1] * c; - v.x = cmd.x + rx; - v.y = cmd.y + ry; - v.z = cmd.z; + draco::rendering::rhi::TexturedVertex v{}; - v.u = QuadUV[i][0]; - v.v = QuadUV[i][1]; + v.x = cmd.x + rx; + v.y = cmd.y + ry; + v.z = cmd.z; - v.color = cmd.color; + v.u = QuadUV[i][0]; + v.v = QuadUV[i][1]; - m_vertices.push_back(v); - } + v.color = cmd.color; - m_indices.push_back(start + 0); - m_indices.push_back(start + 1); - m_indices.push_back(start + 2); + m_vertices.push_back(v); + } - m_indices.push_back(start + 2); - m_indices.push_back(start + 3); - m_indices.push_back(start + 0); - } + m_indices.push_back(start + 0); + m_indices.push_back(start + 1); + m_indices.push_back(start + 2); - void QuadRenderer::flush_to_pass(draco::rendering::rendergraph::Pass& pass) - { - using namespace draco::rendering::rhi; + m_indices.push_back(start + 2); + m_indices.push_back(start + 3); + m_indices.push_back(start + 0); +} - if (m_vertices.empty()) - return; +void QuadRenderer::flush_to_pass(draco::rendering::rendergraph::Pass &pass) { + using namespace draco::rendering::rhi; - // Upload only the exact slices we are using this frame - update_dynamic_vertex_buffer(m_vb, 0, m_vertices.data(), static_cast(m_vertices.size() * sizeof(TexturedVertex))); - update_dynamic_index_buffer(m_ib, 0, m_indices.data(), static_cast(m_indices.size() * sizeof(u16))); + if (m_vertices.empty()) { return; } - RenderPacket pkt{}; - pkt.vertex_buffer = m_vb; - pkt.index_buffer = m_ib; - pkt.pipeline = m_pipeline; - pkt.texture_handle = m_batch_key.texture; - pkt.sampler_uniform = m_sampler; + // Upload only the exact slices we are using this frame + update_dynamic_vertex_buffer(m_vb, 0, m_vertices.data(), + static_cast(m_vertices.size() * + sizeof(TexturedVertex))); + update_dynamic_index_buffer(m_ib, 0, m_indices.data(), + static_cast(m_indices.size() * + sizeof(u16))); - pkt.vertex_count = static_cast(m_vertices.size()); - pkt.index_count = static_cast(m_indices.size()); + RenderPacket pkt{}; + pkt.vertex_buffer = m_vb; + pkt.index_buffer = m_ib; + pkt.pipeline = m_pipeline; + pkt.texture_handle = m_batch_key.texture; + pkt.sampler_uniform = m_sampler; - pkt.sort_key = make_sort_key(0, 0, static_cast(m_pipeline.value), static_cast(m_batch_key.texture.value), 0); + pkt.vertex_count = static_cast(m_vertices.size()); + pkt.index_count = static_cast(m_indices.size()); - bx::mtxIdentity(pkt.model); + pkt.sort_key = + make_sort_key(0, 0, static_cast(m_pipeline.value), + static_cast(m_batch_key.texture.value), 0); - pass.packets.push_back(pkt); + bx::mtxIdentity(pkt.model); - m_vertices.clear(); - m_indices.clear(); - } + pass.packets.push_back(pkt); - void QuadRenderer::shutdown() - { - using namespace draco::rendering::rhi; + m_vertices.clear(); + m_indices.clear(); +} + +void QuadRenderer::shutdown() { + using namespace draco::rendering::rhi; - destroy_buffer(m_vb); - destroy_buffer(m_ib); + destroy_buffer(m_vb); + destroy_buffer(m_ib); - destroy_uniform(m_sampler); - } + destroy_uniform(m_sampler); +} - void QuadRenderer::build_ortho(OrthoCamera& cam, f32 width, f32 height) - { - using namespace draco::rendering::rhi; +void QuadRenderer::build_ortho(OrthoCamera &cam, f32 width, f32 height) { + using namespace draco::rendering::rhi; - identity_matrix(cam.view); - identity_matrix(cam.proj); + identity_matrix(cam.view); + identity_matrix(cam.proj); - f32 rl = std::max(width, 1.0f); - f32 tb = std::max(height, 1.0f); + f32 rl = std::max(width, 1.0F); + f32 tb = std::max(height, 1.0F); - cam.proj[0] = 2.0f / rl; - cam.proj[5] = -2.0f / tb; - cam.proj[10] = -1.0f; + cam.proj[0] = 2.0F / rl; + cam.proj[5] = -2.0F / tb; + cam.proj[10] = -1.0F; - cam.proj[12] = -1.0f; - cam.proj[13] = 1.0f; - } + cam.proj[12] = -1.0F; + cam.proj[13] = 1.0F; } +} // namespace draco::rendering::quad_renderer diff --git a/engine/native/rendering/quad_renderer/quad_renderer.cppm b/engine/native/rendering/quad_renderer/quad_renderer.cppm index 8a41c14c..2cfcb125 100644 --- a/engine/native/rendering/quad_renderer/quad_renderer.cppm +++ b/engine/native/rendering/quad_renderer/quad_renderer.cppm @@ -12,78 +12,87 @@ import rendering.rendergraph; export namespace draco::rendering::quad_renderer { - struct BatchKey { - draco::rendering::rhi::TextureHandle texture = draco::rendering::rhi::InvalidTexture; +struct BatchKey { + draco::rendering::rhi::TextureHandle texture = + draco::rendering::rhi::InvalidTexture; - draco::rendering::rhi::PipelineHandle pipeline = draco::rendering::rhi::InvalidPipeline; + draco::rendering::rhi::PipelineHandle pipeline = + draco::rendering::rhi::InvalidPipeline; - draco::rendering::rhi::SamplerHandle sampler = draco::rendering::rhi::InvalidSampler; + draco::rendering::rhi::SamplerHandle sampler = + draco::rendering::rhi::InvalidSampler; - bool operator==(const BatchKey&) const = default; - }; + bool operator==(BatchKey const &) const = default; +}; - struct QuadCommand { - draco::rendering::rhi::TextureHandle texture = draco::rendering::rhi::InvalidTexture; +struct QuadCommand { + draco::rendering::rhi::TextureHandle texture = + draco::rendering::rhi::InvalidTexture; - f32 x = 0.0f; - f32 y = 0.0f; - f32 z = 0.0f; + f32 x = 0.0F; + f32 y = 0.0F; + f32 z = 0.0F; - f32 width = 1.0f; - f32 height = 1.0f; + f32 width = 1.0F; + f32 height = 1.0F; - f32 rotation = 0.0f; + f32 rotation = 0.0F; - u32 color = 0xffffffff; - }; + u32 color = 0xffff'ffff; +}; - struct OrthoCamera { - f32 view[16]; - f32 proj[16]; +struct OrthoCamera { + f32 view[16]; + f32 proj[16]; - f32 x = 0.0f; - f32 y = 0.0f; - f32 zoom = 1.0f; - }; + f32 x = 0.0F; + f32 y = 0.0F; + f32 zoom = 1.0F; +}; - class QuadRenderer { - public: - static constexpr u32 MaxQuads = 10000; - static constexpr u32 MaxVertices = MaxQuads * 4; - static constexpr u32 MaxIndices = MaxQuads * 6; +class QuadRenderer { + public: + static constexpr u32 MaxQuads = 10000; + static constexpr u32 MaxVertices = MaxQuads * 4; + static constexpr u32 MaxIndices = MaxQuads * 6; - void init(draco::rendering::rhi::PipelineHandle pipeline); + void init(draco::rendering::rhi::PipelineHandle pipeline); - void begin(); + void begin(); - void submit(const QuadCommand& cmd); + void submit(QuadCommand const &cmd); - void flush_to_pass(draco::rendering::rendergraph::Pass& pass); + void flush_to_pass(draco::rendering::rendergraph::Pass &pass); - void shutdown(); + void shutdown(); - static void build_ortho(OrthoCamera& cam, f32 width, f32 height); + static void build_ortho(OrthoCamera &cam, f32 width, f32 height); - private: - void push_quad(const QuadCommand& cmd); + private: + void push_quad(QuadCommand const &cmd); - private: - BatchKey m_batch_key{}; + private: + BatchKey m_batch_key{}; - std::vector m_vertices; + std::vector m_vertices; - std::vector m_indices; + std::vector m_indices; - draco::rendering::rhi::BufferHandle m_vb = draco::rendering::rhi::InvalidBuffer; + draco::rendering::rhi::BufferHandle m_vb = + draco::rendering::rhi::InvalidBuffer; - draco::rendering::rhi::BufferHandle m_ib = draco::rendering::rhi::InvalidBuffer; + draco::rendering::rhi::BufferHandle m_ib = + draco::rendering::rhi::InvalidBuffer; - draco::rendering::rhi::LayoutHandle m_layout = draco::rendering::rhi::InvalidLayout; + draco::rendering::rhi::LayoutHandle m_layout = + draco::rendering::rhi::InvalidLayout; - draco::rendering::rhi::PipelineHandle m_pipeline = draco::rendering::rhi::InvalidPipeline; + draco::rendering::rhi::PipelineHandle m_pipeline = + draco::rendering::rhi::InvalidPipeline; - draco::rendering::rhi::UniformHandle m_sampler = draco::rendering::rhi::InvalidUniform; + draco::rendering::rhi::UniformHandle m_sampler = + draco::rendering::rhi::InvalidUniform; - u32 m_quad_count = 0; - }; -} + u32 m_quad_count = 0; +}; +} // namespace draco::rendering::quad_renderer diff --git a/engine/native/rendering/renderer/renderer.cpp b/engine/native/rendering/renderer/renderer.cpp index 0859b5ed..a5043c29 100644 --- a/engine/native/rendering/renderer/renderer.cpp +++ b/engine/native/rendering/renderer/renderer.cpp @@ -20,143 +20,138 @@ import rendering.mesh; import rendering.material; import rendering.quad_renderer; -namespace draco::rendering::renderer -{ - static constexpr const char* MAIN_PASS = "MainPass"; +namespace draco::rendering::renderer { +static constexpr char const *MAIN_PASS = "MainPass"; - void init(u16 width, u16 height) - { - g_ctx.screen_width = width; - g_ctx.screen_height = height; - } +void init(u16 width, u16 height) { + g_ctx.screen_width = width; + g_ctx.screen_height = height; +} - void resize(u16 width, u16 height) - { - g_ctx.screen_width = width; - g_ctx.screen_height = height; - } +void resize(u16 width, u16 height) { + g_ctx.screen_width = width; + g_ctx.screen_height = height; +} - void begin_frame(const Camera& cam) - { - rhi::begin_frame(); +void begin_frame(Camera const &cam) { + rhi::begin_frame(); - g_ctx.main_camera = cam; - g_ctx.graph.reset(); + g_ctx.main_camera = cam; + g_ctx.graph.reset(); - // Create main pass once per frame - auto& pass = g_ctx.graph.add_pass(MAIN_PASS); + // Create main pass once per frame + auto &pass = g_ctx.graph.add_pass(MAIN_PASS); - pass.view = 0; - pass.framebuffer = rhi::InvalidFramebuffer; + pass.view = 0; + pass.framebuffer = rhi::InvalidFramebuffer; - pass.width = g_ctx.screen_width; - pass.height = g_ctx.screen_height; + pass.width = g_ctx.screen_width; + pass.height = g_ctx.screen_height; - pass.clear_flags = BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH; - pass.clear_color = 0x303030ff; + pass.clear_flags = BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH; + pass.clear_color = 0x3030'30ff; - f32 view_mtx[16]; - f32 proj_mtx[16]; + f32 view_mtx[16]; + f32 proj_mtx[16]; - rhi::look_at(view_mtx, cam.position.data(), cam.target.data(), cam.up.data()); + rhi::look_at(view_mtx, cam.position.data(), cam.target.data(), + cam.up.data()); - f32 aspect = f32(g_ctx.screen_width) / f32(std::max(g_ctx.screen_height, 1)); + f32 aspect = + f32(g_ctx.screen_width) / f32(std::max(g_ctx.screen_height, 1)); - rhi::perspective(proj_mtx, cam.fov, aspect, cam.near_plane, cam.far_plane); + rhi::perspective(proj_mtx, cam.fov, aspect, cam.near_plane, cam.far_plane); - std::memcpy(pass.view_mtx, view_mtx, sizeof(view_mtx)); - std::memcpy(pass.proj_mtx, proj_mtx, sizeof(proj_mtx)); - } + std::memcpy(pass.view_mtx, view_mtx, sizeof(view_mtx)); + std::memcpy(pass.proj_mtx, proj_mtx, sizeof(proj_mtx)); +} - static void build_uniforms(const material::Material& mat, std::vector& out) - { - out.clear(); - out.reserve(mat.uniforms.size()); +static void build_uniforms(material::Material const &mat, + std::vector &out) { + out.clear(); + out.reserve(mat.uniforms.size()); - for (const auto& u : mat.uniforms) - { - rhi::UniformBind bind{}; + for (auto const &u : mat.uniforms) { + rhi::UniformBind bind{}; - bind.handle = rhi::get_uniform(u.name_hash); + bind.handle = rhi::get_uniform(u.name_hash); - bind.data = u.data; - bind.num = u.count; + bind.data = u.data; + bind.num = u.count; - if (bind.handle == rhi::InvalidUniform) - { - std::println("[Renderer] Missing uniform hash: {}", u.name_hash); - continue; - } + if (bind.handle == rhi::InvalidUniform) { + std::println("[Renderer] Missing uniform hash: {}", u.name_hash); + continue; + } - out.push_back(bind); - } - } + out.push_back(bind); + } +} - void submit_entity(const rhi::RenderPacket& packet) - { - auto* pass = g_ctx.graph.get_pass(MAIN_PASS); - if (!pass) return; +void submit_entity(rhi::RenderPacket const &packet) { + auto *pass = g_ctx.graph.get_pass(MAIN_PASS); + if (!pass) { return; } - pass->packets.push_back(packet); - } + pass->packets.push_back(packet); +} - void submit_renderable(const draco::math::Transform& transform, const material::Material& material, mesh::MeshHandle mesh_id) - { - const auto* m = mesh::get(mesh_id); - if (!m) return; +void submit_renderable(draco::math::Transform const &transform, + material::Material const &material, + mesh::MeshHandle mesh_id) { + auto const *m = mesh::get(mesh_id); + if (!m) { return; } - rhi::RenderPacket p{}; + rhi::RenderPacket p{}; - p.vertex_buffer = m->vbh; - p.index_buffer = m->ibh; + p.vertex_buffer = m->vbh; + p.index_buffer = m->ibh; - p.pipeline = material.pipeline; - p.texture_handle = material.texture; - p.texture_unit = material.texture_unit; - p.sampler_uniform = material.sampler; + p.pipeline = material.pipeline; + p.texture_handle = material.texture; + p.texture_unit = material.texture_unit; + p.sampler_uniform = material.sampler; - build_uniforms(material, p.uniforms); + build_uniforms(material, p.uniforms); - f32 model[16]; - draco::math::compute_matrix(transform, model); + f32 model[16]; + draco::math::compute_matrix(transform, model); - std::memcpy(p.model, model, sizeof(model)); + std::memcpy(p.model, model, sizeof(model)); - submit_entity(p); - } + submit_entity(p); +} - void submit_ui(draco::rendering::quad_renderer::QuadRenderer& quad_renderer) - { - auto& ui_pass = g_ctx.graph.add_pass("UIPass"); +void submit_ui(draco::rendering::quad_renderer::QuadRenderer &quad_renderer) { + auto &ui_pass = g_ctx.graph.add_pass("UIPass"); - ui_pass.view = 1; - ui_pass.sort_mode = rendergraph::SortMode::None; + ui_pass.view = 1; + ui_pass.sort_mode = rendergraph::SortMode::None; - ui_pass.framebuffer = rhi::InvalidFramebuffer; + ui_pass.framebuffer = rhi::InvalidFramebuffer; - ui_pass.width = g_ctx.screen_width; - ui_pass.height = g_ctx.screen_height; + ui_pass.width = g_ctx.screen_width; + ui_pass.height = g_ctx.screen_height; - ui_pass.clear_flags = 0; + ui_pass.clear_flags = 0; - draco::rendering::quad_renderer::OrthoCamera ortho; + draco::rendering::quad_renderer::OrthoCamera ortho; - draco::rendering::quad_renderer::QuadRenderer::build_ortho(ortho, (f32)g_ctx.screen_width, (f32)g_ctx.screen_height); + draco::rendering::quad_renderer::QuadRenderer::build_ortho( + ortho, (f32)g_ctx.screen_width, (f32)g_ctx.screen_height + ); - std::memcpy(ui_pass.view_mtx, ortho.view, sizeof(f32) * 16); - std::memcpy(ui_pass.proj_mtx, ortho.proj, sizeof(f32) * 16); + std::memcpy(ui_pass.view_mtx, ortho.view, sizeof(f32) * 16); + std::memcpy(ui_pass.proj_mtx, ortho.proj, sizeof(f32) * 16); - quad_renderer.flush_to_pass(ui_pass); - } + quad_renderer.flush_to_pass(ui_pass); +} - void end_frame() - { - g_ctx.graph.execute(); - rhi::end_frame(); - } +void end_frame() { + g_ctx.graph.execute(); + rhi::end_frame(); +} - rendergraph::RenderGraph& get_graph() - { - return draco::rendering::renderer::g_ctx.graph; - } +rendergraph::RenderGraph &get_graph() { + return draco::rendering::renderer::g_ctx.graph; } +} // namespace draco::rendering::renderer diff --git a/engine/native/rendering/renderer/renderer.cppm b/engine/native/rendering/renderer/renderer.cppm index bcc9d184..322ce98c 100644 --- a/engine/native/rendering/renderer/renderer.cppm +++ b/engine/native/rendering/renderer/renderer.cppm @@ -16,35 +16,37 @@ import rendering.mesh; export namespace draco::rendering::renderer { - struct Camera { - std::array position {0.0f, 0.0f, 0.0f}; - std::array target {0.0f, 0.0f, 0.0f}; - std::array up {0.0f, 1.0f, 0.0f}; - f32 fov = 60.0f; - f32 near_plane = 0.1f; - f32 far_plane = 1000.0f; - }; +struct Camera { + std::array position{0.0F, 0.0F, 0.0F}; + std::array target{0.0F, 0.0F, 0.0F}; + std::array up{0.0F, 1.0F, 0.0F}; + f32 fov = 60.0F; + f32 near_plane = 0.1F; + f32 far_plane = 1000.0F; +}; - struct SceneContext { - u16 screen_width = 0; - u16 screen_height = 0; - Camera main_camera; +struct SceneContext { + u16 screen_width = 0; + u16 screen_height = 0; + Camera main_camera; - draco::rendering::rendergraph::RenderGraph graph; - }; + draco::rendering::rendergraph::RenderGraph graph; +}; - inline SceneContext g_ctx; +inline SceneContext g_ctx; - void init(u16 width, u16 height); - void resize(u16 width, u16 height); +void init(u16 width, u16 height); +void resize(u16 width, u16 height); - void begin_frame(const Camera& cam); +void begin_frame(Camera const &cam); - void submit_entity(draco::rendering::rhi::RenderPacket& packet, u16 view); - void submit_renderable(const draco::math::Transform& transform, const material::Material& material, mesh::MeshHandle mesh_id); - void submit_ui(draco::rendering::quad_renderer::QuadRenderer& quad_renderer); +void submit_entity(draco::rendering::rhi::RenderPacket &packet, u16 view); +void submit_renderable(draco::math::Transform const &transform, + material::Material const &material, + mesh::MeshHandle mesh_id); +void submit_ui(draco::rendering::quad_renderer::QuadRenderer &quad_renderer); - void end_frame(); +void end_frame(); - rendergraph::RenderGraph& get_graph(); -} +rendergraph::RenderGraph &get_graph(); +} // namespace draco::rendering::renderer diff --git a/engine/native/rendering/rendergraph/rendergraph.cpp b/engine/native/rendering/rendergraph/rendergraph.cpp index 51d8d53a..30cfa9a9 100644 --- a/engine/native/rendering/rendergraph/rendergraph.cpp +++ b/engine/native/rendering/rendergraph/rendergraph.cpp @@ -11,111 +11,90 @@ import rendering.rhi; namespace draco::rendering::rendergraph { - static void sort_material(std::vector& packets) - { - std::sort(packets.begin(), packets.end(), - [](const rhi::RenderPacket& a, const rhi::RenderPacket& b) - { - // Pipeline first - if (a.pipeline != b.pipeline) - return a.pipeline.value < b.pipeline.value; - - // Texture second - if (a.texture_handle != b.texture_handle) - return a.texture_handle.value < b.texture_handle.value; - - // Vertex buffer third - if (a.vertex_buffer != b.vertex_buffer) - return a.vertex_buffer.value < b.vertex_buffer.value; - - // Index buffer fallback - return a.index_buffer.value < b.index_buffer.value; - }); - } - - // Placeholder until depth sorting exists - static void sort_front_to_back(std::vector& packets) - { - sort_material(packets); - } - - static void sort_back_to_front(std::vector& packets) - { - sort_material(packets); - } - - static void sort_packets(std::vector& packets, SortMode mode) - { - switch (mode) - { - case SortMode::None: - break; - - case SortMode::Material: - sort_material(packets); - break; - - case SortMode::FrontToBack: - sort_front_to_back(packets); - break; - - case SortMode::BackToFront: - sort_back_to_front(packets); - break; - } - } - - void RenderGraph::reset() - { - m_passes.clear(); // Directly clear - } - - Pass& RenderGraph::add_pass(const std::string& name) - { - m_passes.emplace_back(); - - auto& pass = m_passes.back(); - pass.name = name; - - return pass; - } - - Pass* RenderGraph::get_pass(const std::string& name) - { - for (auto& p : m_passes) - { - if (p.name == name) - return &p; - } - - return nullptr; - } - - void RenderGraph::execute() - { - for (auto& pass : m_passes) - { - // Future dependency handling hook - for (const auto& dep : pass.dependencies) - { - (void)dep; - } - - sort_packets(pass.packets, pass.sort_mode); - - rhi::apply_view(pass.view, {pass.framebuffer, 0, 0, pass.width, pass.height, pass.clear_flags, pass.clear_color}); - - rhi::set_view_projection(pass.view, pass.view_mtx, pass.proj_mtx); - - if (pass.clear_flags) - { - bgfx::setViewClear(pass.view, pass.clear_flags, pass.clear_color); - } - - for (auto& pkt : pass.packets) - { - rhi::submit(pkt, pass.view); - } - } - } +static void sort_material(std::vector &packets) { + std::sort(packets.begin(), packets.end(), + [](rhi::RenderPacket const &a, rhi::RenderPacket const &b) { + // Pipeline first + if (a.pipeline != b.pipeline) { + return a.pipeline.value < b.pipeline.value; + } + + // Texture second + if (a.texture_handle != b.texture_handle) { + return a.texture_handle.value < b.texture_handle.value; + } + + // Vertex buffer third + if (a.vertex_buffer != b.vertex_buffer) { + return a.vertex_buffer.value < b.vertex_buffer.value; + } + + // Index buffer fallback + return a.index_buffer.value < b.index_buffer.value; + }); } + +// Placeholder until depth sorting exists +static void sort_front_to_back(std::vector &packets) { + sort_material(packets); +} + +static void sort_back_to_front(std::vector &packets) { + sort_material(packets); +} + +static void sort_packets(std::vector &packets, + SortMode mode) { + switch (mode) { + case SortMode::None: break; + + case SortMode::Material: sort_material(packets); break; + + case SortMode::FrontToBack: sort_front_to_back(packets); break; + + case SortMode::BackToFront: sort_back_to_front(packets); break; + } +} + +void RenderGraph::reset() { + m_passes.clear(); // Directly clear +} + +Pass &RenderGraph::add_pass(std::string const &name) { + m_passes.emplace_back(); + + auto &pass = m_passes.back(); + pass.name = name; + + return pass; +} + +Pass *RenderGraph::get_pass(std::string const &name) { + for (auto &p : m_passes) { + if (p.name == name) { return &p; } + } + + return nullptr; +} + +void RenderGraph::execute() { + for (auto &pass : m_passes) { + // Future dependency handling hook + for (auto const &dep : pass.dependencies) { (void)dep; } + + sort_packets(pass.packets, pass.sort_mode); + + rhi::apply_view(pass.view, + {pass.framebuffer, 0, 0, pass.width, pass.height, + pass.clear_flags, pass.clear_color}); + + rhi::set_view_projection(pass.view, pass.view_mtx, pass.proj_mtx); + + if (pass.clear_flags) { + bgfx::setViewClear(pass.view, pass.clear_flags, pass.clear_color); + } + + for (auto &pkt : pass.packets) { rhi::submit(pkt, pass.view); } + } +} +} // namespace draco::rendering::rendergraph diff --git a/engine/native/rendering/rendergraph/rendergraph.cppm b/engine/native/rendering/rendergraph/rendergraph.cppm index 6549e1ae..55a690cc 100644 --- a/engine/native/rendering/rendergraph/rendergraph.cppm +++ b/engine/native/rendering/rendergraph/rendergraph.cppm @@ -10,70 +10,58 @@ import rendering.rhi; export namespace draco::rendering::rendergraph { - enum class PassType : u8 - { - Graphics, - Transparent, - Shadow, - PostProcess, - UI - }; - - enum class SortMode : u8 - { - None, - Material, - FrontToBack, - BackToFront - }; - - struct Pass - { - std::string name; - - PassType type = PassType::Graphics; - SortMode sort_mode = SortMode::Material; - - std::vector dependencies; - - rhi::ViewID view = 0; - rhi::FramebufferHandle framebuffer = rhi::InvalidFramebuffer; - - std::vector packets; - - f32 view_mtx[16] = { - 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f - }; - - f32 proj_mtx[16] = { - 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f - }; - - u16 width = 0; - u16 height = 0; - - u32 clear_flags = 0; - u32 clear_color = 0; - }; - - class RenderGraph - { - public: - void reset(); - - Pass& add_pass(const std::string& name); - - Pass* get_pass(const std::string& name); - - void execute(); - - private: - std::vector m_passes; - }; -} +enum class PassType : u8 { + Graphics, + Transparent, + Shadow, + PostProcess, + UI +}; + +enum class SortMode : u8 { + None, + Material, + FrontToBack, + BackToFront +}; + +struct Pass { + std::string name; + + PassType type = PassType::Graphics; + SortMode sort_mode = SortMode::Material; + + std::vector dependencies; + + rhi::ViewID view = 0; + rhi::FramebufferHandle framebuffer = rhi::InvalidFramebuffer; + + std::vector packets; + + f32 view_mtx[16] = {1.0F, 0.0F, 0.0F, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F, + 0.0F, 0.0F, 1.0F, 0.0F, 0.0F, 0.0F, 0.0F, 1.0F}; + + f32 proj_mtx[16] = {1.0F, 0.0F, 0.0F, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F, + 0.0F, 0.0F, 1.0F, 0.0F, 0.0F, 0.0F, 0.0F, 1.0F}; + + u16 width = 0; + u16 height = 0; + + u32 clear_flags = 0; + u32 clear_color = 0; +}; + +class RenderGraph { + public: + void reset(); + + Pass &add_pass(std::string const &name); + + Pass *get_pass(std::string const &name); + + void execute(); + + private: + std::vector m_passes; +}; +} // namespace draco::rendering::rendergraph diff --git a/engine/native/rendering/rhi/buffers.cpp b/engine/native/rendering/rhi/buffers.cpp index d1e6d11b..f3c7a7e2 100644 --- a/engine/native/rendering/rhi/buffers.cpp +++ b/engine/native/rendering/rhi/buffers.cpp @@ -8,95 +8,98 @@ module rendering.rhi; import core.stdtypes; import core.math.constants; -namespace draco::rendering::rhi -{ - BufferHandle create_vertex_buffer(const void* data, u32 size, LayoutHandle layout_h) - { - RHI_ASSERT(data != nullptr, "Vertex buffer data is null"); - RHI_ASSERT(size > 0, "Vertex buffer size is zero"); +namespace draco::rendering::rhi { +BufferHandle +create_vertex_buffer(void const *data, u32 size, LayoutHandle layout_h) { + RHI_ASSERT(data != nullptr, "Vertex buffer data is null"); + RHI_ASSERT(size > 0, "Vertex buffer size is zero"); - auto* layout = get_checked(g_layouts, layout_h, "Layout"); + auto *layout = get_checked(g_layouts, layout_h, "Layout"); - RHI_ASSERT(layout, "Invalid vertex layout"); + RHI_ASSERT(layout, "Invalid vertex layout"); - auto vbh = bgfx::createVertexBuffer(bgfx::copy(data, size), layout->layout); + auto vbh = bgfx::createVertexBuffer(bgfx::copy(data, size), layout->layout); - Buffer buf; - buf.vbh = vbh; + Buffer buf; + buf.vbh = vbh; - return g_buffers.create(buf); - } + return g_buffers.create(buf); +} - BufferHandle create_index_buffer(const void* data, u32 size) - { - RHI_ASSERT(data != nullptr, "Index buffer data is null"); - RHI_ASSERT(size > 0, "Index buffer size is zero"); +BufferHandle create_index_buffer(void const *data, u32 size) { + RHI_ASSERT(data != nullptr, "Index buffer data is null"); + RHI_ASSERT(size > 0, "Index buffer size is zero"); - bgfx::IndexBufferHandle ibh = bgfx::createIndexBuffer(bgfx::copy(data, size), BGFX_BUFFER_INDEX32); + bgfx::IndexBufferHandle ibh = + bgfx::createIndexBuffer(bgfx::copy(data, size), BGFX_BUFFER_INDEX32); - Buffer buf; // Idk why I named it this, it just sounds funny ;) - buf.ibh = ibh; - buf.is_index = true; + Buffer buf; // Idk why I named it this, it just sounds funny ;) + buf.ibh = ibh; + buf.is_index = true; - return g_buffers.create(buf); - } + return g_buffers.create(buf); +} - BufferHandle create_dynamic_vertex_buffer(u32 size, LayoutHandle layout_h) - { - auto* layout = get_checked(g_layouts, layout_h, "Layout"); - RHI_ASSERT(layout, "Invalid layout"); +BufferHandle create_dynamic_vertex_buffer(u32 size, LayoutHandle layout_h) { + auto *layout = get_checked(g_layouts, layout_h, "Layout"); + RHI_ASSERT(layout, "Invalid layout"); - bgfx::DynamicVertexBufferHandle dvbh = bgfx::createDynamicVertexBuffer(size, layout->layout); + bgfx::DynamicVertexBufferHandle dvbh = + bgfx::createDynamicVertexBuffer(size, layout->layout); - RHI_ASSERT(bgfx::isValid(dvbh), "Failed to create dynamic vertex buffer"); + RHI_ASSERT(bgfx::isValid(dvbh), "Failed to create dynamic vertex buffer"); - Buffer buf; - buf.dvbh = dvbh; - buf.is_dynamic = true; + Buffer buf; + buf.dvbh = dvbh; + buf.is_dynamic = true; - return g_buffers.create(buf); - } + return g_buffers.create(buf); +} - void update_dynamic_vertex_buffer(BufferHandle handle, u32 start_vertex, const void* data, u32 size) - { - auto* buf = get_checked(g_buffers, handle, "Buffer"); +void update_dynamic_vertex_buffer(BufferHandle handle, + u32 start_vertex, + void const *data, + u32 size) { + auto *buf = get_checked(g_buffers, handle, "Buffer"); - if (!buf) - return; + if (!buf) { return; } - RHI_ASSERT(buf->is_dynamic && !buf->is_index, "Not a dynamic vertex buffer"); - RHI_ASSERT(bgfx::isValid(buf->dvbh), "Invalid dynamic vertex buffer handle"); + RHI_ASSERT(buf->is_dynamic && !buf->is_index, + "Not a dynamic vertex buffer"); + RHI_ASSERT(bgfx::isValid(buf->dvbh), + "Invalid dynamic vertex buffer handle"); - const bgfx::Memory* mem = bgfx::copy(data, size); + bgfx::Memory const *mem = bgfx::copy(data, size); - bgfx::update(buf->dvbh, start_vertex, mem); - } + bgfx::update(buf->dvbh, start_vertex, mem); +} - BufferHandle create_dynamic_index_buffer(u32 size, u16 flags) - { - bgfx::DynamicIndexBufferHandle ibh = bgfx::createDynamicIndexBuffer(size, flags); +BufferHandle create_dynamic_index_buffer(u32 size, u16 flags) { + bgfx::DynamicIndexBufferHandle ibh = + bgfx::createDynamicIndexBuffer(size, flags); - RHI_ASSERT(bgfx::isValid(ibh), "Invalid dynamic index buffer handle"); + RHI_ASSERT(bgfx::isValid(ibh), "Invalid dynamic index buffer handle"); - Buffer buf{}; - buf.is_dynamic = true; - buf.is_index = true; - buf.dibh = ibh; + Buffer buf{}; + buf.is_dynamic = true; + buf.is_index = true; + buf.dibh = ibh; - return g_buffers.create(buf); - } + return g_buffers.create(buf); +} - void update_dynamic_index_buffer(BufferHandle handle, u32 start_index, const void* data, u32 size) - { - auto* buf = get_checked(g_buffers, handle, "DynamicIndexBuffer"); +void update_dynamic_index_buffer(BufferHandle handle, + u32 start_index, + void const *data, + u32 size) { + auto *buf = get_checked(g_buffers, handle, "DynamicIndexBuffer"); - if (!buf) - return; + if (!buf) { return; } - RHI_ASSERT(buf->is_dynamic && buf->is_index, "Not a dynamic index buffer"); + RHI_ASSERT(buf->is_dynamic && buf->is_index, "Not a dynamic index buffer"); - const bgfx::Memory* mem = bgfx::copy(data, size); + bgfx::Memory const *mem = bgfx::copy(data, size); - bgfx::update(buf->dibh, start_index, mem); - } + bgfx::update(buf->dibh, start_index, mem); } +} // namespace draco::rendering::rhi diff --git a/engine/native/rendering/rhi/commands.cpp b/engine/native/rendering/rhi/commands.cpp index 4b801158..02b7c377 100644 --- a/engine/native/rendering/rhi/commands.cpp +++ b/engine/native/rendering/rhi/commands.cpp @@ -10,141 +10,115 @@ module rendering.rhi; import core.stdtypes; import core.math.constants; -namespace draco::rendering::rhi -{ - void perspective(f32* out, f32 fov, f32 aspect, f32 nearp, f32 farp) - { - bx::mtxProj(out, fov, aspect, nearp, farp, bgfx::getCaps()->homogeneousDepth); - } - - void look_at(f32* out, const f32* eye, const f32* at, const f32* up) - { - bx::Vec3 eye_v { eye[0], eye[1], eye[2] }; - bx::Vec3 at_v { at[0], at[1], at[2] }; - bx::Vec3 up_v { up[0], up[1], up[2] }; - - bx::mtxLookAt(out, eye_v, at_v, up_v); - } - - // Note: Internal use only, use apply_view() instead - void set_view_rect(ViewID view, u16 x, u16 y, u16 w, u16 h) - { - bgfx::setViewRect(view, x, y, w, h); - } - - // Note: Internal use only, use apply_view() instead - void set_view_framebuffer(ViewID view, FramebufferHandle h) - { - auto* fb = get_checked(g_framebuffers, h, "Framebuffer"); - - if (!fb) - return; - - bgfx::setViewFrameBuffer(view, fb->fbh); - } - - void set_view_projection(ViewID view, const f32* view_mtx, const f32* proj_mtx) - { - bgfx::setViewTransform(view, view_mtx, proj_mtx); - } - - void set_scissor(const ScissorRect& r) - { - if (!r.enabled) - bgfx::setScissor(math::UINT16_MAX_VAL); - else - bgfx::setScissor(r.x, r.y, r.w, r.h); - } - - void set_stencil(u32 fstencil, u32 bstencil) - { - bgfx::setStencil(fstencil, bstencil); - } - - void apply_view(ViewID view, const ViewDesc& desc) - { - if (desc.fb != InvalidFramebuffer) - { - auto* fb = get_checked(g_framebuffers, desc.fb, "Framebuffer"); - - if (fb && bgfx::isValid(fb->fbh)) - { - bgfx::setViewFrameBuffer(view, fb->fbh); - } - else - { - RHI_WARN(false, "Framebuffer invalid at apply_view"); - } - } - - bgfx::setViewRect(view, desc.x, desc.y, desc.w, desc.h); - - if (desc.clear_flags != 0) - { - bgfx::setViewClear(view, desc.clear_flags, desc.clear_color); - } - } - - void identity_matrix(f32* mtx) - { - bx::mtxIdentity(mtx); - } - - void submit(const RenderPacket& p, ViewID view) - { - auto* pipeline = get_checked(g_pipelines, p.pipeline, "Pipeline"); - auto* vb = get_checked(g_buffers, p.vertex_buffer, "VertexBuffer"); - Buffer* ib = nullptr; - - if (!pipeline || !vb) - return; - - if (p.index_buffer != InvalidBuffer) - ib = get_checked(g_buffers, p.index_buffer, "IndexBuffer"); - - // Transform matrix (model) - bgfx::setTransform(p.model); - - // Vertex buffer binding with explicit range control - if (vb->is_dynamic) - { - // If count is UINT32_MAX, bgfx will fallback to drawing the full buffer automatically - bgfx::setVertexBuffer(0, vb->dvbh, 0, p.vertex_count); - } else { - bgfx::setVertexBuffer(0, vb->vbh, 0, p.vertex_count); - } - - // Index buffer binding with explicit range control - if (ib && ib->is_index) - { - if (ib->is_dynamic) - { - bgfx::setIndexBuffer(ib->dibh, 0, p.index_count); - } else { - bgfx::setIndexBuffer(ib->ibh, 0, p.index_count); - } - } - - // Uniforms - for (const auto& u : p.uniforms) - { - if (auto* handle = get_checked(g_uniforms, u.handle, "UniformBind")) - { - bgfx::setUniform(*handle, u.data, u.num); - } - } - - // Texture binding - if (auto* tex = get_checked(g_textures, p.texture_handle, "Texture")) - { - if (auto* sampler = get_checked(g_uniforms, p.sampler_uniform, "Sampler")) - { - bgfx::setTexture(p.texture_unit, *sampler, *tex, p.sampler_flags); - } - } - - // Apply pipeline state & submit draw call - bgfx::setState(pipeline->state); - bgfx::submit(view, pipeline->program); - } +namespace draco::rendering::rhi { +void perspective(f32 *out, f32 fov, f32 aspect, f32 nearp, f32 farp) { + bx::mtxProj(out, fov, aspect, nearp, farp, + bgfx::getCaps()->homogeneousDepth); } + +void look_at(f32 *out, f32 const *eye, f32 const *at, f32 const *up) { + bx::Vec3 eye_v{eye[0], eye[1], eye[2]}; + bx::Vec3 at_v{at[0], at[1], at[2]}; + bx::Vec3 up_v{up[0], up[1], up[2]}; + + bx::mtxLookAt(out, eye_v, at_v, up_v); +} + +// Note: Internal use only, use apply_view() instead +void set_view_rect(ViewID view, u16 x, u16 y, u16 w, u16 h) { + bgfx::setViewRect(view, x, y, w, h); +} + +// Note: Internal use only, use apply_view() instead +void set_view_framebuffer(ViewID view, FramebufferHandle h) { + auto *fb = get_checked(g_framebuffers, h, "Framebuffer"); + + if (!fb) { return; } + + bgfx::setViewFrameBuffer(view, fb->fbh); +} + +void +set_view_projection(ViewID view, f32 const *view_mtx, f32 const *proj_mtx) { + bgfx::setViewTransform(view, view_mtx, proj_mtx); +} + +void set_scissor(ScissorRect const &r) { + if (!r.enabled) { bgfx::setScissor(math::UINT16_MAX_VAL); } + else { bgfx::setScissor(r.x, r.y, r.w, r.h); } +} + +void set_stencil(u32 fstencil, u32 bstencil) { + bgfx::setStencil(fstencil, bstencil); +} + +void apply_view(ViewID view, ViewDesc const &desc) { + if (desc.fb != InvalidFramebuffer) { + auto *fb = get_checked(g_framebuffers, desc.fb, "Framebuffer"); + + if (fb && bgfx::isValid(fb->fbh)) { + bgfx::setViewFrameBuffer(view, fb->fbh); + } + else { RHI_WARN(false, "Framebuffer invalid at apply_view"); } + } + + bgfx::setViewRect(view, desc.x, desc.y, desc.w, desc.h); + + if (desc.clear_flags != 0) { + bgfx::setViewClear(view, desc.clear_flags, desc.clear_color); + } +} + +void identity_matrix(f32 *mtx) { + bx::mtxIdentity(mtx); +} + +void submit(RenderPacket const &p, ViewID view) { + auto *pipeline = get_checked(g_pipelines, p.pipeline, "Pipeline"); + auto *vb = get_checked(g_buffers, p.vertex_buffer, "VertexBuffer"); + Buffer *ib = nullptr; + + if (!pipeline || !vb) { return; } + + if (p.index_buffer != InvalidBuffer) { + ib = get_checked(g_buffers, p.index_buffer, "IndexBuffer"); + } + + // Transform matrix (model) + bgfx::setTransform(p.model); + + // Vertex buffer binding with explicit range control + if (vb->is_dynamic) { + // If count is UINT32_MAX, bgfx will fallback to drawing the full buffer automatically + bgfx::setVertexBuffer(0, vb->dvbh, 0, p.vertex_count); + } + else { bgfx::setVertexBuffer(0, vb->vbh, 0, p.vertex_count); } + + // Index buffer binding with explicit range control + if (ib && ib->is_index) { + if (ib->is_dynamic) { + bgfx::setIndexBuffer(ib->dibh, 0, p.index_count); + } + else { bgfx::setIndexBuffer(ib->ibh, 0, p.index_count); } + } + + // Uniforms + for (auto const &u : p.uniforms) { + if (auto *handle = get_checked(g_uniforms, u.handle, "UniformBind")) { + bgfx::setUniform(*handle, u.data, u.num); + } + } + + // Texture binding + if (auto *tex = get_checked(g_textures, p.texture_handle, "Texture")) { + if (auto *sampler = + get_checked(g_uniforms, p.sampler_uniform, "Sampler")) { + bgfx::setTexture(p.texture_unit, *sampler, *tex, p.sampler_flags); + } + } + + // Apply pipeline state & submit draw call + bgfx::setState(pipeline->state); + bgfx::submit(view, pipeline->program); +} +} // namespace draco::rendering::rhi diff --git a/engine/native/rendering/rhi/core.cpp b/engine/native/rendering/rhi/core.cpp index 91700d4a..71ea69ad 100644 --- a/engine/native/rendering/rhi/core.cpp +++ b/engine/native/rendering/rhi/core.cpp @@ -4,7 +4,7 @@ module; #include #include #include -#include +#include #include #include #include @@ -15,320 +15,261 @@ module rendering.rhi; import core.stdtypes; import core.math.constants; -namespace draco::rendering::rhi -{ - using namespace draco::core::memory; - - HandleRegistry g_buffers; - HandleRegistry g_pipelines; - HandleRegistry g_uniforms; - HandleRegistry g_textures; - HandleRegistry g_framebuffers; - HandleRegistry g_shaders; - HandleRegistry g_layouts; - - std::vector g_deletion_queue; - u16 g_width = 0; - u16 g_height = 0; - - void queue_destruction(std::function cb) - { - g_deletion_queue.push_back({ - bgfx::getStats()->gpuFrameNum, - std::move(cb) - }); - } - - // Explicit overloads for each bgfx resource - void destroy_later(bgfx::ShaderHandle handle) - { - queue_destruction([handle]() { - bgfx::destroy(handle); - }); - } - - void destroy_later(bgfx::UniformHandle handle) - { - queue_destruction([handle]() { - bgfx::destroy(handle); - }); - } - - void destroy_later(bgfx::VertexBufferHandle handle) - { - queue_destruction([handle]() { - bgfx::destroy(handle); - }); - } - - void destroy_later(bgfx::IndexBufferHandle handle) - { - queue_destruction([handle]() { - bgfx::destroy(handle); - }); - } - - void destroy_later(bgfx::DynamicVertexBufferHandle handle) - { - queue_destruction([handle]() { - bgfx::destroy(handle); - }); - } - - void destroy_later(bgfx::DynamicIndexBufferHandle handle) - { - queue_destruction([handle]() { - bgfx::destroy(handle); - }); - } - - void destroy_later(bgfx::TextureHandle handle) - { - queue_destruction([handle]() { - bgfx::destroy(handle); - }); - } - - void destroy_later(bgfx::FrameBufferHandle handle) - { - queue_destruction([handle]() { - bgfx::destroy(handle); - }); - } - - void process_deletions() - { - u64 frame = bgfx::getStats()->gpuFrameNum; - - std::erase_if(g_deletion_queue, [frame](const auto& d) - { - if (frame >= d.frame + 2) - { - d.cleanup(); - return true; - } - return false; - }); - } - - bool init(void* display_type, void* window_handle, draco::platform::NativeWindowType window_type, u16 width, u16 height) - { - g_width = width; - g_height = height; - - bgfx::Init init{}; - init.type = bgfx::RendererType::Count; - - init.platformData.ndt = display_type; - init.platformData.nwh = window_handle; - - // Map our internal window type to bgfx's native window handle type - if (window_type == draco::platform::NativeWindowType::Wayland) - { - init.platformData.type = bgfx::NativeWindowHandleType::Wayland; - } - else - { - // Others can work fine with the default type - init.platformData.type = bgfx::NativeWindowHandleType::Default; - } - - init.resolution.width = width; - init.resolution.height = height; - init.resolution.reset = BGFX_RESET_VSYNC; - - if (!bgfx::init(init)) - { - RHI_WARN(false, "bgfx initialization failed"); - return false; - } - - bgfx::setDebug(BGFX_DEBUG_TEXT); - return true; - } - - void resize(u16 width, u16 height) - { - if(width == 0 || height == 0) - return; // Minimized window safety - - if(width == g_width && height == g_height) - return; // No need to resize - - g_width = width; - g_height = height; - - bgfx::reset(width, height, BGFX_RESET_VSYNC); - } - - void shutdown() - { - // Walk all registries and destroy live GPU objects - for (auto& slot : g_buffers.internal().raw()) - { - if (!slot.alive) continue; - - if (bgfx::isValid(slot.value.vbh)) - bgfx::destroy(slot.value.vbh); - - if (bgfx::isValid(slot.value.ibh)) - bgfx::destroy(slot.value.ibh); - - if (bgfx::isValid(slot.value.dvbh)) - bgfx::destroy(slot.value.dvbh); - } - - for (auto& slot : g_pipelines.internal().raw()) - { - if (!slot.alive) continue; - - if (bgfx::isValid(slot.value.program)) - bgfx::destroy(slot.value.program); - } - - for (auto& slot : g_uniforms.internal().raw()) - { - if (!slot.alive) continue; - - if (bgfx::isValid(slot.value)) - bgfx::destroy(slot.value); - } - - for (auto& slot : g_textures.internal().raw()) - { - if (!slot.alive) continue; - - if (bgfx::isValid(slot.value)) - bgfx::destroy(slot.value); - } - - bgfx::shutdown(); - } - - void destroy_buffer(BufferHandle h) - { - auto* buf = get_checked(g_buffers, h, "Buffer"); - - if (!buf) - return; - - if (bgfx::isValid(buf->vbh)) - destroy_later(buf->vbh); - - if (bgfx::isValid(buf->ibh)) - destroy_later(buf->ibh); - - if (bgfx::isValid(buf->dvbh)) - destroy_later(buf->dvbh); - - if (bgfx::isValid(buf->dibh)) - destroy_later(buf->dibh); - - g_buffers.destroy(h); - } - - u64 map_state(PipelineState s, BlendMode blend, DepthTest depth, CullMode cull, bool depth_write) - { - u64 state = 0; - - if ((s & PipelineState::WriteRGB) != PipelineState::Default) - state |= BGFX_STATE_WRITE_RGB; - - if ((s & PipelineState::WriteAlpha) != PipelineState::Default) - state |= BGFX_STATE_WRITE_A; - - if (depth_write) - state |= BGFX_STATE_WRITE_Z; - - switch (depth) - { - case DepthTest::Less: state |= BGFX_STATE_DEPTH_TEST_LESS; break; - case DepthTest::Equal: state |= BGFX_STATE_DEPTH_TEST_EQUAL; break; - case DepthTest::Always: state |= BGFX_STATE_DEPTH_TEST_ALWAYS; break; - case DepthTest::None: break; - } - - - switch (cull) - { - case CullMode::CW: state |= BGFX_STATE_CULL_CW; break; - case CullMode::CCW: state |= BGFX_STATE_CULL_CCW; break; - case CullMode::None: break; - } - - switch (blend) - { - case BlendMode::Alpha: - state |= BGFX_STATE_BLEND_ALPHA; - break; - - case BlendMode::Additive: - state |= BGFX_STATE_BLEND_ADD; - break; - - case BlendMode::Multiply: - state |= BGFX_STATE_BLEND_MULTIPLY; - break; - - case BlendMode::None: - break; - } - - if ((s & PipelineState::MSAA) != PipelineState::Default) - state |= BGFX_STATE_MSAA; - - if ((s & PipelineState::PrimitiveTriStrip) != PipelineState::Default) - state |= BGFX_STATE_PT_TRISTRIP; - - return state; - } - - bgfx::UniformType::Enum map_uniform_type(UniformType t) - { - switch (t) - { - case UniformType::Sampler: return bgfx::UniformType::Sampler; - case UniformType::Vec4: return bgfx::UniformType::Vec4; - case UniformType::Mat3: return bgfx::UniformType::Mat3; - case UniformType::Mat4: return bgfx::UniformType::Mat4; - } - return bgfx::UniformType::Vec4; - } - - bgfx::Attrib::Enum map_attrib(Attrib a) - { - switch (a) - { - case Attrib::Position: return bgfx::Attrib::Position; - case Attrib::Color0: return bgfx::Attrib::Color0; - case Attrib::TexCoord0: return bgfx::Attrib::TexCoord0; - case Attrib::Normal: return bgfx::Attrib::Normal; - case Attrib::Tangent: return bgfx::Attrib::Tangent; - } - - return bgfx::Attrib::Position; - } - - bgfx::AttribType::Enum map_attrib_type(AttribType t) - { - switch (t) - { - case AttribType::Float: return bgfx::AttribType::Float; - case AttribType::Uint8: return bgfx::AttribType::Uint8; - } - - return bgfx::AttribType::Float; - } - - void begin_frame() - { - // Clean up GPU resources safely - process_deletions(); - } - - void end_frame() - { - // Submit frame to GPU - bgfx::frame(); - } +namespace draco::rendering::rhi { +using namespace draco::core::memory; + +HandleRegistry g_buffers; +HandleRegistry g_pipelines; +HandleRegistry g_uniforms; +HandleRegistry g_textures; +HandleRegistry g_framebuffers; +HandleRegistry g_shaders; +HandleRegistry g_layouts; + +std::vector g_deletion_queue; +u16 g_width = 0; +u16 g_height = 0; + +void queue_destruction(std::function cb) { + g_deletion_queue.push_back({bgfx::getStats()->gpuFrameNum, std::move(cb)}); +} + +// Explicit overloads for each bgfx resource +void destroy_later(bgfx::ShaderHandle handle) { + queue_destruction([handle]() { bgfx::destroy(handle); }); +} + +void destroy_later(bgfx::UniformHandle handle) { + queue_destruction([handle]() { bgfx::destroy(handle); }); +} + +void destroy_later(bgfx::VertexBufferHandle handle) { + queue_destruction([handle]() { bgfx::destroy(handle); }); +} + +void destroy_later(bgfx::IndexBufferHandle handle) { + queue_destruction([handle]() { bgfx::destroy(handle); }); +} + +void destroy_later(bgfx::DynamicVertexBufferHandle handle) { + queue_destruction([handle]() { bgfx::destroy(handle); }); +} + +void destroy_later(bgfx::DynamicIndexBufferHandle handle) { + queue_destruction([handle]() { bgfx::destroy(handle); }); +} + +void destroy_later(bgfx::TextureHandle handle) { + queue_destruction([handle]() { bgfx::destroy(handle); }); +} + +void destroy_later(bgfx::FrameBufferHandle handle) { + queue_destruction([handle]() { bgfx::destroy(handle); }); +} + +void process_deletions() { + u64 frame = bgfx::getStats()->gpuFrameNum; + + std::erase_if(g_deletion_queue, [frame](auto const &d) { + if (frame >= d.frame + 2) { + d.cleanup(); + return true; + } + return false; + }); +} + +bool init(void *display_type, + void *window_handle, + draco::platform::NativeWindowType window_type, + u16 width, + u16 height) { + g_width = width; + g_height = height; + + bgfx::Init init{}; + init.type = bgfx::RendererType::Count; + + init.platformData.ndt = display_type; + init.platformData.nwh = window_handle; + + // Map our internal window type to bgfx's native window handle type + if (window_type == draco::platform::NativeWindowType::Wayland) { + init.platformData.type = bgfx::NativeWindowHandleType::Wayland; + } + else { + // Others can work fine with the default type + init.platformData.type = bgfx::NativeWindowHandleType::Default; + } + + init.resolution.width = width; + init.resolution.height = height; + init.resolution.reset = BGFX_RESET_VSYNC; + + if (!bgfx::init(init)) { + RHI_WARN(false, "bgfx initialization failed"); + return false; + } + + bgfx::setDebug(BGFX_DEBUG_TEXT); + return true; +} + +void resize(u16 width, u16 height) { + if (width == 0 || height == 0) { + return; // Minimized window safety + } + + if (width == g_width && height == g_height) { + return; // No need to resize + } + + g_width = width; + g_height = height; + + bgfx::reset(width, height, BGFX_RESET_VSYNC); +} + +void shutdown() { + // Walk all registries and destroy live GPU objects + for (auto &slot : g_buffers.internal().raw()) { + if (!slot.alive) { continue; } + + if (bgfx::isValid(slot.value.vbh)) { bgfx::destroy(slot.value.vbh); } + + if (bgfx::isValid(slot.value.ibh)) { bgfx::destroy(slot.value.ibh); } + + if (bgfx::isValid(slot.value.dvbh)) { bgfx::destroy(slot.value.dvbh); } + } + + for (auto &slot : g_pipelines.internal().raw()) { + if (!slot.alive) { continue; } + + if (bgfx::isValid(slot.value.program)) { + bgfx::destroy(slot.value.program); + } + } + + for (auto &slot : g_uniforms.internal().raw()) { + if (!slot.alive) { continue; } + + if (bgfx::isValid(slot.value)) { bgfx::destroy(slot.value); } + } + + for (auto &slot : g_textures.internal().raw()) { + if (!slot.alive) { continue; } + + if (bgfx::isValid(slot.value)) { bgfx::destroy(slot.value); } + } + + bgfx::shutdown(); +} + +void destroy_buffer(BufferHandle h) { + auto *buf = get_checked(g_buffers, h, "Buffer"); + + if (!buf) { return; } + + if (bgfx::isValid(buf->vbh)) { destroy_later(buf->vbh); } + + if (bgfx::isValid(buf->ibh)) { destroy_later(buf->ibh); } + + if (bgfx::isValid(buf->dvbh)) { destroy_later(buf->dvbh); } + + if (bgfx::isValid(buf->dibh)) { destroy_later(buf->dibh); } + + g_buffers.destroy(h); +} + +u64 map_state(PipelineState s, + BlendMode blend, + DepthTest depth, + CullMode cull, + bool depth_write) { + u64 state = 0; + + if ((s & PipelineState::WriteRGB) != PipelineState::Default) { + state |= BGFX_STATE_WRITE_RGB; + } + + if ((s & PipelineState::WriteAlpha) != PipelineState::Default) { + state |= BGFX_STATE_WRITE_A; + } + + if (depth_write) { state |= BGFX_STATE_WRITE_Z; } + + switch (depth) { + case DepthTest::Less: state |= BGFX_STATE_DEPTH_TEST_LESS; break; + case DepthTest::Equal: state |= BGFX_STATE_DEPTH_TEST_EQUAL; break; + case DepthTest::Always: state |= BGFX_STATE_DEPTH_TEST_ALWAYS; break; + case DepthTest::None: break; + } + + switch (cull) { + case CullMode::CW: state |= BGFX_STATE_CULL_CW; break; + case CullMode::CCW: state |= BGFX_STATE_CULL_CCW; break; + case CullMode::None: break; + } + + switch (blend) { + case BlendMode::Alpha: state |= BGFX_STATE_BLEND_ALPHA; break; + + case BlendMode::Additive: state |= BGFX_STATE_BLEND_ADD; break; + + case BlendMode::Multiply: state |= BGFX_STATE_BLEND_MULTIPLY; break; + + case BlendMode::None: break; + } + + if ((s & PipelineState::MSAA) != PipelineState::Default) { + state |= BGFX_STATE_MSAA; + } + + if ((s & PipelineState::PrimitiveTriStrip) != PipelineState::Default) { + state |= BGFX_STATE_PT_TRISTRIP; + } + + return state; +} + +bgfx::UniformType::Enum map_uniform_type(UniformType t) { + switch (t) { + case UniformType::Sampler: return bgfx::UniformType::Sampler; + case UniformType::Vec4: return bgfx::UniformType::Vec4; + case UniformType::Mat3: return bgfx::UniformType::Mat3; + case UniformType::Mat4: return bgfx::UniformType::Mat4; + } + return bgfx::UniformType::Vec4; +} + +bgfx::Attrib::Enum map_attrib(Attrib a) { + switch (a) { + case Attrib::Position: return bgfx::Attrib::Position; + case Attrib::Color0: return bgfx::Attrib::Color0; + case Attrib::TexCoord0: return bgfx::Attrib::TexCoord0; + case Attrib::Normal: return bgfx::Attrib::Normal; + case Attrib::Tangent: return bgfx::Attrib::Tangent; + } + + return bgfx::Attrib::Position; +} + +bgfx::AttribType::Enum map_attrib_type(AttribType t) { + switch (t) { + case AttribType::Float: return bgfx::AttribType::Float; + case AttribType::Uint8: return bgfx::AttribType::Uint8; + } + + return bgfx::AttribType::Float; +} + +void begin_frame() { + // Clean up GPU resources safely + process_deletions(); +} + +void end_frame() { + // Submit frame to GPU + bgfx::frame(); } +} // namespace draco::rendering::rhi diff --git a/engine/native/rendering/rhi/macros.h b/engine/native/rendering/rhi/macros.h index d055a9cb..1931a308 100644 --- a/engine/native/rendering/rhi/macros.h +++ b/engine/native/rendering/rhi/macros.h @@ -5,11 +5,11 @@ #include #ifndef DRACO_RHI_VALIDATION -#define DRACO_RHI_VALIDATION 1 + #define DRACO_RHI_VALIDATION 1 #endif #if DRACO_RHI_VALIDATION - #define RHI_ASSERT(cond, msg, ...) \ + #define RHI_ASSERT(cond, msg, ...) \ do { \ if (!(cond)) { \ std::println("[RHI ERROR] " msg, ##__VA_ARGS__); \ @@ -17,13 +17,13 @@ } \ } while(0) - #define RHI_WARN(cond, msg, ...) \ + #define RHI_WARN(cond, msg, ...) \ do { \ if (!(cond)) { \ std::println("[RHI WARNING] " msg, ##__VA_ARGS__); \ } \ } while(0) #else - #define RHI_ASSERT(cond, msg, ...) do { (void)(cond); } while(0) - #define RHI_WARN(cond, msg, ...) do { (void)(cond); } while(0) + #define RHI_ASSERT(cond, msg, ...) do { (void)(cond); } while(0) + #define RHI_WARN(cond, msg, ...) do { (void)(cond); } while(0) #endif diff --git a/engine/native/rendering/rhi/pipelines.cpp b/engine/native/rendering/rhi/pipelines.cpp index 15f70d32..211023e6 100644 --- a/engine/native/rendering/rhi/pipelines.cpp +++ b/engine/native/rendering/rhi/pipelines.cpp @@ -9,62 +9,56 @@ module rendering.rhi; import core.stdtypes; import core.math.constants; -namespace draco::rendering::rhi -{ - PipelineHandle create_pipeline(const PipelineDesc& desc) - { - RHI_ASSERT(desc.vs != InvalidShader, "Pipeline missing vertex shader"); - RHI_ASSERT(desc.fs != InvalidShader, "Pipeline missing fragment shader"); - - bgfx::ProgramHandle prog = bgfx::createProgram(resolve(desc.vs), resolve(desc.fs), true); - - u64 state = map_state(desc.state, desc.blend, desc.depth, desc.cull, desc.depth_write); - - return g_pipelines.create({ prog, state }); - } - - LayoutHandle create_vertex_layout(const VertexLayoutDesc& desc) - { - bgfx::VertexLayout layout; - layout.begin(); - - for (const auto& e : desc.elements) - { - layout.add(map_attrib(e.attrib), e.count, map_attrib_type(e.type), e.normalized); - } - - layout.end(); - - return g_layouts.create({ layout }); - } - - ShaderHandle create_shader(const void* data, u32 size) - { - RHI_ASSERT(data && size > 0, "Invalid shader data"); - - bgfx::ShaderHandle sh = bgfx::createShader(bgfx::copy(data, size)); - - return g_shaders.create(sh); - } - - bgfx::ShaderHandle resolve(ShaderHandle h) - { - auto* sh = g_shaders.get(h); - return sh ? *sh : bgfx::ShaderHandle{ bgfx::kInvalidHandle }; - } - - // For debugging/tooling - bgfx::ShaderHandle* get_shader_native(ShaderHandle h) - { - return get_checked(g_shaders, h, "Shader"); - } - - void destroy_shader(ShaderHandle h) - { - if (auto* sh = get_checked(g_shaders, h, "Shader")) - { - destroy_later(*sh); - g_shaders.destroy(h); - } - } +namespace draco::rendering::rhi { +PipelineHandle create_pipeline(PipelineDesc const &desc) { + RHI_ASSERT(desc.vs != InvalidShader, "Pipeline missing vertex shader"); + RHI_ASSERT(desc.fs != InvalidShader, "Pipeline missing fragment shader"); + + bgfx::ProgramHandle prog = + bgfx::createProgram(resolve(desc.vs), resolve(desc.fs), true); + + u64 state = map_state(desc.state, desc.blend, desc.depth, desc.cull, + desc.depth_write); + + return g_pipelines.create({prog, state}); +} + +LayoutHandle create_vertex_layout(VertexLayoutDesc const &desc) { + bgfx::VertexLayout layout; + layout.begin(); + + for (auto const &e : desc.elements) { + layout.add(map_attrib(e.attrib), e.count, map_attrib_type(e.type), + e.normalized); + } + + layout.end(); + + return g_layouts.create({layout}); +} + +ShaderHandle create_shader(void const *data, u32 size) { + RHI_ASSERT(data && size > 0, "Invalid shader data"); + + bgfx::ShaderHandle sh = bgfx::createShader(bgfx::copy(data, size)); + + return g_shaders.create(sh); +} + +bgfx::ShaderHandle resolve(ShaderHandle h) { + auto *sh = g_shaders.get(h); + return sh ? *sh : bgfx::ShaderHandle{bgfx::kInvalidHandle}; +} + +// For debugging/tooling +bgfx::ShaderHandle *get_shader_native(ShaderHandle h) { + return get_checked(g_shaders, h, "Shader"); +} + +void destroy_shader(ShaderHandle h) { + if (auto *sh = get_checked(g_shaders, h, "Shader")) { + destroy_later(*sh); + g_shaders.destroy(h); + } } +} // namespace draco::rendering::rhi diff --git a/engine/native/rendering/rhi/rhi.cppm b/engine/native/rendering/rhi/rhi.cppm index f750aaf2..3981e522 100644 --- a/engine/native/rendering/rhi/rhi.cppm +++ b/engine/native/rendering/rhi/rhi.cppm @@ -18,311 +18,314 @@ import core.memory; import platform; import rendering.rhi.vertex; -export namespace draco::rendering::rhi -{ - struct BufferTag {}; - struct PipelineTag {}; - struct ShaderTag {}; - struct UniformTag {}; - struct TextureTag {}; - struct FramebufferTag {}; - struct LayoutTag {}; - - using BufferHandle = core::memory::Handle; - using PipelineHandle = core::memory::Handle; - using ShaderHandle = core::memory::Handle; - using UniformHandle = core::memory::Handle; - using TextureHandle = core::memory::Handle; - using FramebufferHandle = core::memory::Handle; - using LayoutHandle = core::memory::Handle; - - using ViewID = u16; // bgfx native - using SamplerHandle = u64; // bgfx sampler flags - - inline constexpr BufferHandle InvalidBuffer{}; - inline constexpr PipelineHandle InvalidPipeline{}; - inline constexpr ShaderHandle InvalidShader{}; - inline constexpr UniformHandle InvalidUniform{}; - inline constexpr TextureHandle InvalidTexture{}; - inline constexpr FramebufferHandle InvalidFramebuffer{}; - inline constexpr LayoutHandle InvalidLayout{}; - inline constexpr SamplerHandle InvalidSampler = 0; - inline constexpr ViewID InvalidView = math::UINT16_MAX_VAL; - - enum class PipelineState : u64 { - Default = 0, - WriteRGB = 1ULL << 0, - WriteAlpha = 1ULL << 1, - MSAA = 1ULL << 2, - PrimitiveTriStrip = 1ULL << 3, - }; - - enum class ClearFlags : u32 { - Color = BGFX_CLEAR_COLOR, - Depth = BGFX_CLEAR_DEPTH, - Stencil = BGFX_CLEAR_STENCIL - }; - - struct ViewDesc { - FramebufferHandle fb = InvalidFramebuffer; - u16 x = 0, y = 0, w = 0, h = 0; - u32 clear_flags = 0; - u32 clear_color = 0; - }; - - enum class UniformType - { - Sampler, - Vec4, - Mat3, - Mat4, - }; - - struct UniformBind { - UniformHandle handle; - const void* data; - u16 num; - }; - - enum class TextureFormat { - RGBA8, - BGRA8, - D16, - D24, - D24S8, - D32 - }; - - enum class BlendMode { - None, - Alpha, - Additive, - Multiply - }; - - enum class DepthTest { - None, - Less, - Equal, - Always - }; - - enum class CullMode { - None, - CW, - CCW - }; - - struct Buffer - { - bgfx::VertexBufferHandle vbh = BGFX_INVALID_HANDLE; - bgfx::DynamicVertexBufferHandle dvbh = BGFX_INVALID_HANDLE; - bgfx::IndexBufferHandle ibh = BGFX_INVALID_HANDLE; - bgfx::DynamicIndexBufferHandle dibh; - bool is_dynamic = false; - bool is_index = false; - }; - - struct FramebufferResource { - bgfx::FrameBufferHandle fbh; - TextureHandle texture; - }; - - struct VertexLayoutResource - { - bgfx::VertexLayout layout; - }; - - struct ScissorRect { - u16 x, y, w, h; - bool enabled = true; - }; - - struct DeletionReq { - u64 frame; - std::function cleanup; - }; - - struct PipelineDesc - { - ShaderHandle vs; - ShaderHandle fs; - PipelineState state = PipelineState::Default; - - BlendMode blend = BlendMode::None; - DepthTest depth = DepthTest::Less; - CullMode cull = CullMode::CCW; - - bool depth_write = true; - }; - - struct RenderPacket - { - u64 sort_key = 0; - - BufferHandle vertex_buffer = InvalidBuffer; - BufferHandle index_buffer = InvalidBuffer; - PipelineHandle pipeline = InvalidPipeline; - - u32 vertex_count = math::UINT32_MAX_VAL; - u32 index_count = math::UINT32_MAX_VAL; - - UniformHandle sampler_uniform = InvalidUniform; - SamplerHandle sampler_flags = InvalidSampler; - TextureHandle texture_handle = InvalidTexture; - - f32 color[4] = {1,1,1,1}; - - std::vector uniforms; - u8 texture_unit = 0; - - f32 model[16] = { - 1,0,0,0, - 0,1,0,0, - 0,0,1,0, - 0,0,0,1 - }; - - u32 draw_tags = 0; - }; - - struct Pipeline { - bgfx::ProgramHandle program; - u64 state; - }; - - bool init(void* display_type, void* window_handle, draco::platform::NativeWindowType window_type, u16 width, u16 height); - void resize(u16 width, u16 height); - void shutdown(); - - PipelineHandle create_pipeline(const PipelineDesc&); +export namespace draco::rendering::rhi { +struct BufferTag { }; +struct PipelineTag { }; +struct ShaderTag { }; +struct UniformTag { }; +struct TextureTag { }; +struct FramebufferTag { }; +struct LayoutTag { }; + +using BufferHandle = core::memory::Handle; +using PipelineHandle = core::memory::Handle; +using ShaderHandle = core::memory::Handle; +using UniformHandle = core::memory::Handle; +using TextureHandle = core::memory::Handle; +using FramebufferHandle = core::memory::Handle; +using LayoutHandle = core::memory::Handle; + +using ViewID = u16; // bgfx native +using SamplerHandle = u64; // bgfx sampler flags + +inline constexpr BufferHandle InvalidBuffer{}; +inline constexpr PipelineHandle InvalidPipeline{}; +inline constexpr ShaderHandle InvalidShader{}; +inline constexpr UniformHandle InvalidUniform{}; +inline constexpr TextureHandle InvalidTexture{}; +inline constexpr FramebufferHandle InvalidFramebuffer{}; +inline constexpr LayoutHandle InvalidLayout{}; +inline constexpr SamplerHandle InvalidSampler = 0; +inline constexpr ViewID InvalidView = math::UINT16_MAX_VAL; + +enum class PipelineState : u64 { + Default = 0, + WriteRGB = 1ULL << 0, + WriteAlpha = 1ULL << 1, + MSAA = 1ULL << 2, + PrimitiveTriStrip = 1ULL << 3, +}; + +enum class ClearFlags : u32 { + Color = BGFX_CLEAR_COLOR, + Depth = BGFX_CLEAR_DEPTH, + Stencil = BGFX_CLEAR_STENCIL +}; + +struct ViewDesc { + FramebufferHandle fb = InvalidFramebuffer; + u16 x = 0, y = 0, w = 0, h = 0; + u32 clear_flags = 0; + u32 clear_color = 0; +}; + +enum class UniformType { + Sampler, + Vec4, + Mat3, + Mat4, +}; + +struct UniformBind { + UniformHandle handle; + void const *data; + u16 num; +}; + +enum class TextureFormat { + RGBA8, + BGRA8, + D16, + D24, + D24S8, + D32 +}; + +enum class BlendMode { + None, + Alpha, + Additive, + Multiply +}; + +enum class DepthTest { + None, + Less, + Equal, + Always +}; + +enum class CullMode { + None, + CW, + CCW +}; + +struct Buffer { + bgfx::VertexBufferHandle vbh = BGFX_INVALID_HANDLE; + bgfx::DynamicVertexBufferHandle dvbh = BGFX_INVALID_HANDLE; + bgfx::IndexBufferHandle ibh = BGFX_INVALID_HANDLE; + bgfx::DynamicIndexBufferHandle dibh; + bool is_dynamic = false; + bool is_index = false; +}; + +struct FramebufferResource { + bgfx::FrameBufferHandle fbh; + TextureHandle texture; +}; + +struct VertexLayoutResource { + bgfx::VertexLayout layout; +}; + +struct ScissorRect { + u16 x, y, w, h; + bool enabled = true; +}; + +struct DeletionReq { + u64 frame; + std::function cleanup; +}; + +struct PipelineDesc { + ShaderHandle vs; + ShaderHandle fs; + PipelineState state = PipelineState::Default; + + BlendMode blend = BlendMode::None; + DepthTest depth = DepthTest::Less; + CullMode cull = CullMode::CCW; + + bool depth_write = true; +}; + +struct RenderPacket { + u64 sort_key = 0; + + BufferHandle vertex_buffer = InvalidBuffer; + BufferHandle index_buffer = InvalidBuffer; + PipelineHandle pipeline = InvalidPipeline; + + u32 vertex_count = math::UINT32_MAX_VAL; + u32 index_count = math::UINT32_MAX_VAL; + + UniformHandle sampler_uniform = InvalidUniform; + SamplerHandle sampler_flags = InvalidSampler; + TextureHandle texture_handle = InvalidTexture; + + f32 color[4] = {1, 1, 1, 1}; + + std::vector uniforms; + u8 texture_unit = 0; + + f32 model[16] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}; + + u32 draw_tags = 0; +}; + +struct Pipeline { + bgfx::ProgramHandle program; + u64 state; +}; + +bool init(void *display_type, + void *window_handle, + draco::platform::NativeWindowType window_type, + u16 width, + u16 height); +void resize(u16 width, u16 height); +void shutdown(); + +PipelineHandle create_pipeline(PipelineDesc const &); + +BufferHandle +create_vertex_buffer(void const *data, u32 size, LayoutHandle layout_h); +BufferHandle create_index_buffer(void const *data, u32 size); +void destroy_buffer(BufferHandle handle); + +UniformHandle create_uniform(char const *name, UniformType type, u16 num = 1); +void destroy_uniform(UniformHandle handle); +void set_uniform(UniformHandle handle, void const *value, u16 num = 1); + +TextureHandle +create_texture(void const *data, u32 width, u32 height, u32 flags = 0); +void destroy_texture(TextureHandle handle); + +FramebufferHandle +create_framebuffer(u32 width, u32 height, TextureFormat format); +void destroy_framebuffer(FramebufferHandle handle); +TextureHandle get_framebuffer_texture(FramebufferHandle handle); + +BufferHandle create_dynamic_vertex_buffer(u32 size, LayoutHandle layout); +void update_dynamic_vertex_buffer(BufferHandle handle, + u32 start_vertex, + void const *data, + u32 size); - BufferHandle create_vertex_buffer(const void* data, u32 size, LayoutHandle layout_h); - BufferHandle create_index_buffer(const void* data, u32 size); - void destroy_buffer(BufferHandle handle); +BufferHandle create_dynamic_index_buffer(u32 size, + u16 flags = BGFX_BUFFER_NONE); +void update_dynamic_index_buffer(BufferHandle handle, + u32 start_index, + void const *data, + u32 size); - UniformHandle create_uniform(const char* name, UniformType type, u16 num = 1); - void destroy_uniform(UniformHandle handle); - void set_uniform(UniformHandle handle, const void* value, u16 num = 1); +LayoutHandle create_vertex_layout(VertexLayoutDesc const &desc); - TextureHandle create_texture(const void* data, u32 width, u32 height, u32 flags = 0); - void destroy_texture(TextureHandle handle); +SamplerHandle create_sampler(bool linear, bool clamp); - FramebufferHandle create_framebuffer(u32 width, u32 height, TextureFormat format); - void destroy_framebuffer(FramebufferHandle handle); - TextureHandle get_framebuffer_texture(FramebufferHandle handle); +// Expects bgfx compiled shader binary (shaderc output) +ShaderHandle create_shader(void const *data, u32 size); +bgfx::ShaderHandle resolve(ShaderHandle h); +// For debugging/tooling +bgfx::ShaderHandle *get_shader_native(ShaderHandle h); +void destroy_shader(ShaderHandle h); - BufferHandle create_dynamic_vertex_buffer(u32 size, LayoutHandle layout); - void update_dynamic_vertex_buffer(BufferHandle handle, u32 start_vertex, const void* data, u32 size); +void perspective(f32 *out, f32 fov, f32 aspect, f32 nearp, f32 farp); +void look_at(f32 *out, f32 const *eye, f32 const *at, f32 const *up); - BufferHandle create_dynamic_index_buffer(u32 size, u16 flags = BGFX_BUFFER_NONE); - void update_dynamic_index_buffer(BufferHandle handle, u32 start_index, const void* data, u32 size); +// Note: Internal use only, use apply_view() instead +void set_view_rect(ViewID view, u16 x, u16 y, u16 w, u16 h); +void set_view_framebuffer(ViewID view, FramebufferHandle handle); - LayoutHandle create_vertex_layout(const VertexLayoutDesc& desc); +void set_view_projection(ViewID view, f32 const *view_mtx, f32 const *proj_mtx); +void set_scissor(ScissorRect const &r); +void set_stencil(u32 f_stencil, u32 b_stencil); - SamplerHandle create_sampler(bool linear, bool clamp); +void apply_view(ViewID view, ViewDesc const &desc); - // Expects bgfx compiled shader binary (shaderc output) - ShaderHandle create_shader(const void* data, u32 size); - bgfx::ShaderHandle resolve(ShaderHandle h); - // For debugging/tooling - bgfx::ShaderHandle* get_shader_native(ShaderHandle h); - void destroy_shader(ShaderHandle h); +void identity_matrix(f32 *_mtx); - void perspective(f32* out, f32 fov, f32 aspect, f32 nearp, f32 farp); - void look_at(f32* out, const f32* eye, const f32* at, const f32* up); +u64 +map_state(PipelineState s, BlendMode, DepthTest, CullMode, bool depth_write); +bgfx::UniformType::Enum map_uniform_type(UniformType t); +bgfx::Attrib::Enum map_attrib(Attrib a); +bgfx::AttribType::Enum map_attrib_type(AttribType t); - // Note: Internal use only, use apply_view() instead - void set_view_rect(ViewID view, u16 x, u16 y, u16 w, u16 h); - void set_view_framebuffer(ViewID view, FramebufferHandle handle); +void submit(RenderPacket const &, ViewID); - void set_view_projection(ViewID view, const f32* view_mtx, const f32* proj_mtx); - void set_scissor(const ScissorRect& r); - void set_stencil(u32 f_stencil, u32 b_stencil); +void begin_frame(); +void end_frame(); - void apply_view(ViewID view, const ViewDesc& desc); +template +void destroy_resource(T handle); - void identity_matrix(f32* _mtx); +void process_deletions(); - u64 map_state(PipelineState s, BlendMode, DepthTest, CullMode, bool depth_write); - bgfx::UniformType::Enum map_uniform_type(UniformType t); - bgfx::Attrib::Enum map_attrib(Attrib a); - bgfx::AttribType::Enum map_attrib_type(AttribType t); - - void submit(const RenderPacket&, ViewID); - - void begin_frame(); - void end_frame(); - - template - void destroy_resource(T handle); - - void process_deletions(); - - inline u64 make_sort_key(u8 layer, u8 pass, u16 pipeline, u16 texture, u16 depth = 0) - { - return (u64(layer) << 56) | (u64(pass) << 48) | (u64(pipeline) << 32) | (u64(texture) << 16) | u64(depth); - } +inline u64 +make_sort_key(u8 layer, u8 pass, u16 pipeline, u16 texture, u16 depth = 0) { + return (u64(layer) << 56) | (u64(pass) << 48) | (u64(pipeline) << 32) | + (u64(texture) << 16) | u64(depth); +} - constexpr PipelineState operator|(PipelineState a, PipelineState b) { - return static_cast(static_cast(a) | static_cast(b)); - } +constexpr PipelineState operator|(PipelineState a, PipelineState b) { + return static_cast(static_cast(a) | + static_cast(b)); +} - constexpr PipelineState operator&(PipelineState a, PipelineState b) { - return static_cast(static_cast(a) & static_cast(b)); - } +constexpr PipelineState operator&(PipelineState a, PipelineState b) { + return static_cast(static_cast(a) & + static_cast(b)); } +} // namespace draco::rendering::rhi // These are the things that we don't export but are visible to all implementation files -namespace draco::rendering::rhi -{ - using namespace draco::core::memory; - - extern HandleRegistry g_buffers; - extern HandleRegistry g_pipelines; - extern HandleRegistry g_uniforms; - extern HandleRegistry g_textures; - extern HandleRegistry g_framebuffers; - extern HandleRegistry g_shaders; - extern HandleRegistry g_layouts; - - // Deferred destruction queue (GPU-safe deletion) - extern std::vector g_deletion_queue; - - extern u16 g_width; - extern u16 g_height; - - // Ensures a handle is valid before use - // TODO: Replace with something better - template - auto* get_checked(Registry& reg, HandleT h, const char* name) - { - if (!reg.valid(h)) - { - RHI_WARN(false, "{} handle invalid or stale!", name); - return (decltype(reg.get(h)))nullptr; - } - - return reg.get(h); - } +namespace draco::rendering::rhi { +using namespace draco::core::memory; + +extern HandleRegistry g_buffers; +extern HandleRegistry g_pipelines; +extern HandleRegistry g_uniforms; +extern HandleRegistry g_textures; +extern HandleRegistry g_framebuffers; +extern HandleRegistry g_shaders; +extern HandleRegistry g_layouts; + +// Deferred destruction queue (GPU-safe deletion) +extern std::vector g_deletion_queue; + +extern u16 g_width; +extern u16 g_height; + +// Ensures a handle is valid before use +// TODO: Replace with something better +template +auto *get_checked(Registry ®, HandleT h, char const *name) { + if (!reg.valid(h)) { + RHI_WARN(false, "{} handle invalid or stale!", name); + return (decltype(reg.get(h)))nullptr; + } + + return reg.get(h); } +} // namespace draco::rendering::rhi // Re-export the namespace since the things it uses aren't declared before // This is a bit hacky but it works // The problem is that if we move the unexported namespace above the other exported namespace, stuff isn't declared yet & it gives errors // The same goes for the exported namespace // In a nutshell, they both rely on each other which is why we use this hack (AR-DEV-1) -export namespace draco::rendering::rhi -{ - void queue_destruction(std::function cb); - - // Explicit overloads for each bgfx resource type - void destroy_later(bgfx::ShaderHandle handle); - void destroy_later(bgfx::UniformHandle handle); - void destroy_later(bgfx::VertexBufferHandle handle); - void destroy_later(bgfx::IndexBufferHandle handle); - void destroy_later(bgfx::DynamicVertexBufferHandle handle); - void destroy_later(bgfx::DynamicIndexBufferHandle handle); - void destroy_later(bgfx::TextureHandle handle); - void destroy_later(bgfx::FrameBufferHandle handle); -} +export namespace draco::rendering::rhi { +void queue_destruction(std::function cb); + +// Explicit overloads for each bgfx resource type +void destroy_later(bgfx::ShaderHandle handle); +void destroy_later(bgfx::UniformHandle handle); +void destroy_later(bgfx::VertexBufferHandle handle); +void destroy_later(bgfx::IndexBufferHandle handle); +void destroy_later(bgfx::DynamicVertexBufferHandle handle); +void destroy_later(bgfx::DynamicIndexBufferHandle handle); +void destroy_later(bgfx::TextureHandle handle); +void destroy_later(bgfx::FrameBufferHandle handle); +} // namespace draco::rendering::rhi diff --git a/engine/native/rendering/rhi/texture.cpp b/engine/native/rendering/rhi/texture.cpp index 346eb6c3..dca77a5c 100644 --- a/engine/native/rendering/rhi/texture.cpp +++ b/engine/native/rendering/rhi/texture.cpp @@ -8,119 +8,104 @@ module rendering.rhi; import core.stdtypes; import core.math.constants; -namespace draco::rendering::rhi -{ - UniformHandle create_uniform(const char* name, UniformType type, u16 num) - { - RHI_ASSERT(name != nullptr, "Uniform name is null"); - - auto u = bgfx::createUniform(name, map_uniform_type(type), num); - return g_uniforms.create(u); - } - - void set_uniform(UniformHandle h, const void* data, u16 num) - { - auto* u = get_checked(g_uniforms, h, "Uniform"); - if (!u) return; - - RHI_ASSERT(data != nullptr, "Uniform data is null"); - - bgfx::setUniform(*u, data, num); - } - - void destroy_uniform(UniformHandle h) - { - auto* u = get_checked(g_uniforms, h, "Uniform"); - if (!u) return; - - destroy_later(*u); - g_uniforms.destroy(h); - } - - TextureHandle create_texture(const void* data, u32 w, u32 h, u32 flags) - { - RHI_ASSERT(data != nullptr, "Texture data is null"); - RHI_ASSERT(w > 0 && h > 0, "Invalid texture dimensions"); - - auto tex = bgfx::createTexture2D( - w, h, false, 1, - bgfx::TextureFormat::RGBA8, - flags == 0 ? (BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP) : flags, - bgfx::copy(data, w * h * 4) - ); - - return g_textures.create(tex); - } - - void destroy_texture(TextureHandle h) - { - auto* tex = get_checked(g_textures, h, "Texture"); - if (!tex) return; - - destroy_later(*tex); - g_textures.destroy(h); - } - - FramebufferHandle create_framebuffer(u32 width, u32 height, TextureFormat format) - { - // We set render target flags so it can be attached to a framebuffer object - u64 flags = BGFX_TEXTURE_RT | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP; - - bgfx::TextureFormat::Enum bgfx_format = bgfx::TextureFormat::RGBA8; - - bgfx::TextureHandle th = bgfx::createTexture2D(static_cast(width), static_cast(height), false, 1, bgfx_format, flags); - - RHI_ASSERT(bgfx::isValid(th), "Failed to allocate backing texture for Framebuffer"); - - TextureHandle color_tex_h = g_textures.create(th); - - bgfx::FrameBufferHandle fbh = bgfx::createFrameBuffer(1, &th, false); - - if (!bgfx::isValid(fbh)) - { - RHI_WARN(false, "Failed to construct native bgfx Framebuffer target!"); - // Roll back the allocated texture if the framebuffer generation bricks - destroy_texture(color_tex_h); - return InvalidFramebuffer; - } - - FramebufferResource res{}; - res.fbh = fbh; - res.texture = color_tex_h; - - return g_framebuffers.create(res); - } - - void destroy_framebuffer(FramebufferHandle handle) - { - if (auto* fb = get_checked(g_framebuffers, handle, "Framebuffer")) - { - // Safely queue the native hardware framebuffer destruction 2 frames out - destroy_later(fb->fbh); - - // Clean up the associated internal texture resource using existing pipelines - if (fb->texture != InvalidTexture) - { - if (auto* th = g_textures.get(fb->texture)) - { - destroy_later(*th); - } - g_textures.destroy(fb->texture); - } - - // Evict our registry tracking slot - g_framebuffers.destroy(handle); - } - } - - TextureHandle get_framebuffer_texture(FramebufferHandle handle) - { - auto* fb = get_checked(g_framebuffers, handle, "Framebuffer"); - if (!fb) - { - return InvalidTexture; - } - - return fb->texture; - } +namespace draco::rendering::rhi { +UniformHandle create_uniform(char const *name, UniformType type, u16 num) { + RHI_ASSERT(name != nullptr, "Uniform name is null"); + + auto u = bgfx::createUniform(name, map_uniform_type(type), num); + return g_uniforms.create(u); +} + +void set_uniform(UniformHandle h, void const *data, u16 num) { + auto *u = get_checked(g_uniforms, h, "Uniform"); + if (!u) { return; } + + RHI_ASSERT(data != nullptr, "Uniform data is null"); + + bgfx::setUniform(*u, data, num); +} + +void destroy_uniform(UniformHandle h) { + auto *u = get_checked(g_uniforms, h, "Uniform"); + if (!u) { return; } + + destroy_later(*u); + g_uniforms.destroy(h); +} + +TextureHandle create_texture(void const *data, u32 w, u32 h, u32 flags) { + RHI_ASSERT(data != nullptr, "Texture data is null"); + RHI_ASSERT(w > 0 && h > 0, "Invalid texture dimensions"); + + auto tex = bgfx::createTexture2D( + w, h, false, 1, bgfx::TextureFormat::RGBA8, + flags == 0 ? (BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP) : flags, + bgfx::copy(data, w * h * 4) + ); + + return g_textures.create(tex); +} + +void destroy_texture(TextureHandle h) { + auto *tex = get_checked(g_textures, h, "Texture"); + if (!tex) { return; } + + destroy_later(*tex); + g_textures.destroy(h); +} + +FramebufferHandle +create_framebuffer(u32 width, u32 height, TextureFormat format) { + // We set render target flags so it can be attached to a framebuffer object + u64 flags = BGFX_TEXTURE_RT | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP; + + bgfx::TextureFormat::Enum bgfx_format = bgfx::TextureFormat::RGBA8; + + bgfx::TextureHandle th = + bgfx::createTexture2D(static_cast(width), static_cast(height), + false, 1, bgfx_format, flags); + + RHI_ASSERT(bgfx::isValid(th), + "Failed to allocate backing texture for Framebuffer"); + + TextureHandle color_tex_h = g_textures.create(th); + + bgfx::FrameBufferHandle fbh = bgfx::createFrameBuffer(1, &th, false); + + if (!bgfx::isValid(fbh)) { + RHI_WARN(false, "Failed to construct native bgfx Framebuffer target!"); + // Roll back the allocated texture if the framebuffer generation bricks + destroy_texture(color_tex_h); + return InvalidFramebuffer; + } + + FramebufferResource res{}; + res.fbh = fbh; + res.texture = color_tex_h; + + return g_framebuffers.create(res); +} + +void destroy_framebuffer(FramebufferHandle handle) { + if (auto *fb = get_checked(g_framebuffers, handle, "Framebuffer")) { + // Safely queue the native hardware framebuffer destruction 2 frames out + destroy_later(fb->fbh); + + // Clean up the associated internal texture resource using existing pipelines + if (fb->texture != InvalidTexture) { + if (auto *th = g_textures.get(fb->texture)) { destroy_later(*th); } + g_textures.destroy(fb->texture); + } + + // Evict our registry tracking slot + g_framebuffers.destroy(handle); + } +} + +TextureHandle get_framebuffer_texture(FramebufferHandle handle) { + auto *fb = get_checked(g_framebuffers, handle, "Framebuffer"); + if (!fb) { return InvalidTexture; } + + return fb->texture; } +} // namespace draco::rendering::rhi diff --git a/engine/native/rendering/rhi/uniform_registry.cppm b/engine/native/rendering/rhi/uniform_registry.cppm index bdc9afdf..6d0ef693 100644 --- a/engine/native/rendering/rhi/uniform_registry.cppm +++ b/engine/native/rendering/rhi/uniform_registry.cppm @@ -9,40 +9,34 @@ export module rendering.rhi.uniform_registry; import rendering.rhi; -export namespace draco::rendering::rhi -{ - inline std::unordered_map g_uniform_map; - - inline uint32_t hash_uniform(const std::string& name) - { - return static_cast(std::hash{}(name)); - } - - inline void register_uniform(uint32_t hash, UniformHandle h) - { - g_uniform_map[hash] = h; - } - - inline void unregister_uniform(uint32_t hash, UniformHandle h) - { - auto it = g_uniform_map.find(hash); - - if (it != g_uniform_map.end() && it->second == h) - g_uniform_map.erase(it); - } - - inline void clear_uniform_registry() - { - g_uniform_map.clear(); - } - - inline UniformHandle get_uniform(uint32_t hash) - { - auto it = g_uniform_map.find(hash); - - if (it == g_uniform_map.end()) - return InvalidUniform; - - return it->second; - } +export namespace draco::rendering::rhi { +inline std::unordered_map g_uniform_map; + +inline uint32_t hash_uniform(std::string const &name) { + return static_cast(std::hash{}(name)); +} + +inline void register_uniform(uint32_t hash, UniformHandle h) { + g_uniform_map[hash] = h; +} + +inline void unregister_uniform(uint32_t hash, UniformHandle h) { + auto it = g_uniform_map.find(hash); + + if (it != g_uniform_map.end() && it->second == h) { + g_uniform_map.erase(it); + } +} + +inline void clear_uniform_registry() { + g_uniform_map.clear(); +} + +inline UniformHandle get_uniform(uint32_t hash) { + auto it = g_uniform_map.find(hash); + + if (it == g_uniform_map.end()) { return InvalidUniform; } + + return it->second; } +} // namespace draco::rendering::rhi diff --git a/engine/native/rendering/rhi/vertex.cppm b/engine/native/rendering/rhi/vertex.cppm index ece03c42..96a09b98 100644 --- a/engine/native/rendering/rhi/vertex.cppm +++ b/engine/native/rendering/rhi/vertex.cppm @@ -6,48 +6,46 @@ module; export module rendering.rhi.vertex; export namespace draco::rendering::rhi { - enum class Attrib { - Position, - Color0, - TexCoord0, - Normal, - Tangent - }; - - enum class AttribType { - Float, - Uint8 - }; - - struct VertexElement { - Attrib attrib; - uint16_t count; - AttribType type; - bool normalized = false; - }; - - struct VertexLayoutDesc { - std::vector elements; - }; - - #pragma pack(push, 1) - struct TexturedVertex { - float x, y, z; - float u, v; - uint32_t color; - }; - #pragma pack(pop) - - static_assert(sizeof(TexturedVertex) == 24); - - // Helper to get the standard layout for the current vertex struct - inline VertexLayoutDesc get_textured_vertex_layout() { - return { - .elements = { - { Attrib::Position, 3, AttribType::Float }, - { Attrib::TexCoord0, 2, AttribType::Float }, - { Attrib::Color0, 4, AttribType::Uint8, true } - } - }; - } +enum class Attrib { + Position, + Color0, + TexCoord0, + Normal, + Tangent +}; + +enum class AttribType { + Float, + Uint8 +}; + +struct VertexElement { + Attrib attrib; + uint16_t count; + AttribType type; + bool normalized = false; +}; + +struct VertexLayoutDesc { + std::vector elements; +}; + +#pragma pack(push, 1) +struct TexturedVertex { + float x, y, z; + float u, v; + uint32_t color; +}; +#pragma pack(pop) + +static_assert(sizeof(TexturedVertex) == 24); + +// Helper to get the standard layout for the current vertex struct +inline VertexLayoutDesc get_textured_vertex_layout() { + return { + .elements = {{Attrib::Position, 3, AttribType::Float}, + {Attrib::TexCoord0, 2, AttribType::Float}, + {Attrib::Color0, 4, AttribType::Uint8, true}} + }; } +} // namespace draco::rendering::rhi diff --git a/engine/native/scene/camera/camera_controller.cpp b/engine/native/scene/camera/camera_controller.cpp index f9a1d224..c0e9b201 100644 --- a/engine/native/scene/camera/camera_controller.cpp +++ b/engine/native/scene/camera/camera_controller.cpp @@ -7,94 +7,74 @@ module scene.camera.controller; import input; -namespace draco::scene -{ - void CameraController::init(f32 x, f32 y, f32 z) - { - m_x = x; - m_y = y; - m_z = z; - - m_yaw = 0.0f; - m_pitch = 0.0f; - - m_speed = 5.0f; // units per second - m_sensitivity = 0.002f; // mouse sensitivity - } - - void CameraController::update(f32 dt) - { - m_yaw += draco::input::get_mouse_dx() * m_sensitivity; - m_pitch -= draco::input::get_mouse_dy() * m_sensitivity; // Temp fix to flip mouse input - - // Clamp pitch - if (m_pitch > 1.5f) m_pitch = 1.5f; - if (m_pitch < -1.5f) m_pitch = -1.5f; - - bx::Vec3 forward = { - cosf(m_pitch) * sinf(m_yaw), - sinf(m_pitch), - cosf(m_pitch) * cosf(m_yaw) - }; - - bx::Vec3 right = { - sinf(m_yaw - bx::kPiHalf), - 0.0f, - cosf(m_yaw - bx::kPiHalf) - }; - - f32 velocity = m_speed * dt; - - if (draco::input::is_down(draco::input::Key::W)) - { - m_x += forward.x * velocity; - m_y += forward.y * velocity; - m_z += forward.z * velocity; - } - - if (draco::input::is_down(draco::input::Key::S)) - { - m_x -= forward.x * velocity; - m_y -= forward.y * velocity; - m_z -= forward.z * velocity; - } - - if (draco::input::is_down(draco::input::Key::A)) - { - m_x += right.x * velocity; - m_z += right.z * velocity; - } - - if (draco::input::is_down(draco::input::Key::D)) - { - m_x -= right.x * velocity; - m_z -= right.z * velocity; - } - } - - draco::rendering::renderer::Camera CameraController::get_camera() const - { - bx::Vec3 forward = { - cosf(m_pitch) * sinf(m_yaw), - sinf(m_pitch), - cosf(m_pitch) * cosf(m_yaw) - }; - - draco::rendering::renderer::Camera cam{}; - - cam.position = { m_x, m_y, m_z }; - cam.target = { - m_x + forward.x, - m_y + forward.y, - m_z + forward.z - }; - - cam.up = { 0.0f, 1.0f, 0.0f }; - - cam.fov = 60.0f; - cam.near_plane = 0.1f; - cam.far_plane = 100.0f; - - return cam; - } +namespace draco::scene { +void CameraController::init(f32 x, f32 y, f32 z) { + m_x = x; + m_y = y; + m_z = z; + + m_yaw = 0.0F; + m_pitch = 0.0F; + + m_speed = 5.0F; // units per second + m_sensitivity = 0.002F; // mouse sensitivity +} + +void CameraController::update(f32 dt) { + m_yaw += draco::input::get_mouse_dx() * m_sensitivity; + m_pitch -= draco::input::get_mouse_dy() * + m_sensitivity; // Temp fix to flip mouse input + + // Clamp pitch + if (m_pitch > 1.5F) { m_pitch = 1.5F; } + if (m_pitch < -1.5F) { m_pitch = -1.5F; } + + bx::Vec3 forward = {cosf(m_pitch) * sinf(m_yaw), sinf(m_pitch), + cosf(m_pitch) * cosf(m_yaw)}; + + bx::Vec3 right = {sinf(m_yaw - bx::kPiHalf), 0.0F, + cosf(m_yaw - bx::kPiHalf)}; + + f32 velocity = m_speed * dt; + + if (draco::input::is_down(draco::input::Key::W)) { + m_x += forward.x * velocity; + m_y += forward.y * velocity; + m_z += forward.z * velocity; + } + + if (draco::input::is_down(draco::input::Key::S)) { + m_x -= forward.x * velocity; + m_y -= forward.y * velocity; + m_z -= forward.z * velocity; + } + + if (draco::input::is_down(draco::input::Key::A)) { + m_x += right.x * velocity; + m_z += right.z * velocity; + } + + if (draco::input::is_down(draco::input::Key::D)) { + m_x -= right.x * velocity; + m_z -= right.z * velocity; + } +} + +draco::rendering::renderer::Camera CameraController::get_camera() const { + bx::Vec3 forward = {cosf(m_pitch) * sinf(m_yaw), sinf(m_pitch), + cosf(m_pitch) * cosf(m_yaw)}; + + draco::rendering::renderer::Camera cam{}; + + cam.position = {m_x, m_y, m_z}; + cam.target = {m_x + forward.x, m_y + forward.y, m_z + forward.z}; + + cam.up = {0.0F, 1.0F, 0.0F}; + + cam.fov = 60.0F; + cam.near_plane = 0.1F; + cam.far_plane = 100.0F; + + return cam; } +} // namespace draco::scene diff --git a/engine/native/scene/camera/camera_controller.cppm b/engine/native/scene/camera/camera_controller.cppm index 7dd607a9..930b64c3 100644 --- a/engine/native/scene/camera/camera_controller.cppm +++ b/engine/native/scene/camera/camera_controller.cppm @@ -3,22 +3,20 @@ export module scene.camera.controller; import core.stdtypes; import rendering; -export namespace draco::scene -{ - struct CameraController - { - void init(f32 x = 0.0f, f32 y = 0.0f, f32 z = -2.0f); +export namespace draco::scene { +struct CameraController { + void init(f32 x = 0.0F, f32 y = 0.0F, f32 z = -2.0F); - void update(f32 dt); + void update(f32 dt); - draco::rendering::renderer::Camera get_camera() const; + draco::rendering::renderer::Camera get_camera() const; - private: - // Init with default values - f32 m_x = 0.0f, m_y = 0.0f, m_z = 0.0f; - f32 m_yaw = 0.0f; - f32 m_pitch = 0.0f; - f32 m_speed = 5.0f; - f32 m_sensitivity = 0.1f; - }; -} \ No newline at end of file + private: + // Init with default values + f32 m_x = 0.0F, m_y = 0.0F, m_z = 0.0F; + f32 m_yaw = 0.0F; + f32 m_pitch = 0.0F; + f32 m_speed = 5.0F; + f32 m_sensitivity = 0.1F; +}; +} // namespace draco::scene diff --git a/engine/native/scene/renderable/renderable.cppm b/engine/native/scene/renderable/renderable.cppm index 6d034a43..1aa0f4d8 100644 --- a/engine/native/scene/renderable/renderable.cppm +++ b/engine/native/scene/renderable/renderable.cppm @@ -4,14 +4,12 @@ import rendering.mesh; import rendering.material; import core.math.transform; -export namespace draco::scene::renderable -{ - struct Renderable - { - draco::rendering::mesh::MeshHandle mesh; +export namespace draco::scene::renderable { +struct Renderable { + draco::rendering::mesh::MeshHandle mesh; - draco::math::Transform transform; + draco::math::Transform transform; - draco::rendering::material::Material material; - }; -} \ No newline at end of file + draco::rendering::material::Material material; +}; +} // namespace draco::scene::renderable diff --git a/engine/native/scene/scene.cppm b/engine/native/scene/scene.cppm index 00768e1f..5b6e4405 100644 --- a/engine/native/scene/scene.cppm +++ b/engine/native/scene/scene.cppm @@ -8,10 +8,8 @@ export import scene.renderable; export import scene.transform_component; export import scene.camera.controller; -export namespace draco::scene -{ - struct Scene - { - std::vector renderables; - }; -} +export namespace draco::scene { +struct Scene { + std::vector renderables; +}; +} // namespace draco::scene diff --git a/engine/native/scene/transform_component/transform_component.cpp b/engine/native/scene/transform_component/transform_component.cpp index 3573a1c2..c2a30a8f 100644 --- a/engine/native/scene/transform_component/transform_component.cpp +++ b/engine/native/scene/transform_component/transform_component.cpp @@ -1,9 +1,7 @@ module scene.transform_component; -namespace draco::scene -{ - void mark_dirty(TransformComponent& t) - { - t.dirty = true; - } +namespace draco::scene { +void mark_dirty(TransformComponent &t) { + t.dirty = true; } +} // namespace draco::scene diff --git a/engine/native/scene/transform_component/transform_component.cppm b/engine/native/scene/transform_component/transform_component.cppm index 98ff5b93..91f4f73a 100644 --- a/engine/native/scene/transform_component/transform_component.cppm +++ b/engine/native/scene/transform_component/transform_component.cppm @@ -2,15 +2,13 @@ export module scene.transform_component; import core.math.transform; -export namespace draco::scene -{ - struct TransformComponent - { - math::Transform local; - math::Transform world; +export namespace draco::scene { +struct TransformComponent { + math::Transform local; + math::Transform world; - bool dirty = true; - }; + bool dirty = true; +}; - void mark_dirty(TransformComponent& t); -} +void mark_dirty(TransformComponent &t); +} // namespace draco::scene