diff --git a/aurorastation.dme b/aurorastation.dme index dd8ed153cb0..bee76fd245b 100644 --- a/aurorastation.dme +++ b/aurorastation.dme @@ -23,7 +23,6 @@ #include "code\__DEFINES\_compile_helpers.dm" #include "code\__DEFINES\_flags.dm" #include "code\__DEFINES\_helpers.dm" -#include "code\__DEFINES\_layers.dm" #include "code\__DEFINES\_macros.dm" #include "code\__DEFINES\_protect.dm" #include "code\__DEFINES\_tick.dm" @@ -73,6 +72,7 @@ #include "code\__DEFINES\important_recursive_contents.dm" #include "code\__DEFINES\items_clothing.dm" #include "code\__DEFINES\jobs.dm" +#include "code\__DEFINES\layers.dm" #include "code\__DEFINES\lighting.dm" #include "code\__DEFINES\logging.dm" #include "code\__DEFINES\machinery.dm" @@ -271,6 +271,7 @@ #include "code\_onclick\hud\screen_objects.dm" #include "code\_onclick\hud\skybox.dm" #include "code\_onclick\hud\spell_screen_objects.dm" +#include "code\_onclick\hud\rendering\_renderer.dm" #include "code\_onclick\hud\screen_object_types\ai_screen_objs.dm" #include "code\_onclick\hud\screen_object_types\borer.dm" #include "code\_onclick\hud\screen_object_types\internals.dm" diff --git a/code/__DEFINES/flags.dm b/code/__DEFINES/flags.dm index eb061cca3df..94d6a1756b3 100644 --- a/code/__DEFINES/flags.dm +++ b/code/__DEFINES/flags.dm @@ -10,6 +10,11 @@ var/global/list/bitflags = list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204 #define ZM_ALLOW_ATMOS 4 // If this turf permits passage of air. #define ZM_MIMIC_NO_AO 8 // If the turf shouldn't apply regular turf AO and only do Z-mimic AO. #define ZM_NO_OCCLUDE 16 // Don't occlude below atoms if we're a non-mimic z-turf. +#define ZM_MIMIC_BASETURF 32 // We dont want to show space, we want to show the base turf of the area (for stuff like glass floors) + +// Movable flags. +#define ZMM_IGNORE 1 //! Do not copy this movable. +#define ZMM_MANGLE_PLANES 2 //! Check this movable's overlays/underlays for explicit plane use and mangle for compatibility with Z-Mimic. If you're using emissive overlays, you probably should be using this flag. Expensive, only use if necessary. // Convenience flag. #define ZM_MIMIC_DEFAULTS (ZM_MIMIC_BELOW) @@ -18,10 +23,11 @@ var/global/list/bitflags = list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204 var/list/mimic_defines = list( "ZM_MIMIC_BELOW", "ZM_MIMIC_OVERWRITE", - "ZM_ALLOW_LIGHTING", +// "ZM_ALLOW_LIGHTING", //Exists on Nebula, but not Aurora? "ZM_ALLOW_ATMOS", "ZM_MIMIC_NO_AO", - "ZM_NO_OCCLUDE" + "ZM_NO_OCCLUDE", + "ZM_MIMIC_BASETURF" ) //EMP protection diff --git a/code/__DEFINES/_layers.dm b/code/__DEFINES/layers.dm similarity index 56% rename from code/__DEFINES/_layers.dm rename to code/__DEFINES/layers.dm index a693c5e155a..7bb194fdb9a 100644 --- a/code/__DEFINES/_layers.dm +++ b/code/__DEFINES/layers.dm @@ -1,3 +1,4 @@ +// Planes, layers, and defaults for _renderer.dm /* from stddef.dm, planes & layers built into byond. @@ -15,15 +16,31 @@ FLOAT_PLANE = -32767 */ +#define LOWEST_PLANE -200 + #define CLICKCATCHER_PLANE -100 -#define PLANE_SPACE_BACKGROUND -99 -#define PLANE_SPACE_PARALLAX (PLANE_SPACE_BACKGROUND + 1) // -98 -#define PLANE_SKYBOX (PLANE_SPACE_PARALLAX + 1) // -97 -#define PLANE_SPACE_DUST (PLANE_SPACE_PARALLAX + 1) // -96 -#define PLANE_ABOVE_PARALLAX (PLANE_SPACE_DUST + 1) // -95 +#define SPACE_PLANE -99 + #define SPACE_LAYER 1 -#define DEFAULT_PLANE 0 +#define SKYBOX_PLANE -98 + #define SKYBOX_LAYER 1 + +#define DUST_PLANE -97 + #define DEBRIS_LAYER 1 + #define DUST_LAYER 2 + +// Openspace uses planes -80 through -70. +#define OPENTURF_MAX_PLANE -70 +#define OPENTURF_MAX_DEPTH 10 // The maxiumum number of planes deep we'll go before we just dump everything on the same plane. + +#define OVER_OPENSPACE_PLANE -4 + +#define WARP_EFFECT_PLANE -3 + +#define BLACKNESS_PLANE 0 //Blackness plane as per DM documentation. + +#define DEFAULT_PLANE 1 #define PLATING_LAYER 1 //ABOVE PLATING #define HOLOMAP_LAYER 1.01 @@ -109,45 +126,68 @@ #define OBSERVER_LAYER 5.1 #define OBFUSCATION_LAYER 5.2 + #define OVERMAP_SECTOR_LAYER 60 + #define OVERMAP_IMPORTANT_SECTOR_LAYER 61 + #define OVERMAP_SHIP_LAYER 62 + #define OVERMAP_SHUTTLE_LAYER 63 + #define AREA_LAYER 999 -//FUTURE OVERMAP_PLANE - #define OVERMAP_SECTOR_LAYER 3.15 - #define OVERMAP_IMPORTANT_SECTOR_LAYER 3.16 - #define OVERMAP_SHIP_LAYER 3.17 - #define OVERMAP_SHUTTLE_LAYER 3.18 +#define OBSERVER_PLANE 3 -//FUTURE LIGHTING PLANE - #define LIGHTBULB_LAYER 10 - #define LIGHTING_LAYER 11 - #define ABOVE_LIGHTING_LAYER 12 +#define LIGHTING_PLANE 4 + #define LIGHTING_LAYER 1 -//FUTURE EFFECTS_ABOVE_LIGHTING_PLANE - #define EFFECTS_ABOVE_LIGHTING_LAYER 13 // For overlays you want to be above light. - #define EYEGLOW_LAYER 14 - #define BEAM_PROJECTILE_LAYER 15 - #define SUPERMATTER_WALL_LAYER 16 - #define SPEECH_INDICATOR_LAYER 17 +#define EFFECTS_ABOVE_LIGHTING_PLANE 5 + #define EYEGLOW_LAYER 1 + #define BEAM_PROJECTILE_LAYER 2 + #define SUPERMATTER_WALL_LAYER 3 + #define LIGHTNING_LAYER 4 -//FUTURE FULLSCREEN_PLANE - #define FULLSCREEN_LAYER 18 - #define DAMAGE_LAYER 19 - #define IMPAIRED_LAYER 20 - #define BLIND_LAYER 21 - #define CRIT_LAYER 22 +#define FULLSCREEN_PLANE 6 + #define FULLSCREEN_LAYER 1 + #define DAMAGE_LAYER 2 + #define IMPAIRED_LAYER 3 + #define BLIND_LAYER 4 + #define CRIT_LAYER 5 -//FUTURE HUD_PLANE - #define UNDER_HUD_LAYER 23 - #define HUD_BASE_LAYER 24 - #define HUD_ITEM_LAYER 25 - #define HUD_ABOVE_ITEM_LAYER 26 - #define RADIAL_BACKGROUND_LAYER 26.5 - #define RADIAL_BASE_LAYER 27 - #define RADIAL_CONTENT_LAYER 28 +#define HUD_PLANE 7 + #define UNDER_HUD_LAYER 1 + #define HUD_BASE_LAYER 2 + #define HUD_ITEM_LAYER 3 + #define HUD_ABOVE_ITEM_LAYER 4 + #define RADIAL_BACKGROUND_LAYER 5 + #define RADIAL_BASE_LAYER 6 + #define RADIAL_CONTENT_LAYER 7 + +//-------------------- Rendering --------------------- + +/// Semantics - The final compositor or a filter effect renderer +#define RENDER_GROUP_NONE null + +/// Things to be drawn within the game context +#define RENDER_GROUP_SCENE 990 + +/// Things to be drawn within the screen context +#define RENDER_GROUP_SCREEN 995 + +/// The final render group, for compositing +#define RENDER_GROUP_FINAL 999 + +/// Integer (One of `*_PLANE`). The atom's rendering plane. See `code\__defines\__renderer.dm` for a list of valid planes. Also see the DM Reference for `plane var (atom)`. +/atom/plane = DEFAULT_PLANE #define DEFAULT_APPEARANCE_FLAGS (PIXEL_SCALE) +#define DEFAULT_RENDERER_APPEARANCE_FLAGS (PLANE_MASTER | NO_CLIENT_COLOR) + +/atom/appearance_flags = DEFAULT_APPEARANCE_FLAGS +/atom/movable/appearance_flags = DEFAULT_APPEARANCE_FLAGS | TILE_BOUND // Most AMs are not visibly bigger than a tile. +/image/appearance_flags = DEFAULT_APPEARANCE_FLAGS +/mutable_appearance/appearance_flags = DEFAULT_APPEARANCE_FLAGS // Inherits /image but re docs, subject to change + /atom/proc/hud_layerise() + plane = HUD_PLANE layer = HUD_ITEM_LAYER /image/proc/turf_decal_layerise() diff --git a/code/__DEFINES/zcopy.dm b/code/__DEFINES/zcopy.dm index 833c5cffd5f..3df518d6de1 100644 --- a/code/__DEFINES/zcopy.dm +++ b/code/__DEFINES/zcopy.dm @@ -1,5 +1,2 @@ -#define OPENTURF_MAX_PLANE -71 -#define OPENTURF_CAP_PLANE -70 // The multiplier goes here so it'll be on top of every other overlay. -#define OPENTURF_MAX_DEPTH 10 // The maxiumum number of planes deep we'll go before we just dump everything on the same plane. #define SHADOWER_DARKENING_FACTOR 0.7 // The multiplication factor for openturf shadower darkness. Lighting will be multiplied by this. #define SHADOWER_DARKENING_COLOR "#999999" // The above, but as an RGB string for lighting-less turfs. diff --git a/code/__HELPERS/icon_smoothing.dm b/code/__HELPERS/icon_smoothing.dm index db27cb8cc89..37f336d9754 100644 --- a/code/__HELPERS/icon_smoothing.dm +++ b/code/__HELPERS/icon_smoothing.dm @@ -233,10 +233,10 @@ var/istate = "[((x + y) ^ ~(x * y) + z) % 25]" underlay_appearance.icon = 'icons/turf/space.dmi' underlay_appearance.icon_state = istate - underlay_appearance.plane = PLANE_SPACE_BACKGROUND + underlay_appearance.plane = SPACE_PLANE var/image/dust = image('icons/turf/space_parallax1.dmi', istate) - dust.plane = PLANE_SPACE_DUST + dust.plane = DUST_PLANE dust.alpha = 80 dust.blend_mode = BLEND_ADD U += dust diff --git a/code/__HELPERS/overlay.dm b/code/__HELPERS/overlay.dm index a91ba9104e5..50437886e71 100644 --- a/code/__HELPERS/overlay.dm +++ b/code/__HELPERS/overlay.dm @@ -43,7 +43,7 @@ /proc/make_screen_overlay(icon, icon_state, brightness_factor = null, glow_radius = 1) var/image/overlay = image(icon, icon_state) - overlay.layer = ABOVE_LIGHTING_LAYER + overlay.plane = EFFECTS_ABOVE_LIGHTING_PLANE var/image/underlay = image(overlay) underlay.alpha = 128 underlay.filters = filter(type="bloom", offset=glow_radius, size=glow_radius*2, threshold="#000") diff --git a/code/_onclick/hud/fullscreen.dm b/code/_onclick/hud/fullscreen.dm index 31985b0a5cd..d004285be86 100644 --- a/code/_onclick/hud/fullscreen.dm +++ b/code/_onclick/hud/fullscreen.dm @@ -67,6 +67,7 @@ icon_state = "default" screen_loc = "CENTER-7,CENTER-7" mouse_opacity = MOUSE_OPACITY_TRANSPARENT + plane = FULLSCREEN_PLANE layer = FULLSCREEN_LAYER var/severity = 0 var/allstate = 0 //shows if it should show up for dead people too diff --git a/code/_onclick/hud/rendering/_render_readme.md b/code/_onclick/hud/rendering/_render_readme.md new file mode 100644 index 00000000000..3f1cd6c99af --- /dev/null +++ b/code/_onclick/hud/rendering/_render_readme.md @@ -0,0 +1,62 @@ +# 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 lets 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 a internal byond function that iterates over all objects in a clients 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 notoriosly slow, but we can see it's tick usage through the world.map_cpu var. We can also avoid more complex checks checking whether an object is visible on a clients 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 seperate thread and are not diplayed in the clientside profiler. Render handles the actual drawing of the screen. + +## 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 + +## 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 + +## Baystation/Aurorastation Differences + +The bay implementation differs in some points but you can treat the prior sections as broadly accurate. + +- Locally we have refactored and simplified the above concepts down to `/atom/movable/renderer` and undifferentiated relay movables. +- There is no type destinction between renderers intended to draw normal entities and renderers intended to draw other renderers. +- We instead refer to these as "Plane Renderers" and "Group Renderers", and note that Group Renderers may render *other* Group Renderers in turn. +- We additionally add the extra "Effect Renderer" as a distinct concept. Effect renderers are more virtual, and drawn to other renderers by filters instead. +- See the comments in _renderer.dm for detail next to code. \ No newline at end of file diff --git a/code/_onclick/hud/rendering/_renderer.dm b/code/_onclick/hud/rendering/_renderer.dm new file mode 100644 index 00000000000..bec1cc93e78 --- /dev/null +++ b/code/_onclick/hud/rendering/_renderer.dm @@ -0,0 +1,264 @@ +/* * +* Renderers +* Renderers are virtual objects that act as draw groups of things, including +* other Renderers. Renderers are similar to older uses of PLANE_MASTER but +* employ render targets using the "*" slate prefix to draw off-screen, to be +* composited in a controlled and flexible order and in some cases, reuse for +* visual effects. +*/ + +/// The base /renderer definition and defaults. +/atom/movable/renderer + abstract_type = /atom/movable/renderer + appearance_flags = DEFAULT_RENDERER_APPEARANCE_FLAGS + screen_loc = "CENTER" + plane = LOWEST_PLANE + blend_mode = BLEND_OVERLAY + + /// The compositing renderer this renderer belongs to. + var/group = RENDER_GROUP_FINAL + + /// The relay movable used to composite this renderer to its group. + var/atom/movable/relay // Also see https://secure.byond.com/forum/?post=2141928 maybe. + + /// Optional blend mode override for the renderer's composition relay. + var/relay_blend_mode + + /// If text, uses the text or, if TRUE, uses "*AUTO-[name]" + var/render_target_name = TRUE + + var/mob/owner = null + + +/atom/movable/renderer/Destroy() + owner = null + QDEL_NULL(relay) + return ..() + + +INITIALIZE_IMMEDIATE(/atom/movable/renderer) + + +/atom/movable/renderer/Initialize(mapload, mob/owner) + . = ..() + if (. == INITIALIZE_HINT_QDEL) + return + src.owner = owner + if (isnull(group)) + if (istext(render_target_name)) + render_target = render_target_name + return + if (istext(render_target_name)) + render_target = render_target_name + else if (render_target_name) + render_target = "*[ckey(name)]" + relay = new + relay.screen_loc = "CENTER" + relay.appearance_flags = PASS_MOUSE | NO_CLIENT_COLOR | KEEP_TOGETHER + relay.name = "[render_target] relay" + relay.mouse_opacity = mouse_opacity + relay.render_source = render_target + relay.layer = (plane + abs(LOWEST_PLANE)) * 0.5 + relay.plane = group + if (isnull(relay_blend_mode)) + relay.blend_mode = blend_mode + else + relay.blend_mode = relay_blend_mode + +/** +* Graphic preferences +* +* Some renderers may be able to use a graphic preference to determine how to display effects. For example reduce particle counts or filter variables. +*/ +/atom/movable/renderer/proc/GraphicsUpdate() + return + + +/** +* Renderers on /mob +* We attach renderers to mobs for their lifespan. Only mobs with clients get +* renderers, and they are removed again when the mob loses its client. Mobs +* get their own unique renderer instances but it would not be inconceivable +* to share them globally. +*/ + +/// The list of renderers associated with this mob. +/mob/var/list/atom/movable/renderer/renderers + + +/// Creates the mob's renderers on /Login() +/mob/proc/CreateRenderers() + if (!renderers) + renderers = list() + for (var/atom/movable/renderer/renderer as anything in subtypesof(/atom/movable/renderer)) + if(ispath(renderer, /atom/movable/renderer/shared)) + continue + renderer = new renderer (null, src) + renderers[renderer] = renderer.plane // (renderer = plane) format for visual debugging + if (renderer.relay) + my_client.screen += renderer.relay + my_client.screen += renderer + + for (var/atom/movable/renderer/zrenderer as anything in GLOB.zmimic_renderers) + if (zrenderer.relay) + my_client.screen += zrenderer.relay + my_client.screen += zrenderer + +/// Removes the mob's renderers on /Logout() +/mob/proc/RemoveRenderers() + if(my_client) + for(var/atom/movable/renderer/renderer as anything in renderers) + my_client.screen -= renderer + if (renderer.relay) + my_client.screen -= renderer.relay + qdel(renderer) + for (var/atom/movable/renderer/renderer as anything in GLOB.zmimic_renderers) + my_client.screen -= renderer + if (renderers) + renderers.Cut() + +/* * +* Plane Renderers +* We treat some renderers as planes with layers. When some atom has the same plane +* as a Plane Renderer, it is drawn by that renderer. The layer of the atom determines +* its draw order within the scope of the renderer. The draw order of same-layered things +* is probably by atom contents order, but can be assumed not to matter - if it's out of +* order, it should have a more appropriate layer value. +* Higher plane values are composited over lower. Here, they are ordered from under to over. +*/ + +/// Handles byond internal letterboxing. Avoid touching. +/atom/movable/renderer/letterbox + name = "Letterbox" + group = RENDER_GROUP_SCENE + plane = BLACKNESS_PLANE + blend_mode = BLEND_MULTIPLY + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + +/atom/movable/renderer/space + name = "Space" + group = RENDER_GROUP_SCENE + plane = SPACE_PLANE + +/atom/movable/renderer/skybox + name = "Skybox" + group = RENDER_GROUP_SCENE + plane = SKYBOX_PLANE + relay_blend_mode = BLEND_MULTIPLY + +//Z Mimic planemasters -> Could apply scaling for parallax though that requires copying appearances from adjacent turfs +GLOBAL_LIST_EMPTY(zmimic_renderers) + +/hook/startup/proc/create_global_renderers() //Some (most) renderers probably do not need to be instantiated per mob. So may as well make them global and just add to screen + //Zmimic planemasters + for(var/i = 0 to OPENTURF_MAX_DEPTH) + GLOB.zmimic_renderers += new /atom/movable/renderer/shared/zmimic(null, null, OPENTURF_MAX_PLANE - i) + + return TRUE + +/atom/movable/renderer/shared/zmimic + name = "Zrenderer" + group = RENDER_GROUP_SCENE + +/atom/movable/renderer/shared/zmimic/Initialize(mapload, _owner, _plane) + plane = _plane + name = "Zrenderer [plane]" + . = ..() + +/atom/movable/renderer/shared/zmimic/Destroy() + GLOB.zmimic_renderers -= src + return ..() + +// Draws the game world; live mobs, items, turfs, etc. +/atom/movable/renderer/game + name = "Game" + group = RENDER_GROUP_SCENE + plane = DEFAULT_PLANE + +/// Draws observers; ghosts, camera eyes, etc. +/atom/movable/renderer/observers + name = "Observers" + group = RENDER_GROUP_SCENE + plane = OBSERVER_PLANE + + +/// Draws darkness effects. +/atom/movable/renderer/lighting + name = "Lighting" + group = RENDER_GROUP_SCENE + plane = LIGHTING_PLANE + relay_blend_mode = BLEND_MULTIPLY + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + +/// Draws visuals that should not be affected by darkness. +/atom/movable/renderer/above_lighting + name = "Above Lighting" + group = RENDER_GROUP_SCENE + plane = EFFECTS_ABOVE_LIGHTING_PLANE + + +/// Draws full screen visual effects, like pain and bluespace. +/atom/movable/renderer/screen_effects + name = "Screen Effects" + group = RENDER_GROUP_SCENE + plane = FULLSCREEN_PLANE + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + + +/// Draws user interface elements. +/atom/movable/renderer/interface + name = "Interface" + group = RENDER_GROUP_SCREEN + plane = HUD_PLANE + + +/* * +* Group renderers +* We treat some renderers as render groups that other renderers subscribe to. Renderers +* subscribe themselves to groups by setting a group value equal to the plane of a Group +* Renderer. This value is used for the Renderer's relay to include it into the Group, and +* the Renderer's plane is used as the relay's layer. +* Group renderers can subscribe themselves to other Group Renderers. This allows for more +* granular manipulation of how the final scene is composed. +*/ + +/// Render group for stuff INSIDE the typical game context - people, items, lighting, etc. +/atom/movable/renderer/scene_group + name = "Scene Group" + group = RENDER_GROUP_FINAL + plane = RENDER_GROUP_SCENE + +/// Render group for stuff OUTSIDE the typical game context - UI, full screen effects, etc. +/atom/movable/renderer/screen_group + name = "Screen Group" + group = RENDER_GROUP_FINAL + plane = RENDER_GROUP_SCREEN + + +/// Render group for final compositing before user display. +/atom/movable/renderer/final_group + name = "Final Group" + group = RENDER_GROUP_NONE + plane = RENDER_GROUP_FINAL + + +/* * +* Effect Renderers +* Some renderers are used to produce complex screen effects. These are drawn using filters +* rather than composition groups, and may be added to another renderer in the following +* fashion. Setting a render_target_name with no group is the expected patter for Effect +* Renderers as it allows them to draw to a slate that will be empty unless a relevant +* behavior, such as the effect atom below, causes them to be noticeable. +*/ + +/// Renders the /obj/effect/warp example effect as well as gravity catapult effects +/atom/movable/renderer/warp + name = "Warp Effect" + group = RENDER_GROUP_NONE + plane = WARP_EFFECT_PLANE + render_target_name = "*warp" + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + +/atom/movable/renderer/scene_group/Initialize() + . = ..() + filters += filter(type = "displace", render_source = "*warp", size = 5) diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm index 9a14ebb63b8..ab5ba2a13c8 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -9,6 +9,7 @@ /obj/screen name = "" icon = 'icons/mob/screen/generic.dmi' + plane = HUD_PLANE layer = HUD_BASE_LAYER unacidable = 1 var/obj/master = null //A reference to the object in the slot. Grabs or items, generally. diff --git a/code/_onclick/hud/skybox.dm b/code/_onclick/hud/skybox.dm index 9ffcac7ce27..e10be5e6015 100644 --- a/code/_onclick/hud/skybox.dm +++ b/code/_onclick/hud/skybox.dm @@ -4,8 +4,7 @@ anchored = TRUE simulated = FALSE screen_loc = "CENTER:-224,CENTER:-224" - plane = PLANE_SKYBOX - blend_mode = BLEND_MULTIPLY + plane = SKYBOX_PLANE /client var/obj/skybox/skybox diff --git a/code/controllers/subsystems/icon_cache.dm b/code/controllers/subsystems/icon_cache.dm index e2289dcb4e2..8cc103521d7 100644 --- a/code/controllers/subsystems/icon_cache.dm +++ b/code/controllers/subsystems/icon_cache.dm @@ -99,7 +99,7 @@ SUBSYSTEM_DEF(icon_cache) /datum/controller/subsystem/icon_cache/proc/build_dust_cache() for (var/i in 0 to 25) var/image/im = image('icons/turf/space_parallax1.dmi',"[i]") - im.plane = PLANE_SPACE_DUST + im.plane = DUST_PLANE im.alpha = 80 im.blend_mode = BLEND_ADD space_dust_cache["[i]"] = im diff --git a/code/controllers/subsystems/skybox.dm b/code/controllers/subsystems/skybox.dm index 16c5c7e6e1e..27456738003 100644 --- a/code/controllers/subsystems/skybox.dm +++ b/code/controllers/subsystems/skybox.dm @@ -17,13 +17,13 @@ SUBSYSTEM_DEF(skybox) space_appearance_cache = new(26) for (var/i in 0 to 25) var/mutable_appearance/dust = mutable_appearance('icons/turf/space_dust.dmi', "[i]") - dust.plane = PLANE_SPACE_DUST + dust.plane = DUST_PLANE dust.alpha = 80 dust.blend_mode = BLEND_ADD var/mutable_appearance/space = new /mutable_appearance(/turf/space) space.name = "space" - space.plane = PLANE_SPACE_BACKGROUND + space.plane = SPACE_PLANE space.icon_state = "white" space.overlays += dust space_appearance_cache[i + 1] = space.appearance diff --git a/code/controllers/subsystems/zcopy.dm b/code/controllers/subsystems/zcopy.dm index 3670eea3209..87c63336549 100644 --- a/code/controllers/subsystems/zcopy.dm +++ b/code/controllers/subsystems/zcopy.dm @@ -16,6 +16,15 @@ SUBSYSTEM_DEF(zcopy) var/multiqueue_skips_turf = 0 var/multiqueue_skips_object = 0 + // Caches for fixup. + var/list/fixup_cache = list() + var/list/fixup_known_good = list() + + // Fixup stats. + var/fixup_miss = 0 + var/fixup_noop = 0 + var/fixup_hit = 0 + // Highest Z level in a given Z-group for absolute layering. // zstm[zlev] = group_max var/list/zlev_maximums = list() @@ -90,6 +99,10 @@ SUBSYSTEM_DEF(zcopy) Skips: \ Turfs [multiqueue_skips_turf] \ Objects [multiqueue_skips_object]\ + Fixups: \ + Hits [fixup_hit] \ + Miss [fixup_miss]\ + No Operation [fixup_noop]\ " return ..() @@ -159,20 +172,14 @@ SUBSYSTEM_DEF(zcopy) // Z-Turf on the bottom-most level, just fake-copy space. // If this is ever true, that turf should always pass this condition, so don't bother cleaning up beyond the Destroy() hook. if (!T.below) // Z-turf on the bottom-most level, just fake-copy space. - if (T.z_flags & ZM_MIMIC_OVERWRITE) - T.appearance = SSskybox.space_appearance_cache[(((T.x + T.y) ^ ~(T.x * T.y) + T.z) % 25) + 1] - T.name = initial(T.name) - T.desc = initial(T.desc) - T.gender = initial(T.gender) + flush_z_state(T) + if(T.z_flags & ZM_MIMIC_BASETURF) + simple_appearance_copy(T, get_base_turf_by_area(T), OPENTURF_MAX_PLANE) else - // Some openturfs have icons, so we can't overwrite their appearance. - if (!T.mimic_underlay) - T.mimic_underlay = new(T) - var/atom/movable/openspace/turf_proxy/TO = T.mimic_underlay - TO.appearance = SSskybox.space_appearance_cache[(((T.x + T.y) ^ ~(T.x * T.y) + T.z) % 25) + 1] - TO.name = T.name - TO.gender = T.gender // Need to grab this too so PLURAL works properly in examine. - TO.mouse_opacity = initial(TO.mouse_opacity) + simple_appearance_copy(T, SSskybox.space_appearance_cache[(((T.x + T.y) ^ ~(T.x * T.y) + T.z) % 25) + 1]) + + T.z_generation += 1 + T.z_queued -= 1 if (no_mc_tick) CHECK_TICK @@ -197,10 +204,32 @@ SUBSYSTEM_DEF(zcopy) var/t_target = OPENTURF_MAX_PLANE - turf_depth // This is where the turf (but not the copied atoms) gets put. + + // Turf is set to mimic baseturf, handle that and bail. + if (T.z_flags & ZM_MIMIC_BASETURF) + flush_z_state(T) + simple_appearance_copy(T, get_base_turf_by_area(T), t_target) + + if (T.above) + T.above.update_mimic() + + T.z_queued -= 1 + + if (no_mc_tick) + CHECK_TICK + else if (MC_TICK_CHECK) + break + continue + + // If we previously were MZ_MIMIC_BASETURF, there might be an orphaned proxy. + else if (T.mimic_underlay) + QDEL_NULL(T.mimic_underlay) + // Handle space parallax & starlight. if (T.below.z_eventually_space) T.z_eventually_space = TRUE - t_target = PLANE_SPACE_BACKGROUND + if ((T.below.z_flags & ZM_MIMIC_OVERWRITE) || T.below.type == /turf/space) + t_target = SPACE_PLANE if (T.z_flags & ZM_MIMIC_OVERWRITE) // This openturf doesn't care about its icon, so we can just overwrite it. @@ -245,7 +274,7 @@ SUBSYSTEM_DEF(zcopy) // Add everything below us to the update queue. for (var/thing in T.below) var/atom/movable/object = thing - if (QDELETED(object) || object.no_z_overlay || object.loc != T.below || object.invisibility == INVISIBILITY_ABSTRACT) + if (QDELETED(object) || (object.z_flags & ZMM_IGNORE) || object.loc != T.below || object.invisibility == INVISIBILITY_ABSTRACT) // Don't queue deleted stuff, stuff that's not visible, blacklisted stuff, or stuff that's centered on another tile but intersects ours. continue @@ -261,18 +290,24 @@ SUBSYSTEM_DEF(zcopy) var/override_depth var/original_type = object.type var/original_z = object.z + var/have_performed_fixup = FALSE + switch (object.type) if (/atom/movable/openspace/mimic) var/atom/movable/openspace/mimic/OOO = object original_type = OOO.mimiced_type override_depth = OOO.override_depth original_z = OOO.original_z + have_performed_fixup = OOO.have_performed_fixup if (/atom/movable/openspace/turf_proxy, /atom/movable/openspace/turf_mimic) // If we're a turf overlay (the mimic for a non-OVERWRITE turf), we need to make sure copies of us respect space parallax too if (T.z_eventually_space) // Yes, this is an awful hack; I don't want to add yet another override_* var. - override_depth = OPENTURF_MAX_PLANE - PLANE_SPACE_BACKGROUND + override_depth = OPENTURF_MAX_PLANE - SPACE_PLANE + + if (/atom/movable/openspace/turf_mimic) + original_z += 1 var/atom/movable/openspace/mimic/OO = object.bound_overlay @@ -292,6 +327,7 @@ SUBSYSTEM_DEF(zcopy) OO.mimiced_type = original_type OO.override_depth = override_depth OO.original_z = original_z + OO.have_performed_fixup ||= have_performed_fixup // Multi-queue to maintain ordering of updates to these // queueing it multiple times will result in only the most recent @@ -351,6 +387,7 @@ SUBSYSTEM_DEF(zcopy) OO.set_dir(OO.associated_atom.dir) OO.appearance = OO.associated_atom + OO.z_flags = OO.associated_atom.z_flags OO.plane = OPENTURF_MAX_PLANE - OO.depth OO.opacity = FALSE @@ -359,6 +396,13 @@ SUBSYSTEM_DEF(zcopy) if (OO.bound_overlay) // If we have a bound overlay, queue it too. OO.update_above() + // If an atom has explicit plane sets on its overlays/underlays, we need to replace the appearance so they can be mangled to work with our planing. + if (OO.z_flags & ZMM_MANGLE_PLANES) + var/new_appearance = fixup_appearance_planes(OO.appearance) + if (new_appearance) + OO.appearance = new_appearance + OO.have_performed_fixup = TRUE + if (no_mc_tick) CHECK_TICK else if (MC_TICK_CHECK) @@ -368,11 +412,132 @@ SUBSYSTEM_DEF(zcopy) curr_ov.Cut(1, qo_idex) qo_idex = 1 +/datum/controller/subsystem/zcopy/proc/flush_z_state(turf/T) + if (T.below) // Z-Mimic turfs aren't necessarily above another turf. + if (T.below.mimic_above_copy) + QDEL_NULL(T.below.mimic_above_copy) + if (T.below.mimic_proxy) + QDEL_NULL(T.below.mimic_proxy) + QDEL_NULL(T.mimic_underlay) + for (var/atom/movable/openspace/mimic/OO in T) + qdel(OO) + +/datum/controller/subsystem/zcopy/proc/simple_appearance_copy(turf/T, new_appearance, target_plane) + if (T.z_flags & ZM_MIMIC_OVERWRITE) + T.appearance = new_appearance + T.name = initial(T.name) + T.desc = initial(T.desc) + T.gender = initial(T.gender) + if (T.plane == 0 && target_plane) + T.plane = target_plane + else + // Some openturfs have icons, so we can't overwrite their appearance. + if (!T.mimic_underlay) + T.mimic_underlay = new(T) + var/atom/movable/openspace/turf_proxy/TO = T.mimic_underlay + TO.appearance = new_appearance + TO.name = T.name + TO.gender = T.gender // Need to grab this too so PLURAL works properly in examine. + TO.mouse_opacity = initial(TO.mouse_opacity) + if (TO.plane == 0 && target_plane) + TO.plane = target_plane + +/// Generate a new appearance from `appearance` with planes mangled to work with Z-Mimic. Do not pass a depth. +/datum/controller/subsystem/zcopy/proc/fixup_appearance_planes(appearance, depth = 0) + + // Adding this to guard against a reported runtime - supposed to be impossible, so cause is unclear. + if(!appearance) + return null + + if (fixup_known_good[appearance]) + fixup_hit += 1 + return null + if (fixup_cache[appearance]) + fixup_hit += 1 + return fixup_cache[appearance] + + // If you have more than 4 layers of overlays within overlays, I dunno what to say. + if (depth > 4) + var/icon_name = "[appearance:icon]" + WARNING("Fixup of appearance with icon [icon_name || ""] exceeded maximum recursion limit, bailing") + return null + + var/plane_needs_fix = FALSE + + // Don't fixup the root object's plane. + if (depth > 0) + switch (appearance:plane) + if (DEFAULT_PLANE, FLOAT_PLANE) + plane_needs_fix = FALSE //For lint + else + plane_needs_fix = TRUE + + // Scan & fix overlays + var/list/fixed_overlays + if (appearance:overlays:len) + var/mutated = FALSE + var/fixed_appearance + for (var/i in 1 to appearance:overlays:len) + if ((fixed_appearance = .(appearance:overlays[i], depth + 1))) + mutated = TRUE + if (!fixed_overlays) + fixed_overlays = new( length(appearance:overlays)) + fixed_overlays[i] = fixed_appearance + + if (mutated) + for (var/i in 1 to length(fixed_overlays)) + if (fixed_overlays[i] == null) + fixed_overlays[i] = appearance:overlays[i] + + // Scan & fix underlays + var/list/fixed_underlays + if (appearance:underlays:len) + var/mutated = FALSE + var/fixed_appearance + for (var/i in 1 to appearance:underlays:len) + if ((fixed_appearance = .(appearance:underlays[i], depth + 1))) + mutated = TRUE + if (!fixed_underlays) + fixed_underlays = new( length(appearance:underlays)) + fixed_underlays[i] = fixed_appearance + + if (mutated) + for (var/i in 1 to length(fixed_overlays)) + if (fixed_underlays[i] == null) + fixed_underlays[i] = appearance:underlays[i] + + // If we did nothing (no violations), don't bother creating a new appearance + if (!plane_needs_fix && !fixed_overlays && !fixed_underlays) + fixup_noop += 1 + fixup_known_good[appearance] = TRUE + return null + + fixup_miss += 1 + + var/mutable_appearance/MA = new(appearance) + if (plane_needs_fix) + MA.plane = depth == 0 ? DEFAULT_PLANE : FLOAT_PLANE + MA.layer = FLY_LAYER // probably fine + + if (fixed_overlays) + MA.overlays = fixed_overlays + + if (fixed_underlays) + MA.underlays = fixed_underlays + + fixup_cache[appearance] = MA.appearance + + return MA + #define FMT_DEPTH(X) (X == null ? "(null)" : X) // This is a dummy object used so overlays can be shown in the analyzer. /atom/movable/openspace/debug +/atom/movable/openspace/debug/turf + var/turf/parent + var/computed_depth + /client/proc/analyze_openturf(turf/T) set name = "Analyze Openturf" set desc = "Show the layering of an openturf and everything it's mimicking." @@ -381,6 +546,17 @@ SUBSYSTEM_DEF(zcopy) if (!check_rights(R_DEBUG)) return + var/real_update_count = 0 + var/claimed_update_count = T.z_queued + var/list/tq = SSzcopy.queued_turfs.Copy() + for (var/turf/Tu in tq) + if (Tu == T) + real_update_count += 1 + + CHECK_TICK + + var/list/temp_objects = list() + var/is_above_space = T.is_above_space() var/list/out = list( "", @@ -395,10 +571,26 @@ SUBSYSTEM_DEF(zcopy) "Below: [!T.below ? "(nothing)" : "[T.below] at [T.below.x],[T.below.y],[T.below.z]"]", "Depth: [FMT_DEPTH(T.z_depth)] [T.z_depth == OPENTURF_MAX_DEPTH ? "(max)" : ""]", "Generation: [T.z_generation]", + "Update count: Claimed [claimed_update_count], Actual [real_update_count] - [claimed_update_count == real_update_count ? "OK" : "MISMATCH"]", "