diff --git a/Machines/Atari2600/Atari2600.cpp b/Machines/Atari2600/Atari2600.cpp index 874840dfd..a9ac5d483 100644 --- a/Machines/Atari2600/Atari2600.cpp +++ b/Machines/Atari2600/Atari2600.cpp @@ -122,10 +122,6 @@ class ConcreteMachine: joysticks_.emplace_back(new Joystick(bus_.get(), 4, 1)); } - ~ConcreteMachine() { -// close_output(); - } - std::vector> &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(get_clock_rate() / static_cast(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(clock_rate / static_cast(CPUTicksPerAudioTick))); diff --git a/Machines/Atari2600/Bus.hpp b/Machines/Atari2600/Bus.hpp index e08b33211..ba58c81c5 100644 --- a/Machines/Atari2600/Bus.hpp +++ b/Machines/Atari2600/Bus.hpp @@ -36,7 +36,7 @@ class Bus { // the RIOT, TIA and speaker PIA mos6532_; - std::shared_ptr 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 diff --git a/Machines/Atari2600/Cartridges/Cartridge.hpp b/Machines/Atari2600/Cartridges/Cartridge.hpp index 4276bdc7b..b25f5a3aa 100644 --- a/Machines/Atari2600/Cartridges/Cartridge.hpp +++ b/Machines/Atari2600/Cartridges/Cartridge.hpp @@ -68,7 +68,7 @@ template 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 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 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 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); } diff --git a/Machines/Atari2600/TIA.cpp b/Machines/Atari2600/TIA.cpp index 047f2b9b9..c50f85103 100644 --- a/Machines/Atari2600/TIA.cpp +++ b/Machines/Atari2600/TIA.cpp @@ -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( @@ -113,51 +111,35 @@ TIA::TIA(bool create_crt) { } } -TIA::TIA() : TIA(true) {} - -TIA::TIA(std::function 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(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(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(&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(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(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(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(ColourMode::ScoreLeft)][buffer_value]]; + pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[static_cast(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(ColourMode::ScoreRight)][buffer_value]]; + pixel_target_[target_position] = colour_palette_[colour_mask_by_mode_collision_flags_[static_cast(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((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; } diff --git a/Machines/Atari2600/TIA.hpp b/Machines/Atari2600/TIA.hpp index 1dd80034a..c2d9a25a0 100644 --- a/Machines/Atari2600/TIA.hpp +++ b/Machines/Atari2600/TIA.hpp @@ -10,6 +10,7 @@ #define TIA_hpp #include +#include #include #include @@ -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 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 crt_; - std::function 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_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); }; diff --git a/Outputs/OpenGL/ScanTarget.cpp b/Outputs/OpenGL/ScanTarget.cpp index b60f61cca..f3f4950cc 100644 --- a/Outputs/OpenGL/ScanTarget.cpp +++ b/Outputs/OpenGL/ScanTarget.cpp @@ -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.