mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 03:27:39 +00:00
Make NESEMU1 demo more reliable
This program was originally ported to Cosmopolitan before we had threads so it was designed to use a single thread. That caused issues for people with slower computers, like an Intel Core i5, where Gyarados would go so slow that the audio would skip. I would also get audio skipping when the terminal was put in full screen mode. Now we use two threads and smarter timing, so NESEMU1 should go reliably fast on everyone's computer today.
This commit is contained in:
parent
e975245102
commit
d730fc668c
2 changed files with 125 additions and 134 deletions
|
@ -150,6 +150,10 @@ o/$(MODE)/examples/picol.o: private \
|
||||||
CPPFLAGS += \
|
CPPFLAGS += \
|
||||||
-DSTACK_FRAME_UNLIMITED
|
-DSTACK_FRAME_UNLIMITED
|
||||||
|
|
||||||
|
o/$(MODE)/examples/nesemu1.o: private \
|
||||||
|
CPPFLAGS += \
|
||||||
|
-O3
|
||||||
|
|
||||||
o/$(MODE)/examples/picol.dbg: \
|
o/$(MODE)/examples/picol.dbg: \
|
||||||
$(EXAMPLES_DEPS) \
|
$(EXAMPLES_DEPS) \
|
||||||
o/$(MODE)/examples/picol.o \
|
o/$(MODE)/examples/picol.o \
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
#include "dsp/tty/tty.h"
|
#include "dsp/tty/tty.h"
|
||||||
#include "libc/assert.h"
|
#include "libc/assert.h"
|
||||||
#include "libc/calls/calls.h"
|
#include "libc/calls/calls.h"
|
||||||
#include "libc/calls/struct/itimerval.h"
|
|
||||||
#include "libc/calls/struct/sigset.h"
|
#include "libc/calls/struct/sigset.h"
|
||||||
#include "libc/calls/struct/winsize.h"
|
#include "libc/calls/struct/winsize.h"
|
||||||
#include "libc/calls/termios.h"
|
#include "libc/calls/termios.h"
|
||||||
|
@ -36,20 +35,17 @@
|
||||||
#include "libc/str/str.h"
|
#include "libc/str/str.h"
|
||||||
#include "libc/sysv/consts/ex.h"
|
#include "libc/sysv/consts/ex.h"
|
||||||
#include "libc/sysv/consts/exit.h"
|
#include "libc/sysv/consts/exit.h"
|
||||||
#include "libc/sysv/consts/f.h"
|
|
||||||
#include "libc/sysv/consts/fileno.h"
|
#include "libc/sysv/consts/fileno.h"
|
||||||
#include "libc/sysv/consts/itimer.h"
|
|
||||||
#include "libc/sysv/consts/o.h"
|
|
||||||
#include "libc/sysv/consts/poll.h"
|
#include "libc/sysv/consts/poll.h"
|
||||||
#include "libc/sysv/consts/prio.h"
|
#include "libc/sysv/consts/prio.h"
|
||||||
#include "libc/sysv/consts/sig.h"
|
#include "libc/sysv/consts/sig.h"
|
||||||
#include "libc/sysv/consts/w.h"
|
|
||||||
#include "libc/thread/thread.h"
|
#include "libc/thread/thread.h"
|
||||||
#include "libc/time.h"
|
#include "libc/time.h"
|
||||||
#include "libc/x/xasprintf.h"
|
#include "libc/x/xasprintf.h"
|
||||||
#include "libc/x/xsigaction.h"
|
#include "libc/x/xsigaction.h"
|
||||||
#include "libc/zip.h"
|
#include "libc/zip.h"
|
||||||
#include "third_party/getopt/getopt.internal.h"
|
#include "third_party/getopt/getopt.internal.h"
|
||||||
|
#include "third_party/libcxx/__atomic/atomic.h"
|
||||||
#include "third_party/libcxx/vector"
|
#include "third_party/libcxx/vector"
|
||||||
#include "tool/viz/lib/knobs.h"
|
#include "tool/viz/lib/knobs.h"
|
||||||
|
|
||||||
|
@ -124,15 +120,6 @@ typedef uint8_t u8;
|
||||||
typedef uint16_t u16;
|
typedef uint16_t u16;
|
||||||
typedef uint32_t u32;
|
typedef uint32_t u32;
|
||||||
|
|
||||||
static const struct itimerval kNesFps = {
|
|
||||||
{0, 1. / FPS * 1e6},
|
|
||||||
{0, 1. / FPS * 1e6},
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Frame {
|
|
||||||
char *p, *w, *mem;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Action {
|
struct Action {
|
||||||
int code;
|
int code;
|
||||||
int wait;
|
int wait;
|
||||||
|
@ -148,15 +135,13 @@ struct ZipGames {
|
||||||
char** p;
|
char** p;
|
||||||
};
|
};
|
||||||
|
|
||||||
static int frame_;
|
static const struct timespec kNesFps = {0, 1. / FPS * 1e9};
|
||||||
static size_t vtsize_;
|
|
||||||
static bool artifacts_;
|
static bool artifacts_;
|
||||||
static long tyn_, txn_;
|
static long tyn_, txn_;
|
||||||
static struct Frame vf_[2];
|
|
||||||
static const char* inputfn_;
|
static const char* inputfn_;
|
||||||
static struct Status status_;
|
static struct Status status_;
|
||||||
static volatile bool exited_;
|
static volatile bool exited_;
|
||||||
static volatile bool timeout_;
|
|
||||||
static volatile bool resized_;
|
static volatile bool resized_;
|
||||||
static struct CosmoAudio* ca_;
|
static struct CosmoAudio* ca_;
|
||||||
static struct TtyRgb* ttyrgb_;
|
static struct TtyRgb* ttyrgb_;
|
||||||
|
@ -165,7 +150,7 @@ static struct ZipGames zipgames_;
|
||||||
static struct Action arrow_, button_;
|
static struct Action arrow_, button_;
|
||||||
static struct SamplingSolution* ssy_;
|
static struct SamplingSolution* ssy_;
|
||||||
static struct SamplingSolution* ssx_;
|
static struct SamplingSolution* ssx_;
|
||||||
static unsigned char pixels_[3][DYN][DXN];
|
static unsigned char (*pixels_)[3][DYN][DXN];
|
||||||
static unsigned char palette_[3][64][512][3];
|
static unsigned char palette_[3][64][512][3];
|
||||||
static int joy_current_[2], joy_next_[2], joypos_[2];
|
static int joy_current_[2], joy_next_[2], joypos_[2];
|
||||||
|
|
||||||
|
@ -173,6 +158,11 @@ static int keyframes_ = 10;
|
||||||
static enum TtyBlocksSelection blocks_ = kTtyBlocksUnicode;
|
static enum TtyBlocksSelection blocks_ = kTtyBlocksUnicode;
|
||||||
static enum TtyQuantizationAlgorithm quant_ = kTtyQuantXterm256;
|
static enum TtyQuantizationAlgorithm quant_ = kTtyQuantXterm256;
|
||||||
|
|
||||||
|
static struct timespec deadline_;
|
||||||
|
static std::atomic<void*> pixels_ready_;
|
||||||
|
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
|
||||||
|
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
|
||||||
static int Clamp(int v) {
|
static int Clamp(int v) {
|
||||||
return MAX(0, MIN(255, v));
|
return MAX(0, MIN(255, v));
|
||||||
}
|
}
|
||||||
|
@ -232,18 +222,8 @@ void InitPalette(void) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t Write(int fd, const void* p, size_t n) {
|
|
||||||
int rc;
|
|
||||||
sigset_t ss, oldss;
|
|
||||||
sigfillset(&ss);
|
|
||||||
sigprocmask(SIG_SETMASK, &ss, &oldss);
|
|
||||||
rc = write(fd, p, n);
|
|
||||||
sigprocmask(SIG_SETMASK, &oldss, 0);
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void WriteString(const char* s) {
|
static void WriteString(const char* s) {
|
||||||
Write(STDOUT_FILENO, s, strlen(s));
|
write(STDOUT_FILENO, s, strlen(s));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Exit(int rc) {
|
void Exit(int rc) {
|
||||||
|
@ -264,28 +244,10 @@ void OnCtrlC(void) {
|
||||||
exited_ = true;
|
exited_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnTimer(void) {
|
|
||||||
timeout_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OnResize(void) {
|
void OnResize(void) {
|
||||||
resized_ = true;
|
resized_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnPiped(void) {
|
|
||||||
exited_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void OnSigChld(void) {
|
|
||||||
waitpid(-1, 0, WNOHANG);
|
|
||||||
cosmoaudio_close(ca_);
|
|
||||||
ca_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void InitFrame(struct Frame* f) {
|
|
||||||
f->p = f->w = f->mem = (char*)realloc(f->mem, vtsize_);
|
|
||||||
}
|
|
||||||
|
|
||||||
long ChopAxis(long dn, long sn) {
|
long ChopAxis(long dn, long sn) {
|
||||||
while (HALF(sn) > dn) {
|
while (HALF(sn) > dn) {
|
||||||
sn = HALF(sn);
|
sn = HALF(sn);
|
||||||
|
@ -308,11 +270,6 @@ void GetTermSize(void) {
|
||||||
G = (unsigned char*)realloc(G, tyn_ * txn_);
|
G = (unsigned char*)realloc(G, tyn_ * txn_);
|
||||||
B = (unsigned char*)realloc(B, tyn_ * txn_);
|
B = (unsigned char*)realloc(B, tyn_ * txn_);
|
||||||
ttyrgb_ = (struct TtyRgb*)realloc(ttyrgb_, tyn_ * txn_ * 4);
|
ttyrgb_ = (struct TtyRgb*)realloc(ttyrgb_, tyn_ * txn_ * 4);
|
||||||
vtsize_ = ((tyn_ * txn_ * strlen("\e[48;2;255;48;2;255m▄")) +
|
|
||||||
(tyn_ * strlen("\e[0m\r\n")) + 128);
|
|
||||||
frame_ = 0;
|
|
||||||
InitFrame(&vf_[0]);
|
|
||||||
InitFrame(&vf_[1]);
|
|
||||||
WriteString("\e[0m\e[H\e[J");
|
WriteString("\e[0m\e[H\e[J");
|
||||||
resized_ = false;
|
resized_ = false;
|
||||||
}
|
}
|
||||||
|
@ -320,11 +277,7 @@ void GetTermSize(void) {
|
||||||
void IoInit(void) {
|
void IoInit(void) {
|
||||||
GetTermSize();
|
GetTermSize();
|
||||||
xsigaction(SIGINT, (void*)OnCtrlC, 0, 0, NULL);
|
xsigaction(SIGINT, (void*)OnCtrlC, 0, 0, NULL);
|
||||||
xsigaction(SIGPIPE, (void*)OnPiped, 0, 0, NULL);
|
|
||||||
xsigaction(SIGWINCH, (void*)OnResize, 0, 0, NULL);
|
xsigaction(SIGWINCH, (void*)OnResize, 0, 0, NULL);
|
||||||
xsigaction(SIGALRM, (void*)OnTimer, 0, 0, NULL);
|
|
||||||
xsigaction(SIGCHLD, (void*)OnSigChld, 0, 0, NULL);
|
|
||||||
setitimer(ITIMER_REAL, &kNesFps, NULL);
|
|
||||||
ttyhidecursor(STDOUT_FILENO);
|
ttyhidecursor(STDOUT_FILENO);
|
||||||
ttyraw(kTtySigs);
|
ttyraw(kTtySigs);
|
||||||
ttyquantsetup(quant_, kTtyQuantRgb, blocks_);
|
ttyquantsetup(quant_, kTtyQuantRgb, blocks_);
|
||||||
|
@ -461,57 +414,29 @@ ssize_t ReadKeyboard(void) {
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HasVideo(struct Frame* f) {
|
void ScaleVideoFrameToTeletypewriter(unsigned char (*pixels)[3][DYN][DXN]) {
|
||||||
return f->w < f->p;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HasPendingVideo(void) {
|
|
||||||
return HasVideo(&vf_[0]) || HasVideo(&vf_[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Frame* FlipFrameBuffer(void) {
|
|
||||||
frame_ = !frame_;
|
|
||||||
return &vf_[frame_];
|
|
||||||
}
|
|
||||||
|
|
||||||
void TransmitVideo(void) {
|
|
||||||
ssize_t rc;
|
|
||||||
struct Frame* f;
|
|
||||||
f = &vf_[frame_];
|
|
||||||
if (!HasVideo(f))
|
|
||||||
f = FlipFrameBuffer();
|
|
||||||
if ((rc = Write(STDOUT_FILENO, f->w, f->p - f->w)) != -1) {
|
|
||||||
f->w += rc;
|
|
||||||
} else if (errno == EAGAIN) {
|
|
||||||
// slow teletypewriter
|
|
||||||
} else if (errno == EPIPE) {
|
|
||||||
Exit(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScaleVideoFrameToTeletypewriter(void) {
|
|
||||||
long y, x, yn, xn;
|
long y, x, yn, xn;
|
||||||
yn = DYN, xn = DXN;
|
yn = DYN, xn = DXN;
|
||||||
while (HALF(yn) > tyn_ || HALF(xn) > txn_) {
|
while (HALF(yn) > tyn_ || HALF(xn) > txn_) {
|
||||||
if (HALF(xn) > txn_) {
|
if (HALF(xn) > txn_) {
|
||||||
Magikarp2xX(DYN, DXN, pixels_[0], yn, xn);
|
Magikarp2xX(DYN, DXN, (*pixels)[0], yn, xn);
|
||||||
Magikarp2xX(DYN, DXN, pixels_[1], yn, xn);
|
Magikarp2xX(DYN, DXN, (*pixels)[1], yn, xn);
|
||||||
Magikarp2xX(DYN, DXN, pixels_[2], yn, xn);
|
Magikarp2xX(DYN, DXN, (*pixels)[2], yn, xn);
|
||||||
xn = HALF(xn);
|
xn = HALF(xn);
|
||||||
}
|
}
|
||||||
if (HALF(yn) > tyn_) {
|
if (HALF(yn) > tyn_) {
|
||||||
Magikarp2xY(DYN, DXN, pixels_[0], yn, xn);
|
Magikarp2xY(DYN, DXN, (*pixels)[0], yn, xn);
|
||||||
Magikarp2xY(DYN, DXN, pixels_[1], yn, xn);
|
Magikarp2xY(DYN, DXN, (*pixels)[1], yn, xn);
|
||||||
Magikarp2xY(DYN, DXN, pixels_[2], yn, xn);
|
Magikarp2xY(DYN, DXN, (*pixels)[2], yn, xn);
|
||||||
yn = HALF(yn);
|
yn = HALF(yn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GyaradosUint8(tyn_, txn_, R, DYN, DXN, pixels_[0], tyn_, txn_, yn, xn, 0, 255,
|
GyaradosUint8(tyn_, txn_, R, DYN, DXN, (*pixels)[0], tyn_, txn_, yn, xn, 0,
|
||||||
ssy_, ssx_, true);
|
255, ssy_, ssx_, true);
|
||||||
GyaradosUint8(tyn_, txn_, G, DYN, DXN, pixels_[1], tyn_, txn_, yn, xn, 0, 255,
|
GyaradosUint8(tyn_, txn_, G, DYN, DXN, (*pixels)[1], tyn_, txn_, yn, xn, 0,
|
||||||
ssy_, ssx_, true);
|
255, ssy_, ssx_, true);
|
||||||
GyaradosUint8(tyn_, txn_, B, DYN, DXN, pixels_[2], tyn_, txn_, yn, xn, 0, 255,
|
GyaradosUint8(tyn_, txn_, B, DYN, DXN, (*pixels)[2], tyn_, txn_, yn, xn, 0,
|
||||||
ssy_, ssx_, true);
|
255, ssy_, ssx_, true);
|
||||||
for (y = 0; y < tyn_; ++y) {
|
for (y = 0; y < tyn_; ++y) {
|
||||||
for (x = 0; x < txn_; ++x) {
|
for (x = 0; x < txn_; ++x) {
|
||||||
ttyrgb_[y * txn_ + x] =
|
ttyrgb_[y * txn_ + x] =
|
||||||
|
@ -528,55 +453,104 @@ void KeyCountdown(struct Action* a) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PollAndSynchronize(void) {
|
void Raster(unsigned char (*pixels)[3][DYN][DXN]) {
|
||||||
do {
|
struct TtyRgb bg = {0x12, 0x34, 0x56, 0};
|
||||||
if (ReadKeyboard() == -1) {
|
struct TtyRgb fg = {0x12, 0x34, 0x56, 0};
|
||||||
if (errno != EINTR)
|
ScaleVideoFrameToTeletypewriter(pixels);
|
||||||
Exit(1);
|
char* ansi = (char*)malloc((tyn_ * txn_ * strlen("\e[48;2;255;48;2;255m▄")) +
|
||||||
if (exited_)
|
(tyn_ * strlen("\e[0m\r\n")) + 128);
|
||||||
Exit(0);
|
char* p = ansi;
|
||||||
|
p = stpcpy(p, "\e[0m\e[H");
|
||||||
|
p = ttyraster(p, ttyrgb_, tyn_, txn_, bg, fg);
|
||||||
|
free(pixels);
|
||||||
|
if (status_.wait) {
|
||||||
|
status_.wait--;
|
||||||
|
p = stpcpy(p, "\e[0m\e[H");
|
||||||
|
p = stpcpy(p, status_.text);
|
||||||
|
}
|
||||||
|
size_t n = p - ansi;
|
||||||
|
ssize_t wrote;
|
||||||
|
for (size_t i = 0; i < n; i += wrote) {
|
||||||
|
if ((wrote = write(STDOUT_FILENO, ansi + i, n - i)) == -1) {
|
||||||
|
exited_ = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(ansi);
|
||||||
|
}
|
||||||
|
|
||||||
|
void* RasterThread(void* arg) {
|
||||||
|
sigset_t ss;
|
||||||
|
sigemptyset(&ss);
|
||||||
|
sigaddset(&ss, SIGINT);
|
||||||
|
sigaddset(&ss, SIGHUP);
|
||||||
|
sigaddset(&ss, SIGQUIT);
|
||||||
|
sigaddset(&ss, SIGTERM);
|
||||||
|
sigaddset(&ss, SIGPIPE);
|
||||||
|
sigprocmask(SIG_SETMASK, &ss, 0);
|
||||||
|
for (;;) {
|
||||||
|
unsigned char(*pixels)[3][DYN][DXN];
|
||||||
|
pthread_mutex_lock(&lock);
|
||||||
|
while (!(pixels = (unsigned char(*)[3][DYN][DXN])pixels_ready_.load()))
|
||||||
|
pthread_cond_wait(&cond, &lock);
|
||||||
|
pixels_ready_.store(0);
|
||||||
|
pthread_mutex_unlock(&lock);
|
||||||
if (resized_)
|
if (resized_)
|
||||||
GetTermSize();
|
GetTermSize();
|
||||||
|
Raster(pixels);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FlushScanline(unsigned py) {
|
||||||
|
if (py != DYN - 1)
|
||||||
|
return;
|
||||||
|
pthread_mutex_lock(&lock);
|
||||||
|
if (!pixels_ready_) {
|
||||||
|
pixels_ready_.store(pixels_);
|
||||||
|
pixels_ = 0;
|
||||||
|
pthread_cond_signal(&cond);
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&lock);
|
||||||
|
if (!pixels_)
|
||||||
|
pixels_ = (unsigned char(*)[3][DYN][DXN])malloc(3 * DYN * DXN);
|
||||||
|
if (exited_)
|
||||||
|
Exit(0);
|
||||||
|
do {
|
||||||
|
struct timespec now = timespec_mono();
|
||||||
|
struct timespec remain = timespec_subz(deadline_, now);
|
||||||
|
int remain_ms = timespec_tomillis(remain);
|
||||||
|
struct pollfd fds[] = {{STDIN_FILENO, POLLIN}};
|
||||||
|
int got = poll(fds, 1, remain_ms);
|
||||||
|
if (got == -1) {
|
||||||
|
if (errno == EINTR)
|
||||||
|
continue;
|
||||||
|
Exit(1);
|
||||||
|
}
|
||||||
|
if (got == 1) {
|
||||||
|
do {
|
||||||
|
if (ReadKeyboard() == -1) {
|
||||||
|
if (errno == EINTR)
|
||||||
|
continue;
|
||||||
|
Exit(1);
|
||||||
|
}
|
||||||
|
} while (0);
|
||||||
}
|
}
|
||||||
} while (!timeout_);
|
|
||||||
TransmitVideo();
|
|
||||||
timeout_ = false;
|
|
||||||
KeyCountdown(&arrow_);
|
KeyCountdown(&arrow_);
|
||||||
KeyCountdown(&button_);
|
KeyCountdown(&button_);
|
||||||
joy_next_[0] = arrow_.code | button_.code;
|
joy_next_[0] = arrow_.code | button_.code;
|
||||||
joy_next_[1] = arrow_.code | button_.code;
|
joy_next_[1] = arrow_.code | button_.code;
|
||||||
}
|
now = timespec_mono();
|
||||||
|
do
|
||||||
void Raster(void) {
|
deadline_ = timespec_add(deadline_, kNesFps);
|
||||||
struct Frame* f;
|
while (timespec_cmp(deadline_, now) <= 0);
|
||||||
struct TtyRgb bg = {0x12, 0x34, 0x56, 0};
|
} while (0);
|
||||||
struct TtyRgb fg = {0x12, 0x34, 0x56, 0};
|
|
||||||
ScaleVideoFrameToTeletypewriter();
|
|
||||||
f = &vf_[!frame_];
|
|
||||||
f->p = f->w = f->mem;
|
|
||||||
f->p = stpcpy(f->p, "\e[0m\e[H");
|
|
||||||
f->p = ttyraster(f->p, ttyrgb_, tyn_, txn_, bg, fg);
|
|
||||||
if (status_.wait) {
|
|
||||||
status_.wait--;
|
|
||||||
f->p = stpcpy(f->p, "\e[0m\e[H");
|
|
||||||
f->p = stpcpy(f->p, status_.text);
|
|
||||||
}
|
|
||||||
PollAndSynchronize();
|
|
||||||
}
|
|
||||||
|
|
||||||
void FlushScanline(unsigned py) {
|
|
||||||
if (py == DYN - 1) {
|
|
||||||
if (!timeout_)
|
|
||||||
Raster();
|
|
||||||
timeout_ = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void PutPixel(unsigned px, unsigned py, unsigned pixel, int offset) {
|
static void PutPixel(unsigned px, unsigned py, unsigned pixel, int offset) {
|
||||||
static unsigned prev;
|
static unsigned prev;
|
||||||
pixels_[0][py][px] = palette_[offset][prev % 64][pixel][2];
|
(*pixels_)[0][py][px] = palette_[offset][prev % 64][pixel][2];
|
||||||
pixels_[1][py][px] = palette_[offset][prev % 64][pixel][1];
|
(*pixels_)[1][py][px] = palette_[offset][prev % 64][pixel][1];
|
||||||
pixels_[2][py][px] = palette_[offset][prev % 64][pixel][0];
|
(*pixels_)[2][py][px] = palette_[offset][prev % 64][pixel][0];
|
||||||
prev = pixel;
|
prev = pixel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1732,8 +1706,18 @@ int PlayGame(const char* romfile, const char* opt_tasfile) {
|
||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// initialize screen
|
||||||
|
pixels_ = (unsigned char(*)[3][DYN][DXN])malloc(3 * DYN * DXN);
|
||||||
InitPalette();
|
InitPalette();
|
||||||
|
|
||||||
|
// start raster thread
|
||||||
|
errno_t err;
|
||||||
|
pthread_t th;
|
||||||
|
if ((err = pthread_create(&th, 0, RasterThread, 0))) {
|
||||||
|
fprintf(stderr, "pthread_create: %s\n", strerror(err));
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
// open speaker
|
// open speaker
|
||||||
struct CosmoAudioOpenOptions cao = {};
|
struct CosmoAudioOpenOptions cao = {};
|
||||||
cao.sizeofThis = sizeof(struct CosmoAudioOpenOptions);
|
cao.sizeofThis = sizeof(struct CosmoAudioOpenOptions);
|
||||||
|
@ -1742,6 +1726,9 @@ int PlayGame(const char* romfile, const char* opt_tasfile) {
|
||||||
cao.channels = 1;
|
cao.channels = 1;
|
||||||
cosmoaudio_open(&ca_, &cao);
|
cosmoaudio_open(&ca_, &cao);
|
||||||
|
|
||||||
|
// initialize time
|
||||||
|
deadline_ = timespec_add(timespec_mono(), kNesFps);
|
||||||
|
|
||||||
// Read the ROM file header
|
// Read the ROM file header
|
||||||
u8 rom16count = fgetc(fp);
|
u8 rom16count = fgetc(fp);
|
||||||
u8 vrom8count = fgetc(fp);
|
u8 vrom8count = fgetc(fp);
|
||||||
|
|
Loading…
Reference in a new issue