diff options
Diffstat (limited to 'src/emu/render.cpp')
-rw-r--r-- | src/emu/render.cpp | 1680 |
1 files changed, 1032 insertions, 648 deletions
diff --git a/src/emu/render.cpp b/src/emu/render.cpp index 58d2c2961c9..545827262bb 100644 --- a/src/emu/render.cpp +++ b/src/emu/render.cpp @@ -2,7 +2,7 @@ // copyright-holders:Aaron Giles /*************************************************************************** - render.c + render.cpp Core rendering system. @@ -37,20 +37,27 @@ ***************************************************************************/ #include "emu.h" -#include "emuopts.h" #include "render.h" + +#include "corestr.h" +#include "emuopts.h" +#include "fileio.h" #include "rendfont.h" #include "rendlay.h" #include "rendutil.h" #include "config.h" #include "drivenum.h" -#include "xmlfile.h" +#include "layout/generic.h" + #include "ui/uimain.h" -#include <zlib.h> +#include "util/ioprocsfilter.h" +#include "util/language.h" +#include "util/path.h" +#include "util/xmlfile.h" #include <algorithm> -#include <functional> +#include <limits> @@ -93,6 +100,40 @@ struct render_target::object_transform }; +struct render_target::pointer_info +{ + pointer_info() noexcept + : type(osd::ui_event_handler::pointer::UNKNOWN) + , oldpos(std::numeric_limits<float>::min(), std::numeric_limits<float>::min()) + , newpos(std::numeric_limits<float>::min(), std::numeric_limits<float>::min()) + , oldbuttons(0U) + , newbuttons(0U) + , edges(0U, 0U) + { + } + + osd::ui_event_handler::pointer type; + std::pair<float, float> oldpos; + std::pair<float, float> newpos; + u32 oldbuttons, newbuttons; + std::pair<size_t, size_t> edges; +}; + + +struct render_target::hit_test +{ + hit_test() noexcept + : inbounds(0U, 0U) + , hit(0U) + { + } + + std::pair<u64, u64> inbounds; + u64 hit; +}; + + + //************************************************************************** // GLOBAL VARIABLES @@ -295,7 +336,8 @@ render_texture::render_texture() m_curseq(0) { m_sbounds.set(0, -1, 0, -1); - memset(m_scaled, 0, sizeof(m_scaled)); + for (auto &elem : m_scaled) + elem.seqid = 0; } @@ -335,11 +377,10 @@ void render_texture::reset(render_manager &manager, texture_scaler_func scaler, void render_texture::release() { // free all scaled versions - for (auto & elem : m_scaled) + for (auto &elem : m_scaled) { - m_manager->invalidate_all(elem.bitmap); - global_free(elem.bitmap); - elem.bitmap = nullptr; + m_manager->invalidate_all(elem.bitmap.get()); + elem.bitmap.reset(); elem.seqid = 0; } @@ -348,6 +389,7 @@ void render_texture::release() m_bitmap = nullptr; m_sbounds.set(0, -1, 0, -1); m_format = TEXFORMAT_ARGB32; + m_scaler = nullptr; m_curseq = 0; } @@ -376,12 +418,9 @@ void render_texture::set_bitmap(bitmap_t &bitmap, const rectangle &sbounds, text // invalidate all scaled versions for (auto & elem : m_scaled) { - if (elem.bitmap != nullptr) - { - m_manager->invalidate_all(elem.bitmap); - global_free(elem.bitmap); - } - elem.bitmap = nullptr; + if (elem.bitmap) + m_manager->invalidate_all(elem.bitmap.get()); + elem.bitmap.reset(); elem.seqid = 0; } } @@ -429,6 +468,7 @@ void render_texture::get_scaled(u32 dwidth, u32 dheight, render_texinfo &texinfo texinfo.base = m_bitmap->raw_pixptr(m_sbounds.top(), m_sbounds.left()); texinfo.rowpixels = m_bitmap->rowpixels(); texinfo.width = swidth; + texinfo.width_margin = m_sbounds.left(); texinfo.height = sheight; // palette will be set later texinfo.seqid = ++m_curseq; @@ -442,7 +482,7 @@ void render_texture::get_scaled(u32 dwidth, u32 dheight, render_texinfo &texinfo // is it a size we already have? scaled_texture *scaled = nullptr; int scalenum; - for (scalenum = 0; scalenum < ARRAY_LENGTH(m_scaled); scalenum++) + for (scalenum = 0; scalenum < std::size(m_scaled); scalenum++) { scaled = &m_scaled[scalenum]; @@ -452,27 +492,27 @@ void render_texture::get_scaled(u32 dwidth, u32 dheight, render_texinfo &texinfo } // did we get one? - if (scalenum == ARRAY_LENGTH(m_scaled)) + if (scalenum == std::size(m_scaled)) { int lowest = -1; // didn't find one -- take the entry with the lowest seqnum - for (scalenum = 0; scalenum < ARRAY_LENGTH(m_scaled); scalenum++) - if ((lowest == -1 || m_scaled[scalenum].seqid < m_scaled[lowest].seqid) && !primlist.has_reference(m_scaled[scalenum].bitmap)) + for (scalenum = 0; scalenum < std::size(m_scaled); scalenum++) + if ((lowest == -1 || m_scaled[scalenum].seqid < m_scaled[lowest].seqid) && !primlist.has_reference(m_scaled[scalenum].bitmap.get())) lowest = scalenum; if (-1 == lowest) throw emu_fatalerror("render_texture::get_scaled: Too many live texture instances!"); // throw out any existing entries scaled = &m_scaled[lowest]; - if (scaled->bitmap != nullptr) + if (scaled->bitmap) { - m_manager->invalidate_all(scaled->bitmap); - global_free(scaled->bitmap); + m_manager->invalidate_all(scaled->bitmap.get()); + scaled->bitmap.reset(); } // allocate a new bitmap - scaled->bitmap = global_alloc(bitmap_argb32(dwidth, dheight)); + scaled->bitmap = std::make_unique<bitmap_argb32>(dwidth, dheight); scaled->seqid = ++m_curseq; // let the scaler do the work @@ -480,8 +520,8 @@ void render_texture::get_scaled(u32 dwidth, u32 dheight, render_texinfo &texinfo } // finally fill out the new info - primlist.add_reference(scaled->bitmap); - texinfo.base = &scaled->bitmap->pix32(0); + primlist.add_reference(scaled->bitmap.get()); + texinfo.base = &scaled->bitmap->pix(0); texinfo.rowpixels = scaled->bitmap->rowpixels(); texinfo.width = dwidth; texinfo.height = dheight; @@ -496,7 +536,7 @@ void render_texture::get_scaled(u32 dwidth, u32 dheight, render_texinfo &texinfo // palette for a texture //------------------------------------------------- -const rgb_t *render_texture::get_adjusted_palette(render_container &container) +const rgb_t *render_texture::get_adjusted_palette(render_container &container, u32 &out_length) { // override the palette with our adjusted palette switch (m_format) @@ -506,7 +546,7 @@ const rgb_t *render_texture::get_adjusted_palette(render_container &container) assert(m_bitmap->palette() != nullptr); // return our adjusted palette - return container.bcg_lookup_table(m_format, m_bitmap->palette()); + return container.bcg_lookup_table(m_format, out_length, m_bitmap->palette()); case TEXFORMAT_RGB32: case TEXFORMAT_ARGB32: @@ -515,7 +555,7 @@ const rgb_t *render_texture::get_adjusted_palette(render_container &container) // if no adjustment necessary, return nullptr if (!container.has_brightness_contrast_gamma_changes()) return nullptr; - return container.bcg_lookup_table(m_format); + return container.bcg_lookup_table(m_format, out_length); default: assert(false); @@ -535,8 +575,7 @@ const rgb_t *render_texture::get_adjusted_palette(render_container &container) //------------------------------------------------- render_container::render_container(render_manager &manager, screen_device *screen) - : m_next(nullptr) - , m_manager(manager) + : m_manager(manager) , m_screen(screen) , m_overlaybitmap(nullptr) , m_overlaytexture(nullptr) @@ -679,7 +718,7 @@ float render_container::apply_brightness_contrast_gamma_fp(float value) // given texture mode //------------------------------------------------- -const rgb_t *render_container::bcg_lookup_table(int texformat, palette_t *palette) +const rgb_t *render_container::bcg_lookup_table(int texformat, u32 &out_length, palette_t *palette) { switch (texformat) { @@ -690,15 +729,18 @@ const rgb_t *render_container::bcg_lookup_table(int texformat, palette_t *palett m_bcglookup.resize(palette->max_index()); recompute_lookups(); } - assert (palette == &m_palclient->palette()); + assert(palette == &m_palclient->palette()); + out_length = palette->max_index(); return &m_bcglookup[0]; case TEXFORMAT_RGB32: case TEXFORMAT_ARGB32: case TEXFORMAT_YUY16: + out_length = std::size(m_bcglookup256); return m_bcglookup256; default: + out_length = 0; return nullptr; } } @@ -713,8 +755,8 @@ void render_container::overlay_scale(bitmap_argb32 &dest, bitmap_argb32 &source, // simply replicate the source bitmap over the target for (int y = 0; y < dest.height(); y++) { - u32 *src = &source.pix32(y % source.height()); - u32 *dst = &dest.pix32(y); + u32 const *const src = &source.pix(y % source.height()); + u32 *dst = &dest.pix(y); int sx = 0; // loop over columns @@ -877,25 +919,31 @@ render_container::user_settings::user_settings() // render_target - constructor //------------------------------------------------- -render_target::render_target(render_manager &manager, const internal_layout *layoutfile, u32 flags) - : render_target(manager, layoutfile, flags, CONSTRUCTOR_IMPL) +render_target::render_target(render_manager &manager, render_container *ui, const internal_layout *layoutfile, u32 flags) + : render_target(manager, ui, layoutfile, flags, CONSTRUCTOR_IMPL) { } -render_target::render_target(render_manager &manager, util::xml::data_node const &layout, u32 flags) - : render_target(manager, layout, flags, CONSTRUCTOR_IMPL) +render_target::render_target(render_manager &manager, render_container *ui, util::xml::data_node const &layout, u32 flags) + : render_target(manager, ui, layout, flags, CONSTRUCTOR_IMPL) { } -template <typename T> render_target::render_target(render_manager &manager, T &&layout, u32 flags, constructor_impl_t) +template <typename T> +render_target::render_target(render_manager &manager, render_container *ui, T &&layout, u32 flags, constructor_impl_t) : m_next(nullptr) , m_manager(manager) - , m_curview(nullptr) + , m_ui_container(ui) + , m_curview(0U) , m_flags(flags) , m_listindex(0) , m_width(640) , m_height(480) + , m_keepaspect(false) + , m_int_overscan(false) , m_pixel_aspect(0.0f) + , m_int_scale_x(0) + , m_int_scale_y(0) , m_max_refresh(0) , m_orientation(0) , m_base_view(nullptr) @@ -903,25 +951,31 @@ template <typename T> render_target::render_target(render_manager &manager, T && , m_maxtexwidth(65536) , m_maxtexheight(65536) , m_transform_container(true) + , m_external_artwork(false) { // determine the base layer configuration based on options m_base_layerconfig.set_zoom_to_screen(manager.machine().options().artwork_crop()); // aspect and scale options - m_keepaspect = (manager.machine().options().keep_aspect() && !(flags & RENDER_CREATE_HIDDEN)); - m_int_overscan = manager.machine().options().int_overscan(); - m_int_scale_x = manager.machine().options().int_scale_x(); - m_int_scale_y = manager.machine().options().int_scale_y(); - if (m_manager.machine().options().auto_stretch_xy()) - m_scale_mode = SCALE_FRACTIONAL_AUTO; - else if (manager.machine().options().uneven_stretch_x()) - m_scale_mode = SCALE_FRACTIONAL_X; - else if (manager.machine().options().uneven_stretch_y()) - m_scale_mode = SCALE_FRACTIONAL_Y; - else if (manager.machine().options().uneven_stretch()) - m_scale_mode = SCALE_FRACTIONAL; + if (!(flags & RENDER_CREATE_HIDDEN)) + { + m_keepaspect = manager.machine().options().keep_aspect(); + m_int_overscan = manager.machine().options().int_overscan(); + m_int_scale_x = manager.machine().options().int_scale_x(); + m_int_scale_y = manager.machine().options().int_scale_y(); + if (m_manager.machine().options().auto_stretch_xy()) + m_scale_mode = SCALE_FRACTIONAL_AUTO; + else if (manager.machine().options().uneven_stretch_x()) + m_scale_mode = SCALE_FRACTIONAL_X; + else if (manager.machine().options().uneven_stretch_y()) + m_scale_mode = SCALE_FRACTIONAL_Y; + else if (manager.machine().options().uneven_stretch()) + m_scale_mode = SCALE_FRACTIONAL; + else + m_scale_mode = SCALE_INTEGER; + } else - m_scale_mode = SCALE_INTEGER; + m_scale_mode = SCALE_FRACTIONAL; // determine the base orientation based on options if (!manager.machine().options().rotate()) @@ -945,6 +999,10 @@ template <typename T> render_target::render_target(render_manager &manager, T && // load the layout files load_layout_files(std::forward<T>(layout), flags & RENDER_CREATE_SINGLE_FILE); + for (layout_file &file : m_filelist) + for (layout_view &view : file.views()) + if (!(m_flags & RENDER_CREATE_NO_ART) || !view.has_art()) + m_views.emplace_back(view, view.default_visibility_mask()); // set the current view to the first one set_view(0); @@ -995,9 +1053,9 @@ void render_target::set_bounds(s32 width, s32 height, float pixel_aspect) m_width = width; m_height = height; m_bounds.x0 = m_bounds.y0 = 0; - m_bounds.x1 = (float)width; - m_bounds.y1 = (float)height; - m_pixel_aspect = pixel_aspect != 0.0? pixel_aspect : 1.0; + m_bounds.x1 = float(width); + m_bounds.y1 = float(height); + m_pixel_aspect = pixel_aspect != 0.0F ? pixel_aspect : 1.0F; } @@ -1006,13 +1064,16 @@ void render_target::set_bounds(s32 width, s32 height, float pixel_aspect) // a target //------------------------------------------------- -void render_target::set_view(int viewindex) +void render_target::set_view(unsigned viewindex) { - layout_view *view = view_by_index(viewindex); - if (view) + if (m_views.size() > viewindex) { - m_curview = view; - view->recompute(m_layerconfig); + forget_pointers(); + m_curview = viewindex; + current_view().recompute(visibility_mask(), m_layerconfig.zoom_to_screen()); + current_view().preload(); + m_clickable_items.clear(); + m_clickable_items.resize(current_view().interactive_items().size()); } } @@ -1030,100 +1091,317 @@ void render_target::set_max_texture_size(int maxwidth, int maxheight) //------------------------------------------------- -// configured_view - select a view for this -// target based on the configuration parameters +// pointer_updated - pointer activity within +// target +//------------------------------------------------- + +void render_target::pointer_updated( + osd::ui_event_handler::pointer type, + u16 ptrid, + u16 device, + s32 x, + s32 y, + u32 buttons, + u32 pressed, + u32 released, + s16 clicks) +{ + auto const target_f(map_point_layout(x, y)); + current_view().pointer_updated(type, ptrid, device, target_f.first, target_f.second, buttons, pressed, released, clicks); + + // 64 pointers ought to be enough for anyone + if (64 <= ptrid) + return; + + // just store the updated pointer state + if (m_pointers.size() <= ptrid) + m_pointers.resize(ptrid + 1); + m_pointers[ptrid].type = type; + m_pointers[ptrid].newpos = target_f; + m_pointers[ptrid].newbuttons = buttons; +} + + +//------------------------------------------------- +// pointer_left - pointer left target normally //------------------------------------------------- -int render_target::configured_view(const char *viewname, int targetindex, int numtargets) +void render_target::pointer_left( + osd::ui_event_handler::pointer type, + u16 ptrid, + u16 device, + s32 x, + s32 y, + u32 released, + s16 clicks) { - layout_view *view = nullptr; - int viewindex; + auto const target_f(map_point_layout(x, y)); + current_view().pointer_left(type, ptrid, device, target_f.first, target_f.second, released, clicks); - // auto view just selects the nth view - if (strcmp(viewname, "auto") != 0) + // store the updated state if relevant + if (m_pointers.size() > ptrid) { - // scan for a matching view name - size_t viewlen = strlen(viewname); - for (view = view_by_index(viewindex = 0); view != nullptr; view = view_by_index(++viewindex)) - if (core_strnicmp(view->name().c_str(), viewname, viewlen) == 0) - break; + m_pointers[ptrid].newpos = std::make_pair(std::numeric_limits<float>::min(), std::numeric_limits<float>::min()); + m_pointers[ptrid].newbuttons = 0; } +} - // if we don't have a match, default to the nth view - screen_device_iterator iter(m_manager.machine().root_device()); - int scrcount = iter.count(); - if (view == nullptr && scrcount > 0) + +//------------------------------------------------- +// pointer_aborted - pointer left target +// abnormally +//------------------------------------------------- + +void render_target::pointer_aborted( + osd::ui_event_handler::pointer type, + u16 ptrid, + u16 device, + s32 x, + s32 y, + u32 released, + s16 clicks) +{ + // let layout scripts handle pointer input + auto const target_f(map_point_layout(x, y)); + current_view().pointer_aborted(type, ptrid, device, target_f.first, target_f.second, released, clicks); + + // store the updated state if relevant + if (m_pointers.size() > ptrid) { - // if we have enough targets to be one per screen, assign in order - if (numtargets >= scrcount) + m_pointers[ptrid].newpos = std::make_pair(std::numeric_limits<float>::min(), std::numeric_limits<float>::min()); + m_pointers[ptrid].newbuttons = 0; + } +} + + +//------------------------------------------------- +// forget_pointers - stop processing pointer +// input +//------------------------------------------------- + +void render_target::forget_pointers() +{ + current_view().forget_pointers(); + m_pointers.clear(); + for (size_t i = 0; m_clickable_items.size() > i; ++i) + { + if (m_clickable_items[i].hit) { - int ourindex = index() % scrcount; - screen_device *screen = iter.byindex(ourindex); + layout_view_item const &item(current_view().interactive_items()[i]); + auto const [port, mask] = item.input_tag_and_mask(); + ioport_field *const field(port ? port->field(mask) : nullptr); + if (field) + field->set_value(0); + } + m_clickable_items[i] = hit_test(); + } +} - // find the first view with this screen and this screen only - for (view = view_by_index(viewindex = 0); view != nullptr; view = view_by_index(++viewindex)) + +//------------------------------------------------- +// update_pointer_fields - update inputs for new +// pointer state +//------------------------------------------------- + +void render_target::update_pointer_fields() +{ + auto const &x_edges(current_view().interactive_edges_x()); + auto const &y_edges(current_view().interactive_edges_y()); + + // update items bounds intersection checks for pointers + for (size_t ptr = 0; m_pointers.size() > ptr; ++ptr) + { + auto const [x, y] = m_pointers[ptr].newpos; + auto edges = m_pointers[ptr].edges; + + // check for moving across horizontal edges + if (x < m_pointers[ptr].oldpos.first) + { + while (edges.first && (x < x_edges[edges.first - 1].position())) { - const render_screen_list &viewscreens = view->screens(); - if (viewscreens.count() == 0) - { - view = nullptr; - break; - } - else if (viewscreens.count() == viewscreens.contains(*screen)) - break; + --edges.first; + auto const &edge(x_edges[edges.first]); + if (edge.trailing()) + m_clickable_items[edge.index()].inbounds.first |= u64(1) << ptr; + else + m_clickable_items[edge.index()].inbounds.first &= ~(u64(1) << ptr); + } + } + else if (x > m_pointers[ptr].oldpos.first) + { + while ((x_edges.size() > edges.first) && (x >= x_edges[edges.first].position())) + { + auto const &edge(x_edges[edges.first]); + if (edge.trailing()) + m_clickable_items[edge.index()].inbounds.first &= ~(u64(1) << ptr); + else + m_clickable_items[edge.index()].inbounds.first |= u64(1) << ptr; + ++edges.first; } } - // otherwise, find the first view that has all the screens - if (view == nullptr) + // check for moving across vertical edges + if (y < m_pointers[ptr].oldpos.second) { - for (view = view_by_index(viewindex = 0); view != nullptr; view = view_by_index(++viewindex)) + while (edges.second && (y < y_edges[edges.second - 1].position())) { - render_screen_list const &viewscreens(view->screens()); - if (viewscreens.count() >= scrcount) - { - bool screen_missing(false); - for (screen_device &screen : iter) - { - if (!viewscreens.contains(screen)) - { - screen_missing = true; - break; - } - } - if (!screen_missing) - break; - } + --edges.second; + auto const &edge(y_edges[edges.second]); + if (edge.trailing()) + m_clickable_items[edge.index()].inbounds.second |= u64(1) << ptr; + else + m_clickable_items[edge.index()].inbounds.second &= ~(u64(1) << ptr); } } + else if (y > m_pointers[ptr].oldpos.second) + { + while ((y_edges.size() > edges.second) && (y >= y_edges[edges.second].position())) + { + auto const &edge(y_edges[edges.second]); + if (edge.trailing()) + m_clickable_items[edge.index()].inbounds.second &= ~(u64(1) << ptr); + else + m_clickable_items[edge.index()].inbounds.second |= u64(1) << ptr; + ++edges.second; + } + } + + // update the pointer's state + m_pointers[ptr].oldpos = m_pointers[ptr].newpos; + m_pointers[ptr].oldbuttons = m_pointers[ptr].newbuttons; + m_pointers[ptr].edges = edges; } - // make sure it's a valid view - return (view != nullptr) ? view_index(*view) : 0; + // update item hit states + u64 obscured(0U); + for (size_t i = 0; m_clickable_items.size() > i; ++i) + { + layout_view_item const &item(current_view().interactive_items()[i]); + u64 const inbounds(m_clickable_items[i].inbounds.first & m_clickable_items[i].inbounds.second); + u64 hit(m_clickable_items[i].hit); + for (unsigned ptr = 0; m_pointers.size() > ptr; ++ptr) + { + pointer_info const &pointer(m_pointers[ptr]); + bool const prefilter(BIT(~obscured & inbounds, ptr)); + if (!prefilter || !BIT(pointer.newbuttons, 0) || !item.bounds().includes(pointer.newpos.first, pointer.newpos.second)) + { + hit &= ~(u64(1) << ptr); + } + else + { + hit |= u64(1) << ptr; + if (!item.clickthrough()) + obscured |= u64(1) << ptr; + } + } + + // update field state + if (bool(hit) != bool(m_clickable_items[i].hit)) + { + auto const [port, mask] = item.input_tag_and_mask(); + ioport_field *const field(port ? port->field(mask) : nullptr); + if (field) + { + if (hit) + field->set_value(1); + else + field->clear_value(); + } + } + m_clickable_items[i].hit = hit; + } } //------------------------------------------------- -// view_name - return the name of the given view +// set_visibility_toggle - show or hide selected +// parts of a view //------------------------------------------------- -const char *render_target::view_name(int viewindex) +void render_target::set_visibility_toggle(unsigned index, bool enable) { - layout_view const *const view = view_by_index(viewindex); - return view ? view->name().c_str() : nullptr; + assert(current_view().visibility_toggles().size() > index); + if (enable) + m_views[m_curview].second |= u32(1) << index; + else + m_views[m_curview].second &= ~(u32(1) << index); + update_layer_config(); + current_view().preload(); } //------------------------------------------------- -// render_target_get_view_screens - return a -// bitmask of which screens are visible on a -// given view +// configured_view - select a view for this +// target based on the configuration parameters //------------------------------------------------- -const render_screen_list &render_target::view_screens(int viewindex) +unsigned render_target::configured_view(const char *viewname, int targetindex, int numtargets) { - layout_view *view = view_by_index(viewindex); - return (view != nullptr) ? view->screens() : s_empty_screen_list; + // if it isn't "auto" or an empty string, try to match it as a view name prefix + if (viewname && *viewname && strcmp(viewname, "auto")) + { + // scan for a matching view name + size_t const viewlen = strlen(viewname); + for (unsigned i = 0; m_views.size() > i; ++i) + { + if (!core_strnicmp(m_views[i].first.name().c_str(), viewname, viewlen)) + return i; + } + } + + // if we don't have a match, default to the nth view + std::vector<std::reference_wrapper<screen_device> > screens; + for (screen_device &screen : screen_device_enumerator(m_manager.machine().root_device())) + screens.push_back(screen); + if (!screens.empty()) + { + // if we have enough targets to be one per screen, assign in order + if (numtargets >= screens.size()) + { + // find the first view with this screen and this screen only + layout_view *view = nullptr; + screen_device const &screen = screens[index() % screens.size()]; + for (unsigned i = 0; !view && (m_views.size() > i); ++i) + { + for (layout_view_item &viewitem : m_views[i].first.items()) + { + screen_device const *const viewscreen(viewitem.screen()); + if (viewscreen == &screen) + { + view = &m_views[i].first; + } + else if (viewscreen) + { + view = nullptr; + break; + } + } + } + if (view) + return view_index(*view); + } + + // otherwise, find the first view that has all the screens + for (unsigned i = 0; m_views.size() > i; ++i) + { + layout_view &curview = m_views[i].first; + if (std::find_if(screens.begin(), screens.end(), [&curview] (screen_device &screen) { return !curview.has_screen(screen); }) == screens.end()) + return i; + } + } + + // default to the first view + return 0; +} + + +//------------------------------------------------- +// view_name - return the name of the given view +//------------------------------------------------- + +const char *render_target::view_name(unsigned viewindex) +{ + return (m_views.size() > viewindex) ? m_views[viewindex].first.name().c_str() : nullptr; } @@ -1146,7 +1424,7 @@ void render_target::compute_visible_area(s32 target_width, s32 target_height, fl if (m_keepaspect) { // start with the aspect ratio of the square pixel layout - width = m_curview->effective_aspect(m_layerconfig); + width = current_view().effective_aspect(); height = 1.0f; // first apply target orientation @@ -1182,49 +1460,107 @@ void render_target::compute_visible_area(s32 target_width, s32 target_height, fl // get source size and aspect s32 src_width, src_height; compute_minimum_size(src_width, src_height); - float src_aspect = m_curview->effective_aspect(m_layerconfig); + float src_aspect = current_view().effective_aspect(); // apply orientation if required if (target_orientation & ORIENTATION_SWAP_XY) - src_aspect = 1.0 / src_aspect; + src_aspect = 1.0f / src_aspect; - // get target aspect - float target_aspect = (float)target_width / (float)target_height * target_pixel_aspect; - bool target_is_portrait = (target_aspect < 1.0f); + // we need the ratio of target to source aspect + float aspect_ratio = m_keepaspect ? (float)target_width / (float)target_height * target_pixel_aspect / src_aspect : 1.0f; + + // first compute (a, b) scale factors to fit the screen + float a = (float)target_width / src_width; + float b = (float)target_height / src_height; // apply automatic axial stretching if required int scale_mode = m_scale_mode; - if (m_scale_mode == SCALE_FRACTIONAL_AUTO) + if (scale_mode == SCALE_FRACTIONAL_AUTO) + scale_mode = (m_manager.machine().system().flags & ORIENTATION_SWAP_XY) ^ (target_orientation & ORIENTATION_SWAP_XY) ? + SCALE_FRACTIONAL_Y : SCALE_FRACTIONAL_X; + + // determine the scaling method for each axis + bool a_is_fract = (scale_mode == SCALE_FRACTIONAL_X || scale_mode == SCALE_FRACTIONAL); + bool b_is_fract = (scale_mode == SCALE_FRACTIONAL_Y || scale_mode == SCALE_FRACTIONAL); + + // check if we have user defined scale factors, if so use them instead, but only on integer axes + int a_user = a_is_fract ? 0 : m_int_scale_x; + int b_user = b_is_fract ? 0 : m_int_scale_y; + + // we allow overscan either explicitely or if integer scale factors are forced by user + bool int_overscan = m_int_overscan || (m_keepaspect && (a_user != 0 || b_user != 0)); + float a_max = std::max(a, (float)a_user); + float b_max = std::max(b, (float)b_user); + + + // get the usable bounding box considering the type of scaling for each axis + float usable_aspect = (a_is_fract ? a : std::max(1.0f, floorf(a))) * src_width / + ((b_is_fract ? b : std::max(1.0f, floorf(b))) * src_height) * target_pixel_aspect; + + // depending on the relative shape between target and source, let's define 'a' and 'b' so that: + // * a is the leader axis (first to hit a boundary) + // * b is the follower axis + if (usable_aspect > src_aspect) { - bool is_rotated = (m_manager.machine().system().flags & ORIENTATION_SWAP_XY) ^ (target_orientation & ORIENTATION_SWAP_XY); - scale_mode = is_rotated ^ target_is_portrait ? SCALE_FRACTIONAL_Y : SCALE_FRACTIONAL_X; + std::swap(a, b); + std::swap(a_user, b_user); + std::swap(a_is_fract, b_is_fract); + std::swap(a_max, b_max); + aspect_ratio = 1.0f / aspect_ratio; } - // determine the scale mode for each axis - bool x_is_integer = !((!target_is_portrait && scale_mode == SCALE_FRACTIONAL_X) || (target_is_portrait && scale_mode == SCALE_FRACTIONAL_Y)); - bool y_is_integer = !((target_is_portrait && scale_mode == SCALE_FRACTIONAL_X) || (!target_is_portrait && scale_mode == SCALE_FRACTIONAL_Y)); + // now find an (a, b) pair that best fits our boundaries and scale options + float a_best = 1.0f, b_best = 1.0f; + float diff = 1000; + + // fill (a0, a1) range + float u = a_user == 0 ? a : (float)a_user; + float a_range[] = {a_is_fract ? u : std::max(1.0f, floorf(u)), a_is_fract ? u : std::max(1.0f, roundf(u))}; + + for (float aa : a_range) + { + // apply aspect correction to 'b' axis if needed, considering resulting 'a' borders + float ba = b * (m_keepaspect ? aspect_ratio * (aa / a) : 1.0f); + + // fill (b0, b1) range + float v = b_user == 0 ? ba : (float)b_user; + float b_range[] = {b_is_fract ? v : std::max(1.0f, floorf(v)), b_is_fract ? v : std::max(1.0f, roundf(v))}; - // first compute scale factors to fit the screen - float xscale = (float)target_width / src_width; - float yscale = (float)target_height / src_height; - float maxxscale = std::max(1.0f, float(m_int_overscan ? render_round_nearest(xscale) : floor(xscale))); - float maxyscale = std::max(1.0f, float(m_int_overscan ? render_round_nearest(yscale) : floor(yscale))); + for (float bb : b_range) + { + // we may need to propagate proportions back to 'a' axis + float ab = aa; + if (m_keepaspect && a_user == 0) + { + if (a_is_fract) ab *= (bb / ba); + else if (b_user != 0) ab = std::max(1.0f, roundf(ab * (bb / ba))); + } - // now apply desired scale mode and aspect correction - if (m_keepaspect && target_aspect > src_aspect) xscale *= src_aspect / target_aspect * (maxyscale / yscale); - if (m_keepaspect && target_aspect < src_aspect) yscale *= target_aspect / src_aspect * (maxxscale / xscale); - if (x_is_integer) xscale = std::min(maxxscale, std::max(1.0f, render_round_nearest(xscale))); - if (y_is_integer) yscale = std::min(maxyscale, std::max(1.0f, render_round_nearest(yscale))); + // if overscan isn't allowed, discard values that exceed the usable bounding box, except a minimum of 1.0f + if (!int_overscan && ((ab > a_max && bb > 1.0f) || (bb > b_max && ab > 1.0f))) + continue; + + // score the result + float new_diff = fabsf(aspect_ratio * (a / b) - (ab / bb)); + + if (new_diff <= diff) + { + diff = new_diff; + a_best = ab; + b_best = bb; + } + } + } + a = a_best; + b = b_best; - // check if we have user defined scale factors, if so use them instead - int user_scale_x = target_is_portrait? m_int_scale_y : m_int_scale_x; - int user_scale_y = target_is_portrait? m_int_scale_x : m_int_scale_y; - xscale = user_scale_x > 0 ? user_scale_x : xscale; - yscale = user_scale_y > 0 ? user_scale_y : yscale; + // restore orientation + if (usable_aspect > src_aspect) + std::swap(a, b); // set the final width/height - visible_width = render_round_nearest(src_width * xscale); - visible_height = render_round_nearest(src_height * yscale); + visible_width = render_round_nearest(src_width * a); + visible_height = render_round_nearest(src_height * b); break; } } @@ -1251,16 +1587,16 @@ void render_target::compute_minimum_size(s32 &minwidth, s32 &minheight) return; } - if (!m_curview) + if (m_views.empty()) throw emu_fatalerror("Mandatory artwork is missing"); // scan the current view for all screens - for (layout_view::item &curitem : m_curview->items()) + for (layout_view_item &curitem : current_view().items()) { - if (curitem.screen()) + screen_device const *const screen = curitem.screen(); + if (screen) { // use a hard-coded default visible area for vector screens - screen_device *const screen = curitem.screen(); const rectangle vectorvis(0, 639, 0, 479); const rectangle &visarea = (screen->screen_type() == SCREEN_TYPE_VECTOR) ? vectorvis : screen->visible_area(); @@ -1309,13 +1645,9 @@ void render_target::compute_minimum_size(s32 &minwidth, s32 &minheight) render_primitive_list &render_target::get_primitives() { - // remember the base values if this is the first frame - if (m_base_view == nullptr) - m_base_view = m_curview; - // switch to the next primitive list render_primitive_list &list = m_primlist[m_listindex]; - m_listindex = (m_listindex + 1) % ARRAY_LENGTH(m_primlist); + m_listindex = (m_listindex + 1) % std::size(m_primlist); list.acquire_lock(); // free any previous primitives @@ -1335,9 +1667,11 @@ render_primitive_list &render_target::get_primitives() root_xform.orientation = m_orientation; root_xform.no_center = false; - // iterate over items in the view, but only if we're running if (m_manager.machine().phase() >= machine_phase::RESET) - for (layout_view::item &curitem : m_curview->items()) + { + // we're running - iterate over items in the view + current_view().prepare_items(); + for (layout_view_item &curitem : current_view().visible_items()) { // first apply orientation to the bounds render_bounds bounds = curitem.bounds(); @@ -1350,27 +1684,24 @@ render_primitive_list &render_target::get_primitives() item_xform.yoffs = root_xform.yoffs + bounds.y0 * root_xform.yscale; item_xform.xscale = (bounds.x1 - bounds.x0) * root_xform.xscale; item_xform.yscale = (bounds.y1 - bounds.y0) * root_xform.yscale; - item_xform.color.r = curitem.color().r * root_xform.color.r; - item_xform.color.g = curitem.color().g * root_xform.color.g; - item_xform.color.b = curitem.color().b * root_xform.color.b; - item_xform.color.a = curitem.color().a * root_xform.color.a; + item_xform.color = curitem.color() * root_xform.color; item_xform.orientation = orientation_add(curitem.orientation(), root_xform.orientation); item_xform.no_center = false; // if there is no associated element, it must be a screen element - if (curitem.screen() != nullptr) + if (curitem.screen()) add_container_primitives(list, root_xform, item_xform, curitem.screen()->container(), curitem.blend_mode()); else - add_element_primitives(list, item_xform, *curitem.element(), curitem.state(), curitem.blend_mode()); + add_element_primitives(list, item_xform, curitem); } - - // if we are not in the running stage, draw an outer box + } else { + // if we are not in the running stage, draw an outer box render_primitive *prim = list.alloc(render_primitive::QUAD); - set_render_bounds_xy(prim->bounds, 0.0f, 0.0f, (float)m_width, (float)m_height); + prim->bounds.set_xy(0.0f, 0.0f, (float)m_width, (float)m_height); prim->full_bounds = prim->bounds; - set_render_color(&prim->color, 1.0f, 1.0f, 1.0f, 1.0f); + prim->color.set(1.0f, 0.1f, 0.1f, 0.1f); prim->texture.base = nullptr; prim->flags = PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA); list.append(*prim); @@ -1378,34 +1709,17 @@ render_primitive_list &render_target::get_primitives() if (m_width > 1 && m_height > 1) { prim = list.alloc(render_primitive::QUAD); - set_render_bounds_xy(prim->bounds, 1.0f, 1.0f, (float)(m_width - 1), (float)(m_height - 1)); + prim->bounds.set_xy(1.0f, 1.0f, float(m_width - 1), float(m_height - 1)); prim->full_bounds = prim->bounds; - set_render_color(&prim->color, 1.0f, 0.0f, 0.0f, 0.0f); + prim->color.set(1.0f, 0.0f, 0.0f, 0.0f); prim->texture.base = nullptr; prim->flags = PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA); list.append(*prim); } } - // process the debug containers - for (render_container &debug : m_debug_containers) - { - object_transform ui_xform; - ui_xform.xoffs = 0; - ui_xform.yoffs = 0; - ui_xform.xscale = (float)m_width; - ui_xform.yscale = (float)m_height; - ui_xform.color.r = ui_xform.color.g = ui_xform.color.b = 1.0f; - ui_xform.color.a = 0.9f; - ui_xform.orientation = m_orientation; - ui_xform.no_center = true; - - // add UI elements - add_container_primitives(list, root_xform, ui_xform, debug, BLENDMODE_ALPHA); - } - - // process the UI if we are the UI target - if (is_ui_target()) + // process UI elements if applicable + if (m_ui_container) { // compute the transform for the UI object_transform ui_xform; @@ -1418,7 +1732,7 @@ render_primitive_list &render_target::get_primitives() ui_xform.no_center = false; // add UI elements - add_container_primitives(list, root_xform, ui_xform, m_manager.ui_container(), BLENDMODE_ALPHA); + add_container_primitives(list, root_xform, ui_xform, *m_ui_container, BLENDMODE_ALPHA); } // optimize the list before handing it off @@ -1436,21 +1750,45 @@ render_primitive_list &render_target::get_primitives() bool render_target::map_point_container(s32 target_x, s32 target_y, render_container &container, float &container_x, float &container_y) { - ioport_port *input_port; - ioport_value input_mask; - return map_point_internal(target_x, target_y, &container, container_x, container_y, input_port, input_mask); -} + std::pair<float, float> target_f(map_point_internal(target_x, target_y)); + // explicitly check for the UI container + if (&container == m_ui_container) + { + // this hit test went against the UI container + container_x = float(target_x) / m_width; + container_y = float(target_y) / m_height; + return (target_f.first >= 0.0f) && (target_f.first < 1.0f) && (target_f.second >= 0.0f) && (target_f.second < 1.0f); + } + else + { + if (m_orientation & ORIENTATION_FLIP_X) + target_f.first = 1.0f - target_f.first; + if (m_orientation & ORIENTATION_FLIP_Y) + target_f.second = 1.0f - target_f.second; + if (m_orientation & ORIENTATION_SWAP_XY) + std::swap(target_f.first, target_f.second); + + // try to find the right container + auto const &items(current_view().visible_screen_items()); + auto const found(std::find_if( + items.begin(), + items.end(), + [&container] (layout_view_item &item) { return &item.screen()->container() == &container; })); + if (items.end() != found) + { + // point successfully mapped + layout_view_item &item(*found); + render_bounds const bounds(item.bounds()); + container_x = (target_f.first - bounds.x0) / bounds.width(); + container_y = (target_f.second - bounds.y0) / bounds.height(); + return bounds.includes(target_f.first, target_f.second); + } + } -//------------------------------------------------- -// map_point_input - attempts to map a point on -// the specified render_target to the specified -// container, if possible -//------------------------------------------------- - -bool render_target::map_point_input(s32 target_x, s32 target_y, ioport_port *&input_port, ioport_value &input_mask, float &input_x, float &input_y) -{ - return map_point_internal(target_x, target_y, nullptr, input_x, input_y, input_port, input_mask);; + // default to point not mapped + container_x = container_y = -1.0f; + return false; } @@ -1475,50 +1813,16 @@ void render_target::invalidate_all(void *refptr) //------------------------------------------------- -// debug_alloc - allocate a container for a debug -// view -//------------------------------------------------- - -render_container *render_target::debug_alloc() -{ - return &m_debug_containers.append(*m_manager.container_alloc()); -} - - -//------------------------------------------------- -// debug_free - free a container for a debug view -//------------------------------------------------- - -void render_target::debug_free(render_container &container) -{ - m_debug_containers.remove(container); -} - - -//------------------------------------------------- -// debug_append - move a debug view container to -// the end of the list -//------------------------------------------------- - -void render_target::debug_append(render_container &container) -{ - m_debug_containers.append(m_debug_containers.detach(container)); -} - - -//------------------------------------------------- // resolve_tags - resolve tag lookups //------------------------------------------------- void render_target::resolve_tags() { for (layout_file &file : m_filelist) - { - for (layout_view &view : file.views()) - { - view.resolve_tags(); - } - } + file.resolve_tags(); + + update_layer_config(); + current_view().preload(); } @@ -1529,7 +1833,10 @@ void render_target::resolve_tags() void render_target::update_layer_config() { - m_curview->recompute(m_layerconfig); + forget_pointers(); + current_view().recompute(visibility_mask(), m_layerconfig.zoom_to_screen()); + m_clickable_items.clear(); + m_clickable_items.resize(current_view().interactive_items().size()); } @@ -1540,96 +1847,97 @@ void render_target::update_layer_config() void render_target::load_layout_files(const internal_layout *layoutfile, bool singlefile) { - bool have_artwork = false; + bool have_artwork = false; // if there's an explicit file, load that first - const char *basename = m_manager.machine().basename(); + const std::string &basename = m_manager.machine().basename(); if (layoutfile) - have_artwork |= load_layout_file(basename, *layoutfile); + have_artwork |= load_layout_file(basename.c_str(), *layoutfile); // if we're only loading this file, we know our final result if (!singlefile) - load_additional_layout_files(basename, have_artwork); + load_additional_layout_files(basename.c_str(), have_artwork); } void render_target::load_layout_files(util::xml::data_node const &rootnode, bool singlefile) { - bool have_artwork = false; + bool have_artwork = false; // if there's an explicit file, load that first - const char *basename = m_manager.machine().basename(); - have_artwork |= load_layout_file(m_manager.machine().root_device(), basename, rootnode); + const std::string &basename = m_manager.machine().basename(); + have_artwork |= load_layout_file(m_manager.machine().root_device(), rootnode, m_manager.machine().options().art_path(), basename.c_str()); // if we're only loading this file, we know our final result if (!singlefile) - load_additional_layout_files(basename, have_artwork); + load_additional_layout_files(basename.c_str(), have_artwork); } void render_target::load_additional_layout_files(const char *basename, bool have_artwork) { - bool have_default = false; - bool have_override = false; + using util::lang_translate; + + m_external_artwork = false; // if override_artwork defined, load that and skip artwork other than default - if (m_manager.machine().options().override_artwork()) + const char *const override_art = m_manager.machine().options().override_artwork(); + if (override_art && *override_art) { - if (load_layout_file(m_manager.machine().options().override_artwork(), m_manager.machine().options().override_artwork())) - have_override = true; - else if (load_layout_file(m_manager.machine().options().override_artwork(), "default")) - have_override = true; + if (load_layout_file(override_art, override_art)) + m_external_artwork = true; + else if (load_layout_file(override_art, "default")) + m_external_artwork = true; } const game_driver &system = m_manager.machine().system(); // Skip if override_artwork has found artwork - if (!have_override) + if (!m_external_artwork) { - // try to load a file based on the driver name if (!load_layout_file(basename, system.name)) - have_artwork |= load_layout_file(basename, "default"); + m_external_artwork |= load_layout_file(basename, "default"); else - have_artwork = true; + m_external_artwork = true; + + // try to load another file based on the parent driver name + int cloneof = driver_list::clone(system); + while (0 <= cloneof) + { + if (!m_external_artwork || driver_list::driver(cloneof).flags & MACHINE_IS_BIOS_ROOT) + { + if (!load_layout_file(driver_list::driver(cloneof).name, driver_list::driver(cloneof).name)) + m_external_artwork |= load_layout_file(driver_list::driver(cloneof).name, "default"); + else + m_external_artwork = true; + } + + // Check the parent of the parent to cover bios based artwork + const game_driver &parent(driver_list::driver(cloneof)); + cloneof = driver_list::clone(parent); + } // if a default view has been specified, use that as a fallback - if (system.default_layout != nullptr) + bool have_default = false; + if (system.default_layout) have_default |= load_layout_file(nullptr, *system.default_layout); m_manager.machine().config().apply_default_layouts( [this, &have_default] (device_t &dev, internal_layout const &layout) { have_default |= load_layout_file(nullptr, layout, &dev); }); - // try to load another file based on the parent driver name - int cloneof = driver_list::clone(system); - if (cloneof != -1) - { - if (!load_layout_file(driver_list::driver(cloneof).name, driver_list::driver(cloneof).name)) - have_artwork |= load_layout_file(driver_list::driver(cloneof).name, "default"); - else - have_artwork = true; - } + have_artwork |= m_external_artwork; - // Check the parent of the parent to cover bios based artwork - if (cloneof != -1) { - const game_driver &clone(driver_list::driver(cloneof)); - int cloneofclone = driver_list::clone(clone); - if (cloneofclone != -1 && cloneofclone != cloneof) + // Use fallback artwork if defined and no artwork has been found yet + if (!have_artwork) + { + const char *const fallback_art = m_manager.machine().options().fallback_artwork(); + if (fallback_art && *fallback_art) { - if (!load_layout_file(driver_list::driver(cloneofclone).name, driver_list::driver(cloneofclone).name)) - have_artwork |= load_layout_file(driver_list::driver(cloneofclone).name, "default"); + if (!load_layout_file(fallback_art, fallback_art)) + have_artwork |= load_layout_file(fallback_art, "default"); else have_artwork = true; } } - - // Use fallback artwork if defined and no artwork has been found yet - if (!have_artwork && m_manager.machine().options().fallback_artwork()) - { - if (!load_layout_file(m_manager.machine().options().fallback_artwork(), m_manager.machine().options().fallback_artwork())) - have_artwork |= load_layout_file(m_manager.machine().options().fallback_artwork(), "default"); - else - have_artwork = true; - } - } // local screen info to avoid repeated code @@ -1673,13 +1981,24 @@ void render_target::load_additional_layout_files(const char *basename, bool have bool m_rotated; std::pair<unsigned, unsigned> m_physical, m_native; }; - screen_device_iterator iter(m_manager.machine().root_device()); + screen_device_enumerator iter(m_manager.machine().root_device()); std::vector<screen_info> const screens(std::begin(iter), std::end(iter)); + // need this because views aren't fully set up yet + auto const nth_view = + [this] (unsigned n) -> layout_view * + { + for (layout_file &file : m_filelist) + for (layout_view &view : file.views()) + if (!(m_flags & RENDER_CREATE_NO_ART) || !view.has_art()) + if (n-- == 0) + return &view; + return nullptr; + }; if (screens.empty()) // ensure the fallback view for systems with no screens is loaded if necessary { - if (!view_by_index(0)) + if (!nth_view(0)) { load_layout_file(nullptr, layout_noscreens); if (m_filelist.empty()) @@ -1704,10 +2023,9 @@ void render_target::load_additional_layout_files(const char *basename, bool have throw emu_fatalerror("Couldn't create XML node??"); viewnode->set_attribute( "name", - util::xml::normalize_string( - util::string_format( - "Screen %1$u Standard (%2$u:%3$u)", - i, screens[i].physical_x(), screens[i].physical_y()).c_str())); + util::string_format( + _("view-name", "Screen %1$u Standard (%2$u:%3$u)"), + i, screens[i].physical_x(), screens[i].physical_y()).c_str()); util::xml::data_node *const screennode(viewnode->add_child("screen", nullptr)); if (!screennode) throw emu_fatalerror("Couldn't create XML node??"); @@ -1731,10 +2049,9 @@ void render_target::load_additional_layout_files(const char *basename, bool have throw emu_fatalerror("Couldn't create XML node??"); viewnode->set_attribute( "name", - util::xml::normalize_string( - util::string_format( - "Screen %1$u Pixel Aspect (%2$u:%3$u)", - i, screens[i].native_x(), screens[i].native_y()).c_str())); + util::string_format( + _("view-name", "Screen %1$u Pixel Aspect (%2$u:%3$u)"), + i, screens[i].native_x(), screens[i].native_y()).c_str()); util::xml::data_node *const screennode(viewnode->add_child("screen", nullptr)); if (!screennode) throw emu_fatalerror("Couldn't create XML node??"); @@ -1755,7 +2072,7 @@ void render_target::load_additional_layout_files(const char *basename, bool have util::xml::data_node *const viewnode(layoutnode->add_child("view", nullptr)); if (!viewnode) throw emu_fatalerror("Couldn't create XML node??"); - viewnode->set_attribute("name", "Cocktail"); + viewnode->set_attribute("name", _("view-name", "Cocktail")); util::xml::data_node *const mirrornode(viewnode->add_child("screen", nullptr)); if (!mirrornode) @@ -1792,23 +2109,19 @@ void render_target::load_additional_layout_files(const char *basename, bool have { need_tiles = true; int viewindex(0); - for (layout_view *view = view_by_index(viewindex); need_tiles && view; view = view_by_index(++viewindex)) + for (layout_view *view = nth_view(viewindex); need_tiles && view; view = nth_view(++viewindex)) { - render_screen_list const &viewscreens(view->screens()); - if (viewscreens.count() >= screens.size()) + bool screen_missing(false); + for (screen_device &screen : iter) { - bool screen_missing(false); - for (screen_device &screen : iter) + if (!view->has_screen(screen)) { - if (!viewscreens.contains(screen)) - { - screen_missing = true; - break; - } + screen_missing = true; + break; } - if (!screen_missing) - need_tiles = false; } + if (!screen_missing) + need_tiles = false; } } if (need_tiles) @@ -1852,7 +2165,7 @@ void render_target::load_additional_layout_files(const char *basename, bool have util::xml::data_node *viewnode = layoutnode->add_child("view", nullptr); if (!viewnode) throw emu_fatalerror("Couldn't create XML node??"); - viewnode->set_attribute("name", util::xml::normalize_string(title)); + viewnode->set_attribute("name", title); float ypos(0.0F); for (unsigned y = 0U; rows > y; ypos += heights[y] + spacing, ++y) { @@ -1879,10 +2192,10 @@ void render_target::load_additional_layout_files(const char *basename, bool have }; // generate linear views - generate_view("Left-to-Right", screens.size(), false, [] (unsigned x, unsigned y) { return x; }); - generate_view("Left-to-Right (Gapless)", screens.size(), true, [] (unsigned x, unsigned y) { return x; }); - generate_view("Top-to-Bottom", 1U, false, [] (unsigned x, unsigned y) { return y; }); - generate_view("Top-to-Bottom (Gapless)", 1U, true, [] (unsigned x, unsigned y) { return y; }); + generate_view(_("view-name", "Left-to-Right"), screens.size(), false, [] (unsigned x, unsigned y) { return x; }); + generate_view(_("view-name", "Left-to-Right (Gapless)"), screens.size(), true, [] (unsigned x, unsigned y) { return x; }); + generate_view(_("view-name", "Top-to-Bottom"), 1U, false, [] (unsigned x, unsigned y) { return y; }); + generate_view(_("view-name", "Top-to-Bottom (Gapless)"), 1U, true, [] (unsigned x, unsigned y) { return y; }); // generate fake cocktail view for systems with two screens if (screens.size() == 2U) @@ -1894,7 +2207,7 @@ void render_target::load_additional_layout_files(const char *basename, bool have util::xml::data_node *const viewnode(layoutnode->add_child("view", nullptr)); if (!viewnode) throw emu_fatalerror("Couldn't create XML node??"); - viewnode->set_attribute("name", "Cocktail"); + viewnode->set_attribute("name", _("view-name", "Cocktail")); util::xml::data_node *const mirrornode(viewnode->add_child("screen", nullptr)); if (!mirrornode) @@ -1933,7 +2246,7 @@ void render_target::load_additional_layout_files(const char *basename, bool have if (!remainder || (((majdim + 1) / 2) <= remainder)) { generate_view( - util::string_format("%1$u\xC3\x97%2$u Left-to-Right, Top-to-Bottom", majdim, mindim).c_str(), + util::string_format(_("view-name", u8"%1$u×%2$u Left-to-Right, Top-to-Bottom"), majdim, mindim).c_str(), majdim, false, [&screens, majdim] (unsigned x, unsigned y) @@ -1942,7 +2255,7 @@ void render_target::load_additional_layout_files(const char *basename, bool have return (screens.size() > i) ? int(i) : -1; }); generate_view( - util::string_format("%1$u\xC3\x97%2$u Left-to-Right, Top-to-Bottom (Gapless)", majdim, mindim).c_str(), + util::string_format(_("view-name", u8"%1$u×%2$u Left-to-Right, Top-to-Bottom (Gapless)"), majdim, mindim).c_str(), majdim, true, [&screens, majdim] (unsigned x, unsigned y) @@ -1951,7 +2264,7 @@ void render_target::load_additional_layout_files(const char *basename, bool have return (screens.size() > i) ? int(i) : -1; }); generate_view( - util::string_format("%1$u\xC3\x97%2$u Top-to-Bottom, Left-to-Right", mindim, majdim).c_str(), + util::string_format(_("view-name", u8"%1$u×%2$u Top-to-Bottom, Left-to-Right"), mindim, majdim).c_str(), mindim, false, [&screens, majdim] (unsigned x, unsigned y) @@ -1960,7 +2273,7 @@ void render_target::load_additional_layout_files(const char *basename, bool have return (screens.size() > i) ? int(i) : -1; }); generate_view( - util::string_format("%1$u\xC3\x97%2$u Top-to-Bottom, Left-to-Right (Gapless)", mindim, majdim).c_str(), + util::string_format(_("view-name", u8"%1$u×%2$u Top-to-Bottom, Left-to-Right (Gapless)"), mindim, majdim).c_str(), mindim, true, [&screens, majdim] (unsigned x, unsigned y) @@ -1973,7 +2286,7 @@ void render_target::load_additional_layout_files(const char *basename, bool have } // try to parse it - if (!load_layout_file(m_manager.machine().root_device(), nullptr, *root)) + if (!load_layout_file(m_manager.machine().root_device(), *root, m_manager.machine().options().art_path(), nullptr)) throw emu_fatalerror("Couldn't parse generated layout??"); } } @@ -1987,87 +2300,93 @@ void render_target::load_additional_layout_files(const char *basename, bool have bool render_target::load_layout_file(const char *dirname, const internal_layout &layout_data, device_t *device) { // +1 to ensure data is terminated for XML parser - auto tempout = make_unique_clear<u8 []>(layout_data.decompressed_size + 1); - - z_stream stream; - int zerr; - - /* initialize the stream */ - memset(&stream, 0, sizeof(stream)); - stream.next_out = tempout.get(); - stream.avail_out = layout_data.decompressed_size; - - - zerr = inflateInit(&stream); - if (zerr != Z_OK) - { - fatalerror("could not inflateInit"); - return false; - } - - /* decompress this chunk */ - stream.next_in = (unsigned char *)layout_data.data; - stream.avail_in = layout_data.compressed_size; - zerr = inflate(&stream, Z_NO_FLUSH); - - /* stop at the end of the stream */ - if (zerr == Z_STREAM_END) - { - // OK - } - else if (zerr != Z_OK) + std::unique_ptr<u8 []> tempout(new (std::nothrow) u8 [layout_data.decompressed_size + 1]); + auto inflater(util::zlib_read(util::ram_read(layout_data.data, layout_data.compressed_size), 8192)); + if (!tempout || !inflater) { - fatalerror("decompression error\n"); + osd_printf_error("render_target::load_layout_file: not enough memory to decompress layout\n"); return false; } - /* clean up */ - zerr = inflateEnd(&stream); - if (zerr != Z_OK) + size_t decompressed = 0; + do { - fatalerror("inflateEnd error\n"); - return false; + auto const [err, actual] = read( + *inflater, + &tempout[decompressed], + layout_data.decompressed_size - decompressed); + decompressed += actual; + if (err) + { + osd_printf_error( + "render_target::load_layout_file: error decompressing layout (%s:%d %s)\n", + err.category().name(), + err.value(), + err.message()); + return false; + } + if (!actual && (layout_data.decompressed_size < decompressed)) + { + osd_printf_warning( + "render_target::load_layout_file: expected %u bytes of decompressed data but only got %u\n", + layout_data.decompressed_size, + decompressed); + break; + } } + while (layout_data.decompressed_size > decompressed); + inflater.reset(); + tempout[decompressed] = 0U; util::xml::file::ptr rootnode(util::xml::file::string_read(reinterpret_cast<char const *>(tempout.get()), nullptr)); tempout.reset(); // if we didn't get a properly-formatted XML file, record a warning and exit - if (!load_layout_file(device ? *device : m_manager.machine().root_device(), dirname, *rootnode)) + if (!load_layout_file(device ? *device : m_manager.machine().root_device(), *rootnode, m_manager.machine().options().art_path(), dirname)) { - osd_printf_warning("Improperly formatted XML string, ignoring\n"); + osd_printf_warning("render_target::load_layout_file: Improperly formatted XML string, ignoring\n"); return false; } - else - { - return true; - } + + return true; } bool render_target::load_layout_file(const char *dirname, const char *filename) { // build the path and optionally prepend the directory - std::string fname = std::string(filename).append(".lay"); + std::string fname; if (dirname) - fname.insert(0, PATH_SEPARATOR).insert(0, dirname); - - // attempt to open the file; bail if we can't - emu_file layoutfile(m_manager.machine().options().art_path(), OPEN_FLAG_READ); - osd_file::error const filerr(layoutfile.open(fname.c_str())); - if (filerr != osd_file::error::NONE) - return false; + fname.append(dirname).append(PATH_SEPARATOR); + fname.append(filename).append(".lay"); - // read the file + // attempt to open matching files util::xml::parse_options parseopt; util::xml::parse_error parseerr; parseopt.error = &parseerr; - util::xml::file::ptr rootnode(util::xml::file::read(layoutfile, &parseopt)); - if (!rootnode) + emu_file layoutfile(m_manager.machine().options().art_path(), OPEN_FLAG_READ); + layoutfile.set_restrict_to_mediapath(1); + bool result(false); + for (std::error_condition filerr = layoutfile.open(fname); !filerr; filerr = layoutfile.open_next()) { - if (parseerr.error_message) + // read the file and parse as XML + util::xml::file::ptr const rootnode(util::xml::file::read(layoutfile, &parseopt)); + if (rootnode) + { + // extract directory name from location of layout file + std::string artdir(layoutfile.fullpath()); + auto const dirsep(std::find_if(artdir.rbegin(), artdir.rend(), &util::is_directory_separator)); + artdir.erase(dirsep.base(), artdir.end()); + + // record a warning if we didn't get a properly-formatted XML file + if (!load_layout_file(m_manager.machine().root_device(), *rootnode, nullptr, artdir.c_str())) + osd_printf_warning("Improperly formatted XML layout file '%s', ignoring\n", filename); + else + result = true; + } + else if (parseerr.error_message) { osd_printf_warning( - "Error parsing XML file '%s' at line %d column %d: %s, ignoring\n", + "Error parsing XML layout file '%s' at line %d column %d: %s, ignoring\n", filename, parseerr.error_line, parseerr.error_column, @@ -2075,37 +2394,25 @@ bool render_target::load_layout_file(const char *dirname, const char *filename) } else { - osd_printf_warning("Error parsing XML file '%s', ignorning\n", filename); + osd_printf_warning("Error parsing XML layout file '%s', ignorning\n", filename); } - return false; - } - - // if we didn't get a properly-formatted XML file, record a warning and exit - if (!load_layout_file(m_manager.machine().root_device(), dirname, *rootnode)) - { - osd_printf_warning("Improperly formatted XML file '%s', ignoring\n", filename); - return false; - } - else - { - return true; } + return result; } -bool render_target::load_layout_file(device_t &device, const char *dirname, util::xml::data_node const &rootnode) +bool render_target::load_layout_file(device_t &device, util::xml::data_node const &rootnode, const char *searchpath, const char *dirname) { // parse and catch any errors try { - m_filelist.emplace_back(device, rootnode, dirname); + m_filelist.emplace_back(device, rootnode, searchpath, dirname); } - catch (emu_fatalerror &) + catch (emu_fatalerror const &err) { + osd_printf_warning("%s\n", err.what()); return false; } - emulator_info::layout_file_cb(rootnode); - return true; } @@ -2126,7 +2433,7 @@ void render_target::add_container_primitives(render_primitive_list &list, const cliprect.y0 = xform.yoffs; cliprect.x1 = xform.xoffs + xform.xscale; cliprect.y1 = xform.yoffs + xform.yscale; - sect_render_bounds(cliprect, m_bounds); + cliprect &= m_bounds; float root_xoffs = root_xform.xoffs + fabsf(root_xform.xscale - xform.xscale) * 0.5f; float root_yoffs = root_xform.yoffs + fabsf(root_xform.yscale - xform.yscale) * 0.5f; @@ -2136,7 +2443,7 @@ void render_target::add_container_primitives(render_primitive_list &list, const root_cliprect.y0 = root_yoffs; root_cliprect.x1 = root_xoffs + root_xform.xscale; root_cliprect.y1 = root_yoffs + root_xform.yscale; - sect_render_bounds(root_cliprect, m_bounds); + root_cliprect &= m_bounds; // compute the container transform object_transform container_xform; @@ -2236,11 +2543,11 @@ void render_target::add_container_primitives(render_primitive_list &list, const // clip the primitive if (!m_transform_container && PRIMFLAG_GET_VECTOR(curitem.flags())) { - clipped = render_clip_line(&prim->bounds, &root_cliprect); + clipped = render_clip_line(prim->bounds, root_cliprect); } else { - clipped = render_clip_line(&prim->bounds, &cliprect); + clipped = render_clip_line(prim->bounds, cliprect); } break; @@ -2267,13 +2574,13 @@ void render_target::add_container_primitives(render_primitive_list &list, const curitem.texture()->get_scaled(width, height, prim->texture, list, curitem.flags()); // set the palette - prim->texture.palette = curitem.texture()->get_adjusted_palette(container); + prim->texture.palette = curitem.texture()->get_adjusted_palette(container, prim->texture.palette_length); // determine UV coordinates prim->texcoords = oriented_texcoords[finalorient]; // apply clipping - clipped = render_clip_quad(&prim->bounds, &cliprect, &prim->texcoords); + clipped = render_clip_quad(prim->bounds, cliprect, &prim->texcoords); // apply the final orientation from the quad flags and then build up the final flags prim->flags |= (curitem.flags() & ~(PRIMFLAG_TEXORIENT_MASK | PRIMFLAG_BLENDMODE_MASK | PRIMFLAG_TEXFORMAT_MASK)) @@ -2333,7 +2640,7 @@ void render_target::add_container_primitives(render_primitive_list &list, const prim->texcoords = oriented_texcoords[finalorient]; // apply clipping - clipped = render_clip_quad(&prim->bounds, &cliprect, &prim->texcoords); + clipped = render_clip_quad(prim->bounds, cliprect, &prim->texcoords); // apply the final orientation from the quad flags and then build up the final flags prim->flags |= (curitem.flags() & ~(PRIMFLAG_TEXORIENT_MASK | PRIMFLAG_BLENDMODE_MASK | PRIMFLAG_TEXFORMAT_MASK)) @@ -2349,7 +2656,7 @@ void render_target::add_container_primitives(render_primitive_list &list, const | PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA); // apply clipping - clipped = render_clip_quad(&prim->bounds, &cliprect, nullptr); + clipped = render_clip_quad(prim->bounds, cliprect, nullptr); } } break; @@ -2366,7 +2673,7 @@ void render_target::add_container_primitives(render_primitive_list &list, const // allocate a primitive render_primitive *prim = list.alloc(render_primitive::QUAD); - set_render_bounds_wh(prim->bounds, xform.xoffs, xform.yoffs, xform.xscale, xform.yscale); + prim->bounds.set_wh(xform.xoffs, xform.yoffs, xform.xscale, xform.yscale); prim->full_bounds = prim->bounds; prim->color = container_xform.color; width = render_round_nearest(prim->bounds.x1) - render_round_nearest(prim->bounds.x0); @@ -2395,51 +2702,86 @@ void render_target::add_container_primitives(render_primitive_list &list, const // for an element in the current state //------------------------------------------------- -void render_target::add_element_primitives(render_primitive_list &list, const object_transform &xform, layout_element &element, int state, int blendmode) +void render_target::add_element_primitives(render_primitive_list &list, const object_transform &xform, layout_view_item &item) { - // if we're out of range, bail - if (state > element.maxstate()) - return; - if (state < 0) - state = 0; + layout_element &element(*item.element()); + int const blendmode(item.blend_mode()); + + // limit state range to non-negative values + int const state((std::max)(item.element_state(), 0)); // get a pointer to the relevant texture render_texture *texture = element.state_texture(state); - if (texture != nullptr) + if (texture) { render_primitive *prim = list.alloc(render_primitive::QUAD); // configure the basics prim->color = xform.color; - prim->flags = PRIMFLAG_TEXORIENT(xform.orientation) | PRIMFLAG_BLENDMODE(blendmode) | PRIMFLAG_TEXFORMAT(texture->format()); + prim->flags = + PRIMFLAG_TEXORIENT(xform.orientation) | + PRIMFLAG_TEXFORMAT(texture->format()) | + PRIMFLAG_BLENDMODE(blendmode) | + PRIMFLAG_TEXWRAP((item.scroll_wrap_x() || item.scroll_wrap_y()) ? 1 : 0); // compute the bounds - s32 width = render_round_nearest(xform.xscale); - s32 height = render_round_nearest(xform.yscale); - set_render_bounds_wh(prim->bounds, render_round_nearest(xform.xoffs), render_round_nearest(xform.yoffs), (float) width, (float) height); + float const primwidth(render_round_nearest(xform.xscale)); + float const primheight(render_round_nearest(xform.yscale)); + prim->bounds.set_wh(render_round_nearest(xform.xoffs), render_round_nearest(xform.yoffs), primwidth, primheight); prim->full_bounds = prim->bounds; - if (xform.orientation & ORIENTATION_SWAP_XY) - std::swap(width, height); - width = std::min(width, m_maxtexwidth); - height = std::min(height, m_maxtexheight); // get the scaled texture and append it - - texture->get_scaled(width, height, prim->texture, list, prim->flags); + float const xsize(item.scroll_size_x()); + float const ysize(item.scroll_size_y()); + s32 texwidth = render_round_nearest(((xform.orientation & ORIENTATION_SWAP_XY) ? primheight : primwidth) / xsize); + s32 texheight = render_round_nearest(((xform.orientation & ORIENTATION_SWAP_XY) ? primwidth : primheight) / ysize); + texwidth = (std::min)(texwidth, m_maxtexwidth); + texheight = (std::min)(texheight, m_maxtexheight); + texture->get_scaled(texwidth, texheight, prim->texture, list, prim->flags); // compute the clip rect - render_bounds cliprect; - cliprect.x0 = render_round_nearest(xform.xoffs); - cliprect.y0 = render_round_nearest(xform.yoffs); - cliprect.x1 = render_round_nearest(xform.xoffs + xform.xscale); - cliprect.y1 = render_round_nearest(xform.yoffs + xform.yscale); - sect_render_bounds(cliprect, m_bounds); + render_bounds cliprect = prim->bounds & m_bounds; // determine UV coordinates and apply clipping - prim->texcoords = oriented_texcoords[xform.orientation]; - bool clipped = render_clip_quad(&prim->bounds, &cliprect, &prim->texcoords); + float const xwindow((xform.orientation & ORIENTATION_SWAP_XY) ? primheight : primwidth); + float const ywindow((xform.orientation & ORIENTATION_SWAP_XY) ? primwidth : primheight); + float const xrange(float(texwidth) - (item.scroll_wrap_x() ? 0.0f : xwindow)); + float const yrange(float(texheight) - (item.scroll_wrap_y() ? 0.0f : ywindow)); + float const xoffset(render_round_nearest(item.scroll_pos_x() * xrange) / float(texwidth)); + float const yoffset(render_round_nearest(item.scroll_pos_y() * yrange) / float(texheight)); + float const xend(xoffset + (xwindow / float(texwidth))); + float const yend(yoffset + (ywindow / float(texheight))); + switch (xform.orientation) + { + default: + case 0: + prim->texcoords = render_quad_texuv{ { xoffset, yoffset }, { xend, yoffset }, { xoffset, yend }, { xend, yend } }; + break; + case ORIENTATION_FLIP_X: + prim->texcoords = render_quad_texuv{ { xend, yoffset }, { xoffset, yoffset }, { xend, yend }, { xoffset, yend } }; + break; + case ORIENTATION_FLIP_Y: + prim->texcoords = render_quad_texuv{ { xoffset, yend }, { xend, yend }, { xoffset, yoffset }, { xend, yoffset } }; + break; + case ORIENTATION_FLIP_X | ORIENTATION_FLIP_Y: + prim->texcoords = render_quad_texuv{ { xend, yend }, { xoffset, yend }, { xend, yoffset }, { xoffset, yoffset } }; + break; + case ORIENTATION_SWAP_XY: + prim->texcoords = render_quad_texuv{ { xoffset, yoffset }, { xoffset, yend }, { xend, yoffset }, { xend, yend } }; + break; + case ORIENTATION_SWAP_XY | ORIENTATION_FLIP_X: + prim->texcoords = render_quad_texuv{ { xoffset, yend }, { xoffset, yoffset }, { xend, yend }, { xend, yoffset } }; + break; + case ORIENTATION_SWAP_XY | ORIENTATION_FLIP_Y: + prim->texcoords = render_quad_texuv{ { xend, yoffset }, { xend, yend }, { xoffset, yoffset }, { xoffset, yend } }; + break; + case ORIENTATION_SWAP_XY | ORIENTATION_FLIP_X | ORIENTATION_FLIP_Y: + prim->texcoords = render_quad_texuv{ { xend, yend }, { xend, yoffset }, { xoffset, yend }, { xoffset, yoffset } }; + break; + } // add to the list or free if we're clipped out + bool const clipped = render_clip_quad(prim->bounds, cliprect, &prim->texcoords); list.append_or_return(*prim, clipped); } } @@ -2450,7 +2792,7 @@ void render_target::add_element_primitives(render_primitive_list &list, const ob // mapping points //------------------------------------------------- -bool render_target::map_point_internal(s32 target_x, s32 target_y, render_container *container, float &mapped_x, float &mapped_y, ioport_port *&mapped_input_port, ioport_value &mapped_input_mask) +std::pair<float, float> render_target::map_point_internal(s32 target_x, s32 target_y) { // compute the visible width/height s32 viswidth, visheight; @@ -2458,62 +2800,33 @@ bool render_target::map_point_internal(s32 target_x, s32 target_y, render_contai // create a root transform for the target object_transform root_xform; - root_xform.xoffs = (float)(m_width - viswidth) / 2; - root_xform.yoffs = (float)(m_height - visheight) / 2; - - // default to point not mapped - mapped_x = -1.0; - mapped_y = -1.0; - mapped_input_port = nullptr; - mapped_input_mask = 0; + root_xform.xoffs = float(m_width - viswidth) / 2; + root_xform.yoffs = float(m_height - visheight) / 2; // convert target coordinates to float - float target_fx = (float)(target_x - root_xform.xoffs) / viswidth; - float target_fy = (float)(target_y - root_xform.yoffs) / visheight; - if (m_manager.machine().ui().is_menu_active()) - { - target_fx = (float)target_x / m_width; - target_fy = (float)target_y / m_height; - } - // explicitly check for the UI container - if (container != nullptr && container == &m_manager.ui_container()) - { - // this hit test went against the UI container - if (target_fx >= 0.0f && target_fx < 1.0f && target_fy >= 0.0f && target_fy < 1.0f) - { - // this point was successfully mapped - mapped_x = (float)target_x / m_width; - mapped_y = (float)target_y / m_height; - return true; - } - return false; - } - - // iterate over items in the view - for (layout_view::item &item : m_curview->items()) - { - bool checkit; + if (!m_manager.machine().ui().is_menu_active()) + return std::make_pair(float(target_x - root_xform.xoffs) / viswidth, float(target_y - root_xform.yoffs) / visheight); + else + return std::make_pair(float(target_x) / m_width, float(target_y) / m_height); +} - // if we're looking for a particular container, verify that we have the right one - if (container != nullptr) - checkit = (item.screen() != nullptr && &item.screen()->container() == container); - // otherwise, assume we're looking for an input - else - checkit = item.has_input(); - - // this target is worth looking at; now check the point - if (checkit && target_fx >= item.bounds().x0 && target_fx < item.bounds().x1 && target_fy >= item.bounds().y0 && target_fy < item.bounds().y1) - { - // point successfully mapped - mapped_x = (target_fx - item.bounds().x0) / (item.bounds().x1 - item.bounds().x0); - mapped_y = (target_fy - item.bounds().y0) / (item.bounds().y1 - item.bounds().y0); - mapped_input_port = item.input_tag_and_mask(mapped_input_mask); - return true; - } - } +//------------------------------------------------- +// map_point_layout - map point from screen +// coordinates to layout coordinates +//------------------------------------------------- - return false; +std::pair<float, float> render_target::map_point_layout(s32 target_x, s32 target_y) +{ + using std::swap; + std::pair<float, float> result(map_point_internal(target_x, target_y)); + if (m_orientation & ORIENTATION_FLIP_X) + result.first = 1.0f - result.first; + if (m_orientation & ORIENTATION_FLIP_Y) + result.second = 1.0f - result.second; + if (m_orientation & ORIENTATION_SWAP_XY) + swap(result.first, result.second); + return result; } @@ -2522,15 +2835,9 @@ bool render_target::map_point_internal(s32 target_x, s32 target_y, render_contai // view, or nullptr if it doesn't exist //------------------------------------------------- -layout_view *render_target::view_by_index(int index) +layout_view *render_target::view_by_index(unsigned index) { - // scan the list of views within each layout, skipping those that don't apply - for (layout_file &file : m_filelist) - for (layout_view &view : file.views()) - if (!(m_flags & RENDER_CREATE_NO_ART) || !view.has_art()) - if (index-- == 0) - return &view; - return nullptr; + return (m_views.size() > index) ? &m_views[index].first : nullptr; } @@ -2541,18 +2848,12 @@ layout_view *render_target::view_by_index(int index) int render_target::view_index(layout_view &targetview) const { - // find the first named match - int index = 0; - - // scan the list of views within each layout, skipping those that don't apply - for (layout_file const &file : m_filelist) - for (layout_view const &view : file.views()) - if (!(m_flags & RENDER_CREATE_NO_ART) || !view.has_art()) - { - if (&targetview == &view) - return index; - index++; - } + // return index of view, or zero if not found + for (int index = 0; m_views.size() > index; ++index) + { + if (&m_views[index].first == &targetview) + return index; + } return 0; } @@ -2561,53 +2862,99 @@ int render_target::view_index(layout_view &targetview) const // config_load - process config information //------------------------------------------------- -void render_target::config_load(util::xml::data_node const &targetnode) +void render_target::config_load(util::xml::data_node const *targetnode) { - int tmpint; + // remember the view selected via command line and INI options + if (!m_base_view) + m_base_view = ¤t_view(); + + // bail if no configuration + if (!targetnode) + return; + + // TODO: consider option priority - command line should take precedence over CFG + // not practical at the moment because view selection options are in the OSD layer // find the view - const char *viewname = targetnode.get_attribute_string("view", nullptr); - if (viewname != nullptr) - for (int viewnum = 0; viewnum < 1000; viewnum++) + const char *viewname = targetnode->get_attribute_string("view", nullptr); + if (viewname) + { + for (unsigned viewnum = 0; m_views.size() > viewnum; viewnum++) { - const char *testname = view_name(viewnum); - if (testname == nullptr) - break; - if (!strcmp(viewname, testname)) + if (!strcmp(viewname, view_name(viewnum))) { set_view(viewnum); break; } } + } // modify the artwork config - tmpint = targetnode.get_attribute_int("zoom", -1); - if (tmpint == 0 || tmpint == 1) - set_zoom_to_screen(tmpint); + int const zoom = targetnode->get_attribute_int("zoom", -1); + if (zoom == 0 || zoom == 1) + set_zoom_to_screen(zoom); // apply orientation - tmpint = targetnode.get_attribute_int("rotate", -1); - if (tmpint != -1) - { - if (tmpint == 90) - tmpint = ROT90; - else if (tmpint == 180) - tmpint = ROT180; - else if (tmpint == 270) - tmpint = ROT270; + int rotate = targetnode->get_attribute_int("rotate", -1); + if (rotate != -1) + { + if (rotate == 90) + rotate = ROT90; + else if (rotate == 180) + rotate = ROT180; + else if (rotate == 270) + rotate = ROT270; else - tmpint = ROT0; - set_orientation(orientation_add(tmpint, orientation())); + rotate = ROT0; + set_orientation(orientation_add(rotate, orientation())); // apply the opposite orientation to the UI - if (is_ui_target()) + if (m_ui_container) { - render_container::user_settings settings; - render_container &ui_container = m_manager.ui_container(); + render_container::user_settings settings = m_ui_container->get_user_settings(); + settings.m_orientation = orientation_add(orientation_reverse(rotate), settings.m_orientation); + m_ui_container->set_user_settings(settings); + } + } + + // apply per-view settings + for (util::xml::data_node const *viewnode = targetnode->get_child("view"); viewnode; viewnode = viewnode->get_next_sibling("view")) + { + char const *const viewname = viewnode->get_attribute_string("name", nullptr); + if (!viewname) + continue; + + auto const view = std::find_if(m_views.begin(), m_views.end(), [viewname] (auto const &x) { return x.first.name() == viewname; }); + if (m_views.end() == view) + continue; - ui_container.get_user_settings(settings); - settings.m_orientation = orientation_add(orientation_reverse(tmpint), settings.m_orientation); - ui_container.set_user_settings(settings); + for (util::xml::data_node const *vistogglenode = viewnode->get_child("collection"); vistogglenode; vistogglenode = vistogglenode->get_next_sibling("collection")) + { + char const *const vistogglename = vistogglenode->get_attribute_string("name", nullptr); + if (!vistogglename) + continue; + + auto const &vistoggles = view->first.visibility_toggles(); + auto const vistoggle = std::find_if(vistoggles.begin(), vistoggles.end(), [vistogglename] (auto const &x) { return x.name() == vistogglename; }); + if (vistoggles.end() == vistoggle) + continue; + + int const enable = vistogglenode->get_attribute_int("visible", -1); + if (0 <= enable) + { + if (enable) + view->second |= u32(1) << std::distance(vistoggles.begin(), vistoggle); + else + view->second &= ~(u32(1) << std::distance(vistoggles.begin(), vistoggle)); + } + } + + if (¤t_view() == &view->first) + { + current_view().recompute(visibility_mask(), m_layerconfig.zoom_to_screen()); + current_view().preload(); + m_clickable_items.clear(); + m_clickable_items.resize(current_view().interactive_items().size()); } } } @@ -2626,9 +2973,9 @@ bool render_target::config_save(util::xml::data_node &targetnode) targetnode.set_attribute_int("index", index()); // output the view - if (m_curview != m_base_view) + if (¤t_view() != m_base_view) { - targetnode.set_attribute("view", m_curview->name().c_str()); + targetnode.set_attribute("view", current_view().name().c_str()); changed = true; } @@ -2654,6 +3001,33 @@ bool render_target::config_save(util::xml::data_node &targetnode) changed = true; } + // output layer configuration + for (auto const &view : m_views) + { + u32 const defvismask = view.first.default_visibility_mask(); + if (defvismask != view.second) + { + util::xml::data_node *viewnode = nullptr; + unsigned i = 0; + for (layout_view::visibility_toggle const &toggle : view.first.visibility_toggles()) + { + if (BIT(defvismask, i) != BIT(view.second, i)) + { + if (!viewnode) + { + viewnode = targetnode.add_child("view", nullptr); + viewnode->set_attribute("name", view.first.name().c_str()); + } + util::xml::data_node *const vistogglenode = viewnode->add_child("collection", nullptr); + vistogglenode->set_attribute("name", toggle.name().c_str()); + vistogglenode->set_attribute_int("visible", BIT(view.second, i)); + changed = true; + } + ++i; + } + } + } + return changed; } @@ -2843,9 +3217,9 @@ void render_target::add_clear_extents(render_primitive_list &list) if (x1 - x0 > 0) { render_primitive *prim = list.alloc(render_primitive::QUAD); - set_render_bounds_xy(prim->bounds, (float)x0, (float)y0, (float)x1, (float)y1); + prim->bounds.set_xy(float(x0), float(y0), float(x1), float(y1)); prim->full_bounds = prim->bounds; - set_render_color(&prim->color, 1.0f, 0.0f, 0.0f, 0.0f); + prim->color.set(1.0f, 0.0f, 0.0f, 0.0f); prim->texture.base = nullptr; prim->flags = PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA); clearlist.append(*prim); @@ -2899,7 +3273,7 @@ void render_target::add_clear_and_optimize_primitive_list(render_primitive_list if (PRIMFLAG_GET_BLENDMODE(prim.flags) == BLENDMODE_RGB_MULTIPLY) { // RGB multiply will multiply against 0, leaving nothing - set_render_color(&prim.color, 1.0f, 0.0f, 0.0f, 0.0f); + prim.color.set(1.0f, 0.0f, 0.0f, 0.0f); prim.texture.base = nullptr; prim.flags = (prim.flags & ~PRIMFLAG_BLENDMODE_MASK) | PRIMFLAG_BLENDMODE(BLENDMODE_NONE); } @@ -2938,18 +3312,20 @@ done: //------------------------------------------------- render_manager::render_manager(running_machine &machine) - : m_machine(machine), - m_ui_target(nullptr), - m_live_textures(0), - m_texture_id(0), - m_ui_container(global_alloc(render_container(*this))) + : m_machine(machine) + , m_ui_target(nullptr) + , m_live_textures(0) + , m_texture_id(0) { // register callbacks - machine.configuration().config_register("video", config_load_delegate(&render_manager::config_load, this), config_save_delegate(&render_manager::config_save, this)); + machine.configuration().config_register( + "video", + configuration_manager::load_delegate(&render_manager::config_load, this), + configuration_manager::save_delegate(&render_manager::config_save, this)); // create one container per screen - for (screen_device &screen : screen_device_iterator(machine.root_device())) - screen.set_container(*container_alloc(&screen)); + for (screen_device &screen : screen_device_enumerator(machine.root_device())) + screen.set_container(m_screen_container_list.emplace_back(*this, &screen)); } @@ -2960,8 +3336,8 @@ render_manager::render_manager(running_machine &machine) render_manager::~render_manager() { // free all the containers since they may own textures - container_free(m_ui_container); - m_screen_container_list.reset(); + m_ui_containers.clear(); + m_screen_container_list.clear(); // better not be any outstanding textures when we die assert(m_live_textures == 0); @@ -2975,9 +3351,15 @@ render_manager::~render_manager() bool render_manager::is_live(screen_device &screen) const { // iterate over all live targets and or together their screen masks - for (render_target &target : m_targetlist) - if (!target.hidden() && target.view_screens(target.view()).contains(screen)) - return true; + for (render_target const &target : m_targetlist) + { + if (!target.hidden()) + { + layout_view const *view = &target.current_view(); + if (view->has_visible_screen(screen)) + return true; + } + } return false; } @@ -3010,12 +3392,14 @@ float render_manager::max_update_rate() const render_target *render_manager::target_alloc(const internal_layout *layoutfile, u32 flags) { - return &m_targetlist.append(*global_alloc(render_target(*this, layoutfile, flags))); + render_container *const ui = (flags & RENDER_CREATE_HIDDEN) ? nullptr : &m_ui_containers.emplace_back(*this); + return &m_targetlist.append(*new render_target(*this, ui, layoutfile, flags)); } render_target *render_manager::target_alloc(util::xml::data_node const &layout, u32 flags) { - return &m_targetlist.append(*global_alloc(render_target(*this, layout, flags))); + render_container *const ui = (flags & RENDER_CREATE_HIDDEN) ? nullptr : &m_ui_containers.emplace_back(*this); + return &m_targetlist.append(*new render_target(*this, ui, layout, flags)); } @@ -3052,35 +3436,56 @@ render_target *render_manager::target_by_index(int index) const float render_manager::ui_aspect(render_container *rc) { - int orient; + // work out if this is a UI container + render_target *target = nullptr; + if (!rc) + { + target = &ui_target(); + rc = target->ui_container(); + assert(rc); + } + else + { + for (render_target &t : m_targetlist) + { + if (t.ui_container() == rc) + { + target = &t; + break; + } + } + } + float aspect; - if (rc == m_ui_container || rc == nullptr) { - // ui container, aggregated multi-screen target + if (target) + { + // UI container, aggregated multi-screen target - orient = orientation_add(m_ui_target->orientation(), m_ui_container->orientation()); // based on the orientation of the target, compute height/width or width/height + int const orient = orientation_add(target->orientation(), rc->orientation()); if (!(orient & ORIENTATION_SWAP_XY)) - aspect = (float)m_ui_target->height() / (float)m_ui_target->width(); + aspect = float(target->height()) / float(target->width()); else - aspect = (float)m_ui_target->width() / (float)m_ui_target->height(); + aspect = float(target->width()) / float(target->height()); // if we have a valid pixel aspect, apply that and return - if (m_ui_target->pixel_aspect() != 0.0f) + if (target->pixel_aspect() != 0.0f) { - float pixel_aspect = m_ui_target->pixel_aspect(); + float pixel_aspect = target->pixel_aspect(); if (orient & ORIENTATION_SWAP_XY) pixel_aspect = 1.0f / pixel_aspect; return aspect /= pixel_aspect; } - - } else { + } + else + { // single screen container - orient = rc->orientation(); // based on the orientation of the target, compute height/width or width/height + int const orient = rc->orientation(); if (!(orient & ORIENTATION_SWAP_XY)) aspect = (float)rc->screen()->visible_area().height() / (float)rc->screen()->visible_area().width(); else @@ -3088,12 +3493,7 @@ float render_manager::ui_aspect(render_container *rc) } // clamp for extreme proportions - if (aspect < 0.66f) - aspect = 0.66f; - if (aspect > 1.5f) - aspect = 1.5f; - - return aspect; + return std::clamp(aspect, 0.66f, 1.5f); } @@ -3132,19 +3532,9 @@ void render_manager::texture_free(render_texture *texture) // font_alloc - allocate a new font instance //------------------------------------------------- -render_font *render_manager::font_alloc(const char *filename) -{ - return global_alloc(render_font(*this, filename)); -} - - -//------------------------------------------------- -// font_free - release a font instance -//------------------------------------------------- - -void render_manager::font_free(render_font *font) +std::unique_ptr<render_font> render_manager::font_alloc(const char *filename) { - global_free(font); + return std::unique_ptr<render_font>(new render_font(*this, filename)); } @@ -3177,83 +3567,72 @@ void render_manager::resolve_tags() //------------------------------------------------- -// container_alloc - allocate a new container -//------------------------------------------------- - -render_container *render_manager::container_alloc(screen_device *screen) -{ - auto container = global_alloc(render_container(*this, screen)); - if (screen != nullptr) - m_screen_container_list.append(*container); - return container; -} - - -//------------------------------------------------- -// container_free - release a container -//------------------------------------------------- - -void render_manager::container_free(render_container *container) -{ - m_screen_container_list.remove(*container); -} - - -//------------------------------------------------- // config_load - read and apply data from the // configuration file //------------------------------------------------- -void render_manager::config_load(config_type cfg_type, util::xml::data_node const *parentnode) +void render_manager::config_load(config_type cfg_type, config_level cfg_level, util::xml::data_node const *parentnode) { - // we only care about game files - if (cfg_type != config_type::GAME) + // we only care about system-specific configuration with matching nodes + if (cfg_type == config_type::DEFAULT) + { + // let the targets stabilise themselves + for (render_target &target : m_targetlist) + { + if (!target.hidden()) + target.config_load(nullptr); + } return; - - // might not have any data - if (parentnode == nullptr) + } + else if ((cfg_type != config_type::SYSTEM) || !parentnode) + { return; + } // check the UI target util::xml::data_node const *const uinode = parentnode->get_child("interface"); - if (uinode != nullptr) + if (uinode) { - render_target *target = target_by_index(uinode->get_attribute_int("target", 0)); - if (target != nullptr) + render_target *const target = target_by_index(uinode->get_attribute_int("target", 0)); + if (target) set_ui_target(*target); } // iterate over target nodes for (util::xml::data_node const *targetnode = parentnode->get_child("target"); targetnode; targetnode = targetnode->get_next_sibling("target")) { - render_target *target = target_by_index(targetnode->get_attribute_int("index", -1)); - if (target != nullptr) - target->config_load(*targetnode); + render_target *const target = target_by_index(targetnode->get_attribute_int("index", -1)); + if (target && !target->hidden()) + target->config_load(targetnode); } // iterate over screen nodes for (util::xml::data_node const *screennode = parentnode->get_child("screen"); screennode; screennode = screennode->get_next_sibling("screen")) { - int index = screennode->get_attribute_int("index", -1); - render_container *container = m_screen_container_list.find(index); - render_container::user_settings settings; - - // fetch current settings - container->get_user_settings(settings); - - // fetch color controls - settings.m_brightness = screennode->get_attribute_float("brightness", settings.m_brightness); - settings.m_contrast = screennode->get_attribute_float("contrast", settings.m_contrast); - settings.m_gamma = screennode->get_attribute_float("gamma", settings.m_gamma); - - // fetch positioning controls - settings.m_xoffset = screennode->get_attribute_float("hoffset", settings.m_xoffset); - settings.m_xscale = screennode->get_attribute_float("hstretch", settings.m_xscale); - settings.m_yoffset = screennode->get_attribute_float("voffset", settings.m_yoffset); - settings.m_yscale = screennode->get_attribute_float("vstretch", settings.m_yscale); + int const index = screennode->get_attribute_int("index", -1); + render_container *container = nullptr; + if (index >= 0 && index < m_screen_container_list.size()) + container = &*std::next(m_screen_container_list.begin(), index); - // set the new values - container->set_user_settings(settings); + if (container != nullptr) + { + // fetch current settings + render_container::user_settings settings = container->get_user_settings(); + + // fetch color controls + settings.m_brightness = screennode->get_attribute_float("brightness", settings.m_brightness); + settings.m_contrast = screennode->get_attribute_float("contrast", settings.m_contrast); + settings.m_gamma = screennode->get_attribute_float("gamma", settings.m_gamma); + + // fetch positioning controls + settings.m_xoffset = screennode->get_attribute_float("hoffset", settings.m_xoffset); + settings.m_xscale = screennode->get_attribute_float("hstretch", settings.m_xscale); + settings.m_yoffset = screennode->get_attribute_float("voffset", settings.m_yoffset); + settings.m_yscale = screennode->get_attribute_float("vstretch", settings.m_yscale); + + // set the new values + container->set_user_settings(settings); + } } } @@ -3265,8 +3644,8 @@ void render_manager::config_load(config_type cfg_type, util::xml::data_node cons void render_manager::config_save(config_type cfg_type, util::xml::data_node *parentnode) { - // we only care about game files - if (cfg_type != config_type::GAME) + // we only save system-specific configuration + if (cfg_type != config_type::SYSTEM) return; // write out the interface target @@ -3279,22 +3658,26 @@ void render_manager::config_save(config_type cfg_type, util::xml::data_node *par } // iterate over targets - for (int targetnum = 0; targetnum < 1000; targetnum++) + for (int targetnum = 0; ; ++targetnum) { // get this target and break when we fail render_target *target = target_by_index(targetnum); - if (target == nullptr) + if (!target) + { break; - - // create a node - util::xml::data_node *const targetnode = parentnode->add_child("target", nullptr); - if (targetnode != nullptr && !target->config_save(*targetnode)) - targetnode->delete_node(); + } + else if (!target->hidden()) + { + // create a node + util::xml::data_node *const targetnode = parentnode->add_child("target", nullptr); + if (targetnode && !target->config_save(*targetnode)) + targetnode->delete_node(); + } } // iterate over screen containers int scrnum = 0; - for (render_container *container = m_screen_container_list.first(); container != nullptr; container = container->next(), scrnum++) + for (render_container &container : m_screen_container_list) { // create a node util::xml::data_node *const screennode = parentnode->add_child("screen", nullptr); @@ -3305,8 +3688,7 @@ void render_manager::config_save(config_type cfg_type, util::xml::data_node *par // output the basics screennode->set_attribute_int("index", scrnum); - render_container::user_settings settings; - container->get_user_settings(settings); + render_container::user_settings const settings = container.get_user_settings(); // output the color controls if (settings.m_brightness != machine().options().brightness()) @@ -3356,5 +3738,7 @@ void render_manager::config_save(config_type cfg_type, util::xml::data_node *par if (!changed) screennode->delete_node(); } + + scrnum++; } } |