/* * 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; #define MII_GL_FLOPPY_SEGMENT_COUNT 32 #define MII_GL_FLOPPY_DISC_RADIUS_IN 1.8 #define MII_GL_FLOPPY_DISC_RADIUS_OUT 10 #define MII_GL_FLOPPY_FLUX_RADIUS_IN 2.0 #define MII_GL_FLOPPY_FLUX_RADIUS_OUT 9.8 #include static void mii_gl_make_disc( float_array_t * pos, const double radius_out, const double radius_in, const int count) { float_array_clear(pos); const double astep = 2 * M_PI / count; // Angle step for each blade for (int i = 0; i < MII_GL_FLOPPY_SEGMENT_COUNT; ++i) { double a = i * astep, b = (i + 1) * astep; // Outer vertex double x_out = radius_out * cos(a); double y_out = radius_out * sin(a); double x_out2 = radius_out * cos(b); double y_out2 = radius_out * sin(b); // Inner vertex double x_in = radius_in * cos(a); double y_in = radius_in * sin(a); double x_in2 = radius_in * cos(b); double y_in2 = radius_in * sin(b); // add two triangles winded in the right direction float_array_push(pos, x_out); float_array_push(pos, y_out); float_array_push(pos, x_in); float_array_push(pos, y_in); float_array_push(pos, x_out2); float_array_push(pos, y_out2); float_array_push(pos, x_in2); float_array_push(pos, y_in2); float_array_push(pos, x_out2); float_array_push(pos, y_out2); float_array_push(pos, x_in); float_array_push(pos, y_in); } } static void mii_gl_make_floppy( mii_vtx_t * vtx, float tex_width, bool do_pos, bool do_tex) { vtx->kind = GL_TRIANGLES; if (do_pos) { mii_gl_make_disc(&vtx->pos, MII_GL_FLOPPY_FLUX_RADIUS_OUT, MII_GL_FLOPPY_FLUX_RADIUS_IN, MII_GL_FLOPPY_SEGMENT_COUNT); } if (!do_tex) return; const double tex_y_in = 1; const double tex_y_out = 0; float_array_t * tex = &vtx->tex; float_array_clear(tex); for (int i = 0; i < MII_GL_FLOPPY_SEGMENT_COUNT; ++i) { float_array_push(tex, (i * tex_width) / MII_GL_FLOPPY_SEGMENT_COUNT); float_array_push(tex, tex_y_out); float_array_push(tex, (i * tex_width) / MII_GL_FLOPPY_SEGMENT_COUNT); float_array_push(tex, tex_y_in); float_array_push(tex, ((i + 1) * tex_width) / MII_GL_FLOPPY_SEGMENT_COUNT); float_array_push(tex, tex_y_out); float_array_push(tex, ((i + 1) * tex_width) / MII_GL_FLOPPY_SEGMENT_COUNT); float_array_push(tex, tex_y_in); float_array_push(tex, ((i + 1) * tex_width) / MII_GL_FLOPPY_SEGMENT_COUNT); float_array_push(tex, tex_y_out); float_array_push(tex, (i * tex_width) / MII_GL_FLOPPY_SEGMENT_COUNT); float_array_push(tex, tex_y_in); } } 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_gl_make_disc(&ui->floppy_base, MII_GL_FLOPPY_DISC_RADIUS_OUT, MII_GL_FLOPPY_DISC_RADIUS_IN, MII_GL_FLOPPY_SEGMENT_COUNT); 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); dr->texture.kind = GL_LUMINANCE; glTexImage2D(GL_TEXTURE_2D, 0, 1, dr->pix.row_bytes, dr->texture.size.y, 0, dr->texture.kind, 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; 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); dr->texture.kind = GL_RGBA; glTexImage2D(GL_TEXTURE_2D, 0, 4, MII_VRAM_WIDTH, MII_VRAM_HEIGHT, 0, dr->texture.kind, // note RGBA here, it's quicker!! GL_UNSIGNED_BYTE, // GL_UNSIGNED_INT_8_8_8_8_REV mii->video.pixels); // bind the mui texture using the GL_ARB_texture_rectangle as well dr = &ui->pixels.mui; 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); dr->texture.kind = GL_BGRA; glTexImage2D(GL_TEXTURE_2D, 0, 4, dr->pix.row_bytes / 4, // already power of two. dr->texture.size.y, 0, dr->texture.kind, GL_UNSIGNED_INT_8_8_8_8_REV, 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); mii_gl_make_floppy(&ui->floppy[fi].vtx, 1.0, true, true); } } else { printf("%s No floppy found\n", __func__); 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); mii_gl_make_floppy(&ui->floppy[fi].vtx, 1.0, true, true); } } GLenum err = glGetError(); if (err != GL_NO_ERROR) { printf("%s Error creating texture: %d\n", __func__, err); } } static void _mii_decay_heatmap_one( mii_track_heatmap_t *hm) { uint32_t count = 0; const int decay = 4; #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(decay); 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 > decay ? b - decay : 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), dr->texture.kind, GL_UNSIGNED_INT_8_8_8_8_REV, 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, dr->texture.kind, GL_UNSIGNED_INT_8_8_8_8_REV, 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, dr->texture.kind, GL_UNSIGNED_BYTE, f->track_data); // dont recalculate the vertices, just the texture coordinates mii_gl_make_floppy(&ui->floppy[fi].vtx, ui->floppy[fi].max_width, false, true); } int rm = f->heat->read.tex != f->heat->read.seed || !f->heat->read.cleared; int wm = f->heat->write.tex != f->heat->write.seed || !f->heat->write.cleared; // don't decay if we're not running if (ui->mii.state == MII_RUNNING) _mii_decay_heatmap(f->heat); if (rm) { glPixelStorei(GL_UNPACK_ROW_LENGTH, MII_FLOPPY_HM_TRACK_SIZE); 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, dr->texture.kind, GL_UNSIGNED_BYTE, f->heat->read.map); } if (wm) { glPixelStorei(GL_UNPACK_ROW_LENGTH, MII_FLOPPY_HM_TRACK_SIZE); 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, dr->texture.kind, 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; glEnable(GL_TEXTURE_2D); 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 (ui->mii.state == MII_RUNNING) { if (f->motor) { if (dr->texture.opacity < 1.0f) dr->texture.opacity += 0.10f; if (dr->texture.opacity > 1.0f) 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; } } float main_opacity = dr->texture.opacity; if (main_opacity <= 0.0f) continue; const float angle_offset = 60; // head angle offset on display if (1) { glPushMatrix(); // make floppy slide in/out with opacity glTranslatef(-10 - (100.0 * ( 1.0f - main_opacity) ), 200 + (i * 350), 0); glScalef(15, 15, 1); { glColor4f(0.0f, 0.0f, 0.0f, main_opacity); glDisable(GL_TEXTURE_2D); glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer(2, GL_FLOAT, 0, ui->floppy_base.e); int element_count = ui->floppy_base.count / 2; glDrawArrays(GL_TRIANGLES, 0, element_count); } int track_id = f->track_id[f->qtrack]; double bc = (double)f->bit_position / (double)f->tracks[track_id].bit_count; bc = 360 - (bc * 360.0); bc += angle_offset; if (bc >= 360.0) bc -= 360.0; glRotatef(bc, 0, 0, 1); dr = &ui->pixels.floppy[i].bits; glEnable(GL_TEXTURE_2D); 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); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glColor4f(1.0f, 1.0f, 1.0f, main_opacity); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glVertexPointer(2, GL_FLOAT, 0, ui->floppy[i].vtx.pos.e); glTexCoordPointer(2, GL_FLOAT, 0, ui->floppy[i].vtx.tex.e); int element_count = ui->floppy[i].vtx.pos.count / 2; glDrawArrays(ui->floppy[i].vtx.kind, 0, element_count); // draw heatmap and head with full opacity // otherwise we get wierd artifacts 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); glDrawArrays(ui->floppy[i].vtx.kind, 0, element_count); dr = &ui->pixels.floppy[i].hm_write; glColor4f(1.0f, 0.0f, 0.0f, 1.0); glBindTexture(GL_TEXTURE_2D, dr->texture.id); glDrawArrays(ui->floppy[i].vtx.kind, 0, element_count); } glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); if (main_opacity > 0.8f) { // Draw head small rectangle dr = &ui->pixels.floppy[i].bits; track_id = f->qtrack / 4; glDisable(GL_TEXTURE_2D); glRotatef(-bc + angle_offset, 0, 0, 1); glTranslatef(MII_GL_FLOPPY_FLUX_RADIUS_IN + (((35 - track_id) / 35.0) * (MII_GL_FLOPPY_FLUX_RADIUS_OUT- MII_GL_FLOPPY_FLUX_RADIUS_IN)), 0, 0); const float r = 0.3; glColor4f(1.0f, 0.0f, 0.0f, main_opacity); glBegin(GL_QUADS); glVertex2f(-r, -r); glVertex2f(-r, r); glVertex2f(r, r); glVertex2f(r, -r); glEnd(); } glPopMatrix(); } } /* 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; glEnable(GL_TEXTURE_2D); 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(); }