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:
parent
a0367d6669
commit
ee1a9a4781
@ -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_;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user