NES: fixed clipping, update presets, apu.c/h, vrambuf.c

This commit is contained in:
Steven Hugg 2019-02-24 10:36:38 -05:00
parent 8daa260527
commit db60c8e380
17 changed files with 470 additions and 253 deletions

View File

@ -95,6 +95,7 @@ TODO:
- show .map file in listings? memory map view?
- open ROM from URL?
- NES: disassembly not aligned on PC
- NES: vrambuf.c for Solarian
WEB WORKER FORMAT

20
presets/nes/apu.c Normal file
View File

@ -0,0 +1,20 @@
#include "apu.h"
#include <string.h>
const unsigned char APUINIT[0x13] = {
0x30,0x08,0x00,0x00,
0x30,0x08,0x00,0x00,
0x80,0x00,0x00,0x00,
0x30,0x00,0x00,0x00,
0x00,0x00,0x00
};
void apu_init() {
// from https://wiki.nesdev.com/w/index.php/APU_basics
memcpy((void*)0x4000, APUINIT, sizeof(APUINIT));
APU.fcontrol = 0x40; // frame counter 5-step
APU.status = 0x0f; // turn on all channels except DMC
}

77
presets/nes/apu.h Normal file
View File

@ -0,0 +1,77 @@
#ifndef _APU_H
#define _APU_H
#include <nes.h>
// Functions/macros for direct control
// of APU sound generation
// enable
#define ENABLE_PULSE0 0x1
#define ENABLE_PULSE1 0x2
#define ENABLE_TRIANGLE 0x4
#define ENABLE_NOISE 0x8
#define ENABLE_DMC 0x10
#define APU_ENABLE(enable)\
APU.status = (enable);
// pulse channels
#define DUTY_75 0xc0
#define DUTY_50 0x80
#define DUTY_25 0x40
#define DUTY_12 0x00
#define PULSE_ENVLOOP 0x20
#define PULSE_CONSTVOL 0x10
#define PULSE_VOLENVMASK 0xf
#define PULSE_CH0 0
#define PULSE_CH1 1
#define APU_PULSE_DECAY(channel,period,duty,decay,len)\
APU.pulse[channel].period_low = (period)&0xff;\
APU.pulse[channel].len_period_high = (((period)>>8)&7) | ((len)<<3);\
APU.pulse[channel].control = (duty) | (decay);
#define APU_PULSE_SUSTAIN(channel,period,duty,vol)\
APU.pulse[channel].period_low = (period)&0xff;\
APU.pulse[channel].len_period_high = (((period)>>8)&7);\
APU.pulse[channel].control = (duty) | (vol) | (PULSE_CONSTVOL|PULSE_ENVLOOP);
#define APU_PULSE_CONTROL(channel,duty,decay)\
APU.pulse[channel].control = (duty) | (decay);
// triangle channel
#define TRIANGLE_LC_HALT 0x80
#define TRIANGLE_LC_MASK 0x7f
#define APU_TRIANGLE_SUSTAIN(period)\
APU.triangle.counter = 0xc0;\
APU.triangle.period_low = (period) & 0xff;\
APU.triangle.len_period_high = (period) >> 8;
// noise channel
#define NOISE_ENVLOOP 0x20
#define NOISE_CONSTVOL 0x10
#define NOISE_VOLENVMASK 0xf
#define NOISE_PERIOD_BUZZ 0x80
#define APU_NOISE_SUSTAIN(_period,vol)\
APU.noise.control = (vol) | (NOISE_ENVLOOP|NOISE_CONSTVOL);\
APU.noise.period = (_period);
#define APU_NOISE_DECAY(_period,_decay,_len)\
APU.noise.control = (_decay);\
APU.noise.period = (_period);\
APU.noise.len = (_len);
// initialize APU with default state
void apu_init(void);
#endif

22
presets/nes/bcd.c Normal file
View File

@ -0,0 +1,22 @@
#include "neslib.h"
word bcd_add(word a, word b) {
word result = 0;
byte c = 0;
byte shift = 0;
while (shift < 16) {
byte d = (a & 0xf) + (b & 0xf) + c;
c = 0;
while (d >= 10) {
c++;
d -= 10;
}
result |= d << shift;
shift += 4;
a >>= 4;
b >>= 4;
}
return result;
}

2
presets/nes/bcd.h Normal file
View File

@ -0,0 +1,2 @@
unsigned int bcd_add(unsigned int a, unsigned int b);

View File

@ -8,6 +8,14 @@
// include CC65 NES Header (PPU)
#include <nes.h>
// BCD arithmetic support
#include "bcd.h"
//#link "bcd.c"
// VRAM update buffer
#include "vrambuf.h"
//#link "vrambuf.c"
// link the pattern table into CHR ROM
//#link "chr_generic.s"
@ -22,12 +30,6 @@ extern char demo_sounds[];
typedef enum { SND_START, SND_HIT, SND_COIN, SND_JUMP } SFXIndex;
// define basic types
typedef unsigned char byte;
typedef signed char sbyte;
typedef unsigned short word;
typedef enum { false, true } bool;
///// DEFINES
#define COLS 30 // floor width in tiles
@ -66,7 +68,7 @@ static byte player_screen_y = 0;
// score (BCD)
static byte score = 0;
// flash animation (virtual bright)
// screen flash animation (virtual bright)
static byte vbright = 4;
// random byte between (a ... b-1)
@ -75,15 +77,6 @@ byte rndint(byte a, byte b) {
return (rand() % (b-a)) + a;
}
byte bcdadd(byte a, byte b) {
byte c = (a & 0xf) + (b & 0xf);
if (c < 10) {
return a + b;
} else {
return (c-10) + 0x10 + (a & 0xf0) + (b & 0xf0);
}
}
///// OAM buffer (for sprites)
#define OAMBUF ((unsigned char*) 0x200)
@ -102,46 +95,10 @@ word getntaddr(byte x, byte y) {
// convert nametable address to attribute address
word nt2attraddr(word a) {
return (a & 0x2c00) | 0x3c0 | (a & 0x0C00) |
return (a & 0x2c00) | 0x3c0 |
((a >> 4) & 0x38) | ((a >> 2) & 0x07);
}
///// VRAM UPDATE BUFFER
#define VBUFSIZE 64
byte updbuf[VBUFSIZE]; // update buffer
byte updptr = 0; // index to end of buffer
// add EOF marker to buffer
void cendbuf() {
updbuf[updptr] = NT_UPD_EOF;
}
// flush buffer now, waiting for next frame
void cflushnow() {
// make sure buffer has EOF marker
cendbuf();
// wait for next frame to flush update buffer
// this will also set the scroll registers properly
ppu_wait_frame();
// clear the buffer
updptr = 0;
cendbuf();
}
// add multiple characters to update buffer
// using horizontal increment
void putbytes(word addr, char* str, byte len) {
if (updptr >= VBUFSIZE-4-len) cflushnow();
updbuf[updptr++] = (addr >> 8) | NT_UPD_HORZ;
updbuf[updptr++] = addr & 0xff;
updbuf[updptr++] = len;
while (len--) {
updbuf[updptr++] = *str++;
}
cendbuf();
}
/// METASPRITES
// define a 2x2 metasprite
@ -178,7 +135,13 @@ DEF_METASPRITE_2x2_FLIP(playerLJump, 0xe8, 0);
DEF_METASPRITE_2x2_FLIP(playerLClimb, 0xec, 0);
DEF_METASPRITE_2x2_FLIP(playerLSad, 0xf0, 0);
DEF_METASPRITE_2x2(personToSave, 0xba, 1);
//DEF_METASPRITE_2x2(personToSave, 0xba, 1);
const unsigned char personToSave[]={
0, 0, (0xba)+0, 3,
0, 8, (0xba)+2, 0,
8, 0, (0xba)+1, 3,
8, 8, (0xba)+3, 0,
128};
const unsigned char* const playerRunSeq[16] = {
playerLRun1, playerLRun2, playerLRun3,
@ -353,6 +316,8 @@ void draw_entire_stage() {
byte y;
for (y=0; y<ROWS; y++) {
draw_floor_line(y);
// allow buffer to flush, delaying a frame
cflushnow();
}
}
@ -649,7 +614,7 @@ void pickup_object(Actor* actor) {
sfx_play(SND_HIT,0);
vbright = 8; // flash
} else {
score = bcdadd(score, 1);
score = bcd_add(score, 1);
sfx_play(SND_COIN,0);
}
}

View File

@ -6,8 +6,11 @@
seg.u RAM
org $0
ScrollX byte ; used during NMI
ScrollY byte ; used during NMI
ScrollPos byte ; used during NMI
Rand byte
Temp1 byte
SpriteBuf equ $200
;;;;; NES CARTRIDGE HEADER
@ -24,6 +27,7 @@ Start:
jsr SetPalette ;set colors
jsr FillVRAM ;set PPU RAM
jsr WaitSync ;wait for VSYNC (and PPU warmup)
jsr InitSprites
lda #0
sta PPU_ADDR
sta PPU_ADDR ;PPU addr = 0
@ -51,6 +55,33 @@ FillVRAM: subroutine
bne .loop
rts
;
InitSprites: subroutine
lda #1
ldx #0
.loop
sta SpriteBuf,x
jsr NextRandom
inx
bne .loop
rts
;
MoveSprites: subroutine
lda #1
ldx #0
.loop
sta Temp1
and #3
clc
adc SpriteBuf,x
sta SpriteBuf,x
lda Temp1
jsr NextRandom
inx
bne .loop
rts
; set palette colors
SetPalette: subroutine
@ -76,34 +107,21 @@ SetPalette: subroutine
NMIHandler:
SAVE_REGS
; load sprites
lda #$02
sta PPU_OAM_DMA
; update scroll position (must be done after VRAM updates)
jsr ReadJoypad
pha
and #$03
tay
lda ScrollDirTab,y
clc
adc ScrollX
sta ScrollX
inc ScrollPos
lda ScrollPos
sta PPU_SCROLL
pla
lsr
lsr
and #$03
tay
lda ScrollDirTab,y
clc
adc ScrollY
sta ScrollY
lda #0
sta PPU_SCROLL
; move sprites
jsr MoveSprites
; reload registers
RESTORE_REGS
rti
; Scroll direction lookup table
ScrollDirTab:
hex 00 01 ff 00
;;;;; CONSTANT DATA
align $100

147
presets/nes/horizmask.c Normal file
View File

@ -0,0 +1,147 @@
#include "neslib.h"
#include <string.h>
// 0 = horizontal mirroring
// 1 = vertical mirroring
#define NES_MIRRORING 1
// VRAM update buffer
#include "vrambuf.h"
//#link "vrambuf.c"
// link the pattern table into CHR ROM
//#link "chr_generic.s"
// function to write a string into the name table
// adr = start address in name table
// str = pointer to string
void put_str(unsigned int adr, const char *str) {
vram_adr(adr); // set PPU read/write address
vram_write(str, strlen(str)); // write bytes to PPU
}
static word x_scroll = 0;
static byte bldg_height = 8;
static byte bldg_width = 8;
static byte bldg_char = 1;
static byte bldg_attr = 0x55;
#define PLAYROWS 24
word nt2attraddr(word a) {
return (a & 0x2c00) | 0x3c0 |
((a >> 4) & 0x38) | ((a >> 2) & 0x07);
}
void update_nametable() {
word addr;
char buf[PLAYROWS];
// divide x_scroll by 8
// to get nametable X position
byte x = ((x_scroll >> 3)+32) & 63;
if (x < 32)
addr = NTADR_A(x, 4);
else
addr = NTADR_B(x&31, 4);
// create vertical slice
// clear empty space
memset(buf, 0, PLAYROWS-bldg_height);
// draw roof
buf[PLAYROWS-bldg_height-1] = bldg_char & 3;
// draw rest of building
memset(buf+PLAYROWS-bldg_height, bldg_char, bldg_height);
// draw vertical slice in name table
putbytes(addr ^ 0xc000, buf, sizeof(buf));
// every 4 columns, update attribute table
if ((x & 3) == 1) {
// compute attribute table address
// of upper attribute block
addr = nt2attraddr(addr) + 8*4;
VRAMBUF_PUT(addr, bldg_attr, 0);
// put lower attribute block
addr += 8;
VRAMBUF_PUT(addr, bldg_attr, 0);
cendbuf();
}
// generate new building?
if (--bldg_width == 0) {
bldg_height = (rand8() & 7) + 2;
bldg_width = (rand8() & 3) * 4 + 4;
bldg_char = (rand8() & 15);
bldg_attr = rand8();
}
}
// function to scroll window up and down until end
void scroll_demo() {
// infinite loop
while (1) {
// update nametable every 8 pixels
if ((x_scroll & 7) == 0) {
update_nametable();
}
// manually force vram update
ppu_wait_nmi();
flush_vram_update(updbuf);
cclearbuf();
// reset ppu address
vram_adr(0x0);
// set scroll register
// and increment x_scroll
split(x_scroll++, 0);
}
}
const char PALETTE[32] = {
0x03, // background color
0x11,0x30,0x27, 0, // background 0
0x1c,0x20,0x2c, 0, // background 1
0x00,0x10,0x20, 0, // background 2
0x06,0x16,0x26, 0, // background 3
0x16,0x35,0x24, 0, // sprite 0
0x00,0x37,0x25, 0, // sprite 1
0x0d,0x2d,0x3a, 0, // sprite 2
0x0d,0x27,0x2a // sprite 3
};
// main function, run after console reset
void main(void) {
// set palette colors
pal_all(PALETTE);
// write text to name table
put_str(NTADR_A(7,0), "Nametable A, Line 0");
put_str(NTADR_A(7,1), "Nametable A, Line 1");
put_str(NTADR_A(7,2), "Nametable A, Line 2");
vram_adr(NTADR_A(0,3));
vram_fill(5, 32);
put_str(NTADR_A(2,4), "Nametable A, Line 4");
put_str(NTADR_A(2,15),"Nametable A, Line 15");
put_str(NTADR_A(2,27),"Nametable A, Line 27");
put_str(NTADR_B(2,4), "Nametable B, Line 4");
put_str(NTADR_B(2,15),"Nametable B, Line 15");
put_str(NTADR_B(2,27),"Nametable B, Line 27");
// set attributes
vram_adr(0x23c0);
vram_fill(0x55, 8);
// set sprite 0
oam_clear();
oam_spr(0, 30, 1, 1, 0);
// clip left 8 pixels of screen
ppu_mask(MASK_SPR|MASK_BG);
// clear vram buffer
cclearbuf();
// enable PPU rendering (turn on screen)
ppu_on_all();
// scroll window back and forth
scroll_demo();
}

View File

@ -4,7 +4,8 @@
#include "neslib.h"
typedef unsigned char byte;
#include "apu.h"
//#link "apu.c"
//
// MUSIC ROUTINES
@ -41,13 +42,6 @@ byte next_music_byte() {
return *music_ptr++;
}
#define DUTY_75 0xc0
#define DUTY_50 0x80
#define DUTY_25 0x40
#define DUTY_12 0x00
#define DUTY (DUTY_25 | 0x1) // decay rate == 1
void play_music() {
static byte ch = 0;
if (music_ptr) {
@ -56,25 +50,9 @@ void play_music() {
if ((note & 0x80) == 0) {
int period = note_table[note & 63];
if (ch == 0) {
APU.pulse[0].period_low = period & 0xff;
APU.pulse[0].len_period_high = period >> 8;
APU.pulse[0].control = DUTY;
APU_PULSE_DECAY(0, period, DUTY_25, 2, 10);
} else {
APU.pulse[1].period_low = period & 0xff;
APU.pulse[1].len_period_high = period >> 8;
APU.pulse[1].control = DUTY;
/*
//period = note_table_tri[note & 63];
period >>= 1;
APU.triangle.counter = 0xc0;
APU.triangle.period_low = period & 0xff;
APU.triangle.len_period_high = period >> 8;
*/
/*
APU.noise.control = 0x4;
APU.noise.period = 0xe;
APU.noise.len = 0xf;
*/
APU_PULSE_DECAY(1, period, DUTY_25, 2, 10);
}
ch = ch^1;
} else {
@ -92,24 +70,9 @@ void start_music(const byte* music) {
cur_duration = 0;
}
const byte APUINIT[0x13] = {
0x30,0x08,0x00,0x00,
0x30,0x08,0x00,0x00,
0x80,0x00,0x00,0x00,
0x30,0x00,0x00,0x00,
0x00,0x00,0x00
};
void init_apu() {
// from https://wiki.nesdev.com/w/index.php/APU_basics
memcpy((void*)0x4000, APUINIT, sizeof(APUINIT));
APU.fcontrol = 0x40; // frame counter 5-step
APU.status = 0x0f; // turn on all channels except DMC
}
void main(void)
{
init_apu();
apu_init();
music_ptr = 0;
while (1) {
if (!music_ptr) start_music(music1);

View File

@ -1,3 +1,5 @@
#ifndef _NESLIB_H
#define _NESLIB_H
/*
(C) 2015 Alex Semenov (Shiru)
(C) 2016 Lauri Kasanen
@ -30,8 +32,14 @@
// unrle_vram renamed to vram_unrle, with adr argument removed
// 060414 - many fixes and improvements, including sequental VRAM updates
// previous versions were created since mid-2011, there were many updates
// xxxx19 - updated by sehugg@8bitworkshop
// define basic types for convenience
typedef unsigned char byte; // 8-bit unsigned
typedef signed char sbyte; // 8-bit signed
typedef unsigned short word; // 16-bit signed
typedef enum { false, true } bool; // boolean
@ -300,3 +308,6 @@ void __fastcall__ nmi_set_callback(void (*callback)(void));
#define MSB(x) (((x)>>8))
#define LSB(x) (((x)&0xff))
#endif /* neslib.h */

View File

@ -5,17 +5,23 @@
#include "neslib.h"
// APU (sound) support
#include "apu.h"
//#link "apu.c"
// BCD arithmetic support
#include "bcd.h"
//#link "bcd.c"
// VRAM update buffer
#include "vrambuf.h"
//#link "vrambuf.c"
#define COLS 32
#define ROWS 28
//#define DEBUG_FRAMERATE
typedef unsigned char byte;
typedef signed char sbyte;
typedef unsigned short word;
///
const char PALETTE[32] = {
0x0f,
@ -131,59 +137,11 @@ const char TILESET[128*8*2] = {/*{w:8,h:8,bpp:1,count:128,brev:1,np:2,pofs:8,rem
#define CHAR(x) ((x)-' ')
#define BLANK 0
// VRAM UPDATE BUFFER
#define VBUFSIZE 96
byte updbuf[VBUFSIZE];
byte updptr = 0;
void cendbuf() {
updbuf[updptr] = NT_UPD_EOF;
}
void cflushnow() {
cendbuf();
ppu_wait_nmi();
flush_vram_update(updbuf);
updptr = 0;
cendbuf();
vram_adr(0x0);
}
void vdelay(byte count) {
while (count--) cflushnow();
}
void putchar(byte x, byte y, char ch) {
word addr = NTADR_A(x,y);
if (updptr >= VBUFSIZE-4) cflushnow();
updbuf[updptr++] = addr >> 8;
updbuf[updptr++] = addr & 0xff;
updbuf[updptr++] = ch;
cendbuf();
}
void putbytes(byte x, byte y, char* str, byte len) {
word addr = NTADR_A(x,y);
if (updptr >= VBUFSIZE-4-len) cflushnow();
updbuf[updptr++] = (addr >> 8) | NT_UPD_HORZ;
updbuf[updptr++] = addr & 0xff;
updbuf[updptr++] = len;
while (len--) {
updbuf[updptr++] = *str++;
}
cendbuf();
}
void putstring(byte x, byte y, char* str) {
putbytes(x, y, str, strlen(str));
}
void clrscr() {
updptr = 0;
cendbuf();
ppu_off();
vram_adr(0x2000);
vram_adr(NAMETABLE_A);
vram_fill(BLANK, 32*28);
vram_adr(0x0);
ppu_on_all();
@ -203,26 +161,7 @@ void draw_bcd_word(byte col, byte row, word bcd) {
buf[j] = CHAR('0'+(bcd&0xf));
bcd >>= 4;
}
putbytes(col, row, buf, 5);
}
word bcd_add(word a, word b) {
word result = 0;
byte c = 0;
byte shift = 0;
while (shift < 16) {
byte d = (a & 0xf) + (b & 0xf) + c;
c = 0;
while (d >= 10) {
c++;
d -= 10;
}
result |= d << shift;
shift += 4;
a >>= 4;
b >>= 4;
}
return result;
putbytes(NTADR_A(col, row), buf, 5);
}
// GAME CODE
@ -261,17 +200,17 @@ typedef struct {
} AttackingEnemy;
typedef struct {
signed char dx;
byte xpos;
signed char dy;
byte ypos;
signed char dx;
signed char dy;
} Missile;
typedef struct {
byte name;
byte tag;
byte x;
byte y;
byte name;
byte tag;
} Sprite;
#define ENEMIES_PER_ROW 8
@ -366,7 +305,7 @@ void draw_row(byte row) {
}
x += 3;
}
putbytes(0, y, buf, sizeof(buf));
putbytes(NTADR_A(0, y), buf, sizeof(buf));
}
void draw_next_row() {
@ -724,37 +663,30 @@ void new_player_ship() {
void set_sounds() {
byte i;
byte enable = 0x1 | 0x2 | 0x8;
// these channels decay, so ok to always enable
byte enable = ENABLE_PULSE0|ENABLE_PULSE1|ENABLE_NOISE;
// missile fire sound
if (missiles[PLYRMISSILE].ypos != YOFFSCREEN) {
APU.pulse[0].period_low = missiles[PLYRMISSILE].ypos ^ 0xff;
APU.pulse[0].len_period_high = 0;
APU.pulse[0].control = 0x80 | 0x30 | 6;
APU_PULSE_SUSTAIN(0, missiles[PLYRMISSILE].ypos ^ 0xff, DUTY_50, 6);
} else {
APU.pulse[0].control = 0x30;
APU_PULSE_CONTROL(0, DUTY_50, 1);
}
// enemy explosion sound
if (player_exploding && player_exploding < 8) {
APU.noise.control = 4;
APU.noise.period = 8 + player_exploding;
APU.noise.len = 15;
APU_NOISE_DECAY(8 + player_exploding, 5, 15);
} else if (enemy_exploding) {
APU.noise.control = 2;
APU.noise.period = 8 + enemy_exploding;
APU.noise.len = 8;
APU_NOISE_DECAY(8 + enemy_exploding, 2, 8);
}
// set diving sounds for spaceships
for (i=0; i<2; i++) {
register AttackingEnemy* a = i ? &attackers[4] : &attackers[0];
if (a->findex && !a->returning) {
byte y = a->y >> 8;
APU.triangle.counter = 0xc0;
APU.triangle.period_low = y;
APU.triangle.len_period_high = 1;
enable |= 0x4;
APU_TRIANGLE_SUSTAIN(0x100 | y);
enable |= ENABLE_TRIANGLE;
}
}
APU.status = enable;
APU_ENABLE(enable);
}
static char starx[32];
@ -783,7 +715,7 @@ void play_round() {
register byte t0;
byte end_timer = 255;
add_score(0);
putstring(0, 0, "PLAYER 1");
//putbytes(NTADR_A(0, 1), "PLAYER 1", 8);
setup_formation();
clrobjs();
formation_direction = 1;
@ -851,7 +783,7 @@ void set_shifted_pattern(const byte* src, word dest, byte shift) {
vram_write(buf, sizeof(buf));
}
void setup_tileset() {
void setup_graphics() {
byte i;
word src;
word dest;
@ -869,26 +801,14 @@ void setup_tileset() {
set_shifted_pattern(&TILESET[src], dest, i);
dest += 3*16;
}
}
const byte APUINIT[0x13] = {
0x30,0x08,0x00,0x00,
0x30,0x08,0x00,0x00,
0x80,0x00,0x00,0x00,
0x30,0x00,0x00,0x00,
0x00,0x00,0x00
};
void init_apu() {
// from https://wiki.nesdev.com/w/index.php/APU_basics
memcpy((void*)0x4000, APUINIT, sizeof(APUINIT));
APU.fcontrol = 0x40; // frame counter 5-step
APU.status = 0x0f; // turn on all channels except DMC
// activate vram buffer
cendbuf();
set_vram_update(updbuf);
}
void main() {
setup_tileset();
init_apu();
setup_graphics();
apu_init();
init_stars();
player_score = 0;
while (1) {

View File

@ -12,13 +12,7 @@ extern unsigned char palSprites[16];
extern unsigned char TILESET[8*256];
#define COLS 32
#define ROWS 28
#define NTADR(x,y) ((0x2000|((y)<<5)|(x)))
typedef unsigned char byte;
typedef signed char sbyte;
typedef unsigned short word;
#define ROWS 27
// read a character from VRAM.
// this is tricky because we have to wait
@ -27,10 +21,10 @@ typedef unsigned short word;
// back to the start of the frame.
byte getchar(byte x, byte y) {
// compute VRAM read address
word addr = NTADR(x,y);
word addr = NTADR_A(x,y);
byte rd;
// wait for VBLANK to start
waitvsync();
ppu_wait_nmi();
vram_adr(addr);
vram_read(&rd, 1);
vram_adr(0x0);
@ -60,7 +54,7 @@ void vdelay(byte count) {
}
void cputcxy(byte x, byte y, char ch) {
word addr = NTADR(x,y);
word addr = NTADR_A(x,y);
if (updptr >= 60) cflushnow();
updbuf[updptr++] = addr >> 8;
updbuf[updptr++] = addr & 0xff;
@ -69,7 +63,7 @@ void cputcxy(byte x, byte y, char ch) {
}
void cputsxy(byte x, byte y, char* str) {
word addr = NTADR(x,y);
word addr = NTADR_A(x,y);
byte len = strlen(str);
if (updptr >= 60 - len) cflushnow();
updbuf[updptr++] = (addr >> 8) | NT_UPD_HORZ;

40
presets/nes/vrambuf.c Normal file
View File

@ -0,0 +1,40 @@
#include "neslib.h"
#include "vrambuf.h"
// index to end of buffer
byte updptr = 0;
// add EOF marker to buffer
void cendbuf(void) {
updbuf[updptr] = NT_UPD_EOF;
}
void cclearbuf(void) {
updptr = 0;
cendbuf();
}
// flush buffer now, waiting for next frame
void cflushnow(void) {
// make sure buffer has EOF marker
cendbuf();
// wait for next frame to flush update buffer
// this will also set the scroll registers properly
ppu_wait_frame();
// clear the buffer
cclearbuf();
}
// add multiple characters to update buffer
// using horizontal increment
void putbytes(word addr, char* str, byte len) {
if (updptr >= VBUFSIZE-4-len) cflushnow();
updbuf[updptr++] = (addr >> 8) ^ NT_UPD_HORZ;
updbuf[updptr++] = addr & 0xff;
updbuf[updptr++] = len;
while (len--) {
updbuf[updptr++] = *str++;
}
cendbuf();
}

38
presets/nes/vrambuf.h Normal file
View File

@ -0,0 +1,38 @@
#ifndef _VRAMBUF_H
#define _VRAMBUF_H
#include "neslib.h"
// VBUFSIZE = maximum update buffer bytes
#define VBUFSIZE 128
// update buffer starts at $100 (stack page)
#define updbuf ((byte*)0x100)
// index to end of buffer
extern byte updptr;
// macros
#define VRAMBUF_PUT(addr,len,flags)\
updbuf[updptr++] = ((addr) >> 8) | (flags);\
updbuf[updptr++] = (addr) & 0xff;\
updbuf[updptr++] = (len);
#define VRAMBUF_ADD(b)\
updbuf[updptr++] = (b);
// add EOF marker to buffer
void cendbuf(void);
// clear update buffer
void cclearbuf(void);
// flush buffer now, waiting for next frame
void cflushnow(void);
// add multiple characters to update buffer
// using horizontal increment
void putbytes(word addr, char* str, byte len);
#endif // vrambuf.h

View File

@ -17,6 +17,7 @@ const JSNES_PRESETS = [
{id:'neslib1.c', name:'Text'},
{id:'scroll.c', name:'Scrolling'},
{id:'statusbar.c', name:'Status Bar'},
{id:'horizmask.c', name:'Horizontal Scrolling'},
{id:'sprites.c', name:'Sprites'},
{id:'metasprites.c', name:'Metasprites'},
{id:'flicker.c', name:'Flickering Sprites'},
@ -118,6 +119,7 @@ const _JSNESPlatform = function(mainElement) {
},
//TODO: onBatteryRamWrite
});
//nes.ppu.clipToTvSize = false;
nes.stop = function() {
// TODO: trigger breakpoint
self.pause();
@ -300,10 +302,10 @@ const _JSNESPlatform = function(mainElement) {
var PPUFLAGS = [
["f_nmiOnVblank","NMI_ON_VBLANK"],
["f_spVisibility","SPRITES"],
["f_spClipping","CLIP_SPRITES"],
["f_spClipping","NO_CLIP_SPRITES"],
["f_dispType","MONOCHROME"],
["f_bgVisibility","BACKGROUND"],
["f_bgClipping","CLIP_BACKGROUND"],
["f_bgClipping","NO_CLIP_BACKGROUND"],
];
for (var i=0; i<PPUFLAGS.length; i++) {
var flag = PPUFLAGS[i];

View File

@ -892,7 +892,7 @@ function _recordVideo() {
};
f();
});
return true;
//TODO? return true;
}
function setFrameRateUI(fps:number) {

View File

@ -881,13 +881,12 @@ function linkLD65(step:BuildStep) {
var binpath = "main";
if (staleFiles(step, [binpath])) {
var errors = [];
var errmsg = '';
var LD65 = emglobal.ld65({
instantiateWasm: moduleInstFn('ld65'),
noInitialRun:true,
//logReadFiles:true,
print:print_fn,
printErr:function(s) { errmsg += s + '\n'; }
printErr:function(s) { errors.push({msg:s,line:0}); }
});
var FS = LD65['FS'];
var cfgfile = '/' + platform + '.cfg';
@ -905,8 +904,6 @@ function linkLD65(step:BuildStep) {
'-o', 'main', '-m', 'main.map'].concat(step.args, libargs);
//console.log(args);
execMain(step, LD65, args);
if (errmsg.length)
errors.push({line:0, msg:errmsg});
if (errors.length)
return {errors:errors};
var aout = FS.readFile("main", {encoding:'binary'});