diff --git a/Outputs/OpenGL/ScanTarget.cpp b/Outputs/OpenGL/ScanTarget.cpp index 83fe1613b..846bed6ac 100644 --- a/Outputs/OpenGL/ScanTarget.cpp +++ b/Outputs/OpenGL/ScanTarget.cpp @@ -131,6 +131,8 @@ void ScanTarget::set_modals(Modals modals) { Outputs::Display::ScanTarget::Scan *ScanTarget::begin_scan() { if(allocation_has_failed_) return nullptr; + std::lock_guard lock_guard(write_pointers_mutex_); + const auto result = &scan_buffer_[write_pointers_.scan_buffer]; const auto read_pointers = read_pointers_.load(); @@ -154,6 +156,7 @@ Outputs::Display::ScanTarget::Scan *ScanTarget::begin_scan() { void ScanTarget::end_scan() { if(vended_scan_) { + std::lock_guard lock_guard(write_pointers_mutex_); vended_scan_->data_y = TextureAddressGetY(vended_write_area_pointer_); vended_scan_->line = write_pointers_.line; vended_scan_->scan.end_points[0].data_offset += TextureAddressGetX(vended_write_area_pointer_); @@ -182,6 +185,8 @@ uint8_t *ScanTarget::begin_data(size_t required_length, size_t required_alignmen return nullptr; } + std::lock_guard lock_guard(write_pointers_mutex_); + // Determine where the proposed write area would start and end. uint16_t output_y = TextureAddressGetY(write_pointers_.write_area); @@ -222,6 +227,8 @@ uint8_t *ScanTarget::begin_data(size_t required_length, size_t required_alignmen void ScanTarget::end_data(size_t actual_length) { if(allocation_has_failed_ || !data_is_allocated_) return; + std::lock_guard lock_guard(write_pointers_mutex_); + // Bookend the start of the new data, to safeguard for precision errors in sampling. memcpy( &write_area_texture_[size_t(write_pointers_.write_area - 1) * data_type_size_], @@ -268,6 +275,7 @@ void ScanTarget::announce(Event event, bool is_visible, const Outputs::Display:: if(output_is_visible_ == is_visible) return; if(is_visible) { const auto read_pointers = read_pointers_.load(); + std::lock_guard lock_guard(write_pointers_mutex_); // Commit the most recent line only if any scans fell on it. // Otherwise there's no point outputting it, it'll contribute nothing. @@ -336,14 +344,20 @@ void ScanTarget::announce(Event event, bool is_visible, const Outputs::Display:: void ScanTarget::setup_pipeline() { const auto data_type_size = Outputs::Display::size_for_data_type(modals_.input_data_type); - if(data_type_size != data_type_size_) { - // TODO: flush output. - data_type_size_ = data_type_size; - write_area_texture_.resize(WriteAreaWidth*WriteAreaHeight*data_type_size_); + // Ensure the lock guard here has a restricted scope; this is the only time that a thread + // other than the main owner of write_pointers_ may adjust it. + { + std::lock_guard lock_guard(write_pointers_mutex_); + if(data_type_size != data_type_size_) { + // TODO: flush output. - write_pointers_.scan_buffer = 0; - write_pointers_.write_area = 0; + data_type_size_ = data_type_size; + write_area_texture_.resize(WriteAreaWidth*WriteAreaHeight*data_type_size_); + + write_pointers_.scan_buffer = 0; + write_pointers_.write_area = 0; + } } // Prepare to bind line shaders. diff --git a/Outputs/OpenGL/ScanTarget.hpp b/Outputs/OpenGL/ScanTarget.hpp index f7eeb816e..cda6c47cc 100644 --- a/Outputs/OpenGL/ScanTarget.hpp +++ b/Outputs/OpenGL/ScanTarget.hpp @@ -116,6 +116,10 @@ class ScanTarget: public Outputs::Display::ScanTarget { /// A pointer to the next thing that should be provided to the caller for data. PointerSet write_pointers_; + /// A mutex for gettng access to write_pointers_; access to write_pointers_ + /// or data_type_size_ is almost never contended, so this is cheap for the main use case. + std::mutex write_pointers_mutex_; + /// A pointer to the final thing currently cleared for submission. std::atomic submit_pointers_;