1 // license:BSD-3-Clause
2 // copyright-holders:Roberto Ventura, Leandro Dardini, Yochizo, Nicola Salmoria
3 /******************************************************************************
4 
5     UPL "sprite framebuffer" hardware
6 
7     Functions to emulate the video hardware
8 
9 ******************************************************************************/
10 
11 #include "emu.h"
12 #include "includes/ninjakd2.h"
13 
14 /*************************************
15  *
16  *  Callbacks for the TileMap code
17  *
18  *************************************/
19 
TILE_GET_INFO_MEMBER(ninjakd2_state::get_fg_tile_info)20 TILE_GET_INFO_MEMBER(ninjakd2_state::get_fg_tile_info)
21 {
22 	int const lo = m_fg_videoram[(tile_index << 1)];
23 	int const hi = m_fg_videoram[(tile_index << 1) | 1];
24 	int const tile = ((hi & 0xc0) << 2) | lo;
25 	int const flipyx = (hi & 0x30) >> 4;
26 	int const color = hi & 0x0f;
27 
28 	tileinfo.set(0,
29 			tile,
30 			color,
31 			TILE_FLIPYX(flipyx));
32 }
33 
TILE_GET_INFO_MEMBER(ninjakd2_state::ninjakd2_get_bg_tile_info)34 TILE_GET_INFO_MEMBER(ninjakd2_state::ninjakd2_get_bg_tile_info)
35 {
36 	int const lo = m_bg_videoram[(tile_index << 1)];
37 	int const hi = m_bg_videoram[(tile_index << 1) | 1];
38 	int const tile = ((hi & 0xc0) << 2) | lo;
39 	int const flipyx = (hi & 0x30) >> 4;
40 	int const color = hi & 0x0f;
41 
42 	tileinfo.set(2,
43 			tile,
44 			color,
45 			TILE_FLIPYX(flipyx));
46 }
47 
TILE_GET_INFO_MEMBER(mnight_state::mnight_get_bg_tile_info)48 TILE_GET_INFO_MEMBER(mnight_state::mnight_get_bg_tile_info)
49 {
50 	int const lo = m_bg_videoram[(tile_index << 1)];
51 	int const hi = m_bg_videoram[(tile_index << 1) | 1];
52 	int const tile = ((hi & 0x10) << 6) | ((hi & 0xc0) << 2) | lo;
53 	int const flipy = (hi & 0x20) >> 5;
54 	int const color = hi & 0x0f;
55 
56 	tileinfo.set(2,
57 			tile,
58 			color,
59 			flipy ? TILE_FLIPY : 0);
60 }
61 
TILEMAP_MAPPER_MEMBER(robokid_state::robokid_bg_scan)62 TILEMAP_MAPPER_MEMBER(robokid_state::robokid_bg_scan)
63 {
64 	/* logical (col,row) -> memory offset */
65 	return (col & 0x0f) | ((row & 0x1f) << 4) | ((col & 0x10) << 5);
66 }
67 
TILEMAP_MAPPER_MEMBER(omegaf_state::omegaf_bg_scan)68 TILEMAP_MAPPER_MEMBER(omegaf_state::omegaf_bg_scan)
69 {
70 	/* logical (col,row) -> memory offset */
71 	return (col & 0x0f) | ((row & 0x1f) << 4) | ((col & 0x70) << 5);
72 }
73 
74 template<int Layer>
TILE_GET_INFO_MEMBER(robokid_state::robokid_get_bg_tile_info)75 TILE_GET_INFO_MEMBER(robokid_state::robokid_get_bg_tile_info)
76 {
77 	int const lo = m_robokid_bg_videoram[Layer][(tile_index << 1)];
78 	int const hi = m_robokid_bg_videoram[Layer][(tile_index << 1) | 1];
79 	int const tile = ((hi & 0x10) << 7) | ((hi & 0x20) << 5) | ((hi & 0xc0) << 2) | lo;
80 	int const color = hi & 0x0f;
81 
82 	tileinfo.set(Layer + 2,
83 			tile,
84 			color,
85 			0);
86 }
87 
88 
89 /*************************************
90  *
91  *  Video system start
92  *
93  *************************************/
94 
video_init_common()95 void ninjakd2_state::video_init_common()
96 {
97 	m_fg_tilemap = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(*this, FUNC(ninjakd2_state::get_fg_tile_info)), TILEMAP_SCAN_ROWS, 8, 8, 32, 32);
98 	m_fg_tilemap->set_transparent_pen(0xf);
99 
100 	m_screen->register_screen_bitmap(m_sprites_bitmap);
101 
102 	m_sprites_updated = 0;
103 	m_robokid_sprites = 0;
104 	m_vram_bank_mask = 0;
105 
106 	// register for save states
107 	save_item(NAME(m_sprites_updated));
108 	save_item(NAME(m_next_sprite_overdraw_enabled));
109 }
110 
video_init_banked(uint32_t vram_alloc_size)111 void robokid_state::video_init_banked(uint32_t vram_alloc_size)
112 {
113 	// create video ram
114 	if (vram_alloc_size)
115 	{
116 		for (int i = 0; i < 3; i++)
117 		{
118 			m_robokid_bg_videoram[i] = make_unique_clear<uint8_t[]>(vram_alloc_size);
119 
120 			save_pointer(NAME(m_robokid_bg_videoram[i]), vram_alloc_size, i);
121 		}
122 		m_vram_bank_mask = (vram_alloc_size >> 10) - 1;
123 	}
124 
125 	save_item(NAME(m_robokid_bg_bank));
126 }
127 
128 static bool stencil_ninjakd2( uint16_t pal );
129 static bool stencil_mnight(   uint16_t pal );
130 static bool stencil_arkarea(  uint16_t pal );
131 static bool stencil_robokid(  uint16_t pal );
132 static bool stencil_omegaf(   uint16_t pal );
133 
video_start()134 void ninjakd2_state::video_start()
135 {
136 	video_init_common();
137 
138 	m_bg_tilemap = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(*this, FUNC(ninjakd2_state::ninjakd2_get_bg_tile_info)), TILEMAP_SCAN_ROWS, 16, 16, 32, 32);
139 
140 	m_stencil_compare_function = stencil_ninjakd2;
141 }
142 
VIDEO_START_MEMBER(mnight_state,mnight)143 VIDEO_START_MEMBER(mnight_state,mnight)
144 {
145 	video_init_common();
146 
147 	m_bg_tilemap = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(*this, FUNC(mnight_state::mnight_get_bg_tile_info)), TILEMAP_SCAN_ROWS, 16, 16, 32, 32);
148 
149 	m_stencil_compare_function = stencil_mnight;
150 }
151 
VIDEO_START_MEMBER(mnight_state,arkarea)152 VIDEO_START_MEMBER(mnight_state,arkarea)
153 {
154 	video_init_common();
155 
156 	m_bg_tilemap = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(*this, FUNC(mnight_state::mnight_get_bg_tile_info)), TILEMAP_SCAN_ROWS, 16, 16, 32, 32);
157 
158 	m_stencil_compare_function = stencil_arkarea;
159 }
160 
VIDEO_START_MEMBER(robokid_state,robokid)161 VIDEO_START_MEMBER(robokid_state,robokid)
162 {
163 	video_init_common();
164 	video_init_banked(0x0800);
165 	m_robokid_sprites = 1;
166 
167 	m_robokid_tilemap[0] = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(*this, FUNC(robokid_state::robokid_get_bg_tile_info<0>)), tilemap_mapper_delegate(*this, FUNC(robokid_state::robokid_bg_scan)), 16, 16, 32, 32);
168 	m_robokid_tilemap[1] = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(*this, FUNC(robokid_state::robokid_get_bg_tile_info<1>)), tilemap_mapper_delegate(*this, FUNC(robokid_state::robokid_bg_scan)), 16, 16, 32, 32);
169 	m_robokid_tilemap[2] = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(*this, FUNC(robokid_state::robokid_get_bg_tile_info<2>)), tilemap_mapper_delegate(*this, FUNC(robokid_state::robokid_bg_scan)), 16, 16, 32, 32);
170 
171 	m_robokid_tilemap[1]->set_transparent_pen(0xf);
172 	m_robokid_tilemap[2]->set_transparent_pen(0xf);
173 
174 	m_stencil_compare_function = stencil_robokid;
175 }
176 
VIDEO_START_MEMBER(omegaf_state,omegaf)177 VIDEO_START_MEMBER(omegaf_state,omegaf)
178 {
179 	video_init_common();
180 	video_init_banked(0x2000);
181 	m_robokid_sprites = 1;
182 
183 	m_robokid_tilemap[0] = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(*this, FUNC(omegaf_state::robokid_get_bg_tile_info<0>)), tilemap_mapper_delegate(*this, FUNC(omegaf_state::omegaf_bg_scan)), 16, 16, 128, 32);
184 	m_robokid_tilemap[1] = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(*this, FUNC(omegaf_state::robokid_get_bg_tile_info<1>)), tilemap_mapper_delegate(*this, FUNC(omegaf_state::omegaf_bg_scan)), 16, 16, 128, 32);
185 	m_robokid_tilemap[2] = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(*this, FUNC(omegaf_state::robokid_get_bg_tile_info<2>)), tilemap_mapper_delegate(*this, FUNC(omegaf_state::omegaf_bg_scan)), 16, 16, 128, 32);
186 
187 	m_robokid_tilemap[0]->set_transparent_pen(0xf);
188 	m_robokid_tilemap[1]->set_transparent_pen(0xf);
189 	m_robokid_tilemap[2]->set_transparent_pen(0xf);
190 
191 	m_stencil_compare_function = stencil_omegaf;
192 }
193 
194 
195 
196 /*************************************
197  *
198  *  Memory handlers
199  *
200  *************************************/
201 
ninjakd2_bgvideoram_w(offs_t offset,uint8_t data)202 void ninjakd2_state::ninjakd2_bgvideoram_w(offs_t offset, uint8_t data)
203 {
204 	m_bg_videoram[offset] = data;
205 	m_bg_tilemap->mark_tile_dirty(offset >> 1);
206 }
207 
ninjakd2_fgvideoram_w(offs_t offset,uint8_t data)208 void ninjakd2_state::ninjakd2_fgvideoram_w(offs_t offset, uint8_t data)
209 {
210 	m_fg_videoram[offset] = data;
211 	m_fg_tilemap->mark_tile_dirty(offset >> 1);
212 }
213 
bg_ctrl(int offset,int data,tilemap_t * tilemap)214 void ninjakd2_state::bg_ctrl(int offset, int data, tilemap_t* tilemap)
215 {
216 	int scrollx = tilemap->scrollx(0);
217 	int scrolly = tilemap->scrolly(0);
218 
219 	switch (offset)
220 	{
221 		case 0: scrollx = ((scrollx & ~0xff) | data);        break;
222 		case 1: scrollx = ((scrollx &  0xff) | (data << 8)); break;
223 		case 2: scrolly = ((scrolly & ~0xff) | data);        break;
224 		case 3: scrolly = ((scrolly &  0xff) | (data << 8)); break;
225 		case 4: tilemap->enable(data & 1); break;
226 	}
227 
228 	tilemap->set_scrollx(0, scrollx);
229 	tilemap->set_scrolly(0, scrolly);
230 }
231 
ninjakd2_bg_ctrl_w(offs_t offset,uint8_t data)232 void ninjakd2_state::ninjakd2_bg_ctrl_w(offs_t offset, uint8_t data)
233 {
234 	bg_ctrl(offset, data, m_bg_tilemap);
235 }
236 
ninjakd2_sprite_overdraw_w(uint8_t data)237 void ninjakd2_state::ninjakd2_sprite_overdraw_w(uint8_t data)
238 {
239 	m_next_sprite_overdraw_enabled = data & 1;
240 }
241 
242 
243 
244 /*************************************
245  *
246  *  Video update
247  *
248  *************************************/
249 
draw_sprites(bitmap_ind16 & bitmap)250 void ninjakd2_state::draw_sprites( bitmap_ind16 &bitmap)
251 {
252 	gfx_element* const gfx = m_gfxdecode->gfx(1);
253 	int const big_xshift = m_robokid_sprites ? 1 : 0;
254 	int const big_yshift = m_robokid_sprites ? 0 : 1;
255 
256 	uint8_t* sprptr = &m_spriteram[11];
257 	int sprites_drawn = 0;
258 
259 	/* The sprite generator draws exactly 96 16x16 sprites per frame. When big
260 	   (32x32) sprites are drawn, this counts for 4 sprites drawn, so the sprite
261 	   list is reduced accordingly (i.e. three slots at the end of the list will
262 	   be ignored). Note that a disabled sprite, even if it is not drawn, still
263 	   counts as one sprite drawn.
264 	   This is proven by Mutant Night, which doesn't work correctly (leaves shots
265 	   on screen) if we don't take big sprites into account.
266 	*/
267 
268 	for (;;)
269 	{
270 		if (sprptr[2] & 0x02)
271 		{
272 			int sx = sprptr[1] - ((sprptr[2] & 0x01) << 8);
273 			int sy = sprptr[0];
274 			// Ninja Kid II doesn't use the topmost bit (it has smaller ROMs) so it might not be connected on the board
275 			int code = sprptr[3] + ((sprptr[2] & 0xc0) << 2) + ((sprptr[2] & 0x08) << 7);
276 			int flipx = (sprptr[2] & 0x10) >> 4;
277 			int flipy = (sprptr[2] & 0x20) >> 5;
278 			int const color = sprptr[4] & 0x0f;
279 			// Ninja Kid II doesn't use the 'big' feature so it might not be available on the board
280 			int const big = (sprptr[2] & 0x04) >> 2;
281 
282 			if (flip_screen())
283 			{
284 				sx = 240 - 16*big - sx;
285 				sy = 240 - 16*big - sy;
286 				flipx ^= 1;
287 				flipy ^= 1;
288 			}
289 
290 			if (big)
291 			{
292 				code &= ~3;
293 				code ^= flipx << big_xshift;
294 				code ^= flipy << big_yshift;
295 			}
296 
297 			for (int y = 0; y <= big; ++y)
298 			{
299 				for (int x = 0; x <= big; ++x)
300 				{
301 					int const tile = code ^ (x << big_xshift) ^ (y << big_yshift);
302 
303 						gfx->transpen(bitmap,bitmap.cliprect(),
304 							tile,
305 							color,
306 							flipx,flipy,
307 							sx + 16*x, sy + 16*y, 0xf);
308 
309 					++sprites_drawn;
310 					if (sprites_drawn >= 96)
311 						return;
312 				}
313 			}
314 		}
315 		else
316 		{
317 			++sprites_drawn;
318 			if (sprites_drawn >= 96)
319 				return;
320 		}
321 
322 		sprptr += 16;
323 	}
324 }
325 
stencil_ninjakd2(uint16_t pal)326 static bool stencil_ninjakd2( uint16_t pal ) { return( (pal & 0xf0) == 0xf0 ); }
stencil_mnight(uint16_t pal)327 static bool stencil_mnight(   uint16_t pal ) { return( (pal & 0xf0) == 0xf0 ); }
stencil_arkarea(uint16_t pal)328 static bool stencil_arkarea(  uint16_t pal ) { return( (pal & 0xf0) == 0xf0 ); }
stencil_robokid(uint16_t pal)329 static bool stencil_robokid(  uint16_t pal ) { return( (pal & 0xf0) <  0xe0 ); }
stencil_omegaf(uint16_t pal)330 static bool stencil_omegaf(   uint16_t pal ) { return( true ); }
331 //////            OVERDRAW     STENCIL     UNKNOWN
332 //////  NINJAKD2  023459ABCDE  F           1678
333 //////    MNIGHT  0134568ABCDE F           279
334 //////   ARKAREA  012345679BDE             8ACF
335 //////   ROBOKID  EF           01236       45789ABCD
336 //////    OMEGAF  -            -           -         (unused)
337 // I could not find a port to select overdraw or stencil.
338 // Temporarily, I compare with constant number.
339 // This is very hackish.
340 // (Is there a possibility that software can't select it but hardware can?)
341 
erase_sprites(bitmap_ind16 & bitmap)342 void ninjakd2_state::erase_sprites( bitmap_ind16 &bitmap)
343 {
344 	// if sprite overdraw is disabled, clear the sprite framebuffer
345 	if (!m_next_sprite_overdraw_enabled)
346 	{
347 		m_sprites_bitmap.fill(0xf);
348 	}
349 	else
350 	{
351 		for (int y = 0; y < m_sprites_bitmap.height(); ++y)
352 		{
353 			for (int x = 0; x < m_sprites_bitmap.width(); ++x)
354 			{
355 				uint16_t *const ptr = &m_sprites_bitmap.pix(y, x);
356 				if ( (*m_stencil_compare_function)(*ptr) ) *ptr = 0xf;
357 			}
358 		}
359 	}
360 }
361 
362 
update_sprites()363 void ninjakd2_state::update_sprites()
364 {
365 	////// Before modified, this was written.
366 		// we want to erase the sprites with the old setting and draw them with the
367 		// new one. Not doing this causes a glitch in Ninja Kid II when taking the top
368 		// exit from stage 3.
369 	////// The glitch is correct behavior.
370 	erase_sprites(m_sprites_bitmap);
371 	draw_sprites(m_sprites_bitmap);
372 }
373 
374 
screen_update_ninjakd2(screen_device & screen,bitmap_ind16 & bitmap,const rectangle & cliprect)375 uint32_t ninjakd2_state::screen_update_ninjakd2(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
376 {
377 	// updating sprites here instead than in screen_vblank avoids a palette glitch
378 	// at the end of the "rainbow sky" screens.
379 	update_sprites();
380 	m_sprites_updated = 1;
381 
382 	bitmap.fill(0, cliprect);
383 
384 	m_bg_tilemap->draw(screen, bitmap, cliprect, 0, 0);
385 	copybitmap_trans(bitmap, m_sprites_bitmap, 0, 0, 0, 0, cliprect, 0xf);
386 	m_fg_tilemap->draw(screen, bitmap, cliprect, 0, 0);
387 
388 	return 0;
389 }
390 
screen_update_robokid(screen_device & screen,bitmap_ind16 & bitmap,const rectangle & cliprect)391 uint32_t robokid_state::screen_update_robokid(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
392 {
393 	update_sprites();
394 	m_sprites_updated = 1;
395 
396 	bitmap.fill(0, cliprect);
397 
398 	m_robokid_tilemap[0]->draw(screen, bitmap, cliprect, 0, 0);
399 	m_robokid_tilemap[1]->draw(screen, bitmap, cliprect, 0, 0);
400 	copybitmap_trans(bitmap, m_sprites_bitmap, 0, 0, 0, 0, cliprect, 0xf);
401 	m_robokid_tilemap[2]->draw(screen, bitmap, cliprect, 0, 0);
402 	m_fg_tilemap->draw(screen, bitmap, cliprect, 0, 0);
403 
404 	return 0;
405 }
406 
screen_update_omegaf(screen_device & screen,bitmap_ind16 & bitmap,const rectangle & cliprect)407 uint32_t omegaf_state::screen_update_omegaf(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
408 {
409 	update_sprites();
410 	m_sprites_updated = 1;
411 
412 	bitmap.fill(0, cliprect);
413 
414 	m_robokid_tilemap[0]->draw(screen, bitmap, cliprect, 0, 0);
415 	m_robokid_tilemap[1]->draw(screen, bitmap, cliprect, 0, 0);
416 	m_robokid_tilemap[2]->draw(screen, bitmap, cliprect, 0, 0);
417 	copybitmap_trans(bitmap, m_sprites_bitmap, 0, 0, 0, 0, cliprect, 0xf);
418 	m_fg_tilemap->draw(screen, bitmap, cliprect, 0, 0);
419 
420 	return 0;
421 }
422 
423 
WRITE_LINE_MEMBER(ninjakd2_state::screen_vblank_ninjakd2)424 WRITE_LINE_MEMBER(ninjakd2_state::screen_vblank_ninjakd2)
425 {
426 	// rising edge
427 	if (state)
428 	{
429 		if (!m_sprites_updated)
430 			update_sprites();
431 
432 		m_sprites_updated = 0;
433 
434 		m_maincpu->set_input_line_and_vector(0, HOLD_LINE, 0xd7); /* Z80 - RST 10h */
435 	}
436 }
437