mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-23 18:31:53 +00:00
Merge pull request #693 from TomHarte/STComposite
Adds colour composite output to the ST
This commit is contained in:
commit
b4fd506361
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include "MFP68901.hpp"
|
#include "MFP68901.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
#define LOG_PREFIX "[MFP] "
|
#define LOG_PREFIX "[MFP] "
|
||||||
@ -179,12 +180,26 @@ void MFP68901::run_for(HalfCycles time) {
|
|||||||
cycles_left_ += time;
|
cycles_left_ += time;
|
||||||
|
|
||||||
const int cycles = int(cycles_left_.flush<Cycles>().as_integral());
|
const int cycles = int(cycles_left_.flush<Cycles>().as_integral());
|
||||||
|
if(!cycles) return;
|
||||||
|
|
||||||
for(int c = 0; c < 4; ++c) {
|
for(int c = 0; c < 4; ++c) {
|
||||||
if(timers_[c].mode >= TimerMode::Delay) {
|
if(timers_[c].mode >= TimerMode::Delay) {
|
||||||
const int dividend = (cycles + timers_[c].prescale - timers_[c].divisor);
|
// This code applies the timer prescaling only. prescale_count is used to count
|
||||||
const int decrements = dividend / timers_[c].prescale;
|
// upwards rather than downwards for simplicity, but on the real hardware it's
|
||||||
timers_[c].divisor = timers_[c].prescale - (dividend % timers_[c].prescale);
|
// pretty safe to assume it actually counted downwards. So the clamp to 0 is
|
||||||
if(decrements) decrement_timer(c, decrements);
|
// because gymnastics may need to occur when the prescale value is altered, e.g.
|
||||||
|
// if a prescale of 256 is set and the prescale_count is currently 2 then the
|
||||||
|
// counter should roll over in 254 cycles. If the user at that point changes the
|
||||||
|
// prescale_count to 1 then the counter will need to be altered to -253 and
|
||||||
|
// allowed to keep counting up until it crosses both 0 and 1.
|
||||||
|
const int dividend = timers_[c].prescale_count + cycles;
|
||||||
|
const int decrements = std::max(dividend / timers_[c].prescale, 0);
|
||||||
|
if(decrements) {
|
||||||
|
decrement_timer(c, decrements);
|
||||||
|
timers_[c].prescale_count = dividend % timers_[c].prescale;
|
||||||
|
} else {
|
||||||
|
timers_[c].prescale_count += cycles;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -198,11 +213,17 @@ HalfCycles MFP68901::get_next_sequence_point() {
|
|||||||
void MFP68901::set_timer_mode(int timer, TimerMode mode, int prescale, bool reset_timer) {
|
void MFP68901::set_timer_mode(int timer, TimerMode mode, int prescale, bool reset_timer) {
|
||||||
LOG("Timer " << timer << " mode set: " << int(mode) << "; prescale: " << prescale);
|
LOG("Timer " << timer << " mode set: " << int(mode) << "; prescale: " << prescale);
|
||||||
timers_[timer].mode = mode;
|
timers_[timer].mode = mode;
|
||||||
timers_[timer].prescale = prescale;
|
|
||||||
if(reset_timer) {
|
if(reset_timer) {
|
||||||
timers_[timer].divisor = prescale;
|
timers_[timer].prescale_count = 0;
|
||||||
timers_[timer].value = timers_[timer].reload_value;
|
timers_[timer].value = timers_[timer].reload_value;
|
||||||
|
} else {
|
||||||
|
// This hoop is because the prescale_count here goes upward but I'm assuming it goes downward in
|
||||||
|
// real hardware. Therefore this deals with the "switched to a lower prescaling" case whereby the
|
||||||
|
// old cycle should be allowed naturally to expire.
|
||||||
|
timers_[timer].prescale_count = prescale - (timers_[timer].prescale - timers_[timer].prescale_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
timers_[timer].prescale = prescale;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MFP68901::set_timer_data(int timer, uint8_t value) {
|
void MFP68901::set_timer_data(int timer, uint8_t value) {
|
||||||
|
@ -93,7 +93,7 @@ class MFP68901: public ClockingHint::Source {
|
|||||||
uint8_t value = 0;
|
uint8_t value = 0;
|
||||||
uint8_t reload_value = 0;
|
uint8_t reload_value = 0;
|
||||||
int prescale = 1;
|
int prescale = 1;
|
||||||
int divisor = 1;
|
int prescale_count = 1;
|
||||||
bool event_input = false;
|
bool event_input = false;
|
||||||
} timers_[4];
|
} timers_[4];
|
||||||
uint8_t timer_ab_control_[2] = { 0, 0 };
|
uint8_t timer_ab_control_[2] = { 0, 0 };
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
|
|
||||||
#include "../../../ClockReceiver/JustInTime.hpp"
|
#include "../../../ClockReceiver/JustInTime.hpp"
|
||||||
#include "../../../ClockReceiver/ForceInline.hpp"
|
#include "../../../ClockReceiver/ForceInline.hpp"
|
||||||
|
#include "../../../Configurable/StandardOptions.hpp"
|
||||||
|
|
||||||
#include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
#include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||||
|
|
||||||
@ -40,6 +41,12 @@
|
|||||||
namespace Atari {
|
namespace Atari {
|
||||||
namespace ST {
|
namespace ST {
|
||||||
|
|
||||||
|
std::vector<std::unique_ptr<Configurable::Option>> get_options() {
|
||||||
|
return Configurable::standard_options(
|
||||||
|
static_cast<Configurable::StandardOptions>(Configurable::DisplayRGB | Configurable::DisplayCompositeColour | Configurable::QuickLoadTape)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const int CLOCK_RATE = 8021247;
|
const int CLOCK_RATE = 8021247;
|
||||||
|
|
||||||
using Target = Analyser::Static::Target;
|
using Target = Analyser::Static::Target;
|
||||||
@ -57,6 +64,7 @@ class ConcreteMachine:
|
|||||||
public Activity::Source,
|
public Activity::Source,
|
||||||
public MediaTarget::Machine,
|
public MediaTarget::Machine,
|
||||||
public GI::AY38910::PortHandler,
|
public GI::AY38910::PortHandler,
|
||||||
|
public Configurable::Device,
|
||||||
public Video::RangeObserver {
|
public Video::RangeObserver {
|
||||||
public:
|
public:
|
||||||
ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
|
ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||||
@ -130,6 +138,10 @@ class ConcreteMachine:
|
|||||||
video_->set_scan_target(scan_target);
|
video_->set_scan_target(scan_target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_display_type(Outputs::Display::DisplayType display_type) final {
|
||||||
|
video_->set_display_type(display_type);
|
||||||
|
}
|
||||||
|
|
||||||
Outputs::Speaker::Speaker *get_speaker() final {
|
Outputs::Speaker::Speaker *get_speaker() final {
|
||||||
return &speaker_;
|
return &speaker_;
|
||||||
}
|
}
|
||||||
@ -613,6 +625,30 @@ class ConcreteMachine:
|
|||||||
void video_did_change_access_range(Video *video) final {
|
void video_did_change_access_range(Video *video) final {
|
||||||
video_range_ = video->get_memory_access_range();
|
video_range_ = video->get_memory_access_range();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Configuration options.
|
||||||
|
std::vector<std::unique_ptr<Configurable::Option>> get_options() final {
|
||||||
|
return Atari::ST::get_options();
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_selections(const Configurable::SelectionSet &selections_by_option) final {
|
||||||
|
Configurable::Display display;
|
||||||
|
if(Configurable::get_display(selections_by_option, display)) {
|
||||||
|
set_video_signal_configurable(display);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Configurable::SelectionSet get_accurate_selections() final {
|
||||||
|
Configurable::SelectionSet selection_set;
|
||||||
|
Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour);
|
||||||
|
return selection_set;
|
||||||
|
}
|
||||||
|
|
||||||
|
Configurable::SelectionSet get_user_friendly_selections() final {
|
||||||
|
Configurable::SelectionSet selection_set;
|
||||||
|
Configurable::append_display_selection(selection_set, Configurable::Display::RGB);
|
||||||
|
return selection_set;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,9 @@
|
|||||||
namespace Atari {
|
namespace Atari {
|
||||||
namespace ST {
|
namespace ST {
|
||||||
|
|
||||||
|
/// @returns The options available for an Atari ST.
|
||||||
|
std::vector<std::unique_ptr<Configurable::Option>> get_options();
|
||||||
|
|
||||||
class Machine {
|
class Machine {
|
||||||
public:
|
public:
|
||||||
virtual ~Machine();
|
virtual ~Machine();
|
||||||
|
@ -107,11 +107,11 @@ void DMAController::write(int address, uint16_t value) {
|
|||||||
// of the byte it is adjusting.
|
// of the byte it is adjusting.
|
||||||
case 4: address_ = int((address_ & 0x00ffff) | ((value & 0xff) << 16)); break;
|
case 4: address_ = int((address_ & 0x00ffff) | ((value & 0xff) << 16)); break;
|
||||||
case 5:
|
case 5:
|
||||||
if((value << 8) ^ address_ & ~(value << 8) & 0x8000) address_ += 0x10000;
|
if(((value << 8) ^ address_) & ~(value << 8) & 0x8000) address_ += 0x10000;
|
||||||
address_ = int((address_ & 0xff00ff) | ((value & 0xff) << 8));
|
address_ = int((address_ & 0xff00ff) | ((value & 0xff) << 8));
|
||||||
break;
|
break;
|
||||||
case 6:
|
case 6:
|
||||||
if(value ^ address_ & ~value & 0x80) address_ += 0x100;
|
if((value ^ address_) & ~value & 0x80) address_ += 0x100;
|
||||||
address_ = int((address_ & 0xffff00) | ((value & 0xfe) << 0));
|
address_ = int((address_ & 0xffff00) | ((value & 0xfe) << 0));
|
||||||
break; // Lowest bit: discarded.
|
break; // Lowest bit: discarded.
|
||||||
}
|
}
|
||||||
|
@ -122,6 +122,10 @@ void Video::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
|
|||||||
crt_.set_scan_target(scan_target);
|
crt_.set_scan_target(scan_target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Video::set_display_type(Outputs::Display::DisplayType display_type) {
|
||||||
|
crt_.set_display_type(display_type);
|
||||||
|
}
|
||||||
|
|
||||||
void Video::run_for(HalfCycles duration) {
|
void Video::run_for(HalfCycles duration) {
|
||||||
deferrer_.run_for(duration);
|
deferrer_.run_for(duration);
|
||||||
}
|
}
|
||||||
@ -489,6 +493,7 @@ void Video::Shifter::flush_output(OutputMode next_mode) {
|
|||||||
switch(output_mode_) {
|
switch(output_mode_) {
|
||||||
case OutputMode::Sync: crt_.output_sync(duration_); break;
|
case OutputMode::Sync: crt_.output_sync(duration_); break;
|
||||||
case OutputMode::Blank: crt_.output_blank(duration_); break;
|
case OutputMode::Blank: crt_.output_blank(duration_); break;
|
||||||
|
case OutputMode::ColourBurst: crt_.output_default_colour_burst(duration_); break;
|
||||||
case OutputMode::Border: {
|
case OutputMode::Border: {
|
||||||
// if(!border_colour_) {
|
// if(!border_colour_) {
|
||||||
// crt_.output_blank(duration_);
|
// crt_.output_blank(duration_);
|
||||||
@ -508,9 +513,29 @@ void Video::Shifter::flush_output(OutputMode next_mode) {
|
|||||||
output_mode_ = next_mode;
|
output_mode_ = next_mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Video::Shifter::output_colour_burst(int duration) {
|
||||||
|
// More hackery afoot here; if and when duration_ crosses a threshold of 40,
|
||||||
|
// output 40 cycles of colour burst and then redirect to blank.
|
||||||
|
if(output_mode_ != OutputMode::ColourBurst) {
|
||||||
|
flush_output(OutputMode::ColourBurst);
|
||||||
|
}
|
||||||
|
duration_ += duration;
|
||||||
|
if(duration_ >= 40) {
|
||||||
|
const int blank_duration = duration_ - 40;
|
||||||
|
duration_ = 40;
|
||||||
|
flush_output(OutputMode::Blank);
|
||||||
|
output_blank(blank_duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Video::Shifter::output_blank(int duration) {
|
void Video::Shifter::output_blank(int duration) {
|
||||||
if(output_mode_ != OutputMode::Blank) {
|
if(output_mode_ != OutputMode::Blank) {
|
||||||
|
// Bit of a hack: if this is a transition from sync or we're really in
|
||||||
|
// colour burst, divert into that.
|
||||||
|
if(output_mode_ == OutputMode::Sync || output_mode_ == OutputMode::ColourBurst) {
|
||||||
|
output_colour_burst(duration);
|
||||||
|
return;
|
||||||
|
}
|
||||||
flush_output(OutputMode::Blank);
|
flush_output(OutputMode::Blank);
|
||||||
}
|
}
|
||||||
duration_ += duration;
|
duration_ += duration;
|
||||||
|
@ -37,6 +37,11 @@ class Video {
|
|||||||
*/
|
*/
|
||||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
|
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Sets the type of output.
|
||||||
|
*/
|
||||||
|
void set_display_type(Outputs::Display::DisplayType);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Produces the next @c duration period of pixels.
|
Produces the next @c duration period of pixels.
|
||||||
*/
|
*/
|
||||||
@ -168,13 +173,14 @@ class Video {
|
|||||||
void output_sync(int duration);
|
void output_sync(int duration);
|
||||||
void output_border(int duration, OutputBpp bpp);
|
void output_border(int duration, OutputBpp bpp);
|
||||||
void output_pixels(int duration, OutputBpp bpp);
|
void output_pixels(int duration, OutputBpp bpp);
|
||||||
|
void output_colour_burst(int duration);
|
||||||
|
|
||||||
void load(uint64_t value);
|
void load(uint64_t value);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int duration_ = 0;
|
int duration_ = 0;
|
||||||
enum class OutputMode {
|
enum class OutputMode {
|
||||||
Sync, Blank, Border, Pixels
|
Sync, Blank, Border, Pixels, ColourBurst
|
||||||
} output_mode_ = OutputMode::Sync;
|
} output_mode_ = OutputMode::Sync;
|
||||||
uint16_t border_colour_ = 0;
|
uint16_t border_colour_ = 0;
|
||||||
OutputBpp bpp_ = OutputBpp::Four;
|
OutputBpp bpp_ = OutputBpp::Four;
|
||||||
|
@ -143,6 +143,7 @@ std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> Machin
|
|||||||
|
|
||||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AmstradCPC), AmstradCPC::get_options()));
|
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AmstradCPC), AmstradCPC::get_options()));
|
||||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AppleII), Apple::II::get_options()));
|
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AppleII), Apple::II::get_options()));
|
||||||
|
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AtariST), Atari::ST::get_options()));
|
||||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::ColecoVision), Coleco::Vision::get_options()));
|
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::ColecoVision), Coleco::Vision::get_options()));
|
||||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Electron), Electron::get_options()));
|
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Electron), Electron::get_options()));
|
||||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Macintosh), Apple::Macintosh::get_options()));
|
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Macintosh), Apple::Macintosh::get_options()));
|
||||||
|
@ -4916,7 +4916,7 @@
|
|||||||
"$(USER_LIBRARY_DIR)/Frameworks",
|
"$(USER_LIBRARY_DIR)/Frameworks",
|
||||||
);
|
);
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
GCC_OPTIMIZATION_LEVEL = 3;
|
GCC_OPTIMIZATION_LEVEL = fast;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.12;
|
MACOSX_DEPLOYMENT_TARGET = 10.12;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
};
|
};
|
||||||
|
@ -228,6 +228,7 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K
|
|||||||
case Analyser::Machine::AmstradCPC: return @"CompositeOptions";
|
case Analyser::Machine::AmstradCPC: return @"CompositeOptions";
|
||||||
case Analyser::Machine::AppleII: return @"AppleIIOptions";
|
case Analyser::Machine::AppleII: return @"AppleIIOptions";
|
||||||
case Analyser::Machine::Atari2600: return @"Atari2600Options";
|
case Analyser::Machine::Atari2600: return @"Atari2600Options";
|
||||||
|
case Analyser::Machine::AtariST: return @"CompositeOptions";
|
||||||
case Analyser::Machine::ColecoVision: return @"CompositeOptions";
|
case Analyser::Machine::ColecoVision: return @"CompositeOptions";
|
||||||
case Analyser::Machine::Electron: return @"QuickLoadCompositeOptions";
|
case Analyser::Machine::Electron: return @"QuickLoadCompositeOptions";
|
||||||
case Analyser::Machine::Macintosh: return @"MacintoshOptions";
|
case Analyser::Machine::Macintosh: return @"MacintoshOptions";
|
||||||
|
Loading…
Reference in New Issue
Block a user