1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-04-09 15:39:08 +00:00

Adds first, incomplete attempts to talk to a ScanTarget from the CRT.

Does away with the hassle of `unsigned` while I'm here; that was a schoolboy error.
This commit is contained in:
Thomas Harte 2018-11-03 19:58:44 -04:00
parent 373820f080
commit da4d883321
22 changed files with 335 additions and 511 deletions

View File

@ -68,19 +68,19 @@ template <class BusHandler> class MOS6560 {
audio_generator_(audio_queue_),
speaker_(audio_generator_)
{
crt_->set_svideo_sampling_function(
"vec2 svideo_sample(usampler2D texID, vec2 coordinate, float phase, float amplitude)"
"{"
"vec2 yc = texture(texID, coordinate).rg / vec2(255.0);"
"float phaseOffset = 6.283185308 * 2.0 * yc.y;"
"float chroma = step(yc.y, 0.75) * cos(phase + phaseOffset);"
"return vec2(yc.x, chroma);"
"}");
// crt_->set_svideo_sampling_function(
// "vec2 svideo_sample(usampler2D texID, vec2 coordinate, float phase, float amplitude)"
// "{"
// "vec2 yc = texture(texID, coordinate).rg / vec2(255.0);"
//
// "float phaseOffset = 6.283185308 * 2.0 * yc.y;"
// "float chroma = step(yc.y, 0.75) * cos(phase + phaseOffset);"
//
// "return vec2(yc.x, chroma);"
// "}");
// default to s-video output
crt_->set_video_signal(Outputs::CRT::VideoSignal::SVideo);
// crt_->set_video_signal(Outputs::CRT::VideoSignal::SVideo);
// default to NTSC
set_output_mode(OutputMode::NTSC);
@ -155,7 +155,7 @@ template <class BusHandler> class MOS6560 {
break;
}
crt_->set_new_display_type(static_cast<unsigned int>(timing_.cycles_per_line*4), display_type);
crt_->set_new_display_type(timing_.cycles_per_line*4, display_type);
switch(output_mode) {
case OutputMode::PAL:
@ -465,7 +465,7 @@ template <class BusHandler> class MOS6560 {
enum State {
Sync, ColourBurst, Border, Pixels
} this_state_, output_state_;
unsigned int cycles_in_state_;
int cycles_in_state_;
// counters that cover an entire field
int horizontal_counter_ = 0, vertical_counter_ = 0;
@ -511,7 +511,7 @@ template <class BusHandler> class MOS6560 {
uint16_t colours_[16];
uint16_t *pixel_pointer;
void output_border(unsigned int number_of_cycles) {
void output_border(int number_of_cycles) {
uint16_t *colour_pointer = reinterpret_cast<uint16_t *>(crt_->allocate_write_area(1));
if(colour_pointer) *colour_pointer = registers_.borderColour;
crt_->output_level(number_of_cycles);

View File

@ -86,12 +86,12 @@ TMS9918::TMS9918(Personality p):
Base(p) {
// Unimaginatively, this class just passes RGB through to the shader. Investigation is needed
// into whether there's a more natural form.
crt_->set_rgb_sampling_function(
"vec3 rgb_sample(usampler2D sampler, vec2 coordinate)"
"{"
"return texture(sampler, coordinate).rgb / vec3(255.0);"
"}");
crt_->set_video_signal(Outputs::CRT::VideoSignal::RGB);
// crt_->set_rgb_sampling_function(
// "vec3 rgb_sample(usampler2D sampler, vec2 coordinate)"
// "{"
// "return texture(sampler, coordinate).rgb / vec3(255.0);"
// "}");
// crt_->set_video_signal(Outputs::CRT::VideoSignal::RGB);
crt_->set_visible_area(Outputs::CRT::Rect(0.055f, 0.025f, 0.9f, 0.9f));
// The TMS remains in-phase with the NTSC colour clock; this is an empirical measurement
@ -410,7 +410,7 @@ void TMS9918::run_for(const HalfCycles cycles) {
if(!asked_for_write_area_) {
asked_for_write_area_ = true;
pixel_origin_ = pixel_target_ = reinterpret_cast<uint32_t *>(
crt_->allocate_write_area(static_cast<unsigned int>(line_buffer.next_border_column - line_buffer.first_pixel_output_column))
crt_->allocate_write_area(size_t(line_buffer.next_border_column - line_buffer.first_pixel_output_column))
);
}
@ -427,8 +427,8 @@ void TMS9918::run_for(const HalfCycles cycles) {
}
if(end == line_buffer.next_border_column) {
const unsigned int length = static_cast<unsigned int>(line_buffer.next_border_column - line_buffer.first_pixel_output_column);
crt_->output_data(length * 4, length);
const int length = line_buffer.next_border_column - line_buffer.first_pixel_output_column;
crt_->output_data(length * 4, size_t(length));
pixel_origin_ = pixel_target_ = nullptr;
asked_for_write_area_ = false;
}
@ -479,7 +479,7 @@ void Base::output_border(int cycles, uint32_t cram_dot) {
if(cycles) {
uint32_t *const pixel_target = reinterpret_cast<uint32_t *>(crt_->allocate_write_area(1));
*pixel_target = border_colour;
crt_->output_level(static_cast<unsigned int>(cycles));
crt_->output_level(cycles);
}
}

View File

@ -222,7 +222,7 @@ class CRTCBusHandler {
case OutputMode::Border: output_border(cycles_); break;
case OutputMode::ColourBurst: crt_->output_default_colour_burst(cycles_ * 16); break;
case OutputMode::Pixels:
crt_->output_data(cycles_ * 16, cycles_ * 16 / pixel_divider_);
crt_->output_data(cycles_ * 16, size_t(cycles_ * 16 / pixel_divider_));
pixel_pointer_ = pixel_data_ = nullptr;
break;
}
@ -283,7 +283,7 @@ class CRTCBusHandler {
// widths so it's not necessarily possible to predict the correct number in advance
// and using the upper bound could lead to inefficient behaviour
if(pixel_pointer_ == pixel_data_ + 320) {
crt_->output_data(cycles_ * 16, cycles_ * 16 / pixel_divider_);
crt_->output_data(cycles_ * 16, size_t(cycles_ * 16 / pixel_divider_));
pixel_pointer_ = pixel_data_ = nullptr;
cycles_ = 0;
}
@ -326,14 +326,14 @@ class CRTCBusHandler {
/// Constructs an appropriate CRT for video output.
void setup_output(float aspect_ratio) {
crt_.reset(new Outputs::CRT::CRT(1024, 16, Outputs::CRT::DisplayType::PAL50, 1));
crt_->set_rgb_sampling_function(
"vec3 rgb_sample(usampler2D sampler, vec2 coordinate)"
"{"
"uint sample = texture(texID, coordinate).r;"
"return vec3(float((sample >> 4) & 3u), float((sample >> 2) & 3u), float(sample & 3u)) / 2.0;"
"}");
// crt_->set_rgb_sampling_function(
// "vec3 rgb_sample(usampler2D sampler, vec2 coordinate)"
// "{"
// "uint sample = texture(texID, coordinate).r;"
// "return vec3(float((sample >> 4) & 3u), float((sample >> 2) & 3u), float(sample & 3u)) / 2.0;"
// "}");
crt_->set_visible_area(Outputs::CRT::Rect(0.1072f, 0.1f, 0.842105263157895f, 0.842105263157895f));
crt_->set_video_signal(Outputs::CRT::VideoSignal::RGB);
// crt_->set_video_signal(Outputs::CRT::VideoSignal::RGB);
}
/// Destructs the CRT.
@ -376,7 +376,7 @@ class CRTCBusHandler {
}
private:
void output_border(unsigned int length) {
void output_border(int length) {
uint8_t *colour_pointer = static_cast<uint8_t *>(crt_->allocate_write_area(1));
if(colour_pointer) *colour_pointer = border_;
crt_->output_level(length * 16);
@ -528,7 +528,7 @@ class CRTCBusHandler {
Border,
Pixels
} previous_output_mode_ = OutputMode::Sync;
unsigned int cycles_ = 0;
int cycles_ = 0;
bool was_hsync_ = false, was_vsync_ = false;
int cycles_into_hsync_ = 0;
@ -540,7 +540,7 @@ class CRTCBusHandler {
int next_mode_ = 2, mode_ = 2;
unsigned int pixel_divider_ = 1;
int pixel_divider_ = 1;
uint16_t mode0_output_[256];
uint32_t mode1_output_[256];
uint64_t mode2_output_[256];

View File

@ -17,14 +17,14 @@ VideoBase::VideoBase(bool is_iie, std::function<void(Cycles)> &&target) :
// Set a composite sampling function that assumes one byte per pixel input, and
// accepts any non-zero value as being fully on, zero being fully off.
crt_->set_composite_sampling_function(
"float composite_sample(usampler2D sampler, vec2 coordinate, float phase, float amplitude)"
"{"
"return clamp(texture(sampler, coordinate).r, 0.0, 0.66);"
"}");
// crt_->set_composite_sampling_function(
// "float composite_sample(usampler2D sampler, vec2 coordinate, float phase, float amplitude)"
// "{"
// "return clamp(texture(sampler, coordinate).r, 0.0, 0.66);"
// "}");
// Show only the centre 75% of the TV frame.
crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite);
// crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite);
crt_->set_visible_area(Outputs::CRT::Rect(0.118f, 0.122f, 0.77f, 0.77f));
crt_->set_immediate_default_phase(0.0f);

View File

@ -351,14 +351,14 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
const int blank_end = std::min(first_sync_column, ending_column);
if(blank_end > blank_start) {
if(blank_start > column_) {
crt_->output_sync(static_cast<unsigned int>(blank_start - column_) * 14);
crt_->output_sync((blank_start - column_) * 14);
}
crt_->output_blank(static_cast<unsigned int>(blank_end - blank_start) * 14);
crt_->output_blank((blank_end - blank_start) * 14);
if(blank_end < ending_column) {
crt_->output_sync(static_cast<unsigned int>(ending_column - blank_end) * 14);
crt_->output_sync((ending_column - blank_end) * 14);
}
} else {
crt_->output_sync(static_cast<unsigned int>(cycles_this_line) * 14);
crt_->output_sync((cycles_this_line) * 14);
}
} else {
const GraphicsMode line_mode = graphics_mode(row_);
@ -527,7 +527,7 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
const int colour_burst_start = std::max(first_sync_column + sync_length + 1, column_);
const int colour_burst_end = std::min(first_sync_column + sync_length + 4, ending_column);
if(colour_burst_end > colour_burst_start) {
crt_->output_colour_burst(static_cast<unsigned int>(colour_burst_end - colour_burst_start) * 14, 192);
crt_->output_colour_burst((colour_burst_end - colour_burst_start) * 14, 192);
}
second_blank_start = std::max(first_sync_column + sync_length + 3, column_);
@ -536,7 +536,7 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
}
if(ending_column > second_blank_start) {
crt_->output_blank(static_cast<unsigned int>(ending_column - second_blank_start) * 14);
crt_->output_blank((ending_column - second_blank_start) * 14);
}
}

View File

@ -181,15 +181,15 @@ class ConcreteMachine:
}
// to satisfy Outputs::CRT::Delegate
void crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs) override {
void crt_did_end_batch_of_frames(Outputs::CRT::CRT *crt, int number_of_frames, int number_of_unexpected_vertical_syncs) override {
const std::size_t number_of_frame_records = sizeof(frame_records_) / sizeof(frame_records_[0]);
frame_records_[frame_record_pointer_ % number_of_frame_records].number_of_frames = number_of_frames;
frame_records_[frame_record_pointer_ % number_of_frame_records].number_of_unexpected_vertical_syncs = number_of_unexpected_vertical_syncs;
frame_record_pointer_ ++;
if(frame_record_pointer_ >= 6) {
unsigned int total_number_of_frames = 0;
unsigned int total_number_of_unexpected_vertical_syncs = 0;
int total_number_of_frames = 0;
int total_number_of_unexpected_vertical_syncs = 0;
for(std::size_t c = 0; c < number_of_frame_records; c++) {
total_number_of_frames += frame_records_[c].number_of_frames;
total_number_of_unexpected_vertical_syncs += frame_records_[c].number_of_unexpected_vertical_syncs;
@ -228,10 +228,8 @@ class ConcreteMachine:
// output frame rate tracker
struct FrameRecord {
unsigned int number_of_frames;
unsigned int number_of_unexpected_vertical_syncs;
FrameRecord() : number_of_frames(0), number_of_unexpected_vertical_syncs(0) {}
int number_of_frames = 0;
int number_of_unexpected_vertical_syncs = 0;
} frame_records_[4];
unsigned int frame_record_pointer_ = 0;
bool is_ntsc_ = true;

View File

@ -25,7 +25,7 @@ namespace {
TIA::TIA(bool create_crt) {
if(create_crt) {
crt_.reset(new Outputs::CRT::CRT(cycles_per_line * 2 - 1, 1, Outputs::CRT::DisplayType::NTSC60, 1));
crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite);
// crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite);
set_output_mode(OutputMode::NTSC);
}
@ -123,33 +123,33 @@ void TIA::set_output_mode(Atari2600::TIA::OutputMode output_mode) {
Outputs::CRT::DisplayType display_type;
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));"
"}");
// 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::CRT::DisplayType::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));"
"}");
// 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::CRT::DisplayType::PAL50;
}
crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite);
// crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite);
// 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
@ -407,11 +407,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(static_cast<unsigned int>((horizontal_counter_ - output_cursor) * 2)); \
if(crt_) crt_->function((horizontal_counter_ - output_cursor) * 2); \
horizontal_counter_ %= cycles_per_line; \
return; \
} else { \
if(crt_) crt_->function(static_cast<unsigned int>((target - output_cursor) * 2)); \
if(crt_) crt_->function((target - output_cursor) * 2); \
output_cursor = target; \
} \
}
@ -438,14 +438,14 @@ void TIA::output_for_cycles(int number_of_cycles) {
if(pixel_target_) {
output_pixels(pixels_start_location_, output_cursor);
if(crt_) {
const unsigned int data_length = static_cast<unsigned int>(output_cursor - pixels_start_location_);
crt_->output_data(data_length * 2, 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(static_cast<unsigned int>(duration * 2));
if(crt_) crt_->output_blank(duration * 2);
} else {
if(!pixels_start_location_ && crt_) {
pixels_start_location_ = output_cursor;
@ -462,8 +462,8 @@ void TIA::output_for_cycles(int number_of_cycles) {
}
if(horizontal_counter_ == cycles_per_line && crt_) {
const unsigned int data_length = static_cast<unsigned int>(output_cursor - pixels_start_location_);
crt_->output_data(data_length * 2, 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;
}

View File

@ -92,7 +92,7 @@ class Machine {
Forwards the video signal to the CRT returned by get_crt().
*/
virtual void set_video_signal(Outputs::CRT::VideoSignal video_signal) {
get_crt()->set_video_signal(video_signal);
// get_crt()->set_video_signal(video_signal);
}
private:

View File

@ -171,7 +171,7 @@ class ConcreteMachine:
void setup_output(float aspect_ratio) override {
vdp_.reset(new TI::TMS::TMS9918(TI::TMS::TMS9918A));
get_crt()->set_video_signal(Outputs::CRT::VideoSignal::Composite);
// get_crt()->set_video_signal(Outputs::CRT::VideoSignal::Composite);
}
void close_output() override {

View File

@ -44,12 +44,12 @@ VideoOutput::VideoOutput(uint8_t *memory) : ram_(memory) {
setup_base_address();
crt_.reset(new Outputs::CRT::CRT(crt_cycles_per_line, 8, Outputs::CRT::DisplayType::PAL50, 1));
crt_->set_rgb_sampling_function(
"vec3 rgb_sample(usampler2D sampler, vec2 coordinate)"
"{"
"uint texValue = texture(sampler, coordinate).r;"
"return vec3( uvec3(texValue) & uvec3(4u, 2u, 1u));"
"}");
// crt_->set_rgb_sampling_function(
// "vec3 rgb_sample(usampler2D sampler, vec2 coordinate)"
// "{"
// "uint texValue = texture(sampler, coordinate).r;"
// "return vec3( uvec3(texValue) & uvec3(4u, 2u, 1u));"
// "}");
// TODO: as implied below, I've introduced a clock's latency into the graphics pipeline somehow. Investigate.
crt_->set_visible_area(crt_->get_rect_for_area(first_graphics_line - 1, 256, (first_graphics_cycle+1) * crt_cycles_multiplier, 80 * crt_cycles_multiplier, 4.0f / 3.0f));
}
@ -88,19 +88,19 @@ void VideoOutput::start_pixel_line() {
void VideoOutput::end_pixel_line() {
if(current_output_target_) {
const unsigned int data_length = static_cast<unsigned int>(current_output_target_ - initial_output_target_);
crt_->output_data(data_length * current_output_divider_, data_length);
const int data_length = int(current_output_target_ - initial_output_target_);
crt_->output_data(data_length * current_output_divider_, size_t(data_length));
}
current_character_row_++;
}
void VideoOutput::output_pixels(unsigned int number_of_cycles) {
void VideoOutput::output_pixels(int number_of_cycles) {
if(!number_of_cycles) return;
if(is_blank_line_) {
crt_->output_blank(number_of_cycles * crt_cycles_multiplier);
} else {
unsigned int divider = 1;
int divider = 1;
switch(screen_mode_) {
case 0: case 3: divider = 1; break;
case 1: case 4: case 6: divider = 2; break;
@ -109,11 +109,11 @@ void VideoOutput::output_pixels(unsigned int number_of_cycles) {
if(!initial_output_target_ || divider != current_output_divider_) {
if(current_output_target_) {
const unsigned int data_length = static_cast<unsigned int>(current_output_target_ - initial_output_target_);
crt_->output_data(data_length * current_output_divider_, data_length);
const int data_length = int(current_output_target_ - initial_output_target_);
crt_->output_data(data_length * current_output_divider_, size_t(data_length));
}
current_output_divider_ = divider;
initial_output_target_ = current_output_target_ = crt_->allocate_write_area(640 / current_output_divider_, 8 / divider);
initial_output_target_ = current_output_target_ = crt_->allocate_write_area(size_t(640 / current_output_divider_), size_t(8 / divider));
}
#define get_pixel() \
@ -230,16 +230,16 @@ void VideoOutput::run_for(const Cycles cycles) {
while(number_of_cycles) {
int draw_action_length = screen_map_[screen_map_pointer_].length;
int time_left_in_action = std::min(number_of_cycles, draw_action_length - cycles_into_draw_action_);
if(screen_map_[screen_map_pointer_].type == DrawAction::Pixels) output_pixels(static_cast<unsigned int>(time_left_in_action));
if(screen_map_[screen_map_pointer_].type == DrawAction::Pixels) output_pixels(time_left_in_action);
number_of_cycles -= time_left_in_action;
cycles_into_draw_action_ += time_left_in_action;
if(cycles_into_draw_action_ == draw_action_length) {
switch(screen_map_[screen_map_pointer_].type) {
case DrawAction::Sync: crt_->output_sync(static_cast<unsigned int>(draw_action_length * crt_cycles_multiplier)); break;
case DrawAction::ColourBurst: crt_->output_default_colour_burst(static_cast<unsigned int>(draw_action_length * crt_cycles_multiplier)); break;
case DrawAction::Blank: crt_->output_blank(static_cast<unsigned int>(draw_action_length * crt_cycles_multiplier)); break;
case DrawAction::Pixels: end_pixel_line(); break;
case DrawAction::Sync: crt_->output_sync(draw_action_length * crt_cycles_multiplier); break;
case DrawAction::ColourBurst: crt_->output_default_colour_burst(draw_action_length * crt_cycles_multiplier); break;
case DrawAction::Blank: crt_->output_blank(draw_action_length * crt_cycles_multiplier); break;
case DrawAction::Pixels: end_pixel_line(); break;
}
screen_map_pointer_ = (screen_map_pointer_ + 1) % screen_map_.size();
cycles_into_draw_action_ = 0;

View File

@ -77,7 +77,7 @@ class VideoOutput {
private:
inline void start_pixel_line();
inline void end_pixel_line();
inline void output_pixels(unsigned int number_of_cycles);
inline void output_pixels(int number_of_cycles);
inline void setup_base_address();
int output_position_ = 0;
@ -109,7 +109,7 @@ class VideoOutput {
// CRT output
uint8_t *current_output_target_ = nullptr;
uint8_t *initial_output_target_ = nullptr;
unsigned int current_output_divider_ = 1;
int current_output_divider_ = 1;
std::unique_ptr<Outputs::CRT::CRT> crt_;

View File

@ -172,7 +172,7 @@ class ConcreteMachine:
vdp_->set_tv_standard(
(region_ == Target::Region::Europe) ?
TI::TMS::TVStandard::PAL : TI::TMS::TVStandard::NTSC);
get_crt()->set_video_signal(Outputs::CRT::VideoSignal::Composite);
// get_crt()->set_video_signal(Outputs::CRT::VideoSignal::Composite);
time_until_debounce_ = vdp_->get_time_until_line(-1);
}

View File

@ -24,21 +24,21 @@ VideoOutput::VideoOutput(uint8_t *memory) :
crt_(new Outputs::CRT::CRT(64*6, 6, Outputs::CRT::DisplayType::PAL50, 2)),
v_sync_start_position_(PAL50VSyncStartPosition), v_sync_end_position_(PAL50VSyncEndPosition),
counter_period_(PAL50Period) {
crt_->set_rgb_sampling_function(
"vec3 rgb_sample(usampler2D sampler, vec2 coordinate)"
"{"
"uint texValue = texture(sampler, coordinate).r;"
"return vec3( uvec3(texValue) & uvec3(4u, 2u, 1u));"
"}");
crt_->set_composite_sampling_function(
"float composite_sample(usampler2D sampler, vec2 coordinate, float phase, float amplitude)"
"{"
"uint texValue = uint(dot(texture(sampler, coordinate).rg, uvec2(1, 256)));"
"uint iPhase = uint((phase + 3.141592654 + 0.39269908175) * 2.0 / 3.141592654) & 3u;"
"texValue = (texValue >> (4u*(3u - iPhase))) & 15u;"
"return (float(texValue) - 4.0) / 20.0;"
"}"
);
// crt_->set_rgb_sampling_function(
// "vec3 rgb_sample(usampler2D sampler, vec2 coordinate)"
// "{"
// "uint texValue = texture(sampler, coordinate).r;"
// "return vec3( uvec3(texValue) & uvec3(4u, 2u, 1u));"
// "}");
// crt_->set_composite_sampling_function(
// "float composite_sample(usampler2D sampler, vec2 coordinate, float phase, float amplitude)"
// "{"
// "uint texValue = uint(dot(texture(sampler, coordinate).rg, uvec2(1, 256)));"
// "uint iPhase = uint((phase + 3.141592654 + 0.39269908175) * 2.0 / 3.141592654) & 3u;"
// "texValue = (texValue >> (4u*(3u - iPhase))) & 15u;"
// "return (float(texValue) - 4.0) / 20.0;"
// "}"
// );
crt_->set_composite_function_type(Outputs::CRT::CRT::CompositeSourceType::DiscreteFourSamplesPerCycle, 0.0f);
set_video_signal(Outputs::CRT::VideoSignal::Composite);
@ -47,7 +47,7 @@ VideoOutput::VideoOutput(uint8_t *memory) :
void VideoOutput::set_video_signal(Outputs::CRT::VideoSignal video_signal) {
video_signal_ = video_signal;
crt_->set_video_signal(video_signal);
// crt_->set_video_signal(video_signal);
}
void VideoOutput::set_colour_rom(const std::vector<uint8_t> &rom) {
@ -86,7 +86,7 @@ void VideoOutput::run_for(const Cycles cycles) {
if(counter_ >= v_sync_start_position_ && counter_ < v_sync_end_position_) {
// this is a sync line
cycles_run_for = v_sync_end_position_ - counter_;
clamp(crt_->output_sync(static_cast<unsigned int>(v_sync_end_position_ - v_sync_start_position_) * 6));
clamp(crt_->output_sync((v_sync_end_position_ - v_sync_start_position_) * 6));
} else if(counter_ < 224*64 && h_counter < 40) {
// this is a pixel line
if(!h_counter) {
@ -199,7 +199,7 @@ void VideoOutput::run_for(const Cycles cycles) {
cycles_run_for = 48 - h_counter;
clamp(
int period = (counter_ < 224*64) ? 8 : 48;
crt_->output_blank(static_cast<unsigned int>(period) * 6);
crt_->output_blank(period * 6);
);
} else if(h_counter < 54) {
cycles_run_for = 54 - h_counter;

View File

@ -25,14 +25,14 @@ Video::Video() :
// Set a composite sampling function that assumes two-level input; either a byte is 0, which is black,
// or it is non-zero, which is white.
crt_->set_composite_sampling_function(
"float composite_sample(usampler2D sampler, vec2 coordinate, float phase, float amplitude)"
"{"
"return texture(sampler, coordinate).r;"
"}");
// crt_->set_composite_sampling_function(
// "float composite_sample(usampler2D sampler, vec2 coordinate, float phase, float amplitude)"
// "{"
// "return texture(sampler, coordinate).r;"
// "}");
// Show only the centre 80% of the TV frame.
crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite);
// crt_->set_video_signal(Outputs::CRT::VideoSignal::Composite);
crt_->set_visible_area(Outputs::CRT::Rect(0.1f, 0.1f, 0.8f, 0.8f));
}
@ -48,7 +48,7 @@ void Video::flush() {
void Video::flush(bool next_sync) {
if(sync_) {
// If in sync, that takes priority. Output the proper amount of sync.
crt_->output_sync(static_cast<unsigned int>(time_since_update_.as_int()));
crt_->output_sync(time_since_update_.as_int());
} else {
// If not presently in sync, then...
@ -58,7 +58,7 @@ void Video::flush(bool next_sync) {
int data_length = static_cast<int>(line_data_pointer_ - line_data_);
if(data_length < time_since_update_.as_int() || next_sync) {
auto output_length = std::min(data_length, time_since_update_.as_int());
crt_->output_data(static_cast<unsigned int>(output_length), static_cast<unsigned int>(output_length));
crt_->output_data(output_length);
line_data_pointer_ = line_data_ = nullptr;
time_since_update_ -= HalfCycles(output_length);
} else return;
@ -67,7 +67,7 @@ void Video::flush(bool next_sync) {
// Any pending pixels being dealt with, pad with the white level.
uint8_t *colour_pointer = static_cast<uint8_t *>(crt_->allocate_write_area(1));
if(colour_pointer) *colour_pointer = 0xff;
crt_->output_level(static_cast<unsigned int>(time_since_update_.as_int()));
crt_->output_level(time_since_update_.as_int());
}
time_since_update_ = 0;

View File

@ -718,6 +718,7 @@
4B0B6E121C9DBD5D00FFB60D /* CRTConstants.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTConstants.hpp; sourceTree = "<group>"; };
4B0CCC421C62D0B3001CAC5F /* CRT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRT.cpp; sourceTree = "<group>"; };
4B0CCC431C62D0B3001CAC5F /* CRT.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRT.hpp; sourceTree = "<group>"; };
4B0CD7252189117C00665042 /* ScanTarget.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ScanTarget.hpp; sourceTree = "<group>"; };
4B0E04E81FC9E5DA00F43484 /* CAS.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CAS.cpp; sourceTree = "<group>"; };
4B0E04E91FC9E5DA00F43484 /* CAS.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CAS.hpp; sourceTree = "<group>"; };
4B0E04F81FC9FA3000F43484 /* 9918.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = 9918.hpp; path = 9918/9918.hpp; sourceTree = "<group>"; };
@ -1527,6 +1528,7 @@
4B0CCC411C62D0B3001CAC5F /* CRT */ = {
isa = PBXGroup;
children = (
4B0CD7252189117C00665042 /* ScanTarget.hpp */,
4B0CCC421C62D0B3001CAC5F /* CRT.cpp */,
4B0CCC431C62D0B3001CAC5F /* CRT.hpp */,
4BBF99191C8FC2750075DAFB /* CRTTypes.hpp */,

View File

@ -231,12 +231,12 @@ struct ActivityObserver: public Activity::Observer {
_machine->crt_machine()->setup_output(aspectRatio);
// Since OS X v10.6, Macs have had a gamma of 2.2.
_machine->crt_machine()->get_crt()->set_output_gamma(2.2f);
_machine->crt_machine()->get_crt()->set_target_framebuffer(0);
// _machine->crt_machine()->get_crt()->set_output_gamma(2.2f);
// _machine->crt_machine()->get_crt()->set_target_framebuffer(0);
}
- (void)drawViewForPixelSize:(CGSize)pixelSize onlyIfDirty:(BOOL)onlyIfDirty {
_machine->crt_machine()->get_crt()->draw_frame((unsigned int)pixelSize.width, (unsigned int)pixelSize.height, onlyIfDirty ? true : false);
// _machine->crt_machine()->get_crt()->draw_frame((unsigned int)pixelSize.width, (unsigned int)pixelSize.height, onlyIfDirty ? true : false);
}
- (void)paste:(NSString *)paste {

View File

@ -15,18 +15,18 @@
using namespace Outputs::CRT;
void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, unsigned int vertical_sync_half_lines, bool should_alternate) {
openGL_output_builder_.set_colour_format(colour_space, colour_cycle_numerator, colour_cycle_denominator);
void CRT::set_new_timing(int cycles_per_line, int height_of_display, ColourSpace colour_space, int colour_cycle_numerator, int colour_cycle_denominator, int vertical_sync_half_lines, bool should_alternate) {
// openGL_output_builder_.set_colour_format(colour_space, colour_cycle_numerator, colour_cycle_denominator);
const unsigned int millisecondsHorizontalRetraceTime = 7; // source: Dictionary of Video and Television Technology, p. 234
const unsigned int scanlinesVerticalRetraceTime = 8; // source: ibid
const int millisecondsHorizontalRetraceTime = 7; // source: Dictionary of Video and Television Technology, p. 234
const int scanlinesVerticalRetraceTime = 8; // source: ibid
// To quote:
//
// "retrace interval; The interval of time for the return of the blanked scanning beam of
// a TV picture tube or camera tube to the starting point of a line or field. It is about
// 7 microseconds for horizontal retrace and 500 to 750 microseconds for vertical retrace
// in NTSC and PAL TV."
// To quote:
//
// "retrace interval; The interval of time for the return of the blanked scanning beam of
// a TV picture tube or camera tube to the starting point of a line or field. It is about
// 7 microseconds for horizontal retrace and 500 to 750 microseconds for vertical retrace
// in NTSC and PAL TV."
time_multiplier_ = IntermediateBufferWidth / cycles_per_line;
phase_denominator_ = cycles_per_line * colour_cycle_denominator * time_multiplier_;
@ -35,7 +35,7 @@ void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_di
phase_alternates_ = should_alternate;
is_alernate_line_ &= phase_alternates_;
cycles_per_line_ = cycles_per_line;
unsigned int multiplied_cycles_per_line = cycles_per_line * time_multiplier_;
const int multiplied_cycles_per_line = cycles_per_line * time_multiplier_;
// allow sync to be detected (and acted upon) a line earlier than the specified requirement,
// as a simple way of avoiding not-quite-exact comparison issues while still being true enough to
@ -55,13 +55,15 @@ void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_di
vertical_flywheel_.reset(new Flywheel(multiplied_cycles_per_line * height_of_display, scanlinesVerticalRetraceTime * multiplied_cycles_per_line, (multiplied_cycles_per_line * height_of_display) >> 3));
// figure out the divisor necessary to get the horizontal flywheel into a 16-bit range
unsigned int real_clock_scan_period = (multiplied_cycles_per_line * height_of_display) / (time_multiplier_ * common_output_divisor_);
const int real_clock_scan_period = (multiplied_cycles_per_line * height_of_display) / (time_multiplier_ * common_output_divisor_);
vertical_flywheel_output_divider_ = static_cast<uint16_t>(ceilf(real_clock_scan_period / 65536.0f) * (time_multiplier_ * common_output_divisor_));
openGL_output_builder_.set_timing(cycles_per_line, multiplied_cycles_per_line, height_of_display, horizontal_flywheel_->get_scan_period(), vertical_flywheel_->get_scan_period(), vertical_flywheel_output_divider_);
// openGL_output_builder_.set_timing(cycles_per_line, multiplied_cycles_per_line, height_of_display, horizontal_flywheel_->get_scan_period(), vertical_flywheel_->get_scan_period(), vertical_flywheel_output_divider_);
// TODO: set scan_target modals.
}
void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType displayType) {
void CRT::set_new_display_type(int cycles_per_line, DisplayType displayType) {
switch(displayType) {
case DisplayType::PAL50:
set_new_timing(cycles_per_line, 312, ColourSpace::YUV, 709379, 2500, 5, true); // i.e. 283.7516; 2.5 lines = vertical sync
@ -84,175 +86,99 @@ void CRT::set_composite_function_type(CompositeSourceType type, float offset_of_
}
void CRT::set_input_gamma(float gamma) {
input_gamma_ = gamma;
update_gamma();
// input_gamma_ = gamma;
// update_gamma();
}
void CRT::set_output_gamma(float gamma) {
output_gamma_ = gamma;
update_gamma();
}
CRT::CRT(int common_output_divisor, int buffer_depth) :
common_output_divisor_(common_output_divisor) {}
void CRT::update_gamma() {
float gamma_ratio = input_gamma_ / output_gamma_;
openGL_output_builder_.set_gamma(gamma_ratio);
}
CRT::CRT(unsigned int common_output_divisor, unsigned int buffer_depth) :
common_output_divisor_(common_output_divisor),
openGL_output_builder_(buffer_depth) {}
CRT::CRT( unsigned int cycles_per_line,
unsigned int common_output_divisor,
unsigned int height_of_display,
CRT::CRT( int cycles_per_line,
int common_output_divisor,
int height_of_display,
ColourSpace colour_space,
unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator,
unsigned int vertical_sync_half_lines,
int colour_cycle_numerator, int colour_cycle_denominator,
int vertical_sync_half_lines,
bool should_alternate,
unsigned int buffer_depth) :
int buffer_depth) :
CRT(common_output_divisor, buffer_depth) {
set_new_timing(cycles_per_line, height_of_display, colour_space, colour_cycle_numerator, colour_cycle_denominator, vertical_sync_half_lines, should_alternate);
}
CRT::CRT(unsigned int cycles_per_line, unsigned int common_output_divisor, DisplayType displayType, unsigned int buffer_depth) :
CRT::CRT(int cycles_per_line, int common_output_divisor, DisplayType displayType, int buffer_depth) :
CRT(common_output_divisor, buffer_depth) {
set_new_display_type(cycles_per_line, displayType);
}
// MARK: - Sync loop
Flywheel::SyncEvent CRT::get_next_vertical_sync_event(bool vsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced) {
Flywheel::SyncEvent CRT::get_next_vertical_sync_event(bool vsync_is_requested, int cycles_to_run_for, int *cycles_advanced) {
return vertical_flywheel_->get_next_event_in_period(vsync_is_requested, cycles_to_run_for, cycles_advanced);
}
Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced) {
Flywheel::SyncEvent CRT::get_next_horizontal_sync_event(bool hsync_is_requested, int cycles_to_run_for, int *cycles_advanced) {
return horizontal_flywheel_->get_next_event_in_period(hsync_is_requested, cycles_to_run_for, cycles_advanced);
}
#define output_x1() (*reinterpret_cast<uint16_t *>(&next_output_run[OutputVertexOffsetOfHorizontal + 0]))
#define output_x2() (*reinterpret_cast<uint16_t *>(&next_output_run[OutputVertexOffsetOfHorizontal + 2]))
#define output_position_y() (*reinterpret_cast<uint16_t *>(&next_output_run[OutputVertexOffsetOfVertical + 0]))
#define output_tex_y() (*reinterpret_cast<uint16_t *>(&next_output_run[OutputVertexOffsetOfVertical + 2]))
#define source_input_position_y() (*reinterpret_cast<uint16_t *>(&next_run[SourceVertexOffsetOfInputStart + 2]))
#define source_output_position_x1() (*reinterpret_cast<uint16_t *>(&next_run[SourceVertexOffsetOfOutputStart + 0]))
#define source_output_position_x2() (*reinterpret_cast<uint16_t *>(&next_run[SourceVertexOffsetOfEnds + 2]))
#define source_phase() next_run[SourceVertexOffsetOfPhaseTimeAndAmplitude + 0]
#define source_amplitude() next_run[SourceVertexOffsetOfPhaseTimeAndAmplitude + 1]
void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bool vsync_requested, const Scan::Type type) {
std::unique_lock<std::mutex> output_lock = openGL_output_builder_.get_output_lock();
void CRT::advance_cycles(int number_of_cycles, bool hsync_requested, bool vsync_requested, const Scan::Type type, int number_of_samples) {
number_of_cycles *= time_multiplier_;
bool is_output_run = ((type == Scan::Type::Level) || (type == Scan::Type::Data));
const bool is_output_run = ((type == Scan::Type::Level) || (type == Scan::Type::Data));
const auto total_cycles = number_of_cycles * time_multiplier_;
while(number_of_cycles) {
unsigned int time_until_vertical_sync_event, time_until_horizontal_sync_event;
// Get time until next horizontal and vertical sync generator events.
int time_until_vertical_sync_event, time_until_horizontal_sync_event;
Flywheel::SyncEvent next_vertical_sync_event = get_next_vertical_sync_event(vsync_requested, number_of_cycles, &time_until_vertical_sync_event);
Flywheel::SyncEvent next_horizontal_sync_event = get_next_horizontal_sync_event(hsync_requested, time_until_vertical_sync_event, &time_until_horizontal_sync_event);
// get the next sync event and its timing; hsync request is instantaneous (being edge triggered) so
// set it to false for the next run through this loop (if any)
unsigned int next_run_length = std::min(time_until_vertical_sync_event, time_until_horizontal_sync_event);
phase_numerator_ += next_run_length * colour_cycle_numerator_;
phase_numerator_ %= phase_denominator_;
// Whichever event is scheduled to happen first is the one to advance to.
int next_run_length = std::min(time_until_vertical_sync_event, time_until_horizontal_sync_event);
hsync_requested = false;
vsync_requested = false;
// Determine whether to output any data for this portion of the output; if so then grab somewhere to put it.
bool is_output_segment = ((is_output_run && next_run_length) && !horizontal_flywheel_->is_in_retrace() && !vertical_flywheel_->is_in_retrace());
uint8_t *next_run = nullptr;
if(is_output_segment && !openGL_output_builder_.composite_output_buffer_is_full()) {
bool did_retain_source_data = openGL_output_builder_.texture_builder.retain_latest();
if(did_retain_source_data) {
next_run = openGL_output_builder_.array_builder.get_input_storage(SourceVertexSize);
if(!next_run) {
openGL_output_builder_.texture_builder.discard_latest();
}
}
ScanTarget::Scan *const next_scan = is_output_segment ? scan_target_->get_scan() : nullptr;
// If outputting, store the start location and
if(next_scan) {
next_scan->end_points[0].x = static_cast<uint16_t>(horizontal_flywheel_->get_current_output_position());
next_scan->end_points[0].y = static_cast<uint16_t>(vertical_flywheel_->get_current_output_position());
next_scan->end_points[0].composite_angle = colour_burst_angle_; // TODO.
next_scan->end_points[0].data_offset = static_cast<uint16_t>((total_cycles - number_of_cycles) * number_of_samples / total_cycles);
next_scan->composite_amplitude = colour_burst_amplitude_;
}
if(next_run) {
// output_y and texture locations will be written later; we won't necessarily know what they are
// outside of the locked region
source_output_position_x1() = static_cast<uint16_t>(horizontal_flywheel_->get_current_output_position());
source_phase() = colour_burst_phase_;
// TODO: determine what the PAL phase-shift machines actually do re: the swinging burst.
source_amplitude() = phase_alternates_ ? 128 - colour_burst_amplitude_ : 128 + colour_burst_amplitude_;
}
// decrement the number of cycles left to run for and increment the
// horizontal counter appropriately
// Advance time: that'll affect both the colour subcarrier position and the number of cycles left to run.
phase_numerator_ += next_run_length * colour_cycle_numerator_;
phase_numerator_ %= phase_denominator_;
number_of_cycles -= next_run_length;
// react to the incoming event...
// React to the incoming event.
horizontal_flywheel_->apply_event(next_run_length, (next_run_length == time_until_horizontal_sync_event) ? next_horizontal_sync_event : Flywheel::SyncEvent::None);
vertical_flywheel_->apply_event(next_run_length, (next_run_length == time_until_vertical_sync_event) ? next_vertical_sync_event : Flywheel::SyncEvent::None);
if(next_run) {
source_output_position_x2() = static_cast<uint16_t>(horizontal_flywheel_->get_current_output_position());
}
// if this is horizontal retrace then advance the output line counter and bookend an output run
Flywheel::SyncEvent honoured_event = Flywheel::SyncEvent::None;
if(next_run_length == time_until_vertical_sync_event && next_vertical_sync_event != Flywheel::SyncEvent::None) honoured_event = next_vertical_sync_event;
if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event != Flywheel::SyncEvent::None) honoured_event = next_horizontal_sync_event;
bool needs_endpoint =
(honoured_event == Flywheel::SyncEvent::StartRetrace && is_writing_composite_run_) ||
(honoured_event == Flywheel::SyncEvent::EndRetrace && !horizontal_flywheel_->is_in_retrace() && !vertical_flywheel_->is_in_retrace());
if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event == Flywheel::SyncEvent::StartRetrace) is_alernate_line_ ^= phase_alternates_;
if(needs_endpoint) {
if(
!openGL_output_builder_.array_builder.is_full() &&
!openGL_output_builder_.composite_output_buffer_is_full()) {
if(!is_writing_composite_run_) {
output_run_.x1 = static_cast<uint16_t>(horizontal_flywheel_->get_current_output_position());
output_run_.y = static_cast<uint16_t>(vertical_flywheel_->get_current_output_position() / vertical_flywheel_output_divider_);
} else {
// Get and write all those previously unwritten output ys
const uint16_t output_y = openGL_output_builder_.get_composite_output_y();
// Construct the output run
uint8_t *next_output_run = openGL_output_builder_.array_builder.get_output_storage(OutputVertexSize);
if(next_output_run) {
output_x1() = output_run_.x1;
output_position_y() = output_run_.y;
output_tex_y() = output_y;
output_x2() = static_cast<uint16_t>(horizontal_flywheel_->get_current_output_position());
}
// TODO: below I've assumed a one-to-one correspondance with output runs and input data; that's
// obviously not completely sustainable. It's a latent bug.
openGL_output_builder_.array_builder.flush(
[=] (uint8_t *input_buffer, std::size_t input_size, uint8_t *output_buffer, std::size_t output_size) {
openGL_output_builder_.texture_builder.flush(
[=] (const std::vector<TextureBuilder::WriteArea> &write_areas, std::size_t number_of_write_areas) {
// assert(number_of_write_areas * SourceVertexSize == input_size);
if(number_of_write_areas * SourceVertexSize == input_size) {
for(std::size_t run = 0; run < number_of_write_areas; run++) {
*reinterpret_cast<uint16_t *>(&input_buffer[run * SourceVertexSize + SourceVertexOffsetOfInputStart + 0]) = write_areas[run].x;
*reinterpret_cast<uint16_t *>(&input_buffer[run * SourceVertexSize + SourceVertexOffsetOfInputStart + 2]) = write_areas[run].y;
*reinterpret_cast<uint16_t *>(&input_buffer[run * SourceVertexSize + SourceVertexOffsetOfEnds + 0]) = write_areas[run].x + write_areas[run].length;
}
}
});
for(std::size_t position = 0; position < input_size; position += SourceVertexSize) {
(*reinterpret_cast<uint16_t *>(&input_buffer[position + SourceVertexOffsetOfOutputStart + 2])) = output_y;
}
});
colour_burst_amplitude_ = 0;
}
is_writing_composite_run_ ^= true;
}
// Store an endpoint if necessary.
if(next_scan) {
next_scan->end_points[1].x = static_cast<uint16_t>(horizontal_flywheel_->get_current_output_position());
next_scan->end_points[1].y = static_cast<uint16_t>(vertical_flywheel_->get_current_output_position());
next_scan->end_points[1].composite_angle = colour_burst_angle_; // TODO.
next_scan->end_points[1].data_offset = static_cast<uint16_t>((total_cycles - number_of_cycles) * number_of_samples / total_cycles);
}
// If this is horizontal retrace then announce as such, and prepare for the next line.
if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event == Flywheel::SyncEvent::StartRetrace) {
openGL_output_builder_.increment_composite_output_y();
scan_target_->announce(Outputs::CRT::ScanTarget::Event::HorizontalRetrace);
is_alernate_line_ ^= phase_alternates_;
colour_burst_amplitude_ = 0;
}
// Also announce if this is vertical retrace.
if(next_run_length == time_until_vertical_sync_event && next_horizontal_sync_event == Flywheel::SyncEvent::StartRetrace) {
scan_target_->announce(Outputs::CRT::ScanTarget::Event::VerticalRetrace);
}
// if this is vertical retrace then adcance a field
@ -260,9 +186,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bo
if(delegate_) {
frames_since_last_delegate_call_++;
if(frames_since_last_delegate_call_ == 20) {
output_lock.unlock();
delegate_->crt_did_end_batch_of_frames(this, frames_since_last_delegate_call_, vertical_flywheel_->get_and_reset_number_of_surprises());
output_lock.lock();
frames_since_last_delegate_call_ = 0;
}
}
@ -270,29 +194,18 @@ void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bo
}
}
#undef output_x1
#undef output_x2
#undef output_position_y
#undef output_tex_y
#undef source_input_position_y
#undef source_output_position_x1
#undef source_output_position_x2
#undef source_phase
#undef source_amplitude
// MARK: - stream feeding methods
void CRT::output_scan(const Scan *const scan) {
// simplified colour burst logic: if it's within the back porch we'll take it
if(scan->type == Scan::Type::ColourBurst) {
if(!colour_burst_amplitude_ && horizontal_flywheel_->get_current_time() < (horizontal_flywheel_->get_standard_period() * 12) >> 6) {
unsigned int position_phase = (horizontal_flywheel_->get_current_time() * colour_cycle_numerator_ * 256) / phase_denominator_;
colour_burst_phase_ = (position_phase + scan->phase) & 255;
// int position_phase = (horizontal_flywheel_->get_current_time() * colour_cycle_numerator_ * 256) / phase_denominator_;
// colour_burst_phase_ = (position_phase + scan->phase) & 255;
colour_burst_amplitude_ = scan->amplitude;
if(colour_burst_phase_adjustment_ != 0xff)
colour_burst_phase_ = (colour_burst_phase_ & ~63) + colour_burst_phase_adjustment_;
// if(colour_burst_phase_adjustment_ != 0xff)
// colour_burst_phase_ = (colour_burst_phase_ & ~63) + colour_burst_phase_adjustment_;
}
}
// TODO: inspect raw data for potential colour burst if required; the DPLL and some zero crossing logic
@ -323,7 +236,7 @@ void CRT::output_scan(const Scan *const scan) {
}
}
unsigned int number_of_cycles = scan->number_of_cycles;
int number_of_cycles = scan->number_of_cycles;
bool vsync_requested = false;
// if sync is being accumulated then accumulate it; if it crosses the vertical sync threshold then
@ -332,10 +245,10 @@ void CRT::output_scan(const Scan *const scan) {
cycles_of_sync_ += scan->number_of_cycles;
if(this_is_sync && cycles_of_sync_ >= sync_capacitor_charge_threshold_) {
unsigned int overshoot = std::min(cycles_of_sync_ - sync_capacitor_charge_threshold_, number_of_cycles);
int overshoot = std::min(cycles_of_sync_ - sync_capacitor_charge_threshold_, number_of_cycles);
if(overshoot) {
number_of_cycles -= overshoot;
advance_cycles(number_of_cycles, hsync_requested, false, scan->type);
advance_cycles(number_of_cycles, hsync_requested, false, scan->type, 0);
hsync_requested = false;
number_of_cycles = overshoot;
}
@ -345,35 +258,36 @@ void CRT::output_scan(const Scan *const scan) {
}
}
advance_cycles(number_of_cycles, hsync_requested, vsync_requested, scan->type);
advance_cycles(number_of_cycles, hsync_requested, vsync_requested, scan->type, scan->number_of_samples);
}
/*
These all merely channel into advance_cycles, supplying appropriate arguments
*/
void CRT::output_sync(unsigned int number_of_cycles) {
void CRT::output_sync(int number_of_cycles) {
Scan scan;
scan.type = Scan::Type::Sync;
scan.number_of_cycles = number_of_cycles;
output_scan(&scan);
}
void CRT::output_blank(unsigned int number_of_cycles) {
void CRT::output_blank(int number_of_cycles) {
Scan scan;
scan.type = Scan::Type::Blank;
scan.number_of_cycles = number_of_cycles;
output_scan(&scan);
}
void CRT::output_level(unsigned int number_of_cycles) {
openGL_output_builder_.texture_builder.reduce_previous_allocation_to(1);
void CRT::output_level(int number_of_cycles) {
scan_target_->reduce_previous_allocation_to(1);
Scan scan;
scan.type = Scan::Type::Level;
scan.number_of_cycles = number_of_cycles;
scan.number_of_samples = 1;
output_scan(&scan);
}
void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint8_t amplitude) {
void CRT::output_colour_burst(int number_of_cycles, uint8_t phase, uint8_t amplitude) {
Scan scan;
scan.type = Scan::Type::ColourBurst;
scan.number_of_cycles = number_of_cycles;
@ -382,20 +296,21 @@ void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint
output_scan(&scan);
}
void CRT::output_default_colour_burst(unsigned int number_of_cycles) {
void CRT::output_default_colour_burst(int number_of_cycles) {
output_colour_burst(number_of_cycles, static_cast<uint8_t>((phase_numerator_ * 256) / phase_denominator_));
}
void CRT::set_immediate_default_phase(float phase) {
phase = fmodf(phase, 1.0f);
phase_numerator_ = static_cast<unsigned int>(phase * static_cast<float>(phase_denominator_));
phase_numerator_ = static_cast<int>(phase * static_cast<float>(phase_denominator_));
}
void CRT::output_data(unsigned int number_of_cycles, unsigned int number_of_samples) {
openGL_output_builder_.texture_builder.reduce_previous_allocation_to(number_of_samples);
void CRT::output_data(int number_of_cycles, size_t number_of_samples) {
scan_target_->reduce_previous_allocation_to(number_of_samples);
Scan scan;
scan.type = Scan::Type::Data;
scan.number_of_cycles = number_of_cycles;
scan.number_of_samples = int(number_of_samples);
output_scan(&scan);
}
@ -407,30 +322,30 @@ Outputs::CRT::Rect CRT::get_rect_for_area(int first_line_after_sync, int number_
number_of_lines += 4;
// determine prima facie x extent
unsigned int horizontal_period = horizontal_flywheel_->get_standard_period();
unsigned int horizontal_scan_period = horizontal_flywheel_->get_scan_period();
unsigned int horizontal_retrace_period = horizontal_period - horizontal_scan_period;
int horizontal_period = horizontal_flywheel_->get_standard_period();
int horizontal_scan_period = horizontal_flywheel_->get_scan_period();
int horizontal_retrace_period = horizontal_period - horizontal_scan_period;
// make sure that the requested range is visible
if(static_cast<unsigned int>(first_cycle_after_sync) < horizontal_retrace_period) first_cycle_after_sync = static_cast<int>(horizontal_retrace_period);
if(static_cast<unsigned int>(first_cycle_after_sync + number_of_cycles) > horizontal_scan_period) number_of_cycles = static_cast<int>(horizontal_scan_period - static_cast<unsigned int>(first_cycle_after_sync));
if(static_cast<int>(first_cycle_after_sync) < horizontal_retrace_period) first_cycle_after_sync = static_cast<int>(horizontal_retrace_period);
if(static_cast<int>(first_cycle_after_sync + number_of_cycles) > horizontal_scan_period) number_of_cycles = static_cast<int>(horizontal_scan_period - static_cast<int>(first_cycle_after_sync));
float start_x = static_cast<float>(static_cast<unsigned int>(first_cycle_after_sync) - horizontal_retrace_period) / static_cast<float>(horizontal_scan_period);
float start_x = static_cast<float>(static_cast<int>(first_cycle_after_sync) - horizontal_retrace_period) / static_cast<float>(horizontal_scan_period);
float width = static_cast<float>(number_of_cycles) / static_cast<float>(horizontal_scan_period);
// determine prima facie y extent
unsigned int vertical_period = vertical_flywheel_->get_standard_period();
unsigned int vertical_scan_period = vertical_flywheel_->get_scan_period();
unsigned int vertical_retrace_period = vertical_period - vertical_scan_period;
int vertical_period = vertical_flywheel_->get_standard_period();
int vertical_scan_period = vertical_flywheel_->get_scan_period();
int vertical_retrace_period = vertical_period - vertical_scan_period;
// make sure that the requested range is visible
// if(static_cast<unsigned int>(first_line_after_sync) * horizontal_period < vertical_retrace_period)
// if(static_cast<int>(first_line_after_sync) * horizontal_period < vertical_retrace_period)
// first_line_after_sync = (vertical_retrace_period + horizontal_period - 1) / horizontal_period;
// if((first_line_after_sync + number_of_lines) * horizontal_period > vertical_scan_period)
// number_of_lines = static_cast<int>(horizontal_scan_period - static_cast<unsigned int>(first_cycle_after_sync));
// number_of_lines = static_cast<int>(horizontal_scan_period - static_cast<int>(first_cycle_after_sync));
float start_y = static_cast<float>((static_cast<unsigned int>(first_line_after_sync) * horizontal_period) - vertical_retrace_period) / static_cast<float>(vertical_scan_period);
float height = static_cast<float>(static_cast<unsigned int>(number_of_lines) * horizontal_period) / vertical_scan_period;
float start_y = static_cast<float>((static_cast<int>(first_line_after_sync) * horizontal_period) - vertical_retrace_period) / static_cast<float>(vertical_scan_period);
float height = static_cast<float>(static_cast<int>(number_of_lines) * horizontal_period) / vertical_scan_period;
// adjust to ensure aspect ratio is correct
float adjusted_aspect_ratio = (3.0f*aspect_ratio / 4.0f);

View File

@ -26,17 +26,17 @@ class CRT;
class Delegate {
public:
virtual void crt_did_end_batch_of_frames(CRT *crt, unsigned int number_of_frames, unsigned int number_of_unexpected_vertical_syncs) = 0;
virtual void crt_did_end_batch_of_frames(CRT *crt, int number_of_frames, int number_of_unexpected_vertical_syncs) = 0;
};
class CRT {
private:
CRT(unsigned int common_output_divisor, unsigned int buffer_depth);
CRT(int common_output_divisor, int buffer_depth);
// the incoming clock lengths will be multiplied by something to give at least 1000
// sample points per line
unsigned int time_multiplier_ = 1;
const unsigned int common_output_divisor_ = 1;
int time_multiplier_ = 1;
const int common_output_divisor_ = 1;
// the two flywheels regulating scanning
std::unique_ptr<Flywheel> horizontal_flywheel_, vertical_flywheel_;
@ -46,7 +46,7 @@ class CRT {
enum Type {
Sync, Level, Data, Blank, ColourBurst
} type;
unsigned int number_of_cycles;
int number_of_cycles, number_of_samples;
union {
struct {
uint8_t phase, amplitude;
@ -55,52 +55,37 @@ class CRT {
};
void output_scan(const Scan *scan);
uint8_t colour_burst_phase_ = 0, colour_burst_amplitude_ = 30, colour_burst_phase_adjustment_ = 0;
int16_t colour_burst_angle_ = 0;
uint8_t colour_burst_amplitude_ = 30;
int colour_burst_phase_adjustment_ = 0;
bool is_writing_composite_run_ = false;
unsigned int phase_denominator_ = 1, phase_numerator_ = 1, colour_cycle_numerator_ = 1;
int phase_denominator_ = 1, phase_numerator_ = 1, colour_cycle_numerator_ = 1;
bool is_alernate_line_ = false, phase_alternates_ = false;
// the outer entry point for dispatching output_sync, output_blank, output_level and output_data
void advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bool vsync_requested, const Scan::Type type);
void advance_cycles(int number_of_cycles, bool hsync_requested, bool vsync_requested, const Scan::Type type, int number_of_samples);
// the inner entry point that determines whether and when the next sync event will occur within
// the current output window
Flywheel::SyncEvent get_next_vertical_sync_event(bool vsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced);
Flywheel::SyncEvent get_next_horizontal_sync_event(bool hsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced);
// OpenGL state
OpenGLOutputBuilder openGL_output_builder_;
// temporary storage used during the construction of output runs
struct {
uint16_t x1, y;
} output_run_;
Flywheel::SyncEvent get_next_vertical_sync_event(bool vsync_is_requested, int cycles_to_run_for, int *cycles_advanced);
Flywheel::SyncEvent get_next_horizontal_sync_event(bool hsync_is_requested, int cycles_to_run_for, int *cycles_advanced);
// the delegate
Delegate *delegate_ = nullptr;
unsigned int frames_since_last_delegate_call_ = 0;
// queued tasks for the OpenGL queue; performed before the next draw
std::mutex function_mutex_;
std::vector<std::function<void(void)>> enqueued_openGL_functions_;
inline void enqueue_openGL_function(const std::function<void(void)> &function) {
std::lock_guard<std::mutex> function_guard(function_mutex_);
enqueued_openGL_functions_.push_back(function);
}
int frames_since_last_delegate_call_ = 0;
// sync counter, for determining vertical sync
bool is_receiving_sync_ = false; // true if the CRT is currently receiving sync (i.e. this is for edge triggering of horizontal sync)
bool is_accumulating_sync_ = false; // true if a sync level has triggered the suspicion that a vertical sync might be in progress
bool is_refusing_sync_ = false; // true once a vertical sync has been detected, until a prolonged period of non-sync has ended suspicion of an ongoing vertical sync
unsigned int sync_capacitor_charge_threshold_ = 0; // this charges up during times of sync and depletes otherwise; needs to hit a required threshold to trigger a vertical sync
unsigned int cycles_of_sync_ = 0; // the number of cycles since the potential vertical sync began
unsigned int cycles_since_sync_ = 0; // the number of cycles since last in sync, for defeating the possibility of this being a vertical sync
int sync_capacitor_charge_threshold_ = 0; // this charges up during times of sync and depletes otherwise; needs to hit a required threshold to trigger a vertical sync
int cycles_of_sync_ = 0; // the number of cycles since the potential vertical sync began
int cycles_since_sync_ = 0; // the number of cycles since last in sync, for defeating the possibility of this being a vertical sync
unsigned int cycles_per_line_ = 1;
int cycles_per_line_ = 1;
float input_gamma_ = 1.0f, output_gamma_ = 1.0f;
void update_gamma();
ScanTarget *scan_target_ = nullptr;
public:
/*! Constructs the CRT with a specified clock rate, height and colour subcarrier frequency.
@ -134,14 +119,14 @@ class CRT {
@see @c set_rgb_sampling_function , @c set_composite_sampling_function
*/
CRT(unsigned int cycles_per_line,
unsigned int common_output_divisor,
unsigned int height_of_display,
CRT(int cycles_per_line,
int common_output_divisor,
int height_of_display,
ColourSpace colour_space,
unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator,
unsigned int vertical_sync_half_lines,
int colour_cycle_numerator, int colour_cycle_denominator,
int vertical_sync_half_lines,
bool should_alternate,
unsigned int buffer_depth);
int buffer_depth);
/*! Constructs the CRT with the specified clock rate, with the display height and colour
subcarrier frequency dictated by a standard display type and with the requested number of
@ -150,36 +135,36 @@ class CRT {
Exactly identical to calling the designated constructor with colour subcarrier information
looked up by display type.
*/
CRT(unsigned int cycles_per_line,
unsigned int common_output_divisor,
CRT(int cycles_per_line,
int common_output_divisor,
DisplayType displayType,
unsigned int buffer_depth);
int buffer_depth);
/*! Resets the CRT with new timing information. The CRT then continues as though the new timing had
been provided at construction. */
void set_new_timing(unsigned int cycles_per_line, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, unsigned int vertical_sync_half_lines, bool should_alternate);
void set_new_timing(int cycles_per_line, int height_of_display, ColourSpace colour_space, int colour_cycle_numerator, int colour_cycle_denominator, int vertical_sync_half_lines, bool should_alternate);
/*! Resets the CRT with new timing information derived from a new display type. The CRT then continues
as though the new timing had been provided at construction. */
void set_new_display_type(unsigned int cycles_per_line, DisplayType displayType);
void set_new_display_type(int cycles_per_line, DisplayType displayType);
/*! Output at the sync level.
@param number_of_cycles The amount of time to putput sync for.
*/
void output_sync(unsigned int number_of_cycles);
void output_sync(int number_of_cycles);
/*! Output at the blanking level.
@param number_of_cycles The amount of time to putput the blanking level for.
*/
void output_blank(unsigned int number_of_cycles);
void output_blank(int number_of_cycles);
/*! Outputs the first written to the most-recently created run of data repeatedly for a prolonged period.
@param number_of_cycles The number of cycles to repeat the output for.
*/
void output_level(unsigned int number_of_cycles);
void output_level(int number_of_cycles);
/*! Declares that the caller has created a run of data via @c allocate_write_area and @c get_write_target_for_buffer
that is at least @c number_of_samples long, and that the first @c number_of_samples should be spread
@ -191,11 +176,11 @@ class CRT {
@see @c allocate_write_area , @c get_write_target_for_buffer
*/
void output_data(unsigned int number_of_cycles, unsigned int number_of_samples);
void output_data(int number_of_cycles, size_t number_of_samples);
/*! A shorthand form for output_data that assumes the number of cycles to output for is the same as the number of samples. */
void output_data(unsigned int number_of_cycles) {
output_data(number_of_cycles, number_of_cycles);
void output_data(int number_of_cycles) {
output_data(number_of_cycles, size_t(number_of_cycles));
}
/*! Outputs a colour burst.
@ -208,13 +193,13 @@ class CRT {
@param amplitude The amplitude of the colour burst in 1/256ths of the amplitude of the
positive portion of the wave.
*/
void output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint8_t amplitude = 102);
void output_colour_burst(int number_of_cycles, uint8_t phase, uint8_t amplitude = 102);
/*! Outputs a colour burst exactly in phase with CRT expectations using the idiomatic amplitude.
@param number_of_cycles The length of the colour burst;
*/
void output_default_colour_burst(unsigned int number_of_cycles);
void output_default_colour_burst(int number_of_cycles);
/*! Sets the current phase of the colour subcarrier used by output_default_colour_burst.
@ -235,63 +220,12 @@ class CRT {
@returns A pointer to the allocated area if room is available; @c nullptr otherwise.
*/
inline uint8_t *allocate_write_area(std::size_t required_length, std::size_t required_alignment = 1) {
std::unique_lock<std::mutex> output_lock = openGL_output_builder_.get_output_lock();
return openGL_output_builder_.texture_builder.allocate_write_area(required_length, required_alignment);
}
/*! Causes appropriate OpenGL or OpenGL ES calls to be issued in order to draw the current CRT state.
The caller is responsible for ensuring that a valid OpenGL context exists for the duration of this call.
*/
inline void draw_frame(unsigned int output_width, unsigned int output_height, bool only_if_dirty) {
{
std::lock_guard<std::mutex> function_guard(function_mutex_);
for(std::function<void(void)> function : enqueued_openGL_functions_) {
function();
}
enqueued_openGL_functions_.clear();
}
openGL_output_builder_.draw_frame(output_width, output_height, only_if_dirty);
}
/*! Sets the OpenGL framebuffer to which output is drawn. */
inline void set_target_framebuffer(GLint framebuffer) {
enqueue_openGL_function( [framebuffer, this] {
openGL_output_builder_.set_target_framebuffer(framebuffer);
});
return scan_target_->allocate_write_area(required_length, required_alignment);
}
/*! Sets the gamma exponent for the simulated screen. */
void set_input_gamma(float gamma);
/*! Sets the gamma exponent for the real, tangible screen on which content will be drawn. */
void set_output_gamma(float gamma);
/*! Tells the CRT that the next call to draw_frame will occur on a different OpenGL context than
the previous.
@param should_delete_resources If @c true then all resources, textures, vertex arrays, etc,
currently held by the CRT will be deleted now via calls to glDeleteTexture and equivalent. If
@c false then the references are simply marked as invalid.
*/
inline void set_openGL_context_will_change(bool should_delete_resources) {
enqueue_openGL_function([should_delete_resources, this] {
openGL_output_builder_.set_openGL_context_will_change(should_delete_resources);
});
}
/*! Sets a function that will map from whatever data the machine provided to a composite signal.
@param shader A GLSL fragment including a function with the signature
`float composite_sample(usampler2D texID, vec2 coordinate, float phase, float amplitude)`
that evaluates to the composite signal level as a function of a source buffer, sampling location, colour
carrier phase and amplitude.
*/
inline void set_composite_sampling_function(const std::string &shader) {
enqueue_openGL_function([shader, this] {
openGL_output_builder_.set_composite_sampling_function(shader);
});
}
enum CompositeSourceType {
/// The composite function provides continuous output.
Continuous,
@ -312,51 +246,7 @@ class CRT {
*/
void set_composite_function_type(CompositeSourceType type, float offset_of_first_sample = 0.0f);
/*! Sets a function that will map from whatever data the machine provided to an s-video signal.
If the output mode is composite then a default mapping from RGB to the display's
output mode will be applied.
@param shader A GLSL fragment including a function with the signature
`vec2 svideo_sample(usampler2D texID, vec2 coordinate, float phase, float amplitude)`
that evaluates to the s-video signal level, luminance as the first component and chrominance
as the second, as a function of a source buffer, sampling location and colour
carrier phase; amplitude is supplied for its sign.
*/
inline void set_svideo_sampling_function(const std::string &shader) {
enqueue_openGL_function([shader, this] {
openGL_output_builder_.set_svideo_sampling_function(shader);
});
}
/*! Sets a function that will map from whatever data the machine provided to an RGB signal.
If the output mode is composite or svideo then a default mapping from RGB to the display's
output mode will be applied.
@param shader A GLSL fragent including a function with the signature
`vec3 rgb_sample(usampler2D sampler, vec2 coordinate)` that evaluates to an RGB colour
as a function of:
* `usampler2D sampler` representing the source buffer; and
* `vec2 coordinate` representing the source buffer location to sample from in the range [0, 1).
*/
inline void set_rgb_sampling_function(const std::string &shader) {
enqueue_openGL_function([shader, this] {
openGL_output_builder_.set_rgb_sampling_function(shader);
});
}
inline void set_video_signal(VideoSignal video_signal) {
enqueue_openGL_function([video_signal, this] {
openGL_output_builder_.set_video_signal(video_signal);
});
}
inline void set_visible_area(Rect visible_area) {
enqueue_openGL_function([visible_area, this] {
openGL_output_builder_.set_visible_area(visible_area);
});
}
Rect get_rect_for_area(int first_line_after_sync, int number_of_lines, int first_cycle_after_sync, int number_of_cycles, float aspect_ratio);

View File

@ -12,20 +12,6 @@
namespace Outputs {
namespace CRT {
struct Rect {
struct {
float x, y;
} origin;
struct {
float width, height;
} size;
Rect() {}
Rect(float x, float y, float width, float height) :
origin({x, y}), size({width, height}) {}
};
enum class DisplayType {
PAL50,
NTSC60

View File

@ -29,7 +29,7 @@ struct Flywheel {
@param retrace_time The amount of time it takes to complete a retrace.
@param sync_error_window The permitted deviation of sync timings from the norm.
*/
Flywheel(unsigned int standard_period, unsigned int retrace_time, unsigned int sync_error_window) :
Flywheel(int standard_period, int retrace_time, int sync_error_window) :
standard_period_(standard_period),
retrace_time_(retrace_time),
sync_error_window_(sync_error_window),
@ -60,11 +60,11 @@ struct Flywheel {
@returns The next synchronisation event.
*/
inline SyncEvent get_next_event_in_period(bool sync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced) {
inline SyncEvent get_next_event_in_period(bool sync_is_requested, int cycles_to_run_for, int *cycles_advanced) {
// do we recognise this hsync, thereby adjusting future time expectations?
if(sync_is_requested) {
if(counter_ < sync_error_window_ || counter_ > expected_next_sync_ - sync_error_window_) {
unsigned int time_now = (counter_ < sync_error_window_) ? expected_next_sync_ + counter_ : counter_;
int time_now = (counter_ < sync_error_window_) ? expected_next_sync_ + counter_ : counter_;
expected_next_sync_ = (3*expected_next_sync_ + time_now) >> 2;
} else {
number_of_surprises_++;
@ -78,7 +78,7 @@ struct Flywheel {
}
SyncEvent proposed_event = SyncEvent::None;
unsigned int proposed_sync_time = cycles_to_run_for;
int proposed_sync_time = cycles_to_run_for;
// will we end an ongoing retrace?
if(counter_ < retrace_time_ && counter_ + proposed_sync_time >= retrace_time_) {
@ -104,7 +104,7 @@ struct Flywheel {
@param event The synchronisation event to apply after that period.
*/
inline void apply_event(unsigned int cycles_advanced, SyncEvent event) {
inline void apply_event(int cycles_advanced, SyncEvent event) {
counter_ += cycles_advanced;
switch(event) {
@ -122,9 +122,9 @@ struct Flywheel {
@returns The current output position.
*/
inline unsigned int get_current_output_position() {
inline int get_current_output_position() {
if(counter_ < retrace_time_) {
unsigned int retrace_distance = (counter_ * standard_period_) / retrace_time_;
int retrace_distance = (counter_ * standard_period_) / retrace_time_;
if(retrace_distance > counter_before_retrace_) return 0;
return counter_before_retrace_ - retrace_distance;
}
@ -135,7 +135,7 @@ struct Flywheel {
/*!
@returns the amount of time since retrace last began. Time then counts monotonically up from zero.
*/
inline unsigned int get_current_time() {
inline int get_current_time() {
return counter_;
}
@ -149,14 +149,14 @@ struct Flywheel {
/*!
@returns the expected length of the scan period (excluding retrace).
*/
inline unsigned int get_scan_period() {
inline int get_scan_period() {
return standard_period_ - retrace_time_;
}
/*!
@returns the expected length of a complete scan and retrace cycle.
*/
inline unsigned int get_standard_period() {
inline int get_standard_period() {
return standard_period_;
}
@ -164,8 +164,8 @@ struct Flywheel {
@returns the number of synchronisation events that have seemed surprising since the last time this method was called;
a low number indicates good synchronisation.
*/
inline unsigned int get_and_reset_number_of_surprises() {
unsigned int result = number_of_surprises_;
inline int get_and_reset_number_of_surprises() {
const int result = number_of_surprises_;
number_of_surprises_ = 0;
return result;
}
@ -174,19 +174,19 @@ struct Flywheel {
@returns `true` if a sync is expected soon or the time at which it was expected was recent.
*/
inline bool is_near_expected_sync() {
return abs(static_cast<int>(counter_) - static_cast<int>(expected_next_sync_)) < static_cast<int>(standard_period_) / 50;
return abs(counter_ - expected_next_sync_) < standard_period_ / 50;
}
private:
unsigned int standard_period_; // the normal length of time between syncs
const unsigned int retrace_time_; // a constant indicating the amount of time it takes to perform a retrace
const unsigned int sync_error_window_; // a constant indicating the window either side of the next expected sync in which we'll accept other syncs
const int standard_period_; // the normal length of time between syncs
const int retrace_time_; // a constant indicating the amount of time it takes to perform a retrace
const int sync_error_window_; // a constant indicating the window either side of the next expected sync in which we'll accept other syncs
unsigned int counter_; // time since the _start_ of the last sync
unsigned int counter_before_retrace_; // the value of _counter immediately before retrace began
unsigned int expected_next_sync_; // our current expection of when the next sync will be encountered (which implies velocity)
int counter_; // time since the _start_ of the last sync
int counter_before_retrace_; // the value of _counter immediately before retrace began
int expected_next_sync_; // our current expection of when the next sync will be encountered (which implies velocity)
unsigned int number_of_surprises_; // a count of the surprising syncs
int number_of_surprises_; // a count of the surprising syncs
/*
Implementation notes:

View File

@ -11,6 +11,7 @@
#include "Shader.hpp"
#include "../../CRTTypes.hpp"
#include "../../ScanTarget.hpp"
#include <memory>
namespace OpenGL {

View File

@ -12,6 +12,20 @@
namespace Outputs {
namespace CRT {
struct Rect {
struct Point {
float x, y;
} origin;
struct {
float width, height;
} size;
Rect() {}
Rect(float x, float y, float width, float height) :
origin({x, y}), size({width, height}) {}
};
enum class ColourSpace {
/// YIQ is the NTSC colour space.
YIQ,
@ -54,8 +68,10 @@ struct ScanTarget {
// of a colour subcarrier. So they can be used to generate a luminance signal,
// or an s-video pipeline.
Phase4Luminance4, // 1 byte/pixel; top nibble is a phase offset, bottom nibble is luminance.
Phase8Luminance8, // 1 bytes/pixel; first is phase, second is luminance.
Phase8Luminance8, // 2 bytes/pixel; first is phase, second is luminance.
// Phase is encoded on a 192-unit circle; anything
// greater than 192 implies that the colour part of
// the signal should be omitted.
// The RGB types can directly feed an RGB pipeline, naturally, or can be mapped
// to phase+luminance, or just to luminance.
@ -66,12 +82,28 @@ struct ScanTarget {
Red8Green8Blue8, // 4 bytes/pixel; first is red, second is green, third is blue, fourth is vacant.
} source_data_type;
// If being fed composite data, this defines the colour space in use.
/// If being fed composite data, this defines the colour space in use.
ColourSpace composite_colour_space;
/// Nominates a least common multiple of the potential input pixel clocks;
/// if this isn't a crazy number then it'll be used potentially to optimise
/// the composite encoding and decoding process.
int pixel_clock_least_common_multiple;
/// Provides a pre-estimate of the likely number of left-to-right scans per frame.
/// This isn't a guarantee, but it should provide a decent-enough estimate.
int expected_vertical_lines;
/// Provides an additional restriction on the section of the display that is expected
/// to contain interesting content.
Rect visible_area;
/// Describes the
float intended_gamma;
};
/// Sets the total format of input data.
virtual void set_modals(Modals);
virtual void set_modals(Modals) = 0;
/*
@ -114,14 +146,14 @@ struct ScanTarget {
} end_points[2];
/// For composite video, dictates the amplitude of the colour subcarrier as a proportion of
/// the whole, as determined from the colour burst.
/// the whole, as determined from the colour burst. Will be 0 if there was no colour burst.
uint8_t composite_amplitude;
};
/// Requests a new scan to populate.
///
/// @return A valid pointer, or @c nullptr if insufficient further storage is available.
Scan *get_scan();
virtual Scan *get_scan() = 0;
/// Finds the first available space of at least @c required_length pixels in size which is suitably aligned
/// for writing of @c required_alignment number of pixels at a time.
@ -140,7 +172,7 @@ struct ScanTarget {
///
/// The ScanTarget isn't bound to take any drawing action immediately; it may sit on submitted data for
/// as long as it feels is appropriate subject to an @c flush.
virtual void submit() = 0;
virtual void submit(bool only_if_no_allocation_failures = true) = 0;
/// Discards all data and endpoints supplied since the last @c submit. This is generally used when
/// failures in either get_endpoing_pair of allocate_write_area mean that proceeding would produce
@ -164,7 +196,7 @@ struct ScanTarget {
};
/// Provides a hint that the named event has occurred.
virtual void announce(Event event) = 0;
virtual void announce(Event event) {}
};
}