1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-26 09:29:45 +00:00

Eliminates attempts cleverly to shuffle unsubmitted runs, because no mechanism exists to stop them overwriting previously-submitted-but-not-yet-flushed runs. Which implies that the buffer must be fully circular. The cost of which is sometimes having to make two calls to glTexSubImage2D. Also added some TODOs, and a means for reporting when a retain_latest is ineffective, in which situation it would be inappropriate to attempt to generate correlated geometry

This commit is contained in:
Thomas Harte 2017-07-09 17:50:22 -04:00
parent a0367d6669
commit ee1a9a4781
3 changed files with 89 additions and 86 deletions

View File

@ -140,12 +140,15 @@ void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bo
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()) {
next_run = openGL_output_builder_.array_builder.get_input_storage(SourceVertexSize);
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) {
// output_y and texture locations will be written later; we won't necessarily know what it is outside of the locked region
openGL_output_builder_.texture_builder.retain_latest();
// 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() = (uint16_t)horizontal_flywheel_->get_current_output_position();
source_phase() = colour_burst_phase_;
source_amplitude() = colour_burst_amplitude_;

View File

@ -37,8 +37,8 @@ TextureBuilder::TextureBuilder(size_t bytes_per_pixel, GLenum texture_unit) :
bytes_per_pixel_(bytes_per_pixel),
write_areas_start_x_(0),
write_areas_start_y_(0),
first_unsubmitted_y_(0),
is_full_(false),
did_submit_(false),
number_of_write_areas_(0) {
image_.resize(bytes_per_pixel * InputBufferBuilderWidth * InputBufferBuilderHeight);
glGenTextures(1, &texture_name_);
@ -61,55 +61,44 @@ inline uint8_t *TextureBuilder::pointer_to_location(uint16_t x, uint16_t y) {
}
uint8_t *TextureBuilder::allocate_write_area(size_t required_length) {
// Keep a flag to indicate whether the buffer was full at allocate_write_area; if it was then
// don't return anything now, and decline to act upon follow-up methods. is_full_ may be reset
// by asynchronous calls to submit. was_full_ will not be touched by it.
was_full_ = is_full_;
if(is_full_) return nullptr;
uint16_t starting_x, starting_y;
// If there's not enough space on this line, move to the next. If the next is where the current
// submission group started, trigger is/was_full_ and return nothing.
if(write_areas_start_x_ + required_length + 2 > InputBufferBuilderWidth) {
write_areas_start_x_ = 0;
write_areas_start_y_ = (write_areas_start_y_ + 1) % InputBufferBuilderHeight;
if(!number_of_write_areas_) {
starting_x = write_areas_start_x_;
starting_y = write_areas_start_y_;
} else {
starting_x = write_areas_[number_of_write_areas_ - 1].x + write_areas_[number_of_write_areas_ - 1].length + 1;
starting_y = write_areas_[number_of_write_areas_ - 1].y;
}
if(starting_x + required_length + 2 > InputBufferBuilderWidth) {
starting_x = 0;
starting_y++;
if(starting_y == InputBufferBuilderHeight) {
is_full_ = true;
if(write_areas_start_y_ == first_unsubmitted_y_) {
was_full_ = is_full_ = true;
return nullptr;
}
}
write_area_.x = starting_x + 1;
write_area_.y = starting_y;
// Queue up the latest write area.
write_area_.x = write_areas_start_x_ + 1;
write_area_.y = write_areas_start_y_;
write_area_.length = (uint16_t)required_length;
// Return a video pointer.
return pointer_to_location(write_area_.x, write_area_.y);
}
bool TextureBuilder::is_full() {
return is_full_;
}
void TextureBuilder::retain_latest() {
if(is_full_) return;
if(number_of_write_areas_ < write_areas_.size())
write_areas_[number_of_write_areas_] = write_area_;
else
write_areas_.push_back(write_area_);
number_of_write_areas_++;
}
void TextureBuilder::reduce_previous_allocation_to(size_t actual_length) {
if(is_full_) return;
// If the previous allocate_write_area declined to act, decline also.
if(was_full_) return;
// Update the length of the current write area.
write_area_.length = (uint16_t)actual_length;
// book end the allocation with duplicates of the first and last pixel, to protect
// against rounding errors when this run is drawn
// Bookend the allocation with duplicates of the first and last pixel, to protect
// against rounding errors when this run is drawn.
// TODO: allow somebody else to specify the rule for generating a left-padding value and
// a right-padding value.
uint8_t *start_pointer = pointer_to_location(write_area_.x, write_area_.y);
memcpy( &start_pointer[-bytes_per_pixel_],
start_pointer,
@ -120,56 +109,66 @@ void TextureBuilder::reduce_previous_allocation_to(size_t actual_length) {
bytes_per_pixel_);
}
void TextureBuilder::submit() {
uint16_t height = write_areas_start_y_ + (write_areas_start_x_ ? 1 : 0);
did_submit_ = true;
bool TextureBuilder::retain_latest() {
// If the previous allocate_write_area declined to act, decline also.
if(was_full_) return false;
glTexSubImage2D( GL_TEXTURE_2D, 0,
0, 0,
InputBufferBuilderWidth, height,
formatForDepth(bytes_per_pixel_), GL_UNSIGNED_BYTE,
image_.data());
// Account for the most recently written area as taken.
write_areas_start_x_ += write_area_.length + 2;
// Store into the vector directly if there's already room, otherwise grow the vector.
// Probably I don't need to mess about with this myself; it's unnecessary second-guessing.
// TODO: profile and prove.
if(number_of_write_areas_ < write_areas_.size())
write_areas_[number_of_write_areas_] = write_area_;
else
write_areas_.push_back(write_area_);
number_of_write_areas_++;
return true;
}
bool TextureBuilder::is_full() {
return is_full_;
}
void TextureBuilder::submit() {
if(write_areas_start_y_ < first_unsubmitted_y_) {
// A write area start y less than the first line on which submissions began implies it must have wrapped
// around. So the submission set is everything back to zero before the current write area plus everything
// from the first unsubmitted y downward.
uint16_t height = write_areas_start_y_ + (write_areas_start_x_ ? 1 : 0);
glTexSubImage2D( GL_TEXTURE_2D, 0,
0, 0,
InputBufferBuilderWidth, height,
formatForDepth(bytes_per_pixel_), GL_UNSIGNED_BYTE,
image_.data());
glTexSubImage2D( GL_TEXTURE_2D, 0,
0, first_unsubmitted_y_,
InputBufferBuilderWidth, InputBufferBuilderHeight - first_unsubmitted_y_,
formatForDepth(bytes_per_pixel_), GL_UNSIGNED_BYTE,
image_.data() + first_unsubmitted_y_ * bytes_per_pixel_ * InputBufferBuilderWidth);
} else {
// If the current write area start y is after the first unsubmitted line, just submit the region in between.
uint16_t height = write_areas_start_y_ + (write_areas_start_x_ ? 1 : 0) - first_unsubmitted_y_;
glTexSubImage2D( GL_TEXTURE_2D, 0,
0, first_unsubmitted_y_,
InputBufferBuilderWidth, height,
formatForDepth(bytes_per_pixel_), GL_UNSIGNED_BYTE,
image_.data() + first_unsubmitted_y_ * bytes_per_pixel_ * InputBufferBuilderWidth);
}
// Update the starting location for the next submission, and mark definitively that the buffer is once again not full.
first_unsubmitted_y_ = write_areas_start_y_;
is_full_ = false;
}
void TextureBuilder::flush(const std::function<void(const std::vector<WriteArea> &write_areas, size_t count)> &function) {
bool was_full = is_full_;
if(did_submit_) {
write_areas_start_y_ = write_areas_start_x_ = 0;
is_full_ = false;
// Just throw everything currently in the flush queue to the provided function, and note that
// the queue is now empty.
if(number_of_write_areas_) {
function(write_areas_, number_of_write_areas_);
}
if(number_of_write_areas_ && !was_full) {
if(write_areas_[0].x != write_areas_start_x_+1 || write_areas_[0].y != write_areas_start_y_) {
for(size_t area = 0; area < number_of_write_areas_; area++) {
WriteArea &write_area = write_areas_[area];
if(write_areas_start_x_ + write_area.length + 2 > InputBufferBuilderWidth) {
write_areas_start_x_ = 0;
write_areas_start_y_ ++;
if(write_areas_start_y_ == InputBufferBuilderHeight) {
is_full_ = true;
break;
}
}
memmove(
pointer_to_location(write_areas_start_x_, write_areas_start_y_),
pointer_to_location(write_area.x - 1, write_area.y),
(write_area.length + 2) * bytes_per_pixel_);
write_area.x = write_areas_start_x_ + 1;
write_area.y = write_areas_start_y_;
}
}
if(!is_full_) {
function(write_areas_, number_of_write_areas_);
write_areas_start_x_ = write_areas_[number_of_write_areas_-1].x + write_areas_[number_of_write_areas_-1].length + 1;
write_areas_start_y_ = write_areas_[number_of_write_areas_-1].y;
}
}
did_submit_ = false;
number_of_write_areas_ = 0;
}

View File

@ -68,7 +68,8 @@ class TextureBuilder {
void reduce_previous_allocation_to(size_t actual_length);
/// Allocated runs are provisional; they will not appear in the next flush queue unless retained.
void retain_latest();
/// @returns @c true if a retain succeeded; @c false otherwise.
bool retain_latest();
/// @returns @c true if all future calls to @c allocate_write_area will fail on account of the input texture
/// being full; @c false if calls may succeed.
@ -99,8 +100,8 @@ class TextureBuilder {
// the list of write areas that have ascended to the flush queue
std::vector<WriteArea> write_areas_;
size_t number_of_write_areas_;
bool is_full_;
bool did_submit_;
bool is_full_, was_full_;
uint16_t first_unsubmitted_y_;
inline uint8_t *pointer_to_location(uint16_t x, uint16_t y);
// Usually: the start position for the current batch of write areas.