92#define PROGRAM_SPACE 0x4000
95#define COLS TMS9918_PIXELS_X
99#define H_VIRTUAL_PIXELS 640
100#define V_DISPLAY_LINES 480
101#define FRAME_RATE_HZ 60.0f
102#define CORE_TEMP_C 30.0f
103#define FRAME_US (1000000 / 60)
107#define BLANK_US (FRAME_US / 6)
111#define GIVE_UP_FRAMES 3600
115#define VR_UNLOCK 0x39
116#define VR_GPU_HI 0x36
117#define VR_GPU_LO 0x37
119#if PICO9918_SINGLE_INSTANCE
122static pico9918_t* tms9918;
123#define INSTANCE tms9918
131static void writeVram(
PICO9918_INST_ARG uint16_t addr,
const uint8_t* data,
size_t len)
145static volatile int gpuBusy = 0;
148static DWORD WINAPI gpuBody(LPVOID unused)
159 *t = CreateThread(NULL, 0, gpuBody, NULL, 0, NULL);
162static void threadJoin(
thread_t t) { WaitForSingleObject(t, INFINITE); CloseHandle(t); }
163static void sleepUs(
unsigned us) { Sleep(us / 1000); }
164static uint64_t nowUs(
void)
167 QueryPerformanceFrequency(&f);
168 QueryPerformanceCounter(&t);
169 return (uint64_t)t.QuadPart * 1000000u / (uint64_t)f.QuadPart;
172static void* gpuBody(
void* unused)
181static int threadStart(
thread_t* t) {
return pthread_create(t, NULL, gpuBody, NULL) == 0; }
182static void threadJoin(
thread_t t) { pthread_join(t, NULL); }
183static void sleepUs(
unsigned us)
185 struct timespec ts = {(time_t)(us / 1000000), (long)(us % 1000000) * 1000};
186 nanosleep(&ts, NULL);
188static uint64_t nowUs(
void)
191 clock_gettime(CLOCK_MONOTONIC, &t);
192 return (uint64_t)t.tv_sec * 1000000u + (uint64_t)(t.tv_nsec / 1000);
200static void waitUntil(uint64_t deadlineUs)
204 const uint64_t now = nowUs();
205 if (now >= deadlineUs)
return;
206 const uint64_t left = deadlineUs - now;
207 if (left > 2000) sleepUs((
unsigned)(left - 2000));
211static uint8_t vPixelScale = 1;
212static uint16_t vVirtualPixels = V_DISPLAY_LINES;
231 static PICO9918_PIXEL_T pixels[H_VIRTUAL_PIXELS + 16];
234 for (uint16_t y = 0; y < vVirtualPixels; ++y)
237 waitUntil(startUs + FRAME_US - BLANK_US);
244 waitUntil(startUs + FRAME_US);
247static int writePpm(
const char* path,
const uint8_t* rgb)
249 FILE* f = fopen(path,
"wb");
251 fprintf(f,
"P6\n%d %d\n255\n", COLS, ROWS);
252 fwrite(rgb, 1, (
size_t)COLS * ROWS * 3, f);
257int main(
int argc,
char** argv)
259 const char* out = (argc > 1) ? argv[1] :
"gpu.ppm";
260 const char* bin = (argc > 2) ? argv[2] : MANDEL_BIN;
261 const uint16_t entry = (argc > 3) ? (uint16_t)strtoul(argv[3], NULL, 0) : ENTRY;
263 if ((entry & 1) || entry >= PROGRAM_SPACE)
265 fprintf(stderr,
"%#06x cannot be a start address: the GPU refuses an odd one, and "
266 "a program lives below %#06x\n", entry, PROGRAM_SPACE);
270 static uint8_t program[PROGRAM_SPACE];
271 FILE* f = fopen(bin,
"rb");
274 fprintf(stderr,
"cannot open %s\n", bin);
277 const size_t len = fread(program + entry, 1,
sizeof program - entry, f);
281 fprintf(stderr,
"%s is empty\n", bin);
285#if PICO9918_SINGLE_INSTANCE
289 if (!tms9918)
return 1;
317 if (!threadStart(&gpu))
319 fprintf(stderr,
"no thread to run the GPU on\n");
327 uint64_t nextUs = nowUs();
328 while (gpuBusy && frames < GIVE_UP_FRAMES)
336 fprintf(stderr,
"%s did not finish in %u frames\n", bin, frames);
344 printf(
"%s: %u bytes at 0x%04X, %u us of host time over %u frames\n", bin,
345 (
unsigned)len, entry, (
unsigned)us, frames);
350 static uint8_t rgb[(size_t)COLS * ROWS * 3];
351 for (uint16_t y = 0; y < ROWS; ++y)
356 for (uint16_t x = 0; x < COLS; ++x)
359 uint8_t* px = rgb + ((size_t)y * COLS + x) * 3;
360 px[0] = (uint8_t)(((argb >> 8) & 0xf) * 17);
361 px[1] = (uint8_t)(((argb >> 4) & 0xf) * 17);
362 px[2] = (uint8_t)((argb & 0xf) * 17);
366 if (!writePpm(out, rgb))
368 fprintf(stderr,
"cannot write %s\n", out);
371 printf(
"wrote %s (%dx%d)\n", out, COLS, ROWS);
373#if !PICO9918_SINGLE_INSTANCE
void pico9918_gpu_reset_time(void)
Reset the internal GPU time accumulator to 0.
void pico9918_gpu_init(pico9918_t *tms9918)
Initialize the TMS9900 GPU.
void pico9918_gpu_step(pico9918_t *tms9918)
One pass of that loop: run a pending trigger to completion, then dispatch any flash and config-action...
uint32_t pico9918_gpu_time(uint32_t totalTime)
Return the GPU's CPU time in microseconds.
pico9918-core - GPU Interface
void pico9918_destroy(pico9918_t *tms9918)
destroy a TMS9918
const uint8_t * pico9918_line_source(pico9918_t *tms9918)
where the scanline just generated actually is.
uint8_t pico9918_scan_line(pico9918_t *tms9918, uint16_t y)
generate a scanline
void pico9918_reset(pico9918_t *tms9918)
reset the new TMS9918
uint16_t pico9918_default_palette(int index)
a default palette entry, 0xargb
pico9918-core - core interface
pico9918_t * pico9918_new(void)
create a new TMS9918
#define PICO9918_INST_ARG
declare the instance ahead of other parameters
#define PICO9918_INST_ONLY
pass the instance as the only argument
pico9918_register_t
the eight TMS9918 registers, by number and by what each one holds
#define PICO9918_INST
pass the instance ahead of other arguments
void pico9918_frame_porch(pico9918_t *tms9918)
see the header.
bool pico9918_frame_scanline(pico9918_t *tms9918, uint16_t y, const pico9918_scanline_params_t *params, PICO9918_PIXEL_T *pixels)
see the header.
pico9918_frame_geometry_t pico9918_frame_end(pico9918_t *tms9918, float tempC, float frameRateHz, pico9918_frame_display_t *display)
see the header.
pico9918-core - frame module
pico9918-core - utility / helper functions
static void pico9918_set_address_write(pico9918_t *tms9918, uint16_t addr)
point the VRAM address register at addr for writing, ie.
static void pico9918_write_bytes(pico9918_t *tms9918, const uint8_t *bytes, size_t numBytes)
write a block of bytes to VRAM from the current address
static void pico9918_write_register_value(pico9918_t *tms9918, pico9918_register_t reg, uint8_t value)
write a VDP register as a host would, value byte first
the host's mutable vertical display parameters, as the end-of-frame geometry sees them
uint16_t vVirtualPixels
(in, and out when yScale > 1)
uint8_t vPixelScale
(in, and out when yScale > 1)
the host's per-call display parameters, as the scanline sees them