Ports /tg/ and CM planemasters to replace our renderers. (#21458)

Part one of our nefarious plans to destroy lag forever, and also
accomplish some cool shit.

This replaces our renderer system with plane masters, this will also be
used to turn skyboxes into backdrops and thus totally remove skybox
updating lag. Additionally, this will let us manipulate entire planes
very easily to do all sorts of zany shit. All credit goes to the
original coders, this is some seriously cool stuff.

Also fixes some bizarre smoothing behaviour, reduces updateoverlays
calls since now not every single structure in the game tries smoothing
with nothing.

<img width="1349" height="1349" alt="image"
src="https://github.com/user-attachments/assets/9b7cecd7-3c47-448b-9dd8-9b904640bf82"
/>
<img width="1349" height="1349" alt="image"
src="https://github.com/user-attachments/assets/647d75a2-1bff-45ca-ab92-0aea10631afd"
/>

---------

Co-authored-by: Matt Atlas <liermattia@gmail.com>
This commit is contained in:
Matt Atlas
2025-10-16 11:39:15 +00:00
committed by GitHub
co-authored by Matt Atlas
parent e30443943a
commit cca0dfe7b6
83 changed files with 789 additions and 734 deletions
@@ -104,7 +104,7 @@
if(mapload)
var/turf/T = loc
var/image/I = image(icon, T, icon_state, dir, pixel_x, pixel_y)
I.plane = EFFECTS_ABOVE_LIGHTING_PLANE
I.plane = ABOVE_LIGHTING_PLANE
I.color = color
I.alpha = 125
LAZYADD(T.blueprints, I)
@@ -54,7 +54,7 @@
if(mapload)
var/turf/T = loc
var/image/I = image(icon, T, icon_state, dir, pixel_x, pixel_y)
I.plane = EFFECTS_ABOVE_LIGHTING_PLANE
I.plane = ABOVE_LIGHTING_PLANE
I.color = color
I.alpha = 125
LAZYADD(T.blueprints, I)
+3 -3
View File
@@ -196,7 +196,7 @@
if(mapload)
var/turf/T = loc
var/image/I = image(icon, T, icon_state, dir, pixel_x, pixel_y)
I.plane = EFFECTS_ABOVE_LIGHTING_PLANE
I.plane = ABOVE_LIGHTING_PLANE
I.color = color
I.alpha = 125
LAZYADD(T.blueprints, I)
@@ -493,7 +493,7 @@
if(mapload)
var/turf/T = loc
var/image/I = image(icon, T, icon_state, dir, pixel_x, pixel_y)
I.plane = EFFECTS_ABOVE_LIGHTING_PLANE
I.plane = ABOVE_LIGHTING_PLANE
I.color = color
I.alpha = 125
LAZYADD(T.blueprints, I)
@@ -796,7 +796,7 @@
if(mapload)
var/turf/T = loc
var/image/I = image(icon, T, icon_state, dir, pixel_x, pixel_y)
I.plane = EFFECTS_ABOVE_LIGHTING_PLANE
I.plane = ABOVE_LIGHTING_PLANE
I.color = color
I.alpha = 125
LAZYADD(T.blueprints, I)
-1
View File
@@ -311,7 +311,6 @@ GLOBAL_LIST_EMPTY_TYPED(preferences_datums, /datum/preferences)
O.appearance = MA
O.dir = D
O.hud_layerise()
O.plane = 11 //THIS IS DUMB. Figure out a way to remove emissive blockers from the mob and their overlays.
var/list/screen_locs = preview_screen_locs["[D]"]
var/screen_x = screen_locs[1]
var/screen_x_minor = screen_locs[2]
@@ -1405,7 +1405,7 @@
icon = 'icons/obj/item/clothing/accessory/led_collar.dmi'
icon_state = "led_collar"
item_state = "led_collar"
plane = EFFECTS_ABOVE_LIGHTING_PLANE
plane = ABOVE_LIGHTING_PLANE
contained_sprite = TRUE
slot = ACCESSORY_SLOT_UTILITY_MINOR
@@ -1423,6 +1423,6 @@
/obj/item/clothing/accessory/led_collar/get_accessory_mob_overlay(var/mob/living/carbon/human/H, var/force = FALSE)
var/image/I = ..()
I.plane = EFFECTS_ABOVE_LIGHTING_PLANE
I.plane = ABOVE_LIGHTING_PLANE
I.appearance_flags |= KEEP_APART
return I
@@ -221,7 +221,7 @@
if(operating)
var/image/mwclosed_on = image(icon, "mw_on")
if(!dirtiness)
mwclosed_on.plane = EFFECTS_ABOVE_LIGHTING_PLANE
mwclosed_on.plane = ABOVE_LIGHTING_PLANE
AddOverlays(mwclosed_on)
if(dirtiness)
if(broken)
@@ -66,13 +66,13 @@
icon_state = door_closed
if(!stat)
var/image/ovenclosed_on = image('icons/obj/machinery/cooking_machines.dmi', door_closed_overlay)
ovenclosed_on.plane = EFFECTS_ABOVE_LIGHTING_PLANE
ovenclosed_on.plane = ABOVE_LIGHTING_PLANE
AddOverlays(ovenclosed_on)
else
icon_state = door_open
if(!stat)
var/image/ovenopen_on = image('icons/obj/machinery/cooking_machines.dmi', door_open_overlay)
ovenopen_on.plane = EFFECTS_ABOVE_LIGHTING_PLANE
ovenopen_on.plane = ABOVE_LIGHTING_PLANE
AddOverlays(ovenopen_on)
..()
+2 -2
View File
@@ -41,12 +41,12 @@
set_light(FALSE)
return
var/mutable_appearance/screen_overlay = mutable_appearance(icon, "atm-active", plane = EFFECTS_ABOVE_LIGHTING_PLANE)
var/mutable_appearance/screen_overlay = mutable_appearance(icon, "atm-active", plane = ABOVE_LIGHTING_PLANE)
AddOverlays(screen_overlay)
set_light(1.4, 1, COLOR_CYAN)
if(held_card)
var/mutable_appearance/card_overlay = mutable_appearance(icon, "atm-cardin", plane = EFFECTS_ABOVE_LIGHTING_PLANE)
var/mutable_appearance/card_overlay = mutable_appearance(icon, "atm-cardin", plane = ABOVE_LIGHTING_PLANE)
AddOverlays(card_overlay)
/obj/machinery/atm/process()
+1 -1
View File
@@ -43,7 +43,7 @@
set_light(FALSE)
return
var/mutable_appearance/screen_overlay = mutable_appearance(icon, "kitchenterminal-active", plane = EFFECTS_ABOVE_LIGHTING_PLANE)
var/mutable_appearance/screen_overlay = mutable_appearance(icon, "kitchenterminal-active", plane = ABOVE_LIGHTING_PLANE)
AddOverlays(screen_overlay)
set_light(1.4, 1, COLOR_CYAN)
@@ -82,63 +82,3 @@
layer = BELOW_TABLE_LAYER
alpha = 128
//Spawner object
//Maybe we could pool them in and out
/obj/particle_emitter
name = ""
anchored = TRUE
mouse_opacity = 0
appearance_flags = PIXEL_SCALE
var/particle_type = null
/obj/particle_emitter/Initialize(mapload, time, _color)
. = ..()
if (particle_type)
particles = GLOB.all_particles[particle_type]
if (time > 0)
QDEL_IN(src, time)
color = _color
/obj/particle_emitter/heat
particle_type = "heat"
render_target = HEAT_EFFECT_TARGET
appearance_flags = PIXEL_SCALE | NO_CLIENT_COLOR
/obj/particle_emitter/heat/Initialize()
. = ..()
filters += filter(type = "blur", size = 1)
/obj/particle_emitter/heat/high
particle_type = "high heat"
/obj/particle_emitter/mist
particle_type = "mist"
layer = FIRE_LAYER
/obj/particle_emitter/mist/back
particle_type = "mist_back"
layer = BELOW_OBJ_LAYER
/obj/particle_emitter/mist/back/gas
render_target = COLD_EFFECT_BACK_TARGET
/obj/particle_emitter/mist/back/gas/Initialize(mapload, time, _color)
. = ..()
filters += filter(type="alpha", render_source = COLD_EFFECT_TARGET, flags = MASK_INVERSE)
//for cold gas effect
/obj/particle_emitter/mist/gas
render_target = COLD_EFFECT_TARGET
var/obj/particle_emitter/mist/back/b = /obj/particle_emitter/mist/back/gas
/obj/particle_emitter/mist/gas/Initialize(mapload, time, _color)
. = ..()
b = new b(null)
vis_contents += b
+1 -1
View File
@@ -43,7 +43,7 @@
/obj/machinery/mineral/processing_unit_console/Initialize(mapload, d, populate_components)
. = ..()
var/mutable_appearance/screen_overlay = mutable_appearance(icon, "production_console-screen", plane = EFFECTS_ABOVE_LIGHTING_PLANE)
var/mutable_appearance/screen_overlay = mutable_appearance(icon, "production_console-screen", plane = ABOVE_LIGHTING_PLANE)
AddOverlays(screen_overlay)
set_light(1.4, 1, COLOR_CYAN)
+1 -1
View File
@@ -19,7 +19,7 @@
/obj/machinery/mineral/stacking_unit_console/Initialize(mapload, d, populate_components)
..()
var/mutable_appearance/screen_overlay = mutable_appearance(icon, "production_console-screen", plane = EFFECTS_ABOVE_LIGHTING_PLANE)
var/mutable_appearance/screen_overlay = mutable_appearance(icon, "production_console-screen", plane = ABOVE_LIGHTING_PLANE)
AddOverlays(screen_overlay)
set_light(1.4, 1, COLOR_CYAN)
return INITIALIZE_HINT_LATELOAD
+1
View File
@@ -3,6 +3,7 @@
status_flags = GODMODE
invisibility = INVISIBILITY_ABSTRACT
blocks_emissive = EMISSIVE_BLOCK_NONE
plane = GHOST_PLANE
density = FALSE
anchored = TRUE
+1 -1
View File
@@ -29,7 +29,7 @@
if(!obfuscation)
obfuscation = image(icon, T, icon_state)
obfuscation.layer = OBFUSCATION_LAYER
obfuscation.plane = DEFAULT_PLANE
obfuscation.plane = GAME_PLANE
if(!obfuscation_underlay)
// Creating a new icon of a fairly common icon state, adding some random color to prevent address searching, and hoping being static kills memory locality
var/turf/floor = /turf/simulated/floor/tiled
-2
View File
@@ -1,7 +1,5 @@
/mob/abstract/ghost
stat = DEAD
layer = OBSERVER_LAYER
plane = OBSERVER_PLANE
/// Toggle darkness.
var/see_darkness = FALSE
@@ -4,12 +4,12 @@
icon = 'icons/mob/mob.dmi'
icon_state = "ghost"
density = FALSE
canmove = 0
blinded = 0
anchored = 1 // don't get pushed around
canmove = FALSE
blinded = FALSE
anchored = TRUE // don't get pushed around
invisibility = INVISIBILITY_OBSERVER
simulated = FALSE
universal_speak = 1
universal_speak = TRUE
incorporeal_move = INCORPOREAL_GHOST
mob_thinks = FALSE
interaction_flags_atom = INTERACT_ATOM_MOUSEDROP_IGNORE_CHECKS
@@ -17,7 +17,7 @@
/// If the ghost can re-enter their corpse.
var/can_reenter_corpse
/// The ghost's HUD datum.
var/datum/hud/hud = null
var/datum/hud/hud
/// This variable is set to 1 when you enter the game as an observer. Remains null if you died in the game and are a ghost. Not reliable for admins; they change mobs a lot.
var/started_as_observer
/// If the ghost has enabled antagHUD.
@@ -42,7 +42,7 @@
set_stat(DEAD)
ghostimage = image(src.icon,src,src.icon_state)
ghostimage = image(icon, src, icon_state)
SSmobs.ghost_darkness_images |= ghostimage
updateallghostimages()
@@ -53,7 +53,7 @@
var/originaldesc = desc
var/o_transform = transform
appearance = body
set_appearance(body)
appearance_flags = KEEP_TOGETHER
desc = originaldesc
transform = o_transform
@@ -609,6 +609,14 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
if (ghostimage)
client.images -= ghostimage //remove ourself
/**
* We use this proc to set appearance because doing so resets the plane.
* We want the plane to stay at GHOST_PLANE to avoid weird overlaying stuff.
*/
/mob/abstract/ghost/observer/proc/set_appearance(new_appearance)
appearance = new_appearance
plane = GHOST_PLANE
/mob/abstract/ghost/observer/MayRespawn(var/feedback = 0, var/respawn_type = null)
if(!client)
return 0
@@ -6,7 +6,6 @@
status_flags = GODMODE
invisibility = INVISIBILITY_OBSERVER
see_invisible = SEE_INVISIBLE_OBSERVER
layer = OBSERVER_LAYER
incorporeal_move = INCORPOREAL_GHOST
simulated = FALSE
+1 -1
View File
@@ -341,7 +341,7 @@ ABSTRACT_TYPE(/atom/movable/screen/new_player/selection)
announce_ghost_joinleave(src)
var/mob/living/carbon/human/dummy/mannequin/mannequin = new
client.prefs.dress_preview_mob(mannequin)
observer.appearance = mannequin.appearance
observer.set_appearance(mannequin.appearance)
observer.appearance_flags = KEEP_TOGETHER
observer.alpha = 127
observer.layer = initial(observer.layer)
@@ -39,9 +39,9 @@
var/mutable_appearance/panel_overlay = mutable_appearance(icon, "[icon_state]-panel")
AddOverlays(panel_overlay)
if(length(extract_slimes))
var/mutable_appearance/interior_overlay = mutable_appearance(icon, "[icon_state]-interior", plane = EFFECTS_ABOVE_LIGHTING_PLANE)
var/mutable_appearance/interior_overlay = mutable_appearance(icon, "[icon_state]-interior", plane = ABOVE_LIGHTING_PLANE)
AddOverlays(interior_overlay)
var/mutable_appearance/spinning_overlay = mutable_appearance(icon, "[icon_state]-running", plane = EFFECTS_ABOVE_LIGHTING_PLANE)
var/mutable_appearance/spinning_overlay = mutable_appearance(icon, "[icon_state]-running", plane = ABOVE_LIGHTING_PLANE)
AddOverlays(spinning_overlay)
set_light(2.5, 1, COLOR_VIOLET)
else
@@ -123,7 +123,7 @@
/mob/living/simple_animal/construct/proc/add_glow()
ClearOverlays()
var/overlay_plane = EFFECTS_ABOVE_LIGHTING_PLANE
var/overlay_plane = ABOVE_LIGHTING_PLANE
var/image/glow = image(icon, "glow-[icon_state]")
glow.plane = overlay_plane
@@ -29,7 +29,7 @@
. = ..()
name = "[initial(name)] ([rand(100, 999)])"
eye_overlay = image(icon, "[icon_state]-eyes_emag")
eye_overlay.plane = EFFECTS_ABOVE_LIGHTING_PLANE
eye_overlay.plane = ABOVE_LIGHTING_PLANE
eye_overlay.appearance_flags = KEEP_APART
AddOverlays(eye_overlay)
@@ -187,7 +187,7 @@
/mob/living/simple_animal/hostile/carp/shark/reaver/eel/Initialize()
. = ..()
eye_overlay = image(icon, "eel_eyeglow")
eye_overlay.plane = EFFECTS_ABOVE_LIGHTING_PLANE
eye_overlay.plane = ABOVE_LIGHTING_PLANE
eye_overlay.appearance_flags = KEEP_APART
AddOverlays(eye_overlay)
set_light(MINIMUM_USEFUL_LIGHT_RANGE, 2, LIGHT_COLOR_TUNGSTEN)
-1
View File
@@ -100,7 +100,6 @@
clear_important_client_contents()
enable_client_mobs_in_contents()
AddDefaultRenderers()
update_client_color()
add_click_catcher()
-1
View File
@@ -10,7 +10,6 @@
disconnect_time = world.realtime
log_access("Logout: [key_name(src)]")
SSstatistics.update_status()
ClearRenderers()
clear_important_client_contents()
my_client = null
+5 -5
View File
@@ -62,7 +62,7 @@
desc = DESC_PARENT
icon = 'icons/effects/lighting_overlay.dmi'
icon_state = "dark"
plane = OPENTURF_MAX_PLANE
plane = OPEN_SPACE_PLANE_END
layer = MIMICED_LIGHTING_LAYER
// blend_mode = BLEND_MULTIPLY
color = "#00000033"
@@ -77,7 +77,7 @@
/atom/movable/openspace/multiplier/proc/copy_lighting(atom/movable/lighting_overlay/LO)
appearance = LO
layer = MIMICED_LIGHTING_LAYER
plane = OPENTURF_MAX_PLANE
plane = OPEN_SPACE_PLANE_END
set_invisibility(0)
if (icon_state == null)
blend_mode = BLEND_MULTIPLY
@@ -120,7 +120,7 @@
// Object used to hold a mimiced atom's appearance.
/atom/movable/openspace/mimic
plane = OPENTURF_MAX_PLANE
plane = OPEN_SPACE_PLANE_END
var/atom/movable/associated_atom
var/depth
var/queued = 0
@@ -174,7 +174,7 @@
// This thing holds the mimic appearance for non-OVERWRITE turfs.
/atom/movable/openspace/turf_proxy
plane = OPENTURF_MAX_PLANE
plane = OPEN_SPACE_PLANE_END
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
z_flags = ZMM_IGNORE // Only one of these should ever be visible at a time, the mimic logic will handle that.
@@ -196,7 +196,7 @@
// A type for copying non-overwrite turfs' self-appearance.
/atom/movable/openspace/turf_mimic
plane = OPENTURF_MAX_PLANE // These *should* only ever be at the top?
plane = OPEN_SPACE_PLANE_END // These *should* only ever be at the top?
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
var/turf/delegate
+1 -1
View File
@@ -45,6 +45,6 @@
/obj/item/organ/external/head/unbreakable/revenant/get_additional_images(var/mob/living/carbon/human/H)
var/image/return_image = image(H.species.eyes_icons, H, "[H.species.eyes]_glow")
return_image.plane = EFFECTS_ABOVE_LIGHTING_PLANE
return_image.plane = ABOVE_LIGHTING_PLANE
return_image.appearance_flags = KEEP_APART
return list(return_image)
@@ -22,7 +22,7 @@
var/image/I = image(icon,"[icon_state]decor")
I.appearance_flags = RESET_COLOR
I.color = get_random_colour(0, 150, 255)
I.plane = EFFECTS_ABOVE_LIGHTING_PLANE
I.plane = ABOVE_LIGHTING_PLANE
AddOverlays(I)
set_light(0.3, 0.1, 2, l_color = I.color)
+1 -1
View File
@@ -106,7 +106,7 @@
return INITIALIZE_HINT_QDEL
if(known)
plane = EFFECTS_ABOVE_LIGHTING_PLANE
plane = ABOVE_LIGHTING_PLANE
for(var/obj/machinery/computer/ship/helm/H in SSmachinery.machinery)
H.get_known_sectors()
update_icon()
+1 -1
View File
@@ -107,7 +107,7 @@ By design, d1 is the smallest direction and d2 is the highest
if(mapload)
var/image/I = image(icon, T, icon_state, dir, pixel_x, pixel_y)
I.plane = EFFECTS_ABOVE_LIGHTING_PLANE
I.plane = ABOVE_LIGHTING_PLANE
I.alpha = 125
I.color = color
LAZYADD(T.blueprints, I)
+1 -1
View File
@@ -63,5 +63,5 @@
ClearOverlays()
if(on)
var/image/light = image(icon, src, "[icon_state]-light")
light.plane = EFFECTS_ABOVE_LIGHTING_PLANE
light.plane = ABOVE_LIGHTING_PLANE
AddOverlays(light)
@@ -53,7 +53,7 @@ field_generator power level display
if(state == 2)
AddOverlays("+welding")
var/image/lights_image = image(icon, null, "+lights")
lights_image.plane = EFFECTS_ABOVE_LIGHTING_PLANE
lights_image.plane = ABOVE_LIGHTING_PLANE
AddOverlays(lights_image)
/obj/machinery/field_generator/process()
+1 -1
View File
@@ -15,7 +15,7 @@
if(anchored)
AddOverlays("[icon_state]+bolts")
var/image/lights_image = image(icon, null, "[icon_state]+lights")
lights_image.plane = EFFECTS_ABOVE_LIGHTING_PLANE
lights_image.plane = ABOVE_LIGHTING_PLANE
AddOverlays(lights_image)
/obj/machinery/the_singularitygen/process()
+2 -2
View File
@@ -18,7 +18,7 @@
if(anchored)
AddOverlays("[icon_state]+bolts")
var/image/lights_image = image(icon, null, "[icon_state]+lights")
lights_image.plane = EFFECTS_ABOVE_LIGHTING_PLANE
lights_image.plane = ABOVE_LIGHTING_PLANE
AddOverlays(lights_image)
/obj/machinery/power/tesla_coil/RefreshParts()
@@ -79,7 +79,7 @@
if(anchored)
AddOverlays("[icon_state]+bolts")
var/image/lights_image = image(icon, null, "[icon_state]+lights")
lights_image.plane = EFFECTS_ABOVE_LIGHTING_PLANE
lights_image.plane = ABOVE_LIGHTING_PLANE
AddOverlays(lights_image)
/obj/machinery/power/grounding_rod/attackby(obj/item/attacking_item, mob/user)
+1 -1
View File
@@ -17,7 +17,7 @@
dissipate = 1
dissipate_delay = 10
dissipate_strength = 1
plane = EFFECTS_ABOVE_LIGHTING_PLANE
plane = ABOVE_LIGHTING_PLANE
blend_mode = BLEND_ADD
var/failed_direction = 0
var/list/orbiting_balls = list()
+57
View File
@@ -0,0 +1,57 @@
# The Render Readme
1. [Byond internal functionality](#byond-internal-functionality)
2. [Known internal snowflake](#known-internal-snowflake)
3. [The rendering solution](#the-rendering-solution)
4. [Render plates](#render-plates)
## Byond internal functionality
This part of the guide will assume that you have read the byond reference entry for rendering at www.byond.com/docs/ref//#/{notes}/renderer
When you create an atom, this will always create an internal byond structure called an "appearance". This appearance you will likely be familiar with, as it is exposed through the /atom/var/appearance var. This appearance var holds data on how to render the object, ie what icon/icon_state/color etc it is using. Note that appearance vars will always copy, and do not hold a reference. When you update a var, for example let's pretend we add a filter, the appearance will be updated to include the filter. Note that, however, vis_contents objets are uniquely excluded from appearances. Then, when the filter is updated, the appearance will be recreated, and the atom marked as "dirty". After it has been updated, the SendMaps() function (sometimes also called maptick), which is an internal byond function that iterates over all objects in a client's view and in the clients.mob.contents, checks for "dirty" atoms, then resends any "dirty" appearances to clients as needed and unmarks them as dirty. This function is notoriously slow, but we can see its tick usage through the world.map_cpu var. We can also avoid more complex checks checking whether an object is visible on a client's screen by using the TILE_BOUND appearance flag.
Finally, we arrive at clientside behavior, where we have two main clientside functions: GetMapIcons, and Render. GetMapIcons is repsonsible for actual rendering calculations on the clientside, such as "Group Icons and Set bounds", which performs clientside calculations for transform matrixes. Note that particles here are handled in a separate thread and are not diplayed in the clientside profiler. Render handles the actual drawing of the screen.
For debugging rendering issues its reccomended you do two things:
A) Talk to someone who has inside knowledge(like lummox) about it, most of this is undocumented and bugs often
B) Use the undocumented debug printer which reads of data on icons rendering, this is very dense but can be useful in some cases. To use: Right click top tab -> Options & Messages -> Client -> Command -> Enter ".debug profile mapicons" and press Enter -> go to your Byond directory and find BYOND/cfg/mapicons.json . Yes this is one giant one-line json.
## Known internal snowflake
The following is an incomplete list of pitfalls that come from byond snowflake that are known, this list is obviously incomplete.
1. Transforms are very slow on clientside. This is not usually noticable, but if you start using large amounts of them it will grind you to a halt quickly, regardless of whether its on overlays or objs
2. The darkness plane. The darkness plane has specific variables it needs to render correctly, and these can be found in the plane masters file. it is composed internally of two parts, a black mask over the clients screen, and a non rendering mask that blocks all luminosity=0 turfs and their contents from rendering if the SEE_BLACKNESS flag is set properly. It behaves very oddly, such as forcing itself to ALWAYS render or pre-render on blend_multiply blend mode or refusing to render the black mask properly otherwise. The blocker will always block rendering but the mask can be layered under other objects.
3. render_target/source. Render_target/source will only copy certain rendering instructions, and these are only defined as "etc." in the byond reference. Known non copied appearance vars include: blend_mode, plane, layer, vis_contents, mouse_opacity...
4. Large icons on the screen that peek over the edge will instead of only rendering partly like you would expect will instead stretch the screen while not adgusting the render buffer, which means that you can actively see as tiles and map objects are rendered. You can use this for an easy "offscreen" UI.
5. Numerically large filters on objects of any size will torpedo performance, even though large objects with small filters will perform massively better. (ie blur(size=20) BAD)
6. Texture Atlas: the texture atlas byond uses to render icons is very susceptible to corruption and can regularily replace icons with other icons or just not render at all. This can be exasperated by alt tabbing or pausing the dreamseeker process.
7. The renderer is awful code and lummox said he will try changing a large part of it for 515 so keep an eye on that
8. Byond uses DirectX 9 (Lummox said he wants to update to DirectX 11)
9. Particles are just fancy overlays and are not independent of their owner
10. Maptick items inside mob.contents are cheaper compared to most other movables
11. Displacement filter: The byond "displacement filter" does not, as the name would make you expect, use displacement maps, but instead uses normal maps.
## The rendering solution
One of the main issues with making pretty effects is how objects can only render to one plane, and how filters can only be applied to single objects. Quite simply it means we cant apply effects to multiple planes at once, and an effect to one plane only by treating it as a single unit:
![](https://raw.githubusercontent.com/tgstation/documentation-assets/main/rendering/renderpipe_old.png)
A semi-fix to stop from having to apply effects to every single plane is to use the render controllers, to automatically apply filters and colors automatically onto their controlled planes.
The solution is thus instead we replace plane masters rendering directly to client with planes that render multiple planes onto them as objects in order to be able to affect multiple planes while treating them as a single object. This is done by relaying the plane using a "render relay" onto a "render plate" which acts as a plane master of plane masters of sorts, and since planes are rendered onto it as single objects any filters we apply to them will render over the planes, treating them as a single unit.
![](https://raw.githubusercontent.com/tgstation/documentation-assets/main/rendering/renderpipe_refactored.png)
We can also choose to render these by decreasing the scaling all applied effects (effect_size/number_of_plates_rendered_to) then rendering it onto multiple planes:
![](https://raw.githubusercontent.com/tgstation/documentation-assets/main/rendering/renderpipe_refactored_multiple.png)
Through these this allows us to treat planes as single objects, and lets us distort them as a single unit, most notably works wonders with the displacement filter. Specifically, here you can displacement_filter a plane onto a plate, which then will treat all the other planes rendered on that plate as a single unit.
## Render plates
The rendering system uses two objects to unify planes: render_relay and render_plates. Render relays use render_target/source and the relay_render_to_plane proc to replicate the plane master on the render relay. This render relay is then rendered onto a render_plate, which is a plane master that renders the render_relays onto itself. This plate can then be hierachically rendered with the same process until it reaches the master render_plate, which is the plate that will actually render to the player. These plates naturally in the byond style have quirks. For example, rendering to two plates will double any effects such as color or filters, and as such you need to carefully manage how you render them. Keep in mind as well that when sorting the layers for rendering on a plane that they should not be negative, this is handled automatically in relay_render_to_plane. When debugging note that mouse_opacity can act bizzarly with this method, such as only allowing you to click things that are layered over objects on a certain plane but auomatically setting the mouse_opacity should be handling this. Note that if you decide to manipulate a plane with internal byond objects that you will have to manually extrapolate the vars that are set if you want to render them to another plane (See blackness plane for example), and that this is not documented anywhere.
Goodluck and godspeed with coding
- Just another contributor
+182
View File
@@ -0,0 +1,182 @@
/atom/movable/screen/plane_master
screen_loc = "CENTER"
icon_state = "blank"
appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
blend_mode = BLEND_OVERLAY
plane = LOWEST_EVER_PLANE
var/show_alpha = 255
var/hide_alpha = 0
//--rendering relay vars--
///integer: what plane we will relay this planes render to
var/render_relay_plane = RENDER_PLANE_GAME
///bool: Whether this plane should get a render target automatically generated
var/generate_render_target = TRUE
///integer: blend mode to apply to the render relay in case you dont want to use the plane_masters blend_mode
var/blend_mode_override
///reference: current relay this plane is utilizing to render
var/obj/render_plane_relay/relay
/atom/movable/screen/plane_master/proc/Show(override)
alpha = override || show_alpha
/atom/movable/screen/plane_master/proc/Hide(override)
alpha = override || hide_alpha
//Why do plane masters need a backdrop sometimes? Read https://secure.byond.com/forum/?post=2141928
//Trust me, you need one. Period. If you don't think you do, you're doing something extremely wrong.
/atom/movable/screen/plane_master/proc/backdrop(mob/mymob)
SHOULD_CALL_PARENT(TRUE)
if(!isnull(render_relay_plane))
relay_render_to_plane(mymob, render_relay_plane)
///Contains most things in the game world
/atom/movable/screen/plane_master/game_world
name = "game world plane master"
plane = GAME_PLANE
blend_mode = BLEND_OVERLAY
/atom/movable/screen/plane_master/rendering_plate/game_world/Initialize(mapload, datum/hud/hud_owner)
. = ..()
add_filter("displacer", 1, displacement_map_filter(render_source = DISPLACEMENT_PLATE_RENDER_TARGET, size = 10))
/atom/movable/screen/plane_master/game_world_above
name = "above game world plane master"
plane = ABOVE_GAME_PLANE
/atom/movable/screen/plane_master/ghost
name = "ghost plane master"
plane = GHOST_PLANE
render_relay_plane = RENDER_PLANE_NON_GAME
/// Plane master handling display of building roofs. They're meant to become invisible when inside a building.
/atom/movable/screen/plane_master/roof
name = "roof plane master"
plane = ROOF_PLANE
appearance_flags = PLANE_MASTER
blend_mode = BLEND_OVERLAY
/atom/movable/screen/plane_master/space
name = "space plane master"
plane = SPACE_PLANE
blend_mode = BLEND_OVERLAY
/// Plane master handling skyboxes.
/atom/movable/screen/plane_master/skybox
name = "skybox plane master"
plane = SKYBOX_PLANE
blend_mode = BLEND_MULTIPLY
appearance_flags = PLANE_MASTER
/**
* Plane master handling byond internal blackness
* vars are set as to replicate behavior when rendering to other planes
* do not touch this unless you know what you are doing
*/
/atom/movable/screen/plane_master/blackness
name = "darkness plane master"
plane = BLACKNESS_PLANE
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
blend_mode = BLEND_MULTIPLY
appearance_flags = PLANE_MASTER | NO_CLIENT_COLOR | PIXEL_SCALE
//byond internal end
/*!
* This system works by exploiting BYONDs color matrix filter to use layers to handle emissive blockers.
*
* Emissive overlays are pasted with an atom color that converts them to be entirely some specific color.
* Emissive blockers are pasted with an atom color that converts them to be entirely some different color.
* Emissive overlays and emissive blockers are put onto the same plane.
* The layers for the emissive overlays and emissive blockers cause them to mask eachother similar to normal BYOND objects.
* A color matrix filter is applied to the emissive plane to mask out anything that isn't whatever the emissive color is.
* This is then used to alpha mask the lighting plane.
*/
///Contains all lighting objects
/atom/movable/screen/plane_master/lighting
name = "lighting plane master"
plane = LIGHTING_PLANE
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
blend_mode = BLEND_MULTIPLY
/atom/movable/screen/plane_master/lighting/Initialize()
. = ..()
add_filter("emissives", 1, alpha_mask_filter(render_source = EMISSIVE_RENDER_TARGET, flags = MASK_INVERSE))
/**
* Handles emissive overlays and emissive blockers.
*/
/atom/movable/screen/plane_master/emissive
name = "emissive plane master"
plane = EMISSIVE_PLANE
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
render_target = EMISSIVE_RENDER_TARGET
render_relay_plane = null
/atom/movable/screen/plane_master/emissive/Initialize()
. = ..()
add_filter("em_block_masking", 1, color_matrix_filter(GLOB.em_mask_matrix))
/atom/movable/screen/plane_master/above_lighting
name = "above lighting plane master"
plane = ABOVE_LIGHTING_PLANE
render_relay_plane = RENDER_PLANE_GAME
/atom/movable/screen/plane_master/runechat
name = "runechat plane master"
plane = RUNECHAT_PLANE
appearance_flags = PLANE_MASTER
blend_mode = BLEND_OVERLAY
render_relay_plane = RENDER_PLANE_NON_GAME
/atom/movable/screen/plane_master/runechat/backdrop(mob/mymob)
. = ..()
remove_filter("AO")
add_filter("AO", 1, drop_shadow_filter(x = 0, y = -2, size = 4, color = "#04080FAA"))
/atom/movable/screen/plane_master/fullscreen
name = "fullscreen alert plane"
plane = FULLSCREEN_PLANE
render_relay_plane = RENDER_PLANE_NON_GAME
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
/atom/movable/screen/plane_master/hud
name = "HUD plane"
plane = HUD_PLANE
render_relay_plane = RENDER_PLANE_NON_GAME
/atom/movable/screen/plane_master/cinematic
name = "cinematic plane"
plane = CINEMATIC_PLANE
render_relay_plane = RENDER_PLANE_NON_GAME
/atom/movable/screen/plane_master/displacement
name = "displacement plane"
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
plane = DISPLACEMENT_PLATE_RENDER_LAYER
render_target = DISPLACEMENT_PLATE_RENDER_TARGET
render_relay_plane = null
/atom/movable/screen/plane_master/open_space
name = "open space plane"
plane = OPEN_SPACE_PLANE_END //aurora snowflake: our openspace system works bottom up, not top down like CM's
/atom/movable/screen/plane_master/open_space/Initialize(mapload, offset)
name = "open space plane [offset]"
plane -= offset
. = ..()
add_filter("multizblur", 1, gauss_blur_filter(0.5 + 0.25 * (offset + 1)))
/atom/movable/screen/plane_master/openspace_backdrop
name = "open space plane master"
plane = OPENSPACE_BACKDROP_PLANE
appearance_flags = PLANE_MASTER
blend_mode = BLEND_MULTIPLY
alpha = 255
/atom/movable/screen/plane_master/openspace_backdrop/Initialize()
. = ..()
filters = list()
filters += filter(type = "drop_shadow", color = "#04080FAA", size = -10)
filters += filter(type = "drop_shadow", color = "#04080FAA", size = -15)
filters += filter(type = "drop_shadow", color = "#04080FAA", size = -20)
@@ -0,0 +1,76 @@
///Atom that manages and controls multiple planes. It's an atom so we can hook into add_filter etc. Multiple controllers can control one plane.
/atom/movable/plane_master_controller
///List of planes in this controllers control. Initially this is a normal list, but becomes an assoc list of plane numbers as strings | plane instance
var/list/controlled_planes = list()
///hud that owns this controller
var/datum/hud/owner_hud
INITIALIZE_IMMEDIATE(/atom/movable/plane_master_controller)
///Ensures that all the planes are correctly in the controlled_planes list.
/atom/movable/plane_master_controller/Initialize(mapload, datum/hud/hud)
. = ..()
if(!istype(hud))
return
owner_hud = hud
var/assoc_controlled_planes = list()
for(var/i in controlled_planes)
var/atom/movable/screen/plane_master/instance = owner_hud.plane_masters["[i]"]
if(!instance) //If we looked for a hud that isn't instanced, just keep going
stack_trace("[i] isn't a valid plane master layer for [owner_hud.type], are you sure it exists in the first place?")
continue
assoc_controlled_planes["[i]"] = instance
controlled_planes = assoc_controlled_planes
///Full override so we can just use filterrific
/atom/movable/plane_master_controller/add_filter(name, priority, list/params)
. = ..()
for(var/i in controlled_planes)
var/atom/movable/screen/plane_master/pm_iterator = controlled_planes[i]
pm_iterator.add_filter(name, priority, params)
///Full override so we can just use filterrific
/atom/movable/plane_master_controller/remove_filter(name_or_names)
. = ..()
for(var/i in controlled_planes)
var/atom/movable/screen/plane_master/pm_iterator = controlled_planes[i]
pm_iterator.remove_filter(name_or_names)
/atom/movable/plane_master_controller/update_filters()
. = ..()
for(var/i in controlled_planes)
var/atom/movable/screen/plane_master/pm_iterator = controlled_planes[i]
pm_iterator.update_filters()
///Gets all filters for this controllers plane masters
/atom/movable/plane_master_controller/proc/get_filters(name)
. = list()
for(var/i in controlled_planes)
var/atom/movable/screen/plane_master/pm_iterator = controlled_planes[i]
. += pm_iterator.get_filter(name)
///Transitions all filters owned by this plane master controller
/atom/movable/plane_master_controller/transition_filter(name, list/new_params, time, easing, loop)
. = ..()
for(var/i in controlled_planes)
var/atom/movable/screen/plane_master/pm_iterator = controlled_planes[i]
pm_iterator.transition_filter(name, new_params, time, easing, loop)
/atom/movable/plane_master_controller/game
name = PLANE_MASTERS_GAME
controlled_planes = list(
SPACE_PLANE,
SKYBOX_PLANE,
GAME_PLANE,
LIGHTING_PLANE
)
/// Exists for convienience when referencing all non-master render plates.
/// This is the whole game and the UI, but not the escape menu.
/atom/movable/plane_master_controller/non_master
name = PLANE_MASTERS_NON_MASTER
controlled_planes = list(
RENDER_PLANE_GAME,
RENDER_PLANE_NON_GAME,
)
+79
View File
@@ -0,0 +1,79 @@
/*!
* Custom rendering solution to allow for advanced effects
* We (ab)use plane masters and render source/target to cheaply render 2+ planes as 1
* if you want to read more read the _render_readme.md
*/
/**
* Render relay object assigned to a plane master to be able to relay it's render onto other planes that are not it's own
*/
/obj/render_plane_relay
screen_loc = "1,1"
layer = -1
plane = 0
appearance_flags = PASS_MOUSE | NO_CLIENT_COLOR | KEEP_TOGETHER
/**
* ## Rendering plate
*
* Acts like a plane master, but for plane masters
* Renders other planes onto this plane, through the use of render objects
* Any effects applied onto this plane will act on the unified plane
* IE a bulge filter will apply as if the world was one object
* remember that once planes are unified on a render plate you cant change the layering of them!
*/
/atom/movable/screen/plane_master/rendering_plate
name = "default rendering plate"
///this plate renders the final screen to show to the player
/atom/movable/screen/plane_master/rendering_plate/master
name = "master rendering plate"
plane = RENDER_PLANE_MASTER
render_relay_plane = null
generate_render_target = FALSE
///renders general in charachter game objects
/atom/movable/screen/plane_master/rendering_plate/game_world
name = "game rendering plate"
plane = RENDER_PLANE_GAME
render_relay_plane = RENDER_PLANE_MASTER
///render plate for OOC stuff like ghosts, hud-screen effects, etc
/atom/movable/screen/plane_master/rendering_plate/non_game
name = "non-game rendering plate"
plane = RENDER_PLANE_NON_GAME
render_relay_plane = RENDER_PLANE_MASTER
/**
* Plane master proc called in backdrop() that creates a relay object, sets it as needed and then adds it to the clients screen
* Sets:
* * layer from plane to avoid z-fighting
* * plane to relay the render to
* * render_source so that the plane will render on this object
* * mouse opacity to ensure proper mouse hit tracking
* * name for debugging purposes
* Other vars such as alpha will automatically be applied with the render source
* Arguments:
* * mymob: mob whose plane is being backdropped
* * relay_plane: plane we are relaying this plane master to
*/
/atom/movable/screen/plane_master/proc/relay_render_to_plane(mob/mymob, relay_plane)
if(relay in mymob.client.screen) //backdrop can be called multiple times
return
if(!render_target && generate_render_target)
render_target = "*[name]: AUTOGENERATED RENDER TGT"
relay = new()
relay.render_source = render_target
relay.plane = relay_plane
relay.layer = (plane + abs(LOWEST_EVER_PLANE))*0.5 //layer must be positive but can be a decimal
if(blend_mode_override)
relay.blend_mode = blend_mode_override
else
relay.blend_mode = blend_mode
relay.mouse_opacity = mouse_opacity
relay.name = render_target
mymob.client.add_to_screen(relay)
if(blend_mode != BLEND_MULTIPLY)
blend_mode = BLEND_DEFAULT
+1 -1
View File
@@ -35,7 +35,7 @@
var/motherdock //tag of mothershuttle landmark, defaults to starting location
var/squishes = TRUE //decides whether or not things get squished when it moves.
var/cargo_elevator = FALSE // Snowflake variable for the cargo elevator. Decides whether you will take fall damage or not.
var/cargo_elevator = FALSE // Snowflake variable for the cargo elevator. Decides whether you will take fall damage or not
/datum/shuttle/New(_name, var/obj/effect/shuttle_landmark/initial_location)
..()
+18 -1
View File
@@ -42,7 +42,9 @@
addtimer(CALLBACK(src, PROC_REF(prepare_elevator)), 1 MINUTE) // pseudo initialize. We give mapload some time before initializing rest of the properties
/datum/shuttle/autodock/ferry/supply/proc/prepare_elevator()
var/obj/dest_helper = locate(/obj/effect/landmark/destination_helper/cargo_elevator)
var/obj/dest_helper = SSshuttle.cargo_dest_helper
if(!dest_helper)
crash_with("No cargo destination helper found for the elevator!")
target_dest_x = dest_helper.x
target_dest_y = dest_helper.y
target_dest_z = dest_helper.z
@@ -239,6 +241,21 @@
/obj/effect/landmark/destination_helper/cargo_elevator
name = "cargo elevator destination helper"
/obj/effect/landmark/destination_helper/cargo_elevator/Initialize(mapload)
..()
return INITIALIZE_HINT_LATELOAD
/obj/effect/landmark/destination_helper/cargo_elevator/LateInitialize()
. = ..()
if(SSshuttle.cargo_dest_helper)
log_and_message_admins("Multiple cargo destination helpers detected; overriding.")
SSshuttle.cargo_dest_helper = src
/obj/effect/landmark/destination_helper/cargo_elevator/Destroy()
if(SSshuttle.cargo_dest_helper == src)
SSshuttle.cargo_dest_helper = null
return ..()
/obj/effect/step_trigger/cargo_elevator
name = "cargo elevator shaft"
icon = 'icons/effects/map_effects.dmi'
+2 -2
View File
@@ -222,13 +222,13 @@ GLOBAL_LIST_INIT(can_enter_vent_with, list(
for(var/obj/machinery/atmospherics/A in (pipeline.members || pipeline.edges)) // Adds pipe and manifold images
if(!A.pipe_image)
A.pipe_image = image(A, A.loc, dir = A.dir)
A.pipe_image.plane = EFFECTS_ABOVE_LIGHTING_PLANE
A.pipe_image.plane = ABOVE_LIGHTING_PLANE
pipes_shown += A.pipe_image
client.images += A.pipe_image
for (var/obj/machinery/atmospherics/V in network.normal_members) // Adds vent and scrubber images
if (!V.pipe_image || istype(V, /obj/machinery/atmospherics/unary/vent_pump/))
V.pipe_image = image(V, V.loc, dir = V.dir)
V.pipe_image.plane = EFFECTS_ABOVE_LIGHTING_PLANE
V.pipe_image.plane = ABOVE_LIGHTING_PLANE
pipes_shown += V.pipe_image
client.images += V.pipe_image
+2 -2
View File
@@ -23,7 +23,7 @@
*/
/obj/abstract/weather_system
plane = DEFAULT_PLANE
plane = GAME_PLANE
layer = ABOVE_PROJECTILE_LAYER
icon = 'icons/effects/weather.dmi'
icon_state = "blank"
@@ -119,7 +119,7 @@
// Dummy object for lightning flash animation.
/obj/abstract/lightning_overlay
plane = EFFECTS_ABOVE_LIGHTING_PLANE //Future EMISSIVE_PLANE
plane = ABOVE_LIGHTING_PLANE //Future EMISSIVE_PLANE
layer = LIGHTNING_LAYER
icon = 'icons/effects/weather.dmi'
icon_state = "full"
+1 -4
View File
@@ -79,7 +79,6 @@ var/global/datum/xgm_gas_data/gas_data
desc = "You shouldn't be clicking this."
plane = HEAT_EFFECT_PLANE
gas_id = GAS_HEAT
render_source = HEAT_EFFECT_TARGET
/obj/gas_overlay/heat/Initialize(mapload, gas)
. = ..()
@@ -87,15 +86,13 @@ var/global/datum/xgm_gas_data/gas_data
icon_state = null
/obj/effect/gas_cold_back
render_source = COLD_EFFECT_BACK_TARGET
plane = DEFAULT_PLANE
plane = GAME_PLANE
layer = BELOW_OBJ_LAYER
/obj/gas_overlay/cold
name = "gas"
desc = "You shouldn't be clicking this."
gas_id = GAS_COLD
render_source = COLD_EFFECT_TARGET
var/obj/effect/gas_cold_back/b = null
/obj/gas_overlay/cold/Initialize(mapload, gas)