1 // license:BSD-3-Clause
2 // copyright-holders:David Haywood
3 
4 #include "emu.h"
5 #include "spg110_video.h"
6 
7 #include <algorithm>
8 
9 
10 DEFINE_DEVICE_TYPE(SPG110_VIDEO, spg110_video_device, "spg110_video", "SPG110 System-on-a-Chip (Video)")
11 
spg110_video_device(const machine_config & mconfig,device_type type,const char * tag,device_t * owner,uint32_t clock)12 spg110_video_device::spg110_video_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) :
13 	device_t(mconfig, type, tag, owner, clock),
14 	device_memory_interface(mconfig, *this),
15 	m_space_config("spg110_video", ENDIANNESS_BIG, 16, 32, 0, address_map_constructor(FUNC(spg110_video_device::map_video), this)),
16 	m_cpu(*this, finder_base::DUMMY_TAG),
17 	m_screen(*this, finder_base::DUMMY_TAG),
18 	m_palette(*this, "palette"),
19 	m_gfxdecode(*this, "gfxdecode"),
20 	m_palram(*this, "palram"),
21 	m_palctrlram(*this, "palctrlram"),
22 	m_sprtileno(*this, "sprtileno"),
23 	m_sprattr1(*this, "sprattr1"),
24 	m_sprattr2(*this, "sprattr2"),
25 	m_video_irq_cb(*this)
26 {
27 }
28 
spg110_video_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)29 spg110_video_device::spg110_video_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
30 	: spg110_video_device(mconfig, SPG110_VIDEO, tag, owner, clock)
31 {
32 }
33 
34 template<spg110_video_device::flipx_t FlipX>
draw(const rectangle & cliprect,uint32_t line,uint32_t xoff,uint32_t yoff,uint32_t ctrl,uint32_t bitmap_addr,uint16_t tile,uint8_t yflipmask,uint8_t pal,int32_t h,int32_t w,uint8_t bpp)35 void spg110_video_device::draw(const rectangle &cliprect, uint32_t line, uint32_t xoff, uint32_t yoff, uint32_t ctrl, uint32_t bitmap_addr, uint16_t tile, uint8_t yflipmask, uint8_t pal, int32_t h, int32_t w, uint8_t bpp)
36 {
37 	address_space &space = m_cpu->space(AS_PROGRAM);
38 
39 	uint32_t nc = (bpp + 1) << 1;
40 
41 	switch (bpp)
42 	{
43 	case 0x03: pal = 0; break; // 8 bpp
44 	case 0x02: pal &=0x03; break; // 6 bpp
45 	case 0x01: break; // 4 bpp
46 	case 0x00: break; // 2 bpp
47 	}
48 
49 	uint32_t palette_offset = pal;
50 
51 	palette_offset <<= nc;
52 
53 	uint32_t bits_per_row = nc * w / 16;
54 	uint32_t words_per_tile = bits_per_row * h;
55 	uint32_t m = bitmap_addr + words_per_tile * tile + bits_per_row * (line ^ yflipmask);
56 	uint32_t bits = 0;
57 	uint32_t nbits = 0;
58 	uint32_t y = line;
59 
60 	int yy = (yoff + y) & 0x1ff;
61 	if (yy >= 0x01c0)
62 		yy -= 0x0200;
63 
64 	if (yy > 240 || yy < 0)
65 		return;
66 
67 	int y_index = yy * 320;
68 
69 	for (int32_t x = FlipX ? (w - 1) : 0; FlipX ? x >= 0 : x < w; FlipX ? x-- : x++)
70 	{
71 		int xx = xoff + x;
72 
73 		bits <<= nc;
74 
75 		if (nbits < nc)
76 		{
77 			uint16_t b = space.read_word(m++ & 0x3fffff);
78 			//b = (b << 8) | (b >> 8);
79 			bits |= b << (nc - nbits);
80 			nbits += 16;
81 		}
82 		nbits -= nc;
83 
84 		uint32_t pal = palette_offset + (bits >> 16);
85 		bits &= 0xffff;
86 
87 		xx &= 0x01ff;
88 		if (xx >= 0x01c0)
89 			xx -= 0x0200;
90 
91 		if (xx >= 0 && xx < 320)
92 		{
93 			int pix_index = xx + y_index;
94 			const pen_t *pens = m_palette->pens();
95 			uint32_t paldata = pens[pal];
96 
97 			int transmap = m_palctrlram[(pal & 0xf0)>>4];
98 
99 			bool trans = false;
100 
101 			if (transmap & 0x10) // maybe values other than 0x010 have other meanings, like blending?
102 				if ((pal & 0x0f) == (transmap & 0xf))
103 					trans = true;
104 
105 			if (!trans)
106 			{
107 				m_screenbuf[pix_index] = paldata;
108 			}
109 		}
110 	}
111 }
112 
draw_page(const rectangle & cliprect,uint32_t scanline,int priority,uint32_t bitmap_addr,uint16_t * regs)113 void spg110_video_device::draw_page(const rectangle &cliprect, uint32_t scanline, int priority, uint32_t bitmap_addr, uint16_t *regs)
114 {
115 	uint32_t xscroll = regs[0];
116 	uint32_t yscroll = regs[1];
117 	uint32_t attr = regs[2];
118 	uint32_t ctrl = regs[3];
119 	uint32_t tilemap = regs[4];
120 	uint32_t palette_map = regs[5];
121 	address_space &space2 = this->space(0);
122 
123 	if (!(ctrl & PAGE_ENABLE_MASK))
124 	{
125 		return;
126 	}
127 
128 //  if (((attr & PAGE_PRIORITY_FLAG_MASK) >> PAGE_PRIORITY_FLAG_SHIFT) != priority)
129 //  {
130 //      return;
131 //  }
132 
133 	uint8_t bpp = attr & 0x03;
134 
135 	uint32_t tile_h = 8 << ((attr & PAGE_TILE_HEIGHT_MASK) >> PAGE_TILE_HEIGHT_SHIFT);
136 	uint32_t tile_w = 8 << ((attr & PAGE_TILE_WIDTH_MASK) >> PAGE_TILE_WIDTH_SHIFT);
137 
138 	uint32_t tile_count_x = 512 / tile_w;
139 
140 	uint32_t bitmap_y = (scanline + yscroll) & 0xff;
141 	uint32_t y0 = bitmap_y / tile_h;
142 	uint32_t tile_scanline = bitmap_y % tile_h;
143 	uint32_t tile_address = tile_count_x * y0;
144 
145 	for (uint32_t x0 = 0; x0 < tile_count_x; x0++, tile_address++)
146 	{
147 		uint32_t yy = ((tile_h * y0 - yscroll + 0x10) & 0xff) - 0x10;
148 		uint32_t xx = (tile_w * x0 - xscroll) & 0x1ff;
149 		uint16_t tile = (ctrl & PAGE_WALLPAPER_MASK) ? space2.read_word(tilemap*2) : space2.read_word((tilemap + tile_address)*2);
150 
151 		if (!tile)
152 			continue;
153 
154 		uint8_t pal = 0x000;
155 		uint8_t pri = 0x00;
156 		bool flip_x = false;
157 
158 		if (!(ctrl & 0x0002)) // 'regset'
159 		{
160 			uint16_t extra_attribute = space2.read_word((palette_map * 2) + tile_address);
161 
162 			if (x0 & 1)
163 				extra_attribute = (extra_attribute & 0x00ff);
164 			else
165 				extra_attribute = (extra_attribute & 0xff00) >> 8;
166 
167 			pal = extra_attribute & 0x0f;
168 			pri = (extra_attribute & 0x30) >> 4;
169 			flip_x = extra_attribute & 0x40;
170 		}
171 
172 		if (pri == priority)
173 		{
174 
175 			if (flip_x)
176 				draw<FlipXOn>(cliprect, tile_scanline, xx, yy, ctrl, bitmap_addr, tile, 0, pal, tile_h, tile_w, bpp);
177 			else
178 				draw<FlipXOff>(cliprect, tile_scanline, xx, yy, ctrl, bitmap_addr, tile, 0, pal, tile_h, tile_w, bpp);
179 		}
180 
181 	}
182 }
183 
184 
draw_sprite(const rectangle & cliprect,uint32_t scanline,int priority,uint32_t base_addr)185 void spg110_video_device::draw_sprite(const rectangle &cliprect, uint32_t scanline, int priority, uint32_t base_addr)
186 {
187 
188 	// m_sprtileno  tttt tttt tttt tttt    t =  tile number (all bits?)
189 	// m_sprattr1   xxxx xxxx yyyy yyyy    x = low x bits, y = low y bits
190 	// m_sprattr2   YXzz pppp hhww fFbb    X = high x bit, z = priority, p = palette, h = height, w = width, f = flipy,  F = flipx, b = bpp, Y = high y bit
191 
192 	uint16_t tile = m_sprtileno[base_addr];
193 
194 	if (!tile)
195 	{
196 		return;
197 	}
198 
199 	uint32_t bitmap_addr = 0x40 * m_tilebase;
200 	uint16_t attr1 = m_sprattr1[base_addr];
201 	uint16_t attr2 = m_sprattr2[base_addr];
202 
203 	int x = (attr1 >> 8) & 0xff;
204 	int y = (attr1) & 0xff;
205 	uint8_t pri = (attr2 & 0x3000)>>12;
206 	bool flip_x = (attr2 & 0x0004)>>2;
207 	bool flip_y = (attr2 & 0x0008)>>3;
208 
209 	if (!(attr2 & 0x4000))
210 		x+= 0x100;
211 
212 	if (!(attr2 & 0x8000))
213 		y+= 0x100;
214 
215 	const uint32_t h = 8 << ((attr2 & PAGE_TILE_HEIGHT_MASK) >> PAGE_TILE_HEIGHT_SHIFT);
216 	const uint32_t w = 8 << ((attr2 & PAGE_TILE_WIDTH_MASK) >> PAGE_TILE_WIDTH_SHIFT);
217 
218 //  if (!(m_video_regs[0x42] & SPRITE_COORD_TL_MASK))
219 //  {
220 //      x = (160 + x) - w / 2;
221 //      y = (120 - y) - (h / 2) + 8;
222 //  }
223 
224 	y = 0x1ff - y - 128 + 1;
225 	x = x - 128 + 32;
226 
227 	x -= (w / 2);
228 	y -= (h / 2);
229 
230 	x &= 0x01ff;
231 	y &= 0x01ff;
232 
233 	uint32_t tile_line = ((scanline - y)) % h;
234 	int16_t test_y = (y + tile_line) & 0x1ff;
235 	if (test_y >= 0x01c0)
236 		test_y -= 0x0200;
237 
238 	if (test_y != scanline)
239 	{
240 		return;
241 	}
242 
243 	//bool blend = (attr & 0x4000);
244 	const uint8_t bpp = attr2 & 0x0003;
245 	const uint32_t yflipmask = flip_y ? h - 1 : 0;
246 	const uint32_t palette_offset = (attr2 & 0x0f00) >> 8;
247 
248 	if (pri == priority)
249 	{
250 		if (flip_x)
251 			draw<FlipXOn>(cliprect, tile_line, x, y, 0, bitmap_addr, tile, yflipmask, palette_offset, h, w, bpp);
252 		else
253 			draw<FlipXOff>(cliprect, tile_line, x, y, 0, bitmap_addr, tile, yflipmask, palette_offset, h, w, bpp);
254 	}
255 }
256 
257 
258 
draw_sprites(const rectangle & cliprect,uint32_t scanline,int priority)259 void spg110_video_device::draw_sprites(const rectangle &cliprect, uint32_t scanline, int priority)
260 {
261 	//if (!(m_video_regs[0x42] & SPRITE_ENABLE_MASK))
262 	//{
263 	//  return;
264 	//}
265 
266 	for (uint32_t n = 0; n < 256; n++)
267 	{
268 		draw_sprite(cliprect, scanline, priority, n);
269 	}
270 }
271 
272 
273 /* correct, 4bpp gfxs */
274 static const gfx_layout charlayout =
275 {
276 	8,8,
277 	RGN_FRAC(1,1),
278 	4,
279 	{ STEP4(0,1) },
280 	{ 0*4,1*4,2*4,3*4,4*4,5*4,6*4,7*4 },
281 	{ STEP8(0,4*8) },
282 	8*8*4
283 };
284 
285 static const gfx_layout charlayout6 =
286 {
287 	8,8,
288 	RGN_FRAC(1,1),
289 	6,
290 	{ 0,1,2,3,4,5 },
291 	{ STEP8(0,6) },
292 	{ STEP8(0,6*8) },
293 	8*8*6
294 };
295 
296 static const gfx_layout char16layout =
297 {
298 	16,16,
299 	RGN_FRAC(1,1),
300 	4,
301 	{ STEP4(0,1) },
302 	{ 0*4,1*4,2*4,3*4,4*4,5*4,6*4,7*4, 8*4,9*4,10*4,11*4,12*4,13*4,14*4,15*4 },
303 	{ STEP16(0,4*16) },
304 	16*16*4
305 };
306 
307 static const gfx_layout char32layout =
308 {
309 	32,32,
310 	RGN_FRAC(1,1),
311 	4,
312 	{ STEP4(0,1) },
313 	{ STEP32(0,4) },
314 	{ STEP32(0,4*32) },
315 	32*32*4
316 };
317 
318 
319 
320 static GFXDECODE_START( gfx )
321 	GFXDECODE_ENTRY( ":maincpu", 0, charlayout, 0, 16 )
322 	GFXDECODE_ENTRY( ":maincpu", 0, char16layout, 0, 16 )
323 	GFXDECODE_ENTRY( ":maincpu", 0, char32layout, 0, 16 )
324 	GFXDECODE_ENTRY( ":maincpu", 0, charlayout6, 0, 16 ) // correct for lots of the tiles inc. startup text
325 GFXDECODE_END
326 
327 
device_add_mconfig(machine_config & config)328 void spg110_video_device::device_add_mconfig(machine_config &config)
329 {
330 	PALETTE(config, m_palette).set_entries(0x100);
331 
332 	GFXDECODE(config, m_gfxdecode, m_palette, gfx);
333 }
334 
335 
memory_space_config() const336 device_memory_interface::space_config_vector spg110_video_device::memory_space_config() const
337 {
338 	return space_config_vector {
339 		std::make_pair(0, &m_space_config)
340 	};
341 }
342 
343 
344 // irq source or similar?
spg110_2063_r()345 uint16_t spg110_video_device::spg110_2063_r()
346 {
347 	// checks for bits 0x20 and 0x08 in the IRQ function (all IRQs point to the same place)
348 
349 	// HACK! jak_spdo checks for 0x400 or 0x200 starting some of the games
350 	return m_video_irq_status | 0x0600; /* | 0x0002; */
351 }
352 
spg110_2063_w(uint16_t data)353 void spg110_video_device::spg110_2063_w(uint16_t data)
354 {
355 	// writes 0x28, probably clears the IRQ / IRQ sources? 0x63 is the same offset for this in spg2xx but bits used seem to be different
356 	const uint16_t old = m_video_irq_enable & m_video_irq_status;
357 	m_video_irq_status &= ~data;
358 	const uint16_t changed = old ^ (m_video_irq_enable & m_video_irq_status);
359 	if (changed)
360 		check_video_irq();
361 }
362 
363 
spg110_201c_w(uint16_t data)364 void spg110_video_device::spg110_201c_w(uint16_t data) { logerror("%s: 201c: %04x\n", machine().describe_context(), data); } // during startup text only
spg110_2020_w(offs_t offset,uint16_t data,uint16_t mem_mask)365 void spg110_video_device::spg110_2020_w(offs_t offset, uint16_t data, uint16_t mem_mask) { COMBINE_DATA(&m_tilebase); logerror("%s: 2020: %04x\n", machine().describe_context(), data); } // confirmed as tile base, seems to apply to both layers and sprites, unlike spg2xx which has separate registers
366 
spg110_2028_w(uint16_t data)367 void spg110_video_device::spg110_2028_w(uint16_t data) { logerror("%s: 2028: %04x\n", machine().describe_context(), data); } // startup
spg110_2028_r()368 uint16_t spg110_video_device::spg110_2028_r() { return 0x0000; }
369 
spg110_2029_w(uint16_t data)370 void spg110_video_device::spg110_2029_w(uint16_t data) { logerror("%s: 2029: %04x\n", machine().describe_context(), data); } // 0006, 0008 on startup
spg110_2029_r()371 uint16_t spg110_video_device::spg110_2029_r() { return 0x0000; }
372 
spg110_2031_w(uint16_t data)373 void spg110_video_device::spg110_2031_w(uint16_t data) { logerror("%s: 2031: %04x\n", machine().describe_context(), data); } // 014a or 0000 when ball is in trap
spg110_2032_w(uint16_t data)374 void spg110_video_device::spg110_2032_w(uint16_t data) { logerror("%s: 2032: %04x\n", machine().describe_context(), data); } // 014a most of the time, 0000 very rarely
spg110_2033_w(uint16_t data)375 void spg110_video_device::spg110_2033_w(uint16_t data) { logerror("%s: 2033: %04x\n", machine().describe_context(), data); } // changes, situational, eg when pausing
spg110_2034_w(uint16_t data)376 void spg110_video_device::spg110_2034_w(uint16_t data) { logerror("%s: 2034: %04x\n", machine().describe_context(), data); } // 0141 on every scene transition
spg110_2035_w(uint16_t data)377 void spg110_video_device::spg110_2035_w(uint16_t data) { logerror("%s: 2035: %04x\n", machine().describe_context(), data); } // 0141 on every scene transition
spg110_2036_w(offs_t offset,uint16_t data,uint16_t mem_mask)378 void spg110_video_device::spg110_2036_w(offs_t offset, uint16_t data, uint16_t mem_mask) { logerror("%s: 2036: %04x\n", machine().describe_context(), data); COMBINE_DATA(&m_2036_scroll); } // seems related to ball y position, not scrolling (possibly shadow sprite related?)
379 
spg110_2037_r()380 uint16_t spg110_video_device::spg110_2037_r() { return 0x0000; } // added to something from the PRNG
spg110_2037_w(uint16_t data)381 void spg110_video_device::spg110_2037_w(uint16_t data) { logerror("%s: 2037: %04x\n", machine().describe_context(), data); } // 0126 (always?)
382 
spg110_2039_w(uint16_t data)383 void spg110_video_device::spg110_2039_w(uint16_t data) { logerror("%s: 2039: %04x\n", machine().describe_context(), data); } // 0803 on every scene transition
384 
spg110_203c_w(uint16_t data)385 void spg110_video_device::spg110_203c_w(uint16_t data) { logerror("%s: 203c: %04x\n", machine().describe_context(), data); } // 0006 on startup, twice
386 
spg110_203d_w(uint16_t data)387 void spg110_video_device::spg110_203d_w(uint16_t data) { logerror("%s: 203d: %04x\n", machine().describe_context(), data); } // changes, usually between scenes
388 
spg110_2042_r()389 uint16_t spg110_video_device::spg110_2042_r() { return 0x0000; }
spg110_2042_w(uint16_t data)390 void spg110_video_device::spg110_2042_w(uint16_t data) { logerror("%s: 2042: %04x\n", machine().describe_context(), data);  } // sets bit 0x0004, masks with 0xfffb etc.
391 
spg110_2045_w(uint16_t data)392 void spg110_video_device::spg110_2045_w(uint16_t data) { logerror("%s: 2045: %04x\n", machine().describe_context(), data);  } // 0006 on startup, once
393 
394 
spg110_205x_w(offs_t offset,uint16_t data,uint16_t mem_mask)395 void spg110_video_device::spg110_205x_w(offs_t offset, uint16_t data, uint16_t mem_mask)
396 {
397 	COMBINE_DATA(&m_palctrlram[offset]);
398 }
399 
400 
dma_unk_2061_w(offs_t offset,uint16_t data,uint16_t mem_mask)401 void spg110_video_device::dma_unk_2061_w(offs_t offset, uint16_t data, uint16_t mem_mask) { COMBINE_DATA(&m_dma_unk_2061); }
dma_dst_step_w(offs_t offset,uint16_t data,uint16_t mem_mask)402 void spg110_video_device::dma_dst_step_w(offs_t offset, uint16_t data, uint16_t mem_mask) { COMBINE_DATA(&m_dma_dst_step); }
dma_unk_2067_w(offs_t offset,uint16_t data,uint16_t mem_mask)403 void spg110_video_device::dma_unk_2067_w(offs_t offset, uint16_t data, uint16_t mem_mask) { COMBINE_DATA(&m_dma_src_high); }
dma_src_step_w(offs_t offset,uint16_t data,uint16_t mem_mask)404 void spg110_video_device::dma_src_step_w(offs_t offset, uint16_t data, uint16_t mem_mask) { COMBINE_DATA(&m_dma_src_step); }
405 
dma_dst_w(offs_t offset,uint16_t data,uint16_t mem_mask)406 void spg110_video_device::dma_dst_w(offs_t offset, uint16_t data, uint16_t mem_mask) { COMBINE_DATA(&m_dma_dst); }
dma_src_w(offs_t offset,uint16_t data,uint16_t mem_mask)407 void spg110_video_device::dma_src_w(offs_t offset, uint16_t data, uint16_t mem_mask) { COMBINE_DATA(&m_dma_src); }
408 
dma_len_trigger_w(uint16_t data)409 void spg110_video_device::dma_len_trigger_w(uint16_t data)
410 {
411 	int length = data & 0x1fff;
412 
413 	// this is presumably a counter that underflows to 0x1fff, because that's what the wait loop waits for?
414 	logerror("%s: (trigger len) %04x with values (unk) %04x (dststep) %04x (srchigh) %04x (src step) %04x | (dst) %04x (src) %04x\n", machine().describe_context(), data, m_dma_unk_2061, m_dma_dst_step, m_dma_src_high, m_dma_src_step, m_dma_dst, m_dma_src);
415 
416 	/*
417 	if (m_dma_unk_2061 != 0x0000)
418 	{
419 	    logerror("unknown DMA params are not zero!\n");
420 	}
421 	*/
422 
423 	int source = m_dma_src | m_dma_src_high << 16;
424 	int dest = m_dma_dst;
425 
426 	for (int i = 0; i < length; i++)
427 	{
428 		address_space &mem = m_cpu->space(AS_PROGRAM);
429 		uint16_t val = mem.read_word(source);
430 
431 		this->space(0).write_word(dest * 2, val, 0xffff);
432 
433 		source+=m_dma_src_step;
434 		dest+=m_dma_dst_step;
435 	}
436 
437 	// not sure, spiderman would suggest that some of these need to reset (unless a missing IRQ clears them)
438 	m_dma_unk_2061 = 0;
439 	//m_dma_dst_step = 0; // conyteni says no
440 	m_dma_src_high = 0;
441 	//m_dma_src_step = 0; // conyteni says no
442 	m_dma_dst = 0;
443 	m_dma_src = 0;
444 
445 	// HACK: it really seems this interrupt status is related to the DMA, but jak_capb doesn't ack it, so must also be a way to disable it?
446 	if (m_is_spiderman)
447 	{
448 		const int i = 0x0002;
449 
450 		if (m_video_irq_enable & 1)
451 		{
452 			m_video_irq_status |= i;
453 			check_video_irq();
454 		}
455 	}
456 }
457 
dma_manual_w(uint16_t data)458 void spg110_video_device::dma_manual_w(uint16_t data)
459 {
460 	this->space(0).write_word(m_dma_dst * 2, data, 0xffff);
461 }
462 
dma_manual_r()463 uint16_t spg110_video_device::dma_manual_r()
464 {
465 	uint16_t val = this->space(0).read_word(m_dma_dst * 2);
466 	return val;
467 }
468 
dma_len_status_r()469 uint16_t spg110_video_device::dma_len_status_r()
470 {
471 	return 0x1fff; // DMA related?
472 }
473 
474 
tmap0_regs_r(offs_t offset)475 uint16_t spg110_video_device::tmap0_regs_r(offs_t offset) { return tmap0_regs[offset]; }
tmap1_regs_r(offs_t offset)476 uint16_t spg110_video_device::tmap1_regs_r(offs_t offset) { return tmap1_regs[offset]; }
477 
tilemap_write_regs(int which,uint16_t * regs,int regno,uint16_t data)478 void spg110_video_device::tilemap_write_regs(int which, uint16_t* regs, int regno, uint16_t data)
479 {
480 	switch (regno)
481 	{
482 	case 0x0: // Page X scroll
483 		logerror("video_w: Page %d X Scroll = %04x\n", which, data & 0x01ff);
484 		regs[regno] = data & 0x01ff;
485 		break;
486 
487 	case 0x1: // Page Y scroll
488 		logerror("video_w: Page %d Y Scroll = %04x\n", which, data & 0x00ff);
489 		regs[regno] = data & 0x00ff;
490 		break;
491 
492 	case 0x2: // Page Attributes
493 		// 'priority' can't be priority here as it is on spg2xx, or the scores in attract will be behind the table, it really seems to be per attribute bit instead
494 
495 		logerror("video_w: Page %d Attributes = %04x (Priority:%d, Palette:%d, VSize:%d, HSize:%d, FlipY:%d, FlipX:%d, BPP:%d)\n", which, data
496 			, (data >> 12) & 3, (data >> 8) & 15, 8 << ((data >> 6) & 3), 8 << ((data >> 4) & 3), BIT(data, 3), BIT(data, 2), 2 * ((data & 3) + 1));
497 		regs[regno] = data;
498 		break;
499 
500 	case 0x3: // Page Control
501 		logerror("video_w: Page %d Control = %04x (Blend:%d, HiColor:%d, RowScroll:%d, Enable:%d, Wallpaper:%d, RegSet:%d, Bitmap:%d)\n", which, data
502 			, BIT(data, 8), BIT(data, 7), BIT(data, 4), BIT(data, 3), BIT(data, 2), BIT(data, 1), BIT(data, 0));
503 		regs[regno] = data;
504 		break;
505 
506 	case 0x4: // Page Tile Address
507 		logerror("video_w: Page %d Tile Address = %04x\n", which, data);
508 		regs[regno] = data;
509 		break;
510 
511 	case 0x5: // Page Attribute Address
512 		logerror("video_w: Page %d Attribute Address = %04x\n", which, data);
513 		regs[regno] = data;
514 		break;
515 	}
516 }
517 
518 
tmap0_regs_w(offs_t offset,uint16_t data)519 void spg110_video_device::tmap0_regs_w(offs_t offset, uint16_t data)
520 {
521 	tilemap_write_regs(0, tmap0_regs,offset,data);
522 }
523 
524 
tmap1_regs_w(offs_t offset,uint16_t data)525 void spg110_video_device::tmap1_regs_w(offs_t offset, uint16_t data)
526 {
527 	tilemap_write_regs(1, tmap1_regs,offset,data);
528 }
529 
530 // this seems to be a different, non-cpu mapped space only accessible via the DMA?
map_video(address_map & map)531 void spg110_video_device::map_video(address_map &map)
532 {
533 	// are these addresses hardcoded, or can they move (in which case tilemap system isn't really suitable)
534 	map(0x00000, 0x03fff).ram(); // 2fff?
535 
536 	map(0x04000, 0x041ff).ram().share("sprtileno"); // seems to be 3 blocks, almost certainly spritelist
537 	map(0x04200, 0x043ff).ram().share("sprattr1");
538 	map(0x04400, 0x045ff).ram().share("sprattr2");
539 
540 	map(0x08000, 0x081ff).ram().w(FUNC(spg110_video_device::palette_w)).share("palram"); // palette format unknown
541 }
542 
device_start()543 void spg110_video_device::device_start()
544 {
545 	save_item(NAME(m_dma_src_step));
546 	save_item(NAME(m_dma_dst_step));
547 	save_item(NAME(m_dma_unk_2061));
548 	save_item(NAME(m_dma_src_high));
549 	save_item(NAME(m_dma_dst));
550 	save_item(NAME(m_dma_src));
551 	save_item(NAME(m_bg_scrollx));
552 	save_item(NAME(m_bg_scrolly));
553 	save_item(NAME(m_2036_scroll));
554 
555 	m_video_irq_cb.resolve();
556 }
557 
device_reset()558 void spg110_video_device::device_reset()
559 {
560 	m_dma_src_step = 0;
561 	m_dma_dst_step = 0;
562 	m_dma_unk_2061 = 0;
563 	m_dma_src_high = 0;
564 	m_dma_dst = 0;
565 	m_dma_src = 0;
566 	m_bg_scrollx = 0;
567 	m_bg_scrolly = 0;
568 	m_2036_scroll = 0;
569 
570 	std::fill(std::begin(tmap0_regs), std::end(tmap0_regs), 0);
571 	std::fill(std::begin(tmap1_regs), std::end(tmap1_regs), 0);
572 
573 	// is there actually an enable register here?
574 	m_video_irq_enable = 0xffff;
575 	m_video_irq_status = 0x0000;
576 }
577 
hue2rgb(double p,double q,double t)578 double spg110_video_device::hue2rgb(double p, double q, double t)
579 {
580 	if (t < 0) t += 1;
581 	if (t > 1) t -= 1;
582 	if (t < 1 / 6.0f) return p + (q - p) * 6 * t;
583 	if (t < 1 / 2.0f) return q;
584 	if (t < 2 / 3.0f) return p + (q - p) * (2 / 3.0f - t) * 6;
585 	return p;
586 }
587 
588 
589 // wrong format!
palette_w(offs_t offset,uint16_t data,uint16_t mem_mask)590 void spg110_video_device::palette_w(offs_t offset, uint16_t data, uint16_t mem_mask)
591 {
592 	// probably not
593 	const double h_add = 0.65f;
594 	const double h_divide = 43.2f;
595 
596 	COMBINE_DATA(&m_palram[offset]);
597 
598 	uint16_t dat = m_palram[offset];
599 
600 	// llll lsss sshh hhhh
601 	int l_raw =  (dat & 0xfe00) >> 10;
602 	int sl_raw = (dat & 0x03c0) >> 6;
603 	int h_raw =  (dat & 0x003f) >> 0;
604 
605 	double l = (double)l_raw / 63.0f;
606 	double s = (double)sl_raw / 15.0f;
607 	double h = (double)h_raw / h_divide;
608 
609 	// probably not
610 	h += h_add;
611 
612 	if (h>1.0f)
613 		h-= 1.0f;
614 
615 	double r, g, b;
616 
617 	if (s == 0) {
618 		r = g = b = l; // greyscale
619 	} else {
620 		double q = l < 0.5f ? l * (1 + s) : l + s - l * s;
621 		double p = 2 * l - q;
622 		r = hue2rgb(p, q, h + 1/3.0f);
623 		g = hue2rgb(p, q, h);
624 		b = hue2rgb(p, q, h - 1/3.0f);
625 	}
626 
627 	int r_real = r * 255.0f;
628 	int g_real = g * 255.0f;
629 	int b_real = b * 255.0f;
630 
631 	m_palette->set_pen_color(offset, r_real, g_real, b_real);
632 }
633 
screen_update(screen_device & screen,bitmap_rgb32 & bitmap,const rectangle & cliprect)634 uint32_t spg110_video_device::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect)
635 {
636 	memset(&m_screenbuf[320 * cliprect.min_y], 0, 4 * 320 * ((cliprect.max_y - cliprect.min_y) + 1));
637 
638 	const uint32_t page1_addr = 0x40 * m_tilebase;//0x40 * m_video_regs[0x20];
639 	const uint32_t page2_addr = 0x40 * m_tilebase;//0x40 * m_video_regs[0x21];
640 	uint16_t *page1_regs = tmap0_regs;
641 	uint16_t *page2_regs = tmap1_regs;
642 
643 	for (uint32_t scanline = (uint32_t)cliprect.min_y; scanline <= (uint32_t)cliprect.max_y; scanline++)
644 	{
645 		for (int i = 0; i < 4; i++)
646 		{
647 			draw_page(cliprect, scanline, i, page2_addr, page2_regs);
648 			draw_page(cliprect, scanline, i, page1_addr, page1_regs);
649 			draw_sprites(cliprect, scanline, i);
650 		}
651 	}
652 
653 	for (int y = cliprect.min_y; y <= cliprect.max_y; y++)
654 	{
655 		uint32_t *dest = &bitmap.pix(y, cliprect.min_x);
656 		const uint32_t *src = &m_screenbuf[cliprect.min_x + 320 * y];
657 		std::copy_n(src, cliprect.width(), dest);
658 	}
659 
660 	return 0;
661 }
662 
WRITE_LINE_MEMBER(spg110_video_device::vblank)663 WRITE_LINE_MEMBER(spg110_video_device::vblank)
664 {
665 	const int i = 0x0008;
666 
667 	if (!state)
668 	{
669 		m_video_irq_status &= ~i;
670 		check_video_irq();
671 		return;
672 	}
673 
674 	if (m_video_irq_enable & 1)
675 	{
676 		m_video_irq_status |= i;
677 		check_video_irq();
678 	}
679 }
680 
check_video_irq()681 void spg110_video_device::check_video_irq()
682 {
683 	m_video_irq_cb((m_video_irq_status & m_video_irq_enable) ? ASSERT_LINE : CLEAR_LINE);
684 }
685