summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/video/pwm.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices/video/pwm.cpp')
-rw-r--r--src/devices/video/pwm.cpp269
1 files changed, 269 insertions, 0 deletions
diff --git a/src/devices/video/pwm.cpp b/src/devices/video/pwm.cpp
new file mode 100644
index 00000000000..c54cdbc979a
--- /dev/null
+++ b/src/devices/video/pwm.cpp
@@ -0,0 +1,269 @@
+// license:BSD-3-Clause
+// copyright-holders:hap
+/*
+
+This thing is a generic helper for PWM(strobed) display elements, to prevent flickering
+and optionally handle perceived brightness levels.
+
+Common usecase is to call matrix(selmask, datamask), a collision between the 2 masks
+implies a powered-on display element (eg, a LED, or VFD sprite). The maximum matrix
+size is 64 by 64, simply due to uint64_t constraints. If a larger size is needed,
+create an array of pwm_display_device.
+
+If display elements are directly addressable, you can use write_element or write_row
+to set them. In this case it is required to call update() to apply the changes.
+
+Display element states are sent to output tags "y.x" where y is the matrix row number,
+x is the row bit. It is also sent to "y.a" for all rows. The output state is 0 for off,
+and >0 for on, depending on brightness level. If segmask is defined, it is also sent
+to "digity", for use with multi-state elements, eg. 7seg leds.
+
+If you use this device in a slot, or use multiple of them (or just don't want to use
+the default output tags), set a callback.
+
+TODO:
+- multiple brightness levels doesn't work for SVGs
+
+*/
+
+#include "emu.h"
+#include "video/pwm.h"
+
+#include <algorithm>
+
+
+DEFINE_DEVICE_TYPE(PWM_DISPLAY, pwm_display_device, "pwm_display", "PWM Display")
+
+//-------------------------------------------------
+// constructor
+//-------------------------------------------------
+
+pwm_display_device::pwm_display_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
+ device_t(mconfig, PWM_DISPLAY, tag, owner, clock),
+ m_out_x(*this, "%u.%u", 0U, 0U),
+ m_out_a(*this, "%u.a", 0U),
+ m_out_digit(*this, "digit%u", 0U),
+ m_output_x_cb(*this),
+ m_output_a_cb(*this),
+ m_output_digit_cb(*this)
+{
+ // set defaults (60hz frames, 0.5 interpolation, 1 brightness level)
+ set_refresh(attotime::from_hz(60));
+ set_interpolation(0.5);
+ set_bri_levels(0.02);
+ set_bri_minimum(0);
+ set_size(0, 0);
+ reset_segmask();
+}
+
+
+
+//-------------------------------------------------
+// device_start/reset
+//-------------------------------------------------
+
+ALLOW_SAVE_TYPE(attotime); // m_acc
+
+void pwm_display_device::device_start()
+{
+ // resolve handlers
+ m_out_x.resolve();
+ m_out_a.resolve();
+ m_out_digit.resolve();
+
+ bool cb1 = m_output_x_cb.resolve_safe();
+ bool cb2 = m_output_a_cb.resolve_safe();
+ bool cb3 = m_output_digit_cb.resolve_safe();
+ m_external_output = cb1 || cb2 || cb3;
+
+ // initialize
+ std::fill_n(m_rowdata, ARRAY_LENGTH(m_rowdata), 0);
+ std::fill_n(m_rowdata_prev, ARRAY_LENGTH(m_rowdata_prev), 0);
+ std::fill_n(*m_bri, ARRAY_LENGTH(m_bri) * ARRAY_LENGTH(m_bri[0]), 0.0);
+
+ m_frame_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(pwm_display_device::frame_tick),this));
+ m_update_time = machine().time();
+
+ m_rowsel = 0;
+ m_rowsel_prev = 0;
+
+ // register for savestates
+ save_item(NAME(m_width));
+ save_item(NAME(m_height));
+ save_item(NAME(m_framerate_set));
+ save_item(NAME(m_framerate));
+ save_item(NAME(m_interpolation));
+ save_item(NAME(m_levels));
+ save_item(NAME(m_level_min));
+
+ save_item(NAME(m_segmask));
+ save_item(NAME(m_rowsel));
+ save_item(NAME(m_rowsel_prev));
+ save_item(NAME(m_rowdata));
+ save_item(NAME(m_rowdata_prev));
+
+ save_item(NAME(m_bri));
+ save_item(NAME(m_acc));
+ save_item(NAME(m_update_time));
+}
+
+void pwm_display_device::device_reset()
+{
+ if (m_height > 64 || m_width > 64)
+ fatalerror("%s: Invalid size %d*%d, maximum is 64*64!\n", tag(), m_height, m_width);
+
+ schedule_frame();
+ m_update_time = machine().time();
+}
+
+
+
+//-------------------------------------------------
+// public handlers (most of the interface is in the .h file)
+//-------------------------------------------------
+
+pwm_display_device &pwm_display_device::set_bri_levels(double l0, double l1, double l2, double l3)
+{
+ // init brightness level(s) (if you need to set more than 4, use set_bri_one)
+ reset_bri_levels();
+ m_levels[0] = l0;
+ m_levels[1] = l1;
+ m_levels[2] = l2;
+ m_levels[3] = l3;
+
+ return *this;
+}
+
+void pwm_display_device::segmask(u64 digits, u64 mask)
+{
+ // set a segment mask per selected digit, but leave unselected ones alone
+ for (int y = 0; y < m_height; y++)
+ {
+ if (digits & 1)
+ m_segmask[y] = mask;
+ digits >>= 1;
+ }
+}
+
+void pwm_display_device::matrix_partial(u8 start, u8 height, u64 rowsel, u64 rowdata, bool upd)
+{
+ u64 selmask = (u64(1) << height) - 1;
+ rowsel &= selmask;
+ selmask <<= start;
+ m_rowsel = (m_rowsel & ~selmask) | (rowsel << start);
+
+ // update selected rows
+ u64 rowmask = (u64(1) << m_width) - 1;
+ for (int y = start; y < (start + height) && y < m_height; y++)
+ {
+ m_rowdata[y] = (rowsel & 1) ? (rowdata & rowmask) : 0;
+ rowsel >>= 1;
+ }
+
+ if (upd)
+ update();
+}
+
+void pwm_display_device::update()
+{
+ // call this every time after m_rowdata is changed (automatic with matrix)
+ const attotime now = machine().time();
+ const attotime diff = (m_update_time >= now) ? attotime::zero : now - m_update_time;
+
+ u64 sel = m_rowsel_prev;
+ m_rowsel_prev = m_rowsel;
+
+ // accumulate active time
+ for (int y = 0; y < m_height; y++)
+ {
+ u64 row = m_rowdata_prev[y];
+ m_rowdata_prev[y] = m_rowdata[y];
+
+ if (diff != attotime::zero)
+ {
+ if (sel & 1)
+ m_acc[y][m_width] += diff;
+
+ for (int x = 0; x < m_width; x++)
+ {
+ if (row & 1)
+ m_acc[y][x] += diff;
+ row >>= 1;
+ }
+ }
+ sel >>= 1;
+ }
+
+ m_update_time = now;
+}
+
+
+
+//-------------------------------------------------
+// internal handlers
+//-------------------------------------------------
+
+void pwm_display_device::schedule_frame()
+{
+ std::fill_n(*m_acc, m_height * ARRAY_LENGTH(m_acc[0]), attotime::zero);
+
+ m_framerate = m_framerate_set;
+ m_frame_timer->adjust(m_framerate);
+}
+
+TIMER_CALLBACK_MEMBER(pwm_display_device::frame_tick)
+{
+ update(); // final timeslice
+
+ const double frame_time = m_framerate.as_double();
+ const double factor0 = m_interpolation;
+ const double factor1 = 1.0 - factor0;
+
+ for (int y = 0; y < m_height; y++)
+ {
+ u64 row = 0;
+
+ for (int x = 0; x <= m_width; x++)
+ {
+ // determine brightness level
+ double bri = m_bri[y][x] * factor1 + (m_acc[y][x].as_double() / frame_time) * factor0;
+ if (bri > 1.0) bri = 1.0; // shouldn't happen
+ m_bri[y][x] = bri;
+
+ u8 level;
+ for (level = 0; bri > m_levels[level]; level++) { ; }
+
+ // output to y.x, or y.a when always-on
+ if (x != m_width)
+ {
+ if (level > m_level_min)
+ row |= (u64(1) << x);
+
+ if (m_external_output)
+ m_output_x_cb(x << 6 | y, level);
+ else
+ m_out_x[y][x] = level;
+ }
+ else
+ {
+ if (m_external_output)
+ m_output_a_cb(y, level);
+ else
+ m_out_a[y] = level;
+ }
+ }
+
+ // output to digity (does not support brightness levels)
+ if (m_segmask[y] != 0)
+ {
+ row &= m_segmask[y];
+
+ if (m_external_output)
+ m_output_digit_cb(y, row);
+ else
+ m_out_digit[y] = row;
+ }
+ }
+
+ schedule_frame();
+}