mirror of
https://github.com/TomHarte/CLK.git
synced 2025-01-12 15:31:09 +00:00
Makes ScanTarget
a little more communicative and orthogonal.
This commit is contained in:
parent
491817d85c
commit
6d277fecd5
@ -294,7 +294,7 @@ template <class BusHandler> class MOS6560 {
|
||||
|
||||
pixel_pointer = nullptr;
|
||||
if(output_state_ == State::Pixels) {
|
||||
pixel_pointer = reinterpret_cast<uint16_t *>(crt_->allocate_write_area(260));
|
||||
pixel_pointer = reinterpret_cast<uint16_t *>(crt_->begin_data(260));
|
||||
}
|
||||
}
|
||||
cycles_in_state_++;
|
||||
@ -512,7 +512,7 @@ template <class BusHandler> class MOS6560 {
|
||||
|
||||
uint16_t *pixel_pointer;
|
||||
void output_border(int number_of_cycles) {
|
||||
uint16_t *colour_pointer = reinterpret_cast<uint16_t *>(crt_->allocate_write_area(1));
|
||||
uint16_t *colour_pointer = reinterpret_cast<uint16_t *>(crt_->begin_data(1));
|
||||
if(colour_pointer) *colour_pointer = registers_.borderColour;
|
||||
crt_->output_level(number_of_cycles);
|
||||
}
|
||||
|
@ -411,7 +411,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(size_t(line_buffer.next_border_column - line_buffer.first_pixel_output_column))
|
||||
crt_->begin_data(size_t(line_buffer.next_border_column - line_buffer.first_pixel_output_column))
|
||||
);
|
||||
}
|
||||
|
||||
@ -471,14 +471,14 @@ void Base::output_border(int cycles, uint32_t cram_dot) {
|
||||
palette[background_colour_];
|
||||
|
||||
if(cram_dot) {
|
||||
uint32_t *const pixel_target = reinterpret_cast<uint32_t *>(crt_->allocate_write_area(1));
|
||||
uint32_t *const pixel_target = reinterpret_cast<uint32_t *>(crt_->begin_data(1));
|
||||
*pixel_target = border_colour | cram_dot;
|
||||
crt_->output_level(4);
|
||||
cycles -= 4;
|
||||
}
|
||||
|
||||
if(cycles) {
|
||||
uint32_t *const pixel_target = reinterpret_cast<uint32_t *>(crt_->allocate_write_area(1));
|
||||
uint32_t *const pixel_target = reinterpret_cast<uint32_t *>(crt_->begin_data(1));
|
||||
*pixel_target = border_colour;
|
||||
crt_->output_level(cycles);
|
||||
}
|
||||
|
@ -239,7 +239,7 @@ class CRTCBusHandler {
|
||||
// collect some more pixels if output is ongoing
|
||||
if(previous_output_mode_ == OutputMode::Pixels) {
|
||||
if(!pixel_data_) {
|
||||
pixel_pointer_ = pixel_data_ = crt_->allocate_write_area(320, 8);
|
||||
pixel_pointer_ = pixel_data_ = crt_->begin_data(320, 8);
|
||||
}
|
||||
if(pixel_pointer_) {
|
||||
// the CPC shuffles output lines as:
|
||||
@ -378,7 +378,7 @@ class CRTCBusHandler {
|
||||
|
||||
private:
|
||||
void output_border(int length) {
|
||||
uint8_t *colour_pointer = static_cast<uint8_t *>(crt_->allocate_write_area(1));
|
||||
uint8_t *colour_pointer = static_cast<uint8_t *>(crt_->begin_data(1));
|
||||
if(colour_pointer) *colour_pointer = border_;
|
||||
crt_->output_level(length * 16);
|
||||
}
|
||||
|
@ -402,7 +402,7 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
|
||||
// remain where they would naturally be but auxiliary
|
||||
// graphics appear to the left of that.
|
||||
if(!column_) {
|
||||
pixel_pointer_ = crt_->allocate_write_area(568);
|
||||
pixel_pointer_ = crt_->begin_data(568);
|
||||
graphics_carry_ = 0;
|
||||
was_double_ = true;
|
||||
}
|
||||
|
@ -449,7 +449,7 @@ void TIA::output_for_cycles(int number_of_cycles) {
|
||||
} else {
|
||||
if(!pixels_start_location_ && crt_) {
|
||||
pixels_start_location_ = output_cursor;
|
||||
pixel_target_ = crt_->allocate_write_area(160);
|
||||
pixel_target_ = crt_->begin_data(160);
|
||||
}
|
||||
|
||||
// convert that into pixels
|
||||
|
@ -119,7 +119,7 @@ void VideoOutput::output_pixels(int number_of_cycles) {
|
||||
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(size_t(640 / current_output_divider_), size_t(8 / divider));
|
||||
initial_output_target_ = current_output_target_ = crt_->begin_data(size_t(640 / current_output_divider_), size_t(8 / divider));
|
||||
}
|
||||
|
||||
#define get_pixel() \
|
||||
|
@ -96,7 +96,7 @@ void VideoOutput::run_for(const Cycles cycles) {
|
||||
paper_ = 0x0;
|
||||
use_alternative_character_set_ = use_double_height_characters_ = blink_text_ = false;
|
||||
set_character_set_base_address();
|
||||
pixel_target_ = reinterpret_cast<uint16_t *>(crt_->allocate_write_area(240));
|
||||
pixel_target_ = reinterpret_cast<uint16_t *>(crt_->begin_data(240));
|
||||
|
||||
if(!counter_) {
|
||||
frame_counter_++;
|
||||
|
@ -68,7 +68,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));
|
||||
uint8_t *colour_pointer = static_cast<uint8_t *>(crt_->begin_data(1));
|
||||
if(colour_pointer) *colour_pointer = 0xff;
|
||||
crt_->output_level(time_since_update_.as_int());
|
||||
}
|
||||
@ -92,7 +92,7 @@ void Video::output_byte(uint8_t byte) {
|
||||
|
||||
// Grab a buffer if one isn't already available.
|
||||
if(!line_data_) {
|
||||
line_data_pointer_ = line_data_ = crt_->allocate_write_area(StandardAllocationSize);
|
||||
line_data_pointer_ = line_data_ = crt_->begin_data(StandardAllocationSize);
|
||||
}
|
||||
|
||||
// If a buffer was obtained, serialise the new pixels.
|
||||
@ -101,7 +101,7 @@ void Video::output_byte(uint8_t byte) {
|
||||
if(line_data_pointer_ - line_data_ == StandardAllocationSize) {
|
||||
crt_->output_data(StandardAllocationSize, StandardAllocationSize);
|
||||
time_since_update_ -= StandardAllocationSize;
|
||||
line_data_pointer_ = line_data_ = crt_->allocate_write_area(StandardAllocationSize);
|
||||
line_data_pointer_ = line_data_ = crt_->begin_data(StandardAllocationSize);
|
||||
if(!line_data_) return;
|
||||
}
|
||||
|
||||
|
@ -162,7 +162,7 @@ void CRT::advance_cycles(int number_of_cycles, bool hsync_requested, bool vsync_
|
||||
|
||||
// Determine whether to output any data for this portion of the output; if so then grab somewhere to put it.
|
||||
const bool is_output_segment = ((is_output_run && next_run_length) && !horizontal_flywheel_->is_in_retrace() && !vertical_flywheel_->is_in_retrace());
|
||||
Outputs::Display::ScanTarget::Scan *const next_scan = is_output_segment ? scan_target_->get_scan() : nullptr;
|
||||
Outputs::Display::ScanTarget::Scan *const next_scan = is_output_segment ? scan_target_->begin_scan() : nullptr;
|
||||
did_output |= is_output_segment;
|
||||
|
||||
// If outputting, store the start location and scan constants.
|
||||
@ -182,24 +182,41 @@ void CRT::advance_cycles(int number_of_cycles, bool hsync_requested, bool vsync_
|
||||
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);
|
||||
|
||||
// Store an endpoint if necessary.
|
||||
// End the scan if necessary.
|
||||
if(next_scan) {
|
||||
next_scan->end_points[1].x = uint16_t(horizontal_flywheel_->get_current_output_position());
|
||||
next_scan->end_points[1].y = uint16_t(vertical_flywheel_->get_current_output_position() / vertical_flywheel_output_divider_);
|
||||
next_scan->end_points[1].composite_angle = int16_t((phase_numerator_ << 6) / phase_denominator_) * (is_alernate_line_ ? -1 : 1);
|
||||
next_scan->end_points[1].data_offset = uint16_t((total_cycles - number_of_cycles) * number_of_samples / total_cycles);
|
||||
scan_target_->end_scan();
|
||||
}
|
||||
|
||||
// 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) {
|
||||
scan_target_->announce(Outputs::Display::ScanTarget::Event::HorizontalRetrace);
|
||||
is_alernate_line_ ^= phase_alternates_;
|
||||
colour_burst_amplitude_ = 0;
|
||||
// Announce horizontal retrace events.
|
||||
if(next_run_length == time_until_horizontal_sync_event && next_horizontal_sync_event != Flywheel::SyncEvent::None) {
|
||||
const auto event =
|
||||
(next_horizontal_sync_event == Flywheel::SyncEvent::StartRetrace)
|
||||
? Outputs::Display::ScanTarget::Event::BeginHorizontalRetrace : Outputs::Display::ScanTarget::Event::EndHorizontalRetrace;
|
||||
scan_target_->announce(
|
||||
event,
|
||||
uint16_t(horizontal_flywheel_->get_current_output_position()),
|
||||
uint16_t(vertical_flywheel_->get_current_output_position() / vertical_flywheel_output_divider_));
|
||||
|
||||
// Prepare for the next line.
|
||||
if(next_horizontal_sync_event == Flywheel::SyncEvent::EndRetrace) {
|
||||
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::Display::ScanTarget::Event::VerticalRetrace);
|
||||
// Also announce vertical retrace events.
|
||||
if(next_run_length == time_until_vertical_sync_event && next_horizontal_sync_event != Flywheel::SyncEvent::None) {
|
||||
const auto event =
|
||||
(next_horizontal_sync_event == Flywheel::SyncEvent::StartRetrace)
|
||||
? Outputs::Display::ScanTarget::Event::BeginVerticalRetrace : Outputs::Display::ScanTarget::Event::EndVerticalRetrace;
|
||||
scan_target_->announce(
|
||||
event,
|
||||
uint16_t(horizontal_flywheel_->get_current_output_position()),
|
||||
uint16_t(vertical_flywheel_->get_current_output_position() / vertical_flywheel_output_divider_));
|
||||
}
|
||||
|
||||
// if this is vertical retrace then adcance a field
|
||||
@ -308,7 +325,7 @@ void CRT::output_blank(int number_of_cycles) {
|
||||
}
|
||||
|
||||
void CRT::output_level(int number_of_cycles) {
|
||||
scan_target_->reduce_previous_allocation_to(1);
|
||||
scan_target_->end_data(1);
|
||||
Scan scan;
|
||||
scan.type = Scan::Type::Level;
|
||||
scan.number_of_cycles = number_of_cycles;
|
||||
@ -336,7 +353,7 @@ void CRT::set_immediate_default_phase(float phase) {
|
||||
}
|
||||
|
||||
void CRT::output_data(int number_of_cycles, size_t number_of_samples) {
|
||||
scan_target_->reduce_previous_allocation_to(number_of_samples);
|
||||
scan_target_->end_data(number_of_samples);
|
||||
Scan scan;
|
||||
scan.type = Scan::Type::Data;
|
||||
scan.number_of_cycles = number_of_cycles;
|
||||
|
@ -169,15 +169,14 @@ class CRT {
|
||||
*/
|
||||
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
|
||||
over @c number_of_cycles.
|
||||
/*! Declares that the caller has created a run of data via @c begin_data that is at least @c number_of_samples
|
||||
long, and that the first @c number_of_samples should be spread over @c number_of_cycles.
|
||||
|
||||
@param number_of_cycles The amount of data to output.
|
||||
|
||||
@param number_of_samples The number of samples of input data to output.
|
||||
|
||||
@see @c allocate_write_area , @c get_write_target_for_buffer
|
||||
@see @c begin_data
|
||||
*/
|
||||
void output_data(int number_of_cycles, size_t number_of_samples);
|
||||
|
||||
@ -222,8 +221,8 @@ class CRT {
|
||||
@param required_length The number of samples to allocate.
|
||||
@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) {
|
||||
return scan_target_->allocate_write_area(required_length, required_alignment);
|
||||
inline uint8_t *begin_data(std::size_t required_length, std::size_t required_alignment = 1) {
|
||||
return scan_target_->begin_data(required_length, required_alignment);
|
||||
}
|
||||
|
||||
/*! Sets the gamma exponent for the simulated screen. */
|
||||
|
@ -56,12 +56,14 @@ ScanTarget::ScanTarget() {
|
||||
// write straight into it.
|
||||
|
||||
glGenTextures(1, &write_area_texture_name_);
|
||||
glGenVertexArrays(1, &vertex_array_);
|
||||
}
|
||||
|
||||
ScanTarget::~ScanTarget() {
|
||||
// Release scan space.
|
||||
glDeleteBuffers(1, &scan_buffer_name_);
|
||||
glDeleteTextures(1, &write_area_texture_name_);
|
||||
glDeleteVertexArrays(1, &vertex_array_);
|
||||
}
|
||||
|
||||
void ScanTarget::set_modals(Modals modals) {
|
||||
@ -81,7 +83,7 @@ void ScanTarget::set_modals(Modals modals) {
|
||||
}
|
||||
}
|
||||
|
||||
Outputs::Display::ScanTarget::Scan *ScanTarget::get_scan() {
|
||||
Outputs::Display::ScanTarget::Scan *ScanTarget::begin_scan() {
|
||||
if(allocation_has_failed_) return nullptr;
|
||||
|
||||
const auto result = &scan_buffer_[write_pointers_.scan_buffer];
|
||||
@ -104,7 +106,7 @@ Outputs::Display::ScanTarget::Scan *ScanTarget::get_scan() {
|
||||
return static_cast<Outputs::Display::ScanTarget::Scan *>(result);
|
||||
}
|
||||
|
||||
uint8_t *ScanTarget::allocate_write_area(size_t required_length, size_t required_alignment) {
|
||||
uint8_t *ScanTarget::begin_data(size_t required_length, size_t required_alignment) {
|
||||
if(allocation_has_failed_) return nullptr;
|
||||
|
||||
// Determine where the proposed write area would start and end.
|
||||
@ -143,7 +145,7 @@ uint8_t *ScanTarget::allocate_write_area(size_t required_length, size_t required
|
||||
// write_pointers_.write_area points to the first pixel the client is expected to draw to.
|
||||
}
|
||||
|
||||
void ScanTarget::reduce_previous_allocation_to(size_t actual_length) {
|
||||
void ScanTarget::end_data(size_t actual_length) {
|
||||
if(allocation_has_failed_) return;
|
||||
|
||||
// The write area was allocated in the knowledge that there's sufficient
|
||||
|
@ -29,9 +29,9 @@ class ScanTarget: public Outputs::Display::ScanTarget {
|
||||
|
||||
// Outputs::Display::ScanTarget overrides.
|
||||
void set_modals(Modals) override;
|
||||
Scan *get_scan() override;
|
||||
uint8_t *allocate_write_area(size_t required_length, size_t required_alignment) override;
|
||||
void reduce_previous_allocation_to(size_t actual_length) override;
|
||||
Scan *begin_scan() override;
|
||||
uint8_t *begin_data(size_t required_length, size_t required_alignment) override;
|
||||
void end_data(size_t actual_length) override;
|
||||
void submit() override;
|
||||
|
||||
private:
|
||||
@ -65,6 +65,7 @@ class ScanTarget: public Outputs::Display::ScanTarget {
|
||||
// Maintains a buffer of the most recent 3072 scans.
|
||||
std::array<Scan, 3072> scan_buffer_;
|
||||
GLuint scan_buffer_name_ = 0;
|
||||
GLuint vertex_array_;
|
||||
|
||||
// Uses a texture to vend write areas.
|
||||
std::vector<uint8_t> write_area_texture_;
|
||||
|
@ -196,29 +196,32 @@ struct ScanTarget {
|
||||
/// Requests a new scan to populate.
|
||||
///
|
||||
/// @return A valid pointer, or @c nullptr if insufficient further storage is available.
|
||||
virtual Scan *get_scan() = 0;
|
||||
virtual Scan *begin_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.
|
||||
/// Requests a new scan to populate.
|
||||
virtual void end_scan() {}
|
||||
|
||||
/// Finds the first available storage of at least @c required_length pixels in size which is
|
||||
/// suitably aligned for writing of @c required_alignment number of samples at a time.
|
||||
///
|
||||
/// Calls will be paired off with calls to @c reduce_previous_allocation_to.
|
||||
/// Calls will be paired off with calls to @c end_data.
|
||||
///
|
||||
/// @returns a pointer to the allocated space if any was available; @c nullptr otherwise.
|
||||
virtual uint8_t *allocate_write_area(size_t required_length, size_t required_alignment = 1) = 0;
|
||||
virtual uint8_t *begin_data(size_t required_length, size_t required_alignment = 1) = 0;
|
||||
|
||||
/// Announces that the owner is finished with the region created by the most recent @c allocate_write_area
|
||||
/// Announces that the owner is finished with the region created by the most recent @c begin_data
|
||||
/// and indicates that its actual final size was @c actual_length.
|
||||
///
|
||||
/// It is required that every call to allocate_write_area be paired with a call to reduce_previous_allocation_to.
|
||||
virtual void reduce_previous_allocation_to(size_t actual_length) {}
|
||||
/// It is required that every call to begin_data be paired with a call to end_data.
|
||||
virtual void end_data(size_t actual_length) {}
|
||||
|
||||
/// Marks the end of an atomic set of data. Drawing is best effort, so the scan target should either:
|
||||
///
|
||||
/// (i) output everything received since the previous submit; or
|
||||
/// (ii) output nothing.
|
||||
///
|
||||
/// If there were any allocation failures — i.e. any null responses to allocate_write_area or
|
||||
/// get_scan — then (ii) is a required response. But a scan target may also need to opt for (ii)
|
||||
/// If there were any allocation failures — i.e. any nullptr responses to begin_data or
|
||||
/// begin_scan — then (ii) is a required response. But a scan target may also need to opt for (ii)
|
||||
/// for any other reason.
|
||||
///
|
||||
/// The ScanTarget isn't bound to take any drawing action immediately; it may sit on submitted data for
|
||||
@ -232,12 +235,15 @@ struct ScanTarget {
|
||||
*/
|
||||
|
||||
enum class Event {
|
||||
HorizontalRetrace,
|
||||
VerticalRetrace
|
||||
BeginHorizontalRetrace,
|
||||
EndHorizontalRetrace,
|
||||
|
||||
BeginVerticalRetrace,
|
||||
EndVerticalRetrace,
|
||||
};
|
||||
|
||||
/// Provides a hint that the named event has occurred.
|
||||
virtual void announce(Event event) {}
|
||||
virtual void announce(Event event, uint16_t x, uint16_t y) {}
|
||||
};
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user