diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..c47a995 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "rt64"] + path = rt64 + url = https://github.com/rt64/rt64.git diff --git a/README.md b/README.md new file mode 100644 index 0000000..9d816b0 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# N64 Modern Runtime + +> Note +This repo is a WIP as files are moved out of Zelda64Recomp and genericized. It cannot be used directly in its current state. + +This repo contains two libraries: Ultramodern and Librecomp. + +### Ultramodern + +Ultramodern is a reimplementation of much of the core functionality of libultra. It can be used with either statically recompiled projects that use N64Recomp or direct source ports. It implements the following libultra functionality: +* Threads +* Controllers +* Audio +* Message Queues +* Timers +* RSP Task Handling +* VI timing + +Platform-specific I/O is handled via callbacks that are provided by the project using ultramodern. This includes reading from controllers and playing back audio samples. + +Currently, ultramodern depends directly on [RT64](https://github.com/rt64/rt64) for rendering. It may be decoupled in the future to allow using other renderers for targeting different platforms. + +### Librecomp + +Librecomp is a library meant to be used to bridge the gap between code generated by N64Recomp and ultramodern. It provides wrappers to allow recompiled code to call ultramodern. Librecomp also provides some of the remaining libultra functionality that ultramodern doesn't provide, which includes: +* Overlay handling +* PI DMA (ROM reads) +* EEPROM, SRAM and Flashram saving (these may be partially moved to ultramodern in the future) \ No newline at end of file diff --git a/librecomp/include/disable_warnings.h b/librecomp/include/disable_warnings.h new file mode 100644 index 0000000..6526983 --- /dev/null +++ b/librecomp/include/disable_warnings.h @@ -0,0 +1,4 @@ +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wunused-variable" +#pragma GCC diagnostic ignored "-Wimplicit-function-declaration" +#endif diff --git a/librecomp/include/euc-jp.h b/librecomp/include/euc-jp.h new file mode 100644 index 0000000..9ee246f --- /dev/null +++ b/librecomp/include/euc-jp.h @@ -0,0 +1,11 @@ +#ifndef __EUC_JP_H__ +#define __EUC_JP_H__ + +#include +#include + +namespace Encoding { + std::string decode_eucjp(std::string_view src); +} + +#endif diff --git a/librecomp/include/recomp.h b/librecomp/include/recomp.h new file mode 100644 index 0000000..bbfbba0 --- /dev/null +++ b/librecomp/include/recomp.h @@ -0,0 +1,277 @@ +#ifndef __RECOMP_H__ +#define __RECOMP_H__ + +#include +#include +#include +#include +#include + +typedef uint64_t gpr; + +#define SIGNED(val) \ + ((int64_t)(val)) + +#define ADD32(a, b) \ + ((gpr)(int32_t)((a) + (b))) + +#define SUB32(a, b) \ + ((gpr)(int32_t)((a) - (b))) + +#define MEM_W(offset, reg) \ + (*(int32_t*)(rdram + ((((reg) + (offset))) - 0xFFFFFFFF80000000))) + +#define MEM_H(offset, reg) \ + (*(int16_t*)(rdram + ((((reg) + (offset)) ^ 2) - 0xFFFFFFFF80000000))) + +#define MEM_B(offset, reg) \ + (*(int8_t*)(rdram + ((((reg) + (offset)) ^ 3) - 0xFFFFFFFF80000000))) + +#define MEM_HU(offset, reg) \ + (*(uint16_t*)(rdram + ((((reg) + (offset)) ^ 2) - 0xFFFFFFFF80000000))) + +#define MEM_BU(offset, reg) \ + (*(uint8_t*)(rdram + ((((reg) + (offset)) ^ 3) - 0xFFFFFFFF80000000))) + +#define SD(val, offset, reg) { \ + *(uint32_t*)(rdram + ((((reg) + (offset) + 4)) - 0xFFFFFFFF80000000)) = (uint32_t)((gpr)(val) >> 0); \ + *(uint32_t*)(rdram + ((((reg) + (offset) + 0)) - 0xFFFFFFFF80000000)) = (uint32_t)((gpr)(val) >> 32); \ +} + +static inline uint64_t load_doubleword(uint8_t* rdram, gpr reg, gpr offset) { + uint64_t ret = 0; + uint64_t lo = (uint64_t)(uint32_t)MEM_W(reg, offset + 4); + uint64_t hi = (uint64_t)(uint32_t)MEM_W(reg, offset + 0); + ret = (lo << 0) | (hi << 32); + return ret; +} + +#define LD(offset, reg) \ + load_doubleword(rdram, offset, reg) + +static inline gpr do_lwl(uint8_t* rdram, gpr initial_value, gpr offset, gpr reg) { + // Calculate the overall address + gpr address = (offset + reg); + + // Load the aligned word + gpr word_address = address & ~0x3; + uint32_t loaded_value = MEM_W(0, word_address); + + // Mask the existing value and shift the loaded value appropriately + gpr misalignment = address & 0x3; + gpr masked_value = initial_value & ~(0xFFFFFFFFu << (misalignment * 8)); + loaded_value <<= (misalignment * 8); + + // Cast to int32_t to sign extend first + return (gpr)(int32_t)(masked_value | loaded_value); +} + +static inline gpr do_lwr(uint8_t* rdram, gpr initial_value, gpr offset, gpr reg) { + // Calculate the overall address + gpr address = (offset + reg); + + // Load the aligned word + gpr word_address = address & ~0x3; + uint32_t loaded_value = MEM_W(0, word_address); + + // Mask the existing value and shift the loaded value appropriately + gpr misalignment = address & 0x3; + gpr masked_value = initial_value & ~(0xFFFFFFFFu >> (24 - misalignment * 8)); + loaded_value >>= (24 - misalignment * 8); + + // Cast to int32_t to sign extend first + return (gpr)(int32_t)(masked_value | loaded_value); +} + +static inline void do_swl(uint8_t* rdram, gpr offset, gpr reg, gpr val) { + // Calculate the overall address + gpr address = (offset + reg); + + // Get the initial value of the aligned word + gpr word_address = address & ~0x3; + uint32_t initial_value = MEM_W(0, word_address); + + // Mask the initial value and shift the input value appropriately + gpr misalignment = address & 0x3; + uint32_t masked_initial_value = initial_value & ~(0xFFFFFFFFu >> (misalignment * 8)); + uint32_t shifted_input_value = ((uint32_t)val) >> (misalignment * 8); + MEM_W(0, word_address) = masked_initial_value | shifted_input_value; +} + +static inline void do_swr(uint8_t* rdram, gpr offset, gpr reg, gpr val) { + // Calculate the overall address + gpr address = (offset + reg); + + // Get the initial value of the aligned word + gpr word_address = address & ~0x3; + uint32_t initial_value = MEM_W(0, word_address); + + // Mask the initial value and shift the input value appropriately + gpr misalignment = address & 0x3; + uint32_t masked_initial_value = initial_value & ~(0xFFFFFFFFu << (24 - misalignment * 8)); + uint32_t shifted_input_value = ((uint32_t)val) << (24 - misalignment * 8); + MEM_W(0, word_address) = masked_initial_value | shifted_input_value; +} + +#define S32(val) \ + ((int32_t)(val)) + +#define U32(val) \ + ((uint32_t)(val)) + +#define S64(val) \ + ((int64_t)(val)) + +#define U64(val) \ + ((uint64_t)(val)) + +#define MUL_S(val1, val2) \ + ((val1) * (val2)) + +#define MUL_D(val1, val2) \ + ((val1) * (val2)) + +#define DIV_S(val1, val2) \ + ((val1) / (val2)) + +#define DIV_D(val1, val2) \ + ((val1) / (val2)) + +#define CVT_S_W(val) \ + ((float)((int32_t)(val))) + +#define CVT_D_W(val) \ + ((double)((int32_t)(val))) + +#define CVT_D_S(val) \ + ((double)(val)) + +#define CVT_S_D(val) \ + ((float)(val)) + +#define TRUNC_W_S(val) \ + ((int32_t)(val)) + +#define TRUNC_W_D(val) \ + ((int32_t)(val)) + +#define TRUNC_L_S(val) \ + ((int64_t)(val)) + +#define TRUNC_L_D(val) \ + ((int64_t)(val)) + +#define DEFAULT_ROUNDING_MODE 0 + +static inline int32_t do_cvt_w_s(float val, unsigned int rounding_mode) { + switch (rounding_mode) { + case 0: // round to nearest value + return (int32_t)lroundf(val); + case 1: // round to zero (truncate) + return (int32_t)val; + case 2: // round to positive infinity (ceil) + return (int32_t)ceilf(val); + case 3: // round to negative infinity (floor) + return (int32_t)floorf(val); + } + assert(0); + return 0; +} + +#define CVT_W_S(val) \ + do_cvt_w_s(val, rounding_mode) + +static inline int32_t do_cvt_w_d(double val, unsigned int rounding_mode) { + switch (rounding_mode) { + case 0: // round to nearest value + return (int32_t)lround(val); + case 1: // round to zero (truncate) + return (int32_t)val; + case 2: // round to positive infinity (ceil) + return (int32_t)ceil(val); + case 3: // round to negative infinity (floor) + return (int32_t)floor(val); + } + assert(0); + return 0; +} + +#define CVT_W_D(val) \ + do_cvt_w_d(val, rounding_mode) + +#define NAN_CHECK(val) \ + assert(val == val) + +//#define NAN_CHECK(val) + +typedef union { + double d; + struct { + float fl; + float fh; + }; + struct { + uint32_t u32l; + uint32_t u32h; + }; + uint64_t u64; +} fpr; + +typedef struct { + gpr r0, r1, r2, r3, r4, r5, r6, r7, + r8, r9, r10, r11, r12, r13, r14, r15, + r16, r17, r18, r19, r20, r21, r22, r23, + r24, r25, r26, r27, r28, r29, r30, r31; + fpr f0, f1, f2, f3, f4, f5, f6, f7, + f8, f9, f10, f11, f12, f13, f14, f15, + f16, f17, f18, f19, f20, f21, f22, f23, + f24, f25, f26, f27, f28, f29, f30, f31; + uint64_t hi, lo; + uint32_t* f_odd; + uint32_t status_reg; + uint8_t mips3_float_mode; +} recomp_context; + +// Checks if the target is an even float register or that mips3 float mode is enabled +#define CHECK_FR(ctx, idx) \ + assert(((idx) & 1) == 0 || (ctx)->mips3_float_mode) + +#ifdef __cplusplus +extern "C" { +#endif + +void cop0_status_write(recomp_context* ctx, gpr value); +gpr cop0_status_read(recomp_context* ctx); +void switch_error(const char* func, uint32_t vram, uint32_t jtbl); +void do_break(uint32_t vram); + +typedef void (recomp_func_t)(uint8_t* rdram, recomp_context* ctx); + +recomp_func_t* get_function(int32_t vram); + +#define LOOKUP_FUNC(val) \ + get_function((int32_t)(val)) + +extern int32_t section_addresses[]; + +#define LO16(x) \ + ((x) & 0xFFFF) + +#define HI16(x) \ + (((x) >> 16) + (((x) >> 15) & 1)) + +#define RELOC_HI16(section_index, offset) \ + HI16(section_addresses[section_index] + (offset)) + +#define RELOC_LO16(section_index, offset) \ + LO16(section_addresses[section_index] + (offset)) + +void recomp_syscall_handler(uint8_t* rdram, recomp_context* ctx, int32_t instruction_vram); + +void pause_self(uint8_t *rdram); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/librecomp/include/recomp_game.h b/librecomp/include/recomp_game.h new file mode 100644 index 0000000..d528f18 --- /dev/null +++ b/librecomp/include/recomp_game.h @@ -0,0 +1,48 @@ +#ifndef __RECOMP_GAME__ +#define __RECOMP_GAME__ + +#include +#include + +#include "recomp.h" +#include "../ultramodern/ultramodern.hpp" +#include "rt64_layer.h" + +namespace recomp { + // TODO refactor this to allow a particular project to specify games. + // A register game function should be added which takes: + // * Expected ROM hash + // * Internal ROM name (for error messages) + // * Entrypoint function to run for the game + // * Shader cache (optional) + // The register game function should return a handle struct (probably containing an ID) so the project can check + // which game is running (if any) by querying it. + enum class Game { + OoT, + MM, + None, + Quit + }; + enum class RomValidationError { + Good, + FailedToOpen, + NotARom, + IncorrectRom, + NotYet, + IncorrectVersion, + OtherError + }; + void check_all_stored_roms(); + bool load_stored_rom(Game game); + RomValidationError select_rom(const std::filesystem::path& rom_path, Game game); + bool is_rom_valid(Game game); + bool is_rom_loaded(); + void set_rom_contents(std::vector&& new_rom); + void do_rom_read(uint8_t* rdram, gpr ram_address, uint32_t physical_addr, size_t num_bytes); + void do_rom_pio(uint8_t* rdram, gpr ram_address, uint32_t physical_addr); + void start(ultramodern::WindowHandle window_handle, const ultramodern::audio_callbacks_t& audio_callbacks, const ultramodern::input_callbacks_t& input_callbacks, const ultramodern::gfx_callbacks_t& gfx_callbacks); + void start_game(Game game); + void message_box(const char* message); +} + +#endif diff --git a/librecomp/include/recomp_helpers.h b/librecomp/include/recomp_helpers.h new file mode 100644 index 0000000..cfadf81 --- /dev/null +++ b/librecomp/include/recomp_helpers.h @@ -0,0 +1,50 @@ +#ifndef __RECOMP_HELPERS__ +#define __RECOMP_HELPERS__ + +#include "recomp.h" +#include "../ultramodern/ultra64.h" + +template +T _arg(uint8_t* rdram, recomp_context* ctx) { + static_assert(index < 4, "Only args 0 through 3 supported"); + gpr raw_arg = (&ctx->r4)[index]; + if constexpr (std::is_same_v) { + if constexpr (index < 2) { + static_assert(index != 1, "Floats in arg 1 not supported"); + return ctx->f12.fl; + } + else { + // static_assert in else workaround + [] () { + static_assert(flag, "Floats in a2/a3 not supported"); + }(); + } + } + else if constexpr (std::is_pointer_v) { + static_assert (!std::is_pointer_v>, "Double pointers not supported"); + return TO_PTR(std::remove_pointer_t, raw_arg); + } + else if constexpr (std::is_integral_v) { + static_assert(sizeof(T) <= 4, "64-bit args not supported"); + return static_cast(raw_arg); + } + else { + // static_assert in else workaround + [] () { + static_assert(flag, "Unsupported type"); + }(); + } +} + +template +void _return(recomp_context* ctx, T val) { + static_assert(sizeof(T) <= 4 && "Only 32-bit value returns supported currently"); + if (std::is_same_v) { + ctx->f0.fl = val; + } + else if (std::is_integral_v && sizeof(T) <= 4) { + ctx->r2 = int32_t(val); + } +} + +#endif \ No newline at end of file diff --git a/librecomp/include/recomp_input.h b/librecomp/include/recomp_input.h new file mode 100644 index 0000000..85d9df7 --- /dev/null +++ b/librecomp/include/recomp_input.h @@ -0,0 +1,174 @@ +#ifndef __RECOMP_INPUT_H__ +#define __RECOMP_INPUT_H__ + +#include +#include +#include +#include +#include +#include +#include + +#include "json/json.hpp" + +namespace recomp { + // x-macros to build input enums and arrays. + // First parameter is the enum name, second parameter is the bit field for the input (or 0 if there is no associated one), third is the readable name. + // TODO refactor this to allow projects to rename these, or get rid of the readable name and leave that up to individual projects to map. + #define DEFINE_N64_BUTTON_INPUTS() \ + DEFINE_INPUT(A, 0x8000, "Action") \ + DEFINE_INPUT(B, 0x4000, "Attack/Cancel") \ + DEFINE_INPUT(Z, 0x2000, "Target") \ + DEFINE_INPUT(START, 0x1000, "Start") \ + DEFINE_INPUT(L, 0x0020, "Toggle map") \ + DEFINE_INPUT(R, 0x0010, "Shield") \ + DEFINE_INPUT(C_UP, 0x0008, "Look/Fairy") \ + DEFINE_INPUT(C_LEFT, 0x0002, "Item 1") \ + DEFINE_INPUT(C_DOWN, 0x0004, "Item 2") \ + DEFINE_INPUT(C_RIGHT, 0x0001, "Item 3") \ + DEFINE_INPUT(DPAD_UP, 0x0800, "Special Item 1") \ + DEFINE_INPUT(DPAD_RIGHT, 0x0100, "Special Item 2") \ + DEFINE_INPUT(DPAD_DOWN, 0x0400, "Special Item 3") \ + DEFINE_INPUT(DPAD_LEFT, 0x0200, "Special Item 4") + + #define DEFINE_N64_AXIS_INPUTS() \ + DEFINE_INPUT(Y_AXIS_POS, 0, "Up") \ + DEFINE_INPUT(Y_AXIS_NEG, 0, "Down") \ + DEFINE_INPUT(X_AXIS_NEG, 0, "Left") \ + DEFINE_INPUT(X_AXIS_POS, 0, "Right") \ + + #define DEFINE_ALL_INPUTS() \ + DEFINE_N64_BUTTON_INPUTS() \ + DEFINE_N64_AXIS_INPUTS() + + // Enum containing every recomp input. + #define DEFINE_INPUT(name, value, readable) name, + enum class GameInput { + DEFINE_ALL_INPUTS() + + COUNT, + N64_BUTTON_START = A, + N64_BUTTON_COUNT = C_RIGHT - N64_BUTTON_START + 1, + N64_AXIS_START = X_AXIS_NEG, + N64_AXIS_COUNT = Y_AXIS_POS - N64_AXIS_START + 1, + }; + #undef DEFINE_INPUT + + struct InputField { + uint32_t input_type; + int32_t input_id; + std::string to_string() const; + auto operator<=>(const InputField& rhs) const = default; + }; + + void poll_inputs(); + float get_input_analog(const InputField& field); + float get_input_analog(const std::span fields); + bool get_input_digital(const InputField& field); + bool get_input_digital(const std::span fields); + void get_gyro_deltas(float* x, float* y); + void get_mouse_deltas(float* x, float* y); + + enum class InputDevice { + Controller, + Keyboard, + COUNT + }; + + void start_scanning_input(InputDevice device); + void stop_scanning_input(); + void finish_scanning_input(InputField scanned_field); + void cancel_scanning_input(); + void config_menu_set_cont_or_kb(bool cont_interacted); + InputField get_scanned_input(); + + struct DefaultN64Mappings { + std::vector a; + std::vector b; + std::vector l; + std::vector r; + std::vector z; + std::vector start; + + std::vector c_left; + std::vector c_right; + std::vector c_up; + std::vector c_down; + + std::vector dpad_left; + std::vector dpad_right; + std::vector dpad_up; + std::vector dpad_down; + + std::vector analog_left; + std::vector analog_right; + std::vector analog_up; + std::vector analog_down; + }; + + extern const DefaultN64Mappings default_n64_keyboard_mappings; + extern const DefaultN64Mappings default_n64_controller_mappings; + + constexpr size_t bindings_per_input = 2; + + size_t get_num_inputs(); + const std::string& get_input_name(GameInput input); + const std::string& get_input_enum_name(GameInput input); + GameInput get_input_from_enum_name(const std::string_view name); + InputField& get_input_binding(GameInput input, size_t binding_index, InputDevice device); + void set_input_binding(GameInput input, size_t binding_index, InputDevice device, InputField value); + + void get_n64_input(uint16_t* buttons_out, float* x_out, float* y_out); + void set_rumble(bool); + void update_rumble(); + void handle_events(); + + // Rumble strength ranges from 0 to 100. + int get_rumble_strength(); + void set_rumble_strength(int strength); + + // Gyro and mouse sensitivities range from 0 to 100. + int get_gyro_sensitivity(); + int get_mouse_sensitivity(); + void set_gyro_sensitivity(int strength); + void set_mouse_sensitivity(int strength); + + enum class TargetingMode { + Switch, + Hold, + OptionCount + }; + + NLOHMANN_JSON_SERIALIZE_ENUM(recomp::TargetingMode, { + {recomp::TargetingMode::Switch, "Switch"}, + {recomp::TargetingMode::Hold, "Hold"} + }); + + TargetingMode get_targeting_mode(); + void set_targeting_mode(TargetingMode mode); + + enum class BackgroundInputMode { + On, + Off, + OptionCount + }; + + NLOHMANN_JSON_SERIALIZE_ENUM(recomp::BackgroundInputMode, { + {recomp::BackgroundInputMode::On, "On"}, + {recomp::BackgroundInputMode::Off, "Off"} + }); + + BackgroundInputMode get_background_input_mode(); + void set_background_input_mode(BackgroundInputMode mode); + + bool game_input_disabled(); + bool all_input_disabled(); + + // TODO move these somewhere else. + void quicksave_save(); + void quicksave_load(); + + void open_quit_game_prompt(); +} + +#endif diff --git a/librecomp/include/recomp_overlays.h b/librecomp/include/recomp_overlays.h new file mode 100644 index 0000000..cf1f8a1 --- /dev/null +++ b/librecomp/include/recomp_overlays.h @@ -0,0 +1,10 @@ +#ifndef __RECOMP_OVERLAYS_H__ +#define __RECOMP_OVERLAYS_H__ + +#include + +extern "C" void load_overlays(uint32_t rom, int32_t ram_addr, uint32_t size); +extern "C" void unload_overlays(int32_t ram_addr, uint32_t size); +void init_overlays(); + +#endif \ No newline at end of file diff --git a/librecomp/include/rsp.h b/librecomp/include/rsp.h new file mode 100644 index 0000000..17f8f90 --- /dev/null +++ b/librecomp/include/rsp.h @@ -0,0 +1,94 @@ +#ifndef __RSP_H__ +#define __RSP_H__ + +#include "rsp_vu.h" +#include "recomp.h" +#include + +enum class RspExitReason { + Invalid, + Broke, + ImemOverrun, + UnhandledJumpTarget, + Unsupported +}; + +extern uint8_t dmem[]; +extern uint16_t rspReciprocals[512]; +extern uint16_t rspInverseSquareRoots[512]; + +#define RSP_MEM_B(offset, addr) \ + (*reinterpret_cast(dmem + (0xFFF & (((offset) + (addr)) ^ 3)))) + +#define RSP_MEM_BU(offset, addr) \ + (*reinterpret_cast(dmem + (0xFFF & (((offset) + (addr)) ^ 3)))) + +static inline uint32_t RSP_MEM_W_LOAD(uint32_t offset, uint32_t addr) { + uint32_t out; + for (int i = 0; i < 4; i++) { + reinterpret_cast(&out)[i ^ 3] = RSP_MEM_BU(offset + i, addr); + } + return out; +} + +static inline void RSP_MEM_W_STORE(uint32_t offset, uint32_t addr, uint32_t val) { + for (int i = 0; i < 4; i++) { + RSP_MEM_BU(offset + i, addr) = reinterpret_cast(&val)[i ^ 3]; + } +} + +static inline uint32_t RSP_MEM_HU_LOAD(uint32_t offset, uint32_t addr) { + uint16_t out; + for (int i = 0; i < 2; i++) { + reinterpret_cast(&out)[(i + 2) ^ 3] = RSP_MEM_BU(offset + i, addr); + } + return out; +} + +static inline uint32_t RSP_MEM_H_LOAD(uint32_t offset, uint32_t addr) { + int16_t out; + for (int i = 0; i < 2; i++) { + reinterpret_cast(&out)[(i + 2) ^ 3] = RSP_MEM_BU(offset + i, addr); + } + return out; +} + +static inline void RSP_MEM_H_STORE(uint32_t offset, uint32_t addr, uint32_t val) { + for (int i = 0; i < 2; i++) { + RSP_MEM_BU(offset + i, addr) = reinterpret_cast(&val)[(i + 2) ^ 3]; + } +} + +#define RSP_ADD32(a, b) \ + ((int32_t)((a) + (b))) + +#define RSP_SUB32(a, b) \ + ((int32_t)((a) - (b))) + +#define RSP_SIGNED(val) \ + ((int32_t)(val)) + +#define SET_DMA_DMEM(dmem_addr) dma_dmem_address = (dmem_addr) +#define SET_DMA_DRAM(dram_addr) dma_dram_address = (dram_addr) +#define DO_DMA_READ(rd_len) dma_rdram_to_dmem(rdram, dma_dmem_address, dma_dram_address, (rd_len)) +#define DO_DMA_WRITE(wr_len) dma_dmem_to_rdram(rdram, dma_dmem_address, dma_dram_address, (wr_len)) + +static inline void dma_rdram_to_dmem(uint8_t* rdram, uint32_t dmem_addr, uint32_t dram_addr, uint32_t rd_len) { + rd_len += 1; // Read length is inclusive + dram_addr &= 0xFFFFF8; + assert(dmem_addr + rd_len <= 0x1000); + for (uint32_t i = 0; i < rd_len; i++) { + RSP_MEM_B(i, dmem_addr) = MEM_B(0, (int64_t)(int32_t)(dram_addr + i + 0x80000000)); + } +} + +static inline void dma_dmem_to_rdram(uint8_t* rdram, uint32_t dmem_addr, uint32_t dram_addr, uint32_t wr_len) { + wr_len += 1; // Write length is inclusive + dram_addr &= 0xFFFFF8; + assert(dmem_addr + wr_len <= 0x1000); + for (uint32_t i = 0; i < wr_len; i++) { + MEM_B(0, (int64_t)(int32_t)(dram_addr + i + 0x80000000)) = RSP_MEM_B(i, dmem_addr); + } +} + +#endif diff --git a/librecomp/include/rsp_vu.h b/librecomp/include/rsp_vu.h new file mode 100644 index 0000000..8be1b1c --- /dev/null +++ b/librecomp/include/rsp_vu.h @@ -0,0 +1,204 @@ +// This file is modified from the Ares N64 emulator core. Ares can +// be found at https://github.com/ares-emulator/ares. The original license +// for this portion of Ares is as follows: +// ---------------------------------------------------------------------- +// ares +// +// Copyright(c) 2004 - 2021 ares team, Near et al +// +// Permission to use, copy, modify, and /or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright noticeand this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS.IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// ---------------------------------------------------------------------- +#include + +#define ARCHITECTURE_AMD64 +#define ARCHITECTURE_SUPPORTS_SSE4_1 1 + +#if defined(ARCHITECTURE_AMD64) +#include +using v128 = __m128i; +#elif defined(ARCHITECTURE_ARM64) +#include +using v128 = __m128i; +#endif + +namespace Accuracy { + namespace RSP { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + constexpr bool SISD = false; + constexpr bool SIMD = true; +#else + constexpr bool SISD = true; + constexpr bool SIMD = false; +#endif + } +} + +using u8 = uint8_t; +using s8 = int8_t; +using u16 = uint16_t; +using s16 = int16_t; +using u32 = uint32_t; +using s32 = int32_t; +using u64 = uint64_t; +using s64 = int64_t; +using uint128_t = uint64_t[2]; + +template inline auto sclamp(s64 x) -> s64 { + enum : s64 { b = 1ull << (bits - 1), m = b - 1 }; + return (x > m) ? m : (x < -b) ? -b : x; +} + +template inline auto sclip(s64 x) -> s64 { + enum : u64 { b = 1ull << (bits - 1), m = b * 2 - 1 }; + return ((x & m) ^ b) - b; +} + +struct RSP { + using r32 = uint32_t; + using cr32 = const r32; + + union r128 { + struct { uint64_t u128[2]; }; +#if ARCHITECTURE_SUPPORTS_SSE4_1 + struct { __m128i v128; }; + + operator __m128i() const { return v128; } + auto operator=(__m128i value) { v128 = value; } +#endif + + auto byte(u32 index) -> uint8_t& { return ((uint8_t*)&u128)[15 - index]; } + auto byte(u32 index) const -> uint8_t { return ((uint8_t*)&u128)[15 - index]; } + + auto element(u32 index) -> uint16_t& { return ((uint16_t*)&u128)[7 - index]; } + auto element(u32 index) const -> uint16_t { return ((uint16_t*)&u128)[7 - index]; } + + auto u8(u32 index) -> uint8_t& { return ((uint8_t*)&u128)[15 - index]; } + auto u8(u32 index) const -> uint8_t { return ((uint8_t*)&u128)[15 - index]; } + + auto s16(u32 index) -> int16_t& { return ((int16_t*)&u128)[7 - index]; } + auto s16(u32 index) const -> int16_t { return ((int16_t*)&u128)[7 - index]; } + + auto u16(u32 index) -> uint16_t& { return ((uint16_t*)&u128)[7 - index]; } + auto u16(u32 index) const -> uint16_t { return ((uint16_t*)&u128)[7 - index]; } + + //VCx registers + auto get(u32 index) const -> bool { return u16(index) != 0; } + auto set(u32 index, bool value) -> bool { return u16(index) = 0 - value, value; } + + //vu-registers.cpp + inline auto operator()(u32 index) const -> r128; + }; + using cr128 = const r128; + + struct VU { + r128 r[32]; + r128 acch, accm, accl; + r128 vcoh, vcol; //16-bit little endian + r128 vcch, vccl; //16-bit little endian + r128 vce; // 8-bit little endian + s16 divin; + s16 divout; + bool divdp; + } vpu; + + static constexpr r128 zero{0}; + static constexpr r128 invert{(uint64_t)-1, (uint64_t)-1}; + + inline auto accumulatorGet(u32 index) const -> u64; + inline auto accumulatorSet(u32 index, u64 value) -> void; + inline auto accumulatorSaturate(u32 index, bool slice, u16 negative, u16 positive) const -> u16; + + inline auto CFC2(r32& rt, u8 rd) -> void; + inline auto CTC2(cr32& rt, u8 rd) -> void; + template inline auto LBV(r128& vt, cr32& rs, s8 imm) -> void; + template inline auto LDV(r128& vt, cr32& rs, s8 imm) -> void; + template inline auto LFV(r128& vt, cr32& rs, s8 imm) -> void; + template inline auto LHV(r128& vt, cr32& rs, s8 imm) -> void; + template inline auto LLV(r128& vt, cr32& rs, s8 imm) -> void; + template inline auto LPV(r128& vt, cr32& rs, s8 imm) -> void; + template inline auto LQV(r128& vt, cr32& rs, s8 imm) -> void; + template inline auto LRV(r128& vt, cr32& rs, s8 imm) -> void; + template inline auto LSV(r128& vt, cr32& rs, s8 imm) -> void; + template inline auto LTV(u8 vt, cr32& rs, s8 imm) -> void; + template inline auto LUV(r128& vt, cr32& rs, s8 imm) -> void; + template inline auto LWV(r128& vt, cr32& rs, s8 imm) -> void; + template inline auto MFC2(r32& rt, cr128& vs) -> void; + template inline auto MTC2(cr32& rt, r128& vs) -> void; + template inline auto SBV(cr128& vt, cr32& rs, s8 imm) -> void; + template inline auto SDV(cr128& vt, cr32& rs, s8 imm) -> void; + template inline auto SFV(cr128& vt, cr32& rs, s8 imm) -> void; + template inline auto SHV(cr128& vt, cr32& rs, s8 imm) -> void; + template inline auto SLV(cr128& vt, cr32& rs, s8 imm) -> void; + template inline auto SPV(cr128& vt, cr32& rs, s8 imm) -> void; + template inline auto SQV(cr128& vt, cr32& rs, s8 imm) -> void; + template inline auto SRV(cr128& vt, cr32& rs, s8 imm) -> void; + template inline auto SSV(cr128& vt, cr32& rs, s8 imm) -> void; + template inline auto STV(u8 vt, cr32& rs, s8 imm) -> void; + template inline auto SUV(cr128& vt, cr32& rs, s8 imm) -> void; + template inline auto SWV(cr128& vt, cr32& rs, s8 imm) -> void; + template inline auto VABS(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VADD(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VADDC(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VAND(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VCH(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VCL(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VCR(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VEQ(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VGE(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VLT(r128& vd, cr128& vs, cr128& vt) -> void; + template + inline auto VMACF(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VMACF(r128& vd, cr128& vs, cr128& vt) -> void { VMACF<0, e>(vd, vs, vt); } + template inline auto VMACU(r128& vd, cr128& vs, cr128& vt) -> void { VMACF<1, e>(vd, vs, vt); } + inline auto VMACQ(r128& vd) -> void; + template inline auto VMADH(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VMADL(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VMADM(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VMADN(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VMOV(r128& vd, u8 de, cr128& vt) -> void; + template inline auto VMRG(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VMUDH(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VMUDL(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VMUDM(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VMUDN(r128& vd, cr128& vs, cr128& vt) -> void; + template + inline auto VMULF(r128& rd, cr128& vs, cr128& vt) -> void; + template inline auto VMULF(r128& rd, cr128& vs, cr128& vt) -> void { VMULF<0, e>(rd, vs, vt); } + template inline auto VMULU(r128& rd, cr128& vs, cr128& vt) -> void { VMULF<1, e>(rd, vs, vt); } + template inline auto VMULQ(r128& rd, cr128& vs, cr128& vt) -> void; + template inline auto VNAND(r128& rd, cr128& vs, cr128& vt) -> void; + template inline auto VNE(r128& vd, cr128& vs, cr128& vt) -> void; + inline auto VNOP() -> void; + template inline auto VNOR(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VNXOR(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VOR(r128& vd, cr128& vs, cr128& vt) -> void; + template + inline auto VRCP(r128& vd, u8 de, cr128& vt) -> void; + template inline auto VRCP(r128& vd, u8 de, cr128& vt) -> void { VRCP<0, e>(vd, de, vt); } + template inline auto VRCPL(r128& vd, u8 de, cr128& vt) -> void { VRCP<1, e>(vd, de, vt); } + template inline auto VRCPH(r128& vd, u8 de, cr128& vt) -> void; + template + inline auto VRND(r128& vd, u8 vs, cr128& vt) -> void; + template inline auto VRNDN(r128& vd, u8 vs, cr128& vt) -> void { VRND<0, e>(vd, vs, vt); } + template inline auto VRNDP(r128& vd, u8 vs, cr128& vt) -> void { VRND<1, e>(vd, vs, vt); } + template + inline auto VRSQ(r128& vd, u8 de, cr128& vt) -> void; + template inline auto VRSQ(r128& vd, u8 de, cr128& vt) -> void { VRSQ<0, e>(vd, de, vt); } + template inline auto VRSQL(r128& vd, u8 de, cr128& vt) -> void { VRSQ<1, e>(vd, de, vt); } + template inline auto VRSQH(r128& vd, u8 de, cr128& vt) -> void; + template inline auto VSAR(r128& vd, cr128& vs) -> void; + template inline auto VSUB(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VSUBC(r128& vd, cr128& vs, cr128& vt) -> void; + template inline auto VXOR(r128& rd, cr128& vs, cr128& vt) -> void; + template inline auto VZERO(r128& rd, cr128& vs, cr128& vt) -> void; +}; diff --git a/librecomp/include/rsp_vu_impl.h b/librecomp/include/rsp_vu_impl.h new file mode 100644 index 0000000..dbf46b2 --- /dev/null +++ b/librecomp/include/rsp_vu_impl.h @@ -0,0 +1,1537 @@ +// This file is modified from the Ares N64 emulator core. Ares can +// be found at https://github.com/ares-emulator/ares. The original license +// for this portion of Ares is as follows: +// ---------------------------------------------------------------------- +// ares +// +// Copyright(c) 2004 - 2021 ares team, Near et al +// +// Permission to use, copy, modify, and /or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright noticeand this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS.IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// ---------------------------------------------------------------------- + +#include +#include +using u32 = uint32_t; + +#define ACCH vpu.acch +#define ACCM vpu.accm +#define ACCL vpu.accl +#define VCOH vpu.vcoh +#define VCOL vpu.vcol +#define VCCH vpu.vcch +#define VCCL vpu.vccl +#define VCE vpu.vce + +#define DIVIN vpu.divin +#define DIVOUT vpu.divout +#define DIVDP vpu.divdp + +auto RSP::r128::operator()(u32 index) const -> r128 { + if constexpr (Accuracy::RSP::SISD) { + r128 v{ *this }; + switch (index) { + case 0: break; + case 1: break; + case 2: v.u16(1) = v.u16(0); v.u16(3) = v.u16(2); v.u16(5) = v.u16(4); v.u16(7) = v.u16(6); break; + case 3: v.u16(0) = v.u16(1); v.u16(2) = v.u16(3); v.u16(4) = v.u16(5); v.u16(6) = v.u16(7); break; + case 4: v.u16(1) = v.u16(2) = v.u16(3) = v.u16(0); v.u16(5) = v.u16(6) = v.u16(7) = v.u16(4); break; + case 5: v.u16(0) = v.u16(2) = v.u16(3) = v.u16(1); v.u16(4) = v.u16(6) = v.u16(7) = v.u16(5); break; + case 6: v.u16(0) = v.u16(1) = v.u16(3) = v.u16(2); v.u16(4) = v.u16(5) = v.u16(7) = v.u16(6); break; + case 7: v.u16(0) = v.u16(1) = v.u16(2) = v.u16(3); v.u16(4) = v.u16(5) = v.u16(6) = v.u16(7); break; + case 8: for (u32 n = 0; n < 8; n++) v.u16(n) = v.u16(0); break; + case 9: for (u32 n = 0; n < 8; n++) v.u16(n) = v.u16(1); break; + case 10: for (u32 n = 0; n < 8; n++) v.u16(n) = v.u16(2); break; + case 11: for (u32 n = 0; n < 8; n++) v.u16(n) = v.u16(3); break; + case 12: for (u32 n = 0; n < 8; n++) v.u16(n) = v.u16(4); break; + case 13: for (u32 n = 0; n < 8; n++) v.u16(n) = v.u16(5); break; + case 14: for (u32 n = 0; n < 8; n++) v.u16(n) = v.u16(6); break; + case 15: for (u32 n = 0; n < 8; n++) v.u16(n) = v.u16(7); break; + } + return v; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + static const __m128i shuffle[16] = { + //vector + _mm_set_epi8(15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), //01234567 + _mm_set_epi8(15,14,13,12,11,10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), //01234567 + //scalar quarter + _mm_set_epi8(15,14,15,14,11,10,11,10, 7, 6, 7, 6, 3, 2, 3, 2), //00224466 + _mm_set_epi8(13,12,13,12, 9, 8, 9, 8, 5, 4, 5, 4, 1, 0, 1, 0), //11335577 + //scalar half + _mm_set_epi8(15,14,15,14,15,14,15,14, 7, 6, 7, 6, 7, 6, 7, 6), //00004444 + _mm_set_epi8(13,12,13,12,13,12,13,12, 5, 4, 5, 4, 5, 4, 5, 4), //11115555 + _mm_set_epi8(11,10,11,10,11,10,11,10, 3, 2, 3, 2, 3, 2, 3, 2), //22226666 + _mm_set_epi8(9, 8, 9, 8, 9, 8, 9, 8, 1, 0, 1, 0, 1, 0, 1, 0), //33337777 + //scalar whole + _mm_set_epi8(15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14), //00000000 + _mm_set_epi8(13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12), //11111111 + _mm_set_epi8(11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10), //22222222 + _mm_set_epi8(9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8), //33333333 + _mm_set_epi8(7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6, 7, 6), //44444444 + _mm_set_epi8(5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4, 5, 4), //55555555 + _mm_set_epi8(3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2), //66666666 + _mm_set_epi8(1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0), //77777777 + }; + //todo: benchmark to see if testing for cases 0&1 to return value directly is faster + r128 ret; + ret.v128 = _mm_shuffle_epi8(v128, shuffle[index]); + return ret; +#endif + } +} + +auto RSP::accumulatorGet(u32 index) const -> u64 { + return (u64)ACCH.u16(index) << 32 | (u64)ACCM.u16(index) << 16 | (u64)ACCL.u16(index) << 0; +} + +auto RSP::accumulatorSet(u32 index, u64 value) -> void { + ACCH.u16(index) = value >> 32; + ACCM.u16(index) = value >> 16; + ACCL.u16(index) = value >> 0; +} + +auto RSP::accumulatorSaturate(u32 index, bool slice, u16 negative, u16 positive) const -> u16 { + if (ACCH.s16(index) < 0) { + if (ACCH.u16(index) != 0xffff) return negative; + if (ACCM.s16(index) >= 0) return negative; + } else { + if (ACCH.u16(index) != 0x0000) return positive; + if (ACCM.s16(index) < 0) return positive; + } + return !slice ? ACCL.u16(index) : ACCM.u16(index); +} + +auto RSP::CFC2(r32& rt, u8 rd) -> void { + r128 hi, lo; + switch (rd & 3) { + case 0x00: hi = VCOH; lo = VCOL; break; + case 0x01: hi = VCCH; lo = VCCL; break; + case 0x02: hi = zero; lo = VCE; break; + case 0x03: hi = zero; lo = VCE; break; //unverified + } + + if constexpr (Accuracy::RSP::SISD) { + rt = 0; + for (u32 n = 0; n < 8; n++) { + rt |= lo.get(n) << (0 + n); + rt |= hi.get(n) << (8 + n); + } + rt = s16(rt); + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + static const v128 reverse = _mm_set_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); + rt = s16(_mm_movemask_epi8(_mm_shuffle_epi8(_mm_packs_epi16(hi, lo), reverse))); +#endif + } +} + +auto RSP::CTC2(cr32& rt, u8 rd) -> void { + r128* hi; r128* lo; + r128 null; + switch (rd & 3) { + case 0x00: hi = &VCOH; lo = &VCOL; break; + case 0x01: hi = &VCCH; lo = &VCCL; break; + case 0x02: hi = &null; lo = &VCE; break; + case 0x03: hi = &null; lo = &VCE; break; //unverified + } + + if constexpr (Accuracy::RSP::SISD) { + for (u32 n = 0; n < 8; n++) { + lo->set(n, rt & 1 << (0 + n)); + hi->set(n, rt & 1 << (8 + n)); + } + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + static const v128 mask = _mm_set_epi16(0x0101, 0x0202, 0x0404, 0x0808, 0x1010, 0x2020, 0x4040, 0x8080); + lo->v128 = _mm_cmpeq_epi8(_mm_and_si128(_mm_shuffle_epi8(r128{ ~rt >> 0 }, zero), mask), zero); + hi->v128 = _mm_cmpeq_epi8(_mm_and_si128(_mm_shuffle_epi8(r128{ ~rt >> 8 }, zero), mask), zero); +#endif + } +} + +template +auto RSP::LBV(r128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm; + vt.byte(e) = RSP_MEM_B(0, address); +} + +template +auto RSP::LDV(r128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 8; + auto start = e; + auto end = std::min(start + 8, 16); + for (u32 offset = start; offset < end; offset++) { + vt.byte(offset & 15) = RSP_MEM_B(0, address++); + } +} + +template +auto RSP::LFV(r128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 16; + auto index = (address & 7) - e; + address &= ~7; + auto start = e; + auto end = std::min(start + 8, 16); + r128 tmp; + for (u32 offset = 0; offset < 4; offset++) { + tmp.element(offset + 0) = RSP_MEM_B(0, address + (index + offset * 4 + 0 & 15)) << 7; + tmp.element(offset + 4) = RSP_MEM_B(0, address + (index + offset * 4 + 8 & 15)) << 7; + } + for (u32 offset = start; offset < end; offset++) { + vt.byte(offset) = tmp.byte(offset); + } +} + +template +auto RSP::LHV(r128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 16; + auto index = (address & 7) - e; + address &= ~7; + for (u32 offset = 0; offset < 8; offset++) { + vt.element(offset) = RSP_MEM_B(0, address + (index + offset * 2 & 15)) << 7; + } +} + +template +auto RSP::LLV(r128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 4; + auto start = e; + auto end = std::min(start + 4, 16); + for (u32 offset = start; offset < end; offset++) { + vt.byte(offset & 15) = RSP_MEM_B(0, address++); + } +} + +template +auto RSP::LPV(r128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 8; + auto index = (address & 7) - e; + address &= ~7; + for (u32 offset = 0; offset < 8; offset++) { + vt.element(offset) = RSP_MEM_B(0, address + (index + offset & 15)) << 8; + } +} + +template +auto RSP::LQV(r128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 16; + auto start = e; + auto end = std::min((u32)(16 + e - (address & 15)), (u32)16); + for (u32 offset = start; offset < end; offset++) { + vt.byte(offset & 15) = RSP_MEM_B(0, address++); + } +} + +template +auto RSP::LRV(r128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 16; + auto index = e; + auto start = 16 - ((address & 15) - index); + address &= ~15; + for (u32 offset = start; offset < 16; offset++) { + vt.byte(offset & 15) = RSP_MEM_B(0, address++); + } +} + +template +auto RSP::LSV(r128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 2; + auto start = e; + auto end = std::min(start + 2, 16); + for (u32 offset = start; offset < end; offset++) { + vt.byte(offset & 15) = RSP_MEM_B(0, address++); + } +} + +template +auto RSP::LTV(u8 vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 16; + auto begin = address & ~7; + address = begin + ((e + (address & 8)) & 15); + auto vtbase = vt & ~7; + auto vtoff = e >> 1; + for (u32 i = 0; i < 8; i++) { + vpu.r[vtbase + vtoff].byte(i * 2 + 0) = RSP_MEM_B(0, address++); + if (address == begin + 16) address = begin; + vpu.r[vtbase + vtoff].byte(i * 2 + 1) = RSP_MEM_B(0, address++); + if (address == begin + 16) address = begin; + vtoff = vtoff + 1 & 7; + } +} + +template +auto RSP::LUV(r128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 8; + auto index = (address & 7) - e; + address &= ~7; + for (u32 offset = 0; offset < 8; offset++) { + vt.element(offset) = RSP_MEM_B(0, address + (index + offset & 15)) << 7; + } +} + +template +auto RSP::LWV(r128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 16; + auto start = 16 - e; + auto end = e + 16; + for (u32 offset = start; offset < end; offset++) { + vt.byte(offset & 15) = RSP_MEM_B(0, address); + address += 4; + } +} + +template +auto RSP::MFC2(r32& rt, cr128& vs) -> void { + auto hi = vs.byte(e + 0 & 15); + auto lo = vs.byte(e + 1 & 15); + rt = s16(hi << 8 | lo << 0); +} + +template +auto RSP::MTC2(cr32& rt, r128& vs) -> void { + vs.byte(e + 0) = rt >> 8; + if (e != 15) vs.byte(e + 1) = rt >> 0; +} + +template +auto RSP::SBV(cr128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm; + RSP_MEM_B(0, address) = vt.byte(e); +} + +template +auto RSP::SDV(cr128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 8; + auto start = e; + auto end = start + 8; + for (u32 offset = start; offset < end; offset++) { + RSP_MEM_B(0, address++) = vt.byte(offset & 15); + } +} + +template +auto RSP::SFV(cr128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 16; + auto base = address & 7; + address &= ~7; + switch (e) { + case 0: case 15: + RSP_MEM_B(0, address + (base + 0 & 15)) = vt.element(0) >> 7; + RSP_MEM_B(0, address + (base + 4 & 15)) = vt.element(1) >> 7; + RSP_MEM_B(0, address + (base + 8 & 15)) = vt.element(2) >> 7; + RSP_MEM_B(0, address + (base + 12 & 15)) = vt.element(3) >> 7; + break; + case 1: + RSP_MEM_B(0, address + (base + 0 & 15)) = vt.element(6) >> 7; + RSP_MEM_B(0, address + (base + 4 & 15)) = vt.element(7) >> 7; + RSP_MEM_B(0, address + (base + 8 & 15)) = vt.element(4) >> 7; + RSP_MEM_B(0, address + (base + 12 & 15)) = vt.element(5) >> 7; + break; + case 4: + RSP_MEM_B(0, address + (base + 0 & 15)) = vt.element(1) >> 7; + RSP_MEM_B(0, address + (base + 4 & 15)) = vt.element(2) >> 7; + RSP_MEM_B(0, address + (base + 8 & 15)) = vt.element(3) >> 7; + RSP_MEM_B(0, address + (base + 12 & 15)) = vt.element(0) >> 7; + break; + case 5: + RSP_MEM_B(0, address + (base + 0 & 15)) = vt.element(7) >> 7; + RSP_MEM_B(0, address + (base + 4 & 15)) = vt.element(4) >> 7; + RSP_MEM_B(0, address + (base + 8 & 15)) = vt.element(5) >> 7; + RSP_MEM_B(0, address + (base + 12 & 15)) = vt.element(6) >> 7; + break; + case 8: + RSP_MEM_B(0, address + (base + 0 & 15)) = vt.element(4) >> 7; + RSP_MEM_B(0, address + (base + 4 & 15)) = vt.element(5) >> 7; + RSP_MEM_B(0, address + (base + 8 & 15)) = vt.element(6) >> 7; + RSP_MEM_B(0, address + (base + 12 & 15)) = vt.element(7) >> 7; + break; + case 11: + RSP_MEM_B(0, address + (base + 0 & 15)) = vt.element(3) >> 7; + RSP_MEM_B(0, address + (base + 4 & 15)) = vt.element(0) >> 7; + RSP_MEM_B(0, address + (base + 8 & 15)) = vt.element(1) >> 7; + RSP_MEM_B(0, address + (base + 12 & 15)) = vt.element(2) >> 7; + break; + case 12: + RSP_MEM_B(0, address + (base + 0 & 15)) = vt.element(5) >> 7; + RSP_MEM_B(0, address + (base + 4 & 15)) = vt.element(6) >> 7; + RSP_MEM_B(0, address + (base + 8 & 15)) = vt.element(7) >> 7; + RSP_MEM_B(0, address + (base + 12 & 15)) = vt.element(4) >> 7; + break; + default: + RSP_MEM_B(0, address + (base + 0 & 15)) = 0; + RSP_MEM_B(0, address + (base + 4 & 15)) = 0; + RSP_MEM_B(0, address + (base + 8 & 15)) = 0; + RSP_MEM_B(0, address + (base + 12 & 15)) = 0; + break; + } +} + +template +auto RSP::SHV(cr128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 16; + auto index = address & 7; + address &= ~7; + for (u32 offset = 0; offset < 8; offset++) { + auto byte = e + offset * 2; + auto value = vt.byte(byte + 0 & 15) << 1 | vt.byte(byte + 1 & 15) >> 7; + RSP_MEM_B(0, address + (index + offset * 2 & 15)) = value; + } +} + +template +auto RSP::SLV(cr128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 4; + auto start = e; + auto end = start + 4; + for (u32 offset = start; offset < end; offset++) { + RSP_MEM_B(0, address++) = vt.byte(offset & 15); + } +} + +template +auto RSP::SPV(cr128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 8; + auto start = e; + auto end = start + 8; + for (u32 offset = start; offset < end; offset++) { + if ((offset & 15) < 8) { + RSP_MEM_B(0, address++) = vt.byte((offset & 7) << 1); + } else { + RSP_MEM_B(0, address++) = vt.element(offset & 7) >> 7; + } + } +} + +template +auto RSP::SQV(cr128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 16; + auto start = e; + auto end = start + (16 - (address & 15)); + for (u32 offset = start; offset < end; offset++) { + RSP_MEM_B(0, address++) = vt.byte(offset & 15); + } +} + +template +auto RSP::SRV(cr128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 16; + auto start = e; + auto end = start + (address & 15); + auto base = 16 - (address & 15); + address &= ~15; + for (u32 offset = start; offset < end; offset++) { + RSP_MEM_B(0, address++) = vt.byte(offset + base & 15); + } +} + +template +auto RSP::SSV(cr128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 2; + auto start = e; + auto end = start + 2; + for (u32 offset = start; offset < end; offset++) { + RSP_MEM_B(0, address++) = vt.byte(offset & 15); + } +} + +template +auto RSP::STV(u8 vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 16; + auto start = vt & ~7; + auto end = start + 8; + auto element = 16 - (e & ~1); + auto base = (address & 7) - (e & ~1); + address &= ~7; + for (u32 offset = start; offset < end; offset++) { + RSP_MEM_B(0, address + (base++ & 15)) = vpu.r[offset].byte(element++ & 15); + RSP_MEM_B(0, address + (base++ & 15)) = vpu.r[offset].byte(element++ & 15); + } +} + +template +auto RSP::SUV(cr128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 8; + auto start = e; + auto end = start + 8; + for (u32 offset = start; offset < end; offset++) { + if ((offset & 15) < 8) { + RSP_MEM_B(0, address++) = vt.element(offset & 7) >> 7; + } else { + RSP_MEM_B(0, address++) = vt.byte((offset & 7) << 1); + } + } +} + +template +auto RSP::SWV(cr128& vt, cr32& rs, s8 imm) -> void { + auto address = rs + imm * 16; + auto start = e; + auto end = start + 16; + auto base = address & 7; + address &= ~7; + for (u32 offset = start; offset < end; offset++) { + RSP_MEM_B(0, address + (base++ & 15)) = vt.byte(offset & 15); + } +} + +template +auto RSP::VABS(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + r128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + if (vs.s16(n) < 0) { + if (vte.s16(n) == -32768) { + ACCL.s16(n) = -32768; + vd.s16(n) = 32767; + } else { + ACCL.s16(n) = -vte.s16(n); + vd.s16(n) = -vte.s16(n); + } + } else if (vs.s16(n) > 0) { + ACCL.s16(n) = +vte.s16(n); + vd.s16(n) = +vte.s16(n); + } else { + ACCL.s16(n) = 0; + vd.s16(n) = 0; + } + } + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vs0, slt; + vs0 = _mm_cmpeq_epi16(vs, zero); + slt = _mm_srai_epi16(vs, 15); + vd = _mm_andnot_si128(vs0, vt(e)); + vd = _mm_xor_si128(vd, slt); + ACCL = _mm_sub_epi16(vd, slt); + vd = _mm_subs_epi16(vd, slt); +#endif + } +} + +template +auto RSP::VADD(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + s32 result = vs.s16(n) + vte.s16(n) + VCOL.get(n); + ACCL.s16(n) = result; + vd.s16(n) = sclamp<16>(result); + } + VCOL = zero; + VCOH = zero; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), sum, min, max; + sum = _mm_add_epi16(vs, vte); + ACCL = _mm_sub_epi16(sum, VCOL); + min = _mm_min_epi16(vs, vte); + max = _mm_max_epi16(vs, vte); + min = _mm_subs_epi16(min, VCOL); + vd = _mm_adds_epi16(min, max); + VCOL = zero; + VCOH = zero; +#endif + } +} + +template +auto RSP::VADDC(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + u32 result = vs.u16(n) + vte.u16(n); + ACCL.u16(n) = result; + VCOL.set(n, result >> 16); + } + VCOH = zero; + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), sum; + sum = _mm_adds_epu16(vs, vte); + ACCL = _mm_add_epi16(vs, vte); + VCOL = _mm_cmpeq_epi16(sum, ACCL); + VCOL = _mm_cmpeq_epi16(VCOL, zero); + VCOH = zero; + vd = ACCL; +#endif + } +} + +template +auto RSP::VAND(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + r128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + ACCL.u16(n) = vs.u16(n) & vte.u16(n); + } + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + ACCL = _mm_and_si128(vs, vt(e)); + vd = ACCL; +#endif + } +} + +template +auto RSP::VCH(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + if ((vs.s16(n) ^ vte.s16(n)) < 0) { + s16 result = vs.s16(n) + vte.s16(n); + ACCL.s16(n) = (result <= 0 ? -vte.s16(n) : vs.s16(n)); + VCCL.set(n, result <= 0); + VCCH.set(n, vte.s16(n) < 0); + VCOL.set(n, 1); + VCOH.set(n, result != 0 && vs.u16(n) != (vte.u16(n) ^ 0xffff)); + VCE.set(n, result == -1); + } else { + s16 result = vs.s16(n) - vte.s16(n); + ACCL.s16(n) = (result >= 0 ? vte.s16(n) : vs.s16(n)); + VCCL.set(n, vte.s16(n) < 0); + VCCH.set(n, result >= 0); + VCOL.set(n, 0); + VCOH.set(n, result != 0 && vs.u16(n) != (vte.u16(n) ^ 0xffff)); + VCE.set(n, 0); + } + } + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), nvt, diff, diff0, vtn, dlez, dgez, mask; + VCOL = _mm_xor_si128(vs, vte); + VCOL = _mm_cmplt_epi16(VCOL, zero); + nvt = _mm_xor_si128(vte, VCOL); + nvt = _mm_sub_epi16(nvt, VCOL); + diff = _mm_sub_epi16(vs, nvt); + diff0 = _mm_cmpeq_epi16(diff, zero); + vtn = _mm_cmplt_epi16(vte, zero); + dlez = _mm_cmpgt_epi16(diff, zero); + dgez = _mm_or_si128(dlez, diff0); + dlez = _mm_cmpeq_epi16(zero, dlez); + VCCH = _mm_blendv_epi8(dgez, vtn, VCOL); + VCCL = _mm_blendv_epi8(vtn, dlez, VCOL); + VCE = _mm_cmpeq_epi16(diff, VCOL); + VCE = _mm_and_si128(VCE, VCOL); + VCOH = _mm_or_si128(diff0, VCE); + VCOH = _mm_cmpeq_epi16(VCOH, zero); + mask = _mm_blendv_epi8(VCCH, VCCL, VCOL); + ACCL = _mm_blendv_epi8(vs, nvt, mask); + vd = ACCL; +#endif + } +} + +template +auto RSP::VCL(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + if (VCOL.get(n)) { + if (VCOH.get(n)) { + ACCL.u16(n) = VCCL.get(n) ? -vte.u16(n) : vs.u16(n); + } else { + u16 sum = vs.u16(n) + vte.u16(n); + bool carry = (vs.u16(n) + vte.u16(n)) != sum; + if (VCE.get(n)) { + ACCL.u16(n) = VCCL.set(n, (!sum || !carry)) ? -vte.u16(n) : vs.u16(n); + } else { + ACCL.u16(n) = VCCL.set(n, (!sum && !carry)) ? -vte.u16(n) : vs.u16(n); + } + } + } else { + if (VCOH.get(n)) { + ACCL.u16(n) = VCCH.get(n) ? vte.u16(n) : vs.u16(n); + } else { + ACCL.u16(n) = VCCH.set(n, (s32)vs.u16(n) - (s32)vte.u16(n) >= 0) ? vte.u16(n) : vs.u16(n); + } + } + } + VCOL = zero; + VCOH = zero; + VCE = zero; + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), nvt, diff, ncarry, nvce, diff0, lec1, lec2, leeq, geeq, le, ge, mask; + nvt = _mm_xor_si128(vte, VCOL); + nvt = _mm_sub_epi16(nvt, VCOL); + diff = _mm_sub_epi16(vs, nvt); + ncarry = _mm_adds_epu16(vs, vte); + ncarry = _mm_cmpeq_epi16(diff, ncarry); + nvce = _mm_cmpeq_epi16(VCE, zero); + diff0 = _mm_cmpeq_epi16(diff, zero); + lec1 = _mm_and_si128(diff0, ncarry); + lec1 = _mm_and_si128(nvce, lec1); + lec2 = _mm_or_si128(diff0, ncarry); + lec2 = _mm_and_si128(VCE, lec2); + leeq = _mm_or_si128(lec1, lec2); + geeq = _mm_subs_epu16(vte, vs); + geeq = _mm_cmpeq_epi16(geeq, zero); + le = _mm_andnot_si128(VCOH, VCOL); + le = _mm_blendv_epi8(VCCL, leeq, le); + ge = _mm_or_si128(VCOL, VCOH); + ge = _mm_blendv_epi8(geeq, VCCH, ge); + mask = _mm_blendv_epi8(ge, le, VCOL); + ACCL = _mm_blendv_epi8(vs, nvt, mask); + VCCH = ge; + VCCL = le; + VCOH = zero; + VCOL = zero; + VCE = zero; + vd = ACCL; +#endif + } +} + +template +auto RSP::VCR(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + if ((vs.s16(n) ^ vte.s16(n)) < 0) { + VCCH.set(n, vte.s16(n) < 0); + ACCL.u16(n) = VCCL.set(n, vs.s16(n) + vte.s16(n) + 1 <= 0) ? ~vte.u16(n) : vs.u16(n); + } else { + VCCL.set(n, vte.s16(n) < 0); + ACCL.u16(n) = VCCH.set(n, vs.s16(n) - vte.s16(n) >= 0) ? vte.u16(n) : vs.u16(n); + } + } + VCOL = zero; + VCOH = zero; + VCE = zero; + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), sign, dlez, dgez, nvt, mask; + sign = _mm_xor_si128(vs, vte); + sign = _mm_srai_epi16(sign, 15); + dlez = _mm_and_si128(vs, sign); + dlez = _mm_add_epi16(dlez, vte); + VCCL = _mm_srai_epi16(dlez, 15); + dgez = _mm_or_si128(vs, sign); + dgez = _mm_min_epi16(dgez, vte); + VCCH = _mm_cmpeq_epi16(dgez, vte); + nvt = _mm_xor_si128(vte, sign); + mask = _mm_blendv_epi8(VCCH, VCCL, sign); + ACCL = _mm_blendv_epi8(vs, nvt, mask); + vd = ACCL; + VCOL = zero; + VCOH = zero; + VCE = zero; +#endif + } +} + +template +auto RSP::VEQ(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + ACCL.u16(n) = VCCL.set(n, !VCOH.get(n) && vs.u16(n) == vte.u16(n)) ? vs.u16(n) : vte.u16(n); + } + VCCH = zero; //unverified + VCOL = zero; + VCOH = zero; + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), eq; + eq = _mm_cmpeq_epi16(vs, vte); + VCCL = _mm_andnot_si128(VCOH, eq); + ACCL = _mm_blendv_epi8(vte, vs, VCCL); + VCCH = zero; //unverified + VCOH = zero; + VCOL = zero; + vd = ACCL; +#endif + } +} + +template +auto RSP::VGE(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + ACCL.u16(n) = VCCL.set(n, vs.s16(n) > vte.s16(n) || (vs.s16(n) == vte.s16(n) && (!VCOL.get(n) || !VCOH.get(n)))) ? vs.u16(n) : vte.u16(n); + } + VCCH = zero; //unverified + VCOL = zero; + VCOH = zero; + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), eq, gt, es; + eq = _mm_cmpeq_epi16(vs, vte); + gt = _mm_cmpgt_epi16(vs, vte); + es = _mm_and_si128(VCOH, VCOL); + eq = _mm_andnot_si128(es, eq); + VCCL = _mm_or_si128(gt, eq); + ACCL = _mm_blendv_epi8(vte, vs, VCCL); + VCCH = zero; + VCOH = zero; + VCOL = zero; + vd = ACCL; +#endif + } +} + +template +auto RSP::VLT(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + ACCL.u16(n) = VCCL.set(n, vs.s16(n) < vte.s16(n) || (vs.s16(n) == vte.s16(n) && VCOL.get(n) && VCOH.get(n))) ? vs.u16(n) : vte.u16(n); + } + VCCH = zero; + VCOL = zero; + VCOH = zero; + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), eq, lt; + eq = _mm_cmpeq_epi16(vs, vte); + lt = _mm_cmplt_epi16(vs, vte); + eq = _mm_and_si128(VCOH, eq); + eq = _mm_and_si128(VCOL, eq); + VCCL = _mm_or_si128(lt, eq); + ACCL = _mm_blendv_epi8(vte, vs, VCCL); + VCCH = zero; + VCOH = zero; + VCOL = zero; + vd = ACCL; +#endif + } +} + +template +auto RSP::VMACF(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + accumulatorSet(n, accumulatorGet(n) + (s64)vs.s16(n) * (s64)vte.s16(n) * 2); + if constexpr (U == 0) { + vd.u16(n) = accumulatorSaturate(n, 1, 0x8000, 0x7fff); + } + if constexpr (U == 1) { + vd.u16(n) = ACCH.s16(n) < 0 ? 0x0000 : ACCH.s16(n) || ACCM.s16(n) < 0 ? 0xffff : ACCM.u16(n); + } + } + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), lo, md, hi, carry, omask; + lo = _mm_mullo_epi16(vs, vte); + hi = _mm_mulhi_epi16(vs, vte); + md = _mm_slli_epi16(hi, 1); + carry = _mm_srli_epi16(lo, 15); + hi = _mm_srai_epi16(hi, 15); + md = _mm_or_si128(md, carry); + lo = _mm_slli_epi16(lo, 1); + omask = _mm_adds_epu16(ACCL, lo); + ACCL = _mm_add_epi16(ACCL, lo); + omask = _mm_cmpeq_epi16(ACCL, omask); + omask = _mm_cmpeq_epi16(omask, zero); + md = _mm_sub_epi16(md, omask); + carry = _mm_cmpeq_epi16(md, zero); + carry = _mm_and_si128(carry, omask); + hi = _mm_sub_epi16(hi, carry); + omask = _mm_adds_epu16(ACCM, md); + ACCM = _mm_add_epi16(ACCM, md); + omask = _mm_cmpeq_epi16(ACCM, omask); + omask = _mm_cmpeq_epi16(omask, zero); + ACCH = _mm_add_epi16(ACCH, hi); + ACCH = _mm_sub_epi16(ACCH, omask); + if constexpr (!U) { + lo = _mm_unpacklo_epi16(ACCM, ACCH); + hi = _mm_unpackhi_epi16(ACCM, ACCH); + vd = _mm_packs_epi32(lo, hi); + } else { + r128 mmask, hmask; + mmask = _mm_srai_epi16(ACCM, 15); + hmask = _mm_srai_epi16(ACCH, 15); + md = _mm_or_si128(mmask, ACCM); + omask = _mm_cmpgt_epi16(ACCH, zero); + md = _mm_andnot_si128(hmask, md); + vd = _mm_or_si128(omask, md); + } +#endif + } +} + +auto RSP::VMACQ(r128& vd) -> void { + for (u32 n = 0; n < 8; n++) { + s32 product = ACCH.element(n) << 16 | ACCM.element(n) << 0; + if (product < 0 && !(product & 1 << 5)) product += 32; + else if (product >= 32 && !(product & 1 << 5)) product -= 32; + ACCH.element(n) = product >> 16; + ACCM.element(n) = product >> 0; + vd.element(n) = sclamp<16>(product >> 1) & ~15; + } +} + +template +auto RSP::VMADH(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + s32 result = (accumulatorGet(n) >> 16) + vs.s16(n) * vte.s16(n); + ACCH.u16(n) = result >> 16; + ACCM.u16(n) = result >> 0; + vd.u16(n) = accumulatorSaturate(n, 1, 0x8000, 0x7fff); + } + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), lo, hi, omask; + lo = _mm_mullo_epi16(vs, vte); + hi = _mm_mulhi_epi16(vs, vte); + omask = _mm_adds_epu16(ACCM, lo); + ACCM = _mm_add_epi16(ACCM, lo); + omask = _mm_cmpeq_epi16(ACCM, omask); + omask = _mm_cmpeq_epi16(omask, zero); + hi = _mm_sub_epi16(hi, omask); + ACCH = _mm_add_epi16(ACCH, hi); + lo = _mm_unpacklo_epi16(ACCM, ACCH); + hi = _mm_unpackhi_epi16(ACCM, ACCH); + vd = _mm_packs_epi32(lo, hi); +#endif + } +} + +template +auto RSP::VMADL(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + accumulatorSet(n, accumulatorGet(n) + (u32(vs.u16(n) * vte.u16(n)) >> 16)); + vd.u16(n) = accumulatorSaturate(n, 0, 0x0000, 0xffff); + } + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), hi, omask, nhi, nmd, shi, smd, cmask, cval; + hi = _mm_mulhi_epu16(vs, vte); + omask = _mm_adds_epu16(ACCL, hi); + ACCL = _mm_add_epi16(ACCL, hi); + omask = _mm_cmpeq_epi16(ACCL, omask); + omask = _mm_cmpeq_epi16(omask, zero); + hi = _mm_sub_epi16(zero, omask); + omask = _mm_adds_epu16(ACCM, hi); + ACCM = _mm_add_epi16(ACCM, hi); + omask = _mm_cmpeq_epi16(ACCM, omask); + omask = _mm_cmpeq_epi16(omask, zero); + ACCH = _mm_sub_epi16(ACCH, omask); + nhi = _mm_srai_epi16(ACCH, 15); + nmd = _mm_srai_epi16(ACCM, 15); + shi = _mm_cmpeq_epi16(nhi, ACCH); + smd = _mm_cmpeq_epi16(nhi, nmd); + cmask = _mm_and_si128(smd, shi); + cval = _mm_cmpeq_epi16(nhi, zero); + vd = _mm_blendv_epi8(cval, ACCL, cmask); +#endif + } +} + +template +auto RSP::VMADM(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + accumulatorSet(n, accumulatorGet(n) + vs.s16(n) * vte.u16(n)); + vd.u16(n) = accumulatorSaturate(n, 1, 0x8000, 0x7fff); + } + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), lo, hi, sign, vta, omask; + lo = _mm_mullo_epi16(vs, vte); + hi = _mm_mulhi_epu16(vs, vte); + sign = _mm_srai_epi16(vs, 15); + vta = _mm_and_si128(vte, sign); + hi = _mm_sub_epi16(hi, vta); + omask = _mm_adds_epu16(ACCL, lo); + ACCL = _mm_add_epi16(ACCL, lo); + omask = _mm_cmpeq_epi16(ACCL, omask); + omask = _mm_cmpeq_epi16(omask, zero); + hi = _mm_sub_epi16(hi, omask); + omask = _mm_adds_epu16(ACCM, hi); + ACCM = _mm_add_epi16(ACCM, hi); + omask = _mm_cmpeq_epi16(ACCM, omask); + omask = _mm_cmpeq_epi16(omask, zero); + hi = _mm_srai_epi16(hi, 15); + ACCH = _mm_add_epi16(ACCH, hi); + ACCH = _mm_sub_epi16(ACCH, omask); + lo = _mm_unpacklo_epi16(ACCM, ACCH); + hi = _mm_unpackhi_epi16(ACCM, ACCH); + vd = _mm_packs_epi32(lo, hi); +#endif + } +} + +template +auto RSP::VMADN(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + accumulatorSet(n, accumulatorGet(n) + s64(vs.u16(n) * vte.s16(n))); + vd.u16(n) = accumulatorSaturate(n, 0, 0x0000, 0xffff); + } + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), lo, hi, sign, vsa, omask, nhi, nmd, shi, smd, cmask, cval; + lo = _mm_mullo_epi16(vs, vte); + hi = _mm_mulhi_epu16(vs, vte); + sign = _mm_srai_epi16(vte, 15); + vsa = _mm_and_si128(vs, sign); + hi = _mm_sub_epi16(hi, vsa); + omask = _mm_adds_epu16(ACCL, lo); + ACCL = _mm_add_epi16(ACCL, lo); + omask = _mm_cmpeq_epi16(ACCL, omask); + omask = _mm_cmpeq_epi16(omask, zero); + hi = _mm_sub_epi16(hi, omask); + omask = _mm_adds_epu16(ACCM, hi); + ACCM = _mm_add_epi16(ACCM, hi); + omask = _mm_cmpeq_epi16(ACCM, omask); + omask = _mm_cmpeq_epi16(omask, zero); + hi = _mm_srai_epi16(hi, 15); + ACCH = _mm_add_epi16(ACCH, hi); + ACCH = _mm_sub_epi16(ACCH, omask); + nhi = _mm_srai_epi16(ACCH, 15); + nmd = _mm_srai_epi16(ACCM, 15); + shi = _mm_cmpeq_epi16(nhi, ACCH); + smd = _mm_cmpeq_epi16(nhi, nmd); + cmask = _mm_and_si128(smd, shi); + cval = _mm_cmpeq_epi16(nhi, zero); + vd = _mm_blendv_epi8(cval, ACCL, cmask); +#endif + } +} + +template +auto RSP::VMOV(r128& vd, u8 de, cr128& vt) -> void { + cr128 vte = vt(e); + vd.u16(de) = vte.u16(de); + ACCL = vte; +} + +template +auto RSP::VMRG(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + ACCL.u16(n) = VCCL.get(n) ? vs.u16(n) : vte.u16(n); + } + VCOH = zero; + VCOL = zero; + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + ACCL = _mm_blendv_epi8(vt(e), vs, VCCL); + VCOH = zero; + VCOL = zero; + vd = ACCL; +#endif + } +} + +template +auto RSP::VMUDH(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + accumulatorSet(n, s64(vs.s16(n) * vte.s16(n)) << 16); + vd.u16(n) = accumulatorSaturate(n, 1, 0x8000, 0x7fff); + } + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), lo, hi; + ACCL = zero; + ACCM = _mm_mullo_epi16(vs, vte); + ACCH = _mm_mulhi_epi16(vs, vte); + lo = _mm_unpacklo_epi16(ACCM, ACCH); + hi = _mm_unpackhi_epi16(ACCM, ACCH); + vd = _mm_packs_epi32(lo, hi); +#endif + } +} + +template +auto RSP::VMUDL(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + accumulatorSet(n, u16(vs.u16(n) * vte.u16(n) >> 16)); + } + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + ACCL = _mm_mulhi_epu16(vs, vt(e)); + ACCM = zero; + ACCH = zero; + vd = ACCL; +#endif + } +} + +template +auto RSP::VMUDM(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + accumulatorSet(n, s32(vs.s16(n) * vte.u16(n))); + } + vd = ACCM; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), sign, vta; + ACCL = _mm_mullo_epi16(vs, vte); + ACCM = _mm_mulhi_epu16(vs, vte); + sign = _mm_srai_epi16(vs, 15); + vta = _mm_and_si128(vte, sign); + ACCM = _mm_sub_epi16(ACCM, vta); + ACCH = _mm_srai_epi16(ACCM, 15); + vd = ACCM; +#endif + } +} + +template +auto RSP::VMUDN(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + accumulatorSet(n, s32(vs.u16(n) * vte.s16(n))); + } + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), sign, vsa; + ACCL = _mm_mullo_epi16(vs, vte); + ACCM = _mm_mulhi_epu16(vs, vte); + sign = _mm_srai_epi16(vte, 15); + vsa = _mm_and_si128(vs, sign); + ACCM = _mm_sub_epi16(ACCM, vsa); + ACCH = _mm_srai_epi16(ACCM, 15); + vd = ACCL; +#endif + } +} + +template +auto RSP::VMULF(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + accumulatorSet(n, (s64)vs.s16(n) * (s64)vte.s16(n) * 2 + 0x8000); + if constexpr (U == 0) { + vd.u16(n) = accumulatorSaturate(n, 1, 0x8000, 0x7fff); + } + if constexpr (U == 1) { + vd.u16(n) = ACCH.s16(n) < 0 ? 0x0000 : (ACCH.s16(n) ^ ACCM.s16(n)) < 0 ? 0xffff : ACCM.u16(n); + } + } + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), lo, hi, round, sign1, sign2, neq, eq, neg; + lo = _mm_mullo_epi16(vs, vte); + round = _mm_cmpeq_epi16(zero, zero); + sign1 = _mm_srli_epi16(lo, 15); + lo = _mm_add_epi16(lo, lo); + round = _mm_slli_epi16(round, 15); + hi = _mm_mulhi_epi16(vs, vte); + sign2 = _mm_srli_epi16(lo, 15); + ACCL = _mm_add_epi16(round, lo); + sign1 = _mm_add_epi16(sign1, sign2); + hi = _mm_slli_epi16(hi, 1); + neq = _mm_cmpeq_epi16(vs, vte); + ACCM = _mm_add_epi16(hi, sign1); + neg = _mm_srai_epi16(ACCM, 15); + if constexpr (!U) { + eq = _mm_and_si128(neq, neg); + ACCH = _mm_andnot_si128(neq, neg); + vd = _mm_add_epi16(ACCM, eq); + } else { + ACCH = _mm_andnot_si128(neq, neg); + hi = _mm_or_si128(ACCM, neg); + vd = _mm_andnot_si128(ACCH, hi); + } +#endif + } +} + +template +auto RSP::VMULQ(r128& vd, cr128& vs, cr128& vt) -> void { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + s32 product = (s16)vs.element(n) * (s16)vte.element(n); + if (product < 0) product += 31; //round + ACCH.element(n) = product >> 16; + ACCM.element(n) = product >> 0; + ACCL.element(n) = 0; + vd.element(n) = sclamp<16>(product >> 1) & ~15; + } +} + +template +auto RSP::VNAND(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + ACCL.u16(n) = ~(vs.u16(n) & vte.u16(n)); + } + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + ACCL = _mm_and_si128(vs, vt(e)); + ACCL = _mm_xor_si128(ACCL, invert); + vd = ACCL; +#endif + } +} + +template +auto RSP::VNE(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + ACCL.u16(n) = VCCL.set(n, vs.u16(n) != vte.u16(n) || VCOH.get(n)) ? vs.u16(n) : vte.u16(n); + } + VCCH = zero; //unverified + VCOL = zero; + VCOH = zero; + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), eq, ne; + eq = _mm_cmpeq_epi16(vs, vte); + ne = _mm_cmpeq_epi16(eq, zero); + VCCL = _mm_and_si128(VCOH, eq); + VCCL = _mm_or_si128(VCCL, ne); + ACCL = _mm_blendv_epi8(vte, vs, VCCL); + VCCH = zero; + VCOH = zero; + VCOL = zero; + vd = ACCL; +#endif + } +} + +auto RSP::VNOP() -> void { +} + +template +auto RSP::VNOR(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + ACCL.u16(n) = ~(vs.u16(n) | vte.u16(n)); + } + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + ACCL = _mm_or_si128(vs, vt(e)); + ACCL = _mm_xor_si128(ACCL, invert); + vd = ACCL; +#endif + } +} + +template +auto RSP::VNXOR(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + ACCL.u16(n) = ~(vs.u16(n) ^ vte.u16(n)); + } + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + ACCL = _mm_xor_si128(vs, vt(e)); + ACCL = _mm_xor_si128(ACCL, invert); + vd = ACCL; +#endif + } +} + +template +auto RSP::VOR(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + ACCL.u16(n) = vs.u16(n) | vte.u16(n); + } + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + ACCL = _mm_or_si128(vs, vt(e)); + vd = ACCL; +#endif + } +} + +template +auto RSP::VRCP(r128& vd, u8 de, cr128& vt) -> void { + s32 result = 0; + s32 input = L && DIVDP ? DIVIN << 16 | vt.element(e & 7) : s16(vt.element(e & 7)); + s32 mask = input >> 31; + s32 data = input ^ mask; + if (input > -32768) data -= mask; + if (data == 0) { + result = 0x7fff'ffff; + } else if (input == -32768) { + result = 0xffff'0000; + } else { + u32 shift = __builtin_clz(data); + u32 index = (u64(data) << shift & 0x7fc0'0000) >> 22; + result = rspReciprocals[index]; + result = (0x10000 | result) << 14; + result = result >> 31 - shift ^ mask; + } + DIVDP = 0; + DIVOUT = result >> 16; + ACCL = vt(e); + vd.element(de) = result; +} + +template +auto RSP::VRCPH(r128& vd, u8 de, cr128& vt) -> void { + ACCL = vt(e); + DIVDP = 1; + DIVIN = vt.element(e & 7); + vd.element(de) = DIVOUT; +} + +template +auto RSP::VRND(r128& vd, u8 vs, cr128& vt) -> void { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + s32 product = (s16)vte.element(n); + if (vs & 1) product <<= 16; + s64 acc = 0; + acc |= ACCH.element(n); acc <<= 16; + acc |= ACCM.element(n); acc <<= 16; + acc |= ACCL.element(n); acc <<= 16; + acc >>= 16; + if (D == 0 && acc < 0) acc = sclip<48>(acc + product); + if (D == 1 && acc >= 0) acc = sclip<48>(acc + product); + ACCH.element(n) = acc >> 32; + ACCM.element(n) = acc >> 16; + ACCL.element(n) = acc >> 0; + vd.element(n) = sclamp<16>(acc >> 16); + } +} + +template +auto RSP::VRSQ(r128& vd, u8 de, cr128& vt) -> void { + s32 result = 0; + s32 input = L && DIVDP ? DIVIN << 16 | vt.element(e & 7) : s16(vt.element(e & 7)); + s32 mask = input >> 31; + s32 data = input ^ mask; + if (input > -32768) data -= mask; + if (data == 0) { + result = 0x7fff'ffff; + } else if (input == -32768) { + result = 0xffff'0000; + } else { + u32 shift = __builtin_clz(data); + u32 index = (u64(data) << shift & 0x7fc0'0000) >> 22; + result = rspInverseSquareRoots[index & 0x1fe | shift & 1]; + result = (0x10000 | result) << 14; + result = result >> (31 - shift >> 1) ^ mask; + } + DIVDP = 0; + DIVOUT = result >> 16; + ACCL = vt(e); + vd.element(de) = result; +} + +template +auto RSP::VRSQH(r128& vd, u8 de, cr128& vt) -> void { + ACCL = vt(e); + DIVDP = 1; + DIVIN = vt.element(e & 7); + vd.element(de) = DIVOUT; +} + +template +auto RSP::VSAR(r128& vd, cr128& vs) -> void { + switch (e) { + case 0x8: vd = ACCH; break; + case 0x9: vd = ACCM; break; + case 0xa: vd = ACCL; break; + default: vd = zero; break; + } +} + +template +auto RSP::VSUB(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + s32 result = vs.s16(n) - vte.s16(n) - VCOL.get(n); + ACCL.s16(n) = result; + vd.s16(n) = sclamp<16>(result); + } + VCOL = zero; + VCOH = zero; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), udiff, sdiff, ov; + udiff = _mm_sub_epi16(vte, VCOL); + sdiff = _mm_subs_epi16(vte, VCOL); + ACCL = _mm_sub_epi16(vs, udiff); + ov = _mm_cmpgt_epi16(sdiff, udiff); + vd = _mm_subs_epi16(vs, sdiff); + vd = _mm_adds_epi16(vd, ov); + VCOL = zero; + VCOH = zero; +#endif + } +} + +template +auto RSP::VSUBC(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + u32 result = vs.u16(n) - vte.u16(n); + ACCL.u16(n) = result; + VCOL.set(n, result >> 16); + VCOH.set(n, result != 0); + } + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), equal, udiff, diff0; + udiff = _mm_subs_epu16(vs, vte); + equal = _mm_cmpeq_epi16(vs, vte); + diff0 = _mm_cmpeq_epi16(udiff, zero); + VCOH = _mm_cmpeq_epi16(equal, zero); + VCOL = _mm_andnot_si128(equal, diff0); + ACCL = _mm_sub_epi16(vs, vte); + vd = ACCL; +#endif + } +} + +template +auto RSP::VXOR(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + ACCL.u16(n) = vs.u16(n) ^ vte.u16(n); + } + vd = ACCL; + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + ACCL = _mm_xor_si128(vs, vt(e)); + vd = ACCL; +#endif + } +} + +template +auto RSP::VZERO(r128& vd, cr128& vs, cr128& vt) -> void { + if constexpr (Accuracy::RSP::SISD) { + cr128 vte = vt(e); + for (u32 n = 0; n < 8; n++) { + s32 result = vs.s16(n) + vte.s16(n); + ACCL.s16(n) = result; + vd.s16(n) = 0; + } + } + + if constexpr (Accuracy::RSP::SIMD) { +#if ARCHITECTURE_SUPPORTS_SSE4_1 + r128 vte = vt(e), sum, min, max; + ACCL = _mm_add_epi16(vs, vte); + vd = _mm_xor_si128(vd, vd); +#endif + } +} + +#undef ACCH +#undef ACCM +#undef ACCL +#undef VCOH +#undef VCOL +#undef VCCH +#undef VCCL +#undef VCE + +#undef DIVIN +#undef DIVOUT +#undef DIVDP diff --git a/librecomp/include/sections.h b/librecomp/include/sections.h new file mode 100644 index 0000000..1d1b228 --- /dev/null +++ b/librecomp/include/sections.h @@ -0,0 +1,23 @@ +#ifndef __SECTIONS_H__ +#define __SECTIONS_H__ + +#include +#include "recomp.h" + +#define ARRLEN(x) (sizeof(x) / sizeof((x)[0])) + +typedef struct { + recomp_func_t* func; + uint32_t offset; +} FuncEntry; + +typedef struct { + uint32_t rom_addr; + uint32_t ram_addr; + uint32_t size; + FuncEntry *funcs; + size_t num_funcs; + size_t index; +} SectionTableEntry; + +#endif diff --git a/librecomp/src/ai.cpp b/librecomp/src/ai.cpp new file mode 100644 index 0000000..44dbea2 --- /dev/null +++ b/librecomp/src/ai.cpp @@ -0,0 +1,29 @@ +#include "recomp.h" +#include +#include +#include "../ultramodern/ultra64.h" +#include "../ultramodern/ultramodern.hpp" + +#define VI_NTSC_CLOCK 48681812 + +extern "C" void osAiSetFrequency_recomp(uint8_t* rdram, recomp_context* ctx) { + uint32_t freq = ctx->r4; + // This makes actual audio frequency more accurate to console, but may not be desirable + //uint32_t dacRate = (uint32_t)(((float)VI_NTSC_CLOCK / freq) + 0.5f); + //freq = VI_NTSC_CLOCK / dacRate; + ctx->r2 = freq; + ultramodern::set_audio_frequency(freq); +} + +extern "C" void osAiSetNextBuffer_recomp(uint8_t* rdram, recomp_context* ctx) { + ultramodern::queue_audio_buffer(rdram, ctx->r4, ctx->r5); + ctx->r2 = 0; +} + +extern "C" void osAiGetLength_recomp(uint8_t* rdram, recomp_context* ctx) { + ctx->r2 = ultramodern::get_remaining_audio_bytes(); +} + +extern "C" void osAiGetStatus_recomp(uint8_t* rdram, recomp_context* ctx) { + ctx->r2 = 0x00000000; // Pretend the audio DMAs finish instantly +} diff --git a/librecomp/src/cont.cpp b/librecomp/src/cont.cpp new file mode 100644 index 0000000..42492c3 --- /dev/null +++ b/librecomp/src/cont.cpp @@ -0,0 +1,155 @@ +#include "../ultramodern/ultramodern.hpp" +#include "recomp_helpers.h" + +static ultramodern::input_callbacks_t input_callbacks; + +std::chrono::high_resolution_clock::time_point input_poll_time; + +void update_poll_time() { + input_poll_time = std::chrono::high_resolution_clock::now(); +} + +extern "C" void recomp_set_current_frame_poll_id(uint8_t* rdram, recomp_context* ctx) { + // TODO reimplement the system for tagging polls with IDs to handle games with multithreaded input polling. +} + +extern "C" void recomp_measure_latency(uint8_t* rdram, recomp_context* ctx) { + ultramodern::measure_input_latency(); +} + +void ultramodern::measure_input_latency() { + // printf("Delta: %ld micros\n", std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - input_poll_time)); +} + +void set_input_callbacks(const ultramodern::input_callbacks_t& callbacks) { + input_callbacks = callbacks; +} + +static int max_controllers = 0; + +extern "C" void osContInit_recomp(uint8_t* rdram, recomp_context* ctx) { + PTR(void) bitpattern = _arg<1, PTR(void)>(rdram, ctx); + PTR(void) status = _arg<2, PTR(void)>(rdram, ctx); + + // Set bit 0 to indicate that controller 0 is present + MEM_B(0, bitpattern) = 0x01; + + // Mark controller 0 as present + MEM_H(0, status) = 0x0005; // type: CONT_TYPE_NORMAL (from joybus) + MEM_B(2, status) = 0x00; // status: 0 (from joybus) + MEM_B(3, status) = 0x00; // errno: 0 (from libultra) + + max_controllers = 4; + + // Mark controllers 1-3 as not connected + for (size_t controller = 1; controller < max_controllers; controller++) { + // Libultra doesn't write status or type for absent controllers + MEM_B(4 * controller + 3, status) = 0x80 >> 4; // errno: CONT_NO_RESPONSE_ERROR >> 4 + } + + _return(ctx, 0); +} + +extern "C" void osContStartReadData_recomp(uint8_t* rdram, recomp_context* ctx) { + if (input_callbacks.poll_input) { + input_callbacks.poll_input(); + } + update_poll_time(); + + ultramodern::send_si_message(rdram); +} + +extern "C" void osContGetReadData_recomp(uint8_t* rdram, recomp_context* ctx) { + PTR(void) pad = _arg<0, PTR(void)>(rdram, ctx); + + uint16_t buttons = 0; + float x = 0.0f; + float y = 0.0f; + + if (input_callbacks.get_input) { + input_callbacks.get_input(&buttons, &x, &y); + } + + if (max_controllers > 0) { + // button + MEM_H(0, pad) = buttons; + // stick_x + MEM_B(2, pad) = (int8_t)(127 * x); + // stick_y + MEM_B(3, pad) = (int8_t)(127 * y); + // errno + MEM_B(4, pad) = 0; + } + for (int controller = 1; controller < max_controllers; controller++) { + MEM_B(6 * controller + 4, pad) = 0x80 >> 4; // errno: CONT_NO_RESPONSE_ERROR >> 4 + } +} + +extern "C" void osContStartQuery_recomp(uint8_t * rdram, recomp_context * ctx) { + ultramodern::send_si_message(rdram); +} + +extern "C" void osContGetQuery_recomp(uint8_t * rdram, recomp_context * ctx) { + PTR(void) status = _arg<0, PTR(void)>(rdram, ctx); + + // Mark controller 0 as present + MEM_H(0, status) = 0x0005; // type: CONT_TYPE_NORMAL (from joybus) + MEM_B(2, status) = 0x01; // status: 0x01 (from joybus, indicates that a pak is plugged into the controller) + MEM_B(3, status) = 0x00; // errno: 0 (from libultra) + + // Mark controllers 1-3 as not connected + for (size_t controller = 1; controller < max_controllers; controller++) { + // Libultra doesn't write status or type for absent controllers + MEM_B(4 * controller + 3, status) = 0x80 >> 4; // errno: CONT_NO_RESPONSE_ERROR >> 4 + } +} + +extern "C" void osContSetCh_recomp(uint8_t* rdram, recomp_context* ctx) { + max_controllers = std::min(_arg<0, u8>(rdram, ctx), u8(4)); + _return(ctx, 0); +} + +extern "C" void __osMotorAccess_recomp(uint8_t* rdram, recomp_context* ctx) { + PTR(void) pfs = _arg<0, PTR(void)>(rdram, ctx); + s32 flag = _arg<1, s32>(rdram, ctx); + s32 channel = MEM_W(8, pfs); + + // Only respect accesses to controller 0. + if (channel == 0) { + input_callbacks.set_rumble(flag); + } + + _return(ctx, 0); +} + +extern "C" void osMotorInit_recomp(uint8_t* rdram, recomp_context* ctx) { + PTR(void) pfs = _arg<1, PTR(void)>(rdram, ctx); + s32 channel = _arg<2, s32>(rdram, ctx); + MEM_W(8, pfs) = channel; + + _return(ctx, 0); +} + +extern "C" void osMotorStart_recomp(uint8_t* rdram, recomp_context* ctx) { + PTR(void) pfs = _arg<0, PTR(void)>(rdram, ctx); + s32 channel = MEM_W(8, pfs); + + // Only respect accesses to controller 0. + if (channel == 0) { + input_callbacks.set_rumble(true); + } + + _return(ctx, 0); +} + +extern "C" void osMotorStop_recomp(uint8_t* rdram, recomp_context* ctx) { + PTR(void) pfs = _arg<0, PTR(void)>(rdram, ctx); + s32 channel = MEM_W(8, pfs); + + // Only respect accesses to controller 0. + if (channel == 0) { + input_callbacks.set_rumble(false); + } + + _return(ctx, 0); +} diff --git a/librecomp/src/dp.cpp b/librecomp/src/dp.cpp new file mode 100644 index 0000000..3ad5327 --- /dev/null +++ b/librecomp/src/dp.cpp @@ -0,0 +1,44 @@ +#include "recomp.h" + +enum class RDPStatusBit { + XbusDmem = 0, + Freeze = 1, + Flush = 2, + CommandBusy = 6, + BufferReady = 7, + DmaBusy = 8, + EndValid = 9, + StartValid = 10, +}; + +constexpr void update_bit(uint32_t& state, uint32_t flags, RDPStatusBit bit) { + int set_bit_pos = (int)bit * 2 + 0; + int reset_bit_pos = (int)bit * 2 + 1; + bool set = (flags & (1U << set_bit_pos)) != 0; + bool reset = (flags & (1U << reset_bit_pos)) != 0; + + if (set ^ reset) { + if (set) { + state |= (1U << (int)bit); + } + else { + state &= ~(1U << (int)bit); + } + } +} + +uint32_t rdp_state = 1 << (int)RDPStatusBit::BufferReady; + +extern "C" void osDpSetNextBuffer_recomp(uint8_t* rdram, recomp_context* ctx) { + assert(false); +} + +extern "C" void osDpGetStatus_recomp(uint8_t* rdram, recomp_context* ctx) { + ctx->r2 = rdp_state; +} + +extern "C" void osDpSetStatus_recomp(uint8_t* rdram, recomp_context* ctx) { + update_bit(rdp_state, ctx->r4, RDPStatusBit::XbusDmem); + update_bit(rdp_state, ctx->r4, RDPStatusBit::Freeze); + update_bit(rdp_state, ctx->r4, RDPStatusBit::Flush); +} diff --git a/librecomp/src/eep.cpp b/librecomp/src/eep.cpp new file mode 100644 index 0000000..ddc612d --- /dev/null +++ b/librecomp/src/eep.cpp @@ -0,0 +1,49 @@ +#include "recomp.h" +#include "../ultramodern/ultra64.h" + +void save_write(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count); +void save_read(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count); + +constexpr int eeprom_block_size = 8; +constexpr int eep4_size = 4096; +constexpr int eep4_block_count = eep4_size / eeprom_block_size; +constexpr int eep16_size = 16384; +constexpr int eep16_block_count = eep16_size / eeprom_block_size; + +extern "C" void osEepromProbe_recomp(uint8_t* rdram, recomp_context* ctx) { + ctx->r2 = 0x02; // EEP16K +} + +extern "C" void osEepromWrite_recomp(uint8_t* rdram, recomp_context* ctx) { + assert(false);// ctx->r2 = 8; // CONT_NO_RESPONSE_ERROR +} + +extern "C" void osEepromLongWrite_recomp(uint8_t* rdram, recomp_context* ctx) { + uint8_t eep_address = ctx->r5; + gpr buffer = ctx->r6; + int32_t nbytes = ctx->r7; + + assert(!(nbytes & 7)); + assert(eep_address * eeprom_block_size + nbytes <= eep16_size); + + save_write(rdram, buffer, eep_address * eeprom_block_size, nbytes); + + ctx->r2 = 0; +} + +extern "C" void osEepromRead_recomp(uint8_t* rdram, recomp_context* ctx) { + assert(false);// ctx->r2 = 8; // CONT_NO_RESPONSE_ERROR +} + +extern "C" void osEepromLongRead_recomp(uint8_t* rdram, recomp_context* ctx) { + uint8_t eep_address = ctx->r5; + gpr buffer = ctx->r6; + int32_t nbytes = ctx->r7; + + assert(!(nbytes & 7)); + assert(eep_address * eeprom_block_size + nbytes <= eep16_size); + + save_read(rdram, buffer, eep_address * eeprom_block_size, nbytes); + + ctx->r2 = 0; +} diff --git a/librecomp/src/euc-jp.cpp b/librecomp/src/euc-jp.cpp new file mode 100644 index 0000000..dbeb2fe --- /dev/null +++ b/librecomp/src/euc-jp.cpp @@ -0,0 +1,2587 @@ +// Adapted from https://github.com/odashi/encoding +// Original license as follows: + +// MIT License +// +// Copyright (c) 2017 Yusuke Oda +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#define _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING +#include +#include +#include +#include +#include "euc-jp.h" + +namespace Encoding { + // JIS X 0201 to Unicode + const int jisx0201_2_unicode[] = { + // ASCII Compatible + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, + // Shift_JIS First Byte + 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, + 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, + 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, + 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, + // Halfwidth Katakana + 0xfffd, 0xff61, 0xff62, 0xff63, 0xff64, 0xff65, 0xff66, 0xff67, + 0xff68, 0xff69, 0xff6a, 0xff6b, 0xff6c, 0xff6d, 0xff6e, 0xff6f, + 0xff70, 0xff71, 0xff72, 0xff73, 0xff74, 0xff75, 0xff76, 0xff77, + 0xff78, 0xff79, 0xff7a, 0xff7b, 0xff7c, 0xff7d, 0xff7e, 0xff7f, + 0xff80, 0xff81, 0xff82, 0xff83, 0xff84, 0xff85, 0xff86, 0xff87, + 0xff88, 0xff89, 0xff8a, 0xff8b, 0xff8c, 0xff8d, 0xff8e, 0xff8f, + 0xff90, 0xff91, 0xff92, 0xff93, 0xff94, 0xff95, 0xff96, 0xff97, + 0xff98, 0xff99, 0xff9a, 0xff9b, 0xff9c, 0xff9d, 0xff9e, 0xff9f, + // Shift_JIS First Byte + 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, + 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, + 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, + 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd, 0xfffd + }; + + // JIS X 0213 to Unicode + const int jisx0213_2_unicode[] = { + /* This table is converted by "JIS X 0213:2004 8-bit code vs Unicode mapping table" from x0213.org. + * License: + * Copyright (C) 2001 earthian@tama.or.jp, All Rights Reserved. + * Copyright (C) 2001 I'O, All Rights Reserved. + * Copyright (C) 2009 Project X0213, All Rights Reserved. + * You can use, modify, distribute this table freely. + */ + // plane 1 row 1 + 0x003000, 0x003001, 0x003002, 0x00002c, 0x00002e, 0x0030fb, 0x00003a, 0x00003b, + 0x00003f, 0x000021, 0x00309b, 0x00309c, 0x0000b4, 0x000060, 0x0000a8, 0x00005e, + 0x00203e, 0x00005f, 0x0030fd, 0x0030fe, 0x00309d, 0x00309e, 0x003003, 0x004edd, + 0x003005, 0x003006, 0x003007, 0x0030fc, 0x002014, 0x002010, 0x00002f, 0x00005c, + 0x00301c, 0x002016, 0x00007c, 0x002026, 0x002025, 0x002018, 0x002019, 0x00201c, + 0x00201d, 0x000028, 0x000029, 0x003014, 0x003015, 0x00005b, 0x00005d, 0x00007b, + 0x00007d, 0x003008, 0x003009, 0x00300a, 0x00300b, 0x00300c, 0x00300d, 0x00300e, + 0x00300f, 0x003010, 0x003011, 0x00002b, 0x002212, 0x0000b1, 0x0000d7, 0x0000f7, + 0x00003d, 0x002260, 0x00003c, 0x00003e, 0x002266, 0x002267, 0x00221e, 0x002234, + 0x002642, 0x002640, 0x0000b0, 0x002032, 0x002033, 0x002103, 0x0000a5, 0x000024, + 0x0000a2, 0x0000a3, 0x000025, 0x000023, 0x000026, 0x00002a, 0x000040, 0x0000a7, + 0x002606, 0x002605, 0x0025cb, 0x0025cf, 0x0025ce, 0x0025c7, + // plane 1 row 2 + 0x0025c6, 0x0025a1, 0x0025a0, 0x0025b3, 0x0025b2, 0x0025bd, 0x0025bc, 0x00203b, + 0x003012, 0x002192, 0x002190, 0x002191, 0x002193, 0x003013, 0x000027, 0x000022, + 0x00002d, 0x00007e, 0x003033, 0x003034, 0x003035, 0x00303b, 0x00303c, 0x0030ff, + 0x00309f, 0x002208, 0x00220b, 0x002286, 0x002287, 0x002282, 0x002283, 0x00222a, + 0x002229, 0x002284, 0x002285, 0x00228a, 0x00228b, 0x002209, 0x002205, 0x002305, + 0x002306, 0x002227, 0x002228, 0x0000ac, 0x0021d2, 0x0021d4, 0x002200, 0x002203, + 0x002295, 0x002296, 0x002297, 0x002225, 0x002226, 0x00ff5f, 0x00ff60, 0x003018, + 0x003019, 0x003016, 0x003017, 0x002220, 0x0022a5, 0x002312, 0x002202, 0x002207, + 0x002261, 0x002252, 0x00226a, 0x00226b, 0x00221a, 0x00223d, 0x00221d, 0x002235, + 0x00222b, 0x00222c, 0x002262, 0x002243, 0x002245, 0x002248, 0x002276, 0x002277, + 0x002194, 0x00212b, 0x002030, 0x00266f, 0x00266d, 0x00266a, 0x002020, 0x002021, + 0x0000b6, 0x00266e, 0x00266b, 0x00266c, 0x002669, 0x0025ef, + // plane 1 row 3 + 0x0025b7, 0x0025b6, 0x0025c1, 0x0025c0, 0x002197, 0x002198, 0x002196, 0x002199, + 0x0021c4, 0x0021e8, 0x0021e6, 0x0021e7, 0x0021e9, 0x002934, 0x002935, 0x000030, + 0x000031, 0x000032, 0x000033, 0x000034, 0x000035, 0x000036, 0x000037, 0x000038, + 0x000039, 0x0029bf, 0x0025c9, 0x00303d, 0x00fe46, 0x00fe45, 0x0025e6, 0x002022, + 0x000041, 0x000042, 0x000043, 0x000044, 0x000045, 0x000046, 0x000047, 0x000048, + 0x000049, 0x00004a, 0x00004b, 0x00004c, 0x00004d, 0x00004e, 0x00004f, 0x000050, + 0x000051, 0x000052, 0x000053, 0x000054, 0x000055, 0x000056, 0x000057, 0x000058, + 0x000059, 0x00005a, 0x002213, 0x002135, 0x00210f, 0x0033cb, 0x002113, 0x002127, + 0x000061, 0x000062, 0x000063, 0x000064, 0x000065, 0x000066, 0x000067, 0x000068, + 0x000069, 0x00006a, 0x00006b, 0x00006c, 0x00006d, 0x00006e, 0x00006f, 0x000070, + 0x000071, 0x000072, 0x000073, 0x000074, 0x000075, 0x000076, 0x000077, 0x000078, + 0x000079, 0x00007a, 0x0030a0, 0x002013, 0x0029fa, 0x0029fb, + // plane 1 row 4 + 0x003041, 0x003042, 0x003043, 0x003044, 0x003045, 0x003046, 0x003047, 0x003048, + 0x003049, 0x00304a, 0x00304b, 0x00304c, 0x00304d, 0x00304e, 0x00304f, 0x003050, + 0x003051, 0x003052, 0x003053, 0x003054, 0x003055, 0x003056, 0x003057, 0x003058, + 0x003059, 0x00305a, 0x00305b, 0x00305c, 0x00305d, 0x00305e, 0x00305f, 0x003060, + 0x003061, 0x003062, 0x003063, 0x003064, 0x003065, 0x003066, 0x003067, 0x003068, + 0x003069, 0x00306a, 0x00306b, 0x00306c, 0x00306d, 0x00306e, 0x00306f, 0x003070, + 0x003071, 0x003072, 0x003073, 0x003074, 0x003075, 0x003076, 0x003077, 0x003078, + 0x003079, 0x00307a, 0x00307b, 0x00307c, 0x00307d, 0x00307e, 0x00307f, 0x003080, + 0x003081, 0x003082, 0x003083, 0x003084, 0x003085, 0x003086, 0x003087, 0x003088, + 0x003089, 0x00308a, 0x00308b, 0x00308c, 0x00308d, 0x00308e, 0x00308f, 0x003090, + 0x003091, 0x003092, 0x003093, 0x003094, 0x003095, 0x003096, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 1 row 5 + 0x0030a1, 0x0030a2, 0x0030a3, 0x0030a4, 0x0030a5, 0x0030a6, 0x0030a7, 0x0030a8, + 0x0030a9, 0x0030aa, 0x0030ab, 0x0030ac, 0x0030ad, 0x0030ae, 0x0030af, 0x0030b0, + 0x0030b1, 0x0030b2, 0x0030b3, 0x0030b4, 0x0030b5, 0x0030b6, 0x0030b7, 0x0030b8, + 0x0030b9, 0x0030ba, 0x0030bb, 0x0030bc, 0x0030bd, 0x0030be, 0x0030bf, 0x0030c0, + 0x0030c1, 0x0030c2, 0x0030c3, 0x0030c4, 0x0030c5, 0x0030c6, 0x0030c7, 0x0030c8, + 0x0030c9, 0x0030ca, 0x0030cb, 0x0030cc, 0x0030cd, 0x0030ce, 0x0030cf, 0x0030d0, + 0x0030d1, 0x0030d2, 0x0030d3, 0x0030d4, 0x0030d5, 0x0030d6, 0x0030d7, 0x0030d8, + 0x0030d9, 0x0030da, 0x0030db, 0x0030dc, 0x0030dd, 0x0030de, 0x0030df, 0x0030e0, + 0x0030e1, 0x0030e2, 0x0030e3, 0x0030e4, 0x0030e5, 0x0030e6, 0x0030e7, 0x0030e8, + 0x0030e9, 0x0030ea, 0x0030eb, 0x0030ec, 0x0030ed, 0x0030ee, 0x0030ef, 0x0030f0, + 0x0030f1, 0x0030f2, 0x0030f3, 0x0030f4, 0x0030f5, 0x0030f6, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 1 row 6 + 0x000391, 0x000392, 0x000393, 0x000394, 0x000395, 0x000396, 0x000397, 0x000398, + 0x000399, 0x00039a, 0x00039b, 0x00039c, 0x00039d, 0x00039e, 0x00039f, 0x0003a0, + 0x0003a1, 0x0003a3, 0x0003a4, 0x0003a5, 0x0003a6, 0x0003a7, 0x0003a8, 0x0003a9, + 0x002664, 0x002660, 0x002662, 0x002666, 0x002661, 0x002665, 0x002667, 0x002663, + 0x0003b1, 0x0003b2, 0x0003b3, 0x0003b4, 0x0003b5, 0x0003b6, 0x0003b7, 0x0003b8, + 0x0003b9, 0x0003ba, 0x0003bb, 0x0003bc, 0x0003bd, 0x0003be, 0x0003bf, 0x0003c0, + 0x0003c1, 0x0003c3, 0x0003c4, 0x0003c5, 0x0003c6, 0x0003c7, 0x0003c8, 0x0003c9, + 0x0003c2, 0x0024f5, 0x0024f6, 0x0024f7, 0x0024f8, 0x0024f9, 0x0024fa, 0x0024fb, + 0x0024fc, 0x0024fd, 0x0024fe, 0x002616, 0x002617, 0x003020, 0x00260e, 0x002600, + 0x002601, 0x002602, 0x002603, 0x002668, 0x0025b1, 0x0031f0, 0x0031f1, 0x0031f2, + 0x0031f3, 0x0031f4, 0x0031f5, 0x0031f6, 0x0031f7, 0x0031f8, 0x0031f9, 0x00fffd, + 0x0031fa, 0x0031fb, 0x0031fc, 0x0031fd, 0x0031fe, 0x0031ff, + // plane 1 row 7 + 0x000410, 0x000411, 0x000412, 0x000413, 0x000414, 0x000415, 0x000401, 0x000416, + 0x000417, 0x000418, 0x000419, 0x00041a, 0x00041b, 0x00041c, 0x00041d, 0x00041e, + 0x00041f, 0x000420, 0x000421, 0x000422, 0x000423, 0x000424, 0x000425, 0x000426, + 0x000427, 0x000428, 0x000429, 0x00042a, 0x00042b, 0x00042c, 0x00042d, 0x00042e, + 0x00042f, 0x0023be, 0x0023bf, 0x0023c0, 0x0023c1, 0x0023c2, 0x0023c3, 0x0023c4, + 0x0023c5, 0x0023c6, 0x0023c7, 0x0023c8, 0x0023c9, 0x0023ca, 0x0023cb, 0x0023cc, + 0x000430, 0x000431, 0x000432, 0x000433, 0x000434, 0x000435, 0x000451, 0x000436, + 0x000437, 0x000438, 0x000439, 0x00043a, 0x00043b, 0x00043c, 0x00043d, 0x00043e, + 0x00043f, 0x000440, 0x000441, 0x000442, 0x000443, 0x000444, 0x000445, 0x000446, + 0x000447, 0x000448, 0x000449, 0x00044a, 0x00044b, 0x00044c, 0x00044d, 0x00044e, + 0x00044f, 0x0030f7, 0x0030f8, 0x0030f9, 0x0030fa, 0x0022da, 0x0022db, 0x002153, + 0x002154, 0x002155, 0x002713, 0x002318, 0x002423, 0x0023ce, + // plane 1 row 8 + 0x002500, 0x002502, 0x00250c, 0x002510, 0x002518, 0x002514, 0x00251c, 0x00252c, + 0x002524, 0x002534, 0x00253c, 0x002501, 0x002503, 0x00250f, 0x002513, 0x00251b, + 0x002517, 0x002523, 0x002533, 0x00252b, 0x00253b, 0x00254b, 0x002520, 0x00252f, + 0x002528, 0x002537, 0x00253f, 0x00251d, 0x002530, 0x002525, 0x002538, 0x002542, + 0x003251, 0x003252, 0x003253, 0x003254, 0x003255, 0x003256, 0x003257, 0x003258, + 0x003259, 0x00325a, 0x00325b, 0x00325c, 0x00325d, 0x00325e, 0x00325f, 0x0032b1, + 0x0032b2, 0x0032b3, 0x0032b4, 0x0032b5, 0x0032b6, 0x0032b7, 0x0032b8, 0x0032b9, + 0x0032ba, 0x0032bb, 0x0032bc, 0x0032bd, 0x0032be, 0x0032bf, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x0025d0, 0x0025d1, + 0x0025d2, 0x0025d3, 0x00203c, 0x002047, 0x002048, 0x002049, 0x0001cd, 0x0001ce, + 0x0001d0, 0x001e3e, 0x001e3f, 0x0001f8, 0x0001f9, 0x0001d1, 0x0001d2, 0x0001d4, + 0x0001d6, 0x0001d8, 0x0001da, 0x0001dc, 0x00fffd, 0x00fffd, + // plane 1 row 9 + 0x0020ac, 0x0000a0, 0x0000a1, 0x0000a4, 0x0000a6, 0x0000a9, 0x0000aa, 0x0000ab, + 0x0000ad, 0x0000ae, 0x0000af, 0x0000b2, 0x0000b3, 0x0000b7, 0x0000b8, 0x0000b9, + 0x0000ba, 0x0000bb, 0x0000bc, 0x0000bd, 0x0000be, 0x0000bf, 0x0000c0, 0x0000c1, + 0x0000c2, 0x0000c3, 0x0000c4, 0x0000c5, 0x0000c6, 0x0000c7, 0x0000c8, 0x0000c9, + 0x0000ca, 0x0000cb, 0x0000cc, 0x0000cd, 0x0000ce, 0x0000cf, 0x0000d0, 0x0000d1, + 0x0000d2, 0x0000d3, 0x0000d4, 0x0000d5, 0x0000d6, 0x0000d8, 0x0000d9, 0x0000da, + 0x0000db, 0x0000dc, 0x0000dd, 0x0000de, 0x0000df, 0x0000e0, 0x0000e1, 0x0000e2, + 0x0000e3, 0x0000e4, 0x0000e5, 0x0000e6, 0x0000e7, 0x0000e8, 0x0000e9, 0x0000ea, + 0x0000eb, 0x0000ec, 0x0000ed, 0x0000ee, 0x0000ef, 0x0000f0, 0x0000f1, 0x0000f2, + 0x0000f3, 0x0000f4, 0x0000f5, 0x0000f6, 0x0000f8, 0x0000f9, 0x0000fa, 0x0000fb, + 0x0000fc, 0x0000fd, 0x0000fe, 0x0000ff, 0x000100, 0x00012a, 0x00016a, 0x000112, + 0x00014c, 0x000101, 0x00012b, 0x00016b, 0x000113, 0x00014d, + // plane 1 row 10 + 0x000104, 0x0002d8, 0x000141, 0x00013d, 0x00015a, 0x000160, 0x00015e, 0x000164, + 0x000179, 0x00017d, 0x00017b, 0x000105, 0x0002db, 0x000142, 0x00013e, 0x00015b, + 0x0002c7, 0x000161, 0x00015f, 0x000165, 0x00017a, 0x0002dd, 0x00017e, 0x00017c, + 0x000154, 0x000102, 0x000139, 0x000106, 0x00010c, 0x000118, 0x00011a, 0x00010e, + 0x000143, 0x000147, 0x000150, 0x000158, 0x00016e, 0x000170, 0x000162, 0x000155, + 0x000103, 0x00013a, 0x000107, 0x00010d, 0x000119, 0x00011b, 0x00010f, 0x000111, + 0x000144, 0x000148, 0x000151, 0x000159, 0x00016f, 0x000171, 0x000163, 0x0002d9, + 0x000108, 0x00011c, 0x000124, 0x000134, 0x00015c, 0x00016c, 0x000109, 0x00011d, + 0x000125, 0x000135, 0x00015d, 0x00016d, 0x000271, 0x00028b, 0x00027e, 0x000283, + 0x000292, 0x00026c, 0x00026e, 0x000279, 0x000288, 0x000256, 0x000273, 0x00027d, + 0x000282, 0x000290, 0x00027b, 0x00026d, 0x00025f, 0x000272, 0x00029d, 0x00028e, + 0x000261, 0x00014b, 0x000270, 0x000281, 0x000127, 0x000295, + // plane 1 row 11 + 0x000294, 0x000266, 0x000298, 0x0001c2, 0x000253, 0x000257, 0x000284, 0x000260, + 0x000193, 0x000153, 0x000152, 0x000268, 0x000289, 0x000258, 0x000275, 0x000259, + 0x00025c, 0x00025e, 0x000250, 0x00026f, 0x00028a, 0x000264, 0x00028c, 0x000254, + 0x000251, 0x000252, 0x00028d, 0x000265, 0x0002a2, 0x0002a1, 0x000255, 0x000291, + 0x00027a, 0x000267, 0x00025a, 0x00fffd, 0x0001fd, 0x001f70, 0x001f71, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x001f72, + 0x001f73, 0x000361, 0x0002c8, 0x0002cc, 0x0002d0, 0x0002d1, 0x000306, 0x00203f, + 0x00030b, 0x000301, 0x000304, 0x000300, 0x00030f, 0x00030c, 0x000302, 0x0002e5, + 0x0002e6, 0x0002e7, 0x0002e8, 0x0002e9, 0x00fffd, 0x00fffd, 0x000325, 0x00032c, + 0x000339, 0x00031c, 0x00031f, 0x000320, 0x000308, 0x00033d, 0x000329, 0x00032f, + 0x0002de, 0x000324, 0x000330, 0x00033c, 0x000334, 0x00031d, 0x00031e, 0x000318, + 0x000319, 0x00032a, 0x00033a, 0x00033b, 0x000303, 0x00031a, + // plane 1 row 12 + 0x002776, 0x002777, 0x002778, 0x002779, 0x00277a, 0x00277b, 0x00277c, 0x00277d, + 0x00277e, 0x00277f, 0x0024eb, 0x0024ec, 0x0024ed, 0x0024ee, 0x0024ef, 0x0024f0, + 0x0024f1, 0x0024f2, 0x0024f3, 0x0024f4, 0x002170, 0x002171, 0x002172, 0x002173, + 0x002174, 0x002175, 0x002176, 0x002177, 0x002178, 0x002179, 0x00217a, 0x00217b, + 0x0024d0, 0x0024d1, 0x0024d2, 0x0024d3, 0x0024d4, 0x0024d5, 0x0024d6, 0x0024d7, + 0x0024d8, 0x0024d9, 0x0024da, 0x0024db, 0x0024dc, 0x0024dd, 0x0024de, 0x0024df, + 0x0024e0, 0x0024e1, 0x0024e2, 0x0024e3, 0x0024e4, 0x0024e5, 0x0024e6, 0x0024e7, + 0x0024e8, 0x0024e9, 0x0032d0, 0x0032d1, 0x0032d2, 0x0032d3, 0x0032d4, 0x0032d5, + 0x0032d6, 0x0032d7, 0x0032d8, 0x0032d9, 0x0032da, 0x0032db, 0x0032dc, 0x0032dd, + 0x0032de, 0x0032df, 0x0032e0, 0x0032e1, 0x0032e2, 0x0032e3, 0x0032fa, 0x0032e9, + 0x0032e5, 0x0032ed, 0x0032ec, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x002051, 0x002042, + // plane 1 row 13 + 0x002460, 0x002461, 0x002462, 0x002463, 0x002464, 0x002465, 0x002466, 0x002467, + 0x002468, 0x002469, 0x00246a, 0x00246b, 0x00246c, 0x00246d, 0x00246e, 0x00246f, + 0x002470, 0x002471, 0x002472, 0x002473, 0x002160, 0x002161, 0x002162, 0x002163, + 0x002164, 0x002165, 0x002166, 0x002167, 0x002168, 0x002169, 0x00216a, 0x003349, + 0x003314, 0x003322, 0x00334d, 0x003318, 0x003327, 0x003303, 0x003336, 0x003351, + 0x003357, 0x00330d, 0x003326, 0x003323, 0x00332b, 0x00334a, 0x00333b, 0x00339c, + 0x00339d, 0x00339e, 0x00338e, 0x00338f, 0x0033c4, 0x0033a1, 0x00216b, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00337b, 0x00301d, + 0x00301f, 0x002116, 0x0033cd, 0x002121, 0x0032a4, 0x0032a5, 0x0032a6, 0x0032a7, + 0x0032a8, 0x003231, 0x003232, 0x003239, 0x00337e, 0x00337d, 0x00337c, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00222e, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00221f, + 0x0022bf, 0x00fffd, 0x00fffd, 0x00fffd, 0x002756, 0x00261e, + // plane 1 row 14 + 0x004ff1, 0x02000b, 0x003402, 0x004e28, 0x004e2f, 0x004e30, 0x004e8d, 0x004ee1, + 0x004efd, 0x004eff, 0x004f03, 0x004f0b, 0x004f60, 0x004f48, 0x004f49, 0x004f56, + 0x004f5f, 0x004f6a, 0x004f6c, 0x004f7e, 0x004f8a, 0x004f94, 0x004f97, 0x00fa30, + 0x004fc9, 0x004fe0, 0x005001, 0x005002, 0x00500e, 0x005018, 0x005027, 0x00502e, + 0x005040, 0x00503b, 0x005041, 0x005094, 0x0050cc, 0x0050f2, 0x0050d0, 0x0050e6, + 0x00fa31, 0x005106, 0x005103, 0x00510b, 0x00511e, 0x005135, 0x00514a, 0x00fa32, + 0x005155, 0x005157, 0x0034b5, 0x00519d, 0x0051c3, 0x0051ca, 0x0051de, 0x0051e2, + 0x0051ee, 0x005201, 0x0034db, 0x005213, 0x005215, 0x005249, 0x005257, 0x005261, + 0x005293, 0x0052c8, 0x00fa33, 0x0052cc, 0x0052d0, 0x0052d6, 0x0052db, 0x00fa34, + 0x0052f0, 0x0052fb, 0x005300, 0x005307, 0x00531c, 0x00fa35, 0x005361, 0x005363, + 0x00537d, 0x005393, 0x00539d, 0x0053b2, 0x005412, 0x005427, 0x00544d, 0x00549c, + 0x00546b, 0x005474, 0x00547f, 0x005488, 0x005496, 0x0054a1, + // plane 1 row 15 + 0x0054a9, 0x0054c6, 0x0054ff, 0x00550e, 0x00552b, 0x005535, 0x005550, 0x00555e, + 0x005581, 0x005586, 0x00558e, 0x00fa36, 0x0055ad, 0x0055ce, 0x00fa37, 0x005608, + 0x00560e, 0x00563b, 0x005649, 0x005676, 0x005666, 0x00fa38, 0x00566f, 0x005671, + 0x005672, 0x005699, 0x00569e, 0x0056a9, 0x0056ac, 0x0056b3, 0x0056c9, 0x0056ca, + 0x00570a, 0x02123d, 0x005721, 0x00572f, 0x005733, 0x005734, 0x005770, 0x005777, + 0x00577c, 0x00579c, 0x00fa0f, 0x02131b, 0x0057b8, 0x0057c7, 0x0057c8, 0x0057cf, + 0x0057e4, 0x0057ed, 0x0057f5, 0x0057f6, 0x0057ff, 0x005809, 0x00fa10, 0x005861, + 0x005864, 0x00fa39, 0x00587c, 0x005889, 0x00589e, 0x00fa3a, 0x0058a9, 0x02146e, + 0x0058d2, 0x0058ce, 0x0058d4, 0x0058da, 0x0058e0, 0x0058e9, 0x00590c, 0x008641, + 0x00595d, 0x00596d, 0x00598b, 0x005992, 0x0059a4, 0x0059c3, 0x0059d2, 0x0059dd, + 0x005a13, 0x005a23, 0x005a67, 0x005a6d, 0x005a77, 0x005a7e, 0x005a84, 0x005a9e, + 0x005aa7, 0x005ac4, 0x0218bd, 0x005b19, 0x005b25, 0x00525d, + // plane 1 row 16 + 0x004e9c, 0x005516, 0x005a03, 0x00963f, 0x0054c0, 0x00611b, 0x006328, 0x0059f6, + 0x009022, 0x008475, 0x00831c, 0x007a50, 0x0060aa, 0x0063e1, 0x006e25, 0x0065ed, + 0x008466, 0x0082a6, 0x009bf5, 0x006893, 0x005727, 0x0065a1, 0x006271, 0x005b9b, + 0x0059d0, 0x00867b, 0x0098f4, 0x007d62, 0x007dbe, 0x009b8e, 0x006216, 0x007c9f, + 0x0088b7, 0x005b89, 0x005eb5, 0x006309, 0x006697, 0x006848, 0x0095c7, 0x00978d, + 0x00674f, 0x004ee5, 0x004f0a, 0x004f4d, 0x004f9d, 0x005049, 0x0056f2, 0x005937, + 0x0059d4, 0x005a01, 0x005c09, 0x0060df, 0x00610f, 0x006170, 0x006613, 0x006905, + 0x0070ba, 0x00754f, 0x007570, 0x0079fb, 0x007dad, 0x007def, 0x0080c3, 0x00840e, + 0x008863, 0x008b02, 0x009055, 0x00907a, 0x00533b, 0x004e95, 0x004ea5, 0x0057df, + 0x0080b2, 0x0090c1, 0x0078ef, 0x004e00, 0x0058f1, 0x006ea2, 0x009038, 0x007a32, + 0x008328, 0x00828b, 0x009c2f, 0x005141, 0x005370, 0x0054bd, 0x0054e1, 0x0056e0, + 0x0059fb, 0x005f15, 0x0098f2, 0x006deb, 0x0080e4, 0x00852d, + // plane 1 row 17 + 0x009662, 0x009670, 0x0096a0, 0x0097fb, 0x00540b, 0x0053f3, 0x005b87, 0x0070cf, + 0x007fbd, 0x008fc2, 0x0096e8, 0x00536f, 0x009d5c, 0x007aba, 0x004e11, 0x007893, + 0x0081fc, 0x006e26, 0x005618, 0x005504, 0x006b1d, 0x00851a, 0x009c3b, 0x0059e5, + 0x0053a9, 0x006d66, 0x0074dc, 0x00958f, 0x005642, 0x004e91, 0x00904b, 0x0096f2, + 0x00834f, 0x00990c, 0x0053e1, 0x0055b6, 0x005b30, 0x005f71, 0x006620, 0x0066f3, + 0x006804, 0x006c38, 0x006cf3, 0x006d29, 0x00745b, 0x0076c8, 0x007a4e, 0x009834, + 0x0082f1, 0x00885b, 0x008a60, 0x0092ed, 0x006db2, 0x0075ab, 0x0076ca, 0x0099c5, + 0x0060a6, 0x008b01, 0x008d8a, 0x0095b2, 0x00698e, 0x0053ad, 0x005186, 0x005712, + 0x005830, 0x005944, 0x005bb4, 0x005ef6, 0x006028, 0x0063a9, 0x0063f4, 0x006cbf, + 0x006f14, 0x00708e, 0x007114, 0x007159, 0x0071d5, 0x00733f, 0x007e01, 0x008276, + 0x0082d1, 0x008597, 0x009060, 0x00925b, 0x009d1b, 0x005869, 0x0065bc, 0x006c5a, + 0x007525, 0x0051f9, 0x00592e, 0x005965, 0x005f80, 0x005fdc, + // plane 1 row 18 + 0x0062bc, 0x0065fa, 0x006a2a, 0x006b27, 0x006bb4, 0x00738b, 0x007fc1, 0x008956, + 0x009d2c, 0x009d0e, 0x009ec4, 0x005ca1, 0x006c96, 0x00837b, 0x005104, 0x005c4b, + 0x0061b6, 0x0081c6, 0x006876, 0x007261, 0x004e59, 0x004ffa, 0x005378, 0x006069, + 0x006e29, 0x007a4f, 0x0097f3, 0x004e0b, 0x005316, 0x004eee, 0x004f55, 0x004f3d, + 0x004fa1, 0x004f73, 0x0052a0, 0x0053ef, 0x005609, 0x00590f, 0x005ac1, 0x005bb6, + 0x005be1, 0x0079d1, 0x006687, 0x00679c, 0x0067b6, 0x006b4c, 0x006cb3, 0x00706b, + 0x0073c2, 0x00798d, 0x0079be, 0x007a3c, 0x007b87, 0x0082b1, 0x0082db, 0x008304, + 0x008377, 0x0083ef, 0x0083d3, 0x008766, 0x008ab2, 0x005629, 0x008ca8, 0x008fe6, + 0x00904e, 0x00971e, 0x00868a, 0x004fc4, 0x005ce8, 0x006211, 0x007259, 0x00753b, + 0x0081e5, 0x0082bd, 0x0086fe, 0x008cc0, 0x0096c5, 0x009913, 0x0099d5, 0x004ecb, + 0x004f1a, 0x0089e3, 0x0056de, 0x00584a, 0x0058ca, 0x005efb, 0x005feb, 0x00602a, + 0x006094, 0x006062, 0x0061d0, 0x006212, 0x0062d0, 0x006539, + // plane 1 row 19 + 0x009b41, 0x006666, 0x0068b0, 0x006d77, 0x007070, 0x00754c, 0x007686, 0x007d75, + 0x0082a5, 0x0087f9, 0x00958b, 0x00968e, 0x008c9d, 0x0051f1, 0x0052be, 0x005916, + 0x0054b3, 0x005bb3, 0x005d16, 0x006168, 0x006982, 0x006daf, 0x00788d, 0x0084cb, + 0x008857, 0x008a72, 0x0093a7, 0x009ab8, 0x006d6c, 0x0099a8, 0x0086d9, 0x0057a3, + 0x0067ff, 0x0086ce, 0x00920e, 0x005283, 0x005687, 0x005404, 0x005ed3, 0x0062e1, + 0x0064b9, 0x00683c, 0x006838, 0x006bbb, 0x007372, 0x0078ba, 0x007a6b, 0x00899a, + 0x0089d2, 0x008d6b, 0x008f03, 0x0090ed, 0x0095a3, 0x009694, 0x009769, 0x005b66, + 0x005cb3, 0x00697d, 0x00984d, 0x00984e, 0x00639b, 0x007b20, 0x006a2b, 0x006a7f, + 0x0068b6, 0x009c0d, 0x006f5f, 0x005272, 0x00559d, 0x006070, 0x0062ec, 0x006d3b, + 0x006e07, 0x006ed1, 0x00845b, 0x008910, 0x008f44, 0x004e14, 0x009c39, 0x0053f6, + 0x00691b, 0x006a3a, 0x009784, 0x00682a, 0x00515c, 0x007ac3, 0x0084b2, 0x0091dc, + 0x00938c, 0x00565b, 0x009d28, 0x006822, 0x008305, 0x008431, + // plane 1 row 20 + 0x007ca5, 0x005208, 0x0082c5, 0x0074e6, 0x004e7e, 0x004f83, 0x0051a0, 0x005bd2, + 0x00520a, 0x0052d8, 0x0052e7, 0x005dfb, 0x00559a, 0x00582a, 0x0059e6, 0x005b8c, + 0x005b98, 0x005bdb, 0x005e72, 0x005e79, 0x0060a3, 0x00611f, 0x006163, 0x0061be, + 0x0063db, 0x006562, 0x0067d1, 0x006853, 0x0068fa, 0x006b3e, 0x006b53, 0x006c57, + 0x006f22, 0x006f97, 0x006f45, 0x0074b0, 0x007518, 0x0076e3, 0x00770b, 0x007aff, + 0x007ba1, 0x007c21, 0x007de9, 0x007f36, 0x007ff0, 0x00809d, 0x008266, 0x00839e, + 0x0089b3, 0x008acc, 0x008cab, 0x009084, 0x009451, 0x009593, 0x009591, 0x0095a2, + 0x009665, 0x0097d3, 0x009928, 0x008218, 0x004e38, 0x00542b, 0x005cb8, 0x005dcc, + 0x0073a9, 0x00764c, 0x00773c, 0x005ca9, 0x007feb, 0x008d0b, 0x0096c1, 0x009811, + 0x009854, 0x009858, 0x004f01, 0x004f0e, 0x005371, 0x00559c, 0x005668, 0x0057fa, + 0x005947, 0x005b09, 0x005bc4, 0x005c90, 0x005e0c, 0x005e7e, 0x005fcc, 0x0063ee, + 0x00673a, 0x0065d7, 0x0065e2, 0x00671f, 0x0068cb, 0x0068c4, + // plane 1 row 21 + 0x006a5f, 0x005e30, 0x006bc5, 0x006c17, 0x006c7d, 0x00757f, 0x007948, 0x005b63, + 0x007a00, 0x007d00, 0x005fbd, 0x00898f, 0x008a18, 0x008cb4, 0x008d77, 0x008ecc, + 0x008f1d, 0x0098e2, 0x009a0e, 0x009b3c, 0x004e80, 0x00507d, 0x005100, 0x005993, + 0x005b9c, 0x00622f, 0x006280, 0x0064ec, 0x006b3a, 0x0072a0, 0x007591, 0x007947, + 0x007fa9, 0x0087fb, 0x008abc, 0x008b70, 0x0063ac, 0x0083ca, 0x0097a0, 0x005409, + 0x005403, 0x0055ab, 0x006854, 0x006a58, 0x008a70, 0x007827, 0x006775, 0x009ecd, + 0x005374, 0x005ba2, 0x00811a, 0x008650, 0x009006, 0x004e18, 0x004e45, 0x004ec7, + 0x004f11, 0x0053ca, 0x005438, 0x005bae, 0x005f13, 0x006025, 0x006551, 0x00673d, + 0x006c42, 0x006c72, 0x006ce3, 0x007078, 0x007403, 0x007a76, 0x007aae, 0x007b08, + 0x007d1a, 0x007cfe, 0x007d66, 0x0065e7, 0x00725b, 0x0053bb, 0x005c45, 0x005de8, + 0x0062d2, 0x0062e0, 0x006319, 0x006e20, 0x00865a, 0x008a31, 0x008ddd, 0x0092f8, + 0x006f01, 0x0079a6, 0x009b5a, 0x004ea8, 0x004eab, 0x004eac, + // plane 1 row 22 + 0x004f9b, 0x004fa0, 0x0050d1, 0x005147, 0x007af6, 0x005171, 0x0051f6, 0x005354, + 0x005321, 0x00537f, 0x0053eb, 0x0055ac, 0x005883, 0x005ce1, 0x005f37, 0x005f4a, + 0x00602f, 0x006050, 0x00606d, 0x00631f, 0x006559, 0x006a4b, 0x006cc1, 0x0072c2, + 0x0072ed, 0x0077ef, 0x0080f8, 0x008105, 0x008208, 0x00854e, 0x0090f7, 0x0093e1, + 0x0097ff, 0x009957, 0x009a5a, 0x004ef0, 0x0051dd, 0x005c2d, 0x006681, 0x00696d, + 0x005c40, 0x0066f2, 0x006975, 0x007389, 0x006850, 0x007c81, 0x0050c5, 0x0052e4, + 0x005747, 0x005dfe, 0x009326, 0x0065a4, 0x006b23, 0x006b3d, 0x007434, 0x007981, + 0x0079bd, 0x007b4b, 0x007dca, 0x0082b9, 0x0083cc, 0x00887f, 0x00895f, 0x008b39, + 0x008fd1, 0x0091d1, 0x00541f, 0x009280, 0x004e5d, 0x005036, 0x0053e5, 0x00533a, + 0x0072d7, 0x007396, 0x0077e9, 0x0082e6, 0x008eaf, 0x0099c6, 0x0099c8, 0x0099d2, + 0x005177, 0x00611a, 0x00865e, 0x0055b0, 0x007a7a, 0x005076, 0x005bd3, 0x009047, + 0x009685, 0x004e32, 0x006adb, 0x0091e7, 0x005c51, 0x005c48, + // plane 1 row 23 + 0x006398, 0x007a9f, 0x006c93, 0x009774, 0x008f61, 0x007aaa, 0x00718a, 0x009688, + 0x007c82, 0x006817, 0x007e70, 0x006851, 0x00936c, 0x0052f2, 0x00541b, 0x0085ab, + 0x008a13, 0x007fa4, 0x008ecd, 0x0090e1, 0x005366, 0x008888, 0x007941, 0x004fc2, + 0x0050be, 0x005211, 0x005144, 0x005553, 0x00572d, 0x0073ea, 0x00578b, 0x005951, + 0x005f62, 0x005f84, 0x006075, 0x006176, 0x006167, 0x0061a9, 0x0063b2, 0x00643a, + 0x00656c, 0x00666f, 0x006842, 0x006e13, 0x007566, 0x007a3d, 0x007cfb, 0x007d4c, + 0x007d99, 0x007e4b, 0x007f6b, 0x00830e, 0x00834a, 0x0086cd, 0x008a08, 0x008a63, + 0x008b66, 0x008efd, 0x00981a, 0x009d8f, 0x0082b8, 0x008fce, 0x009be8, 0x005287, + 0x00621f, 0x006483, 0x006fc0, 0x009699, 0x006841, 0x005091, 0x006b20, 0x006c7a, + 0x006f54, 0x007a74, 0x007d50, 0x008840, 0x008a23, 0x006708, 0x004ef6, 0x005039, + 0x005026, 0x005065, 0x00517c, 0x005238, 0x005263, 0x0055a7, 0x00570f, 0x005805, + 0x005acc, 0x005efa, 0x0061b2, 0x0061f8, 0x0062f3, 0x006372, + // plane 1 row 24 + 0x00691c, 0x006a29, 0x00727d, 0x0072ac, 0x00732e, 0x007814, 0x00786f, 0x007d79, + 0x00770c, 0x0080a9, 0x00898b, 0x008b19, 0x008ce2, 0x008ed2, 0x009063, 0x009375, + 0x00967a, 0x009855, 0x009a13, 0x009e78, 0x005143, 0x00539f, 0x0053b3, 0x005e7b, + 0x005f26, 0x006e1b, 0x006e90, 0x007384, 0x0073fe, 0x007d43, 0x008237, 0x008a00, + 0x008afa, 0x009650, 0x004e4e, 0x00500b, 0x0053e4, 0x00547c, 0x0056fa, 0x0059d1, + 0x005b64, 0x005df1, 0x005eab, 0x005f27, 0x006238, 0x006545, 0x0067af, 0x006e56, + 0x0072d0, 0x007cca, 0x0088b4, 0x0080a1, 0x0080e1, 0x0083f0, 0x00864e, 0x008a87, + 0x008de8, 0x009237, 0x0096c7, 0x009867, 0x009f13, 0x004e94, 0x004e92, 0x004f0d, + 0x005348, 0x005449, 0x00543e, 0x005a2f, 0x005f8c, 0x005fa1, 0x00609f, 0x0068a7, + 0x006a8e, 0x00745a, 0x007881, 0x008a9e, 0x008aa4, 0x008b77, 0x009190, 0x004e5e, + 0x009bc9, 0x004ea4, 0x004f7c, 0x004faf, 0x005019, 0x005016, 0x005149, 0x00516c, + 0x00529f, 0x0052b9, 0x0052fe, 0x00539a, 0x0053e3, 0x005411, + // plane 1 row 25 + 0x00540e, 0x005589, 0x005751, 0x0057a2, 0x00597d, 0x005b54, 0x005b5d, 0x005b8f, + 0x005de5, 0x005de7, 0x005df7, 0x005e78, 0x005e83, 0x005e9a, 0x005eb7, 0x005f18, + 0x006052, 0x00614c, 0x006297, 0x0062d8, 0x0063a7, 0x00653b, 0x006602, 0x006643, + 0x0066f4, 0x00676d, 0x006821, 0x006897, 0x0069cb, 0x006c5f, 0x006d2a, 0x006d69, + 0x006e2f, 0x006e9d, 0x007532, 0x007687, 0x00786c, 0x007a3f, 0x007ce0, 0x007d05, + 0x007d18, 0x007d5e, 0x007db1, 0x008015, 0x008003, 0x0080af, 0x0080b1, 0x008154, + 0x00818f, 0x00822a, 0x008352, 0x00884c, 0x008861, 0x008b1b, 0x008ca2, 0x008cfc, + 0x0090ca, 0x009175, 0x009271, 0x00783f, 0x0092fc, 0x0095a4, 0x00964d, 0x009805, + 0x009999, 0x009ad8, 0x009d3b, 0x00525b, 0x0052ab, 0x0053f7, 0x005408, 0x0058d5, + 0x0062f7, 0x006fe0, 0x008c6a, 0x008f5f, 0x009eb9, 0x00514b, 0x00523b, 0x00544a, + 0x0056fd, 0x007a40, 0x009177, 0x009d60, 0x009ed2, 0x007344, 0x006f09, 0x008170, + 0x007511, 0x005ffd, 0x0060da, 0x009aa8, 0x0072db, 0x008fbc, + // plane 1 row 26 + 0x006b64, 0x009803, 0x004eca, 0x0056f0, 0x005764, 0x0058be, 0x005a5a, 0x006068, + 0x0061c7, 0x00660f, 0x006606, 0x006839, 0x0068b1, 0x006df7, 0x0075d5, 0x007d3a, + 0x00826e, 0x009b42, 0x004e9b, 0x004f50, 0x0053c9, 0x005506, 0x005d6f, 0x005de6, + 0x005dee, 0x0067fb, 0x006c99, 0x007473, 0x007802, 0x008a50, 0x009396, 0x0088df, + 0x005750, 0x005ea7, 0x00632b, 0x0050b5, 0x0050ac, 0x00518d, 0x006700, 0x0054c9, + 0x00585e, 0x0059bb, 0x005bb0, 0x005f69, 0x00624d, 0x0063a1, 0x00683d, 0x006b73, + 0x006e08, 0x00707d, 0x0091c7, 0x007280, 0x007815, 0x007826, 0x00796d, 0x00658e, + 0x007d30, 0x0083dc, 0x0088c1, 0x008f09, 0x00969b, 0x005264, 0x005728, 0x006750, + 0x007f6a, 0x008ca1, 0x0051b4, 0x005742, 0x00962a, 0x00583a, 0x00698a, 0x0080b4, + 0x0054b2, 0x005d0e, 0x0057fc, 0x007895, 0x009dfa, 0x004f5c, 0x00524a, 0x00548b, + 0x00643e, 0x006628, 0x006714, 0x0067f5, 0x007a84, 0x007b56, 0x007d22, 0x00932f, + 0x00685c, 0x009bad, 0x007b39, 0x005319, 0x00518a, 0x005237, + // plane 1 row 27 + 0x005bdf, 0x0062f6, 0x0064ae, 0x0064e6, 0x00672d, 0x006bba, 0x0085a9, 0x0096d1, + 0x007690, 0x009bd6, 0x00634c, 0x009306, 0x009bab, 0x0076bf, 0x006652, 0x004e09, + 0x005098, 0x0053c2, 0x005c71, 0x0060e8, 0x006492, 0x006563, 0x00685f, 0x0071e6, + 0x0073ca, 0x007523, 0x007b97, 0x007e82, 0x008695, 0x008b83, 0x008cdb, 0x009178, + 0x009910, 0x0065ac, 0x0066ab, 0x006b8b, 0x004ed5, 0x004ed4, 0x004f3a, 0x004f7f, + 0x00523a, 0x0053f8, 0x0053f2, 0x0055e3, 0x0056db, 0x0058eb, 0x0059cb, 0x0059c9, + 0x0059ff, 0x005b50, 0x005c4d, 0x005e02, 0x005e2b, 0x005fd7, 0x00601d, 0x006307, + 0x00652f, 0x005b5c, 0x0065af, 0x0065bd, 0x0065e8, 0x00679d, 0x006b62, 0x006b7b, + 0x006c0f, 0x007345, 0x007949, 0x0079c1, 0x007cf8, 0x007d19, 0x007d2b, 0x0080a2, + 0x008102, 0x0081f3, 0x008996, 0x008a5e, 0x008a69, 0x008a66, 0x008a8c, 0x008aee, + 0x008cc7, 0x008cdc, 0x0096cc, 0x0098fc, 0x006b6f, 0x004e8b, 0x004f3c, 0x004f8d, + 0x005150, 0x005b57, 0x005bfa, 0x006148, 0x006301, 0x006642, + // plane 1 row 28 + 0x006b21, 0x006ecb, 0x006cbb, 0x00723e, 0x0074bd, 0x0075d4, 0x0078c1, 0x00793a, + 0x00800c, 0x008033, 0x0081ea, 0x008494, 0x008f9e, 0x006c50, 0x009e7f, 0x005f0f, + 0x008b58, 0x009d2b, 0x007afa, 0x008ef8, 0x005b8d, 0x0096eb, 0x004e03, 0x0053f1, + 0x0057f7, 0x005931, 0x005ac9, 0x005ba4, 0x006089, 0x006e7f, 0x006f06, 0x0075be, + 0x008cea, 0x005b9f, 0x008500, 0x007be0, 0x005072, 0x0067f4, 0x00829d, 0x005c61, + 0x00854a, 0x007e1e, 0x00820e, 0x005199, 0x005c04, 0x006368, 0x008d66, 0x00659c, + 0x00716e, 0x00793e, 0x007d17, 0x008005, 0x008b1d, 0x008eca, 0x00906e, 0x0086c7, + 0x0090aa, 0x00501f, 0x0052fa, 0x005c3a, 0x006753, 0x00707c, 0x007235, 0x00914c, + 0x0091c8, 0x00932b, 0x0082e5, 0x005bc2, 0x005f31, 0x0060f9, 0x004e3b, 0x0053d6, + 0x005b88, 0x00624b, 0x006731, 0x006b8a, 0x0072e9, 0x0073e0, 0x007a2e, 0x00816b, + 0x008da3, 0x009152, 0x009996, 0x005112, 0x0053d7, 0x00546a, 0x005bff, 0x006388, + 0x006a39, 0x007dac, 0x009700, 0x0056da, 0x0053ce, 0x005468, + // plane 1 row 29 + 0x005b97, 0x005c31, 0x005dde, 0x004fee, 0x006101, 0x0062fe, 0x006d32, 0x0079c0, + 0x0079cb, 0x007d42, 0x007e4d, 0x007fd2, 0x0081ed, 0x00821f, 0x008490, 0x008846, + 0x008972, 0x008b90, 0x008e74, 0x008f2f, 0x009031, 0x00914b, 0x00916c, 0x0096c6, + 0x00919c, 0x004ec0, 0x004f4f, 0x005145, 0x005341, 0x005f93, 0x00620e, 0x0067d4, + 0x006c41, 0x006e0b, 0x007363, 0x007e26, 0x0091cd, 0x009283, 0x0053d4, 0x005919, + 0x005bbf, 0x006dd1, 0x00795d, 0x007e2e, 0x007c9b, 0x00587e, 0x00719f, 0x0051fa, + 0x008853, 0x008ff0, 0x004fca, 0x005cfb, 0x006625, 0x0077ac, 0x007ae3, 0x00821c, + 0x0099ff, 0x0051c6, 0x005faa, 0x0065ec, 0x00696f, 0x006b89, 0x006df3, 0x006e96, + 0x006f64, 0x0076fe, 0x007d14, 0x005de1, 0x009075, 0x009187, 0x009806, 0x0051e6, + 0x00521d, 0x006240, 0x006691, 0x0066d9, 0x006e1a, 0x005eb6, 0x007dd2, 0x007f72, + 0x0066f8, 0x0085af, 0x0085f7, 0x008af8, 0x0052a9, 0x0053d9, 0x005973, 0x005e8f, + 0x005f90, 0x006055, 0x0092e4, 0x009664, 0x0050b7, 0x00511f, + // plane 1 row 30 + 0x0052dd, 0x005320, 0x005347, 0x0053ec, 0x0054e8, 0x005546, 0x005531, 0x005617, + 0x005968, 0x0059be, 0x005a3c, 0x005bb5, 0x005c06, 0x005c0f, 0x005c11, 0x005c1a, + 0x005e84, 0x005e8a, 0x005ee0, 0x005f70, 0x00627f, 0x006284, 0x0062db, 0x00638c, + 0x006377, 0x006607, 0x00660c, 0x00662d, 0x006676, 0x00677e, 0x0068a2, 0x006a1f, + 0x006a35, 0x006cbc, 0x006d88, 0x006e09, 0x006e58, 0x00713c, 0x007126, 0x007167, + 0x0075c7, 0x007701, 0x00785d, 0x007901, 0x007965, 0x0079f0, 0x007ae0, 0x007b11, + 0x007ca7, 0x007d39, 0x008096, 0x0083d6, 0x00848b, 0x008549, 0x00885d, 0x0088f3, + 0x008a1f, 0x008a3c, 0x008a54, 0x008a73, 0x008c61, 0x008cde, 0x0091a4, 0x009266, + 0x00937e, 0x009418, 0x00969c, 0x009798, 0x004e0a, 0x004e08, 0x004e1e, 0x004e57, + 0x005197, 0x005270, 0x0057ce, 0x005834, 0x0058cc, 0x005b22, 0x005e38, 0x0060c5, + 0x0064fe, 0x006761, 0x006756, 0x006d44, 0x0072b6, 0x007573, 0x007a63, 0x0084b8, + 0x008b72, 0x0091b8, 0x009320, 0x005631, 0x0057f4, 0x0098fe, + // plane 1 row 31 + 0x0062ed, 0x00690d, 0x006b96, 0x0071ed, 0x007e54, 0x008077, 0x008272, 0x0089e6, + 0x0098df, 0x008755, 0x008fb1, 0x005c3b, 0x004f38, 0x004fe1, 0x004fb5, 0x005507, + 0x005a20, 0x005bdd, 0x005be9, 0x005fc3, 0x00614e, 0x00632f, 0x0065b0, 0x00664b, + 0x0068ee, 0x00699b, 0x006d78, 0x006df1, 0x007533, 0x0075b9, 0x00771f, 0x00795e, + 0x0079e6, 0x007d33, 0x0081e3, 0x0082af, 0x0085aa, 0x0089aa, 0x008a3a, 0x008eab, + 0x008f9b, 0x009032, 0x0091dd, 0x009707, 0x004eba, 0x004ec1, 0x005203, 0x005875, + 0x0058ec, 0x005c0b, 0x00751a, 0x005c3d, 0x00814e, 0x008a0a, 0x008fc5, 0x009663, + 0x00976d, 0x007b25, 0x008acf, 0x009808, 0x009162, 0x0056f3, 0x0053a8, 0x009017, + 0x005439, 0x005782, 0x005e25, 0x0063a8, 0x006c34, 0x00708a, 0x007761, 0x007c8b, + 0x007fe0, 0x008870, 0x009042, 0x009154, 0x009310, 0x009318, 0x00968f, 0x00745e, + 0x009ac4, 0x005d07, 0x005d69, 0x006570, 0x0067a2, 0x008da8, 0x0096db, 0x00636e, + 0x006749, 0x006919, 0x0083c5, 0x009817, 0x0096c0, 0x0088fe, + // plane 1 row 32 + 0x006f84, 0x00647a, 0x005bf8, 0x004e16, 0x00702c, 0x00755d, 0x00662f, 0x0051c4, + 0x005236, 0x0052e2, 0x0059d3, 0x005f81, 0x006027, 0x006210, 0x00653f, 0x006574, + 0x00661f, 0x006674, 0x0068f2, 0x006816, 0x006b63, 0x006e05, 0x007272, 0x00751f, + 0x0076db, 0x007cbe, 0x008056, 0x0058f0, 0x0088fd, 0x00897f, 0x008aa0, 0x008a93, + 0x008acb, 0x00901d, 0x009192, 0x009752, 0x009759, 0x006589, 0x007a0e, 0x008106, + 0x0096bb, 0x005e2d, 0x0060dc, 0x00621a, 0x0065a5, 0x006614, 0x006790, 0x0077f3, + 0x007a4d, 0x007c4d, 0x007e3e, 0x00810a, 0x008cac, 0x008d64, 0x008de1, 0x008e5f, + 0x0078a9, 0x005207, 0x0062d9, 0x0063a5, 0x006442, 0x006298, 0x008a2d, 0x007a83, + 0x007bc0, 0x008aac, 0x0096ea, 0x007d76, 0x00820c, 0x008749, 0x004ed9, 0x005148, + 0x005343, 0x005360, 0x005ba3, 0x005c02, 0x005c16, 0x005ddd, 0x006226, 0x006247, + 0x0064b0, 0x006813, 0x006834, 0x006cc9, 0x006d45, 0x006d17, 0x0067d3, 0x006f5c, + 0x00714e, 0x00717d, 0x0065cb, 0x007a7f, 0x007bad, 0x007dda, + // plane 1 row 33 + 0x007e4a, 0x007fa8, 0x00817a, 0x00821b, 0x008239, 0x0085a6, 0x008a6e, 0x008cce, + 0x008df5, 0x009078, 0x009077, 0x0092ad, 0x009291, 0x009583, 0x009bae, 0x00524d, + 0x005584, 0x006f38, 0x007136, 0x005168, 0x007985, 0x007e55, 0x0081b3, 0x007cce, + 0x00564c, 0x005851, 0x005ca8, 0x0063aa, 0x0066fe, 0x0066fd, 0x00695a, 0x0072d9, + 0x00758f, 0x00758e, 0x00790e, 0x007956, 0x0079df, 0x007c97, 0x007d20, 0x007d44, + 0x008607, 0x008a34, 0x00963b, 0x009061, 0x009f20, 0x0050e7, 0x005275, 0x0053cc, + 0x0053e2, 0x005009, 0x0055aa, 0x0058ee, 0x00594f, 0x00723d, 0x005b8b, 0x005c64, + 0x00531d, 0x0060e3, 0x0060f3, 0x00635c, 0x006383, 0x00633f, 0x0063bb, 0x0064cd, + 0x0065e9, 0x0066f9, 0x005de3, 0x0069cd, 0x0069fd, 0x006f15, 0x0071e5, 0x004e89, + 0x0075e9, 0x0076f8, 0x007a93, 0x007cdf, 0x007dcf, 0x007d9c, 0x008061, 0x008349, + 0x008358, 0x00846c, 0x0084bc, 0x0085fb, 0x0088c5, 0x008d70, 0x009001, 0x00906d, + 0x009397, 0x00971c, 0x009a12, 0x0050cf, 0x005897, 0x00618e, + // plane 1 row 34 + 0x0081d3, 0x008535, 0x008d08, 0x009020, 0x004fc3, 0x005074, 0x005247, 0x005373, + 0x00606f, 0x006349, 0x00675f, 0x006e2c, 0x008db3, 0x00901f, 0x004fd7, 0x005c5e, + 0x008cca, 0x0065cf, 0x007d9a, 0x005352, 0x008896, 0x005176, 0x0063c3, 0x005b58, + 0x005b6b, 0x005c0a, 0x00640d, 0x006751, 0x00905c, 0x004ed6, 0x00591a, 0x00592a, + 0x006c70, 0x008a51, 0x00553e, 0x005815, 0x0059a5, 0x0060f0, 0x006253, 0x0067c1, + 0x008235, 0x006955, 0x009640, 0x0099c4, 0x009a28, 0x004f53, 0x005806, 0x005bfe, + 0x008010, 0x005cb1, 0x005e2f, 0x005f85, 0x006020, 0x00614b, 0x006234, 0x0066ff, + 0x006cf0, 0x006ede, 0x0080ce, 0x00817f, 0x0082d4, 0x00888b, 0x008cb8, 0x009000, + 0x00902e, 0x00968a, 0x009edb, 0x009bdb, 0x004ee3, 0x0053f0, 0x005927, 0x007b2c, + 0x00918d, 0x00984c, 0x009df9, 0x006edd, 0x007027, 0x005353, 0x005544, 0x005b85, + 0x006258, 0x00629e, 0x0062d3, 0x006ca2, 0x006fef, 0x007422, 0x008a17, 0x009438, + 0x006fc1, 0x008afe, 0x008338, 0x0051e7, 0x0086f8, 0x0053ea, + // plane 1 row 35 + 0x0053e9, 0x004f46, 0x009054, 0x008fb0, 0x00596a, 0x008131, 0x005dfd, 0x007aea, + 0x008fbf, 0x0068da, 0x008c37, 0x0072f8, 0x009c48, 0x006a3d, 0x008ab0, 0x004e39, + 0x005358, 0x005606, 0x005766, 0x0062c5, 0x0063a2, 0x0065e6, 0x006b4e, 0x006de1, + 0x006e5b, 0x0070ad, 0x0077ed, 0x007aef, 0x007baa, 0x007dbb, 0x00803d, 0x0080c6, + 0x0086cb, 0x008a95, 0x00935b, 0x0056e3, 0x0058c7, 0x005f3e, 0x0065ad, 0x006696, + 0x006a80, 0x006bb5, 0x007537, 0x008ac7, 0x005024, 0x0077e5, 0x005730, 0x005f1b, + 0x006065, 0x00667a, 0x006c60, 0x0075f4, 0x007a1a, 0x007f6e, 0x0081f4, 0x008718, + 0x009045, 0x0099b3, 0x007bc9, 0x00755c, 0x007af9, 0x007b51, 0x0084c4, 0x009010, + 0x0079e9, 0x007a92, 0x008336, 0x005ae1, 0x007740, 0x004e2d, 0x004ef2, 0x005b99, + 0x005fe0, 0x0062bd, 0x00663c, 0x0067f1, 0x006ce8, 0x00866b, 0x008877, 0x008a3b, + 0x00914e, 0x0092f3, 0x0099d0, 0x006a17, 0x007026, 0x00732a, 0x0082e7, 0x008457, + 0x008caf, 0x004e01, 0x005146, 0x0051cb, 0x00558b, 0x005bf5, + // plane 1 row 36 + 0x005e16, 0x005e33, 0x005e81, 0x005f14, 0x005f35, 0x005f6b, 0x005fb4, 0x0061f2, + 0x006311, 0x0066a2, 0x00671d, 0x006f6e, 0x007252, 0x00753a, 0x00773a, 0x008074, + 0x008139, 0x008178, 0x008776, 0x008abf, 0x008adc, 0x008d85, 0x008df3, 0x00929a, + 0x009577, 0x009802, 0x009ce5, 0x0052c5, 0x006357, 0x0076f4, 0x006715, 0x006c88, + 0x0073cd, 0x008cc3, 0x0093ae, 0x009673, 0x006d25, 0x00589c, 0x00690e, 0x0069cc, + 0x008ffd, 0x00939a, 0x0075db, 0x00901a, 0x00585a, 0x006802, 0x0063b4, 0x0069fb, + 0x004f43, 0x006f2c, 0x0067d8, 0x008fbb, 0x008526, 0x007db4, 0x009354, 0x00693f, + 0x006f70, 0x00576a, 0x0058f7, 0x005b2c, 0x007d2c, 0x00722a, 0x00540a, 0x0091e3, + 0x009db4, 0x004ead, 0x004f4e, 0x00505c, 0x005075, 0x005243, 0x008c9e, 0x005448, + 0x005824, 0x005b9a, 0x005e1d, 0x005e95, 0x005ead, 0x005ef7, 0x005f1f, 0x00608c, + 0x0062b5, 0x00633a, 0x0063d0, 0x0068af, 0x006c40, 0x007887, 0x00798e, 0x007a0b, + 0x007de0, 0x008247, 0x008a02, 0x008ae6, 0x008e44, 0x009013, + // plane 1 row 37 + 0x0090b8, 0x00912d, 0x0091d8, 0x009f0e, 0x006ce5, 0x006458, 0x0064e2, 0x006575, + 0x006ef4, 0x007684, 0x007b1b, 0x009069, 0x0093d1, 0x006eba, 0x0054f2, 0x005fb9, + 0x0064a4, 0x008f4d, 0x008fed, 0x009244, 0x005178, 0x00586b, 0x005929, 0x005c55, + 0x005e97, 0x006dfb, 0x007e8f, 0x00751c, 0x008cbc, 0x008ee2, 0x00985b, 0x0070b9, + 0x004f1d, 0x006bbf, 0x006fb1, 0x007530, 0x0096fb, 0x00514e, 0x005410, 0x005835, + 0x005857, 0x0059ac, 0x005c60, 0x005f92, 0x006597, 0x00675c, 0x006e21, 0x00767b, + 0x0083df, 0x008ced, 0x009014, 0x0090fd, 0x00934d, 0x007825, 0x00783a, 0x0052aa, + 0x005ea6, 0x00571f, 0x005974, 0x006012, 0x005012, 0x00515a, 0x0051ac, 0x0051cd, + 0x005200, 0x005510, 0x005854, 0x005858, 0x005957, 0x005b95, 0x005cf6, 0x005d8b, + 0x0060bc, 0x006295, 0x00642d, 0x006771, 0x006843, 0x0068bc, 0x0068df, 0x0076d7, + 0x006dd8, 0x006e6f, 0x006d9b, 0x00706f, 0x0071c8, 0x005f53, 0x0075d8, 0x007977, + 0x007b49, 0x007b54, 0x007b52, 0x007cd6, 0x007d71, 0x005230, + // plane 1 row 38 + 0x008463, 0x008569, 0x0085e4, 0x008a0e, 0x008b04, 0x008c46, 0x008e0f, 0x009003, + 0x00900f, 0x009419, 0x009676, 0x00982d, 0x009a30, 0x0095d8, 0x0050cd, 0x0052d5, + 0x00540c, 0x005802, 0x005c0e, 0x0061a7, 0x00649e, 0x006d1e, 0x0077b3, 0x007ae5, + 0x0080f4, 0x008404, 0x009053, 0x009285, 0x005ce0, 0x009d07, 0x00533f, 0x005f97, + 0x005fb3, 0x006d9c, 0x007279, 0x007763, 0x0079bf, 0x007be4, 0x006bd2, 0x0072ec, + 0x008aad, 0x006803, 0x006a61, 0x0051f8, 0x007a81, 0x006934, 0x005c4a, 0x009cf6, + 0x0082eb, 0x005bc5, 0x009149, 0x00701e, 0x005678, 0x005c6f, 0x0060c7, 0x006566, + 0x006c8c, 0x008c5a, 0x009041, 0x009813, 0x005451, 0x0066c7, 0x00920d, 0x005948, + 0x0090a3, 0x005185, 0x004e4d, 0x0051ea, 0x008599, 0x008b0e, 0x007058, 0x00637a, + 0x00934b, 0x006962, 0x0099b4, 0x007e04, 0x007577, 0x005357, 0x006960, 0x008edf, + 0x0096e3, 0x006c5d, 0x004e8c, 0x005c3c, 0x005f10, 0x008fe9, 0x005302, 0x008cd1, + 0x008089, 0x008679, 0x005eff, 0x0065e5, 0x004e73, 0x005165, + // plane 1 row 39 + 0x005982, 0x005c3f, 0x0097ee, 0x004efb, 0x00598a, 0x005fcd, 0x008a8d, 0x006fe1, + 0x0079b0, 0x007962, 0x005be7, 0x008471, 0x00732b, 0x0071b1, 0x005e74, 0x005ff5, + 0x00637b, 0x00649a, 0x0071c3, 0x007c98, 0x004e43, 0x005efc, 0x004e4b, 0x0057dc, + 0x0056a2, 0x0060a9, 0x006fc3, 0x007d0d, 0x0080fd, 0x008133, 0x0081bf, 0x008fb2, + 0x008997, 0x0086a4, 0x005df4, 0x00628a, 0x0064ad, 0x008987, 0x006777, 0x006ce2, + 0x006d3e, 0x007436, 0x007834, 0x005a46, 0x007f75, 0x0082ad, 0x0099ac, 0x004ff3, + 0x005ec3, 0x0062dd, 0x006392, 0x006557, 0x00676f, 0x0076c3, 0x00724c, 0x0080cc, + 0x0080ba, 0x008f29, 0x00914d, 0x00500d, 0x0057f9, 0x005a92, 0x006885, 0x006973, + 0x007164, 0x0072fd, 0x008cb7, 0x0058f2, 0x008ce0, 0x00966a, 0x009019, 0x00877f, + 0x0079e4, 0x0077e7, 0x008429, 0x004f2f, 0x005265, 0x00535a, 0x0062cd, 0x0067cf, + 0x006cca, 0x00767d, 0x007b94, 0x007c95, 0x008236, 0x008584, 0x008feb, 0x0066dd, + 0x006f20, 0x007206, 0x007e1b, 0x0083ab, 0x0099c1, 0x009ea6, + // plane 1 row 40 + 0x0051fd, 0x007bb1, 0x007872, 0x007bb8, 0x008087, 0x007b48, 0x006ae8, 0x005e61, + 0x00808c, 0x007551, 0x007560, 0x00516b, 0x009262, 0x006e8c, 0x00767a, 0x009197, + 0x009aea, 0x004f10, 0x007f70, 0x00629c, 0x007b4f, 0x0095a5, 0x009ce9, 0x00567a, + 0x005859, 0x0086e4, 0x0096bc, 0x004f34, 0x005224, 0x00534a, 0x0053cd, 0x0053db, + 0x005e06, 0x00642c, 0x006591, 0x00677f, 0x006c3e, 0x006c4e, 0x007248, 0x0072af, + 0x0073ed, 0x007554, 0x007e41, 0x00822c, 0x0085e9, 0x008ca9, 0x007bc4, 0x0091c6, + 0x007169, 0x009812, 0x0098ef, 0x00633d, 0x006669, 0x00756a, 0x0076e4, 0x0078d0, + 0x008543, 0x0086ee, 0x00532a, 0x005351, 0x005426, 0x005983, 0x005e87, 0x005f7c, + 0x0060b2, 0x006249, 0x006279, 0x0062ab, 0x006590, 0x006bd4, 0x006ccc, 0x0075b2, + 0x0076ae, 0x007891, 0x0079d8, 0x007dcb, 0x007f77, 0x0080a5, 0x0088ab, 0x008ab9, + 0x008cbb, 0x00907f, 0x00975e, 0x0098db, 0x006a0b, 0x007c38, 0x005099, 0x005c3e, + 0x005fae, 0x006787, 0x006bd8, 0x007435, 0x007709, 0x007f8e, + // plane 1 row 41 + 0x009f3b, 0x0067ca, 0x007a17, 0x005339, 0x00758b, 0x009aed, 0x005f66, 0x00819d, + 0x0083f1, 0x008098, 0x005f3c, 0x005fc5, 0x007562, 0x007b46, 0x00903c, 0x006867, + 0x0059eb, 0x005a9b, 0x007d10, 0x00767e, 0x008b2c, 0x004ff5, 0x005f6a, 0x006a19, + 0x006c37, 0x006f02, 0x0074e2, 0x007968, 0x008868, 0x008a55, 0x008c79, 0x005edf, + 0x0063cf, 0x0075c5, 0x0079d2, 0x0082d7, 0x009328, 0x0092f2, 0x00849c, 0x0086ed, + 0x009c2d, 0x0054c1, 0x005f6c, 0x00658c, 0x006d5c, 0x007015, 0x008ca7, 0x008cd3, + 0x00983b, 0x00654f, 0x0074f6, 0x004e0d, 0x004ed8, 0x0057e0, 0x00592b, 0x005a66, + 0x005bcc, 0x0051a8, 0x005e03, 0x005e9c, 0x006016, 0x006276, 0x006577, 0x0065a7, + 0x00666e, 0x006d6e, 0x007236, 0x007b26, 0x008150, 0x00819a, 0x008299, 0x008b5c, + 0x008ca0, 0x008ce6, 0x008d74, 0x00961c, 0x009644, 0x004fae, 0x0064ab, 0x006b66, + 0x00821e, 0x008461, 0x00856a, 0x0090e8, 0x005c01, 0x006953, 0x0098a8, 0x00847a, + 0x008557, 0x004f0f, 0x00526f, 0x005fa9, 0x005e45, 0x00670d, + // plane 1 row 42 + 0x00798f, 0x008179, 0x008907, 0x008986, 0x006df5, 0x005f17, 0x006255, 0x006cb8, + 0x004ecf, 0x007269, 0x009b92, 0x005206, 0x00543b, 0x005674, 0x0058b3, 0x0061a4, + 0x00626e, 0x00711a, 0x00596e, 0x007c89, 0x007cde, 0x007d1b, 0x0096f0, 0x006587, + 0x00805e, 0x004e19, 0x004f75, 0x005175, 0x005840, 0x005e63, 0x005e73, 0x005f0a, + 0x0067c4, 0x004e26, 0x00853d, 0x009589, 0x00965b, 0x007c73, 0x009801, 0x0050fb, + 0x0058c1, 0x007656, 0x0078a7, 0x005225, 0x0077a5, 0x008511, 0x007b86, 0x00504f, + 0x005909, 0x007247, 0x007bc7, 0x007de8, 0x008fba, 0x008fd4, 0x00904d, 0x004fbf, + 0x0052c9, 0x005a29, 0x005f01, 0x0097ad, 0x004fdd, 0x008217, 0x0092ea, 0x005703, + 0x006355, 0x006b69, 0x00752b, 0x0088dc, 0x008f14, 0x007a42, 0x0052df, 0x005893, + 0x006155, 0x00620a, 0x0066ae, 0x006bcd, 0x007c3f, 0x0083e9, 0x005023, 0x004ff8, + 0x005305, 0x005446, 0x005831, 0x005949, 0x005b9d, 0x005cf0, 0x005cef, 0x005d29, + 0x005e96, 0x0062b1, 0x006367, 0x00653e, 0x0065b9, 0x00670b, + // plane 1 row 43 + 0x006cd5, 0x006ce1, 0x0070f9, 0x007832, 0x007e2b, 0x0080de, 0x0082b3, 0x00840c, + 0x0084ec, 0x008702, 0x008912, 0x008a2a, 0x008c4a, 0x0090a6, 0x0092d2, 0x0098fd, + 0x009cf3, 0x009d6c, 0x004e4f, 0x004ea1, 0x00508d, 0x005256, 0x00574a, 0x0059a8, + 0x005e3d, 0x005fd8, 0x005fd9, 0x00623f, 0x0066b4, 0x00671b, 0x0067d0, 0x0068d2, + 0x005192, 0x007d21, 0x0080aa, 0x0081a8, 0x008b00, 0x008c8c, 0x008cbf, 0x00927e, + 0x009632, 0x005420, 0x00982c, 0x005317, 0x0050d5, 0x00535c, 0x0058a8, 0x0064b2, + 0x006734, 0x007267, 0x007766, 0x007a46, 0x0091e6, 0x0052c3, 0x006ca1, 0x006b86, + 0x005800, 0x005e4c, 0x005954, 0x00672c, 0x007ffb, 0x0051e1, 0x0076c6, 0x006469, + 0x0078e8, 0x009b54, 0x009ebb, 0x0057cb, 0x0059b9, 0x006627, 0x00679a, 0x006bce, + 0x0054e9, 0x0069d9, 0x005e55, 0x00819c, 0x006795, 0x009baa, 0x0067fe, 0x009c52, + 0x00685d, 0x004ea6, 0x004fe3, 0x0053c8, 0x0062b9, 0x00672b, 0x006cab, 0x008fc4, + 0x004fad, 0x007e6d, 0x009ebf, 0x004e07, 0x006162, 0x006e80, + // plane 1 row 44 + 0x006f2b, 0x008513, 0x005473, 0x00672a, 0x009b45, 0x005df3, 0x007b95, 0x005cac, + 0x005bc6, 0x00871c, 0x006e4a, 0x0084d1, 0x007a14, 0x008108, 0x005999, 0x007c8d, + 0x006c11, 0x007720, 0x0052d9, 0x005922, 0x007121, 0x00725f, 0x0077db, 0x009727, + 0x009d61, 0x00690b, 0x005a7f, 0x005a18, 0x0051a5, 0x00540d, 0x00547d, 0x00660e, + 0x0076df, 0x008ff7, 0x009298, 0x009cf4, 0x0059ea, 0x00725d, 0x006ec5, 0x00514d, + 0x0068c9, 0x007dbf, 0x007dec, 0x009762, 0x009eba, 0x006478, 0x006a21, 0x008302, + 0x005984, 0x005b5f, 0x006bdb, 0x00731b, 0x0076f2, 0x007db2, 0x008017, 0x008499, + 0x005132, 0x006728, 0x009ed9, 0x0076ee, 0x006762, 0x0052ff, 0x009905, 0x005c24, + 0x00623b, 0x007c7e, 0x008cb0, 0x00554f, 0x0060b6, 0x007d0b, 0x009580, 0x005301, + 0x004e5f, 0x0051b6, 0x00591c, 0x00723a, 0x008036, 0x0091ce, 0x005f25, 0x0077e2, + 0x005384, 0x005f79, 0x007d04, 0x0085ac, 0x008a33, 0x008e8d, 0x009756, 0x0067f3, + 0x0085ae, 0x009453, 0x006109, 0x006108, 0x006cb9, 0x007652, + // plane 1 row 45 + 0x008aed, 0x008f38, 0x00552f, 0x004f51, 0x00512a, 0x0052c7, 0x0053cb, 0x005ba5, + 0x005e7d, 0x0060a0, 0x006182, 0x0063d6, 0x006709, 0x0067da, 0x006e67, 0x006d8c, + 0x007336, 0x007337, 0x007531, 0x007950, 0x0088d5, 0x008a98, 0x00904a, 0x009091, + 0x0090f5, 0x0096c4, 0x00878d, 0x005915, 0x004e88, 0x004f59, 0x004e0e, 0x008a89, + 0x008f3f, 0x009810, 0x0050ad, 0x005e7c, 0x005996, 0x005bb9, 0x005eb8, 0x0063da, + 0x0063fa, 0x0064c1, 0x0066dc, 0x00694a, 0x0069d8, 0x006d0b, 0x006eb6, 0x007194, + 0x007528, 0x007aaf, 0x007f8a, 0x008000, 0x008449, 0x0084c9, 0x008981, 0x008b21, + 0x008e0a, 0x009065, 0x00967d, 0x00990a, 0x00617e, 0x006291, 0x006b32, 0x006c83, + 0x006d74, 0x007fcc, 0x007ffc, 0x006dc0, 0x007f85, 0x0087ba, 0x0088f8, 0x006765, + 0x0083b1, 0x00983c, 0x0096f7, 0x006d1b, 0x007d61, 0x00843d, 0x00916a, 0x004e71, + 0x005375, 0x005d50, 0x006b04, 0x006feb, 0x0085cd, 0x00862d, 0x0089a7, 0x005229, + 0x00540f, 0x005c65, 0x00674e, 0x0068a8, 0x007406, 0x007483, + // plane 1 row 46 + 0x0075e2, 0x0088cf, 0x0088e1, 0x0091cc, 0x0096e2, 0x009678, 0x005f8b, 0x007387, + 0x007acb, 0x00844e, 0x0063a0, 0x007565, 0x005289, 0x006d41, 0x006e9c, 0x007409, + 0x007559, 0x00786b, 0x007c92, 0x009686, 0x007adc, 0x009f8d, 0x004fb6, 0x00616e, + 0x0065c5, 0x00865c, 0x004e86, 0x004eae, 0x0050da, 0x004e21, 0x0051cc, 0x005bee, + 0x006599, 0x006881, 0x006dbc, 0x00731f, 0x007642, 0x0077ad, 0x007a1c, 0x007ce7, + 0x00826f, 0x008ad2, 0x00907c, 0x0091cf, 0x009675, 0x009818, 0x00529b, 0x007dd1, + 0x00502b, 0x005398, 0x006797, 0x006dcb, 0x0071d0, 0x007433, 0x0081e8, 0x008f2a, + 0x0096a3, 0x009c57, 0x009e9f, 0x007460, 0x005841, 0x006d99, 0x007d2f, 0x00985e, + 0x004ee4, 0x004f36, 0x004f8b, 0x0051b7, 0x0052b1, 0x005dba, 0x00601c, 0x0073b2, + 0x00793c, 0x0082d3, 0x009234, 0x0096b7, 0x0096f6, 0x00970a, 0x009e97, 0x009f62, + 0x0066a6, 0x006b74, 0x005217, 0x0052a3, 0x0070c8, 0x0088c2, 0x005ec9, 0x00604b, + 0x006190, 0x006f23, 0x007149, 0x007c3e, 0x007df4, 0x00806f, + // plane 1 row 47 + 0x0084ee, 0x009023, 0x00932c, 0x005442, 0x009b6f, 0x006ad3, 0x007089, 0x008cc2, + 0x008def, 0x009732, 0x0052b4, 0x005a41, 0x005eca, 0x005f04, 0x006717, 0x00697c, + 0x006994, 0x006d6a, 0x006f0f, 0x007262, 0x0072fc, 0x007bed, 0x008001, 0x00807e, + 0x00874b, 0x0090ce, 0x00516d, 0x009e93, 0x007984, 0x00808b, 0x009332, 0x008ad6, + 0x00502d, 0x00548c, 0x008a71, 0x006b6a, 0x008cc4, 0x008107, 0x0060d1, 0x0067a0, + 0x009df2, 0x004e99, 0x004e98, 0x009c10, 0x008a6b, 0x0085c1, 0x008568, 0x006900, + 0x006e7e, 0x007897, 0x008155, 0x020b9f, 0x005b41, 0x005b56, 0x005b7d, 0x005b93, + 0x005bd8, 0x005bec, 0x005c12, 0x005c1e, 0x005c23, 0x005c2b, 0x00378d, 0x005c62, + 0x00fa3b, 0x00fa3c, 0x0216b4, 0x005c7a, 0x005c8f, 0x005c9f, 0x005ca3, 0x005caa, + 0x005cba, 0x005ccb, 0x005cd0, 0x005cd2, 0x005cf4, 0x021e34, 0x0037e2, 0x005d0d, + 0x005d27, 0x00fa11, 0x005d46, 0x005d47, 0x005d53, 0x005d4a, 0x005d6d, 0x005d81, + 0x005da0, 0x005da4, 0x005da7, 0x005db8, 0x005dcb, 0x00541e, + // plane 1 row 48 + 0x005f0c, 0x004e10, 0x004e15, 0x004e2a, 0x004e31, 0x004e36, 0x004e3c, 0x004e3f, + 0x004e42, 0x004e56, 0x004e58, 0x004e82, 0x004e85, 0x008c6b, 0x004e8a, 0x008212, + 0x005f0d, 0x004e8e, 0x004e9e, 0x004e9f, 0x004ea0, 0x004ea2, 0x004eb0, 0x004eb3, + 0x004eb6, 0x004ece, 0x004ecd, 0x004ec4, 0x004ec6, 0x004ec2, 0x004ed7, 0x004ede, + 0x004eed, 0x004edf, 0x004ef7, 0x004f09, 0x004f5a, 0x004f30, 0x004f5b, 0x004f5d, + 0x004f57, 0x004f47, 0x004f76, 0x004f88, 0x004f8f, 0x004f98, 0x004f7b, 0x004f69, + 0x004f70, 0x004f91, 0x004f6f, 0x004f86, 0x004f96, 0x005118, 0x004fd4, 0x004fdf, + 0x004fce, 0x004fd8, 0x004fdb, 0x004fd1, 0x004fda, 0x004fd0, 0x004fe4, 0x004fe5, + 0x00501a, 0x005028, 0x005014, 0x00502a, 0x005025, 0x005005, 0x004f1c, 0x004ff6, + 0x005021, 0x005029, 0x00502c, 0x004ffe, 0x004fef, 0x005011, 0x005006, 0x005043, + 0x005047, 0x006703, 0x005055, 0x005050, 0x005048, 0x00505a, 0x005056, 0x00506c, + 0x005078, 0x005080, 0x00509a, 0x005085, 0x0050b4, 0x0050b2, + // plane 1 row 49 + 0x0050c9, 0x0050ca, 0x0050b3, 0x0050c2, 0x0050d6, 0x0050de, 0x0050e5, 0x0050ed, + 0x0050e3, 0x0050ee, 0x0050f9, 0x0050f5, 0x005109, 0x005101, 0x005102, 0x005116, + 0x005115, 0x005114, 0x00511a, 0x005121, 0x00513a, 0x005137, 0x00513c, 0x00513b, + 0x00513f, 0x005140, 0x005152, 0x00514c, 0x005154, 0x005162, 0x007af8, 0x005169, + 0x00516a, 0x00516e, 0x005180, 0x005182, 0x0056d8, 0x00518c, 0x005189, 0x00518f, + 0x005191, 0x005193, 0x005195, 0x005196, 0x0051a4, 0x0051a6, 0x0051a2, 0x0051a9, + 0x0051aa, 0x0051ab, 0x0051b3, 0x0051b1, 0x0051b2, 0x0051b0, 0x0051b5, 0x0051bd, + 0x0051c5, 0x0051c9, 0x0051db, 0x0051e0, 0x008655, 0x0051e9, 0x0051ed, 0x0051f0, + 0x0051f5, 0x0051fe, 0x005204, 0x00520b, 0x005214, 0x00520e, 0x005227, 0x00522a, + 0x00522e, 0x005233, 0x005239, 0x00524f, 0x005244, 0x00524b, 0x00524c, 0x00525e, + 0x005254, 0x00526a, 0x005274, 0x005269, 0x005273, 0x00527f, 0x00527d, 0x00528d, + 0x005294, 0x005292, 0x005271, 0x005288, 0x005291, 0x008fa8, + // plane 1 row 50 + 0x008fa7, 0x0052ac, 0x0052ad, 0x0052bc, 0x0052b5, 0x0052c1, 0x0052cd, 0x0052d7, + 0x0052de, 0x0052e3, 0x0052e6, 0x0098ed, 0x0052e0, 0x0052f3, 0x0052f5, 0x0052f8, + 0x0052f9, 0x005306, 0x005308, 0x007538, 0x00530d, 0x005310, 0x00530f, 0x005315, + 0x00531a, 0x005323, 0x00532f, 0x005331, 0x005333, 0x005338, 0x005340, 0x005346, + 0x005345, 0x004e17, 0x005349, 0x00534d, 0x0051d6, 0x00535e, 0x005369, 0x00536e, + 0x005918, 0x00537b, 0x005377, 0x005382, 0x005396, 0x0053a0, 0x0053a6, 0x0053a5, + 0x0053ae, 0x0053b0, 0x0053b6, 0x0053c3, 0x007c12, 0x0096d9, 0x0053df, 0x0066fc, + 0x0071ee, 0x0053ee, 0x0053e8, 0x0053ed, 0x0053fa, 0x005401, 0x00543d, 0x005440, + 0x00542c, 0x00542d, 0x00543c, 0x00542e, 0x005436, 0x005429, 0x00541d, 0x00544e, + 0x00548f, 0x005475, 0x00548e, 0x00545f, 0x005471, 0x005477, 0x005470, 0x005492, + 0x00547b, 0x005480, 0x005476, 0x005484, 0x005490, 0x005486, 0x0054c7, 0x0054a2, + 0x0054b8, 0x0054a5, 0x0054ac, 0x0054c4, 0x0054c8, 0x0054a8, + // plane 1 row 51 + 0x0054ab, 0x0054c2, 0x0054a4, 0x0054be, 0x0054bc, 0x0054d8, 0x0054e5, 0x0054e6, + 0x00550f, 0x005514, 0x0054fd, 0x0054ee, 0x0054ed, 0x0054fa, 0x0054e2, 0x005539, + 0x005540, 0x005563, 0x00554c, 0x00552e, 0x00555c, 0x005545, 0x005556, 0x005557, + 0x005538, 0x005533, 0x00555d, 0x005599, 0x005580, 0x0054af, 0x00558a, 0x00559f, + 0x00557b, 0x00557e, 0x005598, 0x00559e, 0x0055ae, 0x00557c, 0x005583, 0x0055a9, + 0x005587, 0x0055a8, 0x0055da, 0x0055c5, 0x0055df, 0x0055c4, 0x0055dc, 0x0055e4, + 0x0055d4, 0x005614, 0x0055f7, 0x005616, 0x0055fe, 0x0055fd, 0x00561b, 0x0055f9, + 0x00564e, 0x005650, 0x0071df, 0x005634, 0x005636, 0x005632, 0x005638, 0x00566b, + 0x005664, 0x00562f, 0x00566c, 0x00566a, 0x005686, 0x005680, 0x00568a, 0x0056a0, + 0x005694, 0x00568f, 0x0056a5, 0x0056ae, 0x0056b6, 0x0056b4, 0x0056c2, 0x0056bc, + 0x0056c1, 0x0056c3, 0x0056c0, 0x0056c8, 0x0056ce, 0x0056d1, 0x0056d3, 0x0056d7, + 0x0056ee, 0x0056f9, 0x005700, 0x0056ff, 0x005704, 0x005709, + // plane 1 row 52 + 0x005708, 0x00570b, 0x00570d, 0x005713, 0x005718, 0x005716, 0x0055c7, 0x00571c, + 0x005726, 0x005737, 0x005738, 0x00574e, 0x00573b, 0x005740, 0x00574f, 0x005769, + 0x0057c0, 0x005788, 0x005761, 0x00577f, 0x005789, 0x005793, 0x0057a0, 0x0057b3, + 0x0057a4, 0x0057aa, 0x0057b0, 0x0057c3, 0x0057c6, 0x0057d4, 0x0057d2, 0x0057d3, + 0x00580a, 0x0057d6, 0x0057e3, 0x00580b, 0x005819, 0x00581d, 0x005872, 0x005821, + 0x005862, 0x00584b, 0x005870, 0x006bc0, 0x005852, 0x00583d, 0x005879, 0x005885, + 0x0058b9, 0x00589f, 0x0058ab, 0x0058ba, 0x0058de, 0x0058bb, 0x0058b8, 0x0058ae, + 0x0058c5, 0x0058d3, 0x0058d1, 0x0058d7, 0x0058d9, 0x0058d8, 0x0058e5, 0x0058dc, + 0x0058e4, 0x0058df, 0x0058ef, 0x0058fa, 0x0058f9, 0x0058fb, 0x0058fc, 0x0058fd, + 0x005902, 0x00590a, 0x005910, 0x00591b, 0x0068a6, 0x005925, 0x00592c, 0x00592d, + 0x005932, 0x005938, 0x00593e, 0x007ad2, 0x005955, 0x005950, 0x00594e, 0x00595a, + 0x005958, 0x005962, 0x005960, 0x005967, 0x00596c, 0x005969, + // plane 1 row 53 + 0x005978, 0x005981, 0x00599d, 0x004f5e, 0x004fab, 0x0059a3, 0x0059b2, 0x0059c6, + 0x0059e8, 0x0059dc, 0x00598d, 0x0059d9, 0x0059da, 0x005a25, 0x005a1f, 0x005a11, + 0x005a1c, 0x005a09, 0x005a1a, 0x005a40, 0x005a6c, 0x005a49, 0x005a35, 0x005a36, + 0x005a62, 0x005a6a, 0x005a9a, 0x005abc, 0x005abe, 0x005acb, 0x005ac2, 0x005abd, + 0x005ae3, 0x005ad7, 0x005ae6, 0x005ae9, 0x005ad6, 0x005afa, 0x005afb, 0x005b0c, + 0x005b0b, 0x005b16, 0x005b32, 0x005ad0, 0x005b2a, 0x005b36, 0x005b3e, 0x005b43, + 0x005b45, 0x005b40, 0x005b51, 0x005b55, 0x005b5a, 0x005b5b, 0x005b65, 0x005b69, + 0x005b70, 0x005b73, 0x005b75, 0x005b78, 0x006588, 0x005b7a, 0x005b80, 0x005b83, + 0x005ba6, 0x005bb8, 0x005bc3, 0x005bc7, 0x005bc9, 0x005bd4, 0x005bd0, 0x005be4, + 0x005be6, 0x005be2, 0x005bde, 0x005be5, 0x005beb, 0x005bf0, 0x005bf6, 0x005bf3, + 0x005c05, 0x005c07, 0x005c08, 0x005c0d, 0x005c13, 0x005c20, 0x005c22, 0x005c28, + 0x005c38, 0x005c39, 0x005c41, 0x005c46, 0x005c4e, 0x005c53, + // plane 1 row 54 + 0x005c50, 0x005c4f, 0x005b71, 0x005c6c, 0x005c6e, 0x004e62, 0x005c76, 0x005c79, + 0x005c8c, 0x005c91, 0x005c94, 0x00599b, 0x005cab, 0x005cbb, 0x005cb6, 0x005cbc, + 0x005cb7, 0x005cc5, 0x005cbe, 0x005cc7, 0x005cd9, 0x005ce9, 0x005cfd, 0x005cfa, + 0x005ced, 0x005d8c, 0x005cea, 0x005d0b, 0x005d15, 0x005d17, 0x005d5c, 0x005d1f, + 0x005d1b, 0x005d11, 0x005d14, 0x005d22, 0x005d1a, 0x005d19, 0x005d18, 0x005d4c, + 0x005d52, 0x005d4e, 0x005d4b, 0x005d6c, 0x005d73, 0x005d76, 0x005d87, 0x005d84, + 0x005d82, 0x005da2, 0x005d9d, 0x005dac, 0x005dae, 0x005dbd, 0x005d90, 0x005db7, + 0x005dbc, 0x005dc9, 0x005dcd, 0x005dd3, 0x005dd2, 0x005dd6, 0x005ddb, 0x005deb, + 0x005df2, 0x005df5, 0x005e0b, 0x005e1a, 0x005e19, 0x005e11, 0x005e1b, 0x005e36, + 0x005e37, 0x005e44, 0x005e43, 0x005e40, 0x005e4e, 0x005e57, 0x005e54, 0x005e5f, + 0x005e62, 0x005e64, 0x005e47, 0x005e75, 0x005e76, 0x005e7a, 0x009ebc, 0x005e7f, + 0x005ea0, 0x005ec1, 0x005ec2, 0x005ec8, 0x005ed0, 0x005ecf, + // plane 1 row 55 + 0x005ed6, 0x005ee3, 0x005edd, 0x005eda, 0x005edb, 0x005ee2, 0x005ee1, 0x005ee8, + 0x005ee9, 0x005eec, 0x005ef1, 0x005ef3, 0x005ef0, 0x005ef4, 0x005ef8, 0x005efe, + 0x005f03, 0x005f09, 0x005f5d, 0x005f5c, 0x005f0b, 0x005f11, 0x005f16, 0x005f29, + 0x005f2d, 0x005f38, 0x005f41, 0x005f48, 0x005f4c, 0x005f4e, 0x005f2f, 0x005f51, + 0x005f56, 0x005f57, 0x005f59, 0x005f61, 0x005f6d, 0x005f73, 0x005f77, 0x005f83, + 0x005f82, 0x005f7f, 0x005f8a, 0x005f88, 0x005f91, 0x005f87, 0x005f9e, 0x005f99, + 0x005f98, 0x005fa0, 0x005fa8, 0x005fad, 0x005fbc, 0x005fd6, 0x005ffb, 0x005fe4, + 0x005ff8, 0x005ff1, 0x005fdd, 0x0060b3, 0x005fff, 0x006021, 0x006060, 0x006019, + 0x006010, 0x006029, 0x00600e, 0x006031, 0x00601b, 0x006015, 0x00602b, 0x006026, + 0x00600f, 0x00603a, 0x00605a, 0x006041, 0x00606a, 0x006077, 0x00605f, 0x00604a, + 0x006046, 0x00604d, 0x006063, 0x006043, 0x006064, 0x006042, 0x00606c, 0x00606b, + 0x006059, 0x006081, 0x00608d, 0x0060e7, 0x006083, 0x00609a, + // plane 1 row 56 + 0x006084, 0x00609b, 0x006096, 0x006097, 0x006092, 0x0060a7, 0x00608b, 0x0060e1, + 0x0060b8, 0x0060e0, 0x0060d3, 0x0060b4, 0x005ff0, 0x0060bd, 0x0060c6, 0x0060b5, + 0x0060d8, 0x00614d, 0x006115, 0x006106, 0x0060f6, 0x0060f7, 0x006100, 0x0060f4, + 0x0060fa, 0x006103, 0x006121, 0x0060fb, 0x0060f1, 0x00610d, 0x00610e, 0x006147, + 0x00613e, 0x006128, 0x006127, 0x00614a, 0x00613f, 0x00613c, 0x00612c, 0x006134, + 0x00613d, 0x006142, 0x006144, 0x006173, 0x006177, 0x006158, 0x006159, 0x00615a, + 0x00616b, 0x006174, 0x00616f, 0x006165, 0x006171, 0x00615f, 0x00615d, 0x006153, + 0x006175, 0x006199, 0x006196, 0x006187, 0x0061ac, 0x006194, 0x00619a, 0x00618a, + 0x006191, 0x0061ab, 0x0061ae, 0x0061cc, 0x0061ca, 0x0061c9, 0x0061f7, 0x0061c8, + 0x0061c3, 0x0061c6, 0x0061ba, 0x0061cb, 0x007f79, 0x0061cd, 0x0061e6, 0x0061e3, + 0x0061f6, 0x0061fa, 0x0061f4, 0x0061ff, 0x0061fd, 0x0061fc, 0x0061fe, 0x006200, + 0x006208, 0x006209, 0x00620d, 0x00620c, 0x006214, 0x00621b, + // plane 1 row 57 + 0x00621e, 0x006221, 0x00622a, 0x00622e, 0x006230, 0x006232, 0x006233, 0x006241, + 0x00624e, 0x00625e, 0x006263, 0x00625b, 0x006260, 0x006268, 0x00627c, 0x006282, + 0x006289, 0x00627e, 0x006292, 0x006293, 0x006296, 0x0062d4, 0x006283, 0x006294, + 0x0062d7, 0x0062d1, 0x0062bb, 0x0062cf, 0x0062ff, 0x0062c6, 0x0064d4, 0x0062c8, + 0x0062dc, 0x0062cc, 0x0062ca, 0x0062c2, 0x0062c7, 0x00629b, 0x0062c9, 0x00630c, + 0x0062ee, 0x0062f1, 0x006327, 0x006302, 0x006308, 0x0062ef, 0x0062f5, 0x006350, + 0x00633e, 0x00634d, 0x00641c, 0x00634f, 0x006396, 0x00638e, 0x006380, 0x0063ab, + 0x006376, 0x0063a3, 0x00638f, 0x006389, 0x00639f, 0x0063b5, 0x00636b, 0x006369, + 0x0063be, 0x0063e9, 0x0063c0, 0x0063c6, 0x0063e3, 0x0063c9, 0x0063d2, 0x0063f6, + 0x0063c4, 0x006416, 0x006434, 0x006406, 0x006413, 0x006426, 0x006436, 0x00651d, + 0x006417, 0x006428, 0x00640f, 0x006467, 0x00646f, 0x006476, 0x00644e, 0x00652a, + 0x006495, 0x006493, 0x0064a5, 0x0064a9, 0x006488, 0x0064bc, + // plane 1 row 58 + 0x0064da, 0x0064d2, 0x0064c5, 0x0064c7, 0x0064bb, 0x0064d8, 0x0064c2, 0x0064f1, + 0x0064e7, 0x008209, 0x0064e0, 0x0064e1, 0x0062ac, 0x0064e3, 0x0064ef, 0x00652c, + 0x0064f6, 0x0064f4, 0x0064f2, 0x0064fa, 0x006500, 0x0064fd, 0x006518, 0x00651c, + 0x006505, 0x006524, 0x006523, 0x00652b, 0x006534, 0x006535, 0x006537, 0x006536, + 0x006538, 0x00754b, 0x006548, 0x006556, 0x006555, 0x00654d, 0x006558, 0x00655e, + 0x00655d, 0x006572, 0x006578, 0x006582, 0x006583, 0x008b8a, 0x00659b, 0x00659f, + 0x0065ab, 0x0065b7, 0x0065c3, 0x0065c6, 0x0065c1, 0x0065c4, 0x0065cc, 0x0065d2, + 0x0065db, 0x0065d9, 0x0065e0, 0x0065e1, 0x0065f1, 0x006772, 0x00660a, 0x006603, + 0x0065fb, 0x006773, 0x006635, 0x006636, 0x006634, 0x00661c, 0x00664f, 0x006644, + 0x006649, 0x006641, 0x00665e, 0x00665d, 0x006664, 0x006667, 0x006668, 0x00665f, + 0x006662, 0x006670, 0x006683, 0x006688, 0x00668e, 0x006689, 0x006684, 0x006698, + 0x00669d, 0x0066c1, 0x0066b9, 0x0066c9, 0x0066be, 0x0066bc, + // plane 1 row 59 + 0x0066c4, 0x0066b8, 0x0066d6, 0x0066da, 0x0066e0, 0x00663f, 0x0066e6, 0x0066e9, + 0x0066f0, 0x0066f5, 0x0066f7, 0x00670f, 0x006716, 0x00671e, 0x006726, 0x006727, + 0x009738, 0x00672e, 0x00673f, 0x006736, 0x006741, 0x006738, 0x006737, 0x006746, + 0x00675e, 0x006760, 0x006759, 0x006763, 0x006764, 0x006789, 0x006770, 0x0067a9, + 0x00677c, 0x00676a, 0x00678c, 0x00678b, 0x0067a6, 0x0067a1, 0x006785, 0x0067b7, + 0x0067ef, 0x0067b4, 0x0067ec, 0x0067b3, 0x0067e9, 0x0067b8, 0x0067e4, 0x0067de, + 0x0067dd, 0x0067e2, 0x0067ee, 0x0067b9, 0x0067ce, 0x0067c6, 0x0067e7, 0x006a9c, + 0x00681e, 0x006846, 0x006829, 0x006840, 0x00684d, 0x006832, 0x00684e, 0x0068b3, + 0x00682b, 0x006859, 0x006863, 0x006877, 0x00687f, 0x00689f, 0x00688f, 0x0068ad, + 0x006894, 0x00689d, 0x00689b, 0x006883, 0x006aae, 0x0068b9, 0x006874, 0x0068b5, + 0x0068a0, 0x0068ba, 0x00690f, 0x00688d, 0x00687e, 0x006901, 0x0068ca, 0x006908, + 0x0068d8, 0x006922, 0x006926, 0x0068e1, 0x00690c, 0x0068cd, + // plane 1 row 60 + 0x0068d4, 0x0068e7, 0x0068d5, 0x006936, 0x006912, 0x006904, 0x0068d7, 0x0068e3, + 0x006925, 0x0068f9, 0x0068e0, 0x0068ef, 0x006928, 0x00692a, 0x00691a, 0x006923, + 0x006921, 0x0068c6, 0x006979, 0x006977, 0x00695c, 0x006978, 0x00696b, 0x006954, + 0x00697e, 0x00696e, 0x006939, 0x006974, 0x00693d, 0x006959, 0x006930, 0x006961, + 0x00695e, 0x00695d, 0x006981, 0x00696a, 0x0069b2, 0x0069ae, 0x0069d0, 0x0069bf, + 0x0069c1, 0x0069d3, 0x0069be, 0x0069ce, 0x005be8, 0x0069ca, 0x0069dd, 0x0069bb, + 0x0069c3, 0x0069a7, 0x006a2e, 0x006991, 0x0069a0, 0x00699c, 0x006995, 0x0069b4, + 0x0069de, 0x0069e8, 0x006a02, 0x006a1b, 0x0069ff, 0x006b0a, 0x0069f9, 0x0069f2, + 0x0069e7, 0x006a05, 0x0069b1, 0x006a1e, 0x0069ed, 0x006a14, 0x0069eb, 0x006a0a, + 0x006a12, 0x006ac1, 0x006a23, 0x006a13, 0x006a44, 0x006a0c, 0x006a72, 0x006a36, + 0x006a78, 0x006a47, 0x006a62, 0x006a59, 0x006a66, 0x006a48, 0x006a38, 0x006a22, + 0x006a90, 0x006a8d, 0x006aa0, 0x006a84, 0x006aa2, 0x006aa3, + // plane 1 row 61 + 0x006a97, 0x008617, 0x006abb, 0x006ac3, 0x006ac2, 0x006ab8, 0x006ab3, 0x006aac, + 0x006ade, 0x006ad1, 0x006adf, 0x006aaa, 0x006ada, 0x006aea, 0x006afb, 0x006b05, + 0x008616, 0x006afa, 0x006b12, 0x006b16, 0x009b31, 0x006b1f, 0x006b38, 0x006b37, + 0x0076dc, 0x006b39, 0x0098ee, 0x006b47, 0x006b43, 0x006b49, 0x006b50, 0x006b59, + 0x006b54, 0x006b5b, 0x006b5f, 0x006b61, 0x006b78, 0x006b79, 0x006b7f, 0x006b80, + 0x006b84, 0x006b83, 0x006b8d, 0x006b98, 0x006b95, 0x006b9e, 0x006ba4, 0x006baa, + 0x006bab, 0x006baf, 0x006bb2, 0x006bb1, 0x006bb3, 0x006bb7, 0x006bbc, 0x006bc6, + 0x006bcb, 0x006bd3, 0x006bdf, 0x006bec, 0x006beb, 0x006bf3, 0x006bef, 0x009ebe, + 0x006c08, 0x006c13, 0x006c14, 0x006c1b, 0x006c24, 0x006c23, 0x006c5e, 0x006c55, + 0x006c62, 0x006c6a, 0x006c82, 0x006c8d, 0x006c9a, 0x006c81, 0x006c9b, 0x006c7e, + 0x006c68, 0x006c73, 0x006c92, 0x006c90, 0x006cc4, 0x006cf1, 0x006cd3, 0x006cbd, + 0x006cd7, 0x006cc5, 0x006cdd, 0x006cae, 0x006cb1, 0x006cbe, + // plane 1 row 62 + 0x006cba, 0x006cdb, 0x006cef, 0x006cd9, 0x006cea, 0x006d1f, 0x00884d, 0x006d36, + 0x006d2b, 0x006d3d, 0x006d38, 0x006d19, 0x006d35, 0x006d33, 0x006d12, 0x006d0c, + 0x006d63, 0x006d93, 0x006d64, 0x006d5a, 0x006d79, 0x006d59, 0x006d8e, 0x006d95, + 0x006fe4, 0x006d85, 0x006df9, 0x006e15, 0x006e0a, 0x006db5, 0x006dc7, 0x006de6, + 0x006db8, 0x006dc6, 0x006dec, 0x006dde, 0x006dcc, 0x006de8, 0x006dd2, 0x006dc5, + 0x006dfa, 0x006dd9, 0x006de4, 0x006dd5, 0x006dea, 0x006dee, 0x006e2d, 0x006e6e, + 0x006e2e, 0x006e19, 0x006e72, 0x006e5f, 0x006e3e, 0x006e23, 0x006e6b, 0x006e2b, + 0x006e76, 0x006e4d, 0x006e1f, 0x006e43, 0x006e3a, 0x006e4e, 0x006e24, 0x006eff, + 0x006e1d, 0x006e38, 0x006e82, 0x006eaa, 0x006e98, 0x006ec9, 0x006eb7, 0x006ed3, + 0x006ebd, 0x006eaf, 0x006ec4, 0x006eb2, 0x006ed4, 0x006ed5, 0x006e8f, 0x006ea5, + 0x006ec2, 0x006e9f, 0x006f41, 0x006f11, 0x00704c, 0x006eec, 0x006ef8, 0x006efe, + 0x006f3f, 0x006ef2, 0x006f31, 0x006eef, 0x006f32, 0x006ecc, + // plane 1 row 63 + 0x006f3e, 0x006f13, 0x006ef7, 0x006f86, 0x006f7a, 0x006f78, 0x006f81, 0x006f80, + 0x006f6f, 0x006f5b, 0x006ff3, 0x006f6d, 0x006f82, 0x006f7c, 0x006f58, 0x006f8e, + 0x006f91, 0x006fc2, 0x006f66, 0x006fb3, 0x006fa3, 0x006fa1, 0x006fa4, 0x006fb9, + 0x006fc6, 0x006faa, 0x006fdf, 0x006fd5, 0x006fec, 0x006fd4, 0x006fd8, 0x006ff1, + 0x006fee, 0x006fdb, 0x007009, 0x00700b, 0x006ffa, 0x007011, 0x007001, 0x00700f, + 0x006ffe, 0x00701b, 0x00701a, 0x006f74, 0x00701d, 0x007018, 0x00701f, 0x007030, + 0x00703e, 0x007032, 0x007051, 0x007063, 0x007099, 0x007092, 0x0070af, 0x0070f1, + 0x0070ac, 0x0070b8, 0x0070b3, 0x0070ae, 0x0070df, 0x0070cb, 0x0070dd, 0x0070d9, + 0x007109, 0x0070fd, 0x00711c, 0x007119, 0x007165, 0x007155, 0x007188, 0x007166, + 0x007162, 0x00714c, 0x007156, 0x00716c, 0x00718f, 0x0071fb, 0x007184, 0x007195, + 0x0071a8, 0x0071ac, 0x0071d7, 0x0071b9, 0x0071be, 0x0071d2, 0x0071c9, 0x0071d4, + 0x0071ce, 0x0071e0, 0x0071ec, 0x0071e7, 0x0071f5, 0x0071fc, + // plane 1 row 64 + 0x0071f9, 0x0071ff, 0x00720d, 0x007210, 0x00721b, 0x007228, 0x00722d, 0x00722c, + 0x007230, 0x007232, 0x00723b, 0x00723c, 0x00723f, 0x007240, 0x007246, 0x00724b, + 0x007258, 0x007274, 0x00727e, 0x007282, 0x007281, 0x007287, 0x007292, 0x007296, + 0x0072a2, 0x0072a7, 0x0072b9, 0x0072b2, 0x0072c3, 0x0072c6, 0x0072c4, 0x0072ce, + 0x0072d2, 0x0072e2, 0x0072e0, 0x0072e1, 0x0072f9, 0x0072f7, 0x00500f, 0x007317, + 0x00730a, 0x00731c, 0x007316, 0x00731d, 0x007334, 0x00732f, 0x007329, 0x007325, + 0x00733e, 0x00734e, 0x00734f, 0x009ed8, 0x007357, 0x00736a, 0x007368, 0x007370, + 0x007378, 0x007375, 0x00737b, 0x00737a, 0x0073c8, 0x0073b3, 0x0073ce, 0x0073bb, + 0x0073c0, 0x0073e5, 0x0073ee, 0x0073de, 0x0074a2, 0x007405, 0x00746f, 0x007425, + 0x0073f8, 0x007432, 0x00743a, 0x007455, 0x00743f, 0x00745f, 0x007459, 0x007441, + 0x00745c, 0x007469, 0x007470, 0x007463, 0x00746a, 0x007476, 0x00747e, 0x00748b, + 0x00749e, 0x0074a7, 0x0074ca, 0x0074cf, 0x0074d4, 0x0073f1, + // plane 1 row 65 + 0x0074e0, 0x0074e3, 0x0074e7, 0x0074e9, 0x0074ee, 0x0074f2, 0x0074f0, 0x0074f1, + 0x0074f8, 0x0074f7, 0x007504, 0x007503, 0x007505, 0x00750c, 0x00750e, 0x00750d, + 0x007515, 0x007513, 0x00751e, 0x007526, 0x00752c, 0x00753c, 0x007544, 0x00754d, + 0x00754a, 0x007549, 0x00755b, 0x007546, 0x00755a, 0x007569, 0x007564, 0x007567, + 0x00756b, 0x00756d, 0x007578, 0x007576, 0x007586, 0x007587, 0x007574, 0x00758a, + 0x007589, 0x007582, 0x007594, 0x00759a, 0x00759d, 0x0075a5, 0x0075a3, 0x0075c2, + 0x0075b3, 0x0075c3, 0x0075b5, 0x0075bd, 0x0075b8, 0x0075bc, 0x0075b1, 0x0075cd, + 0x0075ca, 0x0075d2, 0x0075d9, 0x0075e3, 0x0075de, 0x0075fe, 0x0075ff, 0x0075fc, + 0x007601, 0x0075f0, 0x0075fa, 0x0075f2, 0x0075f3, 0x00760b, 0x00760d, 0x007609, + 0x00761f, 0x007627, 0x007620, 0x007621, 0x007622, 0x007624, 0x007634, 0x007630, + 0x00763b, 0x007647, 0x007648, 0x007646, 0x00765c, 0x007658, 0x007661, 0x007662, + 0x007668, 0x007669, 0x00766a, 0x007667, 0x00766c, 0x007670, + // plane 1 row 66 + 0x007672, 0x007676, 0x007678, 0x00767c, 0x007680, 0x007683, 0x007688, 0x00768b, + 0x00768e, 0x007696, 0x007693, 0x007699, 0x00769a, 0x0076b0, 0x0076b4, 0x0076b8, + 0x0076b9, 0x0076ba, 0x0076c2, 0x0076cd, 0x0076d6, 0x0076d2, 0x0076de, 0x0076e1, + 0x0076e5, 0x0076e7, 0x0076ea, 0x00862f, 0x0076fb, 0x007708, 0x007707, 0x007704, + 0x007729, 0x007724, 0x00771e, 0x007725, 0x007726, 0x00771b, 0x007737, 0x007738, + 0x007747, 0x00775a, 0x007768, 0x00776b, 0x00775b, 0x007765, 0x00777f, 0x00777e, + 0x007779, 0x00778e, 0x00778b, 0x007791, 0x0077a0, 0x00779e, 0x0077b0, 0x0077b6, + 0x0077b9, 0x0077bf, 0x0077bc, 0x0077bd, 0x0077bb, 0x0077c7, 0x0077cd, 0x0077d7, + 0x0077da, 0x0077dc, 0x0077e3, 0x0077ee, 0x0077fc, 0x00780c, 0x007812, 0x007926, + 0x007820, 0x00792a, 0x007845, 0x00788e, 0x007874, 0x007886, 0x00787c, 0x00789a, + 0x00788c, 0x0078a3, 0x0078b5, 0x0078aa, 0x0078af, 0x0078d1, 0x0078c6, 0x0078cb, + 0x0078d4, 0x0078be, 0x0078bc, 0x0078c5, 0x0078ca, 0x0078ec, + // plane 1 row 67 + 0x0078e7, 0x0078da, 0x0078fd, 0x0078f4, 0x007907, 0x007912, 0x007911, 0x007919, + 0x00792c, 0x00792b, 0x007940, 0x007960, 0x007957, 0x00795f, 0x00795a, 0x007955, + 0x007953, 0x00797a, 0x00797f, 0x00798a, 0x00799d, 0x0079a7, 0x009f4b, 0x0079aa, + 0x0079ae, 0x0079b3, 0x0079b9, 0x0079ba, 0x0079c9, 0x0079d5, 0x0079e7, 0x0079ec, + 0x0079e1, 0x0079e3, 0x007a08, 0x007a0d, 0x007a18, 0x007a19, 0x007a20, 0x007a1f, + 0x007980, 0x007a31, 0x007a3b, 0x007a3e, 0x007a37, 0x007a43, 0x007a57, 0x007a49, + 0x007a61, 0x007a62, 0x007a69, 0x009f9d, 0x007a70, 0x007a79, 0x007a7d, 0x007a88, + 0x007a97, 0x007a95, 0x007a98, 0x007a96, 0x007aa9, 0x007ac8, 0x007ab0, 0x007ab6, + 0x007ac5, 0x007ac4, 0x007abf, 0x009083, 0x007ac7, 0x007aca, 0x007acd, 0x007acf, + 0x007ad5, 0x007ad3, 0x007ad9, 0x007ada, 0x007add, 0x007ae1, 0x007ae2, 0x007ae6, + 0x007aed, 0x007af0, 0x007b02, 0x007b0f, 0x007b0a, 0x007b06, 0x007b33, 0x007b18, + 0x007b19, 0x007b1e, 0x007b35, 0x007b28, 0x007b36, 0x007b50, + // plane 1 row 68 + 0x007b7a, 0x007b04, 0x007b4d, 0x007b0b, 0x007b4c, 0x007b45, 0x007b75, 0x007b65, + 0x007b74, 0x007b67, 0x007b70, 0x007b71, 0x007b6c, 0x007b6e, 0x007b9d, 0x007b98, + 0x007b9f, 0x007b8d, 0x007b9c, 0x007b9a, 0x007b8b, 0x007b92, 0x007b8f, 0x007b5d, + 0x007b99, 0x007bcb, 0x007bc1, 0x007bcc, 0x007bcf, 0x007bb4, 0x007bc6, 0x007bdd, + 0x007be9, 0x007c11, 0x007c14, 0x007be6, 0x007be5, 0x007c60, 0x007c00, 0x007c07, + 0x007c13, 0x007bf3, 0x007bf7, 0x007c17, 0x007c0d, 0x007bf6, 0x007c23, 0x007c27, + 0x007c2a, 0x007c1f, 0x007c37, 0x007c2b, 0x007c3d, 0x007c4c, 0x007c43, 0x007c54, + 0x007c4f, 0x007c40, 0x007c50, 0x007c58, 0x007c5f, 0x007c64, 0x007c56, 0x007c65, + 0x007c6c, 0x007c75, 0x007c83, 0x007c90, 0x007ca4, 0x007cad, 0x007ca2, 0x007cab, + 0x007ca1, 0x007ca8, 0x007cb3, 0x007cb2, 0x007cb1, 0x007cae, 0x007cb9, 0x007cbd, + 0x007cc0, 0x007cc5, 0x007cc2, 0x007cd8, 0x007cd2, 0x007cdc, 0x007ce2, 0x009b3b, + 0x007cef, 0x007cf2, 0x007cf4, 0x007cf6, 0x007cfa, 0x007d06, + // plane 1 row 69 + 0x007d02, 0x007d1c, 0x007d15, 0x007d0a, 0x007d45, 0x007d4b, 0x007d2e, 0x007d32, + 0x007d3f, 0x007d35, 0x007d46, 0x007d73, 0x007d56, 0x007d4e, 0x007d72, 0x007d68, + 0x007d6e, 0x007d4f, 0x007d63, 0x007d93, 0x007d89, 0x007d5b, 0x007d8f, 0x007d7d, + 0x007d9b, 0x007dba, 0x007dae, 0x007da3, 0x007db5, 0x007dc7, 0x007dbd, 0x007dab, + 0x007e3d, 0x007da2, 0x007daf, 0x007ddc, 0x007db8, 0x007d9f, 0x007db0, 0x007dd8, + 0x007ddd, 0x007de4, 0x007dde, 0x007dfb, 0x007df2, 0x007de1, 0x007e05, 0x007e0a, + 0x007e23, 0x007e21, 0x007e12, 0x007e31, 0x007e1f, 0x007e09, 0x007e0b, 0x007e22, + 0x007e46, 0x007e66, 0x007e3b, 0x007e35, 0x007e39, 0x007e43, 0x007e37, 0x007e32, + 0x007e3a, 0x007e67, 0x007e5d, 0x007e56, 0x007e5e, 0x007e59, 0x007e5a, 0x007e79, + 0x007e6a, 0x007e69, 0x007e7c, 0x007e7b, 0x007e83, 0x007dd5, 0x007e7d, 0x008fae, + 0x007e7f, 0x007e88, 0x007e89, 0x007e8c, 0x007e92, 0x007e90, 0x007e93, 0x007e94, + 0x007e96, 0x007e8e, 0x007e9b, 0x007e9c, 0x007f38, 0x007f3a, + // plane 1 row 70 + 0x007f45, 0x007f4c, 0x007f4d, 0x007f4e, 0x007f50, 0x007f51, 0x007f55, 0x007f54, + 0x007f58, 0x007f5f, 0x007f60, 0x007f68, 0x007f69, 0x007f67, 0x007f78, 0x007f82, + 0x007f86, 0x007f83, 0x007f88, 0x007f87, 0x007f8c, 0x007f94, 0x007f9e, 0x007f9d, + 0x007f9a, 0x007fa3, 0x007faf, 0x007fb2, 0x007fb9, 0x007fae, 0x007fb6, 0x007fb8, + 0x008b71, 0x007fc5, 0x007fc6, 0x007fca, 0x007fd5, 0x007fd4, 0x007fe1, 0x007fe6, + 0x007fe9, 0x007ff3, 0x007ff9, 0x0098dc, 0x008006, 0x008004, 0x00800b, 0x008012, + 0x008018, 0x008019, 0x00801c, 0x008021, 0x008028, 0x00803f, 0x00803b, 0x00804a, + 0x008046, 0x008052, 0x008058, 0x00805a, 0x00805f, 0x008062, 0x008068, 0x008073, + 0x008072, 0x008070, 0x008076, 0x008079, 0x00807d, 0x00807f, 0x008084, 0x008086, + 0x008085, 0x00809b, 0x008093, 0x00809a, 0x0080ad, 0x005190, 0x0080ac, 0x0080db, + 0x0080e5, 0x0080d9, 0x0080dd, 0x0080c4, 0x0080da, 0x0080d6, 0x008109, 0x0080ef, + 0x0080f1, 0x00811b, 0x008129, 0x008123, 0x00812f, 0x00814b, + // plane 1 row 71 + 0x00968b, 0x008146, 0x00813e, 0x008153, 0x008151, 0x0080fc, 0x008171, 0x00816e, + 0x008165, 0x008166, 0x008174, 0x008183, 0x008188, 0x00818a, 0x008180, 0x008182, + 0x0081a0, 0x008195, 0x0081a4, 0x0081a3, 0x00815f, 0x008193, 0x0081a9, 0x0081b0, + 0x0081b5, 0x0081be, 0x0081b8, 0x0081bd, 0x0081c0, 0x0081c2, 0x0081ba, 0x0081c9, + 0x0081cd, 0x0081d1, 0x0081d9, 0x0081d8, 0x0081c8, 0x0081da, 0x0081df, 0x0081e0, + 0x0081e7, 0x0081fa, 0x0081fb, 0x0081fe, 0x008201, 0x008202, 0x008205, 0x008207, + 0x00820a, 0x00820d, 0x008210, 0x008216, 0x008229, 0x00822b, 0x008238, 0x008233, + 0x008240, 0x008259, 0x008258, 0x00825d, 0x00825a, 0x00825f, 0x008264, 0x008262, + 0x008268, 0x00826a, 0x00826b, 0x00822e, 0x008271, 0x008277, 0x008278, 0x00827e, + 0x00828d, 0x008292, 0x0082ab, 0x00829f, 0x0082bb, 0x0082ac, 0x0082e1, 0x0082e3, + 0x0082df, 0x0082d2, 0x0082f4, 0x0082f3, 0x0082fa, 0x008393, 0x008303, 0x0082fb, + 0x0082f9, 0x0082de, 0x008306, 0x0082dc, 0x008309, 0x0082d9, + // plane 1 row 72 + 0x008335, 0x008334, 0x008316, 0x008332, 0x008331, 0x008340, 0x008339, 0x008350, + 0x008345, 0x00832f, 0x00832b, 0x008317, 0x008318, 0x008385, 0x00839a, 0x0083aa, + 0x00839f, 0x0083a2, 0x008396, 0x008323, 0x00838e, 0x008387, 0x00838a, 0x00837c, + 0x0083b5, 0x008373, 0x008375, 0x0083a0, 0x008389, 0x0083a8, 0x0083f4, 0x008413, + 0x0083eb, 0x0083ce, 0x0083fd, 0x008403, 0x0083d8, 0x00840b, 0x0083c1, 0x0083f7, + 0x008407, 0x0083e0, 0x0083f2, 0x00840d, 0x008422, 0x008420, 0x0083bd, 0x008438, + 0x008506, 0x0083fb, 0x00846d, 0x00842a, 0x00843c, 0x00855a, 0x008484, 0x008477, + 0x00846b, 0x0084ad, 0x00846e, 0x008482, 0x008469, 0x008446, 0x00842c, 0x00846f, + 0x008479, 0x008435, 0x0084ca, 0x008462, 0x0084b9, 0x0084bf, 0x00849f, 0x0084d9, + 0x0084cd, 0x0084bb, 0x0084da, 0x0084d0, 0x0084c1, 0x0084c6, 0x0084d6, 0x0084a1, + 0x008521, 0x0084ff, 0x0084f4, 0x008517, 0x008518, 0x00852c, 0x00851f, 0x008515, + 0x008514, 0x0084fc, 0x008540, 0x008563, 0x008558, 0x008548, + // plane 1 row 73 + 0x008541, 0x008602, 0x00854b, 0x008555, 0x008580, 0x0085a4, 0x008588, 0x008591, + 0x00858a, 0x0085a8, 0x00856d, 0x008594, 0x00859b, 0x0085ea, 0x008587, 0x00859c, + 0x008577, 0x00857e, 0x008590, 0x0085c9, 0x0085ba, 0x0085cf, 0x0085b9, 0x0085d0, + 0x0085d5, 0x0085dd, 0x0085e5, 0x0085dc, 0x0085f9, 0x00860a, 0x008613, 0x00860b, + 0x0085fe, 0x0085fa, 0x008606, 0x008622, 0x00861a, 0x008630, 0x00863f, 0x00864d, + 0x004e55, 0x008654, 0x00865f, 0x008667, 0x008671, 0x008693, 0x0086a3, 0x0086a9, + 0x0086aa, 0x00868b, 0x00868c, 0x0086b6, 0x0086af, 0x0086c4, 0x0086c6, 0x0086b0, + 0x0086c9, 0x008823, 0x0086ab, 0x0086d4, 0x0086de, 0x0086e9, 0x0086ec, 0x0086df, + 0x0086db, 0x0086ef, 0x008712, 0x008706, 0x008708, 0x008700, 0x008703, 0x0086fb, + 0x008711, 0x008709, 0x00870d, 0x0086f9, 0x00870a, 0x008734, 0x00873f, 0x008737, + 0x00873b, 0x008725, 0x008729, 0x00871a, 0x008760, 0x00875f, 0x008778, 0x00874c, + 0x00874e, 0x008774, 0x008757, 0x008768, 0x00876e, 0x008759, + // plane 1 row 74 + 0x008753, 0x008763, 0x00876a, 0x008805, 0x0087a2, 0x00879f, 0x008782, 0x0087af, + 0x0087cb, 0x0087bd, 0x0087c0, 0x0087d0, 0x0096d6, 0x0087ab, 0x0087c4, 0x0087b3, + 0x0087c7, 0x0087c6, 0x0087bb, 0x0087ef, 0x0087f2, 0x0087e0, 0x00880f, 0x00880d, + 0x0087fe, 0x0087f6, 0x0087f7, 0x00880e, 0x0087d2, 0x008811, 0x008816, 0x008815, + 0x008822, 0x008821, 0x008831, 0x008836, 0x008839, 0x008827, 0x00883b, 0x008844, + 0x008842, 0x008852, 0x008859, 0x00885e, 0x008862, 0x00886b, 0x008881, 0x00887e, + 0x00889e, 0x008875, 0x00887d, 0x0088b5, 0x008872, 0x008882, 0x008897, 0x008892, + 0x0088ae, 0x008899, 0x0088a2, 0x00888d, 0x0088a4, 0x0088b0, 0x0088bf, 0x0088b1, + 0x0088c3, 0x0088c4, 0x0088d4, 0x0088d8, 0x0088d9, 0x0088dd, 0x0088f9, 0x008902, + 0x0088fc, 0x0088f4, 0x0088e8, 0x0088f2, 0x008904, 0x00890c, 0x00890a, 0x008913, + 0x008943, 0x00891e, 0x008925, 0x00892a, 0x00892b, 0x008941, 0x008944, 0x00893b, + 0x008936, 0x008938, 0x00894c, 0x00891d, 0x008960, 0x00895e, + // plane 1 row 75 + 0x008966, 0x008964, 0x00896d, 0x00896a, 0x00896f, 0x008974, 0x008977, 0x00897e, + 0x008983, 0x008988, 0x00898a, 0x008993, 0x008998, 0x0089a1, 0x0089a9, 0x0089a6, + 0x0089ac, 0x0089af, 0x0089b2, 0x0089ba, 0x0089bd, 0x0089bf, 0x0089c0, 0x0089da, + 0x0089dc, 0x0089dd, 0x0089e7, 0x0089f4, 0x0089f8, 0x008a03, 0x008a16, 0x008a10, + 0x008a0c, 0x008a1b, 0x008a1d, 0x008a25, 0x008a36, 0x008a41, 0x008a5b, 0x008a52, + 0x008a46, 0x008a48, 0x008a7c, 0x008a6d, 0x008a6c, 0x008a62, 0x008a85, 0x008a82, + 0x008a84, 0x008aa8, 0x008aa1, 0x008a91, 0x008aa5, 0x008aa6, 0x008a9a, 0x008aa3, + 0x008ac4, 0x008acd, 0x008ac2, 0x008ada, 0x008aeb, 0x008af3, 0x008ae7, 0x008ae4, + 0x008af1, 0x008b14, 0x008ae0, 0x008ae2, 0x008af7, 0x008ade, 0x008adb, 0x008b0c, + 0x008b07, 0x008b1a, 0x008ae1, 0x008b16, 0x008b10, 0x008b17, 0x008b20, 0x008b33, + 0x0097ab, 0x008b26, 0x008b2b, 0x008b3e, 0x008b28, 0x008b41, 0x008b4c, 0x008b4f, + 0x008b4e, 0x008b49, 0x008b56, 0x008b5b, 0x008b5a, 0x008b6b, + // plane 1 row 76 + 0x008b5f, 0x008b6c, 0x008b6f, 0x008b74, 0x008b7d, 0x008b80, 0x008b8c, 0x008b8e, + 0x008b92, 0x008b93, 0x008b96, 0x008b99, 0x008b9a, 0x008c3a, 0x008c41, 0x008c3f, + 0x008c48, 0x008c4c, 0x008c4e, 0x008c50, 0x008c55, 0x008c62, 0x008c6c, 0x008c78, + 0x008c7a, 0x008c82, 0x008c89, 0x008c85, 0x008c8a, 0x008c8d, 0x008c8e, 0x008c94, + 0x008c7c, 0x008c98, 0x00621d, 0x008cad, 0x008caa, 0x008cbd, 0x008cb2, 0x008cb3, + 0x008cae, 0x008cb6, 0x008cc8, 0x008cc1, 0x008ce4, 0x008ce3, 0x008cda, 0x008cfd, + 0x008cfa, 0x008cfb, 0x008d04, 0x008d05, 0x008d0a, 0x008d07, 0x008d0f, 0x008d0d, + 0x008d10, 0x009f4e, 0x008d13, 0x008ccd, 0x008d14, 0x008d16, 0x008d67, 0x008d6d, + 0x008d71, 0x008d73, 0x008d81, 0x008d99, 0x008dc2, 0x008dbe, 0x008dba, 0x008dcf, + 0x008dda, 0x008dd6, 0x008dcc, 0x008ddb, 0x008dcb, 0x008dea, 0x008deb, 0x008ddf, + 0x008de3, 0x008dfc, 0x008e08, 0x008e09, 0x008dff, 0x008e1d, 0x008e1e, 0x008e10, + 0x008e1f, 0x008e42, 0x008e35, 0x008e30, 0x008e34, 0x008e4a, + // plane 1 row 77 + 0x008e47, 0x008e49, 0x008e4c, 0x008e50, 0x008e48, 0x008e59, 0x008e64, 0x008e60, + 0x008e2a, 0x008e63, 0x008e55, 0x008e76, 0x008e72, 0x008e7c, 0x008e81, 0x008e87, + 0x008e85, 0x008e84, 0x008e8b, 0x008e8a, 0x008e93, 0x008e91, 0x008e94, 0x008e99, + 0x008eaa, 0x008ea1, 0x008eac, 0x008eb0, 0x008ec6, 0x008eb1, 0x008ebe, 0x008ec5, + 0x008ec8, 0x008ecb, 0x008edb, 0x008ee3, 0x008efc, 0x008efb, 0x008eeb, 0x008efe, + 0x008f0a, 0x008f05, 0x008f15, 0x008f12, 0x008f19, 0x008f13, 0x008f1c, 0x008f1f, + 0x008f1b, 0x008f0c, 0x008f26, 0x008f33, 0x008f3b, 0x008f39, 0x008f45, 0x008f42, + 0x008f3e, 0x008f4c, 0x008f49, 0x008f46, 0x008f4e, 0x008f57, 0x008f5c, 0x008f62, + 0x008f63, 0x008f64, 0x008f9c, 0x008f9f, 0x008fa3, 0x008fad, 0x008faf, 0x008fb7, + 0x008fda, 0x008fe5, 0x008fe2, 0x008fea, 0x008fef, 0x009087, 0x008ff4, 0x009005, + 0x008ff9, 0x008ffa, 0x009011, 0x009015, 0x009021, 0x00900d, 0x00901e, 0x009016, + 0x00900b, 0x009027, 0x009036, 0x009035, 0x009039, 0x008ff8, + // plane 1 row 78 + 0x00904f, 0x009050, 0x009051, 0x009052, 0x00900e, 0x009049, 0x00903e, 0x009056, + 0x009058, 0x00905e, 0x009068, 0x00906f, 0x009076, 0x0096a8, 0x009072, 0x009082, + 0x00907d, 0x009081, 0x009080, 0x00908a, 0x009089, 0x00908f, 0x0090a8, 0x0090af, + 0x0090b1, 0x0090b5, 0x0090e2, 0x0090e4, 0x006248, 0x0090db, 0x009102, 0x009112, + 0x009119, 0x009132, 0x009130, 0x00914a, 0x009156, 0x009158, 0x009163, 0x009165, + 0x009169, 0x009173, 0x009172, 0x00918b, 0x009189, 0x009182, 0x0091a2, 0x0091ab, + 0x0091af, 0x0091aa, 0x0091b5, 0x0091b4, 0x0091ba, 0x0091c0, 0x0091c1, 0x0091c9, + 0x0091cb, 0x0091d0, 0x0091d6, 0x0091df, 0x0091e1, 0x0091db, 0x0091fc, 0x0091f5, + 0x0091f6, 0x00921e, 0x0091ff, 0x009214, 0x00922c, 0x009215, 0x009211, 0x00925e, + 0x009257, 0x009245, 0x009249, 0x009264, 0x009248, 0x009295, 0x00923f, 0x00924b, + 0x009250, 0x00929c, 0x009296, 0x009293, 0x00929b, 0x00925a, 0x0092cf, 0x0092b9, + 0x0092b7, 0x0092e9, 0x00930f, 0x0092fa, 0x009344, 0x00932e, + // plane 1 row 79 + 0x009319, 0x009322, 0x00931a, 0x009323, 0x00933a, 0x009335, 0x00933b, 0x00935c, + 0x009360, 0x00937c, 0x00936e, 0x009356, 0x0093b0, 0x0093ac, 0x0093ad, 0x009394, + 0x0093b9, 0x0093d6, 0x0093d7, 0x0093e8, 0x0093e5, 0x0093d8, 0x0093c3, 0x0093dd, + 0x0093d0, 0x0093c8, 0x0093e4, 0x00941a, 0x009414, 0x009413, 0x009403, 0x009407, + 0x009410, 0x009436, 0x00942b, 0x009435, 0x009421, 0x00943a, 0x009441, 0x009452, + 0x009444, 0x00945b, 0x009460, 0x009462, 0x00945e, 0x00946a, 0x009229, 0x009470, + 0x009475, 0x009477, 0x00947d, 0x00945a, 0x00947c, 0x00947e, 0x009481, 0x00947f, + 0x009582, 0x009587, 0x00958a, 0x009594, 0x009596, 0x009598, 0x009599, 0x0095a0, + 0x0095a8, 0x0095a7, 0x0095ad, 0x0095bc, 0x0095bb, 0x0095b9, 0x0095be, 0x0095ca, + 0x006ff6, 0x0095c3, 0x0095cd, 0x0095cc, 0x0095d5, 0x0095d4, 0x0095d6, 0x0095dc, + 0x0095e1, 0x0095e5, 0x0095e2, 0x009621, 0x009628, 0x00962e, 0x00962f, 0x009642, + 0x00964c, 0x00964f, 0x00964b, 0x009677, 0x00965c, 0x00965e, + // plane 1 row 80 + 0x00965d, 0x00965f, 0x009666, 0x009672, 0x00966c, 0x00968d, 0x009698, 0x009695, + 0x009697, 0x0096aa, 0x0096a7, 0x0096b1, 0x0096b2, 0x0096b0, 0x0096b4, 0x0096b6, + 0x0096b8, 0x0096b9, 0x0096ce, 0x0096cb, 0x0096c9, 0x0096cd, 0x00894d, 0x0096dc, + 0x00970d, 0x0096d5, 0x0096f9, 0x009704, 0x009706, 0x009708, 0x009713, 0x00970e, + 0x009711, 0x00970f, 0x009716, 0x009719, 0x009724, 0x00972a, 0x009730, 0x009739, + 0x00973d, 0x00973e, 0x009744, 0x009746, 0x009748, 0x009742, 0x009749, 0x00975c, + 0x009760, 0x009764, 0x009766, 0x009768, 0x0052d2, 0x00976b, 0x009771, 0x009779, + 0x009785, 0x00977c, 0x009781, 0x00977a, 0x009786, 0x00978b, 0x00978f, 0x009790, + 0x00979c, 0x0097a8, 0x0097a6, 0x0097a3, 0x0097b3, 0x0097b4, 0x0097c3, 0x0097c6, + 0x0097c8, 0x0097cb, 0x0097dc, 0x0097ed, 0x009f4f, 0x0097f2, 0x007adf, 0x0097f6, + 0x0097f5, 0x00980f, 0x00980c, 0x009838, 0x009824, 0x009821, 0x009837, 0x00983d, + 0x009846, 0x00984f, 0x00984b, 0x00986b, 0x00986f, 0x009870, + // plane 1 row 81 + 0x009871, 0x009874, 0x009873, 0x0098aa, 0x0098af, 0x0098b1, 0x0098b6, 0x0098c4, + 0x0098c3, 0x0098c6, 0x0098e9, 0x0098eb, 0x009903, 0x009909, 0x009912, 0x009914, + 0x009918, 0x009921, 0x00991d, 0x00991e, 0x009924, 0x009920, 0x00992c, 0x00992e, + 0x00993d, 0x00993e, 0x009942, 0x009949, 0x009945, 0x009950, 0x00994b, 0x009951, + 0x009952, 0x00994c, 0x009955, 0x009997, 0x009998, 0x0099a5, 0x0099ad, 0x0099ae, + 0x0099bc, 0x0099df, 0x0099db, 0x0099dd, 0x0099d8, 0x0099d1, 0x0099ed, 0x0099ee, + 0x0099f1, 0x0099f2, 0x0099fb, 0x0099f8, 0x009a01, 0x009a0f, 0x009a05, 0x0099e2, + 0x009a19, 0x009a2b, 0x009a37, 0x009a45, 0x009a42, 0x009a40, 0x009a43, 0x009a3e, + 0x009a55, 0x009a4d, 0x009a5b, 0x009a57, 0x009a5f, 0x009a62, 0x009a65, 0x009a64, + 0x009a69, 0x009a6b, 0x009a6a, 0x009aad, 0x009ab0, 0x009abc, 0x009ac0, 0x009acf, + 0x009ad1, 0x009ad3, 0x009ad4, 0x009ade, 0x009adf, 0x009ae2, 0x009ae3, 0x009ae6, + 0x009aef, 0x009aeb, 0x009aee, 0x009af4, 0x009af1, 0x009af7, + // plane 1 row 82 + 0x009afb, 0x009b06, 0x009b18, 0x009b1a, 0x009b1f, 0x009b22, 0x009b23, 0x009b25, + 0x009b27, 0x009b28, 0x009b29, 0x009b2a, 0x009b2e, 0x009b2f, 0x009b32, 0x009b44, + 0x009b43, 0x009b4f, 0x009b4d, 0x009b4e, 0x009b51, 0x009b58, 0x009b74, 0x009b93, + 0x009b83, 0x009b91, 0x009b96, 0x009b97, 0x009b9f, 0x009ba0, 0x009ba8, 0x009bb4, + 0x009bc0, 0x009bca, 0x009bb9, 0x009bc6, 0x009bcf, 0x009bd1, 0x009bd2, 0x009be3, + 0x009be2, 0x009be4, 0x009bd4, 0x009be1, 0x009c3a, 0x009bf2, 0x009bf1, 0x009bf0, + 0x009c15, 0x009c14, 0x009c09, 0x009c13, 0x009c0c, 0x009c06, 0x009c08, 0x009c12, + 0x009c0a, 0x009c04, 0x009c2e, 0x009c1b, 0x009c25, 0x009c24, 0x009c21, 0x009c30, + 0x009c47, 0x009c32, 0x009c46, 0x009c3e, 0x009c5a, 0x009c60, 0x009c67, 0x009c76, + 0x009c78, 0x009ce7, 0x009cec, 0x009cf0, 0x009d09, 0x009d08, 0x009ceb, 0x009d03, + 0x009d06, 0x009d2a, 0x009d26, 0x009daf, 0x009d23, 0x009d1f, 0x009d44, 0x009d15, + 0x009d12, 0x009d41, 0x009d3f, 0x009d3e, 0x009d46, 0x009d48, + // plane 1 row 83 + 0x009d5d, 0x009d5e, 0x009d64, 0x009d51, 0x009d50, 0x009d59, 0x009d72, 0x009d89, + 0x009d87, 0x009dab, 0x009d6f, 0x009d7a, 0x009d9a, 0x009da4, 0x009da9, 0x009db2, + 0x009dc4, 0x009dc1, 0x009dbb, 0x009db8, 0x009dba, 0x009dc6, 0x009dcf, 0x009dc2, + 0x009dd9, 0x009dd3, 0x009df8, 0x009de6, 0x009ded, 0x009def, 0x009dfd, 0x009e1a, + 0x009e1b, 0x009e1e, 0x009e75, 0x009e79, 0x009e7d, 0x009e81, 0x009e88, 0x009e8b, + 0x009e8c, 0x009e92, 0x009e95, 0x009e91, 0x009e9d, 0x009ea5, 0x009ea9, 0x009eb8, + 0x009eaa, 0x009ead, 0x009761, 0x009ecc, 0x009ece, 0x009ecf, 0x009ed0, 0x009ed4, + 0x009edc, 0x009ede, 0x009edd, 0x009ee0, 0x009ee5, 0x009ee8, 0x009eef, 0x009ef4, + 0x009ef6, 0x009ef7, 0x009ef9, 0x009efb, 0x009efc, 0x009efd, 0x009f07, 0x009f08, + 0x0076b7, 0x009f15, 0x009f21, 0x009f2c, 0x009f3e, 0x009f4a, 0x009f52, 0x009f54, + 0x009f63, 0x009f5f, 0x009f60, 0x009f61, 0x009f66, 0x009f67, 0x009f6c, 0x009f6a, + 0x009f77, 0x009f72, 0x009f76, 0x009f95, 0x009f9c, 0x009fa0, + // plane 1 row 84 + 0x00582f, 0x0069c7, 0x009059, 0x007464, 0x0051dc, 0x007199, 0x005653, 0x005de2, + 0x005e14, 0x005e18, 0x005e58, 0x005e5e, 0x005ebe, 0x00f928, 0x005ecb, 0x005ef9, + 0x005f00, 0x005f02, 0x005f07, 0x005f1d, 0x005f23, 0x005f34, 0x005f36, 0x005f3d, + 0x005f40, 0x005f45, 0x005f54, 0x005f58, 0x005f64, 0x005f67, 0x005f7d, 0x005f89, + 0x005f9c, 0x005fa7, 0x005faf, 0x005fb5, 0x005fb7, 0x005fc9, 0x005fde, 0x005fe1, + 0x005fe9, 0x00600d, 0x006014, 0x006018, 0x006033, 0x006035, 0x006047, 0x00fa3d, + 0x00609d, 0x00609e, 0x0060cb, 0x0060d4, 0x0060d5, 0x0060dd, 0x0060f8, 0x00611c, + 0x00612b, 0x006130, 0x006137, 0x00fa3e, 0x00618d, 0x00fa3f, 0x0061bc, 0x0061b9, + 0x00fa40, 0x006222, 0x00623e, 0x006243, 0x006256, 0x00625a, 0x00626f, 0x006285, + 0x0062c4, 0x0062d6, 0x0062fc, 0x00630a, 0x006318, 0x006339, 0x006343, 0x006365, + 0x00637c, 0x0063e5, 0x0063ed, 0x0063f5, 0x006410, 0x006414, 0x006422, 0x006479, + 0x006451, 0x006460, 0x00646d, 0x0064ce, 0x0064be, 0x0064bf, + // plane 1 row 85 + 0x0064c4, 0x0064ca, 0x0064d0, 0x0064f7, 0x0064fb, 0x006522, 0x006529, 0x00fa41, + 0x006567, 0x00659d, 0x00fa42, 0x006600, 0x006609, 0x006615, 0x00661e, 0x00663a, + 0x006622, 0x006624, 0x00662b, 0x006630, 0x006631, 0x006633, 0x0066fb, 0x006648, + 0x00664c, 0x0231c4, 0x006659, 0x00665a, 0x006661, 0x006665, 0x006673, 0x006677, + 0x006678, 0x00668d, 0x00fa43, 0x0066a0, 0x0066b2, 0x0066bb, 0x0066c6, 0x0066c8, + 0x003b22, 0x0066db, 0x0066e8, 0x0066fa, 0x006713, 0x00f929, 0x006733, 0x006766, + 0x006747, 0x006748, 0x00677b, 0x006781, 0x006793, 0x006798, 0x00679b, 0x0067bb, + 0x0067f9, 0x0067c0, 0x0067d7, 0x0067fc, 0x006801, 0x006852, 0x00681d, 0x00682c, + 0x006831, 0x00685b, 0x006872, 0x006875, 0x00fa44, 0x0068a3, 0x0068a5, 0x0068b2, + 0x0068c8, 0x0068d0, 0x0068e8, 0x0068ed, 0x0068f0, 0x0068f1, 0x0068fc, 0x00690a, + 0x006949, 0x0235c4, 0x006935, 0x006942, 0x006957, 0x006963, 0x006964, 0x006968, + 0x006980, 0x00fa14, 0x0069a5, 0x0069ad, 0x0069cf, 0x003bb6, + // plane 1 row 86 + 0x003bc3, 0x0069e2, 0x0069e9, 0x0069ea, 0x0069f5, 0x0069f6, 0x006a0f, 0x006a15, + 0x02373f, 0x006a3b, 0x006a3e, 0x006a45, 0x006a50, 0x006a56, 0x006a5b, 0x006a6b, + 0x006a73, 0x023763, 0x006a89, 0x006a94, 0x006a9d, 0x006a9e, 0x006aa5, 0x006ae4, + 0x006ae7, 0x003c0f, 0x00f91d, 0x006b1b, 0x006b1e, 0x006b2c, 0x006b35, 0x006b46, + 0x006b56, 0x006b60, 0x006b65, 0x006b67, 0x006b77, 0x006b82, 0x006ba9, 0x006bad, + 0x00f970, 0x006bcf, 0x006bd6, 0x006bd7, 0x006bff, 0x006c05, 0x006c10, 0x006c33, + 0x006c59, 0x006c5c, 0x006caa, 0x006c74, 0x006c76, 0x006c85, 0x006c86, 0x006c98, + 0x006c9c, 0x006cfb, 0x006cc6, 0x006cd4, 0x006ce0, 0x006ceb, 0x006cee, 0x023cfe, + 0x006d04, 0x006d0e, 0x006d2e, 0x006d31, 0x006d39, 0x006d3f, 0x006d58, 0x006d65, + 0x00fa45, 0x006d82, 0x006d87, 0x006d89, 0x006d94, 0x006daa, 0x006dac, 0x006dbf, + 0x006dc4, 0x006dd6, 0x006dda, 0x006ddb, 0x006ddd, 0x006dfc, 0x00fa46, 0x006e34, + 0x006e44, 0x006e5c, 0x006e5e, 0x006eab, 0x006eb1, 0x006ec1, + // plane 1 row 87 + 0x006ec7, 0x006ece, 0x006f10, 0x006f1a, 0x00fa47, 0x006f2a, 0x006f2f, 0x006f33, + 0x006f51, 0x006f59, 0x006f5e, 0x006f61, 0x006f62, 0x006f7e, 0x006f88, 0x006f8c, + 0x006f8d, 0x006f94, 0x006fa0, 0x006fa7, 0x006fb6, 0x006fbc, 0x006fc7, 0x006fca, + 0x006ff9, 0x006ff0, 0x006ff5, 0x007005, 0x007006, 0x007028, 0x00704a, 0x00705d, + 0x00705e, 0x00704e, 0x007064, 0x007075, 0x007085, 0x0070a4, 0x0070ab, 0x0070b7, + 0x0070d4, 0x0070d8, 0x0070e4, 0x00710f, 0x00712b, 0x00711e, 0x007120, 0x00712e, + 0x007130, 0x007146, 0x007147, 0x007151, 0x00fa48, 0x007152, 0x00715c, 0x007160, + 0x007168, 0x00fa15, 0x007185, 0x007187, 0x007192, 0x0071c1, 0x0071ba, 0x0071c4, + 0x0071fe, 0x007200, 0x007215, 0x007255, 0x007256, 0x003e3f, 0x00728d, 0x00729b, + 0x0072be, 0x0072c0, 0x0072fb, 0x0247f1, 0x007327, 0x007328, 0x00fa16, 0x007350, + 0x007366, 0x00737c, 0x007395, 0x00739f, 0x0073a0, 0x0073a2, 0x0073a6, 0x0073ab, + 0x0073c9, 0x0073cf, 0x0073d6, 0x0073d9, 0x0073e3, 0x0073e9, + // plane 1 row 88 + 0x007407, 0x00740a, 0x00741a, 0x00741b, 0x00fa4a, 0x007426, 0x007428, 0x00742a, + 0x00742b, 0x00742c, 0x00742e, 0x00742f, 0x007430, 0x007444, 0x007446, 0x007447, + 0x00744b, 0x007457, 0x007462, 0x00746b, 0x00746d, 0x007486, 0x007487, 0x007489, + 0x007498, 0x00749c, 0x00749f, 0x0074a3, 0x007490, 0x0074a6, 0x0074a8, 0x0074a9, + 0x0074b5, 0x0074bf, 0x0074c8, 0x0074c9, 0x0074da, 0x0074ff, 0x007501, 0x007517, + 0x00752f, 0x00756f, 0x007579, 0x007592, 0x003f72, 0x0075ce, 0x0075e4, 0x007600, + 0x007602, 0x007608, 0x007615, 0x007616, 0x007619, 0x00761e, 0x00762d, 0x007635, + 0x007643, 0x00764b, 0x007664, 0x007665, 0x00766d, 0x00766f, 0x007671, 0x007681, + 0x00769b, 0x00769d, 0x00769e, 0x0076a6, 0x0076aa, 0x0076b6, 0x0076c5, 0x0076cc, + 0x0076ce, 0x0076d4, 0x0076e6, 0x0076f1, 0x0076fc, 0x00770a, 0x007719, 0x007734, + 0x007736, 0x007746, 0x00774d, 0x00774e, 0x00775c, 0x00775f, 0x007762, 0x00777a, + 0x007780, 0x007794, 0x0077aa, 0x0077e0, 0x00782d, 0x02548e, + // plane 1 row 89 + 0x007843, 0x00784e, 0x00784f, 0x007851, 0x007868, 0x00786e, 0x00fa4b, 0x0078b0, + 0x02550e, 0x0078ad, 0x0078e4, 0x0078f2, 0x007900, 0x0078f7, 0x00791c, 0x00792e, + 0x007931, 0x007934, 0x00fa4c, 0x00fa4d, 0x007945, 0x007946, 0x00fa4e, 0x00fa4f, + 0x00fa50, 0x00795c, 0x00fa51, 0x00fa19, 0x00fa1a, 0x007979, 0x00fa52, 0x00fa53, + 0x00fa1b, 0x007998, 0x0079b1, 0x0079b8, 0x0079c8, 0x0079ca, 0x025771, 0x0079d4, + 0x0079de, 0x0079eb, 0x0079ed, 0x007a03, 0x00fa54, 0x007a39, 0x007a5d, 0x007a6d, + 0x00fa55, 0x007a85, 0x007aa0, 0x0259c4, 0x007ab3, 0x007abb, 0x007ace, 0x007aeb, + 0x007afd, 0x007b12, 0x007b2d, 0x007b3b, 0x007b47, 0x007b4e, 0x007b60, 0x007b6d, + 0x007b6f, 0x007b72, 0x007b9e, 0x00fa56, 0x007bd7, 0x007bd9, 0x007c01, 0x007c31, + 0x007c1e, 0x007c20, 0x007c33, 0x007c36, 0x004264, 0x025da1, 0x007c59, 0x007c6d, + 0x007c79, 0x007c8f, 0x007c94, 0x007ca0, 0x007cbc, 0x007cd5, 0x007cd9, 0x007cdd, + 0x007d07, 0x007d08, 0x007d13, 0x007d1d, 0x007d23, 0x007d31, + // plane 1 row 90 + 0x007d41, 0x007d48, 0x007d53, 0x007d5c, 0x007d7a, 0x007d83, 0x007d8b, 0x007da0, + 0x007da6, 0x007dc2, 0x007dcc, 0x007dd6, 0x007de3, 0x00fa57, 0x007e28, 0x007e08, + 0x007e11, 0x007e15, 0x00fa59, 0x007e47, 0x007e52, 0x007e61, 0x007e8a, 0x007e8d, + 0x007f47, 0x00fa5a, 0x007f91, 0x007f97, 0x007fbf, 0x007fce, 0x007fdb, 0x007fdf, + 0x007fec, 0x007fee, 0x007ffa, 0x00fa5b, 0x008014, 0x008026, 0x008035, 0x008037, + 0x00803c, 0x0080ca, 0x0080d7, 0x0080e0, 0x0080f3, 0x008118, 0x00814a, 0x008160, + 0x008167, 0x008168, 0x00816d, 0x0081bb, 0x0081ca, 0x0081cf, 0x0081d7, 0x00fa5c, + 0x004453, 0x00445b, 0x008260, 0x008274, 0x026aff, 0x00828e, 0x0082a1, 0x0082a3, + 0x0082a4, 0x0082a9, 0x0082ae, 0x0082b7, 0x0082be, 0x0082bf, 0x0082c6, 0x0082d5, + 0x0082fd, 0x0082fe, 0x008300, 0x008301, 0x008362, 0x008322, 0x00832d, 0x00833a, + 0x008343, 0x008347, 0x008351, 0x008355, 0x00837d, 0x008386, 0x008392, 0x008398, + 0x0083a7, 0x0083a9, 0x0083bf, 0x0083c0, 0x0083c7, 0x0083cf, + // plane 1 row 91 + 0x0083d1, 0x0083e1, 0x0083ea, 0x008401, 0x008406, 0x00840a, 0x00fa5f, 0x008448, + 0x00845f, 0x008470, 0x008473, 0x008485, 0x00849e, 0x0084af, 0x0084b4, 0x0084ba, + 0x0084c0, 0x0084c2, 0x026e40, 0x008532, 0x00851e, 0x008523, 0x00852f, 0x008559, + 0x008564, 0x00fa1f, 0x0085ad, 0x00857a, 0x00858c, 0x00858f, 0x0085a2, 0x0085b0, + 0x0085cb, 0x0085ce, 0x0085ed, 0x008612, 0x0085ff, 0x008604, 0x008605, 0x008610, + 0x0270f4, 0x008618, 0x008629, 0x008638, 0x008657, 0x00865b, 0x00f936, 0x008662, + 0x00459d, 0x00866c, 0x008675, 0x008698, 0x0086b8, 0x0086fa, 0x0086fc, 0x0086fd, + 0x00870b, 0x008771, 0x008787, 0x008788, 0x0087ac, 0x0087ad, 0x0087b5, 0x0045ea, + 0x0087d6, 0x0087ec, 0x008806, 0x00880a, 0x008810, 0x008814, 0x00881f, 0x008898, + 0x0088aa, 0x0088ca, 0x0088ce, 0x027684, 0x0088f5, 0x00891c, 0x00fa60, 0x008918, + 0x008919, 0x00891a, 0x008927, 0x008930, 0x008932, 0x008939, 0x008940, 0x008994, + 0x00fa61, 0x0089d4, 0x0089e5, 0x0089f6, 0x008a12, 0x008a15, + // plane 1 row 92 + 0x008a22, 0x008a37, 0x008a47, 0x008a4e, 0x008a5d, 0x008a61, 0x008a75, 0x008a79, + 0x008aa7, 0x008ad0, 0x008adf, 0x008af4, 0x008af6, 0x00fa22, 0x00fa62, 0x00fa63, + 0x008b46, 0x008b54, 0x008b59, 0x008b69, 0x008b9d, 0x008c49, 0x008c68, 0x00fa64, + 0x008ce1, 0x008cf4, 0x008cf8, 0x008cfe, 0x00fa65, 0x008d12, 0x008d1b, 0x008daf, + 0x008dce, 0x008dd1, 0x008dd7, 0x008e20, 0x008e23, 0x008e3d, 0x008e70, 0x008e7b, + 0x028277, 0x008ec0, 0x004844, 0x008efa, 0x008f1e, 0x008f2d, 0x008f36, 0x008f54, + 0x0283cd, 0x008fa6, 0x008fb5, 0x008fe4, 0x008fe8, 0x008fee, 0x009008, 0x00902d, + 0x00fa67, 0x009088, 0x009095, 0x009097, 0x009099, 0x00909b, 0x0090a2, 0x0090b3, + 0x0090be, 0x0090c4, 0x0090c5, 0x0090c7, 0x0090d7, 0x0090dd, 0x0090de, 0x0090ef, + 0x0090f4, 0x00fa26, 0x009114, 0x009115, 0x009116, 0x009122, 0x009123, 0x009127, + 0x00912f, 0x009131, 0x009134, 0x00913d, 0x009148, 0x00915b, 0x009183, 0x00919e, + 0x0091ac, 0x0091b1, 0x0091bc, 0x0091d7, 0x0091fb, 0x0091e4, + // plane 1 row 93 + 0x0091e5, 0x0091ed, 0x0091f1, 0x009207, 0x009210, 0x009238, 0x009239, 0x00923a, + 0x00923c, 0x009240, 0x009243, 0x00924f, 0x009278, 0x009288, 0x0092c2, 0x0092cb, + 0x0092cc, 0x0092d3, 0x0092e0, 0x0092ff, 0x009304, 0x00931f, 0x009321, 0x009325, + 0x009348, 0x009349, 0x00934a, 0x009364, 0x009365, 0x00936a, 0x009370, 0x00939b, + 0x0093a3, 0x0093ba, 0x0093c6, 0x0093de, 0x0093df, 0x009404, 0x0093fd, 0x009433, + 0x00944a, 0x009463, 0x00946b, 0x009471, 0x009472, 0x00958e, 0x00959f, 0x0095a6, + 0x0095a9, 0x0095ac, 0x0095b6, 0x0095bd, 0x0095cb, 0x0095d0, 0x0095d3, 0x0049b0, + 0x0095da, 0x0095de, 0x009658, 0x009684, 0x00f9dc, 0x00969d, 0x0096a4, 0x0096a5, + 0x0096d2, 0x0096de, 0x00fa68, 0x0096e9, 0x0096ef, 0x009733, 0x00973b, 0x00974d, + 0x00974e, 0x00974f, 0x00975a, 0x00976e, 0x009773, 0x009795, 0x0097ae, 0x0097ba, + 0x0097c1, 0x0097c9, 0x0097de, 0x0097db, 0x0097f4, 0x00fa69, 0x00980a, 0x00981e, + 0x00982b, 0x009830, 0x00fa6a, 0x009852, 0x009853, 0x009856, + // plane 1 row 94 + 0x009857, 0x009859, 0x00985a, 0x00f9d0, 0x009865, 0x00986c, 0x0098ba, 0x0098c8, + 0x0098e7, 0x009958, 0x00999e, 0x009a02, 0x009a03, 0x009a24, 0x009a2d, 0x009a2e, + 0x009a38, 0x009a4a, 0x009a4e, 0x009a52, 0x009ab6, 0x009ac1, 0x009ac3, 0x009ace, + 0x009ad6, 0x009af9, 0x009b02, 0x009b08, 0x009b20, 0x004c17, 0x009b2d, 0x009b5e, + 0x009b79, 0x009b66, 0x009b72, 0x009b75, 0x009b84, 0x009b8a, 0x009b8f, 0x009b9e, + 0x009ba7, 0x009bc1, 0x009bce, 0x009be5, 0x009bf8, 0x009bfd, 0x009c00, 0x009c23, + 0x009c41, 0x009c4f, 0x009c50, 0x009c53, 0x009c63, 0x009c65, 0x009c77, 0x009d1d, + 0x009d1e, 0x009d43, 0x009d47, 0x009d52, 0x009d63, 0x009d70, 0x009d7c, 0x009d8a, + 0x009d96, 0x009dc0, 0x009dac, 0x009dbc, 0x009dd7, 0x02a190, 0x009de7, 0x009e07, + 0x009e15, 0x009e7c, 0x009e9e, 0x009ea4, 0x009eac, 0x009eaf, 0x009eb4, 0x009eb5, + 0x009ec3, 0x009ed1, 0x009f10, 0x009f39, 0x009f57, 0x009f90, 0x009f94, 0x009f97, + 0x009fa2, 0x0059f8, 0x005c5b, 0x005e77, 0x007626, 0x007e6b, + // plane 2 row 1 + 0x020089, 0x004e02, 0x004e0f, 0x004e12, 0x004e29, 0x004e2b, 0x004e2e, 0x004e40, + 0x004e47, 0x004e48, 0x0200a2, 0x004e51, 0x003406, 0x0200a4, 0x004e5a, 0x004e69, + 0x004e9d, 0x00342c, 0x00342e, 0x004eb9, 0x004ebb, 0x0201a2, 0x004ebc, 0x004ec3, + 0x004ec8, 0x004ed0, 0x004eeb, 0x004eda, 0x004ef1, 0x004ef5, 0x004f00, 0x004f16, + 0x004f64, 0x004f37, 0x004f3e, 0x004f54, 0x004f58, 0x020213, 0x004f77, 0x004f78, + 0x004f7a, 0x004f7d, 0x004f82, 0x004f85, 0x004f92, 0x004f9a, 0x004fe6, 0x004fb2, + 0x004fbe, 0x004fc5, 0x004fcb, 0x004fcf, 0x004fd2, 0x00346a, 0x004ff2, 0x005000, + 0x005010, 0x005013, 0x00501c, 0x00501e, 0x005022, 0x003468, 0x005042, 0x005046, + 0x00504e, 0x005053, 0x005057, 0x005063, 0x005066, 0x00506a, 0x005070, 0x0050a3, + 0x005088, 0x005092, 0x005093, 0x005095, 0x005096, 0x00509c, 0x0050aa, 0x02032b, + 0x0050b1, 0x0050ba, 0x0050bb, 0x0050c4, 0x0050c7, 0x0050f3, 0x020381, 0x0050ce, + 0x020371, 0x0050d4, 0x0050d9, 0x0050e1, 0x0050e9, 0x003492, + // plane 2 row 2 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 3 + 0x005108, 0x0203f9, 0x005117, 0x00511b, 0x02044a, 0x005160, 0x020509, 0x005173, + 0x005183, 0x00518b, 0x0034bc, 0x005198, 0x0051a3, 0x0051ad, 0x0034c7, 0x0051bc, + 0x0205d6, 0x020628, 0x0051f3, 0x0051f4, 0x005202, 0x005212, 0x005216, 0x02074f, + 0x005255, 0x00525c, 0x00526c, 0x005277, 0x005284, 0x005282, 0x020807, 0x005298, + 0x02083a, 0x0052a4, 0x0052a6, 0x0052af, 0x0052ba, 0x0052bb, 0x0052ca, 0x00351f, + 0x0052d1, 0x0208b9, 0x0052f7, 0x00530a, 0x00530b, 0x005324, 0x005335, 0x00533e, + 0x005342, 0x02097c, 0x02099d, 0x005367, 0x00536c, 0x00537a, 0x0053a4, 0x0053b4, + 0x020ad3, 0x0053b7, 0x0053c0, 0x020b1d, 0x00355d, 0x00355e, 0x0053d5, 0x0053da, + 0x003563, 0x0053f4, 0x0053f5, 0x005455, 0x005424, 0x005428, 0x00356e, 0x005443, + 0x005462, 0x005466, 0x00546c, 0x00548a, 0x00548d, 0x005495, 0x0054a0, 0x0054a6, + 0x0054ad, 0x0054ae, 0x0054b7, 0x0054ba, 0x0054bf, 0x0054c3, 0x020d45, 0x0054ec, + 0x0054ef, 0x0054f1, 0x0054f3, 0x005500, 0x005501, 0x005509, + // plane 2 row 4 + 0x00553c, 0x005541, 0x0035a6, 0x005547, 0x00554a, 0x0035a8, 0x005560, 0x005561, + 0x005564, 0x020de1, 0x00557d, 0x005582, 0x005588, 0x005591, 0x0035c5, 0x0055d2, + 0x020e95, 0x020e6d, 0x0055bf, 0x0055c9, 0x0055cc, 0x0055d1, 0x0055dd, 0x0035da, + 0x0055e2, 0x020e64, 0x0055e9, 0x005628, 0x020f5f, 0x005607, 0x005610, 0x005630, + 0x005637, 0x0035f4, 0x00563d, 0x00563f, 0x005640, 0x005647, 0x00565e, 0x005660, + 0x00566d, 0x003605, 0x005688, 0x00568c, 0x005695, 0x00569a, 0x00569d, 0x0056a8, + 0x0056ad, 0x0056b2, 0x0056c5, 0x0056cd, 0x0056df, 0x0056e8, 0x0056f6, 0x0056f7, + 0x021201, 0x005715, 0x005723, 0x021255, 0x005729, 0x02127b, 0x005745, 0x005746, + 0x00574c, 0x00574d, 0x021274, 0x005768, 0x00576f, 0x005773, 0x005774, 0x005775, + 0x00577b, 0x0212e4, 0x0212d7, 0x0057ac, 0x00579a, 0x00579d, 0x00579e, 0x0057a8, + 0x0057d7, 0x0212fd, 0x0057cc, 0x021336, 0x021344, 0x0057de, 0x0057e6, 0x0057f0, + 0x00364a, 0x0057f8, 0x0057fb, 0x0057fd, 0x005804, 0x00581e, + // plane 2 row 5 + 0x005820, 0x005827, 0x005832, 0x005839, 0x0213c4, 0x005849, 0x00584c, 0x005867, + 0x00588a, 0x00588b, 0x00588d, 0x00588f, 0x005890, 0x005894, 0x00589d, 0x0058aa, + 0x0058b1, 0x02146d, 0x0058c3, 0x0058cd, 0x0058e2, 0x0058f3, 0x0058f4, 0x005905, + 0x005906, 0x00590b, 0x00590d, 0x005914, 0x005924, 0x0215d7, 0x003691, 0x00593d, + 0x003699, 0x005946, 0x003696, 0x026c29, 0x00595b, 0x00595f, 0x021647, 0x005975, + 0x005976, 0x00597c, 0x00599f, 0x0059ae, 0x0059bc, 0x0059c8, 0x0059cd, 0x0059de, + 0x0059e3, 0x0059e4, 0x0059e7, 0x0059ee, 0x021706, 0x021742, 0x0036cf, 0x005a0c, + 0x005a0d, 0x005a17, 0x005a27, 0x005a2d, 0x005a55, 0x005a65, 0x005a7a, 0x005a8b, + 0x005a9c, 0x005a9f, 0x005aa0, 0x005aa2, 0x005ab1, 0x005ab3, 0x005ab5, 0x005aba, + 0x005abf, 0x005ada, 0x005adc, 0x005ae0, 0x005ae5, 0x005af0, 0x005aee, 0x005af5, + 0x005b00, 0x005b08, 0x005b17, 0x005b34, 0x005b2d, 0x005b4c, 0x005b52, 0x005b68, + 0x005b6f, 0x005b7c, 0x005b7f, 0x005b81, 0x005b84, 0x0219c3, + // plane 2 row 6 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 7 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 8 + 0x005b96, 0x005bac, 0x003761, 0x005bc0, 0x003762, 0x005bce, 0x005bd6, 0x00376c, + 0x00376b, 0x005bf1, 0x005bfd, 0x003775, 0x005c03, 0x005c29, 0x005c30, 0x021c56, + 0x005c5f, 0x005c63, 0x005c67, 0x005c68, 0x005c69, 0x005c70, 0x021d2d, 0x021d45, + 0x005c7c, 0x021d78, 0x021d62, 0x005c88, 0x005c8a, 0x0037c1, 0x021da1, 0x021d9c, + 0x005ca0, 0x005ca2, 0x005ca6, 0x005ca7, 0x021d92, 0x005cad, 0x005cb5, 0x021db7, + 0x005cc9, 0x021de0, 0x021e33, 0x005d06, 0x005d10, 0x005d2b, 0x005d1d, 0x005d20, + 0x005d24, 0x005d26, 0x005d31, 0x005d39, 0x005d42, 0x0037e8, 0x005d61, 0x005d6a, + 0x0037f4, 0x005d70, 0x021f1e, 0x0037fd, 0x005d88, 0x003800, 0x005d92, 0x005d94, + 0x005d97, 0x005d99, 0x005db0, 0x005db2, 0x005db4, 0x021f76, 0x005db9, 0x005dd1, + 0x005dd7, 0x005dd8, 0x005de0, 0x021ffa, 0x005de4, 0x005de9, 0x00382f, 0x005e00, + 0x003836, 0x005e12, 0x005e15, 0x003840, 0x005e1f, 0x005e2e, 0x005e3e, 0x005e49, + 0x00385c, 0x005e56, 0x003861, 0x005e6b, 0x005e6c, 0x005e6d, + // plane 2 row 9 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 10 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 11 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 12 + 0x005e6e, 0x02217b, 0x005ea5, 0x005eaa, 0x005eac, 0x005eb9, 0x005ebf, 0x005ec6, + 0x005ed2, 0x005ed9, 0x02231e, 0x005efd, 0x005f08, 0x005f0e, 0x005f1c, 0x0223ad, + 0x005f1e, 0x005f47, 0x005f63, 0x005f72, 0x005f7e, 0x005f8f, 0x005fa2, 0x005fa4, + 0x005fb8, 0x005fc4, 0x0038fa, 0x005fc7, 0x005fcb, 0x005fd2, 0x005fd3, 0x005fd4, + 0x005fe2, 0x005fee, 0x005fef, 0x005ff3, 0x005ffc, 0x003917, 0x006017, 0x006022, + 0x006024, 0x00391a, 0x00604c, 0x00607f, 0x00608a, 0x006095, 0x0060a8, 0x0226f3, + 0x0060b0, 0x0060b1, 0x0060be, 0x0060c8, 0x0060d9, 0x0060db, 0x0060ee, 0x0060f2, + 0x0060f5, 0x006110, 0x006112, 0x006113, 0x006119, 0x00611e, 0x00613a, 0x00396f, + 0x006141, 0x006146, 0x006160, 0x00617c, 0x02285b, 0x006192, 0x006193, 0x006197, + 0x006198, 0x0061a5, 0x0061a8, 0x0061ad, 0x0228ab, 0x0061d5, 0x0061dd, 0x0061df, + 0x0061f5, 0x02298f, 0x006215, 0x006223, 0x006229, 0x006246, 0x00624c, 0x006251, + 0x006252, 0x006261, 0x006264, 0x00627b, 0x00626d, 0x006273, + // plane 2 row 13 + 0x006299, 0x0062a6, 0x0062d5, 0x022ab8, 0x0062fd, 0x006303, 0x00630d, 0x006310, + 0x022b4f, 0x022b50, 0x006332, 0x006335, 0x00633b, 0x00633c, 0x006341, 0x006344, + 0x00634e, 0x022b46, 0x006359, 0x022c1d, 0x022ba6, 0x00636c, 0x006384, 0x006399, + 0x022c24, 0x006394, 0x0063bd, 0x0063f7, 0x0063d4, 0x0063d5, 0x0063dc, 0x0063e0, + 0x0063eb, 0x0063ec, 0x0063f2, 0x006409, 0x00641e, 0x006425, 0x006429, 0x00642f, + 0x00645a, 0x00645b, 0x00645d, 0x006473, 0x00647d, 0x006487, 0x006491, 0x00649d, + 0x00649f, 0x0064cb, 0x0064cc, 0x0064d5, 0x0064d7, 0x022de1, 0x0064e4, 0x0064e5, + 0x0064ff, 0x006504, 0x003a6e, 0x00650f, 0x006514, 0x006516, 0x003a73, 0x00651e, + 0x006532, 0x006544, 0x006554, 0x00656b, 0x00657a, 0x006581, 0x006584, 0x006585, + 0x00658a, 0x0065b2, 0x0065b5, 0x0065b8, 0x0065bf, 0x0065c2, 0x0065c9, 0x0065d4, + 0x003ad6, 0x0065f2, 0x0065f9, 0x0065fc, 0x006604, 0x006608, 0x006621, 0x00662a, + 0x006645, 0x006651, 0x00664e, 0x003aea, 0x0231c3, 0x006657, + // plane 2 row 14 + 0x00665b, 0x006663, 0x0231f5, 0x0231b6, 0x00666a, 0x00666b, 0x00666c, 0x00666d, + 0x00667b, 0x006680, 0x006690, 0x006692, 0x006699, 0x003b0e, 0x0066ad, 0x0066b1, + 0x0066b5, 0x003b1a, 0x0066bf, 0x003b1c, 0x0066ec, 0x003ad7, 0x006701, 0x006705, + 0x006712, 0x023372, 0x006719, 0x0233d3, 0x0233d2, 0x00674c, 0x00674d, 0x006754, + 0x00675d, 0x0233d0, 0x0233e4, 0x0233d5, 0x006774, 0x006776, 0x0233da, 0x006792, + 0x0233df, 0x008363, 0x006810, 0x0067b0, 0x0067b2, 0x0067c3, 0x0067c8, 0x0067d2, + 0x0067d9, 0x0067db, 0x0067f0, 0x0067f7, 0x02344a, 0x023451, 0x02344b, 0x006818, + 0x00681f, 0x00682d, 0x023465, 0x006833, 0x00683b, 0x00683e, 0x006844, 0x006845, + 0x006849, 0x00684c, 0x006855, 0x006857, 0x003b77, 0x00686b, 0x00686e, 0x00687a, + 0x00687c, 0x006882, 0x006890, 0x006896, 0x003b6d, 0x006898, 0x006899, 0x00689a, + 0x00689c, 0x0068aa, 0x0068ab, 0x0068b4, 0x0068bb, 0x0068fb, 0x0234e4, 0x02355a, + 0x00fa13, 0x0068c3, 0x0068c5, 0x0068cc, 0x0068cf, 0x0068d6, + // plane 2 row 15 + 0x0068d9, 0x0068e4, 0x0068e5, 0x0068ec, 0x0068f7, 0x006903, 0x006907, 0x003b87, + 0x003b88, 0x023594, 0x00693b, 0x003b8d, 0x006946, 0x006969, 0x00696c, 0x006972, + 0x00697a, 0x00697f, 0x006992, 0x003ba4, 0x006996, 0x006998, 0x0069a6, 0x0069b0, + 0x0069b7, 0x0069ba, 0x0069bc, 0x0069c0, 0x0069d1, 0x0069d6, 0x023639, 0x023647, + 0x006a30, 0x023638, 0x02363a, 0x0069e3, 0x0069ee, 0x0069ef, 0x0069f3, 0x003bcd, + 0x0069f4, 0x0069fe, 0x006a11, 0x006a1a, 0x006a1d, 0x02371c, 0x006a32, 0x006a33, + 0x006a34, 0x006a3f, 0x006a46, 0x006a49, 0x006a7a, 0x006a4e, 0x006a52, 0x006a64, + 0x02370c, 0x006a7e, 0x006a83, 0x006a8b, 0x003bf0, 0x006a91, 0x006a9f, 0x006aa1, + 0x023764, 0x006aab, 0x006abd, 0x006ac6, 0x006ad4, 0x006ad0, 0x006adc, 0x006add, + 0x0237ff, 0x0237e7, 0x006aec, 0x006af1, 0x006af2, 0x006af3, 0x006afd, 0x023824, + 0x006b0b, 0x006b0f, 0x006b10, 0x006b11, 0x02383d, 0x006b17, 0x003c26, 0x006b2f, + 0x006b4a, 0x006b58, 0x006b6c, 0x006b75, 0x006b7a, 0x006b81, + // plane 2 row 16 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 17 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 18 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 19 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 20 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 21 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 22 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 23 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 24 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 25 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 26 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 27 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 28 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 29 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 30 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 31 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 32 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 33 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 34 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 35 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 36 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 37 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 38 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 39 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 40 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 41 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 42 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 43 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 44 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 45 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 46 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 47 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 48 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 49 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 50 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 51 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 52 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 53 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 54 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 55 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 56 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 57 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 58 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 59 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 60 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 61 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 62 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 63 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 64 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 65 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 66 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 67 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 68 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 69 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 70 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 71 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 72 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 73 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 74 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 75 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 76 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 77 + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, + // plane 2 row 78 + 0x006b9b, 0x006bae, 0x023a98, 0x006bbd, 0x006bbe, 0x006bc7, 0x006bc8, 0x006bc9, + 0x006bda, 0x006be6, 0x006be7, 0x006bee, 0x006bf1, 0x006c02, 0x006c0a, 0x006c0e, + 0x006c35, 0x006c36, 0x006c3a, 0x023c7f, 0x006c3f, 0x006c4d, 0x006c5b, 0x006c6d, + 0x006c84, 0x006c89, 0x003cc3, 0x006c94, 0x006c95, 0x006c97, 0x006cad, 0x006cc2, + 0x006cd0, 0x003cd2, 0x006cd6, 0x006cda, 0x006cdc, 0x006ce9, 0x006cec, 0x006ced, + 0x023d00, 0x006d00, 0x006d0a, 0x006d24, 0x006d26, 0x006d27, 0x006c67, 0x006d2f, + 0x006d3c, 0x006d5b, 0x006d5e, 0x006d60, 0x006d70, 0x006d80, 0x006d81, 0x006d8a, + 0x006d8d, 0x006d91, 0x006d98, 0x023d40, 0x006e17, 0x023dfa, 0x023df9, 0x023dd3, + 0x006dab, 0x006dae, 0x006db4, 0x006dc2, 0x006d34, 0x006dc8, 0x006dce, 0x006dcf, + 0x006dd0, 0x006ddf, 0x006de9, 0x006df6, 0x006e36, 0x006e1e, 0x006e22, 0x006e27, + 0x003d11, 0x006e32, 0x006e3c, 0x006e48, 0x006e49, 0x006e4b, 0x006e4c, 0x006e4f, + 0x006e51, 0x006e53, 0x006e54, 0x006e57, 0x006e63, 0x003d1e, + // plane 2 row 79 + 0x006e93, 0x006ea7, 0x006eb4, 0x006ebf, 0x006ec3, 0x006eca, 0x006ed9, 0x006f35, + 0x006eeb, 0x006ef9, 0x006efb, 0x006f0a, 0x006f0c, 0x006f18, 0x006f25, 0x006f36, + 0x006f3c, 0x023f7e, 0x006f52, 0x006f57, 0x006f5a, 0x006f60, 0x006f68, 0x006f98, + 0x006f7d, 0x006f90, 0x006f96, 0x006fbe, 0x006f9f, 0x006fa5, 0x006faf, 0x003d64, + 0x006fb5, 0x006fc8, 0x006fc9, 0x006fda, 0x006fde, 0x006fe9, 0x024096, 0x006ffc, + 0x007000, 0x007007, 0x00700a, 0x007023, 0x024103, 0x007039, 0x00703a, 0x00703c, + 0x007043, 0x007047, 0x00704b, 0x003d9a, 0x007054, 0x007065, 0x007069, 0x00706c, + 0x00706e, 0x007076, 0x00707e, 0x007081, 0x007086, 0x007095, 0x007097, 0x0070bb, + 0x0241c6, 0x00709f, 0x0070b1, 0x0241fe, 0x0070ec, 0x0070ca, 0x0070d1, 0x0070d3, + 0x0070dc, 0x007103, 0x007104, 0x007106, 0x007107, 0x007108, 0x00710c, 0x003dc0, + 0x00712f, 0x007131, 0x007150, 0x00714a, 0x007153, 0x00715e, 0x003dd4, 0x007196, + 0x007180, 0x00719b, 0x0071a0, 0x0071a2, 0x0071ae, 0x0071af, + // plane 2 row 80 + 0x0071b3, 0x0243bc, 0x0071cb, 0x0071d3, 0x0071d9, 0x0071dc, 0x007207, 0x003e05, + 0x00fa49, 0x00722b, 0x007234, 0x007238, 0x007239, 0x004e2c, 0x007242, 0x007253, + 0x007257, 0x007263, 0x024629, 0x00726e, 0x00726f, 0x007278, 0x00727f, 0x00728e, + 0x0246a5, 0x0072ad, 0x0072ae, 0x0072b0, 0x0072b1, 0x0072c1, 0x003e60, 0x0072cc, + 0x003e66, 0x003e68, 0x0072f3, 0x0072fa, 0x007307, 0x007312, 0x007318, 0x007319, + 0x003e83, 0x007339, 0x00732c, 0x007331, 0x007333, 0x00733d, 0x007352, 0x003e94, + 0x00736b, 0x00736c, 0x024896, 0x00736e, 0x00736f, 0x007371, 0x007377, 0x007381, + 0x007385, 0x00738a, 0x007394, 0x007398, 0x00739c, 0x00739e, 0x0073a5, 0x0073a8, + 0x0073b5, 0x0073b7, 0x0073b9, 0x0073bc, 0x0073bf, 0x0073c5, 0x0073cb, 0x0073e1, + 0x0073e7, 0x0073f9, 0x007413, 0x0073fa, 0x007401, 0x007424, 0x007431, 0x007439, + 0x007453, 0x007440, 0x007443, 0x00744d, 0x007452, 0x00745d, 0x007471, 0x007481, + 0x007485, 0x007488, 0x024a4d, 0x007492, 0x007497, 0x007499, + // plane 2 row 81 + 0x0074a0, 0x0074a1, 0x0074a5, 0x0074aa, 0x0074ab, 0x0074b9, 0x0074bb, 0x0074ba, + 0x0074d6, 0x0074d8, 0x0074de, 0x0074ef, 0x0074eb, 0x024b56, 0x0074fa, 0x024b6f, + 0x007520, 0x007524, 0x00752a, 0x003f57, 0x024c16, 0x00753d, 0x00753e, 0x007540, + 0x007548, 0x00754e, 0x007550, 0x007552, 0x00756c, 0x007572, 0x007571, 0x00757a, + 0x00757d, 0x00757e, 0x007581, 0x024d14, 0x00758c, 0x003f75, 0x0075a2, 0x003f77, + 0x0075b0, 0x0075b7, 0x0075bf, 0x0075c0, 0x0075c6, 0x0075cf, 0x0075d3, 0x0075dd, + 0x0075df, 0x0075e0, 0x0075e7, 0x0075ec, 0x0075ee, 0x0075f1, 0x0075f9, 0x007603, + 0x007618, 0x007607, 0x00760f, 0x003fae, 0x024e0e, 0x007613, 0x00761b, 0x00761c, + 0x024e37, 0x007625, 0x007628, 0x00763c, 0x007633, 0x024e6a, 0x003fc9, 0x007641, + 0x024e8b, 0x007649, 0x007655, 0x003fd7, 0x00766e, 0x007695, 0x00769c, 0x0076a1, + 0x0076a0, 0x0076a7, 0x0076a8, 0x0076af, 0x02504a, 0x0076c9, 0x025055, 0x0076e8, + 0x0076ec, 0x025122, 0x007717, 0x00771a, 0x00772d, 0x007735, + // plane 2 row 82 + 0x0251a9, 0x004039, 0x0251e5, 0x0251cd, 0x007758, 0x007760, 0x00776a, 0x02521e, + 0x007772, 0x00777c, 0x00777d, 0x02524c, 0x004058, 0x00779a, 0x00779f, 0x0077a2, + 0x0077a4, 0x0077a9, 0x0077de, 0x0077df, 0x0077e4, 0x0077e6, 0x0077ea, 0x0077ec, + 0x004093, 0x0077f0, 0x0077f4, 0x0077fb, 0x02542e, 0x007805, 0x007806, 0x007809, + 0x00780d, 0x007819, 0x007821, 0x00782c, 0x007847, 0x007864, 0x00786a, 0x0254d9, + 0x00788a, 0x007894, 0x0078a4, 0x00789d, 0x00789e, 0x00789f, 0x0078bb, 0x0078c8, + 0x0078cc, 0x0078ce, 0x0078d5, 0x0078e0, 0x0078e1, 0x0078e6, 0x0078f9, 0x0078fa, + 0x0078fb, 0x0078fe, 0x0255a7, 0x007910, 0x00791b, 0x007930, 0x007925, 0x00793b, + 0x00794a, 0x007958, 0x00795b, 0x004105, 0x007967, 0x007972, 0x007994, 0x007995, + 0x007996, 0x00799b, 0x0079a1, 0x0079a9, 0x0079b4, 0x0079bb, 0x0079c2, 0x0079c7, + 0x0079cc, 0x0079cd, 0x0079d6, 0x004148, 0x0257a9, 0x0257b4, 0x00414f, 0x007a0a, + 0x007a11, 0x007a15, 0x007a1b, 0x007a1e, 0x004163, 0x007a2d, + // plane 2 row 83 + 0x007a38, 0x007a47, 0x007a4c, 0x007a56, 0x007a59, 0x007a5c, 0x007a5f, 0x007a60, + 0x007a67, 0x007a6a, 0x007a75, 0x007a78, 0x007a82, 0x007a8a, 0x007a90, 0x007aa3, + 0x007aac, 0x0259d4, 0x0041b4, 0x007ab9, 0x007abc, 0x007abe, 0x0041bf, 0x007acc, + 0x007ad1, 0x007ae7, 0x007ae8, 0x007af4, 0x025ae4, 0x025ae3, 0x007b07, 0x025af1, + 0x007b3d, 0x007b27, 0x007b2a, 0x007b2e, 0x007b2f, 0x007b31, 0x0041e6, 0x0041f3, + 0x007b7f, 0x007b41, 0x0041ee, 0x007b55, 0x007b79, 0x007b64, 0x007b66, 0x007b69, + 0x007b73, 0x025bb2, 0x004207, 0x007b90, 0x007b91, 0x007b9b, 0x00420e, 0x007baf, + 0x007bb5, 0x007bbc, 0x007bc5, 0x007bca, 0x025c4b, 0x025c64, 0x007bd4, 0x007bd6, + 0x007bda, 0x007bea, 0x007bf0, 0x007c03, 0x007c0b, 0x007c0e, 0x007c0f, 0x007c26, + 0x007c45, 0x007c4a, 0x007c51, 0x007c57, 0x007c5e, 0x007c61, 0x007c69, 0x007c6e, + 0x007c6f, 0x007c70, 0x025e2e, 0x025e56, 0x025e65, 0x007ca6, 0x025e62, 0x007cb6, + 0x007cb7, 0x007cbf, 0x025ed8, 0x007cc4, 0x025ec2, 0x007cc8, + // plane 2 row 84 + 0x007ccd, 0x025ee8, 0x007cd7, 0x025f23, 0x007ce6, 0x007ceb, 0x025f5c, 0x007cf5, + 0x007d03, 0x007d09, 0x0042c6, 0x007d12, 0x007d1e, 0x025fe0, 0x025fd4, 0x007d3d, + 0x007d3e, 0x007d40, 0x007d47, 0x02600c, 0x025ffb, 0x0042d6, 0x007d59, 0x007d5a, + 0x007d6a, 0x007d70, 0x0042dd, 0x007d7f, 0x026017, 0x007d86, 0x007d88, 0x007d8c, + 0x007d97, 0x026060, 0x007d9d, 0x007da7, 0x007daa, 0x007db6, 0x007db7, 0x007dc0, + 0x007dd7, 0x007dd9, 0x007de6, 0x007df1, 0x007df9, 0x004302, 0x0260ed, 0x00fa58, + 0x007e10, 0x007e17, 0x007e1d, 0x007e20, 0x007e27, 0x007e2c, 0x007e45, 0x007e73, + 0x007e75, 0x007e7e, 0x007e86, 0x007e87, 0x00432b, 0x007e91, 0x007e98, 0x007e9a, + 0x004343, 0x007f3c, 0x007f3b, 0x007f3e, 0x007f43, 0x007f44, 0x007f4f, 0x0034c1, + 0x026270, 0x007f52, 0x026286, 0x007f61, 0x007f63, 0x007f64, 0x007f6d, 0x007f7d, + 0x007f7e, 0x02634c, 0x007f90, 0x00517b, 0x023d0e, 0x007f96, 0x007f9c, 0x007fad, + 0x026402, 0x007fc3, 0x007fcf, 0x007fe3, 0x007fe5, 0x007fef, + // plane 2 row 85 + 0x007ff2, 0x008002, 0x00800a, 0x008008, 0x00800e, 0x008011, 0x008016, 0x008024, + 0x00802c, 0x008030, 0x008043, 0x008066, 0x008071, 0x008075, 0x00807b, 0x008099, + 0x00809c, 0x0080a4, 0x0080a7, 0x0080b8, 0x02667e, 0x0080c5, 0x0080d5, 0x0080d8, + 0x0080e6, 0x0266b0, 0x00810d, 0x0080f5, 0x0080fb, 0x0043ee, 0x008135, 0x008116, + 0x00811e, 0x0043f0, 0x008124, 0x008127, 0x00812c, 0x02671d, 0x00813d, 0x004408, + 0x008169, 0x004417, 0x008181, 0x00441c, 0x008184, 0x008185, 0x004422, 0x008198, + 0x0081b2, 0x0081c1, 0x0081c3, 0x0081d6, 0x0081db, 0x0268dd, 0x0081e4, 0x0268ea, + 0x0081ec, 0x026951, 0x0081fd, 0x0081ff, 0x02696f, 0x008204, 0x0269dd, 0x008219, + 0x008221, 0x008222, 0x026a1e, 0x008232, 0x008234, 0x00823c, 0x008246, 0x008249, + 0x008245, 0x026a58, 0x00824b, 0x004476, 0x00824f, 0x00447a, 0x008257, 0x026a8c, + 0x00825c, 0x008263, 0x026ab7, 0x00fa5d, 0x00fa5e, 0x008279, 0x004491, 0x00827d, + 0x00827f, 0x008283, 0x00828a, 0x008293, 0x0082a7, 0x0082a8, + // plane 2 row 86 + 0x0082b2, 0x0082b4, 0x0082ba, 0x0082bc, 0x0082e2, 0x0082e8, 0x0082f7, 0x008307, + 0x008308, 0x00830c, 0x008354, 0x00831b, 0x00831d, 0x008330, 0x00833c, 0x008344, + 0x008357, 0x0044be, 0x00837f, 0x0044d4, 0x0044b3, 0x00838d, 0x008394, 0x008395, + 0x00839b, 0x00839d, 0x0083c9, 0x0083d0, 0x0083d4, 0x0083dd, 0x0083e5, 0x0083f9, + 0x00840f, 0x008411, 0x008415, 0x026c73, 0x008417, 0x008439, 0x00844a, 0x00844f, + 0x008451, 0x008452, 0x008459, 0x00845a, 0x00845c, 0x026cdd, 0x008465, 0x008476, + 0x008478, 0x00847c, 0x008481, 0x00450d, 0x0084dc, 0x008497, 0x0084a6, 0x0084be, + 0x004508, 0x0084ce, 0x0084cf, 0x0084d3, 0x026e65, 0x0084e7, 0x0084ea, 0x0084ef, + 0x0084f0, 0x0084f1, 0x0084fa, 0x0084fd, 0x00850c, 0x00851b, 0x008524, 0x008525, + 0x00852b, 0x008534, 0x00854f, 0x00856f, 0x004525, 0x004543, 0x00853e, 0x008551, + 0x008553, 0x00855e, 0x008561, 0x008562, 0x026f94, 0x00857b, 0x00857d, 0x00857f, + 0x008581, 0x008586, 0x008593, 0x00859d, 0x00859f, 0x026ff8, + // plane 2 row 87 + 0x026ff6, 0x026ff7, 0x0085b7, 0x0085bc, 0x0085c7, 0x0085ca, 0x0085d8, 0x0085d9, + 0x0085df, 0x0085e1, 0x0085e6, 0x0085f6, 0x008600, 0x008611, 0x00861e, 0x008621, + 0x008624, 0x008627, 0x02710d, 0x008639, 0x00863c, 0x027139, 0x008640, 0x00fa20, + 0x008653, 0x008656, 0x00866f, 0x008677, 0x00867a, 0x008687, 0x008689, 0x00868d, + 0x008691, 0x00869c, 0x00869d, 0x0086a8, 0x00fa21, 0x0086b1, 0x0086b3, 0x0086c1, + 0x0086c3, 0x0086d1, 0x0086d5, 0x0086d7, 0x0086e3, 0x0086e6, 0x0045b8, 0x008705, + 0x008707, 0x00870e, 0x008710, 0x008713, 0x008719, 0x00871f, 0x008721, 0x008723, + 0x008731, 0x00873a, 0x00873e, 0x008740, 0x008743, 0x008751, 0x008758, 0x008764, + 0x008765, 0x008772, 0x00877c, 0x0273db, 0x0273da, 0x0087a7, 0x008789, 0x00878b, + 0x008793, 0x0087a0, 0x0273fe, 0x0045e5, 0x0087be, 0x027410, 0x0087c1, 0x0087ce, + 0x0087f5, 0x0087df, 0x027449, 0x0087e3, 0x0087e5, 0x0087e6, 0x0087ea, 0x0087eb, + 0x0087ed, 0x008801, 0x008803, 0x00880b, 0x008813, 0x008828, + // plane 2 row 88 + 0x00882e, 0x008832, 0x00883c, 0x00460f, 0x00884a, 0x008858, 0x00885f, 0x008864, + 0x027615, 0x027614, 0x008869, 0x027631, 0x00886f, 0x0088a0, 0x0088bc, 0x0088bd, + 0x0088be, 0x0088c0, 0x0088d2, 0x027693, 0x0088d1, 0x0088d3, 0x0088db, 0x0088f0, + 0x0088f1, 0x004641, 0x008901, 0x02770e, 0x008937, 0x027723, 0x008942, 0x008945, + 0x008949, 0x027752, 0x004665, 0x008962, 0x008980, 0x008989, 0x008990, 0x00899f, + 0x0089b0, 0x0089b7, 0x0089d6, 0x0089d8, 0x0089eb, 0x0046a1, 0x0089f1, 0x0089f3, + 0x0089fd, 0x0089ff, 0x0046af, 0x008a11, 0x008a14, 0x027985, 0x008a21, 0x008a35, + 0x008a3e, 0x008a45, 0x008a4d, 0x008a58, 0x008aae, 0x008a90, 0x008ab7, 0x008abe, + 0x008ad7, 0x008afc, 0x027a84, 0x008b0a, 0x008b05, 0x008b0d, 0x008b1c, 0x008b1f, + 0x008b2d, 0x008b43, 0x00470c, 0x008b51, 0x008b5e, 0x008b76, 0x008b7f, 0x008b81, + 0x008b8b, 0x008b94, 0x008b95, 0x008b9c, 0x008b9e, 0x008c39, 0x027bb3, 0x008c3d, + 0x027bbe, 0x027bc7, 0x008c45, 0x008c47, 0x008c4f, 0x008c54, + // plane 2 row 89 + 0x008c57, 0x008c69, 0x008c6d, 0x008c73, 0x027cb8, 0x008c93, 0x008c92, 0x008c99, + 0x004764, 0x008c9b, 0x008ca4, 0x008cd6, 0x008cd5, 0x008cd9, 0x027da0, 0x008cf0, + 0x008cf1, 0x027e10, 0x008d09, 0x008d0e, 0x008d6c, 0x008d84, 0x008d95, 0x008da6, + 0x027fb7, 0x008dc6, 0x008dc8, 0x008dd9, 0x008dec, 0x008e0c, 0x0047fd, 0x008dfd, + 0x008e06, 0x02808a, 0x008e14, 0x008e16, 0x008e21, 0x008e22, 0x008e27, 0x0280bb, + 0x004816, 0x008e36, 0x008e39, 0x008e4b, 0x008e54, 0x008e62, 0x008e6c, 0x008e6d, + 0x008e6f, 0x008e98, 0x008e9e, 0x008eae, 0x008eb3, 0x008eb5, 0x008eb6, 0x008ebb, + 0x028282, 0x008ed1, 0x008ed4, 0x00484e, 0x008ef9, 0x0282f3, 0x008f00, 0x008f08, + 0x008f17, 0x008f2b, 0x008f40, 0x008f4a, 0x008f58, 0x02840c, 0x008fa4, 0x008fb4, + 0x00fa66, 0x008fb6, 0x028455, 0x008fc1, 0x008fc6, 0x00fa24, 0x008fca, 0x008fcd, + 0x008fd3, 0x008fd5, 0x008fe0, 0x008ff1, 0x008ff5, 0x008ffb, 0x009002, 0x00900c, + 0x009037, 0x02856b, 0x009043, 0x009044, 0x00905d, 0x0285c8, + // plane 2 row 90 + 0x0285c9, 0x009085, 0x00908c, 0x009090, 0x00961d, 0x0090a1, 0x0048b5, 0x0090b0, + 0x0090b6, 0x0090c3, 0x0090c8, 0x0286d7, 0x0090dc, 0x0090df, 0x0286fa, 0x0090f6, + 0x0090f2, 0x009100, 0x0090eb, 0x0090fe, 0x0090ff, 0x009104, 0x009106, 0x009118, + 0x00911c, 0x00911e, 0x009137, 0x009139, 0x00913a, 0x009146, 0x009147, 0x009157, + 0x009159, 0x009161, 0x009164, 0x009174, 0x009179, 0x009185, 0x00918e, 0x0091a8, + 0x0091ae, 0x0091b3, 0x0091b6, 0x0091c3, 0x0091c4, 0x0091da, 0x028949, 0x028946, + 0x0091ec, 0x0091ee, 0x009201, 0x00920a, 0x009216, 0x009217, 0x02896b, 0x009233, + 0x009242, 0x009247, 0x00924a, 0x00924e, 0x009251, 0x009256, 0x009259, 0x009260, + 0x009261, 0x009265, 0x009267, 0x009268, 0x028987, 0x028988, 0x00927c, 0x00927d, + 0x00927f, 0x009289, 0x00928d, 0x009297, 0x009299, 0x00929f, 0x0092a7, 0x0092ab, + 0x0289ba, 0x0289bb, 0x0092b2, 0x0092bf, 0x0092c0, 0x0092c6, 0x0092ce, 0x0092d0, + 0x0092d7, 0x0092d9, 0x0092e5, 0x0092e7, 0x009311, 0x028a1e, + // plane 2 row 91 + 0x028a29, 0x0092f7, 0x0092f9, 0x0092fb, 0x009302, 0x00930d, 0x009315, 0x00931d, + 0x00931e, 0x009327, 0x009329, 0x028a71, 0x028a43, 0x009347, 0x009351, 0x009357, + 0x00935a, 0x00936b, 0x009371, 0x009373, 0x0093a1, 0x028a99, 0x028acd, 0x009388, + 0x00938b, 0x00938f, 0x00939e, 0x0093f5, 0x028ae4, 0x028add, 0x0093f1, 0x0093c1, + 0x0093c7, 0x0093dc, 0x0093e2, 0x0093e7, 0x009409, 0x00940f, 0x009416, 0x009417, + 0x0093fb, 0x009432, 0x009434, 0x00943b, 0x009445, 0x028bc1, 0x028bef, 0x00946d, + 0x00946f, 0x009578, 0x009579, 0x009586, 0x00958c, 0x00958d, 0x028d10, 0x0095ab, + 0x0095b4, 0x028d71, 0x0095c8, 0x028dfb, 0x028e1f, 0x00962c, 0x009633, 0x009634, + 0x028e36, 0x00963c, 0x009641, 0x009661, 0x028e89, 0x009682, 0x028eeb, 0x00969a, + 0x028f32, 0x0049e7, 0x0096a9, 0x0096af, 0x0096b3, 0x0096ba, 0x0096bd, 0x0049fa, + 0x028ff8, 0x0096d8, 0x0096da, 0x0096dd, 0x004a04, 0x009714, 0x009723, 0x004a29, + 0x009736, 0x009741, 0x009747, 0x009755, 0x009757, 0x00975b, + // plane 2 row 92 + 0x00976a, 0x0292a0, 0x0292b1, 0x009796, 0x00979a, 0x00979e, 0x0097a2, 0x0097b1, + 0x0097b2, 0x0097be, 0x0097cc, 0x0097d1, 0x0097d4, 0x0097d8, 0x0097d9, 0x0097e1, + 0x0097f1, 0x009804, 0x00980d, 0x00980e, 0x009814, 0x009816, 0x004abc, 0x029490, + 0x009823, 0x009832, 0x009833, 0x009825, 0x009847, 0x009866, 0x0098ab, 0x0098ad, + 0x0098b0, 0x0295cf, 0x0098b7, 0x0098b8, 0x0098bb, 0x0098bc, 0x0098bf, 0x0098c2, + 0x0098c7, 0x0098cb, 0x0098e0, 0x02967f, 0x0098e1, 0x0098e3, 0x0098e5, 0x0098ea, + 0x0098f0, 0x0098f1, 0x0098f3, 0x009908, 0x004b3b, 0x0296f0, 0x009916, 0x009917, + 0x029719, 0x00991a, 0x00991b, 0x00991c, 0x029750, 0x009931, 0x009932, 0x009933, + 0x00993a, 0x00993b, 0x00993c, 0x009940, 0x009941, 0x009946, 0x00994d, 0x00994e, + 0x00995c, 0x00995f, 0x009960, 0x0099a3, 0x0099a6, 0x0099b9, 0x0099bd, 0x0099bf, + 0x0099c3, 0x0099c9, 0x0099d4, 0x0099d9, 0x0099de, 0x0298c6, 0x0099f0, 0x0099f9, + 0x0099fc, 0x009a0a, 0x009a11, 0x009a16, 0x009a1a, 0x009a20, + // plane 2 row 93 + 0x009a31, 0x009a36, 0x009a44, 0x009a4c, 0x009a58, 0x004bc2, 0x009aaf, 0x004bca, + 0x009ab7, 0x004bd2, 0x009ab9, 0x029a72, 0x009ac6, 0x009ad0, 0x009ad2, 0x009ad5, + 0x004be8, 0x009adc, 0x009ae0, 0x009ae5, 0x009ae9, 0x009b03, 0x009b0c, 0x009b10, + 0x009b12, 0x009b16, 0x009b1c, 0x009b2b, 0x009b33, 0x009b3d, 0x004c20, 0x009b4b, + 0x009b63, 0x009b65, 0x009b6b, 0x009b6c, 0x009b73, 0x009b76, 0x009b77, 0x009ba6, + 0x009bac, 0x009bb1, 0x029ddb, 0x029e3d, 0x009bb2, 0x009bb8, 0x009bbe, 0x009bc7, + 0x009bf3, 0x009bd8, 0x009bdd, 0x009be7, 0x009bea, 0x009beb, 0x009bef, 0x009bee, + 0x029e15, 0x009bfa, 0x029e8a, 0x009bf7, 0x029e49, 0x009c16, 0x009c18, 0x009c19, + 0x009c1a, 0x009c1d, 0x009c22, 0x009c27, 0x009c29, 0x009c2a, 0x029ec4, 0x009c31, + 0x009c36, 0x009c37, 0x009c45, 0x009c5c, 0x029ee9, 0x009c49, 0x009c4a, 0x029edb, + 0x009c54, 0x009c58, 0x009c5b, 0x009c5d, 0x009c5f, 0x009c69, 0x009c6a, 0x009c6b, + 0x009c6d, 0x009c6e, 0x009c70, 0x009c72, 0x009c75, 0x009c7a, + // plane 2 row 94 + 0x009ce6, 0x009cf2, 0x009d0b, 0x009d02, 0x029fce, 0x009d11, 0x009d17, 0x009d18, + 0x02a02f, 0x004cc4, 0x02a01a, 0x009d32, 0x004cd1, 0x009d42, 0x009d4a, 0x009d5f, + 0x009d62, 0x02a0f9, 0x009d69, 0x009d6b, 0x02a082, 0x009d73, 0x009d76, 0x009d77, + 0x009d7e, 0x009d84, 0x009d8d, 0x009d99, 0x009da1, 0x009dbf, 0x009db5, 0x009db9, + 0x009dbd, 0x009dc3, 0x009dc7, 0x009dc9, 0x009dd6, 0x009dda, 0x009ddf, 0x009de0, + 0x009de3, 0x009df4, 0x004d07, 0x009e0a, 0x009e02, 0x009e0d, 0x009e19, 0x009e1c, + 0x009e1d, 0x009e7b, 0x022218, 0x009e80, 0x009e85, 0x009e9b, 0x009ea8, 0x02a38c, + 0x009ebd, 0x02a437, 0x009edf, 0x009ee7, 0x009eee, 0x009eff, 0x009f02, 0x004d77, + 0x009f03, 0x009f17, 0x009f19, 0x009f2f, 0x009f37, 0x009f3a, 0x009f3d, 0x009f41, + 0x009f45, 0x009f46, 0x009f53, 0x009f55, 0x009f58, 0x02a5f1, 0x009f5d, 0x02a602, + 0x009f69, 0x02a61a, 0x009f6d, 0x009f70, 0x009f75, 0x02a6b2, 0x00fffd, 0x00fffd, + 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd, 0x00fffd + }; + + /// Unicode registered symbols + enum UnicodeSymbol { + /// Unrecognized character (Geta character) + UNICODE_BAD_SEQUENCE = 0xfffd + }; + + // EUC-JP decoder + std::string decode_eucjp(std::string_view src) { + unsigned int len = 0; + std::u32string utf32_str; + utf32_str.resize(src.size()); + + // decoding + for (size_t i = 0; i < src.size(); ++i, ++len) { + unsigned char b1, b2, b3; + if (len >= utf32_str.size()) { + utf32_str.resize(utf32_str.size() * 2); + } + b1 = src[i]; + // end of text + if (b1 == 0x00) break; + // 1 byte sequence + if (b1 <= 0x7f) utf32_str[len] = (int)b1; + // 3 bytes sequence (JIS X 0213 plane 2) + else if (b1 == 0x8f) { + if ((i += 2) >= src.size()) break; + b2 = src[i - 1], b3 = src[i]; + if (0xa1 <= b2 && b2 <= 0xfe && 0xa1 <= b3 && b3 <= 0xfe) + utf32_str[len] = Encoding::jisx0213_2_unicode[(b2 - 0xa1 + 94) * 94 + (b3 - 0xa1)]; + // bad sequence + else { + utf32_str[len] = Encoding::UNICODE_BAD_SEQUENCE; + i -= 2; + } + } + // 2 bytes sequence + else /* b1 >= 0x80 */ { + if (++i >= src.size()) break; + b2 = src[i]; + // JIS X 0201 kana + if (b1 == 0x8e) utf32_str[len] = Encoding::jisx0201_2_unicode[b2]; + // JIS X 0213 plane 1 + else if (0xa1 <= b1 && b1 <= 0xfe && 0xa1 <= b2 && b2 <= 0xfe) + utf32_str[len] = Encoding::jisx0213_2_unicode[(b1 - 0xa1) * 94 + (b2 - 0xa1)]; + // bad sequence + else { + utf32_str[len] = Encoding::UNICODE_BAD_SEQUENCE; + --i; + } + } + } + utf32_str.resize(len); + + std::wstring_convert, char32_t> conv; + std::string utf8_str = conv.to_bytes(utf32_str); + + return utf8_str; + } +} diff --git a/librecomp/src/flash.cpp b/librecomp/src/flash.cpp new file mode 100644 index 0000000..519eaa3 --- /dev/null +++ b/librecomp/src/flash.cpp @@ -0,0 +1,141 @@ +#include +#include +#include "../ultramodern/ultra64.h" +#include "../ultramodern/ultramodern.hpp" +#include "recomp.h" + +// TODO move this out into ultramodern code + +constexpr uint32_t flash_size = 1024 * 1024 / 8; // 1Mbit +constexpr uint32_t page_size = 128; +constexpr uint32_t pages_per_sector = 128; +constexpr uint32_t page_count = flash_size / page_size; +constexpr uint32_t sector_size = page_size * pages_per_sector; +constexpr uint32_t sector_count = flash_size / sector_size; + +void save_write_ptr(const void* in, uint32_t offset, uint32_t count); +void save_write(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count); +void save_read(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count); +void save_clear(uint32_t start, uint32_t size, char value); + +std::array write_buffer; + +extern "C" void osFlashInit_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = ultramodern::flash_handle; +} + +extern "C" void osFlashReadStatus_recomp(uint8_t * rdram, recomp_context * ctx) { + PTR(u8) flash_status = ctx->r4; + + MEM_B(0, flash_status) = 0; +} + +extern "C" void osFlashReadId_recomp(uint8_t * rdram, recomp_context * ctx) { + PTR(u32) flash_type = ctx->r4; + PTR(u32) flash_maker = ctx->r5; + + // Mimic a real flash chip's type and maker, as some games actually check if one is present. + MEM_W(0, flash_type) = 0x11118001; + MEM_W(0, flash_maker) = 0x00C2001E; +} + +extern "C" void osFlashClearStatus_recomp(uint8_t * rdram, recomp_context * ctx) { + +} + +extern "C" void osFlashAllErase_recomp(uint8_t * rdram, recomp_context * ctx) { + save_clear(0, ultramodern::save_size, 0xFF); + + ctx->r2 = 0; +} + +extern "C" void osFlashAllEraseThrough_recomp(uint8_t * rdram, recomp_context * ctx) { + save_clear(0, ultramodern::save_size, 0xFF); + + ctx->r2 = 0; +} + +// This function is named sector but really means page. +extern "C" void osFlashSectorErase_recomp(uint8_t * rdram, recomp_context * ctx) { + uint32_t page_num = (uint32_t)ctx->r4; + + // Prevent out of bounds erase + if (page_num >= page_count) { + ctx->r2 = -1; + return; + } + + save_clear(page_num * page_size, page_size, 0xFF); + + ctx->r2 = 0; +} + +// Same naming issue as above. +extern "C" void osFlashSectorEraseThrough_recomp(uint8_t * rdram, recomp_context * ctx) { + uint32_t page_num = (uint32_t)ctx->r4; + + // Prevent out of bounds erase + if (page_num >= page_count) { + ctx->r2 = -1; + return; + } + + save_clear(page_num * page_size, page_size, 0xFF); + + ctx->r2 = 0; +} + +extern "C" void osFlashCheckEraseEnd_recomp(uint8_t * rdram, recomp_context * ctx) { + // All erases are blocking in this implementation, so this should always return OK. + ctx->r2 = 0; // FLASH_STATUS_ERASE_OK +} + +extern "C" void osFlashWriteBuffer_recomp(uint8_t * rdram, recomp_context * ctx) { + OSIoMesg* mb = TO_PTR(OSIoMesg, ctx->r4); + int32_t pri = ctx->r5; + PTR(void) dramAddr = ctx->r6; + PTR(OSMesgQueue) mq = ctx->r7; + + // Copy the input data into the write buffer + for (size_t i = 0; i < page_size; i++) { + write_buffer[i] = MEM_B(i, dramAddr); + } + + // Send the message indicating write completion + osSendMesg(PASS_RDRAM mq, 0, OS_MESG_NOBLOCK); + + ctx->r2 = 0; +} + +extern "C" void osFlashWriteArray_recomp(uint8_t * rdram, recomp_context * ctx) { + uint32_t page_num = ctx->r4; + + // Copy the write buffer into the save file + save_write_ptr(write_buffer.data(), page_num * page_size, page_size); + + ctx->r2 = 0; +} + +extern "C" void osFlashReadArray_recomp(uint8_t * rdram, recomp_context * ctx) { + OSIoMesg* mb = TO_PTR(OSIoMesg, ctx->r4); + int32_t pri = ctx->r5; + uint32_t page_num = ctx->r6; + PTR(void) dramAddr = ctx->r7; + uint32_t n_pages = MEM_W(0x10, ctx->r29); + PTR(OSMesgQueue) mq = MEM_W(0x14, ctx->r29); + + uint32_t offset = page_num * page_size; + uint32_t count = n_pages * page_size; + + // Read from the save file into the provided buffer + save_read(PASS_RDRAM dramAddr, offset, count); + + // Send the message indicating read completion + osSendMesg(PASS_RDRAM mq, 0, OS_MESG_NOBLOCK); + + ctx->r2 = 0; +} + +extern "C" void osFlashChange_recomp(uint8_t * rdram, recomp_context * ctx) { + assert(false); +} diff --git a/librecomp/src/math_routines.cpp b/librecomp/src/math_routines.cpp new file mode 100644 index 0000000..234e96f --- /dev/null +++ b/librecomp/src/math_routines.cpp @@ -0,0 +1,82 @@ +#include "../ultramodern/ultramodern.hpp" +#include "recomp.h" + +// TODO remove these by implementing the necessary instructions and control flow handling in the recompiler. +// This has already been partially completed. + +extern "C" void __udivdi3_recomp(uint8_t * rdram, recomp_context * ctx) { + uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu); + uint64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu); + uint64_t ret = a / b; + + ctx->r2 = (int32_t)(ret >> 32); + ctx->r3 = (int32_t)(ret >> 0); +} + +extern "C" void __divdi3_recomp(uint8_t * rdram, recomp_context * ctx) { + int64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu); + int64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu); + int64_t ret = a / b; + + ctx->r2 = (int32_t)(ret >> 32); + ctx->r3 = (int32_t)(ret >> 0); +} + +extern "C" void __umoddi3_recomp(uint8_t * rdram, recomp_context * ctx) { + uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu); + uint64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu); + uint64_t ret = a % b; + + ctx->r2 = (int32_t)(ret >> 32); + ctx->r3 = (int32_t)(ret >> 0); +} + +extern "C" void __ull_div_recomp(uint8_t * rdram, recomp_context * ctx) { + uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu); + uint64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu); + uint64_t ret = a / b; + + ctx->r2 = (int32_t)(ret >> 32); + ctx->r3 = (int32_t)(ret >> 0); +} + +extern "C" void __ll_div_recomp(uint8_t * rdram, recomp_context * ctx) { + int64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu); + int64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu); + int64_t ret = a / b; + + ctx->r2 = (int32_t)(ret >> 32); + ctx->r3 = (int32_t)(ret >> 0); +} + +extern "C" void __ll_mul_recomp(uint8_t * rdram, recomp_context * ctx) { + uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu); + uint64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu); + uint64_t ret = a * b; + + ctx->r2 = (int32_t)(ret >> 32); + ctx->r3 = (int32_t)(ret >> 0); +} + +extern "C" void __ull_rem_recomp(uint8_t * rdram, recomp_context * ctx) { + uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu); + uint64_t b = (ctx->r6 << 32) | ((ctx->r7 << 0) & 0xFFFFFFFFu); + uint64_t ret = a % b; + + ctx->r2 = (int32_t)(ret >> 32); + ctx->r3 = (int32_t)(ret >> 0); +} + +extern "C" void __ull_to_d_recomp(uint8_t * rdram, recomp_context * ctx) { + uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu); + double ret = (double)a; + + ctx->f0.d = ret; +} + +extern "C" void __ull_to_f_recomp(uint8_t * rdram, recomp_context * ctx) { + uint64_t a = (ctx->r4 << 32) | ((ctx->r5 << 0) & 0xFFFFFFFFu); + float ret = (float)a; + + ctx->f0.fl = ret; +} diff --git a/librecomp/src/overlays.cpp b/librecomp/src/overlays.cpp new file mode 100644 index 0000000..3295552 --- /dev/null +++ b/librecomp/src/overlays.cpp @@ -0,0 +1,165 @@ +#include +#include +#include +#include "recomp.h" +#include "recomp_overlays.h" +#include "../RecompiledFuncs/recomp_overlays.inl" + +constexpr size_t num_code_sections = ARRLEN(section_table); + +// SectionTableEntry sections[] defined in recomp_overlays.inl + +struct LoadedSection { + int32_t loaded_ram_addr; + size_t section_table_index; + + LoadedSection(int32_t loaded_ram_addr_, size_t section_table_index_) { + loaded_ram_addr = loaded_ram_addr_; + section_table_index = section_table_index_; + } + + bool operator<(const LoadedSection& rhs) { + return loaded_ram_addr < rhs.loaded_ram_addr; + } +}; + +std::vector loaded_sections{}; +std::unordered_map func_map{}; + +void load_overlay(size_t section_table_index, int32_t ram) { + const SectionTableEntry& section = section_table[section_table_index]; + for (size_t function_index = 0; function_index < section.num_funcs; function_index++) { + const FuncEntry& func = section.funcs[function_index]; + func_map[ram + func.offset] = func.func; + } + loaded_sections.emplace_back(ram, section_table_index); + section_addresses[section.index] = ram; +} + +void load_special_overlay(const SectionTableEntry& section, int32_t ram) { + for (size_t function_index = 0; function_index < section.num_funcs; function_index++) { + const FuncEntry& func = section.funcs[function_index]; + func_map[ram + func.offset] = func.func; + } +} + + +extern "C" { +int32_t section_addresses[num_sections]; +} + +extern "C" void load_overlays(uint32_t rom, int32_t ram_addr, uint32_t size) { + // Search for the first section that's included in the loaded rom range + // Sections were sorted by `init_overlays` so we can use the bounds functions + auto lower = std::lower_bound(§ion_table[0], §ion_table[num_code_sections], rom, + [](const SectionTableEntry& entry, uint32_t addr) { + return entry.rom_addr < addr; + } + ); + auto upper = std::upper_bound(§ion_table[0], §ion_table[num_code_sections], (uint32_t)(rom + size), + [](uint32_t addr, const SectionTableEntry& entry) { + return addr < entry.size + entry.rom_addr; + } + ); + // Load the overlays that were found + for (auto it = lower; it != upper; ++it) { + load_overlay(std::distance(§ion_table[0], it), it->rom_addr - rom + ram_addr); + } +} + +extern "C" void unload_overlays(int32_t ram_addr, uint32_t size); + +extern "C" void unload_overlay_by_id(uint32_t id) { + uint32_t section_table_index = overlay_sections_by_index[id]; + const SectionTableEntry& section = section_table[section_table_index]; + + auto find_it = std::find_if(loaded_sections.begin(), loaded_sections.end(), [section_table_index](const LoadedSection& s) { return s.section_table_index == section_table_index; }); + + if (find_it != loaded_sections.end()) { + // Determine where each function was loaded to and remove that entry from the function map + for (size_t func_index = 0; func_index < section.num_funcs; func_index++) { + const auto& func = section.funcs[func_index]; + uint32_t func_address = func.offset + find_it->loaded_ram_addr; + func_map.erase(func_address); + } + // Reset the section's address in the address table + section_addresses[section.index] = section.ram_addr; + // Remove the section from the loaded section map + loaded_sections.erase(find_it); + } +} + +extern "C" void load_overlay_by_id(uint32_t id, uint32_t ram_addr) { + uint32_t section_table_index = overlay_sections_by_index[id]; + const SectionTableEntry& section = section_table[section_table_index]; + int32_t prev_address = section_addresses[section.index]; + if (/*ram_addr >= 0x80000000 && ram_addr < 0x81000000) {*/ prev_address == section.ram_addr) { + load_overlay(section_table_index, ram_addr); + } + else { + int32_t new_address = prev_address + ram_addr; + unload_overlay_by_id(id); + load_overlay(section_table_index, new_address); + } +} + +extern "C" void unload_overlays(int32_t ram_addr, uint32_t size) { + for (auto it = loaded_sections.begin(); it != loaded_sections.end();) { + const auto& section = section_table[it->section_table_index]; + + // Check if the unloaded region overlaps with the loaded section + if (ram_addr < (it->loaded_ram_addr + section.size) && (ram_addr + size) >= it->loaded_ram_addr) { + // Check if the section isn't entirely in the loaded region + if (ram_addr > it->loaded_ram_addr || (ram_addr + size) < (it->loaded_ram_addr + section.size)) { + fprintf(stderr, + "Cannot partially unload section\n" + " rom: 0x%08X size: 0x%08X loaded_addr: 0x%08X\n" + " unloaded_ram: 0x%08X unloaded_size : 0x%08X\n", + section.rom_addr, section.size, it->loaded_ram_addr, ram_addr, size); + assert(false); + std::exit(EXIT_FAILURE); + } + // Determine where each function was loaded to and remove that entry from the function map + for (size_t func_index = 0; func_index < section.num_funcs; func_index++) { + const auto& func = section.funcs[func_index]; + uint32_t func_address = func.offset + it->loaded_ram_addr; + func_map.erase(func_address); + } + // Reset the section's address in the address table + section_addresses[section.index] = section.ram_addr; + // Remove the section from the loaded section map + it = loaded_sections.erase(it); + // Skip incrementing the iterator + continue; + } + ++it; + } +} + +void load_patch_functions(); + +void init_overlays() { + for (size_t section_index = 0; section_index < num_code_sections; section_index++) { + section_addresses[section_table[section_index].index] = section_table[section_index].ram_addr; + } + + // Sort the executable sections by rom address + std::sort(§ion_table[0], §ion_table[num_code_sections], + [](const SectionTableEntry& a, const SectionTableEntry& b) { + return a.rom_addr < b.rom_addr; + } + ); + + load_patch_functions(); +} + +extern "C" recomp_func_t * get_function(int32_t addr) { + auto func_find = func_map.find(addr); + if (func_find == func_map.end()) { + fprintf(stderr, "Failed to find function at 0x%08X\n", addr); + assert(false); + std::exit(EXIT_FAILURE); + } + return func_find->second; +} + diff --git a/librecomp/src/pak.cpp b/librecomp/src/pak.cpp new file mode 100644 index 0000000..bf50132 --- /dev/null +++ b/librecomp/src/pak.cpp @@ -0,0 +1,35 @@ +#include "recomp.h" +#include "../ultramodern/ultra64.h" +#include "../ultramodern/ultramodern.hpp" + +extern "C" void osPfsInitPak_recomp(uint8_t * rdram, recomp_context* ctx) { + ctx->r2 = 1; // PFS_ERR_NOPACK +} + +extern "C" void osPfsFreeBlocks_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = 1; // PFS_ERR_NOPACK +} + +extern "C" void osPfsAllocateFile_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = 1; // PFS_ERR_NOPACK +} + +extern "C" void osPfsDeleteFile_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = 1; // PFS_ERR_NOPACK +} + +extern "C" void osPfsFileState_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = 1; // PFS_ERR_NOPACK +} + +extern "C" void osPfsFindFile_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = 1; // PFS_ERR_NOPACK +} + +extern "C" void osPfsReadWriteFile_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = 1; // PFS_ERR_NOPACK +} + +extern "C" void osPfsChecker_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = 1; // PFS_ERR_NOPACK +} diff --git a/librecomp/src/patch_loading.cpp b/librecomp/src/patch_loading.cpp new file mode 100644 index 0000000..225bef9 --- /dev/null +++ b/librecomp/src/patch_loading.cpp @@ -0,0 +1,11 @@ +#include +#include +#include +#include "recomp.h" +#include "../../RecompiledPatches/recomp_overlays.inl" + +void load_special_overlay(const SectionTableEntry& section, int32_t ram); + +void load_patch_functions() { + load_special_overlay(section_table[0], section_table[0].ram_addr); +} diff --git a/librecomp/src/pi.cpp b/librecomp/src/pi.cpp new file mode 100644 index 0000000..0c8a09d --- /dev/null +++ b/librecomp/src/pi.cpp @@ -0,0 +1,291 @@ +#include +#include +#include +#include +#include +#include +#include "recomp.h" +#include "recomp_game.h" +#include "recomp_config.h" +#include "../ultramodern/ultra64.h" +#include "../ultramodern/ultramodern.hpp" + +static std::vector rom; + +bool recomp::is_rom_loaded() { + return !rom.empty(); +} + +void recomp::set_rom_contents(std::vector&& new_rom) { + rom = std::move(new_rom); +} + +// Flashram occupies the same physical address as sram, but that issue is avoided because libultra exposes +// a high-level interface for flashram. Because that high-level interface is reimplemented, low level accesses +// that involve physical addresses don't need to be handled for flashram. +constexpr uint32_t sram_base = 0x08000000; +constexpr uint32_t rom_base = 0x10000000; +constexpr uint32_t drive_base = 0x06000000; + +constexpr uint32_t k1_to_phys(uint32_t addr) { + return addr & 0x1FFFFFFF; +} + +constexpr uint32_t phys_to_k1(uint32_t addr) { + return addr | 0xA0000000; +} + +extern "C" void __osPiGetAccess_recomp(uint8_t* rdram, recomp_context* ctx) { +} + +extern "C" void __osPiRelAccess_recomp(uint8_t* rdram, recomp_context* ctx) { +} + +extern "C" void osCartRomInit_recomp(uint8_t* rdram, recomp_context* ctx) { + OSPiHandle* handle = TO_PTR(OSPiHandle, ultramodern::cart_handle); + handle->type = 0; // cart + handle->baseAddress = phys_to_k1(rom_base); + handle->domain = 0; + + ctx->r2 = (gpr)ultramodern::cart_handle; +} + +extern "C" void osDriveRomInit_recomp(uint8_t * rdram, recomp_context * ctx) { + OSPiHandle* handle = TO_PTR(OSPiHandle, ultramodern::drive_handle); + handle->type = 1; // bulk + handle->baseAddress = phys_to_k1(drive_base); + handle->domain = 0; + + ctx->r2 = (gpr)ultramodern::drive_handle; +} + +extern "C" void osCreatePiManager_recomp(uint8_t* rdram, recomp_context* ctx) { + ; +} + +void recomp::do_rom_read(uint8_t* rdram, gpr ram_address, uint32_t physical_addr, size_t num_bytes) { + // TODO use word copies when possible + + // TODO handle misaligned DMA + assert((physical_addr & 0x1) == 0 && "Only PI DMA from aligned ROM addresses is currently supported"); + assert((ram_address & 0x7) == 0 && "Only PI DMA to aligned RDRAM addresses is currently supported"); + assert((num_bytes & 0x1) == 0 && "Only PI DMA with aligned sizes is currently supported"); + uint8_t* rom_addr = rom.data() + physical_addr - rom_base; + for (size_t i = 0; i < num_bytes; i++) { + MEM_B(i, ram_address) = *rom_addr; + rom_addr++; + } +} + +void recomp::do_rom_pio(uint8_t* rdram, gpr ram_address, uint32_t physical_addr) { + assert((physical_addr & 0x3) == 0 && "PIO not 4-byte aligned in device, currently unsupported"); + assert((ram_address & 0x3) == 0 && "PIO not 4-byte aligned in RDRAM, currently unsupported"); + uint8_t* rom_addr = rom.data() + physical_addr - rom_base; + MEM_B(0, ram_address) = *rom_addr++; + MEM_B(1, ram_address) = *rom_addr++; + MEM_B(2, ram_address) = *rom_addr++; + MEM_B(3, ram_address) = *rom_addr++; +} + +struct { + std::array save_buffer; + std::thread saving_thread; + moodycamel::LightweightSemaphore write_sempahore; + std::mutex save_buffer_mutex; +} save_context; + +const std::u8string save_folder = u8"saves"; +const std::u8string save_filename = std::u8string{recomp::mm_game_id} + u8".bin"; + +std::filesystem::path get_save_file_path() { + return recomp::get_app_folder_path() / save_folder / save_filename; +} + +void update_save_file() { + std::ofstream save_file{ get_save_file_path(), std::ios_base::binary }; + + if (save_file.good()) { + std::lock_guard lock{ save_context.save_buffer_mutex }; + save_file.write(save_context.save_buffer.data(), save_context.save_buffer.size()); + } else { + fprintf(stderr, "Failed to save!\n"); + std::exit(EXIT_FAILURE); + } +} + +extern std::atomic_bool exited; + +void saving_thread_func(RDRAM_ARG1) { + while (!exited) { + bool save_buffer_updated = false; + // Repeatedly wait for a new action to be sent. + constexpr int64_t wait_time_microseconds = 10000; + constexpr int max_actions = 128; + int num_actions = 0; + + // Wait up to the given timeout for a write to come in. Allow multiple writes to coalesce together into a single save. + // Cap the number of coalesced writes to guarantee that the save buffer eventually gets written out to the file even if the game + // is constantly sending writes. + while (save_context.write_sempahore.wait(wait_time_microseconds) && num_actions < max_actions) { + save_buffer_updated = true; + num_actions++; + } + + // If an action came through that affected the save file, save the updated contents. + if (save_buffer_updated) { + update_save_file(); + } + } +} + +void save_write_ptr(const void* in, uint32_t offset, uint32_t count) { + { + std::lock_guard lock { save_context.save_buffer_mutex }; + memcpy(&save_context.save_buffer[offset], in, count); + } + + save_context.write_sempahore.signal(); +} + +void save_write(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count) { + { + std::lock_guard lock { save_context.save_buffer_mutex }; + for (uint32_t i = 0; i < count; i++) { + save_context.save_buffer[offset + i] = MEM_B(i, rdram_address); + } + } + + save_context.write_sempahore.signal(); +} + +void save_read(RDRAM_ARG PTR(void) rdram_address, uint32_t offset, uint32_t count) { + std::lock_guard lock { save_context.save_buffer_mutex }; + for (size_t i = 0; i < count; i++) { + MEM_B(i, rdram_address) = save_context.save_buffer[offset + i]; + } +} + +void save_clear(uint32_t start, uint32_t size, char value) { + { + std::lock_guard lock { save_context.save_buffer_mutex }; + std::fill_n(save_context.save_buffer.begin() + start, size, value); + } + + save_context.write_sempahore.signal(); +} + +void ultramodern::init_saving(RDRAM_ARG1) { + std::filesystem::path save_file_path = get_save_file_path(); + + // Ensure the save file directory exists. + std::filesystem::create_directories(save_file_path.parent_path()); + + // Read the save file if it exists. + std::ifstream save_file{ save_file_path, std::ios_base::binary }; + if (save_file.good()) { + save_file.read(save_context.save_buffer.data(), save_context.save_buffer.size()); + } else { + // Otherwise clear the save file to all zeroes. + save_context.save_buffer.fill(0); + } + + save_context.saving_thread = std::thread{saving_thread_func, PASS_RDRAM}; +} + +void ultramodern::join_saving_thread() { + save_context.saving_thread.join(); +} + +void do_dma(RDRAM_ARG PTR(OSMesgQueue) mq, gpr rdram_address, uint32_t physical_addr, uint32_t size, uint32_t direction) { + // TODO asynchronous transfer + // TODO implement unaligned DMA correctly + if (direction == 0) { + if (physical_addr >= rom_base) { + // read cart rom + recomp::do_rom_read(rdram, rdram_address, physical_addr, size); + + // Send a message to the mq to indicate that the transfer completed + osSendMesg(rdram, mq, 0, OS_MESG_NOBLOCK); + } else if (physical_addr >= sram_base) { + // read sram + save_read(rdram, rdram_address, physical_addr - sram_base, size); + + // Send a message to the mq to indicate that the transfer completed + osSendMesg(rdram, mq, 0, OS_MESG_NOBLOCK); + } else { + fprintf(stderr, "[WARN] PI DMA read from unknown region, phys address 0x%08X\n", physical_addr); + } + } else { + if (physical_addr >= rom_base) { + // write cart rom + throw std::runtime_error("ROM DMA write unimplemented"); + } else if (physical_addr >= sram_base) { + // write sram + save_write(rdram, rdram_address, physical_addr - sram_base, size); + + // Send a message to the mq to indicate that the transfer completed + osSendMesg(rdram, mq, 0, OS_MESG_NOBLOCK); + } else { + fprintf(stderr, "[WARN] PI DMA write to unknown region, phys address 0x%08X\n", physical_addr); + } + } +} + +extern "C" void osPiStartDma_recomp(RDRAM_ARG recomp_context* ctx) { + uint32_t mb = ctx->r4; + uint32_t pri = ctx->r5; + uint32_t direction = ctx->r6; + uint32_t devAddr = ctx->r7 | rom_base; + gpr dramAddr = MEM_W(0x10, ctx->r29); + uint32_t size = MEM_W(0x14, ctx->r29); + PTR(OSMesgQueue) mq = MEM_W(0x18, ctx->r29); + uint32_t physical_addr = k1_to_phys(devAddr); + + debug_printf("[pi] DMA from 0x%08X into 0x%08X of size 0x%08X\n", devAddr, dramAddr, size); + + do_dma(PASS_RDRAM mq, dramAddr, physical_addr, size, direction); + + ctx->r2 = 0; +} + +extern "C" void osEPiStartDma_recomp(RDRAM_ARG recomp_context* ctx) { + OSPiHandle* handle = TO_PTR(OSPiHandle, ctx->r4); + OSIoMesg* mb = TO_PTR(OSIoMesg, ctx->r5); + uint32_t direction = ctx->r6; + uint32_t devAddr = handle->baseAddress | mb->devAddr; + gpr dramAddr = mb->dramAddr; + uint32_t size = mb->size; + PTR(OSMesgQueue) mq = mb->hdr.retQueue; + uint32_t physical_addr = k1_to_phys(devAddr); + + debug_printf("[pi] DMA from 0x%08X into 0x%08X of size 0x%08X\n", devAddr, dramAddr, size); + + do_dma(PASS_RDRAM mq, dramAddr, physical_addr, size, direction); + + ctx->r2 = 0; +} + +extern "C" void osEPiReadIo_recomp(RDRAM_ARG recomp_context * ctx) { + OSPiHandle* handle = TO_PTR(OSPiHandle, ctx->r4); + uint32_t devAddr = handle->baseAddress | ctx->r5; + gpr dramAddr = ctx->r6; + uint32_t physical_addr = k1_to_phys(devAddr); + + if (physical_addr > rom_base) { + // cart rom + recomp::do_rom_pio(PASS_RDRAM dramAddr, physical_addr); + } else { + // sram + assert(false && "SRAM ReadIo unimplemented"); + } + + ctx->r2 = 0; +} + +extern "C" void osPiGetStatus_recomp(RDRAM_ARG recomp_context * ctx) { + ctx->r2 = 0; +} + +extern "C" void osPiRawStartDma_recomp(RDRAM_ARG recomp_context * ctx) { + ctx->r2 = 0; +} diff --git a/librecomp/src/print.cpp b/librecomp/src/print.cpp new file mode 100644 index 0000000..b0ea771 --- /dev/null +++ b/librecomp/src/print.cpp @@ -0,0 +1,72 @@ +#include + +#include "../ultramodern/ultra64.h" +#include "../ultramodern/ultramodern.hpp" +#include "recomp.h" +#include "euc-jp.h" + +extern "C" void __checkHardware_msp_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = 0; +} + +extern "C" void __checkHardware_kmc_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = 0; +} + +extern "C" void __checkHardware_isv_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = 0; +} + +extern "C" void __osInitialize_msp_recomp(uint8_t * rdram, recomp_context * ctx) { +} + +extern "C" void __osInitialize_kmc_recomp(uint8_t * rdram, recomp_context * ctx) { +} + +extern "C" void __osInitialize_isv_recomp(uint8_t * rdram, recomp_context * ctx) { +} + +extern "C" void isPrintfInit_recomp(uint8_t * rdram, recomp_context * ctx) { +} + +extern "C" void __osRdbSend_recomp(uint8_t * rdram, recomp_context * ctx) { + gpr buf = ctx->r4; + size_t size = ctx->r5; + u32 type = (u32)ctx->r6; + std::unique_ptr to_print = std::make_unique(size + 1); + + for (size_t i = 0; i < size; i++) { + to_print[i] = MEM_B(i, buf); + } + to_print[size] = '\x00'; + + fwrite(to_print.get(), 1, size, stdout); + + ctx->r2 = size; +} + +extern "C" void is_proutSyncPrintf_recomp(uint8_t * rdram, recomp_context * ctx) { + // Buffering to speed up print performance + static std::vector print_buffer; + + gpr buf = ctx->r5; + size_t size = ctx->r6; + + //for (size_t i = 0; i < size; i++) { + // // Add the new character to the buffer + // char cur_char = MEM_B(i, buf); + + // // If the new character is a newline, flush the buffer + // if (cur_char == '\n') { + // std::string utf8_str = Encoding::decode_eucjp(std::string_view{ print_buffer.data(), print_buffer.size() }); + // puts(utf8_str.c_str()); + // print_buffer.clear(); + // } else { + // print_buffer.push_back(cur_char); + // } + //} + + //fwrite(to_print.get(), size, 1, stdout); + + ctx->r2 = 1; +} diff --git a/librecomp/src/recomp.cpp b/librecomp/src/recomp.cpp new file mode 100644 index 0000000..d75b0f9 --- /dev/null +++ b/librecomp/src/recomp.cpp @@ -0,0 +1,440 @@ +#ifdef _WIN32 +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "recomp.h" +#include "recomp_overlays.h" +#include "recomp_game.h" +#include "recomp_config.h" +#include "xxHash/xxh3.h" +#include "../ultramodern/ultramodern.hpp" +#include "../../RecompiledPatches/patches_bin.h" +#include "mm_shader_cache.h" + +#ifdef _MSC_VER +inline uint32_t byteswap(uint32_t val) { + return _byteswap_ulong(val); +} +#else +constexpr uint32_t byteswap(uint32_t val) { + return __builtin_bswap32(val); +} +#endif + +struct RomEntry { + uint64_t xxhash3_value; + std::u8string stored_filename; + std::string internal_rom_name; +}; + +const std::unordered_map game_roms { + { recomp::Game::MM, { 0xEF18B4A9E2386169ULL, std::u8string{recomp::mm_game_id} + u8".z64", "ZELDA MAJORA'S MASK" }}, +}; + +bool check_hash(const std::vector& rom_data, uint64_t expected_hash) { + uint64_t calculated_hash = XXH3_64bits(rom_data.data(), rom_data.size()); + + return calculated_hash == expected_hash; +} + +static std::vector read_file(const std::filesystem::path& path) { + std::vector ret; + + std::ifstream file{ path, std::ios::binary}; + + if (file.good()) { + file.seekg(0, std::ios::end); + ret.resize(file.tellg()); + file.seekg(0, std::ios::beg); + + file.read(reinterpret_cast(ret.data()), ret.size()); + } + + return ret; +} + +bool write_file(const std::filesystem::path& path, const std::vector& data) { + std::ofstream out_file{ path, std::ios::binary }; + + if (!out_file.good()) { + return false; + } + + out_file.write(reinterpret_cast(data.data()), data.size()); + + return true; +} + +bool check_stored_rom(const RomEntry& game_entry) { + + std::vector stored_rom_data = read_file(recomp::get_app_folder_path() / game_entry.stored_filename); + + if (!check_hash(stored_rom_data, game_entry.xxhash3_value)) { + // Incorrect hash, remove the stored ROM file if it exists. + std::filesystem::remove(recomp::get_app_folder_path() / game_entry.stored_filename); + return false; + } + + return true; +} + +static std::unordered_set valid_game_roms; + +bool recomp::is_rom_valid(recomp::Game game) { + return valid_game_roms.contains(game); +} + +void recomp::check_all_stored_roms() { + for (const auto& cur_rom_entry: game_roms) { + if (check_stored_rom(cur_rom_entry.second)) { + valid_game_roms.insert(cur_rom_entry.first); + } + } +} + +bool recomp::load_stored_rom(recomp::Game game) { + auto find_it = game_roms.find(game); + + if (find_it == game_roms.end()) { + return false; + } + + std::vector stored_rom_data = read_file(recomp::get_app_folder_path() / find_it->second.stored_filename); + + if (!check_hash(stored_rom_data, find_it->second.xxhash3_value)) { + // The ROM no longer has the right hash, delete it. + std::filesystem::remove(recomp::get_app_folder_path() / find_it->second.stored_filename); + return false; + } + + recomp::set_rom_contents(std::move(stored_rom_data)); + return true; +} + +const std::array first_rom_bytes { 0x80, 0x37, 0x12, 0x40 }; + +enum class ByteswapType { + NotByteswapped, + Byteswapped4, + Byteswapped2, + Invalid +}; + +ByteswapType check_rom_start(const std::vector& rom_data) { + if (rom_data.size() < 4) { + return ByteswapType::Invalid; + } + + bool matched_all = true; + + auto check_match = [&](uint8_t index0, uint8_t index1, uint8_t index2, uint8_t index3) { + return + rom_data[0] == first_rom_bytes[index0] && + rom_data[1] == first_rom_bytes[index1] && + rom_data[2] == first_rom_bytes[index2] && + rom_data[3] == first_rom_bytes[index3]; + }; + + // Check if the ROM is already in the correct byte order. + if (check_match(0,1,2,3)) { + return ByteswapType::NotByteswapped; + } + + // Check if the ROM has been byteswapped in groups of 4 bytes. + if (check_match(3,2,1,0)) { + return ByteswapType::Byteswapped4; + } + + // Check if the ROM has been byteswapped in groups of 2 bytes. + if (check_match(1,0,3,2)) { + return ByteswapType::Byteswapped2; + } + + // No match found. + return ByteswapType::Invalid; +} + +void byteswap_data(std::vector& rom_data, size_t index_xor) { + for (size_t rom_pos = 0; rom_pos < rom_data.size(); rom_pos += 4) { + uint8_t temp0 = rom_data[rom_pos + 0]; + uint8_t temp1 = rom_data[rom_pos + 1]; + uint8_t temp2 = rom_data[rom_pos + 2]; + uint8_t temp3 = rom_data[rom_pos + 3]; + + rom_data[rom_pos + (0 ^ index_xor)] = temp0; + rom_data[rom_pos + (1 ^ index_xor)] = temp1; + rom_data[rom_pos + (2 ^ index_xor)] = temp2; + rom_data[rom_pos + (3 ^ index_xor)] = temp3; + } +} + +recomp::RomValidationError recomp::select_rom(const std::filesystem::path& rom_path, Game game) { + auto find_it = game_roms.find(game); + + if (find_it == game_roms.end()) { + return recomp::RomValidationError::OtherError; + } + + const RomEntry& game_entry = find_it->second; + + std::vector rom_data = read_file(rom_path); + + if (rom_data.empty()) { + return recomp::RomValidationError::FailedToOpen; + } + + // Pad the rom to the nearest multiple of 4 bytes. + rom_data.resize((rom_data.size() + 3) & ~3); + + ByteswapType byteswap_type = check_rom_start(rom_data); + + switch (byteswap_type) { + case ByteswapType::Invalid: + return recomp::RomValidationError::NotARom; + case ByteswapType::Byteswapped2: + byteswap_data(rom_data, 1); + break; + case ByteswapType::Byteswapped4: + byteswap_data(rom_data, 3); + break; + case ByteswapType::NotByteswapped: + break; + } + + if (!check_hash(rom_data, game_entry.xxhash3_value)) { + const std::string_view name{ reinterpret_cast(rom_data.data()) + 0x20, game_entry.internal_rom_name.size()}; + if (name == game_entry.internal_rom_name) { + return recomp::RomValidationError::IncorrectVersion; + } + else { + if (game == recomp::Game::MM && std::string_view{ reinterpret_cast(rom_data.data()) + 0x20, 19 } == "THE LEGEND OF ZELDA") { + return recomp::RomValidationError::NotYet; + } + else { + return recomp::RomValidationError::IncorrectRom; + } + } + } + + write_file(recomp::get_app_folder_path() / game_entry.stored_filename, rom_data); + + return recomp::RomValidationError::Good; +} + +extern "C" void osGetMemSize_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = 8 * 1024 * 1024; +} + +enum class StatusReg { + FR = 0x04000000, +}; + +extern "C" void cop0_status_write(recomp_context* ctx, gpr value) { + uint32_t old_sr = ctx->status_reg; + uint32_t new_sr = (uint32_t)value; + uint32_t changed = old_sr ^ new_sr; + + // Check if the FR bit changed + if (changed & (uint32_t)StatusReg::FR) { + // Check if the FR bit was set + if (new_sr & (uint32_t)StatusReg::FR) { + // FR = 1, odd single floats point to their own registers + ctx->f_odd = &ctx->f1.u32l; + ctx->mips3_float_mode = true; + } + // Otherwise, it was cleared + else { + // FR = 0, odd single floats point to the upper half of the previous register + ctx->f_odd = &ctx->f0.u32h; + ctx->mips3_float_mode = false; + } + + // Remove the FR bit from the changed bits as it's been handled + changed &= ~(uint32_t)StatusReg::FR; + } + + // If any other bits were changed, assert false as they're not handled currently + if (changed) { + printf("Unhandled status register bits changed: 0x%08X\n", changed); + assert(false); + exit(EXIT_FAILURE); + } + + // Update the status register in the context + ctx->status_reg = new_sr; +} + +extern "C" gpr cop0_status_read(recomp_context* ctx) { + return (gpr)(int32_t)ctx->status_reg; +} + +extern "C" void switch_error(const char* func, uint32_t vram, uint32_t jtbl) { + printf("Switch-case out of bounds in %s at 0x%08X for jump table at 0x%08X\n", func, vram, jtbl); + assert(false); + exit(EXIT_FAILURE); +} + +extern "C" void do_break(uint32_t vram) { + printf("Encountered break at original vram 0x%08X\n", vram); + assert(false); + exit(EXIT_FAILURE); +} + +void run_thread_function(uint8_t* rdram, uint64_t addr, uint64_t sp, uint64_t arg) { + recomp_context ctx{}; + ctx.r29 = sp; + ctx.r4 = arg; + ctx.mips3_float_mode = 0; + ctx.f_odd = &ctx.f0.u32h; + recomp_func_t* func = get_function(addr); + func(rdram, &ctx); +} + +// Recomp generation functions +extern "C" void recomp_entrypoint(uint8_t * rdram, recomp_context * ctx); +gpr get_entrypoint_address(); +const char* get_rom_name(); + +void read_patch_data(uint8_t* rdram, gpr patch_data_address) { + for (size_t i = 0; i < sizeof(mm_patches_bin); i++) { + MEM_B(i, patch_data_address) = mm_patches_bin[i]; + } +} + +void init(uint8_t* rdram, recomp_context* ctx) { + // Initialize the overlays + init_overlays(); + + // Get entrypoint from recomp function + gpr entrypoint = get_entrypoint_address(); + + // Load overlays in the first 1MB + load_overlays(0x1000, (int32_t)entrypoint, 1024 * 1024); + + // Initial 1MB DMA (rom address 0x1000 = physical address 0x10001000) + recomp::do_rom_read(rdram, entrypoint, 0x10001000, 0x100000); + + // Read in any extra data from patches + read_patch_data(rdram, (gpr)(s32)0x80801000); + + // Set up stack pointer + ctx->r29 = 0xFFFFFFFF803FFFF0u; + + // Set up context floats + ctx->f_odd = &ctx->f0.u32h; + ctx->mips3_float_mode = false; + + // Initialize variables normally set by IPL3 + constexpr int32_t osTvType = 0x80000300; + constexpr int32_t osRomType = 0x80000304; + constexpr int32_t osRomBase = 0x80000308; + constexpr int32_t osResetType = 0x8000030c; + constexpr int32_t osCicId = 0x80000310; + constexpr int32_t osVersion = 0x80000314; + constexpr int32_t osMemSize = 0x80000318; + constexpr int32_t osAppNMIBuffer = 0x8000031c; + MEM_W(osTvType, 0) = 1; // NTSC + MEM_W(osRomBase, 0) = 0xB0000000u; // standard rom base + MEM_W(osResetType, 0) = 0; // cold reset + MEM_W(osMemSize, 0) = 8 * 1024 * 1024; // 8MB +} + +std::atomic game_started = recomp::Game::None; + +void recomp::start_game(recomp::Game game) { + game_started.store(game); + game_started.notify_all(); +} + +bool ultramodern::is_game_started() { + return game_started.load() != recomp::Game::None; +} + +void set_audio_callbacks(const ultramodern::audio_callbacks_t& callbacks); +void set_input_callbacks(const ultramodern::input_callbacks_t& callback); + +std::atomic_bool exited = false; + +void ultramodern::quit() { + exited.store(true); + recomp::Game desired = recomp::Game::None; + game_started.compare_exchange_strong(desired, recomp::Game::Quit); + game_started.notify_all(); +} + +void recomp::start(ultramodern::WindowHandle window_handle, const ultramodern::audio_callbacks_t& audio_callbacks, const ultramodern::input_callbacks_t& input_callbacks, const ultramodern::gfx_callbacks_t& gfx_callbacks_) { + recomp::check_all_stored_roms(); + set_audio_callbacks(audio_callbacks); + set_input_callbacks(input_callbacks); + + ultramodern::gfx_callbacks_t gfx_callbacks = gfx_callbacks_; + + ultramodern::gfx_callbacks_t::gfx_data_t gfx_data{}; + + if (gfx_callbacks.create_gfx) { + gfx_data = gfx_callbacks.create_gfx(); + } + + if (window_handle == ultramodern::WindowHandle{}) { + if (gfx_callbacks.create_window) { + window_handle = gfx_callbacks.create_window(gfx_data); + } + else { + assert(false && "No create_window callback provided"); + } + } + + // Allocate rdram_buffer + std::unique_ptr rdram_buffer = std::make_unique(ultramodern::rdram_size); + std::memset(rdram_buffer.get(), 0, ultramodern::rdram_size); + + std::thread game_thread{[](ultramodern::WindowHandle window_handle, uint8_t* rdram) { + debug_printf("[Recomp] Starting\n"); + + ultramodern::set_native_thread_name("Game Start Thread"); + + ultramodern::preinit(rdram, window_handle); + + game_started.wait(recomp::Game::None); + recomp_context context{}; + + switch (game_started.load()) { + // TODO refactor this to allow a project to specify what entrypoint function to run for a give game. + case recomp::Game::MM: + if (!recomp::load_stored_rom(recomp::Game::MM)) { + recomp::message_box("Error opening stored ROM! Please restart this program."); + } + ultramodern::load_shader_cache({mm_shader_cache_bytes, sizeof(mm_shader_cache_bytes)}); + init(rdram, &context); + try { + recomp_entrypoint(rdram, &context); + } catch (ultramodern::thread_terminated& terminated) { + + } + break; + case recomp::Game::Quit: + break; + } + + debug_printf("[Recomp] Quitting\n"); + }, window_handle, rdram_buffer.get()}; + + while (!exited) { + ultramodern::sleep_milliseconds(1); + if (gfx_callbacks.update_gfx != nullptr) { + gfx_callbacks.update_gfx(gfx_data); + } + } + game_thread.join(); + ultramodern::join_event_threads(); + ultramodern::join_thread_cleaner_thread(); + ultramodern::join_saving_thread(); +} diff --git a/librecomp/src/sp.cpp b/librecomp/src/sp.cpp new file mode 100644 index 0000000..b3ee63f --- /dev/null +++ b/librecomp/src/sp.cpp @@ -0,0 +1,50 @@ +#include +#include +#include "../ultramodern/ultramodern.hpp" +#include "recomp.h" + +extern "C" void osSpTaskLoad_recomp(uint8_t* rdram, recomp_context* ctx) { + // Nothing to do here +} + +bool dump_frame = false; + +extern "C" void osSpTaskStartGo_recomp(uint8_t* rdram, recomp_context* ctx) { + //printf("[sp] osSpTaskStartGo(0x%08X)\n", (uint32_t)ctx->r4); + OSTask* task = TO_PTR(OSTask, ctx->r4); + if (task->t.type == M_GFXTASK) { + //printf("[sp] Gfx task: %08X\n", (uint32_t)ctx->r4); + } else if (task->t.type == M_AUDTASK) { + //printf("[sp] Audio task: %08X\n", (uint32_t)ctx->r4); + } + // For debugging + if (dump_frame) { + char addr_str[32]; + constexpr size_t ram_size = 0x800000; + std::unique_ptr ram_unswapped = std::make_unique(ram_size); + snprintf(addr_str, sizeof(addr_str) - 1, "%08X", task->t.data_ptr); + addr_str[sizeof(addr_str) - 1] = '\0'; + std::ofstream dump_file{ "ramdump" + std::string{ addr_str } + ".bin", std::ios::binary}; + + for (size_t i = 0; i < ram_size; i++) { + ram_unswapped[i] = rdram[i ^ 3]; + } + + dump_file.write(ram_unswapped.get(), ram_size); + dump_frame = false; + } + ultramodern::submit_rsp_task(rdram, ctx->r4); +} + +extern "C" void osSpTaskYield_recomp(uint8_t* rdram, recomp_context* ctx) { + // Ignore yield requests (acts as if the task completed before it received the yield request) +} + +extern "C" void osSpTaskYielded_recomp(uint8_t* rdram, recomp_context* ctx) { + // Task yield requests are ignored, so always return 0 as tasks will never be yielded + ctx->r2 = 0; +} + +extern "C" void __osSpSetPc_recomp(uint8_t* rdram, recomp_context* ctx) { + assert(false); +} diff --git a/librecomp/src/ultra_stubs.cpp b/librecomp/src/ultra_stubs.cpp new file mode 100644 index 0000000..f2ac704 --- /dev/null +++ b/librecomp/src/ultra_stubs.cpp @@ -0,0 +1,45 @@ +#include "../ultramodern/ultra64.h" +#include "../ultramodern/ultramodern.hpp" +#include "recomp.h" + +// None of these functions need to be reimplemented, so stub them out +extern "C" void osUnmapTLBAll_recomp(uint8_t * rdram, recomp_context * ctx) { + // TODO this will need to be implemented in the future for any games that actually use the TLB +} + +extern "C" void osVoiceInit_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = 11; // CONT_ERR_DEVICE +} + +extern "C" void osVoiceSetWord_recomp(uint8_t * rdram, recomp_context * ctx) { + assert(false); +} + +extern "C" void osVoiceCheckWord_recomp(uint8_t * rdram, recomp_context * ctx) { + assert(false); +} + +extern "C" void osVoiceStopReadData_recomp(uint8_t * rdram, recomp_context * ctx) { + assert(false); +} + +extern "C" void osVoiceMaskDictionary_recomp(uint8_t * rdram, recomp_context * ctx) { + assert(false); +} + +extern "C" void osVoiceStartReadData_recomp(uint8_t * rdram, recomp_context * ctx) { + assert(false); +} + +extern "C" void osVoiceControlGain_recomp(uint8_t * rdram, recomp_context * ctx) { + assert(false); +} + +extern "C" void osVoiceGetReadData_recomp(uint8_t * rdram, recomp_context * ctx) { + assert(false); +} + +extern "C" void osVoiceClearDictionary_recomp(uint8_t * rdram, recomp_context * ctx) { + assert(false); +} + diff --git a/librecomp/src/ultra_translation.cpp b/librecomp/src/ultra_translation.cpp new file mode 100644 index 0000000..771758f --- /dev/null +++ b/librecomp/src/ultra_translation.cpp @@ -0,0 +1,163 @@ +#include +#include "../ultramodern/ultra64.h" +#include "../ultramodern/ultramodern.hpp" +#include "recomp.h" + +extern "C" void osInitialize_recomp(uint8_t * rdram, recomp_context * ctx) { + osInitialize(); +} + +extern "C" void __osInitialize_common_recomp(uint8_t * rdram, recomp_context * ctx) { + osInitialize(); +} + +extern "C" void osCreateThread_recomp(uint8_t* rdram, recomp_context* ctx) { + osCreateThread(rdram, (int32_t)ctx->r4, (OSId)ctx->r5, (int32_t)ctx->r6, (int32_t)ctx->r7, + (int32_t)MEM_W(0x10, ctx->r29), (OSPri)MEM_W(0x14, ctx->r29)); +} + +extern "C" void osStartThread_recomp(uint8_t* rdram, recomp_context* ctx) { + osStartThread(rdram, (int32_t)ctx->r4); +} + +extern "C" void osStopThread_recomp(uint8_t * rdram, recomp_context * ctx) { + osStopThread(rdram, (int32_t)ctx->r4); +} + +extern "C" void osDestroyThread_recomp(uint8_t * rdram, recomp_context * ctx) { + osDestroyThread(rdram, (int32_t)ctx->r4); +} + +extern "C" void osYieldThread_recomp(uint8_t * rdram, recomp_context * ctx) { + assert(false); + // osYieldThread(rdram); +} + +extern "C" void osSetThreadPri_recomp(uint8_t* rdram, recomp_context* ctx) { + osSetThreadPri(rdram, (int32_t)ctx->r4, (OSPri)ctx->r5); +} + +extern "C" void osGetThreadPri_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = osGetThreadPri(rdram, (int32_t)ctx->r4); +} + +extern "C" void osGetThreadId_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = osGetThreadId(rdram, (int32_t)ctx->r4); +} + +extern "C" void osCreateMesgQueue_recomp(uint8_t* rdram, recomp_context* ctx) { + osCreateMesgQueue(rdram, (int32_t)ctx->r4, (int32_t)ctx->r5, (s32)ctx->r6); +} + +extern "C" void osRecvMesg_recomp(uint8_t* rdram, recomp_context* ctx) { + ctx->r2 = osRecvMesg(rdram, (int32_t)ctx->r4, (int32_t)ctx->r5, (s32)ctx->r6); +} + +extern "C" void osSendMesg_recomp(uint8_t* rdram, recomp_context* ctx) { + ctx->r2 = osSendMesg(rdram, (int32_t)ctx->r4, (OSMesg)ctx->r5, (s32)ctx->r6); +} + +extern "C" void osJamMesg_recomp(uint8_t* rdram, recomp_context* ctx) { + ctx->r2 = osJamMesg(rdram, (int32_t)ctx->r4, (OSMesg)ctx->r5, (s32)ctx->r6); +} + +extern "C" void osSetEventMesg_recomp(uint8_t* rdram, recomp_context* ctx) { + osSetEventMesg(rdram, (OSEvent)ctx->r4, (int32_t)ctx->r5, (OSMesg)ctx->r6); +} + +extern "C" void osViSetEvent_recomp(uint8_t * rdram, recomp_context * ctx) { + osViSetEvent(rdram, (int32_t)ctx->r4, (OSMesg)ctx->r5, (u32)ctx->r6); +} + +extern "C" void osGetCount_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = osGetCount(); +} + +extern "C" void osGetTime_recomp(uint8_t * rdram, recomp_context * ctx) { + uint64_t total_count = osGetTime(); + ctx->r2 = (int32_t)(total_count >> 32); + ctx->r3 = (int32_t)(total_count >> 0); +} + +extern "C" void osSetTimer_recomp(uint8_t * rdram, recomp_context * ctx) { + uint64_t countdown = ((uint64_t)(ctx->r6) << 32) | ((ctx->r7) & 0xFFFFFFFFu); + uint64_t interval = load_doubleword(rdram, ctx->r29, 0x10); + ctx->r2 = osSetTimer(rdram, (int32_t)ctx->r4, countdown, interval, (int32_t)MEM_W(0x18, ctx->r29), (OSMesg)MEM_W(0x1C, ctx->r29)); +} + +extern "C" void osStopTimer_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = osStopTimer(rdram, (int32_t)ctx->r4); +} + +extern "C" void osVirtualToPhysical_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = osVirtualToPhysical((int32_t)ctx->r4); +} + +extern "C" void osInvalDCache_recomp(uint8_t * rdram, recomp_context * ctx) { + ; +} + +extern "C" void osInvalICache_recomp(uint8_t * rdram, recomp_context * ctx) { + ; +} + +extern "C" void osWritebackDCache_recomp(uint8_t * rdram, recomp_context * ctx) { + ; +} + +extern "C" void osWritebackDCacheAll_recomp(uint8_t * rdram, recomp_context * ctx) { + ; +} + +extern "C" void osSetIntMask_recomp(uint8_t * rdram, recomp_context * ctx) { + ; +} + +extern "C" void __osDisableInt_recomp(uint8_t * rdram, recomp_context * ctx) { + ; +} + +extern "C" void __osRestoreInt_recomp(uint8_t * rdram, recomp_context * ctx) { + ; +} + +extern "C" void __osSetFpcCsr_recomp(uint8_t * rdram, recomp_context * ctx) { + ctx->r2 = 0; +} + +// For the Mario Party games (not working) +//extern "C" void longjmp_recomp(uint8_t * rdram, recomp_context * ctx) { +// RecompJmpBuf* buf = TO_PTR(RecompJmpBuf, ctx->r4); +// +// // Check if this is a buffer that was set up with setjmp +// if (buf->magic == SETJMP_MAGIC) { +// // If so, longjmp to it +// // Setjmp/longjmp does not work across threads, so verify that this buffer was made by this thread +// assert(buf->owner == ultramodern::this_thread()); +// longjmp(buf->storage->buffer, ctx->r5); +// } else { +// // Otherwise, check if it was one built manually by the game with $ra pointing to a function +// gpr sp = MEM_W(0, ctx->r4); +// gpr ra = MEM_W(4, ctx->r4); +// ctx->r29 = sp; +// recomp_func_t* target = LOOKUP_FUNC(ra); +// if (target == nullptr) { +// fprintf(stderr, "Failed to find function for manual longjmp\n"); +// std::quick_exit(EXIT_FAILURE); +// } +// target(rdram, ctx); +// +// // TODO kill this thread if the target function returns +// assert(false); +// } +//} +// +//#undef setjmp_recomp +//extern "C" void setjmp_recomp(uint8_t * rdram, recomp_context * ctx) { +// fprintf(stderr, "Program called setjmp_recomp\n"); +// std::quick_exit(EXIT_FAILURE); +//} +// +//extern "C" int32_t osGetThreadEx(void) { +// return ultramodern::this_thread(); +//} diff --git a/librecomp/src/vi.cpp b/librecomp/src/vi.cpp new file mode 100644 index 0000000..309e984 --- /dev/null +++ b/librecomp/src/vi.cpp @@ -0,0 +1,47 @@ +#include "../ultramodern/ultramodern.hpp" +#include "recomp.h" + +extern "C" void osViSetYScale_recomp(uint8_t* rdram, recomp_context * ctx) { + osViSetYScale(ctx->f12.fl); +} + +extern "C" void osViSetXScale_recomp(uint8_t* rdram, recomp_context * ctx) { + osViSetXScale(ctx->f12.fl); +} + +extern "C" void osCreateViManager_recomp(uint8_t* rdram, recomp_context* ctx) { + ; +} + +extern "C" void osViBlack_recomp(uint8_t* rdram, recomp_context* ctx) { + osViBlack((uint32_t)ctx->r4); +} + +extern "C" void osViSetSpecialFeatures_recomp(uint8_t* rdram, recomp_context* ctx) { + osViSetSpecialFeatures((uint32_t)ctx->r4); +} + +extern "C" void osViGetCurrentFramebuffer_recomp(uint8_t* rdram, recomp_context* ctx) { + ctx->r2 = (gpr)(int32_t)osViGetCurrentFramebuffer(); +} + +extern "C" void osViGetNextFramebuffer_recomp(uint8_t* rdram, recomp_context* ctx) { + ctx->r2 = (gpr)(int32_t)osViGetNextFramebuffer(); +} + +extern "C" void osViSwapBuffer_recomp(uint8_t* rdram, recomp_context* ctx) { + osViSwapBuffer(rdram, (int32_t)ctx->r4); +} + +extern "C" void osViSetMode_recomp(uint8_t* rdram, recomp_context* ctx) { + osViSetMode(rdram, (int32_t)ctx->r4); +} + +extern uint64_t total_vis; + +extern "C" void wait_one_frame(uint8_t* rdram, recomp_context* ctx) { + uint64_t cur_vis = total_vis; + while (cur_vis == total_vis) { + std::this_thread::yield(); + } +} diff --git a/rt64 b/rt64 new file mode 160000 index 0000000..ca26fb8 --- /dev/null +++ b/rt64 @@ -0,0 +1 @@ +Subproject commit ca26fb8096b9af117c668e8dfaa49fdde815c3b7 diff --git a/thirdparty/concurrentqueue/blockingconcurrentqueue.h b/thirdparty/concurrentqueue/blockingconcurrentqueue.h new file mode 100644 index 0000000..205a4db --- /dev/null +++ b/thirdparty/concurrentqueue/blockingconcurrentqueue.h @@ -0,0 +1,582 @@ +// Provides an efficient blocking version of moodycamel::ConcurrentQueue. +// ©2015-2020 Cameron Desrochers. Distributed under the terms of the simplified +// BSD license, available at the top of concurrentqueue.h. +// Also dual-licensed under the Boost Software License (see LICENSE.md) +// Uses Jeff Preshing's semaphore implementation (under the terms of its +// separate zlib license, see lightweightsemaphore.h). + +#pragma once + +#include "concurrentqueue.h" +#include "lightweightsemaphore.h" + +#include +#include +#include +#include +#include + +namespace moodycamel +{ +// This is a blocking version of the queue. It has an almost identical interface to +// the normal non-blocking version, with the addition of various wait_dequeue() methods +// and the removal of producer-specific dequeue methods. +template +class BlockingConcurrentQueue +{ +private: + typedef ::moodycamel::ConcurrentQueue ConcurrentQueue; + typedef ::moodycamel::LightweightSemaphore LightweightSemaphore; + +public: + typedef typename ConcurrentQueue::producer_token_t producer_token_t; + typedef typename ConcurrentQueue::consumer_token_t consumer_token_t; + + typedef typename ConcurrentQueue::index_t index_t; + typedef typename ConcurrentQueue::size_t size_t; + typedef typename std::make_signed::type ssize_t; + + static const size_t BLOCK_SIZE = ConcurrentQueue::BLOCK_SIZE; + static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = ConcurrentQueue::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD; + static const size_t EXPLICIT_INITIAL_INDEX_SIZE = ConcurrentQueue::EXPLICIT_INITIAL_INDEX_SIZE; + static const size_t IMPLICIT_INITIAL_INDEX_SIZE = ConcurrentQueue::IMPLICIT_INITIAL_INDEX_SIZE; + static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = ConcurrentQueue::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE; + static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = ConcurrentQueue::EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE; + static const size_t MAX_SUBQUEUE_SIZE = ConcurrentQueue::MAX_SUBQUEUE_SIZE; + +public: + // Creates a queue with at least `capacity` element slots; note that the + // actual number of elements that can be inserted without additional memory + // allocation depends on the number of producers and the block size (e.g. if + // the block size is equal to `capacity`, only a single block will be allocated + // up-front, which means only a single producer will be able to enqueue elements + // without an extra allocation -- blocks aren't shared between producers). + // This method is not thread safe -- it is up to the user to ensure that the + // queue is fully constructed before it starts being used by other threads (this + // includes making the memory effects of construction visible, possibly with a + // memory barrier). + explicit BlockingConcurrentQueue(size_t capacity = 6 * BLOCK_SIZE) + : inner(capacity), sema(create(0, (int)Traits::MAX_SEMA_SPINS), &BlockingConcurrentQueue::template destroy) + { + assert(reinterpret_cast((BlockingConcurrentQueue*)1) == &((BlockingConcurrentQueue*)1)->inner && "BlockingConcurrentQueue must have ConcurrentQueue as its first member"); + if (!sema) { + MOODYCAMEL_THROW(std::bad_alloc()); + } + } + + BlockingConcurrentQueue(size_t minCapacity, size_t maxExplicitProducers, size_t maxImplicitProducers) + : inner(minCapacity, maxExplicitProducers, maxImplicitProducers), sema(create(0, (int)Traits::MAX_SEMA_SPINS), &BlockingConcurrentQueue::template destroy) + { + assert(reinterpret_cast((BlockingConcurrentQueue*)1) == &((BlockingConcurrentQueue*)1)->inner && "BlockingConcurrentQueue must have ConcurrentQueue as its first member"); + if (!sema) { + MOODYCAMEL_THROW(std::bad_alloc()); + } + } + + // Disable copying and copy assignment + BlockingConcurrentQueue(BlockingConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; + BlockingConcurrentQueue& operator=(BlockingConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; + + // Moving is supported, but note that it is *not* a thread-safe operation. + // Nobody can use the queue while it's being moved, and the memory effects + // of that move must be propagated to other threads before they can use it. + // Note: When a queue is moved, its tokens are still valid but can only be + // used with the destination queue (i.e. semantically they are moved along + // with the queue itself). + BlockingConcurrentQueue(BlockingConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT + : inner(std::move(other.inner)), sema(std::move(other.sema)) + { } + + inline BlockingConcurrentQueue& operator=(BlockingConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT + { + return swap_internal(other); + } + + // Swaps this queue's state with the other's. Not thread-safe. + // Swapping two queues does not invalidate their tokens, however + // the tokens that were created for one queue must be used with + // only the swapped queue (i.e. the tokens are tied to the + // queue's movable state, not the object itself). + inline void swap(BlockingConcurrentQueue& other) MOODYCAMEL_NOEXCEPT + { + swap_internal(other); + } + +private: + BlockingConcurrentQueue& swap_internal(BlockingConcurrentQueue& other) + { + if (this == &other) { + return *this; + } + + inner.swap(other.inner); + sema.swap(other.sema); + return *this; + } + +public: + // Enqueues a single item (by copying it). + // Allocates memory if required. Only fails if memory allocation fails (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0, + // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(T const& item) + { + if ((details::likely)(inner.enqueue(item))) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues a single item (by moving it, if possible). + // Allocates memory if required. Only fails if memory allocation fails (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0, + // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(T&& item) + { + if ((details::likely)(inner.enqueue(std::move(item)))) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues a single item (by copying it) using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails (or + // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(producer_token_t const& token, T const& item) + { + if ((details::likely)(inner.enqueue(token, item))) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues a single item (by moving it, if possible) using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails (or + // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(producer_token_t const& token, T&& item) + { + if ((details::likely)(inner.enqueue(token, std::move(item)))) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues several items. + // Allocates memory if required. Only fails if memory allocation fails (or + // implicit production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE + // is 0, or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Note: Use std::make_move_iterator if the elements should be moved instead of copied. + // Thread-safe. + template + inline bool enqueue_bulk(It itemFirst, size_t count) + { + if ((details::likely)(inner.enqueue_bulk(std::forward(itemFirst), count))) { + sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count); + return true; + } + return false; + } + + // Enqueues several items using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails + // (or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + inline bool enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) + { + if ((details::likely)(inner.enqueue_bulk(token, std::forward(itemFirst), count))) { + sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count); + return true; + } + return false; + } + + // Enqueues a single item (by copying it). + // Does not allocate memory. Fails if not enough room to enqueue (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE + // is 0). + // Thread-safe. + inline bool try_enqueue(T const& item) + { + if (inner.try_enqueue(item)) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues a single item (by moving it, if possible). + // Does not allocate memory (except for one-time implicit producer). + // Fails if not enough room to enqueue (or implicit production is + // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0). + // Thread-safe. + inline bool try_enqueue(T&& item) + { + if (inner.try_enqueue(std::move(item))) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues a single item (by copying it) using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Thread-safe. + inline bool try_enqueue(producer_token_t const& token, T const& item) + { + if (inner.try_enqueue(token, item)) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues a single item (by moving it, if possible) using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Thread-safe. + inline bool try_enqueue(producer_token_t const& token, T&& item) + { + if (inner.try_enqueue(token, std::move(item))) { + sema->signal(); + return true; + } + return false; + } + + // Enqueues several items. + // Does not allocate memory (except for one-time implicit producer). + // Fails if not enough room to enqueue (or implicit production is + // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0). + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + inline bool try_enqueue_bulk(It itemFirst, size_t count) + { + if (inner.try_enqueue_bulk(std::forward(itemFirst), count)) { + sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count); + return true; + } + return false; + } + + // Enqueues several items using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + inline bool try_enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) + { + if (inner.try_enqueue_bulk(token, std::forward(itemFirst), count)) { + sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count); + return true; + } + return false; + } + + + // Attempts to dequeue from the queue. + // Returns false if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + inline bool try_dequeue(U& item) + { + if (sema->tryWait()) { + while (!inner.try_dequeue(item)) { + continue; + } + return true; + } + return false; + } + + // Attempts to dequeue from the queue using an explicit consumer token. + // Returns false if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + inline bool try_dequeue(consumer_token_t& token, U& item) + { + if (sema->tryWait()) { + while (!inner.try_dequeue(token, item)) { + continue; + } + return true; + } + return false; + } + + // Attempts to dequeue several elements from the queue. + // Returns the number of items actually dequeued. + // Returns 0 if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + inline size_t try_dequeue_bulk(It itemFirst, size_t max) + { + size_t count = 0; + max = (size_t)sema->tryWaitMany((LightweightSemaphore::ssize_t)(ssize_t)max); + while (count != max) { + count += inner.template try_dequeue_bulk(itemFirst, max - count); + } + return count; + } + + // Attempts to dequeue several elements from the queue using an explicit consumer token. + // Returns the number of items actually dequeued. + // Returns 0 if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + inline size_t try_dequeue_bulk(consumer_token_t& token, It itemFirst, size_t max) + { + size_t count = 0; + max = (size_t)sema->tryWaitMany((LightweightSemaphore::ssize_t)(ssize_t)max); + while (count != max) { + count += inner.template try_dequeue_bulk(token, itemFirst, max - count); + } + return count; + } + + + + // Blocks the current thread until there's something to dequeue, then + // dequeues it. + // Never allocates. Thread-safe. + template + inline void wait_dequeue(U& item) + { + while (!sema->wait()) { + continue; + } + while (!inner.try_dequeue(item)) { + continue; + } + } + + // Blocks the current thread until either there's something to dequeue + // or the timeout (specified in microseconds) expires. Returns false + // without setting `item` if the timeout expires, otherwise assigns + // to `item` and returns true. + // Using a negative timeout indicates an indefinite timeout, + // and is thus functionally equivalent to calling wait_dequeue. + // Never allocates. Thread-safe. + template + inline bool wait_dequeue_timed(U& item, std::int64_t timeout_usecs) + { + if (!sema->wait(timeout_usecs)) { + return false; + } + while (!inner.try_dequeue(item)) { + continue; + } + return true; + } + + // Blocks the current thread until either there's something to dequeue + // or the timeout expires. Returns false without setting `item` if the + // timeout expires, otherwise assigns to `item` and returns true. + // Never allocates. Thread-safe. + template + inline bool wait_dequeue_timed(U& item, std::chrono::duration const& timeout) + { + return wait_dequeue_timed(item, std::chrono::duration_cast(timeout).count()); + } + + // Blocks the current thread until there's something to dequeue, then + // dequeues it using an explicit consumer token. + // Never allocates. Thread-safe. + template + inline void wait_dequeue(consumer_token_t& token, U& item) + { + while (!sema->wait()) { + continue; + } + while (!inner.try_dequeue(token, item)) { + continue; + } + } + + // Blocks the current thread until either there's something to dequeue + // or the timeout (specified in microseconds) expires. Returns false + // without setting `item` if the timeout expires, otherwise assigns + // to `item` and returns true. + // Using a negative timeout indicates an indefinite timeout, + // and is thus functionally equivalent to calling wait_dequeue. + // Never allocates. Thread-safe. + template + inline bool wait_dequeue_timed(consumer_token_t& token, U& item, std::int64_t timeout_usecs) + { + if (!sema->wait(timeout_usecs)) { + return false; + } + while (!inner.try_dequeue(token, item)) { + continue; + } + return true; + } + + // Blocks the current thread until either there's something to dequeue + // or the timeout expires. Returns false without setting `item` if the + // timeout expires, otherwise assigns to `item` and returns true. + // Never allocates. Thread-safe. + template + inline bool wait_dequeue_timed(consumer_token_t& token, U& item, std::chrono::duration const& timeout) + { + return wait_dequeue_timed(token, item, std::chrono::duration_cast(timeout).count()); + } + + // Attempts to dequeue several elements from the queue. + // Returns the number of items actually dequeued, which will + // always be at least one (this method blocks until the queue + // is non-empty) and at most max. + // Never allocates. Thread-safe. + template + inline size_t wait_dequeue_bulk(It itemFirst, size_t max) + { + size_t count = 0; + max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max); + while (count != max) { + count += inner.template try_dequeue_bulk(itemFirst, max - count); + } + return count; + } + + // Attempts to dequeue several elements from the queue. + // Returns the number of items actually dequeued, which can + // be 0 if the timeout expires while waiting for elements, + // and at most max. + // Using a negative timeout indicates an indefinite timeout, + // and is thus functionally equivalent to calling wait_dequeue_bulk. + // Never allocates. Thread-safe. + template + inline size_t wait_dequeue_bulk_timed(It itemFirst, size_t max, std::int64_t timeout_usecs) + { + size_t count = 0; + max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max, timeout_usecs); + while (count != max) { + count += inner.template try_dequeue_bulk(itemFirst, max - count); + } + return count; + } + + // Attempts to dequeue several elements from the queue. + // Returns the number of items actually dequeued, which can + // be 0 if the timeout expires while waiting for elements, + // and at most max. + // Never allocates. Thread-safe. + template + inline size_t wait_dequeue_bulk_timed(It itemFirst, size_t max, std::chrono::duration const& timeout) + { + return wait_dequeue_bulk_timed(itemFirst, max, std::chrono::duration_cast(timeout).count()); + } + + // Attempts to dequeue several elements from the queue using an explicit consumer token. + // Returns the number of items actually dequeued, which will + // always be at least one (this method blocks until the queue + // is non-empty) and at most max. + // Never allocates. Thread-safe. + template + inline size_t wait_dequeue_bulk(consumer_token_t& token, It itemFirst, size_t max) + { + size_t count = 0; + max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max); + while (count != max) { + count += inner.template try_dequeue_bulk(token, itemFirst, max - count); + } + return count; + } + + // Attempts to dequeue several elements from the queue using an explicit consumer token. + // Returns the number of items actually dequeued, which can + // be 0 if the timeout expires while waiting for elements, + // and at most max. + // Using a negative timeout indicates an indefinite timeout, + // and is thus functionally equivalent to calling wait_dequeue_bulk. + // Never allocates. Thread-safe. + template + inline size_t wait_dequeue_bulk_timed(consumer_token_t& token, It itemFirst, size_t max, std::int64_t timeout_usecs) + { + size_t count = 0; + max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max, timeout_usecs); + while (count != max) { + count += inner.template try_dequeue_bulk(token, itemFirst, max - count); + } + return count; + } + + // Attempts to dequeue several elements from the queue using an explicit consumer token. + // Returns the number of items actually dequeued, which can + // be 0 if the timeout expires while waiting for elements, + // and at most max. + // Never allocates. Thread-safe. + template + inline size_t wait_dequeue_bulk_timed(consumer_token_t& token, It itemFirst, size_t max, std::chrono::duration const& timeout) + { + return wait_dequeue_bulk_timed(token, itemFirst, max, std::chrono::duration_cast(timeout).count()); + } + + + // Returns an estimate of the total number of elements currently in the queue. This + // estimate is only accurate if the queue has completely stabilized before it is called + // (i.e. all enqueue and dequeue operations have completed and their memory effects are + // visible on the calling thread, and no further operations start while this method is + // being called). + // Thread-safe. + inline size_t size_approx() const + { + return (size_t)sema->availableApprox(); + } + + + // Returns true if the underlying atomic variables used by + // the queue are lock-free (they should be on most platforms). + // Thread-safe. + static constexpr bool is_lock_free() + { + return ConcurrentQueue::is_lock_free(); + } + + +private: + template + static inline U* create(A1&& a1, A2&& a2) + { + void* p = (Traits::malloc)(sizeof(U)); + return p != nullptr ? new (p) U(std::forward(a1), std::forward(a2)) : nullptr; + } + + template + static inline void destroy(U* p) + { + if (p != nullptr) { + p->~U(); + } + (Traits::free)(p); + } + +private: + ConcurrentQueue inner; + std::unique_ptr sema; +}; + + +template +inline void swap(BlockingConcurrentQueue& a, BlockingConcurrentQueue& b) MOODYCAMEL_NOEXCEPT +{ + a.swap(b); +} + +} // end namespace moodycamel diff --git a/thirdparty/concurrentqueue/concurrentqueue.h b/thirdparty/concurrentqueue/concurrentqueue.h new file mode 100644 index 0000000..bcb06a1 --- /dev/null +++ b/thirdparty/concurrentqueue/concurrentqueue.h @@ -0,0 +1,3747 @@ +// Provides a C++11 implementation of a multi-producer, multi-consumer lock-free queue. +// An overview, including benchmark results, is provided here: +// http://moodycamel.com/blog/2014/a-fast-general-purpose-lock-free-queue-for-c++ +// The full design is also described in excruciating detail at: +// http://moodycamel.com/blog/2014/detailed-design-of-a-lock-free-queue + +// Simplified BSD license: +// Copyright (c) 2013-2020, Cameron Desrochers. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// - Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// - Redistributions in binary form must reproduce the above copyright notice, this list of +// conditions and the following disclaimer in the documentation and/or other materials +// provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Also dual-licensed under the Boost Software License (see LICENSE.md) + +#pragma once + +#if defined(__GNUC__) && !defined(__INTEL_COMPILER) +// Disable -Wconversion warnings (spuriously triggered when Traits::size_t and +// Traits::index_t are set to < 32 bits, causing integer promotion, causing warnings +// upon assigning any computed values) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" + +#ifdef MCDBGQ_USE_RELACY +#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" +#endif +#endif + +#if defined(_MSC_VER) && (!defined(_HAS_CXX17) || !_HAS_CXX17) +// VS2019 with /W4 warns about constant conditional expressions but unless /std=c++17 or higher +// does not support `if constexpr`, so we have no choice but to simply disable the warning +#pragma warning(push) +#pragma warning(disable: 4127) // conditional expression is constant +#endif + +#if defined(__APPLE__) +#include "TargetConditionals.h" +#endif + +#ifdef MCDBGQ_USE_RELACY +#include "relacy/relacy_std.hpp" +#include "relacy_shims.h" +// We only use malloc/free anyway, and the delete macro messes up `= delete` method declarations. +// We'll override the default trait malloc ourselves without a macro. +#undef new +#undef delete +#undef malloc +#undef free +#else +#include // Requires C++11. Sorry VS2010. +#include +#endif +#include // for max_align_t +#include +#include +#include +#include +#include +#include +#include // for CHAR_BIT +#include +#include // partly for __WINPTHREADS_VERSION if on MinGW-w64 w/ POSIX threading +#include // used for thread exit synchronization + +// Platform-specific definitions of a numeric thread ID type and an invalid value +namespace moodycamel { namespace details { + template struct thread_id_converter { + typedef thread_id_t thread_id_numeric_size_t; + typedef thread_id_t thread_id_hash_t; + static thread_id_hash_t prehash(thread_id_t const& x) { return x; } + }; +} } +#if defined(MCDBGQ_USE_RELACY) +namespace moodycamel { namespace details { + typedef std::uint32_t thread_id_t; + static const thread_id_t invalid_thread_id = 0xFFFFFFFFU; + static const thread_id_t invalid_thread_id2 = 0xFFFFFFFEU; + static inline thread_id_t thread_id() { return rl::thread_index(); } +} } +#elif defined(_WIN32) || defined(__WINDOWS__) || defined(__WIN32__) +// No sense pulling in windows.h in a header, we'll manually declare the function +// we use and rely on backwards-compatibility for this not to break +extern "C" __declspec(dllimport) unsigned long __stdcall GetCurrentThreadId(void); +namespace moodycamel { namespace details { + static_assert(sizeof(unsigned long) == sizeof(std::uint32_t), "Expected size of unsigned long to be 32 bits on Windows"); + typedef std::uint32_t thread_id_t; + static const thread_id_t invalid_thread_id = 0; // See http://blogs.msdn.com/b/oldnewthing/archive/2004/02/23/78395.aspx + static const thread_id_t invalid_thread_id2 = 0xFFFFFFFFU; // Not technically guaranteed to be invalid, but is never used in practice. Note that all Win32 thread IDs are presently multiples of 4. + static inline thread_id_t thread_id() { return static_cast(::GetCurrentThreadId()); } +} } +#elif defined(__arm__) || defined(_M_ARM) || defined(__aarch64__) || (defined(__APPLE__) && TARGET_OS_IPHONE) || defined(MOODYCAMEL_NO_THREAD_LOCAL) +namespace moodycamel { namespace details { + static_assert(sizeof(std::thread::id) == 4 || sizeof(std::thread::id) == 8, "std::thread::id is expected to be either 4 or 8 bytes"); + + typedef std::thread::id thread_id_t; + static const thread_id_t invalid_thread_id; // Default ctor creates invalid ID + + // Note we don't define a invalid_thread_id2 since std::thread::id doesn't have one; it's + // only used if MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED is defined anyway, which it won't + // be. + static inline thread_id_t thread_id() { return std::this_thread::get_id(); } + + template struct thread_id_size { }; + template<> struct thread_id_size<4> { typedef std::uint32_t numeric_t; }; + template<> struct thread_id_size<8> { typedef std::uint64_t numeric_t; }; + + template<> struct thread_id_converter { + typedef thread_id_size::numeric_t thread_id_numeric_size_t; +#ifndef __APPLE__ + typedef std::size_t thread_id_hash_t; +#else + typedef thread_id_numeric_size_t thread_id_hash_t; +#endif + + static thread_id_hash_t prehash(thread_id_t const& x) + { +#ifndef __APPLE__ + return std::hash()(x); +#else + return *reinterpret_cast(&x); +#endif + } + }; +} } +#else +// Use a nice trick from this answer: http://stackoverflow.com/a/8438730/21475 +// In order to get a numeric thread ID in a platform-independent way, we use a thread-local +// static variable's address as a thread identifier :-) +#if defined(__GNUC__) || defined(__INTEL_COMPILER) +#define MOODYCAMEL_THREADLOCAL __thread +#elif defined(_MSC_VER) +#define MOODYCAMEL_THREADLOCAL __declspec(thread) +#else +// Assume C++11 compliant compiler +#define MOODYCAMEL_THREADLOCAL thread_local +#endif +namespace moodycamel { namespace details { + typedef std::uintptr_t thread_id_t; + static const thread_id_t invalid_thread_id = 0; // Address can't be nullptr + static const thread_id_t invalid_thread_id2 = 1; // Member accesses off a null pointer are also generally invalid. Plus it's not aligned. + inline thread_id_t thread_id() { static MOODYCAMEL_THREADLOCAL int x; return reinterpret_cast(&x); } +} } +#endif + +// Constexpr if +#ifndef MOODYCAMEL_CONSTEXPR_IF +#if (defined(_MSC_VER) && defined(_HAS_CXX17) && _HAS_CXX17) || __cplusplus > 201402L +#define MOODYCAMEL_CONSTEXPR_IF if constexpr +#define MOODYCAMEL_MAYBE_UNUSED [[maybe_unused]] +#else +#define MOODYCAMEL_CONSTEXPR_IF if +#define MOODYCAMEL_MAYBE_UNUSED +#endif +#endif + +// Exceptions +#ifndef MOODYCAMEL_EXCEPTIONS_ENABLED +#if (defined(_MSC_VER) && defined(_CPPUNWIND)) || (defined(__GNUC__) && defined(__EXCEPTIONS)) || (!defined(_MSC_VER) && !defined(__GNUC__)) +#define MOODYCAMEL_EXCEPTIONS_ENABLED +#endif +#endif +#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED +#define MOODYCAMEL_TRY try +#define MOODYCAMEL_CATCH(...) catch(__VA_ARGS__) +#define MOODYCAMEL_RETHROW throw +#define MOODYCAMEL_THROW(expr) throw (expr) +#else +#define MOODYCAMEL_TRY MOODYCAMEL_CONSTEXPR_IF (true) +#define MOODYCAMEL_CATCH(...) else MOODYCAMEL_CONSTEXPR_IF (false) +#define MOODYCAMEL_RETHROW +#define MOODYCAMEL_THROW(expr) +#endif + +#ifndef MOODYCAMEL_NOEXCEPT +#if !defined(MOODYCAMEL_EXCEPTIONS_ENABLED) +#define MOODYCAMEL_NOEXCEPT +#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) true +#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) true +#elif defined(_MSC_VER) && defined(_NOEXCEPT) && _MSC_VER < 1800 +// VS2012's std::is_nothrow_[move_]constructible is broken and returns true when it shouldn't :-( +// We have to assume *all* non-trivial constructors may throw on VS2012! +#define MOODYCAMEL_NOEXCEPT _NOEXCEPT +#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) (std::is_rvalue_reference::value && std::is_move_constructible::value ? std::is_trivially_move_constructible::value : std::is_trivially_copy_constructible::value) +#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) ((std::is_rvalue_reference::value && std::is_move_assignable::value ? std::is_trivially_move_assignable::value || std::is_nothrow_move_assignable::value : std::is_trivially_copy_assignable::value || std::is_nothrow_copy_assignable::value) && MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr)) +#elif defined(_MSC_VER) && defined(_NOEXCEPT) && _MSC_VER < 1900 +#define MOODYCAMEL_NOEXCEPT _NOEXCEPT +#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) (std::is_rvalue_reference::value && std::is_move_constructible::value ? std::is_trivially_move_constructible::value || std::is_nothrow_move_constructible::value : std::is_trivially_copy_constructible::value || std::is_nothrow_copy_constructible::value) +#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) ((std::is_rvalue_reference::value && std::is_move_assignable::value ? std::is_trivially_move_assignable::value || std::is_nothrow_move_assignable::value : std::is_trivially_copy_assignable::value || std::is_nothrow_copy_assignable::value) && MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr)) +#else +#define MOODYCAMEL_NOEXCEPT noexcept +#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) noexcept(expr) +#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) noexcept(expr) +#endif +#endif + +#ifndef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED +#ifdef MCDBGQ_USE_RELACY +#define MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED +#else +// VS2013 doesn't support `thread_local`, and MinGW-w64 w/ POSIX threading has a crippling bug: http://sourceforge.net/p/mingw-w64/bugs/445 +// g++ <=4.7 doesn't support thread_local either. +// Finally, iOS/ARM doesn't have support for it either, and g++/ARM allows it to compile but it's unconfirmed to actually work +#if (!defined(_MSC_VER) || _MSC_VER >= 1900) && (!defined(__MINGW32__) && !defined(__MINGW64__) || !defined(__WINPTHREADS_VERSION)) && (!defined(__GNUC__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) && (!defined(__APPLE__) || !TARGET_OS_IPHONE) && !defined(__arm__) && !defined(_M_ARM) && !defined(__aarch64__) +// Assume `thread_local` is fully supported in all other C++11 compilers/platforms +#define MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED // tentatively enabled for now; years ago several users report having problems with it on +#endif +#endif +#endif + +// VS2012 doesn't support deleted functions. +// In this case, we declare the function normally but don't define it. A link error will be generated if the function is called. +#ifndef MOODYCAMEL_DELETE_FUNCTION +#if defined(_MSC_VER) && _MSC_VER < 1800 +#define MOODYCAMEL_DELETE_FUNCTION +#else +#define MOODYCAMEL_DELETE_FUNCTION = delete +#endif +#endif + +namespace moodycamel { namespace details { +#ifndef MOODYCAMEL_ALIGNAS +// VS2013 doesn't support alignas or alignof, and align() requires a constant literal +#if defined(_MSC_VER) && _MSC_VER <= 1800 +#define MOODYCAMEL_ALIGNAS(alignment) __declspec(align(alignment)) +#define MOODYCAMEL_ALIGNOF(obj) __alignof(obj) +#define MOODYCAMEL_ALIGNED_TYPE_LIKE(T, obj) typename details::Vs2013Aligned::value, T>::type + template struct Vs2013Aligned { }; // default, unsupported alignment + template struct Vs2013Aligned<1, T> { typedef __declspec(align(1)) T type; }; + template struct Vs2013Aligned<2, T> { typedef __declspec(align(2)) T type; }; + template struct Vs2013Aligned<4, T> { typedef __declspec(align(4)) T type; }; + template struct Vs2013Aligned<8, T> { typedef __declspec(align(8)) T type; }; + template struct Vs2013Aligned<16, T> { typedef __declspec(align(16)) T type; }; + template struct Vs2013Aligned<32, T> { typedef __declspec(align(32)) T type; }; + template struct Vs2013Aligned<64, T> { typedef __declspec(align(64)) T type; }; + template struct Vs2013Aligned<128, T> { typedef __declspec(align(128)) T type; }; + template struct Vs2013Aligned<256, T> { typedef __declspec(align(256)) T type; }; +#else + template struct identity { typedef T type; }; +#define MOODYCAMEL_ALIGNAS(alignment) alignas(alignment) +#define MOODYCAMEL_ALIGNOF(obj) alignof(obj) +#define MOODYCAMEL_ALIGNED_TYPE_LIKE(T, obj) alignas(alignof(obj)) typename details::identity::type +#endif +#endif +} } + + +// TSAN can false report races in lock-free code. To enable TSAN to be used from projects that use this one, +// we can apply per-function compile-time suppression. +// See https://clang.llvm.org/docs/ThreadSanitizer.html#has-feature-thread-sanitizer +#define MOODYCAMEL_NO_TSAN +#if defined(__has_feature) + #if __has_feature(thread_sanitizer) + #undef MOODYCAMEL_NO_TSAN + #define MOODYCAMEL_NO_TSAN __attribute__((no_sanitize("thread"))) + #endif // TSAN +#endif // TSAN + +// Compiler-specific likely/unlikely hints +namespace moodycamel { namespace details { +#if defined(__GNUC__) + static inline bool (likely)(bool x) { return __builtin_expect((x), true); } + static inline bool (unlikely)(bool x) { return __builtin_expect((x), false); } +#else + static inline bool (likely)(bool x) { return x; } + static inline bool (unlikely)(bool x) { return x; } +#endif +} } + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG +#include "internal/concurrentqueue_internal_debug.h" +#endif + +namespace moodycamel { +namespace details { + template + struct const_numeric_max { + static_assert(std::is_integral::value, "const_numeric_max can only be used with integers"); + static const T value = std::numeric_limits::is_signed + ? (static_cast(1) << (sizeof(T) * CHAR_BIT - 1)) - static_cast(1) + : static_cast(-1); + }; + +#if defined(__GLIBCXX__) + typedef ::max_align_t std_max_align_t; // libstdc++ forgot to add it to std:: for a while +#else + typedef std::max_align_t std_max_align_t; // Others (e.g. MSVC) insist it can *only* be accessed via std:: +#endif + + // Some platforms have incorrectly set max_align_t to a type with <8 bytes alignment even while supporting + // 8-byte aligned scalar values (*cough* 32-bit iOS). Work around this with our own union. See issue #64. + typedef union { + std_max_align_t x; + long long y; + void* z; + } max_align_t; +} + +// Default traits for the ConcurrentQueue. To change some of the +// traits without re-implementing all of them, inherit from this +// struct and shadow the declarations you wish to be different; +// since the traits are used as a template type parameter, the +// shadowed declarations will be used where defined, and the defaults +// otherwise. +struct ConcurrentQueueDefaultTraits +{ + // General-purpose size type. std::size_t is strongly recommended. + typedef std::size_t size_t; + + // The type used for the enqueue and dequeue indices. Must be at least as + // large as size_t. Should be significantly larger than the number of elements + // you expect to hold at once, especially if you have a high turnover rate; + // for example, on 32-bit x86, if you expect to have over a hundred million + // elements or pump several million elements through your queue in a very + // short space of time, using a 32-bit type *may* trigger a race condition. + // A 64-bit int type is recommended in that case, and in practice will + // prevent a race condition no matter the usage of the queue. Note that + // whether the queue is lock-free with a 64-int type depends on the whether + // std::atomic is lock-free, which is platform-specific. + typedef std::size_t index_t; + + // Internally, all elements are enqueued and dequeued from multi-element + // blocks; this is the smallest controllable unit. If you expect few elements + // but many producers, a smaller block size should be favoured. For few producers + // and/or many elements, a larger block size is preferred. A sane default + // is provided. Must be a power of 2. + static const size_t BLOCK_SIZE = 32; + + // For explicit producers (i.e. when using a producer token), the block is + // checked for being empty by iterating through a list of flags, one per element. + // For large block sizes, this is too inefficient, and switching to an atomic + // counter-based approach is faster. The switch is made for block sizes strictly + // larger than this threshold. + static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = 32; + + // How many full blocks can be expected for a single explicit producer? This should + // reflect that number's maximum for optimal performance. Must be a power of 2. + static const size_t EXPLICIT_INITIAL_INDEX_SIZE = 32; + + // How many full blocks can be expected for a single implicit producer? This should + // reflect that number's maximum for optimal performance. Must be a power of 2. + static const size_t IMPLICIT_INITIAL_INDEX_SIZE = 32; + + // The initial size of the hash table mapping thread IDs to implicit producers. + // Note that the hash is resized every time it becomes half full. + // Must be a power of two, and either 0 or at least 1. If 0, implicit production + // (using the enqueue methods without an explicit producer token) is disabled. + static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = 32; + + // Controls the number of items that an explicit consumer (i.e. one with a token) + // must consume before it causes all consumers to rotate and move on to the next + // internal queue. + static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = 256; + + // The maximum number of elements (inclusive) that can be enqueued to a sub-queue. + // Enqueue operations that would cause this limit to be surpassed will fail. Note + // that this limit is enforced at the block level (for performance reasons), i.e. + // it's rounded up to the nearest block size. + static const size_t MAX_SUBQUEUE_SIZE = details::const_numeric_max::value; + + // The number of times to spin before sleeping when waiting on a semaphore. + // Recommended values are on the order of 1000-10000 unless the number of + // consumer threads exceeds the number of idle cores (in which case try 0-100). + // Only affects instances of the BlockingConcurrentQueue. + static const int MAX_SEMA_SPINS = 100; + + // Whether to recycle dynamically-allocated blocks into an internal free list or + // not. If false, only pre-allocated blocks (controlled by the constructor + // arguments) will be recycled, and all others will be `free`d back to the heap. + // Note that blocks consumed by explicit producers are only freed on destruction + // of the queue (not following destruction of the token) regardless of this trait. + static const bool RECYCLE_ALLOCATED_BLOCKS = false; + + +#ifndef MCDBGQ_USE_RELACY + // Memory allocation can be customized if needed. + // malloc should return nullptr on failure, and handle alignment like std::malloc. +#if defined(malloc) || defined(free) + // Gah, this is 2015, stop defining macros that break standard code already! + // Work around malloc/free being special macros: + static inline void* WORKAROUND_malloc(size_t size) { return malloc(size); } + static inline void WORKAROUND_free(void* ptr) { return free(ptr); } + static inline void* (malloc)(size_t size) { return WORKAROUND_malloc(size); } + static inline void (free)(void* ptr) { return WORKAROUND_free(ptr); } +#else + static inline void* malloc(size_t size) { return std::malloc(size); } + static inline void free(void* ptr) { return std::free(ptr); } +#endif +#else + // Debug versions when running under the Relacy race detector (ignore + // these in user code) + static inline void* malloc(size_t size) { return rl::rl_malloc(size, $); } + static inline void free(void* ptr) { return rl::rl_free(ptr, $); } +#endif +}; + + +// When producing or consuming many elements, the most efficient way is to: +// 1) Use one of the bulk-operation methods of the queue with a token +// 2) Failing that, use the bulk-operation methods without a token +// 3) Failing that, create a token and use that with the single-item methods +// 4) Failing that, use the single-parameter methods of the queue +// Having said that, don't create tokens willy-nilly -- ideally there should be +// a maximum of one token per thread (of each kind). +struct ProducerToken; +struct ConsumerToken; + +template class ConcurrentQueue; +template class BlockingConcurrentQueue; +class ConcurrentQueueTests; + + +namespace details +{ + struct ConcurrentQueueProducerTypelessBase + { + ConcurrentQueueProducerTypelessBase* next; + std::atomic inactive; + ProducerToken* token; + + ConcurrentQueueProducerTypelessBase() + : next(nullptr), inactive(false), token(nullptr) + { + } + }; + + template struct _hash_32_or_64 { + static inline std::uint32_t hash(std::uint32_t h) + { + // MurmurHash3 finalizer -- see https://code.google.com/p/smhasher/source/browse/trunk/MurmurHash3.cpp + // Since the thread ID is already unique, all we really want to do is propagate that + // uniqueness evenly across all the bits, so that we can use a subset of the bits while + // reducing collisions significantly + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + return h ^ (h >> 16); + } + }; + template<> struct _hash_32_or_64<1> { + static inline std::uint64_t hash(std::uint64_t h) + { + h ^= h >> 33; + h *= 0xff51afd7ed558ccd; + h ^= h >> 33; + h *= 0xc4ceb9fe1a85ec53; + return h ^ (h >> 33); + } + }; + template struct hash_32_or_64 : public _hash_32_or_64<(size > 4)> { }; + + static inline size_t hash_thread_id(thread_id_t id) + { + static_assert(sizeof(thread_id_t) <= 8, "Expected a platform where thread IDs are at most 64-bit values"); + return static_cast(hash_32_or_64::thread_id_hash_t)>::hash( + thread_id_converter::prehash(id))); + } + + template + static inline bool circular_less_than(T a, T b) + { + static_assert(std::is_integral::value && !std::numeric_limits::is_signed, "circular_less_than is intended to be used only with unsigned integer types"); + return static_cast(a - b) > static_cast(static_cast(1) << (static_cast(sizeof(T) * CHAR_BIT - 1))); + // Note: extra parens around rhs of operator<< is MSVC bug: https://developercommunity2.visualstudio.com/t/C4554-triggers-when-both-lhs-and-rhs-is/10034931 + // silencing the bug requires #pragma warning(disable: 4554) around the calling code and has no effect when done here. + } + + template + static inline char* align_for(char* ptr) + { + const std::size_t alignment = std::alignment_of::value; + return ptr + (alignment - (reinterpret_cast(ptr) % alignment)) % alignment; + } + + template + static inline T ceil_to_pow_2(T x) + { + static_assert(std::is_integral::value && !std::numeric_limits::is_signed, "ceil_to_pow_2 is intended to be used only with unsigned integer types"); + + // Adapted from http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2 + --x; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + for (std::size_t i = 1; i < sizeof(T); i <<= 1) { + x |= x >> (i << 3); + } + ++x; + return x; + } + + template + static inline void swap_relaxed(std::atomic& left, std::atomic& right) + { + T temp = std::move(left.load(std::memory_order_relaxed)); + left.store(std::move(right.load(std::memory_order_relaxed)), std::memory_order_relaxed); + right.store(std::move(temp), std::memory_order_relaxed); + } + + template + static inline T const& nomove(T const& x) + { + return x; + } + + template + struct nomove_if + { + template + static inline T const& eval(T const& x) + { + return x; + } + }; + + template<> + struct nomove_if + { + template + static inline auto eval(U&& x) + -> decltype(std::forward(x)) + { + return std::forward(x); + } + }; + + template + static inline auto deref_noexcept(It& it) MOODYCAMEL_NOEXCEPT -> decltype(*it) + { + return *it; + } + +#if defined(__clang__) || !defined(__GNUC__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) + template struct is_trivially_destructible : std::is_trivially_destructible { }; +#else + template struct is_trivially_destructible : std::has_trivial_destructor { }; +#endif + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED +#ifdef MCDBGQ_USE_RELACY + typedef RelacyThreadExitListener ThreadExitListener; + typedef RelacyThreadExitNotifier ThreadExitNotifier; +#else + class ThreadExitNotifier; + + struct ThreadExitListener + { + typedef void (*callback_t)(void*); + callback_t callback; + void* userData; + + ThreadExitListener* next; // reserved for use by the ThreadExitNotifier + ThreadExitNotifier* chain; // reserved for use by the ThreadExitNotifier + }; + + class ThreadExitNotifier + { + public: + static void subscribe(ThreadExitListener* listener) + { + auto& tlsInst = instance(); + std::lock_guard guard(mutex()); + listener->next = tlsInst.tail; + listener->chain = &tlsInst; + tlsInst.tail = listener; + } + + static void unsubscribe(ThreadExitListener* listener) + { + std::lock_guard guard(mutex()); + if (!listener->chain) { + return; // race with ~ThreadExitNotifier + } + auto& tlsInst = *listener->chain; + listener->chain = nullptr; + ThreadExitListener** prev = &tlsInst.tail; + for (auto ptr = tlsInst.tail; ptr != nullptr; ptr = ptr->next) { + if (ptr == listener) { + *prev = ptr->next; + break; + } + prev = &ptr->next; + } + } + + private: + ThreadExitNotifier() : tail(nullptr) { } + ThreadExitNotifier(ThreadExitNotifier const&) MOODYCAMEL_DELETE_FUNCTION; + ThreadExitNotifier& operator=(ThreadExitNotifier const&) MOODYCAMEL_DELETE_FUNCTION; + + ~ThreadExitNotifier() + { + // This thread is about to exit, let everyone know! + assert(this == &instance() && "If this assert fails, you likely have a buggy compiler! Change the preprocessor conditions such that MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED is no longer defined."); + std::lock_guard guard(mutex()); + for (auto ptr = tail; ptr != nullptr; ptr = ptr->next) { + ptr->chain = nullptr; + ptr->callback(ptr->userData); + } + } + + // Thread-local + static inline ThreadExitNotifier& instance() + { + static thread_local ThreadExitNotifier notifier; + return notifier; + } + + static inline std::mutex& mutex() + { + // Must be static because the ThreadExitNotifier could be destroyed while unsubscribe is called + static std::mutex mutex; + return mutex; + } + + private: + ThreadExitListener* tail; + }; +#endif +#endif + + template struct static_is_lock_free_num { enum { value = 0 }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_CHAR_LOCK_FREE }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_SHORT_LOCK_FREE }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_INT_LOCK_FREE }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_LONG_LOCK_FREE }; }; + template<> struct static_is_lock_free_num { enum { value = ATOMIC_LLONG_LOCK_FREE }; }; + template struct static_is_lock_free : static_is_lock_free_num::type> { }; + template<> struct static_is_lock_free { enum { value = ATOMIC_BOOL_LOCK_FREE }; }; + template struct static_is_lock_free { enum { value = ATOMIC_POINTER_LOCK_FREE }; }; +} + + +struct ProducerToken +{ + template + explicit ProducerToken(ConcurrentQueue& queue); + + template + explicit ProducerToken(BlockingConcurrentQueue& queue); + + ProducerToken(ProducerToken&& other) MOODYCAMEL_NOEXCEPT + : producer(other.producer) + { + other.producer = nullptr; + if (producer != nullptr) { + producer->token = this; + } + } + + inline ProducerToken& operator=(ProducerToken&& other) MOODYCAMEL_NOEXCEPT + { + swap(other); + return *this; + } + + void swap(ProducerToken& other) MOODYCAMEL_NOEXCEPT + { + std::swap(producer, other.producer); + if (producer != nullptr) { + producer->token = this; + } + if (other.producer != nullptr) { + other.producer->token = &other; + } + } + + // A token is always valid unless: + // 1) Memory allocation failed during construction + // 2) It was moved via the move constructor + // (Note: assignment does a swap, leaving both potentially valid) + // 3) The associated queue was destroyed + // Note that if valid() returns true, that only indicates + // that the token is valid for use with a specific queue, + // but not which one; that's up to the user to track. + inline bool valid() const { return producer != nullptr; } + + ~ProducerToken() + { + if (producer != nullptr) { + producer->token = nullptr; + producer->inactive.store(true, std::memory_order_release); + } + } + + // Disable copying and assignment + ProducerToken(ProducerToken const&) MOODYCAMEL_DELETE_FUNCTION; + ProducerToken& operator=(ProducerToken const&) MOODYCAMEL_DELETE_FUNCTION; + +private: + template friend class ConcurrentQueue; + friend class ConcurrentQueueTests; + +protected: + details::ConcurrentQueueProducerTypelessBase* producer; +}; + + +struct ConsumerToken +{ + template + explicit ConsumerToken(ConcurrentQueue& q); + + template + explicit ConsumerToken(BlockingConcurrentQueue& q); + + ConsumerToken(ConsumerToken&& other) MOODYCAMEL_NOEXCEPT + : initialOffset(other.initialOffset), lastKnownGlobalOffset(other.lastKnownGlobalOffset), itemsConsumedFromCurrent(other.itemsConsumedFromCurrent), currentProducer(other.currentProducer), desiredProducer(other.desiredProducer) + { + } + + inline ConsumerToken& operator=(ConsumerToken&& other) MOODYCAMEL_NOEXCEPT + { + swap(other); + return *this; + } + + void swap(ConsumerToken& other) MOODYCAMEL_NOEXCEPT + { + std::swap(initialOffset, other.initialOffset); + std::swap(lastKnownGlobalOffset, other.lastKnownGlobalOffset); + std::swap(itemsConsumedFromCurrent, other.itemsConsumedFromCurrent); + std::swap(currentProducer, other.currentProducer); + std::swap(desiredProducer, other.desiredProducer); + } + + // Disable copying and assignment + ConsumerToken(ConsumerToken const&) MOODYCAMEL_DELETE_FUNCTION; + ConsumerToken& operator=(ConsumerToken const&) MOODYCAMEL_DELETE_FUNCTION; + +private: + template friend class ConcurrentQueue; + friend class ConcurrentQueueTests; + +private: // but shared with ConcurrentQueue + std::uint32_t initialOffset; + std::uint32_t lastKnownGlobalOffset; + std::uint32_t itemsConsumedFromCurrent; + details::ConcurrentQueueProducerTypelessBase* currentProducer; + details::ConcurrentQueueProducerTypelessBase* desiredProducer; +}; + +// Need to forward-declare this swap because it's in a namespace. +// See http://stackoverflow.com/questions/4492062/why-does-a-c-friend-class-need-a-forward-declaration-only-in-other-namespaces +template +inline void swap(typename ConcurrentQueue::ImplicitProducerKVP& a, typename ConcurrentQueue::ImplicitProducerKVP& b) MOODYCAMEL_NOEXCEPT; + + +template +class ConcurrentQueue +{ +public: + typedef ::moodycamel::ProducerToken producer_token_t; + typedef ::moodycamel::ConsumerToken consumer_token_t; + + typedef typename Traits::index_t index_t; + typedef typename Traits::size_t size_t; + + static const size_t BLOCK_SIZE = static_cast(Traits::BLOCK_SIZE); + static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = static_cast(Traits::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD); + static const size_t EXPLICIT_INITIAL_INDEX_SIZE = static_cast(Traits::EXPLICIT_INITIAL_INDEX_SIZE); + static const size_t IMPLICIT_INITIAL_INDEX_SIZE = static_cast(Traits::IMPLICIT_INITIAL_INDEX_SIZE); + static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = static_cast(Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE); + static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = static_cast(Traits::EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE); +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4307) // + integral constant overflow (that's what the ternary expression is for!) +#pragma warning(disable: 4309) // static_cast: Truncation of constant value +#endif + static const size_t MAX_SUBQUEUE_SIZE = (details::const_numeric_max::value - static_cast(Traits::MAX_SUBQUEUE_SIZE) < BLOCK_SIZE) ? details::const_numeric_max::value : ((static_cast(Traits::MAX_SUBQUEUE_SIZE) + (BLOCK_SIZE - 1)) / BLOCK_SIZE * BLOCK_SIZE); +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + static_assert(!std::numeric_limits::is_signed && std::is_integral::value, "Traits::size_t must be an unsigned integral type"); + static_assert(!std::numeric_limits::is_signed && std::is_integral::value, "Traits::index_t must be an unsigned integral type"); + static_assert(sizeof(index_t) >= sizeof(size_t), "Traits::index_t must be at least as wide as Traits::size_t"); + static_assert((BLOCK_SIZE > 1) && !(BLOCK_SIZE & (BLOCK_SIZE - 1)), "Traits::BLOCK_SIZE must be a power of 2 (and at least 2)"); + static_assert((EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD > 1) && !(EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD & (EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD - 1)), "Traits::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD must be a power of 2 (and greater than 1)"); + static_assert((EXPLICIT_INITIAL_INDEX_SIZE > 1) && !(EXPLICIT_INITIAL_INDEX_SIZE & (EXPLICIT_INITIAL_INDEX_SIZE - 1)), "Traits::EXPLICIT_INITIAL_INDEX_SIZE must be a power of 2 (and greater than 1)"); + static_assert((IMPLICIT_INITIAL_INDEX_SIZE > 1) && !(IMPLICIT_INITIAL_INDEX_SIZE & (IMPLICIT_INITIAL_INDEX_SIZE - 1)), "Traits::IMPLICIT_INITIAL_INDEX_SIZE must be a power of 2 (and greater than 1)"); + static_assert((INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) || !(INITIAL_IMPLICIT_PRODUCER_HASH_SIZE & (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE - 1)), "Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE must be a power of 2"); + static_assert(INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0 || INITIAL_IMPLICIT_PRODUCER_HASH_SIZE >= 1, "Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE must be at least 1 (or 0 to disable implicit enqueueing)"); + +public: + // Creates a queue with at least `capacity` element slots; note that the + // actual number of elements that can be inserted without additional memory + // allocation depends on the number of producers and the block size (e.g. if + // the block size is equal to `capacity`, only a single block will be allocated + // up-front, which means only a single producer will be able to enqueue elements + // without an extra allocation -- blocks aren't shared between producers). + // This method is not thread safe -- it is up to the user to ensure that the + // queue is fully constructed before it starts being used by other threads (this + // includes making the memory effects of construction visible, possibly with a + // memory barrier). + explicit ConcurrentQueue(size_t capacity = 32 * BLOCK_SIZE) + : producerListTail(nullptr), + producerCount(0), + initialBlockPoolIndex(0), + nextExplicitConsumerId(0), + globalExplicitConsumerOffset(0) + { + implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); + populate_initial_implicit_producer_hash(); + populate_initial_block_list(capacity / BLOCK_SIZE + ((capacity & (BLOCK_SIZE - 1)) == 0 ? 0 : 1)); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + // Track all the producers using a fully-resolved typed list for + // each kind; this makes it possible to debug them starting from + // the root queue object (otherwise wacky casts are needed that + // don't compile in the debugger's expression evaluator). + explicitProducers.store(nullptr, std::memory_order_relaxed); + implicitProducers.store(nullptr, std::memory_order_relaxed); +#endif + } + + // Computes the correct amount of pre-allocated blocks for you based + // on the minimum number of elements you want available at any given + // time, and the maximum concurrent number of each type of producer. + ConcurrentQueue(size_t minCapacity, size_t maxExplicitProducers, size_t maxImplicitProducers) + : producerListTail(nullptr), + producerCount(0), + initialBlockPoolIndex(0), + nextExplicitConsumerId(0), + globalExplicitConsumerOffset(0) + { + implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); + populate_initial_implicit_producer_hash(); + size_t blocks = (((minCapacity + BLOCK_SIZE - 1) / BLOCK_SIZE) - 1) * (maxExplicitProducers + 1) + 2 * (maxExplicitProducers + maxImplicitProducers); + populate_initial_block_list(blocks); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + explicitProducers.store(nullptr, std::memory_order_relaxed); + implicitProducers.store(nullptr, std::memory_order_relaxed); +#endif + } + + // Note: The queue should not be accessed concurrently while it's + // being deleted. It's up to the user to synchronize this. + // This method is not thread safe. + ~ConcurrentQueue() + { + // Destroy producers + auto ptr = producerListTail.load(std::memory_order_relaxed); + while (ptr != nullptr) { + auto next = ptr->next_prod(); + if (ptr->token != nullptr) { + ptr->token->producer = nullptr; + } + destroy(ptr); + ptr = next; + } + + // Destroy implicit producer hash tables + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE != 0) { + auto hash = implicitProducerHash.load(std::memory_order_relaxed); + while (hash != nullptr) { + auto prev = hash->prev; + if (prev != nullptr) { // The last hash is part of this object and was not allocated dynamically + for (size_t i = 0; i != hash->capacity; ++i) { + hash->entries[i].~ImplicitProducerKVP(); + } + hash->~ImplicitProducerHash(); + (Traits::free)(hash); + } + hash = prev; + } + } + + // Destroy global free list + auto block = freeList.head_unsafe(); + while (block != nullptr) { + auto next = block->freeListNext.load(std::memory_order_relaxed); + if (block->dynamicallyAllocated) { + destroy(block); + } + block = next; + } + + // Destroy initial free list + destroy_array(initialBlockPool, initialBlockPoolSize); + } + + // Disable copying and copy assignment + ConcurrentQueue(ConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; + ConcurrentQueue& operator=(ConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION; + + // Moving is supported, but note that it is *not* a thread-safe operation. + // Nobody can use the queue while it's being moved, and the memory effects + // of that move must be propagated to other threads before they can use it. + // Note: When a queue is moved, its tokens are still valid but can only be + // used with the destination queue (i.e. semantically they are moved along + // with the queue itself). + ConcurrentQueue(ConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT + : producerListTail(other.producerListTail.load(std::memory_order_relaxed)), + producerCount(other.producerCount.load(std::memory_order_relaxed)), + initialBlockPoolIndex(other.initialBlockPoolIndex.load(std::memory_order_relaxed)), + initialBlockPool(other.initialBlockPool), + initialBlockPoolSize(other.initialBlockPoolSize), + freeList(std::move(other.freeList)), + nextExplicitConsumerId(other.nextExplicitConsumerId.load(std::memory_order_relaxed)), + globalExplicitConsumerOffset(other.globalExplicitConsumerOffset.load(std::memory_order_relaxed)) + { + // Move the other one into this, and leave the other one as an empty queue + implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); + populate_initial_implicit_producer_hash(); + swap_implicit_producer_hashes(other); + + other.producerListTail.store(nullptr, std::memory_order_relaxed); + other.producerCount.store(0, std::memory_order_relaxed); + other.nextExplicitConsumerId.store(0, std::memory_order_relaxed); + other.globalExplicitConsumerOffset.store(0, std::memory_order_relaxed); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + explicitProducers.store(other.explicitProducers.load(std::memory_order_relaxed), std::memory_order_relaxed); + other.explicitProducers.store(nullptr, std::memory_order_relaxed); + implicitProducers.store(other.implicitProducers.load(std::memory_order_relaxed), std::memory_order_relaxed); + other.implicitProducers.store(nullptr, std::memory_order_relaxed); +#endif + + other.initialBlockPoolIndex.store(0, std::memory_order_relaxed); + other.initialBlockPoolSize = 0; + other.initialBlockPool = nullptr; + + reown_producers(); + } + + inline ConcurrentQueue& operator=(ConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT + { + return swap_internal(other); + } + + // Swaps this queue's state with the other's. Not thread-safe. + // Swapping two queues does not invalidate their tokens, however + // the tokens that were created for one queue must be used with + // only the swapped queue (i.e. the tokens are tied to the + // queue's movable state, not the object itself). + inline void swap(ConcurrentQueue& other) MOODYCAMEL_NOEXCEPT + { + swap_internal(other); + } + +private: + ConcurrentQueue& swap_internal(ConcurrentQueue& other) + { + if (this == &other) { + return *this; + } + + details::swap_relaxed(producerListTail, other.producerListTail); + details::swap_relaxed(producerCount, other.producerCount); + details::swap_relaxed(initialBlockPoolIndex, other.initialBlockPoolIndex); + std::swap(initialBlockPool, other.initialBlockPool); + std::swap(initialBlockPoolSize, other.initialBlockPoolSize); + freeList.swap(other.freeList); + details::swap_relaxed(nextExplicitConsumerId, other.nextExplicitConsumerId); + details::swap_relaxed(globalExplicitConsumerOffset, other.globalExplicitConsumerOffset); + + swap_implicit_producer_hashes(other); + + reown_producers(); + other.reown_producers(); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + details::swap_relaxed(explicitProducers, other.explicitProducers); + details::swap_relaxed(implicitProducers, other.implicitProducers); +#endif + + return *this; + } + +public: + // Enqueues a single item (by copying it). + // Allocates memory if required. Only fails if memory allocation fails (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0, + // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(T const& item) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue(item); + } + + // Enqueues a single item (by moving it, if possible). + // Allocates memory if required. Only fails if memory allocation fails (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0, + // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(T&& item) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue(std::move(item)); + } + + // Enqueues a single item (by copying it) using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails (or + // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(producer_token_t const& token, T const& item) + { + return inner_enqueue(token, item); + } + + // Enqueues a single item (by moving it, if possible) using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails (or + // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Thread-safe. + inline bool enqueue(producer_token_t const& token, T&& item) + { + return inner_enqueue(token, std::move(item)); + } + + // Enqueues several items. + // Allocates memory if required. Only fails if memory allocation fails (or + // implicit production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE + // is 0, or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Note: Use std::make_move_iterator if the elements should be moved instead of copied. + // Thread-safe. + template + bool enqueue_bulk(It itemFirst, size_t count) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue_bulk(itemFirst, count); + } + + // Enqueues several items using an explicit producer token. + // Allocates memory if required. Only fails if memory allocation fails + // (or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed). + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + bool enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) + { + return inner_enqueue_bulk(token, itemFirst, count); + } + + // Enqueues a single item (by copying it). + // Does not allocate memory. Fails if not enough room to enqueue (or implicit + // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE + // is 0). + // Thread-safe. + inline bool try_enqueue(T const& item) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue(item); + } + + // Enqueues a single item (by moving it, if possible). + // Does not allocate memory (except for one-time implicit producer). + // Fails if not enough room to enqueue (or implicit production is + // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0). + // Thread-safe. + inline bool try_enqueue(T&& item) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue(std::move(item)); + } + + // Enqueues a single item (by copying it) using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Thread-safe. + inline bool try_enqueue(producer_token_t const& token, T const& item) + { + return inner_enqueue(token, item); + } + + // Enqueues a single item (by moving it, if possible) using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Thread-safe. + inline bool try_enqueue(producer_token_t const& token, T&& item) + { + return inner_enqueue(token, std::move(item)); + } + + // Enqueues several items. + // Does not allocate memory (except for one-time implicit producer). + // Fails if not enough room to enqueue (or implicit production is + // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0). + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + bool try_enqueue_bulk(It itemFirst, size_t count) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false; + else return inner_enqueue_bulk(itemFirst, count); + } + + // Enqueues several items using an explicit producer token. + // Does not allocate memory. Fails if not enough room to enqueue. + // Note: Use std::make_move_iterator if the elements should be moved + // instead of copied. + // Thread-safe. + template + bool try_enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) + { + return inner_enqueue_bulk(token, itemFirst, count); + } + + + + // Attempts to dequeue from the queue. + // Returns false if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + bool try_dequeue(U& item) + { + // Instead of simply trying each producer in turn (which could cause needless contention on the first + // producer), we score them heuristically. + size_t nonEmptyCount = 0; + ProducerBase* best = nullptr; + size_t bestSize = 0; + for (auto ptr = producerListTail.load(std::memory_order_acquire); nonEmptyCount < 3 && ptr != nullptr; ptr = ptr->next_prod()) { + auto size = ptr->size_approx(); + if (size > 0) { + if (size > bestSize) { + bestSize = size; + best = ptr; + } + ++nonEmptyCount; + } + } + + // If there was at least one non-empty queue but it appears empty at the time + // we try to dequeue from it, we need to make sure every queue's been tried + if (nonEmptyCount > 0) { + if ((details::likely)(best->dequeue(item))) { + return true; + } + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + if (ptr != best && ptr->dequeue(item)) { + return true; + } + } + } + return false; + } + + // Attempts to dequeue from the queue. + // Returns false if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // This differs from the try_dequeue(item) method in that this one does + // not attempt to reduce contention by interleaving the order that producer + // streams are dequeued from. So, using this method can reduce overall throughput + // under contention, but will give more predictable results in single-threaded + // consumer scenarios. This is mostly only useful for internal unit tests. + // Never allocates. Thread-safe. + template + bool try_dequeue_non_interleaved(U& item) + { + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + if (ptr->dequeue(item)) { + return true; + } + } + return false; + } + + // Attempts to dequeue from the queue using an explicit consumer token. + // Returns false if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + bool try_dequeue(consumer_token_t& token, U& item) + { + // The idea is roughly as follows: + // Every 256 items from one producer, make everyone rotate (increase the global offset) -> this means the highest efficiency consumer dictates the rotation speed of everyone else, more or less + // If you see that the global offset has changed, you must reset your consumption counter and move to your designated place + // If there's no items where you're supposed to be, keep moving until you find a producer with some items + // If the global offset has not changed but you've run out of items to consume, move over from your current position until you find an producer with something in it + + if (token.desiredProducer == nullptr || token.lastKnownGlobalOffset != globalExplicitConsumerOffset.load(std::memory_order_relaxed)) { + if (!update_current_producer_after_rotation(token)) { + return false; + } + } + + // If there was at least one non-empty queue but it appears empty at the time + // we try to dequeue from it, we need to make sure every queue's been tried + if (static_cast(token.currentProducer)->dequeue(item)) { + if (++token.itemsConsumedFromCurrent == EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE) { + globalExplicitConsumerOffset.fetch_add(1, std::memory_order_relaxed); + } + return true; + } + + auto tail = producerListTail.load(std::memory_order_acquire); + auto ptr = static_cast(token.currentProducer)->next_prod(); + if (ptr == nullptr) { + ptr = tail; + } + while (ptr != static_cast(token.currentProducer)) { + if (ptr->dequeue(item)) { + token.currentProducer = ptr; + token.itemsConsumedFromCurrent = 1; + return true; + } + ptr = ptr->next_prod(); + if (ptr == nullptr) { + ptr = tail; + } + } + return false; + } + + // Attempts to dequeue several elements from the queue. + // Returns the number of items actually dequeued. + // Returns 0 if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + size_t try_dequeue_bulk(It itemFirst, size_t max) + { + size_t count = 0; + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + count += ptr->dequeue_bulk(itemFirst, max - count); + if (count == max) { + break; + } + } + return count; + } + + // Attempts to dequeue several elements from the queue using an explicit consumer token. + // Returns the number of items actually dequeued. + // Returns 0 if all producer streams appeared empty at the time they + // were checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + size_t try_dequeue_bulk(consumer_token_t& token, It itemFirst, size_t max) + { + if (token.desiredProducer == nullptr || token.lastKnownGlobalOffset != globalExplicitConsumerOffset.load(std::memory_order_relaxed)) { + if (!update_current_producer_after_rotation(token)) { + return 0; + } + } + + size_t count = static_cast(token.currentProducer)->dequeue_bulk(itemFirst, max); + if (count == max) { + if ((token.itemsConsumedFromCurrent += static_cast(max)) >= EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE) { + globalExplicitConsumerOffset.fetch_add(1, std::memory_order_relaxed); + } + return max; + } + token.itemsConsumedFromCurrent += static_cast(count); + max -= count; + + auto tail = producerListTail.load(std::memory_order_acquire); + auto ptr = static_cast(token.currentProducer)->next_prod(); + if (ptr == nullptr) { + ptr = tail; + } + while (ptr != static_cast(token.currentProducer)) { + auto dequeued = ptr->dequeue_bulk(itemFirst, max); + count += dequeued; + if (dequeued != 0) { + token.currentProducer = ptr; + token.itemsConsumedFromCurrent = static_cast(dequeued); + } + if (dequeued == max) { + break; + } + max -= dequeued; + ptr = ptr->next_prod(); + if (ptr == nullptr) { + ptr = tail; + } + } + return count; + } + + + + // Attempts to dequeue from a specific producer's inner queue. + // If you happen to know which producer you want to dequeue from, this + // is significantly faster than using the general-case try_dequeue methods. + // Returns false if the producer's queue appeared empty at the time it + // was checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + inline bool try_dequeue_from_producer(producer_token_t const& producer, U& item) + { + return static_cast(producer.producer)->dequeue(item); + } + + // Attempts to dequeue several elements from a specific producer's inner queue. + // Returns the number of items actually dequeued. + // If you happen to know which producer you want to dequeue from, this + // is significantly faster than using the general-case try_dequeue methods. + // Returns 0 if the producer's queue appeared empty at the time it + // was checked (so, the queue is likely but not guaranteed to be empty). + // Never allocates. Thread-safe. + template + inline size_t try_dequeue_bulk_from_producer(producer_token_t const& producer, It itemFirst, size_t max) + { + return static_cast(producer.producer)->dequeue_bulk(itemFirst, max); + } + + + // Returns an estimate of the total number of elements currently in the queue. This + // estimate is only accurate if the queue has completely stabilized before it is called + // (i.e. all enqueue and dequeue operations have completed and their memory effects are + // visible on the calling thread, and no further operations start while this method is + // being called). + // Thread-safe. + size_t size_approx() const + { + size_t size = 0; + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + size += ptr->size_approx(); + } + return size; + } + + + // Returns true if the underlying atomic variables used by + // the queue are lock-free (they should be on most platforms). + // Thread-safe. + static constexpr bool is_lock_free() + { + return + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::value == 2 && + details::static_is_lock_free::thread_id_numeric_size_t>::value == 2; + } + + +private: + friend struct ProducerToken; + friend struct ConsumerToken; + struct ExplicitProducer; + friend struct ExplicitProducer; + struct ImplicitProducer; + friend struct ImplicitProducer; + friend class ConcurrentQueueTests; + + enum AllocationMode { CanAlloc, CannotAlloc }; + + + /////////////////////////////// + // Queue methods + /////////////////////////////// + + template + inline bool inner_enqueue(producer_token_t const& token, U&& element) + { + return static_cast(token.producer)->ConcurrentQueue::ExplicitProducer::template enqueue(std::forward(element)); + } + + template + inline bool inner_enqueue(U&& element) + { + auto producer = get_or_add_implicit_producer(); + return producer == nullptr ? false : producer->ConcurrentQueue::ImplicitProducer::template enqueue(std::forward(element)); + } + + template + inline bool inner_enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count) + { + return static_cast(token.producer)->ConcurrentQueue::ExplicitProducer::template enqueue_bulk(itemFirst, count); + } + + template + inline bool inner_enqueue_bulk(It itemFirst, size_t count) + { + auto producer = get_or_add_implicit_producer(); + return producer == nullptr ? false : producer->ConcurrentQueue::ImplicitProducer::template enqueue_bulk(itemFirst, count); + } + + inline bool update_current_producer_after_rotation(consumer_token_t& token) + { + // Ah, there's been a rotation, figure out where we should be! + auto tail = producerListTail.load(std::memory_order_acquire); + if (token.desiredProducer == nullptr && tail == nullptr) { + return false; + } + auto prodCount = producerCount.load(std::memory_order_relaxed); + auto globalOffset = globalExplicitConsumerOffset.load(std::memory_order_relaxed); + if ((details::unlikely)(token.desiredProducer == nullptr)) { + // Aha, first time we're dequeueing anything. + // Figure out our local position + // Note: offset is from start, not end, but we're traversing from end -- subtract from count first + std::uint32_t offset = prodCount - 1 - (token.initialOffset % prodCount); + token.desiredProducer = tail; + for (std::uint32_t i = 0; i != offset; ++i) { + token.desiredProducer = static_cast(token.desiredProducer)->next_prod(); + if (token.desiredProducer == nullptr) { + token.desiredProducer = tail; + } + } + } + + std::uint32_t delta = globalOffset - token.lastKnownGlobalOffset; + if (delta >= prodCount) { + delta = delta % prodCount; + } + for (std::uint32_t i = 0; i != delta; ++i) { + token.desiredProducer = static_cast(token.desiredProducer)->next_prod(); + if (token.desiredProducer == nullptr) { + token.desiredProducer = tail; + } + } + + token.lastKnownGlobalOffset = globalOffset; + token.currentProducer = token.desiredProducer; + token.itemsConsumedFromCurrent = 0; + return true; + } + + + /////////////////////////// + // Free list + /////////////////////////// + + template + struct FreeListNode + { + FreeListNode() : freeListRefs(0), freeListNext(nullptr) { } + + std::atomic freeListRefs; + std::atomic freeListNext; + }; + + // A simple CAS-based lock-free free list. Not the fastest thing in the world under heavy contention, but + // simple and correct (assuming nodes are never freed until after the free list is destroyed), and fairly + // speedy under low contention. + template // N must inherit FreeListNode or have the same fields (and initialization of them) + struct FreeList + { + FreeList() : freeListHead(nullptr) { } + FreeList(FreeList&& other) : freeListHead(other.freeListHead.load(std::memory_order_relaxed)) { other.freeListHead.store(nullptr, std::memory_order_relaxed); } + void swap(FreeList& other) { details::swap_relaxed(freeListHead, other.freeListHead); } + + FreeList(FreeList const&) MOODYCAMEL_DELETE_FUNCTION; + FreeList& operator=(FreeList const&) MOODYCAMEL_DELETE_FUNCTION; + + inline void add(N* node) + { +#ifdef MCDBGQ_NOLOCKFREE_FREELIST + debug::DebugLock lock(mutex); +#endif + // We know that the should-be-on-freelist bit is 0 at this point, so it's safe to + // set it using a fetch_add + if (node->freeListRefs.fetch_add(SHOULD_BE_ON_FREELIST, std::memory_order_acq_rel) == 0) { + // Oh look! We were the last ones referencing this node, and we know + // we want to add it to the free list, so let's do it! + add_knowing_refcount_is_zero(node); + } + } + + inline N* try_get() + { +#ifdef MCDBGQ_NOLOCKFREE_FREELIST + debug::DebugLock lock(mutex); +#endif + auto head = freeListHead.load(std::memory_order_acquire); + while (head != nullptr) { + auto prevHead = head; + auto refs = head->freeListRefs.load(std::memory_order_relaxed); + if ((refs & REFS_MASK) == 0 || !head->freeListRefs.compare_exchange_strong(refs, refs + 1, std::memory_order_acquire, std::memory_order_relaxed)) { + head = freeListHead.load(std::memory_order_acquire); + continue; + } + + // Good, reference count has been incremented (it wasn't at zero), which means we can read the + // next and not worry about it changing between now and the time we do the CAS + auto next = head->freeListNext.load(std::memory_order_relaxed); + if (freeListHead.compare_exchange_strong(head, next, std::memory_order_acquire, std::memory_order_relaxed)) { + // Yay, got the node. This means it was on the list, which means shouldBeOnFreeList must be false no + // matter the refcount (because nobody else knows it's been taken off yet, it can't have been put back on). + assert((head->freeListRefs.load(std::memory_order_relaxed) & SHOULD_BE_ON_FREELIST) == 0); + + // Decrease refcount twice, once for our ref, and once for the list's ref + head->freeListRefs.fetch_sub(2, std::memory_order_release); + return head; + } + + // OK, the head must have changed on us, but we still need to decrease the refcount we increased. + // Note that we don't need to release any memory effects, but we do need to ensure that the reference + // count decrement happens-after the CAS on the head. + refs = prevHead->freeListRefs.fetch_sub(1, std::memory_order_acq_rel); + if (refs == SHOULD_BE_ON_FREELIST + 1) { + add_knowing_refcount_is_zero(prevHead); + } + } + + return nullptr; + } + + // Useful for traversing the list when there's no contention (e.g. to destroy remaining nodes) + N* head_unsafe() const { return freeListHead.load(std::memory_order_relaxed); } + + private: + inline void add_knowing_refcount_is_zero(N* node) + { + // Since the refcount is zero, and nobody can increase it once it's zero (except us, and we run + // only one copy of this method per node at a time, i.e. the single thread case), then we know + // we can safely change the next pointer of the node; however, once the refcount is back above + // zero, then other threads could increase it (happens under heavy contention, when the refcount + // goes to zero in between a load and a refcount increment of a node in try_get, then back up to + // something non-zero, then the refcount increment is done by the other thread) -- so, if the CAS + // to add the node to the actual list fails, decrease the refcount and leave the add operation to + // the next thread who puts the refcount back at zero (which could be us, hence the loop). + auto head = freeListHead.load(std::memory_order_relaxed); + while (true) { + node->freeListNext.store(head, std::memory_order_relaxed); + node->freeListRefs.store(1, std::memory_order_release); + if (!freeListHead.compare_exchange_strong(head, node, std::memory_order_release, std::memory_order_relaxed)) { + // Hmm, the add failed, but we can only try again when the refcount goes back to zero + if (node->freeListRefs.fetch_add(SHOULD_BE_ON_FREELIST - 1, std::memory_order_release) == 1) { + continue; + } + } + return; + } + } + + private: + // Implemented like a stack, but where node order doesn't matter (nodes are inserted out of order under contention) + std::atomic freeListHead; + + static const std::uint32_t REFS_MASK = 0x7FFFFFFF; + static const std::uint32_t SHOULD_BE_ON_FREELIST = 0x80000000; + +#ifdef MCDBGQ_NOLOCKFREE_FREELIST + debug::DebugMutex mutex; +#endif + }; + + + /////////////////////////// + // Block + /////////////////////////// + + enum InnerQueueContext { implicit_context = 0, explicit_context = 1 }; + + struct Block + { + Block() + : next(nullptr), elementsCompletelyDequeued(0), freeListRefs(0), freeListNext(nullptr), dynamicallyAllocated(true) + { +#ifdef MCDBGQ_TRACKMEM + owner = nullptr; +#endif + } + + template + inline bool is_empty() const + { + MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Check flags + for (size_t i = 0; i < BLOCK_SIZE; ++i) { + if (!emptyFlags[i].load(std::memory_order_relaxed)) { + return false; + } + } + + // Aha, empty; make sure we have all other memory effects that happened before the empty flags were set + std::atomic_thread_fence(std::memory_order_acquire); + return true; + } + else { + // Check counter + if (elementsCompletelyDequeued.load(std::memory_order_relaxed) == BLOCK_SIZE) { + std::atomic_thread_fence(std::memory_order_acquire); + return true; + } + assert(elementsCompletelyDequeued.load(std::memory_order_relaxed) <= BLOCK_SIZE); + return false; + } + } + + // Returns true if the block is now empty (does not apply in explicit context) + template + inline bool set_empty(MOODYCAMEL_MAYBE_UNUSED index_t i) + { + MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Set flag + assert(!emptyFlags[BLOCK_SIZE - 1 - static_cast(i & static_cast(BLOCK_SIZE - 1))].load(std::memory_order_relaxed)); + emptyFlags[BLOCK_SIZE - 1 - static_cast(i & static_cast(BLOCK_SIZE - 1))].store(true, std::memory_order_release); + return false; + } + else { + // Increment counter + auto prevVal = elementsCompletelyDequeued.fetch_add(1, std::memory_order_release); + assert(prevVal < BLOCK_SIZE); + return prevVal == BLOCK_SIZE - 1; + } + } + + // Sets multiple contiguous item statuses to 'empty' (assumes no wrapping and count > 0). + // Returns true if the block is now empty (does not apply in explicit context). + template + inline bool set_many_empty(MOODYCAMEL_MAYBE_UNUSED index_t i, size_t count) + { + MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Set flags + std::atomic_thread_fence(std::memory_order_release); + i = BLOCK_SIZE - 1 - static_cast(i & static_cast(BLOCK_SIZE - 1)) - count + 1; + for (size_t j = 0; j != count; ++j) { + assert(!emptyFlags[i + j].load(std::memory_order_relaxed)); + emptyFlags[i + j].store(true, std::memory_order_relaxed); + } + return false; + } + else { + // Increment counter + auto prevVal = elementsCompletelyDequeued.fetch_add(count, std::memory_order_release); + assert(prevVal + count <= BLOCK_SIZE); + return prevVal + count == BLOCK_SIZE; + } + } + + template + inline void set_all_empty() + { + MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Set all flags + for (size_t i = 0; i != BLOCK_SIZE; ++i) { + emptyFlags[i].store(true, std::memory_order_relaxed); + } + } + else { + // Reset counter + elementsCompletelyDequeued.store(BLOCK_SIZE, std::memory_order_relaxed); + } + } + + template + inline void reset_empty() + { + MOODYCAMEL_CONSTEXPR_IF (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) { + // Reset flags + for (size_t i = 0; i != BLOCK_SIZE; ++i) { + emptyFlags[i].store(false, std::memory_order_relaxed); + } + } + else { + // Reset counter + elementsCompletelyDequeued.store(0, std::memory_order_relaxed); + } + } + + inline T* operator[](index_t idx) MOODYCAMEL_NOEXCEPT { return static_cast(static_cast(elements)) + static_cast(idx & static_cast(BLOCK_SIZE - 1)); } + inline T const* operator[](index_t idx) const MOODYCAMEL_NOEXCEPT { return static_cast(static_cast(elements)) + static_cast(idx & static_cast(BLOCK_SIZE - 1)); } + + private: + static_assert(std::alignment_of::value <= sizeof(T), "The queue does not support types with an alignment greater than their size at this time"); + MOODYCAMEL_ALIGNED_TYPE_LIKE(char[sizeof(T) * BLOCK_SIZE], T) elements; + public: + Block* next; + std::atomic elementsCompletelyDequeued; + std::atomic emptyFlags[BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD ? BLOCK_SIZE : 1]; + public: + std::atomic freeListRefs; + std::atomic freeListNext; + bool dynamicallyAllocated; // Perhaps a better name for this would be 'isNotPartOfInitialBlockPool' + +#ifdef MCDBGQ_TRACKMEM + void* owner; +#endif + }; + static_assert(std::alignment_of::value >= std::alignment_of::value, "Internal error: Blocks must be at least as aligned as the type they are wrapping"); + + +#ifdef MCDBGQ_TRACKMEM +public: + struct MemStats; +private: +#endif + + /////////////////////////// + // Producer base + /////////////////////////// + + struct ProducerBase : public details::ConcurrentQueueProducerTypelessBase + { + ProducerBase(ConcurrentQueue* parent_, bool isExplicit_) : + tailIndex(0), + headIndex(0), + dequeueOptimisticCount(0), + dequeueOvercommit(0), + tailBlock(nullptr), + isExplicit(isExplicit_), + parent(parent_) + { + } + + virtual ~ProducerBase() { } + + template + inline bool dequeue(U& element) + { + if (isExplicit) { + return static_cast(this)->dequeue(element); + } + else { + return static_cast(this)->dequeue(element); + } + } + + template + inline size_t dequeue_bulk(It& itemFirst, size_t max) + { + if (isExplicit) { + return static_cast(this)->dequeue_bulk(itemFirst, max); + } + else { + return static_cast(this)->dequeue_bulk(itemFirst, max); + } + } + + inline ProducerBase* next_prod() const { return static_cast(next); } + + inline size_t size_approx() const + { + auto tail = tailIndex.load(std::memory_order_relaxed); + auto head = headIndex.load(std::memory_order_relaxed); + return details::circular_less_than(head, tail) ? static_cast(tail - head) : 0; + } + + inline index_t getTail() const { return tailIndex.load(std::memory_order_relaxed); } + protected: + std::atomic tailIndex; // Where to enqueue to next + std::atomic headIndex; // Where to dequeue from next + + std::atomic dequeueOptimisticCount; + std::atomic dequeueOvercommit; + + Block* tailBlock; + + public: + bool isExplicit; + ConcurrentQueue* parent; + + protected: +#ifdef MCDBGQ_TRACKMEM + friend struct MemStats; +#endif + }; + + + /////////////////////////// + // Explicit queue + /////////////////////////// + + struct ExplicitProducer : public ProducerBase + { + explicit ExplicitProducer(ConcurrentQueue* parent_) : + ProducerBase(parent_, true), + blockIndex(nullptr), + pr_blockIndexSlotsUsed(0), + pr_blockIndexSize(EXPLICIT_INITIAL_INDEX_SIZE >> 1), + pr_blockIndexFront(0), + pr_blockIndexEntries(nullptr), + pr_blockIndexRaw(nullptr) + { + size_t poolBasedIndexSize = details::ceil_to_pow_2(parent_->initialBlockPoolSize) >> 1; + if (poolBasedIndexSize > pr_blockIndexSize) { + pr_blockIndexSize = poolBasedIndexSize; + } + + new_block_index(0); // This creates an index with double the number of current entries, i.e. EXPLICIT_INITIAL_INDEX_SIZE + } + + ~ExplicitProducer() + { + // Destruct any elements not yet dequeued. + // Since we're in the destructor, we can assume all elements + // are either completely dequeued or completely not (no halfways). + if (this->tailBlock != nullptr) { // Note this means there must be a block index too + // First find the block that's partially dequeued, if any + Block* halfDequeuedBlock = nullptr; + if ((this->headIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)) != 0) { + // The head's not on a block boundary, meaning a block somewhere is partially dequeued + // (or the head block is the tail block and was fully dequeued, but the head/tail are still not on a boundary) + size_t i = (pr_blockIndexFront - pr_blockIndexSlotsUsed) & (pr_blockIndexSize - 1); + while (details::circular_less_than(pr_blockIndexEntries[i].base + BLOCK_SIZE, this->headIndex.load(std::memory_order_relaxed))) { + i = (i + 1) & (pr_blockIndexSize - 1); + } + assert(details::circular_less_than(pr_blockIndexEntries[i].base, this->headIndex.load(std::memory_order_relaxed))); + halfDequeuedBlock = pr_blockIndexEntries[i].block; + } + + // Start at the head block (note the first line in the loop gives us the head from the tail on the first iteration) + auto block = this->tailBlock; + do { + block = block->next; + if (block->ConcurrentQueue::Block::template is_empty()) { + continue; + } + + size_t i = 0; // Offset into block + if (block == halfDequeuedBlock) { + i = static_cast(this->headIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)); + } + + // Walk through all the items in the block; if this is the tail block, we need to stop when we reach the tail index + auto lastValidIndex = (this->tailIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)) == 0 ? BLOCK_SIZE : static_cast(this->tailIndex.load(std::memory_order_relaxed) & static_cast(BLOCK_SIZE - 1)); + while (i != BLOCK_SIZE && (block != this->tailBlock || i != lastValidIndex)) { + (*block)[i++]->~T(); + } + } while (block != this->tailBlock); + } + + // Destroy all blocks that we own + if (this->tailBlock != nullptr) { + auto block = this->tailBlock; + do { + auto nextBlock = block->next; + this->parent->add_block_to_free_list(block); + block = nextBlock; + } while (block != this->tailBlock); + } + + // Destroy the block indices + auto header = static_cast(pr_blockIndexRaw); + while (header != nullptr) { + auto prev = static_cast(header->prev); + header->~BlockIndexHeader(); + (Traits::free)(header); + header = prev; + } + } + + template + inline bool enqueue(U&& element) + { + index_t currentTailIndex = this->tailIndex.load(std::memory_order_relaxed); + index_t newTailIndex = 1 + currentTailIndex; + if ((currentTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { + // We reached the end of a block, start a new one + auto startBlock = this->tailBlock; + auto originalBlockIndexSlotsUsed = pr_blockIndexSlotsUsed; + if (this->tailBlock != nullptr && this->tailBlock->next->ConcurrentQueue::Block::template is_empty()) { + // We can re-use the block ahead of us, it's empty! + this->tailBlock = this->tailBlock->next; + this->tailBlock->ConcurrentQueue::Block::template reset_empty(); + + // We'll put the block on the block index (guaranteed to be room since we're conceptually removing the + // last block from it first -- except instead of removing then adding, we can just overwrite). + // Note that there must be a valid block index here, since even if allocation failed in the ctor, + // it would have been re-attempted when adding the first block to the queue; since there is such + // a block, a block index must have been successfully allocated. + } + else { + // Whatever head value we see here is >= the last value we saw here (relatively), + // and <= its current value. Since we have the most recent tail, the head must be + // <= to it. + auto head = this->headIndex.load(std::memory_order_relaxed); + assert(!details::circular_less_than(currentTailIndex, head)); + if (!details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) + || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head))) { + // We can't enqueue in another block because there's not enough leeway -- the + // tail could surpass the head by the time the block fills up! (Or we'll exceed + // the size limit, if the second part of the condition was true.) + return false; + } + // We're going to need a new block; check that the block index has room + if (pr_blockIndexRaw == nullptr || pr_blockIndexSlotsUsed == pr_blockIndexSize) { + // Hmm, the circular block index is already full -- we'll need + // to allocate a new index. Note pr_blockIndexRaw can only be nullptr if + // the initial allocation failed in the constructor. + + MOODYCAMEL_CONSTEXPR_IF (allocMode == CannotAlloc) { + return false; + } + else if (!new_block_index(pr_blockIndexSlotsUsed)) { + return false; + } + } + + // Insert a new block in the circular linked list + auto newBlock = this->parent->ConcurrentQueue::template requisition_block(); + if (newBlock == nullptr) { + return false; + } +#ifdef MCDBGQ_TRACKMEM + newBlock->owner = this; +#endif + newBlock->ConcurrentQueue::Block::template reset_empty(); + if (this->tailBlock == nullptr) { + newBlock->next = newBlock; + } + else { + newBlock->next = this->tailBlock->next; + this->tailBlock->next = newBlock; + } + this->tailBlock = newBlock; + ++pr_blockIndexSlotsUsed; + } + + MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(element)))) { + // The constructor may throw. We want the element not to appear in the queue in + // that case (without corrupting the queue): + MOODYCAMEL_TRY { + new ((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); + } + MOODYCAMEL_CATCH (...) { + // Revert change to the current block, but leave the new block available + // for next time + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? this->tailBlock : startBlock; + MOODYCAMEL_RETHROW; + } + } + else { + (void)startBlock; + (void)originalBlockIndexSlotsUsed; + } + + // Add block to block index + auto& entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront]; + entry.base = currentTailIndex; + entry.block = this->tailBlock; + blockIndex.load(std::memory_order_relaxed)->front.store(pr_blockIndexFront, std::memory_order_release); + pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); + + MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(element)))) { + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + } + + // Enqueue + new ((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); + + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + + template + bool dequeue(U& element) + { + auto tail = this->tailIndex.load(std::memory_order_relaxed); + auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); + if (details::circular_less_than(this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit, tail)) { + // Might be something to dequeue, let's give it a try + + // Note that this if is purely for performance purposes in the common case when the queue is + // empty and the values are eventually consistent -- we may enter here spuriously. + + // Note that whatever the values of overcommit and tail are, they are not going to change (unless we + // change them) and must be the same value at this point (inside the if) as when the if condition was + // evaluated. + + // We insert an acquire fence here to synchronize-with the release upon incrementing dequeueOvercommit below. + // This ensures that whatever the value we got loaded into overcommit, the load of dequeueOptisticCount in + // the fetch_add below will result in a value at least as recent as that (and therefore at least as large). + // Note that I believe a compiler (signal) fence here would be sufficient due to the nature of fetch_add (all + // read-modify-write operations are guaranteed to work on the latest value in the modification order), but + // unfortunately that can't be shown to be correct using only the C++11 standard. + // See http://stackoverflow.com/questions/18223161/what-are-the-c11-memory-ordering-guarantees-in-this-corner-case + std::atomic_thread_fence(std::memory_order_acquire); + + // Increment optimistic counter, then check if it went over the boundary + auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(1, std::memory_order_relaxed); + + // Note that since dequeueOvercommit must be <= dequeueOptimisticCount (because dequeueOvercommit is only ever + // incremented after dequeueOptimisticCount -- this is enforced in the `else` block below), and since we now + // have a version of dequeueOptimisticCount that is at least as recent as overcommit (due to the release upon + // incrementing dequeueOvercommit and the acquire above that synchronizes with it), overcommit <= myDequeueCount. + // However, we can't assert this since both dequeueOptimisticCount and dequeueOvercommit may (independently) + // overflow; in such a case, though, the logic still holds since the difference between the two is maintained. + + // Note that we reload tail here in case it changed; it will be the same value as before or greater, since + // this load is sequenced after (happens after) the earlier load above. This is supported by read-read + // coherency (as defined in the standard), explained here: http://en.cppreference.com/w/cpp/atomic/memory_order + tail = this->tailIndex.load(std::memory_order_acquire); + if ((details::likely)(details::circular_less_than(myDequeueCount - overcommit, tail))) { + // Guaranteed to be at least one element to dequeue! + + // Get the index. Note that since there's guaranteed to be at least one element, this + // will never exceed tail. We need to do an acquire-release fence here since it's possible + // that whatever condition got us to this point was for an earlier enqueued element (that + // we already see the memory effects for), but that by the time we increment somebody else + // has incremented it, and we need to see the memory effects for *that* element, which is + // in such a case is necessarily visible on the thread that incremented it in the first + // place with the more current condition (they must have acquired a tail that is at least + // as recent). + auto index = this->headIndex.fetch_add(1, std::memory_order_acq_rel); + + + // Determine which block the element is in + + auto localBlockIndex = blockIndex.load(std::memory_order_acquire); + auto localBlockIndexHead = localBlockIndex->front.load(std::memory_order_acquire); + + // We need to be careful here about subtracting and dividing because of index wrap-around. + // When an index wraps, we need to preserve the sign of the offset when dividing it by the + // block size (in order to get a correct signed block count offset in all cases): + auto headBase = localBlockIndex->entries[localBlockIndexHead].base; + auto blockBaseIndex = index & ~static_cast(BLOCK_SIZE - 1); + auto offset = static_cast(static_cast::type>(blockBaseIndex - headBase) / static_cast::type>(BLOCK_SIZE)); + auto block = localBlockIndex->entries[(localBlockIndexHead + offset) & (localBlockIndex->size - 1)].block; + + // Dequeue + auto& el = *((*block)[index]); + if (!MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, element = std::move(el))) { + // Make sure the element is still fully dequeued and destroyed even if the assignment + // throws + struct Guard { + Block* block; + index_t index; + + ~Guard() + { + (*block)[index]->~T(); + block->ConcurrentQueue::Block::template set_empty(index); + } + } guard = { block, index }; + + element = std::move(el); // NOLINT + } + else { + element = std::move(el); // NOLINT + el.~T(); // NOLINT + block->ConcurrentQueue::Block::template set_empty(index); + } + + return true; + } + else { + // Wasn't anything to dequeue after all; make the effective dequeue count eventually consistent + this->dequeueOvercommit.fetch_add(1, std::memory_order_release); // Release so that the fetch_add on dequeueOptimisticCount is guaranteed to happen before this write + } + } + + return false; + } + + template + bool MOODYCAMEL_NO_TSAN enqueue_bulk(It itemFirst, size_t count) + { + // First, we need to make sure we have enough room to enqueue all of the elements; + // this means pre-allocating blocks and putting them in the block index (but only if + // all the allocations succeeded). + index_t startTailIndex = this->tailIndex.load(std::memory_order_relaxed); + auto startBlock = this->tailBlock; + auto originalBlockIndexFront = pr_blockIndexFront; + auto originalBlockIndexSlotsUsed = pr_blockIndexSlotsUsed; + + Block* firstAllocatedBlock = nullptr; + + // Figure out how many blocks we'll need to allocate, and do so + size_t blockBaseDiff = ((startTailIndex + count - 1) & ~static_cast(BLOCK_SIZE - 1)) - ((startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1)); + index_t currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); + if (blockBaseDiff > 0) { + // Allocate as many blocks as possible from ahead + while (blockBaseDiff > 0 && this->tailBlock != nullptr && this->tailBlock->next != firstAllocatedBlock && this->tailBlock->next->ConcurrentQueue::Block::template is_empty()) { + blockBaseDiff -= static_cast(BLOCK_SIZE); + currentTailIndex += static_cast(BLOCK_SIZE); + + this->tailBlock = this->tailBlock->next; + firstAllocatedBlock = firstAllocatedBlock == nullptr ? this->tailBlock : firstAllocatedBlock; + + auto& entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront]; + entry.base = currentTailIndex; + entry.block = this->tailBlock; + pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); + } + + // Now allocate as many blocks as necessary from the block pool + while (blockBaseDiff > 0) { + blockBaseDiff -= static_cast(BLOCK_SIZE); + currentTailIndex += static_cast(BLOCK_SIZE); + + auto head = this->headIndex.load(std::memory_order_relaxed); + assert(!details::circular_less_than(currentTailIndex, head)); + bool full = !details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head)); + if (pr_blockIndexRaw == nullptr || pr_blockIndexSlotsUsed == pr_blockIndexSize || full) { + MOODYCAMEL_CONSTEXPR_IF (allocMode == CannotAlloc) { + // Failed to allocate, undo changes (but keep injected blocks) + pr_blockIndexFront = originalBlockIndexFront; + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; + return false; + } + else if (full || !new_block_index(originalBlockIndexSlotsUsed)) { + // Failed to allocate, undo changes (but keep injected blocks) + pr_blockIndexFront = originalBlockIndexFront; + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; + return false; + } + + // pr_blockIndexFront is updated inside new_block_index, so we need to + // update our fallback value too (since we keep the new index even if we + // later fail) + originalBlockIndexFront = originalBlockIndexSlotsUsed; + } + + // Insert a new block in the circular linked list + auto newBlock = this->parent->ConcurrentQueue::template requisition_block(); + if (newBlock == nullptr) { + pr_blockIndexFront = originalBlockIndexFront; + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; + return false; + } + +#ifdef MCDBGQ_TRACKMEM + newBlock->owner = this; +#endif + newBlock->ConcurrentQueue::Block::template set_all_empty(); + if (this->tailBlock == nullptr) { + newBlock->next = newBlock; + } + else { + newBlock->next = this->tailBlock->next; + this->tailBlock->next = newBlock; + } + this->tailBlock = newBlock; + firstAllocatedBlock = firstAllocatedBlock == nullptr ? this->tailBlock : firstAllocatedBlock; + + ++pr_blockIndexSlotsUsed; + + auto& entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront]; + entry.base = currentTailIndex; + entry.block = this->tailBlock; + pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1); + } + + // Excellent, all allocations succeeded. Reset each block's emptiness before we fill them up, and + // publish the new block index front + auto block = firstAllocatedBlock; + while (true) { + block->ConcurrentQueue::Block::template reset_empty(); + if (block == this->tailBlock) { + break; + } + block = block->next; + } + + MOODYCAMEL_CONSTEXPR_IF (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (static_cast(nullptr)) T(details::deref_noexcept(itemFirst)))) { + blockIndex.load(std::memory_order_relaxed)->front.store((pr_blockIndexFront - 1) & (pr_blockIndexSize - 1), std::memory_order_release); + } + } + + // Enqueue, one block at a time + index_t newTailIndex = startTailIndex + static_cast(count); + currentTailIndex = startTailIndex; + auto endBlock = this->tailBlock; + this->tailBlock = startBlock; + assert((startTailIndex & static_cast(BLOCK_SIZE - 1)) != 0 || firstAllocatedBlock != nullptr || count == 0); + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0 && firstAllocatedBlock != nullptr) { + this->tailBlock = firstAllocatedBlock; + } + while (true) { + index_t stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + if (details::circular_less_than(newTailIndex, stopIndex)) { + stopIndex = newTailIndex; + } + MOODYCAMEL_CONSTEXPR_IF (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (static_cast(nullptr)) T(details::deref_noexcept(itemFirst)))) { + while (currentTailIndex != stopIndex) { + new ((*this->tailBlock)[currentTailIndex++]) T(*itemFirst++); + } + } + else { + MOODYCAMEL_TRY { + while (currentTailIndex != stopIndex) { + // Must use copy constructor even if move constructor is available + // because we may have to revert if there's an exception. + // Sorry about the horrible templated next line, but it was the only way + // to disable moving *at compile time*, which is important because a type + // may only define a (noexcept) move constructor, and so calls to the + // cctor will not compile, even if they are in an if branch that will never + // be executed + new ((*this->tailBlock)[currentTailIndex]) T(details::nomove_if(nullptr)) T(details::deref_noexcept(itemFirst)))>::eval(*itemFirst)); + ++currentTailIndex; + ++itemFirst; + } + } + MOODYCAMEL_CATCH (...) { + // Oh dear, an exception's been thrown -- destroy the elements that + // were enqueued so far and revert the entire bulk operation (we'll keep + // any allocated blocks in our linked list for later, though). + auto constructedStopIndex = currentTailIndex; + auto lastBlockEnqueued = this->tailBlock; + + pr_blockIndexFront = originalBlockIndexFront; + pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed; + this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock; + + if (!details::is_trivially_destructible::value) { + auto block = startBlock; + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { + block = firstAllocatedBlock; + } + currentTailIndex = startTailIndex; + while (true) { + stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + if (details::circular_less_than(constructedStopIndex, stopIndex)) { + stopIndex = constructedStopIndex; + } + while (currentTailIndex != stopIndex) { + (*block)[currentTailIndex++]->~T(); + } + if (block == lastBlockEnqueued) { + break; + } + block = block->next; + } + } + MOODYCAMEL_RETHROW; + } + } + + if (this->tailBlock == endBlock) { + assert(currentTailIndex == newTailIndex); + break; + } + this->tailBlock = this->tailBlock->next; + } + + MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (static_cast(nullptr)) T(details::deref_noexcept(itemFirst)))) { + if (firstAllocatedBlock != nullptr) + blockIndex.load(std::memory_order_relaxed)->front.store((pr_blockIndexFront - 1) & (pr_blockIndexSize - 1), std::memory_order_release); + } + + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + + template + size_t dequeue_bulk(It& itemFirst, size_t max) + { + auto tail = this->tailIndex.load(std::memory_order_relaxed); + auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); + auto desiredCount = static_cast(tail - (this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit)); + if (details::circular_less_than(0, desiredCount)) { + desiredCount = desiredCount < max ? desiredCount : max; + std::atomic_thread_fence(std::memory_order_acquire); + + auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(desiredCount, std::memory_order_relaxed); + + tail = this->tailIndex.load(std::memory_order_acquire); + auto actualCount = static_cast(tail - (myDequeueCount - overcommit)); + if (details::circular_less_than(0, actualCount)) { + actualCount = desiredCount < actualCount ? desiredCount : actualCount; + if (actualCount < desiredCount) { + this->dequeueOvercommit.fetch_add(desiredCount - actualCount, std::memory_order_release); + } + + // Get the first index. Note that since there's guaranteed to be at least actualCount elements, this + // will never exceed tail. + auto firstIndex = this->headIndex.fetch_add(actualCount, std::memory_order_acq_rel); + + // Determine which block the first element is in + auto localBlockIndex = blockIndex.load(std::memory_order_acquire); + auto localBlockIndexHead = localBlockIndex->front.load(std::memory_order_acquire); + + auto headBase = localBlockIndex->entries[localBlockIndexHead].base; + auto firstBlockBaseIndex = firstIndex & ~static_cast(BLOCK_SIZE - 1); + auto offset = static_cast(static_cast::type>(firstBlockBaseIndex - headBase) / static_cast::type>(BLOCK_SIZE)); + auto indexIndex = (localBlockIndexHead + offset) & (localBlockIndex->size - 1); + + // Iterate the blocks and dequeue + auto index = firstIndex; + do { + auto firstIndexInBlock = index; + index_t endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; + auto block = localBlockIndex->entries[indexIndex].block; + if (MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, details::deref_noexcept(itemFirst) = std::move((*(*block)[index])))) { + while (index != endIndex) { + auto& el = *((*block)[index]); + *itemFirst++ = std::move(el); + el.~T(); + ++index; + } + } + else { + MOODYCAMEL_TRY { + while (index != endIndex) { + auto& el = *((*block)[index]); + *itemFirst = std::move(el); + ++itemFirst; + el.~T(); + ++index; + } + } + MOODYCAMEL_CATCH (...) { + // It's too late to revert the dequeue, but we can make sure that all + // the dequeued objects are properly destroyed and the block index + // (and empty count) are properly updated before we propagate the exception + do { + block = localBlockIndex->entries[indexIndex].block; + while (index != endIndex) { + (*block)[index++]->~T(); + } + block->ConcurrentQueue::Block::template set_many_empty(firstIndexInBlock, static_cast(endIndex - firstIndexInBlock)); + indexIndex = (indexIndex + 1) & (localBlockIndex->size - 1); + + firstIndexInBlock = index; + endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; + } while (index != firstIndex + actualCount); + + MOODYCAMEL_RETHROW; + } + } + block->ConcurrentQueue::Block::template set_many_empty(firstIndexInBlock, static_cast(endIndex - firstIndexInBlock)); + indexIndex = (indexIndex + 1) & (localBlockIndex->size - 1); + } while (index != firstIndex + actualCount); + + return actualCount; + } + else { + // Wasn't anything to dequeue after all; make the effective dequeue count eventually consistent + this->dequeueOvercommit.fetch_add(desiredCount, std::memory_order_release); + } + } + + return 0; + } + + private: + struct BlockIndexEntry + { + index_t base; + Block* block; + }; + + struct BlockIndexHeader + { + size_t size; + std::atomic front; // Current slot (not next, like pr_blockIndexFront) + BlockIndexEntry* entries; + void* prev; + }; + + + bool new_block_index(size_t numberOfFilledSlotsToExpose) + { + auto prevBlockSizeMask = pr_blockIndexSize - 1; + + // Create the new block + pr_blockIndexSize <<= 1; + auto newRawPtr = static_cast((Traits::malloc)(sizeof(BlockIndexHeader) + std::alignment_of::value - 1 + sizeof(BlockIndexEntry) * pr_blockIndexSize)); + if (newRawPtr == nullptr) { + pr_blockIndexSize >>= 1; // Reset to allow graceful retry + return false; + } + + auto newBlockIndexEntries = reinterpret_cast(details::align_for(newRawPtr + sizeof(BlockIndexHeader))); + + // Copy in all the old indices, if any + size_t j = 0; + if (pr_blockIndexSlotsUsed != 0) { + auto i = (pr_blockIndexFront - pr_blockIndexSlotsUsed) & prevBlockSizeMask; + do { + newBlockIndexEntries[j++] = pr_blockIndexEntries[i]; + i = (i + 1) & prevBlockSizeMask; + } while (i != pr_blockIndexFront); + } + + // Update everything + auto header = new (newRawPtr) BlockIndexHeader; + header->size = pr_blockIndexSize; + header->front.store(numberOfFilledSlotsToExpose - 1, std::memory_order_relaxed); + header->entries = newBlockIndexEntries; + header->prev = pr_blockIndexRaw; // we link the new block to the old one so we can free it later + + pr_blockIndexFront = j; + pr_blockIndexEntries = newBlockIndexEntries; + pr_blockIndexRaw = newRawPtr; + blockIndex.store(header, std::memory_order_release); + + return true; + } + + private: + std::atomic blockIndex; + + // To be used by producer only -- consumer must use the ones in referenced by blockIndex + size_t pr_blockIndexSlotsUsed; + size_t pr_blockIndexSize; + size_t pr_blockIndexFront; // Next slot (not current) + BlockIndexEntry* pr_blockIndexEntries; + void* pr_blockIndexRaw; + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + public: + ExplicitProducer* nextExplicitProducer; + private: +#endif + +#ifdef MCDBGQ_TRACKMEM + friend struct MemStats; +#endif + }; + + + ////////////////////////////////// + // Implicit queue + ////////////////////////////////// + + struct ImplicitProducer : public ProducerBase + { + ImplicitProducer(ConcurrentQueue* parent_) : + ProducerBase(parent_, false), + nextBlockIndexCapacity(IMPLICIT_INITIAL_INDEX_SIZE), + blockIndex(nullptr) + { + new_block_index(); + } + + ~ImplicitProducer() + { + // Note that since we're in the destructor we can assume that all enqueue/dequeue operations + // completed already; this means that all undequeued elements are placed contiguously across + // contiguous blocks, and that only the first and last remaining blocks can be only partially + // empty (all other remaining blocks must be completely full). + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + // Unregister ourselves for thread termination notification + if (!this->inactive.load(std::memory_order_relaxed)) { + details::ThreadExitNotifier::unsubscribe(&threadExitListener); + } +#endif + + // Destroy all remaining elements! + auto tail = this->tailIndex.load(std::memory_order_relaxed); + auto index = this->headIndex.load(std::memory_order_relaxed); + Block* block = nullptr; + assert(index == tail || details::circular_less_than(index, tail)); + bool forceFreeLastBlock = index != tail; // If we enter the loop, then the last (tail) block will not be freed + while (index != tail) { + if ((index & static_cast(BLOCK_SIZE - 1)) == 0 || block == nullptr) { + if (block != nullptr) { + // Free the old block + this->parent->add_block_to_free_list(block); + } + + block = get_block_index_entry_for_index(index)->value.load(std::memory_order_relaxed); + } + + ((*block)[index])->~T(); + ++index; + } + // Even if the queue is empty, there's still one block that's not on the free list + // (unless the head index reached the end of it, in which case the tail will be poised + // to create a new block). + if (this->tailBlock != nullptr && (forceFreeLastBlock || (tail & static_cast(BLOCK_SIZE - 1)) != 0)) { + this->parent->add_block_to_free_list(this->tailBlock); + } + + // Destroy block index + auto localBlockIndex = blockIndex.load(std::memory_order_relaxed); + if (localBlockIndex != nullptr) { + for (size_t i = 0; i != localBlockIndex->capacity; ++i) { + localBlockIndex->index[i]->~BlockIndexEntry(); + } + do { + auto prev = localBlockIndex->prev; + localBlockIndex->~BlockIndexHeader(); + (Traits::free)(localBlockIndex); + localBlockIndex = prev; + } while (localBlockIndex != nullptr); + } + } + + template + inline bool enqueue(U&& element) + { + index_t currentTailIndex = this->tailIndex.load(std::memory_order_relaxed); + index_t newTailIndex = 1 + currentTailIndex; + if ((currentTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { + // We reached the end of a block, start a new one + auto head = this->headIndex.load(std::memory_order_relaxed); + assert(!details::circular_less_than(currentTailIndex, head)); + if (!details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head))) { + return false; + } +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + // Find out where we'll be inserting this block in the block index + BlockIndexEntry* idxEntry; + if (!insert_block_index_entry(idxEntry, currentTailIndex)) { + return false; + } + + // Get ahold of a new block + auto newBlock = this->parent->ConcurrentQueue::template requisition_block(); + if (newBlock == nullptr) { + rewind_block_index_tail(); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + return false; + } +#ifdef MCDBGQ_TRACKMEM + newBlock->owner = this; +#endif + newBlock->ConcurrentQueue::Block::template reset_empty(); + + MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(element)))) { + // May throw, try to insert now before we publish the fact that we have this new block + MOODYCAMEL_TRY { + new ((*newBlock)[currentTailIndex]) T(std::forward(element)); + } + MOODYCAMEL_CATCH (...) { + rewind_block_index_tail(); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + this->parent->add_block_to_free_list(newBlock); + MOODYCAMEL_RETHROW; + } + } + + // Insert the new block into the index + idxEntry->value.store(newBlock, std::memory_order_relaxed); + + this->tailBlock = newBlock; + + MOODYCAMEL_CONSTEXPR_IF (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (static_cast(nullptr)) T(std::forward(element)))) { + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + } + + // Enqueue + new ((*this->tailBlock)[currentTailIndex]) T(std::forward(element)); + + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } + + template + bool dequeue(U& element) + { + // See ExplicitProducer::dequeue for rationale and explanation + index_t tail = this->tailIndex.load(std::memory_order_relaxed); + index_t overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); + if (details::circular_less_than(this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit, tail)) { + std::atomic_thread_fence(std::memory_order_acquire); + + index_t myDequeueCount = this->dequeueOptimisticCount.fetch_add(1, std::memory_order_relaxed); + tail = this->tailIndex.load(std::memory_order_acquire); + if ((details::likely)(details::circular_less_than(myDequeueCount - overcommit, tail))) { + index_t index = this->headIndex.fetch_add(1, std::memory_order_acq_rel); + + // Determine which block the element is in + auto entry = get_block_index_entry_for_index(index); + + // Dequeue + auto block = entry->value.load(std::memory_order_relaxed); + auto& el = *((*block)[index]); + + if (!MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, element = std::move(el))) { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + // Note: Acquiring the mutex with every dequeue instead of only when a block + // is released is very sub-optimal, but it is, after all, purely debug code. + debug::DebugLock lock(producer->mutex); +#endif + struct Guard { + Block* block; + index_t index; + BlockIndexEntry* entry; + ConcurrentQueue* parent; + + ~Guard() + { + (*block)[index]->~T(); + if (block->ConcurrentQueue::Block::template set_empty(index)) { + entry->value.store(nullptr, std::memory_order_relaxed); + parent->add_block_to_free_list(block); + } + } + } guard = { block, index, entry, this->parent }; + + element = std::move(el); // NOLINT + } + else { + element = std::move(el); // NOLINT + el.~T(); // NOLINT + + if (block->ConcurrentQueue::Block::template set_empty(index)) { + { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + // Add the block back into the global free pool (and remove from block index) + entry->value.store(nullptr, std::memory_order_relaxed); + } + this->parent->add_block_to_free_list(block); // releases the above store + } + } + + return true; + } + else { + this->dequeueOvercommit.fetch_add(1, std::memory_order_release); + } + } + + return false; + } + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4706) // assignment within conditional expression +#endif + template + bool enqueue_bulk(It itemFirst, size_t count) + { + // First, we need to make sure we have enough room to enqueue all of the elements; + // this means pre-allocating blocks and putting them in the block index (but only if + // all the allocations succeeded). + + // Note that the tailBlock we start off with may not be owned by us any more; + // this happens if it was filled up exactly to the top (setting tailIndex to + // the first index of the next block which is not yet allocated), then dequeued + // completely (putting it on the free list) before we enqueue again. + + index_t startTailIndex = this->tailIndex.load(std::memory_order_relaxed); + auto startBlock = this->tailBlock; + Block* firstAllocatedBlock = nullptr; + auto endBlock = this->tailBlock; + + // Figure out how many blocks we'll need to allocate, and do so + size_t blockBaseDiff = ((startTailIndex + count - 1) & ~static_cast(BLOCK_SIZE - 1)) - ((startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1)); + index_t currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); + if (blockBaseDiff > 0) { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + do { + blockBaseDiff -= static_cast(BLOCK_SIZE); + currentTailIndex += static_cast(BLOCK_SIZE); + + // Find out where we'll be inserting this block in the block index + BlockIndexEntry* idxEntry = nullptr; // initialization here unnecessary but compiler can't always tell + Block* newBlock; + bool indexInserted = false; + auto head = this->headIndex.load(std::memory_order_relaxed); + assert(!details::circular_less_than(currentTailIndex, head)); + bool full = !details::circular_less_than(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head)); + + if (full || !(indexInserted = insert_block_index_entry(idxEntry, currentTailIndex)) || (newBlock = this->parent->ConcurrentQueue::template requisition_block()) == nullptr) { + // Index allocation or block allocation failed; revert any other allocations + // and index insertions done so far for this operation + if (indexInserted) { + rewind_block_index_tail(); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + } + currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); + for (auto block = firstAllocatedBlock; block != nullptr; block = block->next) { + currentTailIndex += static_cast(BLOCK_SIZE); + idxEntry = get_block_index_entry_for_index(currentTailIndex); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + rewind_block_index_tail(); + } + this->parent->add_blocks_to_free_list(firstAllocatedBlock); + this->tailBlock = startBlock; + + return false; + } + +#ifdef MCDBGQ_TRACKMEM + newBlock->owner = this; +#endif + newBlock->ConcurrentQueue::Block::template reset_empty(); + newBlock->next = nullptr; + + // Insert the new block into the index + idxEntry->value.store(newBlock, std::memory_order_relaxed); + + // Store the chain of blocks so that we can undo if later allocations fail, + // and so that we can find the blocks when we do the actual enqueueing + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) != 0 || firstAllocatedBlock != nullptr) { + assert(this->tailBlock != nullptr); + this->tailBlock->next = newBlock; + } + this->tailBlock = newBlock; + endBlock = newBlock; + firstAllocatedBlock = firstAllocatedBlock == nullptr ? newBlock : firstAllocatedBlock; + } while (blockBaseDiff > 0); + } + + // Enqueue, one block at a time + index_t newTailIndex = startTailIndex + static_cast(count); + currentTailIndex = startTailIndex; + this->tailBlock = startBlock; + assert((startTailIndex & static_cast(BLOCK_SIZE - 1)) != 0 || firstAllocatedBlock != nullptr || count == 0); + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0 && firstAllocatedBlock != nullptr) { + this->tailBlock = firstAllocatedBlock; + } + while (true) { + index_t stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + if (details::circular_less_than(newTailIndex, stopIndex)) { + stopIndex = newTailIndex; + } + MOODYCAMEL_CONSTEXPR_IF (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (static_cast(nullptr)) T(details::deref_noexcept(itemFirst)))) { + while (currentTailIndex != stopIndex) { + new ((*this->tailBlock)[currentTailIndex++]) T(*itemFirst++); + } + } + else { + MOODYCAMEL_TRY { + while (currentTailIndex != stopIndex) { + new ((*this->tailBlock)[currentTailIndex]) T(details::nomove_if(nullptr)) T(details::deref_noexcept(itemFirst)))>::eval(*itemFirst)); + ++currentTailIndex; + ++itemFirst; + } + } + MOODYCAMEL_CATCH (...) { + auto constructedStopIndex = currentTailIndex; + auto lastBlockEnqueued = this->tailBlock; + + if (!details::is_trivially_destructible::value) { + auto block = startBlock; + if ((startTailIndex & static_cast(BLOCK_SIZE - 1)) == 0) { + block = firstAllocatedBlock; + } + currentTailIndex = startTailIndex; + while (true) { + stopIndex = (currentTailIndex & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + if (details::circular_less_than(constructedStopIndex, stopIndex)) { + stopIndex = constructedStopIndex; + } + while (currentTailIndex != stopIndex) { + (*block)[currentTailIndex++]->~T(); + } + if (block == lastBlockEnqueued) { + break; + } + block = block->next; + } + } + + currentTailIndex = (startTailIndex - 1) & ~static_cast(BLOCK_SIZE - 1); + for (auto block = firstAllocatedBlock; block != nullptr; block = block->next) { + currentTailIndex += static_cast(BLOCK_SIZE); + auto idxEntry = get_block_index_entry_for_index(currentTailIndex); + idxEntry->value.store(nullptr, std::memory_order_relaxed); + rewind_block_index_tail(); + } + this->parent->add_blocks_to_free_list(firstAllocatedBlock); + this->tailBlock = startBlock; + MOODYCAMEL_RETHROW; + } + } + + if (this->tailBlock == endBlock) { + assert(currentTailIndex == newTailIndex); + break; + } + this->tailBlock = this->tailBlock->next; + } + this->tailIndex.store(newTailIndex, std::memory_order_release); + return true; + } +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + template + size_t dequeue_bulk(It& itemFirst, size_t max) + { + auto tail = this->tailIndex.load(std::memory_order_relaxed); + auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed); + auto desiredCount = static_cast(tail - (this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit)); + if (details::circular_less_than(0, desiredCount)) { + desiredCount = desiredCount < max ? desiredCount : max; + std::atomic_thread_fence(std::memory_order_acquire); + + auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(desiredCount, std::memory_order_relaxed); + + tail = this->tailIndex.load(std::memory_order_acquire); + auto actualCount = static_cast(tail - (myDequeueCount - overcommit)); + if (details::circular_less_than(0, actualCount)) { + actualCount = desiredCount < actualCount ? desiredCount : actualCount; + if (actualCount < desiredCount) { + this->dequeueOvercommit.fetch_add(desiredCount - actualCount, std::memory_order_release); + } + + // Get the first index. Note that since there's guaranteed to be at least actualCount elements, this + // will never exceed tail. + auto firstIndex = this->headIndex.fetch_add(actualCount, std::memory_order_acq_rel); + + // Iterate the blocks and dequeue + auto index = firstIndex; + BlockIndexHeader* localBlockIndex; + auto indexIndex = get_block_index_index_for_index(index, localBlockIndex); + do { + auto blockStartIndex = index; + index_t endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; + + auto entry = localBlockIndex->index[indexIndex]; + auto block = entry->value.load(std::memory_order_relaxed); + if (MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, details::deref_noexcept(itemFirst) = std::move((*(*block)[index])))) { + while (index != endIndex) { + auto& el = *((*block)[index]); + *itemFirst++ = std::move(el); + el.~T(); + ++index; + } + } + else { + MOODYCAMEL_TRY { + while (index != endIndex) { + auto& el = *((*block)[index]); + *itemFirst = std::move(el); + ++itemFirst; + el.~T(); + ++index; + } + } + MOODYCAMEL_CATCH (...) { + do { + entry = localBlockIndex->index[indexIndex]; + block = entry->value.load(std::memory_order_relaxed); + while (index != endIndex) { + (*block)[index++]->~T(); + } + + if (block->ConcurrentQueue::Block::template set_many_empty(blockStartIndex, static_cast(endIndex - blockStartIndex))) { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + entry->value.store(nullptr, std::memory_order_relaxed); + this->parent->add_block_to_free_list(block); + } + indexIndex = (indexIndex + 1) & (localBlockIndex->capacity - 1); + + blockStartIndex = index; + endIndex = (index & ~static_cast(BLOCK_SIZE - 1)) + static_cast(BLOCK_SIZE); + endIndex = details::circular_less_than(firstIndex + static_cast(actualCount), endIndex) ? firstIndex + static_cast(actualCount) : endIndex; + } while (index != firstIndex + actualCount); + + MOODYCAMEL_RETHROW; + } + } + if (block->ConcurrentQueue::Block::template set_many_empty(blockStartIndex, static_cast(endIndex - blockStartIndex))) { + { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + // Note that the set_many_empty above did a release, meaning that anybody who acquires the block + // we're about to free can use it safely since our writes (and reads!) will have happened-before then. + entry->value.store(nullptr, std::memory_order_relaxed); + } + this->parent->add_block_to_free_list(block); // releases the above store + } + indexIndex = (indexIndex + 1) & (localBlockIndex->capacity - 1); + } while (index != firstIndex + actualCount); + + return actualCount; + } + else { + this->dequeueOvercommit.fetch_add(desiredCount, std::memory_order_release); + } + } + + return 0; + } + + private: + // The block size must be > 1, so any number with the low bit set is an invalid block base index + static const index_t INVALID_BLOCK_BASE = 1; + + struct BlockIndexEntry + { + std::atomic key; + std::atomic value; + }; + + struct BlockIndexHeader + { + size_t capacity; + std::atomic tail; + BlockIndexEntry* entries; + BlockIndexEntry** index; + BlockIndexHeader* prev; + }; + + template + inline bool insert_block_index_entry(BlockIndexEntry*& idxEntry, index_t blockStartIndex) + { + auto localBlockIndex = blockIndex.load(std::memory_order_relaxed); // We're the only writer thread, relaxed is OK + if (localBlockIndex == nullptr) { + return false; // this can happen if new_block_index failed in the constructor + } + size_t newTail = (localBlockIndex->tail.load(std::memory_order_relaxed) + 1) & (localBlockIndex->capacity - 1); + idxEntry = localBlockIndex->index[newTail]; + if (idxEntry->key.load(std::memory_order_relaxed) == INVALID_BLOCK_BASE || + idxEntry->value.load(std::memory_order_relaxed) == nullptr) { + + idxEntry->key.store(blockStartIndex, std::memory_order_relaxed); + localBlockIndex->tail.store(newTail, std::memory_order_release); + return true; + } + + // No room in the old block index, try to allocate another one! + MOODYCAMEL_CONSTEXPR_IF (allocMode == CannotAlloc) { + return false; + } + else if (!new_block_index()) { + return false; + } + else { + localBlockIndex = blockIndex.load(std::memory_order_relaxed); + newTail = (localBlockIndex->tail.load(std::memory_order_relaxed) + 1) & (localBlockIndex->capacity - 1); + idxEntry = localBlockIndex->index[newTail]; + assert(idxEntry->key.load(std::memory_order_relaxed) == INVALID_BLOCK_BASE); + idxEntry->key.store(blockStartIndex, std::memory_order_relaxed); + localBlockIndex->tail.store(newTail, std::memory_order_release); + return true; + } + } + + inline void rewind_block_index_tail() + { + auto localBlockIndex = blockIndex.load(std::memory_order_relaxed); + localBlockIndex->tail.store((localBlockIndex->tail.load(std::memory_order_relaxed) - 1) & (localBlockIndex->capacity - 1), std::memory_order_relaxed); + } + + inline BlockIndexEntry* get_block_index_entry_for_index(index_t index) const + { + BlockIndexHeader* localBlockIndex; + auto idx = get_block_index_index_for_index(index, localBlockIndex); + return localBlockIndex->index[idx]; + } + + inline size_t get_block_index_index_for_index(index_t index, BlockIndexHeader*& localBlockIndex) const + { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + debug::DebugLock lock(mutex); +#endif + index &= ~static_cast(BLOCK_SIZE - 1); + localBlockIndex = blockIndex.load(std::memory_order_acquire); + auto tail = localBlockIndex->tail.load(std::memory_order_acquire); + auto tailBase = localBlockIndex->index[tail]->key.load(std::memory_order_relaxed); + assert(tailBase != INVALID_BLOCK_BASE); + // Note: Must use division instead of shift because the index may wrap around, causing a negative + // offset, whose negativity we want to preserve + auto offset = static_cast(static_cast::type>(index - tailBase) / static_cast::type>(BLOCK_SIZE)); + size_t idx = (tail + offset) & (localBlockIndex->capacity - 1); + assert(localBlockIndex->index[idx]->key.load(std::memory_order_relaxed) == index && localBlockIndex->index[idx]->value.load(std::memory_order_relaxed) != nullptr); + return idx; + } + + bool new_block_index() + { + auto prev = blockIndex.load(std::memory_order_relaxed); + size_t prevCapacity = prev == nullptr ? 0 : prev->capacity; + auto entryCount = prev == nullptr ? nextBlockIndexCapacity : prevCapacity; + auto raw = static_cast((Traits::malloc)( + sizeof(BlockIndexHeader) + + std::alignment_of::value - 1 + sizeof(BlockIndexEntry) * entryCount + + std::alignment_of::value - 1 + sizeof(BlockIndexEntry*) * nextBlockIndexCapacity)); + if (raw == nullptr) { + return false; + } + + auto header = new (raw) BlockIndexHeader; + auto entries = reinterpret_cast(details::align_for(raw + sizeof(BlockIndexHeader))); + auto index = reinterpret_cast(details::align_for(reinterpret_cast(entries) + sizeof(BlockIndexEntry) * entryCount)); + if (prev != nullptr) { + auto prevTail = prev->tail.load(std::memory_order_relaxed); + auto prevPos = prevTail; + size_t i = 0; + do { + prevPos = (prevPos + 1) & (prev->capacity - 1); + index[i++] = prev->index[prevPos]; + } while (prevPos != prevTail); + assert(i == prevCapacity); + } + for (size_t i = 0; i != entryCount; ++i) { + new (entries + i) BlockIndexEntry; + entries[i].key.store(INVALID_BLOCK_BASE, std::memory_order_relaxed); + index[prevCapacity + i] = entries + i; + } + header->prev = prev; + header->entries = entries; + header->index = index; + header->capacity = nextBlockIndexCapacity; + header->tail.store((prevCapacity - 1) & (nextBlockIndexCapacity - 1), std::memory_order_relaxed); + + blockIndex.store(header, std::memory_order_release); + + nextBlockIndexCapacity <<= 1; + + return true; + } + + private: + size_t nextBlockIndexCapacity; + std::atomic blockIndex; + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + public: + details::ThreadExitListener threadExitListener; + private: +#endif + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + public: + ImplicitProducer* nextImplicitProducer; + private: +#endif + +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX + mutable debug::DebugMutex mutex; +#endif +#ifdef MCDBGQ_TRACKMEM + friend struct MemStats; +#endif + }; + + + ////////////////////////////////// + // Block pool manipulation + ////////////////////////////////// + + void populate_initial_block_list(size_t blockCount) + { + initialBlockPoolSize = blockCount; + if (initialBlockPoolSize == 0) { + initialBlockPool = nullptr; + return; + } + + initialBlockPool = create_array(blockCount); + if (initialBlockPool == nullptr) { + initialBlockPoolSize = 0; + } + for (size_t i = 0; i < initialBlockPoolSize; ++i) { + initialBlockPool[i].dynamicallyAllocated = false; + } + } + + inline Block* try_get_block_from_initial_pool() + { + if (initialBlockPoolIndex.load(std::memory_order_relaxed) >= initialBlockPoolSize) { + return nullptr; + } + + auto index = initialBlockPoolIndex.fetch_add(1, std::memory_order_relaxed); + + return index < initialBlockPoolSize ? (initialBlockPool + index) : nullptr; + } + + inline void add_block_to_free_list(Block* block) + { +#ifdef MCDBGQ_TRACKMEM + block->owner = nullptr; +#endif + if (!Traits::RECYCLE_ALLOCATED_BLOCKS && block->dynamicallyAllocated) { + destroy(block); + } + else { + freeList.add(block); + } + } + + inline void add_blocks_to_free_list(Block* block) + { + while (block != nullptr) { + auto next = block->next; + add_block_to_free_list(block); + block = next; + } + } + + inline Block* try_get_block_from_free_list() + { + return freeList.try_get(); + } + + // Gets a free block from one of the memory pools, or allocates a new one (if applicable) + template + Block* requisition_block() + { + auto block = try_get_block_from_initial_pool(); + if (block != nullptr) { + return block; + } + + block = try_get_block_from_free_list(); + if (block != nullptr) { + return block; + } + + MOODYCAMEL_CONSTEXPR_IF (canAlloc == CanAlloc) { + return create(); + } + else { + return nullptr; + } + } + + +#ifdef MCDBGQ_TRACKMEM + public: + struct MemStats { + size_t allocatedBlocks; + size_t usedBlocks; + size_t freeBlocks; + size_t ownedBlocksExplicit; + size_t ownedBlocksImplicit; + size_t implicitProducers; + size_t explicitProducers; + size_t elementsEnqueued; + size_t blockClassBytes; + size_t queueClassBytes; + size_t implicitBlockIndexBytes; + size_t explicitBlockIndexBytes; + + friend class ConcurrentQueue; + + private: + static MemStats getFor(ConcurrentQueue* q) + { + MemStats stats = { 0 }; + + stats.elementsEnqueued = q->size_approx(); + + auto block = q->freeList.head_unsafe(); + while (block != nullptr) { + ++stats.allocatedBlocks; + ++stats.freeBlocks; + block = block->freeListNext.load(std::memory_order_relaxed); + } + + for (auto ptr = q->producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + bool implicit = dynamic_cast(ptr) != nullptr; + stats.implicitProducers += implicit ? 1 : 0; + stats.explicitProducers += implicit ? 0 : 1; + + if (implicit) { + auto prod = static_cast(ptr); + stats.queueClassBytes += sizeof(ImplicitProducer); + auto head = prod->headIndex.load(std::memory_order_relaxed); + auto tail = prod->tailIndex.load(std::memory_order_relaxed); + auto hash = prod->blockIndex.load(std::memory_order_relaxed); + if (hash != nullptr) { + for (size_t i = 0; i != hash->capacity; ++i) { + if (hash->index[i]->key.load(std::memory_order_relaxed) != ImplicitProducer::INVALID_BLOCK_BASE && hash->index[i]->value.load(std::memory_order_relaxed) != nullptr) { + ++stats.allocatedBlocks; + ++stats.ownedBlocksImplicit; + } + } + stats.implicitBlockIndexBytes += hash->capacity * sizeof(typename ImplicitProducer::BlockIndexEntry); + for (; hash != nullptr; hash = hash->prev) { + stats.implicitBlockIndexBytes += sizeof(typename ImplicitProducer::BlockIndexHeader) + hash->capacity * sizeof(typename ImplicitProducer::BlockIndexEntry*); + } + } + for (; details::circular_less_than(head, tail); head += BLOCK_SIZE) { + //auto block = prod->get_block_index_entry_for_index(head); + ++stats.usedBlocks; + } + } + else { + auto prod = static_cast(ptr); + stats.queueClassBytes += sizeof(ExplicitProducer); + auto tailBlock = prod->tailBlock; + bool wasNonEmpty = false; + if (tailBlock != nullptr) { + auto block = tailBlock; + do { + ++stats.allocatedBlocks; + if (!block->ConcurrentQueue::Block::template is_empty() || wasNonEmpty) { + ++stats.usedBlocks; + wasNonEmpty = wasNonEmpty || block != tailBlock; + } + ++stats.ownedBlocksExplicit; + block = block->next; + } while (block != tailBlock); + } + auto index = prod->blockIndex.load(std::memory_order_relaxed); + while (index != nullptr) { + stats.explicitBlockIndexBytes += sizeof(typename ExplicitProducer::BlockIndexHeader) + index->size * sizeof(typename ExplicitProducer::BlockIndexEntry); + index = static_cast(index->prev); + } + } + } + + auto freeOnInitialPool = q->initialBlockPoolIndex.load(std::memory_order_relaxed) >= q->initialBlockPoolSize ? 0 : q->initialBlockPoolSize - q->initialBlockPoolIndex.load(std::memory_order_relaxed); + stats.allocatedBlocks += freeOnInitialPool; + stats.freeBlocks += freeOnInitialPool; + + stats.blockClassBytes = sizeof(Block) * stats.allocatedBlocks; + stats.queueClassBytes += sizeof(ConcurrentQueue); + + return stats; + } + }; + + // For debugging only. Not thread-safe. + MemStats getMemStats() + { + return MemStats::getFor(this); + } + private: + friend struct MemStats; +#endif + + + ////////////////////////////////// + // Producer list manipulation + ////////////////////////////////// + + ProducerBase* recycle_or_create_producer(bool isExplicit) + { +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH + debug::DebugLock lock(implicitProdMutex); +#endif + // Try to re-use one first + for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) { + if (ptr->inactive.load(std::memory_order_relaxed) && ptr->isExplicit == isExplicit) { + bool expected = true; + if (ptr->inactive.compare_exchange_strong(expected, /* desired */ false, std::memory_order_acquire, std::memory_order_relaxed)) { + // We caught one! It's been marked as activated, the caller can have it + return ptr; + } + } + } + + return add_producer(isExplicit ? static_cast(create(this)) : create(this)); + } + + ProducerBase* add_producer(ProducerBase* producer) + { + // Handle failed memory allocation + if (producer == nullptr) { + return nullptr; + } + + producerCount.fetch_add(1, std::memory_order_relaxed); + + // Add it to the lock-free list + auto prevTail = producerListTail.load(std::memory_order_relaxed); + do { + producer->next = prevTail; + } while (!producerListTail.compare_exchange_weak(prevTail, producer, std::memory_order_release, std::memory_order_relaxed)); + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + if (producer->isExplicit) { + auto prevTailExplicit = explicitProducers.load(std::memory_order_relaxed); + do { + static_cast(producer)->nextExplicitProducer = prevTailExplicit; + } while (!explicitProducers.compare_exchange_weak(prevTailExplicit, static_cast(producer), std::memory_order_release, std::memory_order_relaxed)); + } + else { + auto prevTailImplicit = implicitProducers.load(std::memory_order_relaxed); + do { + static_cast(producer)->nextImplicitProducer = prevTailImplicit; + } while (!implicitProducers.compare_exchange_weak(prevTailImplicit, static_cast(producer), std::memory_order_release, std::memory_order_relaxed)); + } +#endif + + return producer; + } + + void reown_producers() + { + // After another instance is moved-into/swapped-with this one, all the + // producers we stole still think their parents are the other queue. + // So fix them up! + for (auto ptr = producerListTail.load(std::memory_order_relaxed); ptr != nullptr; ptr = ptr->next_prod()) { + ptr->parent = this; + } + } + + + ////////////////////////////////// + // Implicit producer hash + ////////////////////////////////// + + struct ImplicitProducerKVP + { + std::atomic key; + ImplicitProducer* value; // No need for atomicity since it's only read by the thread that sets it in the first place + + ImplicitProducerKVP() : value(nullptr) { } + + ImplicitProducerKVP(ImplicitProducerKVP&& other) MOODYCAMEL_NOEXCEPT + { + key.store(other.key.load(std::memory_order_relaxed), std::memory_order_relaxed); + value = other.value; + } + + inline ImplicitProducerKVP& operator=(ImplicitProducerKVP&& other) MOODYCAMEL_NOEXCEPT + { + swap(other); + return *this; + } + + inline void swap(ImplicitProducerKVP& other) MOODYCAMEL_NOEXCEPT + { + if (this != &other) { + details::swap_relaxed(key, other.key); + std::swap(value, other.value); + } + } + }; + + template + friend void moodycamel::swap(typename ConcurrentQueue::ImplicitProducerKVP&, typename ConcurrentQueue::ImplicitProducerKVP&) MOODYCAMEL_NOEXCEPT; + + struct ImplicitProducerHash + { + size_t capacity; + ImplicitProducerKVP* entries; + ImplicitProducerHash* prev; + }; + + inline void populate_initial_implicit_producer_hash() + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) { + return; + } + else { + implicitProducerHashCount.store(0, std::memory_order_relaxed); + auto hash = &initialImplicitProducerHash; + hash->capacity = INITIAL_IMPLICIT_PRODUCER_HASH_SIZE; + hash->entries = &initialImplicitProducerHashEntries[0]; + for (size_t i = 0; i != INITIAL_IMPLICIT_PRODUCER_HASH_SIZE; ++i) { + initialImplicitProducerHashEntries[i].key.store(details::invalid_thread_id, std::memory_order_relaxed); + } + hash->prev = nullptr; + implicitProducerHash.store(hash, std::memory_order_relaxed); + } + } + + void swap_implicit_producer_hashes(ConcurrentQueue& other) + { + MOODYCAMEL_CONSTEXPR_IF (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) { + return; + } + else { + // Swap (assumes our implicit producer hash is initialized) + initialImplicitProducerHashEntries.swap(other.initialImplicitProducerHashEntries); + initialImplicitProducerHash.entries = &initialImplicitProducerHashEntries[0]; + other.initialImplicitProducerHash.entries = &other.initialImplicitProducerHashEntries[0]; + + details::swap_relaxed(implicitProducerHashCount, other.implicitProducerHashCount); + + details::swap_relaxed(implicitProducerHash, other.implicitProducerHash); + if (implicitProducerHash.load(std::memory_order_relaxed) == &other.initialImplicitProducerHash) { + implicitProducerHash.store(&initialImplicitProducerHash, std::memory_order_relaxed); + } + else { + ImplicitProducerHash* hash; + for (hash = implicitProducerHash.load(std::memory_order_relaxed); hash->prev != &other.initialImplicitProducerHash; hash = hash->prev) { + continue; + } + hash->prev = &initialImplicitProducerHash; + } + if (other.implicitProducerHash.load(std::memory_order_relaxed) == &initialImplicitProducerHash) { + other.implicitProducerHash.store(&other.initialImplicitProducerHash, std::memory_order_relaxed); + } + else { + ImplicitProducerHash* hash; + for (hash = other.implicitProducerHash.load(std::memory_order_relaxed); hash->prev != &initialImplicitProducerHash; hash = hash->prev) { + continue; + } + hash->prev = &other.initialImplicitProducerHash; + } + } + } + + // Only fails (returns nullptr) if memory allocation fails + ImplicitProducer* get_or_add_implicit_producer() + { + // Note that since the data is essentially thread-local (key is thread ID), + // there's a reduced need for fences (memory ordering is already consistent + // for any individual thread), except for the current table itself. + + // Start by looking for the thread ID in the current and all previous hash tables. + // If it's not found, it must not be in there yet, since this same thread would + // have added it previously to one of the tables that we traversed. + + // Code and algorithm adapted from http://preshing.com/20130605/the-worlds-simplest-lock-free-hash-table + +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH + debug::DebugLock lock(implicitProdMutex); +#endif + + auto id = details::thread_id(); + auto hashedId = details::hash_thread_id(id); + + auto mainHash = implicitProducerHash.load(std::memory_order_acquire); + assert(mainHash != nullptr); // silence clang-tidy and MSVC warnings (hash cannot be null) + for (auto hash = mainHash; hash != nullptr; hash = hash->prev) { + // Look for the id in this hash + auto index = hashedId; + while (true) { // Not an infinite loop because at least one slot is free in the hash table + index &= hash->capacity - 1u; + + auto probedKey = hash->entries[index].key.load(std::memory_order_relaxed); + if (probedKey == id) { + // Found it! If we had to search several hashes deep, though, we should lazily add it + // to the current main hash table to avoid the extended search next time. + // Note there's guaranteed to be room in the current hash table since every subsequent + // table implicitly reserves space for all previous tables (there's only one + // implicitProducerHashCount). + auto value = hash->entries[index].value; + if (hash != mainHash) { + index = hashedId; + while (true) { + index &= mainHash->capacity - 1u; + auto empty = details::invalid_thread_id; +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + auto reusable = details::invalid_thread_id2; + if (mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_seq_cst, std::memory_order_relaxed) || + mainHash->entries[index].key.compare_exchange_strong(reusable, id, std::memory_order_seq_cst, std::memory_order_relaxed)) { +#else + if (mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_seq_cst, std::memory_order_relaxed)) { +#endif + mainHash->entries[index].value = value; + break; + } + ++index; + } + } + + return value; + } + if (probedKey == details::invalid_thread_id) { + break; // Not in this hash table + } + ++index; + } + } + + // Insert! + auto newCount = 1 + implicitProducerHashCount.fetch_add(1, std::memory_order_relaxed); + while (true) { + // NOLINTNEXTLINE(clang-analyzer-core.NullDereference) + if (newCount >= (mainHash->capacity >> 1) && !implicitProducerHashResizeInProgress.test_and_set(std::memory_order_acquire)) { + // We've acquired the resize lock, try to allocate a bigger hash table. + // Note the acquire fence synchronizes with the release fence at the end of this block, and hence when + // we reload implicitProducerHash it must be the most recent version (it only gets changed within this + // locked block). + mainHash = implicitProducerHash.load(std::memory_order_acquire); + if (newCount >= (mainHash->capacity >> 1)) { + size_t newCapacity = mainHash->capacity << 1; + while (newCount >= (newCapacity >> 1)) { + newCapacity <<= 1; + } + auto raw = static_cast((Traits::malloc)(sizeof(ImplicitProducerHash) + std::alignment_of::value - 1 + sizeof(ImplicitProducerKVP) * newCapacity)); + if (raw == nullptr) { + // Allocation failed + implicitProducerHashCount.fetch_sub(1, std::memory_order_relaxed); + implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed); + return nullptr; + } + + auto newHash = new (raw) ImplicitProducerHash; + newHash->capacity = static_cast(newCapacity); + newHash->entries = reinterpret_cast(details::align_for(raw + sizeof(ImplicitProducerHash))); + for (size_t i = 0; i != newCapacity; ++i) { + new (newHash->entries + i) ImplicitProducerKVP; + newHash->entries[i].key.store(details::invalid_thread_id, std::memory_order_relaxed); + } + newHash->prev = mainHash; + implicitProducerHash.store(newHash, std::memory_order_release); + implicitProducerHashResizeInProgress.clear(std::memory_order_release); + mainHash = newHash; + } + else { + implicitProducerHashResizeInProgress.clear(std::memory_order_release); + } + } + + // If it's < three-quarters full, add to the old one anyway so that we don't have to wait for the next table + // to finish being allocated by another thread (and if we just finished allocating above, the condition will + // always be true) + if (newCount < (mainHash->capacity >> 1) + (mainHash->capacity >> 2)) { + auto producer = static_cast(recycle_or_create_producer(false)); + if (producer == nullptr) { + implicitProducerHashCount.fetch_sub(1, std::memory_order_relaxed); + return nullptr; + } + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + producer->threadExitListener.callback = &ConcurrentQueue::implicit_producer_thread_exited_callback; + producer->threadExitListener.userData = producer; + details::ThreadExitNotifier::subscribe(&producer->threadExitListener); +#endif + + auto index = hashedId; + while (true) { + index &= mainHash->capacity - 1u; + auto empty = details::invalid_thread_id; +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + auto reusable = details::invalid_thread_id2; + if (mainHash->entries[index].key.compare_exchange_strong(reusable, id, std::memory_order_seq_cst, std::memory_order_relaxed)) { + implicitProducerHashCount.fetch_sub(1, std::memory_order_relaxed); // already counted as a used slot + mainHash->entries[index].value = producer; + break; + } +#endif + if (mainHash->entries[index].key.compare_exchange_strong(empty, id, std::memory_order_seq_cst, std::memory_order_relaxed)) { + mainHash->entries[index].value = producer; + break; + } + ++index; + } + return producer; + } + + // Hmm, the old hash is quite full and somebody else is busy allocating a new one. + // We need to wait for the allocating thread to finish (if it succeeds, we add, if not, + // we try to allocate ourselves). + mainHash = implicitProducerHash.load(std::memory_order_acquire); + } + } + +#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED + void implicit_producer_thread_exited(ImplicitProducer* producer) + { + // Remove from hash +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH + debug::DebugLock lock(implicitProdMutex); +#endif + auto hash = implicitProducerHash.load(std::memory_order_acquire); + assert(hash != nullptr); // The thread exit listener is only registered if we were added to a hash in the first place + auto id = details::thread_id(); + auto hashedId = details::hash_thread_id(id); + details::thread_id_t probedKey; + + // We need to traverse all the hashes just in case other threads aren't on the current one yet and are + // trying to add an entry thinking there's a free slot (because they reused a producer) + for (; hash != nullptr; hash = hash->prev) { + auto index = hashedId; + do { + index &= hash->capacity - 1u; + probedKey = id; + if (hash->entries[index].key.compare_exchange_strong(probedKey, details::invalid_thread_id2, std::memory_order_seq_cst, std::memory_order_relaxed)) { + break; + } + ++index; + } while (probedKey != details::invalid_thread_id); // Can happen if the hash has changed but we weren't put back in it yet, or if we weren't added to this hash in the first place + } + + // Mark the queue as being recyclable + producer->inactive.store(true, std::memory_order_release); + } + + static void implicit_producer_thread_exited_callback(void* userData) + { + auto producer = static_cast(userData); + auto queue = producer->parent; + queue->implicit_producer_thread_exited(producer); + } +#endif + + ////////////////////////////////// + // Utility functions + ////////////////////////////////// + + template + static inline void* aligned_malloc(size_t size) + { + MOODYCAMEL_CONSTEXPR_IF (std::alignment_of::value <= std::alignment_of::value) + return (Traits::malloc)(size); + else { + size_t alignment = std::alignment_of::value; + void* raw = (Traits::malloc)(size + alignment - 1 + sizeof(void*)); + if (!raw) + return nullptr; + char* ptr = details::align_for(reinterpret_cast(raw) + sizeof(void*)); + *(reinterpret_cast(ptr) - 1) = raw; + return ptr; + } + } + + template + static inline void aligned_free(void* ptr) + { + MOODYCAMEL_CONSTEXPR_IF (std::alignment_of::value <= std::alignment_of::value) + return (Traits::free)(ptr); + else + (Traits::free)(ptr ? *(reinterpret_cast(ptr) - 1) : nullptr); + } + + template + static inline U* create_array(size_t count) + { + assert(count > 0); + U* p = static_cast(aligned_malloc(sizeof(U) * count)); + if (p == nullptr) + return nullptr; + + for (size_t i = 0; i != count; ++i) + new (p + i) U(); + return p; + } + + template + static inline void destroy_array(U* p, size_t count) + { + if (p != nullptr) { + assert(count > 0); + for (size_t i = count; i != 0; ) + (p + --i)->~U(); + } + aligned_free(p); + } + + template + static inline U* create() + { + void* p = aligned_malloc(sizeof(U)); + return p != nullptr ? new (p) U : nullptr; + } + + template + static inline U* create(A1&& a1) + { + void* p = aligned_malloc(sizeof(U)); + return p != nullptr ? new (p) U(std::forward(a1)) : nullptr; + } + + template + static inline void destroy(U* p) + { + if (p != nullptr) + p->~U(); + aligned_free(p); + } + +private: + std::atomic producerListTail; + std::atomic producerCount; + + std::atomic initialBlockPoolIndex; + Block* initialBlockPool; + size_t initialBlockPoolSize; + +#ifndef MCDBGQ_USEDEBUGFREELIST + FreeList freeList; +#else + debug::DebugFreeList freeList; +#endif + + std::atomic implicitProducerHash; + std::atomic implicitProducerHashCount; // Number of slots logically used + ImplicitProducerHash initialImplicitProducerHash; + std::array initialImplicitProducerHashEntries; + std::atomic_flag implicitProducerHashResizeInProgress; + + std::atomic nextExplicitConsumerId; + std::atomic globalExplicitConsumerOffset; + +#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH + debug::DebugMutex implicitProdMutex; +#endif + +#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG + std::atomic explicitProducers; + std::atomic implicitProducers; +#endif +}; + + +template +ProducerToken::ProducerToken(ConcurrentQueue& queue) + : producer(queue.recycle_or_create_producer(true)) +{ + if (producer != nullptr) { + producer->token = this; + } +} + +template +ProducerToken::ProducerToken(BlockingConcurrentQueue& queue) + : producer(reinterpret_cast*>(&queue)->recycle_or_create_producer(true)) +{ + if (producer != nullptr) { + producer->token = this; + } +} + +template +ConsumerToken::ConsumerToken(ConcurrentQueue& queue) + : itemsConsumedFromCurrent(0), currentProducer(nullptr), desiredProducer(nullptr) +{ + initialOffset = queue.nextExplicitConsumerId.fetch_add(1, std::memory_order_release); + lastKnownGlobalOffset = static_cast(-1); +} + +template +ConsumerToken::ConsumerToken(BlockingConcurrentQueue& queue) + : itemsConsumedFromCurrent(0), currentProducer(nullptr), desiredProducer(nullptr) +{ + initialOffset = reinterpret_cast*>(&queue)->nextExplicitConsumerId.fetch_add(1, std::memory_order_release); + lastKnownGlobalOffset = static_cast(-1); +} + +template +inline void swap(ConcurrentQueue& a, ConcurrentQueue& b) MOODYCAMEL_NOEXCEPT +{ + a.swap(b); +} + +inline void swap(ProducerToken& a, ProducerToken& b) MOODYCAMEL_NOEXCEPT +{ + a.swap(b); +} + +inline void swap(ConsumerToken& a, ConsumerToken& b) MOODYCAMEL_NOEXCEPT +{ + a.swap(b); +} + +template +inline void swap(typename ConcurrentQueue::ImplicitProducerKVP& a, typename ConcurrentQueue::ImplicitProducerKVP& b) MOODYCAMEL_NOEXCEPT +{ + a.swap(b); +} + +} + +#if defined(_MSC_VER) && (!defined(_HAS_CXX17) || !_HAS_CXX17) +#pragma warning(pop) +#endif + +#if defined(__GNUC__) && !defined(__INTEL_COMPILER) +#pragma GCC diagnostic pop +#endif diff --git a/thirdparty/concurrentqueue/lightweightsemaphore.h b/thirdparty/concurrentqueue/lightweightsemaphore.h new file mode 100644 index 0000000..41ba094 --- /dev/null +++ b/thirdparty/concurrentqueue/lightweightsemaphore.h @@ -0,0 +1,425 @@ +// Provides an efficient implementation of a semaphore (LightweightSemaphore). +// This is an extension of Jeff Preshing's sempahore implementation (licensed +// under the terms of its separate zlib license) that has been adapted and +// extended by Cameron Desrochers. + +#pragma once + +#include // For std::size_t +#include +#include // For std::make_signed + +#if defined(_WIN32) +// Avoid including windows.h in a header; we only need a handful of +// items, so we'll redeclare them here (this is relatively safe since +// the API generally has to remain stable between Windows versions). +// I know this is an ugly hack but it still beats polluting the global +// namespace with thousands of generic names or adding a .cpp for nothing. +extern "C" { + struct _SECURITY_ATTRIBUTES; + __declspec(dllimport) void* __stdcall CreateSemaphoreW(_SECURITY_ATTRIBUTES* lpSemaphoreAttributes, long lInitialCount, long lMaximumCount, const wchar_t* lpName); + __declspec(dllimport) int __stdcall CloseHandle(void* hObject); + __declspec(dllimport) unsigned long __stdcall WaitForSingleObject(void* hHandle, unsigned long dwMilliseconds); + __declspec(dllimport) int __stdcall ReleaseSemaphore(void* hSemaphore, long lReleaseCount, long* lpPreviousCount); +} +#elif defined(__MACH__) +#include +#elif defined(__unix__) +#include + +#if defined(__GLIBC_PREREQ) && defined(_GNU_SOURCE) +#if __GLIBC_PREREQ(2,30) +#define MOODYCAMEL_LIGHTWEIGHTSEMAPHORE_MONOTONIC +#endif +#endif +#endif + +namespace moodycamel +{ +namespace details +{ + +// Code in the mpmc_sema namespace below is an adaptation of Jeff Preshing's +// portable + lightweight semaphore implementations, originally from +// https://github.com/preshing/cpp11-on-multicore/blob/master/common/sema.h +// LICENSE: +// Copyright (c) 2015 Jeff Preshing +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgement in the product documentation would be +// appreciated but is not required. +// 2. Altered source versions must be plainly marked as such, and must not be +// misrepresented as being the original software. +// 3. This notice may not be removed or altered from any source distribution. +#if defined(_WIN32) +class Semaphore +{ +private: + void* m_hSema; + + Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; + Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; + +public: + Semaphore(int initialCount = 0) + { + assert(initialCount >= 0); + const long maxLong = 0x7fffffff; + m_hSema = CreateSemaphoreW(nullptr, initialCount, maxLong, nullptr); + assert(m_hSema); + } + + ~Semaphore() + { + CloseHandle(m_hSema); + } + + bool wait() + { + const unsigned long infinite = 0xffffffff; + return WaitForSingleObject(m_hSema, infinite) == 0; + } + + bool try_wait() + { + return WaitForSingleObject(m_hSema, 0) == 0; + } + + bool timed_wait(std::uint64_t usecs) + { + return WaitForSingleObject(m_hSema, (unsigned long)(usecs / 1000)) == 0; + } + + void signal(int count = 1) + { + while (!ReleaseSemaphore(m_hSema, count, nullptr)); + } +}; +#elif defined(__MACH__) +//--------------------------------------------------------- +// Semaphore (Apple iOS and OSX) +// Can't use POSIX semaphores due to http://lists.apple.com/archives/darwin-kernel/2009/Apr/msg00010.html +//--------------------------------------------------------- +class Semaphore +{ +private: + semaphore_t m_sema; + + Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; + Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; + +public: + Semaphore(int initialCount = 0) + { + assert(initialCount >= 0); + kern_return_t rc = semaphore_create(mach_task_self(), &m_sema, SYNC_POLICY_FIFO, initialCount); + assert(rc == KERN_SUCCESS); + (void)rc; + } + + ~Semaphore() + { + semaphore_destroy(mach_task_self(), m_sema); + } + + bool wait() + { + return semaphore_wait(m_sema) == KERN_SUCCESS; + } + + bool try_wait() + { + return timed_wait(0); + } + + bool timed_wait(std::uint64_t timeout_usecs) + { + mach_timespec_t ts; + ts.tv_sec = static_cast(timeout_usecs / 1000000); + ts.tv_nsec = static_cast((timeout_usecs % 1000000) * 1000); + + // added in OSX 10.10: https://developer.apple.com/library/prerelease/mac/documentation/General/Reference/APIDiffsMacOSX10_10SeedDiff/modules/Darwin.html + kern_return_t rc = semaphore_timedwait(m_sema, ts); + return rc == KERN_SUCCESS; + } + + void signal() + { + while (semaphore_signal(m_sema) != KERN_SUCCESS); + } + + void signal(int count) + { + while (count-- > 0) + { + while (semaphore_signal(m_sema) != KERN_SUCCESS); + } + } +}; +#elif defined(__unix__) +//--------------------------------------------------------- +// Semaphore (POSIX, Linux) +//--------------------------------------------------------- +class Semaphore +{ +private: + sem_t m_sema; + + Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; + Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION; + +public: + Semaphore(int initialCount = 0) + { + assert(initialCount >= 0); + int rc = sem_init(&m_sema, 0, static_cast(initialCount)); + assert(rc == 0); + (void)rc; + } + + ~Semaphore() + { + sem_destroy(&m_sema); + } + + bool wait() + { + // http://stackoverflow.com/questions/2013181/gdb-causes-sem-wait-to-fail-with-eintr-error + int rc; + do { + rc = sem_wait(&m_sema); + } while (rc == -1 && errno == EINTR); + return rc == 0; + } + + bool try_wait() + { + int rc; + do { + rc = sem_trywait(&m_sema); + } while (rc == -1 && errno == EINTR); + return rc == 0; + } + + bool timed_wait(std::uint64_t usecs) + { + struct timespec ts; + const int usecs_in_1_sec = 1000000; + const int nsecs_in_1_sec = 1000000000; +#ifdef MOODYCAMEL_LIGHTWEIGHTSEMAPHORE_MONOTONIC + clock_gettime(CLOCK_MONOTONIC, &ts); +#else + clock_gettime(CLOCK_REALTIME, &ts); +#endif + ts.tv_sec += (time_t)(usecs / usecs_in_1_sec); + ts.tv_nsec += (long)(usecs % usecs_in_1_sec) * 1000; + // sem_timedwait bombs if you have more than 1e9 in tv_nsec + // so we have to clean things up before passing it in + if (ts.tv_nsec >= nsecs_in_1_sec) { + ts.tv_nsec -= nsecs_in_1_sec; + ++ts.tv_sec; + } + + int rc; + do { +#ifdef MOODYCAMEL_LIGHTWEIGHTSEMAPHORE_MONOTONIC + rc = sem_clockwait(&m_sema, CLOCK_MONOTONIC, &ts); +#else + rc = sem_timedwait(&m_sema, &ts); +#endif + } while (rc == -1 && errno == EINTR); + return rc == 0; + } + + void signal() + { + while (sem_post(&m_sema) == -1); + } + + void signal(int count) + { + while (count-- > 0) + { + while (sem_post(&m_sema) == -1); + } + } +}; +#else +#error Unsupported platform! (No semaphore wrapper available) +#endif + +} // end namespace details + + +//--------------------------------------------------------- +// LightweightSemaphore +//--------------------------------------------------------- +class LightweightSemaphore +{ +public: + typedef std::make_signed::type ssize_t; + +private: + std::atomic m_count; + details::Semaphore m_sema; + int m_maxSpins; + + bool waitWithPartialSpinning(std::int64_t timeout_usecs = -1) + { + ssize_t oldCount; + int spin = m_maxSpins; + while (--spin >= 0) + { + oldCount = m_count.load(std::memory_order_relaxed); + if ((oldCount > 0) && m_count.compare_exchange_strong(oldCount, oldCount - 1, std::memory_order_acquire, std::memory_order_relaxed)) + return true; + std::atomic_signal_fence(std::memory_order_acquire); // Prevent the compiler from collapsing the loop. + } + oldCount = m_count.fetch_sub(1, std::memory_order_acquire); + if (oldCount > 0) + return true; + if (timeout_usecs < 0) + { + if (m_sema.wait()) + return true; + } + if (timeout_usecs > 0 && m_sema.timed_wait((std::uint64_t)timeout_usecs)) + return true; + // At this point, we've timed out waiting for the semaphore, but the + // count is still decremented indicating we may still be waiting on + // it. So we have to re-adjust the count, but only if the semaphore + // wasn't signaled enough times for us too since then. If it was, we + // need to release the semaphore too. + while (true) + { + oldCount = m_count.load(std::memory_order_acquire); + if (oldCount >= 0 && m_sema.try_wait()) + return true; + if (oldCount < 0 && m_count.compare_exchange_strong(oldCount, oldCount + 1, std::memory_order_relaxed, std::memory_order_relaxed)) + return false; + } + } + + ssize_t waitManyWithPartialSpinning(ssize_t max, std::int64_t timeout_usecs = -1) + { + assert(max > 0); + ssize_t oldCount; + int spin = m_maxSpins; + while (--spin >= 0) + { + oldCount = m_count.load(std::memory_order_relaxed); + if (oldCount > 0) + { + ssize_t newCount = oldCount > max ? oldCount - max : 0; + if (m_count.compare_exchange_strong(oldCount, newCount, std::memory_order_acquire, std::memory_order_relaxed)) + return oldCount - newCount; + } + std::atomic_signal_fence(std::memory_order_acquire); + } + oldCount = m_count.fetch_sub(1, std::memory_order_acquire); + if (oldCount <= 0) + { + if ((timeout_usecs == 0) || (timeout_usecs < 0 && !m_sema.wait()) || (timeout_usecs > 0 && !m_sema.timed_wait((std::uint64_t)timeout_usecs))) + { + while (true) + { + oldCount = m_count.load(std::memory_order_acquire); + if (oldCount >= 0 && m_sema.try_wait()) + break; + if (oldCount < 0 && m_count.compare_exchange_strong(oldCount, oldCount + 1, std::memory_order_relaxed, std::memory_order_relaxed)) + return 0; + } + } + } + if (max > 1) + return 1 + tryWaitMany(max - 1); + return 1; + } + +public: + LightweightSemaphore(ssize_t initialCount = 0, int maxSpins = 10000) : m_count(initialCount), m_maxSpins(maxSpins) + { + assert(initialCount >= 0); + assert(maxSpins >= 0); + } + + bool tryWait() + { + ssize_t oldCount = m_count.load(std::memory_order_relaxed); + while (oldCount > 0) + { + if (m_count.compare_exchange_weak(oldCount, oldCount - 1, std::memory_order_acquire, std::memory_order_relaxed)) + return true; + } + return false; + } + + bool wait() + { + return tryWait() || waitWithPartialSpinning(); + } + + bool wait(std::int64_t timeout_usecs) + { + return tryWait() || waitWithPartialSpinning(timeout_usecs); + } + + // Acquires between 0 and (greedily) max, inclusive + ssize_t tryWaitMany(ssize_t max) + { + assert(max >= 0); + ssize_t oldCount = m_count.load(std::memory_order_relaxed); + while (oldCount > 0) + { + ssize_t newCount = oldCount > max ? oldCount - max : 0; + if (m_count.compare_exchange_weak(oldCount, newCount, std::memory_order_acquire, std::memory_order_relaxed)) + return oldCount - newCount; + } + return 0; + } + + // Acquires at least one, and (greedily) at most max + ssize_t waitMany(ssize_t max, std::int64_t timeout_usecs) + { + assert(max >= 0); + ssize_t result = tryWaitMany(max); + if (result == 0 && max > 0) + result = waitManyWithPartialSpinning(max, timeout_usecs); + return result; + } + + ssize_t waitMany(ssize_t max) + { + ssize_t result = waitMany(max, -1); + assert(result > 0); + return result; + } + + void signal(ssize_t count = 1) + { + assert(count >= 0); + ssize_t oldCount = m_count.fetch_add(count, std::memory_order_release); + ssize_t toRelease = -oldCount < count ? -oldCount : count; + if (toRelease > 0) + { + m_sema.signal((int)toRelease); + } + } + + std::size_t availableApprox() const + { + ssize_t count = m_count.load(std::memory_order_relaxed); + return count > 0 ? static_cast(count) : 0; + } +}; + +} // end namespace moodycamel diff --git a/ultramodern/include/config.hpp b/ultramodern/include/config.hpp new file mode 100644 index 0000000..a8b99e2 --- /dev/null +++ b/ultramodern/include/config.hpp @@ -0,0 +1,75 @@ +#ifndef __CONFIG_HPP__ +#define __CONFIG_HPP__ + +// TODO remove this direct dependency on RT64. +#include "common/rt64_user_configuration.h" + +namespace ultramodern { + enum class Resolution { + Original, + Original2x, + Auto, + OptionCount + }; + enum class WindowMode { + Windowed, + Fullscreen, + OptionCount + }; + enum class HUDRatioMode { + Original, + Clamp16x9, + Full, + OptionCount + }; + enum class GraphicsApi { + Auto, + D3D12, + Vulkan, + OptionCount + }; + + struct GraphicsConfig { + Resolution res_option; + WindowMode wm_option; + HUDRatioMode hr_option; + GraphicsApi api_option; + // TODO make custom enums that map to the RT64 ones to remove the direct dependency on RT64 in this file. + RT64::UserConfiguration::AspectRatio ar_option; + RT64::UserConfiguration::Antialiasing msaa_option; + RT64::UserConfiguration::RefreshRate rr_option; + int rr_manual_value; + int ds_option; + bool developer_mode; + + auto operator<=>(const GraphicsConfig& rhs) const = default; + }; + + void set_graphics_config(const GraphicsConfig& config); + GraphicsConfig get_graphics_config(); + + NLOHMANN_JSON_SERIALIZE_ENUM(ultramodern::Resolution, { + {ultramodern::Resolution::Original, "Original"}, + {ultramodern::Resolution::Original2x, "Original2x"}, + {ultramodern::Resolution::Auto, "Auto"}, + }); + + NLOHMANN_JSON_SERIALIZE_ENUM(ultramodern::WindowMode, { + {ultramodern::WindowMode::Windowed, "Windowed"}, + {ultramodern::WindowMode::Fullscreen, "Fullscreen"} + }); + + NLOHMANN_JSON_SERIALIZE_ENUM(ultramodern::HUDRatioMode, { + {ultramodern::HUDRatioMode::Original, "Original"}, + {ultramodern::HUDRatioMode::Clamp16x9, "Clamp16x9"}, + {ultramodern::HUDRatioMode::Full, "Full"}, + }); + + NLOHMANN_JSON_SERIALIZE_ENUM(ultramodern::GraphicsApi, { + {ultramodern::GraphicsApi::Auto, "Auto"}, + {ultramodern::GraphicsApi::D3D12, "D3D12"}, + {ultramodern::GraphicsApi::Vulkan, "Vulkan"}, + }); +}; + +#endif diff --git a/ultramodern/include/rt64_layer.h b/ultramodern/include/rt64_layer.h new file mode 100644 index 0000000..0928d06 --- /dev/null +++ b/ultramodern/include/rt64_layer.h @@ -0,0 +1,48 @@ +#ifndef __RT64_LAYER_H__ +#define __RT64_LAYER_H__ + +#include "ultramodern.hpp" +#include "config.hpp" + +namespace RT64 { + struct Application; +} + +namespace ultramodern { + enum class RT64SetupResult { + Success, + DynamicLibrariesNotFound, + InvalidGraphicsAPI, + GraphicsAPINotFound, + GraphicsDeviceNotFound + }; + + struct WindowHandle; + struct RT64Context { + public: + ~RT64Context(); + RT64Context(uint8_t* rdram, WindowHandle window_handle, bool developer_mode); + bool valid() { return static_cast(app); } + RT64SetupResult get_setup_result() { return setup_result; } + + void update_config(const GraphicsConfig& old_config, const GraphicsConfig& new_config); + void enable_instant_present(); + void send_dl(const OSTask* task); + void update_screen(uint32_t vi_origin); + void shutdown(); + void set_dummy_vi(); + uint32_t get_display_framerate(); + void load_shader_cache(std::span cache_binary); + private: + RT64SetupResult setup_result; + std::unique_ptr app; + }; + + RT64::UserConfiguration::Antialiasing RT64MaxMSAA(); + bool RT64SamplePositionsSupported(); +} + +void set_rt64_hooks(); + +#endif + diff --git a/ultramodern/include/ultra64.h b/ultramodern/include/ultra64.h new file mode 100644 index 0000000..395cadb --- /dev/null +++ b/ultramodern/include/ultra64.h @@ -0,0 +1,262 @@ +#ifndef __ULTRA64_ultramodern_H__ +#define __ULTRA64_ultramodern_H__ + +#include + +#ifdef __GNUC__ +#define UNUSED __attribute__((unused)) +#define ALIGNED(x) __attribute__((aligned(x))) +#else +#define UNUSED +#define ALIGNED(x) +#endif + +typedef int64_t s64; +typedef uint64_t u64; +typedef int32_t s32; +typedef uint32_t u32; +typedef int16_t s16; +typedef uint16_t u16; +typedef int8_t s8; +typedef uint8_t u8; + +// TODO allow a compile-time flag to be set to switch between recomp mode and +// fully native mode. +#if 0 // For native compilation +# define PTR(x) x* +# define RDRAM_ARG +# define RDRAM_ARG1 +# define PASS_RDRAM +# define PASS_RDRAM1 +# define TO_PTR(type, var) var +# define GET_MEMBER(type, addr, member) (&addr->member) +# ifdef __cplusplus +# define NULLPTR nullptr +# endif +#else +# define PTR(x) int32_t +# define RDRAM_ARG uint8_t *rdram, +# define RDRAM_ARG1 uint8_t *rdram +# define PASS_RDRAM rdram, +# define PASS_RDRAM1 rdram +# define TO_PTR(type, var) ((type*)(&rdram[(uint64_t)var - 0xFFFFFFFF80000000])) +# define GET_MEMBER(type, addr, member) (addr + (intptr_t)&(((type*)nullptr)->member)) +# ifdef __cplusplus +# define NULLPTR (PTR(void))0 +# endif +#endif + +#ifndef NULL +#define NULL (PTR(void) 0) +#endif + +#define OS_MESG_NOBLOCK 0 +#define OS_MESG_BLOCK 1 + +typedef s32 OSPri; +typedef s32 OSId; + +typedef u64 OSTime; + +#define OS_EVENT_SW1 0 /* CPU SW1 interrupt */ +#define OS_EVENT_SW2 1 /* CPU SW2 interrupt */ +#define OS_EVENT_CART 2 /* Cartridge interrupt: used by rmon */ +#define OS_EVENT_COUNTER 3 /* Counter int: used by VI/Timer Mgr */ +#define OS_EVENT_SP 4 /* SP task done interrupt */ +#define OS_EVENT_SI 5 /* SI (controller) interrupt */ +#define OS_EVENT_AI 6 /* AI interrupt */ +#define OS_EVENT_VI 7 /* VI interrupt: used by VI/Timer Mgr */ +#define OS_EVENT_PI 8 /* PI interrupt: used by PI Manager */ +#define OS_EVENT_DP 9 /* DP full sync interrupt */ +#define OS_EVENT_CPU_BREAK 10 /* CPU breakpoint: used by rmon */ +#define OS_EVENT_SP_BREAK 11 /* SP breakpoint: used by rmon */ +#define OS_EVENT_FAULT 12 /* CPU fault event: used by rmon */ +#define OS_EVENT_THREADSTATUS 13 /* CPU thread status: used by rmon */ +#define OS_EVENT_PRENMI 14 /* Pre NMI interrupt */ + +#define M_GFXTASK 1 +#define M_AUDTASK 2 +#define M_VIDTASK 3 +#define M_NJPEGTASK 4 + +///////////// +// Structs // +///////////// + +// Threads + +typedef struct UltraThreadContext UltraThreadContext; + +typedef enum { + STOPPED, + QUEUED, + RUNNING, + BLOCKED +} OSThreadState; + +typedef struct OSThread_t { + PTR(struct OSThread_t) next; // Next thread in the given queue + OSPri priority; + PTR(PTR(struct OSThread_t)) queue; // Queue this thread is in, if any + uint32_t pad2; + uint16_t flags; // These two are swapped to reflect rdram byteswapping + uint16_t state; + OSId id; + int32_t pad3; + UltraThreadContext* context; // An actual pointer regardless of platform + int32_t sp; +} OSThread; + +typedef u32 OSEvent; +typedef PTR(void) OSMesg; + +typedef struct OSMesgQueue { + PTR(OSThread) blocked_on_recv; /* Linked list of threads blocked on receiving from this queue */ + PTR(OSThread) blocked_on_send; /* Linked list of threads blocked on sending to this queue */ + s32 validCount; /* Number of messages in the queue */ + s32 first; /* Index of the first message in the ring buffer */ + s32 msgCount; /* Size of message buffer */ + PTR(OSMesg) msg; /* Pointer to circular buffer to store messages */ +} OSMesgQueue; + +// RSP + +typedef struct { + u32 type; + u32 flags; + + PTR(u64) ucode_boot; + u32 ucode_boot_size; + + PTR(u64) ucode; + u32 ucode_size; + + PTR(u64) ucode_data; + u32 ucode_data_size; + + PTR(u64) dram_stack; + u32 dram_stack_size; + + PTR(u64) output_buff; + PTR(u64) output_buff_size; + + PTR(u64) data_ptr; + u32 data_size; + + PTR(u64) yield_data_ptr; + u32 yield_data_size; +} OSTask_s; + +typedef union { + OSTask_s t; + int64_t force_structure_alignment; +} OSTask; + +// PI + +struct OSIoMesgHdr { + // These 3 reversed due to endianness + u8 status; /* Return status */ + u8 pri; /* Message priority (High or Normal) */ + u16 type; /* Message type */ + PTR(OSMesgQueue) retQueue; /* Return message queue to notify I/O completion */ +}; + +struct OSIoMesg { + OSIoMesgHdr hdr; /* Message header */ + PTR(void) dramAddr; /* RDRAM buffer address (DMA) */ + u32 devAddr; /* Device buffer address (DMA) */ + u32 size; /* DMA transfer size in bytes */ + u32 piHandle; /* PI device handle */ +}; + +struct OSPiHandle { + PTR(OSPiHandle_s) unused; /* point to next handle on the table */ + // These four members reversed due to endianness + u8 relDuration; /* domain release duration */ + u8 pageSize; /* domain page size */ + u8 latency; /* domain latency */ + u8 type; /* DEVICE_TYPE_BULK for disk */ + // These three members reversed due to endianness + u16 padding; /* struct alignment padding */ + u8 domain; /* which domain */ + u8 pulse; /* domain pulse width */ + u32 baseAddress; /* Domain address */ + u32 speed; /* for roms only */ + /* The following are "private" elements" */ + u32 transferInfo[18]; /* for disk only */ +}; + +typedef struct { + u32 ctrl; + u32 width; + u32 burst; + u32 vSync; + u32 hSync; + u32 leap; + u32 hStart; + u32 xScale; + u32 vCurrent; +} OSViCommonRegs; + +typedef struct { + u32 origin; + u32 yScale; + u32 vStart; + u32 vBurst; + u32 vIntr; +} OSViFieldRegs; + +typedef struct { + u8 padding[3]; + u8 type; + OSViCommonRegs comRegs; + OSViFieldRegs fldRegs[2]; +} OSViMode; + +/////////////// +// Functions // +/////////////// + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +void osInitialize(void); + +typedef void (thread_func_t)(PTR(void)); + +void osCreateThread(RDRAM_ARG PTR(OSThread) t, OSId id, PTR(thread_func_t) entry, PTR(void) arg, PTR(void) sp, OSPri p); +void osStartThread(RDRAM_ARG PTR(OSThread) t); +void osStopThread(RDRAM_ARG PTR(OSThread) t); +void osDestroyThread(RDRAM_ARG PTR(OSThread) t); +void osYieldThread(RDRAM_ARG1); +void osSetThreadPri(RDRAM_ARG PTR(OSThread) t, OSPri pri); +OSPri osGetThreadPri(RDRAM_ARG PTR(OSThread) thread); +OSId osGetThreadId(RDRAM_ARG PTR(OSThread) t); + +void osCreateMesgQueue(RDRAM_ARG PTR(OSMesgQueue), PTR(OSMesg), s32); +s32 osSendMesg(RDRAM_ARG PTR(OSMesgQueue), OSMesg, s32); +s32 osJamMesg(RDRAM_ARG PTR(OSMesgQueue), OSMesg, s32); +s32 osRecvMesg(RDRAM_ARG PTR(OSMesgQueue), PTR(OSMesg), s32); +void osSetEventMesg(RDRAM_ARG OSEvent, PTR(OSMesgQueue), OSMesg); +void osViSetEvent(RDRAM_ARG PTR(OSMesgQueue), OSMesg, u32); +void osViSwapBuffer(RDRAM_ARG PTR(void) frameBufPtr); +void osViSetMode(RDRAM_ARG PTR(OSViMode)); +void osViSetSpecialFeatures(uint32_t func); +void osViBlack(uint8_t active); +void osViSetXScale(float scale); +void osViSetYScale(float scale); +PTR(void) osViGetNextFramebuffer(); +PTR(void) osViGetCurrentFramebuffer(); +u32 osGetCount(); +OSTime osGetTime(); +int osSetTimer(RDRAM_ARG PTR(OSTimer) timer, OSTime countdown, OSTime interval, PTR(OSMesgQueue) mq, OSMesg msg); +int osStopTimer(RDRAM_ARG PTR(OSTimer) timer); +u32 osVirtualToPhysical(PTR(void) addr); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif diff --git a/ultramodern/include/ultramodern.hpp b/ultramodern/include/ultramodern.hpp new file mode 100644 index 0000000..af2100d --- /dev/null +++ b/ultramodern/include/ultramodern.hpp @@ -0,0 +1,167 @@ +#ifndef __ultramodern_HPP__ +#define __ultramodern_HPP__ + +#include +#include +#include +#include + +#undef MOODYCAMEL_DELETE_FUNCTION +#define MOODYCAMEL_DELETE_FUNCTION = delete +#include "lightweightsemaphore.h" +#include "ultra64.h" + +#if defined(_WIN32) +# define WIN32_LEAN_AND_MEAN +# include +#elif defined(__ANDROID__) +# include "android/native_window.h" +#elif defined(__linux__) +# include "X11/Xlib.h" +# undef None +# undef Status +# undef LockMask +# undef Always +# undef Success +#endif + +struct UltraThreadContext { + std::thread host_thread; + moodycamel::LightweightSemaphore running; + moodycamel::LightweightSemaphore initialized; +}; + +namespace ultramodern { + +#if defined(_WIN32) + // Native HWND handle to the target window. + struct WindowHandle { + HWND window; + DWORD thread_id = (DWORD)-1; + auto operator<=>(const WindowHandle&) const = default; + }; +#elif defined(__ANDROID__) + using WindowHandle = ANativeWindow*; +#elif defined(__linux__) + struct WindowHandle { + Display* display; + Window window; + auto operator<=>(const WindowHandle&) const = default; + }; +#endif + +// We need a place in rdram to hold the PI handles, so pick an address in extended rdram +constexpr uint32_t rdram_size = 1024 * 1024 * 16; // 16MB to give extra room for anything custom +constexpr int32_t cart_handle = 0x80800000; +constexpr int32_t drive_handle = (int32_t)(cart_handle + sizeof(OSPiHandle)); +constexpr int32_t flash_handle = (int32_t)(drive_handle + sizeof(OSPiHandle)); +constexpr uint32_t save_size = 1024 * 1024 / 8; // Maximum save size, 1Mbit for flash + +// Initialization. +void preinit(RDRAM_ARG WindowHandle window_handle); +void init_saving(RDRAM_ARG1); +void init_events(RDRAM_ARG WindowHandle window_handle); +void init_timers(RDRAM_ARG1); +void init_thread_cleanup(); + +// Thread queues. +constexpr PTR(PTR(OSThread)) running_queue = (PTR(PTR(OSThread)))-1; + +void thread_queue_insert(RDRAM_ARG PTR(PTR(OSThread)) queue, PTR(OSThread) toadd); +PTR(OSThread) thread_queue_pop(RDRAM_ARG PTR(PTR(OSThread)) queue); +bool thread_queue_remove(RDRAM_ARG PTR(PTR(OSThread)) queue_, PTR(OSThread) t_); +bool thread_queue_empty(RDRAM_ARG PTR(PTR(OSThread)) queue); +PTR(OSThread) thread_queue_peek(RDRAM_ARG PTR(PTR(OSThread)) queue); + +// Message queues. +void wait_for_external_message(RDRAM_ARG1); +void wait_for_external_message_timed(RDRAM_ARG1, u32 millis); + +// Thread scheduling. +void check_running_queue(RDRAM_ARG1); +void run_next_thread_and_wait(RDRAM_ARG1); +void resume_thread_and_wait(RDRAM_ARG OSThread* t); +void schedule_running_thread(RDRAM_ARG PTR(OSThread) t); +void cleanup_thread(UltraThreadContext* thread_context); +uint32_t permanent_thread_count(); +uint32_t temporary_thread_count(); +struct thread_terminated : std::exception {}; + +enum class ThreadPriority { + Low, + Normal, + High, + VeryHigh, + Critical +}; + +void set_native_thread_name(const std::string& name); +void set_native_thread_priority(ThreadPriority pri); +PTR(OSThread) this_thread(); +void set_main_thread(); +bool is_game_thread(); +void submit_rsp_task(RDRAM_ARG PTR(OSTask) task); +void send_si_message(RDRAM_ARG1); +uint32_t get_speed_multiplier(); + +// Time +std::chrono::high_resolution_clock::time_point get_start(); +std::chrono::high_resolution_clock::duration time_since_start(); +void measure_input_latency(); +void sleep_milliseconds(uint32_t millis); +void sleep_until(const std::chrono::high_resolution_clock::time_point& time_point); + +// Graphics +void get_window_size(uint32_t& width, uint32_t& height); +uint32_t get_target_framerate(uint32_t original); +uint32_t get_display_refresh_rate(); +void load_shader_cache(std::span cache_data); + +// Audio +void init_audio(); +void set_audio_frequency(uint32_t freq); +void queue_audio_buffer(RDRAM_ARG PTR(s16) audio_data, uint32_t byte_count); +uint32_t get_remaining_audio_bytes(); + +struct audio_callbacks_t { + using queue_samples_t = void(int16_t*, size_t); + using get_samples_remaining_t = size_t(); + using set_frequency_t = void(uint32_t); + queue_samples_t* queue_samples; + get_samples_remaining_t* get_frames_remaining; + set_frequency_t* set_frequency; +}; + +// Input +struct input_callbacks_t { + using poll_input_t = void(void); + using get_input_t = void(uint16_t*, float*, float*); + using set_rumble_t = void(bool); + poll_input_t* poll_input; + get_input_t* get_input; + set_rumble_t* set_rumble; +}; + +struct gfx_callbacks_t { + using gfx_data_t = void*; + using create_gfx_t = gfx_data_t(); + using create_window_t = WindowHandle(gfx_data_t); + using update_gfx_t = void(gfx_data_t); + create_gfx_t* create_gfx; + create_window_t* create_window; + update_gfx_t* update_gfx; +}; +bool is_game_started(); +void quit(); +void join_event_threads(); +void join_thread_cleaner_thread(); +void join_saving_thread(); + +} // namespace ultramodern + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +#define debug_printf(...) +//#define debug_printf(...) printf(__VA_ARGS__); + +#endif diff --git a/ultramodern/src/audio.cpp b/ultramodern/src/audio.cpp new file mode 100644 index 0000000..f4f7257 --- /dev/null +++ b/ultramodern/src/audio.cpp @@ -0,0 +1,67 @@ +#include "ultra64.h" +#include "ultramodern.hpp" +#include + +static uint32_t sample_rate = 48000; + +static ultramodern::audio_callbacks_t audio_callbacks; + +void set_audio_callbacks(const ultramodern::audio_callbacks_t& callbacks) { + audio_callbacks = callbacks; +} + +void ultramodern::init_audio() { + // Pick an initial dummy sample rate; this will be set by the game later to the true sample rate. + set_audio_frequency(48000); +} + +void ultramodern::set_audio_frequency(uint32_t freq) { + if (audio_callbacks.set_frequency) { + audio_callbacks.set_frequency(freq); + } + sample_rate = freq; +} + +void ultramodern::queue_audio_buffer(RDRAM_ARG PTR(int16_t) audio_data_, uint32_t byte_count) { + // Ensure that the byte count is an integer multiple of samples. + assert((byte_count & 1) == 0); + + // Calculate the number of samples from the number of bytes. + uint32_t sample_count = byte_count / sizeof(int16_t); + + // Queue the swapped audio data. + if (audio_callbacks.queue_samples) { + audio_callbacks.queue_samples(TO_PTR(int16_t, audio_data_), sample_count); + } +} + +// For SDL2 +//uint32_t buffer_offset_frames = 1; +// For Godot +float buffer_offset_frames = 0.5f; + +// If there's ever any audio popping, check here first. Some games are very sensitive to +// the remaining sample count and reporting a number that's too high here can lead to issues. +// Reporting a number that's too low can lead to audio lag in some games. +uint32_t ultramodern::get_remaining_audio_bytes() { + // Get the number of remaining buffered audio bytes. + uint32_t buffered_byte_count; + if (audio_callbacks.get_frames_remaining != nullptr) { + buffered_byte_count = audio_callbacks.get_frames_remaining() * 2 * sizeof(int16_t); + } + else { + buffered_byte_count = 100; + } + // Adjust the reported count to be some number of refreshes in the future, which helps ensure that + // there are enough samples even if the audio thread experiences a small amount of lag. This prevents + // audio popping on games that use the buffered audio byte count to determine how many samples + // to generate. + uint32_t samples_per_vi = (sample_rate / 60); + if (buffered_byte_count > static_cast(buffer_offset_frames * sizeof(int16_t) * samples_per_vi)) { + buffered_byte_count -= static_cast(buffer_offset_frames * sizeof(int16_t) * samples_per_vi); + } + else { + buffered_byte_count = 0; + } + return buffered_byte_count; +} diff --git a/ultramodern/src/events.cpp b/ultramodern/src/events.cpp new file mode 100644 index 0000000..957d704 --- /dev/null +++ b/ultramodern/src/events.cpp @@ -0,0 +1,614 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "blockingconcurrentqueue.h" + +#include "ultra64.h" +#include "ultramodern.hpp" +#include "config.hpp" +#include "rt64_layer.h" +#include "recomp.h" +#include "recomp_game.h" +#include "recomp_ui.h" +#include "recomp_input.h" +#include "rsp.h" + +struct SpTaskAction { + OSTask task; +}; + +struct SwapBuffersAction { + uint32_t origin; +}; + +struct UpdateConfigAction { +}; + +struct LoadShaderCacheAction { + std::span data; +}; + +using Action = std::variant; + +static struct { + struct { + std::thread thread; + PTR(OSMesgQueue) mq = NULLPTR; + PTR(void) current_buffer = NULLPTR; + PTR(void) next_buffer = NULLPTR; + OSMesg msg = (OSMesg)0; + int retrace_count = 1; + } vi; + struct { + std::thread gfx_thread; + std::thread task_thread; + PTR(OSMesgQueue) mq = NULLPTR; + OSMesg msg = (OSMesg)0; + } sp; + struct { + PTR(OSMesgQueue) mq = NULLPTR; + OSMesg msg = (OSMesg)0; + } dp; + struct { + PTR(OSMesgQueue) mq = NULLPTR; + OSMesg msg = (OSMesg)0; + } ai; + struct { + PTR(OSMesgQueue) mq = NULLPTR; + OSMesg msg = (OSMesg)0; + } si; + // The same message queue may be used for multiple events, so share a mutex for all of them + std::mutex message_mutex; + uint8_t* rdram; + moodycamel::BlockingConcurrentQueue action_queue{}; + moodycamel::BlockingConcurrentQueue sp_task_queue{}; + moodycamel::ConcurrentQueue deleted_threads{}; +} events_context{}; + +extern "C" void osSetEventMesg(RDRAM_ARG OSEvent event_id, PTR(OSMesgQueue) mq_, OSMesg msg) { + OSMesgQueue* mq = TO_PTR(OSMesgQueue, mq_); + std::lock_guard lock{ events_context.message_mutex }; + + switch (event_id) { + case OS_EVENT_SP: + events_context.sp.msg = msg; + events_context.sp.mq = mq_; + break; + case OS_EVENT_DP: + events_context.dp.msg = msg; + events_context.dp.mq = mq_; + break; + case OS_EVENT_AI: + events_context.ai.msg = msg; + events_context.ai.mq = mq_; + break; + case OS_EVENT_SI: + events_context.si.msg = msg; + events_context.si.mq = mq_; + } +} + +extern "C" void osViSetEvent(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, u32 retrace_count) { + std::lock_guard lock{ events_context.message_mutex }; + events_context.vi.mq = mq_; + events_context.vi.msg = msg; + events_context.vi.retrace_count = retrace_count; +} + +uint64_t total_vis = 0; + + +extern std::atomic_bool exited; + +void set_dummy_vi(); + +void vi_thread_func() { + ultramodern::set_native_thread_name("VI Thread"); + // This thread should be prioritized over every other thread in the application, as it's what allows + // the game to generate new audio and gfx lists. + ultramodern::set_native_thread_priority(ultramodern::ThreadPriority::Critical); + using namespace std::chrono_literals; + + int remaining_retraces = events_context.vi.retrace_count; + + while (!exited) { + // Determine the next VI time (more accurate than adding 16ms each VI interrupt) + auto next = ultramodern::get_start() + (total_vis * 1000000us) / (60 * ultramodern::get_speed_multiplier()); + //if (next > std::chrono::high_resolution_clock::now()) { + // printf("Sleeping for %" PRIu64 " us to get from %" PRIu64 " us to %" PRIu64 " us \n", + // (next - std::chrono::high_resolution_clock::now()) / 1us, + // (std::chrono::high_resolution_clock::now() - events_context.start) / 1us, + // (next - events_context.start) / 1us); + //} else { + // printf("No need to sleep\n"); + //} + // Detect if there's more than a second to wait and wait a fixed amount instead for the next VI if so, as that usually means the system clock went back in time. + if (std::chrono::floor(next - std::chrono::high_resolution_clock::now()) > 1s) { + // printf("Skipping the next VI wait\n"); + next = std::chrono::high_resolution_clock::now(); + } + ultramodern::sleep_until(next); + // Calculate how many VIs have passed + uint64_t new_total_vis = (ultramodern::time_since_start() * (60 * ultramodern::get_speed_multiplier()) / 1000ms) + 1; + if (new_total_vis > total_vis + 1) { + //printf("Skipped % " PRId64 " frames in VI interupt thread!\n", new_total_vis - total_vis - 1); + } + total_vis = new_total_vis; + + remaining_retraces--; + + { + std::lock_guard lock{ events_context.message_mutex }; + uint8_t* rdram = events_context.rdram; + if (remaining_retraces == 0) { + remaining_retraces = events_context.vi.retrace_count; + + if (ultramodern::is_game_started()) { + if (events_context.vi.mq != NULLPTR) { + if (osSendMesg(PASS_RDRAM events_context.vi.mq, events_context.vi.msg, OS_MESG_NOBLOCK) == -1) { + //printf("Game skipped a VI frame!\n"); + } + } + } + else { + set_dummy_vi(); + static bool swap = false; + uint32_t vi_origin = 0x400 + 0x280; // Skip initial RDRAM contents and add the usual origin offset + // Offset by one FB every other frame so RT64 continues drawing + if (swap) { + vi_origin += 0x25800; + } + osViSwapBuffer(rdram, vi_origin); + swap = !swap; + } + } + if (events_context.ai.mq != NULLPTR) { + if (osSendMesg(PASS_RDRAM events_context.ai.mq, events_context.ai.msg, OS_MESG_NOBLOCK) == -1) { + //printf("Game skipped a AI frame!\n"); + } + } + } + + // TODO move recomp code out of ultramodern. + recomp::update_rumble(); + } +} + +void sp_complete() { + uint8_t* rdram = events_context.rdram; + std::lock_guard lock{ events_context.message_mutex }; + osSendMesg(PASS_RDRAM events_context.sp.mq, events_context.sp.msg, OS_MESG_NOBLOCK); +} + +void dp_complete() { + uint8_t* rdram = events_context.rdram; + std::lock_guard lock{ events_context.message_mutex }; + osSendMesg(PASS_RDRAM events_context.dp.mq, events_context.dp.msg, OS_MESG_NOBLOCK); +} + +uint8_t dmem[0x1000]; +uint16_t rspReciprocals[512]; +uint16_t rspInverseSquareRoots[512]; + +using RspUcodeFunc = RspExitReason(uint8_t* rdram); +extern RspUcodeFunc njpgdspMain; +extern RspUcodeFunc aspMain; + +// From Ares emulator. For license details, see rsp_vu.h +void rsp_constants_init() { + rspReciprocals[0] = u16(~0); + for (u16 index = 1; index < 512; index++) { + u64 a = index + 512; + u64 b = (u64(1) << 34) / a; + rspReciprocals[index] = u16((b + 1) >> 8); + } + + for (u16 index = 0; index < 512; index++) { + u64 a = (index + 512) >> ((index % 2 == 1) ? 1 : 0); + u64 b = 1 << 17; + //find the largest b where b < 1.0 / sqrt(a) + while (a * (b + 1) * (b + 1) < (u64(1) << 44)) b++; + rspInverseSquareRoots[index] = u16(b >> 1); + } +} + +// Runs a recompiled RSP microcode +void run_rsp_microcode(uint8_t* rdram, const OSTask* task, RspUcodeFunc* ucode_func) { + // Load the OSTask into DMEM + memcpy(&dmem[0xFC0], task, sizeof(OSTask)); + // Load the ucode data into DMEM + dma_rdram_to_dmem(rdram, 0x0000, task->t.ucode_data, 0xF80 - 1); + // Run the ucode + RspExitReason exit_reason = ucode_func(rdram); + // Ensure that the ucode exited correctly + assert(exit_reason == RspExitReason::Broke); +} + + +void task_thread_func(uint8_t* rdram, moodycamel::LightweightSemaphore* thread_ready) { + ultramodern::set_native_thread_name("SP Task Thread"); + ultramodern::set_native_thread_priority(ultramodern::ThreadPriority::Normal); + + // Notify the caller thread that this thread is ready. + thread_ready->signal(); + + while (true) { + // Wait until an RSP task has been sent + OSTask* task; + events_context.sp_task_queue.wait_dequeue(task); + + if (task == nullptr) { + return; + } + + // Run the correct function based on the task type + if (task->t.type == M_AUDTASK) { + run_rsp_microcode(rdram, task, aspMain); + } + else if (task->t.type == M_NJPEGTASK) { + run_rsp_microcode(rdram, task, njpgdspMain); + } + else { + fprintf(stderr, "Unknown task type: %" PRIu32 "\n", task->t.type); + assert(false); + std::quick_exit(EXIT_FAILURE); + } + + // Tell the game that the RSP has completed + sp_complete(); + } +} + +static std::atomic cur_config{}; + +void ultramodern::set_graphics_config(const ultramodern::GraphicsConfig& config) { + cur_config = config; + events_context.action_queue.enqueue(UpdateConfigAction{}); +} + +ultramodern::GraphicsConfig ultramodern::get_graphics_config() { + return cur_config; +} + +std::atomic_uint32_t display_refresh_rate = 60; + +uint32_t ultramodern::get_target_framerate(uint32_t original) { + ultramodern::GraphicsConfig graphics_config = ultramodern::get_graphics_config(); + + switch (graphics_config.rr_option) { + case RT64::UserConfiguration::RefreshRate::Original: + default: + return original; + case RT64::UserConfiguration::RefreshRate::Manual: + return graphics_config.rr_manual_value; + case RT64::UserConfiguration::RefreshRate::Display: + return display_refresh_rate.load(); + } +} + +uint32_t ultramodern::get_display_refresh_rate() { + return display_refresh_rate.load(); +} + +void ultramodern::load_shader_cache(std::span cache_data) { + events_context.action_queue.enqueue(LoadShaderCacheAction{cache_data}); +} + +std::atomic rt64_setup_result = ultramodern::RT64SetupResult::Success; + +void gfx_thread_func(uint8_t* rdram, moodycamel::LightweightSemaphore* thread_ready, ultramodern::WindowHandle window_handle) { + bool enabled_instant_present = false; + using namespace std::chrono_literals; + + ultramodern::set_native_thread_name("Gfx Thread"); + ultramodern::set_native_thread_priority(ultramodern::ThreadPriority::Normal); + + ultramodern::GraphicsConfig old_config = ultramodern::get_graphics_config(); + + ultramodern::RT64Context rt64{rdram, window_handle, cur_config.load().developer_mode}; + + if (!rt64.valid()) { + // TODO move recomp code out of ultramodern. + rt64_setup_result.store(rt64.get_setup_result()); + // Notify the caller thread that this thread is ready. + thread_ready->signal(); + return; + } + + // TODO move recomp code out of ultramodern. + recomp::update_supported_options(); + + rsp_constants_init(); + + // Notify the caller thread that this thread is ready. + thread_ready->signal(); + + while (!exited) { + // Try to pull an action from the queue + Action action; + if (events_context.action_queue.wait_dequeue_timed(action, 1ms)) { + // Determine the action type and act on it + if (const auto* task_action = std::get_if(&action)) { + // Turn on instant present if the game has been started and it hasn't been turned on yet. + if (ultramodern::is_game_started() && !enabled_instant_present) { + rt64.enable_instant_present(); + enabled_instant_present = true; + } + // Tell the game that the RSP completed instantly. This will allow it to queue other task types, but it won't + // start another graphics task until the RDP is also complete. Games usually preserve the RSP inputs until the RDP + // is finished as well, so sending this early shouldn't be an issue in most cases. + // If this causes issues then the logic can be replaced with responding to yield requests. + sp_complete(); + ultramodern::measure_input_latency(); + + auto rt64_start = std::chrono::high_resolution_clock::now(); + rt64.send_dl(&task_action->task); + auto rt64_end = std::chrono::high_resolution_clock::now(); + dp_complete(); + // printf("RT64 ProcessDList time: %d us\n", static_cast(std::chrono::duration_cast(rt64_end - rt64_start).count())); + } + else if (const auto* swap_action = std::get_if(&action)) { + events_context.vi.current_buffer = events_context.vi.next_buffer; + rt64.update_screen(swap_action->origin); + display_refresh_rate = rt64.get_display_framerate(); + } + else if (const auto* config_action = std::get_if(&action)) { + ultramodern::GraphicsConfig new_config = cur_config; + if (old_config != new_config) { + rt64.update_config(old_config, new_config); + old_config = new_config; + } + } + else if (const auto* load_shader_cache_action = std::get_if(&action)) { + rt64.load_shader_cache(load_shader_cache_action->data); + } + } + } + // TODO move recomp code out of ultramodern. + recomp::destroy_ui(); + rt64.shutdown(); +} + +extern unsigned int VI_STATUS_REG; +extern unsigned int VI_ORIGIN_REG; +extern unsigned int VI_WIDTH_REG; +extern unsigned int VI_INTR_REG; +extern unsigned int VI_V_CURRENT_LINE_REG; +extern unsigned int VI_TIMING_REG; +extern unsigned int VI_V_SYNC_REG; +extern unsigned int VI_H_SYNC_REG; +extern unsigned int VI_LEAP_REG; +extern unsigned int VI_H_START_REG; +extern unsigned int VI_V_START_REG; +extern unsigned int VI_V_BURST_REG; +extern unsigned int VI_X_SCALE_REG; +extern unsigned int VI_Y_SCALE_REG; + +uint32_t hstart = 0; +uint32_t vi_origin_offset = 320 * sizeof(uint16_t); +bool vi_black = false; + +void set_dummy_vi() { + VI_STATUS_REG = 0x311E; + VI_WIDTH_REG = 0x140; + VI_V_SYNC_REG = 0x20D; + VI_H_SYNC_REG = 0xC15; + VI_LEAP_REG = 0x0C150C15; + hstart = 0x006C02EC; + VI_X_SCALE_REG = 0x200; + VI_V_CURRENT_LINE_REG = 0x0; + vi_origin_offset = 0x280; + VI_Y_SCALE_REG = 0x400; + VI_V_START_REG = 0x2501FF; + VI_V_BURST_REG = 0xE0204; + VI_INTR_REG = 0x2; +} + +extern "C" void osViSwapBuffer(RDRAM_ARG PTR(void) frameBufPtr) { + if (vi_black) { + VI_H_START_REG = 0; + } else { + VI_H_START_REG = hstart; + } + events_context.vi.next_buffer = frameBufPtr; + events_context.action_queue.enqueue(SwapBuffersAction{ osVirtualToPhysical(frameBufPtr) + vi_origin_offset }); +} + +extern "C" void osViSetMode(RDRAM_ARG PTR(OSViMode) mode_) { + OSViMode* mode = TO_PTR(OSViMode, mode_); + VI_STATUS_REG = mode->comRegs.ctrl; + VI_WIDTH_REG = mode->comRegs.width; + // burst + VI_V_SYNC_REG = mode->comRegs.vSync; + VI_H_SYNC_REG = mode->comRegs.hSync; + VI_LEAP_REG = mode->comRegs.leap; + hstart = mode->comRegs.hStart; + VI_X_SCALE_REG = mode->comRegs.xScale; + VI_V_CURRENT_LINE_REG = mode->comRegs.vCurrent; + + // TODO swap these every VI to account for fields changing + vi_origin_offset = mode->fldRegs[0].origin; + VI_Y_SCALE_REG = mode->fldRegs[0].yScale; + VI_V_START_REG = mode->fldRegs[0].vStart; + VI_V_BURST_REG = mode->fldRegs[0].vBurst; + VI_INTR_REG = mode->fldRegs[0].vIntr; +} + +#define VI_CTRL_TYPE_16 0x00002 +#define VI_CTRL_TYPE_32 0x00003 +#define VI_CTRL_GAMMA_DITHER_ON 0x00004 +#define VI_CTRL_GAMMA_ON 0x00008 +#define VI_CTRL_DIVOT_ON 0x00010 +#define VI_CTRL_SERRATE_ON 0x00040 +#define VI_CTRL_ANTIALIAS_MASK 0x00300 +#define VI_CTRL_ANTIALIAS_MODE_1 0x00100 +#define VI_CTRL_ANTIALIAS_MODE_2 0x00200 +#define VI_CTRL_ANTIALIAS_MODE_3 0x00300 +#define VI_CTRL_PIXEL_ADV_MASK 0x01000 +#define VI_CTRL_PIXEL_ADV_1 0x01000 +#define VI_CTRL_PIXEL_ADV_2 0x02000 +#define VI_CTRL_PIXEL_ADV_3 0x03000 +#define VI_CTRL_DITHER_FILTER_ON 0x10000 + +#define OS_VI_GAMMA_ON 0x0001 +#define OS_VI_GAMMA_OFF 0x0002 +#define OS_VI_GAMMA_DITHER_ON 0x0004 +#define OS_VI_GAMMA_DITHER_OFF 0x0008 +#define OS_VI_DIVOT_ON 0x0010 +#define OS_VI_DIVOT_OFF 0x0020 +#define OS_VI_DITHER_FILTER_ON 0x0040 +#define OS_VI_DITHER_FILTER_OFF 0x0080 + +extern "C" void osViSetSpecialFeatures(uint32_t func) { + if ((func & OS_VI_GAMMA_ON) != 0) { + VI_STATUS_REG |= VI_CTRL_GAMMA_ON; + } + + if ((func & OS_VI_GAMMA_OFF) != 0) { + VI_STATUS_REG &= ~VI_CTRL_GAMMA_ON; + } + + if ((func & OS_VI_GAMMA_DITHER_ON) != 0) { + VI_STATUS_REG |= VI_CTRL_GAMMA_DITHER_ON; + } + + if ((func & OS_VI_GAMMA_DITHER_OFF) != 0) { + VI_STATUS_REG &= ~VI_CTRL_GAMMA_DITHER_ON; + } + + if ((func & OS_VI_DIVOT_ON) != 0) { + VI_STATUS_REG |= VI_CTRL_DIVOT_ON; + } + + if ((func & OS_VI_DIVOT_OFF) != 0) { + VI_STATUS_REG &= ~VI_CTRL_DIVOT_ON; + } + + if ((func & OS_VI_DITHER_FILTER_ON) != 0) { + VI_STATUS_REG |= VI_CTRL_DITHER_FILTER_ON; + VI_STATUS_REG &= ~VI_CTRL_ANTIALIAS_MASK; + } + + if ((func & OS_VI_DITHER_FILTER_OFF) != 0) { + VI_STATUS_REG &= ~VI_CTRL_DITHER_FILTER_ON; + //VI_STATUS_REG |= __osViNext->modep->comRegs.ctrl & VI_CTRL_ANTIALIAS_MASK; + } +} + +extern "C" void osViBlack(uint8_t active) { + vi_black = active; +} + +extern "C" void osViSetXScale(float scale) { + if (scale != 1.0f) { + assert(false); + } +} + +extern "C" void osViSetYScale(float scale) { + if (scale != 1.0f) { + assert(false); + } +} + +extern "C" PTR(void) osViGetNextFramebuffer() { + return events_context.vi.next_buffer; +} + +extern "C" PTR(void) osViGetCurrentFramebuffer() { + return events_context.vi.current_buffer; +} + +void ultramodern::submit_rsp_task(RDRAM_ARG PTR(OSTask) task_) { + OSTask* task = TO_PTR(OSTask, task_); + + // Send gfx tasks to the graphics action queue + if (task->t.type == M_GFXTASK) { + events_context.action_queue.enqueue(SpTaskAction{ *task }); + } + // Set all other tasks as the RSP task + else { + events_context.sp_task_queue.enqueue(task); + } +} + +void ultramodern::send_si_message(RDRAM_ARG1) { + osSendMesg(PASS_RDRAM events_context.si.mq, events_context.si.msg, OS_MESG_NOBLOCK); +} + +std::string get_graphics_api_name(ultramodern::GraphicsApi api) { + if (api == ultramodern::GraphicsApi::Auto) { +#if defined(_WIN32) + api = ultramodern::GraphicsApi::D3D12; +#elif defined(__gnu_linux__) + api = ultramodern::GraphicsApi::Vulkan; +#else + static_assert(false && "Unimplemented") +#endif + } + + switch (api) { + case ultramodern::GraphicsApi::D3D12: + return "D3D12"; + case ultramodern::GraphicsApi::Vulkan: + return "Vulkan"; + default: + return "[Unknown graphics API]"; + } +} + +void ultramodern::init_events(RDRAM_ARG ultramodern::WindowHandle window_handle) { + moodycamel::LightweightSemaphore gfx_thread_ready; + moodycamel::LightweightSemaphore task_thread_ready; + events_context.rdram = rdram; + events_context.sp.gfx_thread = std::thread{ gfx_thread_func, rdram, &gfx_thread_ready, window_handle }; + events_context.sp.task_thread = std::thread{ task_thread_func, rdram, &task_thread_ready }; + + // Wait for the two sp threads to be ready before continuing to prevent the game from + // running before we're able to handle RSP tasks. + gfx_thread_ready.wait(); + task_thread_ready.wait(); + + ultramodern::RT64SetupResult setup_result = rt64_setup_result.load(); + if (rt64_setup_result != ultramodern::RT64SetupResult::Success) { + auto show_rt64_error = [](const std::string& msg) { + // TODO move recomp code out of ultramodern (message boxes). + recomp::message_box(("An error has been encountered on startup: " + msg).c_str()); + }; + const std::string driver_os_suffix = "\nPlease make sure your GPU drivers and your OS are up to date."; + switch (rt64_setup_result) { + case ultramodern::RT64SetupResult::DynamicLibrariesNotFound: + show_rt64_error("Failed to load dynamic libraries. Make sure the DLLs are next to the recomp executable."); + break; + case ultramodern::RT64SetupResult::InvalidGraphicsAPI: + show_rt64_error(get_graphics_api_name(cur_config.load().api_option) + " is not supported on this platform. Please select a different graphics API."); + break; + case ultramodern::RT64SetupResult::GraphicsAPINotFound: + show_rt64_error("Unable to initialize " + get_graphics_api_name(cur_config.load().api_option) + "." + driver_os_suffix); + break; + case ultramodern::RT64SetupResult::GraphicsDeviceNotFound: + show_rt64_error("Unable to find compatible graphics device." + driver_os_suffix); + break; + } + throw std::runtime_error("Failed to initialize RT64"); + } + + events_context.vi.thread = std::thread{ vi_thread_func }; +} + +void ultramodern::join_event_threads() { + events_context.sp.gfx_thread.join(); + events_context.vi.thread.join(); + + // Send a null RSP task to indicate that the RSP task thread should exit. + events_context.sp_task_queue.enqueue(nullptr); + events_context.sp.task_thread.join(); +} diff --git a/ultramodern/src/mesgqueue.cpp b/ultramodern/src/mesgqueue.cpp new file mode 100644 index 0000000..b13a3c0 --- /dev/null +++ b/ultramodern/src/mesgqueue.cpp @@ -0,0 +1,195 @@ +#include + +#include "blockingconcurrentqueue.h" + +#include "ultra64.h" +#include "ultramodern.hpp" +#include "recomp.h" + +struct QueuedMessage { + PTR(OSMesgQueue) mq; + OSMesg mesg; + bool jam; +}; + +static moodycamel::BlockingConcurrentQueue external_messages {}; + +void enqueue_external_message(PTR(OSMesgQueue) mq, OSMesg msg, bool jam) { + external_messages.enqueue({mq, msg, jam}); +} + +bool do_send(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, bool jam, bool block); + +void dequeue_external_messages(RDRAM_ARG1) { + QueuedMessage to_send; + while (external_messages.try_dequeue(to_send)) { + do_send(PASS_RDRAM to_send.mq, to_send.mesg, to_send.jam, false); + } +} + +void ultramodern::wait_for_external_message(RDRAM_ARG1) { + QueuedMessage to_send; + external_messages.wait_dequeue(to_send); + do_send(PASS_RDRAM to_send.mq, to_send.mesg, to_send.jam, false); +} + +void ultramodern::wait_for_external_message_timed(RDRAM_ARG1, u32 millis) { + QueuedMessage to_send; + if (external_messages.wait_dequeue_timed(to_send, std::chrono::milliseconds{millis})) { + do_send(PASS_RDRAM to_send.mq, to_send.mesg, to_send.jam, false); + } +} + +extern "C" void osCreateMesgQueue(RDRAM_ARG PTR(OSMesgQueue) mq_, PTR(OSMesg) msg, s32 count) { + OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_); + mq->blocked_on_recv = NULLPTR; + mq->blocked_on_send = NULLPTR; + mq->msgCount = count; + mq->msg = msg; + mq->validCount = 0; + mq->first = 0; +} + +s32 MQ_GET_COUNT(OSMesgQueue *mq) { + return mq->validCount; +} + +s32 MQ_IS_EMPTY(OSMesgQueue *mq) { + return mq->validCount == 0; +} + +s32 MQ_IS_FULL(OSMesgQueue* mq) { + return MQ_GET_COUNT(mq) >= mq->msgCount; +} + +bool do_send(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, bool jam, bool block) { + OSMesgQueue* mq = TO_PTR(OSMesgQueue, mq_); + if (!block) { + // If non-blocking, fail if the queue is full. + if (MQ_IS_FULL(mq)) { + return false; + } + } + else { + // Otherwise, yield this thread until the queue has room. + while (MQ_IS_FULL(mq)) { + debug_printf("[Message Queue] Thread %d is blocked on send\n", TO_PTR(OSThread, ultramodern::this_thread())->id); + ultramodern::thread_queue_insert(PASS_RDRAM GET_MEMBER(OSMesgQueue, mq_, blocked_on_send), ultramodern::this_thread()); + ultramodern::run_next_thread_and_wait(PASS_RDRAM1); + } + } + + if (jam) { + // Jams insert at the head of the message queue's buffer. + mq->first = (mq->first + mq->msgCount - 1) % mq->msgCount; + TO_PTR(OSMesg, mq->msg)[mq->first] = msg; + mq->validCount++; + } + else { + // Sends insert at the tail of the message queue's buffer. + s32 last = (mq->first + mq->validCount) % mq->msgCount; + TO_PTR(OSMesg, mq->msg)[last] = msg; + mq->validCount++; + } + + // If any threads were blocked on receiving from this message queue, pop the first one and schedule it. + PTR(PTR(OSThread)) blocked_queue = GET_MEMBER(OSMesgQueue, mq_, blocked_on_recv); + if (!ultramodern::thread_queue_empty(PASS_RDRAM blocked_queue)) { + ultramodern::schedule_running_thread(PASS_RDRAM ultramodern::thread_queue_pop(PASS_RDRAM blocked_queue)); + } + + return true; +} + +bool do_recv(RDRAM_ARG PTR(OSMesgQueue) mq_, PTR(OSMesg) msg_, bool block) { + OSMesgQueue* mq = TO_PTR(OSMesgQueue, mq_); + if (!block) { + // If non-blocking, fail if the queue is empty + if (MQ_IS_EMPTY(mq)) { + return false; + } + } else { + // Otherwise, yield this thread in a loop until the queue is no longer full + while (MQ_IS_EMPTY(mq)) { + debug_printf("[Message Queue] Thread %d is blocked on receive\n", TO_PTR(OSThread, ultramodern::this_thread())->id); + ultramodern::thread_queue_insert(PASS_RDRAM GET_MEMBER(OSMesgQueue, mq_, blocked_on_recv), ultramodern::this_thread()); + ultramodern::run_next_thread_and_wait(PASS_RDRAM1); + } + } + + if (msg_ != NULLPTR) { + *TO_PTR(OSMesg, msg_) = TO_PTR(OSMesg, mq->msg)[mq->first]; + } + + mq->first = (mq->first + 1) % mq->msgCount; + mq->validCount--; + + // If any threads were blocked on sending to this message queue, pop the first one and schedule it. + PTR(PTR(OSThread)) blocked_queue = GET_MEMBER(OSMesgQueue, mq_, blocked_on_send); + if (!ultramodern::thread_queue_empty(PASS_RDRAM blocked_queue)) { + ultramodern::schedule_running_thread(PASS_RDRAM ultramodern::thread_queue_pop(PASS_RDRAM blocked_queue)); + } + + return true; +} + +extern "C" s32 osSendMesg(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, s32 flags) { + OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_); + bool jam = false; + + // Don't directly send to the message queue if this isn't a game thread to avoid contention. + if (!ultramodern::is_game_thread()) { + enqueue_external_message(mq_, msg, jam); + return 0; + } + + // Handle any messages that have been received from an external thread. + dequeue_external_messages(PASS_RDRAM1); + + // Try to send the message. + bool sent = do_send(PASS_RDRAM mq_, msg, jam, flags == OS_MESG_BLOCK); + + // Check the queue to see if this thread should swap execution to another. + ultramodern::check_running_queue(PASS_RDRAM1); + + return sent ? 0 : -1; +} + +extern "C" s32 osJamMesg(RDRAM_ARG PTR(OSMesgQueue) mq_, OSMesg msg, s32 flags) { + OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_); + bool jam = true; + + // Don't directly send to the message queue if this isn't a game thread to avoid contention. + if (!ultramodern::is_game_thread()) { + enqueue_external_message(mq_, msg, jam); + return 0; + } + + // Handle any messages that have been received from an external thread. + dequeue_external_messages(PASS_RDRAM1); + + // Try to send the message. + bool sent = do_send(PASS_RDRAM mq_, msg, jam, flags == OS_MESG_BLOCK); + + // Check the queue to see if this thread should swap execution to another. + ultramodern::check_running_queue(PASS_RDRAM1); + + return sent ? 0 : -1; +} + +extern "C" s32 osRecvMesg(RDRAM_ARG PTR(OSMesgQueue) mq_, PTR(OSMesg) msg_, s32 flags) { + OSMesgQueue *mq = TO_PTR(OSMesgQueue, mq_); + + assert(ultramodern::is_game_thread() && "RecvMesg not allowed outside of game threads."); + + // Handle any messages that have been received from an external thread. + dequeue_external_messages(PASS_RDRAM1); + + // Try to receive a message. + bool received = do_recv(PASS_RDRAM mq_, msg_, flags == OS_MESG_BLOCK); + + // Check the queue to see if this thread should swap execution to another. + ultramodern::check_running_queue(PASS_RDRAM1); + + return received ? 0 : -1; +} diff --git a/ultramodern/src/misc_ultra.cpp b/ultramodern/src/misc_ultra.cpp new file mode 100644 index 0000000..2b794b7 --- /dev/null +++ b/ultramodern/src/misc_ultra.cpp @@ -0,0 +1,22 @@ +#include "ultra64.h" + +#define K0BASE 0x80000000 +#define K1BASE 0xA0000000 +#define K2BASE 0xC0000000 +#define IS_KSEG0(x) ((u32)(x) >= K0BASE && (u32)(x) < K1BASE) +#define IS_KSEG1(x) ((u32)(x) >= K1BASE && (u32)(x) < K2BASE) +#define K0_TO_PHYS(x) ((u32)(x)&0x1FFFFFFF) /* kseg0 to physical */ +#define K1_TO_PHYS(x) ((u32)(x)&0x1FFFFFFF) /* kseg1 to physical */ + +u32 osVirtualToPhysical(PTR(void) addr) { + uintptr_t addr_val = (uintptr_t)addr; + if (IS_KSEG0(addr_val)) { + return K0_TO_PHYS(addr_val); + } else if (IS_KSEG1(addr_val)) { + return K1_TO_PHYS(addr_val); + } else { + // TODO handle TLB mappings + return (u32)addr_val; + } +} + diff --git a/ultramodern/src/port_main.c b/ultramodern/src/port_main.c new file mode 100644 index 0000000..968b4d3 --- /dev/null +++ b/ultramodern/src/port_main.c @@ -0,0 +1,83 @@ +#if 0 + +#include +#include +#include "ultra64.h" + +#define THREAD_STACK_SIZE 0x1000 + +u8 idle_stack[THREAD_STACK_SIZE] ALIGNED(16); +u8 main_stack[THREAD_STACK_SIZE] ALIGNED(16); +u8 thread3_stack[THREAD_STACK_SIZE] ALIGNED(16); +u8 thread4_stack[THREAD_STACK_SIZE] ALIGNED(16); + +OSThread idle_thread; +OSThread main_thread; +OSThread thread3; +OSThread thread4; + +OSMesgQueue queue; +OSMesg buf[1]; + +void thread3_func(UNUSED void *arg) { + OSMesg val; + printf("Thread3 recv\n"); + fflush(stdout); + osRecvMesg(&queue, &val, OS_MESG_BLOCK); + printf("Thread3 complete: %d\n", (int)(intptr_t)val); + fflush(stdout); +} + +void thread4_func(void *arg) { + printf("Thread4 send %d\n", (int)(intptr_t)arg); + fflush(stdout); + osSendMesg(&queue, arg, OS_MESG_BLOCK); + printf("Thread4 complete\n"); + fflush(stdout); +} + +void main_thread_func(UNUSED void* arg) { + osCreateMesgQueue(&queue, buf, sizeof(buf) / sizeof(buf[0])); + + printf("main thread creating thread 3\n"); + osCreateThread(&thread3, 3, thread3_func, NULL, &thread3_stack[THREAD_STACK_SIZE], 14); + printf("main thread starting thread 3\n"); + osStartThread(&thread3); + + printf("main thread creating thread 4\n"); + osCreateThread(&thread4, 4, thread4_func, (void*)10, &thread4_stack[THREAD_STACK_SIZE], 13); + printf("main thread starting thread 4\n"); + osStartThread(&thread4); + + while (1) { + printf("main thread doin stuff\n"); + sleep(1); + } +} + +void idle_thread_func(UNUSED void* arg) { + printf("idle thread\n"); + printf("creating main thread\n"); + osCreateThread(&main_thread, 2, main_thread_func, NULL, &main_stack[THREAD_STACK_SIZE], 11); + printf("starting main thread\n"); + osStartThread(&main_thread); + + // Set this thread's priority to 0, making it the idle thread + osSetThreadPri(NULL, 0); + + // idle + while (1) { + printf("idle thread doin stuff\n"); + sleep(1); + } +} + +void bootproc(void) { + osInitialize(); + + osCreateThread(&idle_thread, 1, idle_thread_func, NULL, &idle_stack[THREAD_STACK_SIZE], 127); + printf("Starting idle thread\n"); + osStartThread(&idle_thread); +} + +#endif diff --git a/ultramodern/src/rt64_layer.cpp b/ultramodern/src/rt64_layer.cpp new file mode 100644 index 0000000..611e1eb --- /dev/null +++ b/ultramodern/src/rt64_layer.cpp @@ -0,0 +1,280 @@ +#include +#include +// #include + +#define HLSL_CPU +#include "hle/rt64_application.h" +#include "rt64_layer.h" +#include "rt64_render_hooks.h" + +ultramodern::RT64Context::~RT64Context() = default; + +static RT64::UserConfiguration::Antialiasing device_max_msaa = RT64::UserConfiguration::Antialiasing::None; +static bool sample_positions_supported = false; + +static uint8_t DMEM[0x1000]; +static uint8_t IMEM[0x1000]; + +unsigned int MI_INTR_REG = 0; + +unsigned int DPC_START_REG = 0; +unsigned int DPC_END_REG = 0; +unsigned int DPC_CURRENT_REG = 0; +unsigned int DPC_STATUS_REG = 0; +unsigned int DPC_CLOCK_REG = 0; +unsigned int DPC_BUFBUSY_REG = 0; +unsigned int DPC_PIPEBUSY_REG = 0; +unsigned int DPC_TMEM_REG = 0; + +unsigned int VI_STATUS_REG = 0; +unsigned int VI_ORIGIN_REG = 0; +unsigned int VI_WIDTH_REG = 0; +unsigned int VI_INTR_REG = 0; +unsigned int VI_V_CURRENT_LINE_REG = 0; +unsigned int VI_TIMING_REG = 0; +unsigned int VI_V_SYNC_REG = 0; +unsigned int VI_H_SYNC_REG = 0; +unsigned int VI_LEAP_REG = 0; +unsigned int VI_H_START_REG = 0; +unsigned int VI_V_START_REG = 0; +unsigned int VI_V_BURST_REG = 0; +unsigned int VI_X_SCALE_REG = 0; +unsigned int VI_Y_SCALE_REG = 0; + +void dummy_check_interrupts() { + +} + +RT64::UserConfiguration::Antialiasing compute_max_supported_aa(RT64::RenderSampleCounts bits) { + if (bits & RT64::RenderSampleCount::Bits::COUNT_2) { + if (bits & RT64::RenderSampleCount::Bits::COUNT_4) { + if (bits & RT64::RenderSampleCount::Bits::COUNT_8) { + return RT64::UserConfiguration::Antialiasing::MSAA8X; + } + return RT64::UserConfiguration::Antialiasing::MSAA4X; + } + return RT64::UserConfiguration::Antialiasing::MSAA2X; + }; + return RT64::UserConfiguration::Antialiasing::None; +} + +void set_application_user_config(RT64::Application* application, const ultramodern::GraphicsConfig& config) { + switch (config.res_option) { + default: + case ultramodern::Resolution::Auto: + application->userConfig.resolution = RT64::UserConfiguration::Resolution::WindowIntegerScale; + application->userConfig.downsampleMultiplier = 1; + break; + case ultramodern::Resolution::Original: + application->userConfig.resolution = RT64::UserConfiguration::Resolution::Manual; + application->userConfig.resolutionMultiplier = config.ds_option; + application->userConfig.downsampleMultiplier = config.ds_option; + break; + case ultramodern::Resolution::Original2x: + application->userConfig.resolution = RT64::UserConfiguration::Resolution::Manual; + application->userConfig.resolutionMultiplier = 2.0 * config.ds_option; + application->userConfig.downsampleMultiplier = config.ds_option; + break; + } + + switch (config.hr_option) { + default: + case ultramodern::HUDRatioMode::Original: + application->userConfig.extAspectRatio = RT64::UserConfiguration::AspectRatio::Original; + break; + case ultramodern::HUDRatioMode::Clamp16x9: + application->userConfig.extAspectRatio = RT64::UserConfiguration::AspectRatio::Manual; + application->userConfig.extAspectTarget = 16.0/9.0; + break; + case ultramodern::HUDRatioMode::Full: + application->userConfig.extAspectRatio = RT64::UserConfiguration::AspectRatio::Expand; + break; + } + + application->userConfig.aspectRatio = config.ar_option; + application->userConfig.antialiasing = config.msaa_option; + application->userConfig.refreshRate = config.rr_option; + application->userConfig.refreshRateTarget = config.rr_manual_value; +} + +ultramodern::RT64SetupResult map_setup_result(RT64::Application::SetupResult rt64_result) { + switch (rt64_result) { + case RT64::Application::SetupResult::Success: + return ultramodern::RT64SetupResult::Success; + case RT64::Application::SetupResult::DynamicLibrariesNotFound: + return ultramodern::RT64SetupResult::DynamicLibrariesNotFound; + case RT64::Application::SetupResult::InvalidGraphicsAPI: + return ultramodern::RT64SetupResult::InvalidGraphicsAPI; + case RT64::Application::SetupResult::GraphicsAPINotFound: + return ultramodern::RT64SetupResult::GraphicsAPINotFound; + case RT64::Application::SetupResult::GraphicsDeviceNotFound: + return ultramodern::RT64SetupResult::GraphicsDeviceNotFound; + } +} + +ultramodern::RT64Context::RT64Context(uint8_t* rdram, ultramodern::WindowHandle window_handle, bool debug) { + static unsigned char dummy_rom_header[0x40]; + set_rt64_hooks(); + + // Set up the RT64 application core fields. + RT64::Application::Core appCore{}; +#if defined(_WIN32) + appCore.window = window_handle.window; +#elif defined(__ANDROID__) + assert(false && "Unimplemented"); +#elif defined(__linux__) + appCore.window.display = window_handle.display; + appCore.window.window = window_handle.window; +#endif + + appCore.checkInterrupts = dummy_check_interrupts; + + appCore.HEADER = dummy_rom_header; + appCore.RDRAM = rdram; + appCore.DMEM = DMEM; + appCore.IMEM = IMEM; + + appCore.MI_INTR_REG = &MI_INTR_REG; + + appCore.DPC_START_REG = &DPC_START_REG; + appCore.DPC_END_REG = &DPC_END_REG; + appCore.DPC_CURRENT_REG = &DPC_CURRENT_REG; + appCore.DPC_STATUS_REG = &DPC_STATUS_REG; + appCore.DPC_CLOCK_REG = &DPC_CLOCK_REG; + appCore.DPC_BUFBUSY_REG = &DPC_BUFBUSY_REG; + appCore.DPC_PIPEBUSY_REG = &DPC_PIPEBUSY_REG; + appCore.DPC_TMEM_REG = &DPC_TMEM_REG; + + appCore.VI_STATUS_REG = &VI_STATUS_REG; + appCore.VI_ORIGIN_REG = &VI_ORIGIN_REG; + appCore.VI_WIDTH_REG = &VI_WIDTH_REG; + appCore.VI_INTR_REG = &VI_INTR_REG; + appCore.VI_V_CURRENT_LINE_REG = &VI_V_CURRENT_LINE_REG; + appCore.VI_TIMING_REG = &VI_TIMING_REG; + appCore.VI_V_SYNC_REG = &VI_V_SYNC_REG; + appCore.VI_H_SYNC_REG = &VI_H_SYNC_REG; + appCore.VI_LEAP_REG = &VI_LEAP_REG; + appCore.VI_H_START_REG = &VI_H_START_REG; + appCore.VI_V_START_REG = &VI_V_START_REG; + appCore.VI_V_BURST_REG = &VI_V_BURST_REG; + appCore.VI_X_SCALE_REG = &VI_X_SCALE_REG; + appCore.VI_Y_SCALE_REG = &VI_Y_SCALE_REG; + + // Set up the RT64 application configuration fields. + RT64::ApplicationConfiguration appConfig; + appConfig.useConfigurationFile = false; + + // Create the RT64 application. + app = std::make_unique(appCore, appConfig); + + // Set initial user config settings based on the current settings. + ultramodern::GraphicsConfig cur_config = ultramodern::get_graphics_config(); + set_application_user_config(app.get(), cur_config); + app->userConfig.developerMode = debug; + // Force gbi depth branches to prevent LODs from kicking in. + app->enhancementConfig.f3dex.forceBranch = true; + // Scale LODs based on the output resolution. + app->enhancementConfig.textureLOD.scale = true; + // Pick an API if the user has set an override. + switch (cur_config.api_option) { + case ultramodern::GraphicsApi::D3D12: + app->userConfig.graphicsAPI = RT64::UserConfiguration::GraphicsAPI::D3D12; + break; + case ultramodern::GraphicsApi::Vulkan: + app->userConfig.graphicsAPI = RT64::UserConfiguration::GraphicsAPI::Vulkan; + break; + default: + case ultramodern::GraphicsApi::Auto: + // Don't override if auto is selected. + break; + } + + // Set up the RT64 application. + uint32_t thread_id = 0; +#ifdef _WIN32 + thread_id = window_handle.thread_id; +#endif + setup_result = map_setup_result(app->setup(thread_id)); + if (setup_result != ultramodern::RT64SetupResult::Success) { + app = nullptr; + return; + } + + // Set the application's fullscreen state. + app->setFullScreen(cur_config.wm_option == ultramodern::WindowMode::Fullscreen); + + // Check if the selected device actually supports MSAA sample positions and MSAA for for the formats that will be used + // and downgrade the configuration accordingly. + if (app->device->getCapabilities().sampleLocations) { + RT64::RenderSampleCounts color_sample_counts = app->device->getSampleCountsSupported(RT64::RenderFormat::R8G8B8A8_UNORM); + RT64::RenderSampleCounts depth_sample_counts = app->device->getSampleCountsSupported(RT64::RenderFormat::D32_FLOAT); + RT64::RenderSampleCounts common_sample_counts = color_sample_counts & depth_sample_counts; + device_max_msaa = compute_max_supported_aa(common_sample_counts); + sample_positions_supported = true; + } + else { + device_max_msaa = RT64::UserConfiguration::Antialiasing::None; + sample_positions_supported = false; + } +} + +void ultramodern::RT64Context::send_dl(const OSTask* task) { + app->state->rsp->reset(); + app->interpreter->loadUCodeGBI(task->t.ucode & 0x3FFFFFF, task->t.ucode_data & 0x3FFFFFF, true); + app->processDisplayLists(app->core.RDRAM, task->t.data_ptr & 0x3FFFFFF, 0, true); +} + +void ultramodern::RT64Context::update_screen(uint32_t vi_origin) { + VI_ORIGIN_REG = vi_origin; + + app->updateScreen(); +} + +void ultramodern::RT64Context::shutdown() { + if (app != nullptr) { + app->end(); + } +} + +void ultramodern::RT64Context::update_config(const ultramodern::GraphicsConfig& old_config, const ultramodern::GraphicsConfig& new_config) { + if (new_config.wm_option != old_config.wm_option) { + app->setFullScreen(new_config.wm_option == ultramodern::WindowMode::Fullscreen); + } + + set_application_user_config(app.get(), new_config); + + app->updateUserConfig(true); + + if (new_config.msaa_option != old_config.msaa_option) { + app->updateMultisampling(); + } +} + +void ultramodern::RT64Context::enable_instant_present() { + // Enable the present early presentation mode for minimal latency. + app->enhancementConfig.presentation.mode = RT64::EnhancementConfiguration::Presentation::Mode::PresentEarly; + + app->updateEnhancementConfig(); +} + +uint32_t ultramodern::RT64Context::get_display_framerate() { + return app->presentQueue->ext.sharedResources->swapChainRate; +} + +void ultramodern::RT64Context::load_shader_cache(std::span cache_binary) { + // TODO figure out how to avoid a copy here. + std::istringstream cache_stream{std::string{cache_binary.data(), cache_binary.size()}}; + + if (!app->rasterShaderCache->loadOfflineList(cache_stream)) { + printf("Failed to preload shader cache!\n"); + assert(false); + } +} + +RT64::UserConfiguration::Antialiasing ultramodern::RT64MaxMSAA() { + return device_max_msaa; +} + +bool ultramodern::RT64SamplePositionsSupported() { + return sample_positions_supported; +} diff --git a/ultramodern/src/scheduling.cpp b/ultramodern/src/scheduling.cpp new file mode 100644 index 0000000..4af1b98 --- /dev/null +++ b/ultramodern/src/scheduling.cpp @@ -0,0 +1,48 @@ +#include "ultramodern.hpp" + +void ultramodern::schedule_running_thread(RDRAM_ARG PTR(OSThread) t_) { + debug_printf("[Scheduling] Adding thread %d to the running queue\n", TO_PTR(OSThread, t_)->id); + thread_queue_insert(PASS_RDRAM running_queue, t_); + TO_PTR(OSThread, t_)->state = OSThreadState::QUEUED; +} + +void swap_to_thread(RDRAM_ARG OSThread *to) { + debug_printf("[Scheduling] Thread %d giving execution to thread %d\n", TO_PTR(OSThread, ultramodern::this_thread())->id, to->id); + // Insert this thread in the running queue. + ultramodern::thread_queue_insert(PASS_RDRAM ultramodern::running_queue, ultramodern::this_thread()); + TO_PTR(OSThread, ultramodern::this_thread())->state = OSThreadState::QUEUED; + // Unpause the target thread and wait for this one to be unpaused. + ultramodern::resume_thread_and_wait(PASS_RDRAM to); +} + +void ultramodern::check_running_queue(RDRAM_ARG1) { + // Check if there are any threads in the running queue. + if (!thread_queue_empty(PASS_RDRAM running_queue)) { + // Check if the highest priority thread in the queue is higher priority than the current thread. + OSThread* next_thread = TO_PTR(OSThread, ultramodern::thread_queue_peek(PASS_RDRAM running_queue)); + OSThread* self = TO_PTR(OSThread, ultramodern::this_thread()); + if (next_thread->priority > self->priority) { + ultramodern::thread_queue_pop(PASS_RDRAM running_queue); + // Swap to the higher priority thread. + swap_to_thread(PASS_RDRAM next_thread); + } + } +} + +extern "C" void pause_self(RDRAM_ARG1) { + while (true) { + // Wait until an external message arrives, then allow the next thread to run. + ultramodern::wait_for_external_message(PASS_RDRAM1); + ultramodern::check_running_queue(PASS_RDRAM1); + } +} + +extern "C" void yield_self(RDRAM_ARG1) { + ultramodern::wait_for_external_message(PASS_RDRAM1); + ultramodern::check_running_queue(PASS_RDRAM1); +} + +extern "C" void yield_self_1ms(RDRAM_ARG1) { + ultramodern::wait_for_external_message_timed(PASS_RDRAM1, 1); + ultramodern::check_running_queue(PASS_RDRAM1); +} diff --git a/ultramodern/src/task_win32.cpp b/ultramodern/src/task_win32.cpp new file mode 100644 index 0000000..c3e511c --- /dev/null +++ b/ultramodern/src/task_win32.cpp @@ -0,0 +1,13 @@ +#ifdef _WIN32 + +#include + +#include "ultra64.h" +#include "ultramodern.hpp" + +extern "C" unsigned int sleep(unsigned int seconds) { + Sleep(seconds * 1000); + return 0; +} + +#endif \ No newline at end of file diff --git a/ultramodern/src/threadqueue.cpp b/ultramodern/src/threadqueue.cpp new file mode 100644 index 0000000..5869811 --- /dev/null +++ b/ultramodern/src/threadqueue.cpp @@ -0,0 +1,66 @@ +#include + +#include "ultramodern.hpp" + +static PTR(OSThread) running_queue_impl = NULLPTR; + +static PTR(OSThread)* queue_to_ptr(RDRAM_ARG PTR(PTR(OSThread)) queue) { + if (queue == ultramodern::running_queue) { + return &running_queue_impl; + } + return TO_PTR(PTR(OSThread), queue); +} + +void ultramodern::thread_queue_insert(RDRAM_ARG PTR(PTR(OSThread)) queue_, PTR(OSThread) toadd_) { + PTR(OSThread)* cur = queue_to_ptr(PASS_RDRAM queue_); + OSThread* toadd = TO_PTR(OSThread, toadd_); + debug_printf("[Thread Queue] Inserting thread %d into queue 0x%08X\n", toadd->id, (uintptr_t)queue_); + while (*cur && TO_PTR(OSThread, *cur)->priority > toadd->priority) { + cur = &TO_PTR(OSThread, *cur)->next; + } + toadd->next = (*cur); + toadd->queue = queue_; + *cur = toadd_; + + debug_printf(" Contains:"); + cur = queue_to_ptr(PASS_RDRAM queue_); + while (*cur) { + debug_printf("%d (%d) ", TO_PTR(OSThread, *cur)->id, TO_PTR(OSThread, *cur)->priority); + cur = &TO_PTR(OSThread, *cur)->next; + } + debug_printf("\n"); +} + +PTR(OSThread) ultramodern::thread_queue_pop(RDRAM_ARG PTR(PTR(OSThread)) queue_) { + PTR(OSThread)* queue = queue_to_ptr(PASS_RDRAM queue_); + PTR(OSThread) ret = *queue; + *queue = TO_PTR(OSThread, ret)->next; + TO_PTR(OSThread, ret)->queue = NULLPTR; + debug_printf("[Thread Queue] Popped thread %d from queue 0x%08X\n", TO_PTR(OSThread, ret)->id, (uintptr_t)queue_); + return ret; +} + +bool ultramodern::thread_queue_remove(RDRAM_ARG PTR(PTR(OSThread)) queue_, PTR(OSThread) t_) { + debug_printf("[Thread Queue] Removing thread %d from queue 0x%08X\n", TO_PTR(OSThread, t_)->id, (uintptr_t)queue_); + + PTR(PTR(OSThread)) cur = queue_; + while (cur != NULLPTR) { + PTR(OSThread)* cur_ptr = queue_to_ptr(PASS_RDRAM queue_); + if (*cur_ptr == t_) { + return true; + } + cur = TO_PTR(OSThread, *cur_ptr)->next; + } + + return false; +} + +bool ultramodern::thread_queue_empty(RDRAM_ARG PTR(PTR(OSThread)) queue_) { + PTR(OSThread)* queue = queue_to_ptr(PASS_RDRAM queue_); + return *queue == NULLPTR; +} + +PTR(OSThread) ultramodern::thread_queue_peek(RDRAM_ARG PTR(PTR(OSThread)) queue_) { + PTR(OSThread)* queue = queue_to_ptr(PASS_RDRAM queue_); + return *queue; +} diff --git a/ultramodern/src/threads.cpp b/ultramodern/src/threads.cpp new file mode 100644 index 0000000..9b17a52 --- /dev/null +++ b/ultramodern/src/threads.cpp @@ -0,0 +1,346 @@ +#include +#include +#include +#include + +#include "ultra64.h" +#include "ultramodern.hpp" +#include "blockingconcurrentqueue.h" + +// Native APIs only used to set thread names for easier debugging +#ifdef _WIN32 +#include +#endif + +extern "C" void bootproc(); + +thread_local bool is_main_thread = false; +// Whether this thread is part of the game (i.e. the start thread or one spawned by osCreateThread) +thread_local bool is_game_thread = false; +thread_local PTR(OSThread) thread_self = NULLPTR; + +void ultramodern::set_main_thread() { + ::is_game_thread = true; + is_main_thread = true; +} + +bool ultramodern::is_game_thread() { + return ::is_game_thread; +} + +#if 0 +int main(int argc, char** argv) { + ultramodern::set_main_thread(); + + bootproc(); +} +#endif + +#if 1 +void run_thread_function(uint8_t* rdram, uint64_t addr, uint64_t sp, uint64_t arg); +#else +#define run_thread_function(func, sp, arg) func(arg) +#endif + +#if defined(_WIN32) +void ultramodern::set_native_thread_name(const std::string& name) { + std::wstring wname{name.begin(), name.end()}; + + HRESULT r; + r = SetThreadDescription( + GetCurrentThread(), + wname.c_str() + ); +} + +void ultramodern::set_native_thread_priority(ThreadPriority pri) { + int nPriority = THREAD_PRIORITY_NORMAL; + + // Convert ThreadPriority to Win32 priority + switch (pri) { + case ThreadPriority::Low: + nPriority = THREAD_PRIORITY_BELOW_NORMAL; + break; + case ThreadPriority::Normal: + nPriority = THREAD_PRIORITY_NORMAL; + break; + case ThreadPriority::High: + nPriority = THREAD_PRIORITY_ABOVE_NORMAL; + break; + case ThreadPriority::VeryHigh: + nPriority = THREAD_PRIORITY_HIGHEST; + break; + case ThreadPriority::Critical: + nPriority = THREAD_PRIORITY_TIME_CRITICAL; + break; + default: + throw std::runtime_error("Invalid thread priority!"); + break; + } + // SetThreadPriority(GetCurrentThread(), nPriority); +} +#elif defined(__linux__) +void ultramodern::set_native_thread_name(const std::string& name) { + pthread_setname_np(pthread_self(), name.c_str()); +} + +void ultramodern::set_native_thread_priority(ThreadPriority pri) { + // TODO linux thread priority + // printf("set_native_thread_priority unimplemented\n"); + // int nPriority = THREAD_PRIORITY_NORMAL; + + // // Convert ThreadPriority to Win32 priority + // switch (pri) { + // case ThreadPriority::Low: + // nPriority = THREAD_PRIORITY_BELOW_NORMAL; + // break; + // case ThreadPriority::Normal: + // nPriority = THREAD_PRIORITY_NORMAL; + // break; + // case ThreadPriority::High: + // nPriority = THREAD_PRIORITY_ABOVE_NORMAL; + // break; + // case ThreadPriority::VeryHigh: + // nPriority = THREAD_PRIORITY_HIGHEST; + // break; + // case ThreadPriority::Critical: + // nPriority = THREAD_PRIORITY_TIME_CRITICAL; + // break; + // default: + // throw std::runtime_error("Invalid thread priority!"); + // break; + // } +} +#endif + +std::atomic_int temporary_threads = 0; +std::atomic_int permanent_threads = 0; + +void wait_for_resumed(RDRAM_ARG UltraThreadContext* thread_context) { + TO_PTR(OSThread, ultramodern::this_thread())->context->running.wait(); + // If this thread's context was replaced by another thread or deleted, destroy it again from its own context. + // This will trigger thread cleanup instead. + if (TO_PTR(OSThread, ultramodern::this_thread())->context != thread_context) { + osDestroyThread(PASS_RDRAM NULLPTR); + } +} + +void resume_thread(OSThread* t) { + debug_printf("[Thread] Resuming execution of thread %d\n", t->id); + t->context->running.signal(); +} + +void run_next_thread(RDRAM_ARG1) { + if (ultramodern::thread_queue_empty(PASS_RDRAM ultramodern::running_queue)) { + throw std::runtime_error("No threads left to run!\n"); + } + + OSThread* to_run = TO_PTR(OSThread, ultramodern::thread_queue_pop(PASS_RDRAM ultramodern::running_queue)); + debug_printf("[Scheduling] Resuming execution of thread %d\n", to_run->id); + to_run->context->running.signal(); +} + +void ultramodern::run_next_thread_and_wait(RDRAM_ARG1) { + UltraThreadContext* cur_context = TO_PTR(OSThread, thread_self)->context; + run_next_thread(PASS_RDRAM1); + wait_for_resumed(PASS_RDRAM cur_context); +} + +void ultramodern::resume_thread_and_wait(RDRAM_ARG OSThread *t) { + UltraThreadContext* cur_context = TO_PTR(OSThread, thread_self)->context; + resume_thread(t); + wait_for_resumed(PASS_RDRAM cur_context); +} + +static void _thread_func(RDRAM_ARG PTR(OSThread) self_, PTR(thread_func_t) entrypoint, PTR(void) arg, UltraThreadContext* thread_context) { + OSThread *self = TO_PTR(OSThread, self_); + debug_printf("[Thread] Thread created: %d\n", self->id); + thread_self = self_; + is_game_thread = true; + + // Set the thread name + ultramodern::set_native_thread_name("Game Thread " + std::to_string(self->id)); + ultramodern::set_native_thread_priority(ultramodern::ThreadPriority::High); + + // TODO fix these being hardcoded (this is only used for quicksaving) + if ((self->id == 2 && self->priority == 5) || self->id == 13) { // slowly, flashrom + temporary_threads.fetch_add(1); + } + else if (self->id != 1 && self->id != 2) { // ignore idle and fault + permanent_threads.fetch_add(1); + } + + // Signal the initialized semaphore to indicate that this thread can be started. + thread_context->initialized.signal(); + + debug_printf("[Thread] Thread waiting to be started: %d\n", self->id); + + // Wait until the thread is marked as running. + wait_for_resumed(PASS_RDRAM thread_context); + + // Make sure the thread wasn't replaced or destroyed before it was started. + if (self->context == thread_context) { + debug_printf("[Thread] Thread started: %d\n", self->id); + try { + // Run the thread's function with the provided argument. + run_thread_function(PASS_RDRAM entrypoint, self->sp, arg); + } catch (ultramodern::thread_terminated& terminated) { + } + } + else { + debug_printf("[Thread] Thread destroyed before being started: %d\n", self->id); + } + + // Check if the thread hasn't been destroyed or replaced. If so, then the thread terminated or destroyed itself, + // so mark this thread as destroyed and run the next queued thread. + if (self->context == thread_context) { + self->context = nullptr; + run_next_thread(PASS_RDRAM1); + } + + // Dispose of this thread now that it's completed or terminated. + ultramodern::cleanup_thread(thread_context); + + // TODO fix these being hardcoded (this is only used for quicksaving) + if ((self->id == 2 && self->priority == 5) || self->id == 13) { // slowly, flashrom + temporary_threads.fetch_sub(1); + } +} + +uint32_t ultramodern::permanent_thread_count() { + return permanent_threads.load(); +} + +uint32_t ultramodern::temporary_thread_count() { + return temporary_threads.load(); +} + +extern "C" void osStartThread(RDRAM_ARG PTR(OSThread) t_) { + OSThread* t = TO_PTR(OSThread, t_); + debug_printf("[os] Start Thread %d\n", t->id); + + // Wait until the thread is initialized to indicate that it's ready to be started. + t->context->initialized.wait(); + + debug_printf("[os] Thread %d is ready to be started\n", t->id); + + // If this is a game thread, insert the new thread into the running queue and then check the running queue. + if (thread_self) { + ultramodern::schedule_running_thread(PASS_RDRAM t_); + ultramodern::check_running_queue(PASS_RDRAM1); + } + // Otherwise, immediately start the thread and terminate this one. + else { + t->state = OSThreadState::QUEUED; + resume_thread(t); + //throw ultramodern::thread_terminated{}; + } +} + +extern "C" void osCreateThread(RDRAM_ARG PTR(OSThread) t_, OSId id, PTR(thread_func_t) entrypoint, PTR(void) arg, PTR(void) sp, OSPri pri) { + debug_printf("[os] Create Thread %d\n", id); + OSThread *t = TO_PTR(OSThread, t_); + + t->next = NULLPTR; + t->queue = NULLPTR; + t->priority = pri; + t->id = id; + t->state = OSThreadState::STOPPED; + t->sp = sp - 0x10; // Set up the first stack frame + + // Spawn a new thread, which will immediately pause itself and wait until it's been started. + // Pass the context as an argument to the thread function to ensure that it can't get cleared before the thread captures its value. + t->context = new UltraThreadContext{}; + t->context->host_thread = std::thread{_thread_func, PASS_RDRAM t_, entrypoint, arg, t->context}; +} + +extern "C" void osStopThread(RDRAM_ARG PTR(OSThread) t_) { + assert(false); +} + +extern "C" void osDestroyThread(RDRAM_ARG PTR(OSThread) t_) { + if (t_ == NULLPTR) { + t_ = thread_self; + } + OSThread* t = TO_PTR(OSThread, t_); + // Check if the thread is destroying itself (arg is null or thread_self) + if (t_ == thread_self) { + throw ultramodern::thread_terminated{}; + } + // Otherwise if the thread isn't stopped, remove it from its currrent queue., + if (t->state != OSThreadState::STOPPED) { + ultramodern::thread_queue_remove(PASS_RDRAM t->queue, t_); + } + // Check if the thread has already been destroyed to prevent destroying it again. + UltraThreadContext* cur_context = t->context; + if (cur_context != nullptr) { + // Mark the target thread as destroyed and resume it. When it starts it'll check this and terminate itself instead of resuming. + t->context = nullptr; + cur_context->running.signal(); + } +} + +extern "C" void osSetThreadPri(RDRAM_ARG PTR(OSThread) t_, OSPri pri) { + if (t_ == NULLPTR) { + t_ = thread_self; + } + OSThread* t = TO_PTR(OSThread, t_); + + if (t->priority != pri) { + t->priority = pri; + + if (t_ != ultramodern::this_thread() && t->state != OSThreadState::STOPPED) { + ultramodern::thread_queue_remove(PASS_RDRAM t->queue, t_); + ultramodern::thread_queue_insert(PASS_RDRAM t->queue, t_); + } + + ultramodern::check_running_queue(PASS_RDRAM1); + } +} + +extern "C" OSPri osGetThreadPri(RDRAM_ARG PTR(OSThread) t) { + if (t == NULLPTR) { + t = thread_self; + } + return TO_PTR(OSThread, t)->priority; +} + +extern "C" OSId osGetThreadId(RDRAM_ARG PTR(OSThread) t) { + if (t == NULLPTR) { + t = thread_self; + } + return TO_PTR(OSThread, t)->id; +} + +PTR(OSThread) ultramodern::this_thread() { + return thread_self; +} + +static std::thread thread_cleaner_thread; +static moodycamel::BlockingConcurrentQueue deleted_threads{}; +extern std::atomic_bool exited; + +void thread_cleaner_func() { + using namespace std::chrono_literals; + while (!exited) { + UltraThreadContext* to_delete; + if (deleted_threads.wait_dequeue_timed(to_delete, 10ms)) { + debug_printf("[Cleanup] Deleting thread context %p\n", to_delete); + + to_delete->host_thread.join(); + delete to_delete; + } + } +} + +void ultramodern::init_thread_cleanup() { + thread_cleaner_thread = std::thread{thread_cleaner_func}; +} + +void ultramodern::cleanup_thread(UltraThreadContext *cur_context) { + deleted_threads.enqueue(cur_context); +} + +void ultramodern::join_thread_cleaner_thread() { + thread_cleaner_thread.join(); +} diff --git a/ultramodern/src/timer.cpp b/ultramodern/src/timer.cpp new file mode 100644 index 0000000..b278ff3 --- /dev/null +++ b/ultramodern/src/timer.cpp @@ -0,0 +1,224 @@ +#include +#include +#include +#include "blockingconcurrentqueue.h" + +#include "ultra64.h" +#include "ultramodern.hpp" + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include "Windows.h" +#endif + +// Start time for the program +static std::chrono::high_resolution_clock::time_point start_time = std::chrono::high_resolution_clock::now(); +// Game speed multiplier (1 means no speedup) +constexpr uint32_t speed_multiplier = 1; +// N64 CPU counter ticks per millisecond +constexpr uint32_t counter_per_ms = 46'875 * speed_multiplier; + +struct OSTimer { + PTR(OSTimer) unused1; + PTR(OSTimer) unused2; + OSTime interval; + OSTime timestamp; + PTR(OSMesgQueue) mq; + OSMesg msg; +}; + +struct AddTimerAction { + PTR(OSTimer) timer; +}; + +struct RemoveTimerAction { + PTR(OSTimer) timer; +}; + +using Action = std::variant; + +struct { + std::thread thread; + moodycamel::BlockingConcurrentQueue action_queue{}; +} timer_context; + +uint64_t duration_to_ticks(std::chrono::high_resolution_clock::duration duration) { + uint64_t delta_micros = std::chrono::duration_cast(duration).count(); + // More accurate than using a floating point timer, will only overflow after running for 12.47 years + // Units: (micros * (counts/millis)) / (micros/millis) = counts + uint64_t total_count = (delta_micros * counter_per_ms) / 1000; + + return total_count; +} + +std::chrono::microseconds ticks_to_duration(uint64_t ticks) { + using namespace std::chrono_literals; + return ticks * 1000us / counter_per_ms; +} + +std::chrono::high_resolution_clock::time_point ticks_to_timepoint(uint64_t ticks) { + return start_time + ticks_to_duration(ticks); +} + +uint64_t time_now() { + return duration_to_ticks(std::chrono::high_resolution_clock::now() - start_time); +} + +void timer_thread(RDRAM_ARG1) { + ultramodern::set_native_thread_name("Timer Thread"); + ultramodern::set_native_thread_priority(ultramodern::ThreadPriority::VeryHigh); + + // Lambda comparator function to keep the set ordered + auto timer_sort = [PASS_RDRAM1](PTR(OSTimer) a_, PTR(OSTimer) b_) { + OSTimer* a = TO_PTR(OSTimer, a_); + OSTimer* b = TO_PTR(OSTimer, b_); + + // Order by timestamp if the timers have different timestamps + if (a->timestamp != b->timestamp) { + return a->timestamp < b->timestamp; + } + + // If they have the exact same timestamp then order by address instead + return a < b; + }; + + // Ordered set of timers that are currently active + std::set active_timers{timer_sort}; + + // Lambda to process a timer action to handle adding and removing timers + auto process_timer_action = [&](const Action& action) { + // Determine the action type and act on it + if (const auto* add_action = std::get_if(&action)) { + active_timers.insert(add_action->timer); + } else if (const auto* remove_action = std::get_if(&action)) { + active_timers.erase(remove_action->timer); + } + }; + + while (true) { + // Empty the action queue + Action cur_action; + while (timer_context.action_queue.try_dequeue(cur_action)) { + process_timer_action(cur_action); + } + + // If there's no timer to act on, wait for one to come in from the action queue + while (active_timers.empty()) { + timer_context.action_queue.wait_dequeue(cur_action); + process_timer_action(cur_action); + } + + // Get the timer that's closest to running out + PTR(OSTimer) cur_timer_ = *active_timers.begin(); + OSTimer* cur_timer = TO_PTR(OSTimer, cur_timer_); + + // Remove the timer from the queue (it may get readded if waiting is interrupted) + active_timers.erase(cur_timer_); + + // Determine how long to wait to reach the timer's timestamp + auto wait_duration = ticks_to_timepoint(cur_timer->timestamp) - std::chrono::high_resolution_clock::now(); + + // Wait for either the duration to complete or a new action to come through + if (wait_duration.count() >= 0 && timer_context.action_queue.wait_dequeue_timed(cur_action, wait_duration)) { + // Timer was interrupted by a new action + // Add the current timer back to the queue (done first in case the action is to remove this timer) + active_timers.insert(cur_timer_); + // Process the new action + process_timer_action(cur_action); + } + else { + // Waiting for the timer completed, so send the timer's message to its message queue + osSendMesg(PASS_RDRAM cur_timer->mq, cur_timer->msg, OS_MESG_NOBLOCK); + // If the timer has a specified interval then reload it with that value + if (cur_timer->interval != 0) { + cur_timer->timestamp = cur_timer->interval + time_now(); + active_timers.insert(cur_timer_); + } + } + } +} + +void ultramodern::init_timers(RDRAM_ARG1) { + timer_context.thread = std::thread{ timer_thread, PASS_RDRAM1 }; + timer_context.thread.detach(); +} + +uint32_t ultramodern::get_speed_multiplier() { + return speed_multiplier; +} + +std::chrono::high_resolution_clock::time_point ultramodern::get_start() { + return start_time; +} + +std::chrono::high_resolution_clock::duration ultramodern::time_since_start() { + return std::chrono::high_resolution_clock::now() - start_time; +} + +extern "C" u32 osGetCount() { + uint64_t total_count = time_now(); + + // Allow for overflows, which is how osGetCount behaves + return (uint32_t)total_count; +} + +extern "C" OSTime osGetTime() { + uint64_t total_count = time_now(); + + return total_count; +} + +extern "C" int osSetTimer(RDRAM_ARG PTR(OSTimer) t_, OSTime countdown, OSTime interval, PTR(OSMesgQueue) mq, OSMesg msg) { + OSTimer* t = TO_PTR(OSTimer, t_); + + // Determine the time when this timer will trigger off + if (countdown == 0) { + // Set the timestamp based on the interval + t->timestamp = interval + time_now(); + } else { + t->timestamp = countdown + time_now(); + } + t->interval = interval; + t->mq = mq; + t->msg = msg; + + timer_context.action_queue.enqueue(AddTimerAction{ t_ }); + + return 0; +} + +extern "C" int osStopTimer(RDRAM_ARG PTR(OSTimer) t_) { + timer_context.action_queue.enqueue(RemoveTimerAction{ t_ }); + + // TODO don't blindly return 0 here; requires some response from the timer thread to know what the returned value was + return 0; +} + +#ifdef _WIN32 + +// The implementations of std::chrono::sleep_until and sleep_for were affected by changing the system clock backwards in older versions +// of Microsoft's STL. This was fixed as of Visual Studio 2022 17.9, but to be safe ultramodern uses Win32 Sleep directly. +void ultramodern::sleep_milliseconds(uint32_t millis) { + Sleep(millis); +} + +void ultramodern::sleep_until(const std::chrono::high_resolution_clock::time_point& time_point) { + auto time_now = std::chrono::high_resolution_clock::now(); + if (time_point > time_now) { + long long delta_ms = std::chrono::ceil(time_point - time_now).count(); + // printf("Sleeping %lld %d ms\n", delta_ms, (uint32_t)delta_ms); + Sleep(delta_ms); + } +} + +#else + +void ultramodern::sleep_milliseconds(uint32_t millis) { + std::this_thread::sleep_for(std::chrono::milliseconds{millis}); +} + +void ultramodern::sleep_until(const std::chrono::high_resolution_clock::time_point& time_point) { + std::this_thread::sleep_until(time_point); +} + +#endif diff --git a/ultramodern/src/ultrainit.cpp b/ultramodern/src/ultrainit.cpp new file mode 100644 index 0000000..9517113 --- /dev/null +++ b/ultramodern/src/ultrainit.cpp @@ -0,0 +1,14 @@ +#include "ultra64.h" +#include "ultramodern.hpp" + +void ultramodern::preinit(RDRAM_ARG ultramodern::WindowHandle window_handle) { + ultramodern::set_main_thread(); + ultramodern::init_events(PASS_RDRAM window_handle); + ultramodern::init_timers(PASS_RDRAM1); + ultramodern::init_audio(); + ultramodern::init_saving(PASS_RDRAM1); + ultramodern::init_thread_cleanup(); +} + +extern "C" void osInitialize() { +}