summaryrefslogblamecommitdiffstatshomepage
path: root/src/devices/video/pwm.cpp
blob: 50b608759ef54da2163fdcd2abb8ed8d8db16e8f (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12











                                                                                       
                                                                                         









                                                                                       




                                                                                         

























                                                                                                                         
                       

                                           
                             
                           
                             









                                                   


                                       



                                                                                                              
 






                                      


















                                                                                                                             
                                     







                                        
                                       

                                     













                                                                                                    






















                                                                                    






                                                                                                  



                           



                     
                                                                         







                                                                                 

                     





































































                                                                                               



                                                          












                                                                     







                                                                                                              

                                             
























                                                                             
                                                                                 












                                                          
// 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 also 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.

Brightness tresholds (0.0 to 1.0) indicate how long an element was powered on in the last
frame, eg. 0.01 means a minimum on-time for 1%. Some games use two levels of brightness
by strobing elements longer.


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
	set_refresh(attotime::from_hz(60));
	set_interpolation(0.5);
	set_bri_levels(0.01);
	set_bri_minimum(0);
	set_bri_maximum(0.0);
	set_size(0, 0);
	reset_segmask();
}



//-------------------------------------------------
//  device_start/reset
//-------------------------------------------------

void pwm_display_device::device_start()
{
	// resolve handlers
	m_external_output = !m_output_x_cb.isnull() || !m_output_a_cb.isnull() || !m_output_digit_cb.isnull();
	m_output_x_cb.resolve_safe();
	m_output_a_cb.resolve_safe();
	m_output_digit_cb.resolve_safe();

	if (!m_external_output)
	{
		m_out_x.resolve();
		m_out_a.resolve();
		m_out_digit.resolve();
	}

	// 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_level_max));

	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_update_time));
	save_item(NAME(m_acc_attos));
	save_item(NAME(m_acc_secs));
}

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();
}



//-------------------------------------------------
//  custom savestate handling (MAME doesn't save array of attotime)
//-------------------------------------------------

void pwm_display_device::device_pre_save()
{
	for (int y = 0; y < ARRAY_LENGTH(m_acc); y++)
		for (int x = 0; x < ARRAY_LENGTH(m_acc[0]); x++)
		{
			m_acc_attos[y][x] = m_acc[y][x].attoseconds();
			m_acc_secs[y][x] = m_acc[y][x].seconds();
		}
}

void pwm_display_device::device_post_load()
{
	for (int y = 0; y < ARRAY_LENGTH(m_acc); y++)
		for (int x = 0; x < ARRAY_LENGTH(m_acc[0]); x++)
			m_acc[y][x] = attotime(m_acc_secs[y][x], m_acc_attos[y][x]);
}



//-------------------------------------------------
//  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();
	set_bri_one(0, l0);
	set_bri_one(1, l1);
	set_bri_one(2, l2);
	set_bri_one(3, l3);

	return *this;
}

pwm_display_device &pwm_display_device::set_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;
	}

	return *this;
}

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)
{
	const double frame_time = m_framerate.as_double();
	const double factor0 = m_interpolation;
	const double factor1 = 1.0 - factor0;

	// determine brightness cutoff
	double cutoff = m_level_max;
	if (cutoff == 0.0)
	{
		u8 level;
		for (level = 1; m_levels[level] < 1.0; level++) { ; }
		cutoff = 4 * m_levels[level - 1];
	}
	if (cutoff > 1.0)
		cutoff = 1.0;

	update(); // final timeslice

	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 > cutoff)
				bri = cutoff;
			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 multiple 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();
}