1 // license:BSD-3-Clause
2 // copyright-holders:Wilbert Pol
3 /**********************************************************************
4 
5     Hudson/NEC HuC6260 Video Colour Encoder
6 
7     The HuC6260 takes a stream of pixel data, looks up the correct
8     palette data and outputs a video stream.
9 
10     The HuC6260 generates the tv control signals. A full line lasts
11     1365 "master" cycles (typically at 21.47727Mhz).
12 
13     HSync is low for 237 and high for 1128 master cycles.
14     VSync is low for 4095 master cycles (3 lines).
15     VSync changes 30 master cycles after HSync would go low.
16 
17 **********************************************************************/
18 
19 #include "emu.h"
20 #include "huc6260.h"
21 
22 #include "screen.h"
23 
24 //#define VERBOSE 1
25 #include "logmacro.h"
26 
27 
28 #define HUC6260_HSYNC_LENGTH    237
29 #define HUC6260_HSYNC_START     ( huc6260_device::WPF - HUC6260_HSYNC_LENGTH )
30 
31 
32 constexpr unsigned huc6260_device::PALETTE_SIZE;
33 constexpr unsigned huc6260_device::WPF;
34 constexpr unsigned huc6260_device::LPF;
35 
palette_init()36 void huc6260_device::palette_init()
37 {
38 	for (int i = 0; i < 512; i++)
39 	{
40 		int r = pal3bit( ( i >> 3 ) & 7 );
41 		int g = pal3bit( ( i >> 6 ) & 7 );
42 		int b = pal3bit( ( i      ) & 7 );
43 		int y = ( ( 66 * r + 129 * g + 25 * b + 128 ) >> 8 ) + 16;
44 
45 		set_pen_color(i, r, g, b);
46 		set_pen_color(512 + i, y, y, y);
47 	}
48 }
49 
50 
51 DEFINE_DEVICE_TYPE(HUC6260, huc6260_device, "huc6260", "Hudson HuC6260 VCE")
52 
53 
huc6260_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)54 huc6260_device::huc6260_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
55 	:   device_t(mconfig, HUC6260, tag, owner, clock),
56 		device_palette_interface(mconfig, *this),
57 		device_video_interface(mconfig, *this),
58 		m_next_pixel_data_cb(*this),
59 		m_time_til_next_event_cb(*this),
60 		m_vsync_changed_cb(*this),
61 		m_hsync_changed_cb(*this)
62 {
63 }
64 
65 
device_timer(emu_timer & timer,device_timer_id id,int param,void * ptr)66 void huc6260_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
67 {
68 	int vpos = screen().vpos();
69 	int hpos = screen().hpos();
70 	int h = m_last_h;
71 	int v = m_last_v;
72 	uint16_t *bitmap_line = &m_bmp->pix(v);
73 
74 	while ( h != hpos || v != vpos )
75 	{
76 		if ( m_pixel_clock == 0 )
77 		{
78 			g_profiler.start( PROFILER_VIDEO );
79 			/* Get next pixel information */
80 			m_pixel_data = m_next_pixel_data_cb();
81 			g_profiler.stop();
82 		}
83 
84 		bitmap_line[ h ] = m_palette[ m_pixel_data ] | m_greyscales;
85 		m_pixel_clock = ( m_pixel_clock + 1 ) % m_pixels_per_clock;
86 		h = ( h + 1 ) % WPF;
87 
88 		switch( h )
89 		{
90 		case HUC6260_HSYNC_START:       /* Start of HSync */
91 			m_hsync_changed_cb( 0 );
92 //          if ( v == 0 )
93 //          {
94 //              /* Check if the screen should be resized */
95 //              m_height = LPF - ( m_blur ? 1 : 0 );
96 //              if ( m_height != video_screen_get_height( m_screen ) )
97 //              {
98 //                  rectangle visible_area;
99 //
100 //                  /* TODO: Set proper visible area parameters */
101 //                  visible_area.min_x = 64;
102 //                  visible_area.min_y = 18;
103 //                  visible_area.max_x = 64 + 1024 + 64 - 1;
104 //                  visible_area.max_y = 18 + 242 - 1;
105 //
106 //                  video_screen_configure( m_screen, WPF, m_height, &visible_area, HZ_TO_ATTOSECONDS( device->clock / ( WPF * m_height ) ) );
107 //              }
108 //          }
109 			break;
110 
111 		case 0:     /* End of HSync */
112 			m_hsync_changed_cb( 1 );
113 			m_pixel_clock = 0;
114 			v = ( v + 1 ) % m_height;
115 			bitmap_line = &m_bmp->pix(v);
116 			break;
117 
118 		case HUC6260_HSYNC_START + 30:      /* End/Start of VSync */
119 			if ( v>= m_height - 4 )
120 			{
121 				m_vsync_changed_cb( ( v >= m_height - 4 && v < m_height - 1 ) ? 0 : 1 );
122 			}
123 			break;
124 		}
125 	}
126 
127 	m_last_h = h;
128 	m_last_v = v;
129 
130 	/* Reschedule timer */
131 	if ( m_last_h < HUC6260_HSYNC_START )
132 	{
133 		/* Next event is start of HSync signal */
134 		v = m_last_v;
135 		h = HUC6260_HSYNC_START;
136 	}
137 	else if ( ( m_last_v == m_height - 4 || m_last_v == m_height - 1 ) && m_last_h < HUC6260_HSYNC_START + 30 )
138 	{
139 		/* Next event is start/end of VSync signal */
140 		v = m_last_v;
141 		h = HUC6260_HSYNC_START + 30;
142 	}
143 	else
144 	{
145 		/* Next event is end of HSync signal */
146 		v = ( m_last_v + 1 ) % m_height;
147 		h = 0;
148 	}
149 
150 	/* Ask our slave device for time until next possible event */
151 	{
152 		uint16_t next_event_clocks = m_time_til_next_event_cb();
153 		int event_hpos, event_vpos;
154 
155 		/* Adjust for pixel clocks per pixel */
156 		next_event_clocks *= m_pixels_per_clock;
157 
158 		/* Adjust for clocks left to go for current pixel */
159 		next_event_clocks += ( m_pixels_per_clock - ( m_pixel_clock + 1 ) );
160 
161 		event_hpos = hpos + next_event_clocks;
162 		event_vpos = vpos;
163 		while ( event_hpos > WPF )
164 		{
165 			event_vpos += 1;
166 			event_hpos -= WPF;
167 		}
168 
169 		if ( event_vpos < v || ( event_vpos == v && event_hpos <= h ) )
170 		{
171 			if ( event_vpos > vpos || ( event_vpos == vpos && event_hpos > hpos ) )
172 			{
173 				v = event_vpos;
174 				h = event_hpos;
175 			}
176 		}
177 	}
178 
179 	m_timer->adjust( screen().time_until_pos( v, h ) );
180 }
181 
182 
video_update(bitmap_ind16 & bitmap,const rectangle & cliprect)183 void huc6260_device::video_update( bitmap_ind16 &bitmap, const rectangle &cliprect )
184 {
185 	copybitmap( bitmap, *m_bmp, 0, 0, 0, 0, cliprect );
186 }
187 
188 
189 // the battlera arcade board reads/writes the palette directly
palette_direct_read(offs_t offset)190 uint8_t huc6260_device::palette_direct_read(offs_t offset)
191 {
192 	if (!(offset&1)) return m_palette[offset>>1];
193 	else return m_palette[offset >> 1] >> 8;
194 }
195 
palette_direct_write(offs_t offset,uint8_t data)196 void huc6260_device::palette_direct_write(offs_t offset, uint8_t data)
197 {
198 	if (!(offset&1)) m_palette[offset>>1] = (m_palette[offset>>1] & 0xff00) | data;
199 	else m_palette[offset>>1] = (m_palette[offset>>1] & 0x00ff) | (data<<8);
200 }
201 
read(offs_t offset)202 uint8_t huc6260_device::read(offs_t offset)
203 {
204 	uint8_t data = 0xFF;
205 
206 	switch ( offset & 7 )
207 	{
208 		case 0x04:  /* Color table data LSB */
209 			data = m_palette[ m_address ] & 0xFF;
210 			break;
211 
212 		case 0x05:  /* Color table data MSB */
213 			data = 0xFE | ( m_palette[ m_address ] >> 8 );
214 
215 			/* Increment internal address */
216 			m_address = ( m_address + 1 ) & 0x1FF;
217 			break;
218 	}
219 
220 	return data;
221 }
222 
223 
write(offs_t offset,uint8_t data)224 void huc6260_device::write(offs_t offset, uint8_t data)
225 {
226 	switch ( offset & 7 )
227 	{
228 		case 0x00:  /* Control register */
229 			m_greyscales = (data & 0x80) << 2; // setup the greyscale base
230 			m_blur = data & 0x04;
231 			m_pixels_per_clock = ( data & 0x02 ) ? 2 : ( ( data & 0x01 ) ? 3 : 4 );
232 			break;
233 
234 		case 0x02:  /* Color table address LSB */
235 			m_address = ( ( m_address & 0xFF00 ) | data ) & 0x1FF;
236 			break;
237 
238 		case 0x03:  /* Color table address MSB */
239 			m_address = ( ( m_address & 0x00FF ) | ( data << 8 ) ) & 0x1FF;
240 			break;
241 
242 		case 0x04:  /* Color table data LSB */
243 			m_palette[ m_address ] = ( ( m_palette[ m_address ] & 0xFF00 ) | data ) & 0x1FF;
244 			break;
245 
246 		case 0x05:  /* Color table data MSB */
247 			m_palette[ m_address ] = ( ( m_palette[ m_address ] & 0x00FF ) | ( data << 8 ) ) & 0x1FF;
248 
249 			/* Increment internal address */
250 			m_address = ( m_address + 1 ) & 0x1FF;
251 			break;
252 	}
253 }
254 
255 
device_start()256 void huc6260_device::device_start()
257 {
258 	m_timer = timer_alloc();
259 	m_bmp = std::make_unique<bitmap_ind16>(WPF, LPF);
260 
261 	/* Resolve callbacks */
262 	m_hsync_changed_cb.resolve();
263 	m_vsync_changed_cb.resolve();
264 	m_next_pixel_data_cb.resolve();
265 	m_time_til_next_event_cb.resolve();
266 
267 	/* We want to have a valid screen and valid callbacks */
268 	assert( ! m_hsync_changed_cb.isnull() );
269 	assert( ! m_vsync_changed_cb.isnull() );
270 	assert( ! m_next_pixel_data_cb.isnull() );
271 	assert( ! m_time_til_next_event_cb.isnull() );
272 
273 	palette_init();
274 
275 	save_item(NAME(m_last_h));
276 	save_item(NAME(m_last_v));
277 	save_item(NAME(m_height));
278 	save_item(NAME(m_palette));
279 	save_item(NAME(m_address));
280 	save_item(NAME(m_greyscales));
281 	save_item(NAME(m_blur));
282 	save_item(NAME(m_pixels_per_clock));
283 	save_item(NAME(m_pixel_data));
284 	save_item(NAME(m_pixel_clock));
285 }
286 
287 
device_reset()288 void huc6260_device::device_reset()
289 {
290 	m_address = 0;
291 	m_greyscales = 0;
292 	m_blur = 0;
293 	m_pixels_per_clock = 4;
294 	m_height = 263;
295 	m_pixel_clock = 0;
296 	memset(m_palette, 0x00, sizeof(m_palette));
297 
298 	m_last_v = screen().vpos();
299 	m_last_h = screen().hpos();
300 	m_timer->adjust( screen().time_until_pos( ( screen().vpos() + 1 ) % 263, 0 ) );
301 }
302