Last active
December 25, 2021 18:53
-
-
Save flamewing/795e3df3bc42f3576dbf894ef6ed7406 to your computer and use it in GitHub Desktop.
Experimental C++20 Mega Drive VDP library
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Overrides for some system files to account for -mshort | |
using usize = unsigned long; | |
using ssize = long; | |
using rsize = usize; | |
using intptr = long; | |
using uintptr = unsigned long; | |
using ptrdiff = long; | |
using wint = unsigned short; | |
using wctype = unsigned short; | |
using int8 = signed char; | |
using uint8 = unsigned char; | |
#ifdef __clang__ | |
using int16 = short; | |
using uint16 = unsigned short; | |
#else | |
using int16 = int; | |
using uint16 = unsigned int; | |
#endif | |
using int32 = long; | |
using uint32 = unsigned long; | |
using int64 = long long; | |
using uint64 = unsigned long long; | |
using int_least8 = signed char; | |
using uint_least8 = unsigned char; | |
using int_least16 = int; | |
using uint_least16 = unsigned int; | |
using int_least32 = long; | |
using uint_least32 = unsigned long; | |
using int_least64 = long long; | |
using uint_least64 = unsigned long long; | |
using int_fast8 = signed char; | |
using uint_fast8 = unsigned char; | |
using int_fast16 = int; | |
using uint_fast16 = unsigned int; | |
using int_fast32 = long; | |
using uint_fast32 = unsigned long; | |
using int_fast64 = long long; | |
using uint_fast64 = unsigned long long; | |
using intmax = long long; | |
using uintmax = unsigned long long; | |
static_assert(sizeof(int8) == 1); | |
static_assert(sizeof(uint8) == 1); | |
static_assert(sizeof(int16) == 2); | |
static_assert(sizeof(uint16) == 2); | |
static_assert(sizeof(int32) == 4); | |
static_assert(sizeof(uint32) == 4); | |
static_assert(sizeof(int64) == 8); | |
static_assert(sizeof(uint64) == 8); | |
static_assert(sizeof(intptr) == 4); | |
static_assert(sizeof(uintptr) == 4); | |
static_assert(sizeof(usize) == 4); | |
static_assert(sizeof(ssize) == 4); | |
#include <array> | |
#include <bit> | |
#include <concepts> | |
#include <cstdint> | |
#include <limits> | |
#include <optional> | |
#include <stdexcept> | |
#include <tuple> | |
#include <type_traits> | |
#include <vector> | |
// Low level layer | |
namespace std23 { | |
template <class T> | |
constexpr std::underlying_type_t<T> to_underlying(T val) noexcept { | |
return static_cast<std::underlying_type_t<T>>(val); | |
} | |
} // namespace std23 | |
namespace detail { | |
template <typename Enum> | |
requires std::is_enum_v<Enum> | |
constexpr auto operator~(const Enum lhs) -> Enum { | |
using T = std::underlying_type_t<Enum>; | |
const T retval = ~std23::to_underlying(lhs); | |
return std::bit_cast<Enum>(retval); | |
} | |
template <typename Enum> | |
requires std::is_enum_v<Enum> | |
constexpr auto operator|(const Enum lhs, const Enum rhs) -> Enum { | |
using T = std::underlying_type_t<Enum>; | |
const T retval = std23::to_underlying(lhs) | std23::to_underlying(rhs); | |
return std::bit_cast<Enum>(retval); | |
} | |
template <typename Enum> | |
requires std::is_enum_v<Enum> | |
constexpr auto operator|=(Enum& lhs, const Enum rhs) -> Enum& { | |
using T = std::underlying_type_t<Enum>; | |
const T retval = std23::to_underlying(lhs) | std23::to_underlying(rhs); | |
lhs = std::bit_cast<Enum>(retval); | |
return lhs; | |
} | |
template <typename Enum> | |
requires std::is_enum_v<Enum> | |
constexpr auto operator&(const Enum lhs, const Enum rhs) -> Enum { | |
using T = std::underlying_type_t<Enum>; | |
const T retval = std23::to_underlying(lhs) & std23::to_underlying(rhs); | |
return std::bit_cast<Enum>(retval); | |
} | |
template <typename Enum> | |
requires std::is_enum_v<Enum> | |
constexpr auto operator&=(Enum& lhs, const Enum rhs) -> Enum& { | |
using T = std::underlying_type_t<Enum>; | |
const T retval = std23::to_underlying(lhs) & std23::to_underlying(rhs); | |
lhs = std::bit_cast<Enum>(retval); | |
return lhs; | |
} | |
template <typename Enum> | |
requires std::is_enum_v<Enum> | |
constexpr auto operator^(const Enum lhs, const Enum rhs) -> Enum { | |
using T = std::underlying_type_t<Enum>; | |
const T retval = std23::to_underlying(lhs) ^ std23::to_underlying(rhs); | |
return std::bit_cast<Enum>(retval); | |
} | |
template <typename Enum> | |
requires std::is_enum_v<Enum> | |
constexpr auto operator^=(Enum& lhs, const Enum rhs) -> Enum& { | |
using T = std::underlying_type_t<Enum>; | |
const T retval = std23::to_underlying(lhs) ^ std23::to_underlying(rhs); | |
lhs = std::bit_cast<Enum>(retval); | |
return lhs; | |
} | |
enum class RegMode01 : uint16_t { | |
ECSYNC_ON = 1U << 0U, | |
HVLATCH_ON = 1U << 1U, | |
PALSEL_ON = 1U << 2U, | |
HINT_ON = 1U << 4U, | |
COL0_BLANK_ON = 1U << 5U, | |
RegisterNumber = 0x8000U | PALSEL_ON, | |
}; | |
enum class RegMode02 : uint16_t { | |
MODE_GEN = 1U << 2U, | |
V30_ON = 1U << 3U, | |
DMA_ON = 1U << 4U, | |
VINT_ON = 1U << 5U, | |
DISPLAY_ON = 1U << 6U, | |
VRAM_128KB_ON = 1U << 7U, | |
RegisterNumber = 0x8100, | |
}; | |
enum class PlaneALoc : uint16_t { | |
RegisterNumber = 0x8200, | |
AddressMask = 0b01111000, | |
AddressDivisor = 0x400, | |
ExtendedVRAMMask = 0b01000000, | |
}; | |
enum class WindowLoc : uint16_t { | |
RegisterNumber = 0x8300, | |
AddressMask = 0b01111110, | |
AddressDivisor = 0x400, | |
ExtendedVRAMMask = 0b01000000, | |
}; | |
enum class PlaneBLoc : uint16_t { | |
RegisterNumber = 0x8400, | |
AddressMask = 0b00001111, | |
AddressDivisor = 0x2000, | |
ExtendedVRAMMask = 0b00001000, | |
}; | |
enum class SpriteLoc : uint16_t { | |
RegisterNumber = 0x8500, | |
AddressMask = 0b11111111, | |
AddressDivisor = 0x200, | |
ExtendedVRAMMask = 0b10000000, | |
}; | |
enum class SpriteGen : uint16_t { | |
RegisterNumber = 0x8600, | |
HIGH_BANK = 1U << 6U, | |
}; | |
enum class Background : uint16_t { | |
RegisterNumber = 0x8700, | |
LineShift = 5, | |
}; | |
enum class Unused88 : uint16_t { | |
RegisterNumber = 0x8800, | |
}; | |
enum class Unused89 : uint16_t { | |
RegisterNumber = 0x8900, | |
}; | |
enum class RegHIntCounter : uint16_t { | |
RegisterNumber = 0x8A00, | |
}; | |
enum class RegMode03 : uint16_t { | |
HSCROLL_TILE = 2U << 0U, | |
HSCROLL_LINE = 3U << 0U, | |
HSCROLL_MASK = HSCROLL_LINE, | |
VSCROLL_CELL = 1U << 2U, | |
EXINT_ON = 1U << 3U, | |
RegisterNumber = 0x8B00, | |
}; | |
enum class RegMode04 : uint16_t { | |
MODE_H40 = (1U << 7U) | (1U << 0U), | |
MODE_H40_FAST = (1U << 0U), | |
MODE_MASK = (1U << 7U) | (1U << 0U), | |
INTERLACE_NORMAL = 1U << 1U, | |
INTERLACE_DOUBLE = 2U << 1U, | |
INTERLACE_MASK = 3U << 1U, | |
SHADOWHILITE_ON = 1U << 3U, | |
PIXEL_BUS_ON = 1U << 4U, | |
HSYNC_OFF = 1U << 5U, | |
PIXEL_CLOCK = 1U << 6U, | |
RegisterNumber = 0x8C00, | |
}; | |
enum class HScrollLoc : uint16_t { | |
RegisterNumber = 0x8D00, | |
AddressMask = 0b01111111, | |
AddressDivisor = 0x400, | |
ExtendedVRAMMask = 0b01000000, | |
}; | |
enum class PlaneGen : uint16_t { | |
RegisterNumber = 0x8E00, | |
PLANE_A_HIGH_BANK = 1U << 0U, | |
PLANE_B_HIGH_BANK = 1U << 4U, | |
}; | |
enum class AutoIncrement : uint16_t { | |
RegisterNumber = 0x8F00, | |
}; | |
enum class PlaneSize : uint16_t { | |
RegisterNumber = 0x9000, | |
VerticalShift = 4, | |
}; | |
enum class WindowPosHoriz : uint16_t { | |
RegisterNumber = 0x9100, | |
DOCK_RIGHT = 1U << 7U, | |
SizeMask = 0b00011111U, | |
}; | |
enum class WindowPosVerti : uint16_t { | |
RegisterNumber = 0x9200, | |
DOCK_BOTTOM = 1U << 7U, | |
SizeMask = 0b00011111U, | |
}; | |
enum class DMALenLow : uint16_t { | |
RegisterNumber = 0x9300, | |
}; | |
enum class DMALenHigh : uint16_t { | |
RegisterNumber = 0x9400, | |
}; | |
enum class DMASrcLow : uint16_t { | |
RegisterNumber = 0x9500, | |
}; | |
enum class DMASrcMid : uint16_t { | |
RegisterNumber = 0x9600, | |
}; | |
enum class DMASrcHigh : uint16_t { | |
RegisterNumber = 0x9700, | |
FILL_FLAG = 2U << 6U, | |
COPY_FLAG = 3U << 6U, | |
DMA_MASK = 3U << 6U, | |
}; | |
using lazy_evaluator_holder = std::tuple< | |
std::optional<RegMode01>, std::optional<RegMode02>, | |
std::optional<PlaneALoc>, std::optional<WindowLoc>, | |
std::optional<PlaneBLoc>, std::optional<SpriteLoc>, | |
std::optional<SpriteGen>, std::optional<Background>, | |
std::optional<Unused88>, std::optional<Unused89>, | |
std::optional<RegHIntCounter>, std::optional<RegMode03>, | |
std::optional<RegMode04>, std::optional<HScrollLoc>, | |
std::optional<PlaneGen>, std::optional<AutoIncrement>, | |
std::optional<PlaneSize>, std::optional<WindowPosHoriz>, | |
std::optional<WindowPosVerti>, std::optional<DMALenLow>, | |
std::optional<DMALenHigh>, std::optional<DMASrcLow>, | |
std::optional<DMASrcMid>, std::optional<DMASrcHigh>>; | |
} // namespace detail | |
enum class HIntCounter : uint8 {}; | |
enum class HorizRes : uint8 { | |
H32, | |
H40, | |
H40Fast, // Expert-only | |
}; | |
enum class VertiRes : uint8 { | |
V28, | |
V30, | |
}; | |
enum class HorizScroll : uint8 { | |
WHOLE_SCREEN, | |
EACH_TILE, | |
EACH_LINE, | |
}; | |
enum class VertiScroll : uint8 { | |
WHOLE_SCREEN, | |
EACH_BLOCK, | |
}; | |
enum class InterlaceMode : uint8 { | |
DISABLED, | |
NORMAL_RESOLUTION, | |
DOUBLE_RESOLUTION, | |
}; | |
enum class PatternBank : uint8 { LOW_BANK, HIGH_BANK }; | |
enum class Palette : uint8 { | |
LINE0 = 0, | |
LINE1 = 1, | |
LINE2 = 2, | |
LINE3 = 3, | |
}; | |
enum class SizeHoriz : uint8 { | |
H32 = 0, | |
H64 = 1, | |
H128 = 3, | |
}; | |
enum class SizeVerti : uint8 { | |
V32 = 0, | |
V64 = 1, | |
V128 = 3, | |
}; | |
enum class WindowHoriz : uint8 { | |
DOCK_LEFT, | |
DOCK_RIGHT, | |
}; | |
enum class WindowVerti : uint8 { | |
DOCK_ABOVE, | |
DOCK_BELOW, | |
}; | |
class VDPConfigurer { | |
using lazy_evaluator_holder = detail::lazy_evaluator_holder; | |
using RegMode01 = detail::RegMode01; | |
using RegMode02 = detail::RegMode02; | |
using PlaneALoc = detail::PlaneALoc; | |
using WindowLoc = detail::WindowLoc; | |
using PlaneBLoc = detail::PlaneBLoc; | |
using SpriteLoc = detail::SpriteLoc; | |
using SpriteGen = detail::SpriteGen; | |
using Background = detail::Background; | |
using Unused88 = detail::Unused88; | |
using Unused89 = detail::Unused89; | |
using RegHIntCounter = detail::RegHIntCounter; | |
using RegMode03 = detail::RegMode03; | |
using RegMode04 = detail::RegMode04; | |
using HScrollLoc = detail::HScrollLoc; | |
using PlaneGen = detail::PlaneGen; | |
using AutoIncrement = detail::AutoIncrement; | |
using PlaneSize = detail::PlaneSize; | |
using WindowPosHoriz = detail::WindowPosHoriz; | |
using WindowPosVerti = detail::WindowPosVerti; | |
using DMALenLow = detail::DMALenLow; | |
using DMALenHigh = detail::DMALenHigh; | |
using DMASrcLow = detail::DMASrcLow; | |
using DMASrcMid = detail::DMASrcMid; | |
using DMASrcHigh = detail::DMASrcHigh; | |
lazy_evaluator_holder reg_data{}; | |
template <typename Enum> | |
requires std::is_enum_v<Enum> | |
constexpr Enum get_register(Enum value) { | |
auto& reg = std::get<std::optional<Enum>>(reg_data); | |
if (!reg.has_value()) { | |
return Enum{}; | |
} | |
return *reg & value; | |
} | |
template <typename Enum> | |
requires std::is_enum_v<Enum> | |
constexpr VDPConfigurer& set_register(Enum value) { | |
auto& reg = std::get<std::optional<Enum>>(reg_data); | |
if (!reg.has_value()) { | |
reg = Enum::RegisterNumber; | |
} | |
*reg |= value; | |
return *this; | |
} | |
template <typename Enum> | |
requires std::is_enum_v<Enum> | |
constexpr VDPConfigurer& set_register(Enum value, Enum mask) { | |
auto& reg = std::get<std::optional<Enum>>(reg_data); | |
if (!reg.has_value()) { | |
reg = Enum::RegisterNumber; | |
} else { | |
*reg &= ~mask; | |
} | |
*reg |= value; | |
return *this; | |
} | |
template <typename Enum> | |
requires std::is_enum_v<Enum> | |
constexpr VDPConfigurer& reset_register(Enum value) { | |
auto& reg = std::get<std::optional<Enum>>(reg_data); | |
if (!reg.has_value()) { | |
reg = Enum::RegisterNumber; | |
} | |
*reg &= ~value; | |
return *this; | |
} | |
template <size_t... Is> | |
inline void apply( | |
volatile uint16* const ctrl16, volatile uint32* const ctrl32, | |
std::index_sequence<Is...>) const noexcept { | |
const size_t count = std::tuple_size_v<lazy_evaluator_holder>; | |
uint32 value = 0; | |
bool has_value = false; | |
auto write_registers_in_pairs = [&](auto&& reg, auto current) mutable { | |
if (!reg.has_value()) { | |
if (has_value && current == count - 1) { | |
*ctrl16 = value; | |
has_value = false; | |
} | |
return; | |
} | |
if (has_value) { | |
*ctrl32 = (value << 16U) | std23::to_underlying(*reg); | |
has_value = false; | |
} else { | |
if (current == count - 1) { | |
*ctrl16 = std23::to_underlying(*reg); | |
} else { | |
value = std23::to_underlying(*reg); | |
} | |
has_value = current != count - 1; | |
} | |
}; | |
(write_registers_in_pairs(std::get<Is>(reg_data), Is), ...); | |
} | |
constexpr VDPConfigurer& set_dma_length_direct(uint16 length) { | |
return set_register(DMALenLow(length % 256)) | |
.set_register(DMALenHigh(length / 256)); | |
} | |
constexpr VDPConfigurer& set_dma_source_direct(uintptr source) { | |
return set_register(DMASrcLow(source % 256)) | |
.set_register(DMASrcMid((source / 256) % 256)) | |
.set_register(DMASrcHigh(source / 65536)); | |
} | |
constexpr VDPConfigurer& clear_dma_flags() { | |
return reset_register(DMASrcHigh::DMA_MASK); | |
} | |
constexpr VDPConfigurer& set_dma_fill_flag() { | |
return set_register(DMASrcHigh::FILL_FLAG, DMASrcHigh::DMA_MASK); | |
} | |
constexpr VDPConfigurer& set_dma_copy_flag() { | |
return set_register(DMASrcHigh::COPY_FLAG, DMASrcHigh::DMA_MASK); | |
} | |
public: | |
constexpr static inline auto begin_configure() noexcept -> VDPConfigurer { | |
return VDPConfigurer{}; | |
} | |
void apply() const noexcept { | |
auto* const ctrl16 | |
= std::bit_cast<volatile uint16* const>(uintptr{0xC00004}); | |
auto* const ctrl32 | |
= std::bit_cast<volatile uint32* const>(uintptr{0xC00004}); | |
apply(ctrl16, ctrl32, | |
std::make_index_sequence< | |
std::tuple_size_v<lazy_evaluator_holder>>{}); | |
} | |
// Interrupts | |
constexpr VDPConfigurer& enable_horizontal_interrupt() { | |
return set_register(RegMode01::HINT_ON); | |
} | |
constexpr VDPConfigurer& enable_horizontal_interrupt(HIntCounter counter) { | |
return set_register(RegMode01::HINT_ON) | |
.set_register(RegHIntCounter{counter}); | |
} | |
constexpr VDPConfigurer& set_horizontal_interrupt_counter( | |
HIntCounter counter) { | |
return set_register(RegHIntCounter{counter}); | |
} | |
constexpr VDPConfigurer& enable_vertical_interrupt() { | |
return set_register(RegMode02::VINT_ON); | |
} | |
constexpr VDPConfigurer& enable_external_interrupt() { | |
return set_register(RegMode03::EXINT_ON); | |
} | |
constexpr VDPConfigurer& enable_hvcounter_latch() { | |
return set_register(RegMode01::HVLATCH_ON); | |
} | |
// Expert options | |
constexpr VDPConfigurer& enable_external_color_sync() { | |
return set_register(RegMode01::ECSYNC_ON); | |
} | |
constexpr VDPConfigurer& enable_extended_vram() { | |
return set_register(RegMode02::VRAM_128KB_ON); | |
} | |
constexpr VDPConfigurer& enable_pixel_clock() { | |
return set_register(RegMode04::PIXEL_CLOCK); | |
} | |
constexpr VDPConfigurer& enable_external_pixel_bus() { | |
return set_register(RegMode04::PIXEL_BUS_ON); | |
} | |
constexpr VDPConfigurer& disable_hsync() { | |
return set_register(RegMode04::HSYNC_OFF); | |
} | |
constexpr VDPConfigurer& set_plane_a_pattern_bank(PatternBank bank) { | |
if (bank == PatternBank::HIGH_BANK) { | |
return set_register(PlaneGen::PLANE_A_HIGH_BANK); | |
} | |
return reset_register(PlaneGen::PLANE_A_HIGH_BANK); | |
} | |
constexpr VDPConfigurer& set_plane_b_pattern_bank(PatternBank bank) { | |
if (bank == PatternBank::HIGH_BANK) { | |
return set_register(PlaneGen::PLANE_B_HIGH_BANK); | |
} | |
return reset_register(PlaneGen::PLANE_B_HIGH_BANK); | |
} | |
constexpr VDPConfigurer& set_sprite_pattern_bank(PatternBank bank) { | |
if (bank == PatternBank::HIGH_BANK) { | |
return set_register(SpriteGen::HIGH_BANK); | |
} | |
return reset_register(SpriteGen::HIGH_BANK); | |
} | |
// Display enable | |
constexpr VDPConfigurer& enable_display() { | |
return set_register(RegMode02::DISPLAY_ON); | |
} | |
// DMA enable | |
constexpr VDPConfigurer& enable_dma() { | |
return set_register(RegMode02::DMA_ON); | |
} | |
// VDP mode | |
constexpr VDPConfigurer& select_genesis() { | |
return set_register(RegMode02::MODE_GEN); | |
} | |
constexpr VDPConfigurer& select_master_system() { | |
return reset_register(RegMode02::MODE_GEN); | |
} | |
constexpr VDPConfigurer& set_base_mode() { | |
return select_genesis() | |
.set_register(RegMode01::PALSEL_ON) | |
.set_plane_a_pattern_bank(PatternBank::LOW_BANK) | |
.set_plane_b_pattern_bank(PatternBank::LOW_BANK) | |
.set_sprite_pattern_bank(PatternBank::LOW_BANK) | |
.set_dma_length_direct(std::numeric_limits<uint16>::max()) | |
.set_dma_source_direct(0) | |
.set_dma_fill_flag(); | |
} | |
constexpr VDPConfigurer& set_unused_registers() { | |
return select_genesis() | |
.set_register(Unused88{}) | |
.set_register(Unused89{}); | |
} | |
// Screen resolution | |
constexpr VDPConfigurer& set_resolution(VertiRes vres) { | |
switch (vres) { | |
case VertiRes::V30: | |
return set_register(RegMode02::V30_ON); | |
case VertiRes::V28: | |
return reset_register(RegMode02::V30_ON); | |
} | |
return *this; | |
} | |
constexpr VDPConfigurer& set_resolution(HorizRes hres) { | |
switch (hres) { | |
case HorizRes::H40Fast: | |
return set_register(RegMode04::MODE_H40_FAST, RegMode04::MODE_MASK); | |
case HorizRes::H40: | |
return set_register(RegMode04::MODE_H40, RegMode04::MODE_MASK); | |
case HorizRes::H32: | |
return reset_register(RegMode04::MODE_MASK); | |
} | |
return *this; | |
} | |
constexpr VDPConfigurer& set_resolution(HorizRes hres, VertiRes vres) { | |
return set_resolution(hres).set_resolution(vres); | |
} | |
// Vertical and horizontal scrolling | |
constexpr VDPConfigurer& set_scroll_mode(HorizScroll hscr) { | |
switch (hscr) { | |
case HorizScroll::WHOLE_SCREEN: | |
return reset_register(RegMode03::HSCROLL_LINE); | |
case HorizScroll::EACH_TILE: | |
return set_register( | |
RegMode03::HSCROLL_TILE, RegMode03::HSCROLL_MASK); | |
case HorizScroll::EACH_LINE: | |
return set_register( | |
RegMode03::HSCROLL_LINE, RegMode03::HSCROLL_MASK); | |
} | |
return *this; | |
} | |
constexpr VDPConfigurer& set_scroll_mode(VertiScroll vscr) { | |
switch (vscr) { | |
case VertiScroll::WHOLE_SCREEN: | |
return reset_register(RegMode03::VSCROLL_CELL); | |
case VertiScroll::EACH_BLOCK: | |
return set_register(RegMode03::VSCROLL_CELL); | |
} | |
return *this; | |
} | |
constexpr VDPConfigurer& set_scroll_mode( | |
HorizScroll hscr, VertiScroll vscr) { | |
return set_scroll_mode(hscr).set_scroll_mode(vscr); | |
} | |
// Interlacing | |
constexpr VDPConfigurer& set_scroll_mode(InterlaceMode mode) { | |
switch (mode) { | |
case InterlaceMode::DOUBLE_RESOLUTION: | |
return set_register( | |
RegMode04::INTERLACE_DOUBLE, RegMode04::INTERLACE_MASK); | |
case InterlaceMode::NORMAL_RESOLUTION: | |
return set_register( | |
RegMode04::INTERLACE_NORMAL, RegMode04::INTERLACE_MASK); | |
case InterlaceMode::DISABLED: | |
return reset_register(RegMode04::INTERLACE_MASK); | |
} | |
return *this; | |
} | |
// Shadow/highlight | |
constexpr VDPConfigurer& enable_shadow_highlight_mode() { | |
return set_register(RegMode04::SHADOWHILITE_ON); | |
} | |
// Table addresses | |
constexpr VDPConfigurer& set_plane_a_location(uint16 addr) { | |
if ((addr % std23::to_underlying(PlaneALoc::AddressDivisor)) != 0) { | |
throw "Plane A must be at a VRAM address divisible by $2000"; | |
} | |
auto loc = PlaneALoc( | |
addr / std23::to_underlying(PlaneALoc::AddressDivisor)); | |
if ((loc & PlaneALoc::AddressMask) != loc) { | |
throw "Divide by cucumber error, please reinstall Universe"; | |
} | |
if ((loc & PlaneALoc::ExtendedVRAMMask) == PlaneALoc::ExtendedVRAMMask | |
&& get_register(RegMode02::VRAM_128KB_ON) | |
!= RegMode02::VRAM_128KB_ON) { | |
throw "Plane A is being placed on the high bank of VRAM, but " | |
"extended VRAM has not been enabled"; | |
} | |
return set_register(loc); | |
} | |
constexpr VDPConfigurer& set_window_location(uint16 addr) { | |
if ((addr % std23::to_underlying(WindowLoc::AddressDivisor)) != 0) { | |
throw "Window must be at a VRAM address divisible by $800"; | |
} | |
auto loc = WindowLoc( | |
addr / std23::to_underlying(WindowLoc::AddressDivisor)); | |
if ((loc & WindowLoc::AddressMask) != loc) { | |
throw "Divide by cucumber error, please reinstall Universe and " | |
"reboot"; | |
} | |
if ((loc & WindowLoc::ExtendedVRAMMask) == WindowLoc::ExtendedVRAMMask | |
&& get_register(RegMode02::VRAM_128KB_ON) | |
!= RegMode02::VRAM_128KB_ON) { | |
throw "Window is being placed on the high bank of VRAM, but " | |
"extended VRAM has not been enabled"; | |
} | |
return set_register(loc); | |
} | |
constexpr VDPConfigurer& set_plane_b_location(uint16 addr) { | |
if ((addr % std23::to_underlying(PlaneBLoc::AddressDivisor)) != 0) { | |
throw "Plane B must be at a VRAM address divisible by $2000"; | |
} | |
auto loc = PlaneBLoc( | |
addr / std23::to_underlying(PlaneBLoc::AddressDivisor)); | |
if ((loc & PlaneBLoc::AddressMask) != loc) { | |
throw "Divide by cucumber error, please reinstall Universe and " | |
"reboot"; | |
} | |
if ((loc & PlaneBLoc::ExtendedVRAMMask) == PlaneBLoc::ExtendedVRAMMask | |
&& get_register(RegMode02::VRAM_128KB_ON) | |
!= RegMode02::VRAM_128KB_ON) { | |
throw "Plane B is being placed on the high bank of VRAM, but " | |
"extended VRAM has not been enabled"; | |
} | |
return set_register(loc); | |
} | |
constexpr VDPConfigurer& set_sprite_table_location(uint16 addr) { | |
if ((addr % std23::to_underlying(SpriteLoc::AddressDivisor)) != 0) { | |
throw "Sprite table must be at a VRAM address divisible by $200"; | |
} | |
auto loc = SpriteLoc( | |
addr / std23::to_underlying(SpriteLoc::AddressDivisor)); | |
if ((loc & SpriteLoc::AddressMask) != loc) { | |
throw "Divide by cucumber error, please reinstall Universe and " | |
"reboot"; | |
} | |
if ((loc & SpriteLoc::ExtendedVRAMMask) == SpriteLoc::ExtendedVRAMMask | |
&& get_register(RegMode02::VRAM_128KB_ON) | |
!= RegMode02::VRAM_128KB_ON) { | |
throw "Sprite table is being placed on the high bank of VRAM, but " | |
"extended VRAM has not been enabled"; | |
} | |
return set_register(loc); | |
} | |
constexpr VDPConfigurer& set_scroll_table_location(uint16 addr) { | |
if ((addr % std23::to_underlying(HScrollLoc::AddressDivisor)) != 0) { | |
throw "Scroll table must be at a VRAM address divisible by $400"; | |
} | |
auto loc = HScrollLoc( | |
addr / std23::to_underlying(HScrollLoc::AddressDivisor)); | |
if ((loc & HScrollLoc::AddressMask) != loc) { | |
throw "Divide by cucumber error, please reinstall Universe and " | |
"reboot"; | |
} | |
if ((loc & HScrollLoc::ExtendedVRAMMask) == HScrollLoc::ExtendedVRAMMask | |
&& get_register(RegMode02::VRAM_128KB_ON) | |
!= RegMode02::VRAM_128KB_ON) { | |
throw "Scroll table is being placed on the high bank of VRAM, but " | |
"extended VRAM has not been enabled"; | |
} | |
return set_register(loc); | |
} | |
// Background color | |
constexpr VDPConfigurer& set_background_color(Palette line, uint8 color) { | |
if ((color % 16) != color) { | |
throw "Invalid color: colors must be on the 0-15 range"; | |
} | |
auto palsel = Background( | |
std23::to_underlying(line) | |
<< std23::to_underlying(Background::LineShift)); | |
return set_register(palsel | Background{color}); | |
} | |
// Plane size | |
constexpr VDPConfigurer& set_plane_size(SizeHoriz hsz, SizeVerti vsz) { | |
if (hsz == SizeHoriz::H128 && vsz != SizeVerti::V32) { | |
throw "Plane size is too large: H128 can only be used with V32"; | |
} | |
if (vsz == SizeVerti::V128 && hsz != SizeHoriz::H32) { | |
throw "Plane size is too large: V128 can only be used with H32"; | |
} | |
auto vertsize = PlaneSize( | |
std23::to_underlying(vsz) | |
<< std23::to_underlying(PlaneSize::VerticalShift)); | |
return set_register(vertsize | PlaneSize{hsz}); | |
} | |
// Window | |
constexpr VDPConfigurer& set_window(WindowHoriz dock, uint8 width) { | |
auto win_width = WindowPosHoriz{width}; | |
if ((win_width & WindowPosHoriz::SizeMask) != win_width) { | |
throw "Window width is too large: maximum size is 31 tiles"; | |
} | |
if (dock == WindowHoriz::DOCK_RIGHT) { | |
return set_register(WindowPosHoriz::DOCK_RIGHT | win_width); | |
} | |
return set_register(win_width); | |
} | |
constexpr VDPConfigurer& set_window(WindowVerti dock, uint8 height) { | |
auto win_height = WindowPosVerti{height}; | |
if ((win_height & WindowPosVerti::SizeMask) != win_height) { | |
throw "Window height is too large: maximum size is 31 tiles"; | |
} | |
if (dock == WindowVerti::DOCK_BELOW) { | |
return set_register(WindowPosVerti::DOCK_BOTTOM | win_height); | |
} | |
return set_register(win_height); | |
} | |
constexpr VDPConfigurer& set_window( | |
WindowHoriz dockh, uint8 width, WindowVerti dockv, uint8 height) { | |
return set_window(dockh, width).set_window(dockv, height); | |
} | |
// VRAM access stuff | |
constexpr VDPConfigurer& set_autoincrement_value(uint8 incr) { | |
return set_register(AutoIncrement{incr}); | |
} | |
// TODO: DMA fill, DMA copy, DMA transfer | |
}; | |
int main() { | |
constexpr const auto VDPoptions | |
= VDPConfigurer::begin_configure() | |
.set_base_mode() | |
.set_unused_registers() | |
.enable_vertical_interrupt() | |
.set_horizontal_interrupt_counter(HIntCounter{0}) | |
.enable_dma() | |
.set_resolution(HorizRes::H40, VertiRes::V28) | |
.set_scroll_mode( | |
HorizScroll::WHOLE_SCREEN, | |
VertiScroll::WHOLE_SCREEN) | |
.set_plane_a_location(0xC000) | |
.set_window_location(0xA000) | |
.set_plane_b_location(0xE000) | |
.set_sprite_table_location(0xF800) | |
.set_scroll_table_location(0xFC00) | |
.set_background_color(Palette::LINE0, 0) | |
.set_autoincrement_value(2) | |
.set_plane_size(SizeHoriz::H64, SizeVerti::V32) | |
.set_window( | |
WindowHoriz::DOCK_LEFT, 0, | |
WindowVerti::DOCK_ABOVE, 0); | |
VDPoptions.apply(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment