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