This probably still isn't correct, but restores text.
This commit is contained in:
1 parent
af56b00ce0
commit
03ccc8186a
2 files changed
+150
-126
No files matched your search
@@ -116,7 +116,7 @@ void MC6847Base::pixel_line(const int line, const int line_begin, const int line
|
||||
|
||||
// TODO: obey graphics mode here, use proper colours, more.
|
||||
if(pixels_) {
|
||||
const int row = line % 12;
|
||||
const int row = address_.row();
|
||||
for(int c = line_begin; c < line_end; c++) {
|
||||
const int column = c - LineLayout::EndOfLeftBorder;
|
||||
const int pixels = font[line_.data[column] & 63][row];
|
||||
@@ -134,6 +134,8 @@ void MC6847Base::pixel_line(const int line, const int line_begin, const int line
|
||||
|
||||
if(end == LineLayout::EndOfPixels) {
|
||||
crt_.output_data(32 * CRTMultiplier, 256);
|
||||
address_.apply_hsync();
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -160,3 +162,7 @@ void MC6847Base::sync_line(const int, const int end) {
|
||||
crt_.output_sync((LineLayout::EndOfLine) * CRTMultiplier);
|
||||
}
|
||||
}
|
||||
|
||||
void MC6847Base::reset() {
|
||||
address_.apply_vertical_preload();
|
||||
}
|
||||
+143
-125
@@ -14,129 +14,9 @@
|
||||
|
||||
namespace Motorola::MC6847 {
|
||||
|
||||
enum class FrameTiming {
|
||||
/// Official frame timing, as implemented by the chip.
|
||||
NTSC,
|
||||
|
||||
/// Standard PAL adaptation of adding 25 lines before vsync and 25 aftter, as used independently by
|
||||
/// the Acorn Atom and Dragon 32.
|
||||
PALPaddedVsync,
|
||||
};
|
||||
constexpr bool is_ntsc(const FrameTiming timing) {
|
||||
return timing == FrameTiming::NTSC;
|
||||
}
|
||||
template <FrameTiming timing> struct FrameLayout {
|
||||
static constexpr int BorderLength = timing == FrameTiming::NTSC ? 27 : 52;
|
||||
|
||||
static constexpr int EndOfPixels = 192;
|
||||
static constexpr int EndOfBottomBorder = EndOfPixels + BorderLength;
|
||||
static constexpr int EndOfFrontPorch = EndOfBottomBorder + 2;
|
||||
static constexpr int EndOfSync = EndOfFrontPorch + 2;
|
||||
static constexpr int EndOfBackPorch = EndOfSync + 12;
|
||||
static constexpr int EndOfField = EndOfBackPorch + BorderLength;
|
||||
};
|
||||
static_assert(FrameLayout<FrameTiming::NTSC>::EndOfField == 262, "NTSC frames should be 262 lines long");
|
||||
static_assert(FrameLayout<FrameTiming::PALPaddedVsync>::EndOfField == 312, "PAL-padded frames should be 312 lines long");
|
||||
|
||||
struct MC6847Base {
|
||||
MC6847Base(Outputs::Display::Type);
|
||||
|
||||
void pixel_line(int line, int begin, int end);
|
||||
void border_line(int begin, int end);
|
||||
void porch_line(int begin, int end);
|
||||
void sync_line(int begin, int end);
|
||||
|
||||
struct LineLayout {
|
||||
// Complete guesses. Who knows?
|
||||
static const int EndOfSync = 4;
|
||||
static const int EndOfLeftBorder = 18;
|
||||
static const int EndOfPixels = 50;
|
||||
static const int EndOfLine = 64;
|
||||
};
|
||||
|
||||
Outputs::CRT::CRT crt_;
|
||||
uint16_t *pixels_ = nullptr;
|
||||
|
||||
struct LineCapture {
|
||||
// TODO: capture mode here.
|
||||
uint8_t data[32];
|
||||
} line_;
|
||||
};
|
||||
|
||||
template <FrameTiming timing, typename MemoryAccessT>
|
||||
class MC6847: private MC6847Base {
|
||||
public:
|
||||
MC6847(MemoryAccessT &memory) :
|
||||
MC6847Base(is_ntsc(timing) ? Outputs::Display::Type::NTSC60 : Outputs::Display::Type::PAL50),
|
||||
memory_(memory) {
|
||||
crt_.set_fixed_framing([&] {
|
||||
run_for(Cycles(10'000));
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Expected input clock: NTSC-style 3.58 Mhz.
|
||||
//
|
||||
void run_for(const Cycles cycles) {
|
||||
// This template binds the drawing logic to whatever was supplied
|
||||
// as MemoryAccessT; a consequence of that is that frame layout logic is here.
|
||||
cycles_ += cycles;
|
||||
position_.advance(
|
||||
cycles_.divide(2).template as<int>(),
|
||||
[&] (const int line, const int begin, const int end) {
|
||||
if(line < FrameLayout<timing>::EndOfPixels) {
|
||||
const int line_start = (line / 12) * 32;
|
||||
Numeric::clamp<LineLayout::EndOfLeftBorder, LineLayout::EndOfPixels>(
|
||||
begin,
|
||||
end,
|
||||
[&](const int fetch_begin, const int fetch_end) {
|
||||
for(int c = fetch_begin; c < fetch_end; c++) {
|
||||
const int column = c - LineLayout::EndOfLeftBorder;
|
||||
line_.data[column] = memory_[0x400 + size_t(line_start + column)];
|
||||
}
|
||||
}
|
||||
);
|
||||
pixel_line(line, begin, end);
|
||||
} else if(line < FrameLayout<timing>::EndOfBottomBorder) {
|
||||
border_line(begin, end);
|
||||
} else if(line < FrameLayout<timing>::EndOfFrontPorch) {
|
||||
porch_line(begin, end);
|
||||
} else if(line < FrameLayout<timing>::EndOfSync) {
|
||||
sync_line(begin, end);
|
||||
} else if(line < FrameLayout<timing>::EndOfBackPorch) {
|
||||
porch_line(begin, end);
|
||||
} else {
|
||||
border_line(begin, end);
|
||||
}
|
||||
},
|
||||
[&] {
|
||||
// TODO: reset counters, etc.
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void set_scan_target(Outputs::Display::ScanTarget *const target) {
|
||||
crt_.set_scan_target(target);
|
||||
}
|
||||
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const {
|
||||
return crt_.get_scaled_scan_status() * 2;
|
||||
}
|
||||
|
||||
void set_display_type(const Outputs::Display::DisplayType display_type) {
|
||||
crt_.set_display_type(display_type);
|
||||
}
|
||||
|
||||
Outputs::Display::DisplayType get_display_type() const {
|
||||
return crt_.get_display_type();
|
||||
}
|
||||
|
||||
private:
|
||||
MemoryAccessT &memory_;
|
||||
Cycles cycles_;
|
||||
Numeric::DividingAccumulator<64, FrameLayout<timing>::EndOfField> position_;
|
||||
};
|
||||
|
||||
/*!
|
||||
Encapsulates the 6847's address-generation logic;
|
||||
*/
|
||||
struct AddressGenerator {
|
||||
// MARK: - Events.
|
||||
|
||||
@@ -150,7 +30,7 @@ struct AddressGenerator {
|
||||
if(y_ == y_divider_ || y_ == 12) { // Complete guess: always wrap at 12 even if an in-frame mode switch
|
||||
// has occurred.
|
||||
y_ = 0;
|
||||
return;
|
||||
++counter_;
|
||||
}
|
||||
|
||||
// Clear either four or five bits.
|
||||
@@ -167,15 +47,22 @@ struct AddressGenerator {
|
||||
counter_ = preload_;
|
||||
}
|
||||
|
||||
uint16_t advance() {
|
||||
void advance() {
|
||||
++x_;
|
||||
if(x_ == x_divider_ || x_ == 3) {
|
||||
++counter_;
|
||||
x_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t address() const {
|
||||
return counter_;
|
||||
}
|
||||
|
||||
int row() const {
|
||||
return y_;
|
||||
}
|
||||
|
||||
// MARK: - Inputs.
|
||||
|
||||
void set_hsync(const bool active) {
|
||||
@@ -237,4 +124,135 @@ private:
|
||||
int y_ = 0;
|
||||
};
|
||||
|
||||
enum class FrameTiming {
|
||||
/// Official frame timing, as implemented by the chip.
|
||||
NTSC,
|
||||
|
||||
/// Standard PAL adaptation of adding 25 lines before vsync and 25 aftter, as used independently by
|
||||
/// the Acorn Atom and Dragon 32.
|
||||
PALPaddedVsync,
|
||||
};
|
||||
constexpr bool is_ntsc(const FrameTiming timing) {
|
||||
return timing == FrameTiming::NTSC;
|
||||
}
|
||||
template <FrameTiming timing> struct FrameLayout {
|
||||
static constexpr int BorderLength = timing == FrameTiming::NTSC ? 27 : 52;
|
||||
|
||||
static constexpr int EndOfPixels = 192;
|
||||
static constexpr int EndOfBottomBorder = EndOfPixels + BorderLength;
|
||||
static constexpr int EndOfFrontPorch = EndOfBottomBorder + 2;
|
||||
static constexpr int EndOfSync = EndOfFrontPorch + 2;
|
||||
static constexpr int EndOfBackPorch = EndOfSync + 12;
|
||||
static constexpr int EndOfField = EndOfBackPorch + BorderLength;
|
||||
};
|
||||
static_assert(FrameLayout<FrameTiming::NTSC>::EndOfField == 262, "NTSC frames should be 262 lines long");
|
||||
static_assert(FrameLayout<FrameTiming::PALPaddedVsync>::EndOfField == 312, "PAL-padded frames should be 312 lines long");
|
||||
|
||||
struct MC6847Base {
|
||||
MC6847Base(Outputs::Display::Type);
|
||||
|
||||
void pixel_line(int line, int begin, int end);
|
||||
void border_line(int begin, int end);
|
||||
void porch_line(int begin, int end);
|
||||
void sync_line(int begin, int end);
|
||||
void reset();
|
||||
|
||||
struct LineLayout {
|
||||
// Complete guesses. Who knows?
|
||||
static const int EndOfSync = 4;
|
||||
static const int EndOfLeftBorder = 18;
|
||||
static const int EndOfPixels = 50;
|
||||
static const int EndOfLine = 64;
|
||||
};
|
||||
|
||||
Outputs::CRT::CRT crt_;
|
||||
uint16_t *pixels_ = nullptr;
|
||||
|
||||
struct LineCapture {
|
||||
// TODO: capture mode here.
|
||||
uint8_t data[32];
|
||||
} line_;
|
||||
|
||||
AddressGenerator address_;
|
||||
};
|
||||
|
||||
template <FrameTiming timing, typename MemoryAccessT>
|
||||
class MC6847: private MC6847Base {
|
||||
public:
|
||||
MC6847(MemoryAccessT &memory) :
|
||||
MC6847Base(is_ntsc(timing) ? Outputs::Display::Type::NTSC60 : Outputs::Display::Type::PAL50),
|
||||
memory_(memory) {
|
||||
// HACK. Allows me to test the address generator locally, prior to integrating it into the SAM.
|
||||
address_.set_preload(0x400);
|
||||
address_.set_mode(0);
|
||||
|
||||
crt_.set_fixed_framing([&] {
|
||||
run_for(Cycles(10'000));
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Expected input clock: NTSC-style 3.58 Mhz.
|
||||
//
|
||||
void run_for(const Cycles cycles) {
|
||||
// This template binds the drawing logic to whatever was supplied
|
||||
// as MemoryAccessT; a consequence of that is that frame layout logic is here.
|
||||
cycles_ += cycles;
|
||||
position_.advance(
|
||||
cycles_.divide(2).template as<int>(),
|
||||
[&] (const int line, const int begin, const int end) {
|
||||
if(line < FrameLayout<timing>::EndOfPixels) {
|
||||
Numeric::clamp<LineLayout::EndOfLeftBorder, LineLayout::EndOfPixels>(
|
||||
begin,
|
||||
end,
|
||||
[&](const int fetch_begin, const int fetch_end) {
|
||||
for(int c = fetch_begin; c < fetch_end; c++) {
|
||||
if(c > LineLayout::EndOfLeftBorder) address_.advance();
|
||||
const int column = c - LineLayout::EndOfLeftBorder;
|
||||
const auto source = address_.address();
|
||||
line_.data[column] = memory_[source];
|
||||
}
|
||||
}
|
||||
);
|
||||
pixel_line(line, begin, end);
|
||||
} else if(line < FrameLayout<timing>::EndOfBottomBorder) {
|
||||
border_line(begin, end);
|
||||
} else if(line < FrameLayout<timing>::EndOfFrontPorch) {
|
||||
porch_line(begin, end);
|
||||
} else if(line < FrameLayout<timing>::EndOfSync) {
|
||||
sync_line(begin, end);
|
||||
} else if(line < FrameLayout<timing>::EndOfBackPorch) {
|
||||
porch_line(begin, end);
|
||||
} else {
|
||||
border_line(begin, end);
|
||||
}
|
||||
},
|
||||
[&] {
|
||||
reset();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void set_scan_target(Outputs::Display::ScanTarget *const target) {
|
||||
crt_.set_scan_target(target);
|
||||
}
|
||||
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const {
|
||||
return crt_.get_scaled_scan_status() * 2;
|
||||
}
|
||||
|
||||
void set_display_type(const Outputs::Display::DisplayType display_type) {
|
||||
crt_.set_display_type(display_type);
|
||||
}
|
||||
|
||||
Outputs::Display::DisplayType get_display_type() const {
|
||||
return crt_.get_display_type();
|
||||
}
|
||||
|
||||
private:
|
||||
MemoryAccessT &memory_;
|
||||
Cycles cycles_;
|
||||
Numeric::DividingAccumulator<64, FrameLayout<timing>::EndOfField> position_;
|
||||
};
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user