mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-27 16:31:31 +00:00
203 lines
7.3 KiB
C++
203 lines
7.3 KiB
C++
//
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
struct DefaultBookender: public TextureBuilder::Bookender {
|
|
public:
|
|
DefaultBookender(std::size_t bytes_per_pixel) : bytes_per_pixel_(bytes_per_pixel) {}
|
|
|
|
void add_bookends(uint8_t *const left_value, uint8_t *const right_value, uint8_t *left_bookend, uint8_t *right_bookend) {
|
|
std::memcpy(left_bookend, left_value, bytes_per_pixel_);
|
|
std::memcpy(right_bookend, right_value, bytes_per_pixel_);
|
|
}
|
|
|
|
private:
|
|
std::size_t bytes_per_pixel_;
|
|
};
|
|
|
|
}
|
|
|
|
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);
|
|
|
|
set_bookender(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.
|
|
// 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) - bytes_per_pixel_;
|
|
bookender_->add_bookends(&start_pointer[bytes_per_pixel_], &start_pointer[actual_length * bytes_per_pixel_], start_pointer, &start_pointer[(actual_length + 1) * bytes_per_pixel_]);
|
|
}
|
|
|
|
void TextureBuilder::set_bookender(std::unique_ptr<TextureBuilder::Bookender> bookender) {
|
|
bookender_ = std::move(bookender);
|
|
if(!bookender_) {
|
|
bookender_.reset(new DefaultBookender(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;
|
|
}
|