@@ -7,11 +7,11 @@
#include <limits>
#include <memory>
#include <string_view>
#include <type_traits>
#include <vector>

#include "Common/Assert.h"
#include "Common/CommonTypes.h"
#include "Common/Concepts.h"

namespace Common::SHA1
{
@@ -32,10 +32,9 @@ std::unique_ptr<Context> CreateContext();

Digest CalculateDigest(const u8* msg, size_t len);

template <typename T>
template <TriviallyCopyable T>
inline Digest CalculateDigest(const std::vector<T>& msg)
{
static_assert(std::is_trivially_copyable_v<T>);
ASSERT(std::numeric_limits<size_t>::max() / sizeof(T) >= msg.size());
return CalculateDigest(reinterpret_cast<const u8*>(msg.data()), sizeof(T) * msg.size());
}
@@ -45,10 +44,9 @@ inline Digest CalculateDigest(const std::string_view& msg)
return CalculateDigest(reinterpret_cast<const u8*>(msg.data()), msg.size());
}

template <typename T, size_t Size>
template <TriviallyCopyable T, size_t Size>
inline Digest CalculateDigest(const std::array<T, Size>& msg)
{
static_assert(std::is_trivially_copyable_v<T>);
return CalculateDigest(reinterpret_cast<const u8*>(msg.data()), sizeof(msg));
}
} // namespace Common::SHA1
@@ -6,7 +6,7 @@
#include <array>
#include <type_traits>

#include "Common/TypeUtils.h"
#include "Common/Concepts.h"

template <std::size_t position, std::size_t bits, typename T, typename StorageType>
struct BitField;
@@ -36,11 +36,9 @@ class EnumMap final
constexpr EnumMap(EnumMap&& other) = default;
constexpr EnumMap& operator=(EnumMap&& other) = default;

// Constructor that accepts exactly size Vs (enforcing that all must be specified).
template <typename... T, typename = std::enable_if_t<Common::IsNOf<V, s_size, T...>::value>>
constexpr EnumMap(T... values) : m_array{static_cast<V>(values)...}
{
}
template <typename... Ts>
requires CountOfTypes<s_size, Ts...> && ConvertibleFromAllOf<V, Ts...>
constexpr EnumMap(Ts... values) : m_array{static_cast<V>(values)...} {}

constexpr const V& operator[](T key) const { return m_array[static_cast<std::size_t>(key)]; }
constexpr V& operator[](T key) { return m_array[static_cast<std::size_t>(key)]; }
@@ -8,6 +8,13 @@

namespace Common
{
template <class T>
#if FMT_VERSION >= 90000
concept FmtCompileString = fmt::detail::is_compile_string<T>::value;
#else
concept FmtCompileString = fmt::is_compile_string<T>::value;
#endif

constexpr std::size_t CountFmtReplacementFields(std::string_view s)
{
std::size_t count = 0;
@@ -0,0 +1,40 @@
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <version>

// C++20 Standard Library support in the Android NDK is lacking. However, the core language feature
// of concepts works fine in all environments Dolphin Emulator currently supports. Please replace
// the usage of this header with the C++ Standard Library equivalent as soon as is possible.

#ifdef __cpp_lib_concepts
#include <concepts>
#else
#include <type_traits>
namespace std
{
// Technically inaccurate because we haven't defined *all* of the standard concepts
#define __cpp_lib_concepts 202002L

template <class T, class U>
concept same_as = std::is_same_v<T, U> && std::is_same_v<U, T>;

template <class T>
concept integral = std::is_integral_v<T>;

template <class T>
concept signed_integral = integral<T> && std::is_signed_v<T>;

template <class T>
concept unsigned_integral = integral<T> && std::is_unsigned_v<T>;

template <class T>
concept floating_point = std::is_floating_point_v<T>;

template <class Derived, class Base>
concept derived_from = std::is_base_of_v<Base, Derived> &&
std::is_convertible_v<const volatile Derived*, const volatile Base*>;
}; // namespace std
#endif
@@ -7,9 +7,9 @@
#include <cstring>
#include <memory>
#include <string>
#include <type_traits>

#include "Common/CommonTypes.h"
#include "Common/Concepts.h"
#include "Common/IOFile.h"
#include "Common/Version.h"

@@ -27,7 +27,7 @@
// value_type[value_size] value;
//}

template <typename K, typename V>
template <Common::TriviallyCopyable K, typename V>
class LinearDiskCacheReader
{
public:
@@ -46,17 +46,14 @@ class LinearDiskCacheReader
// K and V are some POD type
// K : the key type
// V : value array type
template <typename K, typename V>
// Since we're reading/writing directly to the storage of K instances, K must be trivially copyable.
template <Common::TriviallyCopyable K, typename V>
class LinearDiskCache
{
public:
// return number of read entries
u32 OpenAndRead(const std::string& filename, LinearDiskCacheReader<K, V>& reader)
{
// Since we're reading/writing directly to the storage of K instances,
// K must be trivially copyable.
static_assert(std::is_trivially_copyable<K>::value, "K must be a trivially copyable type");

// close any currently opened file
Close();
m_num_entries = 0;
@@ -91,7 +91,7 @@ static const char LOG_LEVEL_TO_CHAR[7] = "-NEWID";
void GenericLogFmtImpl(LogLevel level, LogType type, const char* file, int line,
fmt::string_view format, const fmt::format_args& args);

template <std::size_t NumFields, typename S, typename... Args>
template <std::size_t NumFields, FmtCompileString S, typename... Args>
void GenericLogFmt(LogLevel level, LogType type, const char* file, int line, const S& format,
const Args&... args)
{
@@ -12,6 +12,8 @@

#include "Common/CommonTypes.h"

#include "Common/Future/CppLibConcepts.h"

namespace MathUtil
{
constexpr double TAU = 6.2831853071795865;
@@ -32,11 +34,9 @@ constexpr auto Lerp(const T& x, const T& y, const F& a) -> decltype(x + (y - x)

// Casts the specified value to a Dest. The value will be clamped to fit in the destination type.
// Warning: The result of SaturatingCast(NaN) is undefined.
template <typename Dest, typename T>
template <std::integral Dest, typename T>
constexpr Dest SaturatingCast(T value)
{
static_assert(std::is_integral<Dest>());

[[maybe_unused]] constexpr Dest lo = std::numeric_limits<Dest>::lowest();
constexpr Dest hi = std::numeric_limits<Dest>::max();

@@ -34,23 +34,18 @@ void RegisterStringTranslator(StringTranslator translator);
bool MsgAlertFmtImpl(bool yes_no, MsgType style, Common::Log::LogType log_type, const char* file,
int line, fmt::string_view format, const fmt::format_args& args);

template <std::size_t NumFields, typename S, typename... Args>
template <std::size_t NumFields, FmtCompileString S, typename... Args>
bool MsgAlertFmt(bool yes_no, MsgType style, Common::Log::LogType log_type, const char* file,
int line, const S& format, const Args&... args)
{
static_assert(NumFields == sizeof...(args),
"Unexpected number of replacement fields in format string; did you pass too few or "
"too many arguments?");
#if FMT_VERSION >= 90000
static_assert(fmt::detail::is_compile_string<S>::value);
#else
static_assert(fmt::is_compile_string<S>::value);
#endif
return MsgAlertFmtImpl(yes_no, style, log_type, file, line, format,
fmt::make_format_args(args...));
}

template <std::size_t NumFields, bool has_non_positional_args, typename S, typename... Args>
template <std::size_t NumFields, bool has_non_positional_args, FmtCompileString S, typename... Args>
bool MsgAlertFmtT(bool yes_no, MsgType style, Common::Log::LogType log_type, const char* file,
int line, const S& format, fmt::string_view translated_format,
const Args&... args)
@@ -60,11 +55,6 @@ bool MsgAlertFmtT(bool yes_no, MsgType style, Common::Log::LogType log_type, con
static_assert(NumFields == sizeof...(args),
"Unexpected number of replacement fields in format string; did you pass too few or "
"too many arguments?");
#if FMT_VERSION >= 90000
static_assert(fmt::detail::is_compile_string<S>::value);
#else
static_assert(fmt::is_compile_string<S>::value);
#endif
auto arg_list = fmt::make_format_args(args...);
return MsgAlertFmtImpl(yes_no, style, log_type, file, line, translated_format, arg_list);
}
@@ -19,6 +19,7 @@
#include <fmt/format.h>

#include "Common/BitUtils.h"
#include "Common/Concepts.h"
#include "Common/Random.h"
#include "Common/StringUtil.h"

@@ -315,10 +316,9 @@ u16 ComputeTCPNetworkChecksum(const IPAddress& from, const IPAddress& to, const
return htons(static_cast<u16>(tcp_checksum));
}

template <typename Container, typename T>
template <typename Container, Common::TriviallyCopyable T>
static inline void InsertObj(Container* container, const T& obj)
{
static_assert(std::is_trivially_copyable_v<T>);
const u8* const ptr = reinterpret_cast<const u8*>(&obj);
container->insert(container->end(), ptr, ptr + sizeof(obj));
}
@@ -5,20 +5,19 @@

#include <cstddef>
#include <memory>
#include <type_traits>

#include "Common/CommonTypes.h"
#include "Common/Concepts.h"

namespace Common::Random
{
/// Fill `buffer` with random bytes using a cryptographically secure pseudo-random number generator.
void Generate(void* buffer, std::size_t size);

/// Generates a random value of arithmetic type `T`
template <typename T>
template <Arithmetic T>
T GenerateValue()
{
static_assert(std::is_arithmetic<T>(), "T must be an arithmetic type in GenerateValue.");
T value;
Generate(&value, sizeof(value));
return value;
@@ -8,21 +8,22 @@
#include <SFML/Network/Packet.hpp>

#include "Common/CommonTypes.h"
#include "Common/Concepts.h"
#include "Common/Swap.h"

sf::Packet& operator>>(sf::Packet& packet, Common::BigEndianValue<u16>& data);
sf::Packet& operator>>(sf::Packet& packet, Common::BigEndianValue<u32>& data);
sf::Packet& operator>>(sf::Packet& packet, Common::BigEndianValue<u64>& data);

template <typename Enum, std::enable_if_t<std::is_enum_v<Enum>>* = nullptr>
template <Common::Enumerated Enum>
sf::Packet& operator<<(sf::Packet& packet, Enum e)
{
using Underlying = std::underlying_type_t<Enum>;
packet << static_cast<Underlying>(e);
return packet;
}

template <typename Enum, std::enable_if_t<std::is_enum_v<Enum>>* = nullptr>
template <Common::Enumerated Enum>
sf::Packet& operator>>(sf::Packet& packet, Enum& e)
{
using Underlying = std::underlying_type_t<Enum>;
@@ -16,6 +16,9 @@
#include <vector>

#include "Common/CommonTypes.h"
#include "Common/Concepts.h"

#include "Common/Future/CppLibConcepts.h"

std::string StringFromFormatV(const char* format, va_list args);

@@ -54,7 +57,7 @@ void TruncateToCString(std::string* s);

bool TryParse(const std::string& str, bool* output);

template <typename T, std::enable_if_t<std::is_integral_v<T> || std::is_enum_v<T>>* = nullptr>
template <Common::IntegralOrEnum T>
bool TryParse(const std::string& str, T* output, int base = 0)
{
char* end_ptr = nullptr;
@@ -92,7 +95,7 @@ bool TryParse(const std::string& str, T* output, int base = 0)
return true;
}

template <typename T, std::enable_if_t<std::is_floating_point_v<T>>* = nullptr>
template <std::floating_point T>
bool TryParse(std::string str, T* const output)
{
// Replace commas with dots.
@@ -138,7 +141,7 @@ std::string ValueToString(double value);
std::string ValueToString(int value);
std::string ValueToString(s64 value);
std::string ValueToString(bool value);
template <typename T, std::enable_if_t<std::is_enum<T>::value>* = nullptr>
template <Common::Enumerated T>
std::string ValueToString(T value)
{
return ValueToString(static_cast<std::underlying_type_t<T>>(value));
@@ -4,7 +4,6 @@
#pragma once

#include <cstring>
#include <type_traits>

#ifdef __APPLE__
#include <libkern/OSByteOrder.h>
@@ -17,6 +16,7 @@
#endif

#include "Common/CommonTypes.h"
#include "Common/Concepts.h"

namespace Common
{
@@ -157,19 +157,16 @@ inline void swap<8>(u8* data)
std::memcpy(data, &value, sizeof(u64));
}

template <typename T>
template <Arithmetic T>
inline T FromBigEndian(T data)
{
static_assert(std::is_arithmetic<T>::value, "function only makes sense with arithmetic types");

swap<sizeof(data)>(reinterpret_cast<u8*>(&data));
return data;
}

template <typename value_type>
template <Arithmetic value_type>
struct BigEndianValue
{
static_assert(std::is_arithmetic<value_type>(), "value_type must be an arithmetic type");
BigEndianValue() = default;
explicit BigEndianValue(value_type val) { *this = val; }
operator value_type() const { return FromBigEndian(raw); }
@@ -2,6 +2,7 @@

#pragma once

#include <array>
#include <cstddef>
#include <type_traits>

@@ -67,21 +68,23 @@ static_assert(std::is_same_v<ObjectType<&Bar::c>, Foo>);
static_assert(!std::is_same_v<ObjectType<&Bar::c>, Bar>);
} // namespace detail

// Template for checking if Types is count occurrences of T.
template <typename T, size_t count, typename... Ts>
struct IsNOf : std::integral_constant<bool, std::conjunction_v<std::is_convertible<Ts, T>...> &&
sizeof...(Ts) == count>
// Type trait for checking if the size of Types is equal to N
template <std::size_t N, typename... Ts>
struct IsCountOfTypes : std::bool_constant<sizeof...(Ts) == N>
{
};

static_assert(IsNOf<int, 0>::value);
static_assert(!IsNOf<int, 0, int>::value);
static_assert(IsNOf<int, 1, int>::value);
static_assert(!IsNOf<int, 1>::value);
static_assert(!IsNOf<int, 1, int, int>::value);
static_assert(IsNOf<int, 2, int, int>::value);
static_assert(IsNOf<int, 2, int, short>::value); // Type conversions ARE allowed
static_assert(!IsNOf<int, 2, int, char*>::value);
// Type trait for checking if all of the given Types are convertible to T
template <typename T, typename... Ts>
struct IsConvertibleFromAllOf : std::conjunction<std::is_convertible<Ts, T>...>
{
};

// Type trait for checking if any of the given Types are the same as T
template <typename T, typename... Ts>
struct IsSameAsAnyOf : std::disjunction<std::is_same<Ts, T>...>
{
};

// TODO: This can be replaced with std::array's fill() once C++20 is fully supported.
// Prior to C++20, std::array's fill() function is, unfortunately, not constexpr.
@@ -9,12 +9,12 @@
#include <cstring>
#include <functional>
#include <tuple>
#include <type_traits>

#include "Common/Assert.h"
#include "Common/BitSet.h"
#include "Common/CodeBlock.h"
#include "Common/CommonTypes.h"
#include "Common/Concepts.h"
#include "Common/x64ABI.h"

namespace Gen
@@ -979,13 +979,9 @@ class XEmitter
// Utility functions
// The difference between this and CALL is that this aligns the stack
// where appropriate.
template <typename FunctionPointer>
void ABI_CallFunction(FunctionPointer func)
template <Common::FunctionPointer T>
void ABI_CallFunction(T func)
{
static_assert(std::is_pointer<FunctionPointer>() &&
std::is_function<std::remove_pointer_t<FunctionPointer>>(),
"Supplied type must be a function pointer.");

const void* ptr = reinterpret_cast<const void*>(func);
const u64 address = reinterpret_cast<u64>(ptr);
const u64 distance = address - (reinterpret_cast<u64>(code) + 5);
@@ -1002,65 +998,64 @@ class XEmitter
}
}

template <typename FunctionPointer>
void ABI_CallFunctionC16(FunctionPointer func, u16 param1)
template <Common::FunctionPointer T>
void ABI_CallFunctionC16(T func, u16 param1)
{
MOV(32, R(ABI_PARAM1), Imm32(param1));
ABI_CallFunction(func);
}

template <typename FunctionPointer>
void ABI_CallFunctionCC16(FunctionPointer func, u32 param1, u16 param2)
template <Common::FunctionPointer T>
void ABI_CallFunctionCC16(T func, u32 param1, u16 param2)
{
MOV(32, R(ABI_PARAM1), Imm32(param1));
MOV(32, R(ABI_PARAM2), Imm32(param2));
ABI_CallFunction(func);
}

template <typename FunctionPointer>
void ABI_CallFunctionC(FunctionPointer func, u32 param1)
template <Common::FunctionPointer T>
void ABI_CallFunctionC(T func, u32 param1)
{
MOV(32, R(ABI_PARAM1), Imm32(param1));
ABI_CallFunction(func);
}

template <typename FunctionPointer>
void ABI_CallFunctionCC(FunctionPointer func, u32 param1, u32 param2)
template <Common::FunctionPointer T>
void ABI_CallFunctionCC(T func, u32 param1, u32 param2)
{
MOV(32, R(ABI_PARAM1), Imm32(param1));
MOV(32, R(ABI_PARAM2), Imm32(param2));
ABI_CallFunction(func);
}

template <typename FunctionPointer>
void ABI_CallFunctionCP(FunctionPointer func, u32 param1, const void* param2)
template <Common::FunctionPointer T>
void ABI_CallFunctionCP(T func, u32 param1, const void* param2)
{
MOV(32, R(ABI_PARAM1), Imm32(param1));
MOV(64, R(ABI_PARAM2), Imm64(reinterpret_cast<u64>(param2)));
ABI_CallFunction(func);
}

template <typename FunctionPointer>
void ABI_CallFunctionCCC(FunctionPointer func, u32 param1, u32 param2, u32 param3)
template <Common::FunctionPointer T>
void ABI_CallFunctionCCC(T func, u32 param1, u32 param2, u32 param3)
{
MOV(32, R(ABI_PARAM1), Imm32(param1));
MOV(32, R(ABI_PARAM2), Imm32(param2));
MOV(32, R(ABI_PARAM3), Imm32(param3));
ABI_CallFunction(func);
}

template <typename FunctionPointer>
void ABI_CallFunctionCCP(FunctionPointer func, u32 param1, u32 param2, const void* param3)
template <Common::FunctionPointer T>
void ABI_CallFunctionCCP(T func, u32 param1, u32 param2, const void* param3)
{
MOV(32, R(ABI_PARAM1), Imm32(param1));
MOV(32, R(ABI_PARAM2), Imm32(param2));
MOV(64, R(ABI_PARAM3), Imm64(reinterpret_cast<u64>(param3)));
ABI_CallFunction(func);
}

template <typename FunctionPointer>
void ABI_CallFunctionCCCP(FunctionPointer func, u32 param1, u32 param2, u32 param3,
const void* param4)
template <Common::FunctionPointer T>
void ABI_CallFunctionCCCP(T func, u32 param1, u32 param2, u32 param3, const void* param4)
{
MOV(32, R(ABI_PARAM1), Imm32(param1));
MOV(32, R(ABI_PARAM2), Imm32(param2));
@@ -1069,23 +1064,23 @@ class XEmitter
ABI_CallFunction(func);
}

template <typename FunctionPointer>
void ABI_CallFunctionP(FunctionPointer func, const void* param1)
template <Common::FunctionPointer T>
void ABI_CallFunctionP(T func, const void* param1)
{
MOV(64, R(ABI_PARAM1), Imm64(reinterpret_cast<u64>(param1)));
ABI_CallFunction(func);
}

template <typename FunctionPointer>
void ABI_CallFunctionPC(FunctionPointer func, const void* param1, u32 param2)
template <Common::FunctionPointer T>
void ABI_CallFunctionPC(T func, const void* param1, u32 param2)
{
MOV(64, R(ABI_PARAM1), Imm64(reinterpret_cast<u64>(param1)));
MOV(32, R(ABI_PARAM2), Imm32(param2));
ABI_CallFunction(func);
}

template <typename FunctionPointer>
void ABI_CallFunctionPPC(FunctionPointer func, const void* param1, const void* param2, u32 param3)
template <Common::FunctionPointer T>
void ABI_CallFunctionPPC(T func, const void* param1, const void* param2, u32 param3)
{
MOV(64, R(ABI_PARAM1), Imm64(reinterpret_cast<u64>(param1)));
MOV(64, R(ABI_PARAM2), Imm64(reinterpret_cast<u64>(param2)));
@@ -1094,17 +1089,17 @@ class XEmitter
}

// Pass a register as a parameter.
template <typename FunctionPointer>
void ABI_CallFunctionR(FunctionPointer func, X64Reg reg1)
template <Common::FunctionPointer T>
void ABI_CallFunctionR(T func, X64Reg reg1)
{
if (reg1 != ABI_PARAM1)
MOV(32, R(ABI_PARAM1), R(reg1));
ABI_CallFunction(func);
}

// Pass a pointer and register as a parameter.
template <typename FunctionPointer>
void ABI_CallFunctionPR(FunctionPointer func, const void* ptr, X64Reg reg1)
template <Common::FunctionPointer T>
void ABI_CallFunctionPR(T func, const void* ptr, X64Reg reg1)
{
if (reg1 != ABI_PARAM2)
MOV(64, R(ABI_PARAM2), R(reg1));
@@ -1113,34 +1108,33 @@ class XEmitter
}

// Pass two registers as parameters.
template <typename FunctionPointer>
void ABI_CallFunctionRR(FunctionPointer func, X64Reg reg1, X64Reg reg2)
template <Common::FunctionPointer T>
void ABI_CallFunctionRR(T func, X64Reg reg1, X64Reg reg2)
{
MOVTwo(64, ABI_PARAM1, reg1, 0, ABI_PARAM2, reg2);
ABI_CallFunction(func);
}

// Pass a pointer and two registers as parameters.
template <typename FunctionPointer>
void ABI_CallFunctionPRR(FunctionPointer func, const void* ptr, X64Reg reg1, X64Reg reg2)
template <Common::FunctionPointer T>
void ABI_CallFunctionPRR(T func, const void* ptr, X64Reg reg1, X64Reg reg2)
{
MOVTwo(64, ABI_PARAM2, reg1, 0, ABI_PARAM3, reg2);
MOV(64, R(ABI_PARAM1), Imm64(reinterpret_cast<u64>(ptr)));
ABI_CallFunction(func);
}

template <typename FunctionPointer>
void ABI_CallFunctionAC(int bits, FunctionPointer func, const Gen::OpArg& arg1, u32 param2)
template <Common::FunctionPointer T>
void ABI_CallFunctionAC(int bits, T func, const Gen::OpArg& arg1, u32 param2)
{
if (!arg1.IsSimpleReg(ABI_PARAM1))
MOV(bits, R(ABI_PARAM1), arg1);
MOV(32, R(ABI_PARAM2), Imm32(param2));
ABI_CallFunction(func);
}

template <typename FunctionPointer>
void ABI_CallFunctionPAC(int bits, FunctionPointer func, const void* ptr1, const Gen::OpArg& arg2,
u32 param3)
template <Common::FunctionPointer T>
void ABI_CallFunctionPAC(int bits, T func, const void* ptr1, const Gen::OpArg& arg2, u32 param3)
{
if (!arg2.IsSimpleReg(ABI_PARAM2))
MOV(bits, R(ABI_PARAM2), arg2);
@@ -1149,8 +1143,8 @@ class XEmitter
ABI_CallFunction(func);
}

template <typename FunctionPointer>
void ABI_CallFunctionA(int bits, FunctionPointer func, const Gen::OpArg& arg1)
template <Common::FunctionPointer T>
void ABI_CallFunctionA(int bits, T func, const Gen::OpArg& arg1)
{
if (!arg1.IsSimpleReg(ABI_PARAM1))
MOV(bits, R(ABI_PARAM1), arg1);
@@ -14,6 +14,7 @@
#include "Common/Align.h"
#include "Common/Assert.h"
#include "Common/BitUtils.h"
#include "Common/Concepts.h"
#include "Common/StringUtil.h"

#include "Core/Core.h"
@@ -59,10 +60,9 @@ Cheats::DataType Cheats::GetDataType(const Cheats::SearchValue& value)
return static_cast<DataType>(value.m_value.index());
}

template <typename T>
template <Common::TriviallyCopyable T>
static std::vector<u8> ToByteVector(const T& val)
{
static_assert(std::is_trivially_copyable_v<T>);
const auto* const begin = reinterpret_cast<const u8*>(&val);
const auto* const end = begin + sizeof(T);
return {begin, end};
@@ -5,12 +5,15 @@

#include "Common/Align.h"
#include "Common/CommonTypes.h"
#include "Common/Concepts.h"

#include "Core/PowerPC/MMU.h"
#include "Core/PowerPC/PowerPC.h"

#include <type_traits>

#include "Common/Future/CppLibConcepts.h"

namespace Core
{
class CPUThreadGuard;
@@ -19,15 +22,14 @@ class System;

namespace HLE::SystemVABI
{
// SFINAE
template <typename T>
constexpr bool IS_ARG_POINTER = std::is_union<T>() || std::is_class<T>();
concept arg_ARGPOINTER = Common::Union<T> || Common::Class<T>;
template <typename T>
constexpr bool IS_WORD = std::is_pointer<T>() || (std::is_integral<T>() && sizeof(T) <= 4);
concept arg_WORD = Common::Pointer<T> ||(std::integral<T> && sizeof(T) <= 4);
template <typename T>
constexpr bool IS_DOUBLE_WORD = std::is_integral<T>() && sizeof(T) == 8;
concept arg_DOUBLEWORD = std::integral<T> && sizeof(T) == 8;
template <typename T>
constexpr bool IS_ARG_REAL = std::is_floating_point<T>();
concept arg_ARGREAL = std::floating_point<T>;

// See System V ABI (SVR4) for more details
// -> 3-18 Parameter Passing
@@ -46,8 +48,7 @@ class VAList
}
virtual ~VAList();

// 0 - arg_ARGPOINTER
template <typename T, typename std::enable_if_t<IS_ARG_POINTER<T>>* = nullptr>
template <arg_ARGPOINTER T>
T GetArg(const Core::CPUThreadGuard& guard)
{
T obj;
@@ -61,8 +62,7 @@ class VAList
return obj;
}

// 1 - arg_WORD
template <typename T, typename std::enable_if_t<IS_WORD<T>>* = nullptr>
template <arg_WORD T>
T GetArg(const Core::CPUThreadGuard& guard)
{
static_assert(!std::is_pointer<T>(), "VAList doesn't support pointers");
@@ -83,8 +83,7 @@ class VAList
return static_cast<T>(value);
}

// 2 - arg_DOUBLEWORD
template <typename T, typename std::enable_if_t<IS_DOUBLE_WORD<T>>* = nullptr>
template <arg_DOUBLEWORD T>
T GetArg(const Core::CPUThreadGuard& guard)
{
u64 value;
@@ -106,8 +105,7 @@ class VAList
return static_cast<T>(value);
}

// 3 - arg_ARGREAL
template <typename T, typename std::enable_if_t<IS_ARG_REAL<T>>* = nullptr>
template <arg_ARGREAL T>
T GetArg(const Core::CPUThreadGuard& guard)
{
double value;
@@ -7,16 +7,18 @@
#include <atomic>
#include <string>
#include <tuple>
#include <type_traits>

#include "Common/Assert.h"
#include "Common/BitUtils.h"
#include "Common/CommonTypes.h"
#include "Common/Concepts.h"
#include "Core/ConfigManager.h"
#include "Core/HW/GPFifo.h"
#include "Core/HW/MMIOHandlers.h"
#include "Core/System.h"

#include "Common/Future/CppLibConcepts.h"

namespace MMIO
{
// There are three main MMIO blocks on the Wii (only one on the GameCube):
@@ -98,27 +100,30 @@ inline u16* HighPart(std::atomic<u32>* ptr)
}
} // namespace Utils

template <class Unit>
concept HandlerConstraint = Common::SameAsAnyOf<Unit, u8, u16, u32>;

class Mapping
{
public:
// MMIO registration interface. Use this to register new MMIO handlers.
//
// Example usages can be found in just about any HW/ module in Dolphin's
// codebase.
template <typename Unit>
void RegisterRead(u32 addr, ReadHandlingMethod<Unit>* read)
template <HandlerConstraint T>
void RegisterRead(u32 addr, ReadHandlingMethod<T>* read)
{
GetHandlerForRead<Unit>(addr).ResetMethod(read);
GetHandlerForRead<T>(addr).ResetMethod(read);
}

template <typename Unit>
void RegisterWrite(u32 addr, WriteHandlingMethod<Unit>* write)
template <HandlerConstraint T>
void RegisterWrite(u32 addr, WriteHandlingMethod<T>* write)
{
GetHandlerForWrite<Unit>(addr).ResetMethod(write);
GetHandlerForWrite<T>(addr).ResetMethod(write);
}

template <typename Unit>
void Register(u32 addr, ReadHandlingMethod<Unit>* read, WriteHandlingMethod<Unit>* write)
template <HandlerConstraint T>
void Register(u32 addr, ReadHandlingMethod<T>* read, WriteHandlingMethod<T>* write)
{
RegisterRead(addr, read);
RegisterWrite(addr, write);
@@ -130,33 +135,48 @@ class Mapping
// address. They are used by the Memory:: access functions, which are
// called in interpreter mode, from Dolphin's own code, or from JIT'd code
// where the access address could not be predicted.
template <typename Unit>
Unit Read(u32 addr)
template <HandlerConstraint T>
T Read(u32 addr)
{
return GetHandlerForRead<T>(addr).Read(Core::System::GetInstance(), addr);
}

template <HandlerConstraint T>
void Write(u32 addr, T val)
{
GetHandlerForWrite<T>(addr).Write(Core::System::GetInstance(), addr, val);
}

// Dummy 64 bits variants of these functions. While 64 bits MMIO access is
// not supported, we need these in order to make the code compile.
template <std::same_as<u64> T>
T Read(u32 addr)
{
return GetHandlerForRead<Unit>(addr).Read(Core::System::GetInstance(), addr);
ASSERT(false);
return 0;
}

template <typename Unit>
void Write(u32 addr, Unit val)
template <std::same_as<u64> T>
void Write(u32 addr, T val)
{
GetHandlerForWrite<Unit>(addr).Write(Core::System::GetInstance(), addr, val);
ASSERT(false);
}

// Handlers access interface.
//
// Use when you care more about how to access the MMIO register for an
// address than the current value of that register. For example, this is
// what could be used to implement fast MMIO accesses in Dolphin's JIT.
template <typename Unit>
ReadHandler<Unit>& GetHandlerForRead(u32 addr)
template <HandlerConstraint T>
ReadHandler<T>& GetHandlerForRead(u32 addr)
{
return GetReadHandler<Unit>(UniqueID(addr) / sizeof(Unit));
return GetReadHandler<T>(UniqueID(addr) / sizeof(T));
}

template <typename Unit>
WriteHandler<Unit>& GetHandlerForWrite(u32 addr)
template <HandlerConstraint T>
WriteHandler<T>& GetHandlerForWrite(u32 addr)
{
return GetWriteHandler<Unit>(UniqueID(addr) / sizeof(Unit));
return GetWriteHandler<T>(UniqueID(addr) / sizeof(T));
}

private:
@@ -168,11 +188,11 @@ class Mapping
// Each array contains NUM_MMIOS / sizeof (AccessType) because larger
// access types mean less possible adresses (assuming aligned only
// accesses).
template <typename Unit>
template <HandlerConstraint T>
struct HandlerArray
{
using Read = std::array<ReadHandler<Unit>, NUM_MMIOS / sizeof(Unit)>;
using Write = std::array<WriteHandler<Unit>, NUM_MMIOS / sizeof(Unit)>;
using Read = std::array<ReadHandler<T>, NUM_MMIOS / sizeof(T)>;
using Write = std::array<WriteHandler<T>, NUM_MMIOS / sizeof(T)>;
};

HandlerArray<u8>::Read m_read_handlers8;
@@ -184,45 +204,22 @@ class Mapping
HandlerArray<u32>::Write m_write_handlers32;

// Getter functions for the handler arrays.
template <typename Unit>
ReadHandler<Unit>& GetReadHandler(size_t index)
template <HandlerConstraint T>
ReadHandler<T>& GetReadHandler(size_t index)
{
static_assert(std::is_same<Unit, u8>() || std::is_same<Unit, u16>() ||
std::is_same<Unit, u32>(),
"Invalid unit used");

auto handlers = std::tie(m_read_handlers8, m_read_handlers16, m_read_handlers32);

using ArrayType = typename HandlerArray<Unit>::Read;
using ArrayType = typename HandlerArray<T>::Read;
return std::get<ArrayType&>(handlers)[index];
}

template <typename Unit>
WriteHandler<Unit>& GetWriteHandler(size_t index)
template <HandlerConstraint T>
WriteHandler<T>& GetWriteHandler(size_t index)
{
static_assert(std::is_same<Unit, u8>() || std::is_same<Unit, u16>() ||
std::is_same<Unit, u32>(),
"Invalid unit used");

auto handlers = std::tie(m_write_handlers8, m_write_handlers16, m_write_handlers32);

using ArrayType = typename HandlerArray<Unit>::Write;
using ArrayType = typename HandlerArray<T>::Write;
return std::get<ArrayType&>(handlers)[index];
}
};

// Dummy 64 bits variants of these functions. While 64 bits MMIO access is
// not supported, we need these in order to make the code compile.
template <>
inline u64 Mapping::Read<u64>(u32 addr)
{
DEBUG_ASSERT(false);
return 0;
}

template <>
inline void Mapping::Write(u32 addr, u64 val)
{
DEBUG_ASSERT(false);
}
} // namespace MMIO
@@ -5,9 +5,9 @@

#include <array>
#include <cstddef>
#include <type_traits>
#include <variant>

#include "Common/Concepts.h"
#include "Common/x64Emitter.h"
#include "Core/PowerPC/Jit64/RegCache/CachedReg.h"
#include "Core/PowerPC/PPCAnalyst.h"
@@ -119,6 +119,9 @@ class RCForkGuard
std::array<X64CachedReg, NUM_XREGS> m_xregs;
};

template <class T>
concept RegCacheConstraint = Common::SameAsAnyOf<T, RCOpArg, RCX64Reg>;

class RegCache
{
public:
@@ -135,17 +138,15 @@ class RegCache
void SetEmitter(Gen::XEmitter* emitter);
bool SanityCheck() const;

template <typename... Ts>
template <RegCacheConstraint... Ts>
static void Realize(Ts&... rc)
{
static_assert(((std::is_same<Ts, RCOpArg>() || std::is_same<Ts, RCX64Reg>()) && ...));
(rc.Realize(), ...);
}

template <typename... Ts>
template <RegCacheConstraint... Ts>
static void Unlock(Ts&... rc)
{
static_assert(((std::is_same<Ts, RCOpArg>() || std::is_same<Ts, RCX64Reg>()) && ...));
(rc.Unlock(), ...);
}

@@ -50,6 +50,8 @@

#include "VideoCommon/VideoBackendBase.h"

#include "Common/Future/CppLibConcepts.h"

namespace PowerPC
{
MMU::MMU(Core::System& system, Memory::MemoryManager& memory, PowerPC::PowerPCManager& power_pc)
@@ -145,7 +147,7 @@ static void EFB_Write(u32 data, u32 addr)
}
}

template <XCheckTLBFlag flag, typename T, bool never_translate>
template <XCheckTLBFlag flag, std::integral T, bool never_translate>
T MMU::ReadFromHardware(u32 em_address)
{
const u32 em_address_start_page = em_address & ~HW_PAGE_MASK;
@@ -593,7 +595,7 @@ u64 MMU::Read_U64(const u32 address)
return var;
}

template <typename T>
template <std::integral T>
std::optional<ReadResult<T>> MMU::HostTryReadUX(const Core::CPUThreadGuard& guard,
const u32 address, RequestedAddressSpace space)
{
@@ -11,6 +11,8 @@
#include "Common/BitField.h"
#include "Common/CommonTypes.h"

#include "Common/Future/CppLibConcepts.h"

namespace Core
{
class CPUThreadGuard;
@@ -303,14 +305,14 @@ class MMU
void UpdateBATs(BatTable& bat_table, u32 base_spr);
void UpdateFakeMMUBat(BatTable& bat_table, u32 start_addr);

template <XCheckTLBFlag flag, typename T, bool never_translate = false>
template <XCheckTLBFlag flag, std::integral T, bool never_translate = false>
T ReadFromHardware(u32 em_address);
template <XCheckTLBFlag flag, bool never_translate = false>
void WriteToHardware(u32 em_address, const u32 data, const u32 size);
template <XCheckTLBFlag flag>
bool IsRAMAddress(u32 address, bool translate);

template <typename T>
template <std::integral T>
static std::optional<ReadResult<T>> HostTryReadUX(const Core::CPUThreadGuard& guard,
const u32 address, RequestedAddressSpace space);
static std::optional<WriteResult> HostTryWriteUX(const Core::CPUThreadGuard& guard, const u32 var,
@@ -6,10 +6,10 @@
#include <algorithm>
#include <memory>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>

#include "Common/Concepts.h"
#include "Common/IOFile.h"
#include "Common/Swap.h"

@@ -33,11 +33,9 @@ void Replace(u64 offset, u64 size, u8* out_ptr, u64 replace_offset, u64 replace_
}
}

template <typename T>
template <Common::TriviallyCopyable T>
void Replace(u64 offset, u64 size, u8* out_ptr, u64 replace_offset, const T& replace_value)
{
static_assert(std::is_trivially_copyable_v<T>);

const u8* replace_ptr = reinterpret_cast<const u8*>(&replace_value);
Replace(offset, size, out_ptr, replace_offset, sizeof(T), replace_ptr);
}
@@ -8,11 +8,11 @@
#include <memory>
#include <optional>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>

#include "Common/CommonTypes.h"
#include "Common/Concepts.h"
#include "Common/Crypto/SHA1.h"
#include "Common/StringUtil.h"

@@ -31,10 +31,9 @@ const IOS::ES::TicketReader Volume::INVALID_TICKET{};
const IOS::ES::TMDReader Volume::INVALID_TMD{};
const std::vector<u8> Volume::INVALID_CERT_CHAIN{};

template <typename T>
template <Common::TriviallyCopyable T>
static void AddToSyncHash(Common::SHA1::Context* context, const T& data)
{
static_assert(std::is_trivially_copyable_v<T>);
context->Update(reinterpret_cast<const u8*>(&data), sizeof(data));
}

@@ -20,6 +20,7 @@
#include "Common/Align.h"
#include "Common/Assert.h"
#include "Common/CommonTypes.h"
#include "Common/Concepts.h"
#include "Common/Crypto/SHA1.h"
#include "Common/FileUtil.h"
#include "Common/IOFile.h"
@@ -47,11 +48,9 @@ static void PushBack(std::vector<u8>* vector, const u8* begin, const u8* end)
std::copy(begin, end, vector->data() + offset_in_vector);
}

template <typename T>
template <Common::TriviallyCopyable T>
static void PushBack(std::vector<u8>* vector, const T& x)
{
static_assert(std::is_trivially_copyable_v<T>);

const u8* x_ptr = reinterpret_cast<const u8*>(&x);
PushBack(vector, x_ptr, x_ptr + sizeof(T));
}
@@ -16,21 +16,11 @@
#include "Common/CommonTypes.h"
#include "Common/IniFile.h"
#include "InputCommon/ControllerEmu/Control/Control.h"
#include "InputCommon/ControllerEmu/Setting/NumericSetting.h"
#include "InputCommon/ControllerInterface/CoreDevice.h"

namespace ControllerEmu
{
class Control;

class NumericSettingBase;
struct NumericSettingDetails;

template <typename T>
class NumericSetting;

template <typename T>
class SettingValue;

using InputOverrideFunction = std::function<std::optional<ControlState>(
const std::string_view group_name, const std::string_view control_name, ControlState state)>;

@@ -79,7 +69,7 @@ class ControlGroup
void AddInput(Translatability translate, std::string name, std::string ui_name);
void AddOutput(Translatability translate, std::string name);

template <typename T>
template <SettingConstraint T>
void AddSetting(SettingValue<T>* value, const NumericSettingDetails& details,
std::common_type_t<T> default_value_, std::common_type_t<T> min_value = {},
std::common_type_t<T> max_value = T(100))
@@ -18,6 +18,8 @@
#include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h"
#include "InputCommon/ControllerInterface/CoreDevice.h"

#include "Common/Future/CppLibConcepts.h"

class ControllerInterface;

constexpr const char* DIRECTION_UP = _trans("Up");
@@ -210,17 +212,10 @@ class EmulatedController
std::vector<std::unique_ptr<ControlGroup>> groups;

// Maps a float from -1.0..+1.0 to an integer in the provided range.
template <typename T, typename F>
template <std::integral T, std::floating_point F>
static T MapFloat(F input_value, T zero_value, T neg_1_value = std::numeric_limits<T>::min(),
T pos_1_value = std::numeric_limits<T>::max())
{
static_assert(std::is_integral<T>(), "T is only sane for int types.");
static_assert(std::is_floating_point<F>(), "F is only sane for float types.");

static_assert(std::numeric_limits<long long>::min() <= std::numeric_limits<T>::min() &&
std::numeric_limits<long long>::max() >= std::numeric_limits<T>::max(),
"long long is not a superset of T. use of std::llround is not sane.");

// Here we round when converting from float to int.
// After applying our deadzone, resizing, and reshaping math
// we sometimes have a near-zero value which is slightly negative. (e.g. -0.0001)
@@ -7,6 +7,7 @@
#include <string>

#include "Common/CommonTypes.h"
#include "Common/Concepts.h"
#include "Common/IniFile.h"
#include "InputCommon/ControlReference/ControlReference.h"
#include "InputCommon/ControllerInterface/CoreDevice.h"
@@ -20,6 +21,10 @@ enum class SettingType
Bool,
};

// NumericSetting is only implemented for int, double, and bool.
template <class T>
concept SettingConstraint = Common::SameAsAnyOf<T, int, double, bool>;

enum class SettingVisibility
{
Normal,
@@ -88,19 +93,15 @@ class NumericSettingBase
NumericSettingDetails m_details;
};

template <typename T>
template <SettingConstraint T>
class SettingValue;

template <typename T>
template <SettingConstraint T>
class NumericSetting final : public NumericSettingBase
{
public:
using ValueType = T;

static_assert(std::is_same<ValueType, int>() || std::is_same<ValueType, double>() ||
std::is_same<ValueType, bool>(),
"NumericSetting is only implemented for int, double, and bool.");

NumericSetting(SettingValue<ValueType>* value, const NumericSettingDetails& details,
ValueType default_value, ValueType min_value, ValueType max_value)
: NumericSettingBase(details), m_value(*value), m_default_value(default_value),
@@ -170,7 +171,7 @@ class NumericSetting final : public NumericSettingBase
const ValueType m_max_value;
};

template <typename T>
template <SettingConstraint T>
class SettingValue
{
using ValueType = T;
@@ -7,9 +7,11 @@
#include "Common/StringUtil.h"
#include "VideoCommon/TextureCacheBase.h"

#include "Common/Future/CppLibConcepts.h"

namespace
{
template <typename T, std::enable_if_t<std::is_base_of_v<FBTarget, T>, int> = 0>
template <std::derived_from<FBTarget> T>
std::optional<T> DeserializeFBTargetFromConfig(const picojson::object& obj, std::string_view prefix)
{
T fb;
@@ -3,8 +3,6 @@

#pragma once

#include <type_traits>

#include "Common/Assert.h"
#include "Common/CommonTypes.h"
#include "Common/EnumFormatter.h"
@@ -13,6 +11,8 @@
#include "VideoCommon/CPMemory.h"
#include "VideoCommon/VertexLoaderBase.h"

#include "Common/Future/CppLibConcepts.h"

struct CPState;
class DataReader;

@@ -118,7 +118,7 @@ class Callback
namespace detail
{
// Main logic; split so that the main RunCommand can call OnCommand with the returned size.
template <typename T, typename = std::enable_if_t<std::is_base_of_v<Callback, T>>>
template <std::derived_from<Callback> T>
static DOLPHIN_FORCE_INLINE u32 RunCommand(const u8* data, u32 available, T& callback)
{
if (available < 1)
@@ -248,7 +248,7 @@ static DOLPHIN_FORCE_INLINE u32 RunCommand(const u8* data, u32 available, T& cal
}
} // namespace detail

template <typename T, typename = std::enable_if_t<std::is_base_of_v<Callback, T>>>
template <std::derived_from<Callback> T>
DOLPHIN_FORCE_INLINE u32 RunCommand(const u8* data, u32 available, T& callback)
{
const u32 size = detail::RunCommand(data, available, callback);
@@ -259,7 +259,7 @@ DOLPHIN_FORCE_INLINE u32 RunCommand(const u8* data, u32 available, T& callback)
return size;
}

template <typename T, typename = std::enable_if_t<std::is_base_of_v<Callback, T>>>
template <std::derived_from<Callback> T>
DOLPHIN_FORCE_INLINE u32 Run(const u8* data, u32 available, T& callback)
{
u32 size = 0;
@@ -225,7 +225,7 @@ static void UnserializePipelineUid(const SerializedUidType& uid, UidType& real_u
real_uid.blending_state.hex = uid.blending_state_bits;
}

template <ShaderStage stage, typename K, typename T>
template <ShaderStage stage, Common::TriviallyCopyable K, typename T>
void ShaderCache::LoadShaderCache(T& cache, APIType api_type, const char* type, bool include_gameid)
{
class CacheReader : public LinearDiskCacheReader<K, u8>
@@ -275,7 +275,7 @@ void ShaderCache::ClearShaderCache(T& cache)
cache.shader_map.clear();
}

template <typename KeyType, typename DiskKeyType, typename T>
template <typename KeyType, Common::TriviallyCopyable DiskKeyType, typename T>
void ShaderCache::LoadPipelineCache(T& cache, LinearDiskCache<DiskKeyType, u8>& disk_cache,
APIType api_type, const char* type, bool include_gameid)
{
@@ -171,11 +171,11 @@ class ShaderCache final
void QueueUberPipelineCompile(const GXUberPipelineUid& uid, u32 priority);

// Populating various caches.
template <ShaderStage stage, typename K, typename T>
template <ShaderStage stage, Common::TriviallyCopyable K, typename T>
void LoadShaderCache(T& cache, APIType api_type, const char* type, bool include_gameid);
template <typename T>
void ClearShaderCache(T& cache);
template <typename KeyType, typename DiskKeyType, typename T>
template <typename KeyType, Common::TriviallyCopyable DiskKeyType, typename T>
void LoadPipelineCache(T& cache, LinearDiskCache<DiskKeyType, u8>& disk_cache, APIType api_type,
const char* type, bool include_gameid);
template <typename T, typename Y>
@@ -7,13 +7,13 @@
#include <functional>
#include <iterator>
#include <string>
#include <type_traits>
#include <vector>

#include <fmt/format.h>

#include "Common/BitField.h"
#include "Common/CommonTypes.h"
#include "Common/Concepts.h"
#include "Common/EnumMap.h"
#include "Common/StringUtil.h"
#include "Common/TypeUtils.h"
@@ -65,13 +65,10 @@ class ShaderGeneratorInterface
* NOTE: Because LinearDiskCache reads and writes the storage associated with a ShaderUid instance,
* ShaderUid must be trivially copyable.
*/
template <class uid_data>
template <Common::TriviallyCopyable uid_data>
class ShaderUid : public ShaderGeneratorInterface
{
public:
static_assert(std::is_trivially_copyable_v<uid_data>,
"uid_data must be a trivially copyable type");

ShaderUid() { memset(GetUidData(), 0, GetUidDataSize()); }

bool operator==(const ShaderUid& obj) const
@@ -13,6 +13,8 @@
#include "VideoCommon/VertexLoaderManager.h"
#include "VideoCommon/VertexLoaderUtils.h"

#include "Common/Future/CppLibConcepts.h"

// warning: mapping buffer should be disabled to use this
#define LOG_NORM() // PRIM_LOG("norm: {} {} {}, ", ((float*)g_vertex_manager_write_ptr)[-3],
// ((float*)g_vertex_manager_write_ptr)[-2],
@@ -68,25 +70,23 @@ void Normal_ReadDirect(VertexLoader* loader)
DataSkip<N * 3 * sizeof(T)>();
}

template <typename I, typename T, u32 N, u32 Offset>
template <std::unsigned_integral I, typename T, u32 N, u32 Offset>
void Normal_ReadIndex_Offset(VertexLoader* loader)
{
static_assert(std::is_unsigned_v<I>, "Only unsigned I is sane!");

const auto index = DataRead<I>();
const auto data =
reinterpret_cast<const T*>(VertexLoaderManager::cached_arraybases[CPArray::Normal] +
(index * g_main_cp_state.array_strides[CPArray::Normal]));
ReadIndirect<T, N * 3, Offset * 3>(loader, data);
}

template <typename I, typename T, u32 N>
template <std::unsigned_integral I, typename T, u32 N>
void Normal_ReadIndex(VertexLoader* loader)
{
Normal_ReadIndex_Offset<I, T, N, 0>(loader);
}

template <typename I, typename T>
template <std::unsigned_integral I, typename T>
void Normal_ReadIndex_Indices3(VertexLoader* loader)
{
Normal_ReadIndex_Offset<I, T, 1, 0>(loader);
@@ -4,7 +4,6 @@
#include "VideoCommon/VertexLoader_Position.h"

#include <limits>
#include <type_traits>

#include "Common/CommonTypes.h"
#include "Common/EnumMap.h"
@@ -15,6 +14,8 @@
#include "VideoCommon/VertexLoaderUtils.h"
#include "VideoCommon/VideoCommon.h"

#include "Common/Future/CppLibConcepts.h"

namespace
{
template <typename T>
@@ -46,10 +47,9 @@ void Pos_ReadDirect(VertexLoader* loader)
LOG_VTX();
}

template <typename I, typename T, int N>
template <std::unsigned_integral I, typename T, int N>
void Pos_ReadIndex(VertexLoader* loader)
{
static_assert(std::is_unsigned<I>::value, "Only unsigned I is sane!");
static_assert(N <= 3, "N > 3 is not sane!");

const auto index = DataRead<I>();
@@ -3,15 +3,15 @@

#include "VideoCommon/VertexLoader_TextCoord.h"

#include <type_traits>

#include "Common/CommonTypes.h"
#include "Common/Swap.h"

#include "VideoCommon/VertexLoader.h"
#include "VideoCommon/VertexLoaderManager.h"
#include "VideoCommon/VertexLoaderUtils.h"

#include "Common/Future/CppLibConcepts.h"

namespace
{
void TexCoord_Read_Dummy(VertexLoader* loader)
@@ -42,11 +42,9 @@ void TexCoord_ReadDirect(VertexLoader* loader)
++loader->m_tcIndex;
}

template <typename I, typename T, int N>
template <std::unsigned_integral I, typename T, int N>
void TexCoord_ReadIndex(VertexLoader* loader)
{
static_assert(std::is_unsigned<I>::value, "Only unsigned I is sane!");

const auto index = DataRead<I>();
const auto data = reinterpret_cast<const T*>(
VertexLoaderManager::cached_arraybases[CPArray::TexCoord0 + loader->m_tcIndex] +