1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-23 20:29:42 +00:00

The overall architecture of who has responsibility for what is now very askew but: the CRT now outputs a tightly packed short buffer, with the probable OpenGL destination in mind. So it's now all fixed arithmetic internally. CRTFrame is reduced to a plain C struct with the intention that the OpenGL view will take responsibility for it and stop doing the back-and-forth sprint on getting buffer data. The Atari 2600 now outputs explicit blanks rather than level blacks for its border, so that it's easier visually to debug the CRT in its form as far as it has currently progressed: to drawing lines where the cathode ray gun would run while outputting pixel. I note that I'm still not quite getting vertical sync right yet — I'm just accepting it anywhere in teh frame — but that should be an easy fix.

This commit is contained in:
Thomas Harte 2015-07-24 23:29:45 -04:00
parent 5ab47e600a
commit ecb2898bd5
6 changed files with 167 additions and 188 deletions

View File

@ -81,17 +81,7 @@ void Machine::output_state(OutputState state, uint8_t *pixel)
{
switch(_lastOutputState)
{
case OutputState::Blank: {
_crt->allocate_write_area(1);
_outputBuffer = _crt->get_write_target_for_buffer(0);
if(_outputBuffer)
{
_outputBuffer[0] = _outputBuffer[1] = _outputBuffer[2] = 0;
_outputBuffer[3] = 0xff;
}
_crt->output_level(_lastOutputStateDuration, atari2600DataType);
} break;
case OutputState::Blank: _crt->output_blank(_lastOutputStateDuration); break;
case OutputState::Sync: _crt->output_sync(_lastOutputStateDuration); break;
case OutputState::Pixel: _crt->output_data(_lastOutputStateDuration, atari2600DataType); break;
}

View File

@ -322,6 +322,7 @@
4B14145A1B58879D00E04248 /* CPU6502AllRAM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CPU6502AllRAM.hpp; sourceTree = "<group>"; };
4B14145F1B58885000E04248 /* WolfgangLorenzTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WolfgangLorenzTests.swift; sourceTree = "<group>"; };
4B1414611B58888700E04248 /* KlausDormannTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KlausDormannTests.swift; sourceTree = "<group>"; };
4B2632551B631A510082A461 /* CRTFrame.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = CRTFrame.h; path = ../../Outputs/CRTFrame.h; sourceTree = "<group>"; };
4B366DFA1B5C165A0026627B /* CRT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CRT.cpp; path = ../../Outputs/CRT.cpp; sourceTree = "<group>"; };
4B366DFB1B5C165A0026627B /* CRT.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRT.hpp; path = ../../Outputs/CRT.hpp; sourceTree = "<group>"; };
4B6D7F921B58822000787C9A /* Atari2600.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Atari2600.cpp; sourceTree = "<group>"; };
@ -663,6 +664,7 @@
children = (
4B366DFA1B5C165A0026627B /* CRT.cpp */,
4B366DFB1B5C165A0026627B /* CRT.hpp */,
4B2632551B631A510082A461 /* CRTFrame.h */,
);
name = Outputs;
sourceTree = "<group>";

View File

@ -19,8 +19,8 @@ const char *vertexShader =
"\n"
"void main (void)\n"
"{\n"
"colour = vec4(position, 0.0, 1.0);\n"
"gl_Position = vec4(position, 0.0, 1.0);\n"
"colour = vec4(1.0, 1.0, 1.0, 1.0);\n"
"gl_Position = vec4(position.x * 2.0 - 1.0, 1.0 - position.y * 2.0, 0.0, 1.0);\n"
"}\n";
const char *fragmentShader =
@ -35,12 +35,12 @@ const char *fragmentShader =
"}\n";
@interface CSAtari2600 (Callbacks)
- (void)crtDidEndFrame:(Outputs::CRTFrame *)frame;
- (void)crtDidEndFrame:(CRTFrame *)frame;
@end
struct Atari2600CRTDelegate: public Outputs::CRT::CRTDelegate {
CSAtari2600 *atari;
void crt_did_end_frame(Outputs::CRT *crt, Outputs::CRTFrame *frame) { [atari crtDidEndFrame:frame]; }
void crt_did_end_frame(Outputs::CRT *crt, CRTFrame *frame) { [atari crtDidEndFrame:frame]; }
};
@implementation CSAtari2600 {
@ -48,7 +48,7 @@ struct Atari2600CRTDelegate: public Outputs::CRT::CRTDelegate {
Atari2600CRTDelegate _crtDelegate;
dispatch_queue_t _serialDispatchQueue;
Outputs::CRTFrame *_queuedFrame;
CRTFrame *_queuedFrame;
GLuint _vertexShader, _fragmentShader;
GLuint _shaderProgram;
@ -56,7 +56,7 @@ struct Atari2600CRTDelegate: public Outputs::CRT::CRTDelegate {
GLuint _arrayBuffer, _vertexArray;
}
- (void)crtDidEndFrame:(Outputs::CRTFrame *)frame {
- (void)crtDidEndFrame:(CRTFrame *)frame {
dispatch_async(dispatch_get_main_queue(), ^{
if(_queuedFrame) {
@ -95,14 +95,12 @@ struct Atari2600CRTDelegate: public Outputs::CRT::CRTDelegate {
glGenBuffers(1, &_arrayBuffer);
glBindBuffer(GL_ARRAY_BUFFER, _arrayBuffer);
GLfloat vertices[] = {
0.0f, 0.0f,
0.5f, 0.0f,
0.5f, 0.6f,
0.0f, 0.5f
GLushort vertices[] = {
0, 0,
32767, 32767,
};
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW);
}
if(_queuedFrame)
@ -114,40 +112,12 @@ struct Atari2600CRTDelegate: public Outputs::CRT::CRTDelegate {
GLint position = glGetAttribLocation(_shaderProgram, "position");
glBufferData(GL_ARRAY_BUFFER, _queuedFrame->number_of_runs * sizeof(GLushort) * 8, _queuedFrame->runs, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(position);
glVertexAttribPointer(position, 2, GL_FLOAT, GL_FALSE, 0, (void *)0);
glVertexAttribPointer(position, 2, GL_UNSIGNED_SHORT, GL_TRUE, 4 * sizeof(GLushort), (void *)0);
glDrawArrays(GL_LINES, 0, 4);
/* printf("\n\n===\n\n");
int c = 0;
for(int run = 0; run < _queuedFrame->number_of_runs; run++)
{
char character = ' ';
switch(_queuedFrame->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(_queuedFrame->runs[run].start_point.dst_x < 1.0 / 224.0)
{
printf("\n[%0.2f]: ", _queuedFrame->runs[run].start_point.dst_y);
c++;
}
printf("(%0.2f): ", _queuedFrame->runs[run].start_point.dst_x);
float length = fabsf(_queuedFrame->runs[run].end_point.dst_x - _queuedFrame->runs[run].start_point.dst_x);
int iLength = (int)(length * 64.0);
for(int c = 0; c < iLength; c++)
{
putc(character, stdout);
}
}
printf("\n\n[%d]\n\n", c);*/
glDrawArrays(GL_LINES, 0, _queuedFrame->number_of_runs*2);
}
}

View File

@ -29,10 +29,10 @@ CRT::CRT(int cycles_per_line, int height_of_display, int number_of_buffers, ...)
_horizontal_retrace_time = (millisecondsHorizontalRetraceTime * cycles_per_line) >> 6;
_vertical_retrace_time = scanlinesVerticalRetraceTime * cycles_per_line;
_scanSpeed.x = 1.0f / (float)cycles_per_line;
_scanSpeed.y = 1.0f / (float)(height_of_display * cycles_per_line);
_retraceSpeed.x = 1.0f / (float)_horizontal_retrace_time;
_retraceSpeed.y = 1.0f / (float)_vertical_retrace_time;
_scanSpeed.x = UINT32_MAX / cycles_per_line;
_scanSpeed.y = UINT32_MAX / (height_of_display * cycles_per_line);
_retraceSpeed.x = UINT32_MAX / _horizontal_retrace_time;
_retraceSpeed.y = UINT32_MAX / _vertical_retrace_time;
// generate buffers for signal storage as requested — format is
// number of buffers, size of buffer 1, size of buffer 2...
@ -42,15 +42,15 @@ CRT::CRT(int cycles_per_line, int height_of_display, int number_of_buffers, ...)
{
va_list va;
va_start(va, number_of_buffers);
_frames[frame] = new CRTFrame(bufferWidth, bufferHeight, number_of_buffers, va);
_frame_builders[frame] = new CRTFrameBuilder(bufferWidth, bufferHeight, number_of_buffers, va);
va_end(va);
}
_frames_with_delegate = 0;
_frame_read_pointer = 0;
_current_frame = _frames[0];
_current_frame_builder = _frame_builders[0];
// reset raster position
_rasterPosition.x = _rasterPosition.y = 0.0f;
_rasterPosition.x = _rasterPosition.y = 0;
// reset flywheel sync
_expected_next_hsync = cycles_per_line;
@ -69,7 +69,7 @@ CRT::~CRT()
{
for(int frame = 0; frame < 3; frame++)
{
delete _frames[frame];
delete _frame_builders[frame];
}
}
@ -81,14 +81,14 @@ CRT::SyncEvent CRT::next_vertical_sync_event(bool vsync_is_charging, int cycles_
int proposedSyncTime = cycles_to_run_for;
// have we overrun the maximum permitted number of horizontal syncs for this frame?
if (!_vretrace_counter)
{
float raster_distance = _scanSpeed.y * (float)proposedSyncTime;
if(_rasterPosition.y < 1.02f && _rasterPosition.y + raster_distance >= 1.02f) {
proposedSyncTime = (int)(1.02f - _rasterPosition.y) / _scanSpeed.y;
proposedEvent = SyncEvent::StartVSync;
}
}
// if (!_vretrace_counter)
// {
// float raster_distance = _scanSpeed.y * (float)proposedSyncTime;
// if(_rasterPosition.y < 1.02f && _rasterPosition.y + raster_distance >= 1.02f) {
// proposedSyncTime = (int)(1.02f - _rasterPosition.y) / _scanSpeed.y;
// proposedEvent = SyncEvent::StartVSync;
// }
// }
// will an acceptable vertical sync be triggered?
if (vsync_is_charging && !_vretrace_counter) {
@ -141,10 +141,16 @@ CRT::SyncEvent CRT::next_horizontal_sync_event(bool hsync_is_requested, int cycl
return proposedEvent;
}
void CRT::advance_cycles(int number_of_cycles, bool hsync_requested, const bool vsync_charging, const CRTRun::Type type, const char *data_type)
void CRT::advance_cycles(int number_of_cycles, bool hsync_requested, const bool vsync_charging, const Type type, const char *data_type)
{
// this is safe to keep locally because it accumulates over this run of cycles only
int buffer_offset = 0;
const bool is_output_run = ((type == Type::Level) || (type == Type::Data));
uint16_t tex_x = 0;
uint16_t tex_y = 0;
if(is_output_run && _current_frame_builder) {
tex_x = _current_frame_builder->_write_x_position;
tex_y = _current_frame_builder->_write_y_position;
}
while(number_of_cycles) {
@ -157,50 +163,40 @@ void CRT::advance_cycles(int number_of_cycles, bool hsync_requested, const bool
// set it to false for the next run through this loop (if any)
int next_run_length = std::min(time_until_vertical_sync_event, time_until_horizontal_sync_event);
CRTRun *nextRun = (_current_frame && next_run_length) ? _current_frame->get_next_run() : nullptr;
uint16_t *next_run = (is_output_run && _current_frame_builder && next_run_length) ? _current_frame_builder->get_next_run() : nullptr;
if(nextRun)
if(next_run)
{
// 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;
next_run[0] = _rasterPosition.x >> 16;
next_run[1] = _rasterPosition.y >> 16;
next_run[2] = tex_x;
next_run[3] = tex_y;
}
// 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;
}
// advance the raster position as dictated by current sync status
if (_is_in_hsync)
_rasterPosition.x = (uint32_t)std::max((int64_t)0, (int64_t)_rasterPosition.x - number_of_cycles * (int64_t)_retraceSpeed.x);
else
_rasterPosition.x = (uint32_t)std::min((int64_t)UINT32_MAX, (int64_t)_rasterPosition.x + number_of_cycles * (int64_t)_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 = (uint32_t)std::max((int64_t)0, (int64_t)_rasterPosition.y - number_of_cycles * (int64_t)_retraceSpeed.y);
else
_rasterPosition.y = (uint32_t)std::min((int64_t)UINT32_MAX, (int64_t)_rasterPosition.y + number_of_cycles * (int64_t)_scanSpeed.y);
if(next_run)
{
// store the final raster position
nextRun->end_point.dst_x = _rasterPosition.x;
nextRun->end_point.dst_y = _rasterPosition.y;
next_run[4] = _rasterPosition.x >> 16;
next_run[5] = _rasterPosition.y >> 16;
// if this is a data run then advance the buffer pointer
if(type == CRTRun::Type::Data)
{
buffer_offset += next_run_length;
}
if(type == Type::Data) tex_x += 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 = (_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;
}
next_run[6] = tex_x;
next_run[7] = tex_y;
}
// decrement the number of cycles left to run for and increment the
@ -256,21 +252,21 @@ 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 && _current_frame)
if(_delegate && _current_frame_builder)
{
_current_frame->complete();
_current_frame_builder->complete();
_frames_with_delegate++;
_delegate->crt_did_end_frame(this, _current_frame);
_delegate->crt_did_end_frame(this, &_current_frame_builder->frame);
}
if(_frames_with_delegate < kCRTNumberOfFrames)
{
_frame_read_pointer = (_frame_read_pointer + 1)%kCRTNumberOfFrames;
_current_frame = _frames[_frame_read_pointer];
_current_frame->reset();
_current_frame_builder = _frame_builders[_frame_read_pointer];
_current_frame_builder->reset();
}
else
_current_frame = nullptr;
_current_frame_builder = nullptr;
break;
default: break;
@ -300,104 +296,106 @@ void CRT::output_sync(int number_of_cycles)
{
bool _hsync_requested = !_is_receiving_sync; // ensure this really is edge triggered; someone calling output_sync twice in succession shouldn't trigger it twice
_is_receiving_sync = true;
advance_cycles(number_of_cycles, _hsync_requested, true, CRTRun::Type::Sync, nullptr);
advance_cycles(number_of_cycles, _hsync_requested, true, Type::Sync, nullptr);
}
void CRT::output_blank(int number_of_cycles)
{
_is_receiving_sync = false;
advance_cycles(number_of_cycles, false, false, CRTRun::Type::Blank, nullptr);
advance_cycles(number_of_cycles, false, false, Type::Blank, nullptr);
}
void CRT::output_level(int number_of_cycles, const char *type)
{
_is_receiving_sync = false;
advance_cycles(number_of_cycles, false, false, CRTRun::Type::Level, type);
advance_cycles(number_of_cycles, false, false, Type::Level, type);
}
void CRT::output_data(int number_of_cycles, const char *type)
{
_is_receiving_sync = false;
advance_cycles(number_of_cycles, false, false, CRTRun::Type::Data, type);
advance_cycles(number_of_cycles, false, false, Type::Data, type);
}
#pragma mark - Buffer supply
void CRT::allocate_write_area(int required_length)
{
if(_current_frame) _current_frame->allocate_write_area(required_length);
if(_current_frame_builder) _current_frame_builder->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);
if (!_current_frame_builder) return nullptr;
return _current_frame_builder->get_write_target_for_buffer(buffer);
}
#pragma mark - CRTFrame
CRTFrame::CRTFrame(int width, int height, int number_of_buffers, va_list buffer_sizes)
CRTFrameBuilder::CRTFrameBuilder(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];
frame.size.width = width;
frame.size.height = height;
frame.number_of_buffers = number_of_buffers;
frame.buffers = new CRTBuffer[number_of_buffers];
for(int buffer = 0; buffer < number_of_buffers; buffer++)
{
buffers[buffer].depth = va_arg(buffer_sizes, int);
buffers[buffer].data = new uint8_t[width * height * buffers[buffer].depth];
frame.buffers[buffer].depth = va_arg(buffer_sizes, int);
frame.buffers[buffer].data = new uint8_t[width * height * frame.buffers[buffer].depth];
}
reset();
}
CRTFrame::~CRTFrame()
CRTFrameBuilder::~CRTFrameBuilder()
{
for(int buffer = 0; buffer < number_of_buffers; buffer++)
delete[] buffers[buffer].data;
delete buffers;
for(int buffer = 0; buffer < frame.number_of_buffers; buffer++)
delete[] frame.buffers[buffer].data;
delete frame.buffers;
}
void CRTFrame::reset()
void CRTFrameBuilder::reset()
{
number_of_runs = 0;
_write_allocation_pointer = _write_target_pointer = 0;
frame.number_of_runs = 0;
_write_x_position = _write_y_position = 0;
frame.dirty_size.width = frame.dirty_size.height = 0;
}
void CRTFrame::complete()
void CRTFrameBuilder::complete()
{
runs = &_all_runs[0];
frame.runs = &_all_runs[0];
}
CRTRun *CRTFrame::get_next_run()
uint16_t *CRTFrameBuilder::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())
if(frame.number_of_runs * 8 >= _all_runs.size())
{
_all_runs.resize((_all_runs.size() * 2)+1);
_all_runs.resize(_all_runs.size() + 4096);
}
CRTRun *nextRun = &_all_runs[number_of_runs];
number_of_runs++;
uint16_t *next_run = &_all_runs[frame.number_of_runs * 8];
frame.number_of_runs++;
return nextRun;
return next_run;
}
void CRTFrame::allocate_write_area(int required_length)
void CRTFrameBuilder::allocate_write_area(int required_length)
{
int xPos = _write_allocation_pointer & (size.width - 1);
if (xPos + required_length > size.width)
if (_write_x_position + required_length > frame.size.width)
{
_write_allocation_pointer &= ~(size.width - 1);
_write_allocation_pointer = (_write_allocation_pointer + size.width) & ((size.height-1) * size.width);
_write_x_position = 0;
_write_y_position++;
frame.dirty_size.height++;
}
_write_target_pointer = _write_allocation_pointer;
_write_allocation_pointer += required_length;
_write_target_pointer = (_write_y_position * frame.size.width) + _write_x_position;
_write_x_position += required_length;
frame.dirty_size.width = std::max(frame.dirty_size.width, _write_x_position);
}
uint8_t *CRTFrame::get_write_target_for_buffer(int buffer)
uint8_t *CRTFrameBuilder::get_write_target_for_buffer(int buffer)
{
return &buffers[buffer].data[_write_target_pointer * buffers[buffer].depth];
return &frame.buffers[buffer].data[_write_target_pointer * frame.buffers[buffer].depth];
}

View File

@ -14,48 +14,24 @@
#include <string>
#include <vector>
#include "CRTFrame.h"
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;
struct CRTFrameBuilder {
CRTFrame frame;
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();
CRTFrameBuilder(int width, int height, int number_of_buffers, va_list buffer_sizes);
~CRTFrameBuilder();
private:
std::vector<CRTRun> _all_runs;
std::vector<uint16_t> _all_runs;
void reset();
void complete();
CRTRun *get_next_run();
uint16_t *get_next_run();
friend CRT;
void allocate_write_area(int required_length);
@ -63,7 +39,8 @@ struct CRTFrame {
// 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;
int _write_x_position, _write_y_position;
int _write_target_pointer;
};
static const int kCRTNumberOfFrames = 3;
@ -98,12 +75,12 @@ class CRT {
// the current scanning position
struct Vector {
float x, y;
uint32_t x, y;
} _rasterPosition, _scanSpeed, _retraceSpeed;
// the run delegate and the triple buffer
CRTFrame *_frames[kCRTNumberOfFrames];
CRTFrame *_current_frame;
CRTFrameBuilder *_frame_builders[kCRTNumberOfFrames];
CRTFrameBuilder *_current_frame_builder;
int _frames_with_delegate;
int _frame_read_pointer;
CRTDelegate *_delegate;
@ -123,7 +100,10 @@ class CRT {
bool _is_in_hsync; // true for the duration of a horizontal sync — used to determine beam running direction and speed
// the outer entry point for dispatching output_sync, output_blank, output_level and output_data
void advance_cycles(int number_of_cycles, bool hsync_requested, bool vsync_charging, CRTRun::Type type, const char *data_type);
enum Type {
Sync, Level, Data, Blank
} type;
void advance_cycles(int number_of_cycles, bool hsync_requested, bool vsync_charging, Type type, const char *data_type);
// the inner entry point that determines whether and when the next sync event will occur within
// the current output window

39
Outputs/CRTFrame.h Normal file
View File

@ -0,0 +1,39 @@
//
// 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
struct CRTBuffer {
uint8_t *data;
int depth;
};
typedef struct {
int width, height;
} CRTSize;
struct CRTFrame {
CRTSize size, dirty_size;
int number_of_buffers;
CRTBuffer *buffers;
int number_of_runs;
uint16_t *runs;
};
#ifdef __cplusplus
}
#endif
#endif /* CRTFrame_h */