diff --git a/Makefile b/Makefile index 1d21c6f..52c7148 100644 --- a/Makefile +++ b/Makefile @@ -129,8 +129,13 @@ lsp: -include $(O)/*.d -include $(O)/obj/*.d +DESTDIR := /usr/local install: mkdir -p $(DESTDIR)/bin - mkdir -p $(DESTDIR)/share/games/mii/ cp $(BIN)/mii_emu_gl $(DESTDIR)/bin/ + +avail: + mkdir -p $(DESTDIR)/bin + rm -f $(DESTDIR)/bin/mii_emu_gl && \ + ln -sf $(realpath $(BIN)/mii_emu_gl) $(DESTDIR)/bin/mii_emu_gl diff --git a/README.md b/README.md index 724369c..1ae5be1 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,16 @@

# MII Version Changelog +## 1.7 + * New animated about box, because, everyone loves a good about box. + * Added support for Write Protect of floppy disks; disk can be write protected manually, or if the file lacks write permissions, OR if the file format (NIB, DSK) doesn't support writes. + * New fancypants 'bit view' of the floppy are they are read/written, with a + heat map to show where the head was last. Drive 1 appears left of the screen, + drive 2 on the right. It fades out after the drive motor stops. + +![Heat map disk viewq](docs/heat_map.png) +*DOS 3.3 Disk 'bitstream view' on the left, the green trace shows what's just be read.* + ## 1.6 * Another big update, yanked the old DiskII driver, and replaced it with a homebrew one, low level, with native support for WOZ (1 & 2) files (*read AND write!*) as well as NIB and DSK (read only). diff --git a/docs/heat_map.png b/docs/heat_map.png new file mode 100644 index 0000000..b0e8c57 Binary files /dev/null and b/docs/heat_map.png differ diff --git a/libmui/mui/mui.h b/libmui/mui/mui.h index cfbb27b..d452c27 100644 --- a/libmui/mui/mui.h +++ b/libmui/mui/mui.h @@ -64,6 +64,7 @@ enum mui_key_e { MUI_KEY_RALT, MUI_KEY_RSUPER, MUI_KEY_LSUPER, + MUI_KEY_CAPSLOCK, MUI_KEY_MODIFIERS_LAST, MUI_KEY_F1 = 0x100, MUI_KEY_F2, @@ -330,7 +331,11 @@ typedef struct mui_drawable_t { dispose_pixels : 1, dispose_drawable : 1; // not used internally, but useful for the application - unsigned int texture_id; + struct { + float opacity; + c2_pt_t size; + unsigned int id; + } texture; // (default) position in destination when drawing c2_pt_t origin; mui_clip_stack_t clip; @@ -364,6 +369,11 @@ mui_drawable_init( void mui_drawable_dispose( mui_drawable_t * dr); +// Clear, but do not dispose of the drawable +void +mui_drawable_clear( + mui_drawable_t * dr); + // get/allocate a pixman structure for this drawable union pixman_image * mui_drawable_get_pixman( diff --git a/libmui/mui/mui_controls.c b/libmui/mui/mui_controls.c index bd57f2f..cd5e334 100644 --- a/libmui/mui/mui_controls.c +++ b/libmui/mui/mui_controls.c @@ -148,7 +148,7 @@ _mui_control_highlight_timer_cb( { mui_control_t * c = param; - printf("%s: %s\n", __func__, c->title); +// printf("%s: %s\n", __func__, c->title); mui_control_set_state(c, MUI_CONTROL_STATE_NORMAL); if (c->cdef) c->cdef(c, MUI_CDEF_SELECT, NULL); diff --git a/libmui/mui/mui_drawable.c b/libmui/mui/mui_drawable.c index 17d3689..045457b 100644 --- a/libmui/mui/mui_drawable.c +++ b/libmui/mui/mui_drawable.c @@ -73,6 +73,9 @@ mui_drawable_clear( mui_clip_stack_clear(&dr->clip); if (dr->pix.pixels && dr->dispose_pixels) free(dr->pix.pixels); + static const mui_pixmap_t zero = {}; + dr->pix = zero; + dr->dispose_pixels = 0; dr->_pix_hash = NULL; } diff --git a/src/drivers/mii_disk2.c b/src/drivers/mii_disk2.c index ffc9940..32b241f 100644 --- a/src/drivers/mii_disk2.c +++ b/src/drivers/mii_disk2.c @@ -13,7 +13,7 @@ #include #include "mii.h" #include "mii_bank.h" -#include "mii_disk2.h" + #include "mii_rom_disk2.h" #include "mii_woz.h" #include "mii_floppy.h" @@ -62,8 +62,8 @@ _mii_floppy_motor_off_cb( { mii_card_disk2_t *c = param; mii_floppy_t *f = &c->floppy[c->selected]; - printf("%s drive %d off\n", __func__, c->selected); - if (c->drive[c->selected].file && f->tracks_dirty) +// printf("%s drive %d off\n", __func__, c->selected); + if (c->drive[c->selected].file && f->seed_dirty != f->seed_saved) mii_floppy_update_tracks(f, c->drive[c->selected].file); f->motor = 0; return 0; @@ -103,21 +103,25 @@ _mii_disk2_switch_track( mii_floppy_t *f = &c->floppy[c->selected]; int qtrack = f->qtrack + delta; if (qtrack < 0) qtrack = 0; - if (qtrack >= 35 * 4) qtrack = (35 * 4) -1; + if (qtrack >= MII_FLOPPY_TRACK_COUNT * 4) + qtrack = (MII_FLOPPY_TRACK_COUNT * 4) -1; if (qtrack == f->qtrack) return f->qtrack; uint8_t track_id = f->track_id[f->qtrack]; - if (track_id != MII_FLOPPY_RANDOM_TRACK_ID) - printf("NEW TRACK D%d: %d\n", c->selected, track_id); +// if (track_id != 0xff) +// printf("NEW TRACK D%d: %d\n", c->selected, track_id); uint8_t track_id_new = f->track_id[qtrack]; - + if (track_id_new >= MII_FLOPPY_TRACK_COUNT) + track_id_new = MII_FLOPPY_NOISE_TRACK; /* adapt the bit position from one track to the others, from WOZ specs */ - uint32_t track_size = f->tracks[track_id].bit_count; - uint32_t new_size = f->tracks[track_id_new].bit_count; - uint32_t new_pos = f->bit_position * new_size / track_size; - f->bit_position = new_pos; + if (track_id_new != MII_FLOPPY_NOISE_TRACK) { + uint32_t track_size = f->tracks[track_id].bit_count; + uint32_t new_size = f->tracks[track_id_new].bit_count; + uint32_t new_pos = f->bit_position * new_size / track_size; + f->bit_position = new_pos; + } f->qtrack = qtrack; return f->qtrack; } @@ -208,6 +212,13 @@ _mii_disk2_access( case 0x0C: case 0x0D: c->lss_mode = (c->lss_mode & ~(1 << LOAD_BIT)) | (!!on << LOAD_BIT); + if (!(c->lss_mode & (1 << WRITE_BIT)) && f->heat) { + uint8_t track_id = f->track_id[f->qtrack]; + uint32_t byte_index = f->bit_position >> 3; + unsigned int dstb = byte_index / MII_FLOPPY_HM_HIT_SIZE; + f->heat->read.map[track_id][dstb] = 255; + f->heat->read.seed++; + } break; case 0x0E: case 0x0F: @@ -230,19 +241,23 @@ static int _mii_disk2_command( mii_t * mii, struct mii_slot_t *slot, - uint8_t cmd, + uint32_t cmd, void * param) { mii_card_disk2_t *c = slot->drv_priv; + int res = -1; switch (cmd) { case MII_SLOT_DRIVE_COUNT: - if (param) + if (param) { *(int *)param = 2; + res = 0; + } break; case MII_SLOT_DRIVE_WP ... MII_SLOT_DRIVE_WP + 2 - 1: { int drive = cmd - MII_SLOT_DRIVE_WP; int *wp = param; if (wp) { + res = 0; printf("Drive %d WP: 0x%x set %s\n", drive, c->floppy[drive].write_protected, *wp ? "ON" : "OFF"); @@ -271,9 +286,18 @@ _mii_disk2_command( mii_floppy_init(&c->floppy[drive]); mii_dd_drive_load(&c->drive[drive], file); mii_floppy_load(&c->floppy[drive], file); + res = 0; + } break; + case MII_SLOT_D2_GET_FLOPPY: { + if (param) { + mii_floppy_t ** fp = param; + fp[0] = &c->floppy[0]; + fp[1] = &c->floppy[1]; + res = 0; + } } break; } - return 0; + return res; } static mii_slot_drv_t _driver = { @@ -372,16 +396,16 @@ _mii_disk2_lss_tick( if (c->clock >= f->bit_timing) { c->clock -= f->bit_timing; uint8_t track_id = f->track_id[f->qtrack]; - + uint8_t * track = f->track_data[track_id]; uint32_t byte_index = f->bit_position >> 3; uint8_t bit_index = 7 - (f->bit_position & 7); if (!(c->lss_mode & (1 << WRITE_BIT))) { - uint8_t bit = f->tracks[track_id].data[byte_index]; + uint8_t bit = track[byte_index]; bit = (bit >> bit_index) & 1; c->head = (c->head << 1) | bit; // see WOZ spec for how we do this here if ((c->head & 0xf) == 0) { - bit = f->tracks[MII_FLOPPY_RANDOM_TRACK_ID].data[byte_index]; + bit = f->track_data[MII_FLOPPY_NOISE_TRACK][byte_index]; bit = (bit >> bit_index) & 1; // printf("RANDOM TRACK %2d %2d %2d : %d\n", // track_id, byte_index, bit_index, bit); @@ -390,16 +414,20 @@ _mii_disk2_lss_tick( } c->lss_mode = (c->lss_mode & ~(1 << RP_BIT)) | (bit << RP_BIT); } - if ((c->lss_mode & (1 << WRITE_BIT))) { + if ((c->lss_mode & (1 << WRITE_BIT)) && track_id != 0xff) { uint8_t msb = c->data_register >> 7; - +#if 0 + printf("WRITE %2d %4d %d : %d LSS State %x mode %x\n", + track_id, byte_index, bit_index, msb, + c->lss_state, c->lss_mode); +#endif if (!f->tracks[track_id].dirty) { - printf("DIRTY TRACK %2d \n", track_id); + // printf("DIRTY TRACK %2d \n", track_id); f->tracks[track_id].dirty = 1; - f->tracks_dirty = 1; + f->seed_dirty++; } - f->tracks[track_id].data[byte_index] &= ~(1 << bit_index); - f->tracks[track_id].data[byte_index] |= (msb << bit_index); + f->track_data[track_id][byte_index] &= ~(1 << bit_index); + f->track_data[track_id][byte_index] |= (msb << bit_index); } f->bit_position = (f->bit_position + 1) % f->tracks[track_id].bit_count; } @@ -418,9 +446,17 @@ _mii_disk2_lss_tick( c->data_register = (c->data_register >> 1) | (!!f->write_protected << 7); break; - case 3: // LD + case 3: {// LD + uint8_t track_id = f->track_id[f->qtrack]; c->data_register = c->write_register; - break; + f->seed_dirty++; + if (f->heat) { + uint32_t byte_index = f->bit_position >> 3; + unsigned int dstb = byte_index/MII_FLOPPY_HM_HIT_SIZE; + f->heat->write.map[track_id][dstb] = 255; + f->heat->write.seed++; + } + } break; } } else { // CLR c->data_register = 0; @@ -448,8 +484,9 @@ _mii_mish_d2( for (int i = 0; i < 2; i++) { mii_floppy_t *f = &c->floppy[i]; printf("Drive %d %s\n", f->id, f->write_protected ? "WP" : "RW"); - printf("\tMotor: %3s qtrack:%d Bit %6d\n", - f->motor ? "ON" : "OFF", f->qtrack, f->bit_position); + printf("\tMotor: %3s qtrack:%3d Bit %6d/%6d\n", + f->motor ? "ON" : "OFF", f->qtrack, + f->bit_position, f->tracks[0].bit_count); } return; } @@ -480,7 +517,7 @@ _mii_mish_d2( count = atoi(argv[3]); mii_card_disk2_t *c = _mish_d2; mii_floppy_t *f = &c->floppy[sel]; - uint8_t *data = f->tracks[track].data; + uint8_t *data = f->track_data[track]; for (int i = 0; i < count; i += 8) { uint8_t *line = data + i; @@ -503,6 +540,12 @@ _mii_mish_d2( } return; } + if (!strcmp(argv[1], "dirty")) { + mii_card_disk2_t *c = _mish_d2; + mii_floppy_t *f = &c->floppy[sel]; + f->seed_dirty = f->seed_saved = rand(); + return; + } } #include "mish.h" diff --git a/src/drivers/mii_disk2.h b/src/drivers/mii_disk2.h deleted file mode 100644 index a90d993..0000000 --- a/src/drivers/mii_disk2.h +++ /dev/null @@ -1,9 +0,0 @@ -/* - * mii_disk2.h - * - * Copyright (C) 2023 Michel Pollet - * - * SPDX-License-Identifier: MIT - */ - -#pragma once diff --git a/src/drivers/mii_epromcard.c b/src/drivers/mii_epromcard.c index 8bf663b..3c54499 100644 --- a/src/drivers/mii_epromcard.c +++ b/src/drivers/mii_epromcard.c @@ -103,14 +103,17 @@ static int _mii_ee_command( mii_t * mii, struct mii_slot_t *slot, - uint8_t cmd, + uint32_t cmd, void * param) { mii_card_ee_t *c = slot->drv_priv; + int res = -1; switch (cmd) { case MII_SLOT_DRIVE_COUNT: - if (param) + if (param) { *(int *)param = 1; + res = 0; + } break; case MII_SLOT_DRIVE_LOAD: const char *filename = param; @@ -122,9 +125,10 @@ _mii_ee_command( } mii_dd_drive_load(&c->drive[0], file); c->file = file ? file->map : (uint8_t*)mii_1mb_rom_data; + res = 0; break; } - return 0; + return res; } static mii_slot_drv_t _driver = { diff --git a/src/drivers/mii_smartport.c b/src/drivers/mii_smartport.c index e280c57..01b36fd 100644 --- a/src/drivers/mii_smartport.c +++ b/src/drivers/mii_smartport.c @@ -292,14 +292,17 @@ static int _mii_sm_command( mii_t * mii, struct mii_slot_t *slot, - uint8_t cmd, + uint32_t cmd, void * param) { mii_card_sm_t *c = slot->drv_priv; + int res = -1; switch (cmd) { case MII_SLOT_DRIVE_COUNT: - if (param) + if (param) { *(int *)param = MII_SM_DRIVE_COUNT; + res = 0; + } break; case MII_SLOT_DRIVE_LOAD ... MII_SLOT_DRIVE_LOAD + MII_SM_DRIVE_COUNT - 1: int drive = cmd - MII_SLOT_DRIVE_LOAD; @@ -311,9 +314,10 @@ _mii_sm_command( return -1; } mii_dd_drive_load(&c->drive[drive], file); + res = 0; break; } - return 0; + return res; } static uint8_t diff --git a/src/drivers/mii_ssc.c b/src/drivers/mii_ssc.c index c3e5bee..2fe5eb7 100644 --- a/src/drivers/mii_ssc.c +++ b/src/drivers/mii_ssc.c @@ -119,17 +119,19 @@ static int _mii_ssc_command( mii_t * mii, struct mii_slot_t *slot, - uint8_t cmd, + uint32_t cmd, void * param) { // mii_card_ssc_t *c = slot->drv_priv; + int res = -1; switch (cmd) { case MII_SLOT_SSC_SET_TTY: { const char * tty = param; printf("%s: set tty %s\n", __func__, tty); + res = 0; } break; } - return -1; + return res; } static mii_slot_drv_t _driver = { diff --git a/src/format/mii_floppy.c b/src/format/mii_floppy.c index 6fdae72..55afd24 100644 --- a/src/format/mii_floppy.c +++ b/src/format/mii_floppy.c @@ -25,7 +25,7 @@ mii_floppy_init( f->bit_timing = 32; f->qtrack = 15; // just to see something at seek time f->bit_position = 0; - f->tracks_dirty = 0; + f->seed_dirty = f->seed_saved = 0; f->write_protected &= ~MII_FLOPPY_WP_MANUAL;// keep the manual WP bit /* this will look like this; ie half tracks are 'random' 0: 0 1: 0 2:35 3: 1 @@ -34,9 +34,9 @@ mii_floppy_init( */ for (int i = 0; i < (int)sizeof(f->track_id); i++) f->track_id[i] = ((i + 1) % 4) == 3 ? - MII_FLOPPY_RANDOM_TRACK_ID : ((i + 2) / 4); + MII_FLOPPY_NOISE_TRACK : ((i + 2) / 4); /* generate a buffer with about 30% one bits */ - uint8_t *random = f->tracks[MII_FLOPPY_RANDOM_TRACK_ID].data; + uint8_t *random = f->track_data[MII_FLOPPY_NOISE_TRACK]; memset(random, 0, 256); uint32_t bits = 256 * 8; uint32_t ones = bits * 0.3; // 30% ones @@ -48,29 +48,39 @@ mii_floppy_init( random[bit >> 3] |= (1 << (bit & 7)); ones--; } - // we also fill up the last (ramdom) track with copies of the first - // 256 uint8_ts of itself, to make it look like a real track + // copy all that random stuff across the rest of the 'track' int rbi = 0; - for (int i = 0; i < 36; i++) { + for (int bi = 256; bi < MII_FLOPPY_DEFAULT_TRACK_SIZE; bi++) + random[bi] = random[rbi++ % 256]; + // important, the +1 means we initialize the random track too + for (int i = 0; i < MII_FLOPPY_TRACK_COUNT + 1; i++) { f->tracks[i].dirty = 0; - f->tracks[i].bit_count = MII_FLOPPY_DEFAULT_TRACK_SIZE * 8; + f->tracks[i].bit_count = 6500 * 8; // fill the whole array up to the end.. - for (int bi = 0; bi < (int)sizeof(f->tracks[i].data); bi++) - f->tracks[i].data[bi] = random[rbi++ % 256]; + uint8_t *track = f->track_data[i]; + if (i != MII_FLOPPY_NOISE_TRACK) { +#if 1 + memset(track, 0, MII_FLOPPY_DEFAULT_TRACK_SIZE); +#else + for (int bi = 0; bi < MII_FLOPPY_DEFAULT_TRACK_SIZE; bi++) + track[bi] = random[rbi++ % 256]; +#endif + } } } static void mii_track_write_bits( mii_floppy_track_t * dst, + uint8_t * track_data, uint8_t bits, uint8_t count ) { while (count--) { - uint32_t uint8_t_index = dst->bit_count >> 3; + uint32_t byte_index = dst->bit_count >> 3; uint8_t bit_index = 7 - (dst->bit_count & 7); - dst->data[uint8_t_index] &= ~(1 << bit_index); - dst->data[uint8_t_index] |= (!!(bits >> 7) << bit_index); + track_data[byte_index] &= ~(1 << bit_index); + track_data[byte_index] |= (!!(bits >> 7) << bit_index); dst->bit_count++; bits <<= 1; } @@ -91,8 +101,9 @@ static uint8_t _de44(uint8_t a, uint8_t b) { static void mii_nib_rebit_track( - uint8_t *track, - mii_floppy_track_t * dst) + uint8_t *src_track, + mii_floppy_track_t * dst, + uint8_t * dst_track) { dst->bit_count = 0; uint32_t window = 0; @@ -100,18 +111,18 @@ mii_nib_rebit_track( int seccount = 0; int state = 0; // look for address field do { - window = (window << 8) | track[srci++]; + window = (window << 8) | src_track[srci++]; switch (state) { case 0: { if (window != 0xffd5aa96) break; for (int i = 0; i < (seccount == 0 ? 40 : 20); i++) - mii_track_write_bits(dst, 0xff, 10); - uint8_t * h = track + srci - 4; // incs first 0xff - int tid = _de44(h[6], h[7]); - int sid = _de44(h[8], h[9]); - printf("Track %2d sector %2d\n", tid, sid); - memcpy(dst->data + (dst->bit_count >> 3), track + srci - 4, 15); + mii_track_write_bits(dst, dst_track, 0xff, 10); + uint8_t * h = src_track + srci - 4; // incs first 0xff + // int tid = _de44(h[6], h[7]); + // int sid = _de44(h[8], h[9]); + // printf("Track %2d sector %2d\n", tid, sid); + memcpy(dst_track + (dst->bit_count >> 3), h, 15); dst->bit_count += 15 * 8; srci += 11; state = 1; @@ -120,9 +131,9 @@ mii_nib_rebit_track( if (window != 0xffd5aaad) break; for (int i = 0; i < 4; i++) - mii_track_write_bits(dst, 0xff, 10); - uint8_t *h = track + srci - 4; - memcpy(dst->data + (dst->bit_count >> 3), h, 4 + 342 + 4); + mii_track_write_bits(dst, dst_track, 0xff, 10); + uint8_t *h = src_track + srci - 4; + memcpy(dst_track + (dst->bit_count >> 3), h, 4 + 342 + 4); dst->bit_count += (4 + 342 + 4) * 8; srci += 4 + 342; seccount++; @@ -141,7 +152,7 @@ mii_floppy_load_nib( printf("%s: loading NIB %s\n", __func__, filename); for (int i = 0; i < 35; i++) { uint8_t *track = file->map + (i * 6656); - mii_nib_rebit_track(track, &f->tracks[i]); + mii_nib_rebit_track(track, &f->tracks[i], f->track_data[i]); if (f->tracks[i].bit_count < 100) { printf("%s: %s: Invalid track %d has zero bits!\n", __func__, filename, i); @@ -185,7 +196,8 @@ mii_floppy_write_track_woz( trks->track[track_id].bit_count_le = htole32(f->tracks[track_id].bit_count); uint32_t byte_count = (le32toh(trks->track[track_id].bit_count_le) + 7) >> 3; - memcpy(trks->track[track_id].bits, f->tracks[track_id].data, byte_count); + memcpy(trks->track[track_id].bits, + f->track_data[track_id], byte_count); trks->track[track_id].byte_count_le = htole16(byte_count); } else { mii_woz2_info_t *info = (mii_woz2_info_t *)(header + 1); @@ -199,7 +211,7 @@ mii_floppy_write_track_woz( trks->track[track_id].bit_count_le = htole32(f->tracks[track_id].bit_count); uint32_t byte_count = (le32toh(trks->track[track_id].bit_count_le) + 7) >> 3; - memcpy(track, f->tracks[track_id].data, byte_count); + memcpy(track, f->track_data[track_id], byte_count); } f->tracks[track_id].dirty = 0; return 0; @@ -213,12 +225,10 @@ mii_floppy_woz_load_tmap( uint64_t used_tracks = 0; int tmap_size = le32toh(tmap->chunk.size_le); for (int ti = 0; ti < (int)sizeof(f->track_id) && ti < tmap_size; ti++) { - if (tmap->track_id[ti] == 0xff) { - f->track_id[ti] = MII_FLOPPY_RANDOM_TRACK_ID; - continue; - } - f->track_id[ti] = tmap->track_id[ti]; - used_tracks |= 1L << f->track_id[ti]; + f->track_id[ti] = tmap->track_id[ti] == 0xff ? + MII_FLOPPY_NOISE_TRACK : tmap->track_id[ti]; + if (tmap->track_id[ti] != 0xff) + used_tracks |= 1L << f->track_id[ti]; } return used_tracks; } @@ -259,13 +269,13 @@ mii_floppy_load_woz( (char*)&trks->chunk.id_le, le32toh(trks->chunk.size_le)); #endif int max_track = le32toh(trks->chunk.size_le) / sizeof(trks->track[0]); - for (int i = 0; i < 35 && i < max_track; i++) { + for (int i = 0; i < MII_FLOPPY_TRACK_COUNT && i < max_track; i++) { uint8_t *track = trks->track[i].bits; if (!(used_tracks & (1L << i))) { // printf("WOZ: Track %d not used\n", i); continue; } - memcpy(f->tracks[i].data, track, le16toh(trks->track[i].byte_count_le)); + memcpy(f->track_data[i], track, le16toh(trks->track[i].byte_count_le)); f->tracks[i].bit_count = le32toh(trks->track[i].bit_count_le); } } else { @@ -289,7 +299,7 @@ mii_floppy_load_woz( #endif /* TODO: this doesn't work yet... */ // f->bit_timing = info->optimal_bit_timing; - for (int i = 0; i < 35; i++) { + for (int i = 0; i < MII_FLOPPY_TRACK_COUNT; i++) { if (!(used_tracks & (1L << i))) { // printf("WOZ: Track %d not used\n", i); continue; @@ -297,7 +307,7 @@ mii_floppy_load_woz( uint8_t *track = file->map + (le16toh(trks->track[i].start_block_le) << 9); uint32_t byte_count = (le32toh(trks->track[i].bit_count_le) + 7) >> 3; - memcpy(f->tracks[i].data, track, byte_count); + memcpy(f->track_data[i], track, byte_count); f->tracks[i].bit_count = le32toh(trks->track[i].bit_count_le); } } @@ -434,7 +444,7 @@ mii_floppy_load_dsk( mii_floppy_nibblize_sector(VOLUME_NUMBER, i, phys_sector, &writePtr, track); } - mii_nib_rebit_track(nibbleBuf, &f->tracks[i]); + mii_nib_rebit_track(nibbleBuf, &f->tracks[i], f->track_data[i]); } free(nibbleBuf); // DSK is read only @@ -452,23 +462,23 @@ mii_floppy_update_tracks( return -1; if (f->write_protected & MII_FLOPPY_WP_RO_FILE) return -1; - if (!f->tracks_dirty) + if (f->seed_dirty == f->seed_saved) return 0; - for (int i = 0; i < 35; i++) { + for (int i = 0; i < MII_FLOPPY_TRACK_COUNT; i++) { if (!f->tracks[i].dirty) continue; - printf("%s: track %d is dirty, saving\n", __func__, i); +// printf("%s: track %d is dirty, saving\n", __func__, i); switch (file->format) { case MII_DD_FILE_NIB: break; case MII_DD_FILE_WOZ: mii_floppy_write_track_woz(f, file, i); - printf("%s: WOZ track %d updated\n", __func__, i); +// printf("%s: WOZ track %d updated\n", __func__, i); break; } f->tracks[i].dirty = 0; } - f->tracks_dirty = 0; + f->seed_saved = f->seed_dirty; return 0; } @@ -498,5 +508,6 @@ mii_floppy_load( f->write_protected |= MII_FLOPPY_WP_RO_FILE; else f->write_protected &= ~MII_FLOPPY_WP_RO_FILE; + f->seed_dirty = f->seed_saved = rand(); return res; } diff --git a/src/format/mii_floppy.h b/src/format/mii_floppy.h index 1220248..7e35f6f 100644 --- a/src/format/mii_floppy.h +++ b/src/format/mii_floppy.h @@ -13,9 +13,7 @@ // for NIB and others. can be bigger on .WOZ #define MII_FLOPPY_DEFAULT_TRACK_SIZE 6656 -// track containing random bits -#define MII_FLOPPY_RANDOM_TRACK_ID 35 - +#define MII_FLOPPY_TRACK_COUNT 35 /* * Reasons for write protect. Ie checkbox in the UI, or file format * doesn't support writes, or the file has no write permissions. @@ -29,9 +27,31 @@ enum { typedef struct mii_floppy_track_t { uint8_t dirty : 1; // track has been written to uint32_t bit_count; - uint8_t data[6680]; // max suggested by WOZ spec } mii_floppy_track_t; + +// 32 bytes of track data corresponds to one byte of heatmap +#define MII_FLOPPY_HM_HIT_SIZE 32 +// thats 208 bytes per track or about 7KB*2 for the whole disk for read+write +// we align it on 16 bytes to make it easier to use in a shader +#define MII_FLOPPY_HM_TRACK_SIZE \ + (((MII_FLOPPY_DEFAULT_TRACK_SIZE / MII_FLOPPY_HM_HIT_SIZE) + 15) & ~15) + +typedef struct mii_track_heatmap_t { + // 32 bytes of track data corresponds to one byte of heatmap + uint32_t seed, tex, cleared; + // this needs to be aligned, otherwise SSE code will die horribly + uint8_t map[MII_FLOPPY_TRACK_COUNT][MII_FLOPPY_HM_TRACK_SIZE] + __attribute__((aligned(16))); +} mii_track_heatmap_t; + +typedef struct mii_floppy_heatmap_t { + mii_track_heatmap_t read, write; +} mii_floppy_heatmap_t; + +// +#define MII_FLOPPY_NOISE_TRACK MII_FLOPPY_TRACK_COUNT + typedef struct mii_floppy_t { uint8_t write_protected : 3, id : 2; uint8_t bit_timing; // 0=32 (default) @@ -39,9 +59,16 @@ typedef struct mii_floppy_t { uint8_t stepper; // last step we did... uint8_t qtrack; // quarter track we are on uint32_t bit_position; - uint8_t tracks_dirty; // needs saving - uint8_t track_id[35 * 4]; - mii_floppy_track_t tracks[36]; + // this is incremented each time a track is marked dirty + uint32_t seed_dirty; + uint32_t seed_saved; // last seed we saved at + uint8_t track_id[MII_FLOPPY_TRACK_COUNT * 4]; + mii_floppy_track_t tracks[MII_FLOPPY_TRACK_COUNT + 1]; + // keep all the data together, we'll use it to make a texture + // the last track is used for noise + uint8_t track_data[MII_FLOPPY_TRACK_COUNT + 1][MII_FLOPPY_DEFAULT_TRACK_SIZE]; + /* This is set by the UI to trakc the head movements, no functional use */ + mii_floppy_heatmap_t * heat; // optional heatmap } mii_floppy_t; /* @@ -54,8 +81,8 @@ mii_floppy_init( int mii_floppy_load( - mii_floppy_t *f, - mii_dd_file_t *file ); + mii_floppy_t *f, + mii_dd_file_t *file ); int mii_floppy_update_tracks( diff --git a/src/mii_mish.c b/src/mii_mish.c index 9909a5e..8b0053d 100644 --- a/src/mii_mish.c +++ b/src/mii_mish.c @@ -51,7 +51,8 @@ _mii_mish_cmd( int argc, const char * argv[]) { - const char * state[] = { "RUNNING", "STOPPED", "STEP" }; + const char * state[] = { + "INIT", "RUNNING", "STOPPED", "STEP", "TERMINATE"}; mii_t * mii = param; if (!argv[1]) { show_state: diff --git a/src/mii_slot.h b/src/mii_slot.h index 58732b1..9afc798 100644 --- a/src/mii_slot.h +++ b/src/mii_slot.h @@ -53,7 +53,7 @@ typedef struct mii_slot_drv_t { int (*command)( mii_t * mii, struct mii_slot_t *slot, - uint8_t cmd, + uint32_t cmd, void * param); } mii_slot_drv_t; @@ -80,6 +80,8 @@ enum { MII_SLOT_DRIVE_WP = 0x30, // + drive index 0...n MII_SLOT_SSC_SET_TTY = 0x10, // param is a pathname, or NULL for a pty + // + drive index 0..1. Param is a mii_floppy_t ** + MII_SLOT_D2_GET_FLOPPY = 0x40, }; // send a command to a slot/driver. Return >=0 if ok, -1 if error diff --git a/ui_gl/mii_emu_gl.c b/ui_gl/mii_emu_gl.c index be426af..9e438a5 100644 --- a/ui_gl/mii_emu_gl.c +++ b/ui_gl/mii_emu_gl.c @@ -5,6 +5,9 @@ * * SPDX-License-Identifier: MIT */ +/* + * This is the main file for the X11/GLX version of the MII emulator + */ #define _GNU_SOURCE // for asprintf #include #include @@ -12,7 +15,6 @@ #include #include #include -#include #include #include #include @@ -24,69 +26,38 @@ #include "mish.h" #include "mii_thread.h" -#include "mii_mui.h" -#include "minipt.h" +#include "mii_mui_gl.h" #include "miigl_counter.h" #define MII_ICON64_DEFINE #include "mii-icon-64.h" /* - * Note: This *assumes* that the GL implementation has support for non-power-of-2 - * textures, which is not a given for older implementations. However, I think - * (by 2024) that's a safe assumption. + * Note: This *assumes* that the GL implementation has support for + * non-power-of-2 * textures, which is not a given for older + * implementations. However, I think (by 2024) that's a safe assumption. */ #define WINDOW_WIDTH 1280 #define WINDOW_HEIGHT 720 -#define POWER_OF_TWO 0 - -typedef struct mii_gl_tex_t { -// c2_rect_t frame; - GLuint id; -} mii_gl_tex_t; +#define MII_MUI_GL_POW2 0 typedef struct mii_x11_t { mii_mui_t video; pthread_t cpu_thread; - mui_drawable_t dr; // drawable - uint32_t dr_padded_y; - - union { - struct { - mii_gl_tex_t mii_tex, mui_tex; - }; - mii_gl_tex_t tex[2]; - }; - - c2_rect_t video_frame; // current video frame - float mui_alpha; - void * transision_state; - struct { - mui_time_t start, end; - c2_rect_t from, to; - } transition; - Cursor cursor; Display * dpy; Window win; - long last_button_click; - struct { - int ungrab, grab, grabbed, down; - c2_pt_t pos; - } mouse; - mui_event_t key; XVisualInfo * vis; Colormap cmap; XSetWindowAttributes swa; - XWindowAttributes attr; GLXFBConfig fbc; Atom wm_delete_window; int width, height; GLXContext glContext; - miigl_counter_t videoc, redrawc, sleepc; +// miigl_counter_t videoc, redrawc, sleepc; } mii_x11_t; @@ -120,97 +91,6 @@ has_gl_extension( return false; } -c2_rect_t -c2_rect_interpolate( - c2_rect_t *a, - c2_rect_t *b, - float t) -{ - c2_rect_t r = {}; - r.l = 0.5 + a->l + (b->l - a->l) * t; - r.r = 0.5 + a->r + (b->r - a->r) * t; - r.t = 0.5 + a->t + (b->t - a->t) * t; - r.b = 0.5 + a->b + (b->b - a->b) * t; - return r; -} - -static c2_rect_t -_mii_get_video_position( - mii_x11_t * ui, - bool ui_visible ) -{ - c2_rect_t r = C2_RECT(0, 0, MII_VIDEO_WIDTH, MII_VIDEO_HEIGHT); - if (ui_visible) { - float fac = (ui->attr.height - 38) / (float)MII_VIDEO_HEIGHT; - c2_rect_scale(&r, fac); - c2_rect_offset(&r, - (ui->attr.width / 2) - (c2_rect_width(&r) / 2), 36); - } else { - float fac = (ui->attr.height) / (float)MII_VIDEO_HEIGHT; - c2_rect_scale(&r, fac); - c2_rect_offset(&r, - (ui->attr.width / 2) - (c2_rect_width(&r) / 2), - (ui->attr.height / 2) - (c2_rect_height(&r) / 2)); - c2_rect_inset(&r, 10, 10); - } - return r; -} - -static void -_mii_transition( - mii_x11_t * ui ) -{ - pt_start(ui->transision_state); - - while (ui->video.transition == MII_MUI_TRANSITION_NONE) - pt_yield(ui->transision_state); - - ui->transition.start = mui_get_time(); - ui->transition.end = ui->transition.start + (MUI_TIME_SECOND / 2); - ui->transition.from = ui->video_frame; - - switch (ui->video.transition) { - case MII_MUI_TRANSITION_HIDE_UI: - ui->transition.to = _mii_get_video_position(ui, false); - ui->video.mui_visible = true; - break; - case MII_MUI_TRANSITION_SHOW_UI: - ui->transition.to = _mii_get_video_position(ui, true); - ui->video.mui_visible = true; - break; - } - while (1) { - mui_time_t now = mui_get_time(); - float t = (now - ui->transition.start) / - (float)(ui->transition.end - ui->transition.start); - if (t >= 1.0f) - break; - switch (ui->video.transition) { - case MII_MUI_TRANSITION_HIDE_UI: - ui->mui_alpha = 1.0f - t; - break; - case MII_MUI_TRANSITION_SHOW_UI: - ui->mui_alpha = t; - break; - } - ui->video_frame = c2_rect_interpolate( - &ui->transition.from, &ui->transition.to, t); - pt_yield(ui->transision_state); - } - switch (ui->video.transition) { - case MII_MUI_TRANSITION_HIDE_UI: - ui->video.mui_visible = false; - ui->mui_alpha = 0.0f; - break; - case MII_MUI_TRANSITION_SHOW_UI: - ui->mui_alpha = 1.0f; - break; - } - ui->video.transition = MII_MUI_TRANSITION_NONE; - - pt_end(ui->transision_state); -} - /* * xmodmap -pke or -pk will print the list of keycodes */ @@ -244,6 +124,7 @@ _mii_x11_convert_keycode( case XK_Alt_R: out->key.key = MUI_KEY_RALT; break; case XK_Super_L: out->key.key = MUI_KEY_LSUPER; break; case XK_Super_R: out->key.key = MUI_KEY_RSUPER; break; + case XK_Caps_Lock: out->key.key = MUI_KEY_CAPSLOCK; break; default: out->key.key = sym & 0xff; break; @@ -256,7 +137,7 @@ static int mii_x11_init( struct mii_x11_t *ui ) { - mui_t * mui = &ui->video.mui; +// mui_t * mui = &ui->video.mui; if (!setlocale(LC_ALL,"") || !XSupportsLocale() || @@ -355,6 +236,18 @@ mii_x11_init( sizeof(mii_icon64) / sizeof(mii_icon64[0])); XFlush(ui->dpy); } + { + XSizeHints *hints = XAllocSizeHints(); + hints->flags = PMinSize | PAspect;// | PMaxSize; + hints->min_width = WINDOW_WIDTH / 2; + hints->min_height = WINDOW_HEIGHT / 2; + hints->max_aspect.x = WINDOW_WIDTH; + hints->max_aspect.y = WINDOW_HEIGHT; + hints->min_aspect.x = WINDOW_WIDTH; + hints->min_aspect.y = WINDOW_HEIGHT; + XSetWMNormalHints(ui->dpy, ui->win, hints); + XFree(hints); + } XMapWindow(ui->dpy, ui->win); ui->wm_delete_window = XInternAtom(ui->dpy, "WM_DELETE_WINDOW", False); XSetWMProtocols(ui->dpy, ui->win, &ui->wm_delete_window, 1); @@ -401,92 +294,15 @@ mii_x11_init( ui->glContext = create_context(ui->dpy, ui->fbc, 0, True, attr); } } - XSync(ui->dpy, False); XSetErrorHandler(old_handler); if (gl_err || !ui->glContext) die("[X11]: Failed to create an OpenGL context\n"); glXMakeCurrent(ui->dpy, ui->win, ui->glContext); } - { // create the MUI 'screen' at the window size - mui_pixmap_t* pix = &ui->dr.pix; - pix->size.y = WINDOW_HEIGHT; - pix->size.x = WINDOW_WIDTH; - // annoyingly I have to make it a LOT bigger to handle that the - // non-power-of-2 texture extension is not avialable everywhere - // textures, which is a bit of a waste of memory, but oh well. - -#if POWER_OF_TWO - int padded_x = 1; - int padded_y = 1; - while (padded_x < pix->size.x) - padded_x <<= 1; - while (padded_y < pix->size.y) - padded_y <<= 1; -#else - int padded_x = pix->size.x; - int padded_y = pix->size.y; -#endif - pix->row_bytes = padded_x * 4; - pix->bpp = 32; - - ui->dr_padded_y = padded_y; - printf("MUI Padded UI size is %dx%d\n", padded_x, padded_y); - - pix->pixels = malloc(pix->row_bytes * ui->dr_padded_y); - mui->screen_size = pix->size; - } - { - XGetWindowAttributes(ui->dpy, ui->win, &ui->attr); - ui->mui_alpha = 1.0f; - ui->video.mui_visible = true; - ui->video_frame = _mii_get_video_position(ui, ui->video.mui_visible); - } return 0; } -static void -mii_x11_update_mouse_card( - mii_x11_t * ui) -{ - mii_t * mii = &ui->video.mii; - mui_t * mui = &ui->video.mui; - /* - * We can grab the mouse if it is enabled by the driver, it is in the - * video frame, and there is no active MUI windows (or menus). - */ - if (mii->mouse.enabled && - c2_rect_contains_pt(&ui->video_frame, &ui->mouse.pos) && - !(ui->video.mui_visible && mui_has_active_windows(mui))) { - if (!ui->mouse.grabbed) { - ui->mouse.grab = 1; - ui->mouse.grabbed = 1; - // printf("Grab mouse\n"); - } - } else { - if (ui->mouse.grabbed) { - ui->mouse.ungrab = 1; - ui->mouse.grabbed = 0; - // printf("Ungrab mouse\n"); - } - } - if (!ui->mouse.grabbed) - return; - double x = ui->mouse.pos.x - ui->video_frame.l; - double y = ui->mouse.pos.y - ui->video_frame.t; - // get mouse button state - int button = ui->mouse.down; - // clamp coordinates inside bounds - double vw = c2_rect_width(&ui->video_frame); - double vh = c2_rect_height(&ui->video_frame); - double mw = mii->mouse.max_x - mii->mouse.min_x; - double mh = mii->mouse.max_y - mii->mouse.min_y; - // normalize mouse coordinates - mii->mouse.x = mii->mouse.min_x + (x * mw / vw) + 0.5; - mii->mouse.y = mii->mouse.min_y + (y * mh / vh) + 0.5; - mii->mouse.button = button; -} - static int mii_x11_handle_event( mii_x11_t * ui, @@ -496,15 +312,16 @@ mii_x11_handle_event( /* We don't actually 'grab' as in warp the pointer, we just show/hide it * dynamically when we enter/leave the video rectangle */ - if (ui->mouse.grab) { + if (ui->video.mouse.grab) { XDefineCursor(ui->dpy, ui->win, ui->cursor); - ui->mouse.grab = 0; - } else if (ui->mouse.ungrab) { + ui->video.mouse.grab = 0; + } else if (ui->video.mouse.ungrab) { XUndefineCursor(ui->dpy, ui->win); - ui->mouse.ungrab = 0; + ui->video.mouse.ungrab = 0; } mui_t * mui = &ui->video.mui; mii_t * mii = &ui->video.mii; + switch (evt->type) { case FocusIn: case FocusOut: @@ -517,28 +334,31 @@ mii_x11_handle_event( case KeyRelease: case KeyPress: { int ret, down = (evt->type == KeyPress); - KeySym *code = XGetKeyboardMapping(ui->dpy, ( - KeyCode)evt->xkey.keycode, 1, &ret); - ui->key.type = down ? MUI_EVENT_KEYDOWN : MUI_EVENT_KEYUP; - ui->key.key.up = 0; + KeySym *code = XGetKeyboardMapping(ui->dpy, + (KeyCode)evt->xkey.keycode, 1, &ret); + ui->video.key.type = down ? MUI_EVENT_KEYDOWN : MUI_EVENT_KEYUP; + ui->video.key.key.up = 0; bool handled = false; - bool converted = _mii_x11_convert_keycode(ui, *code, &ui->key); - bool is_modifier = ui->key.key.key >= MUI_KEY_MODIFIERS && - ui->key.key.key <= MUI_KEY_MODIFIERS_LAST; + bool converted = _mii_x11_convert_keycode(ui, *code, + &ui->video.key); + bool is_modifier = ui->video.key.key.key >= MUI_KEY_MODIFIERS && + ui->video.key.key.key <= MUI_KEY_MODIFIERS_LAST; if (converted) { // convert keycodes into a bitfields of current modifiers - if (ui->key.key.key >= MUI_KEY_MODIFIERS && - ui->key.key.key <= MUI_KEY_MODIFIERS_LAST) { + if (ui->video.key.key.key >= MUI_KEY_MODIFIERS && + ui->video.key.key.key <= MUI_KEY_MODIFIERS_LAST) { if (down) - mui->modifier_keys |= (1 << (ui->key.key.key - MUI_KEY_MODIFIERS)); + mui->modifier_keys |= (1 << + (ui->video.key.key.key - MUI_KEY_MODIFIERS)); else - mui->modifier_keys &= ~(1 << (ui->key.key.key - MUI_KEY_MODIFIERS)); + mui->modifier_keys &= ~(1 << + (ui->video.key.key.key - MUI_KEY_MODIFIERS)); } - ui->key.modifiers = mui->modifier_keys; - switch (ui->key.key.key) { + ui->video.key.modifiers = mui->modifier_keys; + switch (ui->video.key.key.key) { case MUI_KEY_RSUPER: case MUI_KEY_LSUPER: { - int apple = ui->key.key.key - MUI_KEY_RSUPER; + int apple = ui->video.key.key.key - MUI_KEY_RSUPER; mii_bank_t *bank = &mii->bank[MII_BANK_MAIN]; uint8_t old = mii_bank_peek(bank, 0xc061 + apple); mii_bank_poke(bank, 0xc061 + apple, down ? 0x80 : 0); @@ -548,7 +368,7 @@ mii_x11_handle_event( } } break; } - handled = mui_handle_event(mui, &ui->key); + handled = mui_handle_event(mui, &ui->video.key); // if not handled and theres a window visible, assume // it's a dialog and it OUGHT to eat the key if (!handled) @@ -556,7 +376,7 @@ mii_x11_handle_event( // printf("%s key handled %d\n", __func__, handled); } if (!handled && down && !is_modifier) { - uint16_t mii_key = ui->key.key.key; + uint16_t mii_key = ui->video.key.key.key; char buf[32] = ""; KeySym keysym = 0; if (XLookupString((XKeyEvent*)evt, buf, 32, &keysym, NULL) != NoSymbol) { @@ -583,23 +403,23 @@ mii_x11_handle_event( case ButtonRelease: { // printf("%s %s button %d grabbed:%d\n", __func__, // evt->type == ButtonPress ? "Down":"Up ", - // evt->xbutton.button, ui->mouse.grabbed); + // evt->xbutton.button, ui->video.mouse.grabbed); switch (evt->xbutton.button) { case Button1: { - ui->mouse.down = evt->type == ButtonPress; - ui->mouse.pos.x = evt->xbutton.x; - ui->mouse.pos.y = evt->xbutton.y; + ui->video.mouse.down = evt->type == ButtonPress; + ui->video.mouse.pos.x = evt->xbutton.x; + ui->video.mouse.pos.y = evt->xbutton.y; if (ui->video.mui_visible) { mui_event_t ev = { - .type = ui->mouse.down ? + .type = ui->video.mouse.down ? MUI_EVENT_BUTTONDOWN : MUI_EVENT_BUTTONUP, - .mouse.where = ui->mouse.pos, + .mouse.where = ui->video.mouse.pos, .modifiers = mui->modifier_keys, // | MUI_MODIFIER_EVENT_TRACE, }; mui_handle_event(mui, &ev); } - mii_x11_update_mouse_card(ui); + mii_mui_update_mouse_card(&ui->video); } break; case Button4: case Button5: { @@ -609,7 +429,7 @@ mii_x11_handle_event( mui_event_t ev = { .type = MUI_EVENT_WHEEL, .modifiers = mui->modifier_keys,// | MUI_MODIFIER_EVENT_TRACE, - .wheel.where = ui->mouse.pos, + .wheel.where = ui->video.mouse.pos, .wheel.delta = evt->xbutton.button == Button4 ? -1 : 1, }; mui_handle_event(mui, &ev); @@ -617,16 +437,27 @@ mii_x11_handle_event( } break; } } break; + case ConfigureNotify: + if (evt->xconfigure.width != ui->video.window_size.x || + evt->xconfigure.height != ui->video.window_size.y) { + // Window is being resized + // Handle the resize event here + ui->video.window_size.x = evt->xconfigure.width; + ui->video.window_size.y = evt->xconfigure.height; + ui->video.video_frame = mii_mui_get_video_position(&ui->video); + mii_mui_update_mouse_card(&ui->video); + } + break; case MotionNotify: { - ui->mouse.pos.x = evt->xmotion.x; - ui->mouse.pos.y = evt->xmotion.y; - mii_x11_update_mouse_card(ui); - if (ui->mouse.grabbed) + ui->video.mouse.pos.x = evt->xmotion.x; + ui->video.mouse.pos.y = evt->xmotion.y; + mii_mui_update_mouse_card(&ui->video); + if (ui->video.mouse.grabbed) break; if (ui->video.mui_visible) { mui_event_t ev = { .type = MUI_EVENT_DRAG, - .mouse.where = ui->mouse.pos, + .mouse.where = ui->video.mouse.pos, .modifiers = mui->modifier_keys, }; mui_handle_event(mui, &ev); @@ -655,133 +486,6 @@ mii_x11_terminate( XCloseDisplay(ui->dpy); } -void -mii_x11_prepare_textures( - mii_x11_t *ui) -{ - mii_t * mii = &ui->video.mii; -// mui_t * mui = &ui->video.mui; - GLuint tex[2]; - glGenTextures(2, tex); - for (int i = 0; i < 2; i++) { - mii_gl_tex_t * t = &ui->tex[i]; - memset(t, 0, sizeof(*t)); - t->id = tex[i]; - } - glEnable(GL_TEXTURE_2D); - // bind the mii texture using the GL_ARB_texture_rectangle extension - glBindTexture(GL_TEXTURE_2D, ui->mii_tex.id); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - // disable the repeat of textures - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - - glTexImage2D(GL_TEXTURE_2D, 0, 4, - MII_VRAM_WIDTH, - MII_VRAM_HEIGHT, 0, GL_BGRA, // note BGRA here, not RGBA - GL_UNSIGNED_BYTE, - mii->video.pixels); - // bind the mui texture using the GL_ARB_texture_rectangle as well - glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, ui->mui_tex.id); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - - glTexImage2D(GL_TEXTURE_2D, 0, 4, - ui->dr.pix.row_bytes / 4, // already power of two. - ui->dr_padded_y, 0, GL_RGBA, - GL_UNSIGNED_BYTE, - ui->dr.pix.pixels); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - -// printf("%s texture created %d\n", __func__, mii_apple_screen_tex); -// display opengl error - GLenum err = glGetError(); - if (err != GL_NO_ERROR) { - printf("Error creating texture: %d\n", err); - } -} - -void -mii_x11_render( - mii_x11_t *ui) -{ - glClearColor( - .6f * ui->mui_alpha, - .6f * ui->mui_alpha, - .6f * ui->mui_alpha, - 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - glPushAttrib(GL_ENABLE_BIT|GL_COLOR_BUFFER_BIT|GL_TRANSFORM_BIT); - glDisable(GL_CULL_FACE); - glDisable(GL_DEPTH_TEST); - glDisable(GL_SCISSOR_TEST); - glEnable(GL_BLEND); - glEnable(GL_TEXTURE_2D); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - /* setup viewport/project */ - glViewport(0, 0, (GLsizei)ui->attr.width, (GLsizei)ui->attr.height); - glMatrixMode(GL_PROJECTION); - glPushMatrix(); - glLoadIdentity(); - glOrtho(0.0f, ui->attr.width, ui->attr.height, 0.0f, -1.0f, 1.0f); - glMatrixMode(GL_MODELVIEW); - glPushMatrix(); - glLoadIdentity(); - // This (was) the recommended way to handle pixel alignment in glOrtho - // mode, but this seems to have changed -- now it looks like Linear filtering -// glTranslatef(0.375f, 0.375f, 0.0f); - { - /* draw mii texture */ - glColor3f(1.0f, 1.0f, 1.0f); - glBindTexture(GL_TEXTURE_2D, ui->mii_tex.id); - glBegin(GL_QUADS); - c2_rect_t r = ui->video_frame; - glTexCoord2f(0, 0); - glVertex2f(r.l, r.t); - glTexCoord2f(MII_VIDEO_WIDTH / (double)MII_VRAM_WIDTH, 0); - glVertex2f(r.r, r.t); - glTexCoord2f(MII_VIDEO_WIDTH / (double)MII_VRAM_WIDTH, - MII_VIDEO_HEIGHT / (double)MII_VRAM_HEIGHT); - glVertex2f(r.r, r.b); - glTexCoord2f(0, - MII_VIDEO_HEIGHT / (double)MII_VRAM_HEIGHT); - glVertex2f(r.l, r.b); - glEnd(); - /* draw mui texture */ - if (ui->mui_alpha > 0.0f) { - glColor4f(1.0f, 1.0f, 1.0f, ui->mui_alpha); - glBindTexture(GL_TEXTURE_2D, ui->mui_tex.id); - glBegin(GL_QUADS); - glTexCoord2f(0, 0); glVertex2f(0, 0); - glTexCoord2f(ui->attr.width / (double)(ui->dr.pix.row_bytes / 4), 0); - glVertex2f(ui->attr.width, 0); - glTexCoord2f(ui->attr.width / (double)(ui->dr.pix.row_bytes / 4), - ui->attr.height / (double)(ui->dr_padded_y)); - glVertex2f(ui->attr.width, ui->attr.height); - glTexCoord2f(0, - ui->attr.height / (double)(ui->dr_padded_y)); - glVertex2f(0, ui->attr.height); - glEnd(); - } - } - glDisable(GL_CULL_FACE); - glDisable(GL_DEPTH_TEST); - glDisable(GL_SCISSOR_TEST); - glDisable(GL_BLEND); - glDisable(GL_TEXTURE_2D); - - glBindTexture(GL_TEXTURE_2D, 0); - glMatrixMode(GL_MODELVIEW); - glPopMatrix(); - glMatrixMode(GL_PROJECTION); - glPopMatrix(); - glPopAttrib(); -} - // TODO factor this into a single table, this is dupped from mii_mui_settings.c! static const struct { const char * name; @@ -885,6 +589,7 @@ mii_x11_reload_config( _mii_ui_load_config(mii, config, &flags); mii_prepare(mii, flags); mii_reset(mii, true); + mii_mui_gl_prepare_textures(&ui->video); /* start the CPU/emulator thread */ ui->cpu_thread = mii_threads_start(mii); @@ -902,7 +607,8 @@ main( mkdir(conf_path, 0755); mii_x11_t * ui = &g_mii; - mii_t * mii = &g_mii.video.mii; + mii_t * mii = &g_mii.video.mii; + mui_t * mui = &g_mii.video.mui; bool no_config_found = false; if (mii_settings_load( @@ -940,17 +646,11 @@ main( printf("MISH_TELNET_PORT = %s\n", getenv("MISH_TELNET_PORT")); } mii_x11_init(ui); - mui_t * mui = &ui->video.mui; // to move to a function later - mui_init(mui); - mui->color.clear.value = 0; + mii_mui_init(&ui->video, C2_PT(WINDOW_WIDTH, WINDOW_HEIGHT)); + mii_mui_gl_init(&ui->video); + asprintf(&mui->pref_directory, "%s/.local/share/mii", getenv("HOME")); - mii_mui_menus_init((mii_mui_t*)ui); - ui->video.mui_visible = 1; - mii_mui_menu_slot_menu_update(&ui->video); - - mii_x11_prepare_textures(ui); - // use a 60fps timerfd here as well int timerfd = timerfd_create(CLOCK_MONOTONIC, 0); if (timerfd < 0) { @@ -980,53 +680,13 @@ main( continue; mii_x11_handle_event(ui, &evt); } - mui_run(mui); - bool draw = false; - if (pixman_region32_not_empty(&mui->inval)) { - draw = true; - mui_draw(mui, &ui->dr, 0); - glBindTexture(GL_TEXTURE_2D, ui->mui_tex.id); - - pixman_region32_intersect_rect(&mui->redraw, &mui->redraw, - 0, 0, ui->dr.pix.size.x, ui->dr.pix.size.y); - int rc = 0; - c2_rect_t *ra = (c2_rect_t*)pixman_region32_rectangles(&mui->redraw, &rc); - // rc = 1; ra = &C2_RECT(0, 0, mui->screen_size.x, mui->screen_size.y); - if (rc) { - // printf("GL: %d rects to redraw\n", rc); - for (int i = 0; i < rc; i++) { - c2_rect_t r = ra[i]; - // printf("GL: %d,%d %dx%d\n", r.l, r.t, c2_rect_width(&r), c2_rect_height(&r)); - glPixelStorei(GL_UNPACK_ROW_LENGTH, ui->dr.pix.row_bytes / 4); - glTexSubImage2D(GL_TEXTURE_2D, 0, r.l, r.t, - c2_rect_width(&r), c2_rect_height(&r), - GL_BGRA, GL_UNSIGNED_BYTE, - ui->dr.pix.pixels + (r.t * ui->dr.pix.row_bytes) + (r.l * 4)); - } - } - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - pixman_region32_clear(&mui->redraw); - } - uint32_t current_frame = mii->video.frame_count; - if (current_frame != mii->video.frame_drawn) { - miigl_counter_tick(&ui->videoc, miigl_get_time()); - draw = true; - mii->video.frame_drawn = current_frame; - // update the whole texture - glBindTexture(GL_TEXTURE_2D, ui->mii_tex.id); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, - MII_VRAM_WIDTH, - MII_VIDEO_HEIGHT, GL_RGBA, - GL_UNSIGNED_BYTE, - mii->video.pixels); - } - /* Draw */ + bool draw = mii_mui_gl_run(&ui->video); if (draw) { - miigl_counter_tick(&ui->redrawc, miigl_get_time()); - XGetWindowAttributes(ui->dpy, ui->win, &ui->attr); - glViewport(0, 0, ui->width, ui->height); - _mii_transition(ui); - mii_x11_render(ui); + // miigl_counter_tick(&ui->redrawc, miigl_get_time()); + // XGetWindowAttributes(ui->dpy, ui->win, &ui->attr); + glViewport(0, 0, ui->video.window_size.x, ui->video.window_size.y); + mii_mui_showhide_ui_machine(&ui->video); + mii_mui_gl_render(&ui->video); glFlush(); glXSwapBuffers(ui->dpy, ui->win); } diff --git a/ui_gl/mii_loadbin.c b/ui_gl/mii_loadbin.c index a4ead0f..a013433 100644 --- a/ui_gl/mii_loadbin.c +++ b/ui_gl/mii_loadbin.c @@ -6,7 +6,6 @@ * SPDX-License-Identifier: MIT */ - #include #include #include @@ -33,7 +32,7 @@ static void * mii_thread_loadbin( void *arg) { - + return NULL; } diff --git a/ui_gl/mii_mui.c b/ui_gl/mii_mui.c new file mode 100644 index 0000000..6e7cf1b --- /dev/null +++ b/ui_gl/mii_mui.c @@ -0,0 +1,193 @@ +/* + * mii_mui.c + * + * Copyright (C) 2024 Michel Pollet + * + * SPDX-License-Identifier: MIT + */ + +/* + * This contains the integration between the MII video and the MUI interface + * without any specific windowing system, it should be possible to use this + * with a native windowing system, or a portable one like SDL2 or GLFW + * This doesn't do anything to draw on screen, it just moves the video + * rectangle around, and handles the mouse mapping to the video frame. + */ +#include +#include + +#include "mii_mui.h" +#include "minipt.h" + +#define MII_MUI_GL_POW2 0 + +c2_rect_t +c2_rect_interpolate( + c2_rect_t *a, + c2_rect_t *b, + float t) +{ + c2_rect_t r = {}; + r.l = 0.5 + a->l + (b->l - a->l) * t; + r.r = 0.5 + a->r + (b->r - a->r) * t; + r.t = 0.5 + a->t + (b->t - a->t) * t; + r.b = 0.5 + a->b + (b->b - a->b) * t; + return r; +} + +c2_rect_t +mii_mui_get_video_position( + mii_mui_t * ui) +{ + c2_rect_t r = C2_RECT(0, 0, MII_VIDEO_WIDTH, MII_VIDEO_HEIGHT); + if (ui->mui_visible) { + float fac = (ui->window_size.y - 38) / (float)MII_VIDEO_HEIGHT; + c2_rect_scale(&r, fac); + c2_rect_offset(&r, + (ui->window_size.x / 2) - (c2_rect_width(&r) / 2), 36); + } else { + float fac = (ui->window_size.y) / (float)MII_VIDEO_HEIGHT; + c2_rect_scale(&r, fac); + c2_rect_offset(&r, + (ui->window_size.x / 2) - (c2_rect_width(&r) / 2), + (ui->window_size.y / 2) - (c2_rect_height(&r) / 2)); + c2_rect_inset(&r, 10, 10); + } + return r; +} + +void +mii_mui_showhide_ui_machine( + mii_mui_t * ui ) +{ + pt_start(ui->transision_state); + + while (ui->transition.state == MII_MUI_TRANSITION_NONE) + pt_yield(ui->transision_state); + + ui->transition.start = mui_get_time(); + ui->transition.end = ui->transition.start + (MUI_TIME_SECOND / 2); + ui->transition.from = ui->video_frame; + + switch (ui->transition.state) { + case MII_MUI_TRANSITION_HIDE_UI: + ui->mui_visible = false; + ui->transition.to = mii_mui_get_video_position(ui); + ui->mui_visible = true; + break; + case MII_MUI_TRANSITION_SHOW_UI: + ui->mui_visible = true; + ui->transition.to = mii_mui_get_video_position(ui); + break; + } + while (1) { + mui_time_t now = mui_get_time(); + float t = (now - ui->transition.start) / + (float)(ui->transition.end - ui->transition.start); + if (t >= 1.0f) + break; + switch (ui->transition.state) { + case MII_MUI_TRANSITION_HIDE_UI: + ui->mui_alpha = 1.0f - t; + break; + case MII_MUI_TRANSITION_SHOW_UI: + ui->mui_alpha = t; + break; + } + ui->video_frame = c2_rect_interpolate( + &ui->transition.from, &ui->transition.to, t); + pt_yield(ui->transision_state); + } + switch (ui->transition.state) { + case MII_MUI_TRANSITION_HIDE_UI: + ui->mui_visible = false; + ui->mui_alpha = 0.0f; + break; + case MII_MUI_TRANSITION_SHOW_UI: + ui->mui_alpha = 1.0f; + break; + } + ui->transition.state = MII_MUI_TRANSITION_NONE; + + pt_end(ui->transision_state); +} + +void +mii_mui_update_mouse_card( + mii_mui_t * ui) +{ + mii_t * mii = &ui->mii; + mui_t * mui = &ui->mui; + /* + * We can grab the mouse if it is enabled by the driver, it is in the + * video frame, and there is no active MUI windows (or menus). + */ + if (mii->mouse.enabled && + c2_rect_contains_pt(&ui->video_frame, &ui->mouse.pos) && + !(ui->mui_visible && mui_has_active_windows(mui))) { + if (!ui->mouse.grabbed) { + ui->mouse.grab = 1; + ui->mouse.grabbed = 1; + // printf("Grab mouse\n"); + } + } else { + if (ui->mouse.grabbed) { + ui->mouse.ungrab = 1; + ui->mouse.grabbed = 0; + // printf("Ungrab mouse\n"); + } + } + if (!ui->mouse.grabbed) + return; + double x = ui->mouse.pos.x - ui->video_frame.l; + double y = ui->mouse.pos.y - ui->video_frame.t; + // get mouse button state + int button = ui->mouse.down; + // clamp coordinates inside bounds + double vw = c2_rect_width(&ui->video_frame); + double vh = c2_rect_height(&ui->video_frame); + double mw = mii->mouse.max_x - mii->mouse.min_x; + double mh = mii->mouse.max_y - mii->mouse.min_y; + // normalize mouse coordinates + mii->mouse.x = mii->mouse.min_x + (x * mw / vw) + 0.5; + mii->mouse.y = mii->mouse.min_y + (y * mh / vh) + 0.5; + mii->mouse.button = button; +} + +void +mii_mui_init( + mii_mui_t * ui, + c2_pt_t window_size) +{ + mui_drawable_t * dr = &ui->pixels.mui; + // annoyingly I have to make it a LOT bigger to handle that the + // non-power-of-2 texture extension is not avialable everywhere + // textures, which is a bit of a waste of memory, but oh well. +#if MII_MUI_GL_POW2 + int padded_x = 1; + int padded_y = 1; + while (padded_x < window_size.x) + padded_x <<= 1; + while (padded_y < window_size.y) + padded_y <<= 1; +#else + int padded_x = window_size.x; + int padded_y = window_size.y; +#endif + mui_drawable_init(dr, C2_PT(padded_x, padded_y), 32, NULL, 0); + dr->texture.size = C2_PT(padded_x, padded_y); + printf("MUI Padded UI size is %dx%d\n", padded_x, padded_y); + ui->mui.screen_size = dr->pix.size; + + ui->window_size = window_size; + ui->mui_alpha = 1.0f; + ui->mui_visible = true; + ui->video_frame = mii_mui_get_video_position(ui); + + mui_t * mui = &ui->mui; + mui_init(mui); + mii_mui_menus_init(ui); + mii_mui_menu_slot_menu_update(ui); + // Tell libmui to clear the background with transparency. + mui->color.clear.value = 0; +} diff --git a/ui_gl/mii_mui.h b/ui_gl/mii_mui.h index 6be2a47..edbbedd 100644 --- a/ui_gl/mii_mui.h +++ b/ui_gl/mii_mui.h @@ -9,8 +9,8 @@ /* This tries to contains a structure that is the MUI interface over the MII video, but without any attachment to x11 or opengl. Basically hopefully - segregating the relevant logic without tying it to a specific windowing system. - + segregating the relevant logic without tying it to a specific windowing + system. Hopefully with a bit more work this OUGHT to allow Windows/macOS port with a native frontend. */ @@ -20,6 +20,7 @@ #include "mii.h" #include "mui.h" #include "mii_mui_settings.h" +#include "mii_floppy.h" enum mii_mui_transition_e { MII_MUI_TRANSITION_NONE, @@ -27,12 +28,46 @@ enum mii_mui_transition_e { MII_MUI_TRANSITION_SHOW_UI, }; +#define MII_PIXEL_LAYERS 8 + typedef struct mii_mui_t { mui_t mui; // mui interface mii_t mii; // apple II emulator + c2_pt_t window_size; + long last_button_click; + struct { + int ungrab, grab, grabbed, down; + c2_pt_t pos; + } mouse; + mui_event_t key; + c2_rect_t video_frame; // current video frame + float mui_alpha; bool mui_visible; - uint8_t transition; + void * transision_state; + struct { + uint8_t state; + mui_time_t start, end; + c2_rect_t from, to; + } transition; + unsigned int tex_id[MII_PIXEL_LAYERS]; + union { + struct { + mui_drawable_t mii; + mui_drawable_t mui; + struct { + mui_drawable_t bits; + mui_drawable_t hm_read; + mui_drawable_t hm_write; + } floppy[2]; + }; + mui_drawable_t v[MII_PIXEL_LAYERS]; + } pixels; + struct { + mii_floppy_t * floppy; + uint32_t seed_load; + float max_width; + } floppy[2]; mii_machine_config_t config; mii_loadbin_conf_t loadbin_conf; @@ -40,6 +75,10 @@ typedef struct mii_mui_t { mii_config_file_t cf; } mii_mui_t; +void +mii_mui_init( + mii_mui_t * ui, + c2_pt_t window_size); void mii_mui_menus_init( @@ -47,6 +86,17 @@ mii_mui_menus_init( void mii_mui_menu_slot_menu_update( mii_mui_t * ui); + +void +mii_mui_update_mouse_card( + mii_mui_t * ui); +void +mii_mui_showhide_ui_machine( + mii_mui_t * ui ); +c2_rect_t +mii_mui_get_video_position( + mii_mui_t * ui); + // slot can be <= 0 to open the machine dialog instead void mii_config_open_slots_dialog( diff --git a/ui_gl/mii_mui_2dsk.c b/ui_gl/mii_mui_2dsk.c index 20d8cca..12016f5 100644 --- a/ui_gl/mii_mui_2dsk.c +++ b/ui_gl/mii_mui_2dsk.c @@ -43,12 +43,12 @@ typedef struct mii_mui_2dsk_t { #include #include -typedef struct mii_floppy_check_t { +typedef struct mii_imagefile_check_t { char * error; char * warning; int file_ro; int file_ro_format; -} mii_floppy_check_t; +} mii_imagefile_check_t; #define NIB_SIZE 232960; #define DSK_SIZE 143360; @@ -64,7 +64,7 @@ _size_string( static int _mii_floppy_check_file( const char * path, - mii_floppy_check_t * out) + mii_imagefile_check_t * out) { char *filename = basename((char*)path); @@ -162,13 +162,15 @@ mii_mui_2dsk_load_conf( for (int i = 0; i < 2; i++) { if (config->drive[i].disk[0]) { ok = 1; - mii_floppy_check_t check = {}; - if (_mii_floppy_check_file(config->drive[i].disk, &check) < 0) { - mui_alert(m->win.ui, C2_PT(0,0), - "Invalid Disk Image", - check.error, MUI_ALERT_FLAG_OK); - free(check.error); - ok = 0; + mii_imagefile_check_t check = {}; + if (m->drive_kind == MII_2DSK_DISKII) { + if (_mii_floppy_check_file(config->drive[i].disk, &check) < 0) { + mui_alert(m->win.ui, C2_PT(0,0), + "Invalid Disk Image", + check.error, MUI_ALERT_FLAG_OK); + free(check.error); + ok = 0; + } } config->drive[i].ro_file = check.file_ro; config->drive[i].ro_format = check.file_ro_format; @@ -389,6 +391,9 @@ mii_mui_load_2dsk( cf, MUI_BUTTON_STYLE_CHECKBOX, "Write Protect", i == 0 ? MII_2DSK_WP1 : MII_2DSK_WP2); + // Smartport don't support write protect right now + if (drive_kind == MII_2DSK_SMARTPORT) + c->state = MUI_CONTROL_STATE_DISABLED; c2_rect_right_of(&cf, cf.r, margin * 0.5); cf.r = c2_rect_width(&w->frame) - margin * 1.2; m->drive[i].warning = c = mui_textbox_new(w, cf, diff --git a/ui_gl/mii_mui_gl.c b/ui_gl/mii_mui_gl.c new file mode 100644 index 0000000..3bade28 --- /dev/null +++ b/ui_gl/mii_mui_gl.c @@ -0,0 +1,446 @@ +/* + * mii_mui_gl.c + * + * Copyright (C) 2024 Michel Pollet + * + * SPDX-License-Identifier: MIT + */ + +/* + * This contains OpenGL code, no x11 or GLx allowed in here, this is to be + * used by a native windowing system, or a portable one like SDL2 or GLFW + */ + +#include +#include +#include + +#ifdef __SSE2__ +#include // SSE2 intrinsics +#endif + +#include "mii_mui_gl.h" +#include "mii_floppy.h" + + +typedef struct c2_rect_f { + float l,t,r,b; +} c2_rect_f; + +void +mii_mui_gl_init( + mii_mui_t *ui) +{ + GLuint tex[MII_PIXEL_LAYERS]; + glGenTextures(MII_PIXEL_LAYERS, tex); + for (int i = 0; i < MII_PIXEL_LAYERS; i++) { + printf("Texture %d created %d\n", i, tex[i]); + ui->pixels.v[i].texture.id = tex[i]; + ui->tex_id[i] = tex[i]; + } + + mii_mui_gl_prepare_textures(ui); +} + +static void +_prep_grayscale_texture( + mui_drawable_t * dr) +{ + dr->texture.size = dr->pix.size; + printf("Creating texture %4d %4dx%3d row_byte %4d\n", + dr->texture.id, + dr->pix.size.x, dr->pix.size.y, + dr->pix.row_bytes); + glBindTexture(GL_TEXTURE_2D, dr->texture.id); +// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); +// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, 1, + dr->pix.row_bytes, dr->texture.size.y, 0, GL_LUMINANCE, + GL_UNSIGNED_BYTE, + dr->pix.pixels); +} + +void +mii_mui_gl_prepare_textures( + mii_mui_t *ui) +{ + mii_t * mii = &ui->mii; + + glEnable(GL_TEXTURE_2D); + mui_drawable_t * dr = &ui->pixels.mii; + // bind the mii texture using the GL_ARB_texture_rectangle extension + printf("Creating texture %4d %4dx%3d row_byte %4d (MII)\n", + dr->texture.id, + dr->pix.size.x, dr->pix.size.y, + dr->pix.row_bytes); + glBindTexture(GL_TEXTURE_2D, dr->texture.id); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + // disable the repeat of textures + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glTexImage2D(GL_TEXTURE_2D, 0, 4, + MII_VRAM_WIDTH, + MII_VRAM_HEIGHT, 0, GL_BGRA, // note BGRA here, not RGBA + GL_UNSIGNED_BYTE, + mii->video.pixels); + + // bind the mui texture using the GL_ARB_texture_rectangle as well + dr = &ui->pixels.mui; + printf("Creating texture %4d %4dx%3d row_byte %4d (MUI)\n", + dr->texture.id, + dr->pix.size.x, dr->pix.size.y, + dr->pix.row_bytes); + glBindTexture(GL_TEXTURE_2D, dr->texture.id); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glTexImage2D(GL_TEXTURE_2D, 0, 4, + dr->pix.row_bytes / 4, // already power of two. + dr->texture.size.y, 0, GL_BGRA, + GL_UNSIGNED_BYTE, + dr->pix.pixels); + + mii_floppy_t * floppy[2] = {}; + for (int i = 0; i < 7; i++) { + if (mii_slot_command(mii, i, MII_SLOT_D2_GET_FLOPPY, floppy) == 0) + break; + } + if (floppy[0]) { + for (int fi = 0; fi < 2; fi++) { + mii_floppy_t * f = floppy[fi]; + ui->floppy[fi].floppy = f; + + dr = &ui->pixels.floppy[fi].bits; + // the init() call clears the structure, keep our id around + unsigned int tex = dr->texture.id; + mui_drawable_init(dr, + C2_PT(MII_FLOPPY_DEFAULT_TRACK_SIZE, MII_FLOPPY_TRACK_COUNT), + 8, floppy[fi]->track_data, MII_FLOPPY_DEFAULT_TRACK_SIZE); + dr->texture.id = tex; + _prep_grayscale_texture(dr); + if (!f->heat) + f->heat = calloc(1, sizeof(*f->heat)); + dr = &ui->pixels.floppy[fi].hm_read; + tex = dr->texture.id; + mui_drawable_init(dr, + C2_PT(MII_FLOPPY_HM_TRACK_SIZE, MII_FLOPPY_TRACK_COUNT), + 8, f->heat->read.map, MII_FLOPPY_HM_TRACK_SIZE); + dr->texture.id = tex; + _prep_grayscale_texture(dr); + dr = &ui->pixels.floppy[fi].hm_write; + tex = dr->texture.id; + mui_drawable_init(dr, + C2_PT(MII_FLOPPY_HM_TRACK_SIZE, MII_FLOPPY_TRACK_COUNT), + 8, f->heat->write.map, MII_FLOPPY_HM_TRACK_SIZE); + dr->texture.id = tex; + _prep_grayscale_texture(dr); + } + } else { + printf("No floppy found\n"); + for (int fi = 0; fi < 2; fi++) { + ui->floppy[fi].floppy = NULL; + mui_drawable_clear(&ui->pixels.floppy[fi].bits); + mui_drawable_clear(&ui->pixels.floppy[fi].hm_read); + mui_drawable_clear(&ui->pixels.floppy[fi].hm_write); + } + } +// printf("%s texture created %d\n", __func__, mii_apple_screen_tex); +// display opengl error + GLenum err = glGetError(); + if (err != GL_NO_ERROR) { + printf("Error creating texture: %d\n", err); + } +} + + +static void +_mii_decay_heatmap_one( + mii_track_heatmap_t *hm) +{ + uint32_t count = 0; +#ifdef __SSE2__ + const int size = (MII_FLOPPY_TRACK_COUNT * MII_FLOPPY_HM_TRACK_SIZE) / 16; + __m128i * hmw = (__m128i*)&hm->map[0]; + const __m128i s = _mm_set1_epi8(2); + for (int i = 0; i < size; i++) { + __m128i b = _mm_load_si128(hmw + i); + __m128i c = _mm_subs_epu8(b, s); + hmw[i] = c; + count += _mm_movemask_epi8(_mm_cmpgt_epi8(c, _mm_setzero_si128())); + } +#else + const int size = MII_FLOPPY_TRACK_COUNT * MII_FLOPPY_HM_TRACK_SIZE; + uint8_t * hmb = (uint8_t*)&hm->map[0]; + for (int i = 0; i < size; i++) { + uint8_t b = hmb[i]; + b = b > 2 ? b - 2 : 0; + hmb[i] = b; + count += !!b; + } +#endif + hm->cleared = count == 0; +} + +static void +_mii_decay_heatmap( + mii_floppy_heatmap_t *h) +{ + if (h->read.seed != h->read.tex || !h->read.cleared) { + h->read.tex = h->read.tex; + _mii_decay_heatmap_one(&h->read); + } + if (h->write.seed != h->write.tex || !h->write.cleared) { + h->write.tex = h->write.tex; + _mii_decay_heatmap_one(&h->write); + } +} + +bool +mii_mui_gl_run( + mii_mui_t *ui) +{ + mii_t * mii = &ui->mii; + mui_t * mui = &ui->mui; + + mui_run(mui); + bool draw = false; + if (pixman_region32_not_empty(&mui->inval)) { + draw = true; + mui_drawable_t * dr = &ui->pixels.mui; + mui_draw(mui, dr, 0); + glBindTexture(GL_TEXTURE_2D, dr->texture.id); + + pixman_region32_intersect_rect(&mui->redraw, &mui->redraw, + 0, 0, dr->pix.size.x, dr->pix.size.y); + int rc = 0; + c2_rect_t *ra = (c2_rect_t*)pixman_region32_rectangles(&mui->redraw, &rc); + // rc = 1; ra = &C2_RECT(0, 0, mui->screen_size.x, mui->screen_size.y); + if (rc) { + // printf("GL: %d rects to redraw\n", rc); + for (int i = 0; i < rc; i++) { + c2_rect_t r = ra[i]; + // printf("GL: %d,%d %dx%d\n", r.l, r.t, c2_rect_width(&r), c2_rect_height(&r)); + glPixelStorei(GL_UNPACK_ROW_LENGTH, dr->pix.row_bytes / 4); + glTexSubImage2D(GL_TEXTURE_2D, 0, r.l, r.t, + c2_rect_width(&r), c2_rect_height(&r), + GL_BGRA, GL_UNSIGNED_BYTE, + dr->pix.pixels + (r.t * dr->pix.row_bytes) + (r.l * 4)); + } + } + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + pixman_region32_clear(&mui->redraw); + } + uint32_t current_frame = mii->video.frame_count; + if (current_frame != mii->video.frame_drawn) { + // miigl_counter_tick(&ui->videoc, miigl_get_time()); + draw = true; + mii->video.frame_drawn = current_frame; + // update the whole texture + mui_drawable_t * dr = &ui->pixels.mii; + glBindTexture(GL_TEXTURE_2D, dr->texture.id); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, + MII_VRAM_WIDTH, + MII_VIDEO_HEIGHT, GL_BGRA, + GL_UNSIGNED_BYTE, + mii->video.pixels); + } + for (int fi = 0; fi < 2; fi++) { + if (!ui->floppy[fi].floppy) + continue; + mui_drawable_t * dr = NULL; + mii_floppy_t * f = ui->floppy[fi].floppy; + if (ui->floppy[fi].seed_load != f->seed_dirty) { + draw = true; + ui->floppy[fi].seed_load = f->seed_dirty; + // printf("Floppy %d: Reloading texture\n", fi); + dr = &ui->pixels.floppy[fi].bits; + int bc = (f->tracks[0].bit_count + 7) / 8; + int max = MII_FLOPPY_DEFAULT_TRACK_SIZE; + ui->floppy[fi].max_width = (double)bc / (double)max; + glBindTexture(GL_TEXTURE_2D, dr->texture.id); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, + dr->pix.row_bytes, dr->pix.size.y, + GL_LUMINANCE, GL_UNSIGNED_BYTE, + f->track_data); + } +// int rm = f->heat->read.tex != f->heat->read.seed; +// int wm = f->heat->write.tex != f->heat->write.seed; + _mii_decay_heatmap(f->heat); + glPixelStorei(GL_UNPACK_ROW_LENGTH, MII_FLOPPY_HM_TRACK_SIZE); +// if (rm) { + dr = &ui->pixels.floppy[fi].hm_read; + glBindTexture(GL_TEXTURE_2D, dr->texture.id); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, + dr->pix.row_bytes, dr->pix.size.y, + GL_LUMINANCE, GL_UNSIGNED_BYTE, + f->heat->read.map); +// } +// if (wm) { + dr = &ui->pixels.floppy[fi].hm_write; + glBindTexture(GL_TEXTURE_2D, dr->texture.id); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, + dr->pix.row_bytes, dr->pix.size.y, + GL_LUMINANCE, GL_UNSIGNED_BYTE, + f->heat->write.map); +// } + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + } + return draw; +} + +void +mii_mui_gl_render( + mii_mui_t *ui) +{ + glClearColor( + .6f * ui->mui_alpha, + .6f * ui->mui_alpha, + .6f * ui->mui_alpha, + 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + glPushAttrib(GL_ENABLE_BIT|GL_COLOR_BUFFER_BIT|GL_TRANSFORM_BIT); + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glDisable(GL_SCISSOR_TEST); + glEnable(GL_BLEND); + glEnable(GL_TEXTURE_2D); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + /* setup viewport/project */ + glViewport(0, 0, + (GLsizei)ui->window_size.x, + (GLsizei)ui->window_size.y); + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + glOrtho(0.0f, ui->window_size.x, ui->window_size.y, + 0.0f, -1.0f, 1.0f); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + // This (was) the recommended way to handle pixel alignment in glOrtho + // mode, but this seems to have changed -- now it looks like Linear filtering +// glTranslatef(0.375f, 0.375f, 0.0f); + { + /* draw mii texture */ + glColor3f(1.0f, 1.0f, 1.0f); + mui_drawable_t * dr = &ui->pixels.mii; + glBindTexture(GL_TEXTURE_2D, dr->texture.id); + glBegin(GL_QUADS); + c2_rect_t r = ui->video_frame; + glTexCoord2f(0, 0); + glVertex2f(r.l, r.t); + glTexCoord2f(MII_VIDEO_WIDTH / (double)MII_VRAM_WIDTH, 0); + glVertex2f(r.r, r.t); + glTexCoord2f(MII_VIDEO_WIDTH / (double)MII_VRAM_WIDTH, + MII_VIDEO_HEIGHT / (double)MII_VRAM_HEIGHT); + glVertex2f(r.r, r.b); + glTexCoord2f(0, + MII_VIDEO_HEIGHT / (double)MII_VRAM_HEIGHT); + glVertex2f(r.l, r.b); + glEnd(); + + /* draw floppy textures, floppy 0 is left of the screen, + floppy 1 is right */ + for (int i = 0; i < 2; i++) { + dr = &ui->pixels.floppy[i].bits; + mii_floppy_t *f = ui->floppy[i].floppy; + if (!f || !dr->pix.pixels) + continue; + if (f->motor) { + dr->texture.opacity = 1.0f; + } else { + if (dr->texture.opacity > 0.0f) + dr->texture.opacity -= 0.01f; + if (dr->texture.opacity < 0.0f) + dr->texture.opacity = 0.0f; + } + if (dr->texture.opacity <= 0.0f) + continue; + c2_rect_t r = C2_RECT_WH( 0, 0, + ui->video_frame.l - 20, + c2_rect_height(&ui->video_frame) - 22); + c2_rect_f tr = { 0, 0, ui->floppy[i].max_width, 1 }; + if (i == 0) + c2_rect_offset(&r, + ui->video_frame.l - c2_rect_width(&r) - 10, + ui->video_frame.t + 10); + else + c2_rect_offset(&r, ui->video_frame.r + 10, + ui->video_frame.t + 10); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glColor4f(1.0f, 1.0f, 1.0f, dr->texture.opacity); + glBindTexture(GL_TEXTURE_2D, dr->texture.id); + glBegin(GL_QUADS); + // rotate texture 90 clockwise, and mirror left-right + glTexCoord2f(tr.l, tr.t); glVertex2f(r.l, r.t); + glTexCoord2f(tr.l, tr.b); glVertex2f(r.r, r.t); + glTexCoord2f(tr.r, tr.b); glVertex2f(r.r, r.b); + glTexCoord2f(tr.r, tr.t); glVertex2f(r.l, r.b); + glEnd(); + + if (f->heat) { + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_COLOR); + dr = &ui->pixels.floppy[i].hm_read; + glColor4f(0.0f, 1.0f, 0.0f, 1.0); + glBindTexture(GL_TEXTURE_2D, dr->texture.id); + glBegin(GL_QUADS); + // rotate texture 90 clockwise, and mirror left-right + glTexCoord2f(tr.l, tr.t); glVertex2f(r.l, r.t); + glTexCoord2f(tr.l, tr.b); glVertex2f(r.r, r.t); + glTexCoord2f(tr.r, tr.b); glVertex2f(r.r, r.b); + glTexCoord2f(tr.r, tr.t); glVertex2f(r.l, r.b); + glEnd(); + dr = &ui->pixels.floppy[i].hm_write; + glColor4f(1.0f, 0.0f, 0.0f, 1.0); + glBindTexture(GL_TEXTURE_2D, dr->texture.id); + glBegin(GL_QUADS); + // rotate texture 90 clockwise, and mirror left-right + glTexCoord2f(tr.l, tr.t); glVertex2f(r.l, r.t); + glTexCoord2f(tr.l, tr.b); glVertex2f(r.r, r.t); + glTexCoord2f(tr.r, tr.b); glVertex2f(r.r, r.b); + glTexCoord2f(tr.r, tr.t); glVertex2f(r.l, r.b); + glEnd(); + } + } + /* draw mui texture */ + if (ui->mui_alpha > 0.0f) { + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glColor4f(1.0f, 1.0f, 1.0f, ui->mui_alpha); + dr = &ui->pixels.mui; + glBindTexture(GL_TEXTURE_2D, dr->texture.id); + glBegin(GL_QUADS); + glTexCoord2f(0, 0); glVertex2f(0, 0); + glTexCoord2f( + ui->window_size.x / (double)(dr->pix.row_bytes / 4), 0); + glVertex2f(ui->window_size.x, 0); + glTexCoord2f(ui->window_size.x / (double)(dr->pix.row_bytes / 4), + ui->window_size.y / (double)(dr->texture.size.y)); + glVertex2f(ui->window_size.x, ui->window_size.y); + glTexCoord2f(0, + ui->window_size.y / (double)(dr->texture.size.y)); + glVertex2f(0, ui->window_size.y); + glEnd(); + } + } + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glDisable(GL_SCISSOR_TEST); + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + + glBindTexture(GL_TEXTURE_2D, 0); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glPopAttrib(); +} diff --git a/ui_gl/mii_mui_gl.h b/ui_gl/mii_mui_gl.h new file mode 100644 index 0000000..086caa8 --- /dev/null +++ b/ui_gl/mii_mui_gl.h @@ -0,0 +1,23 @@ +/* + * mii_mui_gl.h + * + * Copyright (C) 2023 Michel Pollet + * + * SPDX-License-Identifier: MIT + */ +#pragma once + +#include "mii_mui.h" + +void +mii_mui_gl_init( + mii_mui_t *ui); +void +mii_mui_gl_prepare_textures( + mii_mui_t *ui); +void +mii_mui_gl_render( + mii_mui_t *ui); +bool +mii_mui_gl_run( + mii_mui_t *ui); diff --git a/ui_gl/mii_mui_menus.c b/ui_gl/mii_mui_menus.c index c9075da..b50cb0c 100644 --- a/ui_gl/mii_mui_menus.c +++ b/ui_gl/mii_mui_menus.c @@ -39,12 +39,12 @@ mii_quit_confirm_cb( void * param) { mii_mui_t * ui = cb_param; - printf("%s %4.4s\n", __func__, (char*)&what); +// printf("%s %4.4s\n", __func__, (char*)&what); if (what == MUI_CONTROL_ACTION_SELECT) { mui_control_t * c = param; - printf("%s %4.4s\n", __func__, (char*)&c->uid); +// printf("%s %4.4s\n", __func__, (char*)&c->uid); if (c->uid == MUI_ALERT_BUTTON_OK) { - printf("%s Quit\n", __func__); +// printf("%s Quit\n", __func__); mii_t * mii = &ui->mii; mii->state = MII_TERMINATE; } @@ -92,25 +92,6 @@ mii_config_open_slots_dialog( mii_config_save_cb, ui); } - -static void -_mii_show_about( - mii_mui_t * ui) -{ - mui_t * mui = &ui->mui; - mui_window_t *w = mui_window_get_by_id(mui, FCC('a','b','o','t')); - if (w) { - mui_window_select(w); - return; - } - w = mui_alert(mui, C2_PT(0,0), - "About MII", - "Version " MII_VERSION "\n" - "Build " __DATE__ " " __TIME__, - MUI_ALERT_INFO); - mui_window_set_id(w, FCC('a','b','o','t')); -} - static int mii_menubar_action( mui_window_t *win, // window (menubar) @@ -198,7 +179,7 @@ mii_menubar_action( case FCC('s','h','m','b'): { items[i].disabled = (mui_window_front(mui) != NULL) || - (ui->transition != MII_MUI_TRANSITION_NONE); + (ui->transition.state != MII_MUI_TRANSITION_NONE); } break; } } @@ -209,14 +190,12 @@ mii_menubar_action( // (char*)&item->uid, item->title); switch (item->uid) { case FCC('a','b','o','t'): { -// _mii_show_about(ui); mii_mui_about(&ui->mui); } break; case FCC('q','u','i','t'): { -// printf("%s Quit?\n", __func__); if (!ui->mui_visible && - ui->transition == MII_MUI_TRANSITION_NONE) - ui->transition = MII_MUI_TRANSITION_SHOW_UI; + ui->transition.state == MII_MUI_TRANSITION_NONE) + ui->transition.state = MII_MUI_TRANSITION_SHOW_UI; mui_window_t * really = mui_window_get_by_id( &ui->mui, FCC('q','u','i','t')); if (really) @@ -231,12 +210,12 @@ mii_menubar_action( } } break; case FCC('s','h','m','b'): { - if (ui->transition != MII_MUI_TRANSITION_NONE) + if (ui->transition.state != MII_MUI_TRANSITION_NONE) break; if (ui->mui_visible) { - ui->transition = MII_MUI_TRANSITION_HIDE_UI; + ui->transition.state = MII_MUI_TRANSITION_HIDE_UI; } else { - ui->transition = MII_MUI_TRANSITION_SHOW_UI; + ui->transition.state = MII_MUI_TRANSITION_SHOW_UI; } } break; case FCC('d','s','k','0'):