mirror of
https://github.com/TomHarte/CLK.git
synced 2025-01-12 15:31:09 +00:00
Started the transition towards a more natural collection of rolling buffers, with phosphor decay in mind.
This commit is contained in:
parent
f57d6d350b
commit
eeb0e134fd
@ -31,7 +31,7 @@
|
||||
4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */; };
|
||||
4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */; };
|
||||
4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; };
|
||||
4B7BFEFE1C6446EF00089C1C /* CRTFrameBuilder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BFEFD1C6446EF00089C1C /* CRTFrameBuilder.cpp */; };
|
||||
4B7BFEFE1C6446EF00089C1C /* CRTBuilders.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7BFEFD1C6446EF00089C1C /* CRTBuilders.cpp */; };
|
||||
4B92EACA1B7C112B00246143 /* TimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92EAC91B7C112B00246143 /* TimingTests.swift */; };
|
||||
4BB298EE1B587D8400A49093 /* 6502_functional_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E01B587D8300A49093 /* 6502_functional_test.bin */; };
|
||||
4BB298EF1B587D8400A49093 /* AllSuiteA.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4BB297E11B587D8300A49093 /* AllSuiteA.bin */; };
|
||||
@ -331,7 +331,6 @@
|
||||
/* Begin PBXFileReference section */
|
||||
4B0CCC421C62D0B3001CAC5F /* CRT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRT.cpp; sourceTree = "<group>"; };
|
||||
4B0CCC431C62D0B3001CAC5F /* CRT.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRT.hpp; sourceTree = "<group>"; };
|
||||
4B0CCC441C62D0B3001CAC5F /* CRTFrame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CRTFrame.h; sourceTree = "<group>"; };
|
||||
4B0CCC461C62D1A8001CAC5F /* CRTOpenGL.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRTOpenGL.cpp; sourceTree = "<group>"; };
|
||||
4B0EBFB61C487F2F00A11F35 /* AudioQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AudioQueue.h; sourceTree = "<group>"; };
|
||||
4B0EBFB71C487F2F00A11F35 /* AudioQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AudioQueue.m; sourceTree = "<group>"; };
|
||||
@ -373,7 +372,7 @@
|
||||
4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TapeUEF.cpp; sourceTree = "<group>"; };
|
||||
4B69FB431C4D941400B5F0AA /* TapeUEF.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TapeUEF.hpp; sourceTree = "<group>"; };
|
||||
4B69FB451C4D950F00B5F0AA /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
|
||||
4B7BFEFD1C6446EF00089C1C /* CRTFrameBuilder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRTFrameBuilder.cpp; sourceTree = "<group>"; };
|
||||
4B7BFEFD1C6446EF00089C1C /* CRTBuilders.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CRTBuilders.cpp; sourceTree = "<group>"; };
|
||||
4B92EAC91B7C112B00246143 /* TimingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimingTests.swift; sourceTree = "<group>"; };
|
||||
4BAE587D1C447B7A005B9AF0 /* KeyCodes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyCodes.h; sourceTree = "<group>"; };
|
||||
4BB297DF1B587D8200A49093 /* Clock SignalTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Clock SignalTests-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
@ -659,6 +658,7 @@
|
||||
4BB73EC11B587A5100552FC2 /* Clock_SignalUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Clock_SignalUITests.swift; sourceTree = "<group>"; };
|
||||
4BB73EC31B587A5100552FC2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
4BB73ECF1B587A6700552FC2 /* Clock Signal.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = "Clock Signal.entitlements"; sourceTree = "<group>"; };
|
||||
4BCC142A1C6FFD6F0033C621 /* CRTOpenGL.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CRTOpenGL.hpp; sourceTree = "<group>"; };
|
||||
4BE5F85C1C3E1C2500C43F01 /* basic.rom */ = {isa = PBXFileReference; lastKnownFileType = file; path = basic.rom; sourceTree = "<group>"; };
|
||||
4BE5F85D1C3E1C2500C43F01 /* os.rom */ = {isa = PBXFileReference; lastKnownFileType = file; path = os.rom; sourceTree = "<group>"; };
|
||||
4BEDD0F41C6D70B800F6257C /* Flywheel.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Flywheel.hpp; sourceTree = "<group>"; };
|
||||
@ -695,15 +695,15 @@
|
||||
children = (
|
||||
4B0CCC421C62D0B3001CAC5F /* CRT.cpp */,
|
||||
4B0CCC431C62D0B3001CAC5F /* CRT.hpp */,
|
||||
4B0CCC441C62D0B3001CAC5F /* CRTFrame.h */,
|
||||
4B0CCC461C62D1A8001CAC5F /* CRTOpenGL.cpp */,
|
||||
4B7BFEFD1C6446EF00089C1C /* CRTFrameBuilder.cpp */,
|
||||
4B7BFEFD1C6446EF00089C1C /* CRTBuilders.cpp */,
|
||||
4B2039921C67E20B001375C3 /* TextureTarget.cpp */,
|
||||
4B2039931C67E20B001375C3 /* TextureTarget.hpp */,
|
||||
4B2039951C67E2A3001375C3 /* OpenGL.hpp */,
|
||||
4B2039971C67FA92001375C3 /* Shader.cpp */,
|
||||
4B2039981C67FA92001375C3 /* Shader.hpp */,
|
||||
4BEDD0F41C6D70B800F6257C /* Flywheel.hpp */,
|
||||
4BCC142A1C6FFD6F0033C621 /* CRTOpenGL.hpp */,
|
||||
);
|
||||
name = CRT;
|
||||
path = ../../Outputs/CRT;
|
||||
@ -1624,7 +1624,7 @@
|
||||
4B0CCC471C62D1A8001CAC5F /* CRTOpenGL.cpp in Sources */,
|
||||
4B2039941C67E20B001375C3 /* TextureTarget.cpp in Sources */,
|
||||
4B0EBFB81C487F2F00A11F35 /* AudioQueue.m in Sources */,
|
||||
4B7BFEFE1C6446EF00089C1C /* CRTFrameBuilder.cpp in Sources */,
|
||||
4B7BFEFE1C6446EF00089C1C /* CRTBuilders.cpp in Sources */,
|
||||
4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */,
|
||||
4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */,
|
||||
4B2409551C45AB05004DA684 /* Speaker.cpp in Sources */,
|
||||
|
@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
#include "CRT.hpp"
|
||||
#include "CRTOpenGL.hpp"
|
||||
#include <stdarg.h>
|
||||
#include <math.h>
|
||||
|
||||
@ -15,13 +16,6 @@ using namespace Outputs;
|
||||
static const uint32_t kCRTFixedPointRange = 0xf7ffffff;
|
||||
static const uint32_t kCRTFixedPointOffset = 0x04000000;
|
||||
|
||||
//static const size_t kCRTVertexOffsetOfPosition = 0;
|
||||
//static const size_t kCRTVertexOffsetOfTexCoord = 4;
|
||||
//static const size_t kCRTVertexOffsetOfLateral = 8;
|
||||
//static const size_t kCRTVertexOffsetOfPhase = 9;
|
||||
//
|
||||
//static const int kCRTSizeOfVertex = 10;
|
||||
|
||||
#define kRetraceXMask 0x01
|
||||
#define kRetraceYMask 0x02
|
||||
|
||||
@ -41,7 +35,7 @@ void CRT::set_new_timing(unsigned int cycles_per_line, unsigned int height_of_di
|
||||
// a TV picture tube or camera tube to the starting point of a line or field. It is about 7 µs
|
||||
// for horizontal retrace and 500 to 750 µs for vertical retrace in NTSC and PAL TV."
|
||||
|
||||
_time_multiplier = (1000 + cycles_per_line - 1) / cycles_per_line;
|
||||
_time_multiplier = (2000 + cycles_per_line - 1) / cycles_per_line;
|
||||
|
||||
// store fundamental display configuration properties
|
||||
_height_of_display = height_of_display;
|
||||
@ -85,32 +79,38 @@ void CRT::set_new_display_type(unsigned int cycles_per_line, DisplayType display
|
||||
|
||||
void CRT::allocate_buffers(unsigned int number, va_list sizes)
|
||||
{
|
||||
// generate buffers for signal storage as requested — format is
|
||||
// number of buffers, size of buffer 1, size of buffer 2...
|
||||
const uint16_t bufferWidth = 2048;
|
||||
const uint16_t bufferHeight = 2048;
|
||||
for(int frame = 0; frame < sizeof(_frame_builders) / sizeof(*_frame_builders); frame++)
|
||||
for(int builder = 0; builder < sizeof(_run_builders) / sizeof(*_run_builders); builder++)
|
||||
{
|
||||
va_list va;
|
||||
va_copy(va, sizes);
|
||||
_frame_builders[frame] = new CRTFrameBuilder(bufferWidth, bufferHeight, number, va);
|
||||
va_end(va);
|
||||
_run_builders[builder] = new CRTRunBuilder();
|
||||
}
|
||||
_current_frame_builder = _frame_builders[0];
|
||||
|
||||
va_list va;
|
||||
va_copy(va, sizes);
|
||||
_buffer_builder = std::unique_ptr<CRTInputBufferBuilder>(new CRTInputBufferBuilder(number, va));
|
||||
va_end(va);
|
||||
}
|
||||
|
||||
CRT::CRT() :
|
||||
_next_scan(0),
|
||||
_frame_read_pointer(0),
|
||||
_run_write_pointer(0),
|
||||
_sync_capacitor_charge_level(0),
|
||||
_is_receiving_sync(false),
|
||||
_current_frame_mutex(new std::mutex),
|
||||
_output_mutex(new std::mutex),
|
||||
_visible_area(Rect(0, 0, 1, 1)),
|
||||
_rasterPosition({.x = 0, .y = 0})
|
||||
{
|
||||
construct_openGL();
|
||||
}
|
||||
|
||||
CRT::~CRT()
|
||||
{
|
||||
for(int builder = 0; builder < sizeof(_run_builders) / sizeof(*_run_builders); builder++)
|
||||
{
|
||||
delete _run_builders[builder];
|
||||
}
|
||||
destruct_openGL();
|
||||
}
|
||||
|
||||
CRT::CRT(unsigned int cycles_per_line, unsigned int height_of_display, ColourSpace colour_space, unsigned int colour_cycle_numerator, unsigned int colour_cycle_denominator, unsigned int number_of_buffers, ...) : CRT()
|
||||
{
|
||||
set_new_timing(cycles_per_line, height_of_display, colour_space, colour_cycle_numerator, colour_cycle_denominator);
|
||||
@ -131,15 +131,6 @@ CRT::CRT(unsigned int cycles_per_line, DisplayType displayType, unsigned int num
|
||||
va_end(buffer_sizes);
|
||||
}
|
||||
|
||||
CRT::~CRT()
|
||||
{
|
||||
for(int frame = 0; frame < sizeof(_frame_builders) / sizeof(*_frame_builders); frame++)
|
||||
{
|
||||
delete _frame_builders[frame];
|
||||
}
|
||||
destruct_openGL();
|
||||
}
|
||||
|
||||
#pragma mark - Sync loop
|
||||
|
||||
Flywheel::SyncEvent CRT::get_next_vertical_sync_event(bool vsync_is_requested, unsigned int cycles_to_run_for, unsigned int *cycles_advanced)
|
||||
@ -172,7 +163,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi
|
||||
hsync_requested = false;
|
||||
vsync_requested = false;
|
||||
|
||||
uint8_t *next_run = (is_output_run && _current_frame_builder && next_run_length) ? _current_frame_builder->get_next_run() : nullptr;
|
||||
uint8_t *next_run = (is_output_run && next_run_length) ? _run_builders[_run_write_pointer]->get_next_input_run() : nullptr;
|
||||
int lengthMask = (_horizontal_flywheel->is_in_retrace() ? kRetraceXMask : 0) | (_vertical_flywheel->is_in_retrace() ? kRetraceYMask : 0);
|
||||
|
||||
#define position_x(v) (*(uint16_t *)&next_run[kCRTSizeOfVertex*v + kCRTVertexOffsetOfPosition + 0])
|
||||
@ -237,25 +228,11 @@ void CRT::advance_cycles(unsigned int number_of_cycles, unsigned int source_divi
|
||||
|
||||
if(next_run_length == time_until_vertical_sync_event && next_vertical_sync_event == Flywheel::SyncEvent::EndRetrace)
|
||||
{
|
||||
if(_current_frame_builder)
|
||||
{
|
||||
_current_frame_builder->complete();
|
||||
_current_frame_mutex->lock();
|
||||
_current_frame = &_current_frame_builder->frame;
|
||||
_current_frame_mutex->unlock();
|
||||
// TODO: how to communicate did_detect_vsync? Bring the delegate back?
|
||||
// _delegate->crt_did_end_frame(this, &_current_frame_builder->frame, _did_detect_vsync);
|
||||
}
|
||||
|
||||
// if(_frames_with_delegate < kCRTNumberOfFrames)
|
||||
// {
|
||||
_frame_read_pointer = (_frame_read_pointer + 1)%kCRTNumberOfFrames;
|
||||
_current_frame_builder = _frame_builders[_frame_read_pointer];
|
||||
_current_frame_builder->reset();
|
||||
// }
|
||||
// else
|
||||
// _current_frame_builder = nullptr;
|
||||
// TODO: how to communicate did_detect_vsync? Bring the delegate back?
|
||||
// _delegate->crt_did_end_frame(this, &_current_frame_builder->frame, _did_detect_vsync);
|
||||
|
||||
_run_write_pointer = (_run_write_pointer + 1)%kCRTNumberOfFrames;
|
||||
_run_builders[_run_write_pointer]->reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -280,57 +257,69 @@ void CRT::output_scan()
|
||||
*/
|
||||
void CRT::output_sync(unsigned int number_of_cycles)
|
||||
{
|
||||
_output_mutex->lock();
|
||||
_scans[_next_scan].type = Type::Sync;
|
||||
_scans[_next_scan].number_of_cycles = number_of_cycles;
|
||||
output_scan();
|
||||
_output_mutex->unlock();
|
||||
}
|
||||
|
||||
void CRT::output_blank(unsigned int number_of_cycles)
|
||||
{
|
||||
_output_mutex->lock();
|
||||
_scans[_next_scan].type = Type::Blank;
|
||||
_scans[_next_scan].number_of_cycles = number_of_cycles;
|
||||
output_scan();
|
||||
_output_mutex->unlock();
|
||||
}
|
||||
|
||||
void CRT::output_level(unsigned int number_of_cycles)
|
||||
{
|
||||
_output_mutex->lock();
|
||||
_scans[_next_scan].type = Type::Level;
|
||||
_scans[_next_scan].number_of_cycles = number_of_cycles;
|
||||
_scans[_next_scan].tex_x = _current_frame_builder ? _current_frame_builder->_write_x_position : 0;
|
||||
_scans[_next_scan].tex_y = _current_frame_builder ? _current_frame_builder->_write_y_position : 0;
|
||||
_scans[_next_scan].tex_x = _buffer_builder->_write_x_position;
|
||||
_scans[_next_scan].tex_y = _buffer_builder->_write_y_position;
|
||||
output_scan();
|
||||
_output_mutex->unlock();
|
||||
}
|
||||
|
||||
void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint8_t magnitude)
|
||||
{
|
||||
_output_mutex->lock();
|
||||
_scans[_next_scan].type = Type::ColourBurst;
|
||||
_scans[_next_scan].number_of_cycles = number_of_cycles;
|
||||
_scans[_next_scan].phase = phase;
|
||||
_scans[_next_scan].magnitude = magnitude;
|
||||
output_scan();
|
||||
_output_mutex->unlock();
|
||||
}
|
||||
|
||||
void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider)
|
||||
{
|
||||
if(_current_frame_builder) _current_frame_builder->reduce_previous_allocation_to(number_of_cycles / source_divider);
|
||||
_output_mutex->lock();
|
||||
|
||||
_buffer_builder->reduce_previous_allocation_to(number_of_cycles / source_divider);
|
||||
_scans[_next_scan].type = Type::Data;
|
||||
_scans[_next_scan].number_of_cycles = number_of_cycles;
|
||||
_scans[_next_scan].tex_x = _current_frame_builder ? _current_frame_builder->_write_x_position : 0;
|
||||
_scans[_next_scan].tex_y = _current_frame_builder ? _current_frame_builder->_write_y_position : 0;
|
||||
_scans[_next_scan].tex_x = _buffer_builder->_write_x_position;
|
||||
_scans[_next_scan].tex_y = _buffer_builder->_write_y_position;
|
||||
_scans[_next_scan].source_divider = source_divider;
|
||||
output_scan();
|
||||
|
||||
_output_mutex->unlock();
|
||||
}
|
||||
|
||||
#pragma mark - Buffer supply
|
||||
|
||||
void CRT::allocate_write_area(size_t required_length)
|
||||
{
|
||||
if(_current_frame_builder) _current_frame_builder->allocate_write_area(required_length);
|
||||
_output_mutex->lock();
|
||||
_buffer_builder->allocate_write_area(required_length);
|
||||
_output_mutex->unlock();
|
||||
}
|
||||
|
||||
uint8_t *CRT::get_write_target_for_buffer(int buffer)
|
||||
{
|
||||
if (!_current_frame_builder) return nullptr;
|
||||
return _current_frame_builder->get_write_target_for_buffer(buffer);
|
||||
return _buffer_builder->get_write_target_for_buffer(buffer);
|
||||
}
|
||||
|
@ -15,7 +15,6 @@
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
|
||||
#include "CRTFrame.h"
|
||||
#include "Flywheel.hpp"
|
||||
|
||||
namespace Outputs {
|
||||
@ -233,6 +232,9 @@ class CRT {
|
||||
unsigned int _colour_cycle_denominator;
|
||||
OutputDevice _output_device;
|
||||
|
||||
// The user-supplied visible area
|
||||
Rect _visible_area;
|
||||
|
||||
// the current scanning position (TODO: can I eliminate this in favour of just using the flywheels?)
|
||||
struct Vector {
|
||||
uint32_t x, y;
|
||||
@ -274,19 +276,32 @@ class CRT {
|
||||
int _next_scan;
|
||||
void output_scan();
|
||||
|
||||
|
||||
struct CRTFrameBuilder {
|
||||
CRTFrame frame;
|
||||
|
||||
CRTFrameBuilder(uint16_t width, uint16_t height, unsigned int number_of_buffers, va_list buffer_sizes);
|
||||
~CRTFrameBuilder();
|
||||
|
||||
std::vector<uint8_t> _all_runs;
|
||||
|
||||
struct CRTRunBuilder {
|
||||
// Resets the run builder.
|
||||
void reset();
|
||||
void complete();
|
||||
|
||||
uint8_t *get_next_run();
|
||||
// Getter for new storage plus backing storage; in RGB mode input runs will map directly
|
||||
// from the input buffer to the screen. In composite mode input runs will map from the
|
||||
// input buffer to the processing buffer, and output runs will map from the processing
|
||||
// buffer to the screen.
|
||||
uint8_t *get_next_input_run();
|
||||
std::vector<uint8_t> _input_runs;
|
||||
|
||||
uint8_t *get_next_output_run();
|
||||
std::vector<uint8_t> _output_runs;
|
||||
|
||||
// Container for total length in cycles of all contained runs.
|
||||
uint32_t duration;
|
||||
|
||||
// Storage for the length of run data uploaded so far; reset to zero by reset but otherwise
|
||||
// entrusted to the CRT to update.
|
||||
size_t uploaded_run_data;
|
||||
size_t number_of_vertices;
|
||||
};
|
||||
|
||||
struct CRTInputBufferBuilder {
|
||||
CRTInputBufferBuilder(unsigned int number_of_buffers, va_list buffer_sizes);
|
||||
~CRTInputBufferBuilder();
|
||||
|
||||
void allocate_write_area(size_t required_length);
|
||||
void reduce_previous_allocation_to(size_t actual_length);
|
||||
@ -298,26 +313,38 @@ class CRT {
|
||||
uint16_t _write_x_position, _write_y_position;
|
||||
size_t _write_target_pointer;
|
||||
size_t _last_allocation_amount;
|
||||
|
||||
struct Buffer {
|
||||
uint8_t *data;
|
||||
size_t bytes_per_pixel;
|
||||
} *buffers;
|
||||
unsigned int number_of_buffers;
|
||||
|
||||
// Storage for the amount of buffer uploaded so far; initialised correctly by the buffer
|
||||
// builder but otherwise entrusted to the CRT to update.
|
||||
unsigned int last_uploaded_line;
|
||||
};
|
||||
|
||||
// the run and input data buffers
|
||||
static const int kCRTNumberOfFrames = 4;
|
||||
std::unique_ptr<CRTInputBufferBuilder> _buffer_builder;
|
||||
CRTRunBuilder *_run_builders[kCRTNumberOfFrames];
|
||||
int _run_write_pointer;
|
||||
std::shared_ptr<std::mutex> _output_mutex;
|
||||
|
||||
// the triple buffer and OpenGL state
|
||||
CRTFrameBuilder *_frame_builders[kCRTNumberOfFrames];
|
||||
CRTFrameBuilder *_current_frame_builder;
|
||||
CRTFrame *_current_frame, *_last_drawn_frame;
|
||||
std::shared_ptr<std::mutex> _current_frame_mutex;
|
||||
int _frame_read_pointer;
|
||||
Rect _visible_area;
|
||||
|
||||
// OpenGL state, kept behind an opaque pointer to avoid inclusion of the GL headers here.
|
||||
struct OpenGLState;
|
||||
OpenGLState *_openGL_state;
|
||||
|
||||
// Other things the caller may have provided.
|
||||
char *_composite_shader;
|
||||
char *_rgb_shader;
|
||||
|
||||
// Setup and teardown for the OpenGL code
|
||||
void construct_openGL();
|
||||
void destruct_openGL();
|
||||
|
||||
// Methods used by the OpenGL code
|
||||
void prepare_shader();
|
||||
void push_size_uniforms(unsigned int output_width, unsigned int output_height);
|
||||
|
||||
|
101
Outputs/CRT/CRTBuilders.cpp
Normal file
101
Outputs/CRT/CRTBuilders.cpp
Normal file
@ -0,0 +1,101 @@
|
||||
//
|
||||
// CRTFrameBuilder.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 04/02/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "CRT.hpp"
|
||||
#include "CRTOpenGL.hpp"
|
||||
|
||||
using namespace Outputs;
|
||||
|
||||
/*
|
||||
CRTInputBufferBuilder
|
||||
*/
|
||||
|
||||
CRT::CRTInputBufferBuilder::CRTInputBufferBuilder(unsigned int number_of_buffers, va_list buffer_sizes)
|
||||
{
|
||||
this->number_of_buffers = number_of_buffers;
|
||||
buffers = new CRTInputBufferBuilder::Buffer[number_of_buffers];
|
||||
|
||||
for(int buffer = 0; buffer < number_of_buffers; buffer++)
|
||||
{
|
||||
buffers[buffer].bytes_per_pixel = va_arg(buffer_sizes, unsigned int);
|
||||
buffers[buffer].data = new uint8_t[CRTInputBufferBuilderWidth * CRTInputBufferBuilderHeight * buffers[buffer].bytes_per_pixel];
|
||||
}
|
||||
|
||||
_next_write_x_position = _next_write_y_position = 0;
|
||||
last_uploaded_line = 0;
|
||||
}
|
||||
|
||||
CRT::CRTInputBufferBuilder::~CRTInputBufferBuilder()
|
||||
{
|
||||
for(int buffer = 0; buffer < number_of_buffers; buffer++)
|
||||
delete[] buffers[buffer].data;
|
||||
delete buffers;
|
||||
}
|
||||
|
||||
|
||||
void CRT::CRTInputBufferBuilder::allocate_write_area(size_t required_length)
|
||||
{
|
||||
_last_allocation_amount = required_length;
|
||||
|
||||
if(_next_write_x_position + required_length + 2 > CRTInputBufferBuilderWidth)
|
||||
{
|
||||
_next_write_x_position = 0;
|
||||
_next_write_y_position = (_next_write_y_position+1)%CRTInputBufferBuilderWidth;
|
||||
}
|
||||
|
||||
_write_x_position = _next_write_x_position + 1;
|
||||
_write_y_position = _next_write_y_position;
|
||||
_write_target_pointer = (_write_y_position * CRTInputBufferBuilderWidth) + _write_x_position;
|
||||
_next_write_x_position += required_length + 2;
|
||||
}
|
||||
|
||||
void CRT::CRTInputBufferBuilder::reduce_previous_allocation_to(size_t actual_length)
|
||||
{
|
||||
for(int c = 0; c < number_of_buffers; c++)
|
||||
{
|
||||
memcpy( &buffers[c].data[(_write_target_pointer - 1) * buffers[c].bytes_per_pixel],
|
||||
&buffers[c].data[_write_target_pointer * buffers[c].bytes_per_pixel],
|
||||
buffers[c].bytes_per_pixel);
|
||||
|
||||
memcpy( &buffers[c].data[(_write_target_pointer + actual_length) * buffers[c].bytes_per_pixel],
|
||||
&buffers[c].data[(_write_target_pointer + actual_length - 1) * buffers[c].bytes_per_pixel],
|
||||
buffers[c].bytes_per_pixel);
|
||||
}
|
||||
|
||||
_next_write_x_position -= (_last_allocation_amount - actual_length);
|
||||
}
|
||||
|
||||
|
||||
uint8_t *CRT::CRTInputBufferBuilder::get_write_target_for_buffer(int buffer)
|
||||
{
|
||||
return &buffers[buffer].data[_write_target_pointer * buffers[buffer].bytes_per_pixel];
|
||||
}
|
||||
|
||||
/*
|
||||
CRTRunBuilder
|
||||
*/
|
||||
void CRT::CRTRunBuilder::reset()
|
||||
{
|
||||
number_of_vertices = 0;
|
||||
}
|
||||
|
||||
uint8_t *CRT::CRTRunBuilder::get_next_input_run()
|
||||
{
|
||||
const size_t vertices_per_run = 6;
|
||||
|
||||
// get a run from the allocated list, allocating more if we're about to overrun
|
||||
if((number_of_vertices + vertices_per_run) * kCRTSizeOfVertex >= _input_runs.size())
|
||||
{
|
||||
_input_runs.resize(_input_runs.size() + kCRTSizeOfVertex * vertices_per_run * 100);
|
||||
}
|
||||
|
||||
uint8_t *next_run = &_input_runs[number_of_vertices * kCRTSizeOfVertex];
|
||||
number_of_vertices += vertices_per_run;
|
||||
|
||||
return next_run;
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
//
|
||||
// CRTFrame.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 24/07/2015.
|
||||
// Copyright © 2015 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef CRTFrame_h
|
||||
#define CRTFrame_h
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
uint8_t *data;
|
||||
unsigned int depth;
|
||||
} CRTBuffer;
|
||||
|
||||
typedef struct {
|
||||
uint16_t width, height;
|
||||
} CRTSize;
|
||||
|
||||
typedef enum {
|
||||
CRTGeometryModeTriangles
|
||||
} CRTGeometryMode;
|
||||
|
||||
typedef struct {
|
||||
/** The total size, in pixels, of the pixel buffer storage. Guaranteed to be a power of two. */
|
||||
CRTSize size;
|
||||
|
||||
/** The portion of the pixel buffer that has been changed since the last time this set of buffers was provided. */
|
||||
CRTSize dirty_size;
|
||||
|
||||
/** The number of individual buffers that adds up to the complete pixel buffer. */
|
||||
unsigned int number_of_buffers;
|
||||
|
||||
/** A C array of those buffers. */
|
||||
CRTBuffer *buffers;
|
||||
|
||||
/** The number of vertices that constitute the output. */
|
||||
unsigned int number_of_vertices;
|
||||
|
||||
/** The type of output. */
|
||||
CRTGeometryMode geometry_mode;
|
||||
|
||||
/** The size of each vertex in bytes. */
|
||||
size_t size_per_vertex;
|
||||
|
||||
/** The vertex data. */
|
||||
uint8_t *vertices;
|
||||
} CRTFrame;
|
||||
|
||||
// The height of the intermediate buffers.
|
||||
static const int kCRTFrameIntermediateBufferHeight = 2048;
|
||||
|
||||
static const size_t kCRTVertexOffsetOfPosition = 0;
|
||||
static const size_t kCRTVertexOffsetOfTexCoord = 4;
|
||||
static const size_t kCRTVertexOffsetOfLateral = 8;
|
||||
|
||||
static const int kCRTSizeOfVertex = 10;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CRTFrame_h */
|
@ -1,105 +0,0 @@
|
||||
//
|
||||
// CRTFrameBuilder.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 04/02/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "CRT.hpp"
|
||||
|
||||
using namespace Outputs;
|
||||
|
||||
CRT::CRTFrameBuilder::CRTFrameBuilder(uint16_t width, uint16_t height, unsigned int number_of_buffers, va_list buffer_sizes)
|
||||
{
|
||||
frame.size.width = width;
|
||||
frame.size.height = height;
|
||||
frame.number_of_buffers = number_of_buffers;
|
||||
frame.buffers = new CRTBuffer[number_of_buffers];
|
||||
frame.size_per_vertex = kCRTSizeOfVertex;
|
||||
frame.geometry_mode = CRTGeometryModeTriangles;
|
||||
|
||||
for(int buffer = 0; buffer < number_of_buffers; buffer++)
|
||||
{
|
||||
frame.buffers[buffer].depth = va_arg(buffer_sizes, unsigned int);
|
||||
frame.buffers[buffer].data = new uint8_t[width * height * frame.buffers[buffer].depth];
|
||||
}
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
CRT::CRTFrameBuilder::~CRTFrameBuilder()
|
||||
{
|
||||
for(int buffer = 0; buffer < frame.number_of_buffers; buffer++)
|
||||
delete[] frame.buffers[buffer].data;
|
||||
delete frame.buffers;
|
||||
}
|
||||
|
||||
void CRT::CRTFrameBuilder::reset()
|
||||
{
|
||||
frame.number_of_vertices = 0;
|
||||
_next_write_x_position = _next_write_y_position = 0;
|
||||
frame.dirty_size.width = 0;
|
||||
frame.dirty_size.height = 1;
|
||||
}
|
||||
|
||||
void CRT::CRTFrameBuilder::complete()
|
||||
{
|
||||
frame.vertices = &_all_runs[0];
|
||||
}
|
||||
|
||||
uint8_t *CRT::CRTFrameBuilder::get_next_run()
|
||||
{
|
||||
const size_t vertices_per_run = 6;
|
||||
|
||||
// get a run from the allocated list, allocating more if we're about to overrun
|
||||
if((frame.number_of_vertices + vertices_per_run) * frame.size_per_vertex >= _all_runs.size())
|
||||
{
|
||||
_all_runs.resize(_all_runs.size() + frame.size_per_vertex * vertices_per_run * 100);
|
||||
}
|
||||
|
||||
uint8_t *next_run = &_all_runs[frame.number_of_vertices * frame.size_per_vertex];
|
||||
frame.number_of_vertices += vertices_per_run;
|
||||
|
||||
return next_run;
|
||||
}
|
||||
|
||||
void CRT::CRTFrameBuilder::allocate_write_area(size_t required_length)
|
||||
{
|
||||
_last_allocation_amount = required_length;
|
||||
|
||||
if(_next_write_x_position + required_length + 2 > frame.size.width)
|
||||
{
|
||||
_next_write_x_position = 0;
|
||||
_next_write_y_position = (_next_write_y_position+1)&(frame.size.height-1);
|
||||
frame.dirty_size.height++;
|
||||
}
|
||||
|
||||
_write_x_position = _next_write_x_position + 1;
|
||||
_write_y_position = _next_write_y_position;
|
||||
_write_target_pointer = (_write_y_position * frame.size.width) + _write_x_position;
|
||||
_next_write_x_position += required_length + 2;
|
||||
frame.dirty_size.width = std::max(frame.dirty_size.width, _next_write_x_position);
|
||||
}
|
||||
|
||||
void CRT::CRTFrameBuilder::reduce_previous_allocation_to(size_t actual_length)
|
||||
{
|
||||
for(int c = 0; c < frame.number_of_buffers; c++)
|
||||
{
|
||||
memcpy( &frame.buffers[c].data[(_write_target_pointer - 1) * frame.buffers[c].depth],
|
||||
&frame.buffers[c].data[_write_target_pointer * frame.buffers[c].depth],
|
||||
frame.buffers[c].depth);
|
||||
|
||||
memcpy( &frame.buffers[c].data[(_write_target_pointer + actual_length) * frame.buffers[c].depth],
|
||||
&frame.buffers[c].data[(_write_target_pointer + actual_length - 1) * frame.buffers[c].depth],
|
||||
frame.buffers[c].depth);
|
||||
}
|
||||
|
||||
_next_write_x_position -= (_last_allocation_amount - actual_length);
|
||||
}
|
||||
|
||||
|
||||
uint8_t *CRT::CRTFrameBuilder::get_write_target_for_buffer(int buffer)
|
||||
{
|
||||
return &frame.buffers[buffer].data[_write_target_pointer * frame.buffers[buffer].depth];
|
||||
}
|
@ -11,6 +11,7 @@
|
||||
#include "OpenGL.hpp"
|
||||
#include "TextureTarget.hpp"
|
||||
#include "Shader.hpp"
|
||||
#include "CRTOpenGL.hpp"
|
||||
|
||||
using namespace Outputs;
|
||||
|
||||
@ -30,8 +31,6 @@ struct CRT::OpenGLState {
|
||||
|
||||
GLuint defaultFramebuffer;
|
||||
|
||||
CRTSize textureSize;
|
||||
|
||||
std::unique_ptr<OpenGL::TextureTarget> compositeTexture; // receives raw composite levels
|
||||
std::unique_ptr<OpenGL::TextureTarget> filteredYTexture; // receives filtered Y in the R channel plus unfiltered I/U and Q/V in G and B
|
||||
std::unique_ptr<OpenGL::TextureTarget> filteredTexture; // receives filtered YIQ or YUV
|
||||
@ -52,7 +51,6 @@ static GLenum formatForDepth(unsigned int depth)
|
||||
void CRT::construct_openGL()
|
||||
{
|
||||
_openGL_state = nullptr;
|
||||
_current_frame = _last_drawn_frame = nullptr;
|
||||
_composite_shader = _rgb_shader = nullptr;
|
||||
}
|
||||
|
||||
@ -87,56 +85,40 @@ void CRT::draw_frame(unsigned int output_width, unsigned int output_height, bool
|
||||
|
||||
glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *)&_openGL_state->defaultFramebuffer);
|
||||
|
||||
_openGL_state->compositeTexture = std::unique_ptr<OpenGL::TextureTarget>(new OpenGL::TextureTarget(2048, kCRTFrameIntermediateBufferHeight));
|
||||
_openGL_state->filteredYTexture = std::unique_ptr<OpenGL::TextureTarget>(new OpenGL::TextureTarget(2048, kCRTFrameIntermediateBufferHeight));
|
||||
_openGL_state->filteredTexture = std::unique_ptr<OpenGL::TextureTarget>(new OpenGL::TextureTarget(2048, kCRTFrameIntermediateBufferHeight));
|
||||
// _openGL_state->compositeTexture = std::unique_ptr<OpenGL::TextureTarget>(new OpenGL::TextureTarget(2048, kCRTFrameIntermediateBufferHeight));
|
||||
// _openGL_state->filteredYTexture = std::unique_ptr<OpenGL::TextureTarget>(new OpenGL::TextureTarget(2048, kCRTFrameIntermediateBufferHeight));
|
||||
// _openGL_state->filteredTexture = std::unique_ptr<OpenGL::TextureTarget>(new OpenGL::TextureTarget(2048, kCRTFrameIntermediateBufferHeight));
|
||||
}
|
||||
|
||||
// lock down any further work on the current frame
|
||||
_current_frame_mutex->lock();
|
||||
_output_mutex->lock();
|
||||
|
||||
if(!_current_frame && !only_if_dirty)
|
||||
// update uniforms
|
||||
push_size_uniforms(output_width, output_height);
|
||||
glUniform1f(_openGL_state->alphaUniform, 1.0f);
|
||||
|
||||
// submit latest frame data if required
|
||||
/* glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(current_frame->number_of_vertices * current_frame->size_per_vertex), current_frame->vertices, GL_DYNAMIC_DRAW);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, _openGL_state->textureName);
|
||||
if(_openGL_state->textureSize.width != _current_frame->size.width || _openGL_state->textureSize.height != _current_frame->size.height)
|
||||
{
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
GLenum format = formatForDepth(_current_frame->buffers[0].depth);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, (GLint)format, _current_frame->size.width, _current_frame->size.height, 0, format, GL_UNSIGNED_BYTE, _current_frame->buffers[0].data);
|
||||
_openGL_state->textureSize = _current_frame->size;
|
||||
|
||||
if(_openGL_state->textureSizeUniform >= 0)
|
||||
glUniform2f(_openGL_state->textureSizeUniform, _current_frame->size.width, _current_frame->size.height);
|
||||
}
|
||||
else
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, _current_frame->size.width, _current_frame->dirty_size.height, formatForDepth(_current_frame->buffers[0].depth), GL_UNSIGNED_BYTE, _current_frame->buffers[0].data);
|
||||
|
||||
if(_current_frame && (_current_frame != _last_drawn_frame || !only_if_dirty))
|
||||
{
|
||||
// update uniforms
|
||||
push_size_uniforms(output_width, output_height);
|
||||
glUniform1f(_openGL_state->alphaUniform, 1.0f);
|
||||
// draw
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, _openGL_state->defaultFramebuffer);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glDrawArrays(GL_TRIANGLES, 0, (GLsizei)_current_frame->number_of_vertices);*/
|
||||
|
||||
// submit new frame data if required
|
||||
if (_current_frame != _last_drawn_frame)
|
||||
{
|
||||
glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(_current_frame->number_of_vertices * _current_frame->size_per_vertex), _current_frame->vertices, GL_DYNAMIC_DRAW);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, _openGL_state->textureName);
|
||||
if(_openGL_state->textureSize.width != _current_frame->size.width || _openGL_state->textureSize.height != _current_frame->size.height)
|
||||
{
|
||||
GLenum format = formatForDepth(_current_frame->buffers[0].depth);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, (GLint)format, _current_frame->size.width, _current_frame->size.height, 0, format, GL_UNSIGNED_BYTE, _current_frame->buffers[0].data);
|
||||
_openGL_state->textureSize = _current_frame->size;
|
||||
|
||||
if(_openGL_state->textureSizeUniform >= 0)
|
||||
glUniform2f(_openGL_state->textureSizeUniform, _current_frame->size.width, _current_frame->size.height);
|
||||
}
|
||||
else
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, _current_frame->size.width, _current_frame->dirty_size.height, formatForDepth(_current_frame->buffers[0].depth), GL_UNSIGNED_BYTE, _current_frame->buffers[0].data);
|
||||
}
|
||||
|
||||
// draw
|
||||
_openGL_state->compositeTexture->bind_framebuffer();
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, _openGL_state->defaultFramebuffer);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glDrawArrays(GL_TRIANGLES, 0, (GLsizei)_current_frame->number_of_vertices);
|
||||
|
||||
|
||||
_last_drawn_frame = _current_frame;
|
||||
}
|
||||
|
||||
_current_frame_mutex->unlock();
|
||||
_output_mutex->unlock();
|
||||
}
|
||||
|
||||
void CRT::set_openGL_context_will_change(bool should_delete_resources)
|
||||
|
25
Outputs/CRT/CRTOpenGL.hpp
Normal file
25
Outputs/CRT/CRTOpenGL.hpp
Normal file
@ -0,0 +1,25 @@
|
||||
//
|
||||
// CRTOpenGL.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 13/02/2016.
|
||||
// Copyright © 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef CRTOpenGL_h
|
||||
#define CRTOpenGL_h
|
||||
|
||||
const size_t kCRTVertexOffsetOfPosition = 0;
|
||||
const size_t kCRTVertexOffsetOfTexCoord = 2;
|
||||
const size_t kCRTVertexOffsetOfTimestamp = 4;
|
||||
const size_t kCRTVertexOffsetOfLateral = 8;
|
||||
|
||||
const size_t kCRTSizeOfVertex = 10;
|
||||
|
||||
const int CRTInputBufferBuilderWidth = 2048;
|
||||
const int CRTInputBufferBuilderHeight = 1024;
|
||||
|
||||
const int CRTIntermediateBufferWidth = 2048;
|
||||
const int CRTIntermediateBufferHeight = 2048;
|
||||
|
||||
#endif /* CRTOpenGL_h */
|
Loading…
x
Reference in New Issue
Block a user