Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions iris-gui/src/config_ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,9 @@ fn show_general(ui: &mut Ui, cfg: &mut MachineConfig) -> ConfigAction {
ComboBox::from_id_salt("machine_profile")
.selected_text(cfg.machine.profile.label())
.show_ui(ui, |ui| {
ui.selectable_value(&mut cfg.machine.profile, MachineProfile::IndyIp24, MachineProfile::IndyIp24.label());
ui.selectable_value(&mut cfg.machine.profile, MachineProfile::Indigo2Ip22, MachineProfile::Indigo2Ip22.label());
for p in MachineProfile::ALL {
ui.selectable_value(&mut cfg.machine.profile, p, p.label());
}
});
ui.label(
RichText::new(
Expand Down Expand Up @@ -383,12 +384,7 @@ fn show_resolution_picker(ui: &mut Ui, cfg: &mut MachineConfig, running: bool) {
ComboBox::from_id_salt("graphics_resolution")
.selected_text(cfg.graphics.resolution.label())
.show_ui(ui, |ui| {
for mode in [
NewportResolution::Guest,
NewportResolution::Res1024x768,
NewportResolution::Res1280x960,
NewportResolution::Res1280x1024,
] {
for mode in NewportResolution::ALL {
ui.selectable_value(&mut cfg.graphics.resolution, mode, mode.label());
}
});
Expand Down
34 changes: 32 additions & 2 deletions iris-gui/src/dialogs/new_machine.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use eframe::egui::{self, Color32, ComboBox, Grid, RichText, TextEdit};
use iris::config::{MachineConfig, ScsiDeviceConfig, VALID_BANK_SIZES};
use iris::config::{MachineConfig, MachineProfile, ScsiDeviceConfig, VALID_BANK_SIZES};
use iris::vc2_timings::NewportResolution;

use crate::ram::RAM_PRESETS;

Expand All @@ -8,6 +9,10 @@ use crate::ram::RAM_PRESETS;
pub struct NewMachineDialog {
open: bool,
pub name: String,
/// Emulated SGI machine model (Indy IP24 / Indigo2 IP22).
pub profile: MachineProfile,
/// Host-forced Newport video mode (`Guest` leaves it to IRIX/setmon).
pub resolution: NewportResolution,
pub prom_path: String,
pub use_embedded_prom: bool,
pub nvram_path: String,
Expand All @@ -32,6 +37,8 @@ impl Default for NewMachineDialog {
Self {
open: false,
name: "indy".into(),
profile: MachineProfile::default(),
resolution: NewportResolution::default(),
prom_path: "prom.bin".into(),
use_embedded_prom: true,
nvram_path: crate::settings::GuiSettings::default_nvram_path(),
Expand Down Expand Up @@ -74,13 +81,34 @@ impl NewMachineDialog {
.anchor(egui::Align2::CENTER_CENTER, [0.0, 0.0])
.show(ctx, |ui| {
ui.set_min_width(440.0);
ui.label(RichText::new("Configure a new SGI Indy emulation").strong());
ui.label(RichText::new("Configure a new SGI machine").strong());
ui.add_space(4.0);

Grid::new("new_machine_grid").num_columns(2).striped(true).show(ui, |ui| {
ui.label("Name");
ui.add(TextEdit::singleline(&mut self.name).desired_width(200.0));
ui.end_row();

ui.label("Machine model");
ComboBox::from_id_salt("nm_profile")
.selected_text(self.profile.label())
.show_ui(ui, |ui| {
for p in MachineProfile::ALL {
ui.selectable_value(&mut self.profile, p, p.label());
}
});
ui.end_row();

ui.label("Display resolution");
ComboBox::from_id_salt("nm_resolution")
.selected_text(self.resolution.label())
.show_ui(ui, |ui| {
for mode in NewportResolution::ALL {
ui.selectable_value(&mut self.resolution, mode, mode.label());
}
});
ui.end_row();

ui.label("PROM image");
ui.horizontal(|ui| {
ui.add_enabled(!self.use_embedded_prom,
Expand Down Expand Up @@ -197,6 +225,8 @@ impl NewMachineDialog {
.fill(Color32::from_rgb(60, 110, 60))).clicked()
{
let mut cfg = MachineConfig::default();
cfg.machine.profile = self.profile;
cfg.graphics.resolution = self.resolution;
cfg.prom = if self.use_embedded_prom { String::new() } else { self.prom_path.clone() };
// Empty prom path makes Machine::new fall back to embedded
// (the load path warns + falls back when the file is missing).
Expand Down
100 changes: 19 additions & 81 deletions iris-gui/src/framebuffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

use iris::compositor::{Compositor, SwCompositor};
use iris::debug_overlay::DebugOverlay;
use iris::disp::{BarStats, Rex3Screen, StatusBar, StatusBarTexture, STATUS_BAR_HEIGHT};
use iris::disp::{BarStats, Rex3Screen, StatusBar, StatusBarTexture};
use iris::gl_compositor::GlCompositor;
use iris::headless_gl::HeadlessGl;
use iris::rex3::Renderer;
Expand Down Expand Up @@ -71,19 +71,14 @@ impl CaptureRenderer {
}
}

/// IRIX status strip is the bottom `STATUS_BAR_HEIGHT` rows of the visible frame.
fn status_bar_y(height: usize) -> usize {
height.saturating_sub(STATUS_BAR_HEIGHT)
}

impl Renderer for CaptureRenderer {
fn present(
&mut self,
screen: &mut Rex3Screen,
_overlay: &mut DebugOverlay,
status: &mut StatusBar,
_status: &mut StatusBar,
_sbtex: &mut StatusBarTexture,
stats: &BarStats,
_stats: &BarStats,
_need_readback: bool,
live_fb_rgb: Option<&[u32]>,
live_fb_aux: Option<&[u32]>,
Expand All @@ -93,35 +88,24 @@ impl Renderer for CaptureRenderer {
let height = screen.height;
if width == 0 || height == 0 { return; }

// Heartbeat-only refresh: the compositor sets `status_bar_only` when
// nothing in the guest display changed and only the emulator's own
// status bar would. iris-gui shows its own status chrome and never bakes
// that bar into the frame, so there's nothing to push here.
if screen.status_bar_only && self.sink.seq() > 0 {
return;
}

let needed = 2048 * 1024;
if self.last_pixels.len() < needed {
self.last_pixels.resize(needed, 0);
}

if screen.status_bar_only && self.sink.seq() > 0 {
status.update(stats.hb);
let bar_y = status_bar_y(height);
status.render(&mut self.last_pixels, width, bar_y, stats);
pack_rows(
&self.sink,
&self.last_pixels,
width,
height,
bar_y,
STATUS_BAR_HEIGHT,
&mut self.seq,
);
return;
}

let src = screen.compositor_source_from(live_fb_rgb, live_fb_aux);
self.compositor.compose_pixels(&src);
drop(src);
self.last_pixels.copy_from_slice(self.compositor.pixels());

status.update(stats.hb);
status.render(&mut self.last_pixels, width, status_bar_y(height), stats);

pack_sw_frame(&self.sink, &self.last_pixels, width, height, &mut self.seq);
}

Expand Down Expand Up @@ -158,9 +142,9 @@ impl Renderer for GlCaptureRenderer {
&mut self,
screen: &mut Rex3Screen,
_overlay: &mut DebugOverlay,
status: &mut StatusBar,
_status: &mut StatusBar,
_sbtex: &mut StatusBarTexture,
stats: &BarStats,
_stats: &BarStats,
_need_readback: bool,
live_fb_rgb: Option<&[u32]>,
live_fb_aux: Option<&[u32]>,
Expand All @@ -170,27 +154,16 @@ impl Renderer for GlCaptureRenderer {
let height = screen.height;
if width == 0 || height == 0 { return; }

let needed = 2048 * 1024;
if self.last_pixels.len() < needed {
self.last_pixels.resize(needed, 0);
}

// See CaptureRenderer::present — no baked status bar, so a heartbeat-only
// refresh has nothing to push.
if screen.status_bar_only && self.sink.seq() > 0 {
status.update(stats.hb);
let bar_y = status_bar_y(height);
status.render(&mut self.last_pixels, width, bar_y, stats);
pack_rows(
&self.sink,
&self.last_pixels,
width,
height,
bar_y,
STATUS_BAR_HEIGHT,
&mut self.seq,
);
return;
}

let needed = 2048 * 1024;
if self.last_pixels.len() < needed {
self.last_pixels.resize(needed, 0);
}
if self.rgba.len() < needed {
self.rgba.resize(needed, 0);
}
Expand All @@ -201,9 +174,6 @@ impl Renderer for GlCaptureRenderer {
self.compositor.readback_to_screen(&mut self.rgba, width, height, gl);
self.last_pixels.copy_from_slice(&self.rgba[..needed]);

status.update(stats.hb);
status.render(&mut self.last_pixels, width, status_bar_y(height), stats);

pack_sw_frame(&self.sink, &self.last_pixels, width, height, &mut self.seq);
}

Expand Down Expand Up @@ -241,38 +211,6 @@ fn pack_sw_frame(sink: &FrameSink, pixels: &[u32], width: usize, height: usize,
sink.seq.store(*seq, Ordering::Release);
}

fn pack_rows(
sink: &FrameSink,
pixels: &[u32],
width: usize,
height: usize,
start_y: usize,
row_count: usize,
seq: &mut u64,
) {
let mut frame = sink.lock();
if frame.width != width || frame.height != height || frame.rgba.is_empty() {
pack_sw_frame(sink, pixels, width, height, seq);
return;
}

frame.dirty_y = start_y as u32;
frame.dirty_h = row_count as u32;

for y in start_y..start_y + row_count.min(height.saturating_sub(start_y)) {
let src_row = &pixels[y * 2048..y * 2048 + width];
let dst_row = &mut frame.rgba[y * width * 4..(y + 1) * width * 4];
for (dst_px, &word) in dst_row.chunks_exact_mut(4).zip(src_row) {
dst_px.copy_from_slice(&(word | 0xFF00_0000).to_le_bytes());
}
}

*seq = seq.wrapping_add(1);
frame.seq = *seq;
drop(frame);
sink.seq.store(*seq, Ordering::Release);
}

/// Pick CPU or GL capture renderer based on `IRIS_GUI_GL=1`.
pub fn new_capture_renderer(sink: FrameSink) -> Box<dyn Renderer> {
if std::env::var("IRIS_GUI_GL").map(|v| v == "1").unwrap_or(false) {
Expand Down
4 changes: 4 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,10 @@ pub enum MachineProfile {
}

impl MachineProfile {
/// All selectable profiles, in display order. Single source of truth for the
/// GUI dropdowns (Config tab + New Machine dialog) so they never drift.
pub const ALL: [Self; 2] = [Self::IndyIp24, Self::Indigo2Ip22];

pub fn label(self) -> &'static str {
match self {
Self::IndyIp24 => "SGI Indy (IP24)",
Expand Down
9 changes: 9 additions & 0 deletions src/vc2_timings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ pub enum NewportResolution {
}

impl NewportResolution {
/// All selectable modes, in display order. Single source of truth for the
/// GUI dropdowns (Config tab + New Machine dialog) so they never drift.
pub const ALL: [Self; 4] = [
Self::Guest,
Self::Res1024x768,
Self::Res1280x960,
Self::Res1280x1024,
];

pub fn label(self) -> &'static str {
match self {
Self::Guest => "Guest (IRIX / setmon)",
Expand Down
Loading