Skip to content

Commit

Permalink
Update BPMemory.h to Dolphin's version
Browse files Browse the repository at this point in the history
Note that there are some small differences:
- TextureFormat, TLUTFormat, and EFBCopyFormat are normally in a different header, but I've put them here instead
- I've added a function to set the copy format to UPE_Copy
- I've removed the use of Common/Inline.h
- I've removed non-implemented functions and the actual BP memory struct
- I've made the enum of various registers have a base type of u32 (which allows bitshifting with it without warnings)
  • Loading branch information
Pokechu22 committed Apr 17, 2022
1 parent 624a8f4 commit 3e6b1b1
Show file tree
Hide file tree
Showing 12 changed files with 2,489 additions and 859 deletions.
100 changes: 100 additions & 0 deletions common/EnumFormatter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright 2021 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "Common/EnumMap.h"

#include <fmt/format.h>
#include <type_traits>

/*
* Helper for using enums with fmt.
*
* Usage example:
*
* enum class Foo
* {
* A = 0,
* B = 1,
* C = 2,
* };
*
* template <>
* struct fmt::formatter<Foo> : EnumFormatter<Foo::C>
* {
* constexpr formatter() : EnumFormatter({"A", "B", "C"}) {}
* };
*
* enum class Bar
* {
* D = 0,
* E = 1,
* F = 3,
* };
*
* template <>
* struct fmt::formatter<Bar> : EnumFormatter<Bar::F>
* {
* // using std::array here fails due to nullptr not being const char*, at least in MSVC
* // (but only when a field is used; directly in the constructor is OK)
* static constexpr array_type names = {"D", "E", nullptr, "F"};
* constexpr formatter() : EnumFormatter(names) {}
* };
*/
template <auto last_member, typename = decltype(last_member)>
class EnumFormatter
{
// The second template argument is needed to avoid compile errors from ambiguity with multiple
// enums with the same number of members in GCC prior to 8. See https://godbolt.org/z/xcKaW1seW
// and https://godbolt.org/z/hz7Yqq1P5
using T = decltype(last_member);
static_assert(std::is_enum_v<T>);

public:
constexpr auto parse(fmt::format_parse_context& ctx)
{
auto it = ctx.begin(), end = ctx.end();
// 'u' for user display, 's' for shader generation, 'n' for name only
if (it != end && (*it == 'u' || *it == 's' || *it == 'n'))
format_type = *it++;
return it;
}

template <typename FormatContext>
auto format(const T& e, FormatContext& ctx) const
{
const auto value_s = static_cast<std::underlying_type_t<T>>(e); // Possibly signed
const auto value_u = static_cast<std::make_unsigned_t<T>>(value_s); // Always unsigned
const bool has_name = m_names.InBounds(e) && m_names[e] != nullptr;

switch (format_type)
{
default:
case 'u':
if (has_name)
return fmt::format_to(ctx.out(), "{} ({})", m_names[e], value_s);
else
return fmt::format_to(ctx.out(), "Invalid ({})", value_s);
case 's':
if (has_name)
return fmt::format_to(ctx.out(), "{:#x}u /* {} */", value_u, m_names[e]);
else
return fmt::format_to(ctx.out(), "{:#x}u /* Invalid */", value_u);
case 'n':
if (has_name)
return fmt::format_to(ctx.out(), "{}", m_names[e]);
else
return fmt::format_to(ctx.out(), "Invalid ({})", value_s);
}
}

protected:
// This is needed because std::array deduces incorrectly if nullptr is included in the list
using array_type = Common::EnumMap<const char*, last_member>;

constexpr explicit EnumFormatter(const array_type names) : m_names(std::move(names)) {}

const array_type m_names;
char format_type = 'u';
};
83 changes: 83 additions & 0 deletions common/EnumMap.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright 2021 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <array>
#include <type_traits>

#include "Common/TypeUtils.h"

template <std::size_t position, std::size_t bits, typename T, typename StorageType>
struct BitField;

namespace Common
{
// A type that allows lookup of values associated with an enum as the key.
// Designed for enums whose numeric values start at 0 and increment continuously with few gaps.
template <typename V, auto last_member, typename = decltype(last_member)>
class EnumMap final
{
// The third template argument is needed to avoid compile errors from ambiguity with multiple
// enums with the same number of members in GCC prior to 8. See https://godbolt.org/z/xcKaW1seW
// and https://godbolt.org/z/hz7Yqq1P5
using T = decltype(last_member);
static_assert(std::is_enum_v<T>);
static constexpr size_t s_size = static_cast<size_t>(last_member) + 1;

using array_type = std::array<V, s_size>;
using iterator = typename array_type::iterator;
using const_iterator = typename array_type::const_iterator;

public:
constexpr EnumMap() = default;
constexpr EnumMap(const EnumMap& other) = default;
constexpr EnumMap& operator=(const EnumMap& other) = default;
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)...}
{
}

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)]; }

// These only exist to perform the safety check; without them, BitField's implicit conversion
// would work (but since BitField is used for game-generated data, we need to be careful about
// bounds-checking)
template <std::size_t position, std::size_t bits, typename StorageType>
constexpr const V& operator[](BitField<position, bits, T, StorageType> key) const
{
static_assert(1 << bits == s_size, "Unsafe indexing into EnumMap (may go out of bounds)");
return m_array[static_cast<std::size_t>(key.Value())];
}
template <std::size_t position, std::size_t bits, typename StorageType>
constexpr V& operator[](BitField<position, bits, T, StorageType> key)
{
static_assert(1 << bits == s_size, "Unsafe indexing into EnumMap (may go out of bounds)");
return m_array[static_cast<std::size_t>(key.value())];
}

constexpr bool InBounds(T key) const { return static_cast<std::size_t>(key) < s_size; }

constexpr size_t size() const noexcept { return s_size; }

constexpr V* data() { return m_array.data(); }
constexpr const V* data() const { return m_array.data(); }

constexpr iterator begin() { return m_array.begin(); }
constexpr iterator end() { return m_array.end(); }
constexpr const_iterator begin() const { return m_array.begin(); }
constexpr const_iterator end() const { return m_array.end(); }
constexpr const_iterator cbegin() const { return m_array.cbegin(); }
constexpr const_iterator cend() const { return m_array.cend(); }

constexpr void fill(const V& v) { m_array.fill(v); }

private:
array_type m_array{};
};
} // namespace Common
86 changes: 86 additions & 0 deletions common/TypeUtils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright 2021 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <cstddef>
#include <type_traits>

namespace Common
{
template <typename>
struct MemberPointerInfo;
// Helper to get information about a pointer to a data member.
// See https://en.cppreference.com/w/cpp/language/pointer#Pointers_to_members
// This template takes the type for a member pointer.
template <typename M, typename O>
struct MemberPointerInfo<M O::*>
{
using MemberType = M;
using ObjectType = O;
};

// This template takes a specific member pointer.
template <auto member_pointer>
using MemberType = typename MemberPointerInfo<decltype(member_pointer)>::MemberType;

// This template takes a specific member pointer.
template <auto member_pointer>
using ObjectType = typename MemberPointerInfo<decltype(member_pointer)>::ObjectType;

namespace detail
{
template <int x>
struct Data
{
static constexpr int GetX() { return x; }
};
struct Foo
{
Data<1> a;
Data<2> b;
int c;
};
struct Bar : Foo
{
int d;
};

static_assert(std::is_same_v<MemberType<&Foo::a>, Data<1>>);
static_assert(MemberType<&Foo::a>::GetX() == 1);
static_assert(std::is_same_v<MemberType<&Foo::b>, Data<2>>);
static_assert(MemberType<&Foo::b>::GetX() == 2);
static_assert(std::is_same_v<MemberType<&Foo::c>, int>);

static_assert(std::is_same_v<ObjectType<&Foo::a>, Foo>);
static_assert(std::is_same_v<ObjectType<&Foo::b>, Foo>);
static_assert(std::is_same_v<ObjectType<&Foo::c>, Foo>);

static_assert(std::is_same_v<MemberType<&Foo::c>, MemberPointerInfo<int Foo::*>::MemberType>);
static_assert(std::is_same_v<ObjectType<&Foo::c>, MemberPointerInfo<int Foo::*>::ObjectType>);

static_assert(std::is_same_v<MemberType<&Bar::c>, int>);
static_assert(std::is_same_v<MemberType<&Bar::d>, int>);

static_assert(std::is_same_v<ObjectType<&Bar::d>, Bar>);
// Somewhat unexpected behavior:
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>
{
};

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);
} // namespace Common
Loading

0 comments on commit 3e6b1b1

Please sign in to comment.