2017-12-15 01:27:26 +00:00
|
|
|
//
|
|
|
|
// 9918Base.hpp
|
|
|
|
// Clock Signal
|
|
|
|
//
|
|
|
|
// Created by Thomas Harte on 14/12/2017.
|
2018-05-13 19:19:52 +00:00
|
|
|
// Copyright 2017 Thomas Harte. All rights reserved.
|
2017-12-15 01:27:26 +00:00
|
|
|
//
|
|
|
|
|
2024-01-17 04:34:46 +00:00
|
|
|
#pragma once
|
2017-12-15 01:27:26 +00:00
|
|
|
|
2023-01-01 19:20:45 +00:00
|
|
|
#include "ClockConverter.hpp"
|
2017-12-15 01:27:26 +00:00
|
|
|
|
2023-01-05 18:21:03 +00:00
|
|
|
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
|
|
|
#include "../../../Numeric/BitReverse.hpp"
|
|
|
|
#include "../../../Outputs/CRT/CRT.hpp"
|
|
|
|
|
2023-01-26 16:59:27 +00:00
|
|
|
#include "AccessEnums.hpp"
|
2023-02-12 17:58:46 +00:00
|
|
|
#include "LineBuffer.hpp"
|
2023-01-07 03:39:46 +00:00
|
|
|
#include "PersonalityTraits.hpp"
|
2023-02-12 17:58:46 +00:00
|
|
|
#include "Storage.hpp"
|
2023-01-26 16:59:27 +00:00
|
|
|
#include "YamahaCommands.hpp"
|
2023-01-07 03:39:46 +00:00
|
|
|
|
2023-01-01 02:50:57 +00:00
|
|
|
#include <array>
|
2018-10-12 23:50:48 +00:00
|
|
|
#include <cassert>
|
2017-12-15 01:27:26 +00:00
|
|
|
#include <cstdint>
|
2023-01-05 18:21:03 +00:00
|
|
|
#include <cstring>
|
2017-12-15 01:27:26 +00:00
|
|
|
#include <memory>
|
2018-10-26 03:12:41 +00:00
|
|
|
#include <vector>
|
2017-12-15 01:27:26 +00:00
|
|
|
|
2023-04-23 16:08:07 +00:00
|
|
|
namespace TI::TMS {
|
2018-09-18 02:59:16 +00:00
|
|
|
|
2023-01-05 18:18:10 +00:00
|
|
|
constexpr uint8_t StatusInterrupt = 0x80;
|
|
|
|
constexpr uint8_t StatusSpriteOverflow = 0x40;
|
|
|
|
|
|
|
|
constexpr int StatusSpriteCollisionShift = 5;
|
|
|
|
constexpr uint8_t StatusSpriteCollision = 0x20;
|
|
|
|
|
2023-01-19 03:23:19 +00:00
|
|
|
template <Personality personality> struct Base: public Storage<personality> {
|
2023-01-02 03:34:07 +00:00
|
|
|
Base();
|
|
|
|
|
2023-01-01 18:49:11 +00:00
|
|
|
static constexpr int output_lag = 11; // i.e. pixel output will occur 11 cycles
|
|
|
|
// after corresponding data read.
|
|
|
|
|
2024-11-30 20:53:58 +00:00
|
|
|
static constexpr uint32_t palette_pack(const uint8_t r, const uint8_t g, const uint8_t b) {
|
2023-01-02 03:34:07 +00:00
|
|
|
#if TARGET_RT_BIG_ENDIAN
|
|
|
|
return uint32_t((r << 24) | (g << 16) | (b << 8));
|
|
|
|
#else
|
|
|
|
return uint32_t((b << 16) | (g << 8) | r);
|
|
|
|
#endif
|
2023-01-01 18:49:11 +00:00
|
|
|
}
|
2018-10-02 03:03:17 +00:00
|
|
|
|
2023-01-01 18:49:11 +00:00
|
|
|
// The default TMS palette.
|
2023-01-30 02:17:00 +00:00
|
|
|
static constexpr std::array<uint32_t, 16> default_palette {
|
2023-01-01 18:49:11 +00:00
|
|
|
palette_pack(0, 0, 0),
|
|
|
|
palette_pack(0, 0, 0),
|
|
|
|
palette_pack(33, 200, 66),
|
|
|
|
palette_pack(94, 220, 120),
|
|
|
|
|
|
|
|
palette_pack(84, 85, 237),
|
|
|
|
palette_pack(125, 118, 252),
|
|
|
|
palette_pack(212, 82, 77),
|
|
|
|
palette_pack(66, 235, 245),
|
|
|
|
|
|
|
|
palette_pack(252, 85, 84),
|
|
|
|
palette_pack(255, 121, 120),
|
|
|
|
palette_pack(212, 193, 84),
|
|
|
|
palette_pack(230, 206, 128),
|
|
|
|
|
|
|
|
palette_pack(33, 176, 59),
|
|
|
|
palette_pack(201, 91, 186),
|
|
|
|
palette_pack(204, 204, 204),
|
|
|
|
palette_pack(255, 255, 255)
|
|
|
|
};
|
2023-01-30 02:17:00 +00:00
|
|
|
const std::array<uint32_t, 16> &palette() {
|
|
|
|
if constexpr (is_yamaha_vdp(personality)) {
|
2023-02-26 18:42:59 +00:00
|
|
|
return Storage<personality>::solid_background_ ? Storage<personality>::palette_ : Storage<personality>::background_palette_;
|
2023-01-30 02:17:00 +00:00
|
|
|
}
|
|
|
|
return default_palette;
|
|
|
|
}
|
2023-01-01 18:49:11 +00:00
|
|
|
|
|
|
|
Outputs::CRT::CRT crt_;
|
|
|
|
TVStandard tv_standard_ = TVStandard::NTSC;
|
2023-02-01 19:17:49 +00:00
|
|
|
using AddressT = typename Storage<personality>::AddressT;
|
|
|
|
|
2023-02-02 17:12:11 +00:00
|
|
|
/// Mutates @c target such that @c source replaces the @c length bits that currently start
|
|
|
|
/// at bit @c shift . Subsequently ensures @c target is constrained by the
|
2023-02-01 19:17:49 +00:00
|
|
|
/// applicable @c memory_mask.
|
2024-11-30 20:53:58 +00:00
|
|
|
template <int shift, int length = 8> void install_field(AddressT &target, const uint8_t source) {
|
2023-02-02 17:12:11 +00:00
|
|
|
static_assert(length > 0 && length <= 8);
|
|
|
|
constexpr auto source_mask = (1 << length) - 1;
|
2023-02-01 19:17:49 +00:00
|
|
|
constexpr auto mask = AddressT(~(source_mask << shift));
|
|
|
|
target = (
|
|
|
|
(target & mask) |
|
|
|
|
AddressT((source & source_mask) << shift)
|
|
|
|
) & memory_mask(personality);
|
|
|
|
}
|
2023-01-01 18:49:11 +00:00
|
|
|
|
2023-01-05 18:18:10 +00:00
|
|
|
// Personality-specific metrics and converters.
|
|
|
|
ClockConverter<personality> clock_converter_;
|
|
|
|
|
|
|
|
// This VDP's DRAM.
|
2023-01-01 18:49:11 +00:00
|
|
|
std::array<uint8_t, memory_size(personality)> ram_;
|
|
|
|
|
2023-01-05 18:18:10 +00:00
|
|
|
// State of the DRAM/CRAM-access mechanism.
|
2023-02-01 19:17:49 +00:00
|
|
|
AddressT ram_pointer_ = 0;
|
2023-01-01 18:49:11 +00:00
|
|
|
uint8_t read_ahead_buffer_ = 0;
|
|
|
|
MemoryAccess queued_access_ = MemoryAccess::None;
|
|
|
|
int minimum_access_column_ = 0;
|
2017-12-15 01:27:26 +00:00
|
|
|
|
2023-01-05 18:18:10 +00:00
|
|
|
// The main status register.
|
2023-01-01 18:49:11 +00:00
|
|
|
uint8_t status_ = 0;
|
|
|
|
|
|
|
|
// Current state of programmer input.
|
|
|
|
bool write_phase_ = false; // Determines whether the VDP is expecting the low or high byte of a write.
|
|
|
|
uint8_t low_write_ = 0; // Buffers the low byte of a write.
|
|
|
|
|
|
|
|
// Various programmable flags.
|
|
|
|
bool mode1_enable_ = false;
|
|
|
|
bool mode2_enable_ = false;
|
|
|
|
bool mode3_enable_ = false;
|
|
|
|
bool blank_display_ = false;
|
|
|
|
bool sprites_16x16_ = false;
|
|
|
|
bool sprites_magnified_ = false;
|
|
|
|
bool generate_interrupts_ = false;
|
2023-03-30 04:20:03 +00:00
|
|
|
uint8_t sprite_height_ = 8;
|
2023-01-01 18:49:11 +00:00
|
|
|
|
2023-01-05 18:18:10 +00:00
|
|
|
// Programmer-specified addresses.
|
2023-02-02 17:12:11 +00:00
|
|
|
//
|
|
|
|
// The TMS and descendants combine various parts of the address with AND operations,
|
|
|
|
// e.g. the fourth byte in the pattern name table will be at `pattern_name_address_ & 4`;
|
|
|
|
// ordinarily the difference between that and plain substitution is invisible because
|
|
|
|
// the programmer mostly can't set low-enough-order bits. That's not universally true
|
|
|
|
// though, so this implementation uses AND throughout.
|
|
|
|
//
|
|
|
|
// ... therefore, all programmer-specified addresses are seeded as all '1's. As and when
|
|
|
|
// actual addresses are specified, the relevant bits will be substituted in.
|
|
|
|
//
|
|
|
|
// Cf. install_field.
|
2023-02-01 19:17:49 +00:00
|
|
|
AddressT pattern_name_address_ = memory_mask(personality); // Address of the tile map.
|
|
|
|
AddressT colour_table_address_ = memory_mask(personality); // Address of the colour map (if applicable).
|
|
|
|
AddressT pattern_generator_table_address_ = memory_mask(personality); // Address of the tile contents.
|
|
|
|
AddressT sprite_attribute_table_address_ = memory_mask(personality); // Address of the sprite list.
|
|
|
|
AddressT sprite_generator_table_address_ = memory_mask(personality); // Address of the sprite contents.
|
2023-01-01 18:49:11 +00:00
|
|
|
|
2023-01-05 18:18:10 +00:00
|
|
|
// Default colours.
|
2023-01-01 18:49:11 +00:00
|
|
|
uint8_t text_colour_ = 0;
|
|
|
|
uint8_t background_colour_ = 0;
|
|
|
|
|
|
|
|
// Internal mechanisms for position tracking.
|
|
|
|
int latched_column_ = 0;
|
|
|
|
|
2023-01-07 18:10:51 +00:00
|
|
|
// A struct to contain timing information that is a function of the current mode.
|
2023-01-01 18:49:11 +00:00
|
|
|
struct {
|
|
|
|
/*
|
|
|
|
Vertical layout:
|
|
|
|
|
|
|
|
Lines 0 to [pixel_lines]: standard data fetch and drawing will occur.
|
|
|
|
... to [first_vsync_line]: refresh fetches will occur and border will be output.
|
|
|
|
.. to [2.5 or 3 lines later]: vertical sync is output.
|
|
|
|
... to [total lines - 1]: refresh fetches will occur and border will be output.
|
|
|
|
... for one line: standard data fetch will occur, without drawing.
|
|
|
|
*/
|
|
|
|
int total_lines = 262;
|
|
|
|
int pixel_lines = 192;
|
|
|
|
int first_vsync_line = 227;
|
|
|
|
|
|
|
|
// Maximum number of sprite slots to populate;
|
|
|
|
// if sprites beyond this number should be visible
|
|
|
|
// then the appropriate status information will be set.
|
|
|
|
int maximum_visible_sprites = 4;
|
|
|
|
|
|
|
|
// Set the position, in cycles, of the two interrupts,
|
|
|
|
// within a line.
|
2023-04-27 02:49:46 +00:00
|
|
|
//
|
|
|
|
// TODO: redetermine where this number came from.
|
2018-10-02 03:03:17 +00:00
|
|
|
struct {
|
2023-04-26 03:16:21 +00:00
|
|
|
int column = 313;
|
|
|
|
int row = 192;
|
2023-01-01 18:49:11 +00:00
|
|
|
} end_of_frame_interrupt_position;
|
|
|
|
int line_interrupt_position = -1;
|
|
|
|
|
|
|
|
// Enables or disabled the recognition of the sprite
|
|
|
|
// list terminator, and sets the terminator value.
|
|
|
|
bool allow_sprite_terminator = true;
|
2023-03-07 23:19:08 +00:00
|
|
|
uint8_t sprite_terminator(ScreenMode mode) {
|
|
|
|
switch(mode) {
|
|
|
|
default: return 0xd0;
|
|
|
|
case ScreenMode::YamahaGraphics3:
|
|
|
|
case ScreenMode::YamahaGraphics4:
|
|
|
|
case ScreenMode::YamahaGraphics5:
|
|
|
|
case ScreenMode::YamahaGraphics6:
|
|
|
|
case ScreenMode::YamahaGraphics7:
|
|
|
|
return 0xd8;
|
|
|
|
}
|
|
|
|
}
|
2023-01-01 18:49:11 +00:00
|
|
|
} mode_timing_;
|
|
|
|
|
2023-01-19 20:09:16 +00:00
|
|
|
uint8_t line_interrupt_target_ = 0xff;
|
|
|
|
uint8_t line_interrupt_counter_ = 0;
|
2023-01-01 18:49:11 +00:00
|
|
|
bool enable_line_interrupts_ = false;
|
|
|
|
bool line_interrupt_pending_ = false;
|
2023-03-01 03:28:14 +00:00
|
|
|
bool vertical_active_ = false;
|
2023-01-01 18:49:11 +00:00
|
|
|
|
2023-02-02 17:03:33 +00:00
|
|
|
ScreenMode screen_mode_, underlying_mode_;
|
2023-03-30 04:20:03 +00:00
|
|
|
|
|
|
|
using LineBufferArray = std::array<LineBuffer, 313>;
|
|
|
|
LineBufferArray line_buffers_;
|
|
|
|
LineBufferArray::iterator fetch_line_buffer_;
|
|
|
|
LineBufferArray::iterator draw_line_buffer_;
|
|
|
|
void advance(LineBufferArray::iterator &iterator) {
|
|
|
|
++iterator;
|
|
|
|
if(iterator == line_buffers_.end()) {
|
|
|
|
iterator = line_buffers_.begin();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
using SpriteBufferArray = std::array<SpriteBuffer, 313>;
|
|
|
|
SpriteBufferArray sprite_buffers_;
|
|
|
|
SpriteBufferArray::iterator fetch_sprite_buffer_;
|
2023-04-11 03:03:39 +00:00
|
|
|
SpriteBuffer *fetched_sprites_ = nullptr;
|
2023-03-30 04:20:03 +00:00
|
|
|
void advance(SpriteBufferArray::iterator &iterator) {
|
|
|
|
++iterator;
|
|
|
|
if(iterator == sprite_buffers_.end()) {
|
|
|
|
iterator = sprite_buffers_.begin();
|
|
|
|
}
|
|
|
|
}
|
2023-04-11 03:03:39 +00:00
|
|
|
void regress(SpriteBufferArray::iterator &iterator) {
|
|
|
|
if(iterator == sprite_buffers_.begin()) {
|
|
|
|
iterator = sprite_buffers_.end();
|
|
|
|
}
|
|
|
|
--iterator;
|
|
|
|
}
|
2023-03-30 04:20:03 +00:00
|
|
|
|
2023-02-14 03:20:47 +00:00
|
|
|
AddressT tile_offset_ = 0;
|
|
|
|
uint8_t name_[4]{};
|
2023-03-30 04:20:03 +00:00
|
|
|
void posit_sprite(int sprite_number, int sprite_y, uint8_t screen_row);
|
2023-01-01 18:49:11 +00:00
|
|
|
|
|
|
|
// There is a delay between reading into the line buffer and outputting from there to the screen. That delay
|
|
|
|
// is observeable because reading time affects availability of memory accesses and therefore time in which
|
|
|
|
// to update sprites and tiles, but writing time affects when the palette is used and when the collision flag
|
|
|
|
// may end up being set. So the two processes are slightly decoupled. The end of reading one line may overlap
|
|
|
|
// with the beginning of writing the next, hence the two separate line buffers.
|
2023-01-21 01:29:15 +00:00
|
|
|
LineBufferPointer output_pointer_, fetch_pointer_;
|
2023-01-01 18:49:11 +00:00
|
|
|
|
2023-01-21 03:29:49 +00:00
|
|
|
int fetch_line() const;
|
|
|
|
bool is_horizontal_blank() const;
|
2023-02-02 03:25:00 +00:00
|
|
|
VerticalState vertical_state() const;
|
2023-01-21 03:29:49 +00:00
|
|
|
|
|
|
|
int masked_address(int address) const;
|
2023-01-18 17:36:57 +00:00
|
|
|
void write_vram(uint8_t);
|
|
|
|
void write_register(uint8_t);
|
|
|
|
void write_palette(uint8_t);
|
|
|
|
void write_register_indirect(uint8_t);
|
|
|
|
uint8_t read_vram();
|
|
|
|
uint8_t read_register();
|
|
|
|
|
2023-01-21 04:14:57 +00:00
|
|
|
void commit_register(int reg, uint8_t value);
|
|
|
|
|
2023-02-02 17:03:33 +00:00
|
|
|
template <bool check_blank> ScreenMode current_screen_mode() const {
|
|
|
|
if(check_blank && blank_display_) {
|
2023-01-07 19:34:33 +00:00
|
|
|
return ScreenMode::Blank;
|
2023-01-01 18:49:11 +00:00
|
|
|
}
|
2018-10-26 03:12:03 +00:00
|
|
|
|
2023-01-01 18:49:11 +00:00
|
|
|
if constexpr (is_sega_vdp(personality)) {
|
2023-01-19 20:09:16 +00:00
|
|
|
if(Storage<personality>::mode4_enable_) {
|
2023-01-07 19:34:33 +00:00
|
|
|
return ScreenMode::SMSMode4;
|
2018-09-28 01:22:57 +00:00
|
|
|
}
|
2023-01-01 18:49:11 +00:00
|
|
|
}
|
2018-09-28 01:22:57 +00:00
|
|
|
|
2023-01-21 19:35:26 +00:00
|
|
|
if constexpr (is_yamaha_vdp(personality)) {
|
|
|
|
switch(Storage<personality>::mode_) {
|
|
|
|
case 0b00001: return ScreenMode::Text;
|
|
|
|
case 0b01001: return ScreenMode::YamahaText80;
|
|
|
|
case 0b00010: return ScreenMode::MultiColour;
|
|
|
|
case 0b00000: return ScreenMode::YamahaGraphics1;
|
|
|
|
case 0b00100: return ScreenMode::YamahaGraphics2;
|
|
|
|
case 0b01000: return ScreenMode::YamahaGraphics3;
|
|
|
|
case 0b01100: return ScreenMode::YamahaGraphics4;
|
|
|
|
case 0b10000: return ScreenMode::YamahaGraphics5;
|
|
|
|
case 0b10100: return ScreenMode::YamahaGraphics6;
|
|
|
|
case 0b11100: return ScreenMode::YamahaGraphics7;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-01 18:49:11 +00:00
|
|
|
if(!mode1_enable_ && !mode2_enable_ && !mode3_enable_) {
|
2023-01-07 19:34:33 +00:00
|
|
|
return ScreenMode::ColouredText;
|
2023-01-01 18:49:11 +00:00
|
|
|
}
|
2018-09-28 01:22:57 +00:00
|
|
|
|
2023-01-01 18:49:11 +00:00
|
|
|
if(mode1_enable_ && !mode2_enable_ && !mode3_enable_) {
|
2023-01-07 19:34:33 +00:00
|
|
|
return ScreenMode::Text;
|
2023-01-01 18:49:11 +00:00
|
|
|
}
|
2018-09-28 01:22:57 +00:00
|
|
|
|
2023-01-01 18:49:11 +00:00
|
|
|
if(!mode1_enable_ && mode2_enable_ && !mode3_enable_) {
|
2023-01-07 19:34:33 +00:00
|
|
|
return ScreenMode::Graphics;
|
2023-01-01 18:49:11 +00:00
|
|
|
}
|
2018-09-28 01:22:57 +00:00
|
|
|
|
2023-01-01 18:49:11 +00:00
|
|
|
if(!mode1_enable_ && !mode2_enable_ && mode3_enable_) {
|
2023-01-07 19:34:33 +00:00
|
|
|
return ScreenMode::MultiColour;
|
2023-01-01 18:49:11 +00:00
|
|
|
}
|
2018-09-28 01:22:57 +00:00
|
|
|
|
2023-01-01 18:49:11 +00:00
|
|
|
// TODO: undocumented TMS modes.
|
2023-01-07 19:34:33 +00:00
|
|
|
return ScreenMode::Blank;
|
2023-01-01 18:49:11 +00:00
|
|
|
}
|
2018-09-28 01:22:57 +00:00
|
|
|
|
2024-11-30 20:53:58 +00:00
|
|
|
static AddressT rotate(const AddressT address) {
|
2023-03-04 04:06:52 +00:00
|
|
|
return AddressT((address >> 1) | (address << 16)) & memory_mask(personality);
|
|
|
|
}
|
|
|
|
|
2024-11-30 20:53:58 +00:00
|
|
|
AddressT command_address(const Vector location, const bool expansion) const {
|
2023-01-29 23:28:49 +00:00
|
|
|
if constexpr (is_yamaha_vdp(personality)) {
|
2023-03-08 23:27:59 +00:00
|
|
|
switch(this->underlying_mode_) {
|
2023-01-29 23:28:49 +00:00
|
|
|
default:
|
|
|
|
case ScreenMode::YamahaGraphics4: // 256 pixels @ 4bpp
|
2023-02-01 19:17:49 +00:00
|
|
|
return AddressT(
|
2023-03-20 03:00:41 +00:00
|
|
|
((location.v[0] >> 1) & 127) +
|
2023-02-03 02:16:24 +00:00
|
|
|
(location.v[1] << 7)
|
2023-02-01 19:17:49 +00:00
|
|
|
);
|
2023-01-29 23:28:49 +00:00
|
|
|
|
|
|
|
case ScreenMode::YamahaGraphics5: // 512 pixels @ 2bpp
|
2023-02-01 19:17:49 +00:00
|
|
|
return AddressT(
|
2023-03-20 03:00:41 +00:00
|
|
|
((location.v[0] >> 2) & 127) +
|
2023-02-03 02:16:24 +00:00
|
|
|
(location.v[1] << 7)
|
2023-02-01 19:17:49 +00:00
|
|
|
);
|
2023-01-29 23:28:49 +00:00
|
|
|
|
2023-03-08 23:27:59 +00:00
|
|
|
case ScreenMode::YamahaGraphics6: { // 512 pixels @ 4bpp
|
|
|
|
const auto linear_address =
|
|
|
|
AddressT(
|
2023-03-20 03:00:41 +00:00
|
|
|
((location.v[0] >> 1) & 255) +
|
2023-03-08 23:27:59 +00:00
|
|
|
(location.v[1] << 8)
|
|
|
|
);
|
|
|
|
return expansion ? linear_address : rotate(linear_address);
|
|
|
|
}
|
2023-01-29 23:28:49 +00:00
|
|
|
|
2023-03-08 23:27:59 +00:00
|
|
|
case ScreenMode::YamahaGraphics7: { // 256 pixels @ 8bpp
|
|
|
|
const auto linear_address =
|
|
|
|
AddressT(
|
2023-03-20 03:00:41 +00:00
|
|
|
((location.v[0] >> 0) & 255) +
|
2023-03-08 23:27:59 +00:00
|
|
|
(location.v[1] << 8)
|
|
|
|
);
|
|
|
|
return expansion ? linear_address : rotate(linear_address);
|
|
|
|
}
|
2023-01-29 23:28:49 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-30 20:53:58 +00:00
|
|
|
uint8_t extract_colour(const uint8_t byte, const Vector location) const {
|
2023-02-05 02:23:41 +00:00
|
|
|
switch(this->screen_mode_) {
|
|
|
|
default:
|
|
|
|
case ScreenMode::YamahaGraphics4: // 256 pixels @ 4bpp
|
|
|
|
case ScreenMode::YamahaGraphics6: // 512 pixels @ 4bpp
|
|
|
|
return (byte >> (((location.v[0] & 1) ^ 1) << 2)) & 0xf;
|
|
|
|
|
|
|
|
case ScreenMode::YamahaGraphics5: // 512 pixels @ 2bpp
|
|
|
|
return (byte >> (((location.v[0] & 3) ^ 3) << 1)) & 0x3;
|
|
|
|
|
|
|
|
case ScreenMode::YamahaGraphics7: // 256 pixels @ 8bpp
|
|
|
|
return byte;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-30 20:53:58 +00:00
|
|
|
std::pair<uint8_t, uint8_t> command_colour_mask(const Vector location) const {
|
2023-01-29 23:28:49 +00:00
|
|
|
if constexpr (is_yamaha_vdp(personality)) {
|
2023-02-05 02:23:41 +00:00
|
|
|
auto &context = Storage<personality>::command_context_;
|
|
|
|
auto colour = context.latched_colour.has_value() ? context.latched_colour : context.colour;
|
|
|
|
|
2023-01-29 23:28:49 +00:00
|
|
|
switch(this->screen_mode_) {
|
|
|
|
default:
|
|
|
|
case ScreenMode::YamahaGraphics4: // 256 pixels @ 4bpp
|
|
|
|
case ScreenMode::YamahaGraphics6: // 512 pixels @ 4bpp
|
|
|
|
return
|
|
|
|
std::make_pair(
|
2023-02-03 02:16:24 +00:00
|
|
|
0xf0 >> ((location.v[0] & 1) << 2),
|
2023-02-05 02:23:41 +00:00
|
|
|
colour.colour4bpp
|
2023-01-29 23:28:49 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
case ScreenMode::YamahaGraphics5: // 512 pixels @ 2bpp
|
|
|
|
return
|
|
|
|
std::make_pair(
|
2023-02-03 02:16:24 +00:00
|
|
|
0xc0 >> ((location.v[0] & 3) << 1),
|
2023-02-05 02:23:41 +00:00
|
|
|
colour.colour2bpp
|
2023-01-29 23:28:49 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
case ScreenMode::YamahaGraphics7: // 256 pixels @ 8bpp
|
|
|
|
return
|
|
|
|
std::make_pair(
|
|
|
|
0xff,
|
2023-02-05 02:23:41 +00:00
|
|
|
colour.colour
|
2023-01-29 23:28:49 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return std::make_pair(0, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-30 20:53:58 +00:00
|
|
|
void do_external_slot(const int access_column) {
|
2023-01-01 18:49:11 +00:00
|
|
|
// Don't do anything if the required time for the access to become executable
|
|
|
|
// has yet to pass.
|
2023-01-22 03:33:26 +00:00
|
|
|
if(queued_access_ == MemoryAccess::None || access_column < minimum_access_column_) {
|
2023-01-28 03:20:36 +00:00
|
|
|
if constexpr (is_yamaha_vdp(personality)) {
|
2023-01-28 16:55:12 +00:00
|
|
|
using CommandStep = typename Storage<personality>::CommandStep;
|
|
|
|
|
|
|
|
if(
|
|
|
|
Storage<personality>::next_command_step_ == CommandStep::None ||
|
|
|
|
access_column < Storage<personality>::minimum_command_column_
|
|
|
|
) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-02-03 02:16:24 +00:00
|
|
|
auto &context = Storage<personality>::command_context_;
|
2023-03-07 23:19:08 +00:00
|
|
|
const uint8_t *const source = (context.arguments & 0x10) ? Storage<personality>::expansion_ram_.data() : ram_.data();
|
2023-03-22 00:05:34 +00:00
|
|
|
const AddressT source_mask = (context.arguments & 0x10) ? 0xfff : 0x1ffff;
|
2023-03-07 23:19:08 +00:00
|
|
|
uint8_t *const destination = (context.arguments & 0x20) ? Storage<personality>::expansion_ram_.data() : ram_.data();
|
2023-03-22 00:05:34 +00:00
|
|
|
const AddressT destination_mask = (context.arguments & 0x20) ? 0xfff : 0x1ffff;
|
2023-01-28 16:55:12 +00:00
|
|
|
switch(Storage<personality>::next_command_step_) {
|
|
|
|
// Duplicative, but keeps the compiler happy.
|
|
|
|
case CommandStep::None:
|
|
|
|
break;
|
|
|
|
|
2023-03-14 02:51:01 +00:00
|
|
|
case CommandStep::CopySourcePixelToStatus:
|
2023-03-20 03:00:41 +00:00
|
|
|
Storage<personality>::colour_status_ =
|
|
|
|
extract_colour(
|
|
|
|
source[command_address(context.source, context.arguments & 0x10) & source_mask],
|
|
|
|
context.source
|
|
|
|
);
|
2023-03-14 02:51:01 +00:00
|
|
|
|
2023-03-19 03:07:33 +00:00
|
|
|
Storage<personality>::command_->advance();
|
2023-03-14 02:51:01 +00:00
|
|
|
Storage<personality>::update_command_step(access_column);
|
|
|
|
break;
|
|
|
|
|
2023-02-04 16:43:06 +00:00
|
|
|
case CommandStep::ReadSourcePixel:
|
2023-03-20 03:00:41 +00:00
|
|
|
context.latched_colour.set(
|
|
|
|
extract_colour(
|
|
|
|
source[command_address(context.source, context.arguments & 0x10)] & source_mask,
|
|
|
|
context.source)
|
|
|
|
);
|
2023-02-04 16:43:06 +00:00
|
|
|
|
|
|
|
Storage<personality>::minimum_command_column_ = access_column + 32;
|
|
|
|
Storage<personality>::next_command_step_ = CommandStep::ReadDestinationPixel;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CommandStep::ReadDestinationPixel:
|
2023-03-20 03:00:41 +00:00
|
|
|
Storage<personality>::command_latch_ =
|
|
|
|
source[command_address(context.destination, context.arguments & 0x20) & source_mask];
|
2023-01-28 16:55:12 +00:00
|
|
|
|
|
|
|
Storage<personality>::minimum_command_column_ = access_column + 24;
|
|
|
|
Storage<personality>::next_command_step_ = CommandStep::WritePixel;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CommandStep::WritePixel: {
|
2023-02-03 02:16:24 +00:00
|
|
|
const auto [mask, unmasked_colour] = command_colour_mask(context.destination);
|
2023-03-20 03:00:41 +00:00
|
|
|
const auto address = command_address(context.destination, context.arguments & 0x20) & destination_mask;
|
2023-01-29 23:28:49 +00:00
|
|
|
const uint8_t colour = unmasked_colour & mask;
|
2023-02-05 02:23:41 +00:00
|
|
|
context.latched_colour.reset();
|
2023-01-29 23:28:49 +00:00
|
|
|
|
|
|
|
using LogicalOperation = CommandContext::LogicalOperation;
|
2023-02-03 02:16:24 +00:00
|
|
|
if(!context.test_source || colour) {
|
|
|
|
switch(context.pixel_operation) {
|
2023-01-29 23:28:49 +00:00
|
|
|
default:
|
|
|
|
case LogicalOperation::Copy:
|
|
|
|
Storage<personality>::command_latch_ &= ~mask;
|
|
|
|
Storage<personality>::command_latch_ |= colour;
|
|
|
|
break;
|
|
|
|
case LogicalOperation::And:
|
|
|
|
Storage<personality>::command_latch_ &= ~mask | colour;
|
|
|
|
break;
|
|
|
|
case LogicalOperation::Or:
|
|
|
|
Storage<personality>::command_latch_ |= colour;
|
|
|
|
break;
|
|
|
|
case LogicalOperation::Xor:
|
|
|
|
Storage<personality>::command_latch_ ^= colour;
|
|
|
|
break;
|
|
|
|
case LogicalOperation::Not:
|
|
|
|
Storage<personality>::command_latch_ &= ~mask;
|
|
|
|
Storage<personality>::command_latch_ |= colour ^ mask;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2023-01-28 16:55:12 +00:00
|
|
|
|
2023-02-21 03:32:36 +00:00
|
|
|
destination[address] = Storage<personality>::command_latch_;
|
2023-01-28 16:55:12 +00:00
|
|
|
|
2023-03-19 03:07:33 +00:00
|
|
|
Storage<personality>::command_->advance();
|
2023-01-29 02:43:14 +00:00
|
|
|
Storage<personality>::update_command_step(access_column);
|
2023-01-28 16:55:12 +00:00
|
|
|
} break;
|
2023-01-31 18:35:39 +00:00
|
|
|
|
2023-03-09 04:12:02 +00:00
|
|
|
case CommandStep::ReadSourceByte: {
|
|
|
|
Vector source_vector = context.source;
|
|
|
|
if(Storage<personality>::command_->y_only) {
|
|
|
|
source_vector.v[0] = context.destination.v[0];
|
|
|
|
}
|
2023-03-20 03:00:41 +00:00
|
|
|
context.latched_colour.set(source[command_address(source_vector, context.arguments & 0x10) & source_mask]);
|
2023-02-04 16:43:06 +00:00
|
|
|
|
|
|
|
Storage<personality>::minimum_command_column_ = access_column + 24;
|
|
|
|
Storage<personality>::next_command_step_ = CommandStep::WriteByte;
|
2023-03-09 04:12:02 +00:00
|
|
|
} break;
|
2023-02-04 16:43:06 +00:00
|
|
|
|
2023-01-31 18:35:39 +00:00
|
|
|
case CommandStep::WriteByte:
|
2023-03-20 03:00:41 +00:00
|
|
|
destination[command_address(context.destination, context.arguments & 0x20) & destination_mask]
|
|
|
|
= context.latched_colour.has_value() ? context.latched_colour.colour : context.colour.colour;
|
2023-02-05 02:23:41 +00:00
|
|
|
context.latched_colour.reset();
|
|
|
|
|
2023-03-19 03:07:33 +00:00
|
|
|
Storage<personality>::command_->advance();
|
2023-01-31 18:35:39 +00:00
|
|
|
Storage<personality>::update_command_step(access_column);
|
|
|
|
break;
|
2023-01-28 16:55:12 +00:00
|
|
|
}
|
2023-01-28 03:20:36 +00:00
|
|
|
}
|
|
|
|
|
2023-01-01 18:49:11 +00:00
|
|
|
return;
|
2018-09-28 01:22:57 +00:00
|
|
|
}
|
|
|
|
|
2023-02-21 03:27:30 +00:00
|
|
|
// Copy and mutate the RAM pointer.
|
2023-02-01 19:17:49 +00:00
|
|
|
AddressT address = ram_pointer_;
|
2023-01-22 03:33:26 +00:00
|
|
|
++ram_pointer_;
|
|
|
|
|
2023-02-21 03:27:30 +00:00
|
|
|
// Determine the relevant RAM and its mask.
|
|
|
|
uint8_t *ram = ram_.data();
|
|
|
|
AddressT mask = memory_mask(personality);
|
|
|
|
|
2023-01-22 03:33:26 +00:00
|
|
|
if constexpr (is_yamaha_vdp(personality)) {
|
2023-02-07 03:16:31 +00:00
|
|
|
// The Yamaha increments only 14 bits of the address in TMS-compatible modes.
|
|
|
|
if(this->underlying_mode_ < ScreenMode::YamahaText80) {
|
|
|
|
ram_pointer_ = (ram_pointer_ & 0x3fff) | (address & AddressT(~0x3fff));
|
|
|
|
}
|
|
|
|
|
2023-02-02 17:03:33 +00:00
|
|
|
if(this->underlying_mode_ == ScreenMode::YamahaGraphics6 || this->underlying_mode_ == ScreenMode::YamahaGraphics7) {
|
2023-01-22 03:33:26 +00:00
|
|
|
// Rotate address one to the right as the hardware accesses
|
|
|
|
// the underlying banks of memory alternately but presents
|
|
|
|
// them as if linear.
|
2023-03-04 04:06:52 +00:00
|
|
|
address = rotate(address);
|
2023-01-22 03:33:26 +00:00
|
|
|
}
|
2023-02-21 03:27:30 +00:00
|
|
|
|
|
|
|
// Also check whether expansion RAM is the true target here.
|
|
|
|
if(Storage<personality>::command_context_.arguments & 0x40) {
|
|
|
|
ram = Storage<personality>::expansion_ram_.data();
|
|
|
|
mask = AddressT(Storage<personality>::expansion_ram_.size() - 1);
|
|
|
|
}
|
2023-01-22 03:33:26 +00:00
|
|
|
}
|
|
|
|
|
2023-01-01 18:49:11 +00:00
|
|
|
switch(queued_access_) {
|
2023-01-22 03:33:26 +00:00
|
|
|
default: break;
|
2023-01-01 18:49:11 +00:00
|
|
|
|
|
|
|
case MemoryAccess::Write:
|
2023-01-07 14:10:41 +00:00
|
|
|
if constexpr (is_sega_vdp(personality)) {
|
2023-01-19 19:09:31 +00:00
|
|
|
if(Storage<personality>::cram_is_selected_) {
|
2023-01-07 14:10:41 +00:00
|
|
|
// Adjust the palette. In a Master System blue has a slightly different
|
|
|
|
// scale; cf. https://www.retrorgb.com/sega-master-system-non-linear-blue-channel-findings.html
|
|
|
|
constexpr uint8_t rg_scale[] = {0, 85, 170, 255};
|
|
|
|
constexpr uint8_t b_scale[] = {0, 104, 170, 255};
|
2023-01-22 03:33:26 +00:00
|
|
|
Storage<personality>::colour_ram_[address & 0x1f] = palette_pack(
|
2023-01-07 14:10:41 +00:00
|
|
|
rg_scale[(read_ahead_buffer_ >> 0) & 3],
|
|
|
|
rg_scale[(read_ahead_buffer_ >> 2) & 3],
|
|
|
|
b_scale[(read_ahead_buffer_ >> 4) & 3]
|
|
|
|
);
|
|
|
|
|
|
|
|
// Schedule a CRAM dot; this is scheduled for wherever it should appear
|
|
|
|
// on screen. So it's wherever the output stream would be now. Which
|
|
|
|
// is output_lag cycles ago from the point of view of the input stream.
|
2023-01-19 19:09:31 +00:00
|
|
|
auto &dot = Storage<personality>::upcoming_cram_dots_.emplace_back();
|
2023-01-21 01:29:15 +00:00
|
|
|
dot.location.column = fetch_pointer_.column - output_lag;
|
|
|
|
dot.location.row = fetch_pointer_.row;
|
2023-01-07 14:10:41 +00:00
|
|
|
|
|
|
|
// Handle before this row conditionally; then handle after (or, more realistically,
|
|
|
|
// exactly at the end of) naturally.
|
|
|
|
if(dot.location.column < 0) {
|
|
|
|
--dot.location.row;
|
|
|
|
dot.location.column += 342;
|
|
|
|
}
|
|
|
|
dot.location.row += dot.location.column / 342;
|
|
|
|
dot.location.column %= 342;
|
|
|
|
|
2023-01-22 03:33:26 +00:00
|
|
|
dot.value = Storage<personality>::colour_ram_[address & 0x1f];
|
2023-01-07 14:10:41 +00:00
|
|
|
break;
|
2018-10-02 03:03:17 +00:00
|
|
|
}
|
2023-01-01 18:49:11 +00:00
|
|
|
}
|
2023-02-21 03:27:30 +00:00
|
|
|
ram[address & mask] = read_ahead_buffer_;
|
2023-01-01 18:49:11 +00:00
|
|
|
break;
|
|
|
|
case MemoryAccess::Read:
|
2023-02-21 03:27:30 +00:00
|
|
|
read_ahead_buffer_ = ram[address & mask];
|
2023-01-01 18:49:11 +00:00
|
|
|
break;
|
2018-09-28 02:33:41 +00:00
|
|
|
}
|
2023-01-01 18:49:11 +00:00
|
|
|
queued_access_ = MemoryAccess::None;
|
|
|
|
}
|
2018-09-28 02:33:41 +00:00
|
|
|
|
2023-02-04 04:06:27 +00:00
|
|
|
/// Helper for TMS dispatches; contains a switch statement with cases 0 to 170, each of the form:
|
|
|
|
///
|
2023-05-12 18:14:45 +00:00
|
|
|
/// if constexpr (use_end && end == n) return; [[fallthrough]]; case n: fetcher.fetch<n>();
|
2023-02-04 04:06:27 +00:00
|
|
|
///
|
|
|
|
/// i.e. it provides standard glue to enter a fetch sequence at any point, while the fetches themselves are templated on the cycle
|
|
|
|
/// at which they appear for neater expression.
|
2024-11-30 20:53:58 +00:00
|
|
|
template<bool use_end, typename Fetcher> void dispatch(Fetcher &, int start, int end);
|
2023-02-04 04:06:27 +00:00
|
|
|
|
2023-01-05 18:18:10 +00:00
|
|
|
// Various fetchers.
|
2023-03-30 04:20:03 +00:00
|
|
|
template<bool use_end> void fetch_tms_refresh(uint8_t y, int start, int end);
|
|
|
|
template<bool use_end> void fetch_tms_text(uint8_t y, int start, int end);
|
|
|
|
template<bool use_end> void fetch_tms_character(uint8_t y, int start, int end);
|
2023-01-02 03:44:06 +00:00
|
|
|
|
2023-03-30 04:20:03 +00:00
|
|
|
template<bool use_end> void fetch_yamaha(uint8_t y, int start, int end);
|
|
|
|
template<ScreenMode> void fetch_yamaha(uint8_t y, int end);
|
2023-01-02 03:44:06 +00:00
|
|
|
|
2023-03-30 04:20:03 +00:00
|
|
|
template<bool use_end> void fetch_sms(uint8_t y, int start, int end);
|
2018-09-23 19:58:23 +00:00
|
|
|
|
2023-01-05 18:18:10 +00:00
|
|
|
// A helper function to output the current border colour for
|
|
|
|
// the number of cycles supplied.
|
|
|
|
void output_border(int cycles, uint32_t cram_dot);
|
|
|
|
|
|
|
|
// Output serialisation state.
|
2023-01-01 18:49:11 +00:00
|
|
|
uint32_t *pixel_target_ = nullptr, *pixel_origin_ = nullptr;
|
|
|
|
bool asked_for_write_area_ = false;
|
2023-01-05 18:18:10 +00:00
|
|
|
|
|
|
|
// Output serialisers.
|
2023-02-22 03:00:00 +00:00
|
|
|
template <SpriteMode mode = SpriteMode::Mode1> void draw_tms_character(int start, int end);
|
2023-02-16 01:18:56 +00:00
|
|
|
template <bool apply_blink> void draw_tms_text(int start, int end);
|
2023-01-01 18:49:11 +00:00
|
|
|
void draw_sms(int start, int end, uint32_t cram_dot);
|
2023-01-25 04:07:29 +00:00
|
|
|
|
2023-03-30 04:20:03 +00:00
|
|
|
template<ScreenMode mode> void draw_yamaha(uint8_t y, int start, int end);
|
|
|
|
void draw_yamaha(uint8_t y, int start, int end);
|
2023-02-22 03:00:00 +00:00
|
|
|
|
2024-11-30 20:53:58 +00:00
|
|
|
template <SpriteMode mode, bool double_width>
|
|
|
|
void draw_sprites(
|
|
|
|
uint8_t y,
|
|
|
|
int start,
|
|
|
|
int end,
|
|
|
|
const std::array<uint32_t, 16> &palette,
|
|
|
|
int *colour_buffer = nullptr
|
|
|
|
);
|
2017-12-15 01:27:26 +00:00
|
|
|
};
|
|
|
|
|
2023-05-12 17:46:35 +00:00
|
|
|
}
|
|
|
|
|
2023-01-02 03:26:50 +00:00
|
|
|
#include "Fetch.hpp"
|
2023-01-05 18:18:10 +00:00
|
|
|
#include "Draw.hpp"
|