2023-01-26 16:59:27 +00:00
|
|
|
//
|
|
|
|
// YamahaCommands.hpp
|
|
|
|
// Clock Signal
|
|
|
|
//
|
|
|
|
// Created by Thomas Harte on 26/01/2023.
|
|
|
|
// Copyright © 2023 Thomas Harte. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
#ifndef YamahaCommands_hpp
|
|
|
|
#define YamahaCommands_hpp
|
|
|
|
|
|
|
|
#include "AccessEnums.hpp"
|
|
|
|
|
|
|
|
namespace TI {
|
|
|
|
namespace TMS {
|
|
|
|
|
2023-01-26 17:09:06 +00:00
|
|
|
// MARK: - Generics.
|
|
|
|
|
2023-01-27 00:51:56 +00:00
|
|
|
struct Vector {
|
|
|
|
int v[2]{};
|
|
|
|
|
|
|
|
template <int offset, bool high> void set(uint8_t value) {
|
|
|
|
constexpr uint8_t mask = high ? (offset ? 0x3 : 0x1) : 0xff;
|
|
|
|
constexpr int shift = high ? 8 : 0;
|
2023-02-02 03:57:33 +00:00
|
|
|
v[offset] = (v[offset] & ~(mask << shift)) | ((value & mask) << shift);
|
2023-01-27 00:51:56 +00:00
|
|
|
}
|
|
|
|
|
2023-01-27 16:57:40 +00:00
|
|
|
template <int offset> void add(int amount) {
|
|
|
|
v[offset] += amount;
|
|
|
|
|
|
|
|
if constexpr (offset == 1) {
|
|
|
|
v[offset] &= 0x3ff;
|
|
|
|
} else {
|
|
|
|
v[offset] &= 0x1ff;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-27 00:51:56 +00:00
|
|
|
Vector & operator += (const Vector &rhs) {
|
2023-01-27 16:57:40 +00:00
|
|
|
add<0>(rhs.v[0]);
|
|
|
|
add<1>(rhs.v[1]);
|
2023-01-27 00:51:56 +00:00
|
|
|
return *this;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-01-26 16:59:27 +00:00
|
|
|
struct CommandContext {
|
2023-01-27 00:51:56 +00:00
|
|
|
Vector source;
|
|
|
|
Vector destination;
|
|
|
|
Vector size;
|
2023-01-29 18:29:19 +00:00
|
|
|
|
2023-01-26 16:59:27 +00:00
|
|
|
uint8_t arguments = 0;
|
2023-01-29 18:29:19 +00:00
|
|
|
/// Colour as written by the CPU.
|
|
|
|
uint8_t colour = 0;
|
|
|
|
/// The low four bits of the CPU-written colour, repeated twice.
|
2023-01-29 23:28:49 +00:00
|
|
|
uint8_t colour4bpp = 0;
|
2023-01-29 18:29:19 +00:00
|
|
|
/// The low two bits of the CPU-written colour, repeated four times.
|
2023-01-29 23:28:49 +00:00
|
|
|
uint8_t colour2bpp = 0;
|
2023-01-29 18:29:19 +00:00
|
|
|
|
|
|
|
enum class LogicalOperation {
|
|
|
|
Copy = 0b0000,
|
|
|
|
And = 0b0001,
|
|
|
|
Or = 0b0010,
|
|
|
|
Xor = 0b0011,
|
|
|
|
Not = 0b0100,
|
|
|
|
};
|
|
|
|
LogicalOperation pixel_operation;
|
2023-01-29 23:28:49 +00:00
|
|
|
bool test_source;
|
2023-01-26 16:59:27 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
struct Command {
|
|
|
|
// In net:
|
|
|
|
//
|
|
|
|
// This command is blocked until @c access has been performed, reading
|
|
|
|
// from or writing to @c value. It should not be performed until at least
|
|
|
|
// @c cycles have passed.
|
2023-01-27 02:17:11 +00:00
|
|
|
enum class AccessType {
|
2023-02-04 16:02:02 +00:00
|
|
|
/// Plots a single pixel of the current contextual colour at @c destination,
|
|
|
|
/// which occurs as a read, then a 24-cycle gap, then a write.
|
2023-01-29 02:30:45 +00:00
|
|
|
PlotPoint,
|
|
|
|
|
|
|
|
/// Blocks until the next CPU write to the colour register.
|
2023-01-29 18:22:56 +00:00
|
|
|
WaitForColourReceipt,
|
2023-01-31 18:35:39 +00:00
|
|
|
|
2023-02-04 16:02:02 +00:00
|
|
|
/// Writes an entire byte to the address containing the current @c destination.
|
2023-01-31 18:35:39 +00:00
|
|
|
WriteByte,
|
2023-02-04 16:02:02 +00:00
|
|
|
|
|
|
|
/// Copies a single pixel from @c source location to @c destination,
|
|
|
|
/// being a read, a 32-cycle gap, then a PlotPoint.
|
|
|
|
CopyPoint,
|
|
|
|
|
|
|
|
/// Copies a complete byte from @c source location to @c destination,
|
|
|
|
/// being a read, a 24-cycle gap, then a write.
|
|
|
|
CopyByte,
|
|
|
|
|
|
|
|
// ReadPoint,
|
|
|
|
// ReadByte,
|
|
|
|
// WaitForColourSend,
|
2023-01-27 02:17:11 +00:00
|
|
|
};
|
|
|
|
AccessType access = AccessType::PlotPoint;
|
2023-01-26 16:59:27 +00:00
|
|
|
int cycles = 0;
|
2023-01-29 02:45:05 +00:00
|
|
|
bool is_cpu_transfer = false;
|
2023-01-26 17:09:06 +00:00
|
|
|
|
2023-01-26 16:59:27 +00:00
|
|
|
/// Current command parameters.
|
|
|
|
CommandContext &context;
|
|
|
|
Command(CommandContext &context) : context(context) {}
|
2023-02-01 19:20:11 +00:00
|
|
|
virtual ~Command() {}
|
2023-01-26 16:59:27 +00:00
|
|
|
|
2023-01-27 02:31:49 +00:00
|
|
|
/// @returns @c true if all output from this command is done; @c false otherwise.
|
|
|
|
virtual bool done() = 0;
|
|
|
|
|
2023-02-01 02:29:55 +00:00
|
|
|
/// Repopulates the fields above with the next action to take, being provided with the
|
|
|
|
/// number of pixels per byte in the current screen mode.
|
|
|
|
virtual void advance(int pixels_per_byte) = 0;
|
2023-01-31 18:35:39 +00:00
|
|
|
|
|
|
|
protected:
|
2023-02-03 02:16:24 +00:00
|
|
|
template <int axis, bool include_source> void advance_axis(int offset = 1) {
|
2023-02-01 02:29:55 +00:00
|
|
|
context.destination.add<axis>(context.arguments & (0x4 << axis) ? -offset : offset);
|
2023-02-03 02:16:24 +00:00
|
|
|
if constexpr (include_source) {
|
|
|
|
context.source.add<axis>(context.arguments & (0x4 << axis) ? -offset : offset);
|
|
|
|
}
|
2023-01-31 18:35:39 +00:00
|
|
|
}
|
2023-01-26 16:59:27 +00:00
|
|
|
};
|
|
|
|
|
2023-01-26 17:09:06 +00:00
|
|
|
namespace Commands {
|
|
|
|
|
2023-02-03 02:55:00 +00:00
|
|
|
// MARK: - Line drawing.
|
|
|
|
|
2023-01-27 03:02:40 +00:00
|
|
|
/// Implements the LINE command, which is plain-old Bresenham.
|
2023-01-27 02:17:11 +00:00
|
|
|
///
|
|
|
|
/// Per Grauw timing is:
|
|
|
|
///
|
|
|
|
/// * 88 cycles between every pixel plot;
|
|
|
|
/// * plus an additional 32 cycles if a step along the minor axis is taken.
|
2023-01-26 17:09:06 +00:00
|
|
|
struct Line: public Command {
|
2023-01-26 17:55:08 +00:00
|
|
|
public:
|
|
|
|
Line(CommandContext &context) : Command(context) {
|
2023-01-27 16:57:40 +00:00
|
|
|
// context.destination = start position;
|
|
|
|
// context.size.v[0] = long side dots;
|
|
|
|
// context.size.v[1] = short side dots;
|
|
|
|
// context.arguments => direction
|
2023-01-27 02:17:11 +00:00
|
|
|
|
2023-01-27 16:57:40 +00:00
|
|
|
position_ = context.size.v[1];
|
2023-01-27 02:17:11 +00:00
|
|
|
numerator_ = position_ << 1;
|
2023-01-27 16:57:40 +00:00
|
|
|
denominator_ = context.size.v[0] << 1;
|
2023-01-27 02:17:11 +00:00
|
|
|
|
2023-01-27 16:57:40 +00:00
|
|
|
cycles = 32;
|
2023-01-27 02:17:11 +00:00
|
|
|
access = AccessType::PlotPoint;
|
2023-01-26 17:55:08 +00:00
|
|
|
}
|
2023-01-26 17:09:06 +00:00
|
|
|
|
2023-01-27 02:31:49 +00:00
|
|
|
bool done() final {
|
2023-01-27 16:57:40 +00:00
|
|
|
return !context.size.v[0];
|
2023-01-27 02:31:49 +00:00
|
|
|
}
|
2023-01-27 02:17:11 +00:00
|
|
|
|
2023-02-01 02:29:55 +00:00
|
|
|
void advance(int) final {
|
2023-01-27 16:57:40 +00:00
|
|
|
--context.size.v[0];
|
2023-01-27 02:17:11 +00:00
|
|
|
cycles = 88;
|
2023-01-27 16:57:40 +00:00
|
|
|
|
|
|
|
// b0: 1 => long direction is y;
|
|
|
|
// 0 => long direction is x.
|
|
|
|
//
|
|
|
|
// b2: 1 => x direction is left;
|
|
|
|
// 0 => x direction is right.
|
|
|
|
//
|
|
|
|
// b3: 1 => y direction is up;
|
|
|
|
// 0 => y direction is down.
|
|
|
|
if(context.arguments & 0x1) {
|
2023-02-03 02:16:24 +00:00
|
|
|
advance_axis<1, false>();
|
2023-01-27 16:57:40 +00:00
|
|
|
} else {
|
2023-02-03 02:16:24 +00:00
|
|
|
advance_axis<0, false>();
|
2023-01-27 16:57:40 +00:00
|
|
|
}
|
|
|
|
|
2023-01-27 02:17:11 +00:00
|
|
|
position_ -= numerator_;
|
|
|
|
if(position_ < 0) {
|
|
|
|
position_ += denominator_;
|
2023-01-27 16:57:40 +00:00
|
|
|
cycles += 32;
|
|
|
|
|
|
|
|
if(context.arguments & 0x1) {
|
2023-02-03 02:16:24 +00:00
|
|
|
advance_axis<0, false>();
|
2023-01-27 16:57:40 +00:00
|
|
|
} else {
|
2023-02-03 02:16:24 +00:00
|
|
|
advance_axis<1, false>();
|
2023-01-27 16:57:40 +00:00
|
|
|
}
|
2023-01-27 02:17:11 +00:00
|
|
|
}
|
2023-01-26 17:55:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2023-01-27 02:17:11 +00:00
|
|
|
int position_, numerator_, denominator_, duration_;
|
2023-01-26 17:09:06 +00:00
|
|
|
};
|
|
|
|
|
2023-02-03 02:55:00 +00:00
|
|
|
// MARK: - Single pixel manipulation.
|
|
|
|
|
2023-01-27 03:02:40 +00:00
|
|
|
/// Implements the PSET command, which plots a single pixel.
|
|
|
|
///
|
|
|
|
/// No timings are documented, so this'll output as quickly as possible.
|
|
|
|
struct PointSet: public Command {
|
2023-01-27 02:52:41 +00:00
|
|
|
public:
|
2023-01-27 03:02:40 +00:00
|
|
|
PointSet(CommandContext &context) : Command(context) {
|
2023-02-03 02:16:24 +00:00
|
|
|
cycles = 0; // TODO.
|
2023-01-27 02:52:41 +00:00
|
|
|
access = AccessType::PlotPoint;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool done() final {
|
2023-01-27 03:02:40 +00:00
|
|
|
return done_;
|
|
|
|
}
|
|
|
|
|
2023-02-01 02:29:55 +00:00
|
|
|
void advance(int) final {
|
2023-01-27 03:02:40 +00:00
|
|
|
done_ = true;
|
2023-01-27 02:52:41 +00:00
|
|
|
}
|
2023-01-27 03:02:40 +00:00
|
|
|
|
|
|
|
private:
|
|
|
|
bool done_ = false;
|
2023-01-27 02:52:41 +00:00
|
|
|
};
|
|
|
|
|
2023-02-03 02:55:00 +00:00
|
|
|
// TODO: point.
|
|
|
|
|
|
|
|
// MARK: - Rectangular base.
|
|
|
|
|
2023-02-03 02:49:05 +00:00
|
|
|
/// Useful base class for anything that does logical work in a rectangle.
|
|
|
|
template <bool logical, bool include_source> struct Rectangle: public Command {
|
2023-01-29 02:30:45 +00:00
|
|
|
public:
|
2023-02-03 02:49:05 +00:00
|
|
|
Rectangle(CommandContext &context) : Command(context) {
|
2023-01-29 02:30:45 +00:00
|
|
|
start_x_ = context.destination.v[0];
|
|
|
|
width_ = context.size.v[0];
|
|
|
|
}
|
|
|
|
|
2023-02-03 02:49:05 +00:00
|
|
|
/// Advances the current destination and, if @c include_source is @c true also the source;
|
|
|
|
/// @returns @c true if a new row was started; @c false otherwise.
|
|
|
|
///
|
|
|
|
/// @c pixels_per_byte is used for 'fast' (i.e. not logical) rectangles only, setting pace at
|
|
|
|
/// which the source and destination proceed left-to-right.
|
|
|
|
bool advance_pixel(int pixels_per_byte = 0) {
|
|
|
|
if constexpr (logical) {
|
|
|
|
advance_axis<0, include_source>();
|
|
|
|
--context.size.v[0];
|
|
|
|
|
|
|
|
if(context.size.v[0]) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
advance_axis<0, include_source>(pixels_per_byte);
|
|
|
|
context.size.v[0] -= pixels_per_byte;
|
2023-01-29 02:30:45 +00:00
|
|
|
|
2023-02-03 02:49:05 +00:00
|
|
|
if(context.size.v[0] & ~(pixels_per_byte - 1)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2023-01-29 02:30:45 +00:00
|
|
|
|
2023-02-03 02:49:05 +00:00
|
|
|
context.size.v[0] = width_;
|
|
|
|
context.destination.v[0] = start_x_;
|
2023-01-29 02:30:45 +00:00
|
|
|
|
2023-02-03 02:49:05 +00:00
|
|
|
advance_axis<1, include_source>();
|
|
|
|
--context.size.v[1];
|
2023-01-29 02:43:14 +00:00
|
|
|
|
2023-02-03 02:49:05 +00:00
|
|
|
return true;
|
2023-01-29 02:30:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
bool done() final {
|
|
|
|
return !context.size.v[1] || !width_;
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
int start_x_ = 0, width_ = 0;
|
|
|
|
};
|
|
|
|
|
2023-02-03 02:55:00 +00:00
|
|
|
// MARK: - Rectangular manipulations; logical.
|
|
|
|
|
2023-02-03 02:49:05 +00:00
|
|
|
struct LogicalMoveFromCPU: public Rectangle<true, false> {
|
|
|
|
LogicalMoveFromCPU(CommandContext &context) : Rectangle(context) {
|
|
|
|
is_cpu_transfer = true;
|
2023-01-31 18:35:39 +00:00
|
|
|
|
2023-02-03 02:49:05 +00:00
|
|
|
// This command is started with the first colour ready to transfer.
|
|
|
|
cycles = 32;
|
|
|
|
access = AccessType::PlotPoint;
|
2023-01-31 18:35:39 +00:00
|
|
|
}
|
|
|
|
|
2023-02-03 02:49:05 +00:00
|
|
|
void advance(int) final {
|
|
|
|
switch(access) {
|
|
|
|
default: break;
|
|
|
|
|
|
|
|
case AccessType::WaitForColourReceipt:
|
|
|
|
cycles = 32;
|
|
|
|
access = AccessType::PlotPoint;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case AccessType::PlotPoint:
|
|
|
|
cycles = 0;
|
|
|
|
access = AccessType::WaitForColourReceipt;
|
|
|
|
if(advance_pixel()) {
|
|
|
|
cycles = 64;
|
|
|
|
// TODO: I'm not sure this will be honoured per the outer wrapping.
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-02-03 02:55:00 +00:00
|
|
|
// MARK: - Rectangular manipulations; fast.
|
|
|
|
|
2023-02-03 02:49:05 +00:00
|
|
|
struct HighSpeedFill: public Rectangle<false, false> {
|
|
|
|
HighSpeedFill(CommandContext &context) : Rectangle(context) {
|
|
|
|
cycles = 56;
|
|
|
|
access = AccessType::WriteByte;
|
2023-01-31 18:35:39 +00:00
|
|
|
}
|
|
|
|
|
2023-02-01 02:29:55 +00:00
|
|
|
void advance(int pixels_per_byte) final {
|
2023-01-31 18:35:39 +00:00
|
|
|
cycles = 48;
|
2023-02-03 02:49:05 +00:00
|
|
|
if(!advance_pixel(pixels_per_byte)) {
|
2023-02-01 02:29:55 +00:00
|
|
|
cycles += 56;
|
|
|
|
}
|
2023-01-31 18:35:39 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-02-04 15:31:41 +00:00
|
|
|
struct LogicalFill: public Rectangle<false, false> {
|
|
|
|
LogicalFill(CommandContext &context) : Rectangle(context) {
|
|
|
|
cycles = 64;
|
|
|
|
access = AccessType::PlotPoint;
|
|
|
|
}
|
|
|
|
|
|
|
|
void advance(int pixels_per_byte) final {
|
|
|
|
cycles = 72;
|
|
|
|
if(!advance_pixel(pixels_per_byte)) {
|
|
|
|
cycles += 64;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-01-26 17:09:06 +00:00
|
|
|
}
|
2023-01-26 16:59:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* YamahaCommands_hpp */
|