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:
parent
373820f080
commit
da4d883321
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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];
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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_;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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 */,
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
#include "Shader.hpp"
|
||||
#include "../../CRTTypes.hpp"
|
||||
#include "../../ScanTarget.hpp"
|
||||
#include <memory>
|
||||
|
||||
namespace OpenGL {
|
||||
|
@ -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) {}
|
||||
};
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user