mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-26 08:49:37 +00:00
Makes first, faltering steps towards video display.
This commit is contained in:
parent
699fb0aa4b
commit
d37ba62343
@ -102,11 +102,24 @@ class ConcreteMachine:
|
||||
m65816_.run_for(cycles);
|
||||
}
|
||||
|
||||
void set_scan_target(Outputs::Display::ScanTarget *) override {
|
||||
void flush() {
|
||||
video_.flush();
|
||||
}
|
||||
|
||||
void set_scan_target(Outputs::Display::ScanTarget *target) override {
|
||||
video_->set_scan_target(target);
|
||||
}
|
||||
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const override {
|
||||
return Outputs::Display::ScanStatus();
|
||||
return video_->get_scaled_scan_status() * 2.0f; // TODO: expose multiplier and divider via the JustInTime template?
|
||||
}
|
||||
|
||||
void set_display_type(Outputs::Display::DisplayType display_type) final {
|
||||
video_->set_display_type(display_type);
|
||||
}
|
||||
|
||||
Outputs::Display::DisplayType get_display_type() const final {
|
||||
return video_->get_display_type();
|
||||
}
|
||||
|
||||
forceinline Cycles perform_bus_operation(const CPU::WDC65816::BusOperation operation, const uint32_t address, uint8_t *const value) {
|
||||
@ -544,7 +557,7 @@ class ConcreteMachine:
|
||||
// MARK: - Other components.
|
||||
|
||||
Apple::Clock::ParallelClock clock_;
|
||||
JustInTimeActor<Apple::IIgs::Video::Video, 1, 7, Cycles> video_; // i.e. run video at twice the 1Mhz clock.
|
||||
JustInTimeActor<Apple::IIgs::Video::Video, 1, 2, Cycles> video_; // i.e. run video at twice the 1Mhz clock.
|
||||
Apple::IIgs::ADB::GLU adb_glu_;
|
||||
Apple::IIgs::Sound::GLU sound_glu_;
|
||||
Zilog::SCC::z8530 scc_;
|
||||
|
@ -12,14 +12,35 @@ using namespace Apple::IIgs::Video;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int CyclesPerLine = 910;
|
||||
constexpr int CyclesPerTick = 7; // One 'tick' being the non-stretched length of a cycle on the old Apple II 1Mhz clock.
|
||||
constexpr int CyclesPerLine = 456; // Each of the Mega II's cycles lasts 7 cycles, making 455/line except for the
|
||||
// final on on a line which lasts an additional 1 (i.e. is 1/7th longer).
|
||||
constexpr int Lines = 263;
|
||||
constexpr int FinalPixelLine = 192;
|
||||
|
||||
constexpr auto FinalColumn = CyclesPerLine / CyclesPerTick;
|
||||
|
||||
}
|
||||
|
||||
VideoBase::VideoBase() :
|
||||
VideoSwitches<Cycles>(true, Cycles(2), [this] (Cycles cycles) { advance(cycles); }) {
|
||||
VideoSwitches<Cycles>(true, Cycles(2), [this] (Cycles cycles) { advance(cycles); }),
|
||||
crt_(130, 1, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Red4Green4Blue4) {
|
||||
}
|
||||
|
||||
void VideoBase::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
|
||||
crt_.set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
Outputs::Display::ScanStatus VideoBase::get_scaled_scan_status() const {
|
||||
return crt_.get_scaled_scan_status();
|
||||
}
|
||||
|
||||
void VideoBase::set_display_type(Outputs::Display::DisplayType display_type) {
|
||||
crt_.set_display_type(display_type);
|
||||
}
|
||||
|
||||
Outputs::Display::DisplayType VideoBase::get_display_type() const {
|
||||
return crt_.get_display_type();
|
||||
}
|
||||
|
||||
void VideoBase::set_internal_ram(const uint8_t *ram) {
|
||||
@ -27,25 +48,111 @@ void VideoBase::set_internal_ram(const uint8_t *ram) {
|
||||
}
|
||||
|
||||
void VideoBase::advance(Cycles cycles) {
|
||||
// TODO: everything else!
|
||||
const auto old = cycles_into_frame_;
|
||||
const int column_start = (cycles_into_frame_ % CyclesPerLine) / CyclesPerTick;
|
||||
const int row_start = cycles_into_frame_ / CyclesPerLine;
|
||||
|
||||
cycles_into_frame_ = (cycles_into_frame_ + cycles.as<int>()) % (CyclesPerLine * Lines);
|
||||
|
||||
// DEBUGGING HACK!!
|
||||
// Scan the output buffer, assuming this is 40-column text mode, and print anything found.
|
||||
if(cycles_into_frame_ < old) {
|
||||
for(int line = 0; line < 192; line += 8) {
|
||||
const uint16_t address = get_row_address(line);
|
||||
const int column_end = (cycles_into_frame_ % CyclesPerLine) / CyclesPerTick;
|
||||
const int row_end = cycles_into_frame_ / CyclesPerLine;
|
||||
|
||||
bool did_print_line = false;
|
||||
for(int column = 0; column < 40; column++) {
|
||||
const char c = char(ram_[address + column]);
|
||||
if(c > 0) {
|
||||
printf("%c", c);
|
||||
did_print_line = true;
|
||||
if(row_end == row_start) {
|
||||
output_row(row_start, column_start, column_end);
|
||||
} else {
|
||||
output_row(row_start, column_start, FinalColumn);
|
||||
for(int row = row_start+1; row < row_end; row++) {
|
||||
output_row(row, 0, FinalColumn);
|
||||
}
|
||||
if(column_end) {
|
||||
output_row(row_end, 0, column_end);
|
||||
}
|
||||
}
|
||||
if(did_print_line) printf("\n");
|
||||
}
|
||||
|
||||
void VideoBase::output_row(int row, int start, int end) {
|
||||
// Reasoned guesswork ahoy!
|
||||
//
|
||||
// The IIgs VGC can fetch four bytes per column — I'm unclear physically how, but that's definitely true
|
||||
// since the IIgs modes packs 160 bytes work of graphics into the Apple II's usual 40-cycle fetch area;
|
||||
// it's possible that if I understood the meaning of the linear video bit in the new video flag I'd know more.
|
||||
//
|
||||
// Super Hi-Res also fetches 16*2 = 32 bytes of palette and a control byte sometime before each row.
|
||||
// So it needs five windows for that.
|
||||
//
|
||||
// Guessing four cycles of sync, I've chosen to arrange one output row for this emulator as:
|
||||
//
|
||||
// 5 cycles of back porch;
|
||||
// 8 windows left border, the final five of which fetch palette and control if in IIgs mode;
|
||||
// 40 windows of pixel output;
|
||||
// 8 cycles of right border;
|
||||
// 4 cycles of sync (including the extra 1/7th window, as it has to go _somewhere_).
|
||||
//
|
||||
// Otherwise, the first 200 rows may be pixels and the 192 in the middle of those are the II set.
|
||||
constexpr int first_sync_line = 220; // A complete guess. Information needed.
|
||||
|
||||
constexpr int blank_ticks = 5;
|
||||
constexpr int left_border_ticks = 8;
|
||||
constexpr int pixel_ticks = 40;
|
||||
constexpr int right_border_ticks = 8;
|
||||
|
||||
constexpr int start_of_left_border = blank_ticks;
|
||||
constexpr int start_of_pixels = start_of_left_border + left_border_ticks;
|
||||
constexpr int start_of_right_border = start_of_pixels + pixel_ticks;
|
||||
constexpr int start_of_sync = start_of_right_border + right_border_ticks;
|
||||
constexpr int sync_period = CyclesPerLine - start_of_sync*CyclesPerTick;
|
||||
|
||||
// Deal with vertical sync.
|
||||
if(row >= first_sync_line && row < first_sync_line + 3) {
|
||||
// Simplification: just output the whole line at line's end.
|
||||
if(end == FinalColumn) {
|
||||
crt_.output_sync(CyclesPerLine - sync_period);
|
||||
crt_.output_blank(sync_period);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Deal with the pixel area.
|
||||
if(row < Lines) { // TODO: use real test here.
|
||||
|
||||
// Output blank only at the end of its window.
|
||||
if(start < blank_ticks && end >= blank_ticks) {
|
||||
crt_.output_blank(blank_ticks * CyclesPerTick);
|
||||
start = blank_ticks;
|
||||
}
|
||||
|
||||
// Output left border as far as currently known.
|
||||
if(start >= start_of_left_border && start < start_of_pixels) {
|
||||
const int duration = std::max(left_border_ticks, end - start_of_left_border);
|
||||
start += duration;
|
||||
|
||||
// TODO: output real border colour.
|
||||
crt_.output_blank(duration * CyclesPerTick);
|
||||
}
|
||||
|
||||
// Output left border as far as currently known.
|
||||
if(start >= start_of_pixels && start < start_of_right_border) {
|
||||
const int duration = std::max(pixel_ticks, end - start_of_pixels);
|
||||
start += duration;
|
||||
|
||||
// TODO: output real pixels.
|
||||
uint16_t *const pixel = reinterpret_cast<uint16_t *>(crt_.begin_data(2, 2));
|
||||
if(pixel) *pixel = 0xffff;
|
||||
crt_.output_data(duration * CyclesPerTick, 1);
|
||||
}
|
||||
|
||||
// Output left border as far as currently known.
|
||||
if(start >= start_of_right_border && start < start_of_sync) {
|
||||
const int duration = std::max(right_border_ticks, end - start_of_right_border);
|
||||
start += duration;
|
||||
|
||||
// TODO: output real border colour.
|
||||
crt_.output_blank(duration * CyclesPerTick);
|
||||
}
|
||||
|
||||
// Output sync if the moment has arrived.
|
||||
if(end == FinalColumn) {
|
||||
crt_.output_sync(sync_period);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@
|
||||
#define Apple_IIgs_Video_hpp
|
||||
|
||||
#include "../AppleII/VideoSwitches.hpp"
|
||||
#include "../../../Outputs/CRT/CRT.hpp"
|
||||
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
namespace Apple {
|
||||
@ -37,7 +38,21 @@ class VideoBase: public Apple::II::VideoSwitches<Cycles> {
|
||||
|
||||
void notify_clock_tick();
|
||||
|
||||
/// Sets the scan target.
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
|
||||
|
||||
/// Gets the current scan status.
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const;
|
||||
|
||||
/// Sets the type of output.
|
||||
void set_display_type(Outputs::Display::DisplayType);
|
||||
|
||||
/// Gets the type of output.
|
||||
Outputs::Display::DisplayType get_display_type() const;
|
||||
|
||||
private:
|
||||
Outputs::CRT::CRT crt_;
|
||||
|
||||
void advance(Cycles);
|
||||
|
||||
uint8_t new_video_ = 0x01;
|
||||
@ -46,6 +61,8 @@ class VideoBase: public Apple::II::VideoSwitches<Cycles> {
|
||||
|
||||
int cycles_into_frame_ = 0;
|
||||
const uint8_t *ram_ = nullptr;
|
||||
|
||||
void output_row(int row, int start, int end);
|
||||
};
|
||||
|
||||
class Video: public VideoBase {
|
||||
|
Loading…
Reference in New Issue
Block a user