Floppy bitstream view, also major refactor of UI code

Split the UI into separate bits.
+ Bits that are X11/GLX only
+ Bits that are 'pure' opengl
+ Bits that are UI related but not renderer related

Hopefully with help porting to other platformsI

Signed-off-by: Michel Pollet <buserror@gmail.com>
This commit is contained in:
Michel Pollet 2024-02-18 10:32:33 +00:00
parent ab8a746bc8
commit 2e12a37bce
No known key found for this signature in database
23 changed files with 1038 additions and 570 deletions

View File

@ -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

View File

@ -3,6 +3,16 @@
</p>
# 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).

BIN
docs/heat_map.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

View File

@ -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(

View File

@ -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);

View File

@ -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;
}

View File

@ -13,7 +13,7 @@
#include <fcntl.h>
#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"

View File

@ -1,9 +0,0 @@
/*
* mii_disk2.h
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* SPDX-License-Identifier: MIT
*/
#pragma once

View File

@ -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 = {

View File

@ -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

View File

@ -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 = {

View File

@ -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;
}

View File

@ -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(

View File

@ -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:

View File

@ -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

View File

@ -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 <stdio.h>
#include <stdlib.h>
@ -12,7 +15,6 @@
#include <stdbool.h>
#include <stdarg.h>
#include <locale.h>
#include <time.h>
#include <pthread.h>
#include <sys/timerfd.h>
#include <sys/stat.h>
@ -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);
}

View File

@ -6,7 +6,6 @@
* SPDX-License-Identifier: MIT
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -33,7 +32,7 @@ static void *
mii_thread_loadbin(
void *arg)
{
return NULL;
}

193
ui_gl/mii_mui.c Normal file
View File

@ -0,0 +1,193 @@
/*
* mii_mui.c
*
* Copyright (C) 2024 Michel Pollet <buserror@gmail.com>
*
* 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 <stdio.h>
#include <stdlib.h>
#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;
}

View File

@ -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(

View File

@ -43,12 +43,12 @@ typedef struct mii_mui_2dsk_t {
#include <errno.h>
#include <fcntl.h>
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,

446
ui_gl/mii_mui_gl.c Normal file
View File

@ -0,0 +1,446 @@
/*
* mii_mui_gl.c
*
* Copyright (C) 2024 Michel Pollet <buserror@gmail.com>
*
* 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 <stdio.h>
#include <stdlib.h>
#include <GL/gl.h>
#ifdef __SSE2__
#include <emmintrin.h> // 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();
}

23
ui_gl/mii_mui_gl.h Normal file
View File

@ -0,0 +1,23 @@
/*
* mii_mui_gl.h
*
* Copyright (C) 2023 Michel Pollet <buserror@gmail.com>
*
* 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);

View File

@ -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'):