// license:BSD-3-Clause// copyright-holders:Aaron Giles/*************************************************************************** render.c Core rendering system.**************************************************************************** Windows-specific to-do: * no fallback if we run out of video memory Longer-term to do: (once old renderer is gone) * make vector updates asynchronous**************************************************************************** Overview of objects: render_target -- This represents a final rendering target. It is specified using integer width/height values, can have non-square pixels, and you can specify its rotation. It is what really determines the final rendering details. The OSD layer creates one or more of these to encapsulate the rendering process. Each render_target holds a list of layout_files that it can use for drawing. When rendering, it makes use of both layout_files and render_containers. render_container -- Containers are the top of a hierarchy that is not directly related to the objects above. Containers hold high level primitives that are generated at runtime by the video system. They are used currently for each screen and the user interface. These high-level primitives are broken down into low-level primitives at render time.***************************************************************************/#include"emu.h"#include"emuopts.h"#include"render.h"#include"rendfont.h"#include"rendlay.h"#include"rendutil.h"#include"config.h"#include"drivenum.h"#include"xmlfile.h"#include"ui/uimain.h"#include<zlib.h>#include<algorithm>#include<functional>//**************************************************************************// CONSTANTS//**************************************************************************#define INTERNAL_FLAG_CHAR 0x00000001enum{COMPONENT_TYPE_IMAGE=0,COMPONENT_TYPE_RECT,COMPONENT_TYPE_DISK,COMPONENT_TYPE_MAX};enum{CONTAINER_ITEM_LINE=0,CONTAINER_ITEM_QUAD,CONTAINER_ITEM_MAX};//**************************************************************************// TYPE DEFINITIONS//**************************************************************************// an object_transform is used to track transformations when building an object liststructrender_target::object_transform{floatxoffs,yoffs;// offset transformsfloatxscale,yscale;// scale transformsrender_colorcolor;// color transformintorientation;// orientation transformboolno_center;// center the container?};//**************************************************************************// GLOBAL VARIABLES//**************************************************************************// precomputed UV coordinates for various orientationsstaticconstrender_quad_texuvoriented_texcoords[8]={{{0,0},{1,0},{0,1},{1,1}},// 0{{1,0},{0,0},{1,1},{0,1}},// ORIENTATION_FLIP_X{{0,1},{1,1},{0,0},{1,0}},// ORIENTATION_FLIP_Y{{1,1},{0,1},{1,0},{0,0}},// ORIENTATION_FLIP_X | ORIENTATION_FLIP_Y{{0,0},{0,1},{1,0},{1,1}},// ORIENTATION_SWAP_XY{{0,1},{0,0},{1,1},{1,0}},// ORIENTATION_SWAP_XY | ORIENTATION_FLIP_X{{1,0},{1,1},{0,0},{0,1}},// ORIENTATION_SWAP_XY | ORIENTATION_FLIP_Y{{1,1},{1,0},{0,1},{0,0}}// ORIENTATION_SWAP_XY | ORIENTATION_FLIP_X | ORIENTATION_FLIP_Y};//**************************************************************************// INLINE FUNCTIONS//**************************************************************************//-------------------------------------------------// apply_orientation - apply orientation to a// set of bounds//-------------------------------------------------inlinevoidapply_orientation(render_bounds&bounds,intorientation){// swap firstif(orientation&ORIENTATION_SWAP_XY){std::swap(bounds.x0,bounds.y0);std::swap(bounds.x1,bounds.y1);}// apply X flipif(orientation&ORIENTATION_FLIP_X){bounds.x0=1.0f-bounds.x0;bounds.x1=1.0f-bounds.x1;}// apply Y flipif(orientation&ORIENTATION_FLIP_Y){bounds.y0=1.0f-bounds.y0;bounds.y1=1.0f-bounds.y1;}}//-------------------------------------------------// normalize_bounds - normalize bounds so that// x0/y0 are less than x1/y1//-------------------------------------------------inlinevoidnormalize_bounds(render_bounds&bounds){if(bounds.x0>bounds.x1)std::swap(bounds.x0,bounds.x1);if(bounds.y0>bounds.y1)std::swap(bounds.y0,bounds.y1);}//**************************************************************************// RENDER PRIMITIVE//**************************************************************************//-------------------------------------------------// reset - reset the state of a primitive after// it is re-allocated//-------------------------------------------------voidrender_primitive::reset(){// do not clear m_next!memset(&type,0,uintptr_t(&texcoords+1)-uintptr_t(&type));}//**************************************************************************// RENDER PRIMITIVE LIST//**************************************************************************//-------------------------------------------------// render_primitive_list - constructor//-------------------------------------------------render_primitive_list::render_primitive_list(){}//-------------------------------------------------// ~render_primitive_list - destructor//-------------------------------------------------render_primitive_list::~render_primitive_list(){release_all();}//-------------------------------------------------// add_reference - add a new reference//-------------------------------------------------inlinevoidrender_primitive_list::add_reference(void*refptr){// skip if we already have oneif(has_reference(refptr))return;// set the refptr and link us into the listreference*ref=m_reference_allocator.alloc();ref->m_refptr=refptr;m_reflist.append(*ref);}//-------------------------------------------------// has_reference - find a refptr in a reference// list//-------------------------------------------------inlineboolrender_primitive_list::has_reference(void*refptr)const{// skip if we already have onefor(reference&ref:m_reflist)if(ref.m_refptr==refptr)returntrue;returnfalse;}//-------------------------------------------------// alloc - allocate a new empty primitive//-------------------------------------------------inlinerender_primitive*render_primitive_list::alloc(render_primitive::primitive_typetype){render_primitive*result=m_primitive_allocator.alloc();result->reset();result->type=type;returnresult;}//-------------------------------------------------// release_all - release the contents of// a render list//-------------------------------------------------voidrender_primitive_list::release_all(){// release all the live items while under the lockm_primitive_allocator.reclaim_all(m_primlist);m_reference_allocator.reclaim_all(m_reflist);}//-------------------------------------------------// append_or_return - append a primitive to the// end of the list, or return it to the free// list, based on a flag//-------------------------------------------------voidrender_primitive_list::append_or_return(render_primitive&prim,boolclipped){if(!clipped)m_primlist.append(prim);elsem_primitive_allocator.reclaim(prim);}//**************************************************************************// RENDER TEXTURE//**************************************************************************//-------------------------------------------------// render_texture - constructor//-------------------------------------------------render_texture::render_texture():m_manager(nullptr),m_next(nullptr),m_bitmap(nullptr),m_format(TEXFORMAT_ARGB32),m_id(~0ULL),m_old_id(~0ULL),m_scaler(nullptr),m_param(nullptr),m_curseq(0){m_sbounds.set(0,-1,0,-1);memset(m_scaled,0,sizeof(m_scaled));}//-------------------------------------------------// ~render_texture - destructor//-------------------------------------------------render_texture::~render_texture(){release();}//-------------------------------------------------// reset - reset the state of a texture after// it has been re-allocated//-------------------------------------------------voidrender_texture::reset(render_manager&manager,texture_scaler_funcscaler,void*param){m_manager=&manager;if(scaler!=nullptr){assert(m_format==TEXFORMAT_ARGB32);m_scaler=scaler;m_param=param;}m_old_id=m_id;m_id=~0L;}//-------------------------------------------------// release - release resources when we are freed//-------------------------------------------------voidrender_texture::release(){// free all scaled versionsfor(auto&elem:m_scaled){m_manager->invalidate_all(elem.bitmap);global_free(elem.bitmap);elem.bitmap=nullptr;elem.seqid=0;}// invalidate references to the original bitmap as wellm_manager->invalidate_all(m_bitmap);m_bitmap=nullptr;m_sbounds.set(0,-1,0,-1);m_format=TEXFORMAT_ARGB32;m_curseq=0;}//-------------------------------------------------// set_bitmap - set a new source bitmap//-------------------------------------------------voidrender_texture::set_bitmap(bitmap_t&bitmap,constrectangle&sbounds,texture_formatformat){assert(bitmap.cliprect().contains(sbounds));// ensure we have a valid palette for palettized modesif(format==TEXFORMAT_PALETTE16)assert(bitmap.palette()!=nullptr);// invalidate references to the old bitmapif(&bitmap!=m_bitmap&&m_bitmap!=nullptr)m_manager->invalidate_all(m_bitmap);// set the new bitmap/palettem_bitmap=&bitmap;m_sbounds=sbounds;m_format=format;// invalidate all scaled versionsfor(auto&elem:m_scaled){if(elem.bitmap!=nullptr){m_manager->invalidate_all(elem.bitmap);global_free(elem.bitmap);}elem.bitmap=nullptr;elem.seqid=0;}}//-------------------------------------------------// hq_scale - generic high quality resampling// scaler//-------------------------------------------------voidrender_texture::hq_scale(bitmap_argb32&dest,bitmap_argb32&source,constrectangle&sbounds,void*param){render_colorcolor={1.0f,1.0f,1.0f,1.0f};bitmap_argb32sourcesub(source,sbounds);render_resample_argb_bitmap_hq(dest,sourcesub,color);}//-------------------------------------------------// get_scaled - get a scaled bitmap (if we can)//-------------------------------------------------voidrender_texture::get_scaled(u32dwidth,u32dheight,render_texinfo&texinfo,render_primitive_list&primlist,u32flags){// source width/height come from the source boundsintswidth=m_sbounds.width();intsheight=m_sbounds.height();// ensure height/width are non-zeroif(dwidth==0)dwidth=1;if(dheight==0)dheight=1;texinfo.unique_id=m_id;texinfo.old_id=m_old_id;if(m_old_id!=~0ULL)m_old_id=~0ULL;// are we scaler-free? if so, just return the source bitmapif(m_scaler==nullptr||(m_bitmap!=nullptr&&swidth==dwidth&&sheight==dheight)){if(m_bitmap==nullptr)return;// add a reference and set up the source bitmapprimlist.add_reference(m_bitmap);texinfo.base=m_bitmap->raw_pixptr(m_sbounds.top(),m_sbounds.left());texinfo.rowpixels=m_bitmap->rowpixels();texinfo.width=swidth;texinfo.height=sheight;// palette will be set latertexinfo.seqid=++m_curseq;}else{// make sure we can recover the original argb32 bitmapbitmap_argb32dummy;bitmap_argb32&srcbitmap=(m_bitmap!=nullptr)?downcast<bitmap_argb32&>(*m_bitmap):dummy;// is it a size we already have?scaled_texture*scaled=nullptr;intscalenum;for(scalenum=0;scalenum<ARRAY_LENGTH(m_scaled);scalenum++){scaled=&m_scaled[scalenum];// we need a non-NULL bitmap with matching dest sizeif(scaled->bitmap!=nullptr&&dwidth==scaled->bitmap->width()&&dheight==scaled->bitmap->height())break;}// did we get one?if(scalenum==ARRAY_LENGTH(m_scaled)){intlowest=-1;// didn't find one -- take the entry with the lowest seqnumfor(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))lowest=scalenum;if(-1==lowest)throwemu_fatalerror("render_texture::get_scaled: Too many live texture instances!");// throw out any existing entriesscaled=&m_scaled[lowest];if(scaled->bitmap!=nullptr){m_manager->invalidate_all(scaled->bitmap);global_free(scaled->bitmap);}// allocate a new bitmapscaled->bitmap=global_alloc(bitmap_argb32(dwidth,dheight));scaled->seqid=++m_curseq;// let the scaler do the work(*m_scaler)(*scaled->bitmap,srcbitmap,m_sbounds,m_param);}// finally fill out the new infoprimlist.add_reference(scaled->bitmap);texinfo.base=&scaled->bitmap->pix32(0);texinfo.rowpixels=scaled->bitmap->rowpixels();texinfo.width=dwidth;texinfo.height=dheight;// palette will be set latertexinfo.seqid=scaled->seqid;}}//-------------------------------------------------// get_adjusted_palette - return the adjusted// palette for a texture//-------------------------------------------------constrgb_t*render_texture::get_adjusted_palette(render_container&container,u32&out_length){// override the palette with our adjusted paletteswitch(m_format){caseTEXFORMAT_PALETTE16:assert(m_bitmap->palette()!=nullptr);// return our adjusted palettereturncontainer.bcg_lookup_table(m_format,out_length,m_bitmap->palette());caseTEXFORMAT_RGB32:caseTEXFORMAT_ARGB32:caseTEXFORMAT_YUY16:// if no adjustment necessary, return nullptrif(!container.has_brightness_contrast_gamma_changes())returnnullptr;returncontainer.bcg_lookup_table(m_format,out_length);default:assert(false);}returnnullptr;}//**************************************************************************// RENDER CONTAINER//**************************************************************************//-------------------------------------------------// render_container - constructor//-------------------------------------------------render_container::render_container(render_manager&manager,screen_device*screen):m_next(nullptr),m_manager(manager),m_screen(screen),m_overlaybitmap(nullptr),m_overlaytexture(nullptr){// make sure it is emptyempty();// if we have a screen, read and apply the optionsif(m_screen){// set the initial orientation and brightness/contrast/gammam_user.m_orientation=m_screen->orientation();m_user.m_brightness=manager.machine().options().brightness();m_user.m_contrast=manager.machine().options().contrast();m_user.m_gamma=manager.machine().options().gamma();// palette client will be allocated later}recompute_lookups();}//-------------------------------------------------// ~render_container - destructor//-------------------------------------------------render_container::~render_container(){// free all the container itemsempty();// free the overlay texturem_manager.texture_free(m_overlaytexture);}//-------------------------------------------------// set_overlay - set the overlay bitmap for the// container//-------------------------------------------------voidrender_container::set_overlay(bitmap_argb32*bitmap){// free any existing texturem_manager.texture_free(m_overlaytexture);// set the new data and allocate the texturem_overlaybitmap=bitmap;if(m_overlaybitmap!=nullptr){m_overlaytexture=m_manager.texture_alloc(render_container::overlay_scale);m_overlaytexture->set_bitmap(*bitmap,bitmap->cliprect(),TEXFORMAT_ARGB32);}}//-------------------------------------------------// set_user_settings - set the current user// settings for a container//-------------------------------------------------voidrender_container::set_user_settings(constuser_settings&settings){m_user=settings;recompute_lookups();}//-------------------------------------------------// add_line - add a line item to this container//-------------------------------------------------voidrender_container::add_line(floatx0,floaty0,floatx1,floaty1,floatwidth,rgb_targb,u32flags){item&newitem=add_generic(CONTAINER_ITEM_LINE,x0,y0,x1,y1,argb);newitem.m_width=width;newitem.m_flags=flags;}//-------------------------------------------------// add_quad - add a quad item to this container//-------------------------------------------------voidrender_container::add_quad(floatx0,floaty0,floatx1,floaty1,rgb_targb,render_texture*texture,u32flags){item&newitem=add_generic(CONTAINER_ITEM_QUAD,x0,y0,x1,y1,argb);newitem.m_texture=texture;newitem.m_flags=flags;}//-------------------------------------------------// add_char - add a char item to this container//-------------------------------------------------voidrender_container::add_char(floatx0,floaty0,floatheight,floataspect,rgb_targb,render_font&font,u16ch){// compute the bounds of the character cell and get the texturerender_boundsbounds;bounds.x0=x0;bounds.y0=y0;render_texture*texture=font.get_char_texture_and_bounds(height,aspect,ch,bounds);// add it like a quaditem&newitem=add_generic(CONTAINER_ITEM_QUAD,bounds.x0,bounds.y0,bounds.x1,bounds.y1,argb);newitem.m_texture=texture;newitem.m_flags=PRIMFLAG_TEXORIENT(ROT0)|PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)|PRIMFLAG_PACKABLE;newitem.m_internal=INTERNAL_FLAG_CHAR;}//-------------------------------------------------// apply_brightness_contrast_gamma - apply the// container's brightess, contrast, and gamma to// an 8-bit value//-------------------------------------------------u8render_container::apply_brightness_contrast_gamma(u8value){return::apply_brightness_contrast_gamma(value,m_user.m_brightness,m_user.m_contrast,m_user.m_gamma);}//-------------------------------------------------// apply_brightness_contrast_gamma_fp - apply the// container's brightess, contrast, and gamma to// a floating-point value//-------------------------------------------------floatrender_container::apply_brightness_contrast_gamma_fp(floatvalue){return::apply_brightness_contrast_gamma_fp(value,m_user.m_brightness,m_user.m_contrast,m_user.m_gamma);}//-------------------------------------------------// bcg_lookup_table - return the appropriate// brightness/contrast/gamma lookup table for a// given texture mode//-------------------------------------------------constrgb_t*render_container::bcg_lookup_table(inttexformat,u32&out_length,palette_t*palette){switch(texformat){caseTEXFORMAT_PALETTE16:if(m_palclient==nullptr)// if adjusted palette hasn't been created yet, create it{m_palclient=std::make_unique<palette_client>(*palette);m_bcglookup.resize(palette->max_index());recompute_lookups();}assert(palette==&m_palclient->palette());out_length=palette->max_index();return&m_bcglookup[0];caseTEXFORMAT_RGB32:caseTEXFORMAT_ARGB32:caseTEXFORMAT_YUY16:out_length=ARRAY_LENGTH(m_bcglookup256);returnm_bcglookup256;default:out_length=0;returnnullptr;}}//-------------------------------------------------// overlay_scale - scaler for an overlay//-------------------------------------------------voidrender_container::overlay_scale(bitmap_argb32&dest,bitmap_argb32&source,constrectangle&sbounds,void*param){// simply replicate the source bitmap over the targetfor(inty=0;y<dest.height();y++){u32*src=&source.pix32(y%source.height());u32*dst=&dest.pix32(y);intsx=0;// loop over columnsfor(intx=0;x<dest.width();x++){*dst++=src[sx++];if(sx>=source.width())sx=0;}}}//-------------------------------------------------// add_generic - add a generic item to a// container//-------------------------------------------------render_container::item&render_container::add_generic(u8type,floatx0,floaty0,floatx1,floaty1,rgb_targb){item*newitem=m_item_allocator.alloc();assert(x0==x0);assert(x1==x1);assert(y0==y0);assert(y1==y1);// copy the data into the new itemnewitem->m_type=type;newitem->m_bounds.x0=x0;newitem->m_bounds.y0=y0;newitem->m_bounds.x1=x1;newitem->m_bounds.y1=y1;newitem->m_color.r=(float)argb.r()*(1.0f/255.0f);newitem->m_color.g=(float)argb.g()*(1.0f/255.0f);newitem->m_color.b=(float)argb.b()*(1.0f/255.0f);newitem->m_color.a=(float)argb.a()*(1.0f/255.0f);newitem->m_flags=0;newitem->m_internal=0;newitem->m_width=0;newitem->m_texture=nullptr;// add the item to the containerreturnm_itemlist.append(*newitem);}//-------------------------------------------------// recompute_lookups - recompute the lookup table// for the render container//-------------------------------------------------voidrender_container::recompute_lookups(){// recompute the 256 entry lookup tablefor(inti=0;i<0x100;i++){u8adjustedval=apply_brightness_contrast_gamma(i);m_bcglookup256[i+0x000]=adjustedval<<0;m_bcglookup256[i+0x100]=adjustedval<<8;m_bcglookup256[i+0x200]=adjustedval<<16;m_bcglookup256[i+0x300]=adjustedval<<24;}// recompute the palette entriesif(m_palclient!=nullptr){palette_t&palette=m_palclient->palette();constrgb_t*adjusted_palette=palette.entry_list_adjusted();intcolors=palette.max_index();if(has_brightness_contrast_gamma_changes()){for(inti=0;i<colors;i++){rgb_tnewval=adjusted_palette[i];m_bcglookup[i]=(newval&0xff000000)|m_bcglookup256[0x200+newval.r()]|m_bcglookup256[0x100+newval.g()]|m_bcglookup256[0x000+newval.b()];}}elsememcpy(&m_bcglookup[0],adjusted_palette,colors*sizeof(rgb_t));}}//-------------------------------------------------// update_palette - update any dirty palette// entries//-------------------------------------------------voidrender_container::update_palette(){// skip if no clientif(m_palclient==nullptr)return;// get the dirty listu32mindirty,maxdirty;constu32*dirty=m_palclient->dirty_list(mindirty,maxdirty);// iterate over dirty items and update themif(dirty!=nullptr){palette_t&palette=m_palclient->palette();constrgb_t*adjusted_palette=palette.entry_list_adjusted();if(has_brightness_contrast_gamma_changes()){// loop over chunks of 32 entries, since we can quickly examine 32 at a timefor(u32entry32=mindirty/32;entry32<=maxdirty/32;entry32++){u32dirtybits=dirty[entry32];if(dirtybits!=0)// this chunk of 32 has dirty entries; fix them upfor(u32entry=0;entry<32;entry++)if(dirtybits&(1<<entry)){u32finalentry=entry32*32+entry;rgb_tnewval=adjusted_palette[finalentry];m_bcglookup[finalentry]=(newval&0xff000000)|m_bcglookup256[0x200+newval.r()]|m_bcglookup256[0x100+newval.g()]|m_bcglookup256[0x000+newval.b()];}}}elsememcpy(&m_bcglookup[mindirty],&adjusted_palette[mindirty],(maxdirty-mindirty+1)*sizeof(rgb_t));}}//-------------------------------------------------// user_settings - constructor//-------------------------------------------------render_container::user_settings::user_settings():m_orientation(0),m_brightness(1.0f),m_contrast(1.0f),m_gamma(1.0f),m_xscale(1.0f),m_yscale(1.0f),m_xoffset(0.0f),m_yoffset(0.0f){}//**************************************************************************// RENDER TARGET//**************************************************************************//-------------------------------------------------// render_target - constructor//-------------------------------------------------render_target::render_target(render_manager&manager,constinternal_layout*layoutfile,u32flags):render_target(manager,layoutfile,flags,CONSTRUCTOR_IMPL){}render_target::render_target(render_manager&manager,util::xml::data_nodeconst&layout,u32flags):render_target(manager,layout,flags,CONSTRUCTOR_IMPL){}template<typenameT>render_target::render_target(render_manager&manager,T&&layout,u32flags,constructor_impl_t):m_next(nullptr),m_manager(manager),m_curview(nullptr),m_flags(flags),m_listindex(0),m_width(640),m_height(480),m_pixel_aspect(0.0f),m_max_refresh(0),m_orientation(0),m_base_view(nullptr),m_base_orientation(ROT0),m_maxtexwidth(65536),m_maxtexheight(65536),m_transform_container(true),m_external_artwork(false){// determine the base layer configuration based on optionsm_base_layerconfig.set_zoom_to_screen(manager.machine().options().artwork_crop());// aspect and scale optionsm_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;elseif(manager.machine().options().uneven_stretch_x())m_scale_mode=SCALE_FRACTIONAL_X;elseif(manager.machine().options().uneven_stretch_y())m_scale_mode=SCALE_FRACTIONAL_Y;elseif(manager.machine().options().uneven_stretch())m_scale_mode=SCALE_FRACTIONAL;elsem_scale_mode=SCALE_INTEGER;// determine the base orientation based on optionsif(!manager.machine().options().rotate())m_base_orientation=orientation_reverse(manager.machine().system().flags&machine_flags::MASK_ORIENTATION);// rotate left/rightif(manager.machine().options().ror()||(manager.machine().options().auto_ror()&&(manager.machine().system().flags&ORIENTATION_SWAP_XY)))m_base_orientation=orientation_add(ROT90,m_base_orientation);if(manager.machine().options().rol()||(manager.machine().options().auto_rol()&&(manager.machine().system().flags&ORIENTATION_SWAP_XY)))m_base_orientation=orientation_add(ROT270,m_base_orientation);// flip X/Yif(manager.machine().options().flipx())m_base_orientation^=ORIENTATION_FLIP_X;if(manager.machine().options().flipy())m_base_orientation^=ORIENTATION_FLIP_Y;// set the orientation and layerconfig equal to the basem_orientation=m_base_orientation;m_layerconfig=m_base_layerconfig;// load the layout filesload_layout_files(std::forward<T>(layout),flags&RENDER_CREATE_SINGLE_FILE);// set the current view to the first oneset_view(0);// make us the UI target if there is noneif(!hidden()&&manager.m_ui_target==nullptr)manager.set_ui_target(*this);}//-------------------------------------------------// ~render_target - destructor//-------------------------------------------------render_target::~render_target(){}//-------------------------------------------------// is_ui_target - return true if this is the// UI target//-------------------------------------------------boolrender_target::is_ui_target()const{return(this==&m_manager.ui_target());}//-------------------------------------------------// index - return the index of this target//-------------------------------------------------intrender_target::index()const{returnm_manager.m_targetlist.indexof(*this);}//-------------------------------------------------// set_bounds - set the bounds and pixel aspect// of a target//-------------------------------------------------voidrender_target::set_bounds(s32width,s32height,floatpixel_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;}//-------------------------------------------------// set_view - dynamically change the view for// a target//-------------------------------------------------voidrender_target::set_view(intviewindex){layout_view*view=view_by_index(viewindex);if(view){m_curview=view;view->recompute(m_layerconfig);}}//-------------------------------------------------// set_max_texture_size - set the upper bound on// the texture size//-------------------------------------------------voidrender_target::set_max_texture_size(intmaxwidth,intmaxheight){m_maxtexwidth=maxwidth;m_maxtexheight=maxheight;}//-------------------------------------------------// configured_view - select a view for this// target based on the configuration parameters//-------------------------------------------------intrender_target::configured_view(constchar*viewname,inttargetindex,intnumtargets){layout_view*view=nullptr;intviewindex;// auto view just selects the nth viewif(strcmp(viewname,"auto")!=0){// scan for a matching view namesize_tviewlen=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;}// if we don't have a match, default to the nth viewscreen_device_iteratoriter(m_manager.machine().root_device());intscrcount=iter.count();if(view==nullptr&&scrcount>0){// if we have enough targets to be one per screen, assign in orderif(numtargets>=scrcount){intourindex=index()%scrcount;screen_device*screen=iter.byindex(ourindex);assert(screen!=nullptr);// find the first view with this screen and this screen onlyfor(view=view_by_index(viewindex=0);view!=nullptr;view=view_by_index(++viewindex)){autoconst&viewscreens=view->screens();if(viewscreens.empty()){view=nullptr;break;}elseif(std::find_if(viewscreens.begin(),viewscreens.end(),[&screen](autoconst&scr){return&scr.get()!=screen;})==viewscreens.end())break;}}// otherwise, find the first view that has all the screensif(view==nullptr){for(view=view_by_index(viewindex=0);view!=nullptr;view=view_by_index(++viewindex)){if(view->screen_count()>=scrcount){boolscreen_missing(false);for(screen_device&screen:iter){if(!view->has_screen(screen)){screen_missing=true;break;}}if(!screen_missing)break;}}}}// make sure it's a valid viewreturn(view!=nullptr)?view_index(*view):0;}//-------------------------------------------------// view_name - return the name of the given view//-------------------------------------------------constchar*render_target::view_name(intviewindex){layout_viewconst*constview=view_by_index(viewindex);returnview?view->name().c_str():nullptr;}//-------------------------------------------------// compute_visible_area - compute the visible// area for the given target with the current// layout and proposed new parameters//-------------------------------------------------voidrender_target::compute_visible_area(s32target_width,s32target_height,floattarget_pixel_aspect,inttarget_orientation,s32&visible_width,s32&visible_height){switch(m_scale_mode){caseSCALE_FRACTIONAL:{floatwidth,height;floatscale;// constrained caseif(m_keepaspect){// start with the aspect ratio of the square pixel layoutwidth=m_curview->effective_aspect(m_layerconfig);height=1.0f;// first apply target orientationif(target_orientation&ORIENTATION_SWAP_XY)std::swap(width,height);// apply the target pixel aspect ratioheight*=target_pixel_aspect;// based on the height/width ratio of the source and target, compute the scale factorif(width/height>(float)target_width/(float)target_height)scale=(float)target_width/width;elsescale=(float)target_height/height;}// stretch-to-fit caseelse{width=(float)target_width;height=(float)target_height;scale=1.0f;}// set the final width/heightvisible_width=render_round_nearest(width*scale);visible_height=render_round_nearest(height*scale);break;}default:{// get source size and aspects32src_width,src_height;compute_minimum_size(src_width,src_height);floatsrc_aspect=m_curview->effective_aspect(m_layerconfig);// apply orientation if requiredif(target_orientation&ORIENTATION_SWAP_XY)src_aspect=1.0/src_aspect;// get target aspectfloattarget_aspect=(float)target_width/(float)target_height*target_pixel_aspect;booltarget_is_portrait=(target_aspect<1.0f);// apply automatic axial stretching if requiredintscale_mode=m_scale_mode;if(m_scale_mode==SCALE_FRACTIONAL_AUTO){boolis_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;}// determine the scale mode for each axisboolx_is_integer=!((!target_is_portrait&&scale_mode==SCALE_FRACTIONAL_X)||(target_is_portrait&&scale_mode==SCALE_FRACTIONAL_Y));booly_is_integer=!((target_is_portrait&&scale_mode==SCALE_FRACTIONAL_X)||(!target_is_portrait&&scale_mode==SCALE_FRACTIONAL_Y));// first compute scale factors to fit the screenfloatxscale=(float)target_width/src_width;floatyscale=(float)target_height/src_height;floatmaxxscale=std::max(1.0f,float(m_int_overscan?render_round_nearest(xscale):floor(xscale)));floatmaxyscale=std::max(1.0f,float(m_int_overscan?render_round_nearest(yscale):floor(yscale)));// now apply desired scale mode and aspect correctionif(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)));// check if we have user defined scale factors, if so use them insteadintuser_scale_x=target_is_portrait?m_int_scale_y:m_int_scale_x;intuser_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;// set the final width/heightvisible_width=render_round_nearest(src_width*xscale);visible_height=render_round_nearest(src_height*yscale);break;}}}//-------------------------------------------------// compute_minimum_size - compute the "minimum"// size of a target, which is the smallest bounds// that will ensure at least 1 target pixel per// source pixel for all included screens//-------------------------------------------------voidrender_target::compute_minimum_size(s32&minwidth,s32&minheight){floatmaxxscale=1.0f,maxyscale=1.0f;intscreens_considered=0;// early exit in case we are called between device teardown and render teardownif(m_manager.machine().phase()==machine_phase::EXIT){minwidth=640;minheight=480;return;}if(!m_curview)throwemu_fatalerror("Mandatory artwork is missing");// scan the current view for all screensfor(layout_view::item&curitem:m_curview->items()){if(curitem.screen()){// use a hard-coded default visible area for vector screensscreen_device*constscreen=curitem.screen();constrectanglevectorvis(0,639,0,479);constrectangle&visarea=(screen->screen_type()==SCREEN_TYPE_VECTOR)?vectorvis:screen->visible_area();// apply target orientation to the boundsrender_boundsbounds=curitem.bounds();apply_orientation(bounds,m_orientation);normalize_bounds(bounds);// based on the orientation of the screen container, check the bitmapfloatxscale,yscale;if(!(orientation_add(m_orientation,screen->container().orientation())&ORIENTATION_SWAP_XY)){xscale=float(visarea.width())/bounds.width();yscale=float(visarea.height())/bounds.height();}else{xscale=float(visarea.height())/bounds.width();yscale=float(visarea.width())/bounds.height();}// pick the greatermaxxscale=std::max(xscale,maxxscale);maxyscale=std::max(yscale,maxyscale);screens_considered++;}}// if there were no screens considered, pick a nominal defaultif(screens_considered==0){maxxscale=640.0f;maxyscale=480.0f;}// round upminwidth=render_round_nearest(maxxscale);minheight=render_round_nearest(maxyscale);}//-------------------------------------------------// get_primitives - return a list of primitives// for a given render target//-------------------------------------------------render_primitive_list&render_target::get_primitives(){// remember the base values if this is the first frameif(m_base_view==nullptr)m_base_view=m_curview;// switch to the next primitive listrender_primitive_list&list=m_primlist[m_listindex];m_listindex=(m_listindex+1)%ARRAY_LENGTH(m_primlist);list.acquire_lock();// free any previous primitiveslist.release_all();// compute the visible width/heights32viswidth,visheight;compute_visible_area(m_width,m_height,m_pixel_aspect,m_orientation,viswidth,visheight);// create a root transform for the targetobject_transformroot_xform;root_xform.xoffs=(float)(m_width-viswidth)/2;root_xform.yoffs=(float)(m_height-visheight)/2;root_xform.xscale=(float)viswidth;root_xform.yscale=(float)visheight;root_xform.color.r=root_xform.color.g=root_xform.color.b=root_xform.color.a=1.0f;root_xform.orientation=m_orientation;root_xform.no_center=false;// iterate over items in the view, but only if we're runningif(m_manager.machine().phase()>=machine_phase::RESET)for(layout_view::item&curitem:m_curview->items()){// first apply orientation to the boundsrender_boundsbounds=curitem.bounds();apply_orientation(bounds,root_xform.orientation);normalize_bounds(bounds);// apply the transform to the itemobject_transformitem_xform;item_xform.xoffs=root_xform.xoffs+bounds.x0*root_xform.xscale;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.orientation=orientation_add(curitem.orientation(),root_xform.orientation);item_xform.no_center=false;// if there is no associated element, it must be a screen elementif(curitem.screen()!=nullptr)add_container_primitives(list,root_xform,item_xform,curitem.screen()->container(),curitem.blend_mode());elseadd_element_primitives(list,item_xform,*curitem.element(),curitem.state(),curitem.blend_mode());}// if we are not in the running stage, draw an outer boxelse{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->full_bounds=prim->bounds;set_render_color(&prim->color,1.0f,0.1f,0.1f,0.1f);prim->texture.base=nullptr;prim->flags=PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA);list.append(*prim);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->full_bounds=prim->bounds;set_render_color(&prim->color,1.0f,0.0f,0.0f,0.0f);prim->texture.base=nullptr;prim->flags=PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA);list.append(*prim);}}// process the debug containersfor(render_container&debug:m_debug_containers){object_transformui_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 elementsadd_container_primitives(list,root_xform,ui_xform,debug,BLENDMODE_ALPHA);}// process the UI if we are the UI targetif(is_ui_target()){// compute the transform for the UIobject_transformui_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=ui_xform.color.a=1.0f;ui_xform.orientation=m_orientation;ui_xform.no_center=false;// add UI elementsadd_container_primitives(list,root_xform,ui_xform,m_manager.ui_container(),BLENDMODE_ALPHA);}// optimize the list before handing it offadd_clear_and_optimize_primitive_list(list);list.release_lock();returnlist;}//-------------------------------------------------// map_point_container - attempts to map a point// on the specified render_target to the// specified container, if possible//-------------------------------------------------boolrender_target::map_point_container(s32target_x,s32target_y,render_container&container,float&container_x,float&container_y){ioport_port*input_port;ioport_valueinput_mask;returnmap_point_internal(target_x,target_y,&container,container_x,container_y,input_port,input_mask);}//-------------------------------------------------// map_point_input - attempts to map a point on// the specified render_target to the specified// container, if possible//-------------------------------------------------boolrender_target::map_point_input(s32target_x,s32target_y,ioport_port*&input_port,ioport_value&input_mask,float&input_x,float&input_y){returnmap_point_internal(target_x,target_y,nullptr,input_x,input_y,input_port,input_mask);;}//-------------------------------------------------// invalidate_all - if any of our primitive lists// contain a reference to the given pointer,// clear them//-------------------------------------------------voidrender_target::invalidate_all(void*refptr){// iterate through all our primitive listsfor(auto&list:m_primlist){// if we have a reference to this object, release our listlist.acquire_lock();if(list.has_reference(refptr))list.release_all();list.release_lock();}}//-------------------------------------------------// 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//-------------------------------------------------voidrender_target::debug_free(render_container&container){m_debug_containers.remove(container);}//-------------------------------------------------// debug_append - move a debug view container to// the end of the list//-------------------------------------------------voidrender_target::debug_append(render_container&container){m_debug_containers.append(m_debug_containers.detach(container));}//-------------------------------------------------// resolve_tags - resolve tag lookups//-------------------------------------------------voidrender_target::resolve_tags(){for(layout_file&file:m_filelist){for(layout_view&view:file.views()){view.resolve_tags();}}}//-------------------------------------------------// update_layer_config - recompute after a layer// config change//-------------------------------------------------voidrender_target::update_layer_config(){m_curview->recompute(m_layerconfig);}//-------------------------------------------------// load_layout_files - load layout files for a// given render target//-------------------------------------------------voidrender_target::load_layout_files(constinternal_layout*layoutfile,boolsinglefile){boolhave_artwork=false;// if there's an explicit file, load that firstconststd::string&basename=m_manager.machine().basename();if(layoutfile)have_artwork|=load_layout_file(basename.c_str(),*layoutfile);// if we're only loading this file, we know our final resultif(!singlefile)load_additional_layout_files(basename.c_str(),have_artwork);}voidrender_target::load_layout_files(util::xml::data_nodeconst&rootnode,boolsinglefile){boolhave_artwork=false;// if there's an explicit file, load that firstconststd::string&basename=m_manager.machine().basename();have_artwork|=load_layout_file(m_manager.machine().root_device(),basename.c_str(),rootnode);// if we're only loading this file, we know our final resultif(!singlefile)load_additional_layout_files(basename.c_str(),have_artwork);}voidrender_target::load_additional_layout_files(constchar*basename,boolhave_artwork){m_external_artwork=false;// if override_artwork defined, load that and skip artwork other than defaultconstchar*constoverride_art=m_manager.machine().options().override_artwork();if(override_art&&*override_art){if(load_layout_file(override_art,override_art))m_external_artwork=true;elseif(load_layout_file(override_art,"default"))m_external_artwork=true;}constgame_driver&system=m_manager.machine().system();// Skip if override_artwork has found artworkif(!m_external_artwork){// try to load a file based on the driver nameif(!load_layout_file(basename,system.name))m_external_artwork|=load_layout_file(basename,"default");elsem_external_artwork=true;// if a default view has been specified, use that as a fallbackboolhave_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_layoutconst&layout){have_default|=load_layout_file(nullptr,layout,&dev);});// try to load another file based on the parent driver nameintcloneof=driver_list::clone(system);while(0<=cloneof){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");elsem_external_artwork=true;// Check the parent of the parent to cover bios based artworkconstgame_driver&parent(driver_list::driver(cloneof));cloneof=driver_list::clone(parent);}have_artwork|=m_external_artwork;// Use fallback artwork if defined and no artwork has been found yetif(!have_artwork){constchar*constfallback_art=m_manager.machine().options().fallback_artwork();if(fallback_art&&*fallback_art){if(!load_layout_file(fallback_art,fallback_art))have_artwork|=load_layout_file(fallback_art,"default");elsehave_artwork=true;}}}// local screen info to avoid repeated codeclassscreen_info{public:screen_info(screen_deviceconst&screen):m_device(screen),m_rotated(screen.orientation()&ORIENTATION_SWAP_XY),m_physical(screen.physical_aspect()),m_native(screen.visible_area().width(),screen.visible_area().height()){util::reduce_fraction(m_native.first,m_native.second);if(m_rotated){std::swap(m_physical.first,m_physical.second);std::swap(m_native.first,m_native.second);}}screen_deviceconst&device()const{returnm_device.get();}boolrotated()const{returnm_rotated;}boolsquare()const{returnm_physical==m_native;}unsignedphysical_x()const{returnm_physical.first;}unsignedphysical_y()const{returnm_physical.second;}unsignednative_x()const{returnm_native.first;}unsignednative_y()const{returnm_native.second;}std::pair<float,float>tiled_size()const{if(physical_x()==physical_y())returnstd::make_pair(1.0F,1.0F);elseif(physical_x()>physical_y())returnstd::make_pair(1.0F,float(physical_y())/physical_x());elsereturnstd::make_pair(float(physical_x())/physical_y(),1.0F);}private:std::reference_wrapper<screen_deviceconst>m_device;boolm_rotated;std::pair<unsigned,unsigned>m_physical,m_native;};screen_device_iteratoriter(m_manager.machine().root_device());std::vector<screen_info>constscreens(std::begin(iter),std::end(iter));if(screens.empty())// ensure the fallback view for systems with no screens is loaded if necessary{if(!view_by_index(0)){load_layout_file(nullptr,layout_noscreens);if(m_filelist.empty())throwemu_fatalerror("Couldn't parse default layout??");}}else// generate default layouts for larger numbers of screens{util::xml::file::ptrconstroot(util::xml::file::create());if(!root)throwemu_fatalerror("Couldn't create XML document??");util::xml::data_node*constlayoutnode(root->add_child("mamelayout",nullptr));if(!layoutnode)throwemu_fatalerror("Couldn't create XML node??");layoutnode->set_attribute_int("version",2);// generate individual physical aspect viewsfor(unsignedi=0;screens.size()>i;++i){util::xml::data_node*constviewnode(layoutnode->add_child("view",nullptr));if(!viewnode)throwemu_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::xml::data_node*constscreennode(viewnode->add_child("screen",nullptr));if(!screennode)throwemu_fatalerror("Couldn't create XML node??");screennode->set_attribute_int("index",i);util::xml::data_node*constboundsnode(screennode->add_child("bounds",nullptr));if(!boundsnode)throwemu_fatalerror("Couldn't create XML node??");boundsnode->set_attribute_int("x",0);boundsnode->set_attribute_int("y",0);boundsnode->set_attribute_int("width",screens[i].physical_x());boundsnode->set_attribute_int("height",screens[i].physical_y());}// generate individual pixel aspect viewsfor(unsignedi=0;screens.size()>i;++i){if(!screens[i].square()){util::xml::data_node*constviewnode(layoutnode->add_child("view",nullptr));if(!viewnode)throwemu_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::xml::data_node*constscreennode(viewnode->add_child("screen",nullptr));if(!screennode)throwemu_fatalerror("Couldn't create XML node??");screennode->set_attribute_int("index",i);util::xml::data_node*constboundsnode(screennode->add_child("bounds",nullptr));if(!boundsnode)throwemu_fatalerror("Couldn't create XML node??");boundsnode->set_attribute_int("x",0);boundsnode->set_attribute_int("y",0);boundsnode->set_attribute_int("width",screens[i].native_x());boundsnode->set_attribute_int("height",screens[i].native_y());}}// generate the fake cocktail view for single-screen systemsif(screens.size()==1U){util::xml::data_node*constviewnode(layoutnode->add_child("view",nullptr));if(!viewnode)throwemu_fatalerror("Couldn't create XML node??");viewnode->set_attribute("name","Cocktail");util::xml::data_node*constmirrornode(viewnode->add_child("screen",nullptr));if(!mirrornode)throwemu_fatalerror("Couldn't create XML node??");mirrornode->set_attribute_int("index",0);util::xml::data_node*constmirrorbounds(mirrornode->add_child("bounds",nullptr));if(!mirrorbounds)throwemu_fatalerror("Couldn't create XML node??");mirrorbounds->set_attribute_int("x",0);mirrorbounds->set_attribute_float("y",(-0.01*(std::min)(screens[0].physical_x(),screens[0].physical_y()))-screens[0].physical_y());mirrorbounds->set_attribute_int("width",screens[0].physical_x());mirrorbounds->set_attribute_int("height",screens[0].physical_y());util::xml::data_node*constflipper(mirrornode->add_child("orientation",nullptr));if(!flipper)throwemu_fatalerror("Couldn't create XML node??");flipper->set_attribute_int("rotate",180);util::xml::data_node*constscreennode(viewnode->add_child("screen",nullptr));if(!screennode)throwemu_fatalerror("Couldn't create XML node??");screennode->set_attribute_int("index",0);util::xml::data_node*constscreenbounds(screennode->add_child("bounds",nullptr));if(!screenbounds)throwemu_fatalerror("Couldn't create XML node??");screenbounds->set_attribute_int("x",0);screenbounds->set_attribute_int("y",0);screenbounds->set_attribute_int("width",screens[0].physical_x());screenbounds->set_attribute_int("height",screens[0].physical_y());}// generate tiled views if the supplied artwork doesn't provide a view of all screensboolneed_tiles(screens.size()>=3);if(!need_tiles&&(screens.size()>=2)){need_tiles=true;intviewindex(0);for(layout_view*view=view_by_index(viewindex);need_tiles&&view;view=view_by_index(++viewindex)){if(view->screen_count()>=screens.size()){boolscreen_missing(false);for(screen_device&screen:iter){if(!view->has_screen(screen)){screen_missing=true;break;}}if(!screen_missing)need_tiles=false;}}}if(need_tiles){// helpers for generating a view since we do this a lotstd::vector<float>widths(screens.size()),heights(screens.size());std::vector<std::pair<float,float>>sizes(screens.size());std::transform(screens.begin(),screens.end(),sizes.begin(),[](screen_infoconst&s){returns.tiled_size();});autoconstgenerate_view=[&layoutnode,&screens,&widths,&heights,&sizes](charconst*title,unsignedcolumns,boolgapless,auto&&mapper){// calculate necessary widths/heights of rows/columns restricting screens to unit squareassert(0U<columns);assert(screens.size()>=columns);unsignedconstrows((screens.size()+columns-1)/columns);std::fill_n(widths.begin(),columns,0.0F);std::fill_n(heights.begin(),rows,0.0F);for(unsignedy=0U;rows>y;++y){for(unsignedx=0U;columns>x;++x){intconsti(mapper(x,y));if(0<=i){widths[x]=(std::max)(widths[x],sizes[i].first);heights[y]=(std::max)(heights[y],sizes[i].second);}}}// spacing is 1% of minor dimensionfloatspacing(0.0F);if(!gapless){spacing=0.01F*(std::min)(*std::max_element(widths.begin(),widths.begin()+columns),*std::max_element(heights.begin(),heights.begin()+rows));}// actually generate elementsutil::xml::data_node*viewnode=layoutnode->add_child("view",nullptr);if(!viewnode)throwemu_fatalerror("Couldn't create XML node??");viewnode->set_attribute("name",util::xml::normalize_string(title));floatypos(0.0F);for(unsignedy=0U;rows>y;ypos+=heights[y]+spacing,++y){floatxpos(0.0F);for(unsignedx=0U;columns>x;xpos+=widths[x]+spacing,++x){intconsti(mapper(x,y));if(0<=i){util::xml::data_node*constscreennode(viewnode->add_child("screen",nullptr));if(!screennode)throwemu_fatalerror("Couldn't create XML node??");screennode->set_attribute_int("index",i);util::xml::data_node*constboundsnode(screennode->add_child("bounds",nullptr));if(!boundsnode)throwemu_fatalerror("Couldn't create XML node??");boundsnode->set_attribute_float("x",xpos+((widths[x]-sizes[i].first)/2));boundsnode->set_attribute_float("y",ypos+((heights[y]-sizes[i].second)/2));boundsnode->set_attribute_float("width",sizes[i].first);boundsnode->set_attribute_float("height",sizes[i].second);}}}};// generate linear viewsgenerate_view("Left-to-Right",screens.size(),false,[](unsignedx,unsignedy){returnx;});generate_view("Left-to-Right (Gapless)",screens.size(),true,[](unsignedx,unsignedy){returnx;});generate_view("Top-to-Bottom",1U,false,[](unsignedx,unsignedy){returny;});generate_view("Top-to-Bottom (Gapless)",1U,true,[](unsignedx,unsignedy){returny;});// generate fake cocktail view for systems with two screensif(screens.size()==2U){floatconstheight0(float(screens[0].physical_y())/screens[0].physical_x());floatconstheight1(float(screens[1].physical_y())/screens[1].physical_x());floatconstminor_dim((std::max)((std::min)(height0,1.0F),(std::min)(height1,1.0F)));util::xml::data_node*constviewnode(layoutnode->add_child("view",nullptr));if(!viewnode)throwemu_fatalerror("Couldn't create XML node??");viewnode->set_attribute("name","Cocktail");util::xml::data_node*constmirrornode(viewnode->add_child("screen",nullptr));if(!mirrornode)throwemu_fatalerror("Couldn't create XML node??");mirrornode->set_attribute_int("index",1);util::xml::data_node*constmirrorbounds(mirrornode->add_child("bounds",nullptr));if(!mirrorbounds)throwemu_fatalerror("Couldn't create XML node??");mirrorbounds->set_attribute_int("x",0);mirrorbounds->set_attribute_float("y",(-0.01*minor_dim)-height1);mirrorbounds->set_attribute_int("width",1);mirrorbounds->set_attribute_float("height",height1);util::xml::data_node*constflipper(mirrornode->add_child("orientation",nullptr));if(!flipper)throwemu_fatalerror("Couldn't create XML node??");flipper->set_attribute_int("rotate",180);util::xml::data_node*constscreennode(viewnode->add_child("screen",nullptr));if(!screennode)throwemu_fatalerror("Couldn't create XML node??");screennode->set_attribute_int("index",0);util::xml::data_node*constscreenbounds(screennode->add_child("bounds",nullptr));if(!screenbounds)throwemu_fatalerror("Couldn't create XML node??");screenbounds->set_attribute_int("x",0);screenbounds->set_attribute_int("y",0);screenbounds->set_attribute_int("width",1);screenbounds->set_attribute_float("height",height0);}// generate tiled viewsfor(unsignedmindim=2;((screens.size()+mindim-1)/mindim)>=mindim;++mindim){unsignedconstmajdim((screens.size()+mindim-1)/mindim);unsignedconstremainder(screens.size()%majdim);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(),majdim,false,[&screens,majdim](unsignedx,unsignedy){unsignedconsti(x+(y*majdim));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(),majdim,true,[&screens,majdim](unsignedx,unsignedy){unsignedconsti(x+(y*majdim));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(),mindim,false,[&screens,majdim](unsignedx,unsignedy){unsignedconsti((x*majdim)+y);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(),mindim,true,[&screens,majdim](unsignedx,unsignedy){unsignedconsti((x*majdim)+y);return(screens.size()>i)?int(i):-1;});}}}// try to parse itif(!load_layout_file(m_manager.machine().root_device(),nullptr,*root))throwemu_fatalerror("Couldn't parse generated layout??");}}//-------------------------------------------------// load_layout_file - load a single layout file// and append it to our list//-------------------------------------------------boolrender_target::load_layout_file(constchar*dirname,constinternal_layout&layout_data,device_t*device){// +1 to ensure data is terminated for XML parserautotempout=make_unique_clear<u8[]>(layout_data.decompressed_size+1);z_streamstream;intzerr;// initialize the streammemset(&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");returnfalse;}// decompress this chunkstream.next_in=(unsignedchar*)layout_data.data;stream.avail_in=layout_data.compressed_size;zerr=inflate(&stream,Z_NO_FLUSH);// stop at the end of the streamif(zerr==Z_STREAM_END){// OK}elseif(zerr!=Z_OK){fatalerror("decompression error\n");returnfalse;}// clean upzerr=inflateEnd(&stream);if(zerr!=Z_OK){fatalerror("inflateEnd error\n");returnfalse;}util::xml::file::ptrrootnode(util::xml::file::string_read(reinterpret_cast<charconst*>(tempout.get()),nullptr));tempout.reset();// if we didn't get a properly-formatted XML file, record a warning and exitif(!load_layout_file(device?*device:m_manager.machine().root_device(),dirname,*rootnode)){osd_printf_warning("Improperly formatted XML string, ignoring\n");returnfalse;}else{returntrue;}}boolrender_target::load_layout_file(constchar*dirname,constchar*filename){// build the path and optionally prepend the directorystd::stringfname=std::string(filename).append(".lay");if(dirname)fname.insert(0,PATH_SEPARATOR).insert(0,dirname);// attempt to open the file; bail if we can'temu_filelayoutfile(m_manager.machine().options().art_path(),OPEN_FLAG_READ);layoutfile.set_restrict_to_mediapath(1);osd_file::errorconstfilerr(layoutfile.open(fname));if(filerr!=osd_file::error::NONE)returnfalse;// read the fileutil::xml::parse_optionsparseopt;util::xml::parse_errorparseerr;parseopt.error=&parseerr;util::xml::file::ptrrootnode(util::xml::file::read(layoutfile,&parseopt));if(!rootnode){if(parseerr.error_message){osd_printf_warning("Error parsing XML file '%s' at line %d column %d: %s, ignoring\n",filename,parseerr.error_line,parseerr.error_column,parseerr.error_message);}else{osd_printf_warning("Error parsing XML file '%s', ignorning\n",filename);}returnfalse;}// if we didn't get a properly-formatted XML file, record a warning and exitif(!load_layout_file(m_manager.machine().root_device(),dirname,*rootnode)){osd_printf_warning("Improperly formatted XML file '%s', ignoring\n",filename);returnfalse;}else{returntrue;}}boolrender_target::load_layout_file(device_t&device,constchar*dirname,util::xml::data_nodeconst&rootnode){// parse and catch any errorstry{m_filelist.emplace_back(device,rootnode,dirname);}catch(emu_fatalerror&){returnfalse;}emulator_info::layout_file_cb(rootnode);returntrue;}//-------------------------------------------------// add_container_primitives - add primitives// based on the container//-------------------------------------------------voidrender_target::add_container_primitives(render_primitive_list&list,constobject_transform&root_xform,constobject_transform&xform,render_container&container,intblendmode){// first update the palette for the container, if it is dirtycontainer.update_palette();// compute the clip rectrender_boundscliprect;cliprect.x0=xform.xoffs;cliprect.y0=xform.yoffs;cliprect.x1=xform.xoffs+xform.xscale;cliprect.y1=xform.yoffs+xform.yscale;sect_render_bounds(cliprect,m_bounds);floatroot_xoffs=root_xform.xoffs+fabsf(root_xform.xscale-xform.xscale)*0.5f;floatroot_yoffs=root_xform.yoffs+fabsf(root_xform.yscale-xform.yscale)*0.5f;render_boundsroot_cliprect;root_cliprect.x0=root_xoffs;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);// compute the container transformobject_transformcontainer_xform;container_xform.orientation=orientation_add(container.orientation(),xform.orientation);{floatxscale=(container_xform.orientation&ORIENTATION_SWAP_XY)?container.yscale():container.xscale();floatyscale=(container_xform.orientation&ORIENTATION_SWAP_XY)?container.xscale():container.yscale();floatxoffs=(container_xform.orientation&ORIENTATION_SWAP_XY)?container.yoffset():container.xoffset();floatyoffs=(container_xform.orientation&ORIENTATION_SWAP_XY)?container.xoffset():container.yoffset();if(container_xform.orientation&ORIENTATION_FLIP_X)xoffs=-xoffs;if(container_xform.orientation&ORIENTATION_FLIP_Y)yoffs=-yoffs;if(!m_transform_container){xscale=1.0f;yscale=1.0f;xoffs=0.0f;yoffs=0.0f;}container_xform.xscale=xform.xscale*xscale;container_xform.yscale=xform.yscale*yscale;if(xform.no_center){container_xform.xoffs=xform.xscale*(xoffs)+xform.xoffs;container_xform.yoffs=xform.yscale*(yoffs)+xform.yoffs;}else{container_xform.xoffs=xform.xscale*(0.5f-0.5f*xscale+xoffs)+xform.xoffs;container_xform.yoffs=xform.yscale*(0.5f-0.5f*yscale+yoffs)+xform.yoffs;}container_xform.color=xform.color;}// iterate over elementsfor(render_container::item&curitem:container.items()){// compute the oriented boundsrender_boundsbounds=curitem.bounds();apply_orientation(bounds,container_xform.orientation);floatxscale=container_xform.xscale;floatyscale=container_xform.yscale;floatxoffs=container_xform.xoffs;floatyoffs=container_xform.yoffs;if(!m_transform_container&&PRIMFLAG_GET_VECTOR(curitem.flags())){xoffs=root_xoffs;yoffs=root_yoffs;}// allocate the primitive and set the transformed bounds/color datarender_primitive*prim=list.alloc(render_primitive::INVALID);prim->container=&container;/* pass the container along for access to user_settings */prim->bounds.x0=render_round_nearest(xoffs+bounds.x0*xscale);prim->bounds.y0=render_round_nearest(yoffs+bounds.y0*yscale);if(curitem.internal()&INTERNAL_FLAG_CHAR){prim->bounds.x1=prim->bounds.x0+render_round_nearest((bounds.x1-bounds.x0)*xscale);prim->bounds.y1=prim->bounds.y0+render_round_nearest((bounds.y1-bounds.y0)*yscale);}else{prim->bounds.x1=render_round_nearest(xoffs+bounds.x1*xscale);prim->bounds.y1=render_round_nearest(yoffs+bounds.y1*yscale);}// compute the color of the primitiveprim->color.r=container_xform.color.r*curitem.color().r;prim->color.g=container_xform.color.g*curitem.color().g;prim->color.b=container_xform.color.b*curitem.color().b;prim->color.a=container_xform.color.a*curitem.color().a;// copy unclipped boundsprim->full_bounds=prim->bounds;// now switch off the typeboolclipped=true;switch(curitem.type()){caseCONTAINER_ITEM_LINE:// adjust the color for brightness/contrast/gammaprim->color.a=container.apply_brightness_contrast_gamma_fp(prim->color.a);prim->color.r=container.apply_brightness_contrast_gamma_fp(prim->color.r);prim->color.g=container.apply_brightness_contrast_gamma_fp(prim->color.g);prim->color.b=container.apply_brightness_contrast_gamma_fp(prim->color.b);// set the line typeprim->type=render_primitive::LINE;prim->flags|=PRIMFLAG_TYPE_LINE;// scale the width by the minimum of X/Y scale factorsprim->width=curitem.width()*std::min(container_xform.xscale,container_xform.yscale);prim->flags|=curitem.flags();// clip the primitiveif(!m_transform_container&&PRIMFLAG_GET_VECTOR(curitem.flags())){clipped=render_clip_line(&prim->bounds,&root_cliprect);}else{clipped=render_clip_line(&prim->bounds,&cliprect);}break;caseCONTAINER_ITEM_QUAD:// set the quad typeprim->type=render_primitive::QUAD;prim->flags|=PRIMFLAG_TYPE_QUAD;// normalize the boundsnormalize_bounds(prim->bounds);// get the scaled bitmap and set the resulting paletteif(curitem.texture()!=nullptr){// determine the final orientationintfinalorient=orientation_add(PRIMFLAG_GET_TEXORIENT(curitem.flags()),container_xform.orientation);// based on the swap values, get the scaled final textureintwidth=(finalorient&ORIENTATION_SWAP_XY)?(prim->bounds.y1-prim->bounds.y0):(prim->bounds.x1-prim->bounds.x0);intheight=(finalorient&ORIENTATION_SWAP_XY)?(prim->bounds.x1-prim->bounds.x0):(prim->bounds.y1-prim->bounds.y0);width=std::min(width,m_maxtexwidth);height=std::min(height,m_maxtexheight);curitem.texture()->get_scaled(width,height,prim->texture,list,curitem.flags());// set the paletteprim->texture.palette=curitem.texture()->get_adjusted_palette(container,prim->texture.palette_length);// determine UV coordinatesprim->texcoords=oriented_texcoords[finalorient];// apply clippingclipped=render_clip_quad(&prim->bounds,&cliprect,&prim->texcoords);// apply the final orientation from the quad flags and then build up the final flagsprim->flags|=(curitem.flags()&~(PRIMFLAG_TEXORIENT_MASK|PRIMFLAG_BLENDMODE_MASK|PRIMFLAG_TEXFORMAT_MASK))|PRIMFLAG_TEXORIENT(finalorient)|PRIMFLAG_TEXFORMAT(curitem.texture()->format());prim->flags|=blendmode!=-1?PRIMFLAG_BLENDMODE(blendmode):PRIMFLAG_BLENDMODE(PRIMFLAG_GET_BLENDMODE(curitem.flags()));}else{// adjust the color for brightness/contrast/gammaprim->color.r=container.apply_brightness_contrast_gamma_fp(prim->color.r);prim->color.g=container.apply_brightness_contrast_gamma_fp(prim->color.g);prim->color.b=container.apply_brightness_contrast_gamma_fp(prim->color.b);// no textureprim->texture.base=nullptr;if(PRIMFLAG_GET_VECTORBUF(curitem.flags())){// flags X(1) flip-x, Y(2) flip-y, S(4) swap-xy//// X Y S e.g. flips// 0 0 0 asteroid !X !Y// 0 0 1 - X Y// 0 1 0 speedfrk !X Y// 0 1 1 tempest !X Y// 1 0 0 - X !Y// 1 0 1 - x !Y// 1 1 0 solarq X Y// 1 1 1 barrier !X !Yboolflip_x=(m_manager.machine().system().flags&ORIENTATION_FLIP_X)==ORIENTATION_FLIP_X;boolflip_y=(m_manager.machine().system().flags&ORIENTATION_FLIP_Y)==ORIENTATION_FLIP_Y;boolswap_xy=(m_manager.machine().system().flags&ORIENTATION_SWAP_XY)==ORIENTATION_SWAP_XY;intvectororient=0;if(flip_x){vectororient|=ORIENTATION_FLIP_X;}if(flip_y){vectororient|=ORIENTATION_FLIP_Y;}if((flip_x&&flip_y&&swap_xy)||(!flip_x&&!flip_y&&swap_xy)){vectororient^=ORIENTATION_FLIP_X;vectororient^=ORIENTATION_FLIP_Y;}// determine the final orientation (textures are up-side down, so flip axis for vectors to immitate that behavior)intfinalorient=orientation_add(vectororient,container_xform.orientation);// determine UV coordinatesprim->texcoords=oriented_texcoords[finalorient];// apply clippingclipped=render_clip_quad(&prim->bounds,&cliprect,&prim->texcoords);// apply the final orientation from the quad flags and then build up the final flagsprim->flags|=(curitem.flags()&~(PRIMFLAG_TEXORIENT_MASK|PRIMFLAG_BLENDMODE_MASK|PRIMFLAG_TEXFORMAT_MASK))|PRIMFLAG_TEXORIENT(finalorient);prim->flags|=blendmode!=-1?PRIMFLAG_BLENDMODE(blendmode):PRIMFLAG_BLENDMODE(PRIMFLAG_GET_BLENDMODE(curitem.flags()));}else{// set the basic flagsprim->flags|=(curitem.flags()&~PRIMFLAG_BLENDMODE_MASK)|PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA);// apply clippingclipped=render_clip_quad(&prim->bounds,&cliprect,nullptr);}}break;}// add to the list or free if we're clipped outlist.append_or_return(*prim,clipped);}// add the overlay if it existsif(container.overlay()!=nullptr&&m_layerconfig.screen_overlay_enabled()){s32width,height;// allocate a primitiverender_primitive*prim=list.alloc(render_primitive::QUAD);set_render_bounds_wh(prim->bounds,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);height=render_round_nearest(prim->bounds.y1)-render_round_nearest(prim->bounds.y0);container.overlay()->get_scaled((container_xform.orientation&ORIENTATION_SWAP_XY)?height:width,(container_xform.orientation&ORIENTATION_SWAP_XY)?width:height,prim->texture,list);// determine UV coordinatesprim->texcoords=oriented_texcoords[container_xform.orientation];// set the flags and add it to the listprim->flags=PRIMFLAG_TEXORIENT(container_xform.orientation)|PRIMFLAG_BLENDMODE(BLENDMODE_RGB_MULTIPLY)|PRIMFLAG_TEXFORMAT(container.overlay()->format())|PRIMFLAG_TEXSHADE(1);list.append_or_return(*prim,false);}}//-------------------------------------------------// add_element_primitives - add the primitive// for an element in the current state//-------------------------------------------------voidrender_target::add_element_primitives(render_primitive_list&list,constobject_transform&xform,layout_element&element,intstate,intblendmode){// if we're out of range, bailif(state>element.maxstate())return;if(state<0)state=0;// get a pointer to the relevant texturerender_texture*texture=element.state_texture(state);if(texture!=nullptr){render_primitive*prim=list.alloc(render_primitive::QUAD);// configure the basicsprim->color=xform.color;prim->flags=PRIMFLAG_TEXORIENT(xform.orientation)|PRIMFLAG_BLENDMODE(blendmode)|PRIMFLAG_TEXFORMAT(texture->format());// compute the boundss32width=render_round_nearest(xform.xscale);s32height=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);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 ittexture->get_scaled(width,height,prim->texture,list,prim->flags);// compute the clip rectrender_boundscliprect;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);// determine UV coordinates and apply clippingprim->texcoords=oriented_texcoords[xform.orientation];boolclipped=render_clip_quad(&prim->bounds,&cliprect,&prim->texcoords);// add to the list or free if we're clipped outlist.append_or_return(*prim,clipped);}}//-------------------------------------------------// map_point_internal - internal logic for// mapping points//-------------------------------------------------boolrender_target::map_point_internal(s32target_x,s32target_y,render_container*container,float&mapped_x,float&mapped_y,ioport_port*&mapped_input_port,ioport_value&mapped_input_mask){// compute the visible width/heights32viswidth,visheight;compute_visible_area(m_width,m_height,m_pixel_aspect,m_orientation,viswidth,visheight);// create a root transform for the targetobject_transformroot_xform;root_xform.xoffs=(float)(m_width-viswidth)/2;root_xform.yoffs=(float)(m_height-visheight)/2;// default to point not mappedmapped_x=-1.0;mapped_y=-1.0;mapped_input_port=nullptr;mapped_input_mask=0;// convert target coordinates to floatfloattarget_fx=(float)(target_x-root_xform.xoffs)/viswidth;floattarget_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 containerif(container!=nullptr&&container==&m_manager.ui_container()){// this hit test went against the UI containerif(target_fx>=0.0f&&target_fx<1.0f&&target_fy>=0.0f&&target_fy<1.0f){// this point was successfully mappedmapped_x=(float)target_x/m_width;mapped_y=(float)target_y/m_height;returntrue;}returnfalse;}// iterate over items in the viewfor(layout_view::item&item:m_curview->items()){boolcheckit;// if we're looking for a particular container, verify that we have the right oneif(container!=nullptr)checkit=(item.screen()!=nullptr&&&item.screen()->container()==container);// otherwise, assume we're looking for an inputelsecheckit=item.has_input();// this target is worth looking at; now check the pointif(checkit&&target_fx>=item.bounds().x0&&target_fx<item.bounds().x1&&target_fy>=item.bounds().y0&&target_fy<item.bounds().y1){// point successfully mappedmapped_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);returntrue;}}returnfalse;}//-------------------------------------------------// view_name - return the name of the indexed// view, or nullptr if it doesn't exist//-------------------------------------------------layout_view*render_target::view_by_index(intindex){// scan the list of views within each layout, skipping those that don't applyfor(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;returnnullptr;}//-------------------------------------------------// view_index - return the index of the given// view//-------------------------------------------------intrender_target::view_index(layout_view&targetview)const{// find the first named matchintindex=0;// scan the list of views within each layout, skipping those that don't applyfor(layout_fileconst&file:m_filelist)for(layout_viewconst&view:file.views())if(!(m_flags&RENDER_CREATE_NO_ART)||!view.has_art()){if(&targetview==&view)returnindex;index++;}return0;}//-------------------------------------------------// config_load - process config information//-------------------------------------------------voidrender_target::config_load(util::xml::data_nodeconst&targetnode){inttmpint;// find the viewconstchar*viewname=targetnode.get_attribute_string("view",nullptr);if(viewname!=nullptr)for(intviewnum=0;viewnum<1000;viewnum++){constchar*testname=view_name(viewnum);if(testname==nullptr)break;if(!strcmp(viewname,testname)){set_view(viewnum);break;}}// modify the artwork configtmpint=targetnode.get_attribute_int("zoom",-1);if(tmpint==0||tmpint==1)set_zoom_to_screen(tmpint);// apply orientationtmpint=targetnode.get_attribute_int("rotate",-1);if(tmpint!=-1){if(tmpint==90)tmpint=ROT90;elseif(tmpint==180)tmpint=ROT180;elseif(tmpint==270)tmpint=ROT270;elsetmpint=ROT0;set_orientation(orientation_add(tmpint,orientation()));// apply the opposite orientation to the UIif(is_ui_target()){render_container::user_settingssettings;render_container&ui_container=m_manager.ui_container();ui_container.get_user_settings(settings);settings.m_orientation=orientation_add(orientation_reverse(tmpint),settings.m_orientation);ui_container.set_user_settings(settings);}}}//-------------------------------------------------// config_save - save our configuration, or// return false if we are the same as the default//-------------------------------------------------boolrender_target::config_save(util::xml::data_node&targetnode){boolchanged=false;// output the basicstargetnode.set_attribute_int("index",index());// output the viewif(m_curview!=m_base_view){targetnode.set_attribute("view",m_curview->name().c_str());changed=true;}// output the layer configif(m_layerconfig!=m_base_layerconfig){targetnode.set_attribute_int("zoom",m_layerconfig.zoom_to_screen());changed=true;}// output rotationif(m_orientation!=m_base_orientation){introtate=0;if(orientation_add(ROT90,m_base_orientation)==m_orientation)rotate=90;elseif(orientation_add(ROT180,m_base_orientation)==m_orientation)rotate=180;elseif(orientation_add(ROT270,m_base_orientation)==m_orientation)rotate=270;assert(rotate!=0);targetnode.set_attribute_int("rotate",rotate);changed=true;}returnchanged;}//-------------------------------------------------// init_clear_extents - reset the extents list//-------------------------------------------------voidrender_target::init_clear_extents(){m_clear_extents[0]=-m_height;m_clear_extents[1]=1;m_clear_extents[2]=m_width;m_clear_extent_count=3;}//-------------------------------------------------// remove_clear_extent - remove a quad from the// list of stuff to clear, unless it overlaps// a previous quad//-------------------------------------------------boolrender_target::remove_clear_extent(constrender_bounds&bounds){s32*max=&m_clear_extents[MAX_CLEAR_EXTENTS];s32*last=&m_clear_extents[m_clear_extent_count];s32*ext=&m_clear_extents[0];s32boundsx0=ceil(bounds.x0);s32boundsx1=floor(bounds.x1);s32boundsy0=ceil(bounds.y0);s32boundsy1=floor(bounds.y1);s32y0,y1=0;// loop over Y extentswhile(ext<last){s32*linelast;// first entry of each line should always be negativeassert(ext[0]<0.0f);y0=y1;y1=y0-ext[0];// do we intersect this extent?if(boundsy0<y1&&boundsy1>y0){s32*xext;s32x0,x1=0;// split the topif(y0<boundsy0){intdiff=boundsy0-y0;// make a copy of this extentmemmove(&ext[ext[1]+2],&ext[0],(last-ext)*sizeof(*ext));last+=ext[1]+2;if(last>=max)throwemu_fatalerror("render_target::remove_clear_extent: Ran out of clear extents!");// split the extent between piecesext[ext[1]+2]=-(-ext[0]-diff);ext[0]=-diff;// advance to the new extenty0-=ext[0];ext+=ext[1]+2;y1=y0-ext[0];}// split the bottomif(y1>boundsy1){intdiff=y1-boundsy1;// make a copy of this extentmemmove(&ext[ext[1]+2],&ext[0],(last-ext)*sizeof(*ext));last+=ext[1]+2;if(last>=max)throwemu_fatalerror("render_target::remove_clear_extent: Ran out of clear extents!");// split the extent between piecesext[ext[1]+2]=-diff;ext[0]=-(-ext[0]-diff);// recompute y1y1=y0-ext[0];}// now remove the X extentlinelast=&ext[ext[1]+2];xext=&ext[2];while(xext<linelast){x0=x1;x1=x0+xext[0];// do we fully intersect this extent?if(boundsx0>=x0&&boundsx1<=x1){// yes; split itmemmove(&xext[2],&xext[0],(last-xext)*sizeof(*xext));last+=2;linelast+=2;if(last>=max)throwemu_fatalerror("render_target::remove_clear_extent: Ran out of clear extents!");// split this extent into three partsxext[0]=boundsx0-x0;xext[1]=boundsx1-boundsx0;xext[2]=x1-boundsx1;// recompute x1x1=boundsx1;xext+=2;}// do we partially intersect this extent?elseif(boundsx0<x1&&boundsx1>x0)gotoabort;// advancexext++;// do we partially intersect the next extent (which is a non-clear extent)?if(xext<linelast){x0=x1;x1=x0+xext[0];if(boundsx0<x1&&boundsx1>x0)gotoabort;xext++;}}// update the countext[1]=linelast-&ext[2];}// advance to the next rowext+=2+ext[1];}// update the total countm_clear_extent_count=last-&m_clear_extents[0];returntrue;abort:// update the total count even on a failure as we may have split extentsm_clear_extent_count=last-&m_clear_extents[0];returnfalse;}//-------------------------------------------------// add_clear_extents - add the accumulated// extents as a series of quads to clear//-------------------------------------------------voidrender_target::add_clear_extents(render_primitive_list&list){simple_list<render_primitive>clearlist;s32*last=&m_clear_extents[m_clear_extent_count];s32*ext=&m_clear_extents[0];s32y0,y1=0;// loop over all extentswhile(ext<last){s32*linelast=&ext[ext[1]+2];s32*xext=&ext[2];s32x0,x1=0;// first entry should always be negativeassert(ext[0]<0);y0=y1;y1=y0-ext[0];// now remove the X extentwhile(xext<linelast){x0=x1;x1=x0+*xext++;// only add entries for non-zero widthsif(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->full_bounds=prim->bounds;set_render_color(&prim->color,1.0f,0.0f,0.0f,0.0f);prim->texture.base=nullptr;prim->flags=PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA);clearlist.append(*prim);}// skip the non-clearing extentx0=x1;x1=x0+*xext++;}// advance to the next partext+=2+ext[1];}// we know that the first primitive in the list will be the global clip// so we insert the clears immediately afterlist.m_primlist.prepend_list(clearlist);}//-------------------------------------------------// add_clear_and_optimize_primitive_list -// optimize the primitive list//-------------------------------------------------voidrender_target::add_clear_and_optimize_primitive_list(render_primitive_list&list){// start with the assumption that we need to clear the whole screeninit_clear_extents();// scan the list until we hit an intersection quad or a linefor(render_primitive&prim:list){// switch off the typeswitch(prim.type){caserender_primitive::LINE:gotodone;caserender_primitive::QUAD:{// stop when we hit an alpha textureif(PRIMFLAG_GET_TEXFORMAT(prim.flags)==TEXFORMAT_ARGB32)gotodone;// if this quad can't be cleanly removed from the extents list, we're doneif(!remove_clear_extent(prim.bounds))gotodone;// change the blendmode on the first primitive to be NONEif(PRIMFLAG_GET_BLENDMODE(prim.flags)==BLENDMODE_RGB_MULTIPLY){// RGB multiply will multiply against 0, leaving nothingset_render_color(&prim.color,1.0f,0.0f,0.0f,0.0f);prim.texture.base=nullptr;prim.flags=(prim.flags&~PRIMFLAG_BLENDMODE_MASK)|PRIMFLAG_BLENDMODE(BLENDMODE_NONE);}else{// for alpha or add modes, we will blend against 0 or add to 0; treat it like noneprim.flags=(prim.flags&~PRIMFLAG_BLENDMODE_MASK)|PRIMFLAG_BLENDMODE(BLENDMODE_NONE);}// since alpha is disabled, premultiply the RGB values and reset the alpha to 1.0prim.color.r*=prim.color.a;prim.color.g*=prim.color.a;prim.color.b*=prim.color.a;prim.color.a=1.0f;break;}default:throwemu_fatalerror("Unexpected primitive type");}}done:// now add the extents to the clear listadd_clear_extents(list);}//**************************************************************************// CORE IMPLEMENTATION//**************************************************************************//-------------------------------------------------// render_manager - constructor//-------------------------------------------------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))){// register callbacksmachine.configuration().config_register("video",config_load_delegate(&render_manager::config_load,this),config_save_delegate(&render_manager::config_save,this));// create one container per screenfor(screen_device&screen:screen_device_iterator(machine.root_device()))screen.set_container(*container_alloc(&screen));}//-------------------------------------------------// ~render_manager - destructor//-------------------------------------------------render_manager::~render_manager(){// free all the containers since they may own texturescontainer_free(m_ui_container);m_screen_container_list.reset();// better not be any outstanding textures when we dieassert(m_live_textures==0);}//-------------------------------------------------// is_live - return if the screen is 'live'//-------------------------------------------------boolrender_manager::is_live(screen_device&screen)const{// iterate over all live targets and or together their screen masksfor(render_targetconst&target:m_targetlist){if(!target.hidden()){layout_viewconst*view=target.current_view();if(view!=nullptr&&view->has_screen(screen))returntrue;}}returnfalse;}//-------------------------------------------------// max_update_rate - return the smallest maximum// update rate across all targets//-------------------------------------------------floatrender_manager::max_update_rate()const{// iterate over all live targets and or together their screen masksfloatminimum=0;for(render_target&target:m_targetlist)if(target.max_update_rate()!=0){if(minimum==0)minimum=target.max_update_rate();elseminimum=std::min(target.max_update_rate(),minimum);}returnminimum;}//-------------------------------------------------// target_alloc - allocate a new target//-------------------------------------------------render_target*render_manager::target_alloc(constinternal_layout*layoutfile,u32flags){return&m_targetlist.append(*global_alloc(render_target(*this,layoutfile,flags)));}render_target*render_manager::target_alloc(util::xml::data_nodeconst&layout,u32flags){return&m_targetlist.append(*global_alloc(render_target(*this,layout,flags)));}//-------------------------------------------------// target_free - free a target//-------------------------------------------------voidrender_manager::target_free(render_target*target){if(target!=nullptr)m_targetlist.remove(*target);}//-------------------------------------------------// target_by_index - get a render_target by index//-------------------------------------------------render_target*render_manager::target_by_index(intindex)const{// count up the targets until we hit the requested indexfor(render_target&target:m_targetlist)if(!target.hidden())if(index--==0)return⌖returnnullptr;}//-------------------------------------------------// ui_aspect - return the aspect ratio for UI// fonts//-------------------------------------------------floatrender_manager::ui_aspect(render_container*rc){intorient;floataspect;if(rc==m_ui_container||rc==nullptr){// ui container, aggregated multi-screen targetorient=orientation_add(m_ui_target->orientation(),m_ui_container->orientation());// based on the orientation of the target, compute height/width or width/heightif(!(orient&ORIENTATION_SWAP_XY))aspect=(float)m_ui_target->height()/(float)m_ui_target->width();elseaspect=(float)m_ui_target->width()/(float)m_ui_target->height();// if we have a valid pixel aspect, apply that and returnif(m_ui_target->pixel_aspect()!=0.0f){floatpixel_aspect=m_ui_target->pixel_aspect();if(orient&ORIENTATION_SWAP_XY)pixel_aspect=1.0f/pixel_aspect;returnaspect/=pixel_aspect;}}else{// single screen containerorient=rc->orientation();// based on the orientation of the target, compute height/width or width/heightif(!(orient&ORIENTATION_SWAP_XY))aspect=(float)rc->screen()->visible_area().height()/(float)rc->screen()->visible_area().width();elseaspect=(float)rc->screen()->visible_area().width()/(float)rc->screen()->visible_area().height();}// clamp for extreme proportionsif(aspect<0.66f)aspect=0.66f;if(aspect>1.5f)aspect=1.5f;returnaspect;}//-------------------------------------------------// texture_alloc - allocate a new texture//-------------------------------------------------render_texture*render_manager::texture_alloc(texture_scaler_funcscaler,void*param){// allocate a new texture and reset itrender_texture*tex=m_texture_allocator.alloc();tex->reset(*this,scaler,param);tex->set_id(m_texture_id);m_texture_id++;m_live_textures++;returntex;}//-------------------------------------------------// texture_free - release a texture//-------------------------------------------------voidrender_manager::texture_free(render_texture*texture){if(texture!=nullptr){m_live_textures--;texture->release();}m_texture_allocator.reclaim(texture);}//-------------------------------------------------// font_alloc - allocate a new font instance//-------------------------------------------------render_font*render_manager::font_alloc(constchar*filename){returnglobal_alloc(render_font(*this,filename));}//-------------------------------------------------// font_free - release a font instance//-------------------------------------------------voidrender_manager::font_free(render_font*font){global_free(font);}//-------------------------------------------------// invalidate_all - remove all refs to a// particular reference pointer//-------------------------------------------------voidrender_manager::invalidate_all(void*refptr){// permit nullptrif(refptr==nullptr)return;// loop over targetsfor(render_target&target:m_targetlist)target.invalidate_all(refptr);}//-------------------------------------------------// resolve_tags - resolve tag lookups//-------------------------------------------------voidrender_manager::resolve_tags(){for(render_target&target:m_targetlist)target.resolve_tags();}//-------------------------------------------------// container_alloc - allocate a new container//-------------------------------------------------render_container*render_manager::container_alloc(screen_device*screen){autocontainer=global_alloc(render_container(*this,screen));if(screen!=nullptr)m_screen_container_list.append(*container);returncontainer;}//-------------------------------------------------// container_free - release a container//-------------------------------------------------voidrender_manager::container_free(render_container*container){m_screen_container_list.remove(*container);}//-------------------------------------------------// config_load - read and apply data from the// configuration file//-------------------------------------------------voidrender_manager::config_load(config_typecfg_type,util::xml::data_nodeconst*parentnode){// we only care about game filesif(cfg_type!=config_type::GAME)return;// might not have any dataif(parentnode==nullptr)return;// check the UI targetutil::xml::data_nodeconst*constuinode=parentnode->get_child("interface");if(uinode!=nullptr){render_target*target=target_by_index(uinode->get_attribute_int("target",0));if(target!=nullptr)set_ui_target(*target);}// iterate over target nodesfor(util::xml::data_nodeconst*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);}// iterate over screen nodesfor(util::xml::data_nodeconst*screennode=parentnode->get_child("screen");screennode;screennode=screennode->get_next_sibling("screen")){intindex=screennode->get_attribute_int("index",-1);render_container*container=m_screen_container_list.find(index);render_container::user_settingssettings;// fetch current settingscontainer->get_user_settings(settings);// fetch color controlssettings.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 controlssettings.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 valuescontainer->set_user_settings(settings);}}//-------------------------------------------------// config_save - save data to the configuration// file//-------------------------------------------------voidrender_manager::config_save(config_typecfg_type,util::xml::data_node*parentnode){// we only care about game filesif(cfg_type!=config_type::GAME)return;// write out the interface targetif(m_ui_target->index()!=0){// create a node for itutil::xml::data_node*constuinode=parentnode->add_child("interface",nullptr);if(uinode!=nullptr)uinode->set_attribute_int("target",m_ui_target->index());}// iterate over targetsfor(inttargetnum=0;targetnum<1000;targetnum++){// get this target and break when we failrender_target*target=target_by_index(targetnum);if(target==nullptr)break;// create a nodeutil::xml::data_node*consttargetnode=parentnode->add_child("target",nullptr);if(targetnode!=nullptr&&!target->config_save(*targetnode))targetnode->delete_node();}// iterate over screen containersintscrnum=0;for(render_container*container=m_screen_container_list.first();container!=nullptr;container=container->next(),scrnum++){// create a nodeutil::xml::data_node*constscreennode=parentnode->add_child("screen",nullptr);if(screennode!=nullptr){boolchanged=false;// output the basicsscreennode->set_attribute_int("index",scrnum);render_container::user_settingssettings;container->get_user_settings(settings);// output the color controlsif(settings.m_brightness!=machine().options().brightness()){screennode->set_attribute_float("brightness",settings.m_brightness);changed=true;}if(settings.m_contrast!=machine().options().contrast()){screennode->set_attribute_float("contrast",settings.m_contrast);changed=true;}if(settings.m_gamma!=machine().options().gamma()){screennode->set_attribute_float("gamma",settings.m_gamma);changed=true;}// output the positioning controlsif(settings.m_xoffset!=0.0f){screennode->set_attribute_float("hoffset",settings.m_xoffset);changed=true;}if(settings.m_xscale!=1.0f){screennode->set_attribute_float("hstretch",settings.m_xscale);changed=true;}if(settings.m_yoffset!=0.0f){screennode->set_attribute_float("voffset",settings.m_yoffset);changed=true;}if(settings.m_yscale!=1.0f){screennode->set_attribute_float("vstretch",settings.m_yscale);changed=true;}// if nothing changed, kill the nodeif(!changed)screennode->delete_node();}}}