diff options
Diffstat (limited to 'src/emu/screen.cpp')
-rw-r--r-- | src/emu/screen.cpp | 641 |
1 files changed, 445 insertions, 196 deletions
diff --git a/src/emu/screen.cpp b/src/emu/screen.cpp index 103230ba114..0d8e35d6e4c 100644 --- a/src/emu/screen.cpp +++ b/src/emu/screen.cpp @@ -2,21 +2,27 @@ // copyright-holders:Aaron Giles /*************************************************************************** - screen.c + screen.cpp Core MAME screen device. ***************************************************************************/ #include "emu.h" +#include "screen.h" + #include "emuopts.h" -#include "png.h" +#include "fileio.h" +#include "main.h" +#include "render.h" #include "rendutil.h" -#include <nanosvg/src/nanosvg.h> -#include <nanosvg/src/nanosvgrast.h> +#include "nanosvg.h" +#include "png.h" + #include <set> + //************************************************************************** // DEBUGGING //************************************************************************** @@ -40,7 +46,6 @@ u32 screen_device::m_id_counter = 0; class screen_device::svg_renderer { public: svg_renderer(memory_region *region); - ~svg_renderer(); int width() const; int height() const; @@ -66,10 +71,10 @@ private: int x0, y0, x1, y1; }; - NSVGimage *m_image; - NSVGrasterizer *m_rasterizer; + util::nsvg_image_ptr m_image; + util::nsvg_rasterizer_ptr m_rasterizer; std::vector<bool> m_key_state; - std::vector<std::list<NSVGshape *>> m_keyed_shapes; + std::vector<std::vector<NSVGshape *>> m_keyed_shapes; std::unordered_map<std::string, int> m_key_ids; int m_key_count; @@ -91,23 +96,21 @@ private: screen_device::svg_renderer::svg_renderer(memory_region *region) { - char *s = new char[region->bytes()+1]; - memcpy(s, region->base(), region->bytes()); + const std::unique_ptr<char []> s(new char[region->bytes() + 1]); + memcpy(s.get(), region->base(), region->bytes()); s[region->bytes()] = 0; - m_image = nsvgParse(s, "px", 72); - delete[] s; - m_rasterizer = nsvgCreateRasterizer(); + m_image.reset(nsvgParse(s.get(), "px", 72)); + m_rasterizer.reset(nsvgCreateRasterizer()); m_key_count = 0; - for (NSVGshape *shape = m_image->shapes; shape != nullptr; shape = shape->next) + for (NSVGshape *shape = m_image->shapes; shape; shape = shape->next) if(shape->title[0]) { - auto it = m_key_ids.find(shape->title); + const auto it = m_key_ids.find(shape->title); if(it != m_key_ids.end()) m_keyed_shapes[it->second].push_back(shape); else { - int id = m_key_count; - m_key_count++; + const int id = m_key_count++; m_keyed_shapes.resize(m_key_count); m_keyed_shapes[id].push_back(shape); m_key_ids[shape->title] = id; @@ -122,12 +125,6 @@ screen_device::svg_renderer::svg_renderer(memory_region *region) osd_printf_verbose("Parsed SVG '%s', aspect ratio %f\n", region->name(), (m_image->height == 0.0f) ? 0 : m_image->width / m_image->height); } -screen_device::svg_renderer::~svg_renderer() -{ - nsvgDeleteRasterizer(m_rasterizer); - nsvgDelete(m_image); -} - int screen_device::svg_renderer::width() const { return int(m_image->width + 0.5); @@ -149,7 +146,7 @@ void screen_device::svg_renderer::render_state(std::vector<u32> &dest, const std s->flags &= ~NSVG_FLAGS_VISIBLE; } - nsvgRasterize(m_rasterizer, m_image, 0, 0, m_scale, (unsigned char *)&dest[0], m_sx, m_sy, m_sx*4); + nsvgRasterize(m_rasterizer.get(), m_image.get(), 0, 0, m_scale, (unsigned char *)&dest[0], m_sx, m_sy, m_sx*4); // Nanosvg generates non-premultiplied alpha, so remultiply by // alpha to "blend" against a black background. Plus align the @@ -542,12 +539,15 @@ screen_device::screen_device(const machine_config &mconfig, const char *tag, dev , m_yoffset(0.0f) , m_xscale(1.0f) , m_yscale(1.0f) + , m_screen_update_ind16(*this) + , m_screen_update_rgb32(*this) , m_screen_vblank(*this) , m_scanline_cb(*this) , m_palette(*this, finder_base::DUMMY_TAG) , m_video_attributes(0) - , m_svg_region(tag) + , m_svg_region(*this, DEVICE_SELF) , m_container(nullptr) + , m_max_width(100) , m_width(100) , m_height(100) , m_visarea(0, 99, 0, 99) @@ -555,6 +555,7 @@ screen_device::screen_device(const machine_config &mconfig, const char *tag, dev , m_curbitmap(0) , m_curtexture(0) , m_changed(true) + , m_last_partial_reset(attotime::zero) , m_last_partial_scan(0) , m_partial_scan_hpos(0) , m_color(rgb_t(0xff, 0xff, 0xff, 0xff)) @@ -584,10 +585,81 @@ screen_device::screen_device(const machine_config &mconfig, const char *tag, dev screen_device::~screen_device() { + destroy_scan_bitmaps(); } //------------------------------------------------- +// destroy_scan_bitmaps - destroy per-scanline +// bitmaps if applicable +//------------------------------------------------- + +void screen_device::destroy_scan_bitmaps() +{ + if (m_video_attributes & VIDEO_VARIABLE_WIDTH) + { + const bool screen16 = !m_screen_update_ind16.isnull(); + for (int j = 0; j < 2; j++) + { + for (bitmap_t* bitmap : m_scan_bitmaps[j]) + { + if (screen16) + delete (bitmap_ind16*)bitmap; + else + delete (bitmap_rgb32*)bitmap; + } + m_scan_bitmaps[j].clear(); + } + } +} + + +//------------------------------------------------- +// allocate_scan_bitmaps - allocate per-scanline +// bitmaps if applicable +//------------------------------------------------- + +void screen_device::allocate_scan_bitmaps() +{ + if (m_video_attributes & VIDEO_VARIABLE_WIDTH) + { + const bool screen16 = !m_screen_update_ind16.isnull(); + s32 effwidth = std::max(m_max_width, m_visarea.right() + 1); + const s32 old_height = (s32)m_scan_widths.size(); + s32 effheight = std::max(m_height, m_visarea.bottom() + 1); + if (old_height < effheight) + { + for (int i = old_height; i < effheight; i++) + { + for (int j = 0; j < 2; j++) + { + if (screen16) + m_scan_bitmaps[j].push_back(new bitmap_ind16(effwidth, 1)); + else + m_scan_bitmaps[j].push_back(new bitmap_rgb32(effwidth, 1)); + } + m_scan_widths.push_back(effwidth); + } + } + else + { + for (int i = old_height - 1; i >= effheight; i--) + { + for (int j = 0; j < 2; j++) + { + if (screen16) + delete (bitmap_ind16 *)m_scan_bitmaps[j][i]; + else + delete (bitmap_rgb32 *)m_scan_bitmaps[j][i]; + m_scan_bitmaps[j].erase(m_scan_bitmaps[j].begin() + i); + } + m_scan_widths.erase(m_scan_widths.begin() + i); + } + } + } +} + +//------------------------------------------------- // device_validity_check - verify device // configuration //------------------------------------------------- @@ -608,10 +680,15 @@ void screen_device::device_validity_check(validity_checker &valid) const if (m_screen_update_ind16.isnull() && m_screen_update_rgb32.isnull()) osd_printf_error("Missing SCREEN_UPDATE function\n"); } + else + { + if (m_video_attributes & VIDEO_VARIABLE_WIDTH) + osd_printf_error("Non-raster display cannot have a variable width\n"); + } - // check for zero frame rate - if (m_refresh == 0) - osd_printf_error("Invalid (zero) refresh rate\n"); + // check for invalid frame rate + if (m_refresh == 0 || m_refresh > ATTOSECONDS_PER_SECOND) + osd_printf_error("Invalid (under 1Hz) refresh rate\n"); texture_format texformat = !m_screen_update_ind16.isnull() ? TEXFORMAT_PALETTE16 : TEXFORMAT_RGB32; if (m_palette.finder_tag() != finder_base::DUMMY_TAG) @@ -694,10 +771,8 @@ std::pair<unsigned, unsigned> screen_device::physical_aspect() const void screen_device::device_resolve_objects() { // bind our handlers - m_screen_update_ind16.bind_relative_to(*owner()); - m_screen_update_rgb32.bind_relative_to(*owner()); - m_screen_vblank.resolve_safe(); - m_scanline_cb.resolve(); + m_screen_update_ind16.resolve(); + m_screen_update_rgb32.resolve(); // assign our format to the palette before it starts if (m_palette) @@ -717,11 +792,10 @@ void screen_device::device_start() if (m_type == SCREEN_TYPE_SVG) { - memory_region *reg = owner()->memregion(m_svg_region); - if (!reg) - fatalerror("%s: SVG region \"%s\" does not exist\n", tag(), m_svg_region); - m_svg = std::make_unique<svg_renderer>(reg); - machine().output().set_notifier(nullptr, svg_renderer::output_notifier, m_svg.get()); + if (!m_svg_region) + fatalerror("%s: SVG region \"%s\" does not exist\n", tag(), m_svg_region.finder_tag()); + m_svg = std::make_unique<svg_renderer>(m_svg_region); + machine().output().set_global_notifier(svg_renderer::output_notifier, m_svg.get()); // don't do this - SVG units are arbitrary and interpreting them as pixels causes bad things to happen // just render at the size/aspect ratio supplied by the driver @@ -736,7 +810,8 @@ void screen_device::device_start() // configure bitmap formats and allocate screen bitmaps // svg is RGB32 too, and doesn't have any update method - texture_format texformat = !m_screen_update_ind16.isnull() ? TEXFORMAT_PALETTE16 : TEXFORMAT_RGB32; + const bool screen16 = !m_screen_update_ind16.isnull(); + texture_format texformat = screen16 ? TEXFORMAT_PALETTE16 : TEXFORMAT_RGB32; for (auto & elem : m_bitmap) { @@ -752,8 +827,7 @@ void screen_device::device_start() m_texture[1]->set_id((u64(m_unique_id) << 57) | 1); // configure the default cliparea - render_container::user_settings settings; - m_container->get_user_settings(settings); + render_container::user_settings settings = m_container->get_user_settings(); settings.m_xoffset = m_xoffset; settings.m_yoffset = m_yoffset; settings.m_xscale = m_xscale; @@ -761,15 +835,15 @@ void screen_device::device_start() m_container->set_user_settings(settings); // allocate the VBLANK timers - m_vblank_begin_timer = timer_alloc(TID_VBLANK_START); - m_vblank_end_timer = timer_alloc(TID_VBLANK_END); + m_vblank_begin_timer = timer_alloc(FUNC(screen_device::vblank_begin), this); + m_vblank_end_timer = timer_alloc(FUNC(screen_device::vblank_end), this); // allocate a timer to reset partial updates - m_scanline0_timer = timer_alloc(TID_SCANLINE0); + m_scanline0_timer = timer_alloc(FUNC(screen_device::first_scanline_tick), this); // allocate a timer to generate per-scanline updates - if ((m_video_attributes & VIDEO_UPDATE_SCANLINE) != 0 || m_scanline_cb) - m_scanline_timer = timer_alloc(TID_SCANLINE); + if ((m_video_attributes & VIDEO_UPDATE_SCANLINE) != 0 || !m_scanline_cb.isunset()) + m_scanline_timer = timer_alloc(FUNC(screen_device::scanline_tick), this); // configure the screen with the default parameters configure(m_width, m_height, m_visarea, m_refresh); @@ -779,7 +853,7 @@ void screen_device::device_start() m_vblank_end_time = attotime(0, m_vblank_period); // start the timer to generate per-scanline updates - if ((m_video_attributes & VIDEO_UPDATE_SCANLINE) != 0 || m_scanline_cb) + if ((m_video_attributes & VIDEO_UPDATE_SCANLINE) != 0 || !m_scanline_cb.isunset()) m_scanline_timer->adjust(time_until_pos(0)); // create burn-in bitmap @@ -804,6 +878,7 @@ void screen_device::device_start() save_item(NAME(m_visarea.min_y)); save_item(NAME(m_visarea.max_x)); save_item(NAME(m_visarea.max_y)); + save_item(NAME(m_last_partial_reset)); save_item(NAME(m_last_partial_scan)); save_item(NAME(m_frame_period)); save_item(NAME(m_brightness)); @@ -814,9 +889,9 @@ void screen_device::device_start() save_item(NAME(m_vblank_end_time)); save_item(NAME(m_frame_number)); if (m_oldstyle_vblank_supplied) - logerror("%s: Deprecated legacy Old Style screen configured (MCFG_SCREEN_VBLANK_TIME), please use MCFG_SCREEN_RAW_PARAMS instead.\n",this->tag()); + logerror("%s: Deprecated legacy Old Style screen configured (set_vblank_time), please use set_raw instead.\n",this->tag()); - m_is_primary_screen = (this == screen_device_iterator(machine().root_device()).first()); + m_is_primary_screen = (this == screen_device_enumerator(machine().root_device()).first()); } @@ -857,46 +932,39 @@ void screen_device::device_post_load() //------------------------------------------------- -// device_timer - called whenever a device timer -// fires +// timer events //------------------------------------------------- -void screen_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) +TIMER_CALLBACK_MEMBER(screen_device::first_scanline_tick) { - switch (id) + // first scanline + reset_partial_updates(); + if (m_video_attributes & VIDEO_VARIABLE_WIDTH) { - // signal VBLANK start - case TID_VBLANK_START: - vblank_begin(); - break; - - // signal VBLANK end - case TID_VBLANK_END: - vblank_end(); - break; - - // first scanline - case TID_SCANLINE0: - reset_partial_updates(); - break; + pre_update_scanline(0); + } +} - // subsequent scanlines when scanline updates are enabled - case TID_SCANLINE: - if (m_video_attributes & VIDEO_UPDATE_SCANLINE) - { - // force a partial update to the current scanline - update_partial(param); - } - if (m_scanline_cb) - m_scanline_cb(param); - - // compute the next visible scanline - param++; - if (param > m_visarea.bottom()) - param = m_visarea.top(); - m_scanline_timer->adjust(time_until_pos(param), param); - break; +TIMER_CALLBACK_MEMBER(screen_device::scanline_tick) +{ + // subsequent scanlines when scanline updates are enabled + if (m_video_attributes & VIDEO_VARIABLE_WIDTH) + { + pre_update_scanline(param); + } + if (m_video_attributes & VIDEO_UPDATE_SCANLINE) + { + // force a partial update to the current scanline + update_partial(param); } + if (!m_scanline_cb.isunset()) + m_scanline_cb(param); + + // compute the next visible scanline + param++; + if (param > m_visarea.bottom()) + param = m_visarea.top(); + m_scanline_timer->adjust(time_until_pos(param), param); } @@ -918,11 +986,12 @@ void screen_device::configure(int width, int height, const rectangle &visarea, a assert(frame_period > 0); // fill in the new parameters + m_max_width = std::max(m_max_width, width); m_width = width; m_height = height; m_visarea = visarea; - // reallocate bitmap if necessary + // reallocate bitmap(s) if necessary realloc_screen_bitmaps(); // compute timing parameters @@ -944,7 +1013,7 @@ void screen_device::configure(int width, int height, const rectangle &visarea, a // call the VBLANK start timer now; otherwise, adjust it for the future attoseconds_t delta = (machine().time() - m_vblank_start_time).as_attoseconds(); if (delta >= m_frame_period) - vblank_begin(); + vblank_begin(0); else m_vblank_begin_timer->adjust(time_until_vblank_start()); @@ -972,19 +1041,41 @@ void screen_device::reset_origin(int beamy, int beamx) m_vblank_end_time = curtime - attotime(0, beamy * m_scantime + beamx * m_pixeltime); m_vblank_start_time = m_vblank_end_time - attotime(0, m_vblank_period); + // if we are resetting relative to (visarea.bottom() + 1, 0) == VBLANK start, + // call the VBLANK start timer now; otherwise, adjust it for the future + if (beamy == ((m_visarea.bottom() + 1) % m_height) && beamx == 0) + vblank_begin(0); + else + m_vblank_begin_timer->adjust(time_until_vblank_start()); + // if we are resetting relative to (0,0) == VBLANK end, call the // scanline 0 timer by hand now; otherwise, adjust it for the future if (beamy == 0 && beamx == 0) reset_partial_updates(); else m_scanline0_timer->adjust(time_until_pos(0)); +} - // if we are resetting relative to (visarea.bottom() + 1, 0) == VBLANK start, - // call the VBLANK start timer now; otherwise, adjust it for the future - if (beamy == ((m_visarea.bottom() + 1) % m_height) && beamx == 0) - vblank_begin(); - else - m_vblank_begin_timer->adjust(time_until_vblank_start()); + +//------------------------------------------------- +// update_scan_bitmap_size - reallocate the +// bitmap for a specific scanline +//------------------------------------------------- + +void screen_device::update_scan_bitmap_size(int y) +{ + // don't update this line if it exceeds the allocated size, which can happen on initial configuration + if (y >= m_scan_widths.size()) + return; + + // determine effective size to allocate + s32 effwidth = std::max(m_max_width, m_visarea.right() + 1); + + if (m_scan_widths[y] == effwidth) + return; + + m_scan_bitmaps[m_curbitmap][y]->resize(effwidth, 1); + m_scan_widths[y] = effwidth; } @@ -1000,10 +1091,11 @@ void screen_device::realloc_screen_bitmaps() return; // determine effective size to allocate - s32 effwidth = std::max(m_width, m_visarea.right() + 1); + const bool per_scanline = (m_video_attributes & VIDEO_VARIABLE_WIDTH); + s32 effwidth = std::max(per_scanline ? m_max_width : m_width, m_visarea.right() + 1); s32 effheight = std::max(m_height, m_visarea.bottom() + 1); - // reize all registered screen bitmaps + // resize all registered screen bitmaps for (auto &item : m_auto_bitmap_list) item->m_bitmap.resize(effwidth, effheight); @@ -1015,6 +1107,19 @@ void screen_device::realloc_screen_bitmaps() } m_texture[0]->set_bitmap(m_bitmap[0], m_visarea, m_bitmap[0].texformat()); m_texture[1]->set_bitmap(m_bitmap[1], m_visarea, m_bitmap[1].texformat()); + + allocate_scan_bitmaps(); +} + + +//------------------------------------------------- +// pre_update_scanline - check if the bitmap for +// a specific scanline needs its size updated +//------------------------------------------------- + +void screen_device::pre_update_scanline(int y) +{ + update_scan_bitmap_size(y); } @@ -1065,11 +1170,17 @@ bool screen_device::update_partial(int scanline) return false; } + // skip if we already rendered this frame + // this can happen if a cpu timeslice that called update_partial is in the previous frame while scanline 0 already started + if (m_last_partial_scan == 0 && m_last_partial_reset > machine().time()) + { + LOG_PARTIAL_UPDATES(("skipped because frame was already rendered\n")); + return false; + } + // set the range of scanlines to render rectangle clip(m_visarea); - clip.sety( - (std::max)(clip.top(), m_last_partial_scan), - (std::min)(clip.bottom(), scanline)); + clip.sety((std::max)(clip.top(), m_last_partial_scan), (std::min)(clip.bottom(), scanline)); // skip if entirely outside of visible area if (clip.top() > clip.bottom()) @@ -1080,32 +1191,56 @@ bool screen_device::update_partial(int scanline) // otherwise, render LOG_PARTIAL_UPDATES(("updating %d-%d\n", clip.top(), clip.bottom())); - g_profiler.start(PROFILER_VIDEO); - u32 flags; - if (m_type != SCREEN_TYPE_SVG) + u32 flags = 0; { - screen_bitmap &curbitmap = m_bitmap[m_curbitmap]; - switch (curbitmap.format()) + auto profile = g_profiler.start(PROFILER_VIDEO); + if (m_video_attributes & VIDEO_VARIABLE_WIDTH) { - default: - case BITMAP_FORMAT_IND16: flags = m_screen_update_ind16(*this, curbitmap.as_ind16(), clip); break; - case BITMAP_FORMAT_RGB32: flags = m_screen_update_rgb32(*this, curbitmap.as_rgb32(), clip); break; + rectangle scan_clip(clip); + for (int y = clip.top(); y <= clip.bottom(); y++) + { + scan_clip.sety(y, y); + pre_update_scanline(y); + + screen_bitmap &curbitmap = m_bitmap[m_curbitmap]; + switch (curbitmap.format()) + { + default: + case BITMAP_FORMAT_IND16: flags |= m_screen_update_ind16(*this, *(bitmap_ind16 *)m_scan_bitmaps[m_curbitmap][y], scan_clip); break; + case BITMAP_FORMAT_RGB32: flags |= m_screen_update_rgb32(*this, *(bitmap_rgb32 *)m_scan_bitmaps[m_curbitmap][y], scan_clip); break; + } + + m_partial_updates_this_frame++; + } } + else + { + if (m_type != SCREEN_TYPE_SVG) + { + screen_bitmap &curbitmap = m_bitmap[m_curbitmap]; + switch (curbitmap.format()) + { + default: + case BITMAP_FORMAT_IND16: flags = m_screen_update_ind16(*this, curbitmap.as_ind16(), clip); break; + case BITMAP_FORMAT_RGB32: flags = m_screen_update_rgb32(*this, curbitmap.as_rgb32(), clip); break; + } + } + else + { + flags = m_svg->render(*this, m_bitmap[m_curbitmap].as_rgb32(), clip); + } + m_partial_updates_this_frame++; + } + // stop profiling } - else - { - flags = m_svg->render(*this, m_bitmap[m_curbitmap].as_rgb32(), clip); - } - - m_partial_updates_this_frame++; - g_profiler.stop(); // if we modified the bitmap, we have to commit m_changed |= ~flags & UPDATE_HAS_NOT_CHANGED; // remember where we left off m_last_partial_scan = scanline + 1; + m_partial_scan_hpos = 0; return true; } @@ -1139,6 +1274,28 @@ void screen_device::update_now() int current_hpos = hpos(); rectangle clip = m_visarea; + // skip if we already rendered this line + if (current_vpos < m_last_partial_scan) + { + LOG_PARTIAL_UPDATES(("skipped because line was already rendered\n")); + return; + } + + // if beam position is the same, there's nothing to update + if (current_vpos == m_last_partial_scan && current_hpos == m_partial_scan_hpos) + { + LOG_PARTIAL_UPDATES(("skipped because beam position is unchanged\n")); + return; + } + + // skip if we already rendered this frame + // this can happen if a cpu timeslice that called update_now is in the previous frame while scanline 0 already started + if (m_last_partial_scan == 0 && m_partial_scan_hpos == 0 && m_last_partial_reset > machine().time()) + { + LOG_PARTIAL_UPDATES(("skipped because frame was already rendered\n")); + return; + } + 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())); // start off by doing a partial update up to the line before us, in case that was necessary @@ -1148,30 +1305,46 @@ void screen_device::update_now() if (m_partial_scan_hpos > 0) { // now finish the previous partial scanline - clip.set( - (std::max)(clip.left(), m_partial_scan_hpos), - clip.right(), - (std::max)(clip.top(), m_last_partial_scan), - (std::min)(clip.bottom(), m_last_partial_scan)); + clip.set((std::max)(clip.left(), m_partial_scan_hpos), + clip.right(), + (std::max)(clip.top(), m_last_partial_scan), + (std::min)(clip.bottom(), m_last_partial_scan)); // if there's something to draw, do it if (!clip.empty()) { - g_profiler.start(PROFILER_VIDEO); + auto profile = g_profiler.start(PROFILER_VIDEO); + u32 flags = 0; screen_bitmap &curbitmap = m_bitmap[m_curbitmap]; - switch (curbitmap.format()) + if (m_video_attributes & VIDEO_VARIABLE_WIDTH) { - default: - case BITMAP_FORMAT_IND16: m_screen_update_ind16(*this, curbitmap.as_ind16(), clip); break; - case BITMAP_FORMAT_RGB32: m_screen_update_rgb32(*this, curbitmap.as_rgb32(), clip); break; + pre_update_scanline(m_last_partial_scan); + switch (curbitmap.format()) + { + default: + case BITMAP_FORMAT_IND16: flags = m_screen_update_ind16(*this, *(bitmap_ind16 *)m_scan_bitmaps[m_curbitmap][m_last_partial_scan], clip); break; + case BITMAP_FORMAT_RGB32: flags = m_screen_update_rgb32(*this, *(bitmap_rgb32 *)m_scan_bitmaps[m_curbitmap][m_last_partial_scan], clip); break; + } + } + else + { + switch (curbitmap.format()) + { + default: + case BITMAP_FORMAT_IND16: flags = m_screen_update_ind16(*this, curbitmap.as_ind16(), clip); break; + case BITMAP_FORMAT_RGB32: flags = m_screen_update_rgb32(*this, curbitmap.as_rgb32(), clip); break; + } } m_partial_updates_this_frame++; - g_profiler.stop(); - m_partial_scan_hpos = 0; - m_last_partial_scan++; + + // if we modified the bitmap, we have to commit + m_changed |= ~flags & UPDATE_HAS_NOT_CHANGED; } + + m_partial_scan_hpos = 0; + m_last_partial_scan++; } if (current_vpos > m_last_partial_scan) { @@ -1180,47 +1353,54 @@ void screen_device::update_now() } // now draw this partial scanline - clip = m_visarea; - - clip.set( - (std::max)(clip.left(), m_partial_scan_hpos), - (std::min)(clip.right(), current_hpos), - (std::max)(clip.top(), current_vpos), - (std::min)(clip.bottom(), current_vpos)); - - // and if there's something to draw, do it - if (!clip.empty()) + if (current_hpos > 0) { - g_profiler.start(PROFILER_VIDEO); + clip = m_visarea; - LOG_PARTIAL_UPDATES(("doing scanline partial draw: Y %d X %d-%d\n", clip.bottom(), clip.left(), clip.right())); + clip.set((std::max)(clip.left(), m_partial_scan_hpos), + (std::min)(clip.right(), current_hpos - 1), + (std::max)(clip.top(), current_vpos), + (std::min)(clip.bottom(), current_vpos)); - u32 flags; - screen_bitmap &curbitmap = m_bitmap[m_curbitmap]; - switch (curbitmap.format()) + // and if there's something to draw, do it + if (!clip.empty()) { - default: - case BITMAP_FORMAT_IND16: flags = m_screen_update_ind16(*this, curbitmap.as_ind16(), clip); break; - case BITMAP_FORMAT_RGB32: flags = m_screen_update_rgb32(*this, curbitmap.as_rgb32(), clip); break; - } + auto profile = g_profiler.start(PROFILER_VIDEO); - m_partial_updates_this_frame++; - g_profiler.stop(); + LOG_PARTIAL_UPDATES(("doing scanline partial draw: Y %d X %d-%d\n", clip.bottom(), clip.left(), clip.right())); - // if we modified the bitmap, we have to commit - m_changed |= ~flags & UPDATE_HAS_NOT_CHANGED; + u32 flags = 0; + screen_bitmap &curbitmap = m_bitmap[m_curbitmap]; + if (m_video_attributes & VIDEO_VARIABLE_WIDTH) + { + pre_update_scanline(current_vpos); + switch (curbitmap.format()) + { + default: + case BITMAP_FORMAT_IND16: flags = m_screen_update_ind16(*this, *(bitmap_ind16 *)m_scan_bitmaps[m_curbitmap][current_vpos], clip); break; + case BITMAP_FORMAT_RGB32: flags = m_screen_update_rgb32(*this, *(bitmap_rgb32 *)m_scan_bitmaps[m_curbitmap][current_vpos], clip); break; + } + } + else + { + switch (curbitmap.format()) + { + default: + case BITMAP_FORMAT_IND16: flags = m_screen_update_ind16(*this, curbitmap.as_ind16(), clip); break; + case BITMAP_FORMAT_RGB32: flags = m_screen_update_rgb32(*this, curbitmap.as_rgb32(), clip); break; + } + } - // remember where we left off - m_partial_scan_hpos = current_hpos; - m_last_partial_scan = current_vpos; + m_partial_updates_this_frame++; - // if we completed the line, mark it so - if (current_hpos >= m_visarea.right()) - { - m_partial_scan_hpos = 0; - m_last_partial_scan = current_vpos + 1; + // if we modified the bitmap, we have to commit + m_changed |= ~flags & UPDATE_HAS_NOT_CHANGED; } } + + // remember where we left off + m_partial_scan_hpos = current_hpos; + m_last_partial_scan = current_vpos; } @@ -1231,6 +1411,7 @@ void screen_device::update_now() void screen_device::reset_partial_updates() { + m_last_partial_reset = machine().time(); m_last_partial_scan = 0; m_partial_scan_hpos = 0; m_partial_updates_this_frame = 0; @@ -1255,21 +1436,28 @@ u32 screen_device::pixel(s32 x, s32 y) if (x < 0 || y < 0 || x >= srcwidth || y >= srcheight) return 0; + const bool per_scanline = (m_video_attributes & VIDEO_VARIABLE_WIDTH); + switch (curbitmap.format()) { case BITMAP_FORMAT_IND16: { - bitmap_ind16 &srcbitmap = curbitmap.as_ind16(); - const u16 src = srcbitmap.pix(y, x); + bitmap_ind16 &srcbitmap = per_scanline ? *(bitmap_ind16 *)m_scan_bitmaps[m_curbitmap][y] : curbitmap.as_ind16(); + const u16 src = per_scanline ? srcbitmap.pix(0, x) : srcbitmap.pix(y, x); const rgb_t *palette = m_palette->palette()->entry_list_adjusted(); return (u32)palette[src]; } case BITMAP_FORMAT_RGB32: { - // iterate over rows in the destination - bitmap_rgb32 &srcbitmap = curbitmap.as_rgb32(); - return (u32)srcbitmap.pix(y, x); + if (per_scanline) + { + return (u32)(*(bitmap_rgb32 *)m_scan_bitmaps[m_curbitmap][y]).pix(0, x); + } + else + { + return (u32)curbitmap.as_rgb32().pix(y, x); + } } default: @@ -1291,15 +1479,17 @@ void screen_device::pixels(u32 *buffer) const rectangle &visarea = visible_area(); + const bool per_scanline = (m_video_attributes & VIDEO_VARIABLE_WIDTH); + switch (curbitmap.format()) { case BITMAP_FORMAT_IND16: { - bitmap_ind16 &srcbitmap = curbitmap.as_ind16(); const rgb_t *palette = m_palette->palette()->entry_list_adjusted(); for (int y = visarea.min_y; y <= visarea.max_y; y++) { - const u16 *src = &srcbitmap.pix(y, visarea.min_x); + bitmap_ind16 &srcbitmap = per_scanline ? *(bitmap_ind16 *)m_scan_bitmaps[m_curbitmap][y] : curbitmap.as_ind16(); + const u16 *src = &srcbitmap.pix(per_scanline ? 0 : y, visarea.min_x); for (int x = visarea.min_x; x <= visarea.max_x; x++) { *buffer++ = palette[*src++]; @@ -1310,10 +1500,10 @@ void screen_device::pixels(u32 *buffer) case BITMAP_FORMAT_RGB32: { - bitmap_rgb32 &srcbitmap = curbitmap.as_rgb32(); for (int y = visarea.min_y; y <= visarea.max_y; y++) { - const u32 *src = &srcbitmap.pix(y, visarea.min_x); + bitmap_rgb32 &srcbitmap = per_scanline ? *(bitmap_rgb32 *)m_scan_bitmaps[m_curbitmap][y] : curbitmap.as_rgb32(); + const u32 *src = &srcbitmap.pix(per_scanline ? 0 : y, visarea.min_x); for (int x = visarea.min_x; x <= visarea.max_x; x++) { *buffer++ = *src++; @@ -1462,7 +1652,7 @@ void screen_device::register_screen_bitmap(bitmap_t &bitmap) // signal the VBLANK period has begun //------------------------------------------------- -void screen_device::vblank_begin() +TIMER_CALLBACK_MEMBER(screen_device::vblank_begin) { // reset the starting VBLANK time m_vblank_start_time = machine().time(); @@ -1482,7 +1672,7 @@ void screen_device::vblank_begin() // if no VBLANK period, call the VBLANK end callback immediately, otherwise reset the timer if (m_vblank_period == 0) - vblank_end(); + vblank_end(0); else m_vblank_end_timer->adjust(time_until_vblank_end()); } @@ -1493,7 +1683,7 @@ void screen_device::vblank_begin() // signal the VBLANK period has ended //------------------------------------------------- -void screen_device::vblank_end() +TIMER_CALLBACK_MEMBER(screen_device::vblank_end) { // call the screen specific callbacks for (auto &item : m_callback_list) @@ -1510,6 +1700,58 @@ void screen_device::vblank_end() //------------------------------------------------- +// create_composited_bitmap - composite scanline +// bitmaps into the output bitmap +//------------------------------------------------- + +void screen_device::create_composited_bitmap() +{ + screen_bitmap &curbitmap = m_bitmap[m_curtexture]; + if (!curbitmap.valid()) + return; + + s32 dstwidth = std::max(m_max_width, m_visarea.right() + 1); + int dstheight = curbitmap.height(); + + switch (curbitmap.format()) + { + default: + case BITMAP_FORMAT_IND16: + { + for (int y = 0; y < dstheight; y++) + { + const bitmap_ind16 &srcbitmap = *(bitmap_ind16 *)m_scan_bitmaps[m_curbitmap][y]; + u16 *dst = &curbitmap.as_ind16().pix(y); + const u16 *src = &srcbitmap.pix(0); + const int dx = (m_scan_widths[y] << 15) / dstwidth; + for (int x = 0; x < m_scan_widths[y]; x += dx) + { + *dst++ = src[x >> 15]; + } + } + break; + } + + case BITMAP_FORMAT_RGB32: + { + for (int y = 0; y < dstheight; y++) + { + const bitmap_rgb32 &srcbitmap = *(bitmap_rgb32 *)m_scan_bitmaps[m_curbitmap][y]; + u32 *dst = &curbitmap.as_rgb32().pix(y); + const u32 *src = &srcbitmap.pix(0); + const int dx = (m_scan_widths[y] << 15) / dstwidth; + for (int x = 0; x < dstwidth << 15; x += dx) + { + *dst++ = src[x >> 15]; + } + } + break; + } + } +} + + +//------------------------------------------------- // update_quads - set up the quads for this // screen //------------------------------------------------- @@ -1525,6 +1767,10 @@ bool screen_device::update_quads() // if we're not skipping the frame and if the screen actually changed, then update the texture if (!machine().video().skip_this_frame() && m_changed) { + if (m_video_attributes & VIDEO_VARIABLE_WIDTH) + { + create_composited_bitmap(); + } m_texture[m_curbitmap]->set_bitmap(m_bitmap[m_curbitmap], m_visarea, m_bitmap[m_curbitmap].texformat()); m_curtexture = m_curbitmap; m_curbitmap = 1 - m_curbitmap; @@ -1552,7 +1798,8 @@ bool screen_device::update_quads() void screen_device::update_burnin() { -// TODO: other than being unnecessary (we should use our rand function first off), this is a simplification of how analog signals really works! +// TODO: other than being unnecessary, this is a simplification of how analog signals really works! +// It's important not to use machine().rand() here, it can cause machine().rand() used in emulation to desync. #undef rand if (!m_burnin.valid()) return; @@ -1569,8 +1816,8 @@ void screen_device::update_burnin() int ystep = (srcheight << 16) / dstheight; int xstart = (u32(rand()) % 32767) * xstep / 32767; int ystart = (u32(rand()) % 32767) * ystep / 32767; - int srcx, srcy; - int x, y; + + bool per_scanline = (m_video_attributes & VIDEO_VARIABLE_WIDTH); switch (curbitmap.format()) { @@ -1578,13 +1825,13 @@ void screen_device::update_burnin() case BITMAP_FORMAT_IND16: { // iterate over rows in the destination - bitmap_ind16 &srcbitmap = curbitmap.as_ind16(); - for (y = 0, srcy = ystart; y < dstheight; y++, srcy += ystep) + for (int y = 0, srcy = ystart; y < dstheight; y++, srcy += ystep) { - u64 *dst = &m_burnin.pix64(y); - const u16 *src = &srcbitmap.pix16(srcy >> 16); - const rgb_t *palette = m_palette->palette()->entry_list_adjusted(); - for (x = 0, srcx = xstart; x < dstwidth; x++, srcx += xstep) + const bitmap_ind16 &srcbitmap = per_scanline ? *(bitmap_ind16 *)m_scan_bitmaps[m_curbitmap][y] : curbitmap.as_ind16(); + u64 *const dst = &m_burnin.pix(y); + u16 const *const src = &srcbitmap.pix(per_scanline ? 0 : (srcy >> 16)); + rgb_t const *const palette = m_palette->palette()->entry_list_adjusted(); + for (int x = 0, srcx = xstart; x < dstwidth; x++, srcx += xstep) { rgb_t pixel = palette[src[srcx >> 16]]; dst[x] += pixel.g() + pixel.r() + pixel.b(); @@ -1596,12 +1843,12 @@ void screen_device::update_burnin() case BITMAP_FORMAT_RGB32: { // iterate over rows in the destination - bitmap_rgb32 &srcbitmap = curbitmap.as_rgb32(); - for (y = 0, srcy = ystart; y < dstheight; y++, srcy += ystep) + for (int y = 0, srcy = ystart; y < dstheight; y++, srcy += ystep) { - u64 *dst = &m_burnin.pix64(y); - const u32 *src = &srcbitmap.pix32(srcy >> 16); - for (x = 0, srcx = xstart; x < dstwidth; x++, srcx += xstep) + const bitmap_rgb32 &srcbitmap = per_scanline ? *(bitmap_rgb32 *)m_scan_bitmaps[m_curbitmap][y] : curbitmap.as_rgb32(); + u64 *const dst = &m_burnin.pix(y); + u32 const *const src = &srcbitmap.pix(per_scanline ? 0 : (srcy >> 16)); + for (int x = 0, srcx = xstart; x < dstwidth; x++, srcx += xstep) { rgb_t pixel = src[srcx >> 16]; dst[x] += pixel.g() + pixel.r() + pixel.b(); @@ -1629,7 +1876,7 @@ void screen_device::finalize_burnin() m_visarea.top() * m_burnin.height() / m_height, m_visarea.bottom() * m_burnin.height() / m_height); - // wrap a bitmap around the memregion we care about + // wrap a bitmap around the subregion we care about bitmap_argb32 finalmap(scaledvis.width(), scaledvis.height()); int srcwidth = m_burnin.width(); int srcheight = m_burnin.height(); @@ -1643,7 +1890,7 @@ void screen_device::finalize_burnin() u64 maxval = 0; for (int y = 0; y < srcheight; y++) { - u64 *src = &m_burnin.pix64(y); + u64 const *const src = &m_burnin.pix(y); for (int x = 0; x < srcwidth; x++) { minval = std::min(minval, src[x]); @@ -1657,8 +1904,8 @@ void screen_device::finalize_burnin() // now normalize and convert to RGB for (int y = 0, srcy = 0; y < dstheight; y++, srcy += ystep) { - u64 *src = &m_burnin.pix64(srcy >> 16); - u32 *dst = &finalmap.pix32(y); + u64 const *const src = &m_burnin.pix(srcy >> 16); + u32 *const dst = &finalmap.pix(y); for (int x = 0, srcx = 0; x < dstwidth; x++, srcx += xstep) { int brightness = u64(maxval - src[srcx >> 16]) * 255 / (maxval - minval); @@ -1670,26 +1917,23 @@ void screen_device::finalize_burnin() // compute the name and create the file emu_file file(machine().options().snapshot_directory(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); - osd_file::error filerr = file.open(machine().basename(), PATH_SEPARATOR "burnin-", this->tag()+1, ".png") ; - if (filerr == osd_file::error::NONE) + std::error_condition const filerr = file.open(util::string_format("%s" PATH_SEPARATOR "burnin-%s.png", machine().basename(), tag() + 1)); + if (!filerr) { - png_info pnginfo; - char text[256]; + util::png_info pnginfo; // add two text entries describing the image - sprintf(text,"%s %s", emulator_info::get_appname(), emulator_info::get_build_version()); - pnginfo.add_text("Software", text); - sprintf(text, "%s %s", machine().system().manufacturer, machine().system().type.fullname()); - pnginfo.add_text("System", text); + pnginfo.add_text("Software", util::string_format("%s %s", emulator_info::get_appname(), emulator_info::get_build_version())); + pnginfo.add_text("System", util::string_format("%s %s", machine().system().manufacturer, machine().system().type.fullname())); // now do the actual work - png_write_bitmap(file, &pnginfo, finalmap, 0, nullptr); + util::png_write_bitmap(file, &pnginfo, finalmap, 0, nullptr); } } //------------------------------------------------- -// finalize_burnin - finalize the burnin bitmap +// load_effect_overlay - //------------------------------------------------- void screen_device::load_effect_overlay(const char *filename) @@ -1702,10 +1946,15 @@ void screen_device::load_effect_overlay(const char *filename) fullname.append(".png"); // load the file + m_screen_overlay_bitmap.reset(); emu_file file(machine().options().art_path(), OPEN_FLAG_READ); - render_load_png(m_screen_overlay_bitmap, file, nullptr, fullname.c_str()); + if (!file.open(fullname)) + { + render_load_png(m_screen_overlay_bitmap, file); + file.close(); + } if (m_screen_overlay_bitmap.valid()) m_container->set_overlay(&m_screen_overlay_bitmap); else - osd_printf_warning("Unable to load effect PNG file '%s'\n", fullname.c_str()); + osd_printf_warning("Unable to load effect PNG file '%s'\n", fullname); } |