1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-06 01:28:57 +00:00

Merge branch 'master' into HiRes

This commit is contained in:
Thomas Harte 2017-07-08 20:38:25 -04:00
commit 22389a5d2d
14 changed files with 189 additions and 45 deletions

View File

@ -259,7 +259,7 @@ template <class T> class MOS6560 {
if(this_state_ != output_state_) {
switch(output_state_) {
case State::Sync: crt_->output_sync(cycles_in_state_ * 4); break;
case State::ColourBurst: crt_->output_colour_burst(cycles_in_state_ * 4, (is_odd_frame_ || is_odd_line_) ? 128 : 0, 0); break;
case State::ColourBurst: crt_->output_colour_burst(cycles_in_state_ * 4, (is_odd_frame_ || is_odd_line_) ? 128 : 0); break;
case State::Border: output_border(cycles_in_state_ * 4); break;
case State::Pixels: crt_->output_data(cycles_in_state_ * 4, 1); break;
}

View File

@ -23,7 +23,9 @@ Machine::Machine() :
nmi_is_enabled_(false),
tape_player_(ZX8081ClockRate),
use_fast_tape_hack_(false),
tape_advance_delay_(0) {
tape_advance_delay_(0),
tape_is_automatically_playing_(false),
tape_is_playing_(false) {
set_clock_rate(ZX8081ClockRate);
tape_player_.set_motor_control(true);
clear_all_keys();
@ -55,7 +57,7 @@ int Machine::perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
if(is_zx81_) horizontal_counter_ %= 207;
if(!tape_advance_delay_) {
tape_player_.run_for_cycles(cycle.length);
if(tape_is_automatically_playing_ || tape_is_playing_) tape_player_.run_for_cycles(cycle.length);
} else {
tape_advance_delay_ = std::max(tape_advance_delay_ - cycle.length, 0);
}
@ -147,6 +149,9 @@ int Machine::perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
tape_player_.get_tape()->seek(time);
}
}
// Check for automatic tape control.
tape_is_automatically_playing_ = use_automatic_tape_motor_control_ && (address >= automatic_tape_motor_start_address_) && (address < automatic_tape_motor_end_address_);
is_opcode_read = true;
case CPU::Z80::PartialMachineCycle::Read:
@ -209,12 +214,16 @@ void Machine::configure_as_target(const StaticAnalyser::Target &target) {
tape_return_address_ = 0x380;
vsync_start_cycle_ = 16;
vsync_end_cycle_ = 32;
automatic_tape_motor_start_address_ = 0x0340;
automatic_tape_motor_end_address_ = 0x03c3;
} else {
rom_ = zx80_rom_;
tape_trap_address_ = 0x220;
tape_return_address_ = 0x248;
vsync_start_cycle_ = 13;
vsync_end_cycle_ = 33;
automatic_tape_motor_start_address_ = 0x0206;
automatic_tape_motor_end_address_ = 0x024d;
}
rom_mask_ = (uint16_t)(rom_.size() - 1);

View File

@ -63,11 +63,18 @@ class Machine:
void clear_all_keys();
inline void set_use_fast_tape_hack(bool activate) { use_fast_tape_hack_ = activate; }
inline void set_use_automatic_tape_motor_control(bool enabled) {
use_automatic_tape_motor_control_ = enabled;
if(!enabled) tape_is_automatically_playing_ = false;
}
inline void set_tape_is_playing(bool is_playing) { tape_is_playing_ = is_playing; }
private:
std::shared_ptr<Video> video_;
std::vector<uint8_t> zx81_rom_, zx80_rom_;
uint16_t tape_trap_address_, tape_return_address_;
uint16_t automatic_tape_motor_start_address_, automatic_tape_motor_end_address_;
std::vector<uint8_t> ram_;
uint16_t ram_mask_, ram_base_;
@ -94,6 +101,8 @@ class Machine:
uint8_t latched_video_byte_;
bool use_fast_tape_hack_;
bool use_automatic_tape_motor_control_;
bool tape_is_playing_, tape_is_automatically_playing_;
int tape_advance_delay_;
};

View File

@ -96,6 +96,7 @@
4B924E991E74D22700B76AF1 /* AtariStaticAnalyserTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */; };
4B9252CE1E74D28200B76AF1 /* Atari ROMs in Resources */ = {isa = PBXBuildFile; fileRef = 4B9252CD1E74D28200B76AF1 /* Atari ROMs */; };
4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */; };
4B95FA9D1F11893B0008E395 /* ZX8081OptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B95FA9C1F11893B0008E395 /* ZX8081OptionsPanel.swift */; };
4B96F7221D75119A0058BB2D /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B96F7201D75119A0058BB2D /* Tape.cpp */; };
4B9CCDA11DA279CA0098B625 /* Vic20OptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9CCDA01DA279CA0098B625 /* Vic20OptionsPanel.swift */; };
4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BA0F68C1EEA0E8400E9489E /* ZX8081.cpp */; };
@ -609,6 +610,7 @@
4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AtariStaticAnalyserTests.mm; sourceTree = "<group>"; };
4B9252CD1E74D28200B76AF1 /* Atari ROMs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "Atari ROMs"; sourceTree = "<group>"; };
4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6502TimingTests.swift; sourceTree = "<group>"; };
4B95FA9C1F11893B0008E395 /* ZX8081OptionsPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZX8081OptionsPanel.swift; sourceTree = "<group>"; };
4B96F7201D75119A0058BB2D /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Tape.cpp; path = ../../StaticAnalyser/Acorn/Tape.cpp; sourceTree = "<group>"; };
4B96F7211D75119A0058BB2D /* Tape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Tape.hpp; path = ../../StaticAnalyser/Acorn/Tape.hpp; sourceTree = "<group>"; };
4B9CCDA01DA279CA0098B625 /* Vic20OptionsPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Vic20OptionsPanel.swift; sourceTree = "<group>"; };
@ -1312,6 +1314,7 @@
4B8FE2211DA19FB20090D3CE /* MachinePanel.swift */,
4B2A332E1DB86869002876E3 /* OricOptionsPanel.swift */,
4B9CCDA01DA279CA0098B625 /* Vic20OptionsPanel.swift */,
4B95FA9C1F11893B0008E395 /* ZX8081OptionsPanel.swift */,
4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */,
4B8FE2171DA19D5F0090D3CE /* ElectronOptions.xib */,
4B8FE2151DA19D5F0090D3CE /* MachineDocument.xib */,
@ -2566,6 +2569,7 @@
4BD4A8CD1E077E8A0020D856 /* PCMSegment.cpp in Sources */,
4BD14B111D74627C0088EAD6 /* StaticAnalyser.cpp in Sources */,
4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */,
4B95FA9D1F11893B0008E395 /* ZX8081OptionsPanel.swift in Sources */,
4B0CCC451C62D0B3001CAC5F /* CRT.cpp in Sources */,
4BCF1FA41DADC3DD0039D2E7 /* Oric.cpp in Sources */,
4BEA525E1DF33323007E74F2 /* Tape.cpp in Sources */,

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="12120" systemVersion="16F73" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="12121" systemVersion="16F73" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="12120"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="12121"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -12,17 +12,17 @@
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ota-g7-hOL" customClass="MachinePanel" customModule="Clock_Signal" customModuleProvider="target">
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ota-g7-hOL" customClass="ZX8081OptionsPanel" customModule="Clock_Signal" customModuleProvider="target">
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="83" y="102" width="200" height="54"/>
<rect key="contentRect" x="83" y="102" width="261" height="100"/>
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="768"/>
<view key="contentView" id="7Pv-WL-2Rq">
<rect key="frame" x="0.0" y="0.0" width="200" height="54"/>
<rect key="frame" x="0.0" y="0.0" width="261" height="100"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button translatesAutoresizingMaskIntoConstraints="NO" id="sBT-cU-h7s">
<rect key="frame" x="18" y="18" width="164" height="18"/>
<rect key="frame" x="18" y="64" width="225" height="18"/>
<buttonCell key="cell" type="check" title="Load Tapes Quickly" bezelStyle="regularSquare" imagePosition="left" alignment="left" state="on" inset="2" id="w0l-ha-esm">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
@ -31,18 +31,46 @@
<action selector="setFastLoading:" target="ota-g7-hOL" id="me0-h2-Ga5"/>
</connections>
</button>
<button translatesAutoresizingMaskIntoConstraints="NO" id="qSb-72-6Os">
<rect key="frame" x="18" y="44" width="225" height="18"/>
<buttonCell key="cell" type="check" title="Control Tape Motor Automatically" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="CzC-YT-lgA">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="setAutomaticTapeMotorConrol:" target="ota-g7-hOL" id="bpF-1P-tga"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="tkN-gI-RmT">
<rect key="frame" x="20" y="19" width="221" height="19"/>
<buttonCell key="cell" type="roundRect" title="Play Tape" bezelStyle="roundedRect" alignment="center" enabled="NO" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="cTq-f9-Gzx">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="cellTitle"/>
</buttonCell>
<connections>
<action selector="playOrPauseTape:" target="ota-g7-hOL" id="O0K-pL-nOr"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="qSb-72-6Os" firstAttribute="leading" secondItem="7Pv-WL-2Rq" secondAttribute="leading" constant="20" id="05p-Jn-ueX"/>
<constraint firstAttribute="trailing" secondItem="sBT-cU-h7s" secondAttribute="trailing" constant="20" id="79b-2A-2c7"/>
<constraint firstItem="sBT-cU-h7s" firstAttribute="top" secondItem="7Pv-WL-2Rq" secondAttribute="top" constant="20" id="E5m-wo-X92"/>
<constraint firstAttribute="bottom" secondItem="sBT-cU-h7s" secondAttribute="bottom" constant="20" id="eZe-Eu-INg"/>
<constraint firstItem="qSb-72-6Os" firstAttribute="top" secondItem="sBT-cU-h7s" secondAttribute="bottom" constant="6" id="WxD-kP-vwf"/>
<constraint firstAttribute="bottom" secondItem="tkN-gI-RmT" secondAttribute="bottom" constant="20" id="Xnu-On-nOA"/>
<constraint firstItem="tkN-gI-RmT" firstAttribute="leading" secondItem="7Pv-WL-2Rq" secondAttribute="leading" constant="20" id="fHf-K0-PsU"/>
<constraint firstItem="tkN-gI-RmT" firstAttribute="top" secondItem="qSb-72-6Os" secondAttribute="bottom" constant="8" id="gLh-vE-Cqk"/>
<constraint firstAttribute="trailing" secondItem="qSb-72-6Os" secondAttribute="trailing" constant="20" id="mQz-p8-aYf"/>
<constraint firstItem="sBT-cU-h7s" firstAttribute="leading" secondItem="7Pv-WL-2Rq" secondAttribute="leading" constant="20" id="nDy-Xc-Ug9"/>
<constraint firstAttribute="trailing" secondItem="tkN-gI-RmT" secondAttribute="trailing" constant="20" id="vgD-A3-m6T"/>
</constraints>
</view>
<connections>
<outlet property="automaticTapeMotorControlButton" destination="qSb-72-6Os" id="bB6-FP-TKM"/>
<outlet property="fastLoadingButton" destination="sBT-cU-h7s" id="uWa-EB-mbd"/>
<outlet property="playOrPauseTapeButton" destination="tkN-gI-RmT" id="UnJ-nb-3mv"/>
</connections>
<point key="canvasLocation" x="-2" y="-8"/>
<point key="canvasLocation" x="28.5" y="15"/>
</window>
</objects>
</document>

View File

@ -10,6 +10,7 @@
#import "CSElectron.h"
#import "CSOric.h"
#import "CSVic20.h"
#import "CSZX8081.h"
#import "CSStaticAnalyser.h"

View File

@ -0,0 +1,49 @@
//
// ZX8081OptionsPanel.swift
// Clock Signal
//
// Created by Thomas Harte on 08/07/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
class ZX8081OptionsPanel: MachinePanel {
var zx8081: CSZX8081! {
get {
return self.machine as! CSZX8081
}
}
@IBOutlet var automaticTapeMotorControlButton: NSButton!
var automaticTapeMotorControlDefaultsKey: String {
get { return prefixedUserDefaultsKey("automaticTapeMotorControl") }
}
@IBAction func setAutomaticTapeMotorConrol(_ sender: NSButton!) {
let isEnabled = sender.state == NSOnState
UserDefaults.standard.set(isEnabled, forKey: self.automaticTapeMotorControlDefaultsKey)
self.playOrPauseTapeButton.isEnabled = !isEnabled
self.zx8081.useAutomaticTapeMotorControl = isEnabled
}
@IBOutlet var playOrPauseTapeButton: NSButton!
@IBAction func playOrPauseTape(_ sender: NSButton!) {
self.zx8081.tapeIsPlaying = !self.zx8081.tapeIsPlaying
self.playOrPauseTapeButton.title = self.zx8081.tapeIsPlaying
? NSLocalizedString("Stop Tape", comment: "Text for a button that will stop tape playback")
: NSLocalizedString("Play Tape", comment: "Text for a button that will start tape playback")
}
// MARK: option restoration
override func establishStoredOptions() {
super.establishStoredOptions()
let standardUserDefaults = UserDefaults.standard
standardUserDefaults.register(defaults: [
self.automaticTapeMotorControlDefaultsKey: true
])
let automaticTapeMotorControlIsEnabled = standardUserDefaults.bool(forKey: self.automaticTapeMotorControlDefaultsKey)
self.automaticTapeMotorControlButton.state = automaticTapeMotorControlIsEnabled ? NSOnState : NSOffState
self.playOrPauseTapeButton.isEnabled = !automaticTapeMotorControlIsEnabled
self.zx8081.useAutomaticTapeMotorControl = automaticTapeMotorControlIsEnabled
}
}

View File

@ -14,4 +14,7 @@
@property (nonatomic, assign) BOOL useFastLoadingHack;
@property (nonatomic, assign) BOOL useAutomaticTapeMotorControl;
@property (nonatomic, assign) BOOL tapeIsPlaying;
@end

View File

@ -110,4 +110,18 @@
}
}
- (void)setTapeIsPlaying:(BOOL)tapeIsPlaying {
@synchronized(self) {
_tapeIsPlaying = tapeIsPlaying;
_zx8081.set_tape_is_playing(tapeIsPlaying ? true : false);
}
}
- (void)setUseAutomaticTapeMotorControl:(BOOL)useAutomaticTapeMotorControl {
@synchronized(self) {
_useAutomaticTapeMotorControl = useAutomaticTapeMotorControl;
_zx8081.set_use_automatic_tape_motor_control(useAutomaticTapeMotorControl ? true : false);
}
}
@end

View File

@ -8,9 +8,10 @@
#include "CRT.hpp"
#include "CRTOpenGL.hpp"
#include <stdarg.h>
#include <math.h>
#include <cstdarg>
#include <cmath>
#include <algorithm>
#include <cassert>
using namespace Outputs::CRT;
@ -144,6 +145,7 @@ void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bo
if(next_run) {
// output_y and texture locations will be written later; we won't necessarily know what it is outside of the locked region
openGL_output_builder_.texture_builder.retain_latest();
source_output_position_x1() = (uint16_t)horizontal_flywheel_->get_current_output_position();
source_phase() = colour_burst_phase_;
source_amplitude() = colour_burst_amplitude_;
@ -192,9 +194,10 @@ void CRT::advance_cycles(unsigned int number_of_cycles, bool hsync_requested, bo
output_x2() = (uint16_t)horizontal_flywheel_->get_current_output_position();
}
openGL_output_builder_.array_builder.flush(
[output_y, this] (uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size) {
[=] (uint8_t *input_buffer, size_t input_size, uint8_t *output_buffer, size_t output_size) {
openGL_output_builder_.texture_builder.flush(
[output_y, input_buffer] (const std::vector<TextureBuilder::WriteArea> &write_areas, size_t number_of_write_areas) {
[=] (const std::vector<TextureBuilder::WriteArea> &write_areas, size_t number_of_write_areas) {
assert(number_of_write_areas * SourceVertexSize == input_size);
for(size_t run = 0; run < number_of_write_areas; run++) {
*(uint16_t *)&input_buffer[run * SourceVertexSize + SourceVertexOffsetOfInputStart + 0] = write_areas[run].x;
*(uint16_t *)&input_buffer[run * SourceVertexSize + SourceVertexOffsetOfInputStart + 2] = write_areas[run].y;
@ -311,6 +314,7 @@ void CRT::output_blank(unsigned int number_of_cycles) {
}
void CRT::output_level(unsigned int number_of_cycles) {
openGL_output_builder_.texture_builder.reduce_previous_allocation_to(1);
Scan scan {
.type = Scan::Type::Level,
.number_of_cycles = number_of_cycles,
@ -329,13 +333,7 @@ void CRT::output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint
}
void CRT::output_default_colour_burst(unsigned int number_of_cycles) {
Scan scan {
.type = Scan::Type::ColourBurst,
.number_of_cycles = number_of_cycles,
.phase = (uint8_t)((phase_numerator_ * 256) / phase_denominator_ + (is_alernate_line_ ? 128 : 0)),
.amplitude = 32
};
output_scan(&scan);
output_colour_burst(number_of_cycles, (uint8_t)((phase_numerator_ * 256) / phase_denominator_ + (is_alernate_line_ ? 128 : 0)));
}
void CRT::output_data(unsigned int number_of_cycles, unsigned int source_divider) {

View File

@ -187,7 +187,7 @@ class CRT {
@param amplitude The amplitude of the colour burst in 1/256ths of the amplitude of the
positive portion of the wave.
*/
void output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint8_t amplitude);
void output_colour_burst(unsigned int number_of_cycles, uint8_t phase, uint8_t amplitude = 102);
/*! Outputs a colour burst exactly in phase with CRT expectations using the idiomatic amplitude.

View File

@ -39,7 +39,6 @@ TextureBuilder::TextureBuilder(size_t bytes_per_pixel, GLenum texture_unit) :
write_areas_start_y_(0),
is_full_(false),
did_submit_(false),
has_write_area_(false),
number_of_write_areas_(0) {
image_.resize(bytes_per_pixel * InputBufferBuilderWidth * InputBufferBuilderHeight);
glGenTextures(1, &texture_name_);
@ -74,7 +73,6 @@ uint8_t *TextureBuilder::allocate_write_area(size_t required_length) {
starting_y = write_areas_[number_of_write_areas_ - 1].y;
}
WriteArea next_write_area;
if(starting_x + required_length + 2 > InputBufferBuilderWidth) {
starting_x = 0;
starting_y++;
@ -85,33 +83,34 @@ uint8_t *TextureBuilder::allocate_write_area(size_t required_length) {
}
}
next_write_area.x = starting_x + 1;
next_write_area.y = starting_y;
next_write_area.length = (uint16_t)required_length;
if(number_of_write_areas_ < write_areas_.size())
write_areas_[number_of_write_areas_] = next_write_area;
else
write_areas_.push_back(next_write_area);
number_of_write_areas_++;
has_write_area_ = true;
write_area_.x = starting_x + 1;
write_area_.y = starting_y;
write_area_.length = (uint16_t)required_length;
return pointer_to_location(next_write_area.x, next_write_area.y);
return pointer_to_location(write_area_.x, write_area_.y);
}
bool TextureBuilder::is_full() {
return is_full_;
}
void TextureBuilder::reduce_previous_allocation_to(size_t actual_length) {
if(is_full_ || !has_write_area_) return;
void TextureBuilder::retain_latest() {
if(is_full_) return;
if(number_of_write_areas_ < write_areas_.size())
write_areas_[number_of_write_areas_] = write_area_;
else
write_areas_.push_back(write_area_);
number_of_write_areas_++;
}
has_write_area_ = false;
WriteArea &write_area = write_areas_[number_of_write_areas_-1];
write_area.length = (uint16_t)actual_length;
void TextureBuilder::reduce_previous_allocation_to(size_t actual_length) {
if(is_full_) return;
write_area_.length = (uint16_t)actual_length;
// book end the allocation with duplicates of the first and last pixel, to protect
// against rounding errors when this run is drawn
uint8_t *start_pointer = pointer_to_location(write_area.x, write_area.y);
uint8_t *start_pointer = pointer_to_location(write_area_.x, write_area_.y);
memcpy( &start_pointer[-bytes_per_pixel_],
start_pointer,
bytes_per_pixel_);
@ -172,6 +171,5 @@ void TextureBuilder::flush(const std::function<void(const std::vector<WriteArea>
}
did_submit_ = false;
has_write_area_ = false;
number_of_write_areas_ = 0;
}

View File

@ -24,6 +24,32 @@ namespace CRT {
Owns an OpenGL texture resource and provides mechanisms to fill it from bottom left to top right
with runs of data, ensuring each run is neighboured immediately to the left and right by copies of its
first and last pixels.
Intended usage:
(i) allocate a write area with allocate_write_area, supplying a maximum size.
(ii) call reduce_previous_allocation_to to announce the actual size written.
This will cause you to have added source data to the target texture. You can then either use that data
or allow it to expire.
(iii) call retain_latest to add the most recently written write area to the flush queue.
The flush queue contains provisional data, that can sit in the CPU's memory space indefinitely. This facility
is provided because it is expected that a texture will be built alontside some other collection of data
that data in the flush queue is expected to become useful in coordination with something else but should
be retained at least until then.
(iv) call flush to move data to the submit queue.
When you flush, you'll receive a record of the bounds of all newly-flushed areas of source data. That gives
an opportunity to correlate the data with whatever else it is being tied to. It will continue to sit in
the CPU's memory space but has now passed beyond any further modification or reporting.
(v) call submit to move data to the GPU and free up its CPU-side resources.
The data is now on the GPU, for whatever use the caller desires.
*/
class TextureBuilder {
public:
@ -41,6 +67,9 @@ class TextureBuilder {
/// and indicates that its actual final size was @c actual_length.
void reduce_previous_allocation_to(size_t actual_length);
/// Allocated runs are provisional; they will not appear in the next flush queue unless retained.
void retain_latest();
/// @returns @c true if all future calls to @c allocate_write_area will fail on account of the input texture
/// being full; @c false if calls may succeed.
bool is_full();
@ -64,12 +93,14 @@ class TextureBuilder {
std::vector<uint8_t> image_;
GLuint texture_name_;
// the current list of write areas
// the current write area
WriteArea write_area_;
// the list of write areas that have ascended to the flush queue
std::vector<WriteArea> write_areas_;
size_t number_of_write_areas_;
bool is_full_;
bool did_submit_;
bool has_write_area_;
inline uint8_t *pointer_to_location(uint16_t x, uint16_t y);
// Usually: the start position for the current batch of write areas.

View File

@ -54,7 +54,7 @@ Tape::Pulse ZX80O81P::virtual_get_next_pulse() {
// Start with 1 second of silence.
if(!is_past_silence_ || has_finished_data()) {
pulse.type = Pulse::Type::Low;
pulse.length.length = 10;
pulse.length.length = 1;
pulse.length.clock_rate = 1;
is_past_silence_ = true;
has_ended_final_byte_ = has_finished_data();