2016-02-14 01:52:23 +00:00
|
|
|
//
|
2016-11-16 14:57:17 +00:00
|
|
|
// TextureBuilder.cpp
|
2016-02-14 01:52:23 +00:00
|
|
|
// Clock Signal
|
|
|
|
//
|
2016-03-09 01:59:16 +00:00
|
|
|
// Created by Thomas Harte on 08/03/2016.
|
2016-02-14 01:52:23 +00:00
|
|
|
// Copyright © 2016 Thomas Harte. All rights reserved.
|
|
|
|
//
|
|
|
|
|
2016-11-16 14:57:17 +00:00
|
|
|
#include "TextureBuilder.hpp"
|
2016-02-14 01:52:23 +00:00
|
|
|
#include "CRTOpenGL.hpp"
|
2016-11-16 15:13:06 +00:00
|
|
|
#include "OpenGL.hpp"
|
2016-03-09 01:59:16 +00:00
|
|
|
#include <string.h>
|
2016-02-14 01:52:23 +00:00
|
|
|
|
2016-03-09 01:59:16 +00:00
|
|
|
using namespace Outputs::CRT;
|
2016-02-14 01:52:23 +00:00
|
|
|
|
2017-03-26 18:34:47 +00:00
|
|
|
static const GLint internalFormatForDepth(size_t depth) {
|
|
|
|
switch(depth) {
|
2016-11-16 15:13:06 +00:00
|
|
|
default: return GL_FALSE;
|
|
|
|
case 1: return GL_R8UI;
|
|
|
|
case 2: return GL_RG8UI;
|
|
|
|
case 3: return GL_RGB8UI;
|
|
|
|
case 4: return GL_RGBA8UI;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-26 18:34:47 +00:00
|
|
|
static const GLenum formatForDepth(size_t depth) {
|
|
|
|
switch(depth) {
|
2016-11-16 15:13:06 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-17 04:26:04 +00:00
|
|
|
TextureBuilder::TextureBuilder(size_t bytes_per_pixel, GLenum texture_unit) :
|
2017-03-26 18:34:47 +00:00
|
|
|
bytes_per_pixel_(bytes_per_pixel),
|
|
|
|
write_areas_start_x_(0),
|
|
|
|
write_areas_start_y_(0),
|
2017-07-09 21:50:22 +00:00
|
|
|
first_unsubmitted_y_(0),
|
2017-03-26 18:34:47 +00:00
|
|
|
is_full_(false),
|
|
|
|
number_of_write_areas_(0) {
|
2016-11-16 15:13:06 +00:00
|
|
|
image_.resize(bytes_per_pixel * InputBufferBuilderWidth * InputBufferBuilderHeight);
|
|
|
|
glGenTextures(1, &texture_name_);
|
2016-11-17 04:26:04 +00:00
|
|
|
|
|
|
|
glActiveTexture(texture_unit);
|
2016-11-16 15:13:06 +00:00
|
|
|
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);
|
|
|
|
glTexImage2D(GL_TEXTURE_2D, 0, internalFormatForDepth(bytes_per_pixel), InputBufferBuilderWidth, InputBufferBuilderHeight, 0, formatForDepth(bytes_per_pixel), GL_UNSIGNED_BYTE, nullptr);
|
|
|
|
}
|
|
|
|
|
2017-03-26 18:34:47 +00:00
|
|
|
TextureBuilder::~TextureBuilder() {
|
2016-11-16 15:13:06 +00:00
|
|
|
glDeleteTextures(1, &texture_name_);
|
2016-11-16 04:31:32 +00:00
|
|
|
}
|
2016-03-20 02:46:17 +00:00
|
|
|
|
2017-03-26 18:34:47 +00:00
|
|
|
inline uint8_t *TextureBuilder::pointer_to_location(uint16_t x, uint16_t y) {
|
2016-12-06 12:24:07 +00:00
|
|
|
return &image_[((y * InputBufferBuilderWidth) + x) * bytes_per_pixel_];
|
|
|
|
}
|
|
|
|
|
2017-10-18 02:09:48 +00:00
|
|
|
uint8_t *TextureBuilder::allocate_write_area(size_t required_length, size_t required_alignment) {
|
2017-07-09 21:50:22 +00:00
|
|
|
// 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_;
|
2016-12-06 12:24:07 +00:00
|
|
|
if(is_full_) return nullptr;
|
|
|
|
|
2017-07-09 21:50:22 +00:00
|
|
|
// 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.
|
2017-10-18 02:09:48 +00:00
|
|
|
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) {
|
2017-07-09 21:50:22 +00:00
|
|
|
write_areas_start_x_ = 0;
|
2017-10-20 02:02:00 +00:00
|
|
|
alignment_offset = required_alignment - 1;
|
2017-07-09 21:50:22 +00:00
|
|
|
write_areas_start_y_ = (write_areas_start_y_ + 1) % InputBufferBuilderHeight;
|
2016-12-06 12:24:07 +00:00
|
|
|
|
2017-07-09 21:50:22 +00:00
|
|
|
if(write_areas_start_y_ == first_unsubmitted_y_) {
|
|
|
|
was_full_ = is_full_ = true;
|
2016-12-06 12:24:07 +00:00
|
|
|
return nullptr;
|
2016-05-04 11:39:45 +00:00
|
|
|
}
|
2016-05-07 22:37:18 +00:00
|
|
|
}
|
2016-11-16 05:15:50 +00:00
|
|
|
|
2017-07-09 21:50:22 +00:00
|
|
|
// Queue up the latest write area.
|
2017-10-18 02:09:48 +00:00
|
|
|
write_area_.x = write_areas_start_x_ + 1 + static_cast<uint16_t>(alignment_offset);
|
2017-07-09 21:50:22 +00:00
|
|
|
write_area_.y = write_areas_start_y_;
|
2017-07-08 02:25:05 +00:00
|
|
|
write_area_.length = (uint16_t)required_length;
|
2016-12-06 12:24:07 +00:00
|
|
|
|
2017-07-09 21:50:22 +00:00
|
|
|
// Return a video pointer.
|
2017-07-08 02:25:05 +00:00
|
|
|
return pointer_to_location(write_area_.x, write_area_.y);
|
2016-05-07 22:37:18 +00:00
|
|
|
}
|
2016-05-05 11:22:49 +00:00
|
|
|
|
2017-03-26 18:34:47 +00:00
|
|
|
void TextureBuilder::reduce_previous_allocation_to(size_t actual_length) {
|
2017-07-09 21:50:22 +00:00
|
|
|
// If the previous allocate_write_area declined to act, decline also.
|
|
|
|
if(was_full_) return;
|
2016-05-04 11:39:45 +00:00
|
|
|
|
2017-07-09 21:50:22 +00:00
|
|
|
// Update the length of the current write area.
|
2017-07-08 02:25:05 +00:00
|
|
|
write_area_.length = (uint16_t)actual_length;
|
2016-05-08 20:43:11 +00:00
|
|
|
|
2017-07-09 21:50:22 +00:00
|
|
|
// 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.
|
2017-10-18 00:50:46 +00:00
|
|
|
uint8_t *start_pointer = pointer_to_location(write_area_.x, write_area_.y) - bytes_per_pixel_;
|
|
|
|
memcpy( start_pointer,
|
|
|
|
&start_pointer[bytes_per_pixel_],
|
2016-11-16 15:13:06 +00:00
|
|
|
bytes_per_pixel_);
|
2016-02-14 01:52:23 +00:00
|
|
|
|
2017-10-18 00:50:46 +00:00
|
|
|
memcpy( &start_pointer[(actual_length + 1) * bytes_per_pixel_],
|
|
|
|
&start_pointer[actual_length * bytes_per_pixel_],
|
2016-11-16 15:13:06 +00:00
|
|
|
bytes_per_pixel_);
|
2016-05-05 01:27:10 +00:00
|
|
|
}
|
|
|
|
|
2017-07-09 21:50:22 +00:00
|
|
|
bool TextureBuilder::retain_latest() {
|
|
|
|
// If the previous allocate_write_area declined to act, decline also.
|
|
|
|
if(was_full_) return false;
|
2016-12-06 12:24:07 +00:00
|
|
|
|
2017-07-09 21:50:22 +00:00
|
|
|
// Account for the most recently written area as taken.
|
|
|
|
write_areas_start_x_ += write_area_.length + 2;
|
2016-12-06 12:24:07 +00:00
|
|
|
|
2017-07-09 21:50:22 +00:00
|
|
|
// 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_++;
|
2016-12-06 12:24:07 +00:00
|
|
|
|
2017-07-09 21:50:22 +00:00
|
|
|
return true;
|
|
|
|
}
|
2016-12-06 13:08:57 +00:00
|
|
|
|
2017-07-09 23:11:38 +00:00
|
|
|
void TextureBuilder::discard_latest() {
|
|
|
|
if(was_full_) return;
|
|
|
|
number_of_write_areas_--;
|
|
|
|
}
|
|
|
|
|
2017-07-09 21:50:22 +00:00
|
|
|
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);
|
2016-12-06 12:24:07 +00:00
|
|
|
}
|
|
|
|
|
2017-07-09 21:50:22 +00:00
|
|
|
// 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, 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_);
|
|
|
|
}
|
2016-12-06 12:24:07 +00:00
|
|
|
number_of_write_areas_ = 0;
|
|
|
|
}
|