mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-02 16:04:59 +00:00
Merge pull request #832 from TomHarte/MetalScanTarget
Adds a Metal ScanTarget, for macOS.
This commit is contained in:
commit
22c9734874
@ -445,20 +445,20 @@ template <class BusHandler> class MOS6560 {
|
|||||||
// register state
|
// register state
|
||||||
struct {
|
struct {
|
||||||
bool interlaced = false, tall_characters = false;
|
bool interlaced = false, tall_characters = false;
|
||||||
uint8_t first_column_location, first_row_location;
|
uint8_t first_column_location = 0, first_row_location = 0;
|
||||||
uint8_t number_of_columns, number_of_rows;
|
uint8_t number_of_columns = 0, number_of_rows = 0;
|
||||||
uint16_t character_cell_start_address, video_matrix_start_address;
|
uint16_t character_cell_start_address = 0, video_matrix_start_address = 0;
|
||||||
uint16_t backgroundColour, borderColour, auxiliary_colour;
|
uint16_t backgroundColour = 0, borderColour = 0, auxiliary_colour = 0;
|
||||||
bool invertedCells = false;
|
bool invertedCells = false;
|
||||||
|
|
||||||
uint8_t direct_values[16];
|
uint8_t direct_values[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||||
} registers_;
|
} registers_;
|
||||||
|
|
||||||
// output state
|
// output state
|
||||||
enum State {
|
enum State {
|
||||||
Sync, ColourBurst, Border, Pixels
|
Sync, ColourBurst, Border, Pixels
|
||||||
} this_state_, output_state_;
|
} this_state_ = State::Sync, output_state_ = State::Sync;
|
||||||
int cycles_in_state_;
|
int cycles_in_state_ = 0;
|
||||||
|
|
||||||
// counters that cover an entire field
|
// counters that cover an entire field
|
||||||
int horizontal_counter_ = 0, vertical_counter_ = 0;
|
int horizontal_counter_ = 0, vertical_counter_ = 0;
|
||||||
@ -487,23 +487,23 @@ template <class BusHandler> class MOS6560 {
|
|||||||
|
|
||||||
// latches dictating start and length of drawing
|
// latches dictating start and length of drawing
|
||||||
bool vertical_drawing_latch_ = false, horizontal_drawing_latch_ = false;
|
bool vertical_drawing_latch_ = false, horizontal_drawing_latch_ = false;
|
||||||
int rows_this_field_, columns_this_line_;
|
int rows_this_field_ = 0, columns_this_line_ = 0;
|
||||||
|
|
||||||
// current drawing position counter
|
// current drawing position counter
|
||||||
int pixel_line_cycle_, column_counter_;
|
int pixel_line_cycle_ = 0, column_counter_ = 0;
|
||||||
int current_row_;
|
int current_row_ = 0;
|
||||||
uint16_t current_character_row_;
|
uint16_t current_character_row_ = 0;
|
||||||
uint16_t video_matrix_address_counter_, base_video_matrix_address_counter_;
|
uint16_t video_matrix_address_counter_ = 0, base_video_matrix_address_counter_ = 0;
|
||||||
|
|
||||||
// data latched from the bus
|
// data latched from the bus
|
||||||
uint8_t character_code_, character_colour_, character_value_;
|
uint8_t character_code_ = 0, character_colour_ = 0, character_value_ = 0;
|
||||||
|
|
||||||
bool is_odd_frame_ = false, is_odd_line_ = false;
|
bool is_odd_frame_ = false, is_odd_line_ = false;
|
||||||
|
|
||||||
// lookup table from 6560 colour index to appropriate PAL/NTSC value
|
// lookup table from 6560 colour index to appropriate PAL/NTSC value
|
||||||
uint16_t colours_[16];
|
uint16_t colours_[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||||
|
|
||||||
uint16_t *pixel_pointer;
|
uint16_t *pixel_pointer = nullptr;
|
||||||
void output_border(int number_of_cycles) {
|
void output_border(int number_of_cycles) {
|
||||||
uint16_t *colour_pointer = reinterpret_cast<uint16_t *>(crt_.begin_data(1));
|
uint16_t *colour_pointer = reinterpret_cast<uint16_t *>(crt_.begin_data(1));
|
||||||
if(colour_pointer) *colour_pointer = registers_.borderColour;
|
if(colour_pointer) *colour_pointer = registers_.borderColour;
|
||||||
@ -511,13 +511,13 @@ template <class BusHandler> class MOS6560 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
int cycles_per_line;
|
int cycles_per_line = 0;
|
||||||
int line_counter_increment_offset;
|
int line_counter_increment_offset = 0;
|
||||||
int final_line_increment_position;
|
int final_line_increment_position = 0;
|
||||||
int lines_per_progressive_field;
|
int lines_per_progressive_field = 0;
|
||||||
bool supports_interlacing;
|
bool supports_interlacing = 0;
|
||||||
} timing_;
|
} timing_;
|
||||||
OutputMode output_mode_;
|
OutputMode output_mode_ = OutputMode::NTSC;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -190,7 +190,9 @@ void TMS9918::run_for(const HalfCycles cycles) {
|
|||||||
int read_cycles_pool = int_cycles;
|
int read_cycles_pool = int_cycles;
|
||||||
|
|
||||||
while(write_cycles_pool || read_cycles_pool) {
|
while(write_cycles_pool || read_cycles_pool) {
|
||||||
|
#ifndef NDEBUG
|
||||||
LineBufferPointer backup = read_pointer_;
|
LineBufferPointer backup = read_pointer_;
|
||||||
|
#endif
|
||||||
|
|
||||||
if(write_cycles_pool) {
|
if(write_cycles_pool) {
|
||||||
// Determine how much writing to do.
|
// Determine how much writing to do.
|
||||||
@ -329,8 +331,10 @@ void TMS9918::run_for(const HalfCycles cycles) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
assert(backup.row == read_pointer_.row && backup.column == read_pointer_.column);
|
assert(backup.row == read_pointer_.row && backup.column == read_pointer_.column);
|
||||||
backup = write_pointer_;
|
backup = write_pointer_;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
if(read_cycles_pool) {
|
if(read_cycles_pool) {
|
||||||
|
@ -46,6 +46,8 @@ class Keyboard {
|
|||||||
/// Constructs a Keyboard that declares itself to observe only members of @c observed_keys.
|
/// Constructs a Keyboard that declares itself to observe only members of @c observed_keys.
|
||||||
Keyboard(const std::set<Key> &observed_keys, const std::set<Key> &essential_modifiers);
|
Keyboard(const std::set<Key> &observed_keys, const std::set<Key> &essential_modifiers);
|
||||||
|
|
||||||
|
virtual ~Keyboard() {}
|
||||||
|
|
||||||
// Host interface.
|
// Host interface.
|
||||||
|
|
||||||
/// @returns @c true if the key press affects the machine; @c false otherwise.
|
/// @returns @c true if the key press affects the machine; @c false otherwise.
|
||||||
|
@ -541,7 +541,17 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
|
|||||||
const int colour_burst_start = std::max(first_sync_column + sync_length + 1, column_);
|
const int colour_burst_start = std::max(first_sync_column + sync_length + 1, column_);
|
||||||
const int colour_burst_end = std::min(first_sync_column + sync_length + 4, ending_column);
|
const int colour_burst_end = std::min(first_sync_column + sync_length + 4, ending_column);
|
||||||
if(colour_burst_end > colour_burst_start) {
|
if(colour_burst_end > colour_burst_start) {
|
||||||
crt_.output_colour_burst((colour_burst_end - colour_burst_start) * 14, 0);
|
// UGLY HACK AHOY!
|
||||||
|
// The OpenGL scan target introduces a phase error of 1/8th of a wave. The Metal one does not.
|
||||||
|
// Supply the real phase value if this is an Apple build.
|
||||||
|
// TODO: eliminate UGLY HACK.
|
||||||
|
#ifdef __APPLE__
|
||||||
|
constexpr int phase = 224;
|
||||||
|
#else
|
||||||
|
constexpr int phase = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
crt_.output_colour_burst((colour_burst_end - colour_burst_start) * 14, phase);
|
||||||
}
|
}
|
||||||
|
|
||||||
second_blank_start = std::max(first_sync_column + sync_length + 3, column_);
|
second_blank_start = std::max(first_sync_column + sync_length + 3, column_);
|
||||||
|
@ -29,7 +29,17 @@ Video::Video(DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulato
|
|||||||
crt_(704, 1, 370, 6, Outputs::Display::InputDataType::Luminance1) {
|
crt_(704, 1, 370, 6, Outputs::Display::InputDataType::Luminance1) {
|
||||||
|
|
||||||
crt_.set_display_type(Outputs::Display::DisplayType::RGB);
|
crt_.set_display_type(Outputs::Display::DisplayType::RGB);
|
||||||
|
|
||||||
|
// UGLY HACK. UGLY, UGLY HACK. UGLY!
|
||||||
|
// The OpenGL scan target fails properly to place visible areas which are not 4:3.
|
||||||
|
// The [newer] Metal scan target has no such issue. So assume that Apple => Metal,
|
||||||
|
// and set a visible area to work around the OpenGL issue if required.
|
||||||
|
// TODO: eliminate UGLY HACK.
|
||||||
|
#ifdef __APPLE__
|
||||||
|
crt_.set_visible_area(Outputs::Display::Rect(0.08f, 10.0f / 368.0f, 0.82f, 344.0f / 368.0f));
|
||||||
|
#else
|
||||||
crt_.set_visible_area(Outputs::Display::Rect(0.08f, -0.025f, 0.82f, 0.82f));
|
crt_.set_visible_area(Outputs::Display::Rect(0.08f, -0.025f, 0.82f, 0.82f));
|
||||||
|
#endif
|
||||||
crt_.set_aspect_ratio(1.73f); // The Mac uses a non-standard scanning area.
|
crt_.set_aspect_ratio(1.73f); // The Mac uses a non-standard scanning area.
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,10 +115,13 @@ void Video::run_for(HalfCycles duration) {
|
|||||||
|
|
||||||
pixel_buffer_ += 16;
|
pixel_buffer_ += 16;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
video_address_ += size_t(final_pixel_word - first_word);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(final_pixel_word == 32) {
|
if(final_pixel_word == 32) {
|
||||||
crt_.output_data(512);
|
crt_.output_data(512);
|
||||||
|
pixel_buffer_ = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,7 +98,6 @@
|
|||||||
4B055AED1FAE9BA20060FFFF /* Z80Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334831F5DA0360097E338 /* Z80Storage.cpp */; };
|
4B055AED1FAE9BA20060FFFF /* Z80Storage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334831F5DA0360097E338 /* Z80Storage.cpp */; };
|
||||||
4B055AEE1FAE9BBF0060FFFF /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B86E2591F8C628F006FAA45 /* Keyboard.cpp */; };
|
4B055AEE1FAE9BBF0060FFFF /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B86E2591F8C628F006FAA45 /* Keyboard.cpp */; };
|
||||||
4B055AEF1FAE9BF00060FFFF /* Typer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2B3A471F9B8FA70062DABF /* Typer.cpp */; };
|
4B055AEF1FAE9BF00060FFFF /* Typer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2B3A471F9B8FA70062DABF /* Typer.cpp */; };
|
||||||
4B055AF11FAE9C160060FFFF /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */; };
|
|
||||||
4B055AF21FAE9C1C0060FFFF /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B055AF01FAE9C080060FFFF /* OpenGL.framework */; };
|
4B055AF21FAE9C1C0060FFFF /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B055AF01FAE9C080060FFFF /* OpenGL.framework */; };
|
||||||
4B08A2751EE35D56008B7065 /* Z80InterruptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B08A2741EE35D56008B7065 /* Z80InterruptTests.swift */; };
|
4B08A2751EE35D56008B7065 /* Z80InterruptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B08A2741EE35D56008B7065 /* Z80InterruptTests.swift */; };
|
||||||
4B08A2781EE39306008B7065 /* TestMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B08A2771EE39306008B7065 /* TestMachine.mm */; };
|
4B08A2781EE39306008B7065 /* TestMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B08A2771EE39306008B7065 /* TestMachine.mm */; };
|
||||||
@ -155,6 +154,9 @@
|
|||||||
4B1D08061E0F7A1100763741 /* TimeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B1D08051E0F7A1100763741 /* TimeTests.mm */; };
|
4B1D08061E0F7A1100763741 /* TimeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B1D08051E0F7A1100763741 /* TimeTests.mm */; };
|
||||||
4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E85801D176468001EF87D /* 6532Tests.swift */; };
|
4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B1E85801D176468001EF87D /* 6532Tests.swift */; };
|
||||||
4B1EDB451E39A0AC009D6819 /* chip.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B1EDB431E39A0AC009D6819 /* chip.png */; };
|
4B1EDB451E39A0AC009D6819 /* chip.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B1EDB431E39A0AC009D6819 /* chip.png */; };
|
||||||
|
4B228CD524D773B40077EF25 /* CSScanTarget.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B228CD424D773B30077EF25 /* CSScanTarget.mm */; };
|
||||||
|
4B228CD924DA12C60077EF25 /* CSScanTargetView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B228CD824DA12C60077EF25 /* CSScanTargetView.m */; };
|
||||||
|
4B228CDB24DA41890077EF25 /* ScanTarget.metal in Sources */ = {isa = PBXBuildFile; fileRef = 4B228CDA24DA41880077EF25 /* ScanTarget.metal */; };
|
||||||
4B2530F4244E6774007980BF /* fm.json in Resources */ = {isa = PBXBuildFile; fileRef = 4B2530F3244E6773007980BF /* fm.json */; };
|
4B2530F4244E6774007980BF /* fm.json in Resources */ = {isa = PBXBuildFile; fileRef = 4B2530F3244E6773007980BF /* fm.json */; };
|
||||||
4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B2A332B1DB86821002876E3 /* OricOptions.xib */; };
|
4B2A332D1DB86821002876E3 /* OricOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B2A332B1DB86821002876E3 /* OricOptions.xib */; };
|
||||||
4B2A539F1D117D36003C6002 /* CSAudioQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A53911D117D36003C6002 /* CSAudioQueue.m */; };
|
4B2A539F1D117D36003C6002 /* CSAudioQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A53911D117D36003C6002 /* CSAudioQueue.m */; };
|
||||||
@ -215,7 +217,6 @@
|
|||||||
4B54C0C51F8D91D90050900F /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0C41F8D91D90050900F /* Keyboard.cpp */; };
|
4B54C0C51F8D91D90050900F /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0C41F8D91D90050900F /* Keyboard.cpp */; };
|
||||||
4B54C0C81F8D91E50050900F /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0C61F8D91E50050900F /* Keyboard.cpp */; };
|
4B54C0C81F8D91E50050900F /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0C61F8D91E50050900F /* Keyboard.cpp */; };
|
||||||
4B54C0CB1F8D92590050900F /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0CA1F8D92580050900F /* Keyboard.cpp */; };
|
4B54C0CB1F8D92590050900F /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B54C0CA1F8D92580050900F /* Keyboard.cpp */; };
|
||||||
4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */; };
|
|
||||||
4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */; };
|
4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */; };
|
||||||
4B55DD8320DF06680043F2E5 /* MachinePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55DD8020DF06680043F2E5 /* MachinePicker.swift */; };
|
4B55DD8320DF06680043F2E5 /* MachinePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B55DD8020DF06680043F2E5 /* MachinePicker.swift */; };
|
||||||
4B55DD8420DF06680043F2E5 /* MachinePicker.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B55DD8120DF06680043F2E5 /* MachinePicker.xib */; };
|
4B55DD8420DF06680043F2E5 /* MachinePicker.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B55DD8120DF06680043F2E5 /* MachinePicker.xib */; };
|
||||||
@ -370,7 +371,6 @@
|
|||||||
4B778F6123A5F3560000D260 /* Disk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8944FC201967B4007DE474 /* Disk.cpp */; };
|
4B778F6123A5F3560000D260 /* Disk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8944FC201967B4007DE474 /* Disk.cpp */; };
|
||||||
4B778F6223A5F35F0000D260 /* File.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B894500201967B4007DE474 /* File.cpp */; };
|
4B778F6223A5F35F0000D260 /* File.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B894500201967B4007DE474 /* File.cpp */; };
|
||||||
4B778F6323A5F3630000D260 /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B894501201967B4007DE474 /* Tape.cpp */; };
|
4B778F6323A5F3630000D260 /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B894501201967B4007DE474 /* Tape.cpp */; };
|
||||||
4B778F6423A5F3730000D260 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */; };
|
|
||||||
4B7913CC1DFCD80E00175A82 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7913CA1DFCD80E00175A82 /* Video.cpp */; };
|
4B7913CC1DFCD80E00175A82 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7913CA1DFCD80E00175A82 /* Video.cpp */; };
|
||||||
4B79A5011FC913C900EEDAD5 /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B79A4FF1FC913C900EEDAD5 /* MSX.cpp */; };
|
4B79A5011FC913C900EEDAD5 /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B79A4FF1FC913C900EEDAD5 /* MSX.cpp */; };
|
||||||
4B79E4441E3AF38600141F11 /* cassette.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4411E3AF38600141F11 /* cassette.png */; };
|
4B79E4441E3AF38600141F11 /* cassette.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4411E3AF38600141F11 /* cassette.png */; };
|
||||||
@ -768,6 +768,10 @@
|
|||||||
4BB73EAC1B587A5100552FC2 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BB73EAA1B587A5100552FC2 /* MainMenu.xib */; };
|
4BB73EAC1B587A5100552FC2 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BB73EAA1B587A5100552FC2 /* MainMenu.xib */; };
|
||||||
4BB73EB71B587A5100552FC2 /* AllSuiteATests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EB61B587A5100552FC2 /* AllSuiteATests.swift */; };
|
4BB73EB71B587A5100552FC2 /* AllSuiteATests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EB61B587A5100552FC2 /* AllSuiteATests.swift */; };
|
||||||
4BB73EC21B587A5100552FC2 /* Clock_SignalUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EC11B587A5100552FC2 /* Clock_SignalUITests.swift */; };
|
4BB73EC21B587A5100552FC2 /* Clock_SignalUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BB73EC11B587A5100552FC2 /* Clock_SignalUITests.swift */; };
|
||||||
|
4BB8616E24E22DC500A00E03 /* BufferingScanTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB8616D24E22DC500A00E03 /* BufferingScanTarget.cpp */; };
|
||||||
|
4BB8616F24E22DC500A00E03 /* BufferingScanTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BB8616D24E22DC500A00E03 /* BufferingScanTarget.cpp */; };
|
||||||
|
4BB8617124E22F5700A00E03 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BB8617024E22F4900A00E03 /* Accelerate.framework */; };
|
||||||
|
4BB8617224E22F5A00A00E03 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BB8617024E22F4900A00E03 /* Accelerate.framework */; };
|
||||||
4BBB70A4202011C2002FE009 /* MultiMediaTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A3202011C2002FE009 /* MultiMediaTarget.cpp */; };
|
4BBB70A4202011C2002FE009 /* MultiMediaTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A3202011C2002FE009 /* MultiMediaTarget.cpp */; };
|
||||||
4BBB70A5202011C2002FE009 /* MultiMediaTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A3202011C2002FE009 /* MultiMediaTarget.cpp */; };
|
4BBB70A5202011C2002FE009 /* MultiMediaTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A3202011C2002FE009 /* MultiMediaTarget.cpp */; };
|
||||||
4BBB70A8202014E2002FE009 /* MultiProducer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A6202014E2002FE009 /* MultiProducer.cpp */; };
|
4BBB70A8202014E2002FE009 /* MultiProducer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BBB70A6202014E2002FE009 /* MultiProducer.cpp */; };
|
||||||
@ -786,15 +790,12 @@
|
|||||||
4BC1317B2346DF2B00E4FF3D /* MSA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC131782346DF2B00E4FF3D /* MSA.cpp */; };
|
4BC1317B2346DF2B00E4FF3D /* MSA.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC131782346DF2B00E4FF3D /* MSA.cpp */; };
|
||||||
4BC23A2C2467600F001A6030 /* OPLL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC23A2B2467600E001A6030 /* OPLL.cpp */; };
|
4BC23A2C2467600F001A6030 /* OPLL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC23A2B2467600E001A6030 /* OPLL.cpp */; };
|
||||||
4BC23A2D2467600F001A6030 /* OPLL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC23A2B2467600E001A6030 /* OPLL.cpp */; };
|
4BC23A2D2467600F001A6030 /* OPLL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC23A2B2467600E001A6030 /* OPLL.cpp */; };
|
||||||
4BC3C67C24C9230F0027BF76 /* BufferingScanTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3C67A24C9230F0027BF76 /* BufferingScanTarget.cpp */; };
|
|
||||||
4BC3C67D24C9230F0027BF76 /* BufferingScanTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3C67A24C9230F0027BF76 /* BufferingScanTarget.cpp */; };
|
|
||||||
4BC57CD92436A62900FBC404 /* State.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC57CD82436A62900FBC404 /* State.cpp */; };
|
4BC57CD92436A62900FBC404 /* State.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC57CD82436A62900FBC404 /* State.cpp */; };
|
||||||
4BC57CDA2436A62900FBC404 /* State.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC57CD82436A62900FBC404 /* State.cpp */; };
|
4BC57CDA2436A62900FBC404 /* State.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC57CD82436A62900FBC404 /* State.cpp */; };
|
||||||
4BC5C3E022C994CD00795658 /* 68000MoveTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BC5C3DF22C994CC00795658 /* 68000MoveTests.mm */; };
|
4BC5C3E022C994CD00795658 /* 68000MoveTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BC5C3DF22C994CC00795658 /* 68000MoveTests.mm */; };
|
||||||
4BC5FC3020CDDDEF00410AA0 /* AppleIIOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BC5FC2E20CDDDEE00410AA0 /* AppleIIOptions.xib */; };
|
4BC5FC3020CDDDEF00410AA0 /* AppleIIOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BC5FC2E20CDDDEE00410AA0 /* AppleIIOptions.xib */; };
|
||||||
4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC751B11D157E61006C31D9 /* 6522Tests.swift */; };
|
4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC751B11D157E61006C31D9 /* 6522Tests.swift */; };
|
||||||
4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */; };
|
4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */; };
|
||||||
4BC76E6B1C98F43700E6EF73 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */; };
|
|
||||||
4BC890D3230F86020025A55A /* DirectAccessDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC890D1230F86020025A55A /* DirectAccessDevice.cpp */; };
|
4BC890D3230F86020025A55A /* DirectAccessDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC890D1230F86020025A55A /* DirectAccessDevice.cpp */; };
|
||||||
4BC890D4230F86020025A55A /* DirectAccessDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC890D1230F86020025A55A /* DirectAccessDevice.cpp */; };
|
4BC890D4230F86020025A55A /* DirectAccessDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC890D1230F86020025A55A /* DirectAccessDevice.cpp */; };
|
||||||
4BC91B831D1F160E00884B76 /* CommodoreTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC91B811D1F160E00884B76 /* CommodoreTAP.cpp */; };
|
4BC91B831D1F160E00884B76 /* CommodoreTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC91B811D1F160E00884B76 /* CommodoreTAP.cpp */; };
|
||||||
@ -811,19 +812,14 @@
|
|||||||
4BCE0060227D39AB000CA200 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE005E227D39AB000CA200 /* Video.cpp */; };
|
4BCE0060227D39AB000CA200 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE005E227D39AB000CA200 /* Video.cpp */; };
|
||||||
4BCF1FA41DADC3DD0039D2E7 /* Oric.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */; };
|
4BCF1FA41DADC3DD0039D2E7 /* Oric.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */; };
|
||||||
4BD0FBC3233706A200148981 /* CSApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD0FBC2233706A200148981 /* CSApplication.m */; };
|
4BD0FBC3233706A200148981 /* CSApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD0FBC2233706A200148981 /* CSApplication.m */; };
|
||||||
4BD191F42191180E0042E144 /* ScanTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD191F22191180E0042E144 /* ScanTarget.cpp */; };
|
|
||||||
4BD191F52191180E0042E144 /* ScanTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD191F22191180E0042E144 /* ScanTarget.cpp */; };
|
4BD191F52191180E0042E144 /* ScanTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD191F22191180E0042E144 /* ScanTarget.cpp */; };
|
||||||
4BD388882239E198002D14B5 /* 68000Tests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD388872239E198002D14B5 /* 68000Tests.mm */; };
|
4BD388882239E198002D14B5 /* 68000Tests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD388872239E198002D14B5 /* 68000Tests.mm */; };
|
||||||
4BD3A30B1EE755C800B5B501 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD3A3091EE755C800B5B501 /* Video.cpp */; };
|
4BD3A30B1EE755C800B5B501 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD3A3091EE755C800B5B501 /* Video.cpp */; };
|
||||||
4BD424DF2193B5340097291A /* TextureTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD424DD2193B5340097291A /* TextureTarget.cpp */; };
|
|
||||||
4BD424E02193B5340097291A /* TextureTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD424DD2193B5340097291A /* TextureTarget.cpp */; };
|
4BD424E02193B5340097291A /* TextureTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD424DD2193B5340097291A /* TextureTarget.cpp */; };
|
||||||
4BD424E52193B5830097291A /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD424E12193B5820097291A /* Shader.cpp */; };
|
|
||||||
4BD424E62193B5830097291A /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD424E12193B5820097291A /* Shader.cpp */; };
|
4BD424E62193B5830097291A /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD424E12193B5820097291A /* Shader.cpp */; };
|
||||||
4BD424E72193B5830097291A /* Rectangle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD424E22193B5820097291A /* Rectangle.cpp */; };
|
|
||||||
4BD424E82193B5830097291A /* Rectangle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD424E22193B5820097291A /* Rectangle.cpp */; };
|
4BD424E82193B5830097291A /* Rectangle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD424E22193B5820097291A /* Rectangle.cpp */; };
|
||||||
4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD468F51D8DF41D0084958B /* 1770.cpp */; };
|
4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD468F51D8DF41D0084958B /* 1770.cpp */; };
|
||||||
4BD4A8D01E077FD20020D856 /* PCMTrackTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */; };
|
4BD4A8D01E077FD20020D856 /* PCMTrackTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */; };
|
||||||
4BD5D2682199148100DDF17D /* ScanTargetGLSLFragments.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5D2672199148100DDF17D /* ScanTargetGLSLFragments.cpp */; };
|
|
||||||
4BD5D2692199148100DDF17D /* ScanTargetGLSLFragments.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5D2672199148100DDF17D /* ScanTargetGLSLFragments.cpp */; };
|
4BD5D2692199148100DDF17D /* ScanTargetGLSLFragments.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5D2672199148100DDF17D /* ScanTargetGLSLFragments.cpp */; };
|
||||||
4BD61664206B2AC800236112 /* QuickLoadOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BD61662206B2AC700236112 /* QuickLoadOptions.xib */; };
|
4BD61664206B2AC800236112 /* QuickLoadOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BD61662206B2AC700236112 /* QuickLoadOptions.xib */; };
|
||||||
4BD67DCB209BE4D700AB2146 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD67DCA209BE4D600AB2146 /* StaticAnalyser.cpp */; };
|
4BD67DCB209BE4D700AB2146 /* StaticAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD67DCA209BE4D600AB2146 /* StaticAnalyser.cpp */; };
|
||||||
@ -1004,6 +1000,11 @@
|
|||||||
4B1E857B1D174DEC001EF87D /* 6532.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6532.hpp; sourceTree = "<group>"; };
|
4B1E857B1D174DEC001EF87D /* 6532.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6532.hpp; sourceTree = "<group>"; };
|
||||||
4B1E85801D176468001EF87D /* 6532Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6532Tests.swift; sourceTree = "<group>"; };
|
4B1E85801D176468001EF87D /* 6532Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6532Tests.swift; sourceTree = "<group>"; };
|
||||||
4B1EDB431E39A0AC009D6819 /* chip.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = chip.png; sourceTree = "<group>"; };
|
4B1EDB431E39A0AC009D6819 /* chip.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = chip.png; sourceTree = "<group>"; };
|
||||||
|
4B228CD424D773B30077EF25 /* CSScanTarget.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSScanTarget.mm; sourceTree = "<group>"; };
|
||||||
|
4B228CD624D773CA0077EF25 /* CSScanTarget.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CSScanTarget.h; sourceTree = "<group>"; };
|
||||||
|
4B228CD724DA12C50077EF25 /* CSScanTargetView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSScanTargetView.h; sourceTree = "<group>"; };
|
||||||
|
4B228CD824DA12C60077EF25 /* CSScanTargetView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSScanTargetView.m; sourceTree = "<group>"; };
|
||||||
|
4B228CDA24DA41880077EF25 /* ScanTarget.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = ScanTarget.metal; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.metal; };
|
||||||
4B24095A1C45DF85004DA684 /* Stepper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Stepper.hpp; sourceTree = "<group>"; };
|
4B24095A1C45DF85004DA684 /* Stepper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Stepper.hpp; sourceTree = "<group>"; };
|
||||||
4B2530F3244E6773007980BF /* fm.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = fm.json; sourceTree = "<group>"; };
|
4B2530F3244E6773007980BF /* fm.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = fm.json; sourceTree = "<group>"; };
|
||||||
4B2A332C1DB86821002876E3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/OricOptions.xib"; sourceTree = SOURCE_ROOT; };
|
4B2A332C1DB86821002876E3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/OricOptions.xib"; sourceTree = SOURCE_ROOT; };
|
||||||
@ -1115,6 +1116,7 @@
|
|||||||
4B4DC8271D2C2470003C5BF8 /* C1540.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = C1540.hpp; sourceTree = "<group>"; };
|
4B4DC8271D2C2470003C5BF8 /* C1540.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = C1540.hpp; sourceTree = "<group>"; };
|
||||||
4B4DC8291D2C27A4003C5BF8 /* SerialBus.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SerialBus.cpp; sourceTree = "<group>"; };
|
4B4DC8291D2C27A4003C5BF8 /* SerialBus.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SerialBus.cpp; sourceTree = "<group>"; };
|
||||||
4B4DC82A1D2C27A4003C5BF8 /* SerialBus.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SerialBus.hpp; sourceTree = "<group>"; };
|
4B4DC82A1D2C27A4003C5BF8 /* SerialBus.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SerialBus.hpp; sourceTree = "<group>"; };
|
||||||
|
4B4F2B7024DF99D4000DA6B0 /* CSScanTarget+CppScanTarget.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CSScanTarget+CppScanTarget.h"; sourceTree = "<group>"; };
|
||||||
4B50AF7F242817F40099BBD7 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
|
4B50AF7F242817F40099BBD7 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
|
||||||
4B51F70920A521D700AFA2C1 /* Source.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Source.hpp; sourceTree = "<group>"; };
|
4B51F70920A521D700AFA2C1 /* Source.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Source.hpp; sourceTree = "<group>"; };
|
||||||
4B51F70A20A521D700AFA2C1 /* Observer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Observer.hpp; sourceTree = "<group>"; };
|
4B51F70A20A521D700AFA2C1 /* Observer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Observer.hpp; sourceTree = "<group>"; };
|
||||||
@ -1129,8 +1131,6 @@
|
|||||||
4B54C0C71F8D91E50050900F /* Keyboard.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Keyboard.hpp; path = Electron/Keyboard.hpp; sourceTree = "<group>"; };
|
4B54C0C71F8D91E50050900F /* Keyboard.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Keyboard.hpp; path = Electron/Keyboard.hpp; sourceTree = "<group>"; };
|
||||||
4B54C0C91F8D92580050900F /* Keyboard.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Keyboard.hpp; path = ZX8081/Keyboard.hpp; sourceTree = "<group>"; };
|
4B54C0C91F8D92580050900F /* Keyboard.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Keyboard.hpp; path = ZX8081/Keyboard.hpp; sourceTree = "<group>"; };
|
||||||
4B54C0CA1F8D92580050900F /* Keyboard.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Keyboard.cpp; path = ZX8081/Keyboard.cpp; sourceTree = "<group>"; };
|
4B54C0CA1F8D92580050900F /* Keyboard.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Keyboard.cpp; path = ZX8081/Keyboard.cpp; sourceTree = "<group>"; };
|
||||||
4B55CE5B1C3B7D6F0093A61B /* CSOpenGLView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSOpenGLView.h; sourceTree = "<group>"; };
|
|
||||||
4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CSOpenGLView.m; sourceTree = "<group>"; };
|
|
||||||
4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachineDocument.swift; sourceTree = "<group>"; };
|
4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachineDocument.swift; sourceTree = "<group>"; };
|
||||||
4B55DD8020DF06680043F2E5 /* MachinePicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachinePicker.swift; sourceTree = "<group>"; };
|
4B55DD8020DF06680043F2E5 /* MachinePicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachinePicker.swift; sourceTree = "<group>"; };
|
||||||
4B55DD8220DF06680043F2E5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MachinePicker.xib; sourceTree = "<group>"; };
|
4B55DD8220DF06680043F2E5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MachinePicker.xib; sourceTree = "<group>"; };
|
||||||
@ -1638,6 +1638,9 @@
|
|||||||
4BB73EC11B587A5100552FC2 /* Clock_SignalUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Clock_SignalUITests.swift; sourceTree = "<group>"; };
|
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>"; };
|
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>"; };
|
4BB73ECF1B587A6700552FC2 /* Clock Signal.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = "Clock Signal.entitlements"; sourceTree = "<group>"; };
|
||||||
|
4BB8616C24E22DC500A00E03 /* BufferingScanTarget.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = BufferingScanTarget.hpp; sourceTree = "<group>"; };
|
||||||
|
4BB8616D24E22DC500A00E03 /* BufferingScanTarget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BufferingScanTarget.cpp; sourceTree = "<group>"; };
|
||||||
|
4BB8617024E22F4900A00E03 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; };
|
||||||
4BBB709C2020109C002FE009 /* DynamicMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DynamicMachine.hpp; sourceTree = "<group>"; };
|
4BBB709C2020109C002FE009 /* DynamicMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DynamicMachine.hpp; sourceTree = "<group>"; };
|
||||||
4BBB70A2202011C2002FE009 /* MultiMediaTarget.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MultiMediaTarget.hpp; sourceTree = "<group>"; };
|
4BBB70A2202011C2002FE009 /* MultiMediaTarget.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MultiMediaTarget.hpp; sourceTree = "<group>"; };
|
||||||
4BBB70A3202011C2002FE009 /* MultiMediaTarget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MultiMediaTarget.cpp; sourceTree = "<group>"; };
|
4BBB70A3202011C2002FE009 /* MultiMediaTarget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MultiMediaTarget.cpp; sourceTree = "<group>"; };
|
||||||
@ -1670,8 +1673,6 @@
|
|||||||
4BC23A292467600E001A6030 /* OPLBase.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OPLBase.hpp; sourceTree = "<group>"; };
|
4BC23A292467600E001A6030 /* OPLBase.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OPLBase.hpp; sourceTree = "<group>"; };
|
||||||
4BC23A2A2467600E001A6030 /* EnvelopeGenerator.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = EnvelopeGenerator.hpp; sourceTree = "<group>"; };
|
4BC23A2A2467600E001A6030 /* EnvelopeGenerator.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = EnvelopeGenerator.hpp; sourceTree = "<group>"; };
|
||||||
4BC23A2B2467600E001A6030 /* OPLL.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OPLL.cpp; sourceTree = "<group>"; };
|
4BC23A2B2467600E001A6030 /* OPLL.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OPLL.cpp; sourceTree = "<group>"; };
|
||||||
4BC3C67A24C9230F0027BF76 /* BufferingScanTarget.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = BufferingScanTarget.cpp; path = ../../Outputs/ScanTargets/BufferingScanTarget.cpp; sourceTree = "<group>"; };
|
|
||||||
4BC3C67B24C9230F0027BF76 /* BufferingScanTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = BufferingScanTarget.hpp; path = ../../Outputs/ScanTargets/BufferingScanTarget.hpp; sourceTree = "<group>"; };
|
|
||||||
4BC57CD2243427C700FBC404 /* AudioProducer.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = AudioProducer.hpp; sourceTree = "<group>"; };
|
4BC57CD2243427C700FBC404 /* AudioProducer.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = AudioProducer.hpp; sourceTree = "<group>"; };
|
||||||
4BC57CD32434282000FBC404 /* TimedMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TimedMachine.hpp; sourceTree = "<group>"; };
|
4BC57CD32434282000FBC404 /* TimedMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TimedMachine.hpp; sourceTree = "<group>"; };
|
||||||
4BC57CD424342E0600FBC404 /* MachineTypes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MachineTypes.hpp; sourceTree = "<group>"; };
|
4BC57CD424342E0600FBC404 /* MachineTypes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MachineTypes.hpp; sourceTree = "<group>"; };
|
||||||
@ -1682,7 +1683,6 @@
|
|||||||
4BC751B11D157E61006C31D9 /* 6522Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6522Tests.swift; sourceTree = "<group>"; };
|
4BC751B11D157E61006C31D9 /* 6522Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6522Tests.swift; sourceTree = "<group>"; };
|
||||||
4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FIRFilter.cpp; sourceTree = "<group>"; };
|
4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FIRFilter.cpp; sourceTree = "<group>"; };
|
||||||
4BC76E681C98E31700E6EF73 /* FIRFilter.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FIRFilter.hpp; sourceTree = "<group>"; };
|
4BC76E681C98E31700E6EF73 /* FIRFilter.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FIRFilter.hpp; sourceTree = "<group>"; };
|
||||||
4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; };
|
|
||||||
4BC890D1230F86020025A55A /* DirectAccessDevice.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DirectAccessDevice.cpp; sourceTree = "<group>"; };
|
4BC890D1230F86020025A55A /* DirectAccessDevice.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DirectAccessDevice.cpp; sourceTree = "<group>"; };
|
||||||
4BC890D2230F86020025A55A /* DirectAccessDevice.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DirectAccessDevice.hpp; sourceTree = "<group>"; };
|
4BC890D2230F86020025A55A /* DirectAccessDevice.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DirectAccessDevice.hpp; sourceTree = "<group>"; };
|
||||||
4BC91B811D1F160E00884B76 /* CommodoreTAP.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CommodoreTAP.cpp; sourceTree = "<group>"; };
|
4BC91B811D1F160E00884B76 /* CommodoreTAP.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CommodoreTAP.cpp; sourceTree = "<group>"; };
|
||||||
@ -1809,7 +1809,7 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
4B055AF21FAE9C1C0060FFFF /* OpenGL.framework in Frameworks */,
|
4B055AF21FAE9C1C0060FFFF /* OpenGL.framework in Frameworks */,
|
||||||
4B055AF11FAE9C160060FFFF /* Accelerate.framework in Frameworks */,
|
4BB8617224E22F5A00A00E03 /* Accelerate.framework in Frameworks */,
|
||||||
4B055ABD1FAE86530060FFFF /* libz.tbd in Frameworks */,
|
4B055ABD1FAE86530060FFFF /* libz.tbd in Frameworks */,
|
||||||
4B055A7A1FAE78A00060FFFF /* SDL2.framework in Frameworks */,
|
4B055A7A1FAE78A00060FFFF /* SDL2.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
@ -1819,8 +1819,8 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
4BB8617124E22F5700A00E03 /* Accelerate.framework in Frameworks */,
|
||||||
4B50AF80242817F40099BBD7 /* QuartzCore.framework in Frameworks */,
|
4B50AF80242817F40099BBD7 /* QuartzCore.framework in Frameworks */,
|
||||||
4BC76E6B1C98F43700E6EF73 /* Accelerate.framework in Frameworks */,
|
|
||||||
4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */,
|
4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@ -1830,7 +1830,6 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
4B9F11CA2272433900701480 /* libz.tbd in Frameworks */,
|
4B9F11CA2272433900701480 /* libz.tbd in Frameworks */,
|
||||||
4B778F6423A5F3730000D260 /* Accelerate.framework in Frameworks */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -1847,6 +1846,7 @@
|
|||||||
4B055A761FAE78210060FFFF /* Frameworks */ = {
|
4B055A761FAE78210060FFFF /* Frameworks */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
4BB8617024E22F4900A00E03 /* Accelerate.framework */,
|
||||||
4B50AF7F242817F40099BBD7 /* QuartzCore.framework */,
|
4B50AF7F242817F40099BBD7 /* QuartzCore.framework */,
|
||||||
4B055AF01FAE9C080060FFFF /* OpenGL.framework */,
|
4B055AF01FAE9C080060FFFF /* OpenGL.framework */,
|
||||||
4B055A771FAE78210060FFFF /* SDL2.framework */,
|
4B055A771FAE78210060FFFF /* SDL2.framework */,
|
||||||
@ -2060,6 +2060,17 @@
|
|||||||
path = Icons;
|
path = Icons;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
4B228CD324D773B30077EF25 /* ScanTarget */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
4B228CDA24DA41880077EF25 /* ScanTarget.metal */,
|
||||||
|
4B228CD424D773B30077EF25 /* CSScanTarget.mm */,
|
||||||
|
4B228CD624D773CA0077EF25 /* CSScanTarget.h */,
|
||||||
|
4B4F2B7024DF99D4000DA6B0 /* CSScanTarget+CppScanTarget.h */,
|
||||||
|
);
|
||||||
|
path = ScanTarget;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
4B2409591C45DF85004DA684 /* SignalProcessing */ = {
|
4B2409591C45DF85004DA684 /* SignalProcessing */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@ -2223,6 +2234,7 @@
|
|||||||
4BF52672218E752E00313227 /* ScanTarget.hpp */,
|
4BF52672218E752E00313227 /* ScanTarget.hpp */,
|
||||||
4B0CCC411C62D0B3001CAC5F /* CRT */,
|
4B0CCC411C62D0B3001CAC5F /* CRT */,
|
||||||
4BD191D5219113B80042E144 /* OpenGL */,
|
4BD191D5219113B80042E144 /* OpenGL */,
|
||||||
|
4BB8616B24E22DC500A00E03 /* ScanTargets */,
|
||||||
4BD060A41FE49D3C006E14BE /* Speaker */,
|
4BD060A41FE49D3C006E14BE /* Speaker */,
|
||||||
);
|
);
|
||||||
name = Outputs;
|
name = Outputs;
|
||||||
@ -2471,8 +2483,8 @@
|
|||||||
4B55CE5A1C3B7D6F0093A61B /* Views */ = {
|
4B55CE5A1C3B7D6F0093A61B /* Views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
4B55CE5B1C3B7D6F0093A61B /* CSOpenGLView.h */,
|
4B228CD724DA12C50077EF25 /* CSScanTargetView.h */,
|
||||||
4B55CE5C1C3B7D6F0093A61B /* CSOpenGLView.m */,
|
4B228CD824DA12C60077EF25 /* CSScanTargetView.m */,
|
||||||
);
|
);
|
||||||
path = Views;
|
path = Views;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -3311,9 +3323,6 @@
|
|||||||
4BB73E951B587A5100552FC2 = {
|
4BB73E951B587A5100552FC2 = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
4BC3C67A24C9230F0027BF76 /* BufferingScanTarget.cpp */,
|
|
||||||
4BC3C67B24C9230F0027BF76 /* BufferingScanTarget.hpp */,
|
|
||||||
4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */,
|
|
||||||
4B51F70820A521D700AFA2C1 /* Activity */,
|
4B51F70820A521D700AFA2C1 /* Activity */,
|
||||||
4B8944E2201967B4007DE474 /* Analyser */,
|
4B8944E2201967B4007DE474 /* Analyser */,
|
||||||
4BB73EA01B587A5100552FC2 /* Clock Signal */,
|
4BB73EA01B587A5100552FC2 /* Clock Signal */,
|
||||||
@ -3371,6 +3380,7 @@
|
|||||||
4BB73EAA1B587A5100552FC2 /* MainMenu.xib */,
|
4BB73EAA1B587A5100552FC2 /* MainMenu.xib */,
|
||||||
4BE5F85A1C3E1C2500C43F01 /* Resources */,
|
4BE5F85A1C3E1C2500C43F01 /* Resources */,
|
||||||
4BDA00DB22E60EE900AC3CD0 /* ROMRequester */,
|
4BDA00DB22E60EE900AC3CD0 /* ROMRequester */,
|
||||||
|
4B228CD324D773B30077EF25 /* ScanTarget */,
|
||||||
4B55CE5A1C3B7D6F0093A61B /* Views */,
|
4B55CE5A1C3B7D6F0093A61B /* Views */,
|
||||||
);
|
);
|
||||||
path = "Clock Signal";
|
path = "Clock Signal";
|
||||||
@ -3480,6 +3490,16 @@
|
|||||||
path = ../../Processors;
|
path = ../../Processors;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
4BB8616B24E22DC500A00E03 /* ScanTargets */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
4BB8616C24E22DC500A00E03 /* BufferingScanTarget.hpp */,
|
||||||
|
4BB8616D24E22DC500A00E03 /* BufferingScanTarget.cpp */,
|
||||||
|
);
|
||||||
|
name = ScanTargets;
|
||||||
|
path = ../../Outputs/ScanTargets;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
4BBB70A1202011C2002FE009 /* Implementation */ = {
|
4BBB70A1202011C2002FE009 /* Implementation */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@ -4491,7 +4511,7 @@
|
|||||||
4BEBFB522002DB30000708CC /* DiskROM.cpp in Sources */,
|
4BEBFB522002DB30000708CC /* DiskROM.cpp in Sources */,
|
||||||
4BC23A2D2467600F001A6030 /* OPLL.cpp in Sources */,
|
4BC23A2D2467600F001A6030 /* OPLL.cpp in Sources */,
|
||||||
4B055AA11FAE85DA0060FFFF /* OricMFMDSK.cpp in Sources */,
|
4B055AA11FAE85DA0060FFFF /* OricMFMDSK.cpp in Sources */,
|
||||||
4BC3C67D24C9230F0027BF76 /* BufferingScanTarget.cpp in Sources */,
|
4BB8616F24E22DC500A00E03 /* BufferingScanTarget.cpp in Sources */,
|
||||||
4B0ACC2923775819008902D0 /* DMAController.cpp in Sources */,
|
4B0ACC2923775819008902D0 /* DMAController.cpp in Sources */,
|
||||||
4B055A951FAE85BB0060FFFF /* BitReverse.cpp in Sources */,
|
4B055A951FAE85BB0060FFFF /* BitReverse.cpp in Sources */,
|
||||||
4B055ACE1FAE9B030060FFFF /* Plus3.cpp in Sources */,
|
4B055ACE1FAE9B030060FFFF /* Plus3.cpp in Sources */,
|
||||||
@ -4574,11 +4594,11 @@
|
|||||||
4B0E04EA1FC9E5DA00F43484 /* CAS.cpp in Sources */,
|
4B0E04EA1FC9E5DA00F43484 /* CAS.cpp in Sources */,
|
||||||
4B7A90ED20410A85008514A2 /* StaticAnalyser.cpp in Sources */,
|
4B7A90ED20410A85008514A2 /* StaticAnalyser.cpp in Sources */,
|
||||||
4B58601E1F806AB200AEE2E3 /* MFMSectorDump.cpp in Sources */,
|
4B58601E1F806AB200AEE2E3 /* MFMSectorDump.cpp in Sources */,
|
||||||
|
4B228CD924DA12C60077EF25 /* CSScanTargetView.m in Sources */,
|
||||||
4B6AAEAD230E40250078E864 /* Target.cpp in Sources */,
|
4B6AAEAD230E40250078E864 /* Target.cpp in Sources */,
|
||||||
4B448E841F1C4C480009ABD6 /* PulseQueuedTape.cpp in Sources */,
|
4B448E841F1C4C480009ABD6 /* PulseQueuedTape.cpp in Sources */,
|
||||||
4B0E61071FF34737002A9DBD /* MSX.cpp in Sources */,
|
4B0E61071FF34737002A9DBD /* MSX.cpp in Sources */,
|
||||||
4B4518A01F75FD1C00926311 /* CPCDSK.cpp in Sources */,
|
4B4518A01F75FD1C00926311 /* CPCDSK.cpp in Sources */,
|
||||||
4BD424DF2193B5340097291A /* TextureTarget.cpp in Sources */,
|
|
||||||
4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */,
|
4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */,
|
||||||
4BC23A2C2467600F001A6030 /* OPLL.cpp in Sources */,
|
4BC23A2C2467600F001A6030 /* OPLL.cpp in Sources */,
|
||||||
4B322E041F5A2E3C004EB04C /* Z80Base.cpp in Sources */,
|
4B322E041F5A2E3C004EB04C /* Z80Base.cpp in Sources */,
|
||||||
@ -4604,6 +4624,7 @@
|
|||||||
4B1497921EE4B5A800CE2596 /* ZX8081.cpp in Sources */,
|
4B1497921EE4B5A800CE2596 /* ZX8081.cpp in Sources */,
|
||||||
4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */,
|
4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */,
|
||||||
4BDA00E422E663B900AC3CD0 /* NSData+CRC32.m in Sources */,
|
4BDA00E422E663B900AC3CD0 /* NSData+CRC32.m in Sources */,
|
||||||
|
4BB8616E24E22DC500A00E03 /* BufferingScanTarget.cpp in Sources */,
|
||||||
4BB4BFB022A42F290069048D /* MacintoshIMG.cpp in Sources */,
|
4BB4BFB022A42F290069048D /* MacintoshIMG.cpp in Sources */,
|
||||||
4B05401E219D1618001BF69C /* ScanTarget.cpp in Sources */,
|
4B05401E219D1618001BF69C /* ScanTarget.cpp in Sources */,
|
||||||
4B4518861F75E91A00926311 /* MFMDiskController.cpp in Sources */,
|
4B4518861F75E91A00926311 /* MFMDiskController.cpp in Sources */,
|
||||||
@ -4616,13 +4637,11 @@
|
|||||||
4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */,
|
4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */,
|
||||||
4BBFFEE61F7B27F1005F3FEB /* TrackSerialiser.cpp in Sources */,
|
4BBFFEE61F7B27F1005F3FEB /* TrackSerialiser.cpp in Sources */,
|
||||||
4BAE49582032881E004BE78E /* CSZX8081.mm in Sources */,
|
4BAE49582032881E004BE78E /* CSZX8081.mm in Sources */,
|
||||||
4BD424E52193B5830097291A /* Shader.cpp in Sources */,
|
|
||||||
4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */,
|
4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */,
|
||||||
4B894518201967B4007DE474 /* ConfidenceCounter.cpp in Sources */,
|
4B894518201967B4007DE474 /* ConfidenceCounter.cpp in Sources */,
|
||||||
4BCE005A227CFFCA000CA200 /* Macintosh.cpp in Sources */,
|
4BCE005A227CFFCA000CA200 /* Macintosh.cpp in Sources */,
|
||||||
4B6AAEA4230E3E1D0078E864 /* MassStorageDevice.cpp in Sources */,
|
4B6AAEA4230E3E1D0078E864 /* MassStorageDevice.cpp in Sources */,
|
||||||
4B89452E201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
|
4B89452E201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
|
||||||
4BD5D2682199148100DDF17D /* ScanTargetGLSLFragments.cpp in Sources */,
|
|
||||||
4BC890D3230F86020025A55A /* DirectAccessDevice.cpp in Sources */,
|
4BC890D3230F86020025A55A /* DirectAccessDevice.cpp in Sources */,
|
||||||
4B7BA03723CEB86000B98D9E /* BD500.cpp in Sources */,
|
4B7BA03723CEB86000B98D9E /* BD500.cpp in Sources */,
|
||||||
4B38F3481F2EC11D00D9235D /* AmstradCPC.cpp in Sources */,
|
4B38F3481F2EC11D00D9235D /* AmstradCPC.cpp in Sources */,
|
||||||
@ -4638,7 +4657,6 @@
|
|||||||
4B3BF5B01F146265005B6C36 /* CSW.cpp in Sources */,
|
4B3BF5B01F146265005B6C36 /* CSW.cpp in Sources */,
|
||||||
4BCE0060227D39AB000CA200 /* Video.cpp in Sources */,
|
4BCE0060227D39AB000CA200 /* Video.cpp in Sources */,
|
||||||
4B0ACC2E23775819008902D0 /* TIA.cpp in Sources */,
|
4B0ACC2E23775819008902D0 /* TIA.cpp in Sources */,
|
||||||
4BC3C67C24C9230F0027BF76 /* BufferingScanTarget.cpp in Sources */,
|
|
||||||
4B74CF85231370BC00500CE8 /* MacintoshVolume.cpp in Sources */,
|
4B74CF85231370BC00500CE8 /* MacintoshVolume.cpp in Sources */,
|
||||||
4B4518A51F75FD1C00926311 /* SSD.cpp in Sources */,
|
4B4518A51F75FD1C00926311 /* SSD.cpp in Sources */,
|
||||||
4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */,
|
4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */,
|
||||||
@ -4672,7 +4690,8 @@
|
|||||||
4B8334841F5DA0360097E338 /* Z80Storage.cpp in Sources */,
|
4B8334841F5DA0360097E338 /* Z80Storage.cpp in Sources */,
|
||||||
4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */,
|
4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */,
|
||||||
4BDA00E022E644AF00AC3CD0 /* CSROMReceiverView.m in Sources */,
|
4BDA00E022E644AF00AC3CD0 /* CSROMReceiverView.m in Sources */,
|
||||||
4BD191F42191180E0042E144 /* ScanTarget.cpp in Sources */,
|
4B228CDB24DA41890077EF25 /* ScanTarget.metal in Sources */,
|
||||||
|
4B228CD524D773B40077EF25 /* CSScanTarget.mm in Sources */,
|
||||||
4BCD634922D6756400F567F1 /* MacintoshDoubleDensityDrive.cpp in Sources */,
|
4BCD634922D6756400F567F1 /* MacintoshDoubleDensityDrive.cpp in Sources */,
|
||||||
4B0F94FE208C1A1600FE41D9 /* NIB.cpp in Sources */,
|
4B0F94FE208C1A1600FE41D9 /* NIB.cpp in Sources */,
|
||||||
4B89452A201967B4007DE474 /* File.cpp in Sources */,
|
4B89452A201967B4007DE474 /* File.cpp in Sources */,
|
||||||
@ -4699,7 +4718,6 @@
|
|||||||
4B0E04FA1FC9FA3100F43484 /* 9918.cpp in Sources */,
|
4B0E04FA1FC9FA3100F43484 /* 9918.cpp in Sources */,
|
||||||
4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */,
|
4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */,
|
||||||
4B4518841F75E91A00926311 /* UnformattedTrack.cpp in Sources */,
|
4B4518841F75E91A00926311 /* UnformattedTrack.cpp in Sources */,
|
||||||
4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */,
|
|
||||||
4B65086022F4CF8D009C1100 /* Keyboard.cpp in Sources */,
|
4B65086022F4CF8D009C1100 /* Keyboard.cpp in Sources */,
|
||||||
4B894528201967B4007DE474 /* Disk.cpp in Sources */,
|
4B894528201967B4007DE474 /* Disk.cpp in Sources */,
|
||||||
4BBB70A4202011C2002FE009 /* MultiMediaTarget.cpp in Sources */,
|
4BBB70A4202011C2002FE009 /* MultiMediaTarget.cpp in Sources */,
|
||||||
@ -4756,7 +4774,6 @@
|
|||||||
4B37EE821D7345A6006A09A4 /* BinaryDump.cpp in Sources */,
|
4B37EE821D7345A6006A09A4 /* BinaryDump.cpp in Sources */,
|
||||||
4BCE0053227CE8CA000CA200 /* AppleII.cpp in Sources */,
|
4BCE0053227CE8CA000CA200 /* AppleII.cpp in Sources */,
|
||||||
4B8334821F5D9FF70097E338 /* PartialMachineCycle.cpp in Sources */,
|
4B8334821F5D9FF70097E338 /* PartialMachineCycle.cpp in Sources */,
|
||||||
4BD424E72193B5830097291A /* Rectangle.cpp in Sources */,
|
|
||||||
4B1B88C0202E3DB200B67DFF /* MultiConfigurable.cpp in Sources */,
|
4B1B88C0202E3DB200B67DFF /* MultiConfigurable.cpp in Sources */,
|
||||||
4BFF1D3922337B0300838EA1 /* 68000Storage.cpp in Sources */,
|
4BFF1D3922337B0300838EA1 /* 68000Storage.cpp in Sources */,
|
||||||
4B54C0BC1F8D8E790050900F /* KeyboardMachine.cpp in Sources */,
|
4B54C0BC1F8D8E790050900F /* KeyboardMachine.cpp in Sources */,
|
||||||
@ -5096,6 +5113,7 @@
|
|||||||
);
|
);
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
GCC_OPTIMIZATION_LEVEL = 2;
|
GCC_OPTIMIZATION_LEVEL = 2;
|
||||||
|
GCC_PREPROCESSOR_DEFINITIONS = NDEBUG;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
};
|
};
|
||||||
@ -5149,8 +5167,9 @@
|
|||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_PARAMETER = YES;
|
GCC_WARN_UNUSED_PARAMETER = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
MACOSX_DEPLOYMENT_TARGET = 10.13;
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||||
|
MTL_FAST_MATH = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
@ -5200,8 +5219,9 @@
|
|||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_PARAMETER = YES;
|
GCC_WARN_UNUSED_PARAMETER = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
MACOSX_DEPLOYMENT_TARGET = 10.13;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
MTL_FAST_MATH = YES;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||||
@ -5240,7 +5260,7 @@
|
|||||||
GCC_WARN_UNUSED_LABEL = YES;
|
GCC_WARN_UNUSED_LABEL = YES;
|
||||||
INFOPLIST_FILE = "Clock Signal/Info.plist";
|
INFOPLIST_FILE = "Clock Signal/Info.plist";
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.12.2;
|
MTL_TREAT_WARNINGS_AS_ERRORS = YES;
|
||||||
OTHER_CPLUSPLUSFLAGS = (
|
OTHER_CPLUSPLUSFLAGS = (
|
||||||
"$(OTHER_CFLAGS)",
|
"$(OTHER_CFLAGS)",
|
||||||
"-Wreorder",
|
"-Wreorder",
|
||||||
@ -5288,7 +5308,7 @@
|
|||||||
GCC_WARN_UNUSED_LABEL = YES;
|
GCC_WARN_UNUSED_LABEL = YES;
|
||||||
INFOPLIST_FILE = "Clock Signal/Info.plist";
|
INFOPLIST_FILE = "Clock Signal/Info.plist";
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.12.2;
|
MTL_TREAT_WARNINGS_AS_ERRORS = YES;
|
||||||
OTHER_CPLUSPLUSFLAGS = (
|
OTHER_CPLUSPLUSFLAGS = (
|
||||||
"$(OTHER_CFLAGS)",
|
"$(OTHER_CFLAGS)",
|
||||||
"-Wreorder",
|
"-Wreorder",
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
</Testables>
|
</Testables>
|
||||||
</TestAction>
|
</TestAction>
|
||||||
<LaunchAction
|
<LaunchAction
|
||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Release"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
disableMainThreadChecker = "YES"
|
disableMainThreadChecker = "YES"
|
||||||
@ -57,9 +57,13 @@
|
|||||||
isEnabled = "NO">
|
isEnabled = "NO">
|
||||||
</CommandLineArgument>
|
</CommandLineArgument>
|
||||||
<CommandLineArgument
|
<CommandLineArgument
|
||||||
argument = "--volume=0.001"
|
argument = ""/Users/thomasharte/Library/Mobile Documents/com~apple~CloudDocs/Desktop/Soft/Macintosh/MusicWorks 0.42.image""
|
||||||
isEnabled = "YES">
|
isEnabled = "YES">
|
||||||
</CommandLineArgument>
|
</CommandLineArgument>
|
||||||
|
<CommandLineArgument
|
||||||
|
argument = "--volume=0.001"
|
||||||
|
isEnabled = "NO">
|
||||||
|
</CommandLineArgument>
|
||||||
<CommandLineArgument
|
<CommandLineArgument
|
||||||
argument = "--new=amstradcpc"
|
argument = "--new=amstradcpc"
|
||||||
isEnabled = "NO">
|
isEnabled = "NO">
|
||||||
@ -86,7 +90,7 @@
|
|||||||
</CommandLineArgument>
|
</CommandLineArgument>
|
||||||
<CommandLineArgument
|
<CommandLineArgument
|
||||||
argument = ""/Users/thomasharte/Library/Mobile Documents/com~apple~CloudDocs/Desktop/Soft/Amstrad CPC/Robocop.dsk""
|
argument = ""/Users/thomasharte/Library/Mobile Documents/com~apple~CloudDocs/Desktop/Soft/Amstrad CPC/Robocop.dsk""
|
||||||
isEnabled = "YES">
|
isEnabled = "NO">
|
||||||
</CommandLineArgument>
|
</CommandLineArgument>
|
||||||
<CommandLineArgument
|
<CommandLineArgument
|
||||||
argument = "--speed=5"
|
argument = "--speed=5"
|
||||||
@ -94,7 +98,7 @@
|
|||||||
</CommandLineArgument>
|
</CommandLineArgument>
|
||||||
<CommandLineArgument
|
<CommandLineArgument
|
||||||
argument = "--rompath=/Users/thomasharte/Projects/CLK/ROMImages"
|
argument = "--rompath=/Users/thomasharte/Projects/CLK/ROMImages"
|
||||||
isEnabled = "NO">
|
isEnabled = "YES">
|
||||||
</CommandLineArgument>
|
</CommandLineArgument>
|
||||||
<CommandLineArgument
|
<CommandLineArgument
|
||||||
argument = "--help"
|
argument = "--help"
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="15705" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="16097.2" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="macosx"/>
|
<deployment identifier="macosx"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="15705"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="16097.2"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<objects>
|
<objects>
|
||||||
<customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target">
|
<customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target">
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="openGLView" destination="DEG-fq-cjd" id="Gxs-2u-n7B"/>
|
<outlet property="scanTargetView" destination="DEG-fq-cjd" id="5aX-3R-eXQ"/>
|
||||||
<outlet property="volumeSlider" destination="zaz-lB-Iyt" id="flY-Th-oG4"/>
|
<outlet property="volumeSlider" destination="zaz-lB-Iyt" id="flY-Th-oG4"/>
|
||||||
<outlet property="volumeView" destination="4ap-Gi-2AO" id="v4e-k6-Fqf"/>
|
<outlet property="volumeView" destination="4ap-Gi-2AO" id="v4e-k6-Fqf"/>
|
||||||
<outlet property="window" destination="xOd-HO-29H" id="JIz-fz-R2o"/>
|
<outlet property="window" destination="xOd-HO-29H" id="JIz-fz-R2o"/>
|
||||||
@ -27,7 +27,7 @@
|
|||||||
<rect key="frame" x="0.0" y="0.0" width="600" height="450"/>
|
<rect key="frame" x="0.0" y="0.0" width="600" height="450"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<openGLView hidden="YES" wantsLayer="YES" useAuxiliaryDepthBufferStencil="NO" allowOffline="YES" wantsBestResolutionOpenGLSurface="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DEG-fq-cjd" customClass="CSOpenGLView">
|
<openGLView hidden="YES" wantsLayer="YES" useAuxiliaryDepthBufferStencil="NO" allowOffline="YES" wantsBestResolutionOpenGLSurface="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DEG-fq-cjd" customClass="CSScanTargetView">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="600" height="450"/>
|
<rect key="frame" x="0.0" y="0.0" width="600" height="450"/>
|
||||||
</openGLView>
|
</openGLView>
|
||||||
<box hidden="YES" boxType="custom" cornerRadius="4" title="Box" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="4ap-Gi-2AO">
|
<box hidden="YES" boxType="custom" cornerRadius="4" title="Box" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="4ap-Gi-2AO">
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
#import "CSStaticAnalyser.h"
|
#import "CSStaticAnalyser.h"
|
||||||
|
|
||||||
#import "CSAudioQueue.h"
|
#import "CSAudioQueue.h"
|
||||||
#import "CSOpenGLView.h"
|
#import "CSScanTargetView.h"
|
||||||
#import "CSROMReceiverView.h"
|
#import "CSROMReceiverView.h"
|
||||||
|
|
||||||
#import "CSJoystickManager.h"
|
#import "CSJoystickManager.h"
|
||||||
|
@ -14,8 +14,7 @@ class MachineDocument:
|
|||||||
NSDocument,
|
NSDocument,
|
||||||
NSWindowDelegate,
|
NSWindowDelegate,
|
||||||
CSMachineDelegate,
|
CSMachineDelegate,
|
||||||
CSOpenGLViewDelegate,
|
CSScanTargetViewResponderDelegate,
|
||||||
CSOpenGLViewResponderDelegate,
|
|
||||||
CSAudioQueueDelegate,
|
CSAudioQueueDelegate,
|
||||||
CSROMReciverViewDelegate
|
CSROMReciverViewDelegate
|
||||||
{
|
{
|
||||||
@ -45,7 +44,7 @@ class MachineDocument:
|
|||||||
// MARK: - Main NIB connections.
|
// MARK: - Main NIB connections.
|
||||||
|
|
||||||
/// The OpenGL view to receive this machine's display.
|
/// The OpenGL view to receive this machine's display.
|
||||||
@IBOutlet weak var openGLView: CSOpenGLView!
|
@IBOutlet weak var scanTargetView: CSScanTargetView!
|
||||||
|
|
||||||
/// The options panel, if any.
|
/// The options panel, if any.
|
||||||
@IBOutlet var optionsPanel: MachinePanel!
|
@IBOutlet var optionsPanel: MachinePanel!
|
||||||
@ -100,8 +99,7 @@ class MachineDocument:
|
|||||||
actionLock.lock()
|
actionLock.lock()
|
||||||
drawLock.lock()
|
drawLock.lock()
|
||||||
machine = nil
|
machine = nil
|
||||||
openGLView.delegate = nil
|
scanTargetView.invalidate()
|
||||||
openGLView.invalidate()
|
|
||||||
actionLock.unlock()
|
actionLock.unlock()
|
||||||
drawLock.unlock()
|
drawLock.unlock()
|
||||||
|
|
||||||
@ -181,10 +179,10 @@ class MachineDocument:
|
|||||||
// MARK: - Connections Between Machine and the Outside World
|
// MARK: - Connections Between Machine and the Outside World
|
||||||
|
|
||||||
private func setupMachineOutput() {
|
private func setupMachineOutput() {
|
||||||
if let machine = self.machine, let openGLView = self.openGLView, machine.view != openGLView {
|
if let machine = self.machine, let scanTargetView = self.scanTargetView, machine.view != scanTargetView {
|
||||||
// Establish the output aspect ratio and audio.
|
// Establish the output aspect ratio and audio.
|
||||||
let aspectRatio = self.aspectRatio()
|
let aspectRatio = self.aspectRatio()
|
||||||
machine.setView(openGLView, aspectRatio: Float(aspectRatio.width / aspectRatio.height))
|
machine.setView(scanTargetView, aspectRatio: Float(aspectRatio.width / aspectRatio.height))
|
||||||
|
|
||||||
// Attach an options panel if one is available.
|
// Attach an options panel if one is available.
|
||||||
if let optionsPanelNibName = self.machineDescription?.optionsPanelNibName {
|
if let optionsPanelNibName = self.machineDescription?.optionsPanelNibName {
|
||||||
@ -198,20 +196,20 @@ class MachineDocument:
|
|||||||
|
|
||||||
// Callbacks from the OpenGL may come on a different thread, immediately following the .delegate set;
|
// Callbacks from the OpenGL may come on a different thread, immediately following the .delegate set;
|
||||||
// hence the full setup of the best-effort updater prior to setting self as a delegate.
|
// hence the full setup of the best-effort updater prior to setting self as a delegate.
|
||||||
openGLView.delegate = self
|
// scanTargetView.delegate = self
|
||||||
openGLView.responderDelegate = self
|
scanTargetView.responderDelegate = self
|
||||||
|
|
||||||
// If this machine has a mouse, enable mouse capture; also indicate whether usurption
|
// If this machine has a mouse, enable mouse capture; also indicate whether usurption
|
||||||
// of the command key is desired.
|
// of the command key is desired.
|
||||||
openGLView.shouldCaptureMouse = machine.hasMouse
|
scanTargetView.shouldCaptureMouse = machine.hasMouse
|
||||||
openGLView.shouldUsurpCommand = machine.shouldUsurpCommand
|
scanTargetView.shouldUsurpCommand = machine.shouldUsurpCommand
|
||||||
|
|
||||||
setupAudioQueueClockRate()
|
setupAudioQueueClockRate()
|
||||||
|
|
||||||
// Bring OpenGL view-holding window on top of the options panel and show the content.
|
// Bring OpenGL view-holding window on top of the options panel and show the content.
|
||||||
openGLView.isHidden = false
|
scanTargetView.isHidden = false
|
||||||
openGLView.window!.makeKeyAndOrderFront(self)
|
scanTargetView.window!.makeKeyAndOrderFront(self)
|
||||||
openGLView.window!.makeFirstResponder(openGLView)
|
scanTargetView.window!.makeFirstResponder(scanTargetView)
|
||||||
|
|
||||||
// Start forwarding best-effort updates.
|
// Start forwarding best-effort updates.
|
||||||
machine.start()
|
machine.start()
|
||||||
@ -252,18 +250,6 @@ class MachineDocument:
|
|||||||
final func audioQueueIsRunningDry(_ audioQueue: CSAudioQueue) {
|
final func audioQueueIsRunningDry(_ audioQueue: CSAudioQueue) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Responds to the CSOpenGLViewDelegate redraw message by requesting a machine update if this is a timed
|
|
||||||
/// request, and ordering a redraw regardless of the motivation.
|
|
||||||
final func openGLViewRedraw(_ view: CSOpenGLView, event redrawEvent: CSOpenGLViewRedrawEvent) {
|
|
||||||
if drawLock.try() {
|
|
||||||
if redrawEvent == .timer {
|
|
||||||
machine.updateView(forPixelSize: view.backingSize)
|
|
||||||
}
|
|
||||||
machine.drawView(forPixelSize: view.backingSize)
|
|
||||||
drawLock.unlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Pasteboard Forwarding.
|
// MARK: - Pasteboard Forwarding.
|
||||||
|
|
||||||
/// Forwards any text currently on the pasteboard into the active machine.
|
/// Forwards any text currently on the pasteboard into the active machine.
|
||||||
@ -277,7 +263,7 @@ class MachineDocument:
|
|||||||
// MARK: - Runtime Media Insertion.
|
// MARK: - Runtime Media Insertion.
|
||||||
|
|
||||||
/// Delegate message to receive drag and drop files.
|
/// Delegate message to receive drag and drop files.
|
||||||
final func openGLView(_ view: CSOpenGLView, didReceiveFileAt URL: URL) {
|
final func scanTargetView(_ view: CSScanTargetView, didReceiveFileAt URL: URL) {
|
||||||
let mediaSet = CSMediaSet(fileAt: URL)
|
let mediaSet = CSMediaSet(fileAt: URL)
|
||||||
if let mediaSet = mediaSet {
|
if let mediaSet = mediaSet {
|
||||||
mediaSet.apply(to: self.machine)
|
mediaSet.apply(to: self.machine)
|
||||||
@ -310,7 +296,7 @@ class MachineDocument:
|
|||||||
machine.clearAllKeys()
|
machine.clearAllKeys()
|
||||||
machine.joystickManager = nil
|
machine.joystickManager = nil
|
||||||
}
|
}
|
||||||
self.openGLView.releaseMouse()
|
self.scanTargetView.releaseMouse()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Upon becoming key, attaches joystick input to the machine.
|
/// Upon becoming key, attaches joystick input to the machine.
|
||||||
@ -608,23 +594,20 @@ class MachineDocument:
|
|||||||
let url = pictursURL.appendingPathComponent(filename)
|
let url = pictursURL.appendingPathComponent(filename)
|
||||||
|
|
||||||
// Obtain the machine's current display.
|
// Obtain the machine's current display.
|
||||||
var imageRepresentation: NSBitmapImageRep? = nil
|
let imageRepresentation = self.machine.imageRepresentation
|
||||||
self.openGLView.perform {
|
|
||||||
imageRepresentation = self.machine.imageRepresentation
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode as a PNG and save.
|
// Encode as a PNG and save.
|
||||||
let pngData = imageRepresentation!.representation(using: .png, properties: [:])
|
let pngData = imageRepresentation.representation(using: .png, properties: [:])
|
||||||
try! pngData?.write(to: url)
|
try! pngData?.write(to: url)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Window Title Updates.
|
// MARK: - Window Title Updates.
|
||||||
private var unadornedWindowTitle = ""
|
private var unadornedWindowTitle = ""
|
||||||
func openGLViewDidCaptureMouse(_ view: CSOpenGLView) {
|
internal func scanTargetViewDidCaptureMouse(_ view: CSScanTargetView) {
|
||||||
self.windowControllers[0].window?.title = self.unadornedWindowTitle + " (press ⌘+control to release mouse)"
|
self.windowControllers[0].window?.title = self.unadornedWindowTitle + " (press ⌘+control to release mouse)"
|
||||||
}
|
}
|
||||||
|
|
||||||
func openGLViewDidReleaseMouse(_ view: CSOpenGLView) {
|
internal func scanTargetViewDidReleaseMouse(_ view: CSScanTargetView) {
|
||||||
self.windowControllers[0].window?.title = self.unadornedWindowTitle
|
self.windowControllers[0].window?.title = self.unadornedWindowTitle
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -750,7 +733,7 @@ class MachineDocument:
|
|||||||
}
|
}
|
||||||
fileprivate var animationFader: ViewFader? = nil
|
fileprivate var animationFader: ViewFader? = nil
|
||||||
|
|
||||||
func openGLViewDidShowOSMouseCursor(_ view: CSOpenGLView) {
|
internal func scanTargetViewDidShowOSMouseCursor(_ view: CSScanTargetView) {
|
||||||
// The OS mouse cursor became visible, so show the volume controls.
|
// The OS mouse cursor became visible, so show the volume controls.
|
||||||
animationFader = nil
|
animationFader = nil
|
||||||
volumeView.layer?.removeAllAnimations()
|
volumeView.layer?.removeAllAnimations()
|
||||||
@ -758,7 +741,7 @@ class MachineDocument:
|
|||||||
volumeView.layer?.opacity = 1.0
|
volumeView.layer?.opacity = 1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
func openGLViewWillHideOSMouseCursor(_ view: CSOpenGLView) {
|
internal func scanTargetViewWillHideOSMouseCursor(_ view: CSScanTargetView) {
|
||||||
// The OS mouse cursor will be hidden, so hide the volume controls.
|
// The OS mouse cursor will be hidden, so hide the volume controls.
|
||||||
if !volumeView.isHidden && volumeView.layer?.animation(forKey: "opacity") == nil {
|
if !volumeView.isHidden && volumeView.layer?.animation(forKey: "opacity") == nil {
|
||||||
let fadeAnimation = CABasicAnimation(keyPath: "opacity")
|
let fadeAnimation = CABasicAnimation(keyPath: "opacity")
|
||||||
|
@ -9,9 +9,9 @@
|
|||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
#import "CSAudioQueue.h"
|
#import "CSAudioQueue.h"
|
||||||
#import "CSOpenGLView.h"
|
|
||||||
#import "CSStaticAnalyser.h"
|
|
||||||
#import "CSJoystickManager.h"
|
#import "CSJoystickManager.h"
|
||||||
|
#import "CSScanTargetView.h"
|
||||||
|
#import "CSStaticAnalyser.h"
|
||||||
|
|
||||||
@class CSMachine;
|
@class CSMachine;
|
||||||
@protocol CSMachineDelegate
|
@protocol CSMachineDelegate
|
||||||
@ -62,14 +62,11 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
|
|||||||
- (BOOL)isStereo;
|
- (BOOL)isStereo;
|
||||||
- (void)setAudioSamplingRate:(float)samplingRate bufferSize:(NSUInteger)bufferSize stereo:(BOOL)stereo;
|
- (void)setAudioSamplingRate:(float)samplingRate bufferSize:(NSUInteger)bufferSize stereo:(BOOL)stereo;
|
||||||
|
|
||||||
- (void)setView:(nullable CSOpenGLView *)view aspectRatio:(float)aspectRatio;
|
- (void)setView:(nullable CSScanTargetView *)view aspectRatio:(float)aspectRatio;
|
||||||
|
|
||||||
- (void)start;
|
- (void)start;
|
||||||
- (void)stop;
|
- (void)stop;
|
||||||
|
|
||||||
- (void)updateViewForPixelSize:(CGSize)pixelSize;
|
|
||||||
- (void)drawViewForPixelSize:(CGSize)pixelSize;
|
|
||||||
|
|
||||||
- (void)setKey:(uint16_t)key characters:(nullable NSString *)characters isPressed:(BOOL)isPressed;
|
- (void)setKey:(uint16_t)key characters:(nullable NSString *)characters isPressed:(BOOL)isPressed;
|
||||||
- (void)clearAllKeys;
|
- (void)clearAllKeys;
|
||||||
|
|
||||||
@ -77,7 +74,7 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
|
|||||||
- (void)addMouseMotionX:(CGFloat)deltaX y:(CGFloat)deltaY;
|
- (void)addMouseMotionX:(CGFloat)deltaX y:(CGFloat)deltaY;
|
||||||
|
|
||||||
@property (atomic, strong, nullable) CSAudioQueue *audioQueue;
|
@property (atomic, strong, nullable) CSAudioQueue *audioQueue;
|
||||||
@property (nonatomic, readonly, nonnull) CSOpenGLView *view;
|
@property (nonatomic, readonly, nonnull) CSScanTargetView *view;
|
||||||
@property (nonatomic, weak, nullable) id<CSMachineDelegate> delegate;
|
@property (nonatomic, weak, nullable) id<CSMachineDelegate> delegate;
|
||||||
|
|
||||||
@property (nonatomic, readonly, nonnull) NSString *userDefaultsPrefix;
|
@property (nonatomic, readonly, nonnull) NSString *userDefaultsPrefix;
|
||||||
|
@ -9,8 +9,9 @@
|
|||||||
#import "CSMachine.h"
|
#import "CSMachine.h"
|
||||||
#import "CSMachine+Target.h"
|
#import "CSMachine+Target.h"
|
||||||
|
|
||||||
#include "CSROMFetcher.hpp"
|
|
||||||
#import "CSHighPrecisionTimer.h"
|
#import "CSHighPrecisionTimer.h"
|
||||||
|
#include "CSROMFetcher.hpp"
|
||||||
|
#import "CSScanTarget+CppScanTarget.h"
|
||||||
|
|
||||||
#include "MediaTarget.hpp"
|
#include "MediaTarget.hpp"
|
||||||
#include "JoystickMachine.hpp"
|
#include "JoystickMachine.hpp"
|
||||||
@ -31,13 +32,7 @@
|
|||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <bitset>
|
#include <bitset>
|
||||||
|
|
||||||
#import <OpenGL/OpenGL.h>
|
@interface CSMachine() <CSScanTargetViewDisplayLinkDelegate>
|
||||||
#include <OpenGL/gl3.h>
|
|
||||||
|
|
||||||
#include "../../../../Outputs/OpenGL/ScanTarget.hpp"
|
|
||||||
#include "../../../../Outputs/OpenGL/Screenshot.hpp"
|
|
||||||
|
|
||||||
@interface CSMachine() <CSOpenGLViewDisplayLinkDelegate>
|
|
||||||
- (void)speaker:(Outputs::Speaker::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length;
|
- (void)speaker:(Outputs::Speaker::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length;
|
||||||
- (void)speakerDidChangeInputClock:(Outputs::Speaker::Speaker *)speaker;
|
- (void)speakerDidChangeInputClock:(Outputs::Speaker::Speaker *)speaker;
|
||||||
- (void)addLED:(NSString *)led;
|
- (void)addLED:(NSString *)led;
|
||||||
@ -154,7 +149,6 @@ struct ActivityObserver: public Activity::Observer {
|
|||||||
NSMutableArray<NSString *> *_leds;
|
NSMutableArray<NSString *> *_leds;
|
||||||
|
|
||||||
CSHighPrecisionTimer *_timer;
|
CSHighPrecisionTimer *_timer;
|
||||||
CGSize _pixelSize;
|
|
||||||
std::atomic_flag _isUpdating;
|
std::atomic_flag _isUpdating;
|
||||||
Time::Nanos _syncTime;
|
Time::Nanos _syncTime;
|
||||||
Time::Nanos _timeDiff;
|
Time::Nanos _timeDiff;
|
||||||
@ -165,7 +159,11 @@ struct ActivityObserver: public Activity::Observer {
|
|||||||
|
|
||||||
NSTimer *_joystickTimer;
|
NSTimer *_joystickTimer;
|
||||||
|
|
||||||
std::unique_ptr<Outputs::Display::OpenGL::ScanTarget> _scanTarget;
|
// This array exists to reduce blocking on the main queue; anything that would otherwise need
|
||||||
|
// to synchronise on self in order to post input to the machine can instead synchronise on
|
||||||
|
// _inputEvents and add a block to it. The main machine execution loop promises to synchronise
|
||||||
|
// on _inputEvents very briefly at the start of every tick and execute all enqueued blocks.
|
||||||
|
NSMutableArray<dispatch_block_t> *_inputEvents;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithAnalyser:(CSStaticAnalyser *)result missingROMs:(inout NSMutableArray<CSMissingROM *> *)missingROMs {
|
- (instancetype)initWithAnalyser:(CSStaticAnalyser *)result missingROMs:(inout NSMutableArray<CSMissingROM *> *)missingROMs {
|
||||||
@ -217,6 +215,8 @@ struct ActivityObserver: public Activity::Observer {
|
|||||||
_speakerDelegate.machine = self;
|
_speakerDelegate.machine = self;
|
||||||
_speakerDelegate.machineAccessLock = _delegateMachineAccessLock;
|
_speakerDelegate.machineAccessLock = _delegateMachineAccessLock;
|
||||||
|
|
||||||
|
_inputEvents = [[NSMutableArray alloc] init];
|
||||||
|
|
||||||
_joystickMachine = _machine->joystick_machine();
|
_joystickMachine = _machine->joystick_machine();
|
||||||
[self updateJoystickTimer];
|
[self updateJoystickTimer];
|
||||||
_isUpdating.clear();
|
_isUpdating.clear();
|
||||||
@ -245,11 +245,11 @@ struct ActivityObserver: public Activity::Observer {
|
|||||||
_speakerDelegate.machine = nil;
|
_speakerDelegate.machine = nil;
|
||||||
[_delegateMachineAccessLock unlock];
|
[_delegateMachineAccessLock unlock];
|
||||||
|
|
||||||
[_view performWithGLContext:^{
|
// [_view performWithGLContext:^{
|
||||||
@synchronized(self) {
|
// @synchronized(self) {
|
||||||
self->_scanTarget.reset();
|
// self->_scanTarget.reset();
|
||||||
}
|
// }
|
||||||
}];
|
// }];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (float)idealSamplingRateFromRange:(NSRange)range {
|
- (float)idealSamplingRateFromRange:(NSRange)range {
|
||||||
@ -351,30 +351,10 @@ struct ActivityObserver: public Activity::Observer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setView:(CSOpenGLView *)view aspectRatio:(float)aspectRatio {
|
- (void)setView:(CSScanTargetView *)view aspectRatio:(float)aspectRatio {
|
||||||
_view = view;
|
_view = view;
|
||||||
_view.displayLinkDelegate = self;
|
_view.displayLinkDelegate = self;
|
||||||
[view performWithGLContext:^{
|
_machine->scan_producer()->set_scan_target(_view.scanTarget.scanTarget);
|
||||||
[self setupOutputWithAspectRatio:aspectRatio];
|
|
||||||
} flushDrawable:NO];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)setupOutputWithAspectRatio:(float)aspectRatio {
|
|
||||||
_scanTarget = std::make_unique<Outputs::Display::OpenGL::ScanTarget>();
|
|
||||||
_machine->scan_producer()->set_scan_target(_scanTarget.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)updateViewForPixelSize:(CGSize)pixelSize {
|
|
||||||
// _pixelSize = pixelSize;
|
|
||||||
|
|
||||||
// @synchronized(self) {
|
|
||||||
// const auto scan_status = _machine->crt_machine()->get_scan_status();
|
|
||||||
// NSLog(@"FPS (hopefully): %0.2f [retrace: %0.4f]", 1.0f / scan_status.field_duration, scan_status.retrace_duration);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)drawViewForPixelSize:(CGSize)pixelSize {
|
|
||||||
_scanTarget->draw((int)pixelSize.width, (int)pixelSize.height);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)paste:(NSString *)paste {
|
- (void)paste:(NSString *)paste {
|
||||||
@ -384,26 +364,7 @@ struct ActivityObserver: public Activity::Observer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (NSBitmapImageRep *)imageRepresentation {
|
- (NSBitmapImageRep *)imageRepresentation {
|
||||||
// Grab a screenshot.
|
return self.view.imageRepresentation;
|
||||||
Outputs::Display::OpenGL::Screenshot screenshot(4, 3);
|
|
||||||
|
|
||||||
// Generate an NSBitmapImageRep containing the screenshot's data.
|
|
||||||
NSBitmapImageRep *const result =
|
|
||||||
[[NSBitmapImageRep alloc]
|
|
||||||
initWithBitmapDataPlanes:NULL
|
|
||||||
pixelsWide:screenshot.width
|
|
||||||
pixelsHigh:screenshot.height
|
|
||||||
bitsPerSample:8
|
|
||||||
samplesPerPixel:4
|
|
||||||
hasAlpha:YES
|
|
||||||
isPlanar:NO
|
|
||||||
colorSpaceName:NSDeviceRGBColorSpace
|
|
||||||
bytesPerRow:4 * screenshot.width
|
|
||||||
bitsPerPixel:0];
|
|
||||||
|
|
||||||
memcpy(result.bitmapData, screenshot.pixel_data.data(), size_t(screenshot.width*screenshot.height*4));
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)applyMedia:(const Analyser::Static::Media &)media {
|
- (void)applyMedia:(const Analyser::Static::Media &)media {
|
||||||
@ -428,7 +389,8 @@ struct ActivityObserver: public Activity::Observer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (void)setKey:(uint16_t)key characters:(NSString *)characters isPressed:(BOOL)isPressed {
|
- (void)setKey:(uint16_t)key characters:(NSString *)characters isPressed:(BOOL)isPressed {
|
||||||
auto keyboard_machine = _machine->keyboard_machine();
|
[self applyInputEvent:^{
|
||||||
|
auto keyboard_machine = self->_machine->keyboard_machine();
|
||||||
if(keyboard_machine && (self.inputMode != CSMachineKeyboardInputModeJoystick || !keyboard_machine->get_keyboard().is_exclusive())) {
|
if(keyboard_machine && (self.inputMode != CSMachineKeyboardInputModeJoystick || !keyboard_machine->get_keyboard().is_exclusive())) {
|
||||||
Inputs::Keyboard::Key mapped_key = Inputs::Keyboard::Key::Help; // Make an innocuous default guess.
|
Inputs::Keyboard::Key mapped_key = Inputs::Keyboard::Key::Help; // Make an innocuous default guess.
|
||||||
#define BIND(source, dest) case source: mapped_key = Inputs::Keyboard::Key::dest; break;
|
#define BIND(source, dest) case source: mapped_key = Inputs::Keyboard::Key::dest; break;
|
||||||
@ -503,9 +465,8 @@ struct ActivityObserver: public Activity::Observer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto joystick_machine = _machine->joystick_machine();
|
auto joystick_machine = self->_machine->joystick_machine();
|
||||||
if(self.inputMode == CSMachineKeyboardInputModeJoystick && joystick_machine) {
|
if(self.inputMode == CSMachineKeyboardInputModeJoystick && joystick_machine) {
|
||||||
@synchronized(self) {
|
|
||||||
auto &joysticks = joystick_machine->get_joysticks();
|
auto &joysticks = joystick_machine->get_joysticks();
|
||||||
if(!joysticks.empty()) {
|
if(!joysticks.empty()) {
|
||||||
// Convert to a C++ bool so that the following calls are resolved correctly even if overloaded.
|
// Convert to a C++ bool so that the following calls are resolved correctly even if overloaded.
|
||||||
@ -530,49 +491,55 @@ struct ActivityObserver: public Activity::Observer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)applyInputEvent:(dispatch_block_t)event {
|
||||||
|
@synchronized(_inputEvents) {
|
||||||
|
[_inputEvents addObject:event];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)clearAllKeys {
|
- (void)clearAllKeys {
|
||||||
const auto keyboard_machine = _machine->keyboard_machine();
|
const auto keyboard_machine = _machine->keyboard_machine();
|
||||||
if(keyboard_machine) {
|
if(keyboard_machine) {
|
||||||
@synchronized(self) {
|
[self applyInputEvent:^{
|
||||||
keyboard_machine->get_keyboard().reset_all_keys();
|
keyboard_machine->get_keyboard().reset_all_keys();
|
||||||
}
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto joystick_machine = _machine->joystick_machine();
|
const auto joystick_machine = _machine->joystick_machine();
|
||||||
if(joystick_machine) {
|
if(joystick_machine) {
|
||||||
@synchronized(self) {
|
[self applyInputEvent:^{
|
||||||
for(auto &joystick : joystick_machine->get_joysticks()) {
|
for(auto &joystick : joystick_machine->get_joysticks()) {
|
||||||
joystick->reset_all_inputs();
|
joystick->reset_all_inputs();
|
||||||
}
|
}
|
||||||
}
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto mouse_machine = _machine->mouse_machine();
|
const auto mouse_machine = _machine->mouse_machine();
|
||||||
if(mouse_machine) {
|
if(mouse_machine) {
|
||||||
@synchronized(self) {
|
[self applyInputEvent:^{
|
||||||
mouse_machine->get_mouse().reset_all_buttons();
|
mouse_machine->get_mouse().reset_all_buttons();
|
||||||
}
|
}];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setMouseButton:(int)button isPressed:(BOOL)isPressed {
|
- (void)setMouseButton:(int)button isPressed:(BOOL)isPressed {
|
||||||
auto mouse_machine = _machine->mouse_machine();
|
auto mouse_machine = _machine->mouse_machine();
|
||||||
if(mouse_machine) {
|
if(mouse_machine) {
|
||||||
@synchronized(self) {
|
[self applyInputEvent:^{
|
||||||
mouse_machine->get_mouse().set_button_pressed(button % mouse_machine->get_mouse().get_number_of_buttons(), isPressed);
|
mouse_machine->get_mouse().set_button_pressed(button % mouse_machine->get_mouse().get_number_of_buttons(), isPressed);
|
||||||
}
|
}];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)addMouseMotionX:(CGFloat)deltaX y:(CGFloat)deltaY {
|
- (void)addMouseMotionX:(CGFloat)deltaX y:(CGFloat)deltaY {
|
||||||
auto mouse_machine = _machine->mouse_machine();
|
auto mouse_machine = _machine->mouse_machine();
|
||||||
if(mouse_machine) {
|
if(mouse_machine) {
|
||||||
@synchronized(self) {
|
[self applyInputEvent:^{
|
||||||
mouse_machine->get_mouse().move(int(deltaX), int(deltaY));
|
mouse_machine->get_mouse().move(int(deltaX), int(deltaY));
|
||||||
}
|
}];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -737,11 +704,10 @@ struct ActivityObserver: public Activity::Observer {
|
|||||||
|
|
||||||
#pragma mark - Timer
|
#pragma mark - Timer
|
||||||
|
|
||||||
- (void)openGLViewDisplayLinkDidFire:(CSOpenGLView *)view now:(const CVTimeStamp *)now outputTime:(const CVTimeStamp *)outputTime {
|
- (void)scanTargetViewDisplayLinkDidFire:(CSScanTargetView *)view now:(const CVTimeStamp *)now outputTime:(const CVTimeStamp *)outputTime {
|
||||||
// First order of business: grab a timestamp.
|
// First order of business: grab a timestamp.
|
||||||
const auto timeNow = Time::nanos_now();
|
const auto timeNow = Time::nanos_now();
|
||||||
|
|
||||||
CGSize pixelSize = view.backingSize;
|
|
||||||
BOOL isSyncLocking;
|
BOOL isSyncLocking;
|
||||||
@synchronized(self) {
|
@synchronized(self) {
|
||||||
// Store a means to map from CVTimeStamp.hostTime to Time::Nanos;
|
// Store a means to map from CVTimeStamp.hostTime to Time::Nanos;
|
||||||
@ -753,9 +719,6 @@ struct ActivityObserver: public Activity::Observer {
|
|||||||
// Store the next end-of-frame time. TODO: and start of next and implied visible duration, if raster racing?
|
// Store the next end-of-frame time. TODO: and start of next and implied visible duration, if raster racing?
|
||||||
_syncTime = int64_t(now->hostTime) + _timeDiff;
|
_syncTime = int64_t(now->hostTime) + _timeDiff;
|
||||||
|
|
||||||
// Also crib the current view pixel size.
|
|
||||||
_pixelSize = pixelSize;
|
|
||||||
|
|
||||||
// Set the current refresh period.
|
// Set the current refresh period.
|
||||||
_refreshPeriod = double(now->videoRefreshPeriod) / double(now->videoTimeScale);
|
_refreshPeriod = double(now->videoRefreshPeriod) / double(now->videoTimeScale);
|
||||||
|
|
||||||
@ -765,9 +728,7 @@ struct ActivityObserver: public Activity::Observer {
|
|||||||
|
|
||||||
// Draw the current output. (TODO: do this within the timer if either raster racing or, at least, sync matching).
|
// Draw the current output. (TODO: do this within the timer if either raster racing or, at least, sync matching).
|
||||||
if(!isSyncLocking) {
|
if(!isSyncLocking) {
|
||||||
[self.view performWithGLContext:^{
|
[self.view draw];
|
||||||
self->_scanTarget->draw((int)pixelSize.width, (int)pixelSize.height);
|
|
||||||
} flushDrawable:YES];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -783,9 +744,16 @@ struct ActivityObserver: public Activity::Observer {
|
|||||||
lastTime = std::max(timeNow - Time::Nanos(10'000'000'000 / TICKS), lastTime);
|
lastTime = std::max(timeNow - Time::Nanos(10'000'000'000 / TICKS), lastTime);
|
||||||
const auto duration = timeNow - lastTime;
|
const auto duration = timeNow - lastTime;
|
||||||
|
|
||||||
CGSize pixelSize;
|
|
||||||
BOOL splitAndSync = NO;
|
BOOL splitAndSync = NO;
|
||||||
@synchronized(self) {
|
@synchronized(self) {
|
||||||
|
// Post on input events.
|
||||||
|
@synchronized(self->_inputEvents) {
|
||||||
|
for(dispatch_block_t action: self->_inputEvents) {
|
||||||
|
action();
|
||||||
|
}
|
||||||
|
[self->_inputEvents removeAllObjects];
|
||||||
|
}
|
||||||
|
|
||||||
// If this tick includes vsync then inspect the machine.
|
// If this tick includes vsync then inspect the machine.
|
||||||
if(timeNow >= self->_syncTime && lastTime < self->_syncTime) {
|
if(timeNow >= self->_syncTime && lastTime < self->_syncTime) {
|
||||||
splitAndSync = self->_isSyncLocking = self->_scanSynchroniser.can_synchronise(self->_machine->scan_producer()->get_scan_status(), self->_refreshPeriod);
|
splitAndSync = self->_isSyncLocking = self->_scanSynchroniser.can_synchronise(self->_machine->scan_producer()->get_scan_status(), self->_refreshPeriod);
|
||||||
@ -806,7 +774,6 @@ struct ActivityObserver: public Activity::Observer {
|
|||||||
if(!splitAndSync) {
|
if(!splitAndSync) {
|
||||||
self->_machine->timed_machine()->run_for((double)duration / 1e9);
|
self->_machine->timed_machine()->run_for((double)duration / 1e9);
|
||||||
}
|
}
|
||||||
pixelSize = self->_pixelSize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this was not a split-and-sync then dispatch the update request asynchronously, unless
|
// If this was not a split-and-sync then dispatch the update request asynchronously, unless
|
||||||
@ -822,13 +789,10 @@ struct ActivityObserver: public Activity::Observer {
|
|||||||
}
|
}
|
||||||
if(!wasUpdating) {
|
if(!wasUpdating) {
|
||||||
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
|
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
|
||||||
[self.view performWithGLContext:^{
|
[self.view updateBacking];
|
||||||
self->_scanTarget->update((int)pixelSize.width, (int)pixelSize.height);
|
|
||||||
|
|
||||||
if(splitAndSync) {
|
if(splitAndSync) {
|
||||||
self->_scanTarget->draw((int)pixelSize.width, (int)pixelSize.height);
|
[self.view draw];
|
||||||
}
|
}
|
||||||
} flushDrawable:splitAndSync];
|
|
||||||
self->_isUpdating.clear();
|
self->_isUpdating.clear();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
//
|
||||||
|
// CSScanTarget+C__ScanTarget.h
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 08/08/2020.
|
||||||
|
// Copyright © 2020 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import "CSScanTarget.h"
|
||||||
|
#include "ScanTarget.hpp"
|
||||||
|
|
||||||
|
@interface CSScanTarget (CppScanTarget)
|
||||||
|
|
||||||
|
@property (nonatomic, readonly, nonnull) Outputs::Display::ScanTarget *scanTarget;
|
||||||
|
|
||||||
|
@end
|
25
OSBindings/Mac/Clock Signal/ScanTarget/CSScanTarget.h
Normal file
25
OSBindings/Mac/Clock Signal/ScanTarget/CSScanTarget.h
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
//
|
||||||
|
// ScanTarget.h
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 02/08/2020.
|
||||||
|
// Copyright © 2020 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import <MetalKit/MetalKit.h>
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Provides a ScanTarget that uses Metal as its back-end.
|
||||||
|
*/
|
||||||
|
@interface CSScanTarget : NSObject <MTKViewDelegate>
|
||||||
|
|
||||||
|
- (nonnull instancetype)initWithView:(nonnull MTKView *)view;
|
||||||
|
|
||||||
|
// Draws all scans currently residing at the scan target to the backing store,
|
||||||
|
// ready for output when next requested.
|
||||||
|
- (void)updateFrameBuffer;
|
||||||
|
|
||||||
|
- (nonnull NSBitmapImageRep *)imageRepresentation;
|
||||||
|
|
||||||
|
@end
|
1185
OSBindings/Mac/Clock Signal/ScanTarget/CSScanTarget.mm
Normal file
1185
OSBindings/Mac/Clock Signal/ScanTarget/CSScanTarget.mm
Normal file
File diff suppressed because it is too large
Load Diff
588
OSBindings/Mac/Clock Signal/ScanTarget/ScanTarget.metal
Normal file
588
OSBindings/Mac/Clock Signal/ScanTarget/ScanTarget.metal
Normal file
@ -0,0 +1,588 @@
|
|||||||
|
//
|
||||||
|
// ScanTarget.metal
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 04/08/2020.
|
||||||
|
// Copyright © 2020 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <metal_stdlib>
|
||||||
|
|
||||||
|
using namespace metal;
|
||||||
|
|
||||||
|
struct Uniforms {
|
||||||
|
// This is used to scale scan positions, i.e. it provides the range
|
||||||
|
// for mapping from scan-style integer positions into eye space.
|
||||||
|
int2 scale;
|
||||||
|
|
||||||
|
// Applies a multiplication to all cyclesSinceRetrace values.
|
||||||
|
float cycleMultiplier;
|
||||||
|
|
||||||
|
// This provides the intended height of a scan, in eye-coordinate terms.
|
||||||
|
float lineWidth;
|
||||||
|
|
||||||
|
// Provides zoom and offset to scale the source data.
|
||||||
|
float3x3 sourceToDisplay;
|
||||||
|
|
||||||
|
// Provides conversions to and from RGB for the active colour space.
|
||||||
|
half3x3 toRGB;
|
||||||
|
half3x3 fromRGB;
|
||||||
|
|
||||||
|
// Describes the filter in use for chroma filtering; it'll be
|
||||||
|
// 15 coefficients but they're symmetrical around the centre.
|
||||||
|
half3 chromaKernel[8];
|
||||||
|
|
||||||
|
// Describes the filter in use for luma filtering; 15 coefficients
|
||||||
|
// symmetrical around the centre.
|
||||||
|
half lumaKernel[8];
|
||||||
|
|
||||||
|
// Sets the opacity at which output strips are drawn.
|
||||||
|
half outputAlpha;
|
||||||
|
|
||||||
|
// Sets the gamma power to which output colours are raised.
|
||||||
|
half outputGamma;
|
||||||
|
|
||||||
|
// Sets a brightness multiplier for output colours.
|
||||||
|
half outputMultiplier;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr sampler standardSampler( coord::pixel,
|
||||||
|
address::clamp_to_edge, // Although arbitrary, stick with this address mode for compatibility all the way to MTLFeatureSet_iOS_GPUFamily1_v1.
|
||||||
|
filter::nearest);
|
||||||
|
|
||||||
|
constexpr sampler linearSampler( coord::pixel,
|
||||||
|
address::clamp_to_edge, // Although arbitrary, stick with this address mode for compatibility all the way to MTLFeatureSet_iOS_GPUFamily1_v1.
|
||||||
|
filter::linear);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Structs used for receiving data from the emulation.
|
||||||
|
|
||||||
|
// This is intended to match the net effect of `Scan` as defined by the BufferingScanTarget.
|
||||||
|
struct Scan {
|
||||||
|
struct EndPoint {
|
||||||
|
uint16_t position[2];
|
||||||
|
uint16_t dataOffset;
|
||||||
|
int16_t compositeAngle;
|
||||||
|
uint16_t cyclesSinceRetrace;
|
||||||
|
} endPoints[2];
|
||||||
|
|
||||||
|
uint8_t compositeAmplitude;
|
||||||
|
uint16_t dataY;
|
||||||
|
uint16_t line;
|
||||||
|
};
|
||||||
|
|
||||||
|
// This matches the BufferingScanTarget's `Line`.
|
||||||
|
struct Line {
|
||||||
|
struct EndPoint {
|
||||||
|
uint16_t position[2];
|
||||||
|
int16_t compositeAngle;
|
||||||
|
uint16_t cyclesSinceRetrace;
|
||||||
|
} endPoints[2];
|
||||||
|
|
||||||
|
uint8_t compositeAmplitude;
|
||||||
|
uint16_t line;
|
||||||
|
};
|
||||||
|
|
||||||
|
// MARK: - Intermediate structs.
|
||||||
|
|
||||||
|
struct SourceInterpolator {
|
||||||
|
float4 position [[position]];
|
||||||
|
float2 textureCoordinates;
|
||||||
|
half unitColourPhase; // i.e. one unit per circle.
|
||||||
|
half colourPhase; // i.e. 2*pi units per circle, just regular radians.
|
||||||
|
half colourAmplitude [[flat]];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CopyInterpolator {
|
||||||
|
float4 position [[position]];
|
||||||
|
float2 textureCoordinates;
|
||||||
|
};
|
||||||
|
|
||||||
|
// MARK: - Vertex shaders.
|
||||||
|
|
||||||
|
float2 textureLocation(constant Line *line, float offset, constant Uniforms &uniforms) {
|
||||||
|
return float2(
|
||||||
|
uniforms.cycleMultiplier * mix(line->endPoints[0].cyclesSinceRetrace, line->endPoints[1].cyclesSinceRetrace, offset),
|
||||||
|
line->line + 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
float2 textureLocation(constant Scan *scan, float offset, constant Uniforms &) {
|
||||||
|
return float2(
|
||||||
|
mix(scan->endPoints[0].dataOffset, scan->endPoints[1].dataOffset, offset),
|
||||||
|
scan->dataY + 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Input> SourceInterpolator toDisplay(
|
||||||
|
constant Uniforms &uniforms [[buffer(1)]],
|
||||||
|
constant Input *inputs [[buffer(0)]],
|
||||||
|
uint instanceID [[instance_id]],
|
||||||
|
uint vertexID [[vertex_id]]) {
|
||||||
|
SourceInterpolator output;
|
||||||
|
|
||||||
|
// Get start and end vertices in regular float2 form.
|
||||||
|
const float2 start = float2(
|
||||||
|
float(inputs[instanceID].endPoints[0].position[0]) / float(uniforms.scale.x),
|
||||||
|
float(inputs[instanceID].endPoints[0].position[1]) / float(uniforms.scale.y)
|
||||||
|
);
|
||||||
|
const float2 end = float2(
|
||||||
|
float(inputs[instanceID].endPoints[1].position[0]) / float(uniforms.scale.x),
|
||||||
|
float(inputs[instanceID].endPoints[1].position[1]) / float(uniforms.scale.y)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Calculate the tangent and normal.
|
||||||
|
const float2 tangent = (end - start);
|
||||||
|
const float2 normal = float2(tangent.y, -tangent.x) / length(tangent);
|
||||||
|
|
||||||
|
// Load up the colour details.
|
||||||
|
output.colourAmplitude = float(inputs[instanceID].compositeAmplitude) / 255.0f;
|
||||||
|
output.unitColourPhase = mix(
|
||||||
|
float(inputs[instanceID].endPoints[0].compositeAngle),
|
||||||
|
float(inputs[instanceID].endPoints[1].compositeAngle),
|
||||||
|
float((vertexID&2) >> 1)
|
||||||
|
) / 64.0f;
|
||||||
|
output.colourPhase = 2.0f * 3.141592654f * output.unitColourPhase;
|
||||||
|
|
||||||
|
// Hence determine this quad's real shape, using vertexID to pick a corner.
|
||||||
|
|
||||||
|
// position2d is now in the range [0, 1].
|
||||||
|
const float2 sourcePosition = start + (float(vertexID&2) * 0.5f) * tangent + (float(vertexID&1) - 0.5f) * normal * uniforms.lineWidth;
|
||||||
|
const float2 position2d = (uniforms.sourceToDisplay * float3(sourcePosition, 1.0f)).xy;
|
||||||
|
|
||||||
|
output.position = float4(
|
||||||
|
position2d,
|
||||||
|
0.0f,
|
||||||
|
1.0f
|
||||||
|
);
|
||||||
|
output.textureCoordinates = textureLocation(&inputs[instanceID], float((vertexID&2) >> 1), uniforms);
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
// These next two assume the incoming geometry to be a four-vertex triangle strip; each instance will therefore
|
||||||
|
// produce a quad.
|
||||||
|
|
||||||
|
vertex SourceInterpolator scanToDisplay( constant Uniforms &uniforms [[buffer(1)]],
|
||||||
|
constant Scan *scans [[buffer(0)]],
|
||||||
|
uint instanceID [[instance_id]],
|
||||||
|
uint vertexID [[vertex_id]]) {
|
||||||
|
return toDisplay(uniforms, scans, instanceID, vertexID);
|
||||||
|
}
|
||||||
|
|
||||||
|
vertex SourceInterpolator lineToDisplay( constant Uniforms &uniforms [[buffer(1)]],
|
||||||
|
constant Line *lines [[buffer(0)]],
|
||||||
|
uint instanceID [[instance_id]],
|
||||||
|
uint vertexID [[vertex_id]]) {
|
||||||
|
return toDisplay(uniforms, lines, instanceID, vertexID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This assumes that it needs to generate endpoints for a line segment.
|
||||||
|
|
||||||
|
vertex SourceInterpolator scanToComposition( constant Uniforms &uniforms [[buffer(1)]],
|
||||||
|
constant Scan *scans [[buffer(0)]],
|
||||||
|
uint instanceID [[instance_id]],
|
||||||
|
uint vertexID [[vertex_id]],
|
||||||
|
texture2d<float> texture [[texture(0)]]) {
|
||||||
|
SourceInterpolator result;
|
||||||
|
|
||||||
|
// Populate result as if direct texture access were available.
|
||||||
|
result.position.x = uniforms.cycleMultiplier * mix(scans[instanceID].endPoints[0].cyclesSinceRetrace, scans[instanceID].endPoints[1].cyclesSinceRetrace, float(vertexID));
|
||||||
|
result.position.y = scans[instanceID].line;
|
||||||
|
result.position.zw = float2(0.0f, 1.0f);
|
||||||
|
|
||||||
|
result.textureCoordinates.x = mix(scans[instanceID].endPoints[0].dataOffset, scans[instanceID].endPoints[1].dataOffset, float(vertexID));
|
||||||
|
result.textureCoordinates.y = scans[instanceID].dataY;
|
||||||
|
|
||||||
|
result.unitColourPhase = mix(
|
||||||
|
float(scans[instanceID].endPoints[0].compositeAngle),
|
||||||
|
float(scans[instanceID].endPoints[1].compositeAngle),
|
||||||
|
float(vertexID)
|
||||||
|
) / 64.0f;
|
||||||
|
result.colourPhase = 2.0f * 3.141592654f * result.unitColourPhase;
|
||||||
|
result.colourAmplitude = float(scans[instanceID].compositeAmplitude) / 255.0f;
|
||||||
|
|
||||||
|
// Map position into eye space, allowing for target texture dimensions.
|
||||||
|
const float2 textureSize = float2(texture.get_width(), texture.get_height());
|
||||||
|
result.position.xy =
|
||||||
|
((result.position.xy + float2(0.0f, 0.5f)) / textureSize)
|
||||||
|
* float2(2.0f, -2.0f) + float2(-1.0f, 1.0f);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
vertex CopyInterpolator copyVertex(uint vertexID [[vertex_id]], texture2d<float> texture [[texture(0)]]) {
|
||||||
|
CopyInterpolator vert;
|
||||||
|
|
||||||
|
const uint x = vertexID & 1;
|
||||||
|
const uint y = (vertexID >> 1) & 1;
|
||||||
|
|
||||||
|
vert.textureCoordinates = float2(
|
||||||
|
x * texture.get_width(),
|
||||||
|
y * texture.get_height()
|
||||||
|
);
|
||||||
|
vert.position = float4(
|
||||||
|
float(x) * 2.0 - 1.0,
|
||||||
|
1.0 - float(y) * 2.0,
|
||||||
|
0.0,
|
||||||
|
1.0
|
||||||
|
);
|
||||||
|
|
||||||
|
return vert;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Various input format conversion samplers.
|
||||||
|
|
||||||
|
half2 quadrature(float phase) {
|
||||||
|
return half2(cos(phase), sin(phase));
|
||||||
|
}
|
||||||
|
|
||||||
|
half4 composite(half level, half2 quadrature, half amplitude) {
|
||||||
|
return half4(
|
||||||
|
level,
|
||||||
|
half2(0.5f) + quadrature*half(0.5f),
|
||||||
|
amplitude
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The luminance formats can be sampled either in their natural format, or to the intermediate
|
||||||
|
// composite format used for composition. Direct sampling is always for final output, so the two
|
||||||
|
// 8-bit formats also provide a gamma option.
|
||||||
|
|
||||||
|
half convertLuminance1(SourceInterpolator vert [[stage_in]], texture2d<ushort> texture [[texture(0)]]) {
|
||||||
|
return clamp(half(texture.sample(standardSampler, vert.textureCoordinates).r), half(0.0f), half(1.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
half convertLuminance8(SourceInterpolator vert [[stage_in]], texture2d<half> texture [[texture(0)]]) {
|
||||||
|
return texture.sample(standardSampler, vert.textureCoordinates).r;
|
||||||
|
}
|
||||||
|
|
||||||
|
half convertPhaseLinkedLuminance8(SourceInterpolator vert [[stage_in]], texture2d<half> texture [[texture(0)]]) {
|
||||||
|
const int offset = int(vert.unitColourPhase * 4.0f) & 3;
|
||||||
|
auto sample = texture.sample(standardSampler, vert.textureCoordinates);
|
||||||
|
return sample[offset];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#define CompositeSet(name, type) \
|
||||||
|
fragment half4 sample##name(SourceInterpolator vert [[stage_in]], texture2d<type> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { \
|
||||||
|
const half luminance = convert##name(vert, texture) * uniforms.outputMultiplier; \
|
||||||
|
return half4(half3(luminance), uniforms.outputAlpha); \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
fragment half4 sample##name##WithGamma(SourceInterpolator vert [[stage_in]], texture2d<type> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { \
|
||||||
|
const half luminance = pow(convert##name(vert, texture) * uniforms.outputMultiplier, uniforms.outputGamma); \
|
||||||
|
return half4(half3(luminance), uniforms.outputAlpha); \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
fragment half4 compositeSample##name(SourceInterpolator vert [[stage_in]], texture2d<type> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { \
|
||||||
|
const half luminance = convert##name(vert, texture) * uniforms.outputMultiplier; \
|
||||||
|
return composite(luminance, quadrature(vert.colourPhase), vert.colourAmplitude); \
|
||||||
|
}
|
||||||
|
|
||||||
|
CompositeSet(Luminance1, ushort);
|
||||||
|
CompositeSet(Luminance8, half);
|
||||||
|
CompositeSet(PhaseLinkedLuminance8, half);
|
||||||
|
|
||||||
|
#undef CompositeSet
|
||||||
|
|
||||||
|
// The luminance/phase format can produce either composite or S-Video.
|
||||||
|
|
||||||
|
/// @returns A 2d vector comprised where .x = luminance; .y = chroma.
|
||||||
|
half2 convertLuminance8Phase8(SourceInterpolator vert [[stage_in]], texture2d<half> texture [[texture(0)]]) {
|
||||||
|
const auto luminancePhase = texture.sample(standardSampler, vert.textureCoordinates).rg;
|
||||||
|
const half phaseOffset = 3.141592654 * 4.0 * luminancePhase.g;
|
||||||
|
const half rawChroma = step(luminancePhase.g, half(0.75f)) * cos(vert.colourPhase + phaseOffset);
|
||||||
|
return half2(luminancePhase.r, rawChroma);
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment half4 compositeSampleLuminance8Phase8(SourceInterpolator vert [[stage_in]], texture2d<half> texture [[texture(0)]]) {
|
||||||
|
const half2 luminanceChroma = convertLuminance8Phase8(vert, texture);
|
||||||
|
const half luminance = mix(luminanceChroma.r, luminanceChroma.g, vert.colourAmplitude);
|
||||||
|
return composite(luminance, quadrature(vert.colourPhase), vert.colourAmplitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment half4 sampleLuminance8Phase8(SourceInterpolator vert [[stage_in]], texture2d<half> texture [[texture(0)]]) {
|
||||||
|
const half2 luminanceChroma = convertLuminance8Phase8(vert, texture);
|
||||||
|
const half2 qam = quadrature(vert.colourPhase) * half(0.5f);
|
||||||
|
return half4(luminanceChroma.r,
|
||||||
|
half2(0.5f) + luminanceChroma.g*qam,
|
||||||
|
half(1.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment half4 directCompositeSampleLuminance8Phase8(SourceInterpolator vert [[stage_in]], texture2d<half> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) {
|
||||||
|
const half2 luminanceChroma = convertLuminance8Phase8(vert, texture);
|
||||||
|
const half luminance = mix(luminanceChroma.r * uniforms.outputMultiplier, luminanceChroma.g, vert.colourAmplitude);
|
||||||
|
return half4(half3(luminance), uniforms.outputAlpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment half4 directCompositeSampleLuminance8Phase8WithGamma(SourceInterpolator vert [[stage_in]], texture2d<half> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) {
|
||||||
|
const half2 luminanceChroma = convertLuminance8Phase8(vert, texture);
|
||||||
|
const half luminance = mix(pow(luminanceChroma.r * uniforms.outputMultiplier, uniforms.outputGamma), luminanceChroma.g, vert.colourAmplitude);
|
||||||
|
return half4(half3(luminance), uniforms.outputAlpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// All the RGB formats can produce RGB, composite or S-Video.
|
||||||
|
|
||||||
|
half3 convertRed8Green8Blue8(SourceInterpolator vert, texture2d<half> texture) {
|
||||||
|
return texture.sample(standardSampler, vert.textureCoordinates).rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
half3 convertRed4Green4Blue4(SourceInterpolator vert, texture2d<ushort> texture) {
|
||||||
|
const auto sample = texture.sample(standardSampler, vert.textureCoordinates).rg;
|
||||||
|
return clamp(half3(sample.r&15, (sample.g >> 4)&15, sample.g&15), half(0.0f), half(1.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
half3 convertRed2Green2Blue2(SourceInterpolator vert, texture2d<ushort> texture) {
|
||||||
|
const auto sample = texture.sample(standardSampler, vert.textureCoordinates).r;
|
||||||
|
return clamp(half3((sample >> 4)&3, (sample >> 2)&3, sample&3), half(0.0f), half(1.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
half3 convertRed1Green1Blue1(SourceInterpolator vert, texture2d<ushort> texture) {
|
||||||
|
const auto sample = texture.sample(standardSampler, vert.textureCoordinates).r;
|
||||||
|
return clamp(half3(sample&4, sample&2, sample&1), half(0.0f), half(1.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
#define DeclareShaders(name, pixelType) \
|
||||||
|
fragment half4 sample##name(SourceInterpolator vert [[stage_in]], texture2d<pixelType> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { \
|
||||||
|
return half4(convert##name(vert, texture), uniforms.outputAlpha); \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
fragment half4 sample##name##WithGamma(SourceInterpolator vert [[stage_in]], texture2d<pixelType> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { \
|
||||||
|
return half4(pow(convert##name(vert, texture), uniforms.outputGamma), uniforms.outputAlpha); \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
fragment half4 svideoSample##name(SourceInterpolator vert [[stage_in]], texture2d<pixelType> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { \
|
||||||
|
const auto colour = uniforms.fromRGB * convert##name(vert, texture); \
|
||||||
|
const half2 qam = quadrature(vert.colourPhase); \
|
||||||
|
const half chroma = dot(colour.gb, qam); \
|
||||||
|
return half4( \
|
||||||
|
colour.r, \
|
||||||
|
half2(0.5f) + chroma*qam*half(0.5f), \
|
||||||
|
half(1.0f) \
|
||||||
|
); \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
half composite##name(SourceInterpolator vert, texture2d<pixelType> texture, constant Uniforms &uniforms, half2 colourSubcarrier) { \
|
||||||
|
const auto colour = uniforms.fromRGB * convert##name(vert, texture); \
|
||||||
|
return mix(colour.r, dot(colour.gb, colourSubcarrier), half(vert.colourAmplitude)); \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
fragment half4 compositeSample##name(SourceInterpolator vert [[stage_in]], texture2d<pixelType> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { \
|
||||||
|
const half2 colourSubcarrier = quadrature(vert.colourPhase); \
|
||||||
|
return composite(composite##name(vert, texture, uniforms, colourSubcarrier), colourSubcarrier, vert.colourAmplitude); \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
fragment half4 directCompositeSample##name(SourceInterpolator vert [[stage_in]], texture2d<pixelType> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { \
|
||||||
|
const half level = composite##name(vert, texture, uniforms, quadrature(vert.colourPhase)); \
|
||||||
|
return half4(half3(level), uniforms.outputAlpha); \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
fragment half4 directCompositeSample##name##WithGamma(SourceInterpolator vert [[stage_in]], texture2d<pixelType> texture [[texture(0)]], constant Uniforms &uniforms [[buffer(0)]]) { \
|
||||||
|
const half level = pow(composite##name(vert, texture, uniforms, quadrature(vert.colourPhase)), uniforms.outputGamma); \
|
||||||
|
return half4(half3(level), uniforms.outputAlpha); \
|
||||||
|
}
|
||||||
|
|
||||||
|
DeclareShaders(Red8Green8Blue8, half)
|
||||||
|
DeclareShaders(Red4Green4Blue4, ushort)
|
||||||
|
DeclareShaders(Red2Green2Blue2, ushort)
|
||||||
|
DeclareShaders(Red1Green1Blue1, ushort)
|
||||||
|
|
||||||
|
fragment half4 copyFragment(CopyInterpolator vert [[stage_in]], texture2d<half> texture [[texture(0)]]) {
|
||||||
|
return texture.sample(standardSampler, vert.textureCoordinates);
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment half4 interpolateFragment(CopyInterpolator vert [[stage_in]], texture2d<half> texture [[texture(0)]]) {
|
||||||
|
return texture.sample(linearSampler, vert.textureCoordinates);
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment half4 clearFragment(constant Uniforms &uniforms [[buffer(0)]]) {
|
||||||
|
return half4(0.0, 0.0, 0.0, uniforms.outputAlpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Compute kernels
|
||||||
|
|
||||||
|
/// Given input pixels of the form (luminance, 0.5 + 0.5*chrominance*cos(phase), 0.5 + 0.5*chrominance*sin(phase)), applies a lowpass
|
||||||
|
/// filter to the two chrominance parts, then uses the toRGB matrix to convert to RGB and stores.
|
||||||
|
template <bool applyGamma> void filterChromaKernel( texture2d<half, access::read> inTexture [[texture(0)]],
|
||||||
|
texture2d<half, access::write> outTexture [[texture(1)]],
|
||||||
|
uint2 gid [[thread_position_in_grid]],
|
||||||
|
constant Uniforms &uniforms [[buffer(0)]],
|
||||||
|
constant int &offset [[buffer(1)]]) {
|
||||||
|
constexpr half4 moveToZero(0.0f, 0.5f, 0.5f, 0.0f);
|
||||||
|
const half4 rawSamples[] = {
|
||||||
|
inTexture.read(gid + uint2(0, offset)) - moveToZero,
|
||||||
|
inTexture.read(gid + uint2(1, offset)) - moveToZero,
|
||||||
|
inTexture.read(gid + uint2(2, offset)) - moveToZero,
|
||||||
|
inTexture.read(gid + uint2(3, offset)) - moveToZero,
|
||||||
|
inTexture.read(gid + uint2(4, offset)) - moveToZero,
|
||||||
|
inTexture.read(gid + uint2(5, offset)) - moveToZero,
|
||||||
|
inTexture.read(gid + uint2(6, offset)) - moveToZero,
|
||||||
|
inTexture.read(gid + uint2(7, offset)) - moveToZero,
|
||||||
|
inTexture.read(gid + uint2(8, offset)) - moveToZero,
|
||||||
|
inTexture.read(gid + uint2(9, offset)) - moveToZero,
|
||||||
|
inTexture.read(gid + uint2(10, offset)) - moveToZero,
|
||||||
|
inTexture.read(gid + uint2(11, offset)) - moveToZero,
|
||||||
|
inTexture.read(gid + uint2(12, offset)) - moveToZero,
|
||||||
|
inTexture.read(gid + uint2(13, offset)) - moveToZero,
|
||||||
|
inTexture.read(gid + uint2(14, offset)) - moveToZero,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define Sample(x, y) uniforms.chromaKernel[y] * rawSamples[x].rgb
|
||||||
|
const half3 colour =
|
||||||
|
Sample(0, 0) + Sample(1, 1) + Sample(2, 2) + Sample(3, 3) + Sample(4, 4) + Sample(5, 5) + Sample(6, 6) +
|
||||||
|
Sample(7, 7) +
|
||||||
|
Sample(8, 6) + Sample(9, 5) + Sample(10, 4) + Sample(11, 3) + Sample(12, 2) + Sample(13, 1) + Sample(14, 0);
|
||||||
|
#undef Sample
|
||||||
|
|
||||||
|
const half4 output = half4(uniforms.toRGB * colour * uniforms.outputMultiplier, uniforms.outputAlpha);
|
||||||
|
if(applyGamma) {
|
||||||
|
outTexture.write(pow(output, uniforms.outputGamma), gid + uint2(7, offset));
|
||||||
|
} else {
|
||||||
|
outTexture.write(output, gid + uint2(7, offset));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kernel void filterChromaKernelNoGamma( texture2d<half, access::read> inTexture [[texture(0)]],
|
||||||
|
texture2d<half, access::write> outTexture [[texture(1)]],
|
||||||
|
uint2 gid [[thread_position_in_grid]],
|
||||||
|
constant Uniforms &uniforms [[buffer(0)]],
|
||||||
|
constant int &offset [[buffer(1)]]) {
|
||||||
|
filterChromaKernel<false>(inTexture, outTexture, gid, uniforms, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
kernel void filterChromaKernelWithGamma( texture2d<half, access::read> inTexture [[texture(0)]],
|
||||||
|
texture2d<half, access::write> outTexture [[texture(1)]],
|
||||||
|
uint2 gid [[thread_position_in_grid]],
|
||||||
|
constant Uniforms &uniforms [[buffer(0)]],
|
||||||
|
constant int &offset [[buffer(1)]]) {
|
||||||
|
filterChromaKernel<true>(inTexture, outTexture, gid, uniforms, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSeparatedLumaChroma(half luminance, half4 centreSample, texture2d<half, access::write> outTexture, uint2 gid, int offset) {
|
||||||
|
// The mix/steps below ensures that the absence of a colour burst leads the colour subcarrier to be discarded.
|
||||||
|
const half isColour = step(half(0.01f), centreSample.a);
|
||||||
|
const half chroma = (centreSample.r - luminance) / mix(half(1.0f), centreSample.a, isColour);
|
||||||
|
outTexture.write(half4(
|
||||||
|
luminance / mix(half(1.0f), (half(1.0f) - centreSample.a), isColour),
|
||||||
|
isColour * (centreSample.gb - half2(0.5f)) * chroma + half2(0.5f),
|
||||||
|
1.0f
|
||||||
|
),
|
||||||
|
gid + uint2(7, offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Given input pixels of the form:
|
||||||
|
///
|
||||||
|
/// (composite sample, cos(phase), sin(phase), colour amplitude), applies a lowpass
|
||||||
|
///
|
||||||
|
/// Filters to separate luminance, subtracts that and scales and maps the remaining chrominance in order to output
|
||||||
|
/// pixels in the form:
|
||||||
|
///
|
||||||
|
/// (luminance, 0.5 + 0.5*chrominance*cos(phase), 0.5 + 0.5*chrominance*sin(phase))
|
||||||
|
///
|
||||||
|
/// i.e. the input form for the filterChromaKernel, above].
|
||||||
|
kernel void separateLumaKernel15( texture2d<half, access::read> inTexture [[texture(0)]],
|
||||||
|
texture2d<half, access::write> outTexture [[texture(1)]],
|
||||||
|
uint2 gid [[thread_position_in_grid]],
|
||||||
|
constant Uniforms &uniforms [[buffer(0)]],
|
||||||
|
constant int &offset [[buffer(1)]]) {
|
||||||
|
const half4 centreSample = inTexture.read(gid + uint2(7, offset));
|
||||||
|
const half rawSamples[] = {
|
||||||
|
inTexture.read(gid + uint2(0, offset)).r, inTexture.read(gid + uint2(1, offset)).r,
|
||||||
|
inTexture.read(gid + uint2(2, offset)).r, inTexture.read(gid + uint2(3, offset)).r,
|
||||||
|
inTexture.read(gid + uint2(4, offset)).r, inTexture.read(gid + uint2(5, offset)).r,
|
||||||
|
inTexture.read(gid + uint2(6, offset)).r,
|
||||||
|
centreSample.r,
|
||||||
|
inTexture.read(gid + uint2(8, offset)).r,
|
||||||
|
inTexture.read(gid + uint2(9, offset)).r, inTexture.read(gid + uint2(10, offset)).r,
|
||||||
|
inTexture.read(gid + uint2(11, offset)).r, inTexture.read(gid + uint2(12, offset)).r,
|
||||||
|
inTexture.read(gid + uint2(13, offset)).r, inTexture.read(gid + uint2(14, offset)).r,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define Sample(x, y) uniforms.lumaKernel[y] * rawSamples[x]
|
||||||
|
const half luminance =
|
||||||
|
Sample(0, 0) + Sample(1, 1) + Sample(2, 2) + Sample(3, 3) + Sample(4, 4) + Sample(5, 5) + Sample(6, 6) +
|
||||||
|
Sample(7, 7) +
|
||||||
|
Sample(8, 6) + Sample(9, 5) + Sample(10, 4) + Sample(11, 3) + Sample(12, 2) + Sample(13, 1) + Sample(14, 0);
|
||||||
|
#undef Sample
|
||||||
|
|
||||||
|
return setSeparatedLumaChroma(luminance, centreSample, outTexture, gid, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
kernel void separateLumaKernel9( texture2d<half, access::read> inTexture [[texture(0)]],
|
||||||
|
texture2d<half, access::write> outTexture [[texture(1)]],
|
||||||
|
uint2 gid [[thread_position_in_grid]],
|
||||||
|
constant Uniforms &uniforms [[buffer(0)]],
|
||||||
|
constant int &offset [[buffer(1)]]) {
|
||||||
|
const half4 centreSample = inTexture.read(gid + uint2(7, offset));
|
||||||
|
const half rawSamples[] = {
|
||||||
|
inTexture.read(gid + uint2(3, offset)).r, inTexture.read(gid + uint2(4, offset)).r,
|
||||||
|
inTexture.read(gid + uint2(5, offset)).r, inTexture.read(gid + uint2(6, offset)).r,
|
||||||
|
centreSample.r,
|
||||||
|
inTexture.read(gid + uint2(8, offset)).r, inTexture.read(gid + uint2(9, offset)).r,
|
||||||
|
inTexture.read(gid + uint2(10, offset)).r, inTexture.read(gid + uint2(11, offset)).r
|
||||||
|
};
|
||||||
|
|
||||||
|
#define Sample(x, y) uniforms.lumaKernel[y] * rawSamples[x]
|
||||||
|
const half luminance =
|
||||||
|
Sample(0, 3) + Sample(1, 4) + Sample(2, 5) + Sample(3, 6) +
|
||||||
|
Sample(4, 7) +
|
||||||
|
Sample(5, 6) + Sample(6, 5) + Sample(7, 4) + Sample(8, 3);
|
||||||
|
#undef Sample
|
||||||
|
|
||||||
|
return setSeparatedLumaChroma(luminance, centreSample, outTexture, gid, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
kernel void separateLumaKernel7( texture2d<half, access::read> inTexture [[texture(0)]],
|
||||||
|
texture2d<half, access::write> outTexture [[texture(1)]],
|
||||||
|
uint2 gid [[thread_position_in_grid]],
|
||||||
|
constant Uniforms &uniforms [[buffer(0)]],
|
||||||
|
constant int &offset [[buffer(1)]]) {
|
||||||
|
const half4 centreSample = inTexture.read(gid + uint2(7, offset));
|
||||||
|
const half rawSamples[] = {
|
||||||
|
inTexture.read(gid + uint2(4, offset)).r,
|
||||||
|
inTexture.read(gid + uint2(5, offset)).r, inTexture.read(gid + uint2(6, offset)).r,
|
||||||
|
centreSample.r,
|
||||||
|
inTexture.read(gid + uint2(8, offset)).r, inTexture.read(gid + uint2(9, offset)).r,
|
||||||
|
inTexture.read(gid + uint2(10, offset)).r
|
||||||
|
};
|
||||||
|
|
||||||
|
#define Sample(x, y) uniforms.lumaKernel[y] * rawSamples[x]
|
||||||
|
const half luminance =
|
||||||
|
Sample(0, 4) + Sample(1, 5) + Sample(2, 6) +
|
||||||
|
Sample(3, 7) +
|
||||||
|
Sample(4, 6) + Sample(5, 5) + Sample(6, 4);
|
||||||
|
#undef Sample
|
||||||
|
|
||||||
|
return setSeparatedLumaChroma(luminance, centreSample, outTexture, gid, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
kernel void separateLumaKernel5( texture2d<half, access::read> inTexture [[texture(0)]],
|
||||||
|
texture2d<half, access::write> outTexture [[texture(1)]],
|
||||||
|
uint2 gid [[thread_position_in_grid]],
|
||||||
|
constant Uniforms &uniforms [[buffer(0)]],
|
||||||
|
constant int &offset [[buffer(1)]]) {
|
||||||
|
const half4 centreSample = inTexture.read(gid + uint2(7, offset));
|
||||||
|
const half rawSamples[] = {
|
||||||
|
inTexture.read(gid + uint2(5, offset)).r, inTexture.read(gid + uint2(6, offset)).r,
|
||||||
|
centreSample.r,
|
||||||
|
inTexture.read(gid + uint2(8, offset)).r, inTexture.read(gid + uint2(9, offset)).r,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define Sample(x, y) uniforms.lumaKernel[y] * rawSamples[x]
|
||||||
|
const half luminance =
|
||||||
|
Sample(0, 5) + Sample(1, 6) +
|
||||||
|
Sample(2, 7) +
|
||||||
|
Sample(3, 6) + Sample(4, 5);
|
||||||
|
#undef Sample
|
||||||
|
|
||||||
|
return setSeparatedLumaChroma(luminance, centreSample, outTexture, gid, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
kernel void clearKernel( texture2d<half, access::write> outTexture [[texture(1)]],
|
||||||
|
uint2 gid [[thread_position_in_grid]]) {
|
||||||
|
outTexture.write(half4(0.0f, 0.0f, 0.0f, 1.0f), gid);
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// CSOpenGLView.h
|
// CSScanTargetView.h
|
||||||
// Clock Signal
|
// Clock Signal
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 16/07/2015.
|
// Created by Thomas Harte on 16/07/2015.
|
||||||
@ -8,63 +8,12 @@
|
|||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
#import <AppKit/AppKit.h>
|
#import <AppKit/AppKit.h>
|
||||||
|
#import <MetalKit/MetalKit.h>
|
||||||
|
|
||||||
@class CSOpenGLView;
|
@class CSScanTargetView;
|
||||||
|
@class CSScanTarget;
|
||||||
|
|
||||||
typedef NS_ENUM(NSInteger, CSOpenGLViewRedrawEvent) {
|
@protocol CSScanTargetViewResponderDelegate <NSObject>
|
||||||
/// Indicates that AppKit requested a redraw for some reason (mostly likely, the window is being resized). So,
|
|
||||||
/// if the delegate doesn't redraw the view, the user is likely to see a graphical flaw.
|
|
||||||
CSOpenGLViewRedrawEventAppKit,
|
|
||||||
/// Indicates that the view's display-linked timer has triggered a redraw request. So, if the delegate doesn't
|
|
||||||
/// redraw the view, the user will just see the previous drawing without interruption.
|
|
||||||
CSOpenGLViewRedrawEventTimer
|
|
||||||
};
|
|
||||||
|
|
||||||
@protocol CSOpenGLViewDelegate
|
|
||||||
/*!
|
|
||||||
Requests that the delegate produce an image of its current output state. May be called on
|
|
||||||
any queue or thread.
|
|
||||||
@param view The view making the request.
|
|
||||||
@param redrawEvent If @c YES then the delegate may decline to redraw if its output would be
|
|
||||||
identical to the previous frame. If @c NO then the delegate must draw.
|
|
||||||
*/
|
|
||||||
- (void)openGLViewRedraw:(nonnull CSOpenGLView *)view event:(CSOpenGLViewRedrawEvent)redrawEvent;
|
|
||||||
|
|
||||||
/*!
|
|
||||||
Announces receipt of a file by drag and drop to the delegate.
|
|
||||||
@param view The view making the request.
|
|
||||||
@param URL The file URL of the received file.
|
|
||||||
*/
|
|
||||||
- (void)openGLView:(nonnull CSOpenGLView *)view didReceiveFileAtURL:(nonnull NSURL *)URL;
|
|
||||||
|
|
||||||
/*!
|
|
||||||
Announces 'capture' of the mouse — i.e. that the view is now preventing the mouse from exiting
|
|
||||||
the window, in order to forward continuous mouse motion.
|
|
||||||
@param view The view making the announcement.
|
|
||||||
*/
|
|
||||||
- (void)openGLViewDidCaptureMouse:(nonnull CSOpenGLView *)view;
|
|
||||||
|
|
||||||
/*!
|
|
||||||
Announces that the mouse is no longer captured.
|
|
||||||
@param view The view making the announcement.
|
|
||||||
*/
|
|
||||||
- (void)openGLViewDidReleaseMouse:(nonnull CSOpenGLView *)view;
|
|
||||||
|
|
||||||
/*!
|
|
||||||
Announces that the OS mouse cursor is now being displayed again, after having been invisible.
|
|
||||||
@param view The view making the announcement.
|
|
||||||
*/
|
|
||||||
- (void)openGLViewDidShowOSMouseCursor:(nonnull CSOpenGLView *)view;
|
|
||||||
|
|
||||||
/*!
|
|
||||||
Announces that the OS mouse cursor will now be hidden.
|
|
||||||
@param view The view making the announcement.
|
|
||||||
*/
|
|
||||||
- (void)openGLViewWillHideOSMouseCursor:(nonnull CSOpenGLView *)view;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@protocol CSOpenGLViewResponderDelegate <NSObject>
|
|
||||||
/*!
|
/*!
|
||||||
Supplies a keyDown event to the delegate.
|
Supplies a keyDown event to the delegate.
|
||||||
@param event The @c NSEvent describing the keyDown.
|
@param event The @c NSEvent describing the keyDown.
|
||||||
@ -111,41 +60,72 @@ typedef NS_ENUM(NSInteger, CSOpenGLViewRedrawEvent) {
|
|||||||
*/
|
*/
|
||||||
- (void)mouseUp:(nonnull NSEvent *)event;
|
- (void)mouseUp:(nonnull NSEvent *)event;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Announces 'capture' of the mouse — i.e. that the view is now preventing the mouse from exiting
|
||||||
|
the window, in order to forward continuous mouse motion.
|
||||||
|
@param view The view making the announcement.
|
||||||
|
*/
|
||||||
|
- (void)scanTargetViewDidCaptureMouse:(nonnull CSScanTargetView *)view;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Announces that the mouse is no longer captured.
|
||||||
|
@param view The view making the announcement.
|
||||||
|
*/
|
||||||
|
- (void)scanTargetViewDidReleaseMouse:(nonnull CSScanTargetView *)view;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Announces that the OS mouse cursor is now being displayed again, after having been invisible.
|
||||||
|
@param view The view making the announcement.
|
||||||
|
*/
|
||||||
|
- (void)scanTargetViewDidShowOSMouseCursor:(nonnull CSScanTargetView *)view;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Announces that the OS mouse cursor will now be hidden.
|
||||||
|
@param view The view making the announcement.
|
||||||
|
*/
|
||||||
|
- (void)scanTargetViewWillHideOSMouseCursor:(nonnull CSScanTargetView *)view;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Announces receipt of a file by drag and drop to the delegate.
|
||||||
|
@param view The view making the request.
|
||||||
|
@param URL The file URL of the received file.
|
||||||
|
*/
|
||||||
|
- (void)scanTargetView:(nonnull CSScanTargetView *)view didReceiveFileAtURL:(nonnull NSURL *)URL;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Although I'm still on the fence about this as a design decision, CSOpenGLView is itself responsible
|
Although I'm still on the fence about this as a design decision, CSScanTargetView is itself responsible
|
||||||
for creating and destroying a CVDisplayLink. There's a practical reason for this: you'll get real synchronisation
|
for creating and destroying a CVDisplayLink. There's a practical reason for this: you'll get real synchronisation
|
||||||
only if a link is explicitly tied to a particular display, and the CSOpenGLView therefore owns the knowledge
|
only if a link is explicitly tied to a particular display, and the CSScanTargetView therefore owns the knowledge
|
||||||
necessary to decide when to create and modify them. It doesn't currently just propagate "did change screen"-type
|
necessary to decide when to create and modify them. It doesn't currently just propagate "did change screen"-type
|
||||||
messages because I haven't yet found a way to track that other than polling, in which case I might as well put
|
messages because I haven't yet found a way to track that other than polling, in which case I might as well put
|
||||||
that into the display link callback.
|
that into the display link callback.
|
||||||
*/
|
*/
|
||||||
@protocol CSOpenGLViewDisplayLinkDelegate
|
@protocol CSScanTargetViewDisplayLinkDelegate
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Informs the delegate that the display link has fired.
|
Informs the delegate that the display link has fired.
|
||||||
*/
|
*/
|
||||||
- (void)openGLViewDisplayLinkDidFire:(nonnull CSOpenGLView *)view now:(nonnull const CVTimeStamp *)now outputTime:(nonnull const CVTimeStamp *)outputTime;
|
- (void)scanTargetViewDisplayLinkDidFire:(nonnull CSScanTargetView *)view now:(nonnull const CVTimeStamp *)now outputTime:(nonnull const CVTimeStamp *)outputTime;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Provides an OpenGL canvas with a refresh-linked update timer that can forward a subset
|
Provides a visible scan target with a refresh-linked update timer that can forward a subset
|
||||||
of typical first-responder actions.
|
of typical first-responder actions.
|
||||||
*/
|
*/
|
||||||
@interface CSOpenGLView : NSOpenGLView
|
@interface CSScanTargetView : MTKView
|
||||||
|
|
||||||
@property (atomic, weak, nullable) id <CSOpenGLViewDelegate> delegate;
|
@property (nonatomic, weak, nullable) id <CSScanTargetViewResponderDelegate> responderDelegate;
|
||||||
@property (nonatomic, weak, nullable) id <CSOpenGLViewResponderDelegate> responderDelegate;
|
@property (atomic, weak, nullable) id <CSScanTargetViewDisplayLinkDelegate> displayLinkDelegate;
|
||||||
@property (atomic, weak, nullable) id <CSOpenGLViewDisplayLinkDelegate> displayLinkDelegate;
|
|
||||||
|
|
||||||
/// Determines whether the view offers mouse capturing — i.e. if the user clicks on the view then
|
/// Determines whether the view offers mouse capturing — i.e. if the user clicks on the view then
|
||||||
/// then the system cursor is disabled and the mouse events defined by CSOpenGLViewResponderDelegate
|
/// then the system cursor is disabled and the mouse events defined by CSScanTargetViewResponderDelegate
|
||||||
/// are forwarded, unless and until the user releases the mouse using the control+command shortcut.
|
/// are forwarded, unless and until the user releases the mouse using the control+command shortcut.
|
||||||
@property (nonatomic, assign) BOOL shouldCaptureMouse;
|
@property (nonatomic, assign) BOOL shouldCaptureMouse;
|
||||||
|
|
||||||
/// Determines whether the CSOpenGLViewResponderDelegate of this window expects to use the command
|
/// Determines whether the CSScanTargetViewResponderDelegate of this window expects to use the command
|
||||||
/// key as though it were any other key — i.e. all command combinations should be forwarded to the delegate,
|
/// key as though it were any other key — i.e. all command combinations should be forwarded to the delegate,
|
||||||
/// not being allowed to trigger regular application shortcuts such as command+q or command+h.
|
/// not being allowed to trigger regular application shortcuts such as command+q or command+h.
|
||||||
///
|
///
|
||||||
@ -162,19 +142,24 @@ typedef NS_ENUM(NSInteger, CSOpenGLViewRedrawEvent) {
|
|||||||
*/
|
*/
|
||||||
- (void)invalidate;
|
- (void)invalidate;
|
||||||
|
|
||||||
/// The size in pixels of the OpenGL canvas, factoring in screen pixel density and view size in points.
|
|
||||||
@property (nonatomic, readonly) CGSize backingSize;
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Locks this view's OpenGL context and makes it current, performs @c action and then unlocks
|
Ensures output begins on all pending scans.
|
||||||
the context. @c action is performed on the calling queue.
|
|
||||||
*/
|
*/
|
||||||
- (void)performWithGLContext:(nonnull dispatch_block_t)action flushDrawable:(BOOL)flushDrawable;
|
- (void)updateBacking;
|
||||||
- (void)performWithGLContext:(nonnull dispatch_block_t)action;
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Instructs that the mouse cursor, if currently captured, should be released.
|
Instructs that the mouse cursor, if currently captured, should be released.
|
||||||
*/
|
*/
|
||||||
- (void)releaseMouse;
|
- (void)releaseMouse;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@returns An image of the view's current contents.
|
||||||
|
*/
|
||||||
|
- (nonnull NSBitmapImageRep *)imageRepresentation;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@returns The CSScanTarget being used for this display.
|
||||||
|
*/
|
||||||
|
@property(nonatomic, readonly, nonnull) CSScanTarget *scanTarget;
|
||||||
|
|
||||||
@end
|
@end
|
@ -1,24 +1,24 @@
|
|||||||
//
|
//
|
||||||
// CSOpenGLView
|
// CSScanTargetView
|
||||||
// CLK
|
// CLK
|
||||||
//
|
//
|
||||||
// Created by Thomas Harte on 16/07/2015.
|
// Created by Thomas Harte on 16/07/2015.
|
||||||
// Copyright 2015 Thomas Harte. All rights reserved.
|
// Copyright 2015 Thomas Harte. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
#import "CSOpenGLView.h"
|
#import "CSScanTargetView.h"
|
||||||
#import "CSApplication.h"
|
#import "CSApplication.h"
|
||||||
|
#import "CSScanTarget.h"
|
||||||
@import CoreVideo;
|
@import CoreVideo;
|
||||||
@import GLKit;
|
@import GLKit;
|
||||||
|
|
||||||
#include <stdatomic.h>
|
#include <stdatomic.h>
|
||||||
|
|
||||||
@interface CSOpenGLView () <NSDraggingDestination, CSApplicationEventDelegate>
|
@interface CSScanTargetView () <NSDraggingDestination, CSApplicationEventDelegate>
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation CSOpenGLView {
|
@implementation CSScanTargetView {
|
||||||
CVDisplayLinkRef _displayLink;
|
CVDisplayLinkRef _displayLink;
|
||||||
CGSize _backingSize;
|
|
||||||
NSNumber *_currentScreenNumber;
|
NSNumber *_currentScreenNumber;
|
||||||
|
|
||||||
NSTrackingArea *_mouseTrackingArea;
|
NSTrackingArea *_mouseTrackingArea;
|
||||||
@ -27,20 +27,8 @@
|
|||||||
|
|
||||||
atomic_int _isDrawingFlag;
|
atomic_int _isDrawingFlag;
|
||||||
BOOL _isInvalid;
|
BOOL _isInvalid;
|
||||||
}
|
|
||||||
|
|
||||||
- (void)prepareOpenGL {
|
CSScanTarget *_scanTarget;
|
||||||
[super prepareOpenGL];
|
|
||||||
|
|
||||||
// Prepare the atomic int.
|
|
||||||
atomic_init(&_isDrawingFlag, 0);
|
|
||||||
|
|
||||||
// Set the clear colour.
|
|
||||||
[self.openGLContext makeCurrentContext];
|
|
||||||
glClearColor(0.0, 0.0, 0.0, 1.0);
|
|
||||||
|
|
||||||
// Setup the [initial] display link.
|
|
||||||
[self setupDisplayLink];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setupDisplayLink {
|
- (void)setupDisplayLink {
|
||||||
@ -58,17 +46,12 @@
|
|||||||
// Set the renderer output callback function.
|
// Set the renderer output callback function.
|
||||||
CVDisplayLinkSetOutputCallback(_displayLink, DisplayLinkCallback, (__bridge void * __nullable)(self));
|
CVDisplayLinkSetOutputCallback(_displayLink, DisplayLinkCallback, (__bridge void * __nullable)(self));
|
||||||
|
|
||||||
// Set the display link for the current renderer.
|
|
||||||
CGLContextObj cglContext = [[self openGLContext] CGLContextObj];
|
|
||||||
CGLPixelFormatObj cglPixelFormat = [[self pixelFormat] CGLPixelFormatObj];
|
|
||||||
CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(_displayLink, cglContext, cglPixelFormat);
|
|
||||||
|
|
||||||
// Activate the display link.
|
// Activate the display link.
|
||||||
CVDisplayLinkStart(_displayLink);
|
CVDisplayLinkStart(_displayLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
static CVReturn DisplayLinkCallback(__unused CVDisplayLinkRef displayLink, const CVTimeStamp *now, const CVTimeStamp *outputTime, __unused CVOptionFlags flagsIn, __unused CVOptionFlags *flagsOut, void *displayLinkContext) {
|
static CVReturn DisplayLinkCallback(__unused CVDisplayLinkRef displayLink, const CVTimeStamp *now, const CVTimeStamp *outputTime, __unused CVOptionFlags flagsIn, __unused CVOptionFlags *flagsOut, void *displayLinkContext) {
|
||||||
CSOpenGLView *const view = (__bridge CSOpenGLView *)displayLinkContext;
|
CSScanTargetView *const view = (__bridge CSScanTargetView *)displayLinkContext;
|
||||||
|
|
||||||
// Schedule an opportunity to check that the display link is still linked to the correct display.
|
// Schedule an opportunity to check that the display link is still linked to the correct display.
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
dispatch_async(dispatch_get_main_queue(), ^{
|
||||||
@ -78,7 +61,7 @@ static CVReturn DisplayLinkCallback(__unused CVDisplayLinkRef displayLink, const
|
|||||||
// Ensure _isDrawingFlag has value 1 when drawing, 0 otherwise.
|
// Ensure _isDrawingFlag has value 1 when drawing, 0 otherwise.
|
||||||
atomic_store(&view->_isDrawingFlag, 1);
|
atomic_store(&view->_isDrawingFlag, 1);
|
||||||
|
|
||||||
[view.displayLinkDelegate openGLViewDisplayLinkDidFire:view now:now outputTime:outputTime];
|
[view.displayLinkDelegate scanTargetViewDisplayLinkDidFire:view now:now outputTime:outputTime];
|
||||||
/*
|
/*
|
||||||
Do not touch the display link from after this call; there's a bit of a race condition with setupDisplayLink.
|
Do not touch the display link from after this call; there's a bit of a race condition with setupDisplayLink.
|
||||||
Specifically: Apple provides CVDisplayLinkStop but a call to that merely prevents future calls to the callback,
|
Specifically: Apple provides CVDisplayLinkStop but a call to that merely prevents future calls to the callback,
|
||||||
@ -106,30 +89,12 @@ static CVReturn DisplayLinkCallback(__unused CVDisplayLinkRef displayLink, const
|
|||||||
// feels fine.
|
// feels fine.
|
||||||
NSNumber *const screenNumber = self.window.screen.deviceDescription[@"NSScreenNumber"];
|
NSNumber *const screenNumber = self.window.screen.deviceDescription[@"NSScreenNumber"];
|
||||||
if(![_currentScreenNumber isEqual:screenNumber]) {
|
if(![_currentScreenNumber isEqual:screenNumber]) {
|
||||||
// Issue a reshape, in case a switch to/from a Retina display has
|
|
||||||
// happened, changing the results of -convertSizeToBacking:, etc.
|
|
||||||
[self reshape];
|
|
||||||
|
|
||||||
// Also switch display links, to make sure synchronisation is with the display
|
// Also switch display links, to make sure synchronisation is with the display
|
||||||
// the window is actually on, and at its rate.
|
// the window is actually on, and at its rate.
|
||||||
[self setupDisplayLink];
|
[self setupDisplayLink];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)drawAtTime:(const CVTimeStamp *)now frequency:(double)frequency {
|
|
||||||
[self redrawWithEvent:CSOpenGLViewRedrawEventTimer];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)drawRect:(NSRect)dirtyRect {
|
|
||||||
[self redrawWithEvent:CSOpenGLViewRedrawEventAppKit];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)redrawWithEvent:(CSOpenGLViewRedrawEvent)event {
|
|
||||||
[self performWithGLContext:^{
|
|
||||||
[self.delegate openGLViewRedraw:self event:event];
|
|
||||||
} flushDrawable:YES];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)invalidate {
|
- (void)invalidate {
|
||||||
_isInvalid = YES;
|
_isInvalid = YES;
|
||||||
[self stopDisplayLink];
|
[self stopDisplayLink];
|
||||||
@ -160,65 +125,35 @@ static CVReturn DisplayLinkCallback(__unused CVDisplayLinkRef displayLink, const
|
|||||||
CVDisplayLinkRelease(_displayLink);
|
CVDisplayLinkRelease(_displayLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (CGSize)backingSize {
|
- (CSScanTarget *)scanTarget {
|
||||||
@synchronized(self) {
|
return _scanTarget;
|
||||||
return _backingSize;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)reshape {
|
- (void)updateBacking {
|
||||||
[super reshape];
|
[_scanTarget updateFrameBuffer];
|
||||||
@synchronized(self) {
|
|
||||||
_backingSize = [self convertSizeToBacking:self.bounds.size];
|
|
||||||
}
|
|
||||||
|
|
||||||
[self performWithGLContext:^{
|
|
||||||
CGSize viewSize = [self backingSize];
|
|
||||||
glViewport(0, 0, (GLsizei)viewSize.width, (GLsizei)viewSize.height);
|
|
||||||
} flushDrawable:NO];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)awakeFromNib {
|
- (void)awakeFromNib {
|
||||||
NSOpenGLPixelFormatAttribute attributes[] = {
|
// Use the preferred device if available.
|
||||||
NSOpenGLPFADoubleBuffer,
|
if(@available(macOS 10.15, *)) {
|
||||||
NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,
|
self.device = self.preferredDevice;
|
||||||
// NSOpenGLPFAMultisample,
|
} else {
|
||||||
// NSOpenGLPFASampleBuffers, 1,
|
self.device = MTLCreateSystemDefaultDevice();
|
||||||
// NSOpenGLPFASamples, 2,
|
}
|
||||||
0
|
|
||||||
};
|
|
||||||
|
|
||||||
NSOpenGLPixelFormat *pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes];
|
// Configure for explicit drawing.
|
||||||
NSOpenGLContext *context = [[NSOpenGLContext alloc] initWithFormat:pixelFormat shareContext:nil];
|
self.paused = YES;
|
||||||
|
self.enableSetNeedsDisplay = NO;
|
||||||
|
|
||||||
#ifdef DEBUG
|
// Create the scan target.
|
||||||
// When we're using a CoreProfile context, crash if we call a legacy OpenGL function
|
_scanTarget = [[CSScanTarget alloc] initWithView:self];
|
||||||
// This will make it much more obvious where and when such a function call is made so
|
self.delegate = _scanTarget;
|
||||||
// that we can remove such calls.
|
|
||||||
// Without this we'd simply get GL_INVALID_OPERATION error for calling legacy functions
|
|
||||||
// but it would be more difficult to see where that function was called.
|
|
||||||
CGLEnable([context CGLContextObj], kCGLCECrashOnRemovedFunctions);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
self.pixelFormat = pixelFormat;
|
|
||||||
self.openGLContext = context;
|
|
||||||
self.wantsBestResolutionOpenGLSurface = YES;
|
|
||||||
|
|
||||||
// Register to receive dragged and dropped file URLs.
|
// Register to receive dragged and dropped file URLs.
|
||||||
[self registerForDraggedTypes:@[(__bridge NSString *)kUTTypeFileURL]];
|
[self registerForDraggedTypes:@[(__bridge NSString *)kUTTypeFileURL]];
|
||||||
}
|
|
||||||
|
|
||||||
- (void)performWithGLContext:(dispatch_block_t)action flushDrawable:(BOOL)flushDrawable {
|
// Setup the [initial] display link.
|
||||||
CGLLockContext([[self openGLContext] CGLContextObj]);
|
[self setupDisplayLink];
|
||||||
[self.openGLContext makeCurrentContext];
|
|
||||||
action();
|
|
||||||
CGLUnlockContext([[self openGLContext] CGLContextObj]);
|
|
||||||
|
|
||||||
if(flushDrawable) CGLFlushDrawable([[self openGLContext] CGLContextObj]);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)performWithGLContext:(nonnull dispatch_block_t)action {
|
|
||||||
[self performWithGLContext:action flushDrawable:NO];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - NSResponder
|
#pragma mark - NSResponder
|
||||||
@ -259,12 +194,16 @@ static CVReturn DisplayLinkCallback(__unused CVDisplayLinkRef displayLink, const
|
|||||||
[self.responderDelegate paste:sender];
|
[self.responderDelegate paste:sender];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (NSBitmapImageRep *)imageRepresentation {
|
||||||
|
return self.scanTarget.imageRepresentation;
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark - NSDraggingDestination
|
#pragma mark - NSDraggingDestination
|
||||||
|
|
||||||
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender {
|
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender {
|
||||||
for(NSPasteboardItem *item in [[sender draggingPasteboard] pasteboardItems]) {
|
for(NSPasteboardItem *item in [[sender draggingPasteboard] pasteboardItems]) {
|
||||||
NSURL *URL = [NSURL URLWithString:[item stringForType:(__bridge NSString *)kUTTypeFileURL]];
|
NSURL *URL = [NSURL URLWithString:[item stringForType:(__bridge NSString *)kUTTypeFileURL]];
|
||||||
[self.delegate openGLView:self didReceiveFileAtURL:URL];
|
[self.responderDelegate scanTargetView:self didReceiveFileAtURL:URL];
|
||||||
}
|
}
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
@ -300,13 +239,13 @@ static CVReturn DisplayLinkCallback(__unused CVDisplayLinkRef displayLink, const
|
|||||||
|
|
||||||
_mouseHideTimer = [NSTimer scheduledTimerWithTimeInterval:3.0 repeats:NO block:^(__unused NSTimer * _Nonnull timer) {
|
_mouseHideTimer = [NSTimer scheduledTimerWithTimeInterval:3.0 repeats:NO block:^(__unused NSTimer * _Nonnull timer) {
|
||||||
[NSCursor setHiddenUntilMouseMoves:YES];
|
[NSCursor setHiddenUntilMouseMoves:YES];
|
||||||
[self.delegate openGLViewWillHideOSMouseCursor:self];
|
[self.responderDelegate scanTargetViewWillHideOSMouseCursor:self];
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)mouseEntered:(NSEvent *)event {
|
- (void)mouseEntered:(NSEvent *)event {
|
||||||
[self.delegate openGLViewDidShowOSMouseCursor:self];
|
[self.responderDelegate scanTargetViewDidShowOSMouseCursor:self];
|
||||||
[super mouseEntered:event];
|
[super mouseEntered:event];
|
||||||
[self scheduleMouseHide];
|
[self scheduleMouseHide];
|
||||||
}
|
}
|
||||||
@ -315,7 +254,7 @@ static CVReturn DisplayLinkCallback(__unused CVDisplayLinkRef displayLink, const
|
|||||||
[super mouseExited:event];
|
[super mouseExited:event];
|
||||||
[_mouseHideTimer invalidate];
|
[_mouseHideTimer invalidate];
|
||||||
_mouseHideTimer = nil;
|
_mouseHideTimer = nil;
|
||||||
[self.delegate openGLViewWillHideOSMouseCursor:self];
|
[self.responderDelegate scanTargetViewWillHideOSMouseCursor:self];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)releaseMouse {
|
- (void)releaseMouse {
|
||||||
@ -323,8 +262,8 @@ static CVReturn DisplayLinkCallback(__unused CVDisplayLinkRef displayLink, const
|
|||||||
_mouseIsCaptured = NO;
|
_mouseIsCaptured = NO;
|
||||||
CGAssociateMouseAndMouseCursorPosition(true);
|
CGAssociateMouseAndMouseCursorPosition(true);
|
||||||
[NSCursor unhide];
|
[NSCursor unhide];
|
||||||
[self.delegate openGLViewDidReleaseMouse:self];
|
[self.responderDelegate scanTargetViewDidReleaseMouse:self];
|
||||||
[self.delegate openGLViewDidShowOSMouseCursor:self];
|
[self.responderDelegate scanTargetViewDidShowOSMouseCursor:self];
|
||||||
((CSApplication *)[NSApplication sharedApplication]).eventDelegate = nil;
|
((CSApplication *)[NSApplication sharedApplication]).eventDelegate = nil;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -336,7 +275,7 @@ static CVReturn DisplayLinkCallback(__unused CVDisplayLinkRef displayLink, const
|
|||||||
// Mouse capture is off, so don't play games with the cursor, just schedule it to
|
// Mouse capture is off, so don't play games with the cursor, just schedule it to
|
||||||
// hide in the near future.
|
// hide in the near future.
|
||||||
[self scheduleMouseHide];
|
[self scheduleMouseHide];
|
||||||
[self.delegate openGLViewDidShowOSMouseCursor:self];
|
[self.responderDelegate scanTargetViewDidShowOSMouseCursor:self];
|
||||||
} else {
|
} else {
|
||||||
if(_mouseIsCaptured) {
|
if(_mouseIsCaptured) {
|
||||||
// Mouse capture is on, so move the cursor back to the middle of the window, and
|
// Mouse capture is on, so move the cursor back to the middle of the window, and
|
||||||
@ -354,7 +293,7 @@ static CVReturn DisplayLinkCallback(__unused CVDisplayLinkRef displayLink, const
|
|||||||
|
|
||||||
[self.responderDelegate mouseMoved:event];
|
[self.responderDelegate mouseMoved:event];
|
||||||
} else {
|
} else {
|
||||||
[self.delegate openGLViewDidShowOSMouseCursor:self];
|
[self.responderDelegate scanTargetViewDidShowOSMouseCursor:self];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -387,8 +326,8 @@ static CVReturn DisplayLinkCallback(__unused CVDisplayLinkRef displayLink, const
|
|||||||
_mouseIsCaptured = YES;
|
_mouseIsCaptured = YES;
|
||||||
[NSCursor hide];
|
[NSCursor hide];
|
||||||
CGAssociateMouseAndMouseCursorPosition(false);
|
CGAssociateMouseAndMouseCursorPosition(false);
|
||||||
[self.delegate openGLViewWillHideOSMouseCursor:self];
|
[self.responderDelegate scanTargetViewWillHideOSMouseCursor:self];
|
||||||
[self.delegate openGLViewDidCaptureMouse:self];
|
[self.responderDelegate scanTargetViewDidCaptureMouse:self];
|
||||||
if(self.shouldUsurpCommand) {
|
if(self.shouldUsurpCommand) {
|
||||||
((CSApplication *)[NSApplication sharedApplication]).eventDelegate = self;
|
((CSApplication *)[NSApplication sharedApplication]).eventDelegate = self;
|
||||||
}
|
}
|
@ -7,7 +7,6 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#import <XCTest/XCTest.h>
|
#import <XCTest/XCTest.h>
|
||||||
#import <OpenGL/OpenGL.h>
|
|
||||||
|
|
||||||
#include "9918.hpp"
|
#include "9918.hpp"
|
||||||
|
|
||||||
|
@ -194,8 +194,8 @@ Outputs::Display::ScanTarget::Scan::EndPoint CRT::end_point(uint16_t data_offset
|
|||||||
end_point.y = uint16_t(vertical_flywheel_->get_current_output_position() / vertical_flywheel_output_divider_);
|
end_point.y = uint16_t(vertical_flywheel_->get_current_output_position() / vertical_flywheel_output_divider_);
|
||||||
end_point.data_offset = data_offset;
|
end_point.data_offset = data_offset;
|
||||||
|
|
||||||
// TODO: this is a workaround for the limited precision that can be posted onwards;
|
// Ensure .composite_angle is sampled at the location indicated by .cycles_since_end_of_horizontal_retrace.
|
||||||
// it'd be better to make time_multiplier_ an explicit modal and just not divide by it.
|
// TODO: I could supply time_multiplier_ as a modal and just not round .cycles_since_end_of_horizontal_retrace. Would that be better?
|
||||||
const auto lost_precision = cycles_since_horizontal_sync_ % time_multiplier_;
|
const auto lost_precision = cycles_since_horizontal_sync_ % time_multiplier_;
|
||||||
end_point.composite_angle = int16_t(((phase_numerator_ - lost_precision * colour_cycle_numerator_) << 6) / phase_denominator_) * (is_alernate_line_ ? -1 : 1);
|
end_point.composite_angle = int16_t(((phase_numerator_ - lost_precision * colour_cycle_numerator_) << 6) / phase_denominator_) * (is_alernate_line_ ? -1 : 1);
|
||||||
end_point.cycles_since_end_of_horizontal_retrace = uint16_t(cycles_since_horizontal_sync_ / time_multiplier_);
|
end_point.cycles_since_end_of_horizontal_retrace = uint16_t(cycles_since_horizontal_sync_ / time_multiplier_);
|
||||||
@ -427,7 +427,8 @@ void CRT::set_immediate_default_phase(float phase) {
|
|||||||
|
|
||||||
void CRT::output_data(int number_of_cycles, size_t number_of_samples) {
|
void CRT::output_data(int number_of_cycles, size_t number_of_samples) {
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
assert(number_of_samples > 0 && number_of_samples <= allocated_data_length_);
|
assert(number_of_samples > 0);
|
||||||
|
assert(number_of_samples <= allocated_data_length_);
|
||||||
allocated_data_length_ = std::numeric_limits<size_t>::min();
|
allocated_data_length_ = std::numeric_limits<size_t>::min();
|
||||||
#endif
|
#endif
|
||||||
scan_target_->end_data(number_of_samples);
|
scan_target_->end_data(number_of_samples);
|
||||||
|
@ -81,7 +81,7 @@ class CRT {
|
|||||||
|
|
||||||
Outputs::Display::ScanTarget *scan_target_ = &Outputs::Display::NullScanTarget::singleton;
|
Outputs::Display::ScanTarget *scan_target_ = &Outputs::Display::NullScanTarget::singleton;
|
||||||
Outputs::Display::ScanTarget::Modals scan_target_modals_;
|
Outputs::Display::ScanTarget::Modals scan_target_modals_;
|
||||||
static constexpr uint8_t DefaultAmplitude = 80;
|
static constexpr uint8_t DefaultAmplitude = 41; // Based upon a black level to maximum excursion and positive burst peak of: NTSC: 882 & 143; PAL: 933 & 150.
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
size_t allocated_data_length_ = std::numeric_limits<size_t>::min();
|
size_t allocated_data_length_ = std::numeric_limits<size_t>::min();
|
||||||
|
@ -50,7 +50,7 @@ void Metrics::announce_did_resize() {
|
|||||||
frames_missed_ = frames_hit_ = 0;
|
frames_missed_ = frames_hit_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Metrics::announce_draw_status(size_t, std::chrono::high_resolution_clock::duration, bool complete) {
|
void Metrics::announce_draw_status(bool complete) {
|
||||||
if(!complete) {
|
if(!complete) {
|
||||||
++frames_missed_;
|
++frames_missed_;
|
||||||
} else {
|
} else {
|
||||||
@ -79,6 +79,10 @@ void Metrics::announce_draw_status(size_t, std::chrono::high_resolution_clock::d
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Metrics::announce_draw_status(size_t, std::chrono::high_resolution_clock::duration, bool complete) {
|
||||||
|
announce_draw_status(complete);
|
||||||
|
}
|
||||||
|
|
||||||
bool Metrics::should_lower_resolution() const {
|
bool Metrics::should_lower_resolution() const {
|
||||||
// If less than 100 frames are on record, return no opinion; otherwise
|
// If less than 100 frames are on record, return no opinion; otherwise
|
||||||
// suggest a lower resolution if more than 10 frames in the last 100-200
|
// suggest a lower resolution if more than 10 frames in the last 100-200
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
#include "ScanTarget.hpp"
|
#include "ScanTarget.hpp"
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <atomic>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
namespace Outputs {
|
namespace Outputs {
|
||||||
@ -33,6 +34,9 @@ class Metrics {
|
|||||||
/// Provides Metrics with a new data point for output speed estimation.
|
/// Provides Metrics with a new data point for output speed estimation.
|
||||||
void announce_draw_status(size_t lines, std::chrono::high_resolution_clock::duration duration, bool complete);
|
void announce_draw_status(size_t lines, std::chrono::high_resolution_clock::duration duration, bool complete);
|
||||||
|
|
||||||
|
/// Provides Metrics with a new data point for output speed estimation, albeit without line-specific information.
|
||||||
|
void announce_draw_status(bool complete);
|
||||||
|
|
||||||
/// @returns @c true if Metrics thinks a lower output buffer resolution is desirable in the abstract; @c false otherwise.
|
/// @returns @c true if Metrics thinks a lower output buffer resolution is desirable in the abstract; @c false otherwise.
|
||||||
bool should_lower_resolution() const;
|
bool should_lower_resolution() const;
|
||||||
|
|
||||||
@ -48,8 +52,8 @@ class Metrics {
|
|||||||
size_t line_total_history_pointer_ = 0;
|
size_t line_total_history_pointer_ = 0;
|
||||||
void add_line_total(int);
|
void add_line_total(int);
|
||||||
|
|
||||||
int frames_hit_ = 0;
|
std::atomic<int> frames_hit_ = 0;
|
||||||
int frames_missed_ = 0;
|
std::atomic<int> frames_missed_ = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
#if TARGET_OS_IPHONE
|
#if TARGET_OS_IPHONE
|
||||||
#else
|
#else
|
||||||
|
// These remain so that I can, at least for now, build the kiosk version under macOS.
|
||||||
|
// They can be eliminated if and when Apple fully withdraws OpenGL support.
|
||||||
#include <OpenGL/OpenGL.h>
|
#include <OpenGL/OpenGL.h>
|
||||||
#include <OpenGL/gl3.h>
|
#include <OpenGL/gl3.h>
|
||||||
#include <OpenGL/gl3ext.h>
|
#include <OpenGL/gl3ext.h>
|
||||||
|
@ -118,8 +118,9 @@ void ScanTarget::setup_pipeline() {
|
|||||||
const auto data_type_size = Outputs::Display::size_for_data_type(modals.input_data_type);
|
const auto data_type_size = Outputs::Display::size_for_data_type(modals.input_data_type);
|
||||||
|
|
||||||
// Resize the texture only if required.
|
// Resize the texture only if required.
|
||||||
if(data_type_size != write_area_data_size()) {
|
const size_t required_size = WriteAreaWidth*WriteAreaHeight*data_type_size;
|
||||||
write_area_texture_.resize(WriteAreaWidth*WriteAreaHeight*data_type_size);
|
if(required_size != write_area_data_size()) {
|
||||||
|
write_area_texture_.resize(required_size);
|
||||||
set_write_area(write_area_texture_.data());
|
set_write_area(write_area_texture_.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,7 +187,9 @@ void ScanTarget::update(int, int output_height) {
|
|||||||
true);
|
true);
|
||||||
|
|
||||||
// Grab the new output list.
|
// Grab the new output list.
|
||||||
perform([=] (const OutputArea &area) {
|
perform([=] {
|
||||||
|
OutputArea area = get_output_area();
|
||||||
|
|
||||||
// Establish the pipeline if necessary.
|
// Establish the pipeline if necessary.
|
||||||
const auto new_modals = BufferingScanTarget::new_modals();
|
const auto new_modals = BufferingScanTarget::new_modals();
|
||||||
const bool did_setup_pipeline = bool(new_modals);
|
const bool did_setup_pipeline = bool(new_modals);
|
||||||
@ -478,6 +481,7 @@ void ScanTarget::update(int, int output_height) {
|
|||||||
// Grab a fence sync object to avoid busy waiting upon the next extry into this
|
// Grab a fence sync object to avoid busy waiting upon the next extry into this
|
||||||
// function, and reset the is_updating_ flag.
|
// function, and reset the is_updating_ flag.
|
||||||
fence_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
fence_ = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||||
|
complete_output_area(area);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ namespace OpenGL {
|
|||||||
this uses various internal buffers so that the only geometry
|
this uses various internal buffers so that the only geometry
|
||||||
drawn to the target framebuffer is a quad.
|
drawn to the target framebuffer is a quad.
|
||||||
*/
|
*/
|
||||||
class ScanTarget: public Outputs::Display::BufferingScanTarget {
|
class ScanTarget: public Outputs::Display::BufferingScanTarget { // TODO: use private inheritance and expose only display_metrics() and a custom cast?
|
||||||
public:
|
public:
|
||||||
ScanTarget(GLuint target_framebuffer = 0, float output_gamma = 2.2f);
|
ScanTarget(GLuint target_framebuffer = 0, float output_gamma = 2.2f);
|
||||||
~ScanTarget();
|
~ScanTarget();
|
||||||
|
@ -489,38 +489,40 @@ std::unique_ptr<Shader> ScanTarget::conversion_shader() const {
|
|||||||
std::unique_ptr<Shader> ScanTarget::composition_shader() const {
|
std::unique_ptr<Shader> ScanTarget::composition_shader() const {
|
||||||
const auto modals = BufferingScanTarget::modals();
|
const auto modals = BufferingScanTarget::modals();
|
||||||
const std::string vertex_shader =
|
const std::string vertex_shader =
|
||||||
"#version 150\n"
|
R"x(#version 150
|
||||||
|
|
||||||
"in float startDataX;"
|
in float startDataX;
|
||||||
"in float startClock;"
|
in float startClock;
|
||||||
|
|
||||||
"in float endDataX;"
|
in float endDataX;
|
||||||
"in float endClock;"
|
in float endClock;
|
||||||
|
|
||||||
"in float dataY;"
|
in float dataY;
|
||||||
"in float lineY;"
|
in float lineY;
|
||||||
|
|
||||||
"out vec2 textureCoordinate;"
|
out vec2 textureCoordinate;
|
||||||
"uniform usampler2D textureName;"
|
uniform usampler2D textureName;
|
||||||
|
|
||||||
"void main(void) {"
|
void main(void) {
|
||||||
"float lateral = float(gl_VertexID & 1);"
|
float lateral = float(gl_VertexID & 1);
|
||||||
"float longitudinal = float((gl_VertexID & 2) >> 1);"
|
float longitudinal = float((gl_VertexID & 2) >> 1);
|
||||||
|
|
||||||
"textureCoordinate = vec2(mix(startDataX, endDataX, lateral), dataY + 0.5) / textureSize(textureName, 0);"
|
textureCoordinate = vec2(mix(startDataX, endDataX, lateral), dataY + 0.5) / textureSize(textureName, 0);
|
||||||
"vec2 eyePosition = vec2(mix(startClock, endClock, lateral), lineY + longitudinal) / vec2(2048.0, 2048.0);"
|
vec2 eyePosition = vec2(mix(startClock, endClock, lateral), lineY + longitudinal) / vec2(2048.0, 2048.0);
|
||||||
"gl_Position = vec4(eyePosition*2.0 - vec2(1.0), 0.0, 1.0);"
|
gl_Position = vec4(eyePosition*2.0 - vec2(1.0), 0.0, 1.0);
|
||||||
"}";
|
}
|
||||||
|
)x";
|
||||||
|
|
||||||
std::string fragment_shader =
|
std::string fragment_shader =
|
||||||
"#version 150\n"
|
R"x(#version 150
|
||||||
|
|
||||||
"out vec4 fragColour;"
|
out vec4 fragColour;
|
||||||
"in vec2 textureCoordinate;"
|
in vec2 textureCoordinate;
|
||||||
|
|
||||||
"uniform usampler2D textureName;"
|
uniform usampler2D textureName;
|
||||||
|
|
||||||
"void main(void) {";
|
void main(void) {
|
||||||
|
)x";
|
||||||
|
|
||||||
switch(modals.input_data_type) {
|
switch(modals.input_data_type) {
|
||||||
case InputDataType::Luminance1:
|
case InputDataType::Luminance1:
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
#ifndef Outputs_Display_ScanTarget_h
|
#ifndef Outputs_Display_ScanTarget_h
|
||||||
#define Outputs_Display_ScanTarget_h
|
#define Outputs_Display_ScanTarget_h
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include "../ClockReceiver/TimeTypes.hpp"
|
#include "../ClockReceiver/TimeTypes.hpp"
|
||||||
@ -53,6 +54,9 @@ enum class DisplayType {
|
|||||||
|
|
||||||
/*!
|
/*!
|
||||||
Enumerates the potential formats of input data.
|
Enumerates the potential formats of input data.
|
||||||
|
|
||||||
|
All types are designed to be 1, 2 or 4 bytes per pixel; this hopefully creates appropriate alignment
|
||||||
|
on all formats.
|
||||||
*/
|
*/
|
||||||
enum class InputDataType {
|
enum class InputDataType {
|
||||||
|
|
||||||
@ -72,8 +76,10 @@ enum class InputDataType {
|
|||||||
// of a colour subcarrier. So they can be used to generate a luminance signal,
|
// of a colour subcarrier. So they can be used to generate a luminance signal,
|
||||||
// or an s-video pipeline.
|
// or an s-video pipeline.
|
||||||
|
|
||||||
Luminance8Phase8, // 2 bytes/pixel; first is luminance, second is phase.
|
Luminance8Phase8, // 2 bytes/pixel; first is luminance, second is phase
|
||||||
// Phase is encoded on a 192-unit circle; anything
|
// of a cosine wave.
|
||||||
|
//
|
||||||
|
// Phase is encoded on a 128-unit circle; anything
|
||||||
// greater than 192 implies that the colour part of
|
// greater than 192 implies that the colour part of
|
||||||
// the signal should be omitted.
|
// the signal should be omitted.
|
||||||
|
|
||||||
@ -86,7 +92,8 @@ enum class InputDataType {
|
|||||||
Red8Green8Blue8, // 4 bytes/pixel; first is red, second is green, third is blue, fourth is vacant.
|
Red8Green8Blue8, // 4 bytes/pixel; first is red, second is green, third is blue, fourth is vacant.
|
||||||
};
|
};
|
||||||
|
|
||||||
inline size_t size_for_data_type(InputDataType data_type) {
|
/// @returns the number of bytes per sample for data of type @c data_type.
|
||||||
|
constexpr inline size_t size_for_data_type(InputDataType data_type) {
|
||||||
switch(data_type) {
|
switch(data_type) {
|
||||||
case InputDataType::Luminance1:
|
case InputDataType::Luminance1:
|
||||||
case InputDataType::Luminance8:
|
case InputDataType::Luminance8:
|
||||||
@ -107,7 +114,28 @@ inline size_t size_for_data_type(InputDataType data_type) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline DisplayType natural_display_type_for_data_type(InputDataType data_type) {
|
/// @returns @c true if this data type presents normalised data, i.e. each byte holds a
|
||||||
|
/// value in the range [0, 255] representing a real number in the range [0.0, 1.0]; @c false otherwise.
|
||||||
|
constexpr inline size_t data_type_is_normalised(InputDataType data_type) {
|
||||||
|
switch(data_type) {
|
||||||
|
case InputDataType::Luminance8:
|
||||||
|
case InputDataType::Luminance8Phase8:
|
||||||
|
case InputDataType::Red8Green8Blue8:
|
||||||
|
case InputDataType::PhaseLinkedLuminance8:
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
case InputDataType::Luminance1:
|
||||||
|
case InputDataType::Red1Green1Blue1:
|
||||||
|
case InputDataType::Red2Green2Blue2:
|
||||||
|
case InputDataType::Red4Green4Blue4:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @returns The 'natural' display type for data of type @c data_type. The natural display is whichever would
|
||||||
|
/// display it with the least number of conversions. Caveat: a colour display is assumed for pure-composite data types.
|
||||||
|
constexpr inline DisplayType natural_display_type_for_data_type(InputDataType data_type) {
|
||||||
switch(data_type) {
|
switch(data_type) {
|
||||||
default:
|
default:
|
||||||
case InputDataType::Luminance1:
|
case InputDataType::Luminance1:
|
||||||
@ -126,6 +154,34 @@ inline DisplayType natural_display_type_for_data_type(InputDataType data_type) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @returns A 3x3 matrix in row-major order to convert from @c colour_space to RGB.
|
||||||
|
inline std::array<float, 9> to_rgb_matrix(ColourSpace colour_space) {
|
||||||
|
const std::array<float, 9> yiq_to_rgb = {1.0f, 1.0f, 1.0f, 0.956f, -0.272f, -1.106f, 0.621f, -0.647f, 1.703f};
|
||||||
|
const std::array<float, 9> yuv_to_rgb = {1.0f, 1.0f, 1.0f, 0.0f, -0.39465f, 2.03211f, 1.13983f, -0.58060f, 0.0f};
|
||||||
|
|
||||||
|
switch(colour_space) {
|
||||||
|
case ColourSpace::YIQ: return yiq_to_rgb;
|
||||||
|
case ColourSpace::YUV: return yuv_to_rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should be unreachable.
|
||||||
|
return std::array<float, 9>{};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @returns A 3x3 matrix in row-major order to convert to @c colour_space to RGB.
|
||||||
|
inline std::array<float, 9> from_rgb_matrix(ColourSpace colour_space) {
|
||||||
|
const std::array<float, 9> rgb_to_yiq = {0.299f, 0.596f, 0.211f, 0.587f, -0.274f, -0.523f, 0.114f, -0.322f, 0.312f};
|
||||||
|
const std::array<float, 9> rgb_to_yuv = {0.299f, -0.14713f, 0.615f, 0.587f, -0.28886f, -0.51499f, 0.114f, 0.436f, -0.10001f};
|
||||||
|
|
||||||
|
switch(colour_space) {
|
||||||
|
case ColourSpace::YIQ: return rgb_to_yiq;
|
||||||
|
case ColourSpace::YUV: return rgb_to_yuv;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should be unreachable.
|
||||||
|
return std::array<float, 9>{};
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Provides an abstract target for 'scans' i.e. continuous sweeps of output data,
|
Provides an abstract target for 'scans' i.e. continuous sweeps of output data,
|
||||||
which are identified by 2d start and end coordinates, and the PCM-sampled data
|
which are identified by 2d start and end coordinates, and the PCM-sampled data
|
||||||
@ -325,22 +381,22 @@ struct ScanTarget {
|
|||||||
|
|
||||||
struct ScanStatus {
|
struct ScanStatus {
|
||||||
/// The current (prediced) length of a field (including retrace).
|
/// The current (prediced) length of a field (including retrace).
|
||||||
Time::Seconds field_duration;
|
Time::Seconds field_duration = 0.0;
|
||||||
/// The difference applied to the field_duration estimate during the last field.
|
/// The difference applied to the field_duration estimate during the last field.
|
||||||
Time::Seconds field_duration_gradient;
|
Time::Seconds field_duration_gradient = 0.0;
|
||||||
/// The amount of time this device spends in retrace.
|
/// The amount of time this device spends in retrace.
|
||||||
Time::Seconds retrace_duration;
|
Time::Seconds retrace_duration = 0.0;
|
||||||
/// The distance into the current field, from a small negative amount (in retrace) through
|
/// The distance into the current field, from a small negative amount (in retrace) through
|
||||||
/// 0 (start of visible area field) to 1 (end of field).
|
/// 0 (start of visible area field) to 1 (end of field).
|
||||||
///
|
///
|
||||||
/// This will increase monotonically, being a measure
|
/// This will increase monotonically, being a measure
|
||||||
/// of the current vertical position — i.e. if current_position = 0.8 then a caller can
|
/// of the current vertical position — i.e. if current_position = 0.8 then a caller can
|
||||||
/// conclude that the top 80% of the visible part of the display has been painted.
|
/// conclude that the top 80% of the visible part of the display has been painted.
|
||||||
float current_position;
|
float current_position = 0.0f;
|
||||||
/// The total number of hsyncs so far encountered;
|
/// The total number of hsyncs so far encountered;
|
||||||
int hsync_count;
|
int hsync_count = 0;
|
||||||
/// @c true if retrace is currently going on; @c false otherwise.
|
/// @c true if retrace is currently going on; @c false otherwise.
|
||||||
bool is_in_retrace;
|
bool is_in_retrace = false;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
@returns this ScanStatus, with time-relative fields scaled by dividing them by @c dividend.
|
@returns this ScanStatus, with time-relative fields scaled by dividing them by @c dividend.
|
||||||
|
@ -11,12 +11,6 @@
|
|||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
// If enabled, this uses the producer lock to cover both production and consumption
|
|
||||||
// rather than attempting to proceed lockfree. This is primarily for diagnostic purposes;
|
|
||||||
// it allows empirical exploration of whether the logical and memory barriers that are
|
|
||||||
// meant to mediate things between the read pointers and the submit pointers are functioning.
|
|
||||||
#define ONE_BIG_LOCK
|
|
||||||
|
|
||||||
#define TextureAddressGetY(v) uint16_t((v) >> 11)
|
#define TextureAddressGetY(v) uint16_t((v) >> 11)
|
||||||
#define TextureAddressGetX(v) uint16_t((v) & 0x7ff)
|
#define TextureAddressGetX(v) uint16_t((v) & 0x7ff)
|
||||||
#define TextureSub(a, b) (((a) - (b)) & 0x3fffff)
|
#define TextureSub(a, b) (((a) - (b)) & 0x3fffff)
|
||||||
@ -64,13 +58,18 @@ uint8_t *BufferingScanTarget::begin_data(size_t required_length, size_t required
|
|||||||
end_x = aligned_start_x + uint16_t(1 + required_length);
|
end_x = aligned_start_x + uint16_t(1 + required_length);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check whether that steps over the read pointer.
|
// Check whether that steps over the read pointer; if so then the final address will be closer
|
||||||
|
// to the write pointer than the old.
|
||||||
const auto end_address = TextureAddress(end_x, output_y);
|
const auto end_address = TextureAddress(end_x, output_y);
|
||||||
const auto read_pointers = read_pointers_.load(std::memory_order::memory_order_relaxed);
|
const auto read_pointers = read_pointers_.load(std::memory_order::memory_order_relaxed);
|
||||||
|
|
||||||
const auto end_distance = TextureSub(end_address, read_pointers.write_area);
|
const auto end_distance = TextureSub(end_address, read_pointers.write_area);
|
||||||
const auto previous_distance = TextureSub(write_pointers_.write_area, read_pointers.write_area);
|
const auto previous_distance = TextureSub(write_pointers_.write_area, read_pointers.write_area);
|
||||||
|
|
||||||
|
// Perform a quick sanity check.
|
||||||
|
assert(end_distance >= 0);
|
||||||
|
assert(previous_distance >= 0);
|
||||||
|
|
||||||
// If allocating this would somehow make the write pointer back away from the read pointer,
|
// If allocating this would somehow make the write pointer back away from the read pointer,
|
||||||
// there must not be enough space left.
|
// there must not be enough space left.
|
||||||
if(end_distance < previous_distance) {
|
if(end_distance < previous_distance) {
|
||||||
@ -79,6 +78,7 @@ uint8_t *BufferingScanTarget::begin_data(size_t required_length, size_t required
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Everything checks out, note expectation of a future end_data and return the pointer.
|
// Everything checks out, note expectation of a future end_data and return the pointer.
|
||||||
|
assert(!data_is_allocated_);
|
||||||
data_is_allocated_ = true;
|
data_is_allocated_ = true;
|
||||||
vended_write_area_pointer_ = write_pointers_.write_area = TextureAddress(aligned_start_x, output_y);
|
vended_write_area_pointer_ = write_pointers_.write_area = TextureAddress(aligned_start_x, output_y);
|
||||||
|
|
||||||
@ -131,31 +131,42 @@ Outputs::Display::ScanTarget::Scan *BufferingScanTarget::begin_scan() {
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto result = &scan_buffer_[write_pointers_.scan_buffer];
|
const auto result = &scan_buffer_[write_pointers_.scan];
|
||||||
const auto read_pointers = read_pointers_.load(std::memory_order::memory_order_relaxed);
|
const auto read_pointers = read_pointers_.load(std::memory_order::memory_order_relaxed);
|
||||||
|
|
||||||
// Advance the pointer.
|
// Advance the pointer.
|
||||||
const auto next_write_pointer = decltype(write_pointers_.scan_buffer)((write_pointers_.scan_buffer + 1) % scan_buffer_size_);
|
const auto next_write_pointer = decltype(write_pointers_.scan)((write_pointers_.scan + 1) % scan_buffer_size_);
|
||||||
|
|
||||||
// Check whether that's too many.
|
// Check whether that's too many.
|
||||||
if(next_write_pointer == read_pointers.scan_buffer) {
|
if(next_write_pointer == read_pointers.scan) {
|
||||||
allocation_has_failed_ = true;
|
allocation_has_failed_ = true;
|
||||||
vended_scan_ = nullptr;
|
vended_scan_ = nullptr;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
write_pointers_.scan_buffer = next_write_pointer;
|
write_pointers_.scan = next_write_pointer;
|
||||||
++provided_scans_;
|
++provided_scans_;
|
||||||
|
|
||||||
// Fill in extra OpenGL-specific details.
|
// Fill in extra OpenGL-specific details.
|
||||||
result->line = write_pointers_.line;
|
result->line = write_pointers_.line;
|
||||||
|
|
||||||
vended_scan_ = result;
|
vended_scan_ = result;
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
assert(!scan_is_ongoing_);
|
||||||
|
scan_is_ongoing_ = true;
|
||||||
|
#endif
|
||||||
|
|
||||||
return &result->scan;
|
return &result->scan;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BufferingScanTarget::end_scan() {
|
void BufferingScanTarget::end_scan() {
|
||||||
std::lock_guard lock_guard(producer_mutex_);
|
std::lock_guard lock_guard(producer_mutex_);
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
assert(scan_is_ongoing_);
|
||||||
|
scan_is_ongoing_ = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
// Complete the scan only if one is afoot.
|
// Complete the scan only if one is afoot.
|
||||||
if(vended_scan_) {
|
if(vended_scan_) {
|
||||||
vended_scan_->data_y = TextureAddressGetY(vended_write_area_pointer_);
|
vended_scan_->data_y = TextureAddressGetY(vended_write_area_pointer_);
|
||||||
@ -178,7 +189,7 @@ void BufferingScanTarget::announce(Event event, bool is_visible, const Outputs::
|
|||||||
// The previous-frame-is-complete flag is subject to a two-slot queue because
|
// The previous-frame-is-complete flag is subject to a two-slot queue because
|
||||||
// measurement for *this* frame needs to begin now, meaning that the previous
|
// measurement for *this* frame needs to begin now, meaning that the previous
|
||||||
// result needs to be put somewhere — it'll be attached to the first successful
|
// result needs to be put somewhere — it'll be attached to the first successful
|
||||||
// line output.
|
// line output, whenever that comes.
|
||||||
is_first_in_frame_ = true;
|
is_first_in_frame_ = true;
|
||||||
previous_frame_was_complete_ = frame_is_complete_;
|
previous_frame_was_complete_ = frame_is_complete_;
|
||||||
frame_is_complete_ = true;
|
frame_is_complete_ = true;
|
||||||
@ -188,18 +199,18 @@ void BufferingScanTarget::announce(Event event, bool is_visible, const Outputs::
|
|||||||
if(output_is_visible_ == is_visible) return;
|
if(output_is_visible_ == is_visible) return;
|
||||||
output_is_visible_ = is_visible;
|
output_is_visible_ = is_visible;
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
assert(!scan_is_ongoing_);
|
||||||
|
#endif
|
||||||
|
|
||||||
if(is_visible) {
|
if(is_visible) {
|
||||||
const auto read_pointers = read_pointers_.load(std::memory_order::memory_order_relaxed);
|
const auto read_pointers = read_pointers_.load(std::memory_order::memory_order_relaxed);
|
||||||
|
|
||||||
// Attempt to allocate a new line, noting allocation failure if necessary.
|
// Attempt to allocate a new line, noting allocation success or failure.
|
||||||
const auto next_line = uint16_t((write_pointers_.line + 1) % line_buffer_size_);
|
const auto next_line = uint16_t((write_pointers_.line + 1) % line_buffer_size_);
|
||||||
if(next_line == read_pointers.line) {
|
allocation_has_failed_ = next_line == read_pointers.line;
|
||||||
allocation_has_failed_ = true;
|
|
||||||
}
|
|
||||||
provided_scans_ = 0;
|
|
||||||
|
|
||||||
// If there was space for a new line, establish its start.
|
|
||||||
if(!allocation_has_failed_) {
|
if(!allocation_has_failed_) {
|
||||||
|
// If there was space for a new line, establish its start and reset the count of provided scans.
|
||||||
Line &active_line = line_buffer_[size_t(write_pointers_.line)];
|
Line &active_line = line_buffer_[size_t(write_pointers_.line)];
|
||||||
active_line.end_points[0].x = location.x;
|
active_line.end_points[0].x = location.x;
|
||||||
active_line.end_points[0].y = location.y;
|
active_line.end_points[0].y = location.y;
|
||||||
@ -207,16 +218,24 @@ void BufferingScanTarget::announce(Event event, bool is_visible, const Outputs::
|
|||||||
active_line.end_points[0].composite_angle = location.composite_angle;
|
active_line.end_points[0].composite_angle = location.composite_angle;
|
||||||
active_line.line = write_pointers_.line;
|
active_line.line = write_pointers_.line;
|
||||||
active_line.composite_amplitude = composite_amplitude;
|
active_line.composite_amplitude = composite_amplitude;
|
||||||
|
|
||||||
|
provided_scans_ = 0;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Commit the most recent line only if any scans fell on it and all allocation was successful.
|
// Commit the most recent line only if any scans fell on it and all allocation was successful.
|
||||||
if(!allocation_has_failed_ && provided_scans_) {
|
if(!allocation_has_failed_ && provided_scans_) {
|
||||||
|
const auto submit_pointers = submit_pointers_.load(std::memory_order::memory_order_relaxed);
|
||||||
|
|
||||||
// Store metadata.
|
// Store metadata.
|
||||||
LineMetadata &metadata = line_metadata_buffer_[size_t(write_pointers_.line)];
|
LineMetadata &metadata = line_metadata_buffer_[size_t(write_pointers_.line)];
|
||||||
metadata.is_first_in_frame = is_first_in_frame_;
|
metadata.is_first_in_frame = is_first_in_frame_;
|
||||||
metadata.previous_frame_was_complete = previous_frame_was_complete_;
|
metadata.previous_frame_was_complete = previous_frame_was_complete_;
|
||||||
|
metadata.first_scan = submit_pointers.scan;
|
||||||
is_first_in_frame_ = false;
|
is_first_in_frame_ = false;
|
||||||
|
|
||||||
|
// Sanity check.
|
||||||
|
assert(((metadata.first_scan + size_t(provided_scans_)) % scan_buffer_size_) == write_pointers_.scan);
|
||||||
|
|
||||||
// Store actual line data.
|
// Store actual line data.
|
||||||
Line &active_line = line_buffer_[size_t(write_pointers_.line)];
|
Line &active_line = line_buffer_[size_t(write_pointers_.line)];
|
||||||
active_line.end_points[1].x = location.x;
|
active_line.end_points[1].x = location.x;
|
||||||
@ -228,6 +247,7 @@ void BufferingScanTarget::announce(Event event, bool is_visible, const Outputs::
|
|||||||
write_pointers_.line = uint16_t((write_pointers_.line + 1) % line_buffer_size_);
|
write_pointers_.line = uint16_t((write_pointers_.line + 1) % line_buffer_size_);
|
||||||
|
|
||||||
// Update the submit pointers with all lines, scans and data written during this line.
|
// Update the submit pointers with all lines, scans and data written during this line.
|
||||||
|
std::atomic_thread_fence(std::memory_order::memory_order_release);
|
||||||
submit_pointers_.store(write_pointers_, std::memory_order::memory_order_release);
|
submit_pointers_.store(write_pointers_, std::memory_order::memory_order_release);
|
||||||
} else {
|
} else {
|
||||||
// Something failed, or there was nothing on the line anyway, so reset all pointers to where they
|
// Something failed, or there was nothing on the line anyway, so reset all pointers to where they
|
||||||
@ -236,9 +256,8 @@ void BufferingScanTarget::announce(Event event, bool is_visible, const Outputs::
|
|||||||
frame_is_complete_ &= !allocation_has_failed_;
|
frame_is_complete_ &= !allocation_has_failed_;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset the allocation-has-failed flag for the next line
|
// Don't permit anything to be allocated on invisible areas.
|
||||||
// and mark no line as active.
|
allocation_has_failed_ = true;
|
||||||
allocation_has_failed_ = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,6 +267,9 @@ void BufferingScanTarget::will_change_owner() {
|
|||||||
std::lock_guard lock_guard(producer_mutex_);
|
std::lock_guard lock_guard(producer_mutex_);
|
||||||
allocation_has_failed_ = true;
|
allocation_has_failed_ = true;
|
||||||
vended_scan_ = nullptr;
|
vended_scan_ = nullptr;
|
||||||
|
#ifdef DEBUG
|
||||||
|
data_is_allocated_ = false;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
const Outputs::Display::Metrics &BufferingScanTarget::display_metrics() {
|
const Outputs::Display::Metrics &BufferingScanTarget::display_metrics() {
|
||||||
@ -255,16 +277,8 @@ const Outputs::Display::Metrics &BufferingScanTarget::display_metrics() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BufferingScanTarget::set_write_area(uint8_t *base) {
|
void BufferingScanTarget::set_write_area(uint8_t *base) {
|
||||||
// This is a bit of a hack. This call needs the producer mutex and should be
|
|
||||||
// safe to call from a @c perform block in order to support all potential consumers.
|
|
||||||
// But the temporary hack of ONE_BIG_LOCK then implies that either I need a recursive
|
|
||||||
// mutex, or I have to make a coupling assumption about my caller. I've done the latter,
|
|
||||||
// because ONE_BIG_LOCK is really really meant to be temporary. I hope.
|
|
||||||
#ifndef ONE_BIG_LOCK
|
|
||||||
std::lock_guard lock_guard(producer_mutex_);
|
std::lock_guard lock_guard(producer_mutex_);
|
||||||
#endif
|
|
||||||
write_area_ = base;
|
write_area_ = base;
|
||||||
data_type_size_ = Outputs::Display::size_for_data_type(modals_.input_data_type);
|
|
||||||
write_pointers_ = submit_pointers_ = read_pointers_ = PointerSet();
|
write_pointers_ = submit_pointers_ = read_pointers_ = PointerSet();
|
||||||
allocation_has_failed_ = true;
|
allocation_has_failed_ = true;
|
||||||
vended_scan_ = nullptr;
|
vended_scan_ = nullptr;
|
||||||
@ -285,37 +299,52 @@ void BufferingScanTarget::set_modals(Modals modals) {
|
|||||||
|
|
||||||
// MARK: - Consumer.
|
// MARK: - Consumer.
|
||||||
|
|
||||||
void BufferingScanTarget::perform(const std::function<void(const OutputArea &)> &function) {
|
BufferingScanTarget::OutputArea BufferingScanTarget::get_output_area() {
|
||||||
#ifdef ONE_BIG_LOCK
|
|
||||||
std::lock_guard lock_guard(producer_mutex_);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// The area to draw is that between the read pointers, representing wherever reading
|
// The area to draw is that between the read pointers, representing wherever reading
|
||||||
// last stopped, and the submit pointers, representing all the new data that has been
|
// last stopped, and the submit pointers, representing all the new data that has been
|
||||||
// cleared for submission.
|
// cleared for submission.
|
||||||
const auto submit_pointers = submit_pointers_.load(std::memory_order::memory_order_acquire);
|
const auto submit_pointers = submit_pointers_.load(std::memory_order::memory_order_acquire);
|
||||||
const auto read_pointers = read_pointers_.load(std::memory_order::memory_order_relaxed);
|
const auto read_ahead_pointers = read_ahead_pointers_.load(std::memory_order::memory_order_relaxed);
|
||||||
|
std::atomic_thread_fence(std::memory_order::memory_order_acquire);
|
||||||
|
|
||||||
OutputArea area;
|
OutputArea area;
|
||||||
|
|
||||||
area.start.line = read_pointers.line;
|
area.start.line = read_ahead_pointers.line;
|
||||||
area.end.line = submit_pointers.line;
|
area.end.line = submit_pointers.line;
|
||||||
|
|
||||||
area.start.scan = read_pointers.scan_buffer;
|
area.start.scan = read_ahead_pointers.scan;
|
||||||
area.end.scan = submit_pointers.scan_buffer;
|
area.end.scan = submit_pointers.scan;
|
||||||
|
|
||||||
area.start.write_area_x = TextureAddressGetX(read_pointers.write_area);
|
area.start.write_area_x = TextureAddressGetX(read_ahead_pointers.write_area);
|
||||||
area.start.write_area_y = TextureAddressGetY(read_pointers.write_area);
|
area.start.write_area_y = TextureAddressGetY(read_ahead_pointers.write_area);
|
||||||
area.end.write_area_x = TextureAddressGetX(submit_pointers.write_area);
|
area.end.write_area_x = TextureAddressGetX(submit_pointers.write_area);
|
||||||
area.end.write_area_y = TextureAddressGetY(submit_pointers.write_area);
|
area.end.write_area_y = TextureAddressGetY(submit_pointers.write_area);
|
||||||
|
|
||||||
// Perform only while holding the is_updating lock.
|
// Update the read-ahead pointers.
|
||||||
while(is_updating_.test_and_set(std::memory_order_acquire));
|
read_ahead_pointers_.store(submit_pointers, std::memory_order::memory_order_relaxed);
|
||||||
function(area);
|
|
||||||
is_updating_.clear(std::memory_order_release);
|
|
||||||
|
|
||||||
// Update the read pointers.
|
#ifndef NDEBUG
|
||||||
read_pointers_.store(submit_pointers, std::memory_order::memory_order_relaxed);
|
area.counter = output_area_counter_;
|
||||||
|
++output_area_counter_;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return area;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BufferingScanTarget::complete_output_area(const OutputArea &area) {
|
||||||
|
// TODO: check that this is the expected next area if in DEBUG mode.
|
||||||
|
|
||||||
|
PointerSet new_read_pointers;
|
||||||
|
new_read_pointers.line = uint16_t(area.end.line);
|
||||||
|
new_read_pointers.scan = uint16_t(area.end.scan);
|
||||||
|
new_read_pointers.write_area = TextureAddress(area.end.write_area_x, area.end.write_area_y);
|
||||||
|
read_pointers_.store(new_read_pointers, std::memory_order::memory_order_relaxed);
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
// This will fire if the caller is announcing completed output areas out of order.
|
||||||
|
assert(area.counter == output_area_next_returned_);
|
||||||
|
++output_area_next_returned_;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void BufferingScanTarget::perform(const std::function<void(void)> &function) {
|
void BufferingScanTarget::perform(const std::function<void(void)> &function) {
|
||||||
@ -340,6 +369,13 @@ const Outputs::Display::ScanTarget::Modals *BufferingScanTarget::new_modals() {
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
modals_are_dirty_ = false;
|
modals_are_dirty_ = false;
|
||||||
|
|
||||||
|
// MAJOR SHARP EDGE HERE: assume that because the new_modals have been fetched then the caller will
|
||||||
|
// now ensure their texture buffer is appropriate. They might provide a new pointer and might now.
|
||||||
|
// But either way it's now appropriate to start treating the data size as implied by the data type.
|
||||||
|
std::lock_guard lock_guard(producer_mutex_);
|
||||||
|
data_type_size_ = Outputs::Display::size_for_data_type(modals_.input_data_type);
|
||||||
|
|
||||||
return &modals_;
|
return &modals_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +39,6 @@ class BufferingScanTarget: public Outputs::Display::ScanTarget {
|
|||||||
/*! @returns The DisplayMetrics object that this ScanTarget has been providing with announcements and draw overages. */
|
/*! @returns The DisplayMetrics object that this ScanTarget has been providing with announcements and draw overages. */
|
||||||
const Metrics &display_metrics();
|
const Metrics &display_metrics();
|
||||||
|
|
||||||
protected:
|
|
||||||
static constexpr int WriteAreaWidth = 2048;
|
static constexpr int WriteAreaWidth = 2048;
|
||||||
static constexpr int WriteAreaHeight = 2048;
|
static constexpr int WriteAreaHeight = 2048;
|
||||||
|
|
||||||
@ -49,13 +48,15 @@ class BufferingScanTarget: public Outputs::Display::ScanTarget {
|
|||||||
// It is the subclass's responsibility to post timings.
|
// It is the subclass's responsibility to post timings.
|
||||||
Metrics display_metrics_;
|
Metrics display_metrics_;
|
||||||
|
|
||||||
// Extends the definition of a Scan to include two extra fields,
|
/// Extends the definition of a Scan to include two extra fields,
|
||||||
// completing this scan's source data and destination locations.
|
/// completing this scan's source data and destination locations.
|
||||||
struct Scan {
|
struct Scan {
|
||||||
Outputs::Display::ScanTarget::Scan scan;
|
Outputs::Display::ScanTarget::Scan scan;
|
||||||
|
|
||||||
/// Stores the y coordinate for this scan's data within the write area texture.
|
/// Stores the y coordinate for this scan's data within the write area texture.
|
||||||
/// Use this plus the scan's endpoints' data_offsets to locate this data in 2d.
|
/// Use this plus the scan's endpoints' data_offsets to locate this data in 2d.
|
||||||
|
/// Note that the data_offsets will have been adjusted to be relative to the line
|
||||||
|
/// they fall within, not the data allocation.
|
||||||
uint16_t data_y;
|
uint16_t data_y;
|
||||||
/// Stores the y coordinate assigned to this scan within the intermediate buffers.
|
/// Stores the y coordinate assigned to this scan within the intermediate buffers.
|
||||||
/// Use this plus this scan's endpoints' x locations to determine where to composite
|
/// Use this plus this scan's endpoints' x locations to determine where to composite
|
||||||
@ -69,11 +70,12 @@ class BufferingScanTarget: public Outputs::Display::ScanTarget {
|
|||||||
struct Line {
|
struct Line {
|
||||||
struct EndPoint {
|
struct EndPoint {
|
||||||
uint16_t x, y;
|
uint16_t x, y;
|
||||||
uint16_t cycles_since_end_of_horizontal_retrace;
|
|
||||||
int16_t composite_angle;
|
int16_t composite_angle;
|
||||||
|
uint16_t cycles_since_end_of_horizontal_retrace;
|
||||||
} end_points[2];
|
} end_points[2];
|
||||||
uint16_t line;
|
|
||||||
uint8_t composite_amplitude;
|
uint8_t composite_amplitude;
|
||||||
|
uint16_t line;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Provides additional metadata about lines; this is separate because it's unlikely to be of
|
/// Provides additional metadata about lines; this is separate because it's unlikely to be of
|
||||||
@ -86,6 +88,8 @@ class BufferingScanTarget: public Outputs::Display::ScanTarget {
|
|||||||
/// from a frame if performance problems mean that the emulated machine is running
|
/// from a frame if performance problems mean that the emulated machine is running
|
||||||
/// more quickly than complete frames can be generated.
|
/// more quickly than complete frames can be generated.
|
||||||
bool previous_frame_was_complete;
|
bool previous_frame_was_complete;
|
||||||
|
/// The index of the first scan that will appear on this line.
|
||||||
|
size_t first_scan;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Sets the area of memory to use as a scan buffer.
|
/// Sets the area of memory to use as a scan buffer.
|
||||||
@ -112,6 +116,10 @@ class BufferingScanTarget: public Outputs::Display::ScanTarget {
|
|||||||
/// (iii) the number of lines that have been completed.
|
/// (iii) the number of lines that have been completed.
|
||||||
///
|
///
|
||||||
/// New write areas and scans are exposed only upon completion of the corresponding lines.
|
/// New write areas and scans are exposed only upon completion of the corresponding lines.
|
||||||
|
/// The values indicated by the start point are the first that should be drawn. Those indicated
|
||||||
|
/// by the end point are one after the final that should be drawn.
|
||||||
|
///
|
||||||
|
/// So e.g. start.scan = 23, end.scan = 24 means draw a single scan, index 23.
|
||||||
struct OutputArea {
|
struct OutputArea {
|
||||||
struct Endpoint {
|
struct Endpoint {
|
||||||
int write_area_x, write_area_y;
|
int write_area_x, write_area_y;
|
||||||
@ -120,14 +128,32 @@ class BufferingScanTarget: public Outputs::Display::ScanTarget {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Endpoint start, end;
|
Endpoint start, end;
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
size_t counter;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Gets the current range of content that has been posted but not yet returned by
|
||||||
|
/// a previous call to get_output_area().
|
||||||
|
///
|
||||||
|
/// Does not require the caller to be within a @c perform block.
|
||||||
|
OutputArea get_output_area();
|
||||||
|
|
||||||
|
/// Announces that the output area has now completed output, freeing up its memory for
|
||||||
|
/// further modification.
|
||||||
|
///
|
||||||
|
/// It is the caller's responsibility to ensure that the areas passed to complete_output_area
|
||||||
|
/// are those from get_output_area and are marked as completed in the same order that
|
||||||
|
/// they were originally provided.
|
||||||
|
///
|
||||||
|
/// Does not require the caller to be within a @c perform block.
|
||||||
|
void complete_output_area(const OutputArea &);
|
||||||
|
|
||||||
/// Performs @c action ensuring that no other @c perform actions, or any
|
/// Performs @c action ensuring that no other @c perform actions, or any
|
||||||
/// change to modals, occurs simultaneously.
|
/// change to modals, occurs simultaneously.
|
||||||
void perform(const std::function<void(void)> &action);
|
void perform(const std::function<void(void)> &action);
|
||||||
|
|
||||||
/// Acts as per void(void) @c perform but also dequeues all latest available video output.
|
|
||||||
void perform(const std::function<void(const OutputArea &)> &);
|
|
||||||
|
|
||||||
/// @returns new Modals if any have been set since the last call to get_new_modals().
|
/// @returns new Modals if any have been set since the last call to get_new_modals().
|
||||||
/// The caller must be within a @c perform block.
|
/// The caller must be within a @c perform block.
|
||||||
const Modals *new_modals();
|
const Modals *new_modals();
|
||||||
@ -185,7 +211,7 @@ class BufferingScanTarget: public Outputs::Display::ScanTarget {
|
|||||||
int32_t write_area = 1;
|
int32_t write_area = 1;
|
||||||
|
|
||||||
// Points into the scan buffer.
|
// Points into the scan buffer.
|
||||||
uint16_t scan_buffer = 0;
|
uint16_t scan = 0;
|
||||||
|
|
||||||
// Points into the line buffer.
|
// Points into the line buffer.
|
||||||
uint16_t line = 0;
|
uint16_t line = 0;
|
||||||
@ -199,6 +225,8 @@ class BufferingScanTarget: public Outputs::Display::ScanTarget {
|
|||||||
/// may run and is therefore used by both producer and consumer.
|
/// may run and is therefore used by both producer and consumer.
|
||||||
std::atomic<PointerSet> read_pointers_;
|
std::atomic<PointerSet> read_pointers_;
|
||||||
|
|
||||||
|
std::atomic<PointerSet> read_ahead_pointers_;
|
||||||
|
|
||||||
/// This is used as a spinlock to guard `perform` calls.
|
/// This is used as a spinlock to guard `perform` calls.
|
||||||
std::atomic_flag is_updating_;
|
std::atomic_flag is_updating_;
|
||||||
|
|
||||||
@ -226,6 +254,13 @@ class BufferingScanTarget: public Outputs::Display::ScanTarget {
|
|||||||
// from a call to @c get_new_modals.
|
// from a call to @c get_new_modals.
|
||||||
Modals modals_;
|
Modals modals_;
|
||||||
bool modals_are_dirty_ = false;
|
bool modals_are_dirty_ = false;
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
// Debug features; these amount to API validation.
|
||||||
|
bool scan_is_ongoing_ = false;
|
||||||
|
size_t output_area_counter_ = 0;
|
||||||
|
size_t output_area_next_returned_ = 0;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ Clock Signal ('CLK') is an emulator for tourists that seeks to be invisible. Use
|
|||||||
|
|
||||||
macOS and source releases are [hosted on GitHub](https://github.com/TomHarte/CLK/releases). For desktop Linux it is also available as a [Snap](https://snapcraft.io/clock-signal).
|
macOS and source releases are [hosted on GitHub](https://github.com/TomHarte/CLK/releases). For desktop Linux it is also available as a [Snap](https://snapcraft.io/clock-signal).
|
||||||
|
|
||||||
On the Mac it is a native Cocoa application; under Linux, BSD and other UNIXes and UNIX-alikes it can be built either with Qt or with SDL; the Qt build should be considered preliminary and is currently closely bound to X11 as Qt doesn't abstract game-like keyboard handling.
|
On the Mac it is a native Cocoa and Metal application; under Linux, BSD and other UNIXes and UNIX-alikes it uses OpenGL and can be built either with Qt or with SDL.
|
||||||
|
|
||||||
So its aims are:
|
So its aims are:
|
||||||
* single-click load of any piece of source media for any supported platform;
|
* single-click load of any piece of source media for any supported platform;
|
||||||
@ -17,6 +17,7 @@ It currently contains emulations of the:
|
|||||||
* Amstrad CPC;
|
* Amstrad CPC;
|
||||||
* Apple II/II+ and IIe;
|
* Apple II/II+ and IIe;
|
||||||
* Atari 2600;
|
* Atari 2600;
|
||||||
|
* Atari ST;
|
||||||
* ColecoVision;
|
* ColecoVision;
|
||||||
* Commodore Vic-20 (and Commodore 1540/1);
|
* Commodore Vic-20 (and Commodore 1540/1);
|
||||||
* Macintosh 512ke and Plus;
|
* Macintosh 512ke and Plus;
|
||||||
@ -25,8 +26,6 @@ It currently contains emulations of the:
|
|||||||
* Sega Master System; and
|
* Sega Master System; and
|
||||||
* Sinclair ZX80/81.
|
* Sinclair ZX80/81.
|
||||||
|
|
||||||
In addition, emulation of the Atari ST is experimental.
|
|
||||||
|
|
||||||
## Single-click Loading
|
## Single-click Loading
|
||||||
|
|
||||||
Through the combination of static analysis and runtime analysis, CLK seeks to be able automatically to select and configure the appropriate machine to run any provided disk, tape or ROM; to issue any commands necessary to run the software contained on the disk, tape or ROM; and to provide accelerated loading where feasible.
|
Through the combination of static analysis and runtime analysis, CLK seeks to be able automatically to select and configure the appropriate machine to run any provided disk, tape or ROM; to issue any commands necessary to run the software contained on the disk, tape or ROM; and to provide accelerated loading where feasible.
|
||||||
|
Loading…
Reference in New Issue
Block a user