1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-11-26 08:49:37 +00:00

Adds bookending, and finally kills the TextureBuilder. Farewell.

This commit is contained in:
Thomas Harte 2018-11-14 20:49:06 -05:00
parent 0487580a1a
commit 91b19c5c70
4 changed files with 15 additions and 316 deletions

View File

@ -1365,10 +1365,8 @@
4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Oric.cpp; path = Oric/Oric.cpp; sourceTree = "<group>"; };
4BCF1FA31DADC3DD0039D2E7 /* Oric.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Oric.hpp; path = Oric/Oric.hpp; sourceTree = "<group>"; };
4BD060A51FE49D3C006E14BE /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Speaker.hpp; sourceTree = "<group>"; };
4BD191D8219113B80042E144 /* TextureBuilder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TextureBuilder.cpp; sourceTree = "<group>"; };
4BD191D9219113B80042E144 /* OpenGL.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OpenGL.hpp; sourceTree = "<group>"; };
4BD191DC219113B80042E144 /* CRTOpenGL.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRTOpenGL.cpp; sourceTree = "<group>"; };
4BD191DD219113B80042E144 /* TextureBuilder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TextureBuilder.hpp; sourceTree = "<group>"; };
4BD191E0219113B80042E144 /* IntermediateShader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = IntermediateShader.hpp; sourceTree = "<group>"; };
4BD191E1219113B80042E144 /* OutputShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OutputShader.cpp; sourceTree = "<group>"; };
4BD191E3219113B80042E144 /* IntermediateShader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IntermediateShader.cpp; sourceTree = "<group>"; };
@ -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 */,
);

View File

@ -75,6 +75,10 @@ template <typename T> 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() {

View File

@ -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 <cstring>
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<uint16_t>(alignment_offset);
write_area_.x = write_areas_start_x_ + 1;
write_area_.y = write_areas_start_y_;
write_area_.length = static_cast<uint16_t>(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<uint16_t>(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<void(const std::vector<WriteArea> &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;
}

View File

@ -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 <cstdint>
#include <functional>
#include <memory>
#include <vector>
#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<void(const std::vector<WriteArea> &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<uint8_t> 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<WriteArea> 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 */