Skip to content
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,10 +249,11 @@ While the window is open, the Minestom host:
wheel, and number keys reach the server. Game mode, ability, hotbar, time, and max health updates the server sends
meanwhile still reach the client, followed by packets that hide them again. Closing restores the player's game mode
(or the one the server set meanwhile), abilities, position, and held slot.
- hides the HUD by holding the client's world age negative, which the pack's shaders read. The hidden strip at the
bottom of the screen can clip the player's own inventory (E) at small GUI heights, since the server is not told it
opened. While the keyboard prompt is open, the age sits in a second negative band in which the shaders also hide the
untitled anvil that captures the typing.
- hides the HUD by holding the client's world age negative, which the pack's shaders read. The shaders also drop the
crosshair, and the attack indicator quads they match by exact position. The hidden strip at the bottom of the screen
can clip the player's own inventory (E) at small GUI heights, since the server is not told it opened. While the
keyboard prompt is open, the age sits in a second negative band in which the shaders also hide the untitled anvil that
captures the typing.
- closes the window when the player dies or spawns into another instance. Opening one while the player is dead or
respawning throws, so a respawn listener should open it on the next tick.

Expand Down
58 changes: 54 additions & 4 deletions crates/window-core/src/hud/renderer/fullscreen.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! Full-screen snippets of the pack_format 85-88 `core/text` vertex shader, and the `core/position_tex_color`
//! shaders that hide the HUD and the vanilla crosshair while a full-screen window is open, and the keyboard prompt's
//! shaders that hide the HUD, the vanilla crosshair, and the attack indicators while a full-screen window is open, and the keyboard prompt's
//! anvil while it is open (see [`super::prompt`]).
//!
//! The text shader moves every vertex of a full-screen marker (see [`crate::fullscreen`]) in the world pass straight
Expand Down Expand Up @@ -95,7 +95,8 @@ pub(super) fn render_fullscreen_vertex_helpers() -> String {
VERTEX_HELPERS.replace("@PLACEMENT@", CANVAS_PLACEMENT).replace("@DEPTH_STEP@", &format!("{LAYER_DEPTH_STEP:?}"))
}

/// Renders `core/position_tex_color.vsh`, vanilla's GUI sprite shader plus the HUD strip and prompt inputs. Like
/// Renders `core/position_tex_color.vsh`, vanilla's GUI sprite shader plus the HUD strip, attack indicator, and prompt
/// inputs. Like
/// vanilla, it declares its uniform blocks inline, since the shader is also compiled before resource packs load.
pub(in crate::hud) fn render_position_tex_color_vertex() -> String {
let source = r#"#version 330
Expand Down Expand Up @@ -127,7 +128,39 @@ out vec2 texCoord0;
out vec4 vertexColor;
out vec2 windowHudPos;
flat out int windowHudHidden;
out vec4 windowHudIndicator;
out vec2 windowHudGui;
flat out vec4 windowHudRects[4];
@PROMPT@
// Whether a vertex lies on a corner of `rect` (left, top, right, bottom in GUI pixels). `freeRight` also accepts any x
// right of the left edge up to 17 pixels past it, and `freeTop` any y from the top edge down to the bottom edge, for
// the progress sprites whose far edge follows the charge. A quad drawn at the rect has all four vertices on corners.
bool window_fs_on_corner(vec2 pos, vec4 rect, bool freeRight, bool freeTop) {
bool onX = abs(pos.x - rect.x) < 0.01 || abs(pos.x - rect.z) < 0.01
|| (freeRight && pos.x > rect.x + 0.01 && pos.x <= rect.x + 17.0);
bool onY = abs(pos.y - rect.w) < 0.01 || abs(pos.y - rect.y) < 0.01
|| (freeTop && pos.y >= rect.y - 0.01 && pos.y < rect.w - 0.01);
return onX && onY;
}

// Sets, per attack indicator rect, whether a vertex lies on one of its corners (windowHudIndicator) and the rect's
// maximal extent (windowHudRects), in GUI pixels from the top-left with the GUI size gui: the crosshair indicator's 16x4
// background and progress and its 16x16 full sprite, which start at (gui.x / 2 - 8, gui.y / 2 + 9), and the 18x18
// hotbar indicator background and progress in the row gui.y - 20, either 97 pixels right of the centre or 113 left of
// it, depending on the main arm.
void window_fs_indicator(vec2 pos, vec2 gui, bool hidden) {
vec2 centre = floor(gui / 2.0);
vec4 crosshair = vec4(centre.x - 8.0, centre.y + 9.0, centre.x + 8.0, centre.y + 13.0);
vec4 full = vec4(crosshair.xy, crosshair.zy + vec2(0.0, 16.0));
vec4 hotbarRight = vec4(centre.x + 97.0, gui.y - 20.0, centre.x + 115.0, gui.y - 2.0);
vec4 hotbarLeft = hotbarRight - vec4(210.0, 0.0, 210.0, 0.0);
windowHudRects = vec4[4](crosshair + vec4(0.0, 0.0, 1.0, 0.0), full, hotbarRight, hotbarLeft);
windowHudIndicator = hidden ? vec4(
window_fs_on_corner(pos, crosshair, true, false), window_fs_on_corner(pos, full, false, false),
window_fs_on_corner(pos, hotbarRight, false, true), window_fs_on_corner(pos, hotbarLeft, false, true))
: vec4(0.0);
}

void main() {
gl_Position = ProjMat * ModelViewMat * vec4(Position, 1.0);

Expand All @@ -139,15 +172,23 @@ void main() {
windowHudHidden = GameTime < 0.0 && ProjMat[3][3] == 1.0 ? 1 : 0;
windowHudPos = vec2(gl_Position.x / ProjMat[0][0], (gl_Position.y + 1.0) / -ProjMat[1][1]);

// Three matching corners of an axis-aligned quad fix its fourth, so a rect's flag reaches 1.0 across a quad only if
// it is that rect, and the fragment's bounds check confines any sliver of a partial match to the rect itself. The
// GUI size is the client's: the fractional framebuffer size over the scale, rounded up. Fractional sizes are
// multiples of 1 / scale, so 0.01 absorbs float error without rounding a true fraction down.
vec2 gui = ceil(vec2(2.0 / ProjMat[0][0], -2.0 / ProjMat[1][1]) - 0.01);
windowHudGui = vec2((gl_Position.x + 1.0) / ProjMat[0][0], (gl_Position.y - 1.0) / ProjMat[1][1]);
window_fs_indicator(windowHudGui, gui, windowHudHidden == 1);

window_prompt_clip(gl_Position);
}
"#;
source.replace("@PROMPT@", &render_prompt_vertex_helpers())
}

/// Renders `core/position_tex_color.fsh`, which while the HUD is hidden discards GUI sprite fragments inside the HUD
/// strip, and texels carrying the crosshair marker alpha, which the pack's crosshair sprite override sets on its
/// opaque texels. Clipping per fragment cuts sprites crossing the strip's edge instead of distorting them. While the
/// strip, texels carrying the crosshair marker alpha, which the pack's crosshair sprite override sets on its opaque
/// texels, and quads the vertex shader matched to a vanilla attack indicator. Clipping per fragment cuts sprites crossing the strip's edge instead of distorting them. While the
/// prompt's anvil is open, it also discards GUI sprite fragments inside the anvil's rect.
pub(in crate::hud) fn render_position_tex_color_fragment() -> String {
format!(
Expand All @@ -170,13 +211,22 @@ in vec2 texCoord0;
in vec4 vertexColor;
in vec2 windowHudPos;
flat in int windowHudHidden;
in vec4 windowHudIndicator;
in vec2 windowHudGui;
flat in vec4 windowHudRects[4];
{prompt_helpers}
out vec4 fragColor;

void main() {{{prompt_clip}
if (windowHudHidden == 1 && abs(windowHudPos.x) < WINDOW_FS_HUD_STRIP.x && windowHudPos.y < WINDOW_FS_HUD_STRIP.y) {{
discard;
}}
for (int i = 0; i < 4; i++) {{
if (windowHudIndicator[i] > 0.99999 && all(greaterThanEqual(windowHudGui, windowHudRects[i].xy))
&& all(lessThanEqual(windowHudGui, windowHudRects[i].zw))) {{
discard;
}}
}}
vec4 texel = texture(Sampler0, texCoord0);
if (windowHudHidden == 1 && int(round(texel.a * 255.0)) == WINDOW_FS_CROSSHAIR_ALPHA) {{
discard;
Expand Down
10 changes: 10 additions & 0 deletions crates/window-core/src/hud/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,18 @@ fn fullscreen_branch_joins_the_text_shader_and_hides_the_hud_strip() {
["text.vsh", "text.fsh", "position_tex_color.vsh", "position_tex_color.fsh", "gui.vsh", "gui.fsh"]
);
assert!(alone.files[0].contents.contains("void window_fs_apply()"));
assert!(!alone.files[2].contents.contains("gl_VertexID"));
assert!(alone.files[2].contents.contains("ceil(vec2(2.0 / ProjMat[0][0]"));
let fragment = &alone.files[3].contents;
assert!(fragment.contains("const vec2 WINDOW_FS_HUD_STRIP = vec2(96.0, 56.0);"), "{fragment}");
let indicator = &alone.files[2].contents;
assert!(indicator.contains("window_fs_indicator(windowHudGui, gui, windowHudHidden == 1);"), "{indicator}");
assert!(
fragment
.contains("windowHudIndicator[i] > 0.99999 && all(greaterThanEqual(windowHudGui, windowHudRects[i].xy))"),
"{fragment}"
);
assert!(indicator.contains("- 0.01);"), "{indicator}");
assert!(alone.files[2].contents.contains("float GameTime;\n int MenuBlurRadius;\n int UseRgss;\n};"));

// While the prompt's anvil is open, GUI text is hidden only in its field and label bands, and fills only in the
Expand Down
6 changes: 3 additions & 3 deletions plugin/test/golden/fullscreen/expected.sha256
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
b902022fd657ad3b7b7cd44391210c7c6f2d5086bebba022fecbbee2a951df3f dist/assets/minecraft/shaders/core/entity.vsh
d56563e462372062c7a8e7a417b4a1b107f0d291ed561b6d74c6a3c0eb8ffa71 dist/assets/minecraft/shaders/core/gui.fsh
de99b339c5bc4b28a85d9fe06066c528fa7c10497037c51dca18652360f14aa3 dist/assets/minecraft/shaders/core/gui.vsh
13dfc12b3e9554fae3cd1c2c43cd78d92ebd8405c7493b4252da616482ed93cd dist/assets/minecraft/shaders/core/position_tex_color.fsh
df4975456519e08d7a7065027ae566a58314fa95d6a7f58764a2bac5638e1e72 dist/assets/minecraft/shaders/core/position_tex_color.vsh
96913f57883c88fa694c0ba08eeeda4b9d8973313fa1adf4326c58cadab77aa3 dist/assets/minecraft/shaders/core/position_tex_color.fsh
fce31a4ee7abc60a5676275134fba3200604442b2e9031cdaa4cedc8d4c4d2c4 dist/assets/minecraft/shaders/core/position_tex_color.vsh
56bb409c4026de938f0a2f77bd3ac9131658aad33facbb9b92145e9095ba0fdd dist/assets/minecraft/shaders/core/text.fsh
f6a980f98a5850b6530119b72ef064d6e3365f2176c2ba9c50b7e3ce6524173b dist/assets/minecraft/shaders/core/text.vsh
d8c8dc63512f18a6f85b86ce57d473cc2b2b76453ba1c0fd3ba97b47bbb5b26f dist/assets/minecraft/shaders/include/window_fullscreen.glsl
Expand Down Expand Up @@ -35,7 +35,7 @@ a962b9aee37baf82e29390badf062f445b50a8e577de3731aa0947fcdbbd9222 dist/assets/wi
922d405eb87e7d345085f0d93e32a111d1e4bf6ddcc4102f0b1bb0e754ce06be dist/assets/window/textures/font/fullscreen/window/menu_tooltip_220_160_41x16_0_0.png
922d405eb87e7d345085f0d93e32a111d1e4bf6ddcc4102f0b1bb0e754ce06be dist/assets/window/textures/font/fullscreen/window/menu_tooltip_256_160_41x16_0_0.png
eacb1a012fc0820bb358ea06380857fd97d62a420932142014ac89bcc4afbbc3 dist/assets/window/textures/gui/hitbox.png
9d0099d4c382410fe21ad93245efc8a49ca696468c1440ea3f5b9113bc492708 dist/assets/window/window/debug.json
748f9f2749c182fc80ef6f030e0f0719743db138268d617b3005cd0d2e25f75a dist/assets/window/window/debug.json
65dd34ebb2d97b0c2f9ba3e310bc02fe652f4690a069e47d8b3aa99f76a94bfc dist/pack.mcmeta
23bf284dd31ac909286bdae4abd75e24f127fe9c720c9bd0cac4872c6f130a46 kotlin-out/LookoutFullscreen.kt
c7112fbdeac4c678c3fdc1eacdd4fbccedbc28c3b0ff8baf44ef5af2d1d104a2 kotlin-out/MenuFullscreen.kt
Expand Down
Loading