Skip to content

Commit

Permalink
overlay limbs for improved client performance (#4290)
Browse files Browse the repository at this point in the history
from:

![image](https://github.com/cmss13-devs/cmss13/assets/55142896/869653d5-5d30-4053-b40f-973ce7664f9d)

to:

![image](https://github.com/cmss13-devs/cmss13/assets/55142896/2f154f46-a8bd-49ff-9f8d-544603732ca0)


lower number is obviously good, especially on clients because this can
make being on the front with a bunch of humans incredibly laggy



for some reason we use vis_contents for limbs, which simplifies their
icon updating. however it does also make clients lag the fuck out for
whatever reason so we shouldn't do that

also the fact that we copy these to ghosts doesn't help and can really
start multiplying things

can also remove iterating through visobjs in gFI, so ended up porting
over the readability changes from mothblocks on tg here:
tgstation/tgstation#60285

god willing no player facing changes
  • Loading branch information
harryob committed Sep 4, 2023
1 parent 2af77e5 commit 3d8b380
Show file tree
Hide file tree
Showing 6 changed files with 291 additions and 242 deletions.
15 changes: 8 additions & 7 deletions code/__DEFINES/human.dm
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,15 @@
#define ORDER_FOCUS_MAX_LEVEL 50

//Human Overlays Indexes used in update_icons/////////
#define UNDERWEAR_LAYER 41
#define UNDERSHIRT_LAYER 40
#define MUTANTRACE_LAYER 39
#define BODYPARTS_LAYER 42
#define DAMAGE_LAYER 41

/// For use by Hunter Flay
#define FLAY_LAYER 38
#define UNDERWEAR_LAYER 40
#define UNDERSHIRT_LAYER 39
#define MUTANTRACE_LAYER 38

#define DAMAGE_LAYER 37
/// For use by Hunter Flay
#define FLAY_LAYER 37
#define UNIFORM_LAYER 36

/// bs12 specific. this hack is probably gonna come back to haunt me
Expand Down Expand Up @@ -176,7 +177,7 @@
/// If you're hit by an acid DoT
#define EFFECTS_LAYER 1

#define TOTAL_LAYERS 41
#define TOTAL_LAYERS 42
//////////////////////////////////

//Synthetic Defines
Expand Down
276 changes: 123 additions & 153 deletions code/__HELPERS/icons.dm
Original file line number Diff line number Diff line change
Expand Up @@ -323,215 +323,185 @@ world
. = list(r, g, b)
if(usealpha) . += alpha

/// Create a single [/icon] from a given [/atom] or [/image].
///
/// Very low-performance. Should usually only be used for HTML, where BYOND's
/// appearance system (overlays/underlays, etc.) is not available.
///
/// Only the first argument is required.
/proc/getFlatIcon(image/appearance, defdir, deficon, defstate, defblend, start = TRUE, no_anim = FALSE)
// Loop through the underlays, then overlays, sorting them into the layers list
#define PROCESS_OVERLAYS_OR_UNDERLAYS(flat, process, base_layer) \
for (var/i in 1 to process.len) { \
var/image/current = process[i]; \
if (!current) { \
continue; \
} \
if (current.plane != FLOAT_PLANE && current.plane != appearance.plane) { \
continue; \
} \
var/current_layer = current.layer; \
if (current_layer < 0) { \
if (current_layer <= -1000) { \
return flat; \
} \
current_layer = base_layer + appearance.layer + current_layer / 1000; \
} \
for (var/index_to_compare_to in 1 to layers.len) { \
var/compare_to = layers[index_to_compare_to]; \
if (current_layer < layers[compare_to]) { \
layers.Insert(index_to_compare_to, current); \
break; \
} \
} \
layers[current] = current_layer; \
}


// 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)
//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(!appearance || appearance.alpha <= 0)
return icon(flat_template)

if(start)
if(!defdir)
defdir = A.dir
defdir = appearance.dir
if(!deficon)
deficon = A.icon
deficon = appearance.icon
if(!defstate)
defstate = A.icon_state
defstate = appearance.icon_state
if(!defblend)
defblend = A.blend_mode
defblend = appearance.blend_mode

var/curicon = A.icon || deficon
var/curstate = A.icon_state || defstate
var/curicon = appearance.icon || deficon
var/curstate = appearance.icon_state || defstate
var/curdir = (!appearance.dir || appearance.dir == SOUTH) ? defdir : appearance.dir

if(!(noIcon = (!curicon)))
var/render_icon = curicon

if (render_icon)
var/curstates = icon_states(curicon)
if(!(curstate in curstates))
if("" in curstates)
if ("" in curstates)
curstate = ""
else
noIcon = TRUE // Do not render this object.
render_icon = FALSE

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

//These should use the parent's direction (most likely)
if(!A.dir || A.dir == SOUTH)
curdir = defdir
else
curdir = A.dir

//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)
if(render_icon && curdir != SOUTH)
if (
!length(icon_states(icon(curicon, curstate, NORTH))) \
&& !length(icon_states(icon(curicon, curstate, EAST))) \
&& !length(icon_states(icon(curicon, curstate, WEST))) \
)
base_icon_dir = SOUTH

if(!base_icon_dir)
base_icon_dir = curdir

ASSERT(!BLEND_DEFAULT) //I might just be stupid but lets make sure this define is 0.

var/curblend = A.blend_mode || defblend
var/curblend = appearance.blend_mode || defblend

if(length(A.overlays) || length(A.underlays))
var/icon/flat = BLANK
if(appearance.overlays.len || appearance.underlays.len)
var/icon/flat = icon(flat_template)
// 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
if(render_icon)
copy = image(icon=curicon, icon_state=curstate, layer=appearance.layer, dir=base_icon_dir)
copy.color = appearance.color
copy.alpha = appearance.alpha
copy.blend_mode = curblend
layers[copy] = A.layer

// Loop through the underlays, then overlays, sorting them into the layers list
for(var/process_set in 0 to 2)
var/list/process = process_set ? A.overlays : A.underlays
switch(process_set)
if(0)
process = A.underlays
if(1)
process = A.vis_contents
if(2)
process = A.overlays
for(var/i in 1 to length(process))
var/image/current = process[i]
if(!current)
continue
if(current.plane != FLOAT_PLANE && current.plane != A.plane)
continue
if(process_set == 1 && !istype(current))
current = image(icon = current.icon, icon_state = current.icon_state, layer = current.layer, dir = current.dir)
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 length(layers))
var/image/cmp = layers[p]
if(current_layer < layers[cmp])
layers.Insert(p, current)
break
layers[current] = current_layer
layers[copy] = appearance.layer

PROCESS_OVERLAYS_OR_UNDERLAYS(flat, appearance.underlays, 0)
PROCESS_OVERLAYS_OR_UNDERLAYS(flat, appearance.overlays, 1)

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]
var/flatX1 = 1
var/flatX2 = flat.Width()
var/flatY1 = 1
var/flatY2 = flat.Height()

var/addX1 = 0
var/addX2 = 0
var/addY1 = 0
var/addY2 = 0

for(var/V in layers)
var/image/I = V
if(I.alpha == 0)
for(var/image/layer_image as anything in layers)
if(layer_image.alpha == 0)
continue

if(I == copy) // 'I' is an /image based on the object being flattened.
if(layer_image == copy) // 'layer_image' is an /image based on the object being flattened.
curblend = BLEND_OVERLAY
add = icon(I.icon, I.icon_state, base_icon_dir)
add = icon(layer_image.icon, layer_image.icon_state, base_icon_dir)
else // 'I' is an appearance object.
add = getFlatIcon(image(I), curdir, curicon, curstate, curblend, FALSE, no_anim)
add = getFlatIcon(image(layer_image), curdir, curicon, curstate, curblend, FALSE, no_anim)
if(!add)
continue

// 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())
addX1 = min(flatX1, layer_image.pixel_x + 1)
addX2 = max(flatX2, layer_image.pixel_x + add.Width())
addY1 = min(flatY1, layer_image.pixel_y + 1)
addY2 = max(flatY2, layer_image.pixel_y + add.Height())

if (
addX1 != flatX1 \
&& addX2 != flatX2 \
&& addY1 != flatY1 \
&& addY2 != flatY2 \
)

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
addX1 - flatX1 + 1,
addY1 - flatY1 + 1,
addX2 - flatX1 + 1,
addY2 - flatY1 + 1
)
flat_size = add_size.Copy()

flatX1 = addX1
flatX2 = addY1
flatY1 = addX2
flatY2 = addY2

// Blend the overlay into the flattened icon
flat.Blend(add, blendMode2iconMode(curblend), I.pixel_x + 2 - flatX1, I.pixel_y + 2 - flatY1)
flat.Blend(add, blendMode2iconMode(curblend), layer_image.pixel_x + 2 - flatX1, layer_image.pixel_y + 2 - flatY1)

if(A.color)
if(islist(A.color))
flat.MapColors(arglist(A.color))
if(appearance.color)
if(islist(appearance.color))
flat.MapColors(arglist(appearance.color))
else
flat.Blend(A.color, ICON_MULTIPLY)
flat.Blend(appearance.color, ICON_MULTIPLY)

if(A.alpha < 255)
flat.Blend(rgb(255, 255, 255, A.alpha), ICON_MULTIPLY)
if(appearance.alpha < 255)
flat.Blend(rgb(255, 255, 255, appearance.alpha), ICON_MULTIPLY)

if(no_anim)
//Clean up repeated frames
var/icon/cleaned = new /icon()
cleaned.Insert(flat, "", SOUTH, 1, 0)
. = cleaned
return 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
return icon(flat, "", SOUTH)
else if (render_icon) // There's no overlays.
var/icon/final_icon = icon(icon(curicon, curstate, base_icon_dir), "", SOUTH, no_anim ? TRUE : null)

if (appearance.alpha < 255)
final_icon.Blend(rgb(255,255,255, appearance.alpha), ICON_MULTIPLY)

if (appearance.color)
if (islist(appearance.color))
final_icon.MapColors(arglist(appearance.color))
else
final_icon.Blend(appearance.color, ICON_MULTIPLY)

return final_icon

#undef PROCESS_OVERLAYS_OR_UNDERLAYS

/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.
Expand Down
6 changes: 6 additions & 0 deletions code/modules/mob/living/carbon/human/human_defines.dm
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,12 @@
///list of weakrefs of recently dropped objects
var/list/remembered_dropped_objects = list()

/// associated list of body part zone -> currently active limb key
var/list/icon_render_keys = list()

/// static associated list of limb key -> image to avoid unnecessary overlay generation
var/static/list/icon_render_image_cache = list()

/client/var/cached_human_playtime

/client/proc/get_total_human_playtime(skip_cache = FALSE)
Expand Down
Loading

0 comments on commit 3d8b380

Please sign in to comment.