1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-01-27 06:35:04 +00:00

Mostly restores Atari 2600 output. PAL colours need work.

This commit is contained in:
Thomas Harte 2018-11-29 18:26:05 -08:00
parent a25470ee41
commit 6be46ae921
6 changed files with 140 additions and 126 deletions

View File

@ -122,10 +122,6 @@ class ConcreteMachine:
joysticks_.emplace_back(new Joystick(bus_.get(), 4, 1));
}
~ConcreteMachine() {
// close_output();
}
std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
return joysticks_;
}
@ -158,19 +154,11 @@ class ConcreteMachine:
// to satisfy CRTMachine::Machine
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
bus_->tia_.reset(new TIA);
bus_->speaker_.set_input_rate(static_cast<float>(get_clock_rate() / static_cast<double>(CPUTicksPerAudioTick)));
bus_->tia_->get_crt()->set_delegate(this);
bus_->tia_.set_crt_delegate(this);
bus_->tia_.set_scan_target(scan_target);
}
// void close_output() override {
// bus_.reset();
// }
//
// Outputs::CRT::CRT *get_crt() override {
// return bus_->tia_->get_crt();
// }
Outputs::Speaker::Speaker *get_speaker() override {
return &bus_->speaker_;
}
@ -205,10 +193,10 @@ class ConcreteMachine:
double clock_rate;
if(is_ntsc_) {
clock_rate = NTSC_clock_rate;
bus_->tia_->set_output_mode(TIA::OutputMode::NTSC);
bus_->tia_.set_output_mode(TIA::OutputMode::NTSC);
} else {
clock_rate = PAL_clock_rate;
bus_->tia_->set_output_mode(TIA::OutputMode::PAL);
bus_->tia_.set_output_mode(TIA::OutputMode::PAL);
}
bus_->speaker_.set_input_rate(static_cast<float>(clock_rate / static_cast<double>(CPUTicksPerAudioTick)));

View File

@ -36,7 +36,7 @@ class Bus {
// the RIOT, TIA and speaker
PIA mos6532_;
std::shared_ptr<TIA> tia_;
TIA tia_;
Concurrency::DeferringAsyncTaskQueue audio_queue_;
TIASound tia_sound_;
@ -55,7 +55,7 @@ class Bus {
// video backlog accumulation counter
Cycles cycles_since_video_update_;
inline void update_video() {
tia_->run_for(cycles_since_video_update_.flush());
tia_.run_for(cycles_since_video_update_.flush());
}
// RIOT backlog accumulation counter

View File

@ -68,7 +68,7 @@ template<class T> class Cartridge:
// effect until the next read; therefore it isn't safe to assume that signalling ready immediately
// skips to the end of the line.
if(operation == CPU::MOS6502::BusOperation::Ready)
cycles_run_for = tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_);
cycles_run_for = tia_.get_cycles_until_horizontal_blank(cycles_since_video_update_);
cycles_since_speaker_update_ += Cycles(cycles_run_for);
cycles_since_video_update_ += Cycles(cycles_run_for);
@ -101,7 +101,7 @@ template<class T> class Cartridge:
case 0x05: // missile 1 / playfield / ball collisions
case 0x06: // ball / playfield collisions
case 0x07: // player / player, missile / missile collisions
returnValue &= tia_->get_collision_flags(decodedAddress);
returnValue &= tia_.get_collision_flags(decodedAddress);
break;
case 0x08:
@ -120,52 +120,52 @@ template<class T> class Cartridge:
} else {
const uint16_t decodedAddress = address & 0x3f;
switch(decodedAddress) {
case 0x00: update_video(); tia_->set_sync(*value & 0x02); break;
case 0x01: update_video(); tia_->set_blank(*value & 0x02); break;
case 0x00: update_video(); tia_.set_sync(*value & 0x02); break;
case 0x01: update_video(); tia_.set_blank(*value & 0x02); break;
case 0x02: m6502_.set_ready_line(true); break;
case 0x03:
update_video();
tia_->reset_horizontal_counter();
tia_.reset_horizontal_counter();
horizontal_counter_resets_++;
break;
// TODO: audio will now be out of synchronisation. Fix.
case 0x04:
case 0x05: update_video(); tia_->set_player_number_and_size(decodedAddress - 0x04, *value); break;
case 0x05: update_video(); tia_.set_player_number_and_size(decodedAddress - 0x04, *value); break;
case 0x06:
case 0x07: update_video(); tia_->set_player_missile_colour(decodedAddress - 0x06, *value); break;
case 0x08: update_video(); tia_->set_playfield_ball_colour(*value); break;
case 0x09: update_video(); tia_->set_background_colour(*value); break;
case 0x0a: update_video(); tia_->set_playfield_control_and_ball_size(*value); break;
case 0x07: update_video(); tia_.set_player_missile_colour(decodedAddress - 0x06, *value); break;
case 0x08: update_video(); tia_.set_playfield_ball_colour(*value); break;
case 0x09: update_video(); tia_.set_background_colour(*value); break;
case 0x0a: update_video(); tia_.set_playfield_control_and_ball_size(*value); break;
case 0x0b:
case 0x0c: update_video(); tia_->set_player_reflected(decodedAddress - 0x0b, !((*value)&8)); break;
case 0x0c: update_video(); tia_.set_player_reflected(decodedAddress - 0x0b, !((*value)&8)); break;
case 0x0d:
case 0x0e:
case 0x0f: update_video(); tia_->set_playfield(decodedAddress - 0x0d, *value); break;
case 0x0f: update_video(); tia_.set_playfield(decodedAddress - 0x0d, *value); break;
case 0x10:
case 0x11: update_video(); tia_->set_player_position(decodedAddress - 0x10); break;
case 0x11: update_video(); tia_.set_player_position(decodedAddress - 0x10); break;
case 0x12:
case 0x13: update_video(); tia_->set_missile_position(decodedAddress - 0x12); break;
case 0x14: update_video(); tia_->set_ball_position(); break;
case 0x13: update_video(); tia_.set_missile_position(decodedAddress - 0x12); break;
case 0x14: update_video(); tia_.set_ball_position(); break;
case 0x1b:
case 0x1c: update_video(); tia_->set_player_graphic(decodedAddress - 0x1b, *value); break;
case 0x1c: update_video(); tia_.set_player_graphic(decodedAddress - 0x1b, *value); break;
case 0x1d:
case 0x1e: update_video(); tia_->set_missile_enable(decodedAddress - 0x1d, (*value)&2); break;
case 0x1f: update_video(); tia_->set_ball_enable((*value)&2); break;
case 0x1e: update_video(); tia_.set_missile_enable(decodedAddress - 0x1d, (*value)&2); break;
case 0x1f: update_video(); tia_.set_ball_enable((*value)&2); break;
case 0x20:
case 0x21: update_video(); tia_->set_player_motion(decodedAddress - 0x20, *value); break;
case 0x21: update_video(); tia_.set_player_motion(decodedAddress - 0x20, *value); break;
case 0x22:
case 0x23: update_video(); tia_->set_missile_motion(decodedAddress - 0x22, *value); break;
case 0x24: update_video(); tia_->set_ball_motion(*value); break;
case 0x23: update_video(); tia_.set_missile_motion(decodedAddress - 0x22, *value); break;
case 0x24: update_video(); tia_.set_ball_motion(*value); break;
case 0x25:
case 0x26: tia_->set_player_delay(decodedAddress - 0x25, (*value)&1); break;
case 0x27: tia_->set_ball_delay((*value)&1); break;
case 0x26: tia_.set_player_delay(decodedAddress - 0x25, (*value)&1); break;
case 0x27: tia_.set_ball_delay((*value)&1); break;
case 0x28:
case 0x29: update_video(); tia_->set_missile_position_to_player(decodedAddress - 0x28, (*value)&2); break;
case 0x2a: update_video(); tia_->move(); break;
case 0x2b: update_video(); tia_->clear_motion(); break;
case 0x2c: update_video(); tia_->clear_collision_flags(); break;
case 0x29: update_video(); tia_.set_missile_position_to_player(decodedAddress - 0x28, (*value)&2); break;
case 0x2a: update_video(); tia_.move(); break;
case 0x2b: update_video(); tia_.clear_motion(); break;
case 0x2c: update_video(); tia_.clear_collision_flags(); break;
case 0x15:
case 0x16: update_audio(); tia_sound_.set_control(decodedAddress - 0x15, *value); break;
@ -192,7 +192,7 @@ template<class T> class Cartridge:
}
}
if(!tia_->get_cycles_until_horizontal_blank(cycles_since_video_update_)) m6502_.set_ready_line(false);
if(!tia_.get_cycles_until_horizontal_blank(cycles_since_video_update_)) m6502_.set_ready_line(false);
return Cycles(cycles_run_for / 3);
}

View File

@ -22,12 +22,10 @@ namespace {
uint8_t reverse_table[256];
}
TIA::TIA(bool create_crt) {
if(create_crt) {
// crt_.reset(new Outputs::CRT::CRT(cycles_per_line * 2 - 1, 1, Outputs::Display::Type::NTSC60, 1));
// crt_->set_video_signal(Outputs::Display::VideoSignal::Composite);
set_output_mode(OutputMode::NTSC);
}
TIA::TIA():
crt_(cycles_per_line * 2 - 1, 1, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Luminance8Phase8) {
set_output_mode(OutputMode::NTSC);
for(int c = 0; c < 256; c++) {
reverse_table[c] = static_cast<uint8_t>(
@ -113,51 +111,35 @@ TIA::TIA(bool create_crt) {
}
}
TIA::TIA() : TIA(true) {}
TIA::TIA(std::function<void(uint8_t *output_buffer)> line_end_function) : TIA(false) {
line_end_function_ = line_end_function;
}
void TIA::set_output_mode(Atari2600::TIA::OutputMode output_mode) {
Outputs::Display::Type display_type;
tv_standard_ = output_mode;
if(output_mode == OutputMode::NTSC) {
// crt_->set_svideo_sampling_function(
// "vec2 svideo_sample(usampler2D texID, vec2 coordinate, float phase, float amplitude)"
// "{"
// "uint c = texture(texID, coordinate).r;"
// "uint y = c & 14u;"
// "uint iPhase = (c >> 4);"
//
// "float phaseOffset = 6.283185308 * float(iPhase) / 13.0 + 5.074880441076923;"
// "return vec2(float(y) / 14.0, step(1, iPhase) * cos(phase - phaseOffset));"
// "}");
display_type = Outputs::Display::Type::NTSC60;
} else {
// crt_->set_svideo_sampling_function(
// "vec2 svideo_sample(usampler2D texID, vec2 coordinate, float phase, float amplitude)"
// "{"
// "uint c = texture(texID, coordinate).r;"
// "uint y = c & 14u;"
// "uint iPhase = (c >> 4);"
//
// "uint direction = iPhase & 1u;"
// "float phaseOffset = float(7u - direction) + (float(direction) - 0.5) * 2.0 * float(iPhase >> 1);"
// "phaseOffset *= 6.283185308 / 12.0;"
// "return vec2(float(y) / 14.0, step(4, (iPhase + 2u) & 15u) * cos(phase + phaseOffset));"
// "}");
display_type = Outputs::Display::Type::PAL50;
}
// crt_->set_video_signal(Outputs::Display::VideoSignal::Composite);
crt_.set_display_type(Outputs::Display::DisplayType::CompositeColour);
// line number of cycles in a line of video is one less than twice the number of clock cycles per line; the Atari
// outputs 228 colour cycles of material per line when an NTSC line 227.5. Since all clock numbers will be doubled
// later, cycles_per_line * 2 - 1 is therefore the real length of an NTSC line, even though we're going to supply
// cycles_per_line * 2 cycles of information from one sync edge to the next
crt_->set_new_display_type(cycles_per_line * 2 - 1, display_type);
crt_.set_new_display_type(cycles_per_line * 2 - 1, display_type);
/* speaker_->set_input_rate(static_cast<float>(get_clock_rate() / 38.0));*/
// Update the luminance/phase mappings of the current palette.
for(size_t c = 0; c < colour_palette_.size(); ++c) {
set_colour_palette_entry(c, colour_palette_[c].original);
}
}
void TIA::set_crt_delegate(Outputs::CRT::Delegate *delegate) {
crt_.set_delegate(delegate);
}
void TIA::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
crt_.set_scan_target(scan_target);
}
void TIA::run_for(const Cycles cycles) {
@ -198,7 +180,53 @@ int TIA::get_cycles_until_horizontal_blank(const Cycles from_offset) {
}
void TIA::set_background_colour(uint8_t colour) {
colour_palette_[static_cast<int>(ColourIndex::Background)] = colour;
set_colour_palette_entry(size_t(ColourIndex::Background), colour);
}
void TIA::set_colour_palette_entry(size_t index, uint8_t colour) {
const uint8_t luminance = ((colour & 14) * 255) / 14;
uint8_t phase = colour >> 4;
if(tv_standard_ == OutputMode::NTSC) {
if(!phase) phase = 255;
else {
phase = -(phase * 127) / 13;
phase -= 102;
phase &= 127;
}
} else {
// TODO: PAL colours.
// crt_.set_svideo_sampling_function(
// "vec2 svideo_sample(usampler2D texID, vec2 coordinate, float phase, float amplitude)"
// "{"
// "uint c = texture(texID, coordinate).r;"
// "uint y = c & 14u;"
// "uint iPhase = (c >> 4);"
//
// "uint direction = iPhase & 1u;"
// "float phaseOffset = float(7u - direction) + (float(direction) - 0.5) * 2.0 * float(iPhase >> 1);"
// "phaseOffset *= 6.283185308 / 12.0;"
// "return vec2(float(y) / 14.0, step(4, (iPhase + 2u) & 15u) * cos(phase + phaseOffset));"
// "}");
if(phase < 2 || phase > 13) {
phase = 255;
} else {
const auto direction = phase & 1;
phase >>= 1;
if(!direction) phase ^= 0xf;
phase += 7 - direction;
phase = (((phase - 3) & 0xf) * 127) / 12;
phase &= 127;
}
}
colour_palette_[index].original = colour;
uint8_t *target = reinterpret_cast<uint8_t *>(&colour_palette_[index].luminance_phase);
target[0] = luminance;
target[1] = phase;
}
void TIA::set_playfield(uint16_t offset, uint8_t value) {
@ -238,7 +266,7 @@ void TIA::set_playfield_control_and_ball_size(uint8_t value) {
}
void TIA::set_playfield_ball_colour(uint8_t colour) {
colour_palette_[static_cast<int>(ColourIndex::PlayfieldBall)] = colour;
set_colour_palette_entry(size_t(ColourIndex::PlayfieldBall), colour);
}
void TIA::set_player_number_and_size(int player, uint8_t value) {
@ -300,7 +328,7 @@ void TIA::set_player_motion(int player, uint8_t motion) {
void TIA::set_player_missile_colour(int player, uint8_t colour) {
assert(player >= 0 && player < 2);
colour_palette_[static_cast<int>(ColourIndex::PlayerMissile0) + player] = colour;
set_colour_palette_entry(size_t(ColourIndex::PlayerMissile0) + size_t(player), colour);
}
void TIA::set_missile_enable(int missile, bool enabled) {
@ -382,7 +410,6 @@ void TIA::output_for_cycles(int number_of_cycles) {
bool is_reset = output_cursor < 224 && horizontal_counter_ >= 224;
if(!output_cursor) {
if(line_end_function_) line_end_function_(collision_buffer_);
std::memset(collision_buffer_, 0, sizeof(collision_buffer_));
ball_.motion_time %= 228;
@ -407,11 +434,11 @@ void TIA::output_for_cycles(int number_of_cycles) {
#define Period(function, target) \
if(output_cursor < target) { \
if(horizontal_counter_ <= target) { \
if(crt_) crt_->function((horizontal_counter_ - output_cursor) * 2); \
crt_.function((horizontal_counter_ - output_cursor) * 2); \
horizontal_counter_ %= cycles_per_line; \
return; \
} else { \
if(crt_) crt_->function((target - output_cursor) * 2); \
crt_.function((target - output_cursor) * 2); \
output_cursor = target; \
} \
}
@ -437,19 +464,17 @@ void TIA::output_for_cycles(int number_of_cycles) {
if(output_mode_ & blank_flag) {
if(pixel_target_) {
output_pixels(pixels_start_location_, output_cursor);
if(crt_) {
const int data_length = int(output_cursor - pixels_start_location_);
crt_->output_data(data_length * 2, size_t(data_length));
}
const int data_length = int(output_cursor - pixels_start_location_);
crt_.output_data(data_length * 2, size_t(data_length));
pixel_target_ = nullptr;
pixels_start_location_ = 0;
}
int duration = std::min(228, horizontal_counter_) - output_cursor;
if(crt_) crt_->output_blank(duration * 2);
crt_.output_blank(duration * 2);
} else {
if(!pixels_start_location_ && crt_) {
if(!pixels_start_location_) {
pixels_start_location_ = output_cursor;
pixel_target_ = crt_->begin_data(160);
pixel_target_ = reinterpret_cast<uint16_t *>(crt_.begin_data(160));
}
// convert that into pixels
@ -461,9 +486,9 @@ void TIA::output_for_cycles(int number_of_cycles) {
output_cursor++;
}
if(horizontal_counter_ == cycles_per_line && crt_) {
if(horizontal_counter_ == cycles_per_line) {
const int data_length = int(output_cursor - pixels_start_location_);
crt_->output_data(data_length * 2, size_t(data_length));
crt_.output_data(data_length * 2, size_t(data_length));
pixel_target_ = nullptr;
pixels_start_location_ = 0;
}
@ -480,7 +505,7 @@ void TIA::output_pixels(int start, int end) {
if(start < first_pixel_cycle+8 && horizontal_blank_extend_) {
while(start < end && start < first_pixel_cycle+8) {
pixel_target_[target_position] = 0;
pixel_target_[target_position] = 0xff00; // TODO: this assumes little endianness.
start++;
target_position++;
}
@ -489,13 +514,13 @@ void TIA::output_pixels(int start, int end) {
if(playfield_priority_ == PlayfieldPriority::Score) {
while(start < end && start < first_pixel_cycle + 80) {
uint8_t buffer_value = collision_buffer_[start - first_pixel_cycle];
pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreLeft)][buffer_value]];
pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreLeft)][buffer_value]].luminance_phase;
start++;
target_position++;
}
while(start < end) {
uint8_t buffer_value = collision_buffer_[start - first_pixel_cycle];
pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreRight)][buffer_value]];
pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[static_cast<int>(ColourMode::ScoreRight)][buffer_value]].luminance_phase;
start++;
target_position++;
}
@ -503,7 +528,7 @@ void TIA::output_pixels(int start, int end) {
int table_index = static_cast<int>((playfield_priority_ == PlayfieldPriority::Standard) ? ColourMode::Standard : ColourMode::OnTop);
while(start < end) {
uint8_t buffer_value = collision_buffer_[start - first_pixel_cycle];
pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[table_index][buffer_value]];
pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[table_index][buffer_value]].luminance_phase;
start++;
target_position++;
}
@ -518,20 +543,16 @@ void TIA::output_line() {
break;
case sync_flag:
case sync_flag | blank_flag:
if(crt_) {
crt_->output_sync(32);
crt_->output_blank(32);
crt_->output_sync(392);
}
crt_.output_sync(32);
crt_.output_blank(32);
crt_.output_sync(392);
horizontal_blank_extend_ = false;
break;
case blank_flag:
if(crt_) {
crt_->output_blank(32);
crt_->output_sync(32);
crt_->output_default_colour_burst(32);
crt_->output_blank(360);
}
crt_.output_blank(32);
crt_.output_sync(32);
crt_.output_default_colour_burst(32);
crt_.output_blank(360);
horizontal_blank_extend_ = false;
break;
}

View File

@ -10,6 +10,7 @@
#define TIA_hpp
#include <algorithm>
#include <array>
#include <cstdint>
#include <functional>
@ -21,10 +22,6 @@ 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
@ -76,12 +73,11 @@ class TIA {
uint8_t get_collision_flags(int offset);
void clear_collision_flags();
Outputs::CRT::CRT *get_crt() { return crt_.get(); }
void set_crt_delegate(Outputs::CRT::Delegate *);
void set_scan_target(Outputs::Display::ScanTarget *);
private:
TIA(bool create_crt);
std::unique_ptr<Outputs::CRT::CRT> crt_;
std::function<void(uint8_t *output_buffer)> line_end_function_;
Outputs::CRT::CRT crt_;
// the master counter; counts from 0 to 228 with all visible pixels being in the final 160
int horizontal_counter_ = 0;
@ -110,7 +106,7 @@ class TIA {
ScoreRight,
OnTop
};
uint8_t colour_mask_by_mode_collision_flags_[4][64]; // maps from [ColourMode][CollisionMark] to colour_pallete_ entry
uint8_t colour_mask_by_mode_collision_flags_[4][64]; // maps from [ColourMode][CollisionMark] to colour_palette_ entry
enum class ColourIndex {
Background = 0,
@ -118,7 +114,13 @@ class TIA {
PlayerMissile0,
PlayerMissile1
};
uint8_t colour_palette_[4];
struct Colour {
uint16_t luminance_phase;
uint8_t original;
};
std::array<Colour, 4> colour_palette_;
void set_colour_palette_entry(size_t index, uint8_t colour);
OutputMode tv_standard_;
// playfield state
int background_half_mask_ = 0;
@ -305,7 +307,7 @@ class TIA {
inline void output_line();
int pixels_start_location_ = 0;
uint8_t *pixel_target_ = nullptr;
uint16_t *pixel_target_ = nullptr;
inline void output_pixels(int start, int end);
};

View File

@ -16,9 +16,9 @@ namespace {
/// The texture unit from which to source 1bpp input data.
constexpr GLenum SourceData1BppTextureUnit = GL_TEXTURE0;
/// The texture unit from which to source 2bpp input data.
constexpr GLenum SourceData2BppTextureUnit = GL_TEXTURE1;
//constexpr GLenum SourceData2BppTextureUnit = GL_TEXTURE1;
/// The texture unit from which to source 4bpp input data.
constexpr GLenum SourceData4BppTextureUnit = GL_TEXTURE2;
//constexpr GLenum SourceData4BppTextureUnit = GL_TEXTURE2;
/// The texture unit which contains raw line-by-line composite, S-Video or RGB data.
constexpr GLenum UnprocessedLineBufferTextureUnit = GL_TEXTURE3;
@ -102,6 +102,9 @@ ScanTarget::~ScanTarget() {
void ScanTarget::set_modals(Modals modals) {
modals_ = modals;
// TODO: almost none of the below can occur here, as this is not necessarily an OpenGL thread.
// Whoops!
const auto data_type_size = Outputs::Display::size_for_data_type(modals.input_data_type);
if(data_type_size != data_type_size_) {
// TODO: flush output.