// license:BSD-3-Clause // copyright-holders:Couriersud /*************************************************************************** fixfreq.h 2013 Couriersud Fixed frequency monochrome monitor emulation The driver is intended for drivers which provide an analog video signal. VSYNC and HSYNC levels are used to create the bitmap. ***************************************************************************/ #include "emu.h" #include "rendutil.h" #include "fixfreq.h" //#define VERBOSE 1 #include "logmacro.h" #include /*************************************************************************** Fixed frequency monitor ***************************************************************************/ // device type definition DEFINE_DEVICE_TYPE(FIXFREQ, fixedfreq_device, "fixfreq", "Fixed-Frequency Monochrome Monitor") void fixedfreq_monitor_state::update_sync_channel(const time_type &time, const double newval) { const time_type delta_time = time - m_last_sync_time; const int last_vsync = m_sig_vsync; const int last_comp = m_sig_composite; m_vsync_filter += ((double) last_comp - m_vsync_filter) * (1.0 - exp(-delta_time * m_vsync_filter_timeconst)); m_sig_composite = (newval < m_desc.m_sync_threshold) ? 1 : 0 ; m_sig_vsync = (m_vsync_filter > m_vsync_threshold) ? 1 : 0; if (!last_vsync && m_sig_vsync) { //LOG("VSYNC %d %d\n", m_last_x, m_last_y + m_sig_field); m_last_y = m_desc.m_vbackporch - m_desc.m_vsync; m_intf.vsync_start_cb(std::max(time - m_last_vsync_time, m_min_frame_period)); m_last_vsync_time = time; } else if (last_vsync && !m_sig_vsync) { m_sig_field = last_comp; /* force false-progressive */ m_sig_field = (m_sig_field ^ 1) ^ last_comp; /* if there is no field switch, auto switch */ //LOG("Field: %d\n", m_sig_field); } if (!last_comp && m_sig_composite) { /* TODO - time since last hsync and field detection */ //LOG("HSYNC up %d\n", m_last_x); // FIXME: pixels > 50 filters some spurious hysnc on line 27 in breakout if (!m_sig_vsync && (m_last_x > m_desc.m_hscale * 100)) { m_last_y += m_desc.m_fieldcount; m_last_x = 0; m_line_time = time; } //if (m_last_y == 27) printf("HSYNC up %d %d\n", m_last_y, pixels); } else if (last_comp && !m_sig_composite) { /* falling composite */ //LOG("HSYNC down %f %d %f\n", time * 1e6, m_last_x, m_sync_signal); } m_sync_signal = newval; m_last_sync_time = time; } void fixedfreq_monitor_state::update_bm(const time_type &time) { const float pixels = (time - m_line_time) * static_cast(m_desc.m_hscale) / m_clock_period; const int has_fields = (m_desc.m_fieldcount > 1) ? 1: 0; uint32_t col(0xffff0000); // Mark sync areas if (m_sync_signal >= m_desc.m_sync_threshold) col = m_col; m_fragments.push_back({static_cast(m_last_y + m_sig_field * has_fields), m_last_x, pixels, col}); //m_intf.plot_hline(m_last_x, m_last_y + m_sig_field * has_fields, pixels, col); m_last_x = pixels; } void fixedfreq_monitor_state::update_composite_monochrome(const time_type &time, const double data) { update_bm(time); update_sync_channel(time, data); int colv = (int) ((data - m_desc.m_sync_threshold) * m_desc.m_gain * 255.0); if (colv > 255) colv = 255; if (colv < 0) m_col = 0xffff0000; else m_col = 0xff000000 | (colv<<16) | (colv<<8) | colv; } void fixedfreq_monitor_state::update_red(const time_type &time, const double data) { update_bm(time); int colv = (int) ((data - m_desc.m_sync_threshold) * m_desc.m_gain * 255.0); if (colv > 255) colv = 255; if (colv < 0) colv = 0; m_col = (m_col & 0xff00ffff) | (colv<<16); } void fixedfreq_monitor_state::update_green(const time_type &time, const double data) { update_bm(time); //update_sync_channel(ctime, data); int colv = (int) ((data - m_desc.m_sync_threshold) * m_desc.m_gain * 255.0); if (colv > 255) colv = 255; if (colv < 0) colv = 0; m_col = (m_col & 0xffff00ff) | (colv<<8); } void fixedfreq_monitor_state::update_blue(const time_type &time, const double data) { update_bm(time); //update_sync_channel(ctime, data); int colv = (int) ((data - m_desc.m_sync_threshold) * m_desc.m_gain * 255.0); if (colv > 255) colv = 255; if (colv < 0) colv = 0; m_col = (m_col & 0xffffff00) | colv; } void fixedfreq_monitor_state::update_sync(const time_type &time, const double data) { update_bm(time); update_sync_channel(time, data); } fixedfreq_device::fixedfreq_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, type, tag, owner, clock), device_video_interface(mconfig, *this, false), m_htotal(0), m_vtotal(0), m_refresh_period(time_type(0)), m_monitor(), m_state(m_monitor, *this) { } fixedfreq_device::fixedfreq_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : fixedfreq_device(mconfig, FIXFREQ, tag, owner, clock) { } void fixedfreq_device::device_config_complete() { if (!has_screen()) return; if (!screen().refresh_attoseconds()) screen().set_raw(m_monitor.m_monitor_clock, m_monitor.m_hbackporch, 0, m_monitor.m_hbackporch, m_monitor.m_vbackporch, 0, m_monitor.m_vbackporch); if (!screen().has_screen_update()) screen().set_screen_update(*this, FUNC(fixedfreq_device::screen_update)); } void fixedfreq_device::device_start() { m_refresh_period = time_type(0); m_htotal = m_monitor.m_hbackporch; m_vtotal = m_monitor.m_vbackporch; m_state.start(); // FIXME: will be done by netlist going forward save_item(NAME(m_state.m_sync_signal)); save_item(NAME(m_state.m_last_x)); save_item(NAME(m_state.m_last_y)); save_item(NAME(m_state.m_last_sync_time)); save_item(NAME(m_state.m_line_time)); save_item(NAME(m_state.m_last_hsync_time)); save_item(NAME(m_state.m_last_vsync_time)); save_item(NAME(m_refresh_period)); save_item(NAME(m_state.m_clock_period)); /* sync separator */ save_item(NAME(m_state.m_vsync_filter)); save_item(NAME(m_state.m_vsync_threshold)); save_item(NAME(m_state.m_vsync_filter_timeconst)); save_item(NAME(m_state.m_sig_vsync)); save_item(NAME(m_state.m_sig_composite)); save_item(NAME(m_state.m_sig_field)); } void fixedfreq_device::device_reset() { m_state.reset(); } void fixedfreq_device::device_post_load() { //recompute_parameters(); } uint32_t fixedfreq_device::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect) { if (screen.screen_type() == SCREEN_TYPE_RASTER) { for (auto &f : m_state.m_fragments) if (f.y < bitmap.height()) bitmap.plot_box(f.x, f.y, f.xr - f.x, 1, f.col); } else if (screen.screen_type() == SCREEN_TYPE_VECTOR) { constexpr const uint32_t flags(PRIMFLAG_ANTIALIAS(1) | PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_VECTOR(1)); const rectangle &visarea = screen.visible_area(); float xscale = 1.0f / visarea.width(); float yscale = 1.0f / visarea.height(); float xoffs = (float)visarea.min_x; float yoffs = (float)visarea.min_y; screen.container().empty(); screen.container().add_rect(0.0f, 0.0f, 1.0f, 1.0f, rgb_t(0xff,0x00,0x00,0x00), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_VECTORBUF(1)); for (auto &f : m_state.m_fragments) { const float x0((f.x - xoffs) * xscale); const float y0((f.y - yoffs) * yscale); const float x1((f.xr - xoffs) * xscale); const float y1((f.y + 1.0f - yoffs) * yscale); screen.container().add_rect( x0, y0, x1, y1, (0xff << 24) | (f.col & 0xffffff), flags); } } m_state.m_fragments.clear(); return 0; } void fixedfreq_device::vsync_start_cb(double refresh_time) { // toggle bitmap //m_cur_bm ^= 1; rectangle visarea(m_monitor.minh(), m_monitor.maxh(), m_monitor.minv(), m_monitor.maxv()); m_refresh_period = refresh_time; screen().configure(m_htotal * m_monitor.m_hscale, m_vtotal, visarea, DOUBLE_TO_ATTOSECONDS(m_refresh_period)); } NETDEV_ANALOG_CALLBACK_MEMBER(fixedfreq_device::update_composite_monochrome) { // double is good enough for this exercise; const time_type ctime = time.as_double(); m_state.update_composite_monochrome(ctime, data); } NETDEV_ANALOG_CALLBACK_MEMBER(fixedfreq_device::update_red) { // double is good enough for this exercise; const time_type ctime = time.as_double(); m_state.update_red(ctime, data); } NETDEV_ANALOG_CALLBACK_MEMBER(fixedfreq_device::update_green) { // double is good enough for this exercise; const time_type ctime = time.as_double(); m_state.update_green(ctime, data); } NETDEV_ANALOG_CALLBACK_MEMBER(fixedfreq_device::update_blue) { // double is good enough for this exercise; const time_type ctime = time.as_double(); m_state.update_blue(ctime, data); } NETDEV_ANALOG_CALLBACK_MEMBER(fixedfreq_device::update_sync) { // double is good enough for this exercise; const time_type ctime = time.as_double(); m_state.update_sync(ctime, data); } /***************************************************************************/