Skip to content

Instantly share code, notes, and snippets.

@flamewing
Last active December 25, 2021 18:53
Show Gist options
  • Save flamewing/795e3df3bc42f3576dbf894ef6ed7406 to your computer and use it in GitHub Desktop.
Save flamewing/795e3df3bc42f3576dbf894ef6ed7406 to your computer and use it in GitHub Desktop.
Experimental C++20 Mega Drive VDP library
// 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