1 // license:BSD-3-Clause
2 // copyright-holders:Aaron Giles
3 /***************************************************************************
4 
5     screen.cpp
6 
7     Core MAME screen device.
8 
9 ***************************************************************************/
10 
11 #include "emu.h"
12 #include "screen.h"
13 
14 #include "emuopts.h"
15 #include "render.h"
16 #include "rendutil.h"
17 
18 #include "nanosvg.h"
19 #include "localpng.h"
20 
21 #include <set>
22 
23 
24 //**************************************************************************
25 //  DEBUGGING
26 //**************************************************************************
27 
28 #define VERBOSE                     (0)
29 #define LOG_PARTIAL_UPDATES(x)      do { if (VERBOSE) logerror x; } while (0)
30 
31 
32 
33 //**************************************************************************
34 //  GLOBAL VARIABLES
35 //**************************************************************************
36 
37 // device type definition
38 DEFINE_DEVICE_TYPE(SCREEN, screen_device, "screen", "Video Screen")
39 
40 const attotime screen_device::DEFAULT_FRAME_PERIOD(attotime::from_hz(DEFAULT_FRAME_RATE));
41 
42 u32 screen_device::m_id_counter = 0;
43 
44 class screen_device::svg_renderer {
45 public:
46 	svg_renderer(memory_region *region);
47 
48 	int width() const;
49 	int height() const;
50 
51 	int render(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect);
52 
53 	static void output_notifier(const char *outname, s32 value, void *param);
54 
55 private:
56 	struct paired_entry {
57 		int key;
58 		int cache_entry;
paired_entryscreen_device::svg_renderer::paired_entry59 		paired_entry(int k, int c) { key = k; cache_entry = c; }
60 	};
61 
62 	struct cached_bitmap {
63 		int x, y, sx, sy;
64 		std::vector<u32> image;
65 		std::vector<paired_entry> pairs;
66 	};
67 
68 	struct bbox {
69 		int x0, y0, x1, y1;
70 	};
71 
72 	util::nsvg_image_ptr m_image;
73 	util::nsvg_rasterizer_ptr m_rasterizer;
74 	std::vector<bool> m_key_state;
75 	std::vector<std::vector<NSVGshape *>> m_keyed_shapes;
76 	std::unordered_map<std::string, int> m_key_ids;
77 	int m_key_count;
78 
79 	int m_sx, m_sy;
80 	double m_scale;
81 	std::vector<u32> m_background;
82 
83 	std::vector<cached_bitmap> m_cache;
84 
85 	void output_change(const char *outname, s32 value);
86 	void render_state(std::vector<u32> &dest, const std::vector<bool> &state);
87 	void compute_initial_bboxes(std::vector<bbox> &bboxes);
88 	bool compute_mask_intersection_bbox(int key1, int key2, bbox &bb) const;
89 	void compute_diff_image(const std::vector<u32> &rend, const bbox &bb, cached_bitmap &dest) const;
90 	void compute_dual_diff_image(const std::vector<u32> &rend, const bbox &bb, const cached_bitmap &src1, const cached_bitmap &src2, cached_bitmap &dest) const;
91 	void rebuild_cache();
92 	void blit(bitmap_rgb32 &bitmap, const cached_bitmap &src) const;
93 };
94 
svg_renderer(memory_region * region)95 screen_device::svg_renderer::svg_renderer(memory_region *region)
96 {
97 	const std::unique_ptr<char []> s(new char[region->bytes() + 1]);
98 	memcpy(s.get(), region->base(), region->bytes());
99 	s[region->bytes()] = 0;
100 	m_image.reset(nsvgParse(s.get(), "px", 72));
101 	m_rasterizer.reset(nsvgCreateRasterizer());
102 
103 	m_key_count = 0;
104 
105 	for (NSVGshape *shape = m_image->shapes; shape; shape = shape->next)
106 		if(shape->title[0]) {
107 			const auto it = m_key_ids.find(shape->title);
108 			if(it != m_key_ids.end())
109 				m_keyed_shapes[it->second].push_back(shape);
110 			else {
111 				const int id = m_key_count++;
112 				m_keyed_shapes.resize(m_key_count);
113 				m_keyed_shapes[id].push_back(shape);
114 				m_key_ids[shape->title] = id;
115 			}
116 		}
117 	m_key_state.resize(m_key_count);
118 	std::fill(m_key_state.begin(),m_key_state.end(),false);
119 
120 	m_sx = m_sy = 0;
121 	m_scale = 1.0;
122 
123 	osd_printf_verbose("Parsed SVG '%s', aspect ratio %f\n", region->name(), (m_image->height == 0.0f) ? 0 : m_image->width / m_image->height);
124 }
125 
width() const126 int screen_device::svg_renderer::width() const
127 {
128 	return int(m_image->width + 0.5);
129 }
130 
height() const131 int screen_device::svg_renderer::height() const
132 {
133 	return int(m_image->height + 0.5);
134 }
135 
render_state(std::vector<u32> & dest,const std::vector<bool> & state)136 void screen_device::svg_renderer::render_state(std::vector<u32> &dest, const std::vector<bool> &state)
137 {
138 	for(int key = 0; key != m_key_count; key++) {
139 		if (state[key])
140 			for(auto s : m_keyed_shapes[key])
141 				s->flags |= NSVG_FLAGS_VISIBLE;
142 		else
143 			for(auto s : m_keyed_shapes[key])
144 				s->flags &= ~NSVG_FLAGS_VISIBLE;
145 	}
146 
147 	nsvgRasterize(m_rasterizer.get(), m_image.get(), 0, 0, m_scale, (unsigned char *)&dest[0], m_sx, m_sy, m_sx*4);
148 
149 	// Nanosvg generates non-premultiplied alpha, so remultiply by
150 	// alpha to "blend" against a black background.  Plus align the
151 	// channel order to what we do.
152 
153 	u8 *image = (u8 *)&dest[0];
154 	for(unsigned int pixel=0; pixel != m_sy*m_sx; pixel++) {
155 		u8 r = image[0];
156 		u8 g = image[1];
157 		u8 b = image[2];
158 		u8 a = image[3];
159 		if(a != 0xff) {
160 			r = r*a/255;
161 			g = g*a/255;
162 			b = b*a/255;
163 		}
164 		u32 color = 0xff000000 | (r << 16) | (g << 8) | (b << 0);
165 		*(u32 *)image = color;
166 		image += 4;
167 	}
168 }
169 
blit(bitmap_rgb32 & bitmap,const cached_bitmap & src) const170 void screen_device::svg_renderer::blit(bitmap_rgb32 &bitmap, const cached_bitmap &src) const
171 {
172 	if(src.sy) {
173 		const u32 *s = &src.image[0];
174 		for(int y=0; y<src.sy; y++) {
175 			u32 *d = &bitmap.pix(y + src.y, src.x);
176 			for(int x=0; x<src.sx; x++, d++) {
177 				const u32 c = *s++;
178 				if(c)
179 					*d = c;
180 			}
181 		}
182 	}
183 }
184 
render(screen_device & screen,bitmap_rgb32 & bitmap,const rectangle & cliprect)185 int screen_device::svg_renderer::render(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect)
186 {
187 	int nsx = bitmap.width();
188 	int nsy = bitmap.height();
189 
190 	if(nsx != m_sx || nsy != m_sy) {
191 		m_sx = nsx;
192 		m_sy = nsy;
193 		double sx = double(m_sx)/m_image->width;
194 		double sy = double(m_sy)/m_image->height;
195 		m_scale = sx > sy ? sy : sx;
196 		m_background.resize(m_sx * m_sy);
197 		rebuild_cache();
198 	}
199 
200 	for(unsigned int y = 0; y < m_sy; y++)
201 		memcpy(bitmap.raw_pixptr(y, 0), &m_background[y * m_sx], m_sx * 4);
202 
203 	std::list<int> to_draw;
204 	for(int key = 0; key != m_key_count; key++)
205 		if(m_key_state[key])
206 			to_draw.push_back(key);
207 	while(!to_draw.empty()) {
208 		int key = to_draw.front();
209 		to_draw.pop_front();
210 		blit(bitmap, m_cache[key]);
211 		for(auto p : m_cache[key].pairs) {
212 			if(m_key_state[p.key])
213 				to_draw.push_back(p.cache_entry);
214 		}
215 	}
216 
217 	return 0;
218 }
219 
output_notifier(const char * outname,s32 value,void * param)220 void screen_device::svg_renderer::output_notifier(const char *outname, s32 value, void *param)
221 {
222 	static_cast<svg_renderer *>(param)->output_change(outname, value);
223 }
224 
output_change(const char * outname,s32 value)225 void screen_device::svg_renderer::output_change(const char *outname, s32 value)
226 {
227 	auto l = m_key_ids.find(outname);
228 	if (l == m_key_ids.end())
229 		return;
230 	m_key_state[l->second] = value;
231 }
232 
compute_initial_bboxes(std::vector<bbox> & bboxes)233 void screen_device::svg_renderer::compute_initial_bboxes(std::vector<bbox> &bboxes)
234 {
235 	bboxes.resize(m_key_count);
236 	for(int key = 0; key != m_key_count; key++) {
237 		int x0, y0, x1, y1;
238 		x0 = y0 = x1 = y1 = -1;
239 		for(auto s : m_keyed_shapes[key]) {
240 			int xx0 = int(floor(s->bounds[0]*m_scale));
241 			int yy0 = int(floor(s->bounds[1]*m_scale));
242 			int xx1 = int(ceil (s->bounds[2]*m_scale)) + 1;
243 			int yy1 = int(ceil (s->bounds[3]*m_scale)) + 1;
244 			if(xx0 < 0)
245 				xx0 = 0;
246 			if(xx0 >= m_sx)
247 				xx0 = m_sx - 1;
248 			if(xx1 < 0)
249 				xx1 = 0;
250 			if(xx1 >= m_sx)
251 				xx1 = m_sx - 1;
252 			if(yy0 < 0)
253 				yy0 = 0;
254 			if(yy0 >= m_sy)
255 				yy0 = m_sy - 1;
256 			if(yy1 < 0)
257 				yy1 = 0;
258 			if(yy1 >= m_sy)
259 				yy1 = m_sy - 1;
260 
261 			if(x0 == -1) {
262 				x0 = xx0;
263 				y0 = yy0;
264 				x1 = xx1;
265 				y1 = yy1;
266 			} else {
267 				if(xx0 < x0)
268 					x0 = xx0;
269 				if(yy0 < y0)
270 					y0 = yy0;
271 				if(xx1 > x1)
272 					x1 = xx1;
273 				if(yy1 > y1)
274 					y1 = yy1;
275 			}
276 		}
277 		bboxes[key].x0 = x0;
278 		bboxes[key].y0 = y0;
279 		bboxes[key].x1 = x1;
280 		bboxes[key].y1 = y1;
281 	}
282 }
283 
compute_diff_image(const std::vector<u32> & rend,const bbox & bb,cached_bitmap & dest) const284 void screen_device::svg_renderer::compute_diff_image(const std::vector<u32> &rend, const bbox &bb, cached_bitmap &dest) const
285 {
286 	int x0, y0, x1, y1;
287 	x0 = y0 = x1 = y1 = -1;
288 	for(int y = bb.y0; y != bb.y1; y++) {
289 		const u32 *src1 = &m_background[bb.x0 + y * m_sx];
290 		const u32 *src2 = &rend[bb.x0 + y * m_sx];
291 		for(int x = bb.x0; x != bb.x1; x++) {
292 			if(*src1 != *src2) {
293 				if(x0 == -1) {
294 					x0 = x1 = x;
295 					y0 = y1 = y;
296 				} else {
297 					if(x < x0)
298 						x0 = x;
299 					if(y < y0)
300 						y0 = y;
301 					if(x > x1)
302 						x1 = x;
303 					if(y > y1)
304 						y1 = y;
305 				}
306 			}
307 			src1++;
308 			src2++;
309 		}
310 	}
311 	if(x0 == -1) {
312 		dest.x = dest.y = dest.sx = dest.sy = 0;
313 		return;
314 	}
315 
316 	dest.x = x0;
317 	dest.y = y0;
318 	dest.sx = x1+1-x0;
319 	dest.sy = y1+1-y0;
320 	dest.image.resize(dest.sx * dest.sy);
321 	u32 *dst = &dest.image[0];
322 	for(int y = 0; y != dest.sy; y++) {
323 		const u32 *src1 = &m_background[dest.x + (y + dest.y) * m_sx];
324 		const u32 *src2 = &rend[dest.x + (y + dest.y) * m_sx];
325 		for(int x = 0; x != dest.sx; x++) {
326 			if(*src1 != *src2)
327 				*dst = *src2;
328 			else
329 				*dst = 0x00000000;
330 			src1++;
331 			src2++;
332 			dst++;
333 		}
334 	}
335 
336 }
337 
compute_mask_intersection_bbox(int key1,int key2,bbox & bb) const338 bool screen_device::svg_renderer::compute_mask_intersection_bbox(int key1, int key2, bbox &bb) const
339 {
340 	const cached_bitmap &c1 = m_cache[key1];
341 	const cached_bitmap &c2 = m_cache[key2];
342 	if(c1.x >= c2.x + c2.sx ||
343 		c1.x + c1.sx <= c2.x ||
344 		c1.y >= c2.y + c2.sy ||
345 		c1.y + c1.sy <= c2.y)
346 		return false;
347 	int cx0 = c1.x > c2.x ? c1.x : c2.x;
348 	int cy0 = c1.y > c2.y ? c1.y : c2.y;
349 	int cx1 = c1.x + c1.sx < c2.x + c2.sx ? c1.x + c1.sx : c2.x + c2.sx;
350 	int cy1 = c1.y + c1.sy < c2.y + c2.sy ? c1.y + c1.sy : c2.y + c2.sy;
351 
352 	int x0, y0, x1, y1;
353 	x0 = y0 = x1 = y1 = -1;
354 
355 	for(int y = cy0; y < cy1; y++) {
356 		const u32 *src1 = &c1.image[(cx0 - c1.x) + c1.sx * (y - c1.y)];
357 		const u32 *src2 = &c2.image[(cx0 - c2.x) + c2.sx * (y - c2.y)];
358 		for(int x = cx0; x < cx1; x++) {
359 			if(*src1 && *src2 && *src1 != *src2) {
360 				if(x0 == -1) {
361 					x0 = x1 = x;
362 					y0 = y1 = y;
363 				} else {
364 					if(x < x0)
365 						x0 = x;
366 					if(y < y0)
367 						y0 = y;
368 					if(x > x1)
369 						x1 = x;
370 					if(y > y1)
371 						y1 = y;
372 				}
373 			}
374 			src1++;
375 			src2++;
376 		}
377 	}
378 	if(x0 == -1)
379 		return false;
380 	bb.x0 = x0;
381 	bb.x1 = x1;
382 	bb.y0 = y0;
383 	bb.y1 = y1;
384 	return true;
385 }
386 
compute_dual_diff_image(const std::vector<u32> & rend,const bbox & bb,const cached_bitmap & src1,const cached_bitmap & src2,cached_bitmap & dest) const387 void screen_device::svg_renderer::compute_dual_diff_image(const std::vector<u32> &rend, const bbox &bb, const cached_bitmap &src1, const cached_bitmap &src2, cached_bitmap &dest) const
388 {
389 	dest.x = bb.x0;
390 	dest.y = bb.y0;
391 	dest.sx = bb.x1 - bb.x0 + 1;
392 	dest.sy = bb.y1 - bb.y0 + 1;
393 	dest.image.resize(dest.sx*dest.sy);
394 	for(int y = 0; y != dest.sy; y++) {
395 		const u32 *psrc1 = &src1.image[(dest.x - src1.x) + src1.sx * (y + dest.y - src1.y)];
396 		const u32 *psrc2 = &src2.image[(dest.x - src2.x) + src2.sx * (y + dest.y - src2.y)];
397 		const u32 *psrcr = &rend      [ dest.x           +    m_sx * (y + dest.y         )];
398 		u32       *pdest = &dest.image[                    dest.sx *  y                   ];
399 		for(int x = 0; x != dest.sx; x++) {
400 			if(*psrc1 && *psrc2 && *psrc1 != *psrc2)
401 				*pdest = *psrcr;
402 			psrc1++;
403 			psrc2++;
404 			psrcr++;
405 			pdest++;
406 		}
407 	}
408 }
409 
rebuild_cache()410 void screen_device::svg_renderer::rebuild_cache()
411 {
412 	m_cache.clear();
413 	std::vector<u32> rend(m_sx*m_sy);
414 
415 	// Render the background, e.g. with everything off
416 	std::vector<bool> state(m_key_count);
417 	for(int key=0; key != m_key_count; key++)
418 		state[key] = false;
419 
420 	render_state(m_background, state);
421 
422 	// Render each individual element independently.  Try to reduce
423 	// the actual number of render passes with a greedy algorithm
424 	// using the element bounding boxes.
425 	std::vector<bbox> bboxes;
426 	compute_initial_bboxes(bboxes);
427 
428 	m_cache.resize(m_key_count);
429 	std::set<int> to_do;
430 	for(int key=0; key != m_key_count; key++)
431 		to_do.insert(key);
432 
433 	while(!to_do.empty()) {
434 		std::list<int> doing;
435 		for(int key : to_do) {
436 			for(int okey : doing) {
437 				// The bounding boxes include x1/y1, so the comparisons must be strict
438 				if(!(bboxes[key].x0 > bboxes[okey].x1 ||
439 						bboxes[key].x1 < bboxes[okey].x0 ||
440 						bboxes[key].y0 > bboxes[okey].y1 ||
441 						bboxes[key].y1 < bboxes[okey].y0))
442 					goto conflict;
443 			}
444 			doing.push_back(key);
445 		conflict:
446 			;
447 		}
448 		for(int key : doing)
449 			state[key] = true;
450 		render_state(rend, state);
451 		for(int key : doing) {
452 			state[key] = false;
453 			to_do.erase(key);
454 		}
455 		for(int key : doing)
456 			compute_diff_image(rend, bboxes[key], m_cache[key]);
457 	}
458 
459 	// Then it's time to pick up the interactions.
460 	int spos = 0;
461 	int epos = m_key_count;
462 	std::vector<std::list<int> > keys(m_key_count);
463 	std::vector<int> previous;
464 	for(int key = 0; key != m_key_count; key++)
465 		keys[key].push_back(key);
466 	int ckey = m_key_count;
467 	while(spos != epos) {
468 		for(int key = spos; key < epos-1; key++) {
469 			for(int key2 = keys[key].back()+1; key2 < m_key_count; key2++) {
470 				bbox bb;
471 				if(compute_mask_intersection_bbox(key, key2, bb)) {
472 					previous.resize(ckey+1);
473 					previous[ckey] = key;
474 					m_cache[key].pairs.push_back(paired_entry(key2, ckey));
475 					keys.push_back(keys[key]);
476 					keys.back().push_back(key2);
477 					bboxes.push_back(bb);
478 					ckey++;
479 				}
480 			}
481 		}
482 		m_cache.resize(ckey);
483 		std::set<int> to_do;
484 		for(int key = epos; key != ckey; key++)
485 			to_do.insert(key);
486 
487 		while(!to_do.empty()) {
488 			std::list<int> doing;
489 			for(int key : to_do) {
490 				for(int okey : doing) {
491 					// The bounding boxes include x1/y1, so the comparisons must be strict
492 					if(!(bboxes[key].x0 > bboxes[okey].x1 ||
493 							bboxes[key].x1 < bboxes[okey].x0 ||
494 							bboxes[key].y0 > bboxes[okey].y1 ||
495 							bboxes[key].y1 < bboxes[okey].y0))
496 						goto conflict2;
497 				}
498 				doing.push_back(key);
499 			conflict2:
500 				;
501 			}
502 			for(int key : doing)
503 				for(int akey : keys[key])
504 					state[akey] = true;
505 
506 			render_state(rend, state);
507 			for(int key : doing) {
508 				for(int akey : keys[key])
509 					state[akey] = false;
510 				to_do.erase(key);
511 			}
512 			for(int key : doing)
513 				compute_dual_diff_image(rend, bboxes[key], m_cache[previous[key]], m_cache[keys[key].back()], m_cache[key]);
514 		}
515 		spos = epos;
516 		epos = ckey;
517 	}
518 }
519 
520 //**************************************************************************
521 //  SCREEN DEVICE
522 //**************************************************************************
523 
524 //-------------------------------------------------
525 //  screen_device - constructor
526 //-------------------------------------------------
527 
screen_device(const machine_config & mconfig,const char * tag,device_t * owner,u32 clock)528 screen_device::screen_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
529 	: device_t(mconfig, SCREEN, tag, owner, clock)
530 	, m_type(SCREEN_TYPE_RASTER)
531 	, m_orientation(ROT0)
532 	, m_phys_aspect(0U, 0U)
533 	, m_oldstyle_vblank_supplied(false)
534 	, m_refresh(0)
535 	, m_vblank(0)
536 	, m_xoffset(0.0f)
537 	, m_yoffset(0.0f)
538 	, m_xscale(1.0f)
539 	, m_yscale(1.0f)
540 	, m_screen_update_ind16(*this)
541 	, m_screen_update_rgb32(*this)
542 	, m_screen_vblank(*this)
543 	, m_scanline_cb(*this)
544 	, m_palette(*this, finder_base::DUMMY_TAG)
545 	, m_video_attributes(0)
546 	, m_svg_region(*this, DEVICE_SELF)
547 	, m_container(nullptr)
548 	, m_max_width(100)
549 	, m_width(100)
550 	, m_height(100)
551 	, m_visarea(0, 99, 0, 99)
552 	, m_texformat()
553 	, m_curbitmap(0)
554 	, m_curtexture(0)
555 	, m_changed(true)
556 	, m_last_partial_scan(0)
557 	, m_partial_scan_hpos(-1)
558 	, m_color(rgb_t(0xff, 0xff, 0xff, 0xff))
559 	, m_brightness(0xff)
560 	, m_frame_period(DEFAULT_FRAME_PERIOD.as_attoseconds())
561 	, m_scantime(1)
562 	, m_pixeltime(1)
563 	, m_vblank_period(0)
564 	, m_vblank_start_time(attotime::zero)
565 	, m_vblank_end_time(attotime::zero)
566 	, m_vblank_begin_timer(nullptr)
567 	, m_vblank_end_timer(nullptr)
568 	, m_scanline0_timer(nullptr)
569 	, m_scanline_timer(nullptr)
570 	, m_frame_number(0)
571 	, m_partial_updates_this_frame(0)
572 {
573 	m_unique_id = m_id_counter;
574 	m_id_counter++;
575 	memset(m_texture, 0, sizeof(m_texture));
576 }
577 
578 
579 //-------------------------------------------------
580 //  ~screen_device - destructor
581 //-------------------------------------------------
582 
~screen_device()583 screen_device::~screen_device()
584 {
585 	destroy_scan_bitmaps();
586 }
587 
588 
589 //-------------------------------------------------
590 //  destroy_scan_bitmaps - destroy per-scanline
591 //  bitmaps if applicable
592 //-------------------------------------------------
593 
destroy_scan_bitmaps()594 void screen_device::destroy_scan_bitmaps()
595 {
596 	if (m_video_attributes & VIDEO_VARIABLE_WIDTH)
597 	{
598 		const bool screen16 = !m_screen_update_ind16.isnull();
599 		for (int j = 0; j < 2; j++)
600 		{
601 			for (bitmap_t* bitmap : m_scan_bitmaps[j])
602 			{
603 				if (screen16)
604 					delete (bitmap_ind16*)bitmap;
605 				else
606 					delete (bitmap_rgb32*)bitmap;
607 			}
608 			m_scan_bitmaps[j].clear();
609 		}
610 	}
611 }
612 
613 
614 //-------------------------------------------------
615 //  allocate_scan_bitmaps - allocate per-scanline
616 //  bitmaps if applicable
617 //-------------------------------------------------
618 
allocate_scan_bitmaps()619 void screen_device::allocate_scan_bitmaps()
620 {
621 	if (m_video_attributes & VIDEO_VARIABLE_WIDTH)
622 	{
623 		const bool screen16 = !m_screen_update_ind16.isnull();
624 		s32 effwidth = std::max(m_max_width, m_visarea.right() + 1);
625 		const s32 old_height = (s32)m_scan_widths.size();
626 		s32 effheight = std::max(m_height, m_visarea.bottom() + 1);
627 		if (old_height < effheight)
628 		{
629 			for (int i = old_height; i < effheight; i++)
630 			{
631 				for (int j = 0; j < 2; j++)
632 				{
633 					if (screen16)
634 						m_scan_bitmaps[j].push_back(new bitmap_ind16(effwidth, 1));
635 					else
636 						m_scan_bitmaps[j].push_back(new bitmap_rgb32(effwidth, 1));
637 				}
638 				m_scan_widths.push_back(m_width);
639 			}
640 		}
641 		else
642 		{
643 			for (int i = effheight; i < old_height; i++)
644 			{
645 				for (int j = 0; j < 2; j++)
646 				{
647 					if (screen16)
648 						delete (bitmap_ind16 *)m_scan_bitmaps[j][i];
649 					else
650 						delete (bitmap_rgb32 *)m_scan_bitmaps[j][i];
651 					m_scan_bitmaps[j].erase(m_scan_bitmaps[j].begin() + i);
652 				}
653 				m_scan_widths.erase(m_scan_widths.begin() + i);
654 			}
655 		}
656 	}
657 }
658 
659 //-------------------------------------------------
660 //  device_validity_check - verify device
661 //  configuration
662 //-------------------------------------------------
663 
device_validity_check(validity_checker & valid) const664 void screen_device::device_validity_check(validity_checker &valid) const
665 {
666 	// sanity check dimensions
667 	if (m_width <= 0 || m_height <= 0)
668 		osd_printf_error("Invalid display dimensions\n");
669 
670 	// sanity check display area
671 	if (m_type != SCREEN_TYPE_VECTOR && m_type != SCREEN_TYPE_SVG)
672 	{
673 		if (m_visarea.empty() || m_visarea.right() >= m_width || m_visarea.bottom() >= m_height)
674 			osd_printf_error("Invalid display area\n");
675 
676 		// sanity check screen formats
677 		if (m_screen_update_ind16.isnull() && m_screen_update_rgb32.isnull())
678 			osd_printf_error("Missing SCREEN_UPDATE function\n");
679 	}
680 	else
681 	{
682 		if (m_video_attributes & VIDEO_VARIABLE_WIDTH)
683 			osd_printf_error("Non-raster display cannot have a variable width\n");
684 	}
685 
686 	// check for zero frame rate
687 	if (m_refresh == 0)
688 		osd_printf_error("Invalid (zero) refresh rate\n");
689 
690 	texture_format texformat = !m_screen_update_ind16.isnull() ? TEXFORMAT_PALETTE16 : TEXFORMAT_RGB32;
691 	if (m_palette.finder_tag() != finder_base::DUMMY_TAG)
692 	{
693 		if (!m_palette)
694 			osd_printf_error("Screen references non-existent palette tag %s\n", m_palette.finder_tag());
695 
696 		if (texformat == TEXFORMAT_RGB32)
697 			osd_printf_warning("Screen does not need palette defined\n");
698 	}
699 	else if (texformat == TEXFORMAT_PALETTE16)
700 	{
701 		osd_printf_error("Screen does not have palette defined\n");
702 	}
703 }
704 
705 
706 //-------------------------------------------------
707 //  device_config_complete - finalise static
708 //  configuration
709 //-------------------------------------------------
710 
device_config_complete()711 void screen_device::device_config_complete()
712 {
713 	// combine orientation with machine orientation
714 	m_orientation = orientation_add(m_orientation, mconfig().gamedrv().flags & machine_flags::MASK_ORIENTATION);
715 }
716 
717 
718 //-------------------------------------------------
719 //  physical_aspect - determine the physical
720 //  aspect ratio to be used for rendering
721 //-------------------------------------------------
722 
physical_aspect() const723 std::pair<unsigned, unsigned> screen_device::physical_aspect() const
724 {
725 	assert(configured());
726 
727 	std::pair<unsigned, unsigned> phys_aspect = m_phys_aspect;
728 
729 	// physical aspect ratio unconfigured
730 	if (!phys_aspect.first || !phys_aspect.second)
731 	{
732 		switch (m_type)
733 		{
734 		case SCREEN_TYPE_RASTER:
735 		case SCREEN_TYPE_VECTOR:
736 			phys_aspect = std::make_pair(4, 3); // assume standard CRT
737 			break;
738 		case SCREEN_TYPE_LCD:
739 		case SCREEN_TYPE_SVG:
740 			phys_aspect = std::make_pair(~0U, ~0U); // assume square pixels
741 			break;
742 		case SCREEN_TYPE_INVALID:
743 		default:
744 			throw emu_fatalerror("%s: invalid screen type configured\n", tag());
745 		}
746 	}
747 
748 	// square pixels?
749 	if ((~0U == phys_aspect.first) && (~0U == phys_aspect.second))
750 	{
751 		phys_aspect.first = visible_area().width();
752 		phys_aspect.second = visible_area().height();
753 	}
754 
755 	// always keep this in reduced form
756 	util::reduce_fraction(phys_aspect.first, phys_aspect.second);
757 
758 	return phys_aspect;
759 }
760 
761 
762 //-------------------------------------------------
763 //  device_resolve_objects - resolve objects that
764 //  may be needed for other devices to set
765 //  initial conditions at start time
766 //-------------------------------------------------
767 
device_resolve_objects()768 void screen_device::device_resolve_objects()
769 {
770 	// bind our handlers
771 	m_screen_update_ind16.resolve();
772 	m_screen_update_rgb32.resolve();
773 	m_screen_vblank.resolve_safe();
774 	m_scanline_cb.resolve();
775 
776 	// assign our format to the palette before it starts
777 	if (m_palette)
778 		m_palette->m_format = format();
779 }
780 
781 
782 //-------------------------------------------------
783 //  device_start - device-specific startup
784 //-------------------------------------------------
785 
device_start()786 void screen_device::device_start()
787 {
788 	// if we have a palette and it's not started, wait for it
789 	if (m_palette && !m_palette->device().started())
790 		throw device_missing_dependencies();
791 
792 	if (m_type == SCREEN_TYPE_SVG)
793 	{
794 		if (!m_svg_region)
795 			fatalerror("%s: SVG region \"%s\" does not exist\n", tag(), m_svg_region.finder_tag());
796 		m_svg = std::make_unique<svg_renderer>(m_svg_region);
797 		machine().output().set_notifier(nullptr, svg_renderer::output_notifier, m_svg.get());
798 
799 		// don't do this - SVG units are arbitrary and interpreting them as pixels causes bad things to happen
800 		// just render at the size/aspect ratio supplied by the driver
801 		if (false)
802 		{
803 			// The OSD picks up the size before start is called, so this only affect the info display if it's called up in-game
804 			m_width = m_svg->width();
805 			m_height = m_svg->height();
806 			m_visarea.set(0, m_width - 1, 0, m_height - 1);
807 		}
808 	}
809 
810 	// configure bitmap formats and allocate screen bitmaps
811 	// svg is RGB32 too, and doesn't have any update method
812 	const bool screen16 = !m_screen_update_ind16.isnull();
813 	texture_format texformat = screen16 ? TEXFORMAT_PALETTE16 : TEXFORMAT_RGB32;
814 
815 	for (auto & elem : m_bitmap)
816 	{
817 		elem.set_format(format(), texformat);
818 		register_screen_bitmap(elem);
819 	}
820 	register_screen_bitmap(m_priority);
821 
822 	// allocate raw textures
823 	m_texture[0] = machine().render().texture_alloc();
824 	m_texture[0]->set_id(u64(m_unique_id) << 57);
825 	m_texture[1] = machine().render().texture_alloc();
826 	m_texture[1]->set_id((u64(m_unique_id) << 57) | 1);
827 
828 	// configure the default cliparea
829 	render_container::user_settings settings;
830 	m_container->get_user_settings(settings);
831 	settings.m_xoffset = m_xoffset;
832 	settings.m_yoffset = m_yoffset;
833 	settings.m_xscale = m_xscale;
834 	settings.m_yscale = m_yscale;
835 	m_container->set_user_settings(settings);
836 
837 	// allocate the VBLANK timers
838 	m_vblank_begin_timer = timer_alloc(TID_VBLANK_START);
839 	m_vblank_end_timer = timer_alloc(TID_VBLANK_END);
840 
841 	// allocate a timer to reset partial updates
842 	m_scanline0_timer = timer_alloc(TID_SCANLINE0);
843 
844 	// allocate a timer to generate per-scanline updates
845 	if ((m_video_attributes & VIDEO_UPDATE_SCANLINE) != 0 || m_scanline_cb)
846 		m_scanline_timer = timer_alloc(TID_SCANLINE);
847 
848 	// configure the screen with the default parameters
849 	configure(m_width, m_height, m_visarea, m_refresh);
850 
851 	// reset VBLANK timing
852 	m_vblank_start_time = attotime::zero;
853 	m_vblank_end_time = attotime(0, m_vblank_period);
854 
855 	// start the timer to generate per-scanline updates
856 	if ((m_video_attributes & VIDEO_UPDATE_SCANLINE) != 0 || m_scanline_cb)
857 		m_scanline_timer->adjust(time_until_pos(0));
858 
859 	// create burn-in bitmap
860 	if (machine().options().burnin())
861 	{
862 		int width, height;
863 		if (sscanf(machine().options().snap_size(), "%dx%d", &width, &height) != 2 || width == 0 || height == 0)
864 			width = height = 300;
865 		m_burnin.allocate(width, height);
866 		m_burnin.fill(0);
867 	}
868 
869 	// load the effect overlay
870 	const char *overname = machine().options().effect();
871 	if (overname != nullptr && strcmp(overname, "none") != 0)
872 		load_effect_overlay(overname);
873 
874 	// register items for saving
875 	save_item(NAME(m_width));
876 	save_item(NAME(m_height));
877 	save_item(NAME(m_visarea.min_x));
878 	save_item(NAME(m_visarea.min_y));
879 	save_item(NAME(m_visarea.max_x));
880 	save_item(NAME(m_visarea.max_y));
881 	save_item(NAME(m_last_partial_scan));
882 	save_item(NAME(m_frame_period));
883 	save_item(NAME(m_brightness));
884 	save_item(NAME(m_scantime));
885 	save_item(NAME(m_pixeltime));
886 	save_item(NAME(m_vblank_period));
887 	save_item(NAME(m_vblank_start_time));
888 	save_item(NAME(m_vblank_end_time));
889 	save_item(NAME(m_frame_number));
890 	if (m_oldstyle_vblank_supplied)
891 		logerror("%s: Deprecated legacy Old Style screen configured (MCFG_SCREEN_VBLANK_TIME), please use MCFG_SCREEN_RAW_PARAMS instead.\n",this->tag());
892 
893 	m_is_primary_screen = (this == screen_device_iterator(machine().root_device()).first());
894 }
895 
896 
897 //-------------------------------------------------
898 //  device_start - device-specific startup
899 //-------------------------------------------------
900 
device_reset()901 void screen_device::device_reset()
902 {
903 	// reset brightness to default
904 	m_brightness = 0xff;
905 }
906 
907 
908 //-------------------------------------------------
909 //  device_stop - clean up before the machine goes
910 //  away
911 //-------------------------------------------------
912 
device_stop()913 void screen_device::device_stop()
914 {
915 	machine().render().texture_free(m_texture[0]);
916 	machine().render().texture_free(m_texture[1]);
917 	if (m_burnin.valid())
918 		finalize_burnin();
919 }
920 
921 
922 //-------------------------------------------------
923 //  device_post_load - device-specific update
924 //  after a save state is loaded
925 //-------------------------------------------------
926 
device_post_load()927 void screen_device::device_post_load()
928 {
929 	realloc_screen_bitmaps();
930 }
931 
932 
933 //-------------------------------------------------
934 //  device_timer - called whenever a device timer
935 //  fires
936 //-------------------------------------------------
937 
device_timer(emu_timer & timer,device_timer_id id,int param,void * ptr)938 void screen_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
939 {
940 	switch (id)
941 	{
942 		// signal VBLANK start
943 		case TID_VBLANK_START:
944 			vblank_begin();
945 			break;
946 
947 		// signal VBLANK end
948 		case TID_VBLANK_END:
949 			vblank_end();
950 			break;
951 
952 		// first scanline
953 		case TID_SCANLINE0:
954 			reset_partial_updates();
955 			if (m_video_attributes & VIDEO_VARIABLE_WIDTH)
956 			{
957 				pre_update_scanline(0);
958 			}
959 			break;
960 
961 		// subsequent scanlines when scanline updates are enabled
962 		case TID_SCANLINE:
963 			if (m_video_attributes & VIDEO_VARIABLE_WIDTH)
964 			{
965 				pre_update_scanline(param);
966 			}
967 			if (m_video_attributes & VIDEO_UPDATE_SCANLINE)
968 			{
969 				// force a partial update to the current scanline
970 				update_partial(param);
971 			}
972 			if (m_scanline_cb)
973 				m_scanline_cb(param);
974 
975 			// compute the next visible scanline
976 			param++;
977 			if (param > m_visarea.bottom())
978 				param = m_visarea.top();
979 			m_scanline_timer->adjust(time_until_pos(param), param);
980 			break;
981 	}
982 }
983 
984 
985 //-------------------------------------------------
986 //  configure - configure screen parameters
987 //-------------------------------------------------
988 
configure(int width,int height,const rectangle & visarea,attoseconds_t frame_period)989 void screen_device::configure(int width, int height, const rectangle &visarea, attoseconds_t frame_period)
990 {
991 	// validate arguments
992 	assert(width > 0);
993 	assert(height > 0);
994 	assert(visarea.left() >= 0);
995 	assert(visarea.top() >= 0);
996 //  assert(visarea.right() < width);
997 //  assert(visarea.bottom() < height);
998 	assert(m_type == SCREEN_TYPE_VECTOR || m_type == SCREEN_TYPE_SVG || visarea.left() < width);
999 	assert(m_type == SCREEN_TYPE_VECTOR || m_type == SCREEN_TYPE_SVG || visarea.top() < height);
1000 	assert(frame_period > 0);
1001 
1002 	// fill in the new parameters
1003 	m_max_width = std::max(m_max_width, width);
1004 	m_width = width;
1005 	m_height = height;
1006 	m_visarea = visarea;
1007 
1008 	// reallocate bitmap(s) if necessary
1009 	realloc_screen_bitmaps();
1010 
1011 	// compute timing parameters
1012 	m_frame_period = frame_period;
1013 	m_scantime = frame_period / height;
1014 	m_pixeltime = frame_period / (height * width);
1015 
1016 	// if an old style VBLANK_TIME was specified in the MACHINE_CONFIG,
1017 	// use it; otherwise calculate the VBLANK period from the visible area
1018 	if (m_oldstyle_vblank_supplied)
1019 		m_vblank_period = m_vblank;
1020 	else
1021 		m_vblank_period = m_scantime * (height - visarea.height());
1022 
1023 	// we are now fully configured with the new parameters
1024 	// and can safely call time_until_pos(), etc.
1025 
1026 	// if the frame period was reduced so that we are now past the end of the frame,
1027 	// call the VBLANK start timer now; otherwise, adjust it for the future
1028 	attoseconds_t delta = (machine().time() - m_vblank_start_time).as_attoseconds();
1029 	if (delta >= m_frame_period)
1030 		vblank_begin();
1031 	else
1032 		m_vblank_begin_timer->adjust(time_until_vblank_start());
1033 
1034 	// if we are on scanline 0 already, call the scanline 0 timer
1035 	// by hand now; otherwise, adjust it for the future
1036 	if (vpos() == 0)
1037 		reset_partial_updates();
1038 	else
1039 		m_scanline0_timer->adjust(time_until_pos(0));
1040 
1041 	// adjust speed if necessary
1042 	machine().video().update_refresh_speed();
1043 }
1044 
1045 
1046 //-------------------------------------------------
1047 //  reset_origin - reset the timing such that the
1048 //  given (x,y) occurs at the current time
1049 //-------------------------------------------------
1050 
reset_origin(int beamy,int beamx)1051 void screen_device::reset_origin(int beamy, int beamx)
1052 {
1053 	// compute the effective VBLANK start/end times
1054 	attotime curtime = machine().time();
1055 	m_vblank_end_time = curtime - attotime(0, beamy * m_scantime + beamx * m_pixeltime);
1056 	m_vblank_start_time = m_vblank_end_time - attotime(0, m_vblank_period);
1057 
1058 	// if we are resetting relative to (0,0) == VBLANK end, call the
1059 	// scanline 0 timer by hand now; otherwise, adjust it for the future
1060 	if (beamy == 0 && beamx == 0)
1061 		reset_partial_updates();
1062 	else
1063 		m_scanline0_timer->adjust(time_until_pos(0));
1064 
1065 	// if we are resetting relative to (visarea.bottom() + 1, 0) == VBLANK start,
1066 	// call the VBLANK start timer now; otherwise, adjust it for the future
1067 	if (beamy == ((m_visarea.bottom() + 1) % m_height) && beamx == 0)
1068 		vblank_begin();
1069 	else
1070 		m_vblank_begin_timer->adjust(time_until_vblank_start());
1071 }
1072 
1073 
1074 //-------------------------------------------------
1075 //  update_scan_bitmap_size - reallocate the
1076 //  bitmap for a specific scanline
1077 //-------------------------------------------------
1078 
update_scan_bitmap_size(int y)1079 void screen_device::update_scan_bitmap_size(int y)
1080 {
1081 	// don't update this line if it exceeds the allocated size, which can happen on initial configuration
1082 	if (y >= m_scan_widths.size())
1083 		return;
1084 
1085 	// determine effective size to allocate
1086 	s32 effwidth = std::max(m_max_width, m_visarea.right() + 1);
1087 
1088 	if (m_scan_widths[y] == effwidth)
1089 		return;
1090 
1091 	m_scan_bitmaps[m_curbitmap][y]->resize(effwidth, 1);
1092 	m_scan_widths[y] = effwidth;
1093 }
1094 
1095 
1096 //-------------------------------------------------
1097 //  realloc_screen_bitmaps - reallocate bitmaps
1098 //  and textures as necessary
1099 //-------------------------------------------------
1100 
realloc_screen_bitmaps()1101 void screen_device::realloc_screen_bitmaps()
1102 {
1103 	// doesn't apply for vector games
1104 	if (m_type == SCREEN_TYPE_VECTOR)
1105 		return;
1106 
1107 	// determine effective size to allocate
1108 	const bool per_scanline = (m_video_attributes & VIDEO_VARIABLE_WIDTH);
1109 	s32 effwidth = std::max(per_scanline ? m_max_width : m_width, m_visarea.right() + 1);
1110 	s32 effheight = std::max(m_height, m_visarea.bottom() + 1);
1111 
1112 	// reize all registered screen bitmaps
1113 	for (auto &item : m_auto_bitmap_list)
1114 		item->m_bitmap.resize(effwidth, effheight);
1115 
1116 	// re-set up textures
1117 	if (m_palette)
1118 	{
1119 		m_bitmap[0].set_palette(m_palette->palette());
1120 		m_bitmap[1].set_palette(m_palette->palette());
1121 	}
1122 	m_texture[0]->set_bitmap(m_bitmap[0], m_visarea, m_bitmap[0].texformat());
1123 	m_texture[1]->set_bitmap(m_bitmap[1], m_visarea, m_bitmap[1].texformat());
1124 
1125 	allocate_scan_bitmaps();
1126 }
1127 
1128 
1129 //-------------------------------------------------
1130 //  pre_update_scanline - check if the bitmap for
1131 //  a specific scanline needs its size updated
1132 //-------------------------------------------------
1133 
pre_update_scanline(int y)1134 void screen_device::pre_update_scanline(int y)
1135 {
1136 	update_scan_bitmap_size(y);
1137 }
1138 
1139 
1140 //-------------------------------------------------
1141 //  set_visible_area - just set the visible area
1142 //-------------------------------------------------
1143 
set_visible_area(int min_x,int max_x,int min_y,int max_y)1144 void screen_device::set_visible_area(int min_x, int max_x, int min_y, int max_y)
1145 {
1146 	rectangle visarea(min_x, max_x, min_y, max_y);
1147 	assert(!visarea.empty());
1148 	configure(m_width, m_height, visarea, m_frame_period);
1149 }
1150 
1151 
1152 //-------------------------------------------------
1153 //  update_partial - perform a partial update from
1154 //  the last scanline up to and including the
1155 //  specified scanline
1156 //-----------------------------------------------*/
1157 
update_partial(int scanline)1158 bool screen_device::update_partial(int scanline)
1159 {
1160 	LOG_PARTIAL_UPDATES(("Partial: update_partial(%s, %d): ", tag(), scanline));
1161 
1162 	// these two checks only apply if we're allowed to skip frames
1163 	if (!(m_video_attributes & VIDEO_ALWAYS_UPDATE))
1164 	{
1165 		// if skipping this frame, bail
1166 		if (machine().video().skip_this_frame())
1167 		{
1168 			LOG_PARTIAL_UPDATES(("skipped due to frameskipping\n"));
1169 			return false;
1170 		}
1171 
1172 		// skip if this screen is not visible anywhere
1173 		if (!machine().render().is_live(*this))
1174 		{
1175 			LOG_PARTIAL_UPDATES(("skipped because screen not live\n"));
1176 			return false;
1177 		}
1178 	}
1179 
1180 	// skip if we already rendered this line
1181 	if (scanline < m_last_partial_scan)
1182 	{
1183 		LOG_PARTIAL_UPDATES(("skipped because line was already rendered\n"));
1184 		return false;
1185 	}
1186 
1187 	// set the range of scanlines to render
1188 	rectangle clip(m_visarea);
1189 	clip.sety((std::max)(clip.top(), m_last_partial_scan), (std::min)(clip.bottom(), scanline));
1190 
1191 	// skip if entirely outside of visible area
1192 	if (clip.top() > clip.bottom())
1193 	{
1194 		LOG_PARTIAL_UPDATES(("skipped because outside of visible area\n"));
1195 		return false;
1196 	}
1197 
1198 	// otherwise, render
1199 	LOG_PARTIAL_UPDATES(("updating %d-%d\n", clip.top(), clip.bottom()));
1200 	g_profiler.start(PROFILER_VIDEO);
1201 
1202 	u32 flags = 0;
1203 	if (m_video_attributes & VIDEO_VARIABLE_WIDTH)
1204 	{
1205 		rectangle scan_clip(clip);
1206 		for (int y = clip.top(); y <= clip.bottom(); y++)
1207 		{
1208 			scan_clip.sety(y, y);
1209 			pre_update_scanline(y);
1210 
1211 			screen_bitmap &curbitmap = m_bitmap[m_curbitmap];
1212 			switch (curbitmap.format())
1213 			{
1214 				default:
1215 				case BITMAP_FORMAT_IND16:   flags |= m_screen_update_ind16(*this, *(bitmap_ind16 *)m_scan_bitmaps[m_curbitmap][y], scan_clip);   break;
1216 				case BITMAP_FORMAT_RGB32:   flags |= m_screen_update_rgb32(*this, *(bitmap_rgb32 *)m_scan_bitmaps[m_curbitmap][y], scan_clip);   break;
1217 			}
1218 
1219 			m_partial_updates_this_frame++;
1220 		}
1221 	}
1222 	else
1223 	{
1224 		if (m_type != SCREEN_TYPE_SVG)
1225 		{
1226 			screen_bitmap &curbitmap = m_bitmap[m_curbitmap];
1227 			switch (curbitmap.format())
1228 			{
1229 				default:
1230 				case BITMAP_FORMAT_IND16:   flags = m_screen_update_ind16(*this, curbitmap.as_ind16(), clip);   break;
1231 				case BITMAP_FORMAT_RGB32:   flags = m_screen_update_rgb32(*this, curbitmap.as_rgb32(), clip);   break;
1232 			}
1233 		}
1234 		else
1235 		{
1236 			flags = m_svg->render(*this, m_bitmap[m_curbitmap].as_rgb32(), clip);
1237 		}
1238 		m_partial_updates_this_frame++;
1239 	}
1240 
1241 	g_profiler.stop();
1242 
1243 	// if we modified the bitmap, we have to commit
1244 	m_changed |= ~flags & UPDATE_HAS_NOT_CHANGED;
1245 
1246 	// remember where we left off
1247 	m_last_partial_scan = scanline + 1;
1248 	m_partial_scan_hpos = -1;
1249 	return true;
1250 }
1251 
1252 
1253 //-------------------------------------------------
1254 //  update_now - perform an update from the last
1255 //  beam position up to the current beam position
1256 //-------------------------------------------------
1257 
update_now()1258 void screen_device::update_now()
1259 {
1260 	// these two checks only apply if we're allowed to skip frames
1261 	if (!(m_video_attributes & VIDEO_ALWAYS_UPDATE))
1262 	{
1263 		// if skipping this frame, bail
1264 		if (machine().video().skip_this_frame())
1265 		{
1266 			LOG_PARTIAL_UPDATES(("skipped due to frameskipping\n"));
1267 			return;
1268 		}
1269 
1270 		// skip if this screen is not visible anywhere
1271 		if (!machine().render().is_live(*this))
1272 		{
1273 			LOG_PARTIAL_UPDATES(("skipped because screen not live\n"));
1274 			return;
1275 		}
1276 	}
1277 
1278 	int current_vpos = vpos();
1279 	int current_hpos = hpos();
1280 	rectangle clip = m_visarea;
1281 
1282 	// skip if we already rendered this line
1283 	if (current_vpos < m_last_partial_scan)
1284 	{
1285 		LOG_PARTIAL_UPDATES(("skipped because line was already rendered\n"));
1286 		return;
1287 	}
1288 
1289 	// if beam position is the same, there's nothing to update
1290 	if (current_vpos == m_last_partial_scan && current_hpos == m_partial_scan_hpos)
1291 	{
1292 		LOG_PARTIAL_UPDATES(("skipped because beam position is unchanged\n"));
1293 		return;
1294 	}
1295 
1296 	LOG_PARTIAL_UPDATES(("update_now(): Y=%d, X=%d, last partial %d, partial hpos %d  (vis %d %d)\n", current_vpos, current_hpos, m_last_partial_scan, m_partial_scan_hpos, m_visarea.right(), m_visarea.bottom()));
1297 
1298 	// start off by doing a partial update up to the line before us, in case that was necessary
1299 	if (current_vpos > m_last_partial_scan)
1300 	{
1301 		// if the line before us was incomplete, we must do it in two pieces
1302 		if (m_partial_scan_hpos >= 0)
1303 		{
1304 			// now finish the previous partial scanline
1305 			clip.set((std::max)(clip.left(), m_partial_scan_hpos + 1),
1306 					 clip.right(),
1307 					 (std::max)(clip.top(), m_last_partial_scan),
1308 					 (std::min)(clip.bottom(), m_last_partial_scan));
1309 
1310 			// if there's something to draw, do it
1311 			if (!clip.empty())
1312 			{
1313 				g_profiler.start(PROFILER_VIDEO);
1314 
1315 				u32 flags = 0;
1316 				screen_bitmap &curbitmap = m_bitmap[m_curbitmap];
1317 				if (m_video_attributes & VIDEO_VARIABLE_WIDTH)
1318 				{
1319 					pre_update_scanline(m_last_partial_scan);
1320 					switch (curbitmap.format())
1321 					{
1322 						default:
1323 						case BITMAP_FORMAT_IND16:   flags = m_screen_update_ind16(*this, *(bitmap_ind16 *)m_scan_bitmaps[m_curbitmap][m_last_partial_scan], clip);   break;
1324 						case BITMAP_FORMAT_RGB32:   flags = m_screen_update_rgb32(*this, *(bitmap_rgb32 *)m_scan_bitmaps[m_curbitmap][m_last_partial_scan], clip);   break;
1325 					}
1326 				}
1327 				else
1328 				{
1329 					switch (curbitmap.format())
1330 					{
1331 						default:
1332 						case BITMAP_FORMAT_IND16:   flags = m_screen_update_ind16(*this, curbitmap.as_ind16(), clip);   break;
1333 						case BITMAP_FORMAT_RGB32:   flags = m_screen_update_rgb32(*this, curbitmap.as_rgb32(), clip);   break;
1334 					}
1335 				}
1336 
1337 				g_profiler.stop();
1338 				m_partial_updates_this_frame++;
1339 
1340 				// if we modified the bitmap, we have to commit
1341 				m_changed |= ~flags & UPDATE_HAS_NOT_CHANGED;
1342 			}
1343 
1344 			m_partial_scan_hpos = -1;
1345 			m_last_partial_scan++;
1346 		}
1347 		if (current_vpos > m_last_partial_scan)
1348 		{
1349 			update_partial(current_vpos - 1);
1350 		}
1351 	}
1352 
1353 	// now draw this partial scanline
1354 	clip = m_visarea;
1355 
1356 	clip.set((std::max)(clip.left(), m_partial_scan_hpos + 1),
1357 			 (std::min)(clip.right(), current_hpos),
1358 			 (std::max)(clip.top(), current_vpos),
1359 			 (std::min)(clip.bottom(), current_vpos));
1360 
1361 	// and if there's something to draw, do it
1362 	if (!clip.empty())
1363 	{
1364 		g_profiler.start(PROFILER_VIDEO);
1365 
1366 		LOG_PARTIAL_UPDATES(("doing scanline partial draw: Y %d X %d-%d\n", clip.bottom(), clip.left(), clip.right()));
1367 
1368 		u32 flags = 0;
1369 		screen_bitmap &curbitmap = m_bitmap[m_curbitmap];
1370 		if (m_video_attributes & VIDEO_VARIABLE_WIDTH)
1371 		{
1372 			pre_update_scanline(current_vpos);
1373 			switch (curbitmap.format())
1374 			{
1375 				default:
1376 				case BITMAP_FORMAT_IND16:   flags = m_screen_update_ind16(*this, *(bitmap_ind16 *)m_scan_bitmaps[m_curbitmap][current_vpos], clip);   break;
1377 				case BITMAP_FORMAT_RGB32:   flags = m_screen_update_rgb32(*this, *(bitmap_rgb32 *)m_scan_bitmaps[m_curbitmap][current_vpos], clip);   break;
1378 			}
1379 		}
1380 		else
1381 		{
1382 			switch (curbitmap.format())
1383 			{
1384 				default:
1385 				case BITMAP_FORMAT_IND16:   flags = m_screen_update_ind16(*this, curbitmap.as_ind16(), clip);   break;
1386 				case BITMAP_FORMAT_RGB32:   flags = m_screen_update_rgb32(*this, curbitmap.as_rgb32(), clip);   break;
1387 			}
1388 		}
1389 
1390 		m_partial_updates_this_frame++;
1391 		g_profiler.stop();
1392 
1393 		// if we modified the bitmap, we have to commit
1394 		m_changed |= ~flags & UPDATE_HAS_NOT_CHANGED;
1395 	}
1396 
1397 	// remember where we left off
1398 	m_partial_scan_hpos = current_hpos;
1399 	m_last_partial_scan = current_vpos;
1400 }
1401 
1402 
1403 //-------------------------------------------------
1404 //  reset_partial_updates - reset the partial
1405 //  updating state
1406 //-------------------------------------------------
1407 
reset_partial_updates()1408 void screen_device::reset_partial_updates()
1409 {
1410 	m_last_partial_scan = 0;
1411 	m_partial_scan_hpos = -1;
1412 	m_partial_updates_this_frame = 0;
1413 	m_scanline0_timer->adjust(time_until_pos(0));
1414 }
1415 
1416 
1417 //-------------------------------------------------
1418 //  pixel - returns the RGB value of the specified
1419 //  pixel location
1420 //-------------------------------------------------
1421 
pixel(s32 x,s32 y)1422 u32 screen_device::pixel(s32 x, s32 y)
1423 {
1424 	screen_bitmap &curbitmap = m_bitmap[m_curbitmap];
1425 	if (!curbitmap.valid())
1426 		return 0;
1427 
1428 	const int srcwidth = curbitmap.width();
1429 	const int srcheight = curbitmap.height();
1430 
1431 	if (x < 0 || y < 0 || x >= srcwidth || y >= srcheight)
1432 		return 0;
1433 
1434 	const bool per_scanline = (m_video_attributes & VIDEO_VARIABLE_WIDTH);
1435 
1436 	switch (curbitmap.format())
1437 	{
1438 		case BITMAP_FORMAT_IND16:
1439 		{
1440 			bitmap_ind16 &srcbitmap = per_scanline ? *(bitmap_ind16 *)m_scan_bitmaps[m_curbitmap][y] : curbitmap.as_ind16();
1441 			const u16 src = per_scanline ? srcbitmap.pix(0, x) : srcbitmap.pix(y, x);
1442 			const rgb_t *palette = m_palette->palette()->entry_list_adjusted();
1443 			return (u32)palette[src];
1444 		}
1445 
1446 		case BITMAP_FORMAT_RGB32:
1447 		{
1448 			if (per_scanline)
1449 			{
1450 				return (u32)(*(bitmap_rgb32 *)m_scan_bitmaps[m_curbitmap][y]).pix(0, x);
1451 			}
1452 			else
1453 			{
1454 				return (u32)curbitmap.as_rgb32().pix(y, x);
1455 			}
1456 		}
1457 
1458 		default:
1459 			return 0;
1460 	}
1461 }
1462 
1463 
1464 //-------------------------------------------------
1465 //  pixels - fills the specified buffer with the
1466 //  RGB values of each pixel in the screen.
1467 //-------------------------------------------------
1468 
pixels(u32 * buffer)1469 void screen_device::pixels(u32 *buffer)
1470 {
1471 	screen_bitmap &curbitmap = m_bitmap[m_curbitmap];
1472 	if (!curbitmap.valid())
1473 		return;
1474 
1475 	const rectangle &visarea = visible_area();
1476 
1477 	const bool per_scanline = (m_video_attributes & VIDEO_VARIABLE_WIDTH);
1478 
1479 	switch (curbitmap.format())
1480 	{
1481 		case BITMAP_FORMAT_IND16:
1482 		{
1483 			const rgb_t *palette = m_palette->palette()->entry_list_adjusted();
1484 			for (int y = visarea.min_y; y <= visarea.max_y; y++)
1485 			{
1486 				bitmap_ind16 &srcbitmap = per_scanline ? *(bitmap_ind16 *)m_scan_bitmaps[m_curbitmap][y] : curbitmap.as_ind16();
1487 				const u16 *src = &srcbitmap.pix(per_scanline ? 0 : y, visarea.min_x);
1488 				for (int x = visarea.min_x; x <= visarea.max_x; x++)
1489 				{
1490 					*buffer++ = palette[*src++];
1491 				}
1492 			}
1493 			break;
1494 		}
1495 
1496 		case BITMAP_FORMAT_RGB32:
1497 		{
1498 			for (int y = visarea.min_y; y <= visarea.max_y; y++)
1499 			{
1500 				bitmap_rgb32 &srcbitmap = per_scanline ? *(bitmap_rgb32 *)m_scan_bitmaps[m_curbitmap][y] : curbitmap.as_rgb32();
1501 				const u32 *src = &srcbitmap.pix(per_scanline ? 0 : y, visarea.min_x);
1502 				for (int x = visarea.min_x; x <= visarea.max_x; x++)
1503 				{
1504 					*buffer++ = *src++;
1505 				}
1506 			}
1507 			break;
1508 		}
1509 
1510 		default:
1511 			break;
1512 	}
1513 }
1514 
1515 
1516 //-------------------------------------------------
1517 //  vpos - returns the current vertical position
1518 //  of the beam
1519 //-------------------------------------------------
1520 
vpos() const1521 int screen_device::vpos() const
1522 {
1523 	attoseconds_t delta = (machine().time() - m_vblank_start_time).as_attoseconds();
1524 	int vpos;
1525 
1526 	// round to the nearest pixel
1527 	delta += m_pixeltime / 2;
1528 
1529 	// compute the v position relative to the start of VBLANK
1530 	vpos = delta / m_scantime;
1531 
1532 	// adjust for the fact that VBLANK starts at the bottom of the visible area
1533 	return (m_visarea.bottom() + 1 + vpos) % m_height;
1534 }
1535 
1536 
1537 //-------------------------------------------------
1538 //  hpos - returns the current horizontal position
1539 //  of the beam
1540 //-------------------------------------------------
1541 
hpos() const1542 int screen_device::hpos() const
1543 {
1544 	attoseconds_t delta = (machine().time() - m_vblank_start_time).as_attoseconds();
1545 
1546 	// round to the nearest pixel
1547 	delta += m_pixeltime / 2;
1548 
1549 	// compute the v position relative to the start of VBLANK
1550 	int vpos = delta / m_scantime;
1551 
1552 	// subtract that from the total time
1553 	delta -= vpos * m_scantime;
1554 
1555 	// return the pixel offset from the start of this scanline
1556 	return delta / m_pixeltime;
1557 }
1558 
1559 
1560 //-------------------------------------------------
1561 //  time_until_pos - returns the amount of time
1562 //  remaining until the beam is at the given
1563 //  hpos,vpos
1564 //-------------------------------------------------
1565 
time_until_pos(int vpos,int hpos) const1566 attotime screen_device::time_until_pos(int vpos, int hpos) const
1567 {
1568 	// validate arguments
1569 	assert(vpos >= 0);
1570 	assert(hpos >= 0);
1571 
1572 	// since we measure time relative to VBLANK, compute the scanline offset from VBLANK
1573 	vpos += m_height - (m_visarea.bottom() + 1);
1574 	vpos %= m_height;
1575 
1576 	// compute the delta for the given X,Y position
1577 	attoseconds_t targetdelta = (attoseconds_t)vpos * m_scantime + (attoseconds_t)hpos * m_pixeltime;
1578 
1579 	// if we're past that time (within 1/2 of a pixel), head to the next frame
1580 	attoseconds_t curdelta = (machine().time() - m_vblank_start_time).as_attoseconds();
1581 	if (targetdelta <= curdelta + m_pixeltime / 2)
1582 		targetdelta += m_frame_period;
1583 	while (targetdelta <= curdelta)
1584 		targetdelta += m_frame_period;
1585 
1586 	// return the difference
1587 	return attotime(0, targetdelta - curdelta);
1588 }
1589 
1590 
1591 //-------------------------------------------------
1592 //  time_until_vblank_end - returns the amount of
1593 //  time remaining until the end of the current
1594 //  VBLANK (if in progress) or the end of the next
1595 //  VBLANK
1596 //-------------------------------------------------
1597 
time_until_vblank_end() const1598 attotime screen_device::time_until_vblank_end() const
1599 {
1600 	// if we are in the VBLANK region, compute the time until the end of the current VBLANK period
1601 	attotime target_time = m_vblank_end_time;
1602 	if (!vblank())
1603 		target_time += attotime(0, m_frame_period);
1604 	return target_time - machine().time();
1605 }
1606 
1607 
1608 //-------------------------------------------------
1609 //  register_vblank_callback - registers a VBLANK
1610 //  callback
1611 //-------------------------------------------------
1612 
register_vblank_callback(vblank_state_delegate vblank_callback)1613 void screen_device::register_vblank_callback(vblank_state_delegate vblank_callback)
1614 {
1615 	// validate arguments
1616 	assert(!vblank_callback.isnull());
1617 
1618 	// do nothing if we already have this callback registered
1619 	for (auto &item : m_callback_list)
1620 		if (item->m_callback == vblank_callback)
1621 			return;
1622 
1623 	// if not found, register
1624 	m_callback_list.push_back(std::make_unique<callback_item>(vblank_callback));
1625 }
1626 
1627 
1628 //-------------------------------------------------
1629 //  register_screen_bitmap - registers a bitmap
1630 //  that should track the screen size
1631 //-------------------------------------------------
1632 
register_screen_bitmap(bitmap_t & bitmap)1633 void screen_device::register_screen_bitmap(bitmap_t &bitmap)
1634 {
1635 	// append to the list
1636 	m_auto_bitmap_list.push_back(std::make_unique<auto_bitmap_item>(bitmap));
1637 
1638 	// if allocating now, just do it
1639 	bitmap.allocate(width(), height());
1640 	if (m_palette)
1641 		bitmap.set_palette(m_palette->palette());
1642 }
1643 
1644 
1645 //-------------------------------------------------
1646 //  vblank_begin - call any external callbacks to
1647 //  signal the VBLANK period has begun
1648 //-------------------------------------------------
1649 
vblank_begin()1650 void screen_device::vblank_begin()
1651 {
1652 	// reset the starting VBLANK time
1653 	m_vblank_start_time = machine().time();
1654 	m_vblank_end_time = m_vblank_start_time + attotime(0, m_vblank_period);
1655 
1656 	// if this is the primary screen and we need to update now
1657 	if (m_is_primary_screen && !(m_video_attributes & VIDEO_UPDATE_AFTER_VBLANK))
1658 		machine().video().frame_update();
1659 
1660 	// call the screen specific callbacks
1661 	for (auto &item : m_callback_list)
1662 		item->m_callback(*this, true);
1663 	m_screen_vblank(1);
1664 
1665 	// reset the VBLANK start timer for the next frame
1666 	m_vblank_begin_timer->adjust(time_until_vblank_start());
1667 
1668 	// if no VBLANK period, call the VBLANK end callback immediately, otherwise reset the timer
1669 	if (m_vblank_period == 0)
1670 		vblank_end();
1671 	else
1672 		m_vblank_end_timer->adjust(time_until_vblank_end());
1673 }
1674 
1675 
1676 //-------------------------------------------------
1677 //  vblank_end - call any external callbacks to
1678 //  signal the VBLANK period has ended
1679 //-------------------------------------------------
1680 
vblank_end()1681 void screen_device::vblank_end()
1682 {
1683 	// call the screen specific callbacks
1684 	for (auto &item : m_callback_list)
1685 		item->m_callback(*this, false);
1686 	m_screen_vblank(0);
1687 
1688 	// if this is the primary screen and we need to update now
1689 	if (m_is_primary_screen && (m_video_attributes & VIDEO_UPDATE_AFTER_VBLANK))
1690 		machine().video().frame_update();
1691 
1692 	// increment the frame number counter
1693 	m_frame_number++;
1694 }
1695 
1696 
1697 //-------------------------------------------------
1698 //  create_composited_bitmap - composite scanline
1699 //  bitmaps into the output bitmap
1700 //-------------------------------------------------
1701 
create_composited_bitmap()1702 void screen_device::create_composited_bitmap()
1703 {
1704 	screen_bitmap &curbitmap = m_bitmap[m_curtexture];
1705 	if (!curbitmap.valid())
1706 		return;
1707 
1708 	s32 dstwidth = std::max(m_max_width, m_visarea.right() + 1);
1709 	int dstheight = curbitmap.height();
1710 
1711 	switch (curbitmap.format())
1712 	{
1713 		default:
1714 		case BITMAP_FORMAT_IND16:
1715 		{
1716 			for (int y = 0; y < dstheight; y++)
1717 			{
1718 				const bitmap_ind16 &srcbitmap = *(bitmap_ind16 *)m_scan_bitmaps[m_curbitmap][y];
1719 				u16 *dst = &curbitmap.as_ind16().pix(y);
1720 				const u16 *src = &srcbitmap.pix(0);
1721 				const int dx = (m_scan_widths[y] << 15) / dstwidth;
1722 				for (int x = 0; x < m_scan_widths[y]; x += dx)
1723 				{
1724 					*dst++ = src[x >> 15];
1725 				}
1726 			}
1727 			break;
1728 		}
1729 
1730 		case BITMAP_FORMAT_RGB32:
1731 		{
1732 			for (int y = 0; y < dstheight; y++)
1733 			{
1734 				const bitmap_rgb32 &srcbitmap = *(bitmap_rgb32 *)m_scan_bitmaps[m_curbitmap][y];
1735 				u32 *dst = &curbitmap.as_rgb32().pix(y);
1736 				const u32 *src = &srcbitmap.pix(0);
1737 				const int dx = (m_scan_widths[y] << 15) / dstwidth;
1738 				for (int x = 0; x < dstwidth << 15; x += dx)
1739 				{
1740 					*dst++ = src[x >> 15];
1741 				}
1742 			}
1743 			break;
1744 		}
1745 	}
1746 }
1747 
1748 
1749 //-------------------------------------------------
1750 //  update_quads - set up the quads for this
1751 //  screen
1752 //-------------------------------------------------
1753 
update_quads()1754 bool screen_device::update_quads()
1755 {
1756 	// only update if live
1757 	if (machine().render().is_live(*this))
1758 	{
1759 		// only update if empty and not a vector game; otherwise assume the driver did it directly
1760 		if (m_type != SCREEN_TYPE_VECTOR && (m_video_attributes & VIDEO_SELF_RENDER) == 0)
1761 		{
1762 			// if we're not skipping the frame and if the screen actually changed, then update the texture
1763 			if (!machine().video().skip_this_frame() && m_changed)
1764 			{
1765 				if (m_video_attributes & VIDEO_VARIABLE_WIDTH)
1766 				{
1767 					create_composited_bitmap();
1768 				}
1769 				m_texture[m_curbitmap]->set_bitmap(m_bitmap[m_curbitmap], m_visarea, m_bitmap[m_curbitmap].texformat());
1770 				m_curtexture = m_curbitmap;
1771 				m_curbitmap = 1 - m_curbitmap;
1772 			}
1773 
1774 			// brightness adjusted render color
1775 			rgb_t color = m_color - rgb_t(0, 0xff - m_brightness, 0xff - m_brightness, 0xff - m_brightness);
1776 
1777 			// create an empty container with a single quad
1778 			m_container->empty();
1779 			m_container->add_quad(0.0f, 0.0f, 1.0f, 1.0f, color, m_texture[m_curtexture], PRIMFLAG_BLENDMODE(BLENDMODE_NONE) | PRIMFLAG_SCREENTEX(1));
1780 		}
1781 	}
1782 
1783 	// reset the screen changed flags
1784 	bool result = m_changed;
1785 	m_changed = false;
1786 	return result;
1787 }
1788 
1789 
1790 //-------------------------------------------------
1791 //  update_burnin - update the burnin bitmap
1792 //-------------------------------------------------
1793 
update_burnin()1794 void screen_device::update_burnin()
1795 {
1796 // TODO: other than being unnecessary, this is a simplification of how analog signals really works!
1797 // It's important not to use machine().rand() here, it can cause machine().rand() used in emulation to desync.
1798 #undef rand
1799 	if (!m_burnin.valid())
1800 		return;
1801 
1802 	screen_bitmap &curbitmap = m_bitmap[m_curtexture];
1803 	if (!curbitmap.valid())
1804 		return;
1805 
1806 	int srcwidth = curbitmap.width();
1807 	int srcheight = curbitmap.height();
1808 	int dstwidth = m_burnin.width();
1809 	int dstheight = m_burnin.height();
1810 	int xstep = (srcwidth << 16) / dstwidth;
1811 	int ystep = (srcheight << 16) / dstheight;
1812 	int xstart = (u32(rand()) % 32767) * xstep / 32767;
1813 	int ystart = (u32(rand()) % 32767) * ystep / 32767;
1814 
1815 	bool per_scanline = (m_video_attributes & VIDEO_VARIABLE_WIDTH);
1816 
1817 	switch (curbitmap.format())
1818 	{
1819 		default:
1820 		case BITMAP_FORMAT_IND16:
1821 		{
1822 			// iterate over rows in the destination
1823 			for (int y = 0, srcy = ystart; y < dstheight; y++, srcy += ystep)
1824 			{
1825 				const bitmap_ind16 &srcbitmap = per_scanline ? *(bitmap_ind16 *)m_scan_bitmaps[m_curbitmap][y] : curbitmap.as_ind16();
1826 				u64 *const dst = &m_burnin.pix(y);
1827 				u16 const *const src = &srcbitmap.pix(per_scanline ? 0 : (srcy >> 16));
1828 				rgb_t const *const palette = m_palette->palette()->entry_list_adjusted();
1829 				for (int x = 0, srcx = xstart; x < dstwidth; x++, srcx += xstep)
1830 				{
1831 					rgb_t pixel = palette[src[srcx >> 16]];
1832 					dst[x] += pixel.g() + pixel.r() + pixel.b();
1833 				}
1834 			}
1835 			break;
1836 		}
1837 
1838 		case BITMAP_FORMAT_RGB32:
1839 		{
1840 			// iterate over rows in the destination
1841 			for (int y = 0, srcy = ystart; y < dstheight; y++, srcy += ystep)
1842 			{
1843 				const bitmap_rgb32 &srcbitmap = per_scanline ? *(bitmap_rgb32 *)m_scan_bitmaps[m_curbitmap][y] : curbitmap.as_rgb32();
1844 				u64 *const dst = &m_burnin.pix(y);
1845 				u32 const *const src = &srcbitmap.pix(per_scanline ? 0 : (srcy >> 16));
1846 				for (int x = 0, srcx = xstart; x < dstwidth; x++, srcx += xstep)
1847 				{
1848 					rgb_t pixel = src[srcx >> 16];
1849 					dst[x] += pixel.g() + pixel.r() + pixel.b();
1850 				}
1851 			}
1852 			break;
1853 		}
1854 	}
1855 }
1856 
1857 
1858 //-------------------------------------------------
1859 //  finalize_burnin - finalize the burnin bitmap
1860 //-------------------------------------------------
1861 
finalize_burnin()1862 void screen_device::finalize_burnin()
1863 {
1864 	if (!m_burnin.valid())
1865 		return;
1866 
1867 	// compute the scaled visible region
1868 	rectangle scaledvis(
1869 			m_visarea.left() * m_burnin.width() / m_width,
1870 			m_visarea.right() * m_burnin.width() / m_width,
1871 			m_visarea.top() * m_burnin.height() / m_height,
1872 			m_visarea.bottom() * m_burnin.height() / m_height);
1873 
1874 	// wrap a bitmap around the memregion we care about
1875 	bitmap_argb32 finalmap(scaledvis.width(), scaledvis.height());
1876 	int srcwidth = m_burnin.width();
1877 	int srcheight = m_burnin.height();
1878 	int dstwidth = finalmap.width();
1879 	int dstheight = finalmap.height();
1880 	int xstep = (srcwidth << 16) / dstwidth;
1881 	int ystep = (srcheight << 16) / dstheight;
1882 
1883 	// find the maximum value
1884 	u64 minval = ~u64(0);
1885 	u64 maxval = 0;
1886 	for (int y = 0; y < srcheight; y++)
1887 	{
1888 		u64 const *const src = &m_burnin.pix(y);
1889 		for (int x = 0; x < srcwidth; x++)
1890 		{
1891 			minval = std::min(minval, src[x]);
1892 			maxval = std::max(maxval, src[x]);
1893 		}
1894 	}
1895 
1896 	if (minval == maxval)
1897 		return;
1898 
1899 	// now normalize and convert to RGB
1900 	for (int y = 0, srcy = 0; y < dstheight; y++, srcy += ystep)
1901 	{
1902 		u64 const *const src = &m_burnin.pix(srcy >> 16);
1903 		u32 *const dst = &finalmap.pix(y);
1904 		for (int x = 0, srcx = 0; x < dstwidth; x++, srcx += xstep)
1905 		{
1906 			int brightness = u64(maxval - src[srcx >> 16]) * 255 / (maxval - minval);
1907 			dst[x] = rgb_t(0xff, brightness, brightness, brightness);
1908 		}
1909 	}
1910 
1911 	// write the final PNG
1912 
1913 	// compute the name and create the file
1914 	emu_file file(machine().options().snapshot_directory(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS);
1915 	osd_file::error filerr = file.open(util::string_format("%s" PATH_SEPARATOR "burnin-%s.png", machine().basename(), tag() + 1));
1916 	if (filerr == osd_file::error::NONE)
1917 	{
1918 		util::png_info pnginfo;
1919 
1920 		// add two text entries describing the image
1921 		pnginfo.add_text("Software", util::string_format("%s %s", emulator_info::get_appname(), emulator_info::get_build_version()).c_str());
1922 		pnginfo.add_text("System", util::string_format("%s %s", machine().system().manufacturer, machine().system().type.fullname()).c_str());
1923 
1924 		// now do the actual work
1925 		util::png_write_bitmap(file, &pnginfo, finalmap, 0, nullptr);
1926 	}
1927 }
1928 
1929 
1930 //-------------------------------------------------
1931 //  finalize_burnin - finalize the burnin bitmap
1932 //-------------------------------------------------
1933 
load_effect_overlay(const char * filename)1934 void screen_device::load_effect_overlay(const char *filename)
1935 {
1936 	// ensure that there is a .png extension
1937 	std::string fullname(filename);
1938 	int extension = fullname.find_last_of('.');
1939 	if (extension != -1)
1940 		fullname.erase(extension, -1);
1941 	fullname.append(".png");
1942 
1943 	// load the file
1944 	m_screen_overlay_bitmap.reset();
1945 	emu_file file(machine().options().art_path(), OPEN_FLAG_READ);
1946 	if (file.open(fullname) == osd_file::error::NONE)
1947 	{
1948 		render_load_png(m_screen_overlay_bitmap, file);
1949 		file.close();
1950 	}
1951 	if (m_screen_overlay_bitmap.valid())
1952 		m_container->set_overlay(&m_screen_overlay_bitmap);
1953 	else
1954 		osd_printf_warning("Unable to load effect PNG file '%s'\n", fullname);
1955 }
1956