Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/Spectron.Emulation/Devices/Ula.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ public virtual void WritePort(Word address, byte value)
return;
}

var color = SpectrumPalette.GetBorderColor(value);
screenBuffer.UpdateBorder(color, clock.UlaTicks);
screenBuffer.UpdateBorder((byte)(value & 0x07), clock.UlaTicks);
}

internal virtual bool IsUlaPort(Word address) => (address & 0x01) == 0x00;
Expand Down
10 changes: 7 additions & 3 deletions src/Spectron.Emulation/Devices/UlaPlus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ internal bool IsActive
set
{
field = value;
ActiveChanged?.Invoke(EventArgs.Empty);
ActiveChanged?.Invoke(this, EventArgs.Empty);
}
}

internal delegate void ActiveChangedEvent(EventArgs e);
internal event ActiveChangedEvent? ActiveChanged;
internal event EventHandler? ActiveChanged;
internal event EventHandler? PaletteChanged;

public void WritePort(Word address, byte value)
{
Expand All @@ -56,6 +56,7 @@ public void WritePort(Word address, byte value)
var color = ColorFromValue(value);

PaletteColors[paletteIndex][colorIndex] = color;
PaletteChanged?.Invoke(this, EventArgs.Empty);
break;

case Register.ModeGroup:
Expand Down Expand Up @@ -85,6 +86,9 @@ internal Color GetInkColor(byte attribute)
return palette[colorIndex];
}

internal Color GetBorderColor(byte borderIndex, bool isHiRes = false) =>
PaletteColors[isHiRes ? 1 : 0][(borderIndex & 0x07) | 8];

internal Color GetPaperColor(byte attribute)
{
var paletteIndex = attribute >> 6;
Expand Down
35 changes: 19 additions & 16 deletions src/Spectron.Emulation/Devices/UlaTimex.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ internal sealed class UlaTimex(
internal ScreenMode ScreenMode { get; private set; }
internal Color Paper { get; private set; }
internal Color Ink { get; private set; }
internal byte PaperIndex { get; private set; } = 0x07;

internal event EventHandler<EventArgs>? ScreenModeChanged;

Expand Down Expand Up @@ -62,30 +63,32 @@ public override void WritePort(Word address, byte value)

Ink = (value & 0b111_000) switch
{
0b000_000 => SpectrumPalette.Black,
0b001_000 => SpectrumPalette.BrightBlue,
0b010_000 => SpectrumPalette.BrightRed,
0b011_000 => SpectrumPalette.BrightMagenta,
0b100_000 => SpectrumPalette.BrightGreen,
0b101_000 => SpectrumPalette.BrightCyan,
0b110_000 => SpectrumPalette.BrightYellow,
0b111_000 => SpectrumPalette.BrightWhite,
0b000_000 => SpectrumPalette.Black, // 000
0b001_000 => SpectrumPalette.BrightBlue, // 001
0b010_000 => SpectrumPalette.BrightRed, // 010
0b011_000 => SpectrumPalette.BrightMagenta, // 011
0b100_000 => SpectrumPalette.BrightGreen, // 100
0b101_000 => SpectrumPalette.BrightCyan, // 101
0b110_000 => SpectrumPalette.BrightYellow, // 110
0b111_000 => SpectrumPalette.BrightWhite, // 111
_ => throw new ArgumentOutOfRangeException(nameof(value))
};

Paper = (value & 0b111_000) switch
{
0b000_000 => SpectrumPalette.BrightWhite,
0b001_000 => SpectrumPalette.BrightYellow,
0b010_000 => SpectrumPalette.BrightCyan,
0b011_000 => SpectrumPalette.BrightGreen,
0b100_000 => SpectrumPalette.BrightMagenta,
0b101_000 => SpectrumPalette.BrightRed,
0b110_000 => SpectrumPalette.BrightBlue,
0b111_000 => SpectrumPalette.Black,
0b000_000 => SpectrumPalette.BrightWhite, // 111
0b001_000 => SpectrumPalette.BrightYellow, // 110
0b010_000 => SpectrumPalette.BrightCyan, // 101
0b011_000 => SpectrumPalette.BrightGreen, // 100
0b100_000 => SpectrumPalette.BrightMagenta, // 011
0b101_000 => SpectrumPalette.BrightRed, // 010
0b110_000 => SpectrumPalette.BrightBlue, // 001
0b111_000 => SpectrumPalette.Black, // 000
_ => throw new ArgumentOutOfRangeException(nameof(value))
};

PaperIndex = (byte)(~(value >> 3) & 0x07); // Inverted to match the paper color index

if (_lastControlValue != value)
{
ScreenModeChanged?.Invoke(this, EventArgs.Empty);
Expand Down
11 changes: 10 additions & 1 deletion src/Spectron.Emulation/Emulator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,8 @@ private void AddEventHandlers()
{
Cpu.Clock.TicksAdded += (_, previousFrameTicks, _) => ScreenBuffer.UpdateScreen(previousFrameTicks / Clock.Multiplier);
Cpu.BeforeInstruction += BeforeInstruction;
UlaPlus.ActiveChanged += _ => _invalidateScreen = true;
UlaPlus.ActiveChanged += (_, _) => InvalidateUlaPlus();
UlaPlus.PaletteChanged += (_, _) => InvalidateUlaPlus();
Beta128.DiskActivity += _ => DiskDriveManager.OnDiskActivity();

if (Ula is UlaTimex ulaTimex)
Expand Down Expand Up @@ -345,7 +346,15 @@ private void EndFrame()
private void ToggleUlaPlus(bool value)
{
UlaPlus.IsEnabled = value;

InvalidateUlaPlus();
}

private void InvalidateUlaPlus()
{
_invalidateScreen = true;

ScreenBuffer.RefreshBorder(Clock.UlaTicks);
}

private void BeforeInstruction(Word pc)
Expand Down
80 changes: 49 additions & 31 deletions src/Spectron.Emulation/Extensions/EmulatorStateExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,50 +8,65 @@ public static class EmulatorStateExtensions
{
private static readonly int[] Buffer = new int[ScreenSize.ContentHeight * ScreenSize.ContentWidth];

public static int[] GetScreenshot(this StateSnapshot snapshot, bool isFlashOnFrame = false)
extension(StateSnapshot snapshot)
{
var screenMemory = snapshot.ComputerType == ComputerType.Spectrum128K ?
snapshot.Memory.Banks[5] :
snapshot.Memory.Banks[0];

for (var line = 0; line < ScreenSize.ContentHeight; line++)
public int[] GetScreenshot(bool isFlashOnFrame = false)
{
for (var column = 0; column < 32; column++)
{
var bitmapAddress = ScreenAddress.Calculate(column, line) - 0x4000;
var attributeAddress = ScreenAddress.CalculateAttribute(column, line) - 0x4000;
var screenMemory = snapshot.ComputerType == ComputerType.Spectrum128K ?
snapshot.Memory.Banks[5] :
snapshot.Memory.Banks[0];

var bitmap = screenMemory[bitmapAddress];
var attribute = screenMemory[attributeAddress];
var isUlaPlus = snapshot.UlaPlus is { IsEnabled: true, IsActive: true };

var attributeData = FastLookup.AttributeData[attribute];
var isFlashOn = attributeData.IsFlashOn && isFlashOnFrame;
for (var line = 0; line < ScreenSize.ContentHeight; line++)
{
for (var column = 0; column < 32; column++)
{
var bitmapAddress = ScreenAddress.Calculate(column, line) - 0x4000;
var attributeAddress = ScreenAddress.CalculateAttribute(column, line) - 0x4000;

var bufferIndex = 256 * line + 8 * column;
var bitmap = screenMemory[bitmapAddress];
var attribute = screenMemory[attributeAddress];

for (var bit = 0; bit < FastLookup.BitMasks.Length; bit++)
{
Color color;
var attributeData = FastLookup.AttributeData[attribute];
var isFlashOn = attributeData.IsFlashOn && isFlashOnFrame;

if (snapshot.UlaPlus != null && snapshot.UlaPlus.PaletteGroup != 0)
{
color = (bitmap & FastLookup.BitMasks[bit]) != 0 ?
snapshot.UlaPlus.GetInkColor(attribute) :
snapshot.UlaPlus.GetPaperColor(attribute);
}
else
var bufferIndex = 256 * line + 8 * column;

for (var bit = 0; bit < FastLookup.BitMasks.Length; bit++)
{
color = (bitmap & FastLookup.BitMasks[bit]) != 0 ^ isFlashOn ?
attributeData.Ink :
attributeData.Paper;
}
Color color;

Buffer[bufferIndex + bit] = (int)color.Abgr;
if (isUlaPlus)
{
color = (bitmap & FastLookup.BitMasks[bit]) != 0 ?
snapshot.UlaPlus!.GetInkColor(attribute) :
snapshot.UlaPlus!.GetPaperColor(attribute);
}
else
{
color = (bitmap & FastLookup.BitMasks[bit]) != 0 ^ isFlashOn ?
attributeData.Ink :
attributeData.Paper;
}
Comment on lines +40 to +51

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current is easier to read


Buffer[bufferIndex + bit] = (int)color.Abgr;
}
}
}

return Buffer;
}

return Buffer;
public Color GetBorderColor()
{
if (snapshot.UlaPlus is { IsEnabled: true, IsActive: true } ulaPlus)
{
return ulaPlus.GetBorderColor(snapshot.Border);
}

return SpectrumPalette.GetBorderColor(snapshot.Border);
}
}

extension(UlaPlusState ulaPlusState)
Expand All @@ -71,5 +86,8 @@ private Color GetPaperColor(byte attribute)

return ulaPlusState.PaletteColors[paletteIndex][colorIndex];
}

private Color GetBorderColor(byte borderIndex) =>
ulaPlusState.PaletteColors[0][(borderIndex & 0x07) | 8];
}
}
44 changes: 36 additions & 8 deletions src/Spectron.Emulation/Screen/ScreenBuffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,24 @@ public sealed class ScreenBuffer
{
private readonly Border _border;
private readonly Content _content;
private readonly UlaPlus _ulaPlus;

private bool _borderColorChanged = true;
private Color? _lockedBorderColor;
private byte _lockedBorderColorIndex;
private ScreenMode _screenMode = ScreenMode.Spectrum;

public FrameBuffer FrameBuffer { get; }

internal Color LastBorderColor { get; private set; } = SpectrumPalette.White;
internal byte LastBorderColorIndex { get; private set; } = 0x07;

public event EventHandler<EventArgs>? FrameBufferChanged;

internal ScreenBuffer(HardwareSettings hardware, IEmulatorMemory memory, UlaPlus ulaPlus)
{
FrameBuffer = new FrameBuffer();

_ulaPlus = ulaPlus;
_border = new Border(hardware, FrameBuffer);
_content = new Content(hardware, FrameBuffer, memory, ulaPlus);

Expand All @@ -32,14 +35,15 @@ internal ScreenBuffer(HardwareSettings hardware, IEmulatorMemory memory, UlaPlus
}
}

internal void ChangeScreenMode(ScreenMode screenMode, Color ink, Color paper, int frameTicks)
internal void ChangeScreenMode(ScreenMode screenMode, Color ink, Color paper, byte paperIndex, int frameTicks)
{
_lockedBorderColor = screenMode.IsTimexHiRes() ? paper : null;
_lockedBorderColorIndex = paperIndex;

_content.ChangeScreenMode(screenMode, ink, paper);
_border.ChangeScreenMode(screenMode);

_border.Update(_lockedBorderColor ?? LastBorderColor, frameTicks);
_border.Update(BorderColor, frameTicks);

if (_screenMode != screenMode)
{
Expand All @@ -55,13 +59,15 @@ internal void NewFrame()
_content.NewFrame();
}

internal void EndFrame(int frameTicks) => _border.Update(_lockedBorderColor ?? LastBorderColor, frameTicks);
internal void EndFrame(int frameTicks) => _border.Update(BorderColor, frameTicks);

internal void UpdateBorder(Color borderColor, int frameTicks = 0)
internal void UpdateBorder(byte borderColor, int frameTicks = 0)
{
if (LastBorderColor != borderColor)
borderColor &= 0x07;

if (LastBorderColorIndex != borderColor)
{
LastBorderColor = borderColor;
LastBorderColorIndex = borderColor;
_borderColorChanged = true;
}

Expand All @@ -70,11 +76,33 @@ internal void UpdateBorder(Color borderColor, int frameTicks = 0)
return;
}

_border.Update(_lockedBorderColor ?? borderColor, frameTicks);
_border.Update(BorderColor, frameTicks);

_borderColorChanged = false;
}

internal void RefreshBorder(int frameTicks = 0)
{
_borderColorChanged = true;

UpdateBorder(LastBorderColorIndex, frameTicks);
}

private Color BorderColor
{
get
{
if (_ulaPlus is { IsEnabled: true, IsActive: true })
{
return _lockedBorderColor != null ?
_ulaPlus.GetBorderColor(_lockedBorderColorIndex, isHiRes: true) :
_ulaPlus.GetBorderColor(LastBorderColorIndex);
}

return _lockedBorderColor ?? SpectrumPalette.GetBorderColor(LastBorderColorIndex);
}
}

internal void Reset()
{
_border.Reset();
Expand Down
1 change: 1 addition & 0 deletions src/Spectron.Emulation/Screen/ScreenMemoryHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ internal void SetScreenMode(UlaTimex? ulaTimex, int frameTicks = 0)
screenMode,
ulaTimex?.Ink ?? SpectrumPalette.Black,
ulaTimex?.Paper ?? SpectrumPalette.White,
ulaTimex?.PaperIndex ?? 0x07,
frameTicks);
}

Expand Down
3 changes: 0 additions & 3 deletions src/Spectron.Emulation/Screen/SpectrumPalette.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,6 @@ public static class SpectrumPalette
{ 0b0000111, White }
};

internal static readonly Dictionary<Color, byte> ReverseBorderColors =
BorderColors.ToDictionary(x => x.Value, x => x.Key);

private static readonly Dictionary<int, Color> PaperColors = new()
{
{ 0b00000000, Black },
Expand Down
6 changes: 2 additions & 4 deletions src/Spectron.Emulation/Snapshot/SnaSnapshot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ internal void Save(string fileName, Emulator emulator)
InterruptMode = (byte)emulator.Cpu.IM,
Interrupt = (byte)((emulator.Cpu.IFF2 ? 0x04 : 0x00) | (emulator.Cpu.IFF1 ? 0x02 : 0x00)),

BorderColor = SpectrumPalette.ReverseBorderColors[emulator.ScreenBuffer.LastBorderColor]
BorderColor = emulator.ScreenBuffer.LastBorderColorIndex
}
};

Expand Down Expand Up @@ -123,10 +123,8 @@ internal static void Update(Emulator emulator, SnaFile snapshot, bool updateBord
return;
}

var borderColor = SpectrumPalette.GetBorderColor(snapshot.Header.BorderColor);

screenBuffer.Reset();
screenBuffer.UpdateBorder(borderColor);
screenBuffer.UpdateBorder(snapshot.Header.BorderColor);
}

private Emulator CreateEmulator(SnaFile snapshot)
Expand Down
6 changes: 2 additions & 4 deletions src/Spectron.Emulation/Snapshot/SzxSnapshot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -182,10 +182,8 @@ private static void LoadMemory(IMemory memory, List<RamPageBlock> ramPages, Spec

private static void UpdateBorder(ScreenBuffer screenBuffer, SpecRegsBlock specRegs)
{
var borderColor = SpectrumPalette.GetBorderColor(specRegs.Border);

screenBuffer.Reset();
screenBuffer.UpdateBorder(borderColor);
screenBuffer.UpdateBorder(specRegs.Border);
}

private static void LoadUlaPlus(UlaPlus ulaPlus, PaletteBlock? palette)
Expand Down Expand Up @@ -335,7 +333,7 @@ private static void SaveCustomRom(IMemory memory, SzxFile snapshot, CompressionL
}

private static void SaveSpectrumRegisters(ScreenBuffer screenBuffer, SpecRegsBlock specRegs) =>
specRegs.Border = SpectrumPalette.ReverseBorderColors[screenBuffer.LastBorderColor];
specRegs.Border = screenBuffer.LastBorderColorIndex;

private static void SaveUlaPlus(UlaPlus ulaPlus, SzxFile snapshot)
{
Expand Down
Loading
Loading