From 7d2adad67e689adfffc338225d06cf8cd68aaf2e Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 27 Nov 2017 19:43:33 -0500 Subject: [PATCH] Adds the absolute most basic version of in-frame time keeping, to display a white square. --- Components/9918/9918.cpp | 83 ++++++++++++++++++++++++++++++++++++---- Components/9918/9918.hpp | 8 +++- 2 files changed, 81 insertions(+), 10 deletions(-) diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index 477438d50..c236636ce 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -11,7 +11,7 @@ using namespace TI; TMS9918::TMS9918(Personality p) : - crt_(new Outputs::CRT::CRT(911, 1, Outputs::CRT::DisplayType::NTSC60, 3)) { + crt_(new Outputs::CRT::CRT(342, 1, Outputs::CRT::DisplayType::NTSC60, 4)) { crt_->set_rgb_sampling_function( "vec3 rgb_sample(usampler2D sampler, vec2 coordinate, vec2 icoordinate)" "{" @@ -24,8 +24,75 @@ std::shared_ptr TMS9918::get_crt() { } void TMS9918::run_for(const HalfCycles cycles) { + // TODO: all video output (!) + + // As specific as I've been able to get: + // Scanline time is always 227.75 cycles. + // PAL output is 313 lines total. NTSC output is 262 lines total. + // Interrupt is signalled upon entering the lower border. + + // Convert to 342 cycles per line; the internal clock is 1.5 times the + // nominal 3.579545 Mhz that I've advertised for this part. + int int_cycles = (cycles.as_int() * 3) + cycles_error_; + cycles_error_ = int_cycles & 3; + int_cycles >>= 2; + + // + // Break that down as: + // 26 cycles sync; + + while(int_cycles) { + int cycles_left = std::min(342 - column_, int_cycles); + int end_column = std::min(column_ + int_cycles, 342); + if(row_ < 192) { + // Pixels. + if(column_ < 26 && end_column >= 26) { + crt_->output_sync(static_cast(26)); + column_ = 26; + } + if(column_ >= 26 && end_column >= 69) { // TODO: modes other than text + crt_->output_blank(static_cast(69 - 26)); + column_ = 69; + pixel_target_ = crt_->allocate_write_area(256); + } + while(column_ < end_column && column_ < 309) { // TODO: modes other than text + pixel_target_[0] = pixel_target_[1] = pixel_target_[2] = pixel_target_[3] = 0xff; + pixel_target_ += 4; + column_ ++; + } + if(column_ == 309 && end_column > 309) { + crt_->output_data(240, 1); // TODO: modes other than text + } + if(end_column == 342) { + crt_->output_blank(static_cast(342 - 309)); + } + } else if(row_ >= 227 && row_ < 230) { // TODO: don't hard-code NTSC. + // Vertical sync. + crt_->output_sync(static_cast(cycles_left)); + } else { + // Blank. + if(column_ < 26 && end_column >= 26) { + crt_->output_sync(static_cast(26)); + column_ = 26; + } + if(column_ < end_column) { + crt_->output_blank(static_cast(end_column - column_)); + column_ = end_column; + } + } + + int_cycles += cycles_left; + column_ = end_column; + if(column_ == 342) { + column_ = 0; + row_ = (row_ + 1) % 262; // TODO: don't hard-code NTSC. + // TODO: consider triggering an interrupt here. + } + } } +// TODO: as a temporary development measure, memory access below is magically instantaneous. Correct that. + void TMS9918::set_register(int address, uint8_t value) { // Writes to address 0 are writes to the video RAM. Store // the value and return. @@ -50,36 +117,36 @@ void TMS9918::set_register(int address, uint8_t value) { // This is a write to a register. switch(value & 7) { case 0: - screen_mode_ = (screen_mode_ & 6) | ((low_write_ & 2) >> 1); + next_screen_mode_ = (next_screen_mode_ & 6) | ((low_write_ & 2) >> 1); break; case 1: blank_screen_ = !!(low_write_ & 0x40); generate_interrupts_ = !!(low_write_ & 0x20); - screen_mode_ = (screen_mode_ & 1) | ((low_write_ & 0x18) >> 3); + next_screen_mode_ = (screen_mode_ & 1) | ((low_write_ & 0x18) >> 3); sprites_16x16_ = !!(low_write_ & 0x02); sprites_magnified_ = !!(low_write_ & 0x01); reevaluate_interrupts(); break; case 2: - pattern_name_address_ = (low_write_ & 0xf) << 10; + pattern_name_address_ = static_cast((low_write_ & 0xf) << 10); break; case 3: - colour_table_address_ = low_write_ << 6; + colour_table_address_ = static_cast(low_write_ << 6); break; case 4: - pattern_generator_table_address_ = (low_write_ & 0x07) << 11; + pattern_generator_table_address_ = static_cast((low_write_ & 0x07) << 11); break; case 5: - sprite_attribute_table_address_ = (low_write_ & 0x7f) << 7; + sprite_attribute_table_address_ = static_cast((low_write_ & 0x7f) << 7); break; case 6: - sprite_generator_table_address_ = (low_write_ & 0x07) << 11; + sprite_generator_table_address_ = static_cast((low_write_ & 0x07) << 11); break; case 7: diff --git a/Components/9918/9918.hpp b/Components/9918/9918.hpp index 5e10921ab..5b7459ecd 100644 --- a/Components/9918/9918.hpp +++ b/Components/9918/9918.hpp @@ -42,7 +42,7 @@ class TMS9918 { private: std::shared_ptr crt_; - uint16_t ram_[16384]; + uint8_t ram_[16384]; uint16_t ram_pointer_ = 0; uint8_t read_ahead_buffer_ = 0; @@ -53,7 +53,7 @@ class TMS9918 { uint8_t low_write_ = 0; // The various register flags. - int screen_mode_ = 0; + int next_screen_mode_ = 0, screen_mode_ = 0; bool blank_screen_ = true; bool sprites_16x16_ = false; bool sprites_magnified_ = false; @@ -67,6 +67,10 @@ class TMS9918 { uint8_t background_colour_ = 0; void reevaluate_interrupts(); + + int column_ = 0, row_ = 0; + int cycles_error_ = 0; + uint8_t *pixel_target_ = nullptr; }; };