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
1 change: 1 addition & 0 deletions docs/systems/c64/compatible-programs.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ For advanced use, see [Useful tools](useful-tools.md) for how to extract PRG fil
| Unfortunate Coincidence | <https://csdb.dk/release/download.php?id=245796> | `unf-coincidence.prg` | C64 PAL variant, per-line sprites switched on. Sprites only: sprite multiplexing with per-line changes of the scroll/mode and memory setup registers. |
| Smooth and Wonders | <https://csdb.dk/release/download.php?id=245318> | `Smooth_And_Wonders.prg` | C64 PAL variant, per-line sprites switched on. Sprites only: full-frame pictures and text on a 384x273 hyperscreen with the display off and the borders opened, drawn with sprites stretched by rewriting the Y-expand register every line and shown as two alternating frames. |
| Krestage 3 | <https://csdb.dk/release/download.php?id=58941> | `KRESTAGE3.D64` → first file | C64 PAL variant, per-line sprites switched on. Two pictures under a scroller, drawn with X-expanded sprites whose expand, multicolour and priority bits are switched while they shift (the sprite split effect), nine sprites on a line and 50-pixel-wide sprites among them; the demo checks the chip for these before it starts. |
| Chars Sucks | <https://csdb.dk/release/download.php?id=244748> | `TRIAD_Charssucks.d64` → first file | C64 PAL variant, per-line sprites switched on. No characters at all: the display is off for the whole frame and the vertical border left open, the logo and the scroller are sprites behind the idle graphics, and the blocks' shading on the two X-expanded sprites is the idle byte of the VIC-II's bank, rewritten twice per line for a few cycles at a time. |

## Online / modem-style software

Expand Down
2 changes: 1 addition & 1 deletion docs/systems/c64/libraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ rasterizer does hold a character row's 40 screen codes and colour nibbles the wa
the VIC-II does: fetched on the row's first line and shown for its remaining seven, so a screen
write made after that fetch appears from the next row on. When a CPU read is stalled, the VIC-II
and the renderer are brought through the stalled cycles before the CPU continues, so what the
VIC-II fetched during the stall reflects memory before the stalled instruction's write. The VIC-II's bank follows the levels on CIA 2 port A's two bank pins, not the bytes written: a bit the direction register makes an input floats up through its pull-up, so a program selects a bank with `$DD02` as well as with `$DD00`.
VIC-II fetched during the stall reflects memory before the stalled instruction's write. A CPU write into the VIC-II's bank is likewise seen by the chip's fetches from the cycle after the write on, not from the instruction's end: the chip reads in a cycle's first clock phase and the CPU writes in its second, so a byte rewritten in the middle of a line reaches the screen from the column after the write's cycle (an idle byte changed mid-line, graphics rewritten as they are fetched). The VIC-II's bank follows the levels on CIA 2 port A's two bank pins, not the bytes written: a bit the direction register makes an input floats up through its pull-up, so a program selects a bank with `$DD02` as well as with `$DD00`.

The VIC-II also takes the bus from the CPU as on hardware: 40 cycles on every bad line (BA low
from cycle 12, video matrix fetches in cycles 15-54) and two cycles per sprite with DMA on, BA
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public class C64MenuViewModel : ViewModelBase, ISystemMenuContributor
{"unfortunatecoincidence", new C64DownloadProgramInfo("Unfortunate Coincidence", "https://csdb.dk/release/download.php?id=245796", downloadType: C64DownloadProgramType.Prg, c64Variant: "C64PAL", audioEnabled: true, requiresPerLineSprites: true)},
{"smoothandwonders", new C64DownloadProgramInfo("Smooth and Wonders", "https://csdb.dk/release/download.php?id=245318", downloadType: C64DownloadProgramType.Prg, c64Variant: "C64PAL", audioEnabled: true, requiresPerLineSprites: true)},
{"krestage3", new C64DownloadProgramInfo("Krestage 3", "https://csdb.dk/release/download.php?id=58941", downloadType: C64DownloadProgramType.D64Zip, c64Variant: "C64PAL", audioEnabled: true, requiresPerLineSprites: true, directLoadPRGName: "*")},
{"charssucks", new C64DownloadProgramInfo("Chars Sucks", "https://csdb.dk/release/download.php?id=244748", downloadType: C64DownloadProgramType.D64, c64Variant: "C64PAL", audioEnabled: true, requiresPerLineSprites: true, directLoadPRGName: "*")},
{"ghostngoblins", new C64DownloadProgramInfo("Ghosts 'n Goblins", "https://csdb.dk/release/download.php?id=120333", downloadType: C64DownloadProgramType.D64Zip, c64Variant: "C64PAL", keyboardJoystickEnabled: true, keyboardJoystickNumber: 2, requiresBitmap: true, audioEnabled: true, directLoadPRGName: "*")},
{"gianasisters", new C64DownloadProgramInfo("Giana Sisters", "https://csdb.dk/release/download.php?id=161456", downloadType: C64DownloadProgramType.D64Zip, c64Variant: "C64PAL", keyboardJoystickEnabled: true, keyboardJoystickNumber: 2, requiresBitmap: true, audioEnabled: true, directLoadPRGName: "*")},
{"greenberet", new C64DownloadProgramInfo("Green Beret", "https://csdb.dk/release/download.php?id=157678", downloadType: C64DownloadProgramType.D64Zip, c64Variant: "C64PAL", keyboardJoystickEnabled: true, keyboardJoystickNumber: 2, requiresBitmap: true, audioEnabled: true)},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@
{"unfortunatecoincidence", new C64DownloadProgramInfo("Unfortunate Coincidence", "https://csdb.dk/release/download.php?id=245796", downloadType: C64DownloadProgramType.Prg, c64Variant: "C64PAL", audioEnabled: true, requiresPerLineSprites: true)},
{"smoothandwonders", new C64DownloadProgramInfo("Smooth and Wonders", "https://csdb.dk/release/download.php?id=245318", downloadType: C64DownloadProgramType.Prg, c64Variant: "C64PAL", audioEnabled: true, requiresPerLineSprites: true)},
{"krestage3", new C64DownloadProgramInfo("Krestage 3", "https://csdb.dk/release/download.php?id=58941", downloadType: C64DownloadProgramType.D64Zip, c64Variant: "C64PAL", audioEnabled: true, requiresPerLineSprites: true, directLoadPRGName: "*")},
{"charssucks", new C64DownloadProgramInfo("Chars Sucks", "https://csdb.dk/release/download.php?id=244748", downloadType: C64DownloadProgramType.D64, c64Variant: "C64PAL", audioEnabled: true, requiresPerLineSprites: true, directLoadPRGName: "*")},
{"lastninja", new C64DownloadProgramInfo("Last Ninja", "https://csdb.dk/release/download.php?id=101848", downloadType: C64DownloadProgramType.D64Zip, keyboardJoystickEnabled: true, keyboardJoystickNumber: 2, requiresBitmap: true, audioEnabled: false, directLoadPRGName: "*")},
{"minizork", new C64DownloadProgramInfo("Mini Zork", "https://csdb.dk/release/download.php?id=42919", audioEnabled: false, directLoadPRGName: "*")},
{"montezuma", new C64DownloadProgramInfo("Montezuma's Revenge", "https://csdb.dk/release/download.php?id=128101", downloadType: C64DownloadProgramType.D64Zip, keyboardJoystickEnabled: true, keyboardJoystickNumber: 2, audioEnabled: true, directLoadPRGName: "*")},
Expand Down
8 changes: 8 additions & 0 deletions src/libraries/Highbyte.DotNet6502.Systems.Commodore64/C64.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ public class C64 : ISystem, ISystemMonitor, ISystemState, ISystemCleanup, ISyste

private IRenderProvider? _renderProvider;
public IRenderProvider? RenderProvider => _renderProvider;

/// <summary>
/// The current render provider if it draws cycle by cycle and can be caught up to the VIC-II in
/// the middle of an instruction (see <see cref="IVic2CycleRenderer"/>), otherwise null.
/// </summary>
internal IVic2CycleRenderer? Vic2CycleRenderer { get; private set; }
public List<IRenderProvider> RenderProviders { get; } = new();

private IAudioProvider? _audioProvider;
Expand Down Expand Up @@ -429,11 +435,13 @@ private void SetCurrentRenderProvider(Type? renderProviderType)
if (renderProviderType == null)
{
_renderProvider = null;
Vic2CycleRenderer = null;
return;
}
var renderProvider = RenderProviders.SingleOrDefault(rp => rp.GetType() == renderProviderType)
?? throw new ArgumentException("The specified render provider type is not available.");
_renderProvider = renderProvider;
Vic2CycleRenderer = renderProvider as IVic2CycleRenderer;
}

private static void ConfigureRenderer(C64 c64, C64Config config)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Highbyte.DotNet6502.Systems.Commodore64.Render.Rasterizer;

/// <summary>
/// A render provider that draws the VIC-II's output cycle by cycle and can therefore be brought
/// up to the chip's position in the middle of a CPU instruction, not only when the instruction
/// has ended (<see cref="IRenderGenerator.OnAfterInstruction"/>). The C64 asks for that where the
/// order of the chip's fetches and the CPU's accesses within an instruction decides the picture:
/// while a read is held by the chip's bus request, and before a write into the chip's bank lands.
/// </summary>
public interface IVic2CycleRenderer
{
/// <summary>Draw every cycle between where the renderer last stopped and where the VIC-II is now.</summary>
void CatchUpToVic2();
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ namespace Highbyte.DotNet6502.Systems.Commodore64.Render.Rasterizer;
/// </summary>
public interface IVic2RasterizerPixelGenerator
{
/// <summary>Draw the pixels of the cycles the CPU has executed since the last call.</summary>
void OnAfterInstruction();
/// <summary>Draw the pixels of every cycle between where the generator last stopped and where the VIC-II is now.</summary>
void CatchUpToVic2();

/// <summary>Finish the frame: the last line, the end-of-frame sprite pass, register resync.</summary>
void OnEndFrame();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ namespace Highbyte.DotNet6502.Systems.Commodore64.Render.Rasterizer;
/// - Fine scroll per raster line
/// - Sprites (Standard, MultiColor). Multiplexing supported when perLineSprites is enabled.

public sealed class Vic2Rasterizer : IRenderProvider, IVideoFrameLayerProvider
public sealed class Vic2Rasterizer : IRenderProvider, IVideoFrameLayerProvider, IVic2CycleRenderer
{
public string Name => "Vic2Rasterizer";

Expand Down Expand Up @@ -114,18 +114,22 @@ public Vic2Rasterizer(C64 c64, bool useDoubleBuffering = true, bool perLineSprit
//}

// Called after each instruction
public void OnAfterInstruction()
public void OnAfterInstruction() => CatchUpToVic2();

// Called after each instruction, and by the C64 in the middle of one where the order of the
// chip's fetches and the CPU's accesses decides the picture (see IVic2CycleRenderer).
public void CatchUpToVic2()
{
// Write pixels of current x,y into _back at [y*StrideBytes + x*4 ..]
_pixelGenerator.OnAfterInstruction();
_pixelGenerator.CatchUpToVic2();
}

//public void OnEndScanline(int y)
//{
// ScanlineCompleted?.Invoke(this, y);
//}

// Called once per frame after all OnAfterInstruction calls are executed
// Called once per frame after all OnAfterInstruction and CatchUpToVic2 calls are executed
public void OnEndFrame()
{
_pixelGenerator.OnEndFrame();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ public sealed class Vic2RasterizerUintPixelGenerator : IVic2RasterizerPixelGener
private byte _backgroundColor3;

// Journal of VIC-II register writes, filled by the VIC-II as the CPU writes (see
// Vic2.RegisterWriteObserver) and consumed cycle by cycle in OnAfterInstruction, which then
// Vic2.RegisterWriteObserver) and consumed cycle by cycle in CatchUpToVic2, which then
// keeps only the entries it could not apply yet (a write on the very cycle it stopped at takes
// effect on the next one). One instruction makes at most a few writes, so the capacity is only
// reached when this generator is not the render provider being driven; then the journal is
Expand Down Expand Up @@ -175,7 +175,7 @@ private struct RegisterWrite
private readonly Action<Span<uint>, int, int, int> _setForegroundPixels; // source, sourceIndex, destIndex, width
private readonly Action<int, int> _clearForegroundPixels; // destIndex, width

// When true, sprites are rendered per raster line during OnAfterInstruction (enables
// When true, sprites are rendered per raster line during CatchUpToVic2 (enables
// sprite multiplexing) instead of once at end-of-frame. See DrawSpritesForLine.
private readonly bool _perLineSprites;

Expand Down Expand Up @@ -521,7 +521,7 @@ private void FinishLineRuns()
/// Write screen data for all clock cycles since last time this method was called.
/// Instructions can take different amount of cycles to execute, so this method is called after each instruction to update the screen data and will catch up on what's to do since last time it was called.
/// </summary>
public void OnAfterInstruction()
public void CatchUpToVic2()
{
if (_registerWritesOverflowed)
ResyncColorRegisters();
Expand Down Expand Up @@ -685,7 +685,7 @@ public void OnEndFrame()
FinishLineRuns();
ResyncColorRegisters();

// Per-line mode draws sprites during OnAfterInstruction; skip the end-of-frame pass.
// Per-line mode draws sprites during CatchUpToVic2; skip the end-of-frame pass.
if (!_perLineSprites)
{
DrawSpritesToBitmapBackedByPixelArray();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ public sealed class Vic2RasterizerSequencerPixelGenerator : IVic2RasterizerPixel
private byte _backgroundColor3;

// Journal of VIC-II register writes, filled by the VIC-II as the CPU writes (see
// Vic2.RegisterWriteObserver) and consumed cycle by cycle in OnAfterInstruction, which then
// Vic2.RegisterWriteObserver) and consumed cycle by cycle in CatchUpToVic2, which then
// keeps only the entries it could not apply yet (a write on the very cycle it stopped at takes
// effect on the next one). One instruction makes at most a few writes, so the capacity is only
// reached when this generator is not the render provider being driven; then the journal is
Expand Down Expand Up @@ -235,7 +235,7 @@ private struct RegisterWrite
private readonly Action<Span<uint>, int, int, int> _setForegroundPixels; // source, sourceIndex, destIndex, width
private readonly Action<int, int> _clearForegroundPixels; // destIndex, width

// When true, sprites are rendered per raster line during OnAfterInstruction (enables
// When true, sprites are rendered per raster line during CatchUpToVic2 (enables
// sprite multiplexing) instead of once at end-of-frame. See DrawSpritesForLine.
private readonly bool _perLineSprites;

Expand Down Expand Up @@ -668,7 +668,7 @@ private void FinishLineRuns()
/// Write screen data for all clock cycles since last time this method was called.
/// Instructions can take different amount of cycles to execute, so this method is called after each instruction to update the screen data and will catch up on what's to do since last time it was called.
/// </summary>
public void OnAfterInstruction()
public void CatchUpToVic2()
{
if (_registerWritesOverflowed)
ResyncColorRegisters();
Expand Down Expand Up @@ -829,7 +829,7 @@ public void OnEndFrame()
FinishLineRuns();
ResyncColorRegisters();

// Per-line mode draws sprites during OnAfterInstruction; skip the end-of-frame pass. A
// Per-line mode draws sprites during CatchUpToVic2; skip the end-of-frame pass. A
// sprite the VIC-II never output this frame (its X never met the beam, or met it only
// inside its own fetch) is not shown, as on the chip.
if (!_perLineSprites)
Expand Down
29 changes: 19 additions & 10 deletions src/libraries/Highbyte.DotNet6502.Systems.Commodore64/Video/Vic2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,13 @@ private void MapRegisterMirrors(

/// <summary>
/// Method to be called before each write to memory by the CPU.
/// It's used for optimization to detect changes in VIC2 video memory.
/// For a write into the VIC-II's bank it first brings the VIC-II and the renderer through the
/// write's own cycle, so the fetches of the cycles before the write, and the fetch of the
/// write's cycle itself (the chip reads in the first clock phase, the CPU writes in the
/// second), see memory as it was: a byte the CPU rewrites in the middle of a line reaches the
/// screen from the column after the write on, as on hardware (an idle byte changed mid-line,
/// a sprite's data rewritten as it is fetched). It's also used for optimization to detect
/// changes in VIC2 video memory.
/// </summary>
/// <param name="c64Address"></param>
/// <param name="value"></param>
Expand All @@ -635,6 +641,9 @@ public void InspectVic2MemoryValueUpdateFromCPU(ushort c64Address, byte value)
var vic2Address = GetVic2FromC64Address(c64Address);
if (vic2Address.HasValue)
{
CatchUpTo(C64.CPU.BusCycles);
C64.Vic2CycleRenderer?.CatchUpToVic2();

SpriteManager.DetectChangesToSpriteData(vic2Address.Value, value);

if (DisplayMode == DispMode.Text)
Expand All @@ -660,35 +669,35 @@ public void InspectVic2MemoryValueUpdateFromCPU(ushort c64Address, byte value)
case 0:
vic2Address = c64Address switch
{
>= 0x0000 and < 0x0fff => c64Address, // video ram
>= 0x1000 and < 0x1fff => c64Address, // chargen ROM
>= 0x2000 and < 0x3fff => c64Address, // video ram
>= 0x0000 and <= 0x0fff => c64Address, // video ram
>= 0x1000 and <= 0x1fff => c64Address, // chargen ROM
>= 0x2000 and <= 0x3fff => c64Address, // video ram
_ => null, // not a address mapped by VIC2
};
break;

case 1:
vic2Address = c64Address switch
{
>= 0x4000 and < 0x7fff => (ushort)(c64Address - 0x4000), // video ram
>= 0x4000 and <= 0x7fff => (ushort)(c64Address - 0x4000), // video ram
_ => null, // not a address mapped by VIC2
};
break;

case 2:
vic2Address = c64Address switch
{
>= 0x8000 and < 0x8fff => (ushort)(c64Address - 0x8000), // video ram
>= 0x9000 and < 0x9fff => (ushort)(c64Address - 0x8000), // chargen rom
>= 0xa000 and < 0xbfff => (ushort)(c64Address - 0x8000), // video ram
>= 0x8000 and <= 0x8fff => (ushort)(c64Address - 0x8000), // video ram
>= 0x9000 and <= 0x9fff => (ushort)(c64Address - 0x8000), // chargen rom
>= 0xa000 and <= 0xbfff => (ushort)(c64Address - 0x8000), // video ram
_ => null, // not a address mapped by VIC2
};
break;

case 3:
vic2Address = c64Address switch
{
>= 0xc000 and < 0xffff => (ushort)(c64Address - 0xc000), // video ram
>= 0xc000 and <= 0xffff => (ushort)(c64Address - 0xc000), // video ram
_ => null, // not a address mapped by VIC2
};
break;
Expand Down Expand Up @@ -1610,7 +1619,7 @@ private void AdvanceRaster(ulong cyclesConsumed, ulong endBusCycle)
// Per-line sprite processing (rendering + collision are gated by the same config flag).
// Capture the shared start-of-line sprite snapshot once here; both the per-line collision
// (below) and the rasterizer's per-line sprite pass (later this instruction, in its
// OnAfterInstruction) read it - so the registers are sampled once per line, not twice.
// CatchUpToVic2) read it - so the registers are sampled once per line, not twice.
if (SpriteManager.PerLineCollisionEnabled)
{
SpriteManager.CaptureLineSpriteSnapshot(line);
Expand Down
Loading
Loading