atari8: renamed to a8lib*

This commit is contained in:
Steven Hugg committed 2026-09-19 19:23:55 +02:00
1 parent 087759549d
commit aef3898aa7
17 files changed
+628 -600

No files matched your search

-1
View File
@@ -1 +0,0 @@
../atari8-800/common.c
-1
View File
@@ -1 +0,0 @@
../atari8-800/common.h
-1
View File
@@ -1 +0,0 @@
../atari8-800/common_irq.s
-1
View File
@@ -1 +0,0 @@
../atari8-800/libdemo.c
-1
View File
@@ -1 +0,0 @@
../atari8-800/pokeymusic.ca65
+163
View File
@@ -0,0 +1,163 @@
/*
* a8lib.c - helper library for cc65 Atari 8-bit programming.
* See a8lib.h for the interface and libdemo.c for an example.
*/
#include "a8lib.h"
/*==========================================================================*/
/* Display list builder */
/*==========================================================================*/
/* Make sure this array doesn't cross a 1K boundary */
#ifdef __CC65__
/* we really should use a custom .cfg file with the align attribute */
/* but let's just put it here and maybe no one will notice */
#if defined(__ATARI5200__)
byte* a8_dlist = (byte*)0x1700;
#else
byte* a8_dlist = (byte*)0x9700;
#endif
#else
byte a8_dlist[256];
#pragma align(a8_dlist, 0x100)
#endif
byte a8_dlist_len;
byte a8_dli_count;
byte a8_dli_lines = 1; /* DLI bits in the installed list */
byte a8_dmactl_val = 0x22; /* playfield normal + DMA fetch */
void a8_set_dmactl(byte v) {
a8_dmactl_val = v;
A8_SDMCTL = v;
ANTIC.dmactl = v;
}
void a8_dlist_reset(void) {
a8_dlist_len = 0;
a8_dli_count = 0;
a8_dli_lines = 1;
// TODO: a8_dli_clear();
// TODO: a8_scroll_reset();
}
void a8_dlist_byte(byte b) {
a8_dlist[a8_dlist_len++] = b;
}
void a8_dlist_blank(byte n) {
if (n < 1) n = 1;
while (n > 8) {
a8_dlist_byte(0x70); /* DL_BLK8 */
n -= 8;
}
a8_dlist_byte((byte)((n - 1) << 4));
}
byte a8_dlist_line(byte mode, const void* data, byte dli) {
byte idx = 0xff;
if (dli) {
mode = DL_DLI(mode);
idx = a8_dli_count;
if (a8_dli_count < 0xff) a8_dli_count++;
}
if (data) mode = DL_LMS(mode);
a8_dlist_byte(mode);
if (data) {
word a = (word)data;
a8_dlist_byte((byte)(a & 0xff));
a8_dlist_byte((byte)(a >> 8));
}
return idx;
}
void a8_dlist_finish(void) {
word a = (word)a8_dlist;
a8_dlist_byte(DL_JVB);
a8_dlist_byte((byte)(a & 0xff));
a8_dlist_byte((byte)(a >> 8));
/* One DLI fires per frame for each flagged line, so wrapping the
dispatch index at this count keeps it in phase without a VBI hook. */
a8_dli_lines = a8_dli_count ? a8_dli_count : 1;
}
void a8_dlist_install(void) {
word a = (word)a8_dlist;
#if defined(__ATARI5200__)
*(byte*)A8_SDLSTL_ADDR = (byte)(a & 0xff);
*(byte*)A8_SDLSTH_ADDR = (byte)(a >> 8);
#else
#ifdef __CC65__
OS.sdlstl = (byte)(a & 0xff);
OS.sdlsth = (byte)(a >> 8);
#else
OS._sdl._st.stl = (byte)(a & 0xff);
OS._sdl._st.sth = (byte)(a >> 8);
#endif
#endif
ANTIC.dlistl = (byte)(a & 0xff);
ANTIC.dlisth = (byte)(a >> 8);
a8_set_dmactl(0x22);
a8_waitvsync();
}
void a8_dlist_off(void) {
a8_set_dmactl(0x00);
}
void a8_set_playfield_width(byte w) {
a8_set_dmactl((byte)((a8_dmactl_val & ~0x03) | (w & 0x03)));
}
/*==========================================================================*/
/* Colors */
/*==========================================================================*/
void a8_set_colpf(byte i, byte c) {
if (i > 3) i = 3;
((byte*)&GTIA_WRITE.colpf0)[i] = c;
A8_SHDW_PF[i] = c;
}
void a8_set_colbk(byte c) {
GTIA_WRITE.colbk = c;
A8_SHDW_BK = c;
}
void a8_set_colpm(byte i, byte c) {
if (i > 3) i = 3;
((byte*)&GTIA_WRITE.colpm0)[i] = c;
A8_SHDW_PM[i] = c;
}
/*==========================================================================*/
/* Input and utility */
/*==========================================================================*/
byte a8_get_console(void) {
return GTIA_READ.consol;
}
byte a8_get_trigger(byte n) {
return (byte)(((byte*)&GTIA_READ.trig0)[n] & 1 ? 0 : 1);
}
#if !defined(__ATARI5200__)
byte a8_get_stick(byte n) {
byte v = n ? PIA.portb : PIA.porta;
return (byte)((~v) & 0x1f);
}
#endif
void a8_waitvsync(void) {
#if defined(__ATARI5200__)
volatile byte* rtc = (volatile byte*)0x0002; /* RTCLOK+1 */
#else
volatile byte* rtc = &OS.rtclok[2]; /* frame counter */
#endif
byte t = *rtc;
while (*rtc == t) ;
}
@@ -1,5 +1,5 @@
#ifndef _ATARI8_COMMON_H
#define _ATARI8_COMMON_H
#ifndef _ATARI8_A8LIB_H
#define _ATARI8_A8LIB_H
/*
* common.h - small helper library for cc65 Atari 8-bit programming.
@@ -46,6 +46,36 @@ typedef unsigned int word;
#define A8_AUDC_POLYS_NONE 0xA0 /* pure square wave */
#define A8_AUDC_POLYS_4 0xC0 /* rumble */
/* Target-specific display-list / NMI vector locations. The 5200 BIOS
keeps its page-2 vectors in a more compact layout than the 800 OS. */
#if defined(__ATARI5200__)
#define A8_VDSLST_ADDR 0x0206
#define A8_SDLSTL_ADDR 0x0005
#define A8_SDLSTH_ADDR 0x0006
#define A8_SDMCTL_ADDR 0x0007
#define A8_CHBASE_VAL 0xf8
#else
#define A8_VDSLST_ADDR 0x0200
#define A8_CHBASE_VAL 0xe0
#endif
#define A8_VDSLST (*(void(**)(void))A8_VDSLST_ADDR)
/* OS shadow registers. The 800's vertical-blank routine copies these to
the hardware every frame, so anything we change mid-frame has to be
written here too or it gets stomped. The 5200's BIOS uses the same
zero-page slots (cc65's 5200 conio uses them as well). */
#if defined(__ATARI5200__)
#define A8_SHDW_PM ((byte*)0x0008)
#define A8_SHDW_PF ((byte*)0x000c)
#define A8_SHDW_BK (*(byte*)0x0010)
#define A8_SDMCTL (*(byte*)0x0007)
#else
#define A8_SHDW_PM (&OS.pcolr0)
#define A8_SHDW_PF (&OS.color0)
#define A8_SHDW_BK (OS.color4)
#define A8_SDMCTL (OS.sdmctl)
#endif
/*==========================================================================*/
/* Display list builder (ANTIC) */
@@ -56,7 +86,9 @@ typedef unsigned int word;
extern byte a8_dlist_len; /* current length in bytes */
extern byte a8_dli_count; /* number of DLI bits emitted so far */
/* reset: call a8_dli_clear() and a8_scroll_reset() first, if linked */
void a8_dlist_reset(void);
/* add a byte to the dlist */
void a8_dlist_byte(byte b);
/* Emit n blank scanlines (any count; splits into DL_BLK8 chunks). */
void a8_dlist_blank(byte n);
@@ -83,20 +115,25 @@ void a8_set_playfield_width(byte w);
/* Registers that a8_dli_set() understands. Anything else can be written
from a user callback with a8_dli_hook. */
enum {
A8_REG_COLBK = 0,
A8_REG_COLPF0, A8_REG_COLPF1, A8_REG_COLPF2, A8_REG_COLPF3,
A8_REG_COLPM0, A8_REG_COLPM1, A8_REG_COLPM2, A8_REG_COLPM3,
A8_REG_PRIOR,
A8_REG_HPOSP0, A8_REG_HPOSP1, A8_REG_HPOSP2, A8_REG_HPOSP3,
A8_REG_HPOSP0 = 0, A8_REG_HPOSP1, A8_REG_HPOSP2, A8_REG_HPOSP3,
A8_REG_HPOSM0, A8_REG_HPOSM1, A8_REG_HPOSM2, A8_REG_HPOSM3,
A8_REG_SIZEP0, A8_REG_SIZEP1, A8_REG_SIZEP2, A8_REG_SIZEP3,
A8_REG_SIZEM,
A8_REG_GRAFP0, A8_REG_GRAFP1, A8_REG_GRAFP2, A8_REG_GRAFP3,
A8_REG_GRAFM,
A8_REG_VDELAY, A8_REG_GRACTL, A8_REG_HITCLR,
A8_REG_CHBASE, A8_REG_CHACTL,
A8_REG_HSCROL, A8_REG_VSCROL, A8_REG_DMACTL,
A8_REG_COUNT
A8_REG_COLPM0, A8_REG_COLPM1, A8_REG_COLPM2, A8_REG_COLPM3,
A8_REG_COLPF0, A8_REG_COLPF1, A8_REG_COLPF2, A8_REG_COLPF3,
A8_REG_COLBK,
A8_REG_PRIOR,
A8_REG_VDELAY,
A8_REG_GRACTL,
A8_REG_HITCLR,
/* If high bit set, ANTIC, otherwise GTIA. */
A8_REG_DMACTL = 0x80,
A8_REG_CHACTL = 0x81,
A8_REG_HSCROL = 0x84,
A8_REG_VSCROL = 0x85,
A8_REG_CHBASE = 0x89,
};
/* How many display-list DLIs we can service, and how many register
+58
View File
@@ -0,0 +1,58 @@
#include <string.h>
#include "a8lib.h"
/*==========================================================================*/
/* DLI */
/*==========================================================================*/
/* reg,val pairs terminated by A8_DLI_NONE in the reg slot */
byte a8_dli_tab[A8_DLI_LINES][A8_DLI_WRITES * 2];
byte a8_dli_line;
void (*a8_dli_hook)(void);
void a8_dli_clear(void) {
memset(a8_dli_tab, 0xff, sizeof(a8_dli_tab));
a8_dli_line = 0;
}
void a8_dli_set(byte line, byte reg, byte val) {
byte j;
byte* dli_line = a8_dli_tab[line];
if (line >= A8_DLI_LINES) return;
for (j = 0; j < A8_DLI_WRITES; j++) {
if (*dli_line == 0xff) {
dli_line[0] = reg;
dli_line[1] = val;
return;
}
dli_line += 2;
}
}
void a8_dli_install(void) {
a8_dli_line = 0;
A8_VDSLST = a8_dli_stub;
ANTIC.nmien = NMIEN_VBI | NMIEN_DLI;
}
void a8_dli_remove(void) {
ANTIC.nmien = NMIEN_VBI;
}
#ifdef __OSCAR64C__
__hwinterrupt void a8_dli_stub() {
if (a8_dli_line >= a8_dli_lines) a8_dli_line = 0;
byte* w = &a8_dli_tab[a8_dli_line][0];
for (byte i = 0; i < A8_DLI_WRITES; i++) {
byte r = w[0];
if (r == 0xff) break;
void* base = r & 0x80 ? (void*)&ANTIC : (void*)&GTIA_WRITE;
((byte*)base)[r & 0x1f] = w[1];
w += 2;
}
music_duty();
a8_dli_line++;
}
#endif
@@ -1,13 +1,7 @@
;
; common_irq.s - interrupt trampoline for presets/atari8-800/common.c
;
; _a8_dli_stub
; The Atari DLI is delivered as an NMI: the OS (or the 5200 BIOS) jumps
; through the VDSLST vector, and the handler has to finish with RTI.
; cc65 has no way to express that in C, so this tiny stub saves the C
; runtime zero page, calls the C dispatcher, and returns with RTI.
;
; It also restores A/X/Y, so the interrupted program sees no side effects.
;
.export _a8_dli_stub
.import _a8_dli_dispatch
@@ -61,12 +55,18 @@ _a8_dli_stub:
lda _a8_dli_tab,y
; if (r == 0xff) break;
bmi @doneline
; *a8_dli_regs[r] = w[1];
asl
tax
lda _a8_dli_regs,x
; lo byte
pha
and #$1f
sta tmp
lda _a8_dli_regs+1,x
pla
; hi byte calculated ($d4 or $d0)
; TODO: Atari 5200
asl
lda #$d0
bcc @gtia
lda #$d4
@gtia:
sta tmp+1
iny
lda _a8_dli_tab,y
+155
View File
@@ -0,0 +1,155 @@
#include <string.h>
#include "a8lib.h"
extern byte a8_dmactl_val;
extern void a8_set_dmactl(byte val);
/*==========================================================================*/
/* Player/Missile graphics */
/*==========================================================================*/
/* Need 2K for a single-line P/M area, plus up to 2K of alignment slack. */
#ifdef __CC65__
#if defined(__ATARI5200__)
static byte* a8_pmg_base = (byte*)0x1800;
#else
static byte* a8_pmg_base = (byte*)0x9800;
#endif
#else
byte a8_pmg_base[0x800];
#pragma align(a8_pmg_base, 0x800)
#endif
static byte a8_pmg_mode;
byte* a8_pmg_player(byte i) {
if (a8_pmg_mode == A8_PMG_SINGLE)
return a8_pmg_base + 0x400 + ((word)i << 8);
return a8_pmg_base + 0x200 + ((word)i << 7);
}
byte* a8_pmg_missile(byte i) {
(void)i; /* the four missiles share one 128-byte region */
return a8_pmg_base + ((a8_pmg_mode == A8_PMG_SINGLE) ? 0x300 : 0x180);
}
void a8_pmg_clear(void) {
memset(a8_pmg_base, 0, 0x800);
}
void a8_pmg_init(byte mode) {
word i;
a8_pmg_mode = mode;
a8_pmg_clear();
GTIA_WRITE.gractl = 0; /* disable while we set up */
ANTIC.pmbase = (byte)((word)a8_pmg_base >> 8);
a8_dmactl_val = (a8_dmactl_val & ~0x1c) | 0x0c | (mode & 0x10);
a8_set_dmactl(a8_dmactl_val);
GTIA_WRITE.gractl = GRACTL_PLAYERS | GRACTL_MISSLES;
GTIA_WRITE.prior = PRIOR_P03_PF03;
for (i = 0; i < 4; i++)
a8_pmg_set_color(i, (byte)(0x10 + i * 0x20));
}
void a8_pmg_off(void) {
GTIA_WRITE.gractl = 0;
a8_dmactl_val &= ~0x1c;
a8_set_dmactl(a8_dmactl_val);
}
void a8_pmg_set_x(byte i, byte x) {
((byte*)&GTIA_WRITE.hposp0)[i] = x;
}
void a8_pmg_set_color(byte i, byte c) {
((byte*)&GTIA_WRITE.colpm0)[i] = c;
A8_SHDW_PM[i] = c;
}
void a8_pmg_set_size(byte i, byte size) {
((byte*)&GTIA_WRITE.sizep0)[i] = size;
}
void a8_pmg_set_shape(byte i, const byte* shape, byte len, byte y) {
byte* p = a8_pmg_player(i) + y;
while (len--) *p++ = *shape++;
}
void a8_pmg_set_missile_x(byte i, byte x) {
((byte*)&GTIA_WRITE.hposm0)[i] = x;
}
void a8_pmg_set_missile_width(byte i, byte size) {
GTIA_WRITE.sizem = (byte)((GTIA_WRITE.sizem & ~(3 << (i * 2)))
| ((size & 3) << (i * 2)));
}
byte a8_pmg_hit_pf(byte i) {
return ((byte*)&GTIA_READ.p0pf)[i];
}
byte a8_pmg_hit_pl(void) {
byte i, m = 0;
for (i = 0; i < 4; i++) m |= ((byte*)&GTIA_READ.p0pl)[i];
return m;
}
void a8_pmg_clear_collisions(void) {
GTIA_WRITE.hitclr = 0;
}
static byte pmg_sizem;
void a8_pmg_5th_enable(byte on) {
if (on) {
GTIA_WRITE.prior |= PRIOR_5TH_PLAYER;
GTIA_WRITE.gractl |= GRACTL_MISSLES;
GTIA_WRITE.sizem = 0; /* every missile 2 px wide, so they tile */
} else {
GTIA_WRITE.prior &= (byte)~PRIOR_5TH_PLAYER;
}
pmg_sizem = 0;
}
/*
0: 8 color clocks (1 per pixel)
1: 16 color clocks (2 per pixel)
3: 32 color clocks (4 per pixel)
*/
void a8_pmg_5th_size(byte size) {
size &= 3;
pmg_sizem = size;
size |= size << 2;
size |= size << 4;
GTIA_WRITE.sizem = size;
}
static const byte pmg_size_tab[4] = { 2, 4, 2, 8 };
void a8_pmg_5th_x(byte x) {
byte* m = (byte*)&GTIA_WRITE.hposm0;
byte inc = pmg_size_tab[pmg_sizem];
m[0] = x;
x += inc;
m[1] = x;
x += inc;
m[2] = x;
x += inc;
m[3] = x;
}
void a8_pmg_5th_shape(const byte* shape, byte len, byte y) {
byte* p = a8_pmg_missile(0) + y;
while (len--) {
/* GRAFM packs two bits per missile, M0 in bits 1:0, and each missile
shifts its pair out MSB first, so the leftmost pixel is bit 1. Swap
the bit pairs to accept the usual player order (bit 7 = leftmost). */
byte s = *shape++;
*p++ = (byte)(((s >> 6) & 0x03)
| ((s >> 2) & 0x0c)
| ((s << 2) & 0x30)
| ((s << 6) & 0xc0));
}
}
+67
View File
@@ -0,0 +1,67 @@
#ifdef __CC65__
#include <6502.h>
#endif
#include "a8lib.h"
/*==========================================================================*/
/* POKEY */
/*==========================================================================*/
static byte a8_irq_stack[128];
void a8_pokey_init(void) {
POKEY_WRITE.skctl = SKCTL_KEYBOARD_DEBOUNCE | SKCTL_KEYBOARD_SCANNING;
POKEY_WRITE.audctl = AUDCTL_CLOCKBASE_15HZ;
a8_pokey_stop_all();
}
void a8_pokey_sound(byte chan, byte freq, byte ctrl, byte vol) {
byte* p = (byte*)&POKEY_WRITE.audf1 + (word)(chan << 1);
p[0] = freq;
p[1] = (byte)(ctrl | (vol & 0x0f));
}
void a8_pokey_stop(byte chan) {
((byte*)&POKEY_WRITE.audc1)[chan << 1] = 0;
}
void a8_pokey_stop_all(void) {
byte i;
for (i = 0; i < 4; i++) a8_pokey_stop(i);
}
#ifdef __CC65__
/* called from set_irq() */
unsigned char a8_pokey_music_update() {
music_tick();
music_duty();
return IRQ_NOT_HANDLED;
}
void a8_pokey_music_init(void) {
set_irq(a8_pokey_music_update, a8_irq_stack, sizeof(a8_irq_stack));
}
void a8_pokey_music_done(void) {
reset_irq();
}
#endif
#ifdef __OSCAR64C__
void a8_pokey_music_update() {
__asm { pha; txa; pha; tya; pha; } // save regs
music_tick();
music_duty();
__asm { pla; tay; pla; tax; pla; } // restore regs
}
void a8_pokey_music_init(void) {
OS.vvblki = a8_pokey_music_update;
}
void a8_pokey_music_done(void) {
//reset_irq();
}
#endif
+70
View File
@@ -0,0 +1,70 @@
#include "a8lib.h"
/*==========================================================================*/
/* Scrolling */
/*==========================================================================*/
extern byte* a8_dlist;
static byte* a8_scroll_lmsp[A8_SCROLL_MAX];
static const byte* a8_scroll_base[A8_SCROLL_MAX];
static byte a8_scroll_count;
byte a8_scroll_x_shift = 4;
byte a8_scroll_x_mask = 15;
byte a8_scroll_y_shift = 3;
byte a8_scroll_y_mask = 7;
/* Indexed by ANTIC mode: scanlines per line, bytes per HSCROL line, and
log2 of color clocks per byte. */
const byte a8_mode_height[16] = { 0,0, 8,10, 8,16, 8,16, 8, 4, 4, 2, 1, 2, 1, 1 };
static const byte a8_mode_stride[16] = { 0,0, 48,48,48,48,24,24,12,12,24,24,24,48,48,48 };
static const byte a8_mode_xshift[16] = { 0,0, 2, 2, 2, 2, 3, 3, 4, 4, 3, 3, 3, 2, 2, 2 };
byte a8_scroll_mode(byte mode) {
byte h;
mode &= 15;
a8_scroll_x_shift = a8_mode_xshift[mode];
a8_scroll_x_mask = (byte)((1 << a8_scroll_x_shift) - 1);
h = a8_mode_height[mode];
a8_scroll_y_shift = 0;
while (h > 1) { h >>= 1; a8_scroll_y_shift++; }
a8_scroll_y_mask = (byte)((1 << a8_scroll_y_shift) - 1);
return a8_mode_stride[mode];
}
void a8_scroll_reset(void) {
a8_scroll_count = 0;
}
byte a8_scroll_line(byte mode, const void* base, byte dli) {
byte idx = a8_dlist_line(mode, base, dli);
a8_scroll_add(base, &a8_dlist[a8_dlist_len - 2]);
return idx;
}
void a8_scroll_add(const void* base, byte* lms) {
if (a8_scroll_count >= A8_SCROLL_MAX) return;
a8_scroll_lmsp[a8_scroll_count] = lms;
a8_scroll_base[a8_scroll_count] = (const byte*)base;
a8_scroll_count++;
}
void a8_scroll_move(word byteoff, byte xfine) {
byte i;
for (i = 0; i < a8_scroll_count; i++) {
word a = (word)a8_scroll_base[i] + byteoff;
a8_scroll_lmsp[i][0] = (byte)a;
a8_scroll_lmsp[i][1] = (byte)(a >> 8);
}
/* HSCROL delays the picture, so count down as the LMS counts up */
ANTIC.hscrol = xfine ^ a8_scroll_x_mask;
}
void a8_scroll_set(byte x, byte y, byte stride) {
word off = (word)(y >> a8_scroll_y_shift) * stride + (x >> a8_scroll_x_shift);
a8_scroll_move(off, (byte)(x & a8_scroll_x_mask));
ANTIC.vscrol = y & a8_scroll_y_mask;
}
-546
View File
@@ -1,546 +0,0 @@
/*
* common.c - helper library for cc65 Atari 8-bit programming.
* See common.h for the interface and libdemo.c for an example.
*
* Link it with:
* //#link "common.c"
* //#link "common_irq.s"
*/
#ifdef __CC65__
#include <6502.h>
#endif
#include <string.h>
#include "common.h"
/* Target-specific display-list / NMI vector locations. The 5200 BIOS
keeps its page-2 vectors in a more compact layout than the 800 OS. */
#if defined(__ATARI5200__)
#define A8_VDSLST_ADDR 0x0206
#define A8_SDLSTL_ADDR 0x0005
#define A8_SDLSTH_ADDR 0x0006
#define A8_SDMCTL_ADDR 0x0007
#define A8_CHBASE_VAL 0xf8
#else
#define A8_VDSLST_ADDR 0x0200
#define A8_CHBASE_VAL 0xe0
#endif
#define A8_VDSLST (*(void(**)(void))A8_VDSLST_ADDR)
/* OS shadow registers. The 800's vertical-blank routine copies these to
the hardware every frame, so anything we change mid-frame has to be
written here too or it gets stomped. The 5200's BIOS uses the same
zero-page slots (cc65's 5200 conio uses them as well). */
#if defined(__ATARI5200__)
#define A8_SHDW_PM ((byte*)0x0008)
#define A8_SHDW_PF ((byte*)0x000c)
#define A8_SHDW_BK (*(byte*)0x0010)
#define A8_SDMCTL (*(byte*)0x0007)
#else
#define A8_SHDW_PM (&OS.pcolr0)
#define A8_SHDW_PF (&OS.color0)
#define A8_SHDW_BK (OS.color4)
#define A8_SDMCTL (OS.sdmctl)
#endif
/*==========================================================================*/
/* Display list builder */
/*==========================================================================*/
/* Make sure this array doesn't cross a 1K boundary */
#ifdef __CC65__
/* we really should use a custom .cfg file with the align attribute */
/* but let's just put it here and maybe no one will notice */
#if defined(__ATARI5200__)
static byte* a8_dlist = (byte*)0x1700;
#else
static byte* a8_dlist = (byte*)0x9700;
#endif
#else
byte a8_dlist[256];
#pragma align(a8_dlist, 0x100)
#endif
byte a8_dlist_len;
byte a8_dli_count;
byte a8_dli_lines = 1; /* DLI bits in the installed list */
byte a8_dmactl_val = 0x22; /* playfield normal + DMA fetch */
static void a8_set_dmactl(byte v) {
a8_dmactl_val = v;
A8_SDMCTL = v;
ANTIC.dmactl = v;
}
void a8_dlist_reset(void) {
a8_dlist_len = 0;
a8_dli_count = 0;
a8_dli_lines = 1;
a8_dli_clear();
a8_scroll_reset();
}
void a8_dlist_byte(byte b) {
a8_dlist[a8_dlist_len++] = b;
}
void a8_dlist_blank(byte n) {
if (n < 1) n = 1;
while (n > 8) {
a8_dlist_byte(0x70); /* DL_BLK8 */
n -= 8;
}
a8_dlist_byte((byte)((n - 1) << 4));
}
byte a8_dlist_line(byte mode, const void* data, byte dli) {
byte idx = 0xff;
if (dli) {
mode = DL_DLI(mode);
idx = a8_dli_count;
if (a8_dli_count < 0xff) a8_dli_count++;
}
if (data) mode = DL_LMS(mode);
a8_dlist_byte(mode);
if (data) {
word a = (word)data;
a8_dlist_byte((byte)(a & 0xff));
a8_dlist_byte((byte)(a >> 8));
}
return idx;
}
void a8_dlist_finish(void) {
word a = (word)a8_dlist;
a8_dlist_byte(DL_JVB);
a8_dlist_byte((byte)(a & 0xff));
a8_dlist_byte((byte)(a >> 8));
/* One DLI fires per frame for each flagged line, so wrapping the
dispatch index at this count keeps it in phase without a VBI hook. */
a8_dli_lines = a8_dli_count ? a8_dli_count : 1;
}
void a8_dlist_install(void) {
word a = (word)a8_dlist;
#if defined(__ATARI5200__)
*(byte*)A8_SDLSTL_ADDR = (byte)(a & 0xff);
*(byte*)A8_SDLSTH_ADDR = (byte)(a >> 8);
#else
#ifdef __CC65__
OS.sdlstl = (byte)(a & 0xff);
OS.sdlsth = (byte)(a >> 8);
#else
OS._sdl._st.stl = (byte)(a & 0xff);
OS._sdl._st.sth = (byte)(a >> 8);
#endif
#endif
ANTIC.dlistl = (byte)(a & 0xff);
ANTIC.dlisth = (byte)(a >> 8);
a8_set_dmactl(0x22);
a8_waitvsync();
}
void a8_dlist_off(void) {
a8_set_dmactl(0x00);
}
void a8_set_playfield_width(byte w) {
a8_set_dmactl((byte)((a8_dmactl_val & ~0x03) | (w & 0x03)));
}
/*==========================================================================*/
/* DLI */
/*==========================================================================*/
/* reg,val pairs terminated by A8_DLI_NONE in the reg slot */
byte a8_dli_tab[A8_DLI_LINES][A8_DLI_WRITES * 2];
byte a8_dli_line;
void (*a8_dli_hook)(void);
volatile byte* const a8_dli_regs[A8_REG_COUNT] = {
&GTIA_WRITE.colbk,
&GTIA_WRITE.colpf0, &GTIA_WRITE.colpf1, &GTIA_WRITE.colpf2, &GTIA_WRITE.colpf3,
&GTIA_WRITE.colpm0, &GTIA_WRITE.colpm1, &GTIA_WRITE.colpm2, &GTIA_WRITE.colpm3,
&GTIA_WRITE.prior,
&GTIA_WRITE.hposp0, &GTIA_WRITE.hposp1, &GTIA_WRITE.hposp2, &GTIA_WRITE.hposp3,
&GTIA_WRITE.hposm0, &GTIA_WRITE.hposm1, &GTIA_WRITE.hposm2, &GTIA_WRITE.hposm3,
&GTIA_WRITE.sizep0, &GTIA_WRITE.sizep1, &GTIA_WRITE.sizep2, &GTIA_WRITE.sizep3,
&GTIA_WRITE.sizem,
&GTIA_WRITE.grafp0, &GTIA_WRITE.grafp1, &GTIA_WRITE.grafp2, &GTIA_WRITE.grafp3,
&GTIA_WRITE.grafm,
&GTIA_WRITE.vdelay, &GTIA_WRITE.gractl, &GTIA_WRITE.hitclr,
&ANTIC.chbase, &ANTIC.chactl,
&ANTIC.hscrol, &ANTIC.vscrol, &ANTIC.dmactl
};
void a8_dli_clear(void) {
memset(a8_dli_tab, 0xff, sizeof(a8_dli_tab));
a8_dli_line = 0;
}
void a8_dli_set(byte line, byte reg, byte val) {
byte j;
byte* dli_line = a8_dli_tab[line];
if (line >= A8_DLI_LINES || reg >= A8_REG_COUNT) return;
for (j = 0; j < A8_DLI_WRITES; j++) {
if (*dli_line == 0xff) {
dli_line[0] = reg;
dli_line[1] = val;
return;
}
dli_line += 2;
}
}
void a8_dli_install(void) {
a8_dli_line = 0;
A8_VDSLST = a8_dli_stub;
ANTIC.nmien = NMIEN_VBI | NMIEN_DLI;
}
void a8_dli_remove(void) {
ANTIC.nmien = NMIEN_VBI;
}
#ifdef __OSCAR64C__
__hwinterrupt void a8_dli_stub() {
if (a8_dli_line >= a8_dli_lines) a8_dli_line = 0;
byte* w = &a8_dli_tab[a8_dli_line][0];
for (byte i = 0; i < A8_DLI_WRITES; i++) {
byte r = w[0];
if (r == 0xff) break;
*a8_dli_regs[r] = w[1];
w += 2;
}
music_duty();
a8_dli_line++;
}
#endif
/*==========================================================================*/
/* Scrolling */
/*==========================================================================*/
static byte* a8_scroll_lmsp[A8_SCROLL_MAX];
static const byte* a8_scroll_base[A8_SCROLL_MAX];
static byte a8_scroll_count;
byte a8_scroll_x_shift = 4;
byte a8_scroll_x_mask = 15;
byte a8_scroll_y_shift = 3;
byte a8_scroll_y_mask = 7;
/* Indexed by ANTIC mode: scanlines per line, bytes per HSCROL line, and
log2 of color clocks per byte. */
const byte a8_mode_height[16] = { 0,0, 8,10, 8,16, 8,16, 8, 4, 4, 2, 1, 2, 1, 1 };
static const byte a8_mode_stride[16] = { 0,0, 48,48,48,48,24,24,12,12,24,24,24,48,48,48 };
static const byte a8_mode_xshift[16] = { 0,0, 2, 2, 2, 2, 3, 3, 4, 4, 3, 3, 3, 2, 2, 2 };
byte a8_scroll_mode(byte mode) {
byte h;
mode &= 15;
a8_scroll_x_shift = a8_mode_xshift[mode];
a8_scroll_x_mask = (byte)((1 << a8_scroll_x_shift) - 1);
h = a8_mode_height[mode];
a8_scroll_y_shift = 0;
while (h > 1) { h >>= 1; a8_scroll_y_shift++; }
a8_scroll_y_mask = (byte)((1 << a8_scroll_y_shift) - 1);
return a8_mode_stride[mode];
}
void a8_scroll_reset(void) {
a8_scroll_count = 0;
}
byte a8_scroll_line(byte mode, const void* base, byte dli) {
byte idx = a8_dlist_line(mode, base, dli);
a8_scroll_add(base, &a8_dlist[a8_dlist_len - 2]);
return idx;
}
void a8_scroll_add(const void* base, byte* lms) {
if (a8_scroll_count >= A8_SCROLL_MAX) return;
a8_scroll_lmsp[a8_scroll_count] = lms;
a8_scroll_base[a8_scroll_count] = (const byte*)base;
a8_scroll_count++;
}
void a8_scroll_move(word byteoff, byte xfine) {
byte i;
for (i = 0; i < a8_scroll_count; i++) {
word a = (word)a8_scroll_base[i] + byteoff;
a8_scroll_lmsp[i][0] = (byte)a;
a8_scroll_lmsp[i][1] = (byte)(a >> 8);
}
/* HSCROL delays the picture, so count down as the LMS counts up */
ANTIC.hscrol = xfine ^ a8_scroll_x_mask;
}
void a8_scroll_set(byte x, byte y, byte stride) {
word off = (word)(y >> a8_scroll_y_shift) * stride + (x >> a8_scroll_x_shift);
a8_scroll_move(off, (byte)(x & a8_scroll_x_mask));
ANTIC.vscrol = y & a8_scroll_y_mask;
}
/*==========================================================================*/
/* Player/Missile graphics */
/*==========================================================================*/
/* Need 2K for a single-line P/M area, plus up to 2K of alignment slack. */
#ifdef __CC65__
#if defined(__ATARI5200__)
static byte* a8_pmg_base = (byte*)0x1800;
#else
static byte* a8_pmg_base = (byte*)0x9800;
#endif
#else
byte a8_pmg_base[0x800];
#pragma align(a8_pmg_base, 0x800)
#endif
static byte a8_pmg_mode;
byte* a8_pmg_player(byte i) {
if (a8_pmg_mode == A8_PMG_SINGLE)
return a8_pmg_base + 0x400 + ((word)i << 8);
return a8_pmg_base + 0x200 + ((word)i << 7);
}
byte* a8_pmg_missile(byte i) {
(void)i; /* the four missiles share one 128-byte region */
return a8_pmg_base + ((a8_pmg_mode == A8_PMG_SINGLE) ? 0x300 : 0x180);
}
void a8_pmg_clear(void) {
memset(a8_pmg_base, 0, 0x800);
}
void a8_pmg_init(byte mode) {
word i;
a8_pmg_mode = mode;
a8_pmg_clear();
GTIA_WRITE.gractl = 0; /* disable while we set up */
ANTIC.pmbase = (byte)((word)a8_pmg_base >> 8);
a8_dmactl_val = (a8_dmactl_val & ~0x1c) | 0x0c | (mode & 0x10);
a8_set_dmactl(a8_dmactl_val);
GTIA_WRITE.gractl = GRACTL_PLAYERS | GRACTL_MISSLES;
GTIA_WRITE.prior = PRIOR_P03_PF03;
for (i = 0; i < 4; i++)
a8_pmg_set_color(i, (byte)(0x10 + i * 0x20));
}
void a8_pmg_off(void) {
GTIA_WRITE.gractl = 0;
a8_dmactl_val &= ~0x1c;
a8_set_dmactl(a8_dmactl_val);
}
void a8_pmg_set_x(byte i, byte x) {
((byte*)&GTIA_WRITE.hposp0)[i] = x;
}
void a8_pmg_set_color(byte i, byte c) {
((byte*)&GTIA_WRITE.colpm0)[i] = c;
A8_SHDW_PM[i] = c;
}
void a8_pmg_set_size(byte i, byte size) {
((byte*)&GTIA_WRITE.sizep0)[i] = size;
}
void a8_pmg_set_shape(byte i, const byte* shape, byte len, byte y) {
byte* p = a8_pmg_player(i) + y;
while (len--) *p++ = *shape++;
}
void a8_pmg_set_missile_x(byte i, byte x) {
((byte*)&GTIA_WRITE.hposm0)[i] = x;
}
void a8_pmg_set_missile_width(byte i, byte size) {
GTIA_WRITE.sizem = (byte)((GTIA_WRITE.sizem & ~(3 << (i * 2)))
| ((size & 3) << (i * 2)));
}
byte a8_pmg_hit_pf(byte i) {
return ((byte*)&GTIA_READ.p0pf)[i];
}
byte a8_pmg_hit_pl(void) {
byte i, m = 0;
for (i = 0; i < 4; i++) m |= ((byte*)&GTIA_READ.p0pl)[i];
return m;
}
void a8_pmg_clear_collisions(void) {
GTIA_WRITE.hitclr = 0;
}
static byte pmg_sizem;
void a8_pmg_5th_enable(byte on) {
if (on) {
GTIA_WRITE.prior |= PRIOR_5TH_PLAYER;
GTIA_WRITE.gractl |= GRACTL_MISSLES;
GTIA_WRITE.sizem = 0; /* every missile 2 px wide, so they tile */
} else {
GTIA_WRITE.prior &= (byte)~PRIOR_5TH_PLAYER;
}
pmg_sizem = 0;
}
/*
0: 8 color clocks (1 per pixel)
1: 16 color clocks (2 per pixel)
3: 32 color clocks (4 per pixel)
*/
void a8_pmg_5th_size(byte size) {
size &= 3;
pmg_sizem = size;
size |= size << 2;
size |= size << 4;
GTIA_WRITE.sizem = size;
}
static const byte pmg_size_tab[4] = { 2, 4, 2, 8 };
void a8_pmg_5th_x(byte x) {
byte* m = (byte*)&GTIA_WRITE.hposm0;
byte inc = pmg_size_tab[pmg_sizem];
m[0] = x;
x += inc;
m[1] = x;
x += inc;
m[2] = x;
x += inc;
m[3] = x;
}
void a8_pmg_5th_shape(const byte* shape, byte len, byte y) {
byte* p = a8_pmg_missile(0) + y;
while (len--) {
/* GRAFM packs two bits per missile, M0 in bits 1:0, and each missile
shifts its pair out MSB first, so the leftmost pixel is bit 1. Swap
the bit pairs to accept the usual player order (bit 7 = leftmost). */
byte s = *shape++;
*p++ = (byte)(((s >> 6) & 0x03)
| ((s >> 2) & 0x0c)
| ((s << 2) & 0x30)
| ((s << 6) & 0xc0));
}
}
/*==========================================================================*/
/* POKEY */
/*==========================================================================*/
static byte a8_irq_stack[128];
void a8_pokey_init(void) {
POKEY_WRITE.skctl = SKCTL_KEYBOARD_DEBOUNCE | SKCTL_KEYBOARD_SCANNING;
POKEY_WRITE.audctl = AUDCTL_CLOCKBASE_15HZ;
a8_pokey_stop_all();
}
void a8_pokey_sound(byte chan, byte freq, byte ctrl, byte vol) {
byte* p = (byte*)&POKEY_WRITE.audf1 + (word)(chan << 1);
p[0] = freq;
p[1] = (byte)(ctrl | (vol & 0x0f));
}
void a8_pokey_stop(byte chan) {
((byte*)&POKEY_WRITE.audc1)[chan << 1] = 0;
}
void a8_pokey_stop_all(void) {
byte i;
for (i = 0; i < 4; i++) a8_pokey_stop(i);
}
#ifdef __CC65__
/* called from set_irq() */
unsigned char a8_pokey_music_update() {
music_tick();
music_duty();
return IRQ_NOT_HANDLED;
}
void a8_pokey_music_init(void) {
set_irq(a8_pokey_music_update, a8_irq_stack, sizeof(a8_irq_stack));
}
void a8_pokey_music_done(void) {
reset_irq();
}
#endif
#ifdef __OSCAR64C__
void a8_pokey_music_update() {
__asm { pha; txa; pha; tya; pha; } // save regs
music_tick();
music_duty();
__asm { pla; tay; pla; tax; pla; } // restore regs
}
void a8_pokey_music_init(void) {
OS.vvblki = a8_pokey_music_update;
}
void a8_pokey_music_done(void) {
//reset_irq();
}
#endif
/*==========================================================================*/
/* Colors */
/*==========================================================================*/
void a8_set_colpf(byte i, byte c) {
if (i > 3) i = 3;
((byte*)&GTIA_WRITE.colpf0)[i] = c;
A8_SHDW_PF[i] = c;
}
void a8_set_colbk(byte c) {
GTIA_WRITE.colbk = c;
A8_SHDW_BK = c;
}
void a8_set_colpm(byte i, byte c) {
if (i > 3) i = 3;
((byte*)&GTIA_WRITE.colpm0)[i] = c;
A8_SHDW_PM[i] = c;
}
/*==========================================================================*/
/* Input and utility */
/*==========================================================================*/
byte a8_get_console(void) {
return GTIA_READ.consol;
}
byte a8_get_trigger(byte n) {
return (byte)(((byte*)&GTIA_READ.trig0)[n] & 1 ? 0 : 1);
}
#if !defined(__ATARI5200__)
byte a8_get_stick(byte n) {
byte v = n ? PIA.portb : PIA.porta;
return (byte)((~v) & 0x1f);
}
#endif
void a8_waitvsync(void) {
#if defined(__ATARI5200__)
volatile byte* rtc = (volatile byte*)0x0002; /* RTCLOK+1 */
#else
volatile byte* rtc = &OS.rtclok[2]; /* frame counter */
#endif
byte t = *rtc;
while (*rtc == t) ;
}
+7 -3
View File
@@ -7,12 +7,15 @@
* - a POKEY melody driven from the vblank interrupt
*/
//#link "common.c"
//#link "common_irq.s"
//#link "a8lib.c"
//#link "a8lib_dli.c"
//#link "a8lib_pmg.c"
//#link "a8lib_pokey.c"
//#link "a8lib_dli_asm.ca65"
//#link "pokeymusic.ca65"
#include <6502.h>
#include "common.h"
#include "a8lib.h"
/* 24 rows x 40 columns of text screen codes. */
static byte screen[24][40];
@@ -36,6 +39,7 @@ static void fill_screen(void) {
static void build_display(void) {
byte i, d;
a8_dli_clear();
a8_dlist_reset();
a8_dlist_blank(24);
/* First text line carries the LMS pointer and the first DLI band. */
+41 -16
View File
@@ -5,8 +5,11 @@
#include <conio.h>
#include "atari.h"
#include "common.h"
//#link "common.c"
#include "a8lib.h"
//#link "a8lib.c"
//#link "a8lib_dli.c"
//#link "a8lib_pmg.c"
//#link "a8lib_pokey.c"
//#link "pokeymusic.cpp"
/* 24 rows x 40 columns of text screen codes. */
@@ -33,6 +36,7 @@ static void fill_screen(void) {
static void build_display(void) {
byte i, d;
a8_dli_clear();
a8_dlist_reset();
a8_dlist_blank(24);
/* First text line carries the LMS pointer and the first DLI band. */
@@ -52,9 +56,10 @@ static void build_display(void) {
a8_dlist_finish();
}
int main() {
int main(void) {
static byte x = 128;
static char dx = 2;
static byte dx = 2;
byte i;
fill_screen();
build_display();
@@ -70,29 +75,49 @@ int main() {
a8_dli_install();
a8_pmg_init(A8_PMG_DOUBLE);
a8_pmg_set_x(0, x);
a8_pmg_set_color(0, A8_COLOR(HUE_GREY, 0x0f));
a8_pmg_set_size(0, A8_PMG_NORMAL);
a8_pmg_set_shape(0, ball, sizeof(ball), 0x38);
// set up all 4 sprites
for (i=0; i<4; i++) {
a8_pmg_set_x(i, x);
a8_pmg_set_color(i, A8_COLOR(HUE_GREY, 0x0f-i));
a8_pmg_set_size(i, A8_PMG_NORMAL);
a8_pmg_set_shape(i, ball, sizeof(ball), i*16 + 8);
}
// combine missiles into 5th sprite
// colors are taken from the 4 sprites
a8_pmg_5th_enable(1);
a8_pmg_5th_x(100);
a8_pmg_5th_shape(ball, sizeof(ball), 0x48);
// set interrupt and start music
a8_pokey_init();
a8_pokey_music_init();
music_start(music1);
while (1) {
a8_waitvsync();
x += dx;
if (x < 48) { x = 48; dx = 2; }
if (x > 208) { x = 208; dx = -2; }
a8_pmg_set_x(0, x);
if (a8_trigger(0)) {
a8_pmg_set_size(0, A8_PMG_QUADW);
if (x > 208) { x = 208; dx = (byte)-2; }
// set sprite sizes if joystick moved or trigger pushed
if (a8_get_trigger(0)) {
a8_pmg_5th_size(A8_PMG_QUADW);
} else {
a8_pmg_5th_size(A8_PMG_NORMAL);
}
if (a8_get_stick(0)) {
a8_pmg_set_size(a8_get_stick(0) & 3, A8_PMG_QUADW);
} else {
a8_pmg_set_size(0, A8_PMG_NORMAL);
a8_pmg_set_size(1, A8_PMG_NORMAL);
a8_pmg_set_size(2, A8_PMG_NORMAL);
a8_pmg_set_size(3, A8_PMG_NORMAL);
}
// set X positions
a8_pmg_set_x(0, x);
a8_pmg_set_x(1, x);
a8_pmg_set_x(2, x);
a8_pmg_set_x(3, x);
a8_pmg_5th_x(255-x);
}
return 0;
}
}
+4 -4
View File
@@ -16,12 +16,11 @@
* PAD scanlines of overlap below, so it can scroll without leaving the page.
*/
//#link "common.c"
//#link "common_irq.s"
//#link "pokeymusic.ca65"
//#link "a8lib.c"
//#link "a8lib_scroll.c"
#include <string.h>
#include "common.h"
#include "a8lib.h"
#define MODE DL_MAP160x2x4 /* try DL_CHR40x8x4, DL_MAP320x1x1, ... */
#define SCANLINES 192 /* visible scanlines */
@@ -78,6 +77,7 @@ static void build_display(void) {
/* too many bands? shrink the window */
if (lines > band_lines * MAX_BANDS) lines = band_lines * MAX_BANDS;
a8_scroll_reset();
a8_dlist_reset();
top = 24 + (SCANLINES - lines * h) / 2;
a8_dlist_blank(top);
+2 -2
View File
@@ -9,13 +9,13 @@ var Atari8_PRESETS : Preset[] = [
{id:'hellopm.dasm', name:'Hello Sprites'},
{id:'helloconio.c', name:'Text Mode', category:'C'},
{id:'siegegame.c', name:'Siege Game'},
{id:'tgidemo.c', name:'TGI Demo'},
{id:'hellodlist.c', name:'Display List'},
{id:'libdemo.c', name:'Library Demo'},
];
var Atari800_PRESETS = Atari8_PRESETS.concat([
//{id:'testmusic.c', name:'POKEY Music'},
{id:'tgidemo.c', name:'TGI Demo'},
{id:'libdemo.c', name:'Library Demo'},
{id:'scrolldemo.c', name:'Scrolling'},
{id:'hello.cpp', name:'Hello Oscar64', category:'Oscar64'},
{id:'libdemo.cpp', name:'Library Demo Oscar64'},