From 1e5b2dcab8347950d44dca2a65a8252eacd8c3cd Mon Sep 17 00:00:00 2001 From: Steven Hugg Date: Mon, 3 Dec 2018 10:51:47 -0500 Subject: [PATCH] Raster/VectorVideo classes; aclib.[ch] --- doc/notes.txt | 4 +- presets/astrocade/aclib.c | 128 ++++++++++++++++++++++++ presets/astrocade/aclib.h | 68 +++++++++++++ presets/astrocade/cosmic.c | 196 ++----------------------------------- src/emu.ts | 141 ++++++++++++++------------ src/platform/sms.ts | 1 + src/project.ts | 13 ++- 7 files changed, 296 insertions(+), 255 deletions(-) create mode 100644 presets/astrocade/aclib.c create mode 100644 presets/astrocade/aclib.h diff --git a/doc/notes.txt b/doc/notes.txt index 212e59b8..b1c06aa2 100644 --- a/doc/notes.txt +++ b/doc/notes.txt @@ -73,19 +73,17 @@ TODO: - granular control over time scrubbing, show CPU state - error showing replay div before rom starts - compiler flags for final ROM build -- coleco sprites need brev - workermain: split build functions, better msg types - vcs: INPTx needs to be added to control state - rename, delete, save as - sdcc: can't link asm files before c files - what if >1 file with same name? (local/nonlocal/directory) -- can't upload files already exist -- upload files w/o new project - what if .c and .s names collide? - live coding URL - memory viewer: ROM/RAM/VRAM/etc - resize memory browser when split resize - preroll the emulator so optimizer does its thing before loading rom +- wasm dynamic linking of emulators (https://github.com/WebAssembly/tool-conventions/blob/master/DynamicLinking.md) WEB WORKER FORMAT diff --git a/presets/astrocade/aclib.c b/presets/astrocade/aclib.c new file mode 100644 index 00000000..e89b9440 --- /dev/null +++ b/presets/astrocade/aclib.c @@ -0,0 +1,128 @@ + +#include +#include "aclib.h" + +#define EXIT_CLIPDEST(addr) if ((((word)addr)&0xfff) >= 0xe10) return + +// clear screen and set graphics mode +void clrscr() { + memset(vidmem, 0, VHEIGHT*VBWIDTH); // clear page 1 +} + +// draw vertical line +void vline(byte x, byte y1, byte y2, byte col, byte op) { + byte xb = x>>2; // divide x by 4 + byte* dest = &vmagic[y1][xb]; // destination address + byte y; + hw_magic = M_SHIFT(x) | op; // set magic register + col <<= 6; // put color in high pixel + for (y=y1; y<=y2; y++) { + EXIT_CLIPDEST(dest); + *dest = col; // shift + xor color + dest += VBWIDTH; // dest address to next scanline + } +} + +// draw a pixel +void pixel(byte x, byte y, byte col, byte op) { + vline(x, y, y, col, op); // draw line with 1-pixel height +} + +// render a sprite with the given graphics operation +void render_sprite(const byte* src, byte x, byte y, byte op) { + byte i,j; + byte w = *src++; // get width from 1st byte of sprite + byte h = *src++; // get height from 2nd byte of sprite + byte xb = x>>2; // divide x by 4 + byte* dest = &vmagic[y][xb]; // destination address + hw_magic = M_SHIFT(x) | op; // set magic register + for (j=0; j>2; // divide x by 4 + byte* dest = &vidmem[y][xb]; // destination address + for (j=0; j>2; // divide x by 4 + byte* dest = &vmagic[y][xb]; // destination address + hw_magic = M_SHIFT(x) | M_XPAND | op; + for (byte i=0; i<8; i++) { + char b = *src++; + EXIT_CLIPDEST(dest); + *dest++ = b; // expand lower nibble -> 1st byte + *dest++ = b; // expand upper nibble -> 2nd byte + *dest++ = 0; // leftover -> 3rd byte + *dest = 0; // reset upper/lower flag + dest += VBWIDTH-3; // we incremented 3 bytes for this line + } +} + +void draw_string(const char* str, byte x, byte y) { + do { + byte ch = *str++; + if (!ch) break; + draw_char(ch, x, y, M_MOVE); + x += 8; + } while (1); +} + +void draw_bcd_word(word bcd, byte x, byte y, byte op) { + byte j; + x += 3*8; + for (j=0; j<4; j++) { + draw_char('0'+(bcd&0xf), x, y, op); + x -= 8; + bcd >>= 4; + } +} + +// add two 16-bit BCD values +word bcd_add(word a, word b) { + a; b; // to avoid warning +__asm + ld hl,#4 + add hl,sp + ld iy,#2 + add iy,sp + ld a,0 (iy) + add a, (hl) + daa + ld c,a + ld a,1 (iy) + inc hl + adc a, (hl) + daa + ld b,a + ld l, c + ld h, b +__endasm; +} + diff --git a/presets/astrocade/aclib.h b/presets/astrocade/aclib.h new file mode 100644 index 00000000..ee83c145 --- /dev/null +++ b/presets/astrocade/aclib.h @@ -0,0 +1,68 @@ + +#ifndef _ACLIB_H +#define _ACLIB_H + +typedef unsigned char byte; +typedef signed char sbyte; +typedef unsigned short word; + +/// HARDWARE + +__sfr __at(0x00) hw_col0r; // palette 0 +__sfr __at(0x01) hw_col1r; +__sfr __at(0x02) hw_col2r; +__sfr __at(0x03) hw_col3r; +__sfr __at(0x04) hw_col0l; +__sfr __at(0x05) hw_col1l; +__sfr __at(0x06) hw_col2l; +__sfr __at(0x07) hw_col3l; // palette 7 + +__sfr __at(0x09) hw_horcb; // horiz color boundary +__sfr __at(0x0a) hw_verbl; // vertical blanking line * 2 +__sfr __at(0x0c) hw_magic; // magic register +__sfr __at(0x19) hw_xpand; // expander register + +__sfr __at(0x08) hw_intst; // intercept test feedback + +__sfr __at(0x10) hw_p1ctrl; // player controls +__sfr __at(0x11) hw_p2ctrl; // player controls +__sfr __at(0x12) hw_p3ctrl; // player controls +__sfr __at(0x13) hw_p4ctrl; // player controls + +#define M_SHIFT0 0x00 +#define M_SHIFT1 0x01 +#define M_SHIFT2 0x02 +#define M_SHIFT3 0x03 +#define M_XPAND 0x08 +#define M_MOVE 0x00 +#define M_OR 0x10 +#define M_XOR 0x20 +#define M_FLOP 0x40 +#define M_SHIFT(x) ((x)&3) +#define XPAND_COLORS(off,on) (((off)&3) | (((on)&3)<<2)) + +#define VHEIGHT 89 // number of scanlines +#define VBWIDTH 40 // number of bytes per scanline +#define PIXWIDTH 160 // 4 pixels per byte + +byte __at (0x0000) vmagic[VHEIGHT][VBWIDTH]; +byte __at (0x4000) vidmem[VHEIGHT][VBWIDTH]; + +#define LOCHAR 32 +#define HICHAR 127 +#define FONT_BWIDTH 1 +#define FONT_HEIGHT 8 + +/// GRAPHICS FUNCTIONS + +void clrscr(); +void vline(byte x, byte y1, byte y2, byte col, byte op); +void pixel(byte x, byte y, byte col, byte op); +void render_sprite(const byte* src, byte x, byte y, byte op); +void erase_sprite(const byte* src, byte x, byte y); +void draw_char(byte ch, byte x, byte y, byte op); +void draw_string(const char* str, byte x, byte y); +void draw_bcd_word(word bcd, byte x, byte y, byte op); +word bcd_add(word a, word b); + +#endif diff --git a/presets/astrocade/cosmic.c b/presets/astrocade/cosmic.c index 3133a28c..089bdebd 100644 --- a/presets/astrocade/cosmic.c +++ b/presets/astrocade/cosmic.c @@ -1,192 +1,18 @@ - + /* * An Astrocade port of the Cosmic Impalas game * described in the book * "Making 8-bit Arcade Games in C" */ -//#link "acheader.s" - #include -#define EXIT_CLIPY(y) if (((unsigned char)y)>=VHEIGHT) return -#define EXIT_CLIPDEST(addr) if ((((word)addr)&0xfff) >= 0xe10) return +#include "aclib.h" -typedef unsigned char byte; -typedef signed char sbyte; -typedef unsigned short word; +//#link "aclib.c" -/// HARDWARE +//#link "acheader.s" -__sfr __at(0x00) hw_col0r; // palette 0 -__sfr __at(0x01) hw_col1r; -__sfr __at(0x02) hw_col2r; -__sfr __at(0x03) hw_col3r; -__sfr __at(0x04) hw_col0l; -__sfr __at(0x05) hw_col1l; -__sfr __at(0x06) hw_col2l; -__sfr __at(0x07) hw_col3l; // palette 7 - -__sfr __at(0x09) hw_horcb; // horiz color boundary -__sfr __at(0x0a) hw_verbl; // vertical blanking line * 2 -__sfr __at(0x0c) hw_magic; // magic register -__sfr __at(0x19) hw_xpand; // expander register - -__sfr __at(0x08) hw_intst; // intercept test feedback - -__sfr __at(0x10) hw_p1ctrl; // player controls -__sfr __at(0x11) hw_p2ctrl; // player controls -__sfr __at(0x12) hw_p3ctrl; // player controls -__sfr __at(0x13) hw_p4ctrl; // player controls - -#define M_SHIFT0 0x00 -#define M_SHIFT1 0x01 -#define M_SHIFT2 0x02 -#define M_SHIFT3 0x03 -#define M_XPAND 0x08 -#define M_MOVE 0x00 -#define M_OR 0x10 -#define M_XOR 0x20 -#define M_FLOP 0x40 -#define M_SHIFT(x) ((x)&3) -#define XPAND_COLORS(off,on) (((off)&3) | (((on)&3)<<2)) - -/// GRAPHICS FUNCTIONS - -#define VHEIGHT 89 // number of scanlines -#define VBWIDTH 40 // number of bytes per scanline -#define PIXWIDTH 160 // 4 pixels per byte - -byte __at (0x0000) vmagic[VHEIGHT][VBWIDTH]; -byte __at (0x4000) vidmem[VHEIGHT][VBWIDTH]; - -// clear screen and set graphics mode -void clrscr() { - memset(vidmem, 0, VHEIGHT*VBWIDTH); // clear page 1 -} - -// draw vertical line -void vline(byte x, byte y1, byte y2, byte col, byte op) { - byte xb = x>>2; // divide x by 4 - byte* dest = &vmagic[y1][xb]; // destination address - byte y; - hw_magic = M_SHIFT(x) | op; // set magic register - col <<= 6; // put color in high pixel - for (y=y1; y<=y2; y++) { - EXIT_CLIPDEST(dest); - *dest = col; // shift + xor color - dest += VBWIDTH; // dest address to next scanline - } -} - -// draw a pixel -void pixel(byte x, byte y, byte col, byte op) { - vline(x, y, y, col, op); // draw line with 1-pixel height -} - -// render a sprite with the given graphics operation -void render_sprite(const byte* src, byte x, byte y, byte op) { - byte i,j; - byte w = *src++; // get width from 1st byte of sprite - byte h = *src++; // get height from 2nd byte of sprite - byte xb = x>>2; // divide x by 4 - byte* dest = &vmagic[y][xb]; // destination address - hw_magic = M_SHIFT(x) | op; // set magic register - for (j=0; j>2; // divide x by 4 - byte* dest = &vidmem[y][xb]; // destination address - for (j=0; j>2; // divide x by 4 - byte* dest = &vmagic[y][xb]; // destination address - hw_magic = M_SHIFT(x) | M_XPAND | op; - for (byte i=0; i<8; i++) { - char b = *src++; - EXIT_CLIPDEST(dest); - *dest++ = b; // expand lower nibble -> 1st byte - *dest++ = b; // expand upper nibble -> 2nd byte - *dest++ = 0; // leftover -> 3rd byte - *dest = 0; // reset upper/lower flag - dest += VBWIDTH-3; // we incremented 3 bytes for this line - } -} - -void draw_string(const char* str, byte x, byte y) { - do { - byte ch = *str++; - if (!ch) break; - draw_char(ch, x, y, M_MOVE); - x += 8; - } while (1); -} - -void draw_bcd_word(word bcd, byte x, byte y, byte op) { - byte j; - x += 3*8; - for (j=0; j<4; j++) { - draw_char('0'+(bcd&0xf), x, y, op); - x -= 8; - bcd >>= 4; - } -} - -// add two 16-bit BCD values -word bcd_add(word a, word b) __naked { - a; b; // to avoid warning -__asm - push ix - ld ix,#0 - add ix,sp - ld a,4 (ix) - add a, 6 (ix) - daa - ld c,a - ld a,5 (ix) - adc a, 7 (ix) - daa - ld b,a - ld l, c - ld h, b - pop ix - ret -__endasm; -} - -///// // // GAME GRAPHICS @@ -315,8 +141,8 @@ void xor_player_derez() { byte x = player_x+13; byte y = player_y+PLYRHEIGHT-1; byte* rand = (byte*) &clrscr; // use code as random #'s - for (j=1; j<=0x1f; j++) { - for (i=0; i<50; i++) { + for (j=1; j<=0xf; j++) { + for (i=0; i<100; i++) { signed char xx = x + (*rand++ & 0xf) - 15; signed char yy = y - (*rand++ & j); pixel(xx, yy, *rand++, M_XOR); @@ -561,13 +387,9 @@ void attract_mode() { void setup_registers() { hw_col0r = 0x00; - hw_col1r = 0x20; - hw_col2r = 0xe0; - hw_col3r = 0xa0; - hw_col0l = 0x10; - hw_col1l = 0x30; - hw_col2l = 0xc0; - hw_col3l = 0xf0; + hw_col1r = 0x2f; + hw_col2r = 0xef; + hw_col3r = 0xaf; hw_horcb = 0; hw_verbl = VHEIGHT*2; } diff --git a/src/emu.ts b/src/emu.ts index 7d75af36..7a206013 100644 --- a/src/emu.ts +++ b/src/emu.ts @@ -31,7 +31,7 @@ export function setNoiseSeed(x : number) { type KeyboardCallback = (which:number, charCode:number, flags:number) => void; -function __createCanvas(mainElement:HTMLElement, width:number, height:number) : HTMLElement { +function __createCanvas(mainElement:HTMLElement, width:number, height:number) : HTMLCanvasElement { var canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height; @@ -55,15 +55,33 @@ function _setKeyboardEvents(canvas:HTMLElement, callback:KeyboardCallback) { type VideoCanvasOptions = {rotate?:number, overscan?:boolean}; -export var RasterVideo = function(mainElement:HTMLElement, width:number, height:number, options?:VideoCanvasOptions) { - var canvas, ctx; - var imageData, arraybuf, buf8, datau32; - var vcanvas; +export class RasterVideo { + + mainElement : HTMLElement; + width : number; + height : number; + options : VideoCanvasOptions; + + constructor(mainElement:HTMLElement, width:number, height:number, options?:VideoCanvasOptions) { + this.mainElement = mainElement; + this.width = width; + this.height = height; + this.options = options; + } - this.paddle_x = 255; - this.paddle_y = 255; + canvas : HTMLCanvasElement; + ctx; + imageData; + arraybuf; + buf8; + datau32; + vcanvas; - this.setRotate = function(rotate) { + paddle_x = 255; + paddle_y = 255; + + setRotate(rotate:number) { + var canvas = this.canvas; if (rotate) { // TODO: aspect ratio? canvas.style.transform = "rotate("+rotate+"deg)"; @@ -75,79 +93,77 @@ export var RasterVideo = function(mainElement:HTMLElement, width:number, height: } } - this.create = function() { - this.canvas = canvas = __createCanvas(mainElement, width, height); - vcanvas = $(canvas); - if (options && options.rotate) { - this.setRotate(options.rotate); + create() { + var canvas; + this.canvas = canvas = __createCanvas(this.mainElement, this.width, this.height); + this.vcanvas = $(canvas); + if (this.options && this.options.rotate) { + this.setRotate(this.options.rotate); } - if (options && options.overscan) { - vcanvas.css('padding','0px'); + if (this.options && this.options.overscan) { + this.vcanvas.css('padding','0px'); } - ctx = canvas.getContext('2d'); - imageData = ctx.createImageData(width, height); - datau32 = new Uint32Array(imageData.data.buffer); + this.ctx = canvas.getContext('2d'); + this.imageData = this.ctx.createImageData(this.width, this.height); + this.datau32 = new Uint32Array(this.imageData.data.buffer); } - this.setKeyboardEvents = function(callback) { - _setKeyboardEvents(canvas, callback); + setKeyboardEvents(callback) { + _setKeyboardEvents(this.canvas, callback); } - this.getFrameData = function() { return datau32; } + getFrameData() { return this.datau32; } - this.getContext = function() { return ctx; } + getContext() { return this.ctx; } - this.updateFrame = function(sx:number, sy:number, dx:number, dy:number, width?:number, height?:number) { - if (width && height) - ctx.putImageData(imageData, sx, sy, dx, dy, width, height); + updateFrame(sx:number, sy:number, dx:number, dy:number, w?:number, h?:number) { + if (w && h) + this.ctx.putImageData(this.imageData, sx, sy, dx, dy, w, h); else - ctx.putImageData(imageData, 0, 0); - if (frameUpdateFunction) frameUpdateFunction(canvas); + this.ctx.putImageData(this.imageData, 0, 0); + if (frameUpdateFunction) frameUpdateFunction(this.canvas); } - this.setupMouseEvents = function(el? : HTMLElement) { - if (!el) el = canvas; + setupMouseEvents(el? : HTMLElement) { + if (!el) el = this.canvas; $(el).mousemove( (e) => { // TODO: get coords right - var x = e.pageX - vcanvas.offset().left; - var y = e.pageY - vcanvas.offset().top; - var new_x = Math.floor(x * 255 / vcanvas.width() - 20); - var new_y = Math.floor(y * 255 / vcanvas.height() - 20); + var x = e.pageX - this.vcanvas.offset().left; + var y = e.pageY - this.vcanvas.offset().top; + var new_x = Math.floor(x * 255 / this.vcanvas.width() - 20); + var new_y = Math.floor(y * 255 / this.vcanvas.height() - 20); this.paddle_x = clamp(0, 255, new_x); this.paddle_y = clamp(0, 255, new_y); }); }; } -export var VectorVideo = function(mainElement:HTMLElement, width:number, height:number) { - var self = this; - var canvas, ctx; - var persistenceAlpha = 0.5; - var jitter = 1.0; - var gamma = 0.8; - var sx = width/1024.0; - var sy = height/1024.0; +export class VectorVideo extends RasterVideo { - this.create = function() { - this.canvas = canvas = __createCanvas(mainElement, width, height); - ctx = canvas.getContext('2d'); + persistenceAlpha = 0.5; + jitter = 1.0; + gamma = 0.8; + sx : number; + sy : number; + + create() { + super.create(); + this.sx = this.width/1024.0; + this.sy = this.height/1024.0; } - this.setKeyboardEvents = function(callback) { - _setKeyboardEvents(canvas, callback); - } - - this.clear = function() { + clear() { + var ctx = this.ctx; ctx.globalCompositeOperation = 'source-over'; - ctx.globalAlpha = persistenceAlpha; + ctx.globalAlpha = this.persistenceAlpha; ctx.fillStyle = '#000000'; - ctx.fillRect(0, 0, width, height); + ctx.fillRect(0, 0, this.width, this.height); ctx.globalAlpha = 1.0; ctx.globalCompositeOperation = 'lighter'; - if (frameUpdateFunction) frameUpdateFunction(canvas); + if (frameUpdateFunction) frameUpdateFunction(this.canvas); } - var COLORS = [ + COLORS = [ '#111111', '#1111ff', '#11ff11', @@ -158,26 +174,29 @@ export var VectorVideo = function(mainElement:HTMLElement, width:number, height: '#ffffff' ]; - this.drawLine = function(x1:number, y1:number, x2:number, y2:number, intensity:number, color:number) { + drawLine(x1:number, y1:number, x2:number, y2:number, intensity:number, color:number) { + var ctx = this.ctx; + var sx = this.sx; + var sy = this.sy; //console.log(x1,y1,x2,y2,intensity,color); if (intensity > 0) { // TODO: landscape vs portrait - var alpha = Math.pow(intensity / 255.0, gamma); + var alpha = Math.pow(intensity / 255.0, this.gamma); ctx.globalAlpha = alpha; ctx.beginPath(); // TODO: bright dots - var jx = jitter * (Math.random() - 0.5); - var jy = jitter * (Math.random() - 0.5); + var jx = this.jitter * (Math.random() - 0.5); + var jy = this.jitter * (Math.random() - 0.5); x1 += jx; x2 += jx; y1 += jy; y2 += jy; - ctx.moveTo(x1*sx, height-y1*sy); + ctx.moveTo(x1*sx, this.height-y1*sy); if (x1 == x2 && y1 == y2) - ctx.lineTo(x2*sx+1, height-y2*sy); + ctx.lineTo(x2*sx+1, this.height-y2*sy); else - ctx.lineTo(x2*sx, height-y2*sy); - ctx.strokeStyle = COLORS[color & 7]; + ctx.lineTo(x2*sx, this.height-y2*sy); + ctx.strokeStyle = this.COLORS[color & 7]; ctx.stroke(); } } diff --git a/src/platform/sms.ts b/src/platform/sms.ts index 1dbf9f85..052d18a6 100644 --- a/src/platform/sms.ts +++ b/src/platform/sms.ts @@ -11,6 +11,7 @@ import { ColecoVision_PRESETS } from "./coleco"; // http://www.smspower.org/uploads/Development/sg1000.txt // http://www.smspower.org/uploads/Development/richard.txt // http://www.smspower.org/uploads/Development/msvdp-20021112.txt +// http://www.smspower.org/uploads/Development/SN76489-20030421.txt // TODO: merge w/ coleco export var SG1000_PRESETS = [ diff --git a/src/project.ts b/src/project.ts index e79894c9..e53a99a1 100644 --- a/src/project.ts +++ b/src/project.ts @@ -70,21 +70,26 @@ export class CodeProject { if (dir.length > 0 && dir != 'local') // TODO files.push(dir + '/' + fn); } - + parseIncludeDependencies(text:string):string[] { var files = []; + var m; if (this.platform_id.startsWith('verilog')) { + // include verilog includes var re1 = /^\s*(`include|[.]include)\s+"(.+?)"/gmi; - var m; while (m = re1.exec(text)) { this.pushAllFiles(files, m[2]); } // include .arch (json) statements var re2 = /^\s*([.]arch)\s+(\w+)/gmi; - var m; while (m = re2.exec(text)) { this.pushAllFiles(files, m[2]+".json"); } + // include $readmem[bh] (TODO) + var re3 = /\b\$readmem[bh]\("(.+?)"/gmi; + while (m = re3.exec(text)) { + this.pushAllFiles(files, m[2]); + } } else { // for .asm -- [.]include "file" // for .c -- #include "file" @@ -98,12 +103,12 @@ export class CodeProject { parseLinkDependencies(text:string):string[] { var files = []; + var m; if (this.platform_id.startsWith('verilog')) { // } else { // for .c -- //#link "file" (or ;link or #link) var re = /^\s*([;#]|[/][/][#])link\s+"(.+?)"/gm; - var m; while (m = re.exec(text)) { this.pushAllFiles(files, m[2]); }