diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index 586b1c813..48f765f78 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -1365,10 +1365,8 @@ 4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Oric.cpp; path = Oric/Oric.cpp; sourceTree = ""; }; 4BCF1FA31DADC3DD0039D2E7 /* Oric.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Oric.hpp; path = Oric/Oric.hpp; sourceTree = ""; }; 4BD060A51FE49D3C006E14BE /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Speaker.hpp; sourceTree = ""; }; - 4BD191D8219113B80042E144 /* TextureBuilder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextureBuilder.cpp; sourceTree = ""; }; 4BD191D9219113B80042E144 /* OpenGL.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OpenGL.hpp; sourceTree = ""; }; 4BD191DC219113B80042E144 /* CRTOpenGL.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRTOpenGL.cpp; sourceTree = ""; }; - 4BD191DD219113B80042E144 /* TextureBuilder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TextureBuilder.hpp; sourceTree = ""; }; 4BD191E0219113B80042E144 /* IntermediateShader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = IntermediateShader.hpp; sourceTree = ""; }; 4BD191E1219113B80042E144 /* OutputShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OutputShader.cpp; sourceTree = ""; }; 4BD191E3219113B80042E144 /* IntermediateShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IntermediateShader.cpp; sourceTree = ""; }; @@ -3028,10 +3026,8 @@ 4BD191DC219113B80042E144 /* CRTOpenGL.cpp */, 4BD191F22191180E0042E144 /* ScanTarget.cpp */, 4BD5D2672199148100DDF17D /* ScanTargetGLSLFragments.cpp */, - 4BD191D8219113B80042E144 /* TextureBuilder.cpp */, 4BD191D9219113B80042E144 /* OpenGL.hpp */, 4BD191F32191180E0042E144 /* ScanTarget.hpp */, - 4BD191DD219113B80042E144 /* TextureBuilder.hpp */, 4BD424DC2193B5340097291A /* Primitives */, 4BD191DF219113B80042E144 /* Shaders */, ); diff --git a/Outputs/OpenGL/ScanTarget.cpp b/Outputs/OpenGL/ScanTarget.cpp index d29b7c0ee..b7eebe49c 100644 --- a/Outputs/OpenGL/ScanTarget.cpp +++ b/Outputs/OpenGL/ScanTarget.cpp @@ -75,6 +75,10 @@ template void ScanTarget::allocate_buffer(const T &array, GLuint &b ScanTarget::ScanTarget() : unprocessed_line_texture_(LineBufferWidth, LineBufferHeight, UnprocessedLineBufferTextureUnit, GL_LINEAR) { + // Ensure proper initialisation of the two atomic pointer sets. + read_pointers_.store(write_pointers_); + submit_pointers_.store(write_pointers_); + // Allocate space for the scans and lines. allocate_buffer(scan_buffer_, scan_buffer_name_, scan_vertex_array_); allocate_buffer(line_buffer_, line_buffer_name_, line_vertex_array_); @@ -244,11 +248,21 @@ 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_) return; -// memset(&write_area_texture_[size_t(write_pointers_.write_area) * data_type_size_], 0xff, actual_length * data_type_size_); + // 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_], + &write_area_texture_[size_t(write_pointers_.write_area) * data_type_size_], + data_type_size_); // The write area was allocated in the knowledge that there's sufficient // distance left on the current line, so there's no need to worry about carry. write_pointers_.write_area += actual_length + 1; + + // Also bookend the end. + memcpy( + &write_area_texture_[size_t(write_pointers_.write_area) * data_type_size_], + &write_area_texture_[size_t(write_pointers_.write_area - 1) * data_type_size_], + data_type_size_); } void ScanTarget::submit() { diff --git a/Outputs/OpenGL/TextureBuilder.cpp b/Outputs/OpenGL/TextureBuilder.cpp deleted file mode 100644 index 102653771..000000000 --- a/Outputs/OpenGL/TextureBuilder.cpp +++ /dev/null @@ -1,179 +0,0 @@ -// -// TextureBuilder.cpp -// Clock Signal -// -// Created by Thomas Harte on 08/03/2016. -// Copyright 2016 Thomas Harte. All rights reserved. -// - -#include "TextureBuilder.hpp" -#include "CRTOpenGL.hpp" -#include "OpenGL.hpp" - -#include - -using namespace Outputs::CRT; - -namespace { - -const GLint internalFormatForDepth(std::size_t depth) { - switch(depth) { - default: return GL_FALSE; - case 1: return GL_R8UI; - case 2: return GL_RG8UI; - case 3: return GL_RGB8UI; - case 4: return GL_RGBA8UI; - } -} - -const GLenum formatForDepth(std::size_t depth) { - switch(depth) { - default: return GL_FALSE; - case 1: return GL_RED_INTEGER; - case 2: return GL_RG_INTEGER; - case 3: return GL_RGB_INTEGER; - case 4: return GL_RGBA_INTEGER; - } -} - -} - -TextureBuilder::TextureBuilder(std::size_t bytes_per_pixel, GLenum texture_unit) : - bytes_per_pixel_(bytes_per_pixel), texture_unit_(texture_unit) { - image_.resize(bytes_per_pixel * InputBufferBuilderWidth * InputBufferBuilderHeight); - glGenTextures(1, &texture_name_); - - bind(); - glTexImage2D(GL_TEXTURE_2D, 0, internalFormatForDepth(bytes_per_pixel), InputBufferBuilderWidth, InputBufferBuilderHeight, 0, formatForDepth(bytes_per_pixel), GL_UNSIGNED_BYTE, nullptr); -} - -TextureBuilder::~TextureBuilder() { - glDeleteTextures(1, &texture_name_); -} - -void TextureBuilder::bind() { - glActiveTexture(texture_unit_); - glBindTexture(GL_TEXTURE_2D, texture_name_); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); -} - -inline uint8_t *TextureBuilder::pointer_to_location(uint16_t x, uint16_t y) { - return &image_[((y * InputBufferBuilderWidth) + x) * bytes_per_pixel_]; -} - -uint8_t *TextureBuilder::allocate_write_area(std::size_t required_length, std::size_t required_alignment) { - // 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; - - // 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. - std::size_t alignment_offset = (required_alignment - ((write_areas_start_x_ + 1) % required_alignment)) % required_alignment; - if(write_areas_start_x_ + required_length + 2 + alignment_offset > InputBufferBuilderWidth) { - write_areas_start_x_ = 0; - alignment_offset = required_alignment - 1; - write_areas_start_y_ = (write_areas_start_y_ + 1) % InputBufferBuilderHeight; - - if(write_areas_start_y_ == first_unsubmitted_y_) { - was_full_ = is_full_ = true; - return nullptr; - } - } - - // Queue up the latest write area. - write_areas_start_x_ += static_cast(alignment_offset); - write_area_.x = write_areas_start_x_ + 1; - write_area_.y = write_areas_start_y_; - write_area_.length = static_cast(required_length); - - // Return a video pointer. - return pointer_to_location(write_area_.x, write_area_.y); -} - -void TextureBuilder::reduce_previous_allocation_to(std::size_t actual_length) { - // 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 = static_cast(actual_length); - - // Bookend the allocation with duplicates of the first and last pixel, to protect - // against rounding errors when this run is drawn. - uint8_t *start_pointer = pointer_to_location(write_area_.x, write_area_.y) - bytes_per_pixel_; - std::memcpy(start_pointer, &start_pointer[bytes_per_pixel_], bytes_per_pixel_); - std::memcpy(&start_pointer[(actual_length + 1) * bytes_per_pixel_], &start_pointer[actual_length * bytes_per_pixel_], bytes_per_pixel_); -} - -bool TextureBuilder::retain_latest() { - // If the previous allocate_write_area declined to act, decline also. - if(was_full_) return false; - - // 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; -} - -void TextureBuilder::discard_latest() { - if(was_full_) return; - number_of_write_areas_--; -} - -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 &write_areas, std::size_t count)> &function) { - // 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_); - } - number_of_write_areas_ = 0; -} diff --git a/Outputs/OpenGL/TextureBuilder.hpp b/Outputs/OpenGL/TextureBuilder.hpp deleted file mode 100644 index 456a4e792..000000000 --- a/Outputs/OpenGL/TextureBuilder.hpp +++ /dev/null @@ -1,132 +0,0 @@ -// -// TextureBuilder.hpp -// Clock Signal -// -// Created by Thomas Harte on 08/03/2016. -// Copyright 2016 Thomas Harte. All rights reserved. -// - -#ifndef Outputs_CRT_Internals_TextureBuilder_hpp -#define Outputs_CRT_Internals_TextureBuilder_hpp - -#include -#include -#include -#include - -#include "OpenGL.hpp" -#include "CRTConstants.hpp" - -namespace Outputs { -namespace CRT { - -/*! - Owns an OpenGL texture resource and provides mechanisms to fill it from bottom left to top right - with runs of data, ensuring each run is neighboured immediately to the left and right by copies of its - first and last pixels. - - Although this class is not itself inherently thread safe, it is built to permit one serialised stream - of calls to provide source data, with an interceding (but also serialised) submission to the GPU at any time. - - - Intended usage by the data generator: - - (i) allocate a write area with allocate_write_area, supplying a maximum size. - (ii) call reduce_previous_allocation_to to announce the actual size written. - - This will cause you to have added source data to the target texture. You can then either use that data - or allow it to expire. - - (iii) call retain_latest to add the most recently written write area to the flush queue. - - The flush queue contains provisional data, that can sit in the CPU's memory space indefinitely. This facility - is provided because it is expected that a texture will be built alontside some other collection of data; - that data in the flush queue is expected to become useful in coordination with something else but should - be retained at least until then. - - (iv) call flush to move data to the submit queue. - - When you flush, you'll receive a record of the bounds of all newly-flushed areas of source data. That gives - an opportunity to correlate the data with whatever else it is being tied to. It will continue to sit in - the CPU's memory space but has now passed beyond any further modification or reporting. - - - Intended usage by the GPU owner: - - (i) call submit to move data to the GPU and free up its CPU-side resources. - - The latest data is now on the GPU, regardless of where the data provider may be in its process; only data - that has entered the submission queue is uploaded. - -*/ -class TextureBuilder { - public: - /// Constructs an instance of InputTextureBuilder that contains a texture of colour depth @c bytes_per_pixel; - /// this creates a new texture and binds it to the current active texture unit. - TextureBuilder(std::size_t bytes_per_pixel, GLenum texture_unit); - virtual ~TextureBuilder(); - - /// 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. - /// Calls must be paired off with calls to @c reduce_previous_allocation_to. - /// @returns a pointer to the allocated space if any was available; @c nullptr otherwise. - uint8_t *allocate_write_area(std::size_t required_length, std::size_t required_alignment = 1); - - /// Announces that the owner is finished with the region created by the most recent @c allocate_write_area - /// and indicates that its actual final size was @c actual_length. - void reduce_previous_allocation_to(std::size_t actual_length); - - /// Allocated runs are provisional; they will not appear in the next flush queue unless retained. - /// @returns @c true if a retain succeeded; @c false otherwise. - bool retain_latest(); - - // Undoes the most recent retain_latest. Undefined behaviour if a submission has occurred in the interim. - void discard_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. - bool is_full(); - - /// Updates the currently-bound texture with all new data provided since the last @c submit. - void submit(); - - struct WriteArea { - uint16_t x = 0, y = 0, length = 0; - }; - /// Finalises all write areas allocated since the last call to @c flush. Only finalised areas will be - /// submitted upon the next @c submit. The supplied function will be called with a list of write areas - /// allocated, indicating their final resting locations and their lengths. - void flush(const std::function &write_areas, std::size_t count)> &); - - /// Binds this texture to the unit supplied at instantiation. - void bind(); - - private: - // the buffer size and target unit - std::size_t bytes_per_pixel_; - GLenum texture_unit_; - - // the buffer - std::vector image_; - GLuint texture_name_; - - // the current write area - WriteArea write_area_; - - // the list of write areas that have ascended to the flush queue - std::vector write_areas_; - std::size_t number_of_write_areas_ = 0; - bool is_full_ = false, was_full_ = false; - uint16_t first_unsubmitted_y_ = 0; - inline uint8_t *pointer_to_location(uint16_t x, uint16_t y); - - // Usually: the start position for the current batch of write areas. - // Caveat: reset to the origin upon a submit. So used in comparison by flush to - // determine whether the current batch of write areas needs to be relocated. - uint16_t write_areas_start_x_ = 0, write_areas_start_y_ = 0; -}; - -} -} - -#endif /* Outputs_CRT_Internals_TextureBuilder_hpp */