1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-30 19:30:17 +00:00
CLK/Machines/Atari2600/TIA.hpp

334 lines
11 KiB
C++
Raw Normal View History

//
// TIA.hpp
// Clock Signal
//
// Created by Thomas Harte on 28/01/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef TIA_hpp
#define TIA_hpp
#include <cstdint>
#include "../CRTMachine.hpp"
namespace Atari2600 {
class TIA {
public:
TIA();
// The supplied hook is for unit testing only; if instantiated with a line_end_function then it will
// be called with the latest collision buffer upon the conclusion of each line. What's a collision
// buffer? It's an implementation detail. If you're not writing a unit test, leave it alone.
TIA(std::function<void(uint8_t *output_buffer)> line_end_function);
enum class OutputMode {
NTSC, PAL
};
/*!
2017-07-25 01:48:34 +00:00
Advances the TIA by @c cycles. Any queued setters take effect in the first cycle performed.
*/
void run_for(const Cycles cycles);
void set_output_mode(OutputMode output_mode);
void set_sync(bool sync);
void set_blank(bool blank);
void reset_horizontal_counter(); // Reset is delayed by four cycles.
/*!
@returns the number of cycles between (current TIA time) + from_offset to the current or
next horizontal blanking period. Returns numbers in the range [0, 227].
*/
int get_cycles_until_horizontal_blank(const Cycles from_offset);
void set_background_colour(uint8_t colour);
void set_playfield(uint16_t offset, uint8_t value);
void set_playfield_control_and_ball_size(uint8_t value);
void set_playfield_ball_colour(uint8_t colour);
void set_player_number_and_size(int player, uint8_t value);
void set_player_graphic(int player, uint8_t value);
void set_player_reflected(int player, bool reflected);
void set_player_delay(int player, bool delay);
void set_player_position(int player);
void set_player_motion(int player, uint8_t motion);
void set_player_missile_colour(int player, uint8_t colour);
void set_missile_enable(int missile, bool enabled);
void set_missile_position(int missile);
void set_missile_position_to_player(int missile, bool lock);
void set_missile_motion(int missile, uint8_t motion);
void set_ball_enable(bool enabled);
void set_ball_delay(bool delay);
void set_ball_position();
void set_ball_motion(uint8_t motion);
void move();
void clear_motion();
uint8_t get_collision_flags(int offset);
void clear_collision_flags();
virtual std::shared_ptr<Outputs::CRT::CRT> get_crt() { return crt_; }
private:
2017-02-22 12:14:30 +00:00
TIA(bool create_crt);
std::shared_ptr<Outputs::CRT::CRT> crt_;
std::function<void(uint8_t *output_buffer)> line_end_function_;
// the master counter; counts from 0 to 228 with all visible pixels being in the final 160
int horizontal_counter_;
// contains flags to indicate whether sync or blank are currently active
int output_mode_;
// keeps track of the target pixel buffer for this line and when it was acquired, and a corresponding collision buffer
alignas(alignof(uint32_t)) uint8_t collision_buffer_[160];
enum class CollisionType : uint8_t {
Playfield = (1 << 0),
Ball = (1 << 1),
Player0 = (1 << 2),
Player1 = (1 << 3),
Missile0 = (1 << 4),
Missile1 = (1 << 5)
};
int collision_flags_;
int collision_flags_by_buffer_vaules_[64];
// colour mapping tables
enum class ColourMode {
Standard = 0,
ScoreLeft,
ScoreRight,
OnTop
};
uint8_t colour_mask_by_mode_collision_flags_[4][64]; // maps from [ColourMode][CollisionMark] to colour_pallete_ entry
enum class ColourIndex {
Background = 0,
PlayfieldBall,
PlayerMissile0,
PlayerMissile1
};
uint8_t colour_palette_[4];
// playfield state
int background_half_mask_;
enum class PlayfieldPriority {
Standard,
Score,
OnTop
} playfield_priority_;
uint32_t background_[2]; // contains two 20-bit bitfields representing the background state;
// at index 0 is the left-hand side of the playfield with bit 0 being
// the first bit to display, bit 1 the second, etc. Index 1 contains
// a mirror image of index 0. If the playfield is being displayed in
// mirroring mode, background_[0] will be output on the left and
// background_[1] on the right; otherwise background_[0] will be
// output twice.
// objects
template<class T> struct Object {
// the two programmer-set values
int position;
int motion;
// motion_step_ is the current motion counter value; motion_time_ is the next time it will fire
int motion_step;
int motion_time;
// indicates whether this object is currently undergoing motion
bool is_moving;
2017-03-26 19:47:04 +00:00
Object() : position(0), motion(0), motion_step(0), motion_time(0), is_moving(false) {};
};
// player state
struct Player: public Object<Player> {
2017-03-26 19:47:04 +00:00
Player() :
adder(4),
copy_flags(0),
graphic{0, 0},
reverse_mask(false),
graphic_index(0),
pixel_position(32),
pixel_counter(0),
latched_pixel4_time(-1),
copy_index_(0),
queue_read_pointer_(0),
queue_write_pointer_(0) {}
int adder;
int copy_flags; // a bit field, corresponding to the first few values of NUSIZ
uint8_t graphic[2]; // the player graphic; 1 = new, 0 = current
int reverse_mask; // 7 for a reflected player, 0 for normal
int graphic_index;
int pixel_position, pixel_counter;
int latched_pixel4_time;
const bool enqueues = true;
inline void skip_pixels(const int count, int from_horizontal_counter) {
int old_pixel_counter = pixel_counter;
pixel_position = std::min(32, pixel_position + count * adder);
pixel_counter += count;
if(!copy_index_ && old_pixel_counter < 4 && pixel_counter >= 4) {
latched_pixel4_time = from_horizontal_counter + 4 - old_pixel_counter;
}
}
inline void reset_pixels(int copy) {
pixel_position = pixel_counter = 0;
copy_index_ = copy;
}
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) {
output_pixels(target, count, collision_identity, pixel_position, adder, reverse_mask);
skip_pixels(count, from_horizontal_counter);
}
void dequeue_pixels(uint8_t *const target, const uint8_t collision_identity, const int time_now) {
while(queue_read_pointer_ != queue_write_pointer_) {
uint8_t *const start_ptr = &target[queue_[queue_read_pointer_].start];
if(queue_[queue_read_pointer_].end > time_now) {
const int length = time_now - queue_[queue_read_pointer_].start;
output_pixels(start_ptr, length, collision_identity, queue_[queue_read_pointer_].pixel_position, queue_[queue_read_pointer_].adder, queue_[queue_read_pointer_].reverse_mask);
queue_[queue_read_pointer_].pixel_position += length * queue_[queue_read_pointer_].adder;
queue_[queue_read_pointer_].start = time_now;
return;
} else {
output_pixels(start_ptr, queue_[queue_read_pointer_].end - queue_[queue_read_pointer_].start, collision_identity, queue_[queue_read_pointer_].pixel_position, queue_[queue_read_pointer_].adder, queue_[queue_read_pointer_].reverse_mask);
}
queue_read_pointer_ = (queue_read_pointer_ + 1)&3;
}
}
void enqueue_pixels(const int start, const int end, int from_horizontal_counter) {
queue_[queue_write_pointer_].start = start;
queue_[queue_write_pointer_].end = end;
queue_[queue_write_pointer_].pixel_position = pixel_position;
queue_[queue_write_pointer_].adder = adder;
queue_[queue_write_pointer_].reverse_mask = reverse_mask;
queue_write_pointer_ = (queue_write_pointer_ + 1)&3;
skip_pixels(end - start, from_horizontal_counter);
}
private:
int copy_index_;
struct QueuedPixels {
int start, end;
int pixel_position;
int adder;
int reverse_mask;
2017-03-26 19:47:04 +00:00
QueuedPixels() : start(0), end(0), pixel_position(0), adder(0), reverse_mask(false) {}
} queue_[4];
int queue_read_pointer_, queue_write_pointer_;
2017-07-22 01:51:18 +00:00
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int output_pixel_position, int output_adder, int output_reverse_mask) {
if(output_pixel_position == 32 || !graphic[graphic_index]) return;
int output_cursor = 0;
2017-07-22 01:51:18 +00:00
while(output_pixel_position < 32 && output_cursor < count) {
int shift = (output_pixel_position >> 2) ^ output_reverse_mask;
target[output_cursor] |= ((graphic[graphic_index] >> shift)&1) * collision_identity;
output_cursor++;
2017-07-22 01:51:18 +00:00
output_pixel_position += output_adder;
}
}
} player_[2];
// common actor for things that appear as a horizontal run of pixels
struct HorizontalRun: public Object<HorizontalRun> {
int pixel_position;
int size;
const bool enqueues = false;
inline void skip_pixels(const int count, int from_horizontal_counter) {
pixel_position = std::max(0, pixel_position - count);
}
inline void reset_pixels(int copy) {
pixel_position = size;
}
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) {
int output_cursor = 0;
while(pixel_position && output_cursor < count)
{
target[output_cursor] |= collision_identity;
output_cursor++;
pixel_position--;
}
}
void dequeue_pixels(uint8_t *const target, const uint8_t collision_identity, const int time_now) {}
void enqueue_pixels(const int start, const int end, int from_horizontal_counter) {}
HorizontalRun() : pixel_position(0), size(1) {}
};
// missile state
struct Missile: public HorizontalRun {
bool enabled;
bool locked_to_player;
int copy_flags;
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) {
if(!pixel_position) return;
if(enabled && !locked_to_player) {
HorizontalRun::output_pixels(target, count, collision_identity, from_horizontal_counter);
} else {
skip_pixels(count, from_horizontal_counter);
}
}
Missile() : enabled(false), copy_flags(0) {}
} missile_[2];
2017-02-19 13:02:54 +00:00
// ball state
struct Ball: public HorizontalRun {
2017-02-19 13:02:54 +00:00
bool enabled[2];
int enabled_index;
const int copy_flags = 0;
2017-02-19 13:02:54 +00:00
inline void output_pixels(uint8_t *const target, const int count, const uint8_t collision_identity, int from_horizontal_counter) {
if(!pixel_position) return;
if(enabled[enabled_index]) {
HorizontalRun::output_pixels(target, count, collision_identity, from_horizontal_counter);
} else {
skip_pixels(count, from_horizontal_counter);
}
}
2017-03-26 19:47:04 +00:00
Ball() : enabled{false, false}, enabled_index(0) {}
2017-02-19 13:02:54 +00:00
} ball_;
2017-02-20 23:04:40 +00:00
// motion
bool horizontal_blank_extend_;
template<class T> void perform_border_motion(T &object, int start, int end);
template<class T> void perform_motion_step(T &object);
// drawing methods and state
void draw_missile(Missile &, Player &, const uint8_t collision_identity, int start, int end);
template<class T> void draw_object(T &, const uint8_t collision_identity, int start, int end);
template<class T> void draw_object_visible(T &, const uint8_t collision_identity, int start, int end, int time_now);
2017-02-22 12:14:30 +00:00
inline void draw_playfield(int start, int end);
inline void output_for_cycles(int number_of_cycles);
inline void output_line();
int pixels_start_location_;
uint8_t *pixel_target_;
inline void output_pixels(int start, int end);
};
}
#endif /* TIA_hpp */