vc3/vm/main.c
2025-03-31 23:37:50 +02:00

478 lines
12 KiB
C

#include "common/fmt_binary.h"
#include "common/op_str.h"
#include "common/video_character_display.h"
#include "vm.h"
#include <SDL2/SDL.h>
#include <SDL2/SDL_error.h>
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_keycode.h>
#include <SDL2/SDL_pixels.h>
#include <SDL2/SDL_render.h>
#include <SDL2/SDL_video.h>
#include <bits/pthreadtypes.h>
#include <errno.h>
#include <pthread.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
Interrupt* data;
size_t capacity;
size_t front;
size_t back;
} InterruptQueue;
void int_queue_construct(InterruptQueue* queue, size_t capacity)
{
*queue = (InterruptQueue) {
.data = malloc(sizeof(Interrupt) * capacity),
.capacity = capacity,
.back = 0,
.front = 0,
};
}
void int_queue_destroy(InterruptQueue* queue)
{
free(queue->data);
}
void int_queue_push(InterruptQueue* queue, Interrupt req)
{
size_t front = queue->front + 1;
if (front >= queue->capacity) {
front = 0;
}
if (front == queue->back) {
fprintf(stderr, "error: queue overflow\n");
exit(1);
}
queue->data[queue->front] = req;
queue->front = front;
}
size_t int_queue_size(const InterruptQueue* queue)
{
return queue->front >= queue->back
? queue->front - queue->back
: (queue->capacity - queue->back) + queue->front;
}
Interrupt int_queue_pop(InterruptQueue* queue)
{
if (queue->back == queue->front) {
fprintf(stderr, "error: queue underflow\n");
exit(1);
}
Interrupt val = queue->data[queue->back];
size_t back = queue->back + 1;
if (back >= queue->capacity) {
back = 0;
}
queue->back = back;
return val;
}
typedef struct {
IODevice io_device;
SDL_Window* window;
SDL_Renderer* renderer;
SDL_Texture* buffer_texture;
pthread_t render_thread;
atomic_bool should_run;
pthread_mutex_t mutex;
pthread_cond_t interrupt_waiter;
InterruptQueue int_queue;
} SdlDevice;
int sdldevice_construct(SdlDevice* device);
void sdldevice_destroy(SdlDevice* device);
void sdldevice_set_char(
IODevice* device_device, uint16_t offset, uint8_t value);
void sdldevice_wait_for_interrupt(IODevice* io_device);
void* sdldevice_thread_entry(void* data);
void sdldevice_poll_events(SdlDevice* device);
Interrupt sdldevice_maybe_next_interrupt(IODevice* io_device);
int sdldevice_construct(SdlDevice* device)
{
int res;
res = SDL_Init(SDL_INIT_VIDEO);
if (res != 0) {
fprintf(stderr, "error: could not init sdl: %s\n", SDL_GetError());
return -1;
}
SDL_Window* window;
SDL_Renderer* renderer;
res = SDL_CreateWindowAndRenderer(
vcd_width_in_px, vcd_height_in_px, 0, &window, &renderer);
if (res != 0) {
fprintf(stderr,
"error: could not create window/renderer: %s\n",
SDL_GetError());
return -1;
}
SDL_Texture* buffer_texture = SDL_CreateTexture(renderer,
SDL_PIXELFORMAT_RGBA32,
SDL_TEXTUREACCESS_STREAMING,
vcd_width_in_px,
vcd_height_in_px);
if (buffer_texture == NULL) {
fprintf(stderr,
"error: could not create buffer texture: %s\n",
SDL_GetError());
return -1;
}
*device = (SdlDevice) {
.io_device = (IODevice) {
.self = device,
.set_char = sdldevice_set_char,
.wait_for_interrupt = sdldevice_wait_for_interrupt,
.maybe_next_interrupt = sdldevice_maybe_next_interrupt,
},
.window = window,
.renderer = renderer,
.buffer_texture = buffer_texture,
.render_thread = (pthread_t) { 0 },
.should_run = true,
.mutex = PTHREAD_MUTEX_INITIALIZER,
.interrupt_waiter = PTHREAD_COND_INITIALIZER,
.int_queue = {0},
};
SDL_RenderPresent(device->renderer);
pthread_create(
&device->render_thread, NULL, sdldevice_thread_entry, device);
int_queue_construct(&device->int_queue, 128);
return 0;
}
void sdldevice_destroy(SdlDevice* device)
{
device->should_run = false;
pthread_join(device->render_thread, NULL);
pthread_mutex_destroy(&device->mutex);
pthread_cond_destroy(&device->interrupt_waiter);
int_queue_destroy(&device->int_queue);
SDL_DestroyTexture(device->buffer_texture);
SDL_DestroyRenderer(device->renderer);
SDL_DestroyWindow(device->window);
SDL_Quit();
}
extern const uint64_t charset[];
void sdldevice_set_char(IODevice* io_device, uint16_t offset, uint8_t value)
{
if (!((value >= 'A' && value <= 'Z') || value == ' ')) {
printf("sdldevice: invalid char value = %d\n", value);
return;
}
SdlDevice* device = io_device->self;
pthread_mutex_lock(&device->mutex);
SDL_Color* buffer;
int pitch;
int res = SDL_LockTexture(
device->buffer_texture, NULL, (void**)&buffer, &pitch);
if (res != 0) {
fprintf(stderr, "error: could not lock texture: %s\n", SDL_GetError());
pthread_mutex_unlock(&device->mutex);
return;
}
for (int ch_y = 0; ch_y < vcd_ch_height; ++ch_y) {
for (int ch_x = 0; ch_x < vcd_ch_width; ++ch_x) {
bool ch = charset[value]
>> (ch_y * vcd_ch_width + (vcd_ch_width - ch_x - 1))
& 1;
for (int px_y = 0; px_y < vcd_px_height; ++px_y) {
for (int px_x = 0; px_x < vcd_px_width; ++px_x) {
int x = (offset % vcd_width_in_ch * vcd_ch_width + ch_x)
* vcd_px_width
+ px_x;
int y = (offset / vcd_width_in_ch * vcd_ch_height + ch_y)
* vcd_px_height
+ px_y;
buffer[y * vcd_width_in_px + x] = ch
? (SDL_Color) { 0xff, 0xff, 0xff, 0xff }
: (SDL_Color) { 0x00, 0x00, 0x00, 0xff };
}
}
}
}
SDL_UnlockTexture(device->buffer_texture);
SDL_RenderCopy(device->renderer, device->buffer_texture, NULL, NULL);
SDL_RenderPresent(device->renderer);
pthread_mutex_unlock(&device->mutex);
}
void sdldevice_wait_for_interrupt(IODevice* io_device)
{
SdlDevice* device = io_device->self;
pthread_mutex_lock(&device->mutex);
// printf("vm: waiting for interrupt...\n");
pthread_cond_wait(&device->interrupt_waiter, &device->mutex);
// printf("vm: got interrupt!\n");
pthread_mutex_unlock(&device->mutex);
}
void* sdldevice_thread_entry(void* data)
{
SdlDevice* device = (SdlDevice*)data;
while (device->should_run) {
sdldevice_poll_events(device);
if (!device->should_run)
break;
SDL_Delay(6);
}
return NULL;
}
static inline bool is_exit_event(SDL_Event* event)
{
return event->type == SDL_QUIT
|| (event->type == SDL_KEYDOWN
&& event->key.keysym.scancode == SDL_SCANCODE_ESCAPE
// && event->key.keysym.mod & KMOD_CTRL
);
}
void sdldevice_poll_events(SdlDevice* device)
{
pthread_mutex_lock(&device->mutex);
bool should_notify = false;
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (is_exit_event(&event)) {
device->should_run = false;
int_queue_push(&device->int_queue,
(Interrupt) {
.type = InterruptType_Shutdown,
});
should_notify = true;
break;
} else if (event.type == SDL_KEYDOWN) {
int_queue_push(&device->int_queue,
(Interrupt) {
.type = InterruptType_KeyEvent,
.keycode = (uint16_t)event.key.keysym.scancode,
});
should_notify = true;
}
}
pthread_mutex_unlock(&device->mutex);
if (should_notify) {
// printf("sdldevice: interrupt occured!\n");
pthread_cond_signal(&device->interrupt_waiter);
}
}
Interrupt sdldevice_maybe_next_interrupt(IODevice* io_device)
{
SdlDevice* device = io_device->self;
pthread_mutex_lock(&device->mutex);
Interrupt val = {
.type = InterruptType_None,
};
if (int_queue_size(&device->int_queue) > 0) {
val = int_queue_pop(&device->int_queue);
}
pthread_mutex_unlock(&device->mutex);
return val;
}
typedef struct {
Drive drive;
uint64_t* mem;
} MemDrive;
void memdrive_construct(MemDrive* drive, uint64_t* mem, size_t mem_size);
void memdrive_drive_read(Drive* in_drive, uint8_t* block, uint16_t i);
void memdrive_drive_write(Drive* in_drive, const uint8_t* block, uint16_t i);
void memdrive_construct(MemDrive* drive, uint64_t* mem, size_t mem_size)
{
const uint16_t block_size = 512;
*drive = (MemDrive) {
.drive = (Drive) {
.self = drive,
.block_size = block_size,
.block_amount = (uint16_t)(mem_size / block_size),
.read = memdrive_drive_read,
.write = memdrive_drive_write,
},
.mem = mem,
};
}
void memdrive_drive_read(Drive* in_drive, uint8_t* block, uint16_t i)
{
MemDrive* drive = in_drive->self;
memcpy(block,
&drive->mem[i * drive->drive.block_size],
drive->drive.block_size);
}
void memdrive_drive_write(Drive* in_drive, const uint8_t* block, uint16_t i)
{
MemDrive* drive = in_drive->self;
memcpy(&drive->mem[i * drive->drive.block_size],
block,
drive->drive.block_size);
}
typedef struct {
Drive drive;
FILE* fp;
} FileDrive;
void filedrive_construct(
FileDrive* drive, FILE* fp, uint16_t block_size, uint16_t block_amount);
void filedrive_drive_read(Drive* in_drive, uint8_t* block, uint16_t i);
void filedrive_drive_write(Drive* in_drive, const uint8_t* block, uint16_t i);
void filedrive_construct(
FileDrive* drive, FILE* fp, uint16_t block_size, uint16_t block_amount)
{
*drive = (FileDrive) {
.drive = (Drive) {
.self = drive,
.block_size = block_size,
.block_amount = block_size,
.read = filedrive_drive_read,
.write = filedrive_drive_write,
},
.fp = fp,
};
}
void filedrive_drive_read(Drive* in_drive, uint8_t* block, uint16_t i)
{
FileDrive* drive = in_drive->self;
fseek(drive->fp, drive->drive.block_size * i, SEEK_SET);
fread(block, sizeof(uint8_t), drive->drive.block_size, drive->fp);
}
void filedrive_drive_write(Drive* in_drive, const uint8_t* block, uint16_t i)
{
FileDrive* drive = in_drive->self;
fseek(drive->fp, drive->drive.block_size * i, SEEK_SET);
fwrite(block, sizeof(uint8_t), drive->drive.block_size, drive->fp);
}
__attribute__((unused)) static inline void dump_program(uint16_t* program)
{
for (size_t rip = 20; rip < 60; ++rip) {
uint16_t val = program[rip];
printf("[%02lx] = %02x %02x %c%c%c%c %c%c%c%c %c%c%c%c %c%c%c%c "
"%s\n",
rip * 2,
val >> 8,
val & 0xff,
fmt_binary(val >> 8),
fmt_binary(val & 0xff),
op_str(val & 0x3f));
}
}
int main(void)
{
int res;
int label_ids = 0;
SdlDevice io_device;
res = sdldevice_construct(&io_device);
if (res != 0) {
exit(1);
}
FILE* fp = fopen("build/image", "rb");
if (!fp) {
fprintf(
stderr, "error: could not open build/image: %s\n", strerror(errno));
exit(1);
}
FileDrive drive;
filedrive_construct(&drive, fp, 512, 32);
vm_start(&drive.drive, &io_device.io_device);
fclose(fp);
sdldevice_destroy(&io_device);
}
const char* __asan_default_options(void)
{
return "detect_leaks=0";
}
const uint64_t charset[] = {
[' '] = 0x0000000000000000,
['A'] = 0x66667E66667E1800,
['B'] = 0x7C7E667C667E7800,
['C'] = 0x3C7E6060607E3C00,
['D'] = 0x7C7E6666667E7C00,
['E'] = 0x7E7E607E607E7E00,
['F'] = 0x60607878607E7E00,
['G'] = 0x7E7E666E607E7E00,
['H'] = 0x6666667E7E666600,
['I'] = 0x7E7E1818187E7E00,
['J'] = 0x3C7E6606067E7E00,
['K'] = 0x666E7C7C6E666600,
['L'] = 0x7E7E606060606000,
['M'] = 0xC3C3C3C3DBFFE700,
['N'] = 0x6666666E7E766600,
['O'] = 0x3C66666666663C00,
['P'] = 0x60607C7E667C7C00,
['Q'] = 0x3F7E6E66667E3C00,
['R'] = 0x666C7C7E667E7C00,
['S'] = 0x3C66063C60663C00,
['T'] = 0x18181818187E7E00,
['U'] = 0x3C7E666666666600,
['V'] = 0x183C7E6666666600,
['W'] = 0xE7FFDBDBC3C3C300,
['X'] = 0x6666663C3C666600,
['Y'] = 0x181818183C666600,
['Z'] = 0x7E7E30180C7E7E00,
};