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:
parent
0487580a1a
commit
91b19c5c70
@ -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 */,
|
||||
);
|
||||
|
@ -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() {
|
||||
|
@ -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;
|
||||
}
|
@ -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 */
|
Loading…
Reference in New Issue
Block a user