1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-06-18 13:29:41 +00:00

Made an attempt to switch to a triple-buffering scheme for CRT outputs, with an eye towards asynchronicity.

This commit is contained in:
Thomas Harte 2015-07-22 20:33:20 -04:00
parent b503e13380
commit 065050115f
4 changed files with 228 additions and 126 deletions

View File

@ -83,8 +83,12 @@ void Machine::output_state(OutputState state, uint8_t *pixel)
case OutputState::Blank: {
_crt->allocate_write_area(1);
_outputBuffer = _crt->get_write_target_for_buffer(0);
_outputBuffer[0] = _outputBuffer[1] = _outputBuffer[2] = 0;
_outputBuffer[3] = 0xff;
if(_outputBuffer)
{
_outputBuffer[0] = _outputBuffer[1] = _outputBuffer[2] = 0;
_outputBuffer[3] = 0xff;
}
_crt->output_level(_lastOutputStateDuration, atari2600DataType);
} break;
case OutputState::Sync: _crt->output_sync(_lastOutputStateDuration); break;
@ -100,7 +104,7 @@ void Machine::output_state(OutputState state, uint8_t *pixel)
}
}
if(state == OutputState::Pixel)
if(state == OutputState::Pixel && _outputBuffer)
{
_outputBuffer[(_lastOutputStateDuration * 4) + 0] = pixel[0];
_outputBuffer[(_lastOutputStateDuration * 4) + 1] = pixel[1];

View File

@ -10,34 +10,36 @@
#import "Atari2600.hpp"
class Atari2600CRTDelegate: public Outputs::CRT::CRTDelegate {
void crt_did_start_vertical_retrace_with_runs(Outputs::CRT::CRTRun *runs, int runs_to_draw)
void crt_did_end_frame(Outputs::CRT *crt, Outputs::CRTFrame *frame)
{
printf("===\n\n");
for(int run = 0; run < runs_to_draw; run++)
{
char character = ' ';
switch(runs[run].type)
{
case Outputs::CRT::CRTRun::Type::Sync: character = '<'; break;
case Outputs::CRT::CRTRun::Type::Level: character = '_'; break;
case Outputs::CRT::CRTRun::Type::Data: character = '-'; break;
case Outputs::CRT::CRTRun::Type::Blank: character = ' '; break;
}
// printf("===\n\n");
// for(int run = 0; run < runs_to_draw; run++)
// {
// char character = ' ';
// switch(runs[run].type)
// {
// case Outputs::CRTRun::Type::Sync: character = '<'; break;
// case Outputs::CRTRun::Type::Level: character = '_'; break;
// case Outputs::CRTRun::Type::Data: character = '-'; break;
// case Outputs::CRTRun::Type::Blank: character = ' '; break;
// }
//
// if(runs[run].start_point.dst_x > runs[run].end_point.dst_x)
// {
// printf("\n");
// }
//
// float length = fabsf(runs[run].end_point.dst_x - runs[run].start_point.dst_x);
// int iLength = (int)(length * 64.0);
// for(int c = 0; c < iLength; c++)
// {
// putc(character, stdout);
// }
//
// if (runs[run].type == Outputs::CRTRun::Type::Sync) printf("\n");
// }
float length = fabsf(runs[run].end_point.dst_x - runs[run].start_point.dst_x);
int iLength = (int)(length * 64.0);
for(int c = 0; c < iLength; c++)
{
putc(character, stdout);
}
if (runs[run].type == Outputs::CRT::CRTRun::Type::Sync) printf("\n");
}
crt->return_frame();
}
};

View File

@ -9,16 +9,14 @@
#include "CRT.hpp"
#include <stdarg.h>
static const int bufferWidth = 512;
static const int bufferHeight = 512;
using namespace Outputs;
CRT::CRT(int cycles_per_line, int height_of_display, int number_of_buffers, ...)
{
static const int syncCapacityLineChargeThreshold = 3;
static const int millisecondsHorizontalRetraceTime = 16;
static const int scanlinesVerticalRetraceTime = 26;
const int syncCapacityLineChargeThreshold = 3;
const int millisecondsHorizontalRetraceTime = 16;
const int scanlinesVerticalRetraceTime = 26;
// store fundamental display configuration properties
_height_of_display = height_of_display;
@ -38,24 +36,18 @@ CRT::CRT(int cycles_per_line, int height_of_display, int number_of_buffers, ...)
// generate buffers for signal storage as requested — format is
// number of buffers, size of buffer 1, size of buffer 2...
_numberOfBuffers = number_of_buffers;
_bufferSizes = new int[_numberOfBuffers];
_buffers = new uint8_t *[_numberOfBuffers];
va_list va;
va_start(va, number_of_buffers);
for(int c = 0; c < _numberOfBuffers; c++)
const int bufferWidth = 512;
const int bufferHeight = 512;
for(int frame = 0; frame < 3; frame++)
{
_bufferSizes[c] = va_arg(va, int);
_buffers[c] = new uint8_t[bufferHeight * bufferWidth * _bufferSizes[c]];
va_list va;
va_start(va, number_of_buffers);
_frames[frame] = new CRTFrame(bufferWidth, bufferHeight, number_of_buffers, va);
va_end(va);
}
va_end(va);
// reset pointer into output buffers
_write_allocation_pointer = 0;
// reset the run buffer pointer
_run_pointer = 0;
_frames_with_delegate = 0;
_frame_read_pointer = 0;
_current_frame = _frames[0];
// reset raster position
_rasterPosition.x = _rasterPosition.y = 0.0f;
@ -75,12 +67,10 @@ CRT::CRT(int cycles_per_line, int height_of_display, int number_of_buffers, ...)
CRT::~CRT()
{
delete[] _bufferSizes;
for(int c = 0; c < _numberOfBuffers; c++)
for(int frame = 0; frame < 3; frame++)
{
delete[] _buffers[c];
delete _frames[frame];
}
delete[] _buffers;
}
#pragma mark - Sync loop
@ -154,54 +144,50 @@ void CRT::advance_cycles(int number_of_cycles, bool hsync_requested, const bool
SyncEvent next_event = advance_to_next_sync_event(hsync_requested, vsync_charging, number_of_cycles, &next_run_length);
hsync_requested = false;
// get a run from the allocated list, allocating more if we're about to overrun
if(_run_pointer >= _all_runs.size())
if(_current_frame)
{
_all_runs.resize((_all_runs.size() * 2)+1);
}
CRTRun *nextRun = _current_frame->get_next_run();
CRTRun *nextRun = &_all_runs[_run_pointer];
_run_pointer++;
// set the type, initial raster position and type of this run
nextRun->type = type;
nextRun->start_point.dst_x = _rasterPosition.x;
nextRun->start_point.dst_y = _rasterPosition.y;
nextRun->data_type = data_type;
// set the type, initial raster position and type of this run
nextRun->type = type;
nextRun->start_point.dst_x = _rasterPosition.x;
nextRun->start_point.dst_y = _rasterPosition.y;
nextRun->data_type = data_type;
// if this is a data or level run then store a starting data position
if(type == CRTRun::Type::Data || type == CRTRun::Type::Level)
{
nextRun->start_point.src_x = (_current_frame->_write_target_pointer + buffer_offset) & (_current_frame->size.width - 1);
nextRun->start_point.src_y = (_current_frame->_write_target_pointer + buffer_offset) / _current_frame->size.width;
}
// if this is a data or level run then store a starting data position
if(type == CRTRun::Type::Data || type == CRTRun::Type::Level)
{
nextRun->start_point.src_x = (_write_target_pointer + buffer_offset) & (bufferWidth - 1);
nextRun->start_point.src_y = (_write_target_pointer + buffer_offset) / bufferWidth;
}
// advance the raster position as dictated by current sync status
if (_is_in_hsync)
_rasterPosition.x = std::max(0.0f, _rasterPosition.x - (float)number_of_cycles * _retraceSpeed.x);
else
_rasterPosition.x = std::min(1.0f, _rasterPosition.x + (float)number_of_cycles * _scanSpeed.x);
// advance the raster position as dictated by current sync status
if (_is_in_hsync)
_rasterPosition.x = std::max(0.0f, _rasterPosition.x - (float)number_of_cycles * _retraceSpeed.x);
else
_rasterPosition.x = std::min(1.0f, _rasterPosition.x + (float)number_of_cycles * _scanSpeed.x);
if (_vretrace_counter > 0)
_rasterPosition.y = std::max(0.0f, _rasterPosition.y - (float)number_of_cycles * _retraceSpeed.y);
else
_rasterPosition.y = std::min(1.0f, _rasterPosition.y + (float)number_of_cycles * _scanSpeed.y);
if (_vretrace_counter > 0)
_rasterPosition.y = std::max(0.0f, _rasterPosition.y - (float)number_of_cycles * _retraceSpeed.y);
else
_rasterPosition.y = std::min(1.0f, _rasterPosition.y + (float)number_of_cycles * _scanSpeed.y);
// store the final raster position
nextRun->end_point.dst_x = _rasterPosition.x;
nextRun->end_point.dst_y = _rasterPosition.y;
// store the final raster position
nextRun->end_point.dst_x = _rasterPosition.x;
nextRun->end_point.dst_y = _rasterPosition.y;
// if this is a data run then advance the buffer pointer
if(type == CRTRun::Type::Data)
{
buffer_offset += next_run_length;
}
// if this is a data run then advance the buffer pointer
if(type == CRTRun::Type::Data)
{
buffer_offset += next_run_length;
}
// if this is a data or level run then store the end point
if(type == CRTRun::Type::Data || type == CRTRun::Type::Level)
{
nextRun->end_point.src_x = (_write_target_pointer + buffer_offset) & (bufferWidth - 1);
nextRun->end_point.src_y = (_write_target_pointer + buffer_offset) / bufferWidth;
// if this is a data or level run then store the end point
if(type == CRTRun::Type::Data || type == CRTRun::Type::Level)
{
nextRun->end_point.src_x = (_current_frame->_write_target_pointer + buffer_offset) & (_current_frame->size.width - 1);
nextRun->end_point.src_y = (_current_frame->_write_target_pointer + buffer_offset) / _current_frame->size.width;
}
}
// decrement the number of cycles left to run for and increment the
@ -247,9 +233,19 @@ void CRT::advance_cycles(int number_of_cycles, bool hsync_requested, const bool
// end of vertical sync: tell the delegate that we finished vertical sync,
// releasing all runs back into the common pool
case SyncEvent::EndVSync:
if(_delegate != nullptr)
_delegate->crt_did_start_vertical_retrace_with_runs(&_all_runs[0], _run_pointer);
_run_pointer = 0;
if(_delegate && _current_frame)
{
_frames_with_delegate++;
_delegate->crt_did_end_frame(this, _current_frame);
}
if(_frames_with_delegate < kCRTNumberOfFrames)
{
_frame_read_pointer = (_frame_read_pointer + 1)%kCRTNumberOfFrames;
_current_frame = _frames[_frame_read_pointer];
}
else
_current_frame = nullptr;
break;
default: break;
@ -257,6 +253,11 @@ void CRT::advance_cycles(int number_of_cycles, bool hsync_requested, const bool
}
}
void CRT::return_frame()
{
_frames_with_delegate--;
}
#pragma mark - delegate
void CRT::set_delegate(CRTDelegate *delegate)
@ -298,18 +299,79 @@ void CRT::output_data(int number_of_cycles, const char *type)
void CRT::allocate_write_area(int required_length)
{
int xPos = _write_allocation_pointer & (bufferWidth - 1);
if (xPos + required_length > bufferWidth)
if(_current_frame) _current_frame->allocate_write_area(required_length);
}
uint8_t *CRT::get_write_target_for_buffer(int buffer)
{
if (!_current_frame) return nullptr;
return _current_frame->get_write_target_for_buffer(buffer);
}
#pragma mark - CRTFrame
CRTFrame::CRTFrame(int width, int height, int number_of_buffers, va_list buffer_sizes)
{
size.width = width;
size.height = height;
this->number_of_buffers = number_of_buffers;
buffers = new CRTBuffer[number_of_buffers];
for(int buffer = 0; buffer < number_of_buffers; buffer++)
{
_write_allocation_pointer &= ~(bufferWidth - 1);
_write_allocation_pointer = (_write_allocation_pointer + bufferWidth) & ((bufferHeight-1) * bufferWidth);
buffers[buffer].depth = va_arg(buffer_sizes, int);
buffers[buffer].data = new uint8_t[width * height * buffers[buffer].depth];
}
reset();
}
CRTFrame::~CRTFrame()
{
for(int buffer = 0; buffer < number_of_buffers; buffer++)
delete[] buffers[buffer].data;
delete buffers;
}
void CRTFrame::reset()
{
number_of_runs = 0;
_write_allocation_pointer = _write_target_pointer = 0;
}
void CRTFrame::complete()
{
runs = &_all_runs[0];
}
CRTRun *CRTFrame::get_next_run()
{
// get a run from the allocated list, allocating more if we're about to overrun
if(number_of_runs >= _all_runs.size())
{
_all_runs.resize((_all_runs.size() * 2)+1);
}
CRTRun *nextRun = &_all_runs[number_of_runs];
number_of_runs++;
return nextRun;
}
void CRTFrame::allocate_write_area(int required_length)
{
int xPos = _write_allocation_pointer & (size.width - 1);
if (xPos + required_length > size.width)
{
_write_allocation_pointer &= ~(size.width - 1);
_write_allocation_pointer = (_write_allocation_pointer + size.width) & ((size.height-1) * size.width);
}
_write_target_pointer = _write_allocation_pointer;
_write_allocation_pointer += required_length;
}
uint8_t *CRT::get_write_target_for_buffer(int buffer)
uint8_t *CRTFrame::get_write_target_for_buffer(int buffer)
{
return &_buffers[buffer][_write_target_pointer * _bufferSizes[buffer]];
return &buffers[buffer].data[_write_target_pointer * buffers[buffer].depth];
}

View File

@ -10,11 +10,64 @@
#define CRT_cpp
#include <stdint.h>
#include <stdarg.h>
#include <string>
#include <vector>
namespace Outputs {
struct CRTBuffer {
uint8_t *data;
int depth;
};
struct CRTRun {
struct Point {
float dst_x, dst_y;
int src_x, src_y;
} start_point, end_point;
enum Type {
Sync, Level, Data, Blank
} type;
const char *data_type;
};
class CRT;
struct CRTFrame {
struct {
int width, height;
} size;
int number_of_buffers;
CRTBuffer *buffers;
int number_of_runs;
CRTRun *runs;
CRTFrame(int width, int height, int number_of_buffers, va_list buffer_sizes);
~CRTFrame();
private:
std::vector<CRTRun> _all_runs;
void reset();
void complete();
CRTRun *get_next_run();
friend CRT;
void allocate_write_area(int required_length);
uint8_t *get_write_target_for_buffer(int buffer);
// a pointer to the section of content buffer currently being
// returned and to where the next section will begin
int _write_allocation_pointer, _write_target_pointer;
};
static const int kCRTNumberOfFrames = 3;
class CRT {
public:
CRT(int cycles_per_line, int height_of_display, int number_of_buffers, ...);
@ -25,24 +78,12 @@ class CRT {
void output_level(int number_of_cycles, const char *type);
void output_data(int number_of_cycles, const char *type);
struct CRTRun {
struct Point {
float dst_x, dst_y;
int src_x, src_y;
} start_point, end_point;
enum Type {
Sync, Level, Data, Blank
} type;
const char *data_type;
};
class CRTDelegate {
public:
virtual void crt_did_start_vertical_retrace_with_runs(CRTRun *runs, int runs_to_draw) = 0;
virtual void crt_did_end_frame(CRT *crt, CRTFrame *frame) = 0;
};
void set_delegate(CRTDelegate *delegate);
void return_frame();
void allocate_write_area(int required_length);
uint8_t *get_write_target_for_buffer(int buffer);
@ -55,24 +96,17 @@ class CRT {
// properties directly derived from there
int _hsync_error_window; // the permitted window around the expected sync position in which a sync pulse will be recognised; calculated once at init
// the run delegate, buffer and buffer pointer
CRTDelegate *_delegate;
std::vector<CRTRun> _all_runs;
int _run_pointer;
// the current scanning position
struct Vector {
float x, y;
} _rasterPosition, _scanSpeed, _retraceSpeed;
// the content buffers
uint8_t **_buffers;
int *_bufferSizes;
int _numberOfBuffers;
// a pointer to the section of content buffer currently being
// returned and to where the next section will begin
int _write_allocation_pointer, _write_target_pointer;
// the run delegate and the triple buffer
CRTFrame *_frames[kCRTNumberOfFrames];
CRTFrame *_current_frame;
int _frames_with_delegate;
int _frame_read_pointer;
CRTDelegate *_delegate;
// outer elements of sync separation
bool _is_receiving_sync; // true if the CRT is currently receiving sync (i.e. this is for edge triggering of horizontal sync)