From 46d4d187b403fe7b55dde50da93b8584735a64a9 Mon Sep 17 00:00:00 2001 From: kevinz000 <2003111+kevinz000@users.noreply.github.com> Date: Thu, 20 Dec 2018 19:09:46 -0800 Subject: [PATCH 1/2] Speeds up getFlatIcon (#5765) * GetFlatIcon speedup * profiling * uhh * Woohoo! --- code/_helpers/icons.dm | 320 +++++++++++++++++++++-------------------- 1 file changed, 162 insertions(+), 158 deletions(-) diff --git a/code/_helpers/icons.dm b/code/_helpers/icons.dm index 32af8ac846..a1e7c8ea59 100644 --- a/code/_helpers/icons.dm +++ b/code/_helpers/icons.dm @@ -627,22 +627,44 @@ proc/ColorTone(rgb, tone) if(gray <= tone_gray) return BlendRGB("#000000", tone, gray/(tone_gray || 1)) else return BlendRGB(tone, "#ffffff", (gray-tone_gray)/((255-tone_gray) || 1)) - -/* -Get flat icon by DarkCampainger. As it says on the tin, will return an icon with all the overlays -as a single icon. Useful for when you want to manipulate an icon via the above as overlays are not normally included. -The _flatIcons list is a cache for generated icon files. -*/ - +// Ported from /tg/station +// Creates a single icon from a given /atom or /image. Only the first argument is required. /proc/getFlatIcon(image/A, defdir, deficon, defstate, defblend, start = TRUE, no_anim = FALSE) - // We start with a blank canvas, otherwise some icon procs crash silently - var/icon/flat = icon('icons/effects/effects.dmi', "nothing") // Final flattened icon - if(!A) - return flat - if(A.alpha <= 0) - return flat - var/noIcon = FALSE + //Define... defines. + var/static/icon/flat_template = icon('icons/effects/effects.dmi', "nothing") + #define BLANK icon(flat_template) + #define SET_SELF(SETVAR) do { \ + var/icon/SELF_ICON=icon(icon(curicon, curstate, base_icon_dir),"",SOUTH,no_anim?1:null); \ + if(A.alpha<255) { \ + SELF_ICON.Blend(rgb(255,255,255,A.alpha),ICON_MULTIPLY);\ + } \ + if(A.color) { \ + if(islist(A.color)){ \ + SELF_ICON.MapColors(arglist(A.color))} \ + else{ \ + SELF_ICON.Blend(A.color,ICON_MULTIPLY)} \ + } \ + ##SETVAR=SELF_ICON;\ + } while (0) + #define INDEX_X_LOW 1 + #define INDEX_X_HIGH 2 + #define INDEX_Y_LOW 3 + #define INDEX_Y_HIGH 4 + + #define flatX1 flat_size[INDEX_X_LOW] + #define flatX2 flat_size[INDEX_X_HIGH] + #define flatY1 flat_size[INDEX_Y_LOW] + #define flatY2 flat_size[INDEX_Y_HIGH] + #define addX1 add_size[INDEX_X_LOW] + #define addX2 add_size[INDEX_X_HIGH] + #define addY1 add_size[INDEX_Y_LOW] + #define addY2 add_size[INDEX_Y_HIGH] + + if(!A || A.alpha <= 0) + return BLANK + + var/noIcon = FALSE if(start) if(!defdir) defdir = A.dir @@ -653,26 +675,16 @@ The _flatIcons list is a cache for generated icon files. if(!defblend) defblend = A.blend_mode - var/curicon - if(A.icon) - curicon = A.icon - else - curicon = deficon + var/curicon = A.icon || deficon + var/curstate = A.icon_state || defstate - if(!curicon) - noIcon = TRUE // Do not render this object. - - var/curstate - if(A.icon_state) - curstate = A.icon_state - else - curstate = defstate - - if(!noIcon && !(curstate in icon_states(curicon))) - if("" in icon_states(curicon)) - curstate = "" - else - noIcon = TRUE // Do not render this object. + if(!((noIcon = (!curicon)))) + var/curstates = icon_states(curicon) + if(!(curstate in curstates)) + if("" in curstates) + curstate = "" + else + noIcon = TRUE // Do not render this object. var/curdir var/base_icon_dir //We'll use this to get the icon state to display if not null BUT NOT pass it to overlays as the dir we have @@ -683,148 +695,140 @@ The _flatIcons list is a cache for generated icon files. else curdir = A.dir - //Let's check if the icon actually contains any diagonals, just skip if it's south to save (lot of) time - if(curdir != SOUTH) - var/icon/test_icon - var/directionals_exist = FALSE - var/list/dirs_to_check = cardinal - SOUTH - outer: - for(var/possible_dir in dirs_to_check) - test_icon = icon(curicon,curstate,possible_dir,frame=1) - for(var/x in 1 to world.icon_size) - for(var/y in 1 to world.icon_size) - if(!isnull(test_icon.GetPixel(x,y))) - directionals_exist = TRUE - break outer - if(!directionals_exist) + //Try to remove/optimize this section ASAP, CPU hog. + //Determines if there's directionals. + if(!noIcon && curdir != SOUTH) + var/exist = FALSE + var/static/list/checkdirs = list(NORTH, EAST, WEST) + for(var/i in checkdirs) //Not using GLOB for a reason. + if(length(icon_states(icon(curicon, curstate, i)))) + exist = TRUE + break + if(!exist) base_icon_dir = SOUTH + // + if(!base_icon_dir) base_icon_dir = curdir - var/curblend - if(A.blend_mode == BLEND_DEFAULT) - curblend = defblend - else - curblend = A.blend_mode + ASSERT(!BLEND_DEFAULT) //I might just be stupid but lets make sure this define is 0. - // Before processing overlays, make sure any pending overlays are applied - if (isloc(A)) - var/atom/aAtom = A - if(aAtom.flags & OVERLAY_QUEUED) - COMPILE_OVERLAYS(aAtom) + var/curblend = A.blend_mode || defblend - // Layers will be a sorted list of icons/overlays, based on the order in which they are displayed - var/list/layers = list() - var/image/copy - // Add the atom's icon itself, without pixel_x/y offsets. - if(!noIcon) - copy = image(icon=curicon, icon_state=curstate, layer=A.layer, dir=base_icon_dir) - copy.color = A.color - copy.alpha = A.alpha - copy.blend_mode = curblend - layers[copy] = A.layer + if(A.overlays.len || A.underlays.len) + var/icon/flat = BLANK + // Layers will be a sorted list of icons/overlays, based on the order in which they are displayed + var/list/layers = list() + var/image/copy + // Add the atom's icon itself, without pixel_x/y offsets. + if(!noIcon) + copy = image(icon=curicon, icon_state=curstate, layer=A.layer, dir=base_icon_dir) + copy.color = A.color + copy.alpha = A.alpha + copy.blend_mode = curblend + layers[copy] = A.layer - // Loop through the underlays, then overlays, sorting them into the layers list - var/list/process = A.underlays // Current list being processed - var/pSet=0 // Which list is being processed: 0 = underlays, 1 = overlays - var/curIndex=1 // index of 'current' in list being processed - var/current // Current overlay being sorted - var/currentLayer // Calculated layer that overlay appears on (special case for FLOAT_LAYER) - var/compare // The overlay 'add' is being compared against - var/cmpIndex // The index in the layers list of 'compare' - while(TRUE) - if(curIndex<=process.len) - current = process[curIndex] - if(!current) - curIndex++ //Try the next layer + // Loop through the underlays, then overlays, sorting them into the layers list + for(var/process_set in 0 to 1) + var/list/process = process_set? A.overlays : A.underlays + for(var/i in 1 to process.len) + var/image/current = process[i] + if(!current) + continue + if(current.plane != FLOAT_PLANE && current.plane != A.plane) + continue + var/current_layer = current.layer + if(current_layer < 0) + if(current_layer <= -1000) + return flat + current_layer = process_set + A.layer + current_layer / 1000 + + for(var/p in 1 to layers.len) + var/image/cmp = layers[p] + if(current_layer < layers[cmp]) + layers.Insert(p, current) + break + layers[current] = current_layer + + //sortTim(layers, /proc/cmp_image_layer_asc) + + var/icon/add // Icon of overlay being added + + // Current dimensions of flattened icon + var/list/flat_size = list(1, flat.Width(), 1, flat.Height()) + // Dimensions of overlay being added + var/list/add_size[4] + + for(var/V in layers) + var/image/I = V + if(I.alpha == 0) continue - var/image/I = current - if(I.plane != FLOAT_PLANE && I.plane != A.plane) - curIndex++ + + if(I == copy) // 'I' is an /image based on the object being flattened. + curblend = BLEND_OVERLAY + add = icon(I.icon, I.icon_state, base_icon_dir) + else // 'I' is an appearance object. + add = getFlatIcon(image(I), curdir, curicon, curstate, curblend, FALSE, no_anim) + if(!add) continue - currentLayer = I.layer - if(currentLayer<0) // Special case for FLOAT_LAYER - if(currentLayer <= -1000) - return flat - if(pSet == 0) // Underlay - currentLayer = A.layer+currentLayer/1000 - else // Overlay - currentLayer = A.layer+(1000+currentLayer)/1000 + // Find the new dimensions of the flat icon to fit the added overlay + add_size = list( + min(flatX1, I.pixel_x+1), + max(flatX2, I.pixel_x+add.Width()), + min(flatY1, I.pixel_y+1), + max(flatY2, I.pixel_y+add.Height()) + ) - // Sort add into layers list - for(cmpIndex=1,cmpIndex<=layers.len,cmpIndex++) - compare = layers[cmpIndex] - if(currentLayer < layers[compare]) // Associated value is the calculated layer - layers.Insert(cmpIndex,current) - layers[current] = currentLayer - break - if(cmpIndex>layers.len) // Reached end of list without inserting - layers[current]=currentLayer // Place at end + if(flat_size ~! add_size) + // Resize the flattened icon so the new icon fits + flat.Crop( + addX1 - flatX1 + 1, + addY1 - flatY1 + 1, + addX2 - flatX1 + 1, + addY2 - flatY1 + 1 + ) + flat_size = add_size.Copy() - curIndex++ + // Blend the overlay into the flattened icon + flat.Blend(add, blendMode2iconMode(curblend), I.pixel_x + 2 - flatX1, I.pixel_y + 2 - flatY1) - if(curIndex>process.len) - if(pSet == 0) // Switch to overlays - curIndex = 1 - pSet = 1 - process = A.overlays - else // All done - break + if(A.color) + if(islist(A.color)) + flat.MapColors(arglist(A.color)) + else + flat.Blend(A.color, ICON_MULTIPLY) - var/icon/add // Icon of overlay being added + if(A.alpha < 255) + flat.Blend(rgb(255, 255, 255, A.alpha), ICON_MULTIPLY) - // Current dimensions of flattened icon - var/flatX1=1 - var/flatX2=flat.Width() - var/flatY1=1 - var/flatY2=flat.Height() - // Dimensions of overlay being added - var/addX1 - var/addX2 - var/addY1 - var/addY2 - - for(var/V in layers) - var/image/I = V - if(I.alpha == 0) - continue - - if(I == copy) // 'I' is an /image based on the object being flattened. - curblend = BLEND_OVERLAY - add = icon(I.icon, I.icon_state, base_icon_dir) - else // 'I' is an appearance object. - add = getFlatIcon(new/image(I), curdir, curicon, curstate, curblend, FALSE, no_anim) - - // Find the new dimensions of the flat icon to fit the added overlay - addX1 = min(flatX1, I.pixel_x+1) - addX2 = max(flatX2, I.pixel_x+add.Width()) - addY1 = min(flatY1, I.pixel_y+1) - addY2 = max(flatY2, I.pixel_y+add.Height()) - - if(addX1!=flatX1 || addX2!=flatX2 || addY1!=flatY1 || addY2!=flatY2) - // Resize the flattened icon so the new icon fits - flat.Crop(addX1-flatX1+1, addY1-flatY1+1, addX2-flatX1+1, addY2-flatY1+1) - flatX1=addX1;flatX2=addX2 - flatY1=addY1;flatY2=addY2 - - // Blend the overlay into the flattened icon - flat.Blend(add, blendMode2iconMode(curblend), I.pixel_x + 2 - flatX1, I.pixel_y + 2 - flatY1) - - if(A.color) - flat.Blend(A.color, ICON_MULTIPLY) - if(A.alpha < 255) - flat.Blend(rgb(255, 255, 255, A.alpha), ICON_MULTIPLY) - - if(no_anim) - //Clean up repeated frames - var/icon/cleaned = new /icon() - cleaned.Insert(flat, "", SOUTH, 1, 0) - return cleaned - else - return icon(flat, "", SOUTH) + if(no_anim) + //Clean up repeated frames + var/icon/cleaned = new /icon() + cleaned.Insert(flat, "", SOUTH, 1, 0) + . = cleaned + else + . = icon(flat, "", SOUTH) + else //There's no overlays. + if(!noIcon) + SET_SELF(.) + //Clear defines + #undef flatX1 + #undef flatX2 + #undef flatY1 + #undef flatY2 + #undef addX1 + #undef addX2 + #undef addY1 + #undef addY2 + #undef INDEX_X_LOW + #undef INDEX_X_HIGH + #undef INDEX_Y_LOW + #undef INDEX_Y_HIGH + #undef BLANK + #undef SET_SELF /proc/getIconMask(atom/A)//By yours truly. Creates a dynamic mask for a mob/whatever. /N var/icon/alpha_mask = new(A.icon,A.icon_state)//So we want the default icon and icon state of A.