chore(history): squash 59 commit(s) from 2026-09-23
- refactor(hud/weather/render): Scoreboard 931→240 + Rain 903→159 + UiMeshGeometry 956→99 (8.5.2) - refactor(chams): Chams 985 → 129 + 4×≤195 (8.5.2) - refactor(module): Module 1031 → 139 + 4×≤134 (8.5.2) - refactor(tooltips): BetterTooltips 960 → 181 + 4×≤199 (8.5.2) - refactor(clickgui): ClickGuiPickerState 970→166 + ModulesMenuScreen 966→145 (8.5.2) - refactor(clickgui): CombatProtocolHeuristicsEditorState 932→79 + 4×≤192 (8.5.2) - refactor(hud): CompactHudStatModel 894 → 125 + 4×≤175 (8.5.2) - refactor(iris): ShaderPatchCompiler 843→66 + 4×≤174 (8.5.2) - refactor(tab): CustomTabList 834→136 + 4×≤166 (8.5.2) - refactor(relations): OnlineRelationPlayerPickerComponent 837→74 + 4×≤192 (8.5.2) - refactor(hits): HitEffect 770→120 + 4×≤176 (8.5.2) - refactor(predict): Predictions 815→150 + 4×≤195 (8.5.2) - refactor(config): ConfigProfilesComponent 799→125 + 4×≤195 (8.5.2) - refactor(sim): PlayerMovementSimulation 757→157 + 4×≤194 (8.5.2) - refactor(predict): ProjectilePuncher 747→104 + 3×≤134 (8.5.2) - refactor(crosshair): Crosshair 743→143 + 4×≤167 (8.5.2) - refactor(mediaplayer): MediaPlayer 743→174 + 4×≤196 (8.5.2) - refactor(hud): Itemizer 699→193 + Cooldowns 707→151 (8.5.2) - refactor(hud): Potions 617→176 + 4×≤166 (8.5.2) - refactor(hud): Admins 702→118 + 4×≤138 (8.5.2) - refactor(hud): Keybinds 611→157 + 4×≤160 (8.5.2) - refactor(hud): ModuleList 499→181 + 3×≤150 (8.5.2) - refactor(hud): Radar 392→161 + 2×≤124 (8.5.2) - refactor(hud): Fps 309→168 + 3×≤107 (8.5.2) - refactor(hud): Memory 304→151 + 3×≤144 (8.5.2) - refactor(hud): Tps 339→154 + 3×≤143 (8.5.2) - refactor(hud): Ping 309→145 + 3×≤125 (8.5.2) - refactor(hud): SpeedBps 334→156 + 3×≤142 (8.5.2) - refactor(hud): GameTime 314→150 + 3×≤127 (8.5.2) - refactor(hud): SystemTime 300→136 + 3×≤130 (8.5.2) - refactor(hud): Coordinates 337→174 + 2×≤172 (8.5.2) - refactor(hud): Armor 324→162 + 2×≤148 (8.5.2) - refactor(hud): Inventory 476→153 + 3×≤181 (8.5.2) - docs(todo): обновлён 8.5.2 — отмечены готовые гиганты (BetterButtons/InventorySwap/Svg/Темы/WorldParticles/Module/Chams и хвост) - docs(api): папка combatant-client-26.2/docs + addons.md (API можно ломать при рефакторе) - refactor(visuals): BlockHighlight 692→271 + 4×≤200 (8.5.2) - refactor(mixins): GameRendererMixin 830→344 (хуки) + 4 хелпера (8.5.2) - refactor(visuals): WorldParticles 821→260 + 6×≤200, режимы добиты (8.5.2) - docs(todo): 8.5.2 — WorldParticles/GameRendererMixin/BlockHighlight отмечены ГОТОВО - refactor(render): MeshBuilder 738→447 (фасад write-API) + 3 хелпера, FrameStats снесён (8.5.2) - docs(todo): Фаза 10 — дизайн платформы (аккаунты/конфиги/аватарки), backlog RPC-чата - docs(todo): Подсистема 3 — Аддоны 2.0 (скрипты/версии) + витрина аддонов + админка - docs(todo): Подсистема 2 — RPC-чат/друзья/виджеты-телеметрия + рейт-лимиты, доп. правки Подсистемы 3 - chore: переименование combatant-client-26.2 -> mod, добавлены backend/ и frontend/ - docs(backend): implementation plan for accounts-service (Подсистема 1, часть 1) - chore: пересоздать backend/frontend через cargo new и bun create vite + tailwind; убрать таблицу компонентов из TODO.md - docs(backend): edition 2024 в плане вместо 2021 - chore: обновить версии до реально актуальных (проверено компиляцией) - docs: правила платформы (лимит 250 строк, антипаттерны backend/frontend по итогам ресёрча) + frontend/ARCHITECTURE.md - docs: добавить правило ≤4 файла на папку в правила платформы (backend/frontend) - docs: commit messages in English from now on - chore: gitignore .superpowers/ scratch directory - docs: commit messages in English from now on (mod) - feat(backend): scaffold accounts-service with health check - chore(backend): remove target/ build artifacts from git, add gitignore - chore: track docs/ in git (was accidentally gitignored, never committed) - chore(backend): convert to Cargo workspace, plan full microservice layout - feat(backend): accounts/avatars/device_links schema + migration - feat(backend): argon2id password hashing
This commit is contained in:
parent
ec10395511
commit
9d08fa910a
2239 changed files with 35265 additions and 28110 deletions
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render;
|
||||
|
||||
import com.mojang.blaze3d.PrimitiveTopology;
|
||||
import com.mojang.blaze3d.pipeline.BlendFunction;
|
||||
import com.mojang.blaze3d.pipeline.ColorTargetState;
|
||||
import com.mojang.blaze3d.pipeline.DepthStencilState;
|
||||
import com.mojang.blaze3d.pipeline.RenderPipeline;
|
||||
import com.mojang.blaze3d.platform.CompareOp;
|
||||
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
|
||||
import net.minecraft.client.renderer.BindGroupLayouts;
|
||||
import net.minecraft.client.renderer.rendertype.RenderSetup;
|
||||
import net.minecraft.client.renderer.rendertype.RenderType;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import dev.loki.lovisual.mixins.accessors.renderer.RenderLayerInvokerAccessor;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public enum LoVisualEntityRenderTypes {
|
||||
;
|
||||
|
||||
private static final RenderPipeline ENTITY_TRANSLUCENT_NO_DEPTH_WRITE = RenderPipeline.builder()
|
||||
.withLocation(Identifier.fromNamespaceAndPath("lovisual", "pipeline/entity_translucent_no_depth_write"))
|
||||
.withBindGroupLayout(BindGroupLayouts.GLOBALS)
|
||||
.withBindGroupLayout(BindGroupLayouts.MATRICES_PROJECTION)
|
||||
.withBindGroupLayout(BindGroupLayouts.FOG)
|
||||
.withBindGroupLayout(BindGroupLayouts.LIGHTING)
|
||||
.withBindGroupLayout(BindGroupLayouts.SAMPLER0_SAMPLER1_SAMPLER2)
|
||||
.withVertexShader("core/entity")
|
||||
.withFragmentShader("core/entity")
|
||||
.withVertexBinding(0, DefaultVertexFormat.ENTITY)
|
||||
.withPrimitiveTopology(PrimitiveTopology.QUADS)
|
||||
.withShaderDefine("ALPHA_CUTOUT", 0.1f)
|
||||
.withShaderDefine("PER_FACE_LIGHTING")
|
||||
.withColorTargetState(new ColorTargetState(BlendFunction.TRANSLUCENT))
|
||||
.withDepthStencilState(new DepthStencilState(CompareOp.GREATER_THAN_OR_EQUAL, false))
|
||||
.build();
|
||||
|
||||
private static final RenderPipeline ENTITY_TRANSLUCENT_NO_DEPTH_WRITE_NO_CULL = RenderPipeline.builder()
|
||||
.withLocation(Identifier.fromNamespaceAndPath("lovisual", "pipeline/entity_translucent_no_depth_write_no_cull"))
|
||||
.withBindGroupLayout(BindGroupLayouts.GLOBALS)
|
||||
.withBindGroupLayout(BindGroupLayouts.MATRICES_PROJECTION)
|
||||
.withBindGroupLayout(BindGroupLayouts.FOG)
|
||||
.withBindGroupLayout(BindGroupLayouts.LIGHTING)
|
||||
.withBindGroupLayout(BindGroupLayouts.SAMPLER0_SAMPLER1_SAMPLER2)
|
||||
.withVertexShader("core/entity")
|
||||
.withFragmentShader("core/entity")
|
||||
.withVertexBinding(0, DefaultVertexFormat.ENTITY)
|
||||
.withPrimitiveTopology(PrimitiveTopology.QUADS)
|
||||
.withShaderDefine("ALPHA_CUTOUT", 0.1f)
|
||||
.withShaderDefine("PER_FACE_LIGHTING")
|
||||
.withColorTargetState(new ColorTargetState(BlendFunction.TRANSLUCENT))
|
||||
.withDepthStencilState(new DepthStencilState(CompareOp.GREATER_THAN_OR_EQUAL, false))
|
||||
.withCull(false)
|
||||
.build();
|
||||
|
||||
private static final Map<Key, RenderType> ENTITY_TRANSLUCENT_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
public static RenderType entityTranslucentNoDepthWrite(Identifier texture, boolean cull) {
|
||||
return ENTITY_TRANSLUCENT_CACHE.computeIfAbsent(new Key(texture, cull), LoVisualEntityRenderTypes::createEntityTranslucent);
|
||||
}
|
||||
|
||||
public static List<RenderPipeline> irisMappedPipelines() {
|
||||
return List.of(ENTITY_TRANSLUCENT_NO_DEPTH_WRITE, ENTITY_TRANSLUCENT_NO_DEPTH_WRITE_NO_CULL);
|
||||
}
|
||||
|
||||
private static RenderType createEntityTranslucent(Key key) {
|
||||
RenderPipeline pipeline = key.cull
|
||||
? ENTITY_TRANSLUCENT_NO_DEPTH_WRITE
|
||||
: ENTITY_TRANSLUCENT_NO_DEPTH_WRITE_NO_CULL;
|
||||
|
||||
RenderSetup setup = RenderSetup.builder(pipeline)
|
||||
.withTexture("Sampler0", key.texture)
|
||||
.useLightmap()
|
||||
.useOverlay()
|
||||
.affectsCrumbling()
|
||||
.sortOnUpload()
|
||||
.setOutline(RenderSetup.OutlineProperty.AFFECTS_OUTLINE)
|
||||
.createRenderSetup();
|
||||
return RenderLayerInvokerAccessor.callOf(
|
||||
key.cull ? "lovisual_entity_translucent_no_depth_write" : "lovisual_entity_translucent_no_depth_write_no_cull",
|
||||
setup
|
||||
);
|
||||
}
|
||||
|
||||
private record Key(Identifier texture, boolean cull) {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render;
|
||||
|
||||
public enum ShaderEspRenderContext {
|
||||
;
|
||||
|
||||
private static final ThreadLocal<Integer> DEPTH = ThreadLocal.withInitial(() -> 0);
|
||||
|
||||
public static Scope enter() {
|
||||
DEPTH.set(DEPTH.get() + 1);
|
||||
return new Scope();
|
||||
}
|
||||
|
||||
public static boolean isActive() {
|
||||
return DEPTH.get() > 0;
|
||||
}
|
||||
|
||||
public static final class Scope implements AutoCloseable {
|
||||
private boolean closed;
|
||||
|
||||
private Scope() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
closed = true;
|
||||
int next = Math.max(0, DEPTH.get() - 1);
|
||||
if (next == 0) {
|
||||
DEPTH.remove();
|
||||
} else {
|
||||
DEPTH.set(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.compat;
|
||||
|
||||
import dev.tr7zw.entityculling.versionless.access.Cullable;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
|
||||
public enum EntityCullingCompat {
|
||||
;
|
||||
|
||||
private static final String MOD_ID = "entityculling";
|
||||
|
||||
public static void forceVisibleForShaderEsp(Entity entity) {
|
||||
if (entity == null || !FabricLoader.getInstance().isModLoaded(MOD_ID)) {
|
||||
return;
|
||||
}
|
||||
if (!(entity instanceof Cullable cullable)) {
|
||||
return;
|
||||
}
|
||||
|
||||
cullable.setCulled(false);
|
||||
cullable.setOutOfCamera(false);
|
||||
cullable.setTimeout();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.effects;
|
||||
|
||||
import com.mojang.blaze3d.textures.GpuTextureView;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.PortalProcessor;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import dev.loki.lovisual.mixins.accessors.entity.EntityAccessor;
|
||||
import dev.loki.lovisual.render.engine.pipeline.LoVisualRenderPipelines;
|
||||
import dev.loki.lovisual.render.engine.postprocess.post.PostProcessManager;
|
||||
import dev.loki.lovisual.render.engine.postprocess.post.PostProcessPass;
|
||||
import dev.loki.lovisual.render.engine.renderer.FullScreenRenderer;
|
||||
import dev.loki.lovisual.render.engine.uniform.impl.world.HeatUniforms;
|
||||
|
||||
public final class NetherPortalRiftPass implements PostProcessPass {
|
||||
private static final float FADE_IN_SECONDS = 0.25f;
|
||||
private static final float FADE_OUT_SECONDS = 0.35f;
|
||||
|
||||
private float strength;
|
||||
private long lastUpdateMs;
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
return mc != null && mc.player != null && mc.level != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 11;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Phase getPhase() {
|
||||
return Phase.POST_HAND;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean render(GpuTextureView src, GpuTextureView dst, float tickDelta) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || mc.player == null || mc.level == null) return false;
|
||||
if (src == null || dst == null) return false;
|
||||
|
||||
PortalProcessor portalManager = null;
|
||||
if (mc.player instanceof EntityAccessor accessor) {
|
||||
portalManager = accessor.lovisual$getPortalManager();
|
||||
}
|
||||
boolean active = portalManager != null && portalManager.isInsidePortalThisTick() && isInNetherPortalBlock();
|
||||
float t = smoothTowards(active ? 1.0f : 0.0f);
|
||||
if (t <= 0.001f) return false;
|
||||
|
||||
float time = (System.currentTimeMillis() % 100000L) / 1000.0f;
|
||||
float intensity = 0.75f * t;
|
||||
float distortion = 0.65f * t;
|
||||
float scale = 1.25f;
|
||||
float speed = 1.65f;
|
||||
float vignetteStrength = 0.85f * t;
|
||||
float vignetteRadius = 0.38f;
|
||||
float vignetteSoftness = 0.28f;
|
||||
|
||||
HeatUniforms.update(
|
||||
intensity,
|
||||
distortion,
|
||||
scale,
|
||||
speed,
|
||||
vignetteStrength,
|
||||
vignetteRadius,
|
||||
vignetteSoftness,
|
||||
time
|
||||
);
|
||||
|
||||
FullScreenRenderer.ensureInit();
|
||||
FullScreenRenderer.begin("LoVisual Fullscreen Pass")
|
||||
.attachment(dst)
|
||||
.pipeline(LoVisualRenderPipelines.PORTAL_RIFT)
|
||||
.uniform("Heat", HeatUniforms.get())
|
||||
.sampler("u_Texture", src, PostProcessManager.getSampler())
|
||||
.end();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private float smoothTowards(float target) {
|
||||
long now = System.currentTimeMillis();
|
||||
float dt = (lastUpdateMs == 0L) ? 0.016f : (now - lastUpdateMs) / 1000.0f;
|
||||
lastUpdateMs = now;
|
||||
|
||||
float rate = target >= strength ? (1.0f / FADE_IN_SECONDS) : (1.0f / FADE_OUT_SECONDS);
|
||||
float step = Mth.clamp(rate * dt, 0.0f, 1.0f);
|
||||
strength += (target - strength) * step;
|
||||
strength = Mth.clamp(strength, 0.0f, 1.0f);
|
||||
return strength;
|
||||
}
|
||||
|
||||
private boolean isInNetherPortalBlock() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || mc.level == null || mc.player == null) return false;
|
||||
var pos = mc.player.blockPosition();
|
||||
if (mc.level.getBlockState(pos).is(Blocks.NETHER_PORTAL)) return true;
|
||||
return mc.level.getBlockState(pos.above()).is(Blocks.NETHER_PORTAL);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.effects;
|
||||
|
||||
import com.mojang.blaze3d.textures.GpuTextureView;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.util.Mth;
|
||||
import dev.loki.lovisual.render.engine.pipeline.LoVisualRenderPipelines;
|
||||
import dev.loki.lovisual.render.engine.postprocess.post.PostProcessManager;
|
||||
import dev.loki.lovisual.render.engine.postprocess.post.PostProcessPass;
|
||||
import dev.loki.lovisual.render.engine.renderer.FullScreenRenderer;
|
||||
import dev.loki.lovisual.render.engine.uniform.impl.post.PostProcessUniforms;
|
||||
|
||||
public final class SleepOverlayPass implements PostProcessPass {
|
||||
private static final float FADE_IN_SECONDS = 0.6f;
|
||||
private static final float FADE_OUT_SECONDS = 0.6f;
|
||||
|
||||
private float strength;
|
||||
private long lastUpdateMs;
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
return mc != null && mc.player != null && mc.level != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 12;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Phase getPhase() {
|
||||
return Phase.POST_HAND;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean render(GpuTextureView src, GpuTextureView dst, float tickDelta) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || mc.player == null || mc.level == null) return false;
|
||||
if (src == null || dst == null) return false;
|
||||
|
||||
boolean sleeping = mc.player.isSleeping();
|
||||
float t = smoothTowards(sleeping ? 1.0f : 0.0f);
|
||||
if (t <= 0.001f) return false;
|
||||
|
||||
float vignette = 1.35f * t;
|
||||
float desat = 0.2f * t;
|
||||
float contrast = 0.0f;
|
||||
|
||||
PostProcessUniforms.update(vignette, desat, contrast, 0.0f);
|
||||
|
||||
FullScreenRenderer.ensureInit();
|
||||
FullScreenRenderer.begin("LoVisual Fullscreen Pass")
|
||||
.attachment(dst)
|
||||
.pipeline(LoVisualRenderPipelines.SLEEP_OVERLAY)
|
||||
.uniform("PostProcess", PostProcessUniforms.get())
|
||||
.sampler("u_Texture", src, PostProcessManager.getSampler())
|
||||
.end();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private float smoothTowards(float target) {
|
||||
long now = System.currentTimeMillis();
|
||||
float dt = (lastUpdateMs == 0L) ? 0.016f : (now - lastUpdateMs) / 1000.0f;
|
||||
lastUpdateMs = now;
|
||||
|
||||
float rate = target >= strength ? (1.0f / FADE_IN_SECONDS) : (1.0f / FADE_OUT_SECONDS);
|
||||
float step = Mth.clamp(rate * dt, 0.0f, 1.0f);
|
||||
strength += (target - strength) * step;
|
||||
strength = Mth.clamp(strength, 0.0f, 1.0f);
|
||||
return strength;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine;
|
||||
|
||||
import dev.loki.lovisual.render.engine.guard.boundary.RenderBoundaryAudit;
|
||||
import dev.loki.lovisual.render.engine.renderer.Renderer2D;
|
||||
import dev.loki.lovisual.render.engine.svg.parser.SvgRegistry;
|
||||
import dev.loki.lovisual.render.engine.visuals.LoVisualVisuals;
|
||||
import dev.loki.lovisual.render.iris.runtime.IrisRuntime;
|
||||
|
||||
public enum LoVisualRenderEngineBootstrap {
|
||||
;
|
||||
|
||||
public static void init() {
|
||||
RenderBoundaryAudit.runOnce();
|
||||
// RHI backend selection must be lazy: onInitializeClient can run before RenderSystem has
|
||||
// created the final GpuDevice, which would permanently select the wrong backend on Vulkan.
|
||||
Renderer2D.init();
|
||||
IrisRuntime.registerLoVisualPipelines();
|
||||
SvgRegistry.init();
|
||||
LoVisualVisuals.init();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine;
|
||||
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import dev.loki.lovisual.render.engine.core.context.ViewportContext;
|
||||
|
||||
/**
|
||||
* Deprecated compatibility facade. Use ViewportContext directly.
|
||||
*/
|
||||
@Deprecated(forRemoval = false)
|
||||
public enum Projection2D {
|
||||
;
|
||||
|
||||
public static void beginUnscaled(GuiGraphicsExtractor ctx) {
|
||||
ViewportContext.beginUnscaled(ctx);
|
||||
}
|
||||
|
||||
public static void beginUnscaledLogical(GuiGraphicsExtractor ctx) {
|
||||
ViewportContext.beginUnscaledLogical(ctx);
|
||||
}
|
||||
|
||||
public static void beginScaled(GuiGraphicsExtractor ctx) {
|
||||
ViewportContext.beginScaled(ctx);
|
||||
}
|
||||
|
||||
public static void end(GuiGraphicsExtractor ctx) {
|
||||
ViewportContext.end(ctx);
|
||||
}
|
||||
|
||||
public static float getScaleFactor() {
|
||||
return ViewportContext.getScaleFactor();
|
||||
}
|
||||
|
||||
public static float getUiScale() {
|
||||
return ViewportContext.getUiScale();
|
||||
}
|
||||
|
||||
public static GuiGraphicsExtractor getCurrentContext() {
|
||||
return ViewportContext.getCurrentContext();
|
||||
}
|
||||
|
||||
public static void unscaledProjection() {
|
||||
ViewportContext.unscaledProjection();
|
||||
}
|
||||
|
||||
public static void logicalProjection() {
|
||||
ViewportContext.logicalProjection();
|
||||
}
|
||||
|
||||
public static void scaledProjection() {
|
||||
ViewportContext.scaledProjection();
|
||||
}
|
||||
|
||||
public static void projectionForSize(float width, float height) {
|
||||
ViewportContext.projectionForSize(width, height);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine;
|
||||
|
||||
import net.minecraft.client.renderer.culling.Frustum;
|
||||
import net.minecraft.world.level.material.FogType;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Quaternionf;
|
||||
import dev.loki.lovisual.render.engine.core.frame.RenderFrameContext;
|
||||
|
||||
/**
|
||||
* Engine global render flags/state. Set these in render hooks.
|
||||
*/
|
||||
public enum RenderState {
|
||||
;
|
||||
public static final Quaternionf cameraRotation = new Quaternionf();
|
||||
public static final Matrix4f worldProjection = new Matrix4f().identity();
|
||||
public static boolean rendering3D = false;
|
||||
/**
|
||||
* Legacy alias for vanilla tick progress.
|
||||
*/
|
||||
public static float tickDelta = 0f;
|
||||
public static float tickProgress = 0f;
|
||||
public static float frameDeltaTicks = 0f;
|
||||
public static float frameDeltaSeconds = 0f;
|
||||
public static float fixedDeltaTicks = 0f;
|
||||
public static Vec3 cameraPos = Vec3.ZERO;
|
||||
public static float cameraYaw = 0f;
|
||||
public static float cameraPitch = 0f;
|
||||
public static Vec3 cameraLook = Vec3.ZERO;
|
||||
public static FogType cameraSubmersion = FogType.NONE;
|
||||
public static boolean worldTranslucent = false;
|
||||
public static @Nullable Frustum frustum;
|
||||
public static float lineWidth = 1.0f;
|
||||
// Active world frame id (set on begin).
|
||||
public static int activeWorldFrameId = -1;
|
||||
|
||||
public static void applyContext(RenderFrameContext context) {
|
||||
if (context == null) return;
|
||||
tickDelta = context.tickDelta();
|
||||
tickProgress = context.tickProgress();
|
||||
frameDeltaTicks = context.frameDeltaTicks();
|
||||
frameDeltaSeconds = context.frameDeltaSeconds();
|
||||
fixedDeltaTicks = context.fixedDeltaTicks();
|
||||
if (context.camera() != null) {
|
||||
cameraPos = context.camera().position();
|
||||
cameraLook = context.camera().look();
|
||||
cameraRotation.set(context.camera().rotation());
|
||||
worldProjection.set(context.camera().projection());
|
||||
}
|
||||
}
|
||||
|
||||
public static void clearContextBackedState() {
|
||||
rendering3D = false;
|
||||
worldTranslucent = false;
|
||||
activeWorldFrameId = -1;
|
||||
tickDelta = 0f;
|
||||
tickProgress = 0f;
|
||||
frameDeltaTicks = 0f;
|
||||
frameDeltaSeconds = 0f;
|
||||
fixedDeltaTicks = 0f;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.animation;
|
||||
import dev.loki.lovisual.render.engine.animation.text.AnimatedTextLine;
|
||||
import dev.loki.lovisual.render.engine.animation.text.AnimatedTextStyle;
|
||||
|
||||
|
||||
import dev.loki.lovisual.render.engine.text.render.TextRenderer;
|
||||
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
public final class AnimatedClockText {
|
||||
private static final DateTimeFormatter FORMAT_MINUTES = DateTimeFormatter.ofPattern("HH:mm");
|
||||
private static final DateTimeFormatter FORMAT_SECONDS = DateTimeFormatter.ofPattern("HH:mm:ss");
|
||||
|
||||
private final AnimatedTextLine line = new AnimatedTextLine();
|
||||
private AnimatedTextStyle style = AnimatedTextStyle.clockLiquidGlass();
|
||||
private boolean showSeconds;
|
||||
|
||||
public void setStyle(AnimatedTextStyle style) {
|
||||
this.style = style != null ? style : AnimatedTextStyle.clockLiquidGlass();
|
||||
}
|
||||
|
||||
public void update(LocalTime time, boolean showSeconds, long nowMs) {
|
||||
this.showSeconds = showSeconds;
|
||||
LocalTime safeTime = time != null ? time : LocalTime.now();
|
||||
line.setText(safeTime.format(showSeconds ? FORMAT_SECONDS : FORMAT_MINUTES), nowMs, style);
|
||||
}
|
||||
|
||||
public String text() {
|
||||
return line.text();
|
||||
}
|
||||
|
||||
public boolean showSeconds() {
|
||||
return showSeconds;
|
||||
}
|
||||
|
||||
public float width(TextRenderer renderer, float size) {
|
||||
return line.width(renderer, size, style);
|
||||
}
|
||||
|
||||
public float height(TextRenderer renderer, float size) {
|
||||
return line.height(renderer, size, style);
|
||||
}
|
||||
|
||||
public void renderLiquidGlassCentered(TextRenderer renderer,
|
||||
float centerX,
|
||||
float y,
|
||||
float size,
|
||||
int argb,
|
||||
long nowMs) {
|
||||
line.renderLiquidGlassCentered(renderer, centerX, y, size, argb, nowMs, style);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.animation;
|
||||
|
||||
/**
|
||||
* Общий набор строковых опций {@link ModeValue}-настройки цвета и парсер в
|
||||
* {@link AnimatedRenderColors.Mode} — вынесено, чтобы каждый модуль с
|
||||
* анимированным цветом не дублировал один и тот же switch (см. HitEffect,
|
||||
* HitBubbles).
|
||||
*/
|
||||
public enum AnimatedColorModeOptions {
|
||||
;
|
||||
|
||||
public static final String[] OPTIONS = {
|
||||
"Static", "Rainbow", "LightRainbow", "Sky", "Fade", "DoubleColor", "Analogous", "Theme"
|
||||
};
|
||||
|
||||
public static AnimatedRenderColors.Mode parse(String value) {
|
||||
return switch (value == null ? "" : value) {
|
||||
case "Rainbow" -> AnimatedRenderColors.Mode.RAINBOW;
|
||||
case "LightRainbow" -> AnimatedRenderColors.Mode.LIGHT_RAINBOW;
|
||||
case "Sky" -> AnimatedRenderColors.Mode.SKY;
|
||||
case "Fade" -> AnimatedRenderColors.Mode.FADE;
|
||||
case "DoubleColor" -> AnimatedRenderColors.Mode.DOUBLE_COLOR;
|
||||
case "Analogous" -> AnimatedRenderColors.Mode.ANALOGOUS;
|
||||
case "Theme" -> AnimatedRenderColors.Mode.THEME;
|
||||
default -> AnimatedRenderColors.Mode.STATIC;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,339 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.animation;
|
||||
|
||||
import dev.loki.lovisual.features.theme.Theme;
|
||||
import dev.loki.lovisual.features.theme.Themes;
|
||||
import dev.loki.lovisual.render.engine.math.ColorMath;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
/**
|
||||
* Shared animated color resolver for visual modules and HUD text gradients.
|
||||
*
|
||||
* <p>Callers provide the semantic mode, speed, phase/index and the configured
|
||||
* primary/secondary colors. The resolver keeps the old module color semantics
|
||||
* in one place instead of duplicating ColorUtils switch blocks in every module.</p>
|
||||
*/
|
||||
public enum AnimatedRenderColors {
|
||||
;
|
||||
|
||||
private static final int FULL_ALPHA = 0xFF000000;
|
||||
private static final float MODULE_LIST_ANIMATED_WEIGHT = 0.86f;
|
||||
private static final float MODULE_LIST_PALETTE_CYCLES_PER_SECOND = 0.025f;
|
||||
|
||||
public enum Mode {
|
||||
STATIC,
|
||||
RAINBOW,
|
||||
LIGHT_RAINBOW,
|
||||
SKY,
|
||||
FADE,
|
||||
DOUBLE_COLOR,
|
||||
ANALOGOUS,
|
||||
THEME
|
||||
}
|
||||
|
||||
public static boolean usesSecondary(Mode mode) {
|
||||
return mode == Mode.DOUBLE_COLOR || mode == Mode.ANALOGOUS;
|
||||
}
|
||||
|
||||
public static boolean animated(Mode mode) {
|
||||
return mode != null && mode != Mode.STATIC;
|
||||
}
|
||||
|
||||
public static int shaderMode(Mode mode) {
|
||||
return switch (mode != null ? mode : Mode.STATIC) {
|
||||
case STATIC -> 0;
|
||||
case RAINBOW -> 1;
|
||||
case LIGHT_RAINBOW -> 2;
|
||||
case SKY -> 3;
|
||||
case FADE -> 4;
|
||||
case DOUBLE_COLOR -> 5;
|
||||
case ANALOGOUS -> 6;
|
||||
case THEME -> 7;
|
||||
};
|
||||
}
|
||||
|
||||
public static float angularOffset01(Mode mode, int speed) {
|
||||
if (!animated(mode)) {
|
||||
return 0.0f;
|
||||
}
|
||||
int safeSpeed = Math.max(1, speed);
|
||||
return wrap01(AnimationUtility.time(1.0f / (safeSpeed * 360.0f)));
|
||||
}
|
||||
|
||||
public static int angularPrimaryColor(Mode mode, int primaryArgb) {
|
||||
if (mode == Mode.THEME) {
|
||||
return ColorMath.colorWithAlpha(Theme.theme().accent(), alpha(primaryArgb));
|
||||
}
|
||||
return primaryArgb;
|
||||
}
|
||||
|
||||
public static int angularSecondaryColor(Mode mode, int primaryArgb, int secondaryArgb) {
|
||||
return switch (mode != null ? mode : Mode.STATIC) {
|
||||
case ANALOGOUS -> analogous(secondaryArgb);
|
||||
case THEME -> ColorMath.colorWithAlpha(Theme.theme().accentSoft(), alpha(primaryArgb));
|
||||
case FADE, STATIC, RAINBOW, LIGHT_RAINBOW, SKY -> primaryArgb;
|
||||
case DOUBLE_COLOR -> secondaryArgb;
|
||||
};
|
||||
}
|
||||
|
||||
public static int resolve(Mode mode, int speed, int phase, int primaryArgb, int secondaryArgb) {
|
||||
return resolve(mode, speed, phase, primaryArgb, secondaryArgb, false);
|
||||
}
|
||||
|
||||
public static int resolve(Mode mode,
|
||||
int speed,
|
||||
int phase,
|
||||
int primaryArgb,
|
||||
int secondaryArgb,
|
||||
boolean preservePrimaryAlpha) {
|
||||
Mode resolved = mode != null ? mode : Mode.STATIC;
|
||||
int safeSpeed = Math.max(1, speed);
|
||||
int index = Math.floorMod(phase, 36000);
|
||||
int alpha = alpha(primaryArgb);
|
||||
|
||||
return switch (resolved) {
|
||||
case SKY -> withAlpha(skyRainbowRgb(safeSpeed, index), preservePrimaryAlpha ? alpha : 255);
|
||||
case LIGHT_RAINBOW -> withAlpha(rainbowRgb(safeSpeed, index, 0.6f, 1.0f), preservePrimaryAlpha ? alpha : 255);
|
||||
case RAINBOW -> withAlpha(rainbowRgb(safeSpeed, index, 1.0f, 1.0f), preservePrimaryAlpha ? alpha : 255);
|
||||
case FADE -> fade(primaryArgb, safeSpeed, index, preservePrimaryAlpha ? alpha : 255);
|
||||
case DOUBLE_COLOR -> twoColor(primaryArgb, secondaryArgb, safeSpeed, index);
|
||||
case ANALOGOUS -> twoColorHue(primaryArgb, analogous(secondaryArgb), safeSpeed, index);
|
||||
case THEME -> themeColor(safeSpeed, index, preservePrimaryAlpha ? alpha : 255);
|
||||
case STATIC -> primaryArgb;
|
||||
};
|
||||
}
|
||||
|
||||
public static int mixArgb(int a, int b, float t) {
|
||||
float clamped = clamp01(t);
|
||||
int aa = Math.round(alpha(a) + (alpha(b) - alpha(a)) * clamped);
|
||||
int rr = Math.round(red(a) + (red(b) - red(a)) * clamped);
|
||||
int gg = Math.round(green(a) + (green(b) - green(a)) * clamped);
|
||||
int bb = Math.round(blue(a) + (blue(b) - blue(a)) * clamped);
|
||||
return argb(aa, rr, gg, bb);
|
||||
}
|
||||
|
||||
public static int withAlpha(int argb, int alpha) {
|
||||
return (argb & 0x00FFFFFF) | (clamp255(alpha) << 24);
|
||||
}
|
||||
|
||||
public static int scaleAlpha(int argb, float scale) {
|
||||
return withAlpha(argb, Math.round(alpha(argb) * clamp01(scale)));
|
||||
}
|
||||
|
||||
public static int brighten(int argb, float factor) {
|
||||
float f = Math.max(0.0f, factor);
|
||||
return argb(alpha(argb), clamp255(Math.round(red(argb) * f)), clamp255(Math.round(green(argb) * f)), clamp255(Math.round(blue(argb) * f)));
|
||||
}
|
||||
|
||||
public static int analogousColor(int argb) {
|
||||
return analogous(argb);
|
||||
}
|
||||
|
||||
public static void moduleListBlockGlyphGradient(int topArgb,
|
||||
int bottomArgb,
|
||||
Mode mode,
|
||||
int speed,
|
||||
double glyphY0,
|
||||
double glyphY1,
|
||||
double blockY0,
|
||||
double blockY1,
|
||||
int glyphIndex,
|
||||
float timeSec,
|
||||
boolean topRainbow,
|
||||
boolean bottomRainbow,
|
||||
boolean shimmer,
|
||||
int[] out) {
|
||||
if (out == null || out.length < 4) return;
|
||||
|
||||
float topT = blockProgress(glyphY0, blockY0, blockY1);
|
||||
float bottomT = blockProgress(glyphY1, blockY0, blockY1);
|
||||
int topColor = moduleListBlockColor(topArgb, bottomArgb, mode, speed, topT, glyphIndex, timeSec, topRainbow, bottomRainbow, shimmer);
|
||||
int bottomColor = moduleListBlockColor(topArgb, bottomArgb, mode, speed, bottomT, glyphIndex, timeSec, topRainbow, bottomRainbow, shimmer);
|
||||
|
||||
out[0] = topColor;
|
||||
out[1] = bottomColor;
|
||||
out[2] = bottomColor;
|
||||
out[3] = topColor;
|
||||
}
|
||||
|
||||
public static int moduleListBlockColor(int topArgb,
|
||||
int bottomArgb,
|
||||
Mode mode,
|
||||
int speed,
|
||||
float blockProgress,
|
||||
int glyphIndex,
|
||||
float timeSec,
|
||||
boolean topRainbow,
|
||||
boolean bottomRainbow,
|
||||
boolean shimmer) {
|
||||
int safeSpeed = Math.max(1, speed);
|
||||
float t = clamp01(blockProgress);
|
||||
Mode resolved = mode != null ? mode : Mode.STATIC;
|
||||
|
||||
int base = mixArgb(topArgb, bottomArgb, t);
|
||||
|
||||
boolean rainbowEndpoints = topRainbow || bottomRainbow;
|
||||
if (rainbowEndpoints) {
|
||||
int endpointPalette = moduleListPaletteColor(Mode.RAINBOW, safeSpeed, t, timeSec,
|
||||
topRainbow ? topArgb : bottomArgb,
|
||||
bottomRainbow ? bottomArgb : topArgb);
|
||||
base = mixArgb(base, endpointPalette, MODULE_LIST_ANIMATED_WEIGHT);
|
||||
}
|
||||
|
||||
if (resolved != Mode.STATIC) {
|
||||
int animated = moduleListPaletteColor(resolved, safeSpeed, t, timeSec, topArgb, bottomArgb);
|
||||
base = mixArgb(base, animated, MODULE_LIST_ANIMATED_WEIGHT);
|
||||
}
|
||||
|
||||
if (shimmer) {
|
||||
float wave = 0.5f + 0.5f * (float) Math.sin(timeSec * safeSpeed * 0.16f + t * 6.2831855f + glyphIndex * 0.035f);
|
||||
int dark = mixArgb(base, withAlpha(0xFF000000, alpha(base)), 0.18f);
|
||||
int light = mixArgb(base, withAlpha(0xFFFFFFFF, alpha(base)), 0.28f);
|
||||
base = mixArgb(dark, light, wave);
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
private static int moduleListPaletteColor(Mode mode,
|
||||
int speed,
|
||||
float blockProgress,
|
||||
float timeSec,
|
||||
int primaryArgb,
|
||||
int secondaryArgb) {
|
||||
int safeSpeed = Math.max(1, speed);
|
||||
float t = clamp01(blockProgress);
|
||||
float paletteOffset = animated(mode) ? paletteOffset(timeSec, safeSpeed) : 0.0f;
|
||||
float paletteT = wrap01(t + paletteOffset);
|
||||
int alpha = Math.round(alpha(primaryArgb) + (alpha(secondaryArgb) - alpha(primaryArgb)) * t);
|
||||
|
||||
return switch (mode != null ? mode : Mode.STATIC) {
|
||||
case RAINBOW -> withAlpha(Color.HSBtoRGB(paletteT, 1.0f, 1.0f), alpha);
|
||||
case LIGHT_RAINBOW -> withAlpha(Color.HSBtoRGB(paletteT, 0.6f, 1.0f), alpha);
|
||||
case SKY -> withAlpha(Color.HSBtoRGB(skyHue(paletteT), 0.5f, 1.0f), alpha);
|
||||
case FADE -> fadeThroughValue(primaryArgb, cyclicMix(paletteT), alpha);
|
||||
case DOUBLE_COLOR -> mixArgb(primaryArgb, secondaryArgb, cyclicMix(paletteT));
|
||||
case ANALOGOUS -> mixArgb(primaryArgb, analogous(secondaryArgb), cyclicMix(paletteT));
|
||||
case THEME -> themeColor(safeSpeed, Math.round(paletteT * 360.0f), alpha);
|
||||
case STATIC -> mixArgb(primaryArgb, secondaryArgb, t);
|
||||
};
|
||||
}
|
||||
|
||||
private static int themeColor(int speed, int index, int alpha) {
|
||||
Themes.Theme theme = Theme.theme();
|
||||
int primary = withAlpha(theme.accent(), alpha);
|
||||
int secondary = withAlpha(theme.accentSoft(), alpha);
|
||||
return twoColor(primary, secondary, speed, index);
|
||||
}
|
||||
|
||||
private static float paletteOffset(float timeSec, int speed) {
|
||||
return wrap01(timeSec * Math.max(1, speed) * MODULE_LIST_PALETTE_CYCLES_PER_SECOND);
|
||||
}
|
||||
|
||||
private static float cyclicMix(float value) {
|
||||
float wrapped = wrap01(value);
|
||||
return wrapped < 0.5f ? wrapped * 2.0f : (1.0f - wrapped) * 2.0f;
|
||||
}
|
||||
|
||||
private static float blockProgress(double y, double blockY0, double blockY1) {
|
||||
double height = Math.max(1.0, blockY1 - blockY0);
|
||||
return clamp01((float) ((y - blockY0) / height));
|
||||
}
|
||||
|
||||
private static int fadeThroughValue(int primaryArgb, float blockProgress, int alpha) {
|
||||
float[] hsb = Color.RGBtoHSB(red(primaryArgb), green(primaryArgb), blue(primaryArgb), null);
|
||||
float value = 0.38f + 0.62f * clamp01(blockProgress);
|
||||
int rgb = Color.HSBtoRGB(hsb[0], hsb[1], value) & 0x00FFFFFF;
|
||||
return (clamp255(alpha) << 24) | rgb;
|
||||
}
|
||||
|
||||
private static float skyHue(float t) {
|
||||
float wrapped = wrap01(t);
|
||||
return wrapped < 0.5f ? -wrapped : wrapped;
|
||||
}
|
||||
|
||||
private static float wrap01(float value) {
|
||||
return value - (float) Math.floor(value);
|
||||
}
|
||||
|
||||
private static int rainbowRgb(int speed, int index, float saturation, float brightness) {
|
||||
int angle = (int) ((System.currentTimeMillis() / Math.max(1, speed) + index) % 360);
|
||||
return FULL_ALPHA | (Color.HSBtoRGB(angle / 360.0f, saturation, brightness) & 0x00FFFFFF);
|
||||
}
|
||||
|
||||
private static int skyRainbowRgb(int speed, int index) {
|
||||
int angle = (int) ((System.currentTimeMillis() / Math.max(1, speed) + index) % 360);
|
||||
float hue = ((angle %= 360) / 360.0f) < 0.5f ? -(angle / 360.0f) : (angle / 360.0f);
|
||||
return FULL_ALPHA | (Color.HSBtoRGB(hue, 0.5f, 1.0f) & 0x00FFFFFF);
|
||||
}
|
||||
|
||||
private static int fade(int primaryArgb, int speed, int index, int alpha) {
|
||||
float[] hsb = Color.RGBtoHSB(red(primaryArgb), green(primaryArgb), blue(primaryArgb), null);
|
||||
int angle = (int) ((System.currentTimeMillis() / Math.max(1, speed) + index) % 360);
|
||||
angle = (angle > 180 ? 360 - angle : angle) + 180;
|
||||
int rgb = Color.HSBtoRGB(hsb[0], hsb[1], angle / 360.0f) & 0x00FFFFFF;
|
||||
return (clamp255(alpha) << 24) | rgb;
|
||||
}
|
||||
|
||||
private static int twoColor(int primaryArgb, int secondaryArgb, int speed, int index) {
|
||||
int angle = (int) ((System.currentTimeMillis() / Math.max(1, speed) + index) % 360);
|
||||
angle = (angle >= 180 ? 360 - angle : angle) * 2;
|
||||
return mixArgb(primaryArgb, secondaryArgb, angle / 360.0f);
|
||||
}
|
||||
|
||||
private static int twoColorHue(int primaryArgb, int secondaryArgb, int speed, int index) {
|
||||
int angle = (int) ((System.currentTimeMillis() / Math.max(1, speed) + index) % 360);
|
||||
angle = (angle >= 180 ? 360 - angle : angle) * 2;
|
||||
float t = clamp01(angle / 360.0f);
|
||||
float[] a = Color.RGBtoHSB(red(primaryArgb), green(primaryArgb), blue(primaryArgb), null);
|
||||
float[] b = Color.RGBtoHSB(red(secondaryArgb), green(secondaryArgb), blue(secondaryArgb), null);
|
||||
int rgb = Color.HSBtoRGB(lerp(a[0], b[0], t), lerp(a[1], b[1], t), lerp(a[2], b[2], t)) & 0x00FFFFFF;
|
||||
int outAlpha = Math.round(alpha(primaryArgb) + (alpha(secondaryArgb) - alpha(primaryArgb)) * t);
|
||||
return (clamp255(outAlpha) << 24) | rgb;
|
||||
}
|
||||
|
||||
private static int analogous(int argb) {
|
||||
float[] hsb = Color.RGBtoHSB(red(argb), green(argb), blue(argb), null);
|
||||
int rgb = Color.HSBtoRGB(hsb[0] - 0.84f, hsb[1], hsb[2]) & 0x00FFFFFF;
|
||||
return (alpha(argb) << 24) | rgb;
|
||||
}
|
||||
|
||||
private static int argb(int a, int r, int g, int b) {
|
||||
return (clamp255(a) << 24) | (clamp255(r) << 16) | (clamp255(g) << 8) | clamp255(b);
|
||||
}
|
||||
|
||||
private static int alpha(int argb) {
|
||||
return (argb >>> 24) & 0xFF;
|
||||
}
|
||||
|
||||
private static int red(int argb) {
|
||||
return (argb >>> 16) & 0xFF;
|
||||
}
|
||||
|
||||
private static int green(int argb) {
|
||||
return (argb >>> 8) & 0xFF;
|
||||
}
|
||||
|
||||
private static int blue(int argb) {
|
||||
return argb & 0xFF;
|
||||
}
|
||||
|
||||
private static int clamp255(int value) {
|
||||
return Math.max(0, Math.min(255, value));
|
||||
}
|
||||
|
||||
private static float clamp01(float value) {
|
||||
return Math.max(0.0f, Math.min(1.0f, value));
|
||||
}
|
||||
|
||||
private static float lerp(float a, float b, float t) {
|
||||
return a + (b - a) * clamp01(t);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.animation;
|
||||
|
||||
import net.minecraft.util.Mth;
|
||||
import dev.loki.lovisual.util.FastFps;
|
||||
|
||||
public enum AnimationUtility {
|
||||
;
|
||||
|
||||
public static float deltaTime() {
|
||||
return FastFps.getDeltaSeconds();
|
||||
}
|
||||
|
||||
public static float time(float speed) {
|
||||
return (System.currentTimeMillis() % 1_000_000L) * speed;
|
||||
}
|
||||
|
||||
public static float clamp(float value, float min, float max) {
|
||||
return Math.max(min, Math.min(max, value));
|
||||
}
|
||||
|
||||
public static double clamp(double value, double min, double max) {
|
||||
return Math.max(min, Math.min(max, value));
|
||||
}
|
||||
|
||||
public static float clamp01(float value) {
|
||||
return clamp(value, 0f, 1f);
|
||||
}
|
||||
|
||||
public static float lerp(float start, float end, float t) {
|
||||
return start + (end - start) * clamp01(t);
|
||||
}
|
||||
|
||||
public static double lerp(double start, double end, double t) {
|
||||
return start + (end - start) * clamp(t, 0.0, 1.0);
|
||||
}
|
||||
|
||||
public static float approach(float value, float target, float speed) {
|
||||
return lerp(value, target, speed);
|
||||
}
|
||||
|
||||
public static double approach(double value, double target, double speed) {
|
||||
return lerp(value, target, speed);
|
||||
}
|
||||
|
||||
public static float approach(float value, float target, float dt, float speed) {
|
||||
return approach(value, target, dt * speed);
|
||||
}
|
||||
|
||||
public static float snap(float value, float target, float epsilon) {
|
||||
return Math.abs(value - target) <= Math.max(0f, epsilon) ? target : value;
|
||||
}
|
||||
|
||||
public static float smoothstep(float value) {
|
||||
float t = clamp01(value);
|
||||
return t * t * (3f - 2f * t);
|
||||
}
|
||||
|
||||
public static float easeOutCubic(float value) {
|
||||
float t = clamp01(value);
|
||||
return 1f - (float) Math.pow(1f - t, 3f);
|
||||
}
|
||||
|
||||
public static float easeInCubic(float value) {
|
||||
float t = clamp01(value);
|
||||
return t * t * t;
|
||||
}
|
||||
|
||||
public static float easeInOutCubic(float value) {
|
||||
float t = clamp01(value);
|
||||
return t < 0.5f
|
||||
? 4f * t * t * t
|
||||
: 1f - (float) Math.pow(-2f * t + 2f, 3f) * 0.5f;
|
||||
}
|
||||
|
||||
public static float easeOutBack(float value) {
|
||||
return easeOutBack(value, 1.70158f);
|
||||
}
|
||||
|
||||
public static float easeOutBack(float value, float overshoot) {
|
||||
float t = clamp01(value);
|
||||
float c1 = Math.max(0f, overshoot);
|
||||
float c3 = c1 + 1f;
|
||||
float p = t - 1f;
|
||||
return 1f + c3 * p * p * p + c1 * p * p;
|
||||
}
|
||||
|
||||
public static float easeInBack(float value) {
|
||||
return easeInBack(value, 1.70158f);
|
||||
}
|
||||
|
||||
public static float easeInBack(float value, float overshoot) {
|
||||
float t = clamp01(value);
|
||||
float c1 = Math.max(0f, overshoot);
|
||||
float c3 = c1 + 1f;
|
||||
return c3 * t * t * t - c1 * t * t;
|
||||
}
|
||||
|
||||
public static boolean blink(long intervalMs) {
|
||||
if (intervalMs <= 0L) return true;
|
||||
return (System.currentTimeMillis() / intervalMs) % 2L == 0L;
|
||||
}
|
||||
|
||||
public static float fast(float end, float start, float multiple) {
|
||||
float clampedDelta = Mth.clamp(deltaTime() * multiple, 0f, 1f);
|
||||
return approach(end, start, clampedDelta);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.animation.text;
|
||||
import dev.loki.lovisual.render.engine.animation.AnimationUtility;
|
||||
|
||||
|
||||
public final class AnimatedGlyphSlot {
|
||||
private char current;
|
||||
private char previous;
|
||||
private long animationStartMs;
|
||||
private boolean animating;
|
||||
|
||||
public AnimatedGlyphSlot(char initial) {
|
||||
this.current = initial;
|
||||
this.previous = initial;
|
||||
this.animationStartMs = 0L;
|
||||
this.animating = false;
|
||||
}
|
||||
|
||||
public char current() {
|
||||
return current;
|
||||
}
|
||||
|
||||
public char previous() {
|
||||
return previous;
|
||||
}
|
||||
|
||||
public boolean isAnimating(long nowMs, AnimatedTextStyle style) {
|
||||
if (!animating) return false;
|
||||
if (progress(nowMs, style) >= 1.0f) {
|
||||
animating = false;
|
||||
previous = current;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public float progress(long nowMs, AnimatedTextStyle style) {
|
||||
if (!animating || style == null) return 1.0f;
|
||||
return AnimationUtility.clamp01((nowMs - animationStartMs) / (float) style.durationMs());
|
||||
}
|
||||
|
||||
public void set(char next, long nowMs, AnimatedTextStyle style) {
|
||||
if (next == current) return;
|
||||
|
||||
boolean animate = shouldAnimate(current, next, style);
|
||||
previous = current;
|
||||
current = next;
|
||||
if (animate) {
|
||||
animationStartMs = nowMs;
|
||||
animating = true;
|
||||
} else {
|
||||
animationStartMs = 0L;
|
||||
animating = false;
|
||||
previous = current;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean shouldAnimate(char oldGlyph, char newGlyph, AnimatedTextStyle style) {
|
||||
if (style == null || style.transition() == AnimatedTextTransition.NONE) return false;
|
||||
if (oldGlyph == 0 || newGlyph == 0) return false;
|
||||
if (!style.animateDigitsOnly()) return true;
|
||||
return Character.isDigit(oldGlyph) && Character.isDigit(newGlyph);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,231 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.animation.text;
|
||||
import dev.loki.lovisual.render.engine.animation.AnimationUtility;
|
||||
|
||||
|
||||
import dev.loki.lovisual.render.engine.color.RenderColor;
|
||||
import dev.loki.lovisual.render.engine.text.render.TextRenderer;
|
||||
import dev.loki.lovisual.render.helpers.clip.ScissorFunction;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public final class AnimatedTextLine {
|
||||
private final List<AnimatedGlyphSlot> slots = new ArrayList<>();
|
||||
private String text = "";
|
||||
|
||||
public void setText(String nextText, long nowMs, AnimatedTextStyle style) {
|
||||
String next = nextText != null ? nextText : "";
|
||||
if (next.equals(text) && slots.size() == next.length()) return;
|
||||
|
||||
while (slots.size() < next.length()) {
|
||||
slots.add(new AnimatedGlyphSlot(next.charAt(slots.size())));
|
||||
}
|
||||
while (slots.size() > next.length()) {
|
||||
slots.remove(slots.size() - 1);
|
||||
}
|
||||
|
||||
for (int i = 0; i < next.length(); i++) {
|
||||
slots.get(i).set(next.charAt(i), nowMs, style);
|
||||
}
|
||||
text = next;
|
||||
}
|
||||
|
||||
public String text() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public float width(TextRenderer renderer, float size, AnimatedTextStyle style) {
|
||||
if (renderer == null || text.isEmpty()) return 0.0f;
|
||||
Metrics metrics = measure(renderer, size, style);
|
||||
return metrics.totalWidth;
|
||||
}
|
||||
|
||||
public float height(TextRenderer renderer, float size, AnimatedTextStyle style) {
|
||||
if (renderer == null) return 0.0f;
|
||||
renderer.begin(size, true, style != null && style.bigGlyphs());
|
||||
try {
|
||||
return (float) renderer.getHeight(false);
|
||||
} finally {
|
||||
renderer.end();
|
||||
}
|
||||
}
|
||||
|
||||
public void renderLiquidGlassCentered(TextRenderer renderer,
|
||||
float centerX,
|
||||
float y,
|
||||
float size,
|
||||
int argb,
|
||||
long nowMs,
|
||||
AnimatedTextStyle style) {
|
||||
float w = width(renderer, size, style);
|
||||
renderLiquidGlass(renderer, centerX - w * 0.5f, y, size, argb, nowMs, style);
|
||||
}
|
||||
|
||||
public void renderLiquidGlass(TextRenderer renderer,
|
||||
float x,
|
||||
float y,
|
||||
float size,
|
||||
int argb,
|
||||
long nowMs,
|
||||
AnimatedTextStyle style) {
|
||||
if (renderer == null || text.isEmpty()) return;
|
||||
AnimatedTextStyle resolved = style != null ? style : AnimatedTextStyle.clockLiquidGlass();
|
||||
Metrics metrics = measure(renderer, size, resolved);
|
||||
if (metrics.totalWidth <= 0.0f || metrics.height <= 0.0f) return;
|
||||
|
||||
float cursorX = x;
|
||||
for (int i = 0; i < slots.size(); i++) {
|
||||
AnimatedGlyphSlot slot = slots.get(i);
|
||||
char current = slot.current();
|
||||
float slotW = metrics.slotWidths[i];
|
||||
float currentW = metrics.glyphWidths[i];
|
||||
float previousW = metrics.previousGlyphWidths[i];
|
||||
float glyphX = cursorX + (slotW - currentW) * 0.5f;
|
||||
float previousGlyphX = cursorX + (slotW - previousW) * 0.5f;
|
||||
|
||||
if (slot.isAnimating(nowMs, resolved)) {
|
||||
renderAnimatedSlot(renderer, slot, previousGlyphX, glyphX, cursorX, y, slotW, metrics.height, size, argb, nowMs, resolved);
|
||||
} else {
|
||||
renderGlyph(renderer, Character.toString(current), glyphX, y, size, argb, resolved);
|
||||
}
|
||||
cursorX += slotW;
|
||||
}
|
||||
}
|
||||
|
||||
private void renderAnimatedSlot(TextRenderer renderer,
|
||||
AnimatedGlyphSlot slot,
|
||||
float previousGlyphX,
|
||||
float glyphX,
|
||||
float slotX,
|
||||
float y,
|
||||
float slotW,
|
||||
float height,
|
||||
float size,
|
||||
int argb,
|
||||
long nowMs,
|
||||
AnimatedTextStyle style) {
|
||||
float raw = slot.progress(nowMs, style);
|
||||
float move = AnimationUtility.easeInOutCubic(raw);
|
||||
float fadeIn = phase(raw, 0.08f, 0.86f);
|
||||
float fadeOut = 1.0f - phase(raw, 0.14f, 0.78f);
|
||||
float settle = AnimationUtility.easeOutBack(raw, 0.34f);
|
||||
float offset = Math.max(1.0f, height * style.rollOffsetScale());
|
||||
int oldColor = scaleAlpha(argb, fadeOut);
|
||||
int newColor = scaleAlpha(argb, fadeIn);
|
||||
|
||||
float oldY = y;
|
||||
float newY = y;
|
||||
switch (style.transition()) {
|
||||
case FADE -> {
|
||||
oldY = y;
|
||||
newY = y;
|
||||
}
|
||||
case ROLL_DOWN -> {
|
||||
oldY = y + move * offset;
|
||||
newY = y - (1.0f - settle) * offset;
|
||||
}
|
||||
case ROLL_UP -> {
|
||||
oldY = y - move * offset;
|
||||
newY = y + (1.0f - settle) * offset;
|
||||
}
|
||||
case NONE -> {
|
||||
renderGlyph(renderer, Character.toString(slot.current()), glyphX, y, size, argb, style);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
float padY = height * style.clipPaddingScale();
|
||||
boolean clipped = ScissorFunction.pushRaw(
|
||||
slotX - 1.0f,
|
||||
y - padY,
|
||||
Math.max(1.0f, slotW + 2.0f),
|
||||
Math.max(1.0f, height + padY * 2.0f)
|
||||
);
|
||||
try {
|
||||
if (((oldColor >>> 24) & 0xFF) > 1) {
|
||||
renderGlyph(renderer, Character.toString(slot.previous()), previousGlyphX, oldY, size, oldColor, style);
|
||||
}
|
||||
if (((newColor >>> 24) & 0xFF) > 1) {
|
||||
renderGlyph(renderer, Character.toString(slot.current()), glyphX, newY, size, newColor, style);
|
||||
}
|
||||
} finally {
|
||||
if (clipped) ScissorFunction.pop();
|
||||
}
|
||||
}
|
||||
|
||||
private void renderGlyph(TextRenderer renderer,
|
||||
String glyph,
|
||||
float x,
|
||||
float y,
|
||||
float size,
|
||||
int argb,
|
||||
AnimatedTextStyle style) {
|
||||
if (glyph == null || glyph.isEmpty()) return;
|
||||
renderer.setAlpha(1.0);
|
||||
renderer.begin(size, false, style != null && style.bigGlyphs());
|
||||
try {
|
||||
renderer.renderLiquidGlass(glyph, x, y, new RenderColor(argb), false);
|
||||
} finally {
|
||||
renderer.end();
|
||||
}
|
||||
}
|
||||
|
||||
private Metrics measure(TextRenderer renderer, float size, AnimatedTextStyle style) {
|
||||
int count = slots.size();
|
||||
float[] slotWidths = new float[count];
|
||||
float[] glyphWidths = new float[count];
|
||||
float[] previousGlyphWidths = new float[count];
|
||||
float total = 0.0f;
|
||||
float height;
|
||||
|
||||
renderer.begin(size, true, style != null && style.bigGlyphs());
|
||||
try {
|
||||
float tabularDigitW = 0.0f;
|
||||
if (style != null && style.tabularDigits()) {
|
||||
for (char c = '0'; c <= '9'; c++) {
|
||||
tabularDigitW = Math.max(tabularDigitW, (float) renderer.getWidth(Character.toString(c), false));
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < count; i++) {
|
||||
AnimatedGlyphSlot slot = slots.get(i);
|
||||
char glyph = slot.current();
|
||||
char previous = slot.previous();
|
||||
float glyphW = (float) renderer.getWidth(Character.toString(glyph), false);
|
||||
float previousW = (float) renderer.getWidth(Character.toString(previous), false);
|
||||
float slotW = Math.max(glyphW, previousW);
|
||||
if (style != null && style.tabularDigits() && (Character.isDigit(glyph) || Character.isDigit(previous))) {
|
||||
slotW = Math.max(slotW, tabularDigitW);
|
||||
}
|
||||
glyphWidths[i] = glyphW;
|
||||
previousGlyphWidths[i] = previousW;
|
||||
slotWidths[i] = slotW;
|
||||
total += slotW;
|
||||
}
|
||||
height = (float) renderer.getHeight(false);
|
||||
} finally {
|
||||
renderer.end();
|
||||
}
|
||||
return new Metrics(slotWidths, glyphWidths, previousGlyphWidths, total, height);
|
||||
}
|
||||
|
||||
private static float phase(float value, float from, float to) {
|
||||
if (to <= from) return value >= to ? 1.0f : 0.0f;
|
||||
return AnimationUtility.smoothstep((value - from) / (to - from));
|
||||
}
|
||||
|
||||
private static int scaleAlpha(int argb, float factor) {
|
||||
int a = (argb >>> 24) & 0xFF;
|
||||
int scaled = Math.round(a * AnimationUtility.clamp01(factor));
|
||||
return (argb & 0x00FFFFFF) | ((scaled & 0xFF) << 24);
|
||||
}
|
||||
|
||||
private record Metrics(float[] slotWidths, float[] glyphWidths, float[] previousGlyphWidths, float totalWidth, float height) {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.animation.text;
|
||||
|
||||
public final class AnimatedTextStyle {
|
||||
private final AnimatedTextTransition transition;
|
||||
private final long durationMs;
|
||||
private final float rollOffsetScale;
|
||||
private final float clipPaddingScale;
|
||||
private final boolean tabularDigits;
|
||||
private final boolean animateDigitsOnly;
|
||||
private final boolean bigGlyphs;
|
||||
|
||||
public AnimatedTextStyle(AnimatedTextTransition transition,
|
||||
long durationMs,
|
||||
float rollOffsetScale,
|
||||
float clipPaddingScale,
|
||||
boolean tabularDigits,
|
||||
boolean animateDigitsOnly,
|
||||
boolean bigGlyphs) {
|
||||
this.transition = transition != null ? transition : AnimatedTextTransition.NONE;
|
||||
this.durationMs = Math.max(1L, durationMs);
|
||||
this.rollOffsetScale = Math.max(0.0f, rollOffsetScale);
|
||||
this.clipPaddingScale = Math.max(0.0f, clipPaddingScale);
|
||||
this.tabularDigits = tabularDigits;
|
||||
this.animateDigitsOnly = animateDigitsOnly;
|
||||
this.bigGlyphs = bigGlyphs;
|
||||
}
|
||||
|
||||
public static AnimatedTextStyle clockLiquidGlass() {
|
||||
return new AnimatedTextStyle(
|
||||
AnimatedTextTransition.ROLL_UP,
|
||||
520L,
|
||||
0.58f,
|
||||
0.18f,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
public AnimatedTextTransition transition() {
|
||||
return transition;
|
||||
}
|
||||
|
||||
public long durationMs() {
|
||||
return durationMs;
|
||||
}
|
||||
|
||||
public float rollOffsetScale() {
|
||||
return rollOffsetScale;
|
||||
}
|
||||
|
||||
public float clipPaddingScale() {
|
||||
return clipPaddingScale;
|
||||
}
|
||||
|
||||
public boolean tabularDigits() {
|
||||
return tabularDigits;
|
||||
}
|
||||
|
||||
public boolean animateDigitsOnly() {
|
||||
return animateDigitsOnly;
|
||||
}
|
||||
|
||||
public boolean bigGlyphs() {
|
||||
return bigGlyphs;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.animation.text;
|
||||
|
||||
public enum AnimatedTextTransition {
|
||||
NONE,
|
||||
FADE,
|
||||
ROLL_UP,
|
||||
ROLL_DOWN
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.color;
|
||||
|
||||
public enum ColorBlendMode {
|
||||
ORIGINAL("Original"),
|
||||
SOFT("Soft"),
|
||||
ATMOSPHERIC("Atmospheric"),
|
||||
CHROMATIC("Chromatic"),
|
||||
ESSENCE("Essence");
|
||||
|
||||
private final String id;
|
||||
|
||||
ColorBlendMode(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public static ColorBlendMode fromId(String id) {
|
||||
if (id == null) {
|
||||
return ORIGINAL;
|
||||
}
|
||||
for (ColorBlendMode mode : values()) {
|
||||
if (mode.id.equalsIgnoreCase(id) || mode.name().equalsIgnoreCase(id)) {
|
||||
return mode;
|
||||
}
|
||||
}
|
||||
return ORIGINAL;
|
||||
}
|
||||
|
||||
public String id() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.color;
|
||||
|
||||
public enum ColorEffects {
|
||||
;
|
||||
|
||||
public static int argb(int rgb, float alpha) {
|
||||
int a = clamp255(Math.round(clamp01(alpha) * 255.0f));
|
||||
return (a << 24) | (rgb & 0x00FFFFFF);
|
||||
}
|
||||
|
||||
public static int argbWithBaseAlpha(int baseArgb, int rgb, float alphaMultiplier) {
|
||||
int baseAlpha = (baseArgb >>> 24) & 0xFF;
|
||||
int alpha = clamp255(Math.round(baseAlpha * clamp01(alphaMultiplier)));
|
||||
return (alpha << 24) | (rgb & 0x00FFFFFF);
|
||||
}
|
||||
|
||||
public static int multiplyAlpha(int argb, float alphaMultiplier) {
|
||||
int alpha = (argb >>> 24) & 0xFF;
|
||||
int outAlpha = clamp255(Math.round(alpha * clamp01(alphaMultiplier)));
|
||||
return (outAlpha << 24) | (argb & 0x00FFFFFF);
|
||||
}
|
||||
|
||||
public static int tintRgb(int sourceRgb, int tintRgb, float strength, ColorBlendMode mode, float phase) {
|
||||
float t = clamp01(strength);
|
||||
if (t <= 0.0f || mode == null || mode == ColorBlendMode.ORIGINAL) {
|
||||
return sourceRgb & 0x00FFFFFF;
|
||||
}
|
||||
|
||||
int source = sourceRgb & 0x00FFFFFF;
|
||||
int tint = tintRgb & 0x00FFFFFF;
|
||||
return switch (mode) {
|
||||
case ORIGINAL -> source;
|
||||
case SOFT -> mixRgb(source, tint, t * 0.72f);
|
||||
case ATMOSPHERIC -> mixRgb(source, screenRgb(source, tint), t * 0.64f);
|
||||
case CHROMATIC -> chromaticTint(source, tint, t, phase);
|
||||
case ESSENCE -> essenceTint(source, tint, t, phase);
|
||||
};
|
||||
}
|
||||
|
||||
public static int mixRgb(int a, int b, float amount) {
|
||||
float t = clamp01(amount);
|
||||
int ar = (a >>> 16) & 0xFF;
|
||||
int ag = (a >>> 8) & 0xFF;
|
||||
int ab = a & 0xFF;
|
||||
int br = (b >>> 16) & 0xFF;
|
||||
int bg = (b >>> 8) & 0xFF;
|
||||
int bb = b & 0xFF;
|
||||
int r = Math.round(ar + (br - ar) * t);
|
||||
int g = Math.round(ag + (bg - ag) * t);
|
||||
int bl = Math.round(ab + (bb - ab) * t);
|
||||
return (clamp255(r) << 16) | (clamp255(g) << 8) | clamp255(bl);
|
||||
}
|
||||
|
||||
public static int screenRgb(int a, int b) {
|
||||
int ar = (a >>> 16) & 0xFF;
|
||||
int ag = (a >>> 8) & 0xFF;
|
||||
int ab = a & 0xFF;
|
||||
int br = (b >>> 16) & 0xFF;
|
||||
int bg = (b >>> 8) & 0xFF;
|
||||
int bb = b & 0xFF;
|
||||
int r = 255 - ((255 - ar) * (255 - br)) / 255;
|
||||
int g = 255 - ((255 - ag) * (255 - bg)) / 255;
|
||||
int bl = 255 - ((255 - ab) * (255 - bb)) / 255;
|
||||
return (clamp255(r) << 16) | (clamp255(g) << 8) | clamp255(bl);
|
||||
}
|
||||
|
||||
public static int overlayRgb(int a, int b) {
|
||||
int ar = (a >>> 16) & 0xFF;
|
||||
int ag = (a >>> 8) & 0xFF;
|
||||
int ab = a & 0xFF;
|
||||
int br = (b >>> 16) & 0xFF;
|
||||
int bg = (b >>> 8) & 0xFF;
|
||||
int bb = b & 0xFF;
|
||||
int r = overlayChannel(ar, br);
|
||||
int g = overlayChannel(ag, bg);
|
||||
int bl = overlayChannel(ab, bb);
|
||||
return (r << 16) | (g << 8) | bl;
|
||||
}
|
||||
|
||||
public static float hash01(float seed) {
|
||||
float value = (float) (Math.sin(seed * 12.9898f + 78.233f) * 43758.5453f);
|
||||
return value - (float) Math.floor(value);
|
||||
}
|
||||
|
||||
private static int chromaticTint(int source, int tint, float strength, float phase) {
|
||||
int shifted = rotateRgb(tint, hash01(phase) < 0.5f ? 1 : 2);
|
||||
int sourceScreen = screenRgb(source, shifted);
|
||||
int colorized = mixRgb(shifted, sourceScreen, 0.42f);
|
||||
return mixRgb(source, colorized, strength * 0.72f);
|
||||
}
|
||||
|
||||
private static int essenceTint(int source, int tint, float strength, float phase) {
|
||||
int warm = mixRgb(tint, rotateRgb(tint, 1), 0.22f + hash01(phase) * 0.28f);
|
||||
int overlay = overlayRgb(source, warm);
|
||||
int glow = screenRgb(source, warm);
|
||||
int enhanced = mixRgb(overlay, glow, 0.36f);
|
||||
return mixRgb(source, enhanced, strength * 0.82f);
|
||||
}
|
||||
|
||||
private static int rotateRgb(int rgb, int steps) {
|
||||
int r = (rgb >>> 16) & 0xFF;
|
||||
int g = (rgb >>> 8) & 0xFF;
|
||||
int b = rgb & 0xFF;
|
||||
int s = Math.floorMod(steps, 3);
|
||||
if (s == 1) {
|
||||
return (g << 16) | (b << 8) | r;
|
||||
}
|
||||
if (s == 2) {
|
||||
return (b << 16) | (r << 8) | g;
|
||||
}
|
||||
return rgb & 0x00FFFFFF;
|
||||
}
|
||||
|
||||
private static int overlayChannel(int a, int b) {
|
||||
if (a < 128) {
|
||||
return clamp255((2 * a * b) / 255);
|
||||
}
|
||||
return clamp255(255 - (2 * (255 - a) * (255 - b)) / 255);
|
||||
}
|
||||
|
||||
private static float clamp01(float value) {
|
||||
if (value < 0.0f) return 0.0f;
|
||||
if (value > 1.0f) return 1.0f;
|
||||
return value;
|
||||
}
|
||||
|
||||
private static int clamp255(int value) {
|
||||
if (value < 0) return 0;
|
||||
if (value > 255) return 255;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.color;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
public enum ColorUtils {
|
||||
;
|
||||
|
||||
public static Color rainbow(int speed, int index, float saturation, float brightness, float opacity) {
|
||||
int angle = (int) ((System.currentTimeMillis() / speed + index) % 360);
|
||||
float hue = angle / 360f;
|
||||
Color color = new Color(Color.HSBtoRGB(hue, saturation, brightness));
|
||||
return new Color(color.getRed(), color.getGreen(), color.getBlue(), clampAlpha(opacity));
|
||||
}
|
||||
|
||||
public static Color skyRainbow(int speed, int index) {
|
||||
int angle = (int) ((System.currentTimeMillis() / speed + index) % 360);
|
||||
float hue = ((angle %= 360) / 360.0) < 0.5 ? -((float) (angle / 360.0)) : (float) (angle / 360.0);
|
||||
return Color.getHSBColor(hue, 0.5f, 1.0f);
|
||||
}
|
||||
|
||||
public static Color fade(int speed, int index, Color color, float alpha) {
|
||||
float[] hsb = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null);
|
||||
int angle = (int) ((System.currentTimeMillis() / speed + index) % 360);
|
||||
angle = (angle > 180 ? 360 - angle : angle) + 180;
|
||||
Color colorHSB = new Color(Color.HSBtoRGB(hsb[0], hsb[1], angle / 360f));
|
||||
return new Color(colorHSB.getRed(), colorHSB.getGreen(), colorHSB.getBlue(), clampAlpha(alpha));
|
||||
}
|
||||
|
||||
public static Color twoColorEffect(Color c1, Color c2, double speed, double count) {
|
||||
int angle = (int) (((System.currentTimeMillis()) / speed + count) % 360);
|
||||
angle = (angle >= 180 ? 360 - angle : angle) * 2;
|
||||
return interpolateColorC(c1, c2, angle / 360f);
|
||||
}
|
||||
|
||||
public static Color getAnalogousColor(Color color) {
|
||||
float[] hsb = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null);
|
||||
float degree = 0.84f;
|
||||
float newHueSubtracted = hsb[0] - degree;
|
||||
return new Color(Color.HSBtoRGB(newHueSubtracted, hsb[1], hsb[2]));
|
||||
}
|
||||
|
||||
public static Color interpolateColorsBackAndForth(int speed, int index, Color start, Color end, boolean trueColor) {
|
||||
int angle = (int) (((System.currentTimeMillis()) / speed + index) % 360);
|
||||
angle = (angle >= 180 ? 360 - angle : angle) * 2;
|
||||
return trueColor ? interpolateColorHue(start, end, angle / 360f) : interpolateColorC(start, end, angle / 360f);
|
||||
}
|
||||
|
||||
public static Color interpolateColorC(Color color1, Color color2, float amount) {
|
||||
amount = Math.min(1f, Math.max(0f, amount));
|
||||
return new Color(
|
||||
interpolateInt(color1.getRed(), color2.getRed(), amount),
|
||||
interpolateInt(color1.getGreen(), color2.getGreen(), amount),
|
||||
interpolateInt(color1.getBlue(), color2.getBlue(), amount),
|
||||
interpolateInt(color1.getAlpha(), color2.getAlpha(), amount)
|
||||
);
|
||||
}
|
||||
|
||||
public static Color interpolateColorHue(Color color1, Color color2, float amount) {
|
||||
amount = Math.min(1f, Math.max(0f, amount));
|
||||
float[] c1 = Color.RGBtoHSB(color1.getRed(), color1.getGreen(), color1.getBlue(), null);
|
||||
float[] c2 = Color.RGBtoHSB(color2.getRed(), color2.getGreen(), color2.getBlue(), null);
|
||||
Color result = Color.getHSBColor(interpolateFloat(c1[0], c2[0], amount), interpolateFloat(c1[1], c2[1], amount), interpolateFloat(c1[2], c2[2], amount));
|
||||
return new Color(result.getRed(), result.getGreen(), result.getBlue(), interpolateInt(color1.getAlpha(), color2.getAlpha(), amount));
|
||||
}
|
||||
|
||||
private static int interpolateInt(int oldValue, int newValue, double t) {
|
||||
return (int) (oldValue + (newValue - oldValue) * t);
|
||||
}
|
||||
|
||||
private static float interpolateFloat(float oldValue, float newValue, double t) {
|
||||
return (float) (oldValue + (newValue - oldValue) * t);
|
||||
}
|
||||
|
||||
private static int clampAlpha(float opacity) {
|
||||
return Math.max(0, Math.min(255, (int) (opacity * 255)));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.color;
|
||||
|
||||
public class RenderColor {
|
||||
public int r;
|
||||
public int g;
|
||||
public int b;
|
||||
public int a;
|
||||
|
||||
public RenderColor(int r, int g, int b, int a) {
|
||||
this.r = clamp(r);
|
||||
this.g = clamp(g);
|
||||
this.b = clamp(b);
|
||||
this.a = clamp(a);
|
||||
}
|
||||
|
||||
public RenderColor(int argb) {
|
||||
this.a = (argb >>> 24) & 0xFF;
|
||||
this.r = (argb >>> 16) & 0xFF;
|
||||
this.g = (argb >>> 8) & 0xFF;
|
||||
this.b = argb & 0xFF;
|
||||
}
|
||||
|
||||
private static int clamp(int v) {
|
||||
if (v < 0) return 0;
|
||||
if (v > 255) return 255;
|
||||
return v;
|
||||
}
|
||||
|
||||
public int argb() {
|
||||
return (a << 24) | (r << 16) | (g << 8) | b;
|
||||
}
|
||||
|
||||
public RenderColor copy() {
|
||||
return new RenderColor(r, g, b, a);
|
||||
}
|
||||
|
||||
public void setAlpha(int alpha) {
|
||||
this.a = clamp(alpha);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.command.core;
|
||||
import dev.loki.lovisual.render.engine.command.core.UiCommandKind;
|
||||
|
||||
/**
|
||||
* Normalized UI command root.
|
||||
* <p>
|
||||
* Renderer2D facade methods and the newer shape/effect/text/item command families can
|
||||
* coexist while callers move toward explicit UI command boundaries.
|
||||
*/
|
||||
public interface UiCommand {
|
||||
UiCommandKind kind();
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.command.core;
|
||||
|
||||
import dev.loki.lovisual.render.engine.core.system.LoVisualRenderSystem;
|
||||
import dev.loki.lovisual.render.engine.core.frame.RenderFrameContext;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import dev.loki.lovisual.render.engine.command.core.UiCommand;
|
||||
import dev.loki.lovisual.render.engine.command.core.UiCommandStats;
|
||||
import dev.loki.lovisual.render.engine.command.world.RenderCommandBuffer;
|
||||
import dev.loki.lovisual.render.engine.command.world.UiStatsSnapshot;
|
||||
|
||||
/**
|
||||
* High-level normalized UI command stream.
|
||||
*/
|
||||
public final class UiCommandBuffer implements RenderCommandBuffer {
|
||||
private final List<UiCommand> commands = new ArrayList<>();
|
||||
private final UiCommandStats stats = new UiCommandStats();
|
||||
|
||||
public void beginFrame(RenderFrameContext context) {
|
||||
if (context != null) {
|
||||
stats.beginFrame(context.frameId());
|
||||
}
|
||||
}
|
||||
|
||||
public void add(UiCommand command) {
|
||||
if (command == null) return;
|
||||
RenderFrameContext ctx = LoVisualRenderSystem.currentContext();
|
||||
if (ctx != null) stats.beginFrame(ctx.frameId());
|
||||
commands.add(command);
|
||||
stats.record(command);
|
||||
}
|
||||
|
||||
public List<UiCommand> commands() {
|
||||
return commands;
|
||||
}
|
||||
|
||||
public List<UiCommand> snapshot() {
|
||||
return Collections.unmodifiableList(commands);
|
||||
}
|
||||
|
||||
public UiCommandStats stats() {
|
||||
return stats;
|
||||
}
|
||||
|
||||
public UiStatsSnapshot statsSnapshot() {
|
||||
return stats.snapshot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
commands.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return commands.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void submit(RenderFrameContext context) {
|
||||
beginFrame(context);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.command.core;
|
||||
|
||||
public enum UiCommandKind {
|
||||
SHAPE,
|
||||
PATH,
|
||||
PRIMITIVE,
|
||||
TEXTURE,
|
||||
TEXT,
|
||||
ITEM,
|
||||
BLUR_REGION,
|
||||
LIQUID_GLASS_REGION,
|
||||
EFFECT_REGION
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.command.core;
|
||||
import dev.loki.lovisual.render.engine.command.core.UiCommand;
|
||||
import dev.loki.lovisual.render.engine.command.world.UiStatsSnapshot;
|
||||
|
||||
public final class UiCommandStats {
|
||||
private long frameId;
|
||||
private int recordedCommands;
|
||||
private int shapeCommands;
|
||||
private int pathCommands;
|
||||
private int textureCommands;
|
||||
private int textCommands;
|
||||
private int itemCommands;
|
||||
private int effectCommands;
|
||||
private int compiledBatches;
|
||||
private int backendCommands;
|
||||
|
||||
public void beginFrame(long frameId) {
|
||||
if (this.frameId == frameId) return;
|
||||
this.frameId = frameId;
|
||||
recordedCommands = shapeCommands = pathCommands = textureCommands = textCommands = itemCommands = effectCommands = 0;
|
||||
compiledBatches = backendCommands = 0;
|
||||
}
|
||||
|
||||
public void record(UiCommand command) {
|
||||
if (command == null) return;
|
||||
recordedCommands++;
|
||||
switch (command.kind()) {
|
||||
case SHAPE -> shapeCommands++;
|
||||
case PATH -> pathCommands++;
|
||||
case TEXTURE -> textureCommands++;
|
||||
case TEXT -> textCommands++;
|
||||
case ITEM -> itemCommands++;
|
||||
case BLUR_REGION, LIQUID_GLASS_REGION, EFFECT_REGION -> effectCommands++;
|
||||
case PRIMITIVE -> backendCommands++;
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addCompiledBatches(int count) {
|
||||
compiledBatches += Math.max(0, count);
|
||||
}
|
||||
|
||||
public void addBackendCommand() {
|
||||
backendCommands++;
|
||||
}
|
||||
|
||||
public UiStatsSnapshot snapshot() {
|
||||
return new UiStatsSnapshot(frameId, recordedCommands, shapeCommands, pathCommands, textureCommands,
|
||||
textCommands, itemCommands, effectCommands, compiledBatches, backendCommands);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.command.effect;
|
||||
import dev.loki.lovisual.render.engine.command.core.UiCommand;
|
||||
import dev.loki.lovisual.render.engine.command.core.UiCommandKind;
|
||||
|
||||
/**
|
||||
* Explicit marker for draw work that is executed by the ordered Renderer2D batch backend.
|
||||
*
|
||||
* <p>The command/RHI layer can see these batches instead of treating them as hidden side
|
||||
* effects inside Renderer2D. The payload is intentionally small: it is for scheduling and
|
||||
* profiling boundaries, not for replaying meshes.</p>
|
||||
*/
|
||||
public record UiBatchBackendCommand(String backend, String batchType) implements UiCommand {
|
||||
@Override
|
||||
public UiCommandKind kind() {
|
||||
return UiCommandKind.PRIMITIVE;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.command.effect;
|
||||
import dev.loki.lovisual.render.engine.command.core.UiCommand;
|
||||
import dev.loki.lovisual.render.engine.command.core.UiCommandKind;
|
||||
|
||||
public record UiEffectCommand(UiCommandKind kind, float x, float y, float width, float height,
|
||||
float radius) implements UiCommand {
|
||||
public UiEffectCommand {
|
||||
if (kind != UiCommandKind.BLUR_REGION && kind != UiCommandKind.LIQUID_GLASS_REGION) {
|
||||
throw new IllegalArgumentException("UiEffectCommand kind must be a UI effect region");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.command.effect;
|
||||
|
||||
import dev.loki.lovisual.render.engine.renderer.ui.draw.effect.UiEffectSpec;
|
||||
import dev.loki.lovisual.render.engine.command.core.UiCommand;
|
||||
import dev.loki.lovisual.render.engine.command.core.UiCommandKind;
|
||||
|
||||
public record UiEffectRegionCommand(UiEffectSpec effect) implements UiCommand {
|
||||
@Override
|
||||
public UiCommandKind kind() {
|
||||
return UiCommandKind.EFFECT_REGION;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.command.shape;
|
||||
|
||||
import dev.loki.lovisual.render.engine.renderer.ui.draw.paint.UiPaint;
|
||||
import dev.loki.lovisual.render.engine.renderer.ui.draw.shape.UiShape;
|
||||
import dev.loki.lovisual.render.engine.renderer.ui.draw.edge.UiStroke;
|
||||
import dev.loki.lovisual.render.engine.command.core.UiCommand;
|
||||
import dev.loki.lovisual.render.engine.command.core.UiCommandKind;
|
||||
|
||||
public record UiPathCommand(UiShape path, UiPaint paint, UiStroke stroke, boolean fill) implements UiCommand {
|
||||
@Override
|
||||
public UiCommandKind kind() {
|
||||
return UiCommandKind.PATH;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.command.shape;
|
||||
import dev.loki.lovisual.render.engine.command.core.UiCommand;
|
||||
import dev.loki.lovisual.render.engine.command.core.UiCommandKind;
|
||||
|
||||
public record UiPrimitiveCommand(float x, float y, float width, float height, int argb) implements UiCommand {
|
||||
@Override
|
||||
public UiCommandKind kind() {
|
||||
return UiCommandKind.PRIMITIVE;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.command.shape;
|
||||
|
||||
import dev.loki.lovisual.render.engine.renderer.ui.draw.paint.UiPaint;
|
||||
import dev.loki.lovisual.render.engine.renderer.ui.draw.shape.UiShape;
|
||||
import dev.loki.lovisual.render.engine.renderer.ui.draw.edge.UiStroke;
|
||||
import dev.loki.lovisual.render.engine.command.core.UiCommand;
|
||||
import dev.loki.lovisual.render.engine.command.core.UiCommandKind;
|
||||
|
||||
public record UiShapeCommand(UiShape shape, UiPaint paint, UiStroke stroke, boolean fill) implements UiCommand {
|
||||
@Override
|
||||
public UiCommandKind kind() {
|
||||
return UiCommandKind.SHAPE;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.command.texture;
|
||||
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import dev.loki.lovisual.render.engine.command.core.UiCommand;
|
||||
import dev.loki.lovisual.render.engine.command.core.UiCommandKind;
|
||||
|
||||
public record UiItemCommand(ItemStack stack, int x, int y, float scale) implements UiCommand {
|
||||
@Override
|
||||
public UiCommandKind kind() {
|
||||
return UiCommandKind.ITEM;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.command.texture;
|
||||
import dev.loki.lovisual.render.engine.command.core.UiCommand;
|
||||
import dev.loki.lovisual.render.engine.command.core.UiCommandKind;
|
||||
|
||||
public record UiTextCommand(String text, float x, float y, int argb, float size) implements UiCommand {
|
||||
@Override
|
||||
public UiCommandKind kind() {
|
||||
return UiCommandKind.TEXT;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.command.texture;
|
||||
|
||||
import com.mojang.blaze3d.textures.GpuTextureView;
|
||||
import dev.loki.lovisual.render.engine.command.core.UiCommand;
|
||||
import dev.loki.lovisual.render.engine.command.core.UiCommandKind;
|
||||
|
||||
public record UiTextureCommand(GpuTextureView texture, float x, float y, float width, float height,
|
||||
int argb) implements UiCommand {
|
||||
@Override
|
||||
public UiCommandKind kind() {
|
||||
return UiCommandKind.TEXTURE;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.command.texture;
|
||||
|
||||
import com.mojang.blaze3d.textures.GpuSampler;
|
||||
import com.mojang.blaze3d.textures.GpuTextureView;
|
||||
import dev.loki.lovisual.render.engine.renderer.ui.draw.paint.UiPaint;
|
||||
import dev.loki.lovisual.render.engine.renderer.ui.draw.shape.UiShape;
|
||||
import dev.loki.lovisual.render.engine.command.core.UiCommand;
|
||||
import dev.loki.lovisual.render.engine.command.core.UiCommandKind;
|
||||
|
||||
public record UiTextureDrawCommand(GpuTextureView view,
|
||||
GpuSampler sampler,
|
||||
UiShape shape,
|
||||
UiPaint tint,
|
||||
float u0,
|
||||
float v0,
|
||||
float u1,
|
||||
float v1,
|
||||
boolean masked) implements UiCommand {
|
||||
@Override
|
||||
public UiCommandKind kind() {
|
||||
return UiCommandKind.TEXTURE;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.command.world;
|
||||
|
||||
import dev.loki.lovisual.render.engine.core.frame.RenderFrameContext;
|
||||
|
||||
public interface RenderCommandBuffer {
|
||||
void clear();
|
||||
|
||||
int size();
|
||||
|
||||
void submit(RenderFrameContext context);
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.command.world;
|
||||
|
||||
public record UiStatsSnapshot(long frameId,
|
||||
int recordedCommands,
|
||||
int shapeCommands,
|
||||
int pathCommands,
|
||||
int textureCommands,
|
||||
int textCommands,
|
||||
int itemCommands,
|
||||
int effectCommands,
|
||||
int compiledBatches,
|
||||
int backendCommands) {
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.command.world;
|
||||
|
||||
import dev.loki.lovisual.render.engine.core.system.LoVisualRenderSystem;
|
||||
import dev.loki.lovisual.render.engine.core.frame.RenderFrameContext;
|
||||
import dev.loki.lovisual.render.engine.rhi.command.RhiDrawCommand;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import dev.loki.lovisual.render.engine.command.world.RenderCommandBuffer;
|
||||
|
||||
public final class WorldCommandBuffer implements RenderCommandBuffer {
|
||||
private final List<RhiDrawCommand> commands = new ArrayList<>();
|
||||
|
||||
public void add(RhiDrawCommand command) {
|
||||
if (command != null) commands.add(command);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
commands.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return commands.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void submit(RenderFrameContext context) {
|
||||
for (RhiDrawCommand command : commands) {
|
||||
LoVisualRenderSystem.rhi().drawMesh(command);
|
||||
}
|
||||
clear();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.compat.immediatelyfast;
|
||||
|
||||
import net.raphimc.immediatelyfast.ImmediatelyFast;
|
||||
import net.raphimc.immediatelyfast.feature.core.ImmediatelyFastConfig;
|
||||
|
||||
public enum ImmediatelyFastBridge {
|
||||
;
|
||||
|
||||
static ImmediatelyFastRuntimeSnapshot snapshot(String version) {
|
||||
ImmediatelyFastConfig config = ImmediatelyFast.config;
|
||||
if (config == null) {
|
||||
return defaults(version, "config not initialized yet");
|
||||
}
|
||||
|
||||
boolean enhancedBatching = config.enhanced_batching;
|
||||
boolean fastTextLookup = config.fast_text_lookup;
|
||||
boolean fontAtlasResizing = config.font_atlas_resizing;
|
||||
boolean skipTextSorting = config.skip_text_translucency_sorting;
|
||||
boolean signTextBuffering = config.experimental_sign_text_buffering;
|
||||
boolean framebufferSwitching = config.avoid_redundant_framebuffer_switching;
|
||||
boolean appleUploadFix = config.fix_slow_buffer_upload_on_apple_gpu;
|
||||
|
||||
return new ImmediatelyFastRuntimeSnapshot(
|
||||
true,
|
||||
version,
|
||||
framebufferSwitching,
|
||||
enhancedBatching,
|
||||
fastTextLookup || fontAtlasResizing || skipTextSorting || signTextBuffering,
|
||||
enhancedBatching || signTextBuffering || appleUploadFix,
|
||||
true,
|
||||
"ok"
|
||||
);
|
||||
}
|
||||
|
||||
static ImmediatelyFastRuntimeSnapshot defaults(String version, String status) {
|
||||
return new ImmediatelyFastRuntimeSnapshot(
|
||||
true,
|
||||
version,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
status == null || status.isBlank() ? "using default assumptions" : status
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.compat.immediatelyfast;
|
||||
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.fabricmc.loader.api.ModContainer;
|
||||
import dev.loki.lovisual.util.logging.DebugLog;
|
||||
|
||||
public enum ImmediatelyFastRuntime {
|
||||
;
|
||||
private static final String MOD_ID = "immediatelyfast";
|
||||
private static volatile boolean loggedConfigFailure;
|
||||
|
||||
public static ImmediatelyFastRuntimeSnapshot snapshot() {
|
||||
FabricLoader loader = FabricLoader.getInstance();
|
||||
if (!loader.isModLoaded(MOD_ID)) {
|
||||
return ImmediatelyFastRuntimeSnapshot.UNLOADED;
|
||||
}
|
||||
|
||||
String version = loader.getModContainer(MOD_ID)
|
||||
.map(ImmediatelyFastRuntime::versionOf)
|
||||
.orElse("");
|
||||
|
||||
try {
|
||||
return ImmediatelyFastBridge.snapshot(version);
|
||||
} catch (LinkageError | RuntimeException t) {
|
||||
if (!loggedConfigFailure) {
|
||||
loggedConfigFailure = true;
|
||||
DebugLog.warn("[ImmediatelyFastCompat] Runtime probe failed: " + t);
|
||||
}
|
||||
return ImmediatelyFastBridge.defaults(version, t.getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isModLoaded() {
|
||||
return FabricLoader.getInstance().isModLoaded(MOD_ID);
|
||||
}
|
||||
|
||||
public static boolean framebufferPassesModified() {
|
||||
return snapshot().framebufferPassesModified();
|
||||
}
|
||||
|
||||
public static boolean guiBatchingModified() {
|
||||
return snapshot().guiBatchingModified();
|
||||
}
|
||||
|
||||
public static boolean textRenderingModified() {
|
||||
return snapshot().textRenderingModified();
|
||||
}
|
||||
|
||||
public static boolean transientBufferHandlingModified() {
|
||||
return snapshot().transientBufferHandlingModified();
|
||||
}
|
||||
|
||||
private static String versionOf(ModContainer mod) {
|
||||
return mod.getMetadata().getVersion().getFriendlyString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.compat.immediatelyfast;
|
||||
|
||||
public record ImmediatelyFastRuntimeSnapshot(
|
||||
boolean modLoaded,
|
||||
String version,
|
||||
boolean framebufferPassesModified,
|
||||
boolean guiBatchingModified,
|
||||
boolean textRenderingModified,
|
||||
boolean transientBufferHandlingModified,
|
||||
boolean configAvailable,
|
||||
String status
|
||||
) {
|
||||
public static final ImmediatelyFastRuntimeSnapshot UNLOADED = new ImmediatelyFastRuntimeSnapshot(
|
||||
false,
|
||||
"",
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
"immediatelyfast mod not loaded"
|
||||
);
|
||||
|
||||
private static String yn(boolean value) {
|
||||
return value ? "y" : "n";
|
||||
}
|
||||
|
||||
public String shortLine() {
|
||||
if (!modLoaded) {
|
||||
return "immediatelyfast: not loaded";
|
||||
}
|
||||
String displayVersion = version == null || version.isBlank() ? "<unknown>" : version;
|
||||
String config = configAvailable ? "config" : "defaults";
|
||||
return "immediatelyfast: " + displayVersion
|
||||
+ ", " + config
|
||||
+ ", fb/gui/text/buf "
|
||||
+ yn(framebufferPassesModified)
|
||||
+ "/" + yn(guiBatchingModified)
|
||||
+ "/" + yn(textRenderingModified)
|
||||
+ "/" + yn(transientBufferHandlingModified);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.core.context;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Quaternionf;
|
||||
|
||||
/**
|
||||
* Camera data captured once for a LoVisual frame/phase.
|
||||
*/
|
||||
public record CameraContext(Vec3 position, Vec3 look, Quaternionf rotation, Matrix4f projection, Matrix4f modelView) {
|
||||
public CameraContext(Vec3 position, Vec3 look, Quaternionf rotation, Matrix4f projection, Matrix4f modelView) {
|
||||
this.position = position;
|
||||
this.look = look;
|
||||
this.rotation = new Quaternionf(rotation);
|
||||
this.projection = new Matrix4f(projection);
|
||||
this.modelView = new Matrix4f(modelView);
|
||||
}
|
||||
|
||||
public static CameraContext capture(Matrix4f projection, Matrix4f modelView) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || mc.gameRenderer == null || mc.gameRenderer.mainCamera() == null) {
|
||||
return new CameraContext(Vec3.ZERO, Vec3.ZERO, new Quaternionf(), projection, modelView);
|
||||
}
|
||||
|
||||
var camera = mc.gameRenderer.mainCamera();
|
||||
var forward = camera.forwardVector();
|
||||
return new CameraContext(
|
||||
camera.position(),
|
||||
new Vec3(forward.x(), forward.y(), forward.z()),
|
||||
camera.rotation(),
|
||||
projection,
|
||||
modelView
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Quaternionf rotation() {
|
||||
return new Quaternionf(rotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Matrix4f projection() {
|
||||
return new Matrix4f(projection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Matrix4f modelView() {
|
||||
return new Matrix4f(modelView);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.core.context;
|
||||
|
||||
import com.mojang.blaze3d.pipeline.RenderTarget;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public record FramebufferContext(@Nullable RenderTarget mainFramebuffer) {
|
||||
public static FramebufferContext capture() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
return new FramebufferContext(mc != null ? mc.gameRenderer.mainRenderTarget() : null);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,298 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.core.context;
|
||||
import dev.loki.lovisual.render.engine.Projection2D;
|
||||
|
||||
|
||||
import com.mojang.blaze3d.ProjectionType;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.renderer.Projection;
|
||||
import net.minecraft.client.renderer.ProjectionMatrixBuffer;
|
||||
import org.joml.Matrix4f;
|
||||
import dev.loki.lovisual.render.engine.RenderState;
|
||||
import dev.loki.lovisual.render.engine.math.HudScale;
|
||||
import dev.loki.lovisual.render.engine.renderer.MeshRenderer;
|
||||
import dev.loki.lovisual.render.engine.renderer.Renderer2D;
|
||||
|
||||
/**
|
||||
* Unified 2D viewport/projection runtime state.
|
||||
* <p>
|
||||
* This replaces the old Projection2D ownership model: callers use ViewportContext for
|
||||
* projection mode switches, UI scale information, DrawContext tracking and immutable
|
||||
* per-frame viewport snapshots.
|
||||
*/
|
||||
public record ViewportContext(
|
||||
int framebufferWidth,
|
||||
int framebufferHeight,
|
||||
float scaleFactor,
|
||||
float width,
|
||||
float height,
|
||||
float uiScale,
|
||||
ProjectionMode projectionMode,
|
||||
Matrix4f projectionMatrix
|
||||
) {
|
||||
private static ProjectionMatrixBuffer matrixBuffer;
|
||||
|
||||
private static volatile ViewportContext current = fallback(ProjectionMode.SCALED);
|
||||
private static float activeUiScale = 1.0f;
|
||||
private static GuiGraphicsExtractor currentContext;
|
||||
private static ProjectionMode activeMode = ProjectionMode.SCALED;
|
||||
|
||||
private static ProjectionMatrixBuffer matrixBuffer() {
|
||||
ProjectionMatrixBuffer buffer = matrixBuffer;
|
||||
if (buffer == null) {
|
||||
buffer = new ProjectionMatrixBuffer("lovisual-projection-matrix");
|
||||
matrixBuffer = buffer;
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public static ViewportContext capture() {
|
||||
return capture(activeMode);
|
||||
}
|
||||
|
||||
public static ViewportContext capture(ProjectionMode mode) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || mc.getWindow() == null) {
|
||||
return fallback(mode);
|
||||
}
|
||||
|
||||
int fbw = Math.max(1, mc.getWindow().getWidth());
|
||||
int fbh = Math.max(1, mc.getWindow().getHeight());
|
||||
float scaleFactor = Math.max(1.0f, (float) mc.getWindow().getGuiScale());
|
||||
|
||||
float width;
|
||||
float height;
|
||||
float uiScale;
|
||||
switch (mode) {
|
||||
case UNSCALED -> {
|
||||
width = fbw;
|
||||
height = fbh;
|
||||
uiScale = 1.0f;
|
||||
}
|
||||
case LOGICAL -> {
|
||||
width = HudScale.virtualWidth(fbw, fbh);
|
||||
height = HudScale.virtualHeight(fbw, fbh);
|
||||
uiScale = HudScale.scale(fbw, fbh);
|
||||
}
|
||||
case CUSTOM -> {
|
||||
ViewportContext previous = current;
|
||||
width = previous != null ? previous.width : fbw;
|
||||
height = previous != null ? previous.height : fbh;
|
||||
uiScale = previous != null ? previous.uiScale : 1.0f;
|
||||
}
|
||||
case SCALED -> {
|
||||
width = fbw / scaleFactor;
|
||||
height = fbh / scaleFactor;
|
||||
uiScale = 1.0f;
|
||||
}
|
||||
default -> throw new IllegalStateException("Unhandled projection mode: " + mode);
|
||||
}
|
||||
|
||||
return new ViewportContext(fbw, fbh, scaleFactor, width, height, uiScale, mode, matrixFor(width, height));
|
||||
}
|
||||
|
||||
public static ViewportContext current() {
|
||||
return current;
|
||||
}
|
||||
|
||||
public static GuiGraphicsExtractor getCurrentContext() {
|
||||
return currentContext;
|
||||
}
|
||||
|
||||
public static float getScaleFactor() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || mc.getWindow() == null) return 1.0f;
|
||||
return (float) mc.getWindow().getGuiScale();
|
||||
}
|
||||
|
||||
public static float getUiScale() {
|
||||
return activeUiScale;
|
||||
}
|
||||
|
||||
public static ProjectionMode activeMode() {
|
||||
return activeMode;
|
||||
}
|
||||
|
||||
public static void beginUnscaled(GuiGraphicsExtractor ctx) {
|
||||
begin(ctx, ProjectionMode.UNSCALED);
|
||||
}
|
||||
|
||||
public static void beginUnscaledLogical(GuiGraphicsExtractor ctx) {
|
||||
begin(ctx, ProjectionMode.LOGICAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach the extractor and switch to logical HUD projection without creating a new
|
||||
* GuiGraphics stratum. Use this only from hooks that are already inside an exact
|
||||
* vanilla stratum, for example PlayerTabOverlay.extractRenderState.
|
||||
*/
|
||||
public static void beginCurrentStratumUnscaledLogical(GuiGraphicsExtractor ctx) {
|
||||
begin(ctx, ProjectionMode.LOGICAL, false);
|
||||
}
|
||||
|
||||
public static void beginScaled(GuiGraphicsExtractor ctx) {
|
||||
begin(ctx, ProjectionMode.SCALED);
|
||||
}
|
||||
|
||||
public static void end(GuiGraphicsExtractor ctx) {
|
||||
begin(ctx, ProjectionMode.SCALED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore vanilla scaled projection without allocating another GuiGraphics stratum.
|
||||
* Use after beginCurrentStratumUnscaledLogical() from hooks that are already inside
|
||||
* a vanilla-created stratum.
|
||||
*/
|
||||
public static void endCurrentStratum(GuiGraphicsExtractor ctx) {
|
||||
begin(ctx, ProjectionMode.SCALED, false);
|
||||
}
|
||||
|
||||
public static void unscaledProjection() {
|
||||
apply(ProjectionMode.UNSCALED);
|
||||
}
|
||||
|
||||
public static void logicalProjection() {
|
||||
apply(ProjectionMode.LOGICAL);
|
||||
}
|
||||
|
||||
public static void scaledProjection() {
|
||||
apply(ProjectionMode.SCALED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply an already captured 2D viewport/projection without causing Renderer2D flushes.
|
||||
* Used by render-thread replay of 2D batches recorded during GUI extraction.
|
||||
*/
|
||||
public static void applyCaptured(ViewportContext next) {
|
||||
if (next == null) return;
|
||||
Matrix4f matrix = next.projectionMatrix != null ? new Matrix4f(next.projectionMatrix) : matrixFor(next.width, next.height);
|
||||
RenderSystem.setProjectionMatrix(matrixBuffer().getBuffer(matrix), ProjectionType.ORTHOGRAPHIC);
|
||||
MeshRenderer.setProjection(matrix);
|
||||
activeUiScale = next.uiScale;
|
||||
activeMode = next.projectionMode;
|
||||
current = new ViewportContext(
|
||||
next.framebufferWidth,
|
||||
next.framebufferHeight,
|
||||
next.scaleFactor,
|
||||
next.width,
|
||||
next.height,
|
||||
next.uiScale,
|
||||
next.projectionMode,
|
||||
matrix
|
||||
);
|
||||
RenderState.rendering3D = false;
|
||||
}
|
||||
|
||||
public static void projectionForSize(float width, float height) {
|
||||
float safeWidth = Math.max(1.0f, width);
|
||||
float safeHeight = Math.max(1.0f, height);
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
int fbw = mc != null && mc.getWindow() != null ? Math.max(1, mc.getWindow().getWidth()) : (int) safeWidth;
|
||||
int fbh = mc != null && mc.getWindow() != null ? Math.max(1, mc.getWindow().getHeight()) : (int) safeHeight;
|
||||
float scaleFactor = mc != null && mc.getWindow() != null ? Math.max(1.0f, (float) mc.getWindow().getGuiScale()) : 1.0f;
|
||||
|
||||
Matrix4f matrix = matrixFor(safeWidth, safeHeight);
|
||||
ViewportContext next = new ViewportContext(fbw, fbh, scaleFactor, safeWidth, safeHeight, 1.0f, ProjectionMode.CUSTOM, new Matrix4f(matrix));
|
||||
if (isActiveProjection(next, ProjectionMode.CUSTOM)) {
|
||||
RenderSystem.setProjectionMatrix(matrixBuffer().getBuffer(matrix), ProjectionType.ORTHOGRAPHIC);
|
||||
MeshRenderer.setProjection(matrix);
|
||||
current = next;
|
||||
RenderState.rendering3D = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Renderer2D.flushBatch(Renderer2D.FlushReason.PROJECTION);
|
||||
RenderSystem.setProjectionMatrix(matrixBuffer().getBuffer(matrix), ProjectionType.ORTHOGRAPHIC);
|
||||
MeshRenderer.setProjection(matrix);
|
||||
|
||||
activeUiScale = 1.0f;
|
||||
activeMode = ProjectionMode.CUSTOM;
|
||||
current = next;
|
||||
RenderState.rendering3D = false;
|
||||
}
|
||||
|
||||
private static void begin(GuiGraphicsExtractor ctx, ProjectionMode mode) {
|
||||
begin(ctx, mode, true);
|
||||
}
|
||||
|
||||
private static void begin(GuiGraphicsExtractor ctx, ProjectionMode mode, boolean nextStratum) {
|
||||
currentContext = ctx;
|
||||
if (nextStratum && ctx != null) ctx.nextStratum();
|
||||
apply(mode, true);
|
||||
}
|
||||
|
||||
private static void apply(ProjectionMode mode) {
|
||||
apply(mode, true);
|
||||
}
|
||||
|
||||
private static void apply(ProjectionMode mode, boolean flush) {
|
||||
ViewportContext next = capture(mode);
|
||||
if (isActiveProjection(next, mode)) {
|
||||
RenderSystem.setProjectionMatrix(matrixBuffer().getBuffer(next.projectionMatrix), ProjectionType.ORTHOGRAPHIC);
|
||||
MeshRenderer.setProjection(next.projectionMatrix);
|
||||
activeUiScale = next.uiScale;
|
||||
activeMode = mode;
|
||||
current = next;
|
||||
RenderState.rendering3D = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (flush) Renderer2D.flushBatch(Renderer2D.FlushReason.PROJECTION);
|
||||
RenderSystem.setProjectionMatrix(matrixBuffer().getBuffer(next.projectionMatrix), ProjectionType.ORTHOGRAPHIC);
|
||||
MeshRenderer.setProjection(next.projectionMatrix);
|
||||
activeUiScale = next.uiScale;
|
||||
activeMode = mode;
|
||||
current = next;
|
||||
RenderState.rendering3D = false;
|
||||
}
|
||||
|
||||
private static boolean isActiveProjection(ViewportContext next, ProjectionMode mode) {
|
||||
ViewportContext previous = current;
|
||||
return previous != null
|
||||
&& activeMode == mode
|
||||
&& previous.framebufferWidth == next.framebufferWidth
|
||||
&& previous.framebufferHeight == next.framebufferHeight
|
||||
&& Float.compare(previous.scaleFactor, next.scaleFactor) == 0
|
||||
&& Float.compare(previous.width, next.width) == 0
|
||||
&& Float.compare(previous.height, next.height) == 0
|
||||
&& Float.compare(previous.uiScale, next.uiScale) == 0;
|
||||
}
|
||||
|
||||
private static ViewportContext fallback(ProjectionMode mode) {
|
||||
Matrix4f matrix = matrixFor(1.0f, 1.0f);
|
||||
return new ViewportContext(1, 1, 1.0f, 1.0f, 1.0f, 1.0f, mode, matrix);
|
||||
}
|
||||
|
||||
private static Matrix4f matrixFor(float width, float height) {
|
||||
Projection projection = new Projection();
|
||||
projection.setupOrtho(-1000.0f, 1000.0f, width, height, true);
|
||||
return projection.getMatrix(new Matrix4f());
|
||||
}
|
||||
|
||||
public enum ProjectionMode {
|
||||
/**
|
||||
* Framebuffer pixel coordinates.
|
||||
*/
|
||||
UNSCALED,
|
||||
/**
|
||||
* LoVisual logical HUD coordinates from HudScale.
|
||||
*/
|
||||
LOGICAL,
|
||||
/**
|
||||
* Vanilla scaled GUI coordinates.
|
||||
*/
|
||||
SCALED,
|
||||
/**
|
||||
* Explicit caller-provided projection size.
|
||||
*/
|
||||
CUSTOM
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.core.frame;
|
||||
|
||||
/**
|
||||
* Backend-visible lifecycle state for one Minecraft-presented frame.
|
||||
*/
|
||||
public enum FrameLifecycle {
|
||||
IDLE,
|
||||
RECORDING,
|
||||
SUBMITTED,
|
||||
PRESENTED
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.core.frame;
|
||||
import dev.loki.lovisual.render.engine.RenderState;
|
||||
|
||||
|
||||
import dev.loki.lovisual.render.engine.core.policy.DepthProvider;
|
||||
import dev.loki.lovisual.render.engine.core.policy.fog.FogProvider;
|
||||
import dev.loki.lovisual.render.engine.core.policy.LightPolicy;
|
||||
import dev.loki.lovisual.render.engine.core.policy.VisibilityProvider;
|
||||
import dev.loki.lovisual.render.sodium.bridge.SodiumFrameContext;
|
||||
import dev.loki.lovisual.render.engine.core.context.CameraContext;
|
||||
import dev.loki.lovisual.render.engine.core.context.FramebufferContext;
|
||||
import dev.loki.lovisual.render.engine.core.context.ViewportContext;
|
||||
import dev.loki.lovisual.render.engine.core.frame.RenderPhase;
|
||||
|
||||
/**
|
||||
* Immutable per-phase context. Old RenderState should eventually become only a shim over this.
|
||||
*/
|
||||
public record RenderFrameContext(long frameId, float tickProgress, float frameDeltaTicks, float fixedDeltaTicks,
|
||||
RenderPhase phase, CameraContext camera, ViewportContext viewport,
|
||||
FramebufferContext framebuffer, DepthProvider depthProvider, FogProvider fogProvider,
|
||||
LightPolicy lightPolicy, VisibilityProvider visibilityProvider,
|
||||
SodiumFrameContext sodium) {
|
||||
private static final float TICKS_PER_SECOND = 20.0f;
|
||||
|
||||
/**
|
||||
* Legacy alias for render interpolation progress.
|
||||
*/
|
||||
public float tickDelta() {
|
||||
return tickProgress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fractional progress inside the current game tick, used for world/entity interpolation.
|
||||
*/
|
||||
@Override
|
||||
public float tickProgress() {
|
||||
return tickProgress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Time since previous rendered frame, in vanilla tick units.
|
||||
*/
|
||||
@Override
|
||||
public float frameDeltaTicks() {
|
||||
return frameDeltaTicks;
|
||||
}
|
||||
|
||||
public float frameDeltaSeconds() {
|
||||
return frameDeltaTicks / TICKS_PER_SECOND;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vanilla fixed delta in tick units, clamped by RenderTickCounter.Dynamic.
|
||||
*/
|
||||
@Override
|
||||
public float fixedDeltaTicks() {
|
||||
return fixedDeltaTicks;
|
||||
}
|
||||
|
||||
public RenderFrameContext withPhase(RenderPhase newPhase) {
|
||||
return new RenderFrameContext(frameId, tickProgress, frameDeltaTicks, fixedDeltaTicks,
|
||||
newPhase, camera, viewport, framebuffer,
|
||||
depthProvider, fogProvider, lightPolicy, visibilityProvider, sodium);
|
||||
}
|
||||
|
||||
public RenderFrameContext withTiming(float newTickProgress,
|
||||
float newFrameDeltaTicks,
|
||||
float newFixedDeltaTicks) {
|
||||
return new RenderFrameContext(frameId, newTickProgress, newFrameDeltaTicks, newFixedDeltaTicks,
|
||||
phase, camera, viewport, framebuffer,
|
||||
depthProvider, fogProvider, lightPolicy, visibilityProvider, sodium);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.core.frame;
|
||||
|
||||
/**
|
||||
* Explicit render phase used by the LoVisual frame graph.
|
||||
* This is intentionally independent from Fabric/Minecraft event names: event hooks map into these phases.
|
||||
*/
|
||||
public enum RenderPhase {
|
||||
NONE,
|
||||
WORLD_BEFORE_ENTITIES,
|
||||
WORLD_AFTER_ENTITIES,
|
||||
WORLD_BEFORE_TRANSLUCENT,
|
||||
WORLD_AFTER_TRANSLUCENT,
|
||||
WORLD_POST_PRE_HAND,
|
||||
WORLD_POST_HAND,
|
||||
HUD_CAPTURE,
|
||||
HUD_MAIN,
|
||||
HUD_EFFECTS,
|
||||
/**
|
||||
* Dedicated topmost in-game screen layer. Used for LoVisual ClickGUI so the whole
|
||||
* vanilla InGameHud is below it even on Minecraft versions where Screen rendering
|
||||
* and HUD rendering interleave differently.
|
||||
*/
|
||||
SCREEN_TOP,
|
||||
SCREEN,
|
||||
LEGACY_SHADOW_TERRAIN
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.core.frame;
|
||||
import dev.loki.lovisual.render.engine.core.frame.RenderFrameContext;
|
||||
import dev.loki.lovisual.render.engine.core.frame.RenderPhase;
|
||||
import dev.loki.lovisual.render.engine.core.system.LoVisualRenderSystem;
|
||||
|
||||
/**
|
||||
* AutoCloseable phase guard. Every render hook should use this instead of raw enterPhase().
|
||||
* It restores the previous RenderFrameContext phase and closes profiler scopes even on exceptions.
|
||||
*/
|
||||
public final class RenderPhaseScope implements AutoCloseable {
|
||||
private final RenderPhase phase;
|
||||
private final RenderFrameContext previous;
|
||||
private final AutoCloseable profilerScope;
|
||||
private boolean closed;
|
||||
|
||||
public RenderPhaseScope(RenderPhase phase, RenderFrameContext previous, AutoCloseable profilerScope) {
|
||||
|
||||
this.phase = phase == null ? RenderPhase.NONE : phase;
|
||||
this.previous = previous;
|
||||
this.profilerScope = profilerScope;
|
||||
}
|
||||
|
||||
public RenderPhase phase() {
|
||||
return phase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (closed) return;
|
||||
closed = true;
|
||||
try {
|
||||
if (profilerScope != null) {
|
||||
profilerScope.close();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
// Profiling must never break render lifecycle recovery.
|
||||
} finally {
|
||||
LoVisualRenderSystem.restorePhase(previous);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.core.policy;
|
||||
|
||||
import com.mojang.blaze3d.textures.GpuTextureView;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface DepthProvider {
|
||||
DepthProvider NONE = () -> null;
|
||||
|
||||
@Nullable GpuTextureView depthAttachment();
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.core.policy;
|
||||
|
||||
/**
|
||||
* Placeholder for LoVisual lighting policy. Kept explicit so fullbright/ambient/shaderpack hooks do not leak into renderers.
|
||||
*/
|
||||
public interface LightPolicy {
|
||||
LightPolicy VANILLA = new LightPolicy() {
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.core.policy;
|
||||
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import dev.loki.lovisual.render.engine.core.policy.VisibilityQuery;
|
||||
|
||||
public interface VisibilityProvider {
|
||||
VisibilityProvider ALWAYS_VISIBLE = new VisibilityProvider() {
|
||||
@Override
|
||||
public boolean isBoxVisible(AABB box) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBoxVisible(AABB box, VisibilityQuery query) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String debugName() {
|
||||
return "always_visible";
|
||||
}
|
||||
};
|
||||
|
||||
boolean isBoxVisible(AABB box);
|
||||
|
||||
default boolean isBoxVisible(AABB box, VisibilityQuery query) {
|
||||
return isBoxVisible(box);
|
||||
}
|
||||
|
||||
default String debugName() {
|
||||
return getClass().getSimpleName();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.core.policy;
|
||||
|
||||
import net.minecraft.world.phys.AABB;
|
||||
|
||||
/**
|
||||
* Per-call visibility policy. Lets important/special overlays opt out of Sodium section culling.
|
||||
*/
|
||||
public record VisibilityQuery(AABB box,
|
||||
boolean useSectionVisibility,
|
||||
boolean alwaysVisible,
|
||||
String reason) {
|
||||
public static VisibilityQuery worldOverlay(AABB box) {
|
||||
return new VisibilityQuery(box, true, false, "world_overlay");
|
||||
}
|
||||
|
||||
public static VisibilityQuery alwaysVisible(AABB box, String reason) {
|
||||
return new VisibilityQuery(box, false, true, reason != null ? reason : "always_visible");
|
||||
}
|
||||
|
||||
public static VisibilityQuery noSectionVisibility(AABB box, String reason) {
|
||||
return new VisibilityQuery(box, false, false, reason != null ? reason : "section_visibility_disabled");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.core.policy.fog;
|
||||
|
||||
import com.mojang.blaze3d.buffers.GpuBufferSlice;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface FogProvider {
|
||||
public FogProvider NONE = () -> null;
|
||||
|
||||
@Nullable GpuBufferSlice fogUniformSlice();
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.core.policy.fog;
|
||||
|
||||
import com.mojang.blaze3d.buffers.GpuBufferSlice;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.fog.FogRenderer;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import dev.loki.lovisual.mixins.accessors.renderer.GameRendererAccessor;
|
||||
import dev.loki.lovisual.render.engine.core.policy.fog.FogProvider;
|
||||
|
||||
/**
|
||||
* Vanilla world fog provider used by world pass compilation until Stage 8 pipeline metadata is global.
|
||||
*/
|
||||
public final class VanillaWorldFogProvider implements FogProvider {
|
||||
public static final VanillaWorldFogProvider INSTANCE = new VanillaWorldFogProvider();
|
||||
|
||||
private VanillaWorldFogProvider() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable GpuBufferSlice fogUniformSlice() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || !(mc.gameRenderer instanceof GameRendererAccessor accessor)) return null;
|
||||
FogRenderer fogRenderer = accessor.lovisual$getFogRenderer();
|
||||
return fogRenderer != null ? fogRenderer.getBuffer(FogRenderer.FogMode.WORLD) : null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,374 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.core.system;
|
||||
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import dev.loki.lovisual.render.engine.compat.immediatelyfast.ImmediatelyFastRuntime;
|
||||
import dev.loki.lovisual.render.engine.compat.immediatelyfast.ImmediatelyFastRuntimeSnapshot;
|
||||
import dev.loki.lovisual.render.engine.renderer.Renderer3D;
|
||||
import dev.loki.lovisual.render.iris.runtime.IrisRuntime;
|
||||
import dev.loki.lovisual.render.iris.runtime.IrisRuntimeSnapshot;
|
||||
import dev.loki.lovisual.render.sodium.interop.SodiumTerrainInteropStatsSnapshot;
|
||||
import dev.loki.lovisual.render.sodium.visibility.SodiumVisibilityStatsSnapshot;
|
||||
import org.joml.Matrix4f;
|
||||
import dev.loki.lovisual.render.engine.RenderState;
|
||||
import dev.loki.lovisual.render.engine.core.policy.LightPolicy;
|
||||
import dev.loki.lovisual.render.engine.core.policy.fog.VanillaWorldFogProvider;
|
||||
import dev.loki.lovisual.render.engine.depth.WorldSceneDepth;
|
||||
import dev.loki.lovisual.render.engine.framegraph.LoVisualFrameGraph;
|
||||
import dev.loki.lovisual.render.engine.profiler.frame.FrameStutterProfiler;
|
||||
import dev.loki.lovisual.render.engine.profiler.frame.RenderFrameProfiler;
|
||||
import dev.loki.lovisual.render.engine.rhi.resource.LoVisualRhi;
|
||||
import dev.loki.lovisual.render.engine.rhi.stats.RhiStatsSnapshot;
|
||||
import dev.loki.lovisual.render.engine.rhi.backend.SodiumGlBackend;
|
||||
import dev.loki.lovisual.render.engine.rhi.backend.vulkan.LoVisualVulkanBackend;
|
||||
import dev.loki.lovisual.render.engine.rhi.resource.manager.RenderResourceManager;
|
||||
import dev.loki.lovisual.render.engine.rhi.resource.manager.RenderResourceStatsSnapshot;
|
||||
import dev.loki.lovisual.render.engine.rhi.uniform.LoVisualUniformAllocator;
|
||||
import dev.loki.lovisual.render.engine.rhi.uniform.stats.UniformAllocatorStatsSnapshot;
|
||||
import dev.loki.lovisual.render.engine.text.command.TextCommandStatsSnapshot;
|
||||
import dev.loki.lovisual.render.engine.text.render.TextRenderSystem;
|
||||
import dev.loki.lovisual.render.engine.world.stats.WorldRenderStatsSnapshot;
|
||||
import dev.loki.lovisual.render.sodium.bridge.SodiumFrameContext;
|
||||
import dev.loki.lovisual.render.sodium.bridge.SodiumRenderBridge;
|
||||
import dev.loki.lovisual.util.logging.DebugLog;
|
||||
|
||||
import java.util.Locale;
|
||||
import dev.loki.lovisual.render.engine.core.context.CameraContext;
|
||||
import dev.loki.lovisual.render.engine.core.context.FramebufferContext;
|
||||
import dev.loki.lovisual.render.engine.core.context.ViewportContext;
|
||||
import dev.loki.lovisual.render.engine.core.frame.FrameLifecycle;
|
||||
import dev.loki.lovisual.render.engine.core.frame.RenderFrameContext;
|
||||
import dev.loki.lovisual.render.engine.core.frame.RenderPhase;
|
||||
import dev.loki.lovisual.render.engine.core.frame.RenderPhaseScope;
|
||||
|
||||
/**
|
||||
* Single owner of LoVisual render lifecycle.
|
||||
* - one LoVisual frame per Minecraft-presented frame;
|
||||
* - many scoped RenderPhase entries inside that frame;
|
||||
* - backend submission is flushed before framePresented(), not by random renderer classes;
|
||||
* - RenderState is a compatibility shim, not the lifecycle owner.
|
||||
*/
|
||||
public enum LoVisualRenderSystem {
|
||||
;
|
||||
private static final SodiumRenderBridge SODIUM = new SodiumRenderBridge();
|
||||
private static final LoVisualFrameGraph FRAME_GRAPH = new LoVisualFrameGraph();
|
||||
private static final LoVisualUniformAllocator UNIFORMS = new LoVisualUniformAllocator();
|
||||
|
||||
private static LoVisualRhi rhi;
|
||||
private static BackendKind backendKind = BackendKind.UNKNOWN;
|
||||
private static RenderFrameContext currentContext;
|
||||
private static long frameId;
|
||||
private static boolean initialized;
|
||||
private static boolean frameOpen;
|
||||
private static boolean submissionEnded;
|
||||
private static FrameLifecycle lifecycle = FrameLifecycle.IDLE;
|
||||
|
||||
private enum BackendKind {
|
||||
UNKNOWN,
|
||||
GL,
|
||||
VULKAN
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
if (initialized) {
|
||||
ensureBackendMatchesDevice();
|
||||
return;
|
||||
}
|
||||
BackendKind desired = detectBackendKind();
|
||||
backendKind = desired == BackendKind.UNKNOWN ? BackendKind.GL : desired;
|
||||
rhi = createBackend(backendKind);
|
||||
initialized = true;
|
||||
DebugLog.renderThreadOnChange(
|
||||
"lovisual.rhi.backend",
|
||||
backendKind,
|
||||
"[LoVisualRHI] backend initialized: %s",
|
||||
backendKind
|
||||
);
|
||||
ensureBackendMatchesDevice();
|
||||
}
|
||||
|
||||
private static LoVisualRhi createBackend(BackendKind kind) {
|
||||
if (kind == BackendKind.VULKAN) {
|
||||
return new LoVisualVulkanBackend();
|
||||
}
|
||||
return new SodiumGlBackend();
|
||||
}
|
||||
|
||||
private static void ensureBackendMatchesDevice() {
|
||||
if (!initialized || rhi == null) return;
|
||||
BackendKind desired = detectBackendKind();
|
||||
if (desired == BackendKind.UNKNOWN || desired == backendKind) return;
|
||||
|
||||
if (frameOpen) {
|
||||
DebugLog.warnOnChange(
|
||||
"lovisual.rhi.backend.switch.deferred",
|
||||
backendKind + "->" + desired + "|frameOpen",
|
||||
"[LoVisualRHI] backend switch deferred until frame end: %s -> %s",
|
||||
backendKind,
|
||||
desired
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
LoVisualRhi previous = rhi;
|
||||
BackendKind previousKind = backendKind;
|
||||
LoVisualRhi next;
|
||||
try {
|
||||
next = createBackend(desired);
|
||||
} catch (Throwable t) {
|
||||
DebugLog.error("[LoVisualRHI] failed to create backend " + desired, t);
|
||||
return;
|
||||
}
|
||||
|
||||
rhi = next;
|
||||
backendKind = desired;
|
||||
try {
|
||||
previous.close();
|
||||
} catch (Throwable t) {
|
||||
DebugLog.warnOnChange(
|
||||
"lovisual.rhi.backend.old.close.failed",
|
||||
previousKind + "|" + desired + "|" + t.getClass().getSimpleName(),
|
||||
"[LoVisualRHI] old backend close failed during switch %s -> %s: %s: %s",
|
||||
previousKind,
|
||||
desired,
|
||||
t.getClass().getSimpleName(),
|
||||
t.getMessage()
|
||||
);
|
||||
}
|
||||
DebugLog.renderThreadOnChange(
|
||||
"lovisual.rhi.backend",
|
||||
backendKind,
|
||||
"[LoVisualRHI] backend switched: %s -> %s",
|
||||
previousKind,
|
||||
backendKind
|
||||
);
|
||||
}
|
||||
|
||||
private static BackendKind detectBackendKind() {
|
||||
String backendName = null;
|
||||
try {
|
||||
var device = RenderSystem.tryGetDevice();
|
||||
if (device != null && device.getDeviceInfo() != null) {
|
||||
backendName = device.getDeviceInfo().backendName();
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
// Fall through to RenderSystem backend description.
|
||||
}
|
||||
|
||||
if (backendName == null || backendName.isBlank()) {
|
||||
try {
|
||||
backendName = RenderSystem.getBackendDescription();
|
||||
} catch (Throwable ignored) {
|
||||
backendName = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (backendName == null || backendName.isBlank()) return BackendKind.UNKNOWN;
|
||||
String normalized = backendName.toLowerCase(Locale.ROOT);
|
||||
if (normalized.contains("vulkan")) return BackendKind.VULKAN;
|
||||
if (normalized.contains("opengl") || normalized.contains("gl")) return BackendKind.GL;
|
||||
return BackendKind.UNKNOWN;
|
||||
}
|
||||
|
||||
public static LoVisualRhi rhi() {
|
||||
init();
|
||||
ensureBackendMatchesDevice();
|
||||
return rhi;
|
||||
}
|
||||
|
||||
public static SodiumRenderBridge sodium() {
|
||||
return SODIUM;
|
||||
}
|
||||
|
||||
public static RenderResourceManager resources() {
|
||||
return rhi().resources();
|
||||
}
|
||||
|
||||
public static LoVisualFrameGraph frameGraph() {
|
||||
return FRAME_GRAPH;
|
||||
}
|
||||
|
||||
public static LoVisualUniformAllocator uniforms() {
|
||||
return UNIFORMS;
|
||||
}
|
||||
|
||||
public static RenderFrameContext currentContext() {
|
||||
return currentContext;
|
||||
}
|
||||
|
||||
public static FrameLifecycle lifecycle() {
|
||||
return lifecycle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the LoVisual frame if needed, otherwise refreshes camera/viewport data without advancing frameId.
|
||||
*/
|
||||
public static RenderFrameContext beginFrame(float tickDelta, Matrix4f projection, Matrix4f modelView) {
|
||||
return beginFrame(tickDelta, tickDelta, tickDelta, projection, modelView);
|
||||
}
|
||||
|
||||
public static RenderFrameContext beginFrame(float tickProgress,
|
||||
float frameDeltaTicks,
|
||||
float fixedDeltaTicks,
|
||||
Matrix4f projection,
|
||||
Matrix4f modelView) {
|
||||
LoVisualRhi activeRhi = rhi();
|
||||
SodiumFrameContext sodiumFrame;
|
||||
if (!frameOpen) {
|
||||
frameId++;
|
||||
activeRhi.beginFrame(frameId);
|
||||
UNIFORMS.beginFrame(frameId);
|
||||
TextRenderSystem.beginFrame();
|
||||
sodiumFrame = SODIUM.beginFrame();
|
||||
RenderFrameProfiler.beginFrame(frameId);
|
||||
frameOpen = true;
|
||||
submissionEnded = false;
|
||||
lifecycle = FrameLifecycle.RECORDING;
|
||||
} else {
|
||||
sodiumFrame = SODIUM.currentFrameContext();
|
||||
}
|
||||
|
||||
RenderPhase phase = currentContext != null ? currentContext.phase() : RenderPhase.NONE;
|
||||
RenderFrameContext ctx = new RenderFrameContext(
|
||||
frameId,
|
||||
tickProgress,
|
||||
frameDeltaTicks,
|
||||
fixedDeltaTicks,
|
||||
phase,
|
||||
CameraContext.capture(projection, modelView),
|
||||
ViewportContext.capture(),
|
||||
FramebufferContext.capture(),
|
||||
WorldSceneDepth::mainDepthView,
|
||||
VanillaWorldFogProvider.INSTANCE,
|
||||
LightPolicy.VANILLA,
|
||||
SODIUM.visibilityProvider(),
|
||||
sodiumFrame
|
||||
);
|
||||
currentContext = ctx;
|
||||
RenderState.applyContext(ctx);
|
||||
return ctx;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes only timing data for phases that do not own the world camera/projection
|
||||
* themselves, for example Fabric HUD callbacks.
|
||||
*/
|
||||
public static RenderFrameContext updateFrameTiming(float tickProgress,
|
||||
float frameDeltaTicks,
|
||||
float fixedDeltaTicks) {
|
||||
RenderFrameContext base = ensureFrameContext();
|
||||
currentContext = base.withTiming(tickProgress, frameDeltaTicks, fixedDeltaTicks);
|
||||
RenderState.applyContext(currentContext);
|
||||
return currentContext;
|
||||
}
|
||||
|
||||
public static RenderFrameContext ensureFrameContext() {
|
||||
if (currentContext != null) return currentContext;
|
||||
return beginFrame(RenderState.tickProgress, RenderState.frameDeltaTicks, RenderState.fixedDeltaTicks, new Matrix4f(), new Matrix4f(RenderSystem.getModelViewStack()));
|
||||
}
|
||||
|
||||
public static RenderPhaseScope phase(RenderPhase phase) {
|
||||
return phase(phase, null);
|
||||
}
|
||||
|
||||
public static RenderPhaseScope phase(RenderPhase phase, String label) {
|
||||
RenderFrameContext before = currentContext;
|
||||
RenderFrameContext base = ensureFrameContext();
|
||||
RenderPhase next = phase == null ? RenderPhase.NONE : phase;
|
||||
currentContext = base.withPhase(next);
|
||||
RenderState.applyContext(currentContext);
|
||||
return new RenderPhaseScope(next, before, RenderFrameProfiler.phase(next, label));
|
||||
}
|
||||
|
||||
public static void restorePhase(RenderFrameContext previous) {
|
||||
|
||||
currentContext = previous;
|
||||
if (currentContext != null) {
|
||||
RenderState.applyContext(currentContext);
|
||||
} else {
|
||||
RenderState.clearContextBackedState();
|
||||
}
|
||||
}
|
||||
|
||||
public static void endRenderSubmission() {
|
||||
if (!initialized || !frameOpen || submissionEnded) return;
|
||||
try {
|
||||
TextRenderSystem.flush();
|
||||
rhi.endRenderSubmission();
|
||||
lifecycle = FrameLifecycle.SUBMITTED;
|
||||
} finally {
|
||||
submissionEnded = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after the window surface has been presented. Rendering submission must already
|
||||
* be closed before GpuSurface.blitFromTexture(), otherwise late LoVisual draws miss the
|
||||
* presented frame and can leave stale main-target contents for the next frame.
|
||||
*/
|
||||
public static void onFramePresented() {
|
||||
if (!initialized) return;
|
||||
try {
|
||||
if (!submissionEnded) {
|
||||
// Fallback for unusual renderFrame paths. Normal visible frames close in
|
||||
// GpuSurfaceMixin#lovisual$beforeSurfaceBlit, before the surface copy.
|
||||
endRenderSubmission();
|
||||
}
|
||||
rhi.framePresented();
|
||||
UNIFORMS.onFramePresented();
|
||||
if (FrameStutterProfiler.isEnabled()) {
|
||||
FrameStutterProfiler.onFramePresented(rhiStatsSnapshot(), uniformStatsSnapshot(), resourceStatsSnapshot());
|
||||
}
|
||||
RenderFrameProfiler.endFrame(null, null);
|
||||
lifecycle = FrameLifecycle.PRESENTED;
|
||||
} finally {
|
||||
currentContext = null;
|
||||
frameOpen = false;
|
||||
submissionEnded = false;
|
||||
lifecycle = FrameLifecycle.IDLE;
|
||||
RenderState.clearContextBackedState();
|
||||
}
|
||||
}
|
||||
|
||||
public static RhiStatsSnapshot rhiStatsSnapshot() {
|
||||
return rhi().stats().snapshot();
|
||||
}
|
||||
|
||||
public static SodiumVisibilityStatsSnapshot sodiumVisibilityStatsSnapshot() {
|
||||
return SODIUM.visibilityStatsSnapshot();
|
||||
}
|
||||
|
||||
public static SodiumTerrainInteropStatsSnapshot sodiumTerrainStatsSnapshot() {
|
||||
return SODIUM.terrainInterop().statsSnapshot();
|
||||
}
|
||||
|
||||
public static IrisRuntimeSnapshot irisSnapshot() {
|
||||
return IrisRuntime.snapshot();
|
||||
}
|
||||
|
||||
public static ImmediatelyFastRuntimeSnapshot immediatelyFastSnapshot() {
|
||||
return ImmediatelyFastRuntime.snapshot();
|
||||
}
|
||||
|
||||
public static UniformAllocatorStatsSnapshot uniformStatsSnapshot() {
|
||||
return UNIFORMS.statsSnapshot();
|
||||
}
|
||||
|
||||
public static RenderResourceStatsSnapshot resourceStatsSnapshot() {
|
||||
return resources().statsSnapshot();
|
||||
}
|
||||
|
||||
public static WorldRenderStatsSnapshot worldRenderStatsSnapshot() {
|
||||
return Renderer3D.worldStatsSnapshot();
|
||||
}
|
||||
|
||||
public static TextCommandStatsSnapshot textStatsSnapshot() {
|
||||
return TextRenderSystem.statsSnapshot();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.core.system;
|
||||
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Matrix4fc;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
public enum LoVisualWorldMatrices {
|
||||
;
|
||||
private static final Matrix4f POSITION = new Matrix4f();
|
||||
private static final Matrix4f RENDER_PROJECTION = new Matrix4f();
|
||||
private static final Matrix4f FRUSTUM_PROJECTION = new Matrix4f();
|
||||
private static Vec3 CAMERA_POS;
|
||||
private static boolean valid;
|
||||
|
||||
public static void reset() {
|
||||
valid = false;
|
||||
}
|
||||
|
||||
public static void capture(Matrix4fc positionMatrix, Matrix4fc renderProjectionMatrix, Matrix4fc frustumProjectionMatrix) {
|
||||
capture(positionMatrix, renderProjectionMatrix, frustumProjectionMatrix, null);
|
||||
}
|
||||
|
||||
public static void capture(Matrix4fc positionMatrix, Matrix4fc renderProjectionMatrix, Matrix4fc frustumProjectionMatrix, Vec3 cameraPos) {
|
||||
if (positionMatrix == null || renderProjectionMatrix == null || frustumProjectionMatrix == null) {
|
||||
valid = false;
|
||||
CAMERA_POS = null;
|
||||
return;
|
||||
}
|
||||
|
||||
POSITION.set(positionMatrix);
|
||||
RENDER_PROJECTION.set(renderProjectionMatrix);
|
||||
FRUSTUM_PROJECTION.set(frustumProjectionMatrix);
|
||||
CAMERA_POS = cameraPos;
|
||||
valid = true;
|
||||
}
|
||||
|
||||
public static void capturePosition(Matrix4fc positionMatrix, Vec3 cameraPos) {
|
||||
if (!valid || positionMatrix == null) {
|
||||
return;
|
||||
}
|
||||
POSITION.set(positionMatrix);
|
||||
if (cameraPos != null) {
|
||||
CAMERA_POS = cameraPos;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isValid() {
|
||||
return valid;
|
||||
}
|
||||
|
||||
public static Matrix4f positionMatrix() {
|
||||
return valid ? new Matrix4f(POSITION) : null;
|
||||
}
|
||||
|
||||
public static Matrix4f renderProjectionMatrix() {
|
||||
return valid ? new Matrix4f(RENDER_PROJECTION) : null;
|
||||
}
|
||||
|
||||
public static Matrix4f frustumProjectionMatrix() {
|
||||
return valid ? new Matrix4f(FRUSTUM_PROJECTION) : null;
|
||||
}
|
||||
|
||||
public static Vec3 cameraPosition() {
|
||||
return valid ? CAMERA_POS : null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,336 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.debug;
|
||||
|
||||
import com.mojang.blaze3d.ProjectionType;
|
||||
import com.mojang.blaze3d.buffers.GpuBufferSlice;
|
||||
import com.mojang.blaze3d.pipeline.RenderTarget;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import net.minecraft.client.DeltaTracker;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphicsExtractor;
|
||||
import net.minecraft.client.renderer.Projection;
|
||||
import net.minecraft.client.renderer.ProjectionMatrixBuffer;
|
||||
import org.joml.Matrix4f;
|
||||
import dev.loki.lovisual.render.engine.RenderState;
|
||||
import dev.loki.lovisual.render.engine.core.system.LoVisualRenderSystem;
|
||||
import dev.loki.lovisual.render.engine.core.frame.RenderPhase;
|
||||
import dev.loki.lovisual.render.engine.core.frame.RenderPhaseScope;
|
||||
import dev.loki.lovisual.render.engine.renderer.MeshRenderer;
|
||||
import dev.loki.lovisual.render.engine.renderer.Renderer2D;
|
||||
import dev.loki.lovisual.util.logging.DebugLog;
|
||||
|
||||
/**
|
||||
* Render-thread 2D diagnostics.
|
||||
*
|
||||
* <p>There are two independent probes:</p>
|
||||
* <ul>
|
||||
* <li>GUI-state probe: writes vanilla {@link GuiGraphicsExtractor#fill} rectangles during HUD extraction.</li>
|
||||
* <li>Immediate probe: draws with {@link Renderer2D} after vanilla {@code GuiRenderer.render()}.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>This deliberately does not move production HUD rendering. It only tells which side is broken:
|
||||
* vanilla GUI/root-layer extraction or LoVisual immediate Renderer2D/RHI.</p>
|
||||
*/
|
||||
public enum RenderThread2DDebugRenderer {
|
||||
;
|
||||
|
||||
private static ProjectionMatrixBuffer debugProjection;
|
||||
private static final long LOG_INTERVAL_NS = 1_000_000_000L;
|
||||
|
||||
private static volatile long immediateFrame;
|
||||
private static volatile long guiStateFrame;
|
||||
private static volatile long lastImmediateNs;
|
||||
private static volatile long lastGuiStateNs;
|
||||
private static volatile int lastFramebufferWidth;
|
||||
private static volatile int lastFramebufferHeight;
|
||||
private static volatile int lastGuiWidth;
|
||||
private static volatile int lastGuiHeight;
|
||||
private static volatile int lastImmediateDraws;
|
||||
private static volatile int lastImmediateBatches;
|
||||
private static volatile int lastImmediateVertices;
|
||||
private static volatile int lastImmediateIndices;
|
||||
private static volatile int lastGuiStateRects;
|
||||
private static volatile String lastImmediateError = "";
|
||||
private static volatile String lastGuiStateError = "";
|
||||
|
||||
private static long lastImmediateLogNs;
|
||||
private static long lastGuiStateLogNs;
|
||||
private static boolean immediateFailureLogged;
|
||||
private static boolean guiStateFailureLogged;
|
||||
|
||||
private static ProjectionMatrixBuffer debugProjection() {
|
||||
ProjectionMatrixBuffer buffer = debugProjection;
|
||||
if (buffer == null) {
|
||||
buffer = new ProjectionMatrixBuffer("lovisual-debug-2d-render-thread-projection");
|
||||
debugProjection = buffer;
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public static void extractGuiStateProbe(GuiGraphicsExtractor ctx) {
|
||||
if (!DebugLog.isRenderThreadDebugEnabled()) return;
|
||||
if (ctx == null) {
|
||||
recordGuiStateFailure("GuiGraphicsExtractor null");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
int gw = Math.max(1, ctx.guiWidth());
|
||||
int gh = Math.max(1, ctx.guiHeight());
|
||||
int rects = drawGuiStateProbe(ctx, gw, gh);
|
||||
|
||||
guiStateFrame++;
|
||||
lastGuiStateNs = System.nanoTime();
|
||||
lastGuiWidth = gw;
|
||||
lastGuiHeight = gh;
|
||||
lastGuiStateRects = rects;
|
||||
lastGuiStateError = "";
|
||||
guiStateFailureLogged = false;
|
||||
|
||||
logGuiStateIfDue(gw, gh, rects);
|
||||
} catch (Throwable t) {
|
||||
recordGuiStateFailure(t.getClass().getSimpleName() + ": " + String.valueOf(t.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
public static void renderImmediateAfterGui(DeltaTracker tickCounter) {
|
||||
if (!DebugLog.isRenderThreadDebugEnabled()) return;
|
||||
|
||||
try {
|
||||
RenderSystem.assertOnRenderThread();
|
||||
} catch (Throwable t) {
|
||||
recordImmediateFailure("not on render thread: " + t.getClass().getSimpleName());
|
||||
return;
|
||||
}
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || mc.gameRenderer == null) {
|
||||
recordImmediateFailure("minecraft/gameRenderer null");
|
||||
return;
|
||||
}
|
||||
if (Renderer2D.COLOR == null) {
|
||||
recordImmediateFailure("Renderer2D.COLOR null");
|
||||
return;
|
||||
}
|
||||
|
||||
RenderTarget target = mc.gameRenderer.mainRenderTarget();
|
||||
if (target == null) {
|
||||
recordImmediateFailure("main render target null");
|
||||
return;
|
||||
}
|
||||
|
||||
int fbw = Math.max(1, target.width);
|
||||
int fbh = Math.max(1, target.height);
|
||||
|
||||
GpuBufferSlice previousProjection = RenderSystem.getProjectionMatrixBuffer();
|
||||
ProjectionType previousProjectionType = RenderSystem.getProjectionType();
|
||||
Matrix4f previousMeshProjection = MeshRenderer.projection();
|
||||
boolean previousRendering3D = RenderState.rendering3D;
|
||||
|
||||
var modelView = RenderSystem.getModelViewStack();
|
||||
boolean pushedModelView = false;
|
||||
try (RenderPhaseScope ignored = LoVisualRenderSystem.phase(RenderPhase.HUD_MAIN, "debug:2d_immediate_after_gui")) {
|
||||
modelView.pushMatrix();
|
||||
pushedModelView = true;
|
||||
modelView.identity();
|
||||
|
||||
Matrix4f projection = framebufferOrtho(fbw, fbh);
|
||||
RenderSystem.setProjectionMatrix(debugProjection().getBuffer(projection), ProjectionType.ORTHOGRAPHIC);
|
||||
MeshRenderer.setProjection(projection);
|
||||
RenderState.rendering3D = false;
|
||||
|
||||
Renderer2D.BatchStats stats = Renderer2D.getBatchStats();
|
||||
int beforeFrameDraws = stats.getFrameDraws();
|
||||
int beforeFrameBatches = stats.getFrameBatches();
|
||||
int beforeFrameVertices = stats.getFrameVertices();
|
||||
int beforeFrameIndices = stats.getFrameIndices();
|
||||
|
||||
drawImmediateProbe(fbw, fbh);
|
||||
|
||||
immediateFrame++;
|
||||
lastImmediateNs = System.nanoTime();
|
||||
lastFramebufferWidth = fbw;
|
||||
lastFramebufferHeight = fbh;
|
||||
lastImmediateDraws = Math.max(0, stats.getFrameDraws() - beforeFrameDraws);
|
||||
lastImmediateBatches = Math.max(0, stats.getFrameBatches() - beforeFrameBatches);
|
||||
lastImmediateVertices = Math.max(0, stats.getFrameVertices() - beforeFrameVertices);
|
||||
lastImmediateIndices = Math.max(0, stats.getFrameIndices() - beforeFrameIndices);
|
||||
lastImmediateError = "";
|
||||
immediateFailureLogged = false;
|
||||
|
||||
logImmediateIfDue(fbw, fbh, stats);
|
||||
} catch (Throwable t) {
|
||||
recordImmediateFailure(t.getClass().getSimpleName() + ": " + String.valueOf(t.getMessage()));
|
||||
} finally {
|
||||
if (pushedModelView) {
|
||||
modelView.popMatrix();
|
||||
}
|
||||
MeshRenderer.setProjection(previousMeshProjection);
|
||||
RenderState.rendering3D = previousRendering3D;
|
||||
if (previousProjection != null && previousProjectionType != null) {
|
||||
RenderSystem.setProjectionMatrix(previousProjection, previousProjectionType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Matrix4f framebufferOrtho(int width, int height) {
|
||||
Projection projection = new Projection();
|
||||
projection.setupOrtho(-1000.0f, 1000.0f, Math.max(1.0f, width), Math.max(1.0f, height), true);
|
||||
return projection.getMatrix(new Matrix4f());
|
||||
}
|
||||
|
||||
private static int drawGuiStateProbe(GuiGraphicsExtractor ctx, int gw, int gh) {
|
||||
int rects = 0;
|
||||
ctx.nextStratum();
|
||||
|
||||
// Vanilla GUI/root-layer probe. It uses scaled GUI coordinates and vanilla GUI render states.
|
||||
ctx.fill(2, 2, 16, 16, 0xFFFF3344);
|
||||
rects++;
|
||||
ctx.fill(Math.max(0, gw - 16), 2, gw - 2, 16, 0xFF44FF66);
|
||||
rects++;
|
||||
ctx.fill(2, Math.max(0, gh - 16), 16, gh - 2, 0xFF4488FF);
|
||||
rects++;
|
||||
ctx.fill(Math.max(0, gw - 16), Math.max(0, gh - 16), gw - 2, gh - 2, 0xFFFFFF44);
|
||||
rects++;
|
||||
|
||||
ctx.fill(8, 24, Math.min(gw - 8, 190), Math.min(gh - 8, 52), 0xCC111119);
|
||||
rects++;
|
||||
ctx.fill(12, 28, Math.min(gw - 12, 44), Math.min(gh - 12, 48), 0xFFFF3344);
|
||||
rects++;
|
||||
ctx.fill(48, 28, Math.min(gw - 12, 80), Math.min(gh - 12, 48), 0xFF44FF66);
|
||||
rects++;
|
||||
ctx.fill(84, 28, Math.min(gw - 12, 116), Math.min(gh - 12, 48), 0xFF4488FF);
|
||||
rects++;
|
||||
|
||||
return rects;
|
||||
}
|
||||
|
||||
private static void drawImmediateProbe(int fbw, int fbh) {
|
||||
Renderer2D renderer = Renderer2D.COLOR;
|
||||
double panelX = 8.0;
|
||||
double panelY = 72.0;
|
||||
double panelW = 292.0;
|
||||
double panelH = 92.0;
|
||||
double pulse = (immediateFrame % 120L) / 119.0;
|
||||
double markerX = panelX + 110.0 + pulse * 150.0;
|
||||
|
||||
renderer.begin();
|
||||
|
||||
// Framebuffer-space immediate Renderer2D/RHI probe. No text/glyph dependency.
|
||||
renderer.quad(0, 0, 12, 12, 0xFFFF3355);
|
||||
renderer.quad(fbw - 12.0, 0, 12, 12, 0xFF55FF66);
|
||||
renderer.quad(0, fbh - 12.0, 12, 12, 0xFF5599FF);
|
||||
renderer.quad(fbw - 12.0, fbh - 12.0, 12, 12, 0xFFFFFF55);
|
||||
|
||||
renderer.quad(panelX, panelY, panelW, panelH, 0xCC050509);
|
||||
renderer.boxLines(panelX, panelY, panelW, panelH, 0xFFFF33FF);
|
||||
renderer.line(panelX, panelY, panelX + panelW, panelY + panelH, 0x80FFFFFF);
|
||||
renderer.line(panelX + panelW, panelY, panelX, panelY + panelH, 0x80FFFFFF);
|
||||
|
||||
renderer.quad(panelX + 14.0, panelY + 14.0, 24.0, 24.0, 0xFFFF3355);
|
||||
renderer.quad(panelX + 44.0, panelY + 14.0, 24.0, 24.0, 0xFF55FF66);
|
||||
renderer.quad(panelX + 74.0, panelY + 14.0, 24.0, 24.0, 0xFF5599FF);
|
||||
renderer.quad(panelX + 14.0, panelY + 50.0, 244.0, 16.0, 0x66333333);
|
||||
renderer.boxLines(panelX + 14.0, panelY + 50.0, 244.0, 16.0, 0x99FFFFFF);
|
||||
renderer.quad(markerX, panelY + 51.5, 28.0, 13.0, 0xFFFFFF55);
|
||||
|
||||
renderer.boxLines(18.0, 18.0, Math.max(1.0, fbw - 36.0), Math.max(1.0, fbh - 36.0), 0x55FFFFFF);
|
||||
|
||||
renderer.render();
|
||||
}
|
||||
|
||||
private static void logGuiStateIfDue(int gw, int gh, int rects) {
|
||||
long now = System.nanoTime();
|
||||
if (now - lastGuiStateLogNs < LOG_INTERVAL_NS) return;
|
||||
lastGuiStateLogNs = now;
|
||||
DebugLog.renderThread(
|
||||
"2D debug gui_state frame=%d gui=%dx%d rects=%d err=%s",
|
||||
guiStateFrame,
|
||||
gw,
|
||||
gh,
|
||||
rects,
|
||||
lastGuiStateError == null || lastGuiStateError.isBlank() ? "none" : lastGuiStateError
|
||||
);
|
||||
}
|
||||
|
||||
private static void logImmediateIfDue(int fbw, int fbh, Renderer2D.BatchStats stats) {
|
||||
long now = System.nanoTime();
|
||||
if (now - lastImmediateLogNs < LOG_INTERVAL_NS) return;
|
||||
lastImmediateLogNs = now;
|
||||
DebugLog.renderThread(
|
||||
"2D debug immediate after_gui frame=%d fb=%dx%d drawDelta=%d/%d v=%d i=%d lastBatch=%d/%d reason=%s err=%s",
|
||||
immediateFrame,
|
||||
fbw,
|
||||
fbh,
|
||||
lastImmediateDraws,
|
||||
lastImmediateBatches,
|
||||
lastImmediateVertices,
|
||||
lastImmediateIndices,
|
||||
stats.getLastDraws(),
|
||||
stats.getLastOrder(),
|
||||
stats.getLastFlushReason(),
|
||||
stats.getLastError() == null || stats.getLastError().isBlank() ? "none" : stats.getLastError()
|
||||
);
|
||||
}
|
||||
|
||||
private static void recordImmediateFailure(String reason) {
|
||||
String safeReason = reason == null || reason.isBlank() ? "unknown" : reason;
|
||||
lastImmediateError = safeReason;
|
||||
if (!immediateFailureLogged) {
|
||||
immediateFailureLogged = true;
|
||||
DebugLog.renderThread("2D debug immediate skipped/failed: %s", safeReason);
|
||||
}
|
||||
}
|
||||
|
||||
private static void recordGuiStateFailure(String reason) {
|
||||
String safeReason = reason == null || reason.isBlank() ? "unknown" : reason;
|
||||
lastGuiStateError = safeReason;
|
||||
if (!guiStateFailureLogged) {
|
||||
guiStateFailureLogged = true;
|
||||
DebugLog.renderThread("2D debug gui_state skipped/failed: %s", safeReason);
|
||||
}
|
||||
}
|
||||
|
||||
public static Snapshot snapshot() {
|
||||
return new Snapshot(
|
||||
immediateFrame,
|
||||
guiStateFrame,
|
||||
lastImmediateNs,
|
||||
lastGuiStateNs,
|
||||
lastFramebufferWidth,
|
||||
lastFramebufferHeight,
|
||||
lastGuiWidth,
|
||||
lastGuiHeight,
|
||||
lastImmediateDraws,
|
||||
lastImmediateBatches,
|
||||
lastImmediateVertices,
|
||||
lastImmediateIndices,
|
||||
lastGuiStateRects,
|
||||
lastImmediateError,
|
||||
lastGuiStateError
|
||||
);
|
||||
}
|
||||
|
||||
public record Snapshot(long immediateFrame,
|
||||
long guiStateFrame,
|
||||
long lastImmediateNs,
|
||||
long lastGuiStateNs,
|
||||
int framebufferWidth,
|
||||
int framebufferHeight,
|
||||
int guiWidth,
|
||||
int guiHeight,
|
||||
int immediateDraws,
|
||||
int immediateBatches,
|
||||
int immediateVertices,
|
||||
int immediateIndices,
|
||||
int guiStateRects,
|
||||
String immediateError,
|
||||
String guiStateError) {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,234 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.depth;
|
||||
|
||||
import com.mojang.blaze3d.GpuFormat;
|
||||
import com.mojang.blaze3d.pipeline.RenderTarget;
|
||||
import com.mojang.blaze3d.pipeline.TextureTarget;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.mojang.blaze3d.textures.GpuTexture;
|
||||
import com.mojang.blaze3d.textures.GpuTextureView;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import dev.loki.lovisual.mixininterface.device.IMsaaTexture;
|
||||
import dev.loki.lovisual.render.engine.core.system.LoVisualRenderSystem;
|
||||
import dev.loki.lovisual.render.engine.msaa.MsaaWorldTarget;
|
||||
|
||||
/**
|
||||
* Captures the main framebuffer depth BEFORE translucent layers.
|
||||
* Used to render custom geometry above liquids while still depth-testing vs solids.
|
||||
*/
|
||||
public enum PreTranslucentDepth {
|
||||
;
|
||||
private static TextureTarget depthCopy;
|
||||
private static GpuTextureView msaaDepthView;
|
||||
private static GpuTexture msaaDepthCopy;
|
||||
private static GpuTextureView msaaDepthCopyView;
|
||||
private static int msaaSamples;
|
||||
private static int depthCopyW = -1;
|
||||
private static int depthCopyH = -1;
|
||||
private static int msaaDepthCopyW = -1;
|
||||
private static int msaaDepthCopyH = -1;
|
||||
private static boolean captured;
|
||||
private static boolean capturedMsaa;
|
||||
private static boolean requestedNextFrame;
|
||||
|
||||
/**
|
||||
* The full-screen depth copy in {@link #captureFrom} is expensive, and its capture point
|
||||
* (before the translucent groups in the main pass) runs BEFORE any PRE_DEPTH consumer renders
|
||||
* in the frame. Consumers ({@link #getDepthView()}, {@link #getDepthViewFor(GpuTextureView)})
|
||||
* therefore declare demand here; the next frame's capture consults it via {@link #takeRequested()}.
|
||||
* One-frame latency is invisible: until the copy exists, getters fall back to the live
|
||||
* framebuffer/WorldSceneDepth views.
|
||||
*/
|
||||
public static void requestForNextFrame() {
|
||||
requestedNextFrame = true;
|
||||
}
|
||||
|
||||
/** @return and clears the demand flag set by {@link #requestForNextFrame()}. */
|
||||
public static boolean takeRequested() {
|
||||
boolean requested = requestedNextFrame;
|
||||
requestedNextFrame = false;
|
||||
return requested;
|
||||
}
|
||||
|
||||
public static void captureFrom(RenderTarget source) {
|
||||
if (source == null) return;
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null) return;
|
||||
|
||||
int w = source.width;
|
||||
int h = source.height;
|
||||
if (w <= 0 || h <= 0) return;
|
||||
|
||||
captured = false;
|
||||
capturedMsaa = false;
|
||||
|
||||
boolean isMsaa = source.getDepthTexture() instanceof IMsaaTexture msaa && msaa.lovisual$isMsaa();
|
||||
if (isMsaa) {
|
||||
int samples = ((IMsaaTexture) source.getDepthTexture()).lovisual$getSamples();
|
||||
ensureMsaaDepthCopy(w, h, samples);
|
||||
if (msaaDepthCopy != null && source.getDepthTexture() != null) {
|
||||
try {
|
||||
RenderSystem.getDevice().createCommandEncoder().copyTextureToTexture(
|
||||
source.getDepthTexture(),
|
||||
msaaDepthCopy,
|
||||
0, 0, 0,
|
||||
0, 0,
|
||||
w, h
|
||||
);
|
||||
msaaDepthView = msaaDepthCopyView;
|
||||
capturedMsaa = true;
|
||||
if (LoVisualRenderSystem.rhi().msaa().eagerPreTranslucentDepthResolve()) {
|
||||
captured = resolveMsaaDepthCopy();
|
||||
}
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
// A resize can race this capture point on some setups: framebuffer.textureHeight
|
||||
// may already report the new window size while the backing MSAA depth texture
|
||||
// is still the previous one. Do not crash the render thread; skip this frame and
|
||||
// force a fresh allocation on the next capture.
|
||||
releaseMsaaDepthCopy();
|
||||
capturedMsaa = false;
|
||||
msaaDepthView = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
msaaDepthView = null;
|
||||
capturedMsaa = false;
|
||||
depthCopy = LoVisualRenderSystem.resources().persistentFramebuffer(
|
||||
"lovisual-pretrans-depth", w, h, true, "PreTranslucentDepth");
|
||||
depthCopyW = w;
|
||||
depthCopyH = h;
|
||||
depthCopy.copyDepthFrom(source);
|
||||
captured = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static void capture() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null) return;
|
||||
RenderTarget fb = mc.gameRenderer.mainRenderTarget();
|
||||
if (fb == null) return;
|
||||
captureFrom(fb);
|
||||
}
|
||||
|
||||
public static GpuTextureView getDepthView() {
|
||||
requestForNextFrame();
|
||||
if (MsaaWorldTarget.isActive() && capturedMsaa && msaaDepthView != null) {
|
||||
return msaaDepthView;
|
||||
}
|
||||
if (captured && depthCopy != null) {
|
||||
return depthCopy.getDepthTextureView();
|
||||
}
|
||||
if (capturedMsaa && msaaDepthView != null && resolveMsaaDepthCopy()) {
|
||||
return depthCopy.getDepthTextureView();
|
||||
}
|
||||
if (capturedMsaa
|
||||
&& msaaDepthView != null
|
||||
&& !LoVisualRenderSystem.rhi().msaa().supportsDepthSnapshotResolve()) {
|
||||
return msaaDepthView;
|
||||
}
|
||||
return WorldSceneDepth.mainDepthView();
|
||||
}
|
||||
|
||||
public static GpuTextureView getDepthViewFor(GpuTextureView colorAttachment) {
|
||||
requestForNextFrame();
|
||||
int requiredSamples = samples(colorAttachment);
|
||||
if (requiredSamples > 1
|
||||
&& capturedMsaa
|
||||
&& msaaDepthView != null
|
||||
&& samples(msaaDepthView) == requiredSamples) {
|
||||
return msaaDepthView;
|
||||
}
|
||||
if (requiredSamples == 1 && captured && depthCopy != null) {
|
||||
return depthCopy.getDepthTextureView();
|
||||
}
|
||||
if (requiredSamples == 1 && capturedMsaa && msaaDepthView != null && resolveMsaaDepthCopy()) {
|
||||
return depthCopy.getDepthTextureView();
|
||||
}
|
||||
GpuTextureView fallback = WorldSceneDepth.mainDepthView();
|
||||
return samples(fallback) == requiredSamples ? fallback : null;
|
||||
}
|
||||
|
||||
public static void reset() {
|
||||
captured = false;
|
||||
capturedMsaa = false;
|
||||
msaaDepthView = null;
|
||||
}
|
||||
|
||||
private static void ensureMsaaDepthCopy(int w, int h, int samples) {
|
||||
if (!LoVisualRenderSystem.rhi().msaa().supported()) return;
|
||||
if (msaaDepthCopy == null || w != msaaDepthCopyW || h != msaaDepthCopyH || msaaSamples != samples) {
|
||||
releaseMsaaDepthCopy();
|
||||
int usage = GpuTexture.USAGE_COPY_DST | GpuTexture.USAGE_COPY_SRC | GpuTexture.USAGE_RENDER_ATTACHMENT;
|
||||
msaaDepthCopy = LoVisualRenderSystem.rhi().msaa().createTexture(
|
||||
"lovisual-pretrans-depth-msaa",
|
||||
usage,
|
||||
GpuFormat.D32_FLOAT,
|
||||
w,
|
||||
h,
|
||||
samples
|
||||
);
|
||||
msaaDepthCopyView = RenderSystem.getDevice().createTextureView(msaaDepthCopy);
|
||||
msaaSamples = samples;
|
||||
msaaDepthCopyW = w;
|
||||
msaaDepthCopyH = h;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ensureResolvedDepthCopy(int w, int h) {
|
||||
depthCopy = LoVisualRenderSystem.resources().persistentFramebuffer(
|
||||
"lovisual-pretrans-depth",
|
||||
w,
|
||||
h,
|
||||
true,
|
||||
"PreTranslucentDepth"
|
||||
);
|
||||
depthCopyW = w;
|
||||
depthCopyH = h;
|
||||
}
|
||||
|
||||
private static boolean resolveMsaaDepthCopy() {
|
||||
if (captured) return true;
|
||||
if (!capturedMsaa || msaaDepthCopyView == null || msaaDepthCopyW <= 0 || msaaDepthCopyH <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (!LoVisualRenderSystem.rhi().msaa().supportsDepthSnapshotResolve()) {
|
||||
return false;
|
||||
}
|
||||
ensureResolvedDepthCopy(msaaDepthCopyW, msaaDepthCopyH);
|
||||
captured = depthCopy != null
|
||||
&& depthCopy.getDepthTextureView() != null
|
||||
&& LoVisualRenderSystem.rhi().msaa().resolveDepthSnapshot(
|
||||
msaaDepthCopyView,
|
||||
depthCopy.getDepthTextureView()
|
||||
);
|
||||
return captured;
|
||||
}
|
||||
|
||||
private static int samples(GpuTextureView view) {
|
||||
if (view == null || view.texture() == null) return 1;
|
||||
return view.texture() instanceof IMsaaTexture msaa && msaa.lovisual$isMsaa()
|
||||
? msaa.lovisual$getSamples()
|
||||
: 1;
|
||||
}
|
||||
|
||||
private static void releaseMsaaDepthCopy() {
|
||||
if (msaaDepthCopyView != null) {
|
||||
msaaDepthCopyView.close();
|
||||
msaaDepthCopyView = null;
|
||||
}
|
||||
if (msaaDepthCopy != null) {
|
||||
LoVisualRenderSystem.resources().retireTexture(msaaDepthCopy);
|
||||
msaaDepthCopy = null;
|
||||
}
|
||||
msaaDepthView = null;
|
||||
msaaSamples = 0;
|
||||
msaaDepthCopyW = -1;
|
||||
msaaDepthCopyH = -1;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,224 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.depth;
|
||||
|
||||
import com.mojang.blaze3d.pipeline.RenderTarget;
|
||||
import com.mojang.blaze3d.pipeline.TextureTarget;
|
||||
import com.mojang.blaze3d.textures.GpuTextureView;
|
||||
import dev.loki.lovisual.mixininterface.device.IMsaaTexture;
|
||||
import dev.loki.lovisual.render.engine.core.system.LoVisualRenderSystem;
|
||||
|
||||
/**
|
||||
* Persistent copy of the depth buffers that vanilla composes through its world transparency
|
||||
* frame graph. The copy is taken by a vanilla FramePass after transparency composition has
|
||||
* been scheduled, so DoF samples the same scene-stage depth that contributed to final world
|
||||
* color instead of relying on a single late main depth attachment.
|
||||
*/
|
||||
public enum WorldSceneDepth {
|
||||
;
|
||||
private static final Stage MAIN = new Stage("main", "lovisual-world-scene-depth-main");
|
||||
private static final Stage TRANSLUCENT = new Stage("translucent", "lovisual-world-scene-depth-translucent");
|
||||
private static final Stage ITEM_ENTITY = new Stage("item_entity", "lovisual-world-scene-depth-item-entity");
|
||||
private static final Stage PARTICLES = new Stage("particles", "lovisual-world-scene-depth-particles");
|
||||
private static final Stage WEATHER = new Stage("weather", "lovisual-world-scene-depth-weather");
|
||||
private static final Stage CLOUDS = new Stage("clouds", "lovisual-world-scene-depth-clouds");
|
||||
|
||||
private static int width = -1;
|
||||
private static int height = -1;
|
||||
private static long frameSerial;
|
||||
private static boolean valid;
|
||||
|
||||
public static void beginFrame(int w, int h) {
|
||||
width = Math.max(1, w);
|
||||
height = Math.max(1, h);
|
||||
frameSerial++;
|
||||
valid = false;
|
||||
MAIN.reset();
|
||||
TRANSLUCENT.reset();
|
||||
ITEM_ENTITY.reset();
|
||||
PARTICLES.reset();
|
||||
WEATHER.reset();
|
||||
CLOUDS.reset();
|
||||
}
|
||||
|
||||
public static void captureMain(RenderTarget source) {
|
||||
valid |= MAIN.capture(source, width, height);
|
||||
}
|
||||
|
||||
/** Captures the final PRE_HAND main depth, resolving an MSAA source when necessary. */
|
||||
public static boolean captureResolvedMain(RenderTarget source) {
|
||||
if (source == null) {
|
||||
return false;
|
||||
}
|
||||
int w = Math.max(1, source.width);
|
||||
int h = Math.max(1, source.height);
|
||||
if (width != w || height != h) {
|
||||
width = w;
|
||||
height = h;
|
||||
}
|
||||
boolean captured = MAIN.capture(source, width, height);
|
||||
valid |= captured;
|
||||
return captured;
|
||||
}
|
||||
|
||||
public static void captureTranslucent(RenderTarget source) {
|
||||
valid |= TRANSLUCENT.capture(source, width, height);
|
||||
}
|
||||
|
||||
public static void captureItemEntity(RenderTarget source) {
|
||||
valid |= ITEM_ENTITY.capture(source, width, height);
|
||||
}
|
||||
|
||||
public static void captureParticles(RenderTarget source) {
|
||||
valid |= PARTICLES.capture(source, width, height);
|
||||
}
|
||||
|
||||
public static void captureWeather(RenderTarget source) {
|
||||
valid |= WEATHER.capture(source, width, height);
|
||||
}
|
||||
|
||||
public static void captureClouds(RenderTarget source) {
|
||||
valid |= CLOUDS.capture(source, width, height);
|
||||
}
|
||||
|
||||
public static boolean isValid() {
|
||||
return valid && (hasMain() || hasTranslucent() || hasItemEntity() || hasParticles() || hasWeather() || hasClouds());
|
||||
}
|
||||
|
||||
public static long frameSerial() {
|
||||
return frameSerial;
|
||||
}
|
||||
|
||||
public static int width() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public static int height() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public static boolean hasMain() {
|
||||
return MAIN.hasDepth();
|
||||
}
|
||||
|
||||
public static boolean hasTranslucent() {
|
||||
return TRANSLUCENT.hasDepth();
|
||||
}
|
||||
|
||||
public static boolean hasItemEntity() {
|
||||
return ITEM_ENTITY.hasDepth();
|
||||
}
|
||||
|
||||
public static boolean hasParticles() {
|
||||
return PARTICLES.hasDepth();
|
||||
}
|
||||
|
||||
public static boolean hasWeather() {
|
||||
return WEATHER.hasDepth();
|
||||
}
|
||||
|
||||
public static boolean hasClouds() {
|
||||
return CLOUDS.hasDepth();
|
||||
}
|
||||
|
||||
public static GpuTextureView mainDepthView() {
|
||||
return MAIN.depthView();
|
||||
}
|
||||
|
||||
public static GpuTextureView translucentDepthView() {
|
||||
return TRANSLUCENT.depthView();
|
||||
}
|
||||
|
||||
public static GpuTextureView itemEntityDepthView() {
|
||||
return ITEM_ENTITY.depthView();
|
||||
}
|
||||
|
||||
public static GpuTextureView particlesDepthView() {
|
||||
return PARTICLES.depthView();
|
||||
}
|
||||
|
||||
public static GpuTextureView weatherDepthView() {
|
||||
return WEATHER.depthView();
|
||||
}
|
||||
|
||||
public static GpuTextureView cloudsDepthView() {
|
||||
return CLOUDS.depthView();
|
||||
}
|
||||
|
||||
public static void reset() {
|
||||
valid = false;
|
||||
MAIN.reset();
|
||||
TRANSLUCENT.reset();
|
||||
ITEM_ENTITY.reset();
|
||||
PARTICLES.reset();
|
||||
WEATHER.reset();
|
||||
CLOUDS.reset();
|
||||
}
|
||||
|
||||
private static final class Stage {
|
||||
private final String stageName;
|
||||
private final String framebufferName;
|
||||
private TextureTarget depthCopy;
|
||||
private boolean captured;
|
||||
|
||||
private Stage(String stageName, String framebufferName) {
|
||||
this.stageName = stageName;
|
||||
this.framebufferName = framebufferName;
|
||||
}
|
||||
|
||||
private boolean capture(RenderTarget source, int w, int h) {
|
||||
captured = false;
|
||||
if (source == null || source.getDepthTexture() == null || w <= 0 || h <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
depthCopy = LoVisualRenderSystem.resources().persistentFramebuffer(
|
||||
framebufferName,
|
||||
w,
|
||||
h,
|
||||
true,
|
||||
"WorldSceneDepth:" + stageName
|
||||
);
|
||||
if (depthCopy == null || depthCopy.getDepthTexture() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean msaa = source.getDepthTexture() instanceof IMsaaTexture texture && texture.lovisual$isMsaa();
|
||||
if (msaa) {
|
||||
if (LoVisualRenderSystem.rhi().msaa().supportsDepthSnapshotResolve()) {
|
||||
captured = LoVisualRenderSystem.rhi().msaa().resolveDepthSnapshot(
|
||||
source.getDepthTextureView(),
|
||||
depthCopy.getDepthTextureView()
|
||||
);
|
||||
} else {
|
||||
captured = LoVisualRenderSystem.rhi().msaa().resolve(source, depthCopy, false, true);
|
||||
}
|
||||
} else {
|
||||
depthCopy.copyDepthFrom(source);
|
||||
captured = true;
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
captured = false;
|
||||
}
|
||||
return captured;
|
||||
}
|
||||
|
||||
private boolean hasDepth() {
|
||||
return captured && depthCopy != null && depthCopy.getDepthTextureView() != null;
|
||||
}
|
||||
|
||||
private GpuTextureView depthView() {
|
||||
return hasDepth() ? depthCopy.getDepthTextureView() : null;
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
captured = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.framegraph;
|
||||
|
||||
import dev.loki.lovisual.render.engine.core.system.LoVisualRenderSystem;
|
||||
import dev.loki.lovisual.render.engine.core.frame.RenderFrameContext;
|
||||
import dev.loki.lovisual.render.engine.core.frame.RenderPhase;
|
||||
import dev.loki.lovisual.render.engine.core.frame.RenderPhaseScope;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class LoVisualFrameGraph {
|
||||
private final PassScheduler scheduler = new PassScheduler();
|
||||
|
||||
public void add(RenderPhase phase, Consumer<RenderFrameContext> handler) {
|
||||
add(phase, phase == null ? "unnamed" : phase.name().toLowerCase(), handler);
|
||||
}
|
||||
|
||||
public void add(RenderPhase phase, String label, Consumer<RenderFrameContext> handler) {
|
||||
if (handler == null) return;
|
||||
scheduler.add(new RenderPassNode(phase, label, handler));
|
||||
}
|
||||
|
||||
public void execute(RenderPhase phase, RenderFrameContext baseContext) {
|
||||
String label = "framegraph:" + (phase == null ? "none" : phase.name().toLowerCase());
|
||||
try (RenderPhaseScope ignored = LoVisualRenderSystem.phase(phase, label)) {
|
||||
RenderFrameContext ctx = LoVisualRenderSystem.currentContext();
|
||||
if (ctx == null && baseContext != null) ctx = baseContext.withPhase(phase);
|
||||
if (ctx == null) return;
|
||||
for (RenderPassNode node : scheduler.nodes(phase)) {
|
||||
try (RenderPhaseScope nodeScope = LoVisualRenderSystem.phase(phase, "pass:" + node.label())) {
|
||||
node.execute(LoVisualRenderSystem.currentContext());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
scheduler.clear();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.framegraph;
|
||||
|
||||
import dev.loki.lovisual.render.engine.core.frame.RenderPhase;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
|
||||
public final class PassScheduler {
|
||||
private final EnumMap<RenderPhase, List<RenderPassNode>> nodes = new EnumMap<>(RenderPhase.class);
|
||||
|
||||
public void add(RenderPassNode node) {
|
||||
if (node == null) return;
|
||||
nodes.computeIfAbsent(node.phase(), ignored -> new ArrayList<>()).add(node);
|
||||
}
|
||||
|
||||
public List<RenderPassNode> nodes(RenderPhase phase) {
|
||||
List<RenderPassNode> list = nodes.get(phase);
|
||||
if (list == null || list.isEmpty()) return List.of();
|
||||
return List.copyOf(list);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
nodes.clear();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.framegraph;
|
||||
|
||||
import dev.loki.lovisual.render.engine.core.frame.RenderFrameContext;
|
||||
import dev.loki.lovisual.render.engine.core.frame.RenderPhase;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* A named frame-graph node bound to a LoVisual render phase.
|
||||
*/
|
||||
public final class RenderPassNode {
|
||||
private final RenderPhase phase;
|
||||
private final String label;
|
||||
private final Consumer<RenderFrameContext> renderer;
|
||||
|
||||
public RenderPassNode(RenderPhase phase, String label, Consumer<RenderFrameContext> renderer) {
|
||||
this.phase = phase == null ? RenderPhase.NONE : phase;
|
||||
this.label = label == null || label.isBlank() ? this.phase.name().toLowerCase() : label;
|
||||
this.renderer = Objects.requireNonNull(renderer, "renderer");
|
||||
}
|
||||
|
||||
public RenderPhase phase() {
|
||||
return phase;
|
||||
}
|
||||
|
||||
public String label() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public void execute(RenderFrameContext context) {
|
||||
renderer.accept(context);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.guard;
|
||||
import dev.loki.lovisual.render.engine.rhi.backend.SodiumGlBackend;
|
||||
|
||||
/**
|
||||
* Old rendering paths that are no longer allowed as production behavior after the RHI migration.
|
||||
*/
|
||||
public enum LegacyRenderPath {
|
||||
IMMEDIATE_MESH_UPLOAD("MeshBuilder immediate GPU upload"),
|
||||
LEGACY_FULLSCREEN_MESH("FullScreenRenderer.mesh compatibility path"),
|
||||
LEGACY_MESH_RENDERER("MeshRenderer legacy facade"),
|
||||
DIRECT_GL_OUTSIDE_BACKEND("direct GL call outside SodiumGlBackend boundary"),
|
||||
DIRECT_SODIUM_OUTSIDE_BRIDGE("direct Sodium call outside SodiumRenderBridge/backend boundary"),
|
||||
PIPELINE_IDENTITY_POLICY("pipeline policy inferred from RenderPipeline object identity"),
|
||||
LOCAL_FRAMEBUFFER_POOL("subsystem-local framebuffer/texture pool");
|
||||
|
||||
private final String description;
|
||||
|
||||
LegacyRenderPath(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String description() {
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.guard;
|
||||
|
||||
import dev.loki.lovisual.render.engine.core.system.LoVisualRenderSystem;
|
||||
import dev.loki.lovisual.render.engine.rhi.backend.SodiumGlBackend;
|
||||
|
||||
/**
|
||||
* Final architecture lock for the LoVisual RHI migration.
|
||||
* <p>
|
||||
* This class is intentionally small and dependency-light: it is used from low-level legacy facades to make sure old
|
||||
* render paths cannot silently become production paths again. A path can only be enabled through an explicit system
|
||||
* property, and every use is counted in RHI stats.
|
||||
*/
|
||||
public enum RenderArchitectureGuard {
|
||||
;
|
||||
public static final boolean DEBUG = Boolean.getBoolean("lovisual.render.debug");
|
||||
public static final boolean STRICT = !Boolean.getBoolean("lovisual.render.allowLegacyPaths");
|
||||
public static final boolean ALLOW_LEGACY_MESH_RENDERER = Boolean.getBoolean("lovisual.render.allowLegacyMeshRenderer");
|
||||
public static final boolean ALLOW_LEGACY_FULLSCREEN_MESH = Boolean.getBoolean("lovisual.render.allowLegacyFullscreenMesh");
|
||||
public static final boolean ALLOW_IMMEDIATE_UPLOAD = Boolean.getBoolean("lovisual.rhi.allowImmediateFallback")
|
||||
|| Boolean.getBoolean("lovisual.render.allowImmediateUpload");
|
||||
|
||||
public static void requireAllowed(LegacyRenderPath path, String owner) {
|
||||
boolean allowed = switch (path) {
|
||||
case IMMEDIATE_MESH_UPLOAD -> ALLOW_IMMEDIATE_UPLOAD;
|
||||
case LEGACY_FULLSCREEN_MESH -> ALLOW_LEGACY_FULLSCREEN_MESH;
|
||||
case LEGACY_MESH_RENDERER -> ALLOW_LEGACY_MESH_RENDERER;
|
||||
default -> !STRICT;
|
||||
};
|
||||
|
||||
record(path);
|
||||
if (allowed) return;
|
||||
|
||||
throw new IllegalStateException("Legacy render path is locked: " + path.description()
|
||||
+ " owner=" + owner
|
||||
+ ". Use LoVisualRHI/SodiumGlBackend path instead. "
|
||||
+ "Temporary override: -D" + overrideProperty(path) + "=true");
|
||||
}
|
||||
|
||||
public static void record(LegacyRenderPath path) {
|
||||
try {
|
||||
LoVisualRenderSystem.rhi().stats().legacyPath(path);
|
||||
} catch (Throwable ignored) {
|
||||
// Guard must be safe during bootstrap/static initialization.
|
||||
}
|
||||
}
|
||||
|
||||
private static String overrideProperty(LegacyRenderPath path) {
|
||||
return switch (path) {
|
||||
case IMMEDIATE_MESH_UPLOAD -> "lovisual.rhi.allowImmediateFallback";
|
||||
case LEGACY_FULLSCREEN_MESH -> "lovisual.render.allowLegacyFullscreenMesh";
|
||||
case LEGACY_MESH_RENDERER -> "lovisual.render.allowLegacyMeshRenderer";
|
||||
default -> "lovisual.render.allowLegacyPaths";
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.guard.boundary;
|
||||
|
||||
/**
|
||||
* Central list of architecture boundary package rules.
|
||||
* <p>
|
||||
* This is used by debug audits and as documentation in code. The rules are deliberately string-based so they do not
|
||||
* introduce hard dependencies from high-level renderer code to GL/Sodium classes.
|
||||
*/
|
||||
public enum RenderBoundary {
|
||||
;
|
||||
public static final String RHI_BACKEND_PACKAGE = "dev.loki.lovisual.render.engine.rhi";
|
||||
public static final String SODIUM_GL_BACKEND_PACKAGE = "dev.loki.lovisual.render.engine.rhi";
|
||||
public static final String SODIUM_BRIDGE_PACKAGE = "dev.loki.lovisual.render.sodium";
|
||||
public static final String PROFILER_PACKAGE = "dev.loki.lovisual.render.engine.profiler";
|
||||
public static final String LEGACY_MSAA_PACKAGE = "dev.loki.lovisual.render.engine.msaa";
|
||||
|
||||
public static boolean mayUseGl(String className) {
|
||||
return className.startsWith(SODIUM_GL_BACKEND_PACKAGE)
|
||||
|| className.startsWith(PROFILER_PACKAGE)
|
||||
|| className.startsWith(LEGACY_MSAA_PACKAGE);
|
||||
}
|
||||
|
||||
public static boolean mayUseSodiumInternals(String className) {
|
||||
return className.startsWith(SODIUM_BRIDGE_PACKAGE)
|
||||
|| className.startsWith("dev.loki.lovisual.render.sodium.terrain")
|
||||
|| className.startsWith("dev.loki.lovisual.render.engine.visuals.lighting")
|
||||
|| className.startsWith("dev.loki.lovisual.render.helpers");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.guard.boundary;
|
||||
import dev.loki.lovisual.render.engine.guard.RenderArchitectureGuard;
|
||||
|
||||
|
||||
import dev.loki.lovisual.util.logging.DebugLog;
|
||||
|
||||
import java.util.List;
|
||||
import dev.loki.lovisual.render.engine.rhi.pipeline.pipeline.RenderPipelineSpec;
|
||||
|
||||
/**
|
||||
* Lightweight runtime boundary audit. It does not scan source files; it validates explicitly registered legacy
|
||||
* exceptions and prints the architectural rules once during bootstrap in debug mode.
|
||||
*/
|
||||
public enum RenderBoundaryAudit {
|
||||
;
|
||||
private static final List<String> LOCKED_RULES = List.of(
|
||||
"Renderer3D/Renderer2D/Text/PostProcess must submit through LoVisualRHI or command layers",
|
||||
"Direct GL is backend/profiler/MSAA-legacy only",
|
||||
"Direct Sodium runtime access is SodiumRenderBridge/terrain interop only",
|
||||
"Immediate mesh upload is emergency fallback only",
|
||||
"Individual uniform flipFrame calls are forbidden",
|
||||
"Fullscreen MeshBuilder quad is forbidden as production path",
|
||||
"Pipeline policy must come from RenderPipelineSpec metadata"
|
||||
);
|
||||
private static boolean ran;
|
||||
|
||||
public static void runOnce() {
|
||||
if (ran) return;
|
||||
ran = true;
|
||||
if (!RenderArchitectureGuard.DEBUG) return;
|
||||
DebugLog.renderThread("[LoVisualRHI] render architecture boundary locked:");
|
||||
for (String rule : LOCKED_RULES) {
|
||||
DebugLog.renderThread("[LoVisualRHI] - " + rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.guard.boundary;
|
||||
import dev.loki.lovisual.render.engine.guard.LegacyRenderPath;
|
||||
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Marks a deliberate exception to the post-RHI boundary rules.
|
||||
* <p>
|
||||
* Every usage should be temporary or isolated. New renderer features should not add this annotation unless the code is
|
||||
* a backend/profiler/interop implementation.
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR})
|
||||
public @interface RenderBoundaryExempt {
|
||||
public LegacyRenderPath value();
|
||||
|
||||
public String reason();
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.light;
|
||||
|
||||
import net.minecraft.util.Mth;
|
||||
|
||||
public enum LightmapState {
|
||||
;
|
||||
private static volatile float baseAmbient = 0.0f;
|
||||
private static volatile float ambient = 0.0f;
|
||||
|
||||
public static void setAmbient(float baseValue, float value) {
|
||||
baseAmbient = Mth.clamp(baseValue, 0.0f, 1.0f);
|
||||
ambient = Mth.clamp(value, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
public static float getAmbient() {
|
||||
return ambient;
|
||||
}
|
||||
|
||||
public static float getBaseAmbient() {
|
||||
return baseAmbient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns override ambient if raised above base, otherwise -1.
|
||||
*/
|
||||
public static float getOverrideAmbient() {
|
||||
return ambient > baseAmbient + 1.0e-4f ? ambient : -1.0f;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.math;
|
||||
|
||||
import net.minecraft.util.Mth;
|
||||
|
||||
public enum ColorMath {
|
||||
;
|
||||
|
||||
public static int colorWithAlpha(int rgb, int alpha) {
|
||||
int a = clamp255(alpha);
|
||||
return (a << 24) | (rgb & 0x00FFFFFF);
|
||||
}
|
||||
|
||||
public static int mixRgb(int rgbA, int rgbB, float t) {
|
||||
float clamped = Mth.clamp(t, 0f, 1f);
|
||||
int ar = (rgbA >>> 16) & 0xFF;
|
||||
int ag = (rgbA >>> 8) & 0xFF;
|
||||
int ab = rgbA & 0xFF;
|
||||
int br = (rgbB >>> 16) & 0xFF;
|
||||
int bg = (rgbB >>> 8) & 0xFF;
|
||||
int bb = rgbB & 0xFF;
|
||||
|
||||
int r = Math.round(ar + (br - ar) * clamped);
|
||||
int g = Math.round(ag + (bg - ag) * clamped);
|
||||
int b = Math.round(ab + (bb - ab) * clamped);
|
||||
return (clamp255(r) << 16) | (clamp255(g) << 8) | clamp255(b);
|
||||
}
|
||||
|
||||
public static int scaleAlpha(int argb, float alpha) {
|
||||
int a = (argb >>> 24) & 0xFF;
|
||||
int na = Math.round(a * Mth.clamp(alpha, 0f, 1f));
|
||||
return (argb & 0x00FFFFFF) | ((na & 0xFF) << 24);
|
||||
}
|
||||
|
||||
public static int premultiplyAlpha(int rgb, int alpha) {
|
||||
int a = clamp255(alpha);
|
||||
int r = (rgb >>> 16) & 0xFF;
|
||||
int g = (rgb >>> 8) & 0xFF;
|
||||
int b = rgb & 0xFF;
|
||||
r = (r * a + 127) / 255;
|
||||
g = (g * a + 127) / 255;
|
||||
b = (b * a + 127) / 255;
|
||||
return (a << 24) | (r << 16) | (g << 8) | b;
|
||||
}
|
||||
|
||||
public static int premultiplyArgb(int argb) {
|
||||
return premultiplyAlpha(argb & 0x00FFFFFF, (argb >>> 24) & 0xFF);
|
||||
}
|
||||
|
||||
private static int clamp255(int v) {
|
||||
return Math.min(255, Math.max(0, v));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.math;
|
||||
|
||||
public enum HudScale {
|
||||
;
|
||||
public static final float BASE_WIDTH = 1920f;
|
||||
public static final float BASE_HEIGHT = 1080f;
|
||||
public static final float FIXED_HUD_FONT_SIZE = 20f;
|
||||
|
||||
public static float scale(int screenW, int screenH) {
|
||||
if (screenW <= 0 || screenH <= 0) return 1f;
|
||||
float sx = screenW / BASE_WIDTH;
|
||||
float sy = screenH / BASE_HEIGHT;
|
||||
return Math.min(sx, sy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logical (virtual) width in UI units for given framebuffer size.
|
||||
*/
|
||||
public static float virtualWidth(int screenW, int screenH) {
|
||||
float scale = scale(screenW, screenH);
|
||||
if (scale <= 0f) return screenW;
|
||||
return screenW / scale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logical (virtual) height in UI units for given framebuffer size.
|
||||
*/
|
||||
public static float virtualHeight(int screenW, int screenH) {
|
||||
float scale = scale(screenW, screenH);
|
||||
if (scale <= 0f) return screenH;
|
||||
return screenH / scale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert framebuffer-space value to logical UI units.
|
||||
*/
|
||||
public static float toVirtual(float value, float scale) {
|
||||
if (scale <= 0f) return value;
|
||||
return value / scale;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert logical UI units to framebuffer-space value.
|
||||
*/
|
||||
public static float toFramebuffer(float value, float scale) {
|
||||
if (scale <= 0f) return value;
|
||||
return value * scale;
|
||||
}
|
||||
|
||||
public static float s(float value, float scale) {
|
||||
return value * scale;
|
||||
}
|
||||
|
||||
public static float hudFontSize() {
|
||||
return FIXED_HUD_FONT_SIZE;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.math;
|
||||
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import dev.loki.lovisual.render.engine.RenderState;
|
||||
|
||||
public enum RenderMath {
|
||||
;
|
||||
|
||||
public static double lerp(double a, double b, double t) {
|
||||
return a + (b - a) * t;
|
||||
}
|
||||
|
||||
public static float lerp(float a, float b, float t) {
|
||||
return a + (b - a) * t;
|
||||
}
|
||||
|
||||
public static Vec3 getLerpedPos(Entity e, float tickDelta) {
|
||||
return e.getPosition(tickDelta);
|
||||
}
|
||||
|
||||
public static Vec3 getLerpedPosRelative(Entity e, float tickDelta) {
|
||||
Vec3 p = getLerpedPos(e, tickDelta);
|
||||
return p.subtract(RenderState.cameraPos);
|
||||
}
|
||||
|
||||
public static float smoothLerp(float current, float target, float dt, float speed) {
|
||||
if (current < 0.0f) return target;
|
||||
float t = 1.0f - (float) Math.exp(-speed * Math.max(0.0f, dt));
|
||||
return Mth.lerp(t, current, target);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.msaa;
|
||||
|
||||
import com.mojang.blaze3d.GpuFormat;
|
||||
import com.mojang.blaze3d.pipeline.RenderTarget;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.mojang.blaze3d.textures.GpuTexture;
|
||||
import dev.loki.lovisual.mixininterface.device.IMsaaTexture;
|
||||
import dev.loki.lovisual.render.engine.core.system.LoVisualRenderSystem;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public final class MsaaFramebuffer extends RenderTarget {
|
||||
private int samples;
|
||||
|
||||
public MsaaFramebuffer(String name, int width, int height, boolean useDepthAttachment, int samples) {
|
||||
super(name, useDepthAttachment, GpuFormat.RGBA8_UNORM);
|
||||
this.samples = samples;
|
||||
RenderSystem.assertOnRenderThread();
|
||||
this.resize(width, height);
|
||||
}
|
||||
|
||||
private static int textureSamples(GpuTexture texture) {
|
||||
return texture instanceof IMsaaTexture msaa ? msaa.lovisual$getSamples() : 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createBuffers(int width, int height) {
|
||||
RenderSystem.assertOnRenderThread();
|
||||
if (!LoVisualRenderSystem.rhi().msaa().supported()) {
|
||||
throw new IllegalStateException("MSAA device not available");
|
||||
}
|
||||
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
|
||||
int usage = GpuTexture.USAGE_COPY_DST
|
||||
| GpuTexture.USAGE_COPY_SRC
|
||||
| GpuTexture.USAGE_TEXTURE_BINDING
|
||||
| GpuTexture.USAGE_RENDER_ATTACHMENT;
|
||||
|
||||
try {
|
||||
if (this.useDepth) {
|
||||
this.depthTexture = LoVisualRenderSystem.rhi().msaa().createTexture(
|
||||
this.label + " / Depth",
|
||||
usage,
|
||||
GpuFormat.D32_FLOAT,
|
||||
width,
|
||||
height,
|
||||
samples
|
||||
);
|
||||
this.depthTextureView = RenderSystem.getDevice().createTextureView(this.depthTexture);
|
||||
}
|
||||
|
||||
this.colorTexture = LoVisualRenderSystem.rhi().msaa().createTexture(
|
||||
this.label + " / Color",
|
||||
usage,
|
||||
GpuFormat.RGBA8_UNORM,
|
||||
width,
|
||||
height,
|
||||
samples
|
||||
);
|
||||
this.colorTextureView = RenderSystem.getDevice().createTextureView(this.colorTexture);
|
||||
|
||||
int colorSamples = textureSamples(this.colorTexture);
|
||||
int depthSamples = this.depthTexture != null ? textureSamples(this.depthTexture) : colorSamples;
|
||||
if (colorSamples <= 1 || depthSamples <= 1 || colorSamples != depthSamples) {
|
||||
throw new IllegalStateException("Incomplete MSAA target sample layout: color="
|
||||
+ colorSamples + ", depth=" + depthSamples + ", requested=" + samples);
|
||||
}
|
||||
this.samples = colorSamples;
|
||||
} catch (RuntimeException | Error t) {
|
||||
this.destroyBuffers();
|
||||
throw t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.msaa;
|
||||
|
||||
import com.mojang.blaze3d.pipeline.RenderTarget;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import dev.loki.lovisual.render.engine.core.system.LoVisualRenderSystem;
|
||||
|
||||
@Deprecated
|
||||
public enum MsaaResolveUtil {
|
||||
;
|
||||
|
||||
public static boolean resolve(RenderTarget src, RenderTarget dst, boolean color, boolean depth) {
|
||||
RenderSystem.assertOnRenderThread();
|
||||
return LoVisualRenderSystem.rhi().msaa().resolve(src, dst, color, depth);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.msaa;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
|
||||
@Deprecated
|
||||
public enum MsaaTextureRegistry {
|
||||
;
|
||||
private static final Int2IntOpenHashMap SAMPLES = new Int2IntOpenHashMap();
|
||||
|
||||
public static void register(int glId, int samples) {
|
||||
if (glId <= 0 || samples <= 1) return;
|
||||
SAMPLES.put(glId, samples);
|
||||
}
|
||||
|
||||
public static void unregister(int glId) {
|
||||
if (glId <= 0) return;
|
||||
SAMPLES.remove(glId);
|
||||
}
|
||||
|
||||
public static boolean isMsaa(int glId) {
|
||||
return glId > 0 && SAMPLES.containsKey(glId);
|
||||
}
|
||||
|
||||
public static int getSamples(int glId) {
|
||||
return glId > 0 ? SAMPLES.getOrDefault(glId, 1) : 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,267 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.msaa;
|
||||
|
||||
import com.mojang.blaze3d.pipeline.RenderTarget;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import dev.loki.lovisual.render.engine.core.system.LoVisualRenderSystem;
|
||||
import dev.loki.lovisual.mixins.accessors.renderer.GameRendererAccessor;
|
||||
import dev.loki.lovisual.util.logging.DebugLog;
|
||||
|
||||
public enum MsaaWorldTarget {
|
||||
;
|
||||
private static MsaaFramebuffer msaa;
|
||||
private static int samples;
|
||||
private static int allocationRequestSamples;
|
||||
private static int width;
|
||||
private static int height;
|
||||
private static boolean active;
|
||||
private static int requestedSamples;
|
||||
private static boolean supported;
|
||||
private static boolean lastResolveOk;
|
||||
private static boolean resolveDepth;
|
||||
private static String state = "never";
|
||||
|
||||
public static void begin(Minecraft mc, int sampleCount) {
|
||||
begin(mc, sampleCount, true);
|
||||
}
|
||||
|
||||
public static void begin(Minecraft mc, int sampleCount, boolean needDepthResolve) {
|
||||
begin(mc, sampleCount, needDepthResolve, false);
|
||||
}
|
||||
|
||||
public static void begin(Minecraft mc,
|
||||
int sampleCount,
|
||||
boolean needDepthResolve,
|
||||
boolean preserveMainColor) {
|
||||
requestedSamples = sampleCount;
|
||||
resolveDepth = needDepthResolve;
|
||||
if (mc == null) {
|
||||
state = "no_client";
|
||||
active = false;
|
||||
return;
|
||||
}
|
||||
if (sampleCount <= 1) {
|
||||
state = "off";
|
||||
supported = false;
|
||||
if (active || msaa != null) {
|
||||
shutdown(mc);
|
||||
}
|
||||
return;
|
||||
}
|
||||
supported = LoVisualRenderSystem.rhi().msaa().supported();
|
||||
if (!supported) {
|
||||
state = "unsupported";
|
||||
shutdown(mc);
|
||||
return;
|
||||
}
|
||||
int w = mc.getWindow().getWidth();
|
||||
int h = mc.getWindow().getHeight();
|
||||
if (w <= 0 || h <= 0) {
|
||||
state = "bad_size";
|
||||
shutdown(mc);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean reusableTarget = msaa != null
|
||||
&& allocationRequestSamples == sampleCount
|
||||
&& width == w
|
||||
&& height == h;
|
||||
ensureBuffer(w, h, sampleCount);
|
||||
if (msaa == null) {
|
||||
state = "alloc_failed";
|
||||
shutdown(mc);
|
||||
return;
|
||||
}
|
||||
|
||||
RenderTarget main = rawMain(mc);
|
||||
if (main == null) {
|
||||
state = "no_main";
|
||||
shutdown(mc);
|
||||
return;
|
||||
}
|
||||
|
||||
if (preserveMainColor) {
|
||||
if (!reusableTarget) {
|
||||
state = "seed_unavailable";
|
||||
active = false;
|
||||
return;
|
||||
}
|
||||
boolean seeded = LoVisualRenderSystem.rhi().msaa().resolve(main, msaa, true, false);
|
||||
if (!seeded) {
|
||||
lastResolveOk = false;
|
||||
state = "seed_failed";
|
||||
active = false;
|
||||
DebugLog.warnOnChange(
|
||||
"msaa.world.seed_failed",
|
||||
sampleCount + "|" + w + "x" + h,
|
||||
"[MSAA] could not preserve post-processed main color for the late world pass; rendering that pass without MSAA. requested=%d size=%dx%d",
|
||||
sampleCount,
|
||||
w,
|
||||
h
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
LoVisualRenderSystem.rhi().msaa().prepareTarget(msaa, main, true, needDepthResolve);
|
||||
} catch (Throwable t) {
|
||||
LoVisualRenderSystem.rhi().msaa().abandonTarget(msaa);
|
||||
lastResolveOk = false;
|
||||
state = "prepare_failed";
|
||||
DebugLog.warnOnChange("msaa.world.prepare_failed",
|
||||
sampleCount + "|" + t.getClass().getSimpleName() + "|" + t.getMessage(),
|
||||
"[MSAA] world target preparation failed; disabling target. requested=%d size=%dx%d error=%s: %s",
|
||||
sampleCount, w, h, t.getClass().getSimpleName(), t.getMessage());
|
||||
shutdown(mc);
|
||||
return;
|
||||
}
|
||||
|
||||
state = "active";
|
||||
active = true;
|
||||
}
|
||||
|
||||
public static void resolveToMain(Minecraft mc) {
|
||||
if (mc == null) return;
|
||||
if (!active || msaa == null) {
|
||||
return;
|
||||
}
|
||||
RenderTarget main = rawMain(mc);
|
||||
if (main == null) {
|
||||
LoVisualRenderSystem.rhi().msaa().abandonTarget(msaa);
|
||||
active = false;
|
||||
lastResolveOk = false;
|
||||
state = "resolve_no_main";
|
||||
return;
|
||||
}
|
||||
lastResolveOk = LoVisualRenderSystem.rhi().msaa().resolve(msaa, main, true, resolveDepth);
|
||||
active = false;
|
||||
}
|
||||
|
||||
public static void shutdown(Minecraft mc) {
|
||||
if (!active && msaa == null) {
|
||||
return;
|
||||
}
|
||||
if (active) {
|
||||
resolveToMain(mc);
|
||||
}
|
||||
if (msaa != null) {
|
||||
MsaaFramebuffer retired = msaa;
|
||||
LoVisualRenderSystem.rhi().msaa().abandonTarget(retired);
|
||||
LoVisualRenderSystem.resources().retire(retired::destroyBuffers);
|
||||
msaa = null;
|
||||
}
|
||||
active = false;
|
||||
samples = 0;
|
||||
allocationRequestSamples = 0;
|
||||
width = 0;
|
||||
height = 0;
|
||||
}
|
||||
|
||||
private static void ensureBuffer(int w, int h, int sampleCount) {
|
||||
if (msaa == null || allocationRequestSamples != sampleCount) {
|
||||
MsaaFramebuffer previous = msaa;
|
||||
try {
|
||||
MsaaFramebuffer next = new MsaaFramebuffer("lovisual-msaa-world", w, h, true, sampleCount);
|
||||
msaa = next;
|
||||
samples = next.getSamples();
|
||||
allocationRequestSamples = sampleCount;
|
||||
width = w;
|
||||
height = h;
|
||||
if (previous != null) {
|
||||
LoVisualRenderSystem.rhi().msaa().abandonTarget(previous);
|
||||
LoVisualRenderSystem.resources().retire(previous::destroyBuffers);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
if (previous != null) {
|
||||
LoVisualRenderSystem.rhi().msaa().abandonTarget(previous);
|
||||
LoVisualRenderSystem.resources().retire(previous::destroyBuffers);
|
||||
}
|
||||
msaa = null;
|
||||
samples = 0;
|
||||
allocationRequestSamples = 0;
|
||||
width = 0;
|
||||
height = 0;
|
||||
state = "alloc_failed";
|
||||
DebugLog.warnOnChange("msaa.world.alloc_failed", sampleCount + "|" + t.getClass().getSimpleName() + "|" + t.getMessage(),
|
||||
"[MSAA] world MSAA allocation failed; disabling target. requested=%d size=%dx%d error=%s: %s",
|
||||
sampleCount, w, h, t.getClass().getSimpleName(), t.getMessage());
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (w != width || h != height) {
|
||||
try {
|
||||
msaa.resize(w, h);
|
||||
samples = msaa.getSamples();
|
||||
width = w;
|
||||
height = h;
|
||||
} catch (Throwable t) {
|
||||
MsaaFramebuffer retired = msaa;
|
||||
LoVisualRenderSystem.rhi().msaa().abandonTarget(retired);
|
||||
LoVisualRenderSystem.resources().retire(retired::destroyBuffers);
|
||||
msaa = null;
|
||||
samples = 0;
|
||||
allocationRequestSamples = 0;
|
||||
width = 0;
|
||||
height = 0;
|
||||
state = "resize_failed";
|
||||
DebugLog.warnOnChange("msaa.world.resize_failed", sampleCount + "|" + t.getClass().getSimpleName() + "|" + t.getMessage(),
|
||||
"[MSAA] world MSAA resize failed; disabling target. requested=%d size=%dx%d error=%s: %s",
|
||||
sampleCount, w, h, t.getClass().getSimpleName(), t.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isActive() {
|
||||
return active && msaa != null;
|
||||
}
|
||||
|
||||
private static RenderTarget rawMain(Minecraft mc) {
|
||||
return mc != null && mc.gameRenderer != null
|
||||
? ((GameRendererAccessor) mc.gameRenderer).lovisual$getMainRenderTargetRaw()
|
||||
: null;
|
||||
}
|
||||
|
||||
public static RenderTarget getFramebufferOverride() {
|
||||
return active ? msaa : null;
|
||||
}
|
||||
|
||||
public static MsaaFramebuffer getMsaaFramebuffer() {
|
||||
return msaa;
|
||||
}
|
||||
|
||||
public static int getSamples() {
|
||||
return msaa != null ? msaa.getSamples() : 0;
|
||||
}
|
||||
|
||||
public static DebugStatus debugStatus() {
|
||||
return new DebugStatus(
|
||||
requestedSamples,
|
||||
samples,
|
||||
width,
|
||||
height,
|
||||
supported,
|
||||
active,
|
||||
msaa != null,
|
||||
lastResolveOk,
|
||||
state
|
||||
);
|
||||
}
|
||||
|
||||
public record DebugStatus(int requestedSamples,
|
||||
int targetSamples,
|
||||
int width,
|
||||
int height,
|
||||
boolean supported,
|
||||
boolean active,
|
||||
boolean allocated,
|
||||
boolean lastResolveOk,
|
||||
String state) {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.pipeline;
|
||||
|
||||
import com.mojang.blaze3d.platform.CompareOp;
|
||||
|
||||
public enum DepthTestFunction {
|
||||
NO_DEPTH_TEST(null),
|
||||
LEQUAL_DEPTH_TEST(CompareOp.LESS_THAN_OR_EQUAL),
|
||||
GEQUAL_DEPTH_TEST(CompareOp.GREATER_THAN_OR_EQUAL);
|
||||
|
||||
private final CompareOp compareOp;
|
||||
|
||||
DepthTestFunction(CompareOp compareOp) {
|
||||
this.compareOp = compareOp;
|
||||
}
|
||||
|
||||
public CompareOp compareOp() {
|
||||
return compareOp;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.pipeline;
|
||||
|
||||
import com.mojang.blaze3d.GpuFormat;
|
||||
import com.mojang.blaze3d.pipeline.*;
|
||||
import com.mojang.blaze3d.shaders.UniformType;
|
||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||
import dev.loki.lovisual.mixininterface.render.IRenderPipeline;
|
||||
import dev.loki.lovisual.render.engine.rhi.clip.ShapeClipRenderPassContract;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Small delegating builder so we can tag RenderPipeline with additional features.
|
||||
*/
|
||||
public class ExtendedRenderPipelineBuilder {
|
||||
private final RenderPipeline.Builder delegate;
|
||||
private final List<String> samplers = new ArrayList<>();
|
||||
private final List<UniformSpec> uniforms = new ArrayList<>();
|
||||
private boolean lineSmooth;
|
||||
private ShapeClipRenderPassContract shapeClipContract = ShapeClipRenderPassContract.NONE;
|
||||
private DepthTestFunction depthTestFunction;
|
||||
private net.minecraft.resources.Identifier location;
|
||||
private boolean depthWrite = true;
|
||||
private boolean customDepthState;
|
||||
private boolean bindGroupLayoutApplied;
|
||||
|
||||
public ExtendedRenderPipelineBuilder(RenderPipeline.Snippet... snippets) {
|
||||
this.delegate = RenderPipeline.builder(snippets);
|
||||
}
|
||||
|
||||
public ExtendedRenderPipelineBuilder withLocation(net.minecraft.resources.Identifier id) {
|
||||
this.location = id;
|
||||
delegate.withLocation(id);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExtendedRenderPipelineBuilder withVertexFormat(VertexFormat format, com.mojang.blaze3d.PrimitiveTopology mode) {
|
||||
delegate.withVertexBinding(0, format);
|
||||
delegate.withPrimitiveTopology(mode);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExtendedRenderPipelineBuilder withVertexShader(net.minecraft.resources.Identifier id) {
|
||||
delegate.withVertexShader(id);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExtendedRenderPipelineBuilder withFragmentShader(net.minecraft.resources.Identifier id) {
|
||||
delegate.withFragmentShader(id);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExtendedRenderPipelineBuilder withDepthTestFunction(DepthTestFunction fn) {
|
||||
this.depthTestFunction = fn;
|
||||
this.customDepthState = true;
|
||||
applyDepthStencilState();
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExtendedRenderPipelineBuilder withDepthWrite(boolean write) {
|
||||
this.depthWrite = write;
|
||||
this.customDepthState = true;
|
||||
applyDepthStencilState();
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExtendedRenderPipelineBuilder withBlend(BlendFunction blend) {
|
||||
delegate.withColorTargetState(new ColorTargetState(Optional.of(blend), GpuFormat.RGBA8_UNORM, ColorTargetState.WRITE_ALL));
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExtendedRenderPipelineBuilder withCull(boolean cull) {
|
||||
delegate.withCull(isUiCanvasPipeline() ? false : cull);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExtendedRenderPipelineBuilder withSampler(String name) {
|
||||
samplers.add(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExtendedRenderPipelineBuilder withUniform(String name, UniformType type) {
|
||||
uniforms.add(new UniformSpec(name, type));
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExtendedRenderPipelineBuilder withLineSmooth() {
|
||||
this.lineSmooth = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Declares that this pipeline participates in shape clipping. The backend decides how the
|
||||
* attachment is represented. GL uses stencil; Vulkan must create/route the equivalent
|
||||
* render-pass/mask attachment explicitly.
|
||||
*/
|
||||
public ExtendedRenderPipelineBuilder withShapeClipSupport() {
|
||||
this.shapeClipContract = ShapeClipRenderPassContract.WHEN_ACTIVE;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ExtendedRenderPipelineBuilder withShapeClipContract(ShapeClipRenderPassContract contract) {
|
||||
this.shapeClipContract = contract == null ? ShapeClipRenderPassContract.NONE : contract;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RenderPipeline build() {
|
||||
applyBindGroupLayout();
|
||||
RenderPipeline pipeline = delegate.build();
|
||||
IRenderPipeline lovisualPipeline = (IRenderPipeline) pipeline;
|
||||
lovisualPipeline.lovisual$setLineSmooth(lineSmooth);
|
||||
lovisualPipeline.lovisual$setShapeClipContract(shapeClipContract);
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
public RenderPipeline.Snippet buildSnippet() {
|
||||
applyBindGroupLayout();
|
||||
return delegate.buildSnippet();
|
||||
}
|
||||
|
||||
private boolean isUiCanvasPipeline() {
|
||||
if (location == null || !"lovisual".equals(location.getNamespace())) return false;
|
||||
String path = location.getPath();
|
||||
return path.startsWith("pipeline/ui_")
|
||||
|| path.startsWith("pipeline/menu_")
|
||||
|| path.equals("pipeline/gui_texture_lookup");
|
||||
}
|
||||
|
||||
private void applyDepthStencilState() {
|
||||
if (!customDepthState) return;
|
||||
if (depthTestFunction == null || depthTestFunction.compareOp() == null) {
|
||||
delegate.withDepthStencilState(Optional.empty());
|
||||
return;
|
||||
}
|
||||
delegate.withDepthStencilState(new DepthStencilState(depthTestFunction.compareOp(), depthWrite));
|
||||
}
|
||||
|
||||
private void applyBindGroupLayout() {
|
||||
if (bindGroupLayoutApplied || samplers.isEmpty() && uniforms.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
BindGroupLayout.Builder layout = BindGroupLayout.builder();
|
||||
for (String sampler : samplers) {
|
||||
layout.withSampler(sampler);
|
||||
}
|
||||
for (UniformSpec uniform : uniforms) {
|
||||
layout.withUniform(uniform.name(), uniform.type());
|
||||
}
|
||||
delegate.withBindGroupLayout(layout.build());
|
||||
bindGroupLayoutApplied = true;
|
||||
}
|
||||
|
||||
private record UniformSpec(String name, UniformType type) {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,439 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.pipeline;
|
||||
|
||||
import com.mojang.blaze3d.PrimitiveTopology;
|
||||
import com.mojang.blaze3d.pipeline.BlendFunction;
|
||||
import com.mojang.blaze3d.pipeline.RenderPipeline;
|
||||
import com.mojang.blaze3d.systems.GpuDevice;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import dev.loki.lovisual.mixininterface.render.IRenderPipeline;
|
||||
import dev.loki.lovisual.render.engine.pipeline.pipelines.PipelineTemplates;
|
||||
import dev.loki.lovisual.render.engine.pipeline.pipelines.ScreenPipelines;
|
||||
import dev.loki.lovisual.render.engine.pipeline.pipelines.UiPipelines;
|
||||
import dev.loki.lovisual.render.engine.pipeline.pipelines.WorldPipelines;
|
||||
import dev.loki.lovisual.render.engine.rhi.clip.ShapeClipRenderPassContract;
|
||||
import dev.loki.lovisual.render.engine.rhi.pipeline.layout.RenderPipelineRegistry;
|
||||
import dev.loki.lovisual.render.engine.shader.LoVisualShaderSources;
|
||||
import dev.loki.lovisual.render.engine.vertex.LoVisualVertexFormats;
|
||||
import dev.loki.lovisual.util.logging.DebugLog;
|
||||
import dev.loki.lovisual.features.module.modules.visuals.effects.hits.KillEffect;
|
||||
import dev.loki.lovisual.features.module.modules.visuals.esp.ESP;
|
||||
import dev.loki.lovisual.features.module.modules.visuals.fx.MotionBlur;
|
||||
import dev.loki.lovisual.features.module.modules.visuals.fx.PostFX;
|
||||
import dev.loki.lovisual.features.module.modules.visuals.scene.BlockHighlight;
|
||||
import dev.loki.lovisual.features.module.modules.visuals.weather.sky.ShaderSky;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Pipeline registry for LoVisual's custom rendering engine.
|
||||
*
|
||||
* <p>Public API facade: every shader identifier and pipeline constant keeps its original
|
||||
* name and value. The repetitive builder chains live in the template classes of
|
||||
* {@link dev.loki.lovisual.render.engine.pipeline.pipelines}.</p>
|
||||
*
|
||||
* <p>Keep this class free of module-specific logic: it is the foundation for all custom 3D/2D effects.</p>
|
||||
*/
|
||||
public enum LoVisualRenderPipelines {
|
||||
;
|
||||
// ======================
|
||||
// Shaders (assets/lovisual/shaders/*)
|
||||
// ======================
|
||||
// Core colored / line / fog shaders
|
||||
public static final Identifier SHADER_POS_COLOR_VERT = shader("pos_color.vert");
|
||||
public static final Identifier SHADER_WIDE_LINE_VERT = shader("wide_line.vert");
|
||||
public static final Identifier SHADER_POS_COLOR_FRAG = shader("pos_color.frag");
|
||||
public static final Identifier SHADER_POS_COLOR_FOG_VERT = shader("pos_color_fog.vert");
|
||||
public static final Identifier SHADER_POS_COLOR_FOG_FRAG = shader("pos_color_fog.frag");
|
||||
// Rect-parameter vertex shaders (batched shapes)
|
||||
public static final Identifier SHADER_POS_COLOR_RECT_PARAMS_VERT = shader("pos_color_rect_params.vert");
|
||||
public static final Identifier SHADER_POS_COLOR_RECT_PARAMS2_VERT = shader("pos_color_rect_params2.vert");
|
||||
public static final Identifier SHADER_POS_LOCAL_COLOR_RECT_PARAMS_VERT = shader("pos_local_color_rect_params.vert");
|
||||
public static final Identifier SHADER_POS_LOCAL_COLOR_RECT_PARAMS2_VERT = shader("pos_local_color_rect_params2.vert");
|
||||
public static final Identifier SHADER_POS_LOCAL_COLOR_RECT_PARAMS5_VERT = shader("pos_local_color_rect_params5.vert");
|
||||
public static final Identifier SHADER_POS_LOCAL_COLOR_RECT_PARAMS5_GENERIC_VERT = shader("pos_local_color_rect_params5_generic.vert");
|
||||
// Textured primitives & GUI lookup
|
||||
public static final Identifier SHADER_POS_TEX_COLOR_VERT = shader("pos_tex_color.vert");
|
||||
public static final Identifier SHADER_POS_TEX_COLOR_PARAMS2_VERT = shader("pos_tex_color_params2.vert");
|
||||
public static final Identifier SHADER_POS_TEX_COLOR_FRAG = shader("pos_tex_color.frag");
|
||||
public static final Identifier SHADER_GUI_TEXTURE_LOOKUP_FRAG = shader("gui_texture_lookup.frag");
|
||||
public static final Identifier SHADER_POS_TEX_COLOR_TINT_FRAG = shader("pos_tex_color_tint.frag");
|
||||
public static final Identifier SHADER_POS_TEX_COLOR_SKY_FOG_VERT = shader("pos_tex_color_sky_fog.vert");
|
||||
public static final Identifier SHADER_POS_TEX_COLOR_SKY_FOG_FRAG = shader("pos_tex_color_sky_fog.frag");
|
||||
public static final Identifier SHADER_POS_TEX_COLOR_SKY_FOG_ADDITIVE_FRAG = shader("pos_tex_color_sky_fog_additive.frag");
|
||||
// Skyboxes & shader skies
|
||||
public static final Identifier SHADER_POS_COLOR_SKYBOX_SHADER_VERT = shader("pos_color_skybox_shader.vert");
|
||||
public static final Identifier SHADER_REIMAGINED_SKYBOX_FRAG = shader("reimagined_skybox.frag");
|
||||
public static final Identifier SHADER_SHADER_SKY_THUNDER_FRAG = shader("shader_sky_thunder.frag");
|
||||
public static final Identifier SHADER_SHADER_SKY_PULSAR_FRAG = shader("shader_sky_pulsar.frag");
|
||||
public static final Identifier SHADER_POS_TEX_COLOR_RECT_PARAMS_VERT = shader("pos_tex_color_rect_params.vert");
|
||||
public static final Identifier SHADER_POS_TEX_LOCAL_COLOR_RECT_PARAMS_VERT = shader("pos_tex_local_color_rect_params.vert");
|
||||
public static final Identifier SHADER_POS_TEX_LOCAL_COLOR_RECT_PARAMS2_VERT = shader("pos_tex_local_color_rect_params2.vert");
|
||||
public static final Identifier SHADER_POS_TEX_COLOR_RECT_PARAMS2_VERT = shader("pos_tex_color_rect_params2.vert");
|
||||
// Text & MSDF
|
||||
public static final Identifier SHADER_TEXT_VERT = shader("text.vert");
|
||||
public static final Identifier SHADER_TEXT_FRAG = shader("text.frag");
|
||||
public static final Identifier SHADER_TEXT_MSDF_FRAG = shader("text_msdf.frag");
|
||||
public static final Identifier SHADER_SVG_MSDF_FRAG = shader("svg_msdf.frag");
|
||||
public static final Identifier SHADER_TEXT_LIQUID_GLASS_FRAG = shader("text_liquid_glass.frag");
|
||||
public static final Identifier SHADER_TEXT_LIQUID_GLASS_MSDF_FRAG = shader("text_liquid_glass_msdf.frag");
|
||||
// Batched UI shapes (SDF fragments)
|
||||
public static final Identifier SHADER_CIRCLE_BATCH_FRAG = shader("circle_batch.frag");
|
||||
public static final Identifier SHADER_ARC_BATCH_FRAG = shader("arc_batch.frag");
|
||||
public static final Identifier SHADER_ORBIZ_RING_BATCH_FRAG = shader("orbiz_ring_batch.frag");
|
||||
public static final Identifier SHADER_WORLD_DECAL_SDF_FRAG = shader("world_decal_sdf.frag");
|
||||
public static final Identifier SHADER_ROUNDED_RECT_STROKE_CORNERS_BATCH_FRAG = shader("rounded_rect_stroke_corners_batch.frag");
|
||||
public static final Identifier SHADER_ROUNDED_RECT_BATCH_FRAG = shader("rounded_rect_batch.frag");
|
||||
public static final Identifier SHADER_UI_ROUNDED_FILL_SMOKE_BATCH_FRAG = shader("ui_rounded_fill_smoke_batch.frag");
|
||||
public static final Identifier SHADER_ROUNDED_RECT_STROKE_BATCH_FRAG = shader("rounded_rect_stroke_batch.frag");
|
||||
public static final Identifier SHADER_ROUNDED_RECT_STROKE_ANGULAR_BATCH_FRAG = shader("rounded_rect_stroke_angular_batch.frag");
|
||||
public static final Identifier SHADER_CHAMFERED_RECT_BATCH_FRAG = shader("chamfered_rect_batch.frag");
|
||||
public static final Identifier SHADER_UI_SHAPE_BATCH_FRAG = shader("ui_shape_batch.frag");
|
||||
public static final Identifier SHADER_CHAMFERED_RECT_STROKE_BATCH_FRAG = shader("chamfered_rect_stroke_batch.frag");
|
||||
public static final Identifier SHADER_ROUNDED_RECT_CORNERS_BATCH_FRAG = shader("rounded_rect_corners_batch.frag");
|
||||
public static final Identifier SHADER_ROUNDED_RECT_GLOW_BATCH_FRAG = shader("rounded_rect_glow_batch.frag");
|
||||
public static final Identifier SHADER_ROUNDED_RECT_SHADOW_BATCH_FRAG = shader("rounded_rect_shadow_batch.frag");
|
||||
public static final Identifier SHADER_ROUNDED_RECT_SOFT_SHADOW_BATCH_FRAG = shader("rounded_rect_soft_shadow_batch.frag");
|
||||
public static final Identifier SHADER_RADIAL_GLOW_BATCH_FRAG = shader("radial_glow_batch.frag");
|
||||
public static final Identifier SHADER_ROUNDED_RECT_TEX_BATCH_FRAG = shader("rounded_rect_tex_batch.frag");
|
||||
public static final Identifier SHADER_ROUNDED_RECT_TEX_MASK_BATCH_FRAG = shader("rounded_rect_tex_mask_batch.frag");
|
||||
public static final Identifier SHADER_UI_BLUR_BATCH_FRAG = shader("ui_blur_batch.frag");
|
||||
public static final Identifier SHADER_UI_BLUR_BATCH_CORNERS_FRAG = shader("ui_blur_batch_corners.frag");
|
||||
public static final Identifier SHADER_UI_LIQUID_GLASS_BATCH_FRAG = shader("ui_liquid_glass_batch.frag");
|
||||
// Fullscreen post-processing & overlays
|
||||
public static final Identifier SHADER_DAMAGE_TINT_VERT = shader("damage_tint.vert");
|
||||
public static final Identifier SHADER_DAMAGE_TINT_FRAG = shader("damage_tint.frag");
|
||||
public static final Identifier SHADER_POST_FX_FRAG = shader("post_fx.frag");
|
||||
public static final Identifier SHADER_MOTION_BLUR_FRAG = shader("motion_blur.frag");
|
||||
public static final Identifier SHADER_DEPTH_OF_FIELD_FRAG = shader("depth_of_field.frag");
|
||||
public static final Identifier SHADER_WET_SURFACE_FRAG = shader("wet_surface.frag");
|
||||
public static final Identifier SHADER_HEAT_FX_FRAG = shader("heat_fx.frag");
|
||||
public static final Identifier SHADER_ESP_GRADIENT_FRAG = shader("shader_esp_gradient.frag");
|
||||
public static final Identifier SHADER_ESP_SHADOW_FRAG = shader("shader_esp_shadow.frag");
|
||||
public static final Identifier SHADER_ESP_SMOKE_FRAG = shader("shader_esp_smoke.frag");
|
||||
public static final Identifier SHADER_PORTAL_RIFT_FRAG = shader("portal_rift.frag");
|
||||
public static final Identifier SHADER_SLEEP_OVERLAY_FRAG = shader("sleep_overlay.frag");
|
||||
// Hand effects & chams
|
||||
public static final Identifier SHADER_HAND_SMOKE_FRAG = shader("hand_smoke.frag");
|
||||
public static final Identifier SHADER_HAND_METALLIC_FRAG = shader("hand_metallic.frag");
|
||||
public static final Identifier SHADER_HAND_NOISE_FRAG = shader("hand_noise.frag");
|
||||
public static final Identifier SHADER_UI_BLUR_FRAG = shader("ui_blur.frag");
|
||||
public static final Identifier SHADER_HAND_GLASS_FRAG = shader("hand_glass.frag");
|
||||
public static final Identifier SHADER_HAND_CHAMS_SOLID_FRAG = shader("hand_chams_solid.frag");
|
||||
public static final Identifier SHADER_HAND_CHAMS_CHROMA_FRAG = shader("hand_chams_chroma.frag");
|
||||
public static final Identifier SHADER_HAND_CHAMS_STRIPES_FRAG = shader("hand_chams_stripes.frag");
|
||||
public static final Identifier SHADER_HAND_CHAMS_GLOW_FRAG = shader("hand_chams_glow.frag");
|
||||
public static final Identifier SHADER_HAND_CHAMS_SNOW_FRAG = shader("hand_chams_snow.frag");
|
||||
public static final Identifier SHADER_HAND_CHAMS_INVERT_FRAG = shader("hand_chams_invert.frag");
|
||||
public static final Identifier SHADER_HAND_CHAMS_BALATRO_FRAG = shader("hand_chams_balatro.frag");
|
||||
public static final Identifier SHADER_HAND_CHAMS_BLEND_FRAG = shader("hand_chams_blend.frag");
|
||||
// Sky & menu backgrounds
|
||||
public static final Identifier SHADER_SKY_SUN_FRAG = shader("sky_sun.frag");
|
||||
public static final Identifier SHADER_MENU_BACKGROUND_WAVES_FRAG = shader("menu_background_waves.frag");
|
||||
public static final Identifier SHADER_MENU_BACKGROUND_AURORA_FRAG = shader("menu_background_aurora.frag");
|
||||
public static final Identifier SHADER_SKY_SHADER_SKY_FRAG = shader("sky_shader_sky.frag");
|
||||
public static final Identifier SHADER_SKY_SHADER_WATER_FRAG = shader("sky_shader_water.frag");
|
||||
// KillEffect procedural beam shaders
|
||||
public static final Identifier SHADER_KILL_EFFECT_PORTAL_FRAG = shader("kill_effect_portal.frag");
|
||||
public static final Identifier SHADER_KILL_EFFECT_TORNADO_FRAG = shader("kill_effect_tornado.frag");
|
||||
// BlockHighlight procedural shader outlines
|
||||
public static final Identifier SHADER_BLOCK_HIGHLIGHT_WATERCAUSTIC_FRAG = shader("block_highlight_watercaustic.frag");
|
||||
public static final Identifier SHADER_BLOCK_HIGHLIGHT_SCANLINES_FRAG = shader("block_highlight_scanlines.frag");
|
||||
public static final Identifier SHADER_BLOCK_HIGHLIGHT_HOLOGRAM_FRAG = shader("block_highlight_hologram.frag");
|
||||
// SkyShader nebula
|
||||
public static final Identifier SHADER_SKY_SHADER_NEBULA_FRAG = shader("sky_shader_nebula.frag");
|
||||
public static final Identifier SHADER_SKY_SHADER_CAUSTIC_FRAG = shader("sky_shader_caustic.frag");
|
||||
// SkyShader starry (procedural stars + milky way + round moon)
|
||||
public static final Identifier SHADER_SKY_SHADER_STARRY_FRAG = shader("sky_shader_starry.frag");
|
||||
|
||||
private static final List<RenderPipeline> PIPELINES = new ArrayList<>();
|
||||
|
||||
public static final RenderPipeline GUI_TEXTURE_LOOKUP = add(ScreenPipelines.guiTextureLookup());
|
||||
|
||||
// ======================
|
||||
// World (3D)
|
||||
// ======================
|
||||
/** No depth test; translucent; triangles. */
|
||||
public static final RenderPipeline WORLD_COLORED = add(WorldPipelines.colored("world_colored", false, BlendFunction.TRANSLUCENT));
|
||||
/** No depth test; translucent; lines. */
|
||||
public static final RenderPipeline WORLD_COLORED_LINES = add(WorldPipelines.coloredLines("world_colored_lines", false));
|
||||
/** Screen-space wide line expansion for WORLD_COLORED_LINES. Uses triangles; never depends on glLineWidth. */
|
||||
public static final RenderPipeline WORLD_WIDE_COLORED_LINES = add(WorldPipelines.wideLines("world_wide_colored_lines", false));
|
||||
/** Depth test (GEQUAL); translucent; triangles. */
|
||||
public static final RenderPipeline WORLD_COLORED_DEPTH = add(WorldPipelines.colored("world_colored_depth", true, BlendFunction.TRANSLUCENT));
|
||||
/** No depth test; translucent; textured triangles. */
|
||||
public static final RenderPipeline WORLD_TEXTURED = add(WorldPipelines.textured("world_textured", false, BlendFunction.TRANSLUCENT));
|
||||
/** Depth test (GEQUAL); translucent; textured triangles. */
|
||||
public static final RenderPipeline WORLD_TEXTURED_DEPTH = add(WorldPipelines.textured("world_textured_depth", true, BlendFunction.TRANSLUCENT));
|
||||
/** Depth test (GEQUAL); additive; colored triangles. */
|
||||
public static final RenderPipeline WORLD_COLORED_ADDITIVE_DEPTH = add(WorldPipelines.colored("world_colored_additive_depth", true, PipelineTemplates.ADDITIVE));
|
||||
/** No depth test; additive; colored triangles. */
|
||||
public static final RenderPipeline WORLD_COLORED_ADDITIVE = add(WorldPipelines.colored("world_colored_additive", false, PipelineTemplates.ADDITIVE));
|
||||
/** No depth test; translucent; world decal quads with SDF fill/stroke. */
|
||||
public static final RenderPipeline WORLD_DECAL_SDF = add(WorldPipelines.decalSdf("world_decal_sdf", false));
|
||||
/** Depth test (GEQUAL); translucent; world decal quads with SDF fill/stroke. */
|
||||
public static final RenderPipeline WORLD_DECAL_SDF_DEPTH = add(WorldPipelines.decalSdf("world_decal_sdf_depth", true));
|
||||
/** No depth test; translucent; world text quads. */
|
||||
public static final RenderPipeline WORLD_TEXT = add(WorldPipelines.worldText("world_text", false, SHADER_TEXT_FRAG, false));
|
||||
/** Depth test (GEQUAL); translucent; world text quads. */
|
||||
public static final RenderPipeline WORLD_TEXT_DEPTH = add(WorldPipelines.worldText("world_text_depth", true, SHADER_TEXT_FRAG, false));
|
||||
/** No depth test; translucent; world MSDF text quads. */
|
||||
public static final RenderPipeline WORLD_TEXT_MSDF = add(WorldPipelines.worldText("world_text_msdf", false, SHADER_TEXT_MSDF_FRAG, true));
|
||||
/** Depth test (GEQUAL); translucent; world MSDF text quads. */
|
||||
public static final RenderPipeline WORLD_TEXT_MSDF_DEPTH = add(WorldPipelines.worldText("world_text_msdf_depth", true, SHADER_TEXT_MSDF_FRAG, true));
|
||||
/** No depth test; additive; textured triangles. */
|
||||
public static final RenderPipeline WORLD_TEXTURED_ADDITIVE = add(WorldPipelines.textured("world_textured_additive", false, PipelineTemplates.ADDITIVE));
|
||||
/** No depth test; additive; textured triangles with shader-side tint. */
|
||||
public static final RenderPipeline WORLD_TEXTURED_TINT_ADDITIVE = add(WorldPipelines.tintedTextured("world_textured_tint_additive", false));
|
||||
/** Depth test (GEQUAL); additive; textured triangles. */
|
||||
public static final RenderPipeline WORLD_TEXTURED_ADDITIVE_DEPTH = add(WorldPipelines.textured("world_textured_additive_depth", true, PipelineTemplates.ADDITIVE));
|
||||
/** Depth test (GEQUAL); translucent; lines. */
|
||||
public static final RenderPipeline WORLD_COLORED_LINES_DEPTH = add(WorldPipelines.coloredLines("world_colored_lines_depth", true));
|
||||
/** Screen-space wide line expansion for WORLD_COLORED_LINES_DEPTH. Uses triangles; never depends on glLineWidth. */
|
||||
public static final RenderPipeline WORLD_WIDE_COLORED_LINES_DEPTH = add(WorldPipelines.wideLines("world_wide_colored_lines_depth", true));
|
||||
/** Depth test (GEQUAL); translucent; triangles; lets liquids blend over (no depth write). */
|
||||
public static final RenderPipeline WORLD_COLORED_LIQUID_BLEND = add(WorldPipelines.colored("world_colored_liquid_blend", true, BlendFunction.TRANSLUCENT));
|
||||
/** Depth test (GEQUAL); translucent; lines; lets liquids blend over (no depth write). */
|
||||
public static final RenderPipeline WORLD_COLORED_LINES_LIQUID_BLEND = add(WorldPipelines.coloredLines("world_colored_lines_liquid_blend", true));
|
||||
/** Screen-space wide line expansion for WORLD_COLORED_LINES_LIQUID_BLEND. Uses triangles; never depends on glLineWidth. */
|
||||
public static final RenderPipeline WORLD_WIDE_COLORED_LINES_LIQUID_BLEND = add(WorldPipelines.wideLines("world_wide_colored_lines_liquid_blend", true));
|
||||
/** Depth test (GEQUAL); translucent; textured triangles; lets liquids blend over (no depth write). */
|
||||
public static final RenderPipeline WORLD_TEXTURED_LIQUID_BLEND = add(WorldPipelines.textured("world_textured_liquid_blend", true, BlendFunction.TRANSLUCENT));
|
||||
/** Depth test (GEQUAL); additive; textured triangles; lets liquids blend over (no depth write). */
|
||||
public static final RenderPipeline WORLD_TEXTURED_ADDITIVE_LIQUID_BLEND = add(WorldPipelines.textured("world_textured_additive_liquid_blend", true, PipelineTemplates.ADDITIVE));
|
||||
/** Depth test (GEQUAL); translucent; triangles; intended for above-liquids pass (no depth write). */
|
||||
public static final RenderPipeline WORLD_COLORED_LIQUID_IGNORE = add(WorldPipelines.colored("world_colored_liquid_ignore", true, BlendFunction.TRANSLUCENT));
|
||||
/** Depth test (GEQUAL); translucent; lines; writes depth so liquids render below (above-liquids). */
|
||||
public static final RenderPipeline WORLD_COLORED_LINES_LIQUID_IGNORE = add(WorldPipelines.coloredLines("world_colored_lines_liquid_ignore", true));
|
||||
/** Screen-space wide line expansion for WORLD_COLORED_LINES_LIQUID_IGNORE. Uses triangles; never depends on glLineWidth. */
|
||||
public static final RenderPipeline WORLD_WIDE_COLORED_LINES_LIQUID_IGNORE = add(WorldPipelines.wideLines("world_wide_colored_lines_liquid_ignore", true));
|
||||
/** Depth test (GEQUAL); additive; textured triangles; intended for pre-water depth. */
|
||||
public static final RenderPipeline WORLD_TEXTURED_ADDITIVE_LIQUID_IGNORE = add(WorldPipelines.textured("world_textured_additive_liquid_ignore", true, PipelineTemplates.ADDITIVE));
|
||||
/** Depth test (GEQUAL); additive; textured triangles with shader-side tint; intended for pre-water depth. */
|
||||
public static final RenderPipeline WORLD_TEXTURED_TINT_ADDITIVE_LIQUID_IGNORE = add(WorldPipelines.tintedTextured("world_textured_tint_additive_liquid_ignore", true));
|
||||
/** Depth test (GEQUAL); translucent; textured triangles; intended for pre-water depth. */
|
||||
public static final RenderPipeline WORLD_TEXTURED_LIQUID_IGNORE = add(WorldPipelines.textured("world_textured_liquid_ignore", true, BlendFunction.TRANSLUCENT));
|
||||
|
||||
// ======================
|
||||
// UI (2D)
|
||||
// ======================
|
||||
/** UI triangles (pos2 + color). */
|
||||
public static final RenderPipeline UI_COLORED = add(UiPipelines.colored("ui_colored", PrimitiveTopology.TRIANGLES));
|
||||
/** UI lines (pos2 + color). */
|
||||
public static final RenderPipeline UI_COLORED_LINES = add(UiPipelines.colored("ui_colored_lines", PrimitiveTopology.DEBUG_LINES));
|
||||
/** UI textured triangles (pos2 + tex + color). */
|
||||
public static final RenderPipeline UI_TEXTURED = add(UiPipelines.textured("ui_textured", SHADER_POS_TEX_COLOR_VERT, SHADER_POS_TEX_COLOR_FRAG, BlendFunction.TRANSLUCENT, true, null, "u_Texture"));
|
||||
/** UI MSDF SVG quads (pos2 + tex + color). */
|
||||
public static final RenderPipeline UI_SVG_MSDF = add(UiPipelines.textured("ui_svg_msdf", SHADER_POS_TEX_COLOR_VERT, SHADER_SVG_MSDF_FRAG, BlendFunction.TRANSLUCENT, true, "MsdfText", "u_Texture"));
|
||||
/** UI textured triangles (pos2 + tex + color), additive blend. */
|
||||
public static final RenderPipeline UI_TEXTURED_ADDITIVE = add(UiPipelines.textured("ui_textured_additive", SHADER_POS_TEX_COLOR_VERT, SHADER_POS_TEX_COLOR_FRAG, PipelineTemplates.ADDITIVE, true, null, "u_Texture"));
|
||||
/** UI text (pos2 + tex + color). */
|
||||
public static final RenderPipeline UI_TEXT = add(UiPipelines.textured("ui_text", SHADER_TEXT_VERT, SHADER_TEXT_FRAG, BlendFunction.TRANSLUCENT, true, null, "u_Texture"));
|
||||
/** UI MSDF text (pos2 + tex + color). */
|
||||
public static final RenderPipeline UI_TEXT_MSDF = add(UiPipelines.textured("ui_text_msdf", SHADER_TEXT_VERT, SHADER_TEXT_MSDF_FRAG, BlendFunction.TRANSLUCENT, true, "MsdfText", "u_Texture"));
|
||||
public static final RenderPipeline UI_TEXT_LIQUID_GLASS = add(UiPipelines.textLiquidGlass("ui_text_liquid_glass", SHADER_TEXT_LIQUID_GLASS_FRAG, false));
|
||||
public static final RenderPipeline UI_TEXT_LIQUID_GLASS_MSDF = add(UiPipelines.textLiquidGlass("ui_text_liquid_glass_msdf", SHADER_TEXT_LIQUID_GLASS_MSDF_FRAG, true));
|
||||
public static final RenderPipeline UI_BLUR = add(UiPipelines.textured("ui_blur", SHADER_POS_TEX_COLOR_VERT, SHADER_UI_BLUR_FRAG, BlendFunction.TRANSLUCENT, false, "UIBlur", "u_Texture"));
|
||||
/** Fullscreen menu background (pos2). */
|
||||
public static final RenderPipeline MENU_BACKGROUND_WAVES = add(ScreenPipelines.screen("menu_background_waves", SHADER_MENU_BACKGROUND_WAVES_FRAG, "MenuBackground"));
|
||||
/** Fullscreen SkyShader passes (pos2). Sky/water/caustic share the same uniform layout. */
|
||||
public static final RenderPipeline SKY_SHADER_SKY = add(ScreenPipelines.screen("sky_shader_sky", SHADER_SKY_SHADER_SKY_FRAG, "SkyShader"));
|
||||
public static final RenderPipeline SKY_SHADER_WATER = add(ScreenPipelines.screen("sky_shader_water", SHADER_SKY_SHADER_WATER_FRAG, "SkyShader"));
|
||||
public static final RenderPipeline SKY_SHADER_CAUSTIC = add(ScreenPipelines.screen("sky_shader_caustic", SHADER_SKY_SHADER_CAUSTIC_FRAG, "SkyShader"));
|
||||
public static final RenderPipeline SKY_SHADER_NEBULA = add(ScreenPipelines.screen("sky_shader_nebula", SHADER_SKY_SHADER_NEBULA_FRAG, "SkyShaderData"));
|
||||
public static final RenderPipeline SKY_SHADER_STARRY = add(ScreenPipelines.screen("sky_shader_starry", SHADER_SKY_SHADER_STARRY_FRAG, "SkyShaderStarry"));
|
||||
/** Fullscreen main menu background (pos2). */
|
||||
public static final RenderPipeline MENU_BACKGROUND_AURORA = add(ScreenPipelines.screen("menu_background_aurora", SHADER_MENU_BACKGROUND_AURORA_FRAG, "MenuBackground"));
|
||||
/** Fullscreen postprocess (pos2). */
|
||||
public static final RenderPipeline DAMAGE_TINT = add(ScreenPipelines.screen("damage_tint", SHADER_DAMAGE_TINT_FRAG, "PostProcess", "u_Texture"));
|
||||
/** Fullscreen visual grading + LUT (pos2). */
|
||||
public static final RenderPipeline POST_FX = add(ScreenPipelines.screen("post_fx", SHADER_POST_FX_FRAG, "PostFX", "u_Texture", "u_Lut"));
|
||||
/** Fullscreen camera-only perceptual motion blur (pos2). */
|
||||
public static final RenderPipeline MOTION_BLUR = add(ScreenPipelines.screen("motion_blur", SHADER_MOTION_BLUR_FRAG, "MotionBlur", "u_Texture", "u_PreviousColor"));
|
||||
/** Fullscreen scene-depth-aware far depth of field (pos2). */
|
||||
public static final RenderPipeline DEPTH_OF_FIELD = add(ScreenPipelines.screen("depth_of_field", SHADER_DEPTH_OF_FIELD_FRAG, "DepthOfField",
|
||||
"u_Texture", "u_MainDepth", "u_TranslucentDepth", "u_ItemEntityDepth", "u_ParticlesDepth", "u_WeatherDepth", "u_CloudsDepth"));
|
||||
/** Fullscreen wet-ground screen-space reflection pass (pos2). */
|
||||
public static final RenderPipeline WET_SURFACE = add(ScreenPipelines.screen("wet_surface", SHADER_WET_SURFACE_FRAG, "WetSurface", "u_Texture", "u_MainDepth"));
|
||||
/** Fullscreen heat distortion + vignette (pos2). */
|
||||
public static final RenderPipeline HEAT_FX = add(ScreenPipelines.screen("heat_fx", SHADER_HEAT_FX_FRAG, "Heat", "u_Texture"));
|
||||
/** Fullscreen shader ESP gradient composite (pos2). */
|
||||
public static final RenderPipeline SHADER_ESP_GRADIENT = add(ScreenPipelines.screen("shader_esp_gradient", SHADER_ESP_GRADIENT_FRAG, "ShaderEspGradient", "u_Texture"));
|
||||
/** Fullscreen shader ESP separable shadow blur (pos2). */
|
||||
public static final RenderPipeline SHADER_ESP_SHADOW = add(ScreenPipelines.screen("shader_esp_shadow", SHADER_ESP_SHADOW_FRAG, "ShaderEspBlur", "u_Texture", "u_Mask"));
|
||||
/** Fullscreen shader ESP procedural smoke fill (pos2). */
|
||||
public static final RenderPipeline SHADER_ESP_SMOKE = add(ScreenPipelines.screen("shader_esp_smoke", SHADER_ESP_SMOKE_FRAG, "ShaderEspSmoke", "u_Texture"));
|
||||
/** No depth test; additive; textured-color triangles with procedural sun shading. */
|
||||
public static final RenderPipeline WORLD_SKY_SUN = add(WorldPipelines.skySun("world_sky_sun"));
|
||||
/** No depth test; additive; KillEffect "Portal" procedural beam shader. */
|
||||
public static final RenderPipeline WORLD_KILL_EFFECT_PORTAL =
|
||||
add(WorldPipelines.killEffectBeam("world_kill_effect_portal", SHADER_KILL_EFFECT_PORTAL_FRAG));
|
||||
/** No depth test; additive; KillEffect "Tornado" procedural beam shader. */
|
||||
public static final RenderPipeline WORLD_KILL_EFFECT_TORNADO =
|
||||
add(WorldPipelines.killEffectBeam("world_kill_effect_tornado", SHADER_KILL_EFFECT_TORNADO_FRAG));
|
||||
/** No depth test; additive; BlockHighlight "WaterCaustic" shader outline. */
|
||||
public static final RenderPipeline WORLD_BLOCK_HIGHLIGHT_WATERCAUSTIC =
|
||||
add(WorldPipelines.waterHighlight("world_block_highlight_watercaustic", SHADER_BLOCK_HIGHLIGHT_WATERCAUSTIC_FRAG));
|
||||
/** No depth test; additive; BlockHighlight "ScanLines" shader outline. */
|
||||
public static final RenderPipeline WORLD_BLOCK_HIGHLIGHT_SCANLINES =
|
||||
add(WorldPipelines.waterHighlight("world_block_highlight_scanlines", SHADER_BLOCK_HIGHLIGHT_SCANLINES_FRAG));
|
||||
/** No depth test; additive; BlockHighlight "Hologram" shader outline. */
|
||||
public static final RenderPipeline WORLD_BLOCK_HIGHLIGHT_HOLOGRAM =
|
||||
add(WorldPipelines.waterHighlight("world_block_highlight_hologram", SHADER_BLOCK_HIGHLIGHT_HOLOGRAM_FRAG));
|
||||
/** Fullscreen nether portal rift (pos2). */
|
||||
public static final RenderPipeline PORTAL_RIFT = add(ScreenPipelines.screen("portal_rift", SHADER_PORTAL_RIFT_FRAG, "Heat", "u_Texture"));
|
||||
/** Fullscreen sleep overlay (pos2). */
|
||||
public static final RenderPipeline SLEEP_OVERLAY = add(ScreenPipelines.screen("sleep_overlay", SHADER_SLEEP_OVERLAY_FRAG, "PostProcess", "u_Texture"));
|
||||
/** Fullscreen hand glass (pos2). */
|
||||
public static final RenderPipeline HAND_GLASS = add(ScreenPipelines.handEffect("hand_glass", SHADER_HAND_GLASS_FRAG, "HandGlass"));
|
||||
public static final RenderPipeline HAND_SMOKE = add(ScreenPipelines.handEffect("hand_smoke", SHADER_HAND_SMOKE_FRAG, "HandSmoke"));
|
||||
public static final RenderPipeline HAND_METALLIC = add(ScreenPipelines.handEffect("hand_metallic", SHADER_HAND_METALLIC_FRAG, "HandMetallic"));
|
||||
public static final RenderPipeline HAND_NOISE = add(ScreenPipelines.handEffect("hand_noise", SHADER_HAND_NOISE_FRAG, "HandNoise"));
|
||||
public static final RenderPipeline HAND_CHAMS_SOLID = add(ScreenPipelines.handEffect("hand_chams_solid", SHADER_HAND_CHAMS_SOLID_FRAG, "HandChams"));
|
||||
public static final RenderPipeline HAND_CHAMS_CHROMA = add(ScreenPipelines.handEffect("hand_chams_chroma", SHADER_HAND_CHAMS_CHROMA_FRAG, "HandChams"));
|
||||
public static final RenderPipeline HAND_CHAMS_STRIPES = add(ScreenPipelines.handEffect("hand_chams_stripes", SHADER_HAND_CHAMS_STRIPES_FRAG, "HandChams"));
|
||||
public static final RenderPipeline HAND_CHAMS_GLOW = add(ScreenPipelines.handEffect("hand_chams_glow", SHADER_HAND_CHAMS_GLOW_FRAG, "HandChams"));
|
||||
public static final RenderPipeline HAND_CHAMS_SNOW = add(ScreenPipelines.handEffect("hand_chams_snow", SHADER_HAND_CHAMS_SNOW_FRAG, "HandChams"));
|
||||
public static final RenderPipeline HAND_CHAMS_INVERT = add(ScreenPipelines.handEffect("hand_chams_invert", SHADER_HAND_CHAMS_INVERT_FRAG, "HandChams"));
|
||||
public static final RenderPipeline HAND_CHAMS_BALATRO = add(ScreenPipelines.handEffect("hand_chams_balatro", SHADER_HAND_CHAMS_BALATRO_FRAG, "HandChams"));
|
||||
public static final RenderPipeline HAND_CHAMS_BLEND = add(ScreenPipelines.handBlendEffect("hand_chams_blend", SHADER_HAND_CHAMS_BLEND_FRAG));
|
||||
/** No depth test; translucent; textured skybox with vanilla sky/fog blending. */
|
||||
public static final RenderPipeline WORLD_SKYBOX_TEXTURED_FOG = add(WorldPipelines.texturedSkyboxFog("world_skybox_textured_fog", SHADER_POS_TEX_COLOR_SKY_FOG_FRAG, BlendFunction.TRANSLUCENT));
|
||||
/** No depth test; additive; textured skybox with vanilla sky/fog blending. */
|
||||
public static final RenderPipeline WORLD_SKYBOX_TEXTURED_FOG_ADDITIVE = add(WorldPipelines.texturedSkyboxFog("world_skybox_textured_fog_additive", SHADER_POS_TEX_COLOR_SKY_FOG_ADDITIVE_FRAG, PipelineTemplates.ADDITIVE));
|
||||
/** Fullscreen procedural Reimagined skybox with sky/fog blending. */
|
||||
public static final RenderPipeline WORLD_REIMAGINED_SKYBOX_SHADER = add(WorldPipelines.proceduralSkyboxFog("world_reimagined_skybox_shader", SHADER_REIMAGINED_SKYBOX_FRAG));
|
||||
/** Fullscreen procedural ShaderSky (Thunder) with sky/fog blending. */
|
||||
public static final RenderPipeline WORLD_SHADER_SKY_THUNDER = add(ScreenPipelines.screen("world_shader_sky_thunder", SHADER_SHADER_SKY_THUNDER_FRAG, "ShaderSky"));
|
||||
/** Fullscreen procedural ShaderSky (Pulsar) with sky/fog blending. */
|
||||
public static final RenderPipeline WORLD_SHADER_SKY_PULSAR = add(ScreenPipelines.screen("world_shader_sky_pulsar", SHADER_SHADER_SKY_PULSAR_FRAG, "ShaderSky"));
|
||||
|
||||
// ======================
|
||||
// Batched UI shapes (2D)
|
||||
// ======================
|
||||
public static final RenderPipeline UI_CIRCLE_BATCH = add(UiPipelines.batch("ui_circle_batch",
|
||||
LoVisualVertexFormats.POS2_LOCAL_COLOR_RECT_PARAMS, SHADER_POS_LOCAL_COLOR_RECT_PARAMS_VERT, SHADER_CIRCLE_BATCH_FRAG, BlendFunction.TRANSLUCENT, true));
|
||||
public static final RenderPipeline UI_ARC_BATCH = add(UiPipelines.batch("ui_arc_batch",
|
||||
LoVisualVertexFormats.POS2_LOCAL_COLOR_RECT_PARAMS2, SHADER_POS_LOCAL_COLOR_RECT_PARAMS2_VERT, SHADER_ARC_BATCH_FRAG, BlendFunction.TRANSLUCENT, true));
|
||||
public static final RenderPipeline UI_ORBIZ_RING_BATCH = add(UiPipelines.batch("ui_orbiz_ring_batch",
|
||||
LoVisualVertexFormats.POS2_LOCAL_COLOR_RECT_PARAMS5, SHADER_POS_LOCAL_COLOR_RECT_PARAMS5_GENERIC_VERT, SHADER_ORBIZ_RING_BATCH_FRAG, BlendFunction.TRANSLUCENT, true));
|
||||
public static final RenderPipeline UI_ROUNDED_BATCH = add(UiPipelines.batch("ui_rounded_batch",
|
||||
LoVisualVertexFormats.POS2_LOCAL_COLOR_RECT_PARAMS, SHADER_POS_LOCAL_COLOR_RECT_PARAMS_VERT, SHADER_ROUNDED_RECT_BATCH_FRAG, BlendFunction.TRANSLUCENT, true));
|
||||
public static final RenderPipeline UI_ROUNDED_FILL_SMOKE_BATCH = add(UiPipelines.batch("ui_rounded_fill_smoke_batch",
|
||||
LoVisualVertexFormats.POS2_LOCAL_COLOR_RECT_PARAMS5, SHADER_POS_LOCAL_COLOR_RECT_PARAMS5_VERT, SHADER_UI_ROUNDED_FILL_SMOKE_BATCH_FRAG, BlendFunction.TRANSLUCENT, true));
|
||||
public static final RenderPipeline UI_ROUNDED_STROKE_BATCH = add(UiPipelines.batch("ui_rounded_stroke_batch",
|
||||
LoVisualVertexFormats.POS2_LOCAL_COLOR_RECT_PARAMS, SHADER_POS_LOCAL_COLOR_RECT_PARAMS_VERT, SHADER_ROUNDED_RECT_STROKE_BATCH_FRAG, BlendFunction.TRANSLUCENT, true));
|
||||
public static final RenderPipeline UI_ROUNDED_STROKE_ANGULAR_BATCH = add(UiPipelines.batch("ui_rounded_stroke_angular_batch",
|
||||
LoVisualVertexFormats.POS2_LOCAL_COLOR_RECT_PARAMS2, SHADER_POS_LOCAL_COLOR_RECT_PARAMS2_VERT, SHADER_ROUNDED_RECT_STROKE_ANGULAR_BATCH_FRAG, BlendFunction.TRANSLUCENT, true));
|
||||
public static final RenderPipeline UI_CHAMFERED_BATCH = add(UiPipelines.batch("ui_chamfered_batch",
|
||||
LoVisualVertexFormats.POS2_LOCAL_COLOR_RECT_PARAMS2, SHADER_POS_LOCAL_COLOR_RECT_PARAMS2_VERT, SHADER_CHAMFERED_RECT_BATCH_FRAG, BlendFunction.TRANSLUCENT, true));
|
||||
public static final RenderPipeline UI_SHAPE_BATCH = add(UiPipelines.batch("ui_shape_batch",
|
||||
LoVisualVertexFormats.POS2_LOCAL_COLOR_RECT_PARAMS5, SHADER_POS_LOCAL_COLOR_RECT_PARAMS5_VERT, SHADER_UI_SHAPE_BATCH_FRAG, BlendFunction.TRANSLUCENT, true));
|
||||
public static final RenderPipeline UI_CHAMFERED_STROKE_BATCH = add(UiPipelines.batch("ui_chamfered_stroke_batch",
|
||||
LoVisualVertexFormats.POS2_LOCAL_COLOR_RECT_PARAMS2, SHADER_POS_LOCAL_COLOR_RECT_PARAMS2_VERT, SHADER_CHAMFERED_RECT_STROKE_BATCH_FRAG, BlendFunction.TRANSLUCENT, true));
|
||||
public static final RenderPipeline UI_ROUNDED_STROKE_CORNERS_BATCH = add(UiPipelines.batch("ui_rounded_stroke_corners_batch",
|
||||
LoVisualVertexFormats.POS2_LOCAL_COLOR_RECT_PARAMS2, SHADER_POS_LOCAL_COLOR_RECT_PARAMS2_VERT, SHADER_ROUNDED_RECT_STROKE_CORNERS_BATCH_FRAG, BlendFunction.TRANSLUCENT, true));
|
||||
public static final RenderPipeline UI_ROUNDED_CORNERS_BATCH = add(UiPipelines.batch("ui_rounded_corners_batch",
|
||||
LoVisualVertexFormats.POS2_LOCAL_COLOR_RECT_PARAMS2, SHADER_POS_LOCAL_COLOR_RECT_PARAMS2_VERT, SHADER_ROUNDED_RECT_CORNERS_BATCH_FRAG, BlendFunction.TRANSLUCENT, true));
|
||||
public static final RenderPipeline UI_ROUNDED_GLOW_BATCH = add(UiPipelines.batch("ui_rounded_glow_batch",
|
||||
LoVisualVertexFormats.POS2_LOCAL_COLOR_RECT_PARAMS, SHADER_POS_LOCAL_COLOR_RECT_PARAMS_VERT, SHADER_ROUNDED_RECT_GLOW_BATCH_FRAG, PipelineTemplates.ADDITIVE, true));
|
||||
public static final RenderPipeline UI_ROUNDED_SHADOW_BATCH = add(UiPipelines.batch("ui_rounded_shadow_batch",
|
||||
LoVisualVertexFormats.POS2_LOCAL_COLOR_RECT_PARAMS, SHADER_POS_LOCAL_COLOR_RECT_PARAMS_VERT, SHADER_ROUNDED_RECT_SHADOW_BATCH_FRAG, BlendFunction.TRANSLUCENT, true));
|
||||
public static final RenderPipeline UI_ROUNDED_SOFT_SHADOW_BATCH = add(UiPipelines.batch("ui_rounded_soft_shadow_batch",
|
||||
LoVisualVertexFormats.POS2_LOCAL_COLOR_RECT_PARAMS, SHADER_POS_LOCAL_COLOR_RECT_PARAMS_VERT, SHADER_ROUNDED_RECT_SOFT_SHADOW_BATCH_FRAG, BlendFunction.TRANSLUCENT, true));
|
||||
public static final RenderPipeline UI_RADIAL_GLOW_BATCH = add(UiPipelines.batch("ui_radial_glow_batch",
|
||||
LoVisualVertexFormats.POS2_LOCAL_COLOR_RECT_PARAMS2, SHADER_POS_LOCAL_COLOR_RECT_PARAMS2_VERT, SHADER_RADIAL_GLOW_BATCH_FRAG, PipelineTemplates.ADDITIVE, true));
|
||||
public static final RenderPipeline UI_ROUNDED_TEXTURED_BATCH = add(UiPipelines.batch("ui_rounded_textured_batch",
|
||||
LoVisualVertexFormats.POS2_TEXTURE_LOCAL_COLOR_RECT_PARAMS, SHADER_POS_TEX_LOCAL_COLOR_RECT_PARAMS_VERT, SHADER_ROUNDED_RECT_TEX_BATCH_FRAG, BlendFunction.TRANSLUCENT, true, "u_Texture"));
|
||||
public static final RenderPipeline UI_ROUNDED_TEXTURED_MASK_BATCH = add(UiPipelines.batch("ui_rounded_textured_mask_batch",
|
||||
LoVisualVertexFormats.POS2_TEXTURE_LOCAL_COLOR_RECT_PARAMS, SHADER_POS_TEX_LOCAL_COLOR_RECT_PARAMS_VERT, SHADER_ROUNDED_RECT_TEX_MASK_BATCH_FRAG, BlendFunction.TRANSLUCENT, true, "u_Texture"));
|
||||
public static final RenderPipeline UI_BLUR_BATCH = add(UiPipelines.batch("ui_blur_batch",
|
||||
LoVisualVertexFormats.POS2_LOCAL_COLOR_RECT_PARAMS, SHADER_POS_LOCAL_COLOR_RECT_PARAMS_VERT, SHADER_UI_BLUR_BATCH_FRAG, BlendFunction.TRANSLUCENT, false, "u_Texture"));
|
||||
public static final RenderPipeline UI_BLUR_BATCH_CORNERS = add(UiPipelines.batch("ui_blur_batch_corners",
|
||||
LoVisualVertexFormats.POS2_LOCAL_COLOR_RECT_PARAMS2, SHADER_POS_LOCAL_COLOR_RECT_PARAMS2_VERT, SHADER_UI_BLUR_BATCH_CORNERS_FRAG, BlendFunction.TRANSLUCENT, false, "u_Texture"));
|
||||
public static final RenderPipeline UI_LIQUID_GLASS_BATCH = add(UiPipelines.batch("ui_liquid_glass_batch",
|
||||
LoVisualVertexFormats.POS2_TEXTURE_LOCAL_COLOR_RECT_PARAMS2, SHADER_POS_TEX_LOCAL_COLOR_RECT_PARAMS2_VERT, SHADER_UI_LIQUID_GLASS_BATCH_FRAG, BlendFunction.TRANSLUCENT, false, "u_Texture", "u_BlurTexture"));
|
||||
|
||||
/**
|
||||
* Compile all registered pipelines (use after resource reload).
|
||||
*/
|
||||
public static void precompile(ResourceManager resources) {
|
||||
final GpuDevice device;
|
||||
try {
|
||||
device = RenderSystem.getDevice();
|
||||
} catch (Throwable t) {
|
||||
DebugLog.warn("[LoVisual] RenderSystem not ready in precompile(ResourceManager)");
|
||||
return;
|
||||
}
|
||||
|
||||
DebugLog.renderThread("[LoVisual] Precompiling pipelines: " + PIPELINES.size());
|
||||
|
||||
for (RenderPipeline pipeline : PIPELINES) {
|
||||
DebugLog.renderThread("[LoVisual] -> pipeline " + pipeline.getLocation());
|
||||
DebugLog.renderThread(" VS = " + pipeline.getVertexShader());
|
||||
DebugLog.renderThread(" FS = " + pipeline.getFragmentShader());
|
||||
DebugLog.renderThread(" VF = " + pipeline.getVertexFormatBinding(0));
|
||||
DebugLog.renderThread(" MODE = " + pipeline.getPrimitiveTopology());
|
||||
|
||||
device.precompilePipeline(pipeline, (identifier, shaderType) -> {
|
||||
DebugLog.renderThread("[LoVisual] loading " + shaderType + " " + identifier);
|
||||
return LoVisualShaderSources.load(resources, identifier, shaderType);
|
||||
});
|
||||
}
|
||||
|
||||
DebugLog.renderThread("[LoVisual] Precompile finished");
|
||||
}
|
||||
|
||||
/**
|
||||
* compile using the current MinecraftClient resource manager.
|
||||
*/
|
||||
|
||||
public static List<RenderPipeline> all() {
|
||||
return Collections.unmodifiableList(PIPELINES);
|
||||
}
|
||||
|
||||
public static RenderPipeline.Snippet meshUniforms() {
|
||||
return PipelineTemplates.MESH_UNIFORMS;
|
||||
}
|
||||
|
||||
public static RenderPipeline registerAddonPipeline(RenderPipeline pipeline) {
|
||||
return add(pipeline);
|
||||
}
|
||||
|
||||
private static RenderPipeline add(RenderPipeline pipeline) {
|
||||
declareDefaultShapeClipContract(pipeline);
|
||||
PIPELINES.add(pipeline);
|
||||
RenderPipelineRegistry.global().registerNative(pipeline);
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
private static void declareDefaultShapeClipContract(RenderPipeline pipeline) {
|
||||
if (!(pipeline instanceof IRenderPipeline lovisualPipeline) || pipeline.getLocation() == null) return;
|
||||
ShapeClipRenderPassContract current = lovisualPipeline.lovisual$getShapeClipContract();
|
||||
if (current != ShapeClipRenderPassContract.NONE) return;
|
||||
|
||||
String namespace = pipeline.getLocation().getNamespace();
|
||||
String path = pipeline.getLocation().getPath();
|
||||
if ("lovisual".equals(namespace) && (path.startsWith("pipeline/ui_") || path.equals("pipeline/gui_texture_lookup"))) {
|
||||
lovisualPipeline.lovisual$setShapeClipContract(ShapeClipRenderPassContract.WHEN_ACTIVE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* LoVisual shader asset identifier for "shaders/<name>".
|
||||
*/
|
||||
private static Identifier shader(String name) {
|
||||
return Identifier.fromNamespaceAndPath("lovisual", "shaders/" + name);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.pipeline.pipelines;
|
||||
|
||||
import com.mojang.blaze3d.pipeline.BlendFunction;
|
||||
import com.mojang.blaze3d.pipeline.RenderPipeline;
|
||||
import com.mojang.blaze3d.platform.BlendFactor;
|
||||
import com.mojang.blaze3d.shaders.UniformType;
|
||||
import dev.loki.lovisual.render.engine.pipeline.DepthTestFunction;
|
||||
import dev.loki.lovisual.render.engine.pipeline.ExtendedRenderPipelineBuilder;
|
||||
import net.minecraft.resources.Identifier;
|
||||
|
||||
/**
|
||||
* Shared building blocks for the LoVisual pipeline factories: uniform snippets, blend
|
||||
* presets and the common depth/blend/cull tail that every custom pipeline applies.
|
||||
*/
|
||||
public final class PipelineTemplates {
|
||||
/** Additive blending (SRC_ALPHA, ONE) shared by all additive LoVisual pipelines. */
|
||||
public static final BlendFunction ADDITIVE = new BlendFunction(BlendFactor.SRC_ALPHA, BlendFactor.ONE);
|
||||
|
||||
public static final RenderPipeline.Snippet MESH_UNIFORMS = uniformSnippet("MeshData");
|
||||
public static final RenderPipeline.Snippet FOG_UNIFORMS = uniformSnippet("Fog");
|
||||
public static final RenderPipeline.Snippet UI_BATCH_UNIFORMS = uniformSnippet("UIBatch");
|
||||
|
||||
private PipelineTemplates() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Snippet declaring a single {@link UniformType#UNIFORM_BUFFER} uniform.
|
||||
*/
|
||||
public static RenderPipeline.Snippet uniformSnippet(String name) {
|
||||
return new ExtendedRenderPipelineBuilder()
|
||||
.withUniform(name, UniformType.UNIFORM_BUFFER)
|
||||
.buildSnippet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Location identifier for a "pipeline/<name>" entry in the lovisual namespace.
|
||||
*/
|
||||
public static Identifier location(String name) {
|
||||
return Identifier.fromNamespaceAndPath("lovisual", "pipeline/" + name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the shared pipeline tail (depth test, depth write off, blend, cull) and builds.
|
||||
*/
|
||||
public static RenderPipeline finish(ExtendedRenderPipelineBuilder builder, boolean depthTest, BlendFunction blend, boolean cull) {
|
||||
return builder
|
||||
.withDepthTestFunction(depthTest ? DepthTestFunction.GEQUAL_DEPTH_TEST : DepthTestFunction.NO_DEPTH_TEST)
|
||||
.withDepthWrite(false)
|
||||
.withBlend(blend)
|
||||
.withCull(cull)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.pipeline.pipelines;
|
||||
|
||||
import com.mojang.blaze3d.PrimitiveTopology;
|
||||
import com.mojang.blaze3d.pipeline.BlendFunction;
|
||||
import com.mojang.blaze3d.pipeline.RenderPipeline;
|
||||
import com.mojang.blaze3d.shaders.UniformType;
|
||||
import dev.loki.lovisual.render.engine.pipeline.ExtendedRenderPipelineBuilder;
|
||||
import dev.loki.lovisual.render.engine.pipeline.LoVisualRenderPipelines;
|
||||
import dev.loki.lovisual.render.engine.vertex.LoVisualVertexFormats;
|
||||
import net.minecraft.client.renderer.RenderPipelines;
|
||||
import net.minecraft.resources.Identifier;
|
||||
|
||||
/**
|
||||
* Factory templates for LoVisual's fullscreen pos2 pipelines (post effects, hand effects,
|
||||
* shader skies) plus the vanilla-textured GUI lookup pipeline. Samplers are declared
|
||||
* before the single uniform buffer, matching the original declaration order.
|
||||
*/
|
||||
public final class ScreenPipelines {
|
||||
private ScreenPipelines() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimal vanilla-textured pipeline used for GUI texture lookups.
|
||||
*/
|
||||
public static RenderPipeline guiTextureLookup() {
|
||||
return new ExtendedRenderPipelineBuilder(RenderPipelines.GUI_TEXTURED_SNIPPET)
|
||||
.withLocation(PipelineTemplates.location("gui_texture_lookup"))
|
||||
.withFragmentShader(LoVisualRenderPipelines.SHADER_GUI_TEXTURE_LOOKUP_FRAG)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fullscreen pos2 pass: damage-tint vertex shader, no depth test, translucent, no culling.
|
||||
*/
|
||||
public static RenderPipeline screen(String name, Identifier fragmentShader, String uniform, String... samplers) {
|
||||
ExtendedRenderPipelineBuilder builder = base(name)
|
||||
.withVertexFormat(LoVisualVertexFormats.POS2, PrimitiveTopology.TRIANGLES)
|
||||
.withVertexShader(LoVisualRenderPipelines.SHADER_DAMAGE_TINT_VERT)
|
||||
.withFragmentShader(fragmentShader);
|
||||
for (String sampler : samplers) {
|
||||
builder.withSampler(sampler);
|
||||
}
|
||||
builder.withUniform(uniform, UniformType.UNIFORM_BUFFER);
|
||||
return PipelineTemplates.finish(builder, false, BlendFunction.TRANSLUCENT, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fullscreen hand effect with the standard "u_Src"/"u_Mask" sampler pair.
|
||||
*/
|
||||
public static RenderPipeline handEffect(String name, Identifier fragmentShader, String uniform) {
|
||||
return screen(name, fragmentShader, uniform, "u_Src", "u_Mask");
|
||||
}
|
||||
|
||||
/**
|
||||
* Fullscreen chams blend effect sampling two shader textures plus the mask.
|
||||
*/
|
||||
public static RenderPipeline handBlendEffect(String name, Identifier fragmentShader) {
|
||||
return screen(name, fragmentShader, "HandBlend", "u_Shader1", "u_Shader2", "u_Mask");
|
||||
}
|
||||
|
||||
private static ExtendedRenderPipelineBuilder base(String name) {
|
||||
return new ExtendedRenderPipelineBuilder(PipelineTemplates.MESH_UNIFORMS)
|
||||
.withLocation(PipelineTemplates.location(name));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.pipeline.pipelines;
|
||||
|
||||
import com.mojang.blaze3d.PrimitiveTopology;
|
||||
import com.mojang.blaze3d.pipeline.BlendFunction;
|
||||
import com.mojang.blaze3d.pipeline.RenderPipeline;
|
||||
import com.mojang.blaze3d.shaders.UniformType;
|
||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||
import dev.loki.lovisual.render.engine.pipeline.ExtendedRenderPipelineBuilder;
|
||||
import dev.loki.lovisual.render.engine.pipeline.LoVisualRenderPipelines;
|
||||
import dev.loki.lovisual.render.engine.vertex.LoVisualVertexFormats;
|
||||
import net.minecraft.resources.Identifier;
|
||||
|
||||
/**
|
||||
* Factory templates for LoVisual's UI (2D) pipelines: the base colored/textured set and
|
||||
* the batched SDF shape pipelines built on the local-color rect-params vertex formats.
|
||||
* Samplers are declared before extra uniform buffers, matching the original order.
|
||||
*/
|
||||
public final class UiPipelines {
|
||||
private UiPipelines() {
|
||||
}
|
||||
|
||||
/**
|
||||
* UI colored primitives (pos2 + color); the canvas check in the builder still applies.
|
||||
*/
|
||||
public static RenderPipeline colored(String name, PrimitiveTopology topology) {
|
||||
return PipelineTemplates.finish(base(name)
|
||||
.withVertexFormat(LoVisualVertexFormats.POS2_COLOR, topology)
|
||||
.withVertexShader(LoVisualRenderPipelines.SHADER_POS_COLOR_VERT)
|
||||
.withFragmentShader(LoVisualRenderPipelines.SHADER_POS_COLOR_FRAG), false, BlendFunction.TRANSLUCENT, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* UI textured triangles (pos2 + tex + color); {@code uniform} may be null.
|
||||
*/
|
||||
public static RenderPipeline textured(String name, Identifier vertexShader, Identifier fragmentShader,
|
||||
BlendFunction blend, boolean cull, String uniform, String... samplers) {
|
||||
ExtendedRenderPipelineBuilder builder = base(name)
|
||||
.withVertexFormat(LoVisualVertexFormats.POS2_TEXTURE_COLOR, PrimitiveTopology.TRIANGLES)
|
||||
.withVertexShader(vertexShader)
|
||||
.withFragmentShader(fragmentShader);
|
||||
for (String sampler : samplers) {
|
||||
builder.withSampler(sampler);
|
||||
}
|
||||
if (uniform != null) {
|
||||
builder.withUniform(uniform, UniformType.UNIFORM_BUFFER);
|
||||
}
|
||||
return PipelineTemplates.finish(builder, false, blend, cull);
|
||||
}
|
||||
|
||||
/**
|
||||
* UI text with liquid glass sampling; MSDF variants add the "MsdfText" uniform.
|
||||
*/
|
||||
public static RenderPipeline textLiquidGlass(String name, Identifier fragmentShader, boolean msdf) {
|
||||
ExtendedRenderPipelineBuilder builder = base(name)
|
||||
.withVertexFormat(LoVisualVertexFormats.POS2_TEXTURE_COLOR, PrimitiveTopology.TRIANGLES)
|
||||
.withVertexShader(LoVisualRenderPipelines.SHADER_TEXT_VERT)
|
||||
.withFragmentShader(fragmentShader)
|
||||
.withSampler("u_Texture")
|
||||
.withSampler("u_SceneTexture")
|
||||
.withSampler("u_BlurTexture")
|
||||
.withUniform("UIBatch", UniformType.UNIFORM_BUFFER);
|
||||
if (msdf) {
|
||||
builder.withUniform("MsdfText", UniformType.UNIFORM_BUFFER);
|
||||
}
|
||||
return PipelineTemplates.finish(builder, false, BlendFunction.TRANSLUCENT, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Batched SDF shape pipeline (local-color rect-params formats) with the shared UI batch
|
||||
* uniforms; optional texture samplers keep their original declaration order.
|
||||
*/
|
||||
public static RenderPipeline batch(String name, VertexFormat format, Identifier vertexShader, Identifier fragmentShader,
|
||||
BlendFunction blend, boolean cull, String... samplers) {
|
||||
ExtendedRenderPipelineBuilder builder = new ExtendedRenderPipelineBuilder(PipelineTemplates.MESH_UNIFORMS, PipelineTemplates.UI_BATCH_UNIFORMS)
|
||||
.withLocation(PipelineTemplates.location(name))
|
||||
.withVertexFormat(format, PrimitiveTopology.TRIANGLES)
|
||||
.withVertexShader(vertexShader)
|
||||
.withFragmentShader(fragmentShader);
|
||||
for (String sampler : samplers) {
|
||||
builder.withSampler(sampler);
|
||||
}
|
||||
return PipelineTemplates.finish(builder, false, blend, cull);
|
||||
}
|
||||
|
||||
private static ExtendedRenderPipelineBuilder base(String name) {
|
||||
return new ExtendedRenderPipelineBuilder(PipelineTemplates.MESH_UNIFORMS)
|
||||
.withLocation(PipelineTemplates.location(name));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,175 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.pipeline.pipelines;
|
||||
|
||||
import com.mojang.blaze3d.PrimitiveTopology;
|
||||
import com.mojang.blaze3d.pipeline.BlendFunction;
|
||||
import com.mojang.blaze3d.pipeline.RenderPipeline;
|
||||
import com.mojang.blaze3d.shaders.UniformType;
|
||||
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
|
||||
import dev.loki.lovisual.render.engine.pipeline.ExtendedRenderPipelineBuilder;
|
||||
import dev.loki.lovisual.render.engine.pipeline.LoVisualRenderPipelines;
|
||||
import dev.loki.lovisual.render.engine.vertex.LoVisualVertexFormats;
|
||||
import dev.loki.lovisual.features.module.modules.visuals.effects.hits.KillEffect;
|
||||
import dev.loki.lovisual.features.module.modules.visuals.scene.BlockHighlight;
|
||||
import net.minecraft.resources.Identifier;
|
||||
|
||||
/**
|
||||
* Factory templates for LoVisual's world-space (3D) pipelines. They all share the mesh
|
||||
* uniform snippet, disable depth writing and culling; only depth test, blending, shaders
|
||||
* and vertex format vary. Shader identifiers come from the public facade constants.
|
||||
*/
|
||||
public final class WorldPipelines {
|
||||
private WorldPipelines() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Solid colored triangles; {@code depthTest} selects GEQUAL vs no depth test.
|
||||
*/
|
||||
public static RenderPipeline colored(String name, boolean depthTest, BlendFunction blend) {
|
||||
return PipelineTemplates.finish(base(name)
|
||||
.withVertexFormat(DefaultVertexFormat.POSITION_COLOR, PrimitiveTopology.TRIANGLES)
|
||||
.withVertexShader(LoVisualRenderPipelines.SHADER_POS_COLOR_VERT)
|
||||
.withFragmentShader(LoVisualRenderPipelines.SHADER_POS_COLOR_FRAG), depthTest, blend, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Smooth debug lines rendered with the plain color shader.
|
||||
*/
|
||||
public static RenderPipeline coloredLines(String name, boolean depthTest) {
|
||||
return PipelineTemplates.finish(base(name)
|
||||
.withLineSmooth()
|
||||
.withVertexFormat(DefaultVertexFormat.POSITION_COLOR, PrimitiveTopology.DEBUG_LINES)
|
||||
.withVertexShader(LoVisualRenderPipelines.SHADER_POS_COLOR_VERT)
|
||||
.withFragmentShader(LoVisualRenderPipelines.SHADER_POS_COLOR_FRAG), depthTest, BlendFunction.TRANSLUCENT, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Screen-space wide line expansion; triangles, never depends on glLineWidth.
|
||||
*/
|
||||
public static RenderPipeline wideLines(String name, boolean depthTest) {
|
||||
return PipelineTemplates.finish(base(name)
|
||||
.withVertexFormat(LoVisualVertexFormats.POS3_COLOR_LINE, PrimitiveTopology.TRIANGLES)
|
||||
.withVertexShader(LoVisualRenderPipelines.SHADER_WIDE_LINE_VERT)
|
||||
.withFragmentShader(LoVisualRenderPipelines.SHADER_POS_COLOR_FRAG), depthTest, BlendFunction.TRANSLUCENT, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Textured triangles sampling "u_Texture".
|
||||
*/
|
||||
public static RenderPipeline textured(String name, boolean depthTest, BlendFunction blend) {
|
||||
return PipelineTemplates.finish(base(name)
|
||||
.withVertexFormat(DefaultVertexFormat.POSITION_TEX_COLOR, PrimitiveTopology.TRIANGLES)
|
||||
.withVertexShader(LoVisualRenderPipelines.SHADER_POS_TEX_COLOR_VERT)
|
||||
.withFragmentShader(LoVisualRenderPipelines.SHADER_POS_TEX_COLOR_FRAG)
|
||||
.withSampler("u_Texture"), depthTest, blend, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Textured triangles with shader-side tint ("TextureTint" uniform), additive blending.
|
||||
*/
|
||||
public static RenderPipeline tintedTextured(String name, boolean depthTest) {
|
||||
return PipelineTemplates.finish(base(name)
|
||||
.withVertexFormat(DefaultVertexFormat.POSITION_TEX_COLOR, PrimitiveTopology.TRIANGLES)
|
||||
.withVertexShader(LoVisualRenderPipelines.SHADER_POS_TEX_COLOR_VERT)
|
||||
.withFragmentShader(LoVisualRenderPipelines.SHADER_POS_TEX_COLOR_TINT_FRAG)
|
||||
.withSampler("u_Texture")
|
||||
.withUniform("TextureTint", UniformType.UNIFORM_BUFFER), depthTest, PipelineTemplates.ADDITIVE, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* World decal quads with SDF fill/stroke.
|
||||
*/
|
||||
public static RenderPipeline decalSdf(String name, boolean depthTest) {
|
||||
return PipelineTemplates.finish(base(name)
|
||||
.withVertexFormat(LoVisualVertexFormats.POS3_TEXTURE_COLOR_PARAMS2, PrimitiveTopology.TRIANGLES)
|
||||
.withVertexShader(LoVisualRenderPipelines.SHADER_POS_TEX_COLOR_PARAMS2_VERT)
|
||||
.withFragmentShader(LoVisualRenderPipelines.SHADER_WORLD_DECAL_SDF_FRAG), depthTest, BlendFunction.TRANSLUCENT, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* World text quads; MSDF variants add the "MsdfText" uniform after "u_Texture".
|
||||
*/
|
||||
public static RenderPipeline worldText(String name, boolean depthTest, Identifier fragmentShader, boolean msdf) {
|
||||
ExtendedRenderPipelineBuilder builder = base(name)
|
||||
.withVertexFormat(DefaultVertexFormat.POSITION_TEX_COLOR, PrimitiveTopology.TRIANGLES)
|
||||
.withVertexShader(LoVisualRenderPipelines.SHADER_TEXT_VERT)
|
||||
.withFragmentShader(fragmentShader)
|
||||
.withSampler("u_Texture");
|
||||
if (msdf) {
|
||||
builder.withUniform("MsdfText", UniformType.UNIFORM_BUFFER);
|
||||
}
|
||||
return PipelineTemplates.finish(builder, depthTest, BlendFunction.TRANSLUCENT, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Textured triangles with procedural sun shading ("SkySun" uniform), additive.
|
||||
*/
|
||||
public static RenderPipeline skySun(String name) {
|
||||
return PipelineTemplates.finish(base(name)
|
||||
.withVertexFormat(DefaultVertexFormat.POSITION_TEX_COLOR, PrimitiveTopology.TRIANGLES)
|
||||
.withVertexShader(LoVisualRenderPipelines.SHADER_POS_TEX_COLOR_VERT)
|
||||
.withFragmentShader(LoVisualRenderPipelines.SHADER_SKY_SUN_FRAG)
|
||||
.withUniform("SkySun", UniformType.UNIFORM_BUFFER), false, PipelineTemplates.ADDITIVE, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Textured triangles with a procedural KillEffect beam shader ("KillEffectBeam" uniform), additive.
|
||||
*/
|
||||
public static RenderPipeline killEffectBeam(String name, Identifier fragmentShader) {
|
||||
return PipelineTemplates.finish(base(name)
|
||||
.withVertexFormat(DefaultVertexFormat.POSITION_TEX_COLOR, PrimitiveTopology.TRIANGLES)
|
||||
.withVertexShader(LoVisualRenderPipelines.SHADER_POS_TEX_COLOR_VERT)
|
||||
.withFragmentShader(fragmentShader)
|
||||
.withUniform("KillEffectBeam", UniformType.UNIFORM_BUFFER), false, PipelineTemplates.ADDITIVE, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Textured triangles with a procedural BlockHighlight shader outline ("WaterData" uniform), additive.
|
||||
*/
|
||||
public static RenderPipeline waterHighlight(String name, Identifier fragmentShader) {
|
||||
return PipelineTemplates.finish(base(name)
|
||||
.withVertexFormat(DefaultVertexFormat.POSITION_TEX_COLOR, PrimitiveTopology.TRIANGLES)
|
||||
.withVertexShader(LoVisualRenderPipelines.SHADER_POS_TEX_COLOR_VERT)
|
||||
.withFragmentShader(fragmentShader)
|
||||
.withUniform("WaterData", UniformType.UNIFORM_BUFFER), false, PipelineTemplates.ADDITIVE, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Textured skybox with vanilla sky/fog blending ("SkyboxShader" uniform + fog snippet).
|
||||
*/
|
||||
public static RenderPipeline texturedSkyboxFog(String name, Identifier fragmentShader, BlendFunction blend) {
|
||||
return PipelineTemplates.finish(fogBase(name)
|
||||
.withVertexFormat(DefaultVertexFormat.POSITION_TEX_COLOR, PrimitiveTopology.TRIANGLES)
|
||||
.withVertexShader(LoVisualRenderPipelines.SHADER_POS_TEX_COLOR_SKY_FOG_VERT)
|
||||
.withFragmentShader(fragmentShader)
|
||||
.withSampler("u_Texture")
|
||||
.withUniform("SkyboxShader", UniformType.UNIFORM_BUFFER), false, blend, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fullscreen procedural skybox with sky/fog blending ("SkyboxShader" uniform + fog snippet).
|
||||
*/
|
||||
public static RenderPipeline proceduralSkyboxFog(String name, Identifier fragmentShader) {
|
||||
return PipelineTemplates.finish(fogBase(name)
|
||||
.withVertexFormat(LoVisualVertexFormats.POS2, PrimitiveTopology.TRIANGLES)
|
||||
.withVertexShader(LoVisualRenderPipelines.SHADER_DAMAGE_TINT_VERT)
|
||||
.withFragmentShader(fragmentShader)
|
||||
.withUniform("SkyboxShader", UniformType.UNIFORM_BUFFER), false, BlendFunction.TRANSLUCENT, false);
|
||||
}
|
||||
|
||||
private static ExtendedRenderPipelineBuilder base(String name) {
|
||||
return new ExtendedRenderPipelineBuilder(PipelineTemplates.MESH_UNIFORMS)
|
||||
.withLocation(PipelineTemplates.location(name));
|
||||
}
|
||||
|
||||
private static ExtendedRenderPipelineBuilder fogBase(String name) {
|
||||
return new ExtendedRenderPipelineBuilder(PipelineTemplates.MESH_UNIFORMS, PipelineTemplates.FOG_UNIFORMS)
|
||||
.withLocation(PipelineTemplates.location(name));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.postprocess.blur;
|
||||
|
||||
import dev.loki.lovisual.config.values.mode.EnumValue;
|
||||
|
||||
public enum DepthOfFieldDepthSource implements EnumValue.IdProvider {
|
||||
WORLD_SCENE("world_scene"),
|
||||
MAIN("main"),
|
||||
PRE_TRANSLUCENT("pre_translucent"),
|
||||
OFF("off");
|
||||
|
||||
private final String id;
|
||||
|
||||
DepthOfFieldDepthSource(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String id() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.postprocess.blur;
|
||||
|
||||
import dev.loki.lovisual.config.values.mode.EnumValue;
|
||||
|
||||
public enum DepthOfFieldQuality implements EnumValue.IdProvider {
|
||||
LOW("low", 8),
|
||||
MEDIUM("medium", 14),
|
||||
HIGH("high", 22);
|
||||
|
||||
private final String id;
|
||||
private final int taps;
|
||||
|
||||
DepthOfFieldQuality(String id, int taps) {
|
||||
this.id = id;
|
||||
this.taps = taps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public int taps() {
|
||||
return taps;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.postprocess.blur;
|
||||
|
||||
import dev.loki.lovisual.config.values.mode.EnumValue;
|
||||
|
||||
public enum MotionBlurMode implements EnumValue.IdProvider {
|
||||
WORLD_AND_HAND("world_and_hand"),
|
||||
WORLD_ONLY("world_only");
|
||||
|
||||
private final String id;
|
||||
|
||||
MotionBlurMode(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String id() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.postprocess.blur;
|
||||
|
||||
import com.mojang.blaze3d.pipeline.TextureTarget;
|
||||
import com.mojang.blaze3d.textures.GpuTextureView;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import dev.loki.lovisual.render.engine.core.system.LoVisualRenderSystem;
|
||||
import dev.loki.lovisual.render.engine.postprocess.post.PostProcessManager;
|
||||
|
||||
public final class MotionBlurResources {
|
||||
private TextureTarget previousColor;
|
||||
private boolean previousValid;
|
||||
|
||||
public boolean ensure(Minecraft mc) {
|
||||
if (mc == null || mc.getWindow() == null) return false;
|
||||
return ensure(
|
||||
mc.getWindow().getWidth(),
|
||||
mc.getWindow().getHeight()
|
||||
);
|
||||
}
|
||||
|
||||
public boolean ensure(int width, int height) {
|
||||
int w = Math.max(1, width);
|
||||
int h = Math.max(1, height);
|
||||
if (w <= 0 || h <= 0) return false;
|
||||
|
||||
TextureTarget next = LoVisualRenderSystem.resources().persistentFramebuffer(
|
||||
"lovisual-motion-blur-previous-color", w, h, false, "MotionBlurResources");
|
||||
if (previousColor != next) {
|
||||
previousColor = next;
|
||||
previousValid = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public GpuTextureView previousColorView() {
|
||||
return previousColor != null ? previousColor.getColorTextureView() : null;
|
||||
}
|
||||
|
||||
public boolean hasPreviousColor() {
|
||||
return previousValid && previousColorView() != null;
|
||||
}
|
||||
|
||||
public void captureCurrent(GpuTextureView src) {
|
||||
GpuTextureView previous = previousColorView();
|
||||
if (src == null || previous == null) return;
|
||||
PostProcessManager.copy(src, previous);
|
||||
previousValid = true;
|
||||
}
|
||||
|
||||
public void invalidate() {
|
||||
previousValid = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.postprocess.graph;
|
||||
|
||||
import com.mojang.blaze3d.textures.GpuTextureView;
|
||||
import dev.loki.lovisual.render.engine.core.frame.RenderFrameContext;
|
||||
import dev.loki.lovisual.render.engine.postprocess.post.PostProcessContext;
|
||||
import dev.loki.lovisual.render.engine.postprocess.post.PostProcessPass;
|
||||
import dev.loki.lovisual.render.engine.rhi.resource.LoVisualRhi;
|
||||
|
||||
import java.util.Set;
|
||||
import dev.loki.lovisual.render.engine.postprocess.graph.PostProcessGraphPass;
|
||||
import dev.loki.lovisual.render.engine.postprocess.graph.resource.PostProcessGraphResources;
|
||||
import dev.loki.lovisual.render.engine.postprocess.graph.resource.PostProcessResolution;
|
||||
import dev.loki.lovisual.render.engine.postprocess.graph.resource.PostProcessResource;
|
||||
|
||||
public final class LegacyPostProcessGraphPass implements PostProcessGraphPass {
|
||||
private final PostProcessPass delegate;
|
||||
private final String id;
|
||||
|
||||
public LegacyPostProcessGraphPass(PostProcessPass delegate) {
|
||||
this.delegate = delegate;
|
||||
this.id = delegate.getClass().getName();
|
||||
}
|
||||
|
||||
public PostProcessPass delegate() {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int priority() {
|
||||
return delegate.getPriority();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PostProcessPass.Phase phase() {
|
||||
return delegate.getPhase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<PostProcessResource> reads() {
|
||||
return Set.of(PostProcessResource.GRAPH_SOURCE_COLOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<PostProcessResource> writes() {
|
||||
return Set.of(PostProcessResource.GRAPH_DEST_COLOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PostProcessResolution resolution() {
|
||||
return PostProcessResolution.FULL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enabled(RenderFrameContext context) {
|
||||
return delegate.isActive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(RenderFrameContext context, LoVisualRhi rhi, PostProcessGraphResources resources) {
|
||||
GpuTextureView src = resources.currentSource();
|
||||
GpuTextureView dst = resources.currentDestination();
|
||||
PostProcessContext legacyContext = resources.legacyContext();
|
||||
if (src == null || dst == null || legacyContext == null) return false;
|
||||
return delegate.render(legacyContext, src, dst);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.postprocess.graph;
|
||||
|
||||
import com.mojang.blaze3d.textures.GpuTextureView;
|
||||
import dev.loki.lovisual.render.engine.core.frame.RenderFrameContext;
|
||||
import dev.loki.lovisual.render.engine.postprocess.post.PostProcessPass;
|
||||
import dev.loki.lovisual.render.engine.profiler.frame.RenderCostProfiler;
|
||||
import dev.loki.lovisual.render.engine.rhi.resource.LoVisualRhi;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import dev.loki.lovisual.render.engine.postprocess.graph.resource.HistoryBufferManager;
|
||||
import dev.loki.lovisual.render.engine.postprocess.graph.LegacyPostProcessGraphPass;
|
||||
import dev.loki.lovisual.render.engine.postprocess.graph.PostProcessGraphPass;
|
||||
import dev.loki.lovisual.render.engine.postprocess.graph.resource.PostProcessGraphResources;
|
||||
|
||||
public final class PostProcessGraph implements AutoCloseable {
|
||||
private final List<PostProcessGraphPass> passes = new ArrayList<>();
|
||||
private final PostProcessGraphResources resources = new PostProcessGraphResources();
|
||||
private final HistoryBufferManager history = new HistoryBufferManager();
|
||||
|
||||
public void add(PostProcessGraphPass pass) {
|
||||
if (pass == null) return;
|
||||
for (PostProcessGraphPass existing : passes) {
|
||||
if (existing.getId().equals(pass.getId())) return;
|
||||
}
|
||||
passes.add(pass);
|
||||
passes.sort(Comparator
|
||||
.comparingInt(PostProcessGraphPass::priority)
|
||||
.thenComparing(PostProcessGraphPass::id));
|
||||
}
|
||||
|
||||
public void addLegacy(PostProcessPass pass) {
|
||||
if (pass != null) add(new LegacyPostProcessGraphPass(pass));
|
||||
}
|
||||
|
||||
public boolean hasActivePass(PostProcessPass.Phase phase, RenderFrameContext context) {
|
||||
for (PostProcessGraphPass pass : passes) {
|
||||
if (pass.phase() == phase && pass.enabled(context)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes one postprocess phase as a ping-pong graph.
|
||||
* <p>
|
||||
* Flow:
|
||||
* main color -> graph source -> active passes -> final graph source -> main color
|
||||
*/
|
||||
public boolean execute(PostProcessPass.Phase phase,
|
||||
float tickDelta,
|
||||
RenderFrameContext context,
|
||||
LoVisualRhi rhi,
|
||||
GraphCopy copy) {
|
||||
if (!hasActivePass(phase, context)) return false;
|
||||
try (RenderCostProfiler.Scope ignoredGraph = RenderCostProfiler.postPass("graph:", phase.toString())) {
|
||||
if (!resources.prepare(phase, tickDelta)) return false;
|
||||
|
||||
GpuTextureView mainColor = resources.mainColor();
|
||||
GpuTextureView source = resources.currentSource();
|
||||
if (mainColor == null || source == null) return false;
|
||||
|
||||
copy.copy(mainColor, source);
|
||||
resources.resetPingPong();
|
||||
|
||||
boolean anyApplied = false;
|
||||
for (PostProcessGraphPass pass : passes) {
|
||||
if (pass.phase() != phase || !pass.enabled(context)) continue;
|
||||
boolean applied;
|
||||
try (RenderCostProfiler.Scope ignoredPass = RenderCostProfiler.postPass(pass.getId())) {
|
||||
applied = pass.execute(context, rhi, resources);
|
||||
}
|
||||
if (applied) {
|
||||
anyApplied = true;
|
||||
resources.advancePingPong();
|
||||
}
|
||||
}
|
||||
|
||||
if (!anyApplied) return false;
|
||||
GpuTextureView finalColor = resources.finalColor();
|
||||
if (finalColor != null && mainColor != null) {
|
||||
copy.copy(finalColor, mainColor);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public HistoryBufferManager history() {
|
||||
return history;
|
||||
}
|
||||
|
||||
public List<PostProcessGraphPass> passes() {
|
||||
return List.copyOf(passes);
|
||||
}
|
||||
|
||||
public PostProcessGraphResources resources() {
|
||||
return resources;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
resources.close();
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface GraphCopy {
|
||||
void copy(GpuTextureView src, GpuTextureView dst);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.postprocess.graph;
|
||||
|
||||
import dev.loki.lovisual.render.engine.core.frame.RenderFrameContext;
|
||||
import dev.loki.lovisual.render.engine.postprocess.post.PostProcessPass;
|
||||
import dev.loki.lovisual.render.engine.rhi.resource.LoVisualRhi;
|
||||
|
||||
import java.util.Set;
|
||||
import dev.loki.lovisual.render.engine.postprocess.graph.resource.PostProcessGraphResources;
|
||||
import dev.loki.lovisual.render.engine.postprocess.graph.resource.PostProcessResolution;
|
||||
import dev.loki.lovisual.render.engine.postprocess.graph.resource.PostProcessResource;
|
||||
|
||||
public interface PostProcessGraphPass {
|
||||
String id();
|
||||
|
||||
default String getId() {
|
||||
return id();
|
||||
}
|
||||
|
||||
int priority();
|
||||
|
||||
PostProcessPass.Phase phase();
|
||||
|
||||
Set<PostProcessResource> reads();
|
||||
|
||||
Set<PostProcessResource> writes();
|
||||
|
||||
PostProcessResolution resolution();
|
||||
|
||||
boolean enabled(RenderFrameContext context);
|
||||
|
||||
/**
|
||||
* Returns true when the pass wrote its destination and the graph should advance ping-pong state.
|
||||
*/
|
||||
boolean execute(RenderFrameContext context, LoVisualRhi rhi, PostProcessGraphResources resources);
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.postprocess.graph.resource;
|
||||
|
||||
import com.mojang.blaze3d.textures.GpuTextureView;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Owner for temporal/history resources used by motion blur and future temporal effects.
|
||||
*/
|
||||
public final class HistoryBufferManager {
|
||||
private @Nullable GpuTextureView previousColor;
|
||||
private long previousFrameId = -1L;
|
||||
|
||||
public void update(long frameId, @Nullable GpuTextureView finalColor) {
|
||||
previousFrameId = frameId;
|
||||
previousColor = finalColor;
|
||||
}
|
||||
|
||||
public @Nullable GpuTextureView previousColor() {
|
||||
return previousColor;
|
||||
}
|
||||
|
||||
public long previousFrameId() {
|
||||
return previousFrameId;
|
||||
}
|
||||
|
||||
public void invalidate() {
|
||||
previousColor = null;
|
||||
previousFrameId = -1L;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.postprocess.graph.resource;
|
||||
|
||||
import com.mojang.blaze3d.pipeline.RenderTarget;
|
||||
import com.mojang.blaze3d.pipeline.TextureTarget;
|
||||
import com.mojang.blaze3d.textures.GpuTextureView;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import dev.loki.lovisual.features.module.lifecycle.Modules;
|
||||
import dev.loki.lovisual.features.module.modules.visuals.scene.visual.ReimaginedVisual;
|
||||
import dev.loki.lovisual.render.engine.core.system.LoVisualRenderSystem;
|
||||
import dev.loki.lovisual.render.engine.depth.PreTranslucentDepth;
|
||||
import dev.loki.lovisual.render.engine.depth.WorldSceneDepth;
|
||||
import dev.loki.lovisual.render.engine.postprocess.post.PostProcessContext;
|
||||
import dev.loki.lovisual.render.engine.postprocess.post.PostProcessPass;
|
||||
import dev.loki.lovisual.render.iris.scene.IrisSceneDepth;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import dev.loki.lovisual.render.engine.rhi.resource.manager.RenderResourceManager;
|
||||
import dev.loki.lovisual.render.engine.postprocess.graph.PostProcessGraph;
|
||||
import dev.loki.lovisual.render.engine.postprocess.graph.resource.PostProcessResource;
|
||||
|
||||
public final class PostProcessGraphResources implements AutoCloseable {
|
||||
private final EnumMap<PostProcessResource, GpuTextureView> views = new EnumMap<>(PostProcessResource.class);
|
||||
|
||||
private @Nullable RenderTarget mainFramebuffer;
|
||||
private @Nullable TextureTarget ping;
|
||||
private @Nullable TextureTarget pong;
|
||||
private @Nullable GpuTextureView pingView;
|
||||
private @Nullable GpuTextureView pongView;
|
||||
private @Nullable PostProcessContext legacyContext;
|
||||
|
||||
private boolean usePingAsSource = true;
|
||||
private boolean prepared;
|
||||
|
||||
public boolean prepare(PostProcessPass.Phase phase, float tickDelta) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || mc.getWindow() == null) return false;
|
||||
RenderTarget main = mc.gameRenderer.mainRenderTarget();
|
||||
if (main == null) return false;
|
||||
|
||||
int w = Math.max(1, mc.getWindow().getWidth());
|
||||
int h = Math.max(1, mc.getWindow().getHeight());
|
||||
ensurePingPong(w, h);
|
||||
if (ping == null || pong == null) return false;
|
||||
|
||||
pingView = ping.getColorTextureView();
|
||||
pongView = pong.getColorTextureView();
|
||||
if (main.getColorTextureView() == null || pingView == null || pongView == null) return false;
|
||||
|
||||
clear();
|
||||
this.mainFramebuffer = main;
|
||||
GpuTextureView mainDepth = IrisSceneDepth.isValid()
|
||||
? IrisSceneDepth.mainDepthView()
|
||||
: WorldSceneDepth.hasMain() ? WorldSceneDepth.mainDepthView() : main.getDepthTextureView();
|
||||
GpuTextureView preTranslucentDepth = IrisSceneDepth.isValid()
|
||||
? IrisSceneDepth.preTranslucentDepthView()
|
||||
: needsPreTranslucentDepth() ? PreTranslucentDepth.getDepthView() : null;
|
||||
GpuTextureView staticWorldDepth = IrisSceneDepth.isValid()
|
||||
? null
|
||||
: WorldSceneDepth.hasItemEntity() ? WorldSceneDepth.itemEntityDepthView() : null;
|
||||
put(PostProcessResource.MAIN_COLOR, main.getColorTextureView());
|
||||
put(PostProcessResource.MAIN_DEPTH, mainDepth);
|
||||
put(PostProcessResource.PRE_TRANSLUCENT_DEPTH, preTranslucentDepth);
|
||||
put(PostProcessResource.GRAPH_SOURCE_COLOR, pingView);
|
||||
put(PostProcessResource.GRAPH_DEST_COLOR, pongView);
|
||||
this.legacyContext = new PostProcessContext(
|
||||
phase,
|
||||
tickDelta,
|
||||
main,
|
||||
main.getColorTextureView(),
|
||||
mainDepth,
|
||||
preTranslucentDepth,
|
||||
staticWorldDepth,
|
||||
main.width,
|
||||
main.height
|
||||
);
|
||||
this.usePingAsSource = true;
|
||||
this.prepared = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void resetPingPong() {
|
||||
usePingAsSource = true;
|
||||
put(PostProcessResource.GRAPH_SOURCE_COLOR, pingView);
|
||||
put(PostProcessResource.GRAPH_DEST_COLOR, pongView);
|
||||
}
|
||||
|
||||
public void advancePingPong() {
|
||||
usePingAsSource = !usePingAsSource;
|
||||
put(PostProcessResource.GRAPH_SOURCE_COLOR, currentSource());
|
||||
put(PostProcessResource.GRAPH_DEST_COLOR, currentDestination());
|
||||
}
|
||||
|
||||
public @Nullable GpuTextureView currentSource() {
|
||||
return usePingAsSource ? pingView : pongView;
|
||||
}
|
||||
|
||||
public @Nullable GpuTextureView currentDestination() {
|
||||
return usePingAsSource ? pongView : pingView;
|
||||
}
|
||||
|
||||
public @Nullable GpuTextureView finalColor() {
|
||||
return currentSource();
|
||||
}
|
||||
|
||||
public boolean isPrepared() {
|
||||
return prepared;
|
||||
}
|
||||
|
||||
public @Nullable RenderTarget mainFramebuffer() {
|
||||
return mainFramebuffer;
|
||||
}
|
||||
|
||||
public @Nullable GpuTextureView mainColor() {
|
||||
return get(PostProcessResource.MAIN_COLOR);
|
||||
}
|
||||
|
||||
public @Nullable PostProcessContext legacyContext() {
|
||||
return legacyContext;
|
||||
}
|
||||
|
||||
public void put(PostProcessResource resource, @Nullable GpuTextureView view) {
|
||||
if (view != null) views.put(resource, view);
|
||||
}
|
||||
|
||||
public @Nullable GpuTextureView get(PostProcessResource resource) {
|
||||
return views.get(resource);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
views.clear();
|
||||
mainFramebuffer = null;
|
||||
legacyContext = null;
|
||||
prepared = false;
|
||||
}
|
||||
|
||||
private void ensurePingPong(int w, int h) {
|
||||
ping = LoVisualRenderSystem.resources().persistentFramebuffer(
|
||||
"lovisual-postprocess-graph-ping", w, h, false, "PostProcessGraph");
|
||||
pong = LoVisualRenderSystem.resources().persistentFramebuffer(
|
||||
"lovisual-postprocess-graph-pong", w, h, false, "PostProcessGraph");
|
||||
}
|
||||
|
||||
private static boolean needsPreTranslucentDepth() {
|
||||
ReimaginedVisual module = Modules.get(ReimaginedVisual.class);
|
||||
return module != null && module.needsPreTranslucentDepthCapture();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// Framebuffers are owned by RenderResourceManager. This object only drops references.
|
||||
ping = null;
|
||||
pong = null;
|
||||
pingView = null;
|
||||
pongView = null;
|
||||
clear();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.postprocess.graph.resource;
|
||||
|
||||
public enum PostProcessResolution {
|
||||
FULL,
|
||||
HALF,
|
||||
QUARTER,
|
||||
CUSTOM
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.postprocess.graph.resource;
|
||||
|
||||
public enum PostProcessResource {
|
||||
MAIN_COLOR,
|
||||
MAIN_DEPTH,
|
||||
PRE_TRANSLUCENT_DEPTH,
|
||||
HISTORY_COLOR,
|
||||
GRAPH_SOURCE_COLOR,
|
||||
GRAPH_DEST_COLOR,
|
||||
TEMP_COLOR,
|
||||
CUSTOM
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.postprocess.post;
|
||||
|
||||
import com.mojang.blaze3d.pipeline.RenderTarget;
|
||||
import com.mojang.blaze3d.textures.GpuTextureView;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import dev.loki.lovisual.render.engine.postprocess.post.PostProcessPass;
|
||||
|
||||
public record PostProcessContext(
|
||||
PostProcessPass.Phase phase,
|
||||
float tickDelta,
|
||||
RenderTarget mainFramebuffer,
|
||||
GpuTextureView mainColor,
|
||||
@Nullable GpuTextureView mainDepth,
|
||||
@Nullable GpuTextureView preTranslucentDepth,
|
||||
@Nullable GpuTextureView staticWorldDepth,
|
||||
int width,
|
||||
int height
|
||||
) {
|
||||
}
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.postprocess.post;
|
||||
|
||||
import com.mojang.blaze3d.ProjectionType;
|
||||
import com.mojang.blaze3d.buffers.GpuBufferSlice;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.mojang.blaze3d.textures.AddressMode;
|
||||
import com.mojang.blaze3d.textures.FilterMode;
|
||||
import com.mojang.blaze3d.textures.GpuSampler;
|
||||
import com.mojang.blaze3d.textures.GpuTextureView;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import org.joml.Matrix4f;
|
||||
import dev.loki.lovisual.render.engine.RenderState;
|
||||
import dev.loki.lovisual.render.engine.core.system.LoVisualRenderSystem;
|
||||
import dev.loki.lovisual.render.engine.pipeline.LoVisualRenderPipelines;
|
||||
import dev.loki.lovisual.render.engine.postprocess.graph.PostProcessGraph;
|
||||
import dev.loki.lovisual.render.engine.profiler.frame.RenderCostProfiler;
|
||||
import dev.loki.lovisual.render.engine.renderer.FullScreenRenderer;
|
||||
import dev.loki.lovisual.render.engine.renderer.MeshRenderer;
|
||||
import dev.loki.lovisual.render.engine.rhi.command.FullscreenDrawCommand;
|
||||
import dev.loki.lovisual.render.engine.uniform.impl.post.PostProcessUniforms;
|
||||
import dev.loki.lovisual.Lifecycle;
|
||||
|
||||
import java.util.OptionalDouble;
|
||||
import dev.loki.lovisual.render.engine.postprocess.post.PostProcessPass;
|
||||
|
||||
public enum PostProcessManager {
|
||||
;
|
||||
private static final PostProcessGraph GRAPH = new PostProcessGraph();
|
||||
private static GpuSampler sampler;
|
||||
|
||||
public static void register(PostProcessPass pass) {
|
||||
GRAPH.addLegacy(pass);
|
||||
}
|
||||
|
||||
public static PostProcessGraph graph() {
|
||||
return GRAPH;
|
||||
}
|
||||
|
||||
public static void renderAll(PostProcessPass.Phase phase, float tickDelta) {
|
||||
if (!Lifecycle.canRunRender()) return;
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || mc.gameRenderer.mainRenderTarget() == null) return;
|
||||
|
||||
FullScreenRenderer.ensureInit();
|
||||
ensureSampler();
|
||||
|
||||
var mv = RenderSystem.getModelViewStack();
|
||||
Matrix4f previousProjection = MeshRenderer.projection();
|
||||
GpuBufferSlice previousProjectionBuffer = RenderSystem.getProjectionMatrixBuffer();
|
||||
ProjectionType previousProjectionType = RenderSystem.getProjectionType();
|
||||
boolean previousRendering3D = RenderState.rendering3D;
|
||||
mv.pushMatrix();
|
||||
mv.identity();
|
||||
MeshRenderer.setProjection(new Matrix4f().identity());
|
||||
RenderState.rendering3D = false;
|
||||
|
||||
try {
|
||||
GRAPH.execute(
|
||||
phase,
|
||||
tickDelta,
|
||||
LoVisualRenderSystem.ensureFrameContext(),
|
||||
LoVisualRenderSystem.rhi(),
|
||||
PostProcessManager::copy
|
||||
);
|
||||
} finally {
|
||||
MeshRenderer.setProjection(previousProjection);
|
||||
if (previousProjectionBuffer != null && previousProjectionType != null) {
|
||||
RenderSystem.setProjectionMatrix(previousProjectionBuffer, previousProjectionType);
|
||||
}
|
||||
RenderState.rendering3D = previousRendering3D;
|
||||
mv.popMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
private static void ensureSampler() {
|
||||
if (sampler != null) return;
|
||||
sampler = RenderSystem.getDevice().createSampler(
|
||||
AddressMode.CLAMP_TO_EDGE,
|
||||
AddressMode.CLAMP_TO_EDGE,
|
||||
FilterMode.LINEAR,
|
||||
FilterMode.LINEAR,
|
||||
1,
|
||||
OptionalDouble.empty()
|
||||
);
|
||||
}
|
||||
|
||||
public static GpuSampler getSampler() {
|
||||
ensureSampler();
|
||||
return sampler;
|
||||
}
|
||||
|
||||
public static void copy(GpuTextureView src, GpuTextureView dst) {
|
||||
try (RenderCostProfiler.Scope ignored = RenderCostProfiler.postPass("copy")) {
|
||||
if (src == null || dst == null || src == dst) return;
|
||||
if (LoVisualRenderSystem.rhi().textureBlitter().copyFast(src, dst)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ensureSampler();
|
||||
PostProcessUniforms.update(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
LoVisualRenderSystem.rhi().drawFullscreen(
|
||||
FullscreenDrawCommand.builder("LoVisual PostProcess Copy")
|
||||
.colorAttachment(dst)
|
||||
.pipeline(LoVisualRenderPipelines.DAMAGE_TINT)
|
||||
.uniform("PostProcess", PostProcessUniforms.get())
|
||||
.sampler("u_Texture", src, sampler)
|
||||
.build()
|
||||
);
|
||||
LoVisualRenderSystem.rhi().stats().textureShaderCopy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.postprocess.post;
|
||||
|
||||
import com.mojang.blaze3d.textures.GpuTextureView;
|
||||
import dev.loki.lovisual.render.engine.postprocess.post.PostProcessContext;
|
||||
|
||||
public interface PostProcessPass {
|
||||
boolean isActive();
|
||||
|
||||
boolean render(GpuTextureView src, GpuTextureView dst, float tickDelta);
|
||||
|
||||
default boolean render(PostProcessContext context, GpuTextureView src, GpuTextureView dst) {
|
||||
return render(src, dst, context != null ? context.tickDelta() : 0.0f);
|
||||
}
|
||||
|
||||
default int getPriority() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
default Phase getPhase() {
|
||||
return Phase.PRE_HAND;
|
||||
}
|
||||
|
||||
enum Phase {PRE_HAND, POST_HAND}
|
||||
}
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.postprocess.render;
|
||||
|
||||
import dev.loki.lovisual.features.theme.Theme;
|
||||
import com.mojang.blaze3d.ProjectionType;
|
||||
import com.mojang.blaze3d.buffers.GpuBufferSlice;
|
||||
import com.mojang.blaze3d.pipeline.RenderTarget;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import dev.loki.lovisual.render.engine.renderer.Renderer2D;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.ProjectionMatrixBuffer;
|
||||
import net.minecraft.util.Util;
|
||||
import org.joml.Matrix4f;
|
||||
import dev.loki.lovisual.config.i18n.MainConfig;
|
||||
import dev.loki.lovisual.features.theme.Themes;
|
||||
import dev.loki.lovisual.render.engine.RenderState;
|
||||
import dev.loki.lovisual.render.engine.pipeline.LoVisualRenderPipelines;
|
||||
import dev.loki.lovisual.render.engine.renderer.FullScreenRenderer;
|
||||
import dev.loki.lovisual.render.engine.renderer.MeshRenderer;
|
||||
import dev.loki.lovisual.render.engine.uniform.impl.ui.MenuBackgroundUniforms;
|
||||
import dev.loki.lovisual.features.module.lifecycle.Modules;
|
||||
import dev.loki.lovisual.features.module.modules.visuals.optimization.FpsOptimizer;
|
||||
|
||||
public enum MenuBackgroundRenderer {
|
||||
;
|
||||
private static final Matrix4f IDENTITY = new Matrix4f();
|
||||
private static final int DEFAULT_ACCENT_RGB = 0x5CC8E7;
|
||||
private static final int DEFAULT_BG_RGB = 0x0E1720;
|
||||
private static ProjectionMatrixBuffer projection;
|
||||
private static boolean deferredPending;
|
||||
private static boolean deferredAurora;
|
||||
|
||||
public static void render(Minecraft mc) {
|
||||
render(mc, false);
|
||||
}
|
||||
|
||||
public static void render(Minecraft mc, boolean aurora) {
|
||||
|
||||
if (mc == null) return;
|
||||
FpsOptimizer fpsOptimizer = Modules.get(FpsOptimizer.class);
|
||||
if (fpsOptimizer != null && fpsOptimizer.isMenuBackgroundDisabled()) return;
|
||||
if (Renderer2D.isDeferredExtractRecording()) {
|
||||
deferredPending = true;
|
||||
deferredAurora = aurora;
|
||||
return;
|
||||
}
|
||||
|
||||
renderNow(mc, aurora);
|
||||
}
|
||||
|
||||
public static void drainDeferred(Minecraft mc) {
|
||||
if (!deferredPending) return;
|
||||
boolean aurora = deferredAurora;
|
||||
deferredPending = false;
|
||||
renderNow(mc, aurora);
|
||||
}
|
||||
|
||||
private static void renderNow(Minecraft mc, boolean aurora) {
|
||||
|
||||
if (mc == null) return;
|
||||
|
||||
RenderTarget fb = mc.gameRenderer.mainRenderTarget();
|
||||
if (fb == null) return;
|
||||
|
||||
int w = mc.getWindow().getWidth();
|
||||
int h = mc.getWindow().getHeight();
|
||||
if (w <= 0 || h <= 0) return;
|
||||
|
||||
FullScreenRenderer.ensureInit();
|
||||
|
||||
float time = (float) (Util.getMillis() / 1000.0);
|
||||
int accentRgb = DEFAULT_ACCENT_RGB;
|
||||
int bgRgb = DEFAULT_BG_RGB;
|
||||
MainConfig cfg = MainConfig.get();
|
||||
if (cfg != null && cfg.isMenuBackgroundUseTheme()) {
|
||||
Themes.Theme theme = Theme.theme();
|
||||
if (theme != null) {
|
||||
accentRgb = theme.accent() & 0x00FFFFFF;
|
||||
bgRgb = theme.windowBg() & 0x00FFFFFF;
|
||||
}
|
||||
}
|
||||
MenuBackgroundUniforms.update(w, h, time, accentRgb, bgRgb);
|
||||
|
||||
var mv = RenderSystem.getModelViewStack();
|
||||
boolean pushedModelView = false;
|
||||
|
||||
GpuBufferSlice prevProj = RenderSystem.getProjectionMatrixBuffer();
|
||||
ProjectionType prevProjType = RenderSystem.getProjectionType();
|
||||
Matrix4f prevMeshProjection = MeshRenderer.projection();
|
||||
boolean prevRendering3D = RenderState.rendering3D;
|
||||
|
||||
try {
|
||||
mv.pushMatrix();
|
||||
pushedModelView = true;
|
||||
mv.identity();
|
||||
|
||||
if (projection == null) {
|
||||
projection = new ProjectionMatrixBuffer("lovisual-menu-bg-projection");
|
||||
}
|
||||
Matrix4f proj = IDENTITY.identity();
|
||||
RenderSystem.setProjectionMatrix(projection.getBuffer(proj), ProjectionType.PERSPECTIVE);
|
||||
MeshRenderer.setProjection(proj);
|
||||
RenderState.rendering3D = false;
|
||||
|
||||
var pipeline = aurora
|
||||
? LoVisualRenderPipelines.MENU_BACKGROUND_AURORA
|
||||
: LoVisualRenderPipelines.MENU_BACKGROUND_WAVES;
|
||||
|
||||
FullScreenRenderer.begin("LoVisual Fullscreen Pass")
|
||||
.attachment(fb)
|
||||
.pipeline(pipeline)
|
||||
.uniform("MenuBackground", MenuBackgroundUniforms.get())
|
||||
.end();
|
||||
} finally {
|
||||
if (pushedModelView) {
|
||||
mv.popMatrix();
|
||||
}
|
||||
MeshRenderer.setProjection(prevMeshProjection);
|
||||
RenderState.rendering3D = prevRendering3D;
|
||||
if (prevProj != null && prevProjType != null) {
|
||||
RenderSystem.setProjectionMatrix(prevProj, prevProjType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,194 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.postprocess.render;
|
||||
|
||||
import com.mojang.blaze3d.ProjectionType;
|
||||
import com.mojang.blaze3d.buffers.GpuBufferSlice;
|
||||
import com.mojang.blaze3d.pipeline.RenderPipeline;
|
||||
import com.mojang.blaze3d.pipeline.RenderTarget;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import dev.loki.lovisual.render.engine.RenderState;
|
||||
import dev.loki.lovisual.render.engine.pipeline.LoVisualRenderPipelines;
|
||||
import dev.loki.lovisual.render.engine.renderer.FullScreenRenderer;
|
||||
import dev.loki.lovisual.render.engine.renderer.MeshRenderer;
|
||||
import dev.loki.lovisual.render.engine.uniform.impl.sky.postprocess.SkyShaderNebulaUniforms;
|
||||
import dev.loki.lovisual.render.engine.uniform.impl.sky.postprocess.SkyShaderUniforms;
|
||||
import net.minecraft.client.Camera;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.ProjectionMatrixBuffer;
|
||||
import net.minecraft.util.Util;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Quaternionf;
|
||||
|
||||
/**
|
||||
* Fullscreen SkyShader pass: sky / water / caustic procedural GLSL effect drawn behind the world,
|
||||
* with a camera-direction-driven ray so it reads correctly regardless of look direction.
|
||||
*/
|
||||
public enum SkyShaderRenderer {
|
||||
;
|
||||
|
||||
public enum Mode {
|
||||
SKY, WATER, CAUSTIC, NEBULA
|
||||
}
|
||||
|
||||
private static final Matrix4f IDENTITY = new Matrix4f();
|
||||
private static ProjectionMatrixBuffer projection;
|
||||
private static long startMillis = -1L;
|
||||
|
||||
public static void reset() {
|
||||
startMillis = -1L;
|
||||
}
|
||||
|
||||
public static void render(Minecraft mc, Mode mode, float speed, float scale, float intensity,
|
||||
float alpha, int colorRgb) {
|
||||
if (mc == null || mc.level == null || mc.player == null) return;
|
||||
|
||||
RenderTarget fb = mc.gameRenderer.mainRenderTarget();
|
||||
if (fb == null) return;
|
||||
|
||||
int w = mc.getWindow().getWidth();
|
||||
int h = mc.getWindow().getHeight();
|
||||
if (w <= 0 || h <= 0) return;
|
||||
|
||||
FullScreenRenderer.ensureInit();
|
||||
|
||||
if (startMillis < 0) startMillis = Util.getMillis();
|
||||
float time = (Util.getMillis() - startMillis) / 1000.0f;
|
||||
|
||||
float cr = ((colorRgb >>> 16) & 0xFF) / 255f;
|
||||
float cg = ((colorRgb >>> 8) & 0xFF) / 255f;
|
||||
float cb = (colorRgb & 0xFF) / 255f;
|
||||
|
||||
Camera cam = mc.gameRenderer.mainCamera();
|
||||
float yawRad = (float) Math.toRadians(-cam.yRot());
|
||||
float pitchRad = (float) Math.toRadians(cam.xRot());
|
||||
float fov = (float) mc.options.fov().get().intValue();
|
||||
|
||||
SkyShaderUniforms.update(time, speed, scale, intensity, w, h, alpha, fov, cr, cg, cb, yawRad, pitchRad);
|
||||
|
||||
var mv = RenderSystem.getModelViewStack();
|
||||
boolean pushedModelView = false;
|
||||
|
||||
GpuBufferSlice prevProj = RenderSystem.getProjectionMatrixBuffer();
|
||||
ProjectionType prevProjType = RenderSystem.getProjectionType();
|
||||
Matrix4f prevMeshProjection = MeshRenderer.projection();
|
||||
boolean prevRendering3D = RenderState.rendering3D;
|
||||
|
||||
try {
|
||||
mv.pushMatrix();
|
||||
pushedModelView = true;
|
||||
mv.identity();
|
||||
|
||||
if (projection == null) {
|
||||
projection = new ProjectionMatrixBuffer("lovisual-sky-shader-projection");
|
||||
}
|
||||
Matrix4f proj = IDENTITY.identity();
|
||||
RenderSystem.setProjectionMatrix(projection.getBuffer(proj), ProjectionType.PERSPECTIVE);
|
||||
MeshRenderer.setProjection(proj);
|
||||
RenderState.rendering3D = false;
|
||||
|
||||
RenderPipeline pipeline = switch (mode) {
|
||||
case WATER -> LoVisualRenderPipelines.SKY_SHADER_WATER;
|
||||
case CAUSTIC -> LoVisualRenderPipelines.SKY_SHADER_CAUSTIC;
|
||||
default -> LoVisualRenderPipelines.SKY_SHADER_SKY;
|
||||
};
|
||||
|
||||
FullScreenRenderer.begin("LoVisual Fullscreen Pass")
|
||||
.attachment(fb)
|
||||
.pipeline(pipeline)
|
||||
.uniform("SkyShader", SkyShaderUniforms.get())
|
||||
.end();
|
||||
} finally {
|
||||
if (pushedModelView) {
|
||||
mv.popMatrix();
|
||||
}
|
||||
MeshRenderer.setProjection(prevMeshProjection);
|
||||
RenderState.rendering3D = prevRendering3D;
|
||||
if (prevProj != null && prevProjType != null) {
|
||||
RenderSystem.setProjectionMatrix(prevProj, prevProjType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Domain-warped nebula sky (quaternion-based ray dir instead of yaw/pitch). */
|
||||
public static void renderNebula(Minecraft mc, float speed, float intensity, float alpha,
|
||||
int nebColor1Rgb, int nebColor2Rgb, int starColorRgb) {
|
||||
if (mc == null || mc.level == null || mc.player == null) return;
|
||||
|
||||
RenderTarget fb = mc.gameRenderer.mainRenderTarget();
|
||||
if (fb == null) return;
|
||||
|
||||
int w = mc.getWindow().getWidth();
|
||||
int h = mc.getWindow().getHeight();
|
||||
if (w <= 0 || h <= 0) return;
|
||||
|
||||
FullScreenRenderer.ensureInit();
|
||||
|
||||
if (startMillis < 0) startMillis = Util.getMillis();
|
||||
float time = (Util.getMillis() - startMillis) / 1000.0f;
|
||||
|
||||
Camera cam = mc.gameRenderer.mainCamera();
|
||||
Quaternionf rot = cam.rotation();
|
||||
float fov = (float) mc.options.fov().get().intValue();
|
||||
|
||||
SkyShaderNebulaUniforms.update(w, h, time, speed, intensity, alpha, fov,
|
||||
rot.x(), rot.y(), rot.z(), rot.w(),
|
||||
channelR(nebColor1Rgb), channelG(nebColor1Rgb), channelB(nebColor1Rgb),
|
||||
channelR(nebColor2Rgb), channelG(nebColor2Rgb), channelB(nebColor2Rgb),
|
||||
channelR(starColorRgb), channelG(starColorRgb), channelB(starColorRgb));
|
||||
|
||||
var mv = RenderSystem.getModelViewStack();
|
||||
boolean pushedModelView = false;
|
||||
|
||||
GpuBufferSlice prevProj = RenderSystem.getProjectionMatrixBuffer();
|
||||
ProjectionType prevProjType = RenderSystem.getProjectionType();
|
||||
Matrix4f prevMeshProjection = MeshRenderer.projection();
|
||||
boolean prevRendering3D = RenderState.rendering3D;
|
||||
|
||||
try {
|
||||
mv.pushMatrix();
|
||||
pushedModelView = true;
|
||||
mv.identity();
|
||||
|
||||
if (projection == null) {
|
||||
projection = new ProjectionMatrixBuffer("lovisual-sky-shader-projection");
|
||||
}
|
||||
Matrix4f proj = IDENTITY.identity();
|
||||
RenderSystem.setProjectionMatrix(projection.getBuffer(proj), ProjectionType.PERSPECTIVE);
|
||||
MeshRenderer.setProjection(proj);
|
||||
RenderState.rendering3D = false;
|
||||
|
||||
FullScreenRenderer.begin("LoVisual Fullscreen Pass")
|
||||
.attachment(fb)
|
||||
.pipeline(LoVisualRenderPipelines.SKY_SHADER_NEBULA)
|
||||
.uniform("SkyShaderData", SkyShaderNebulaUniforms.get())
|
||||
.end();
|
||||
} finally {
|
||||
if (pushedModelView) {
|
||||
mv.popMatrix();
|
||||
}
|
||||
MeshRenderer.setProjection(prevMeshProjection);
|
||||
RenderState.rendering3D = prevRendering3D;
|
||||
if (prevProj != null && prevProjType != null) {
|
||||
RenderSystem.setProjectionMatrix(prevProj, prevProjType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static float channelR(int rgb) {
|
||||
return ((rgb >>> 16) & 0xFF) / 255f;
|
||||
}
|
||||
|
||||
private static float channelG(int rgb) {
|
||||
return ((rgb >>> 8) & 0xFF) / 255f;
|
||||
}
|
||||
|
||||
private static float channelB(int rgb) {
|
||||
return (rgb & 0xFF) / 255f;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.postprocess.render;
|
||||
|
||||
import com.mojang.blaze3d.ProjectionType;
|
||||
import com.mojang.blaze3d.buffers.GpuBufferSlice;
|
||||
import com.mojang.blaze3d.pipeline.RenderTarget;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import dev.loki.lovisual.render.engine.RenderState;
|
||||
import dev.loki.lovisual.features.module.modules.visuals.weather.WorldTweaks;
|
||||
import dev.loki.lovisual.render.engine.pipeline.LoVisualRenderPipelines;
|
||||
import dev.loki.lovisual.render.sky.CustomSkyboxRenderer;
|
||||
import dev.loki.lovisual.render.engine.renderer.FullScreenRenderer;
|
||||
import dev.loki.lovisual.render.engine.renderer.MeshRenderer;
|
||||
import dev.loki.lovisual.render.engine.uniform.impl.sky.postprocess.SkyShaderStarryUniforms;
|
||||
import net.minecraft.client.Camera;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.ProjectionMatrixBuffer;
|
||||
import net.minecraft.util.Util;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Quaternionf;
|
||||
|
||||
/**
|
||||
* Fullscreen "Starry" sky pass: procedural star field + milky way band + a round moon
|
||||
* placed opposite the sun (driven by the world day cycle). Mirrors the scaffolding of
|
||||
* {@link SkyShaderRenderer#renderNebula}; split out to keep both files within the size limit.
|
||||
*/
|
||||
public enum SkyShaderStarryRenderer {
|
||||
;
|
||||
private static final Matrix4f IDENTITY = new Matrix4f();
|
||||
private static ProjectionMatrixBuffer projection;
|
||||
private static long startMillis = -1L;
|
||||
|
||||
public static void reset() {
|
||||
startMillis = -1L;
|
||||
}
|
||||
|
||||
public static void render(Minecraft mc, float speed, float intensity, float alpha,
|
||||
float starDensity, float milkyWayIntensity, float moonSize,
|
||||
boolean showMoon, int moonColorArgb, int starColorArgb) {
|
||||
if (mc == null || mc.level == null || mc.player == null) return;
|
||||
|
||||
RenderTarget fb = mc.gameRenderer.mainRenderTarget();
|
||||
if (fb == null) return;
|
||||
|
||||
int w = mc.getWindow().getWidth();
|
||||
int h = mc.getWindow().getHeight();
|
||||
if (w <= 0 || h <= 0) return;
|
||||
|
||||
FullScreenRenderer.ensureInit();
|
||||
if (startMillis < 0) startMillis = Util.getMillis();
|
||||
float time = (Util.getMillis() - startMillis) / 1000.0f;
|
||||
|
||||
Camera cam = mc.gameRenderer.mainCamera();
|
||||
Quaternionf rot = cam.rotation();
|
||||
float fov = (float) mc.options.fov().get().intValue();
|
||||
|
||||
float[] moonDir = moonDirection(mc);
|
||||
if (!showMoon) {
|
||||
// The shader clamps the angular radius, so hiding the moon means pointing it
|
||||
// below the horizon where horizonFade discards it.
|
||||
moonDir = new float[]{0f, -1f, 0f};
|
||||
}
|
||||
float moonPhase = ((WorldTweaks.getServerTimeOfDay() / 24000f) % 8f) / 8f;
|
||||
|
||||
SkyShaderStarryUniforms.update(w, h, time, speed, intensity, alpha, fov,
|
||||
rot.x(), rot.y(), rot.z(), rot.w(),
|
||||
moonDir[0], moonDir[1], moonDir[2], moonSize,
|
||||
moonPhase, milkyWayIntensity, starDensity,
|
||||
channel(moonColorArgb, 16), channel(moonColorArgb, 8), channel(moonColorArgb, 0),
|
||||
channel(starColorArgb, 16), channel(starColorArgb, 8), channel(starColorArgb, 0));
|
||||
|
||||
var mv = RenderSystem.getModelViewStack();
|
||||
boolean pushedModelView = false;
|
||||
GpuBufferSlice prevProj = RenderSystem.getProjectionMatrixBuffer();
|
||||
ProjectionType prevProjType = RenderSystem.getProjectionType();
|
||||
Matrix4f prevMeshProjection = MeshRenderer.projection();
|
||||
boolean prevRendering3D = RenderState.rendering3D;
|
||||
|
||||
try {
|
||||
mv.pushMatrix();
|
||||
pushedModelView = true;
|
||||
mv.identity();
|
||||
|
||||
if (projection == null) {
|
||||
projection = new ProjectionMatrixBuffer("lovisual-sky-shader-starry-projection");
|
||||
}
|
||||
Matrix4f proj = IDENTITY.identity();
|
||||
RenderSystem.setProjectionMatrix(projection.getBuffer(proj), ProjectionType.PERSPECTIVE);
|
||||
MeshRenderer.setProjection(proj);
|
||||
RenderState.rendering3D = false;
|
||||
|
||||
FullScreenRenderer.begin("LoVisual Fullscreen Pass")
|
||||
.attachment(fb)
|
||||
.pipeline(LoVisualRenderPipelines.SKY_SHADER_STARRY)
|
||||
.uniform("SkyShaderStarry", SkyShaderStarryUniforms.get())
|
||||
.end();
|
||||
} finally {
|
||||
if (pushedModelView) {
|
||||
mv.popMatrix();
|
||||
}
|
||||
MeshRenderer.setProjection(prevMeshProjection);
|
||||
RenderState.rendering3D = prevRendering3D;
|
||||
if (prevProj != null && prevProjType != null) {
|
||||
RenderSystem.setProjectionMatrix(prevProj, prevProjType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* World-space direction to the moon: opposite the sun along its day arc
|
||||
* (CustomSkyboxRenderer sunDir = (-sin a, cos a, 0), so moon = (sin a, -cos a, 0)).
|
||||
* Sun angle comes from the last rendered sky pass; fallback derives it from the day clock.
|
||||
*
|
||||
* @return normalized xyz
|
||||
*/
|
||||
private static float[] moonDirection(Minecraft mc) {
|
||||
float sunAngleRad;
|
||||
if (CustomSkyboxRenderer.hasLastSunAngle()) {
|
||||
sunAngleRad = CustomSkyboxRenderer.getLastSunAngle();
|
||||
} else {
|
||||
float timeOfDay = (WorldTweaks.getServerTimeOfDay() % 24000L) / 24000f;
|
||||
sunAngleRad = timeOfDay * (float) (Math.PI * 2.0);
|
||||
}
|
||||
float x = (float) Math.sin(sunAngleRad);
|
||||
float y = (float) -Math.cos(sunAngleRad);
|
||||
return new float[]{x, y, 0f};
|
||||
}
|
||||
|
||||
private static float channel(int argb, int shift) {
|
||||
return ((argb >>> shift) & 0xFF) / 255f;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.prewarm;
|
||||
|
||||
import dev.loki.lovisual.events.meta.EventHandler;
|
||||
import dev.loki.lovisual.events.impl.render.RenderPrewarmCollectEvent;
|
||||
import dev.loki.lovisual.features.gui.clickgui.layout.screen.modules.model.ModulesMenuCategory;
|
||||
import dev.loki.lovisual.features.gui.clickgui.layout.screen.settings.MenuScreen;
|
||||
import dev.loki.lovisual.features.gui.hud.base.AbstractHudElement;
|
||||
import dev.loki.lovisual.features.gui.hud.draggable.DraggableHudElement;
|
||||
import dev.loki.lovisual.features.gui.hud.draggable.DraggableHudElementRegistry;
|
||||
import dev.loki.lovisual.features.gui.hud.nondraggable.StaticHudElementRegistry;
|
||||
import dev.loki.lovisual.features.module.core.Module;
|
||||
import dev.loki.lovisual.features.module.core.ModuleManager;
|
||||
import dev.loki.lovisual.render.engine.text.font.info.FontInfo;
|
||||
|
||||
public enum RenderPrewarmContributors {
|
||||
INSTANCE;
|
||||
|
||||
@EventHandler(priority = 1000)
|
||||
private void onCollect(RenderPrewarmCollectEvent event) {
|
||||
collectCoreGui(event);
|
||||
collectEnabledModules(event);
|
||||
collectEnabledHud(event);
|
||||
}
|
||||
|
||||
private static void collectCoreGui(RenderPrewarmCollectEvent event) {
|
||||
event.font("Inter", FontInfo.Type.Regular)
|
||||
.font("Inter", FontInfo.Type.Bold)
|
||||
.font("Inter", FontInfo.Type.Italic)
|
||||
.font("InterMedium", FontInfo.Type.Regular)
|
||||
.font("OnestMedium", FontInfo.Type.Regular)
|
||||
.font("Onest", FontInfo.Type.Regular)
|
||||
.font("Onest", FontInfo.Type.Bold)
|
||||
.font("OnestMedium", FontInfo.Type.Regular)
|
||||
.font("OnestBold", FontInfo.Type.Regular)
|
||||
.font("OnestLight", FontInfo.Type.Regular)
|
||||
.font("Monsterrat", FontInfo.Type.Regular)
|
||||
.font("Comfortaa", FontInfo.Type.Regular)
|
||||
.font("Icons", FontInfo.Type.Regular)
|
||||
.font("IconsNur", FontInfo.Type.Regular)
|
||||
.font("GuiIcons", FontInfo.Type.Regular)
|
||||
.font("RichIcons", FontInfo.Type.Regular);
|
||||
|
||||
for (ModulesMenuCategory category : ModulesMenuCategory.values()) {
|
||||
event.svg(category.icon());
|
||||
}
|
||||
for (MenuScreen.Category category : MenuScreen.Category.values()) {
|
||||
if (category.svgIcon()) {
|
||||
event.svg(category.token());
|
||||
}
|
||||
}
|
||||
|
||||
event.svg("arrow")
|
||||
.svg("check")
|
||||
.svg("save")
|
||||
.svg("x")
|
||||
.svg("palette")
|
||||
.svg("brush")
|
||||
.svg("paintbrush")
|
||||
.svg("copy")
|
||||
.svg("trash")
|
||||
.svg("folder-cog")
|
||||
.svg("user-pen")
|
||||
.svg("columns-3-cog");
|
||||
}
|
||||
|
||||
private static void collectEnabledModules(RenderPrewarmCollectEvent event) {
|
||||
for (Module module : ModuleManager.getModules()) {
|
||||
if (module != null && module.isEnabled()) {
|
||||
module.collectRenderPrewarm(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void collectEnabledHud(RenderPrewarmCollectEvent event) {
|
||||
for (DraggableHudElement element : DraggableHudElementRegistry.getWidgets()) {
|
||||
if (element != null && element.isEnabled()) {
|
||||
element.collectRenderPrewarm(event);
|
||||
}
|
||||
}
|
||||
for (AbstractHudElement element : StaticHudElementRegistry.getAll()) {
|
||||
if (element != null && element.isEnabled()) {
|
||||
element.collectRenderPrewarm(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* This file is part of the LoVisual Client distribution.
|
||||
* Copyright (c) 2026 loki5512344.
|
||||
*
|
||||
* Licensed under the GNU General Public License v3.0.
|
||||
*/
|
||||
|
||||
package dev.loki.lovisual.render.engine.prewarm;
|
||||
|
||||
import dev.loki.lovisual.events.Events;
|
||||
import dev.loki.lovisual.events.impl.render.RenderPrewarmCollectEvent;
|
||||
import dev.loki.lovisual.features.gui.hud.script.HudScriptLayouts;
|
||||
import dev.loki.lovisual.render.engine.svg.parser.SvgMsdfRegistry;
|
||||
import dev.loki.lovisual.render.engine.text.font.Fonts;
|
||||
import dev.loki.lovisual.Lifecycle;
|
||||
import dev.loki.lovisual.util.logging.DebugLog;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.resources.Identifier;
|
||||
|
||||
public enum RenderPrewarmManager {
|
||||
;
|
||||
|
||||
public static void prewarm(String reason) {
|
||||
if (!Lifecycle.isActive()) return;
|
||||
|
||||
RenderPrewarmCollectEvent event = new RenderPrewarmCollectEvent(reason);
|
||||
Events.BUS.post(event);
|
||||
UiScriptStats uiScripts = prewarmUiScripts();
|
||||
if (event.isEmpty()) {
|
||||
DebugLog.renderThread("[LoVisual][Prewarm] %s: ui-scripts=%d changed, %d cached, %d errors",
|
||||
event.reason(), uiScripts.changed(), uiScripts.cached(), uiScripts.errors());
|
||||
return;
|
||||
}
|
||||
|
||||
int fonts = Fonts.prewarmRenderers(event.fonts());
|
||||
int svg = 0;
|
||||
for (Identifier id : event.svgMsdfIcons()) {
|
||||
if (SvgMsdfRegistry.preload(id)) {
|
||||
svg++;
|
||||
}
|
||||
}
|
||||
|
||||
DebugLog.renderThread("[LoVisual][Prewarm] %s: fonts=%d/%d svg-msdf=%d/%d ui-scripts=%d changed, %d cached, %d errors",
|
||||
event.reason(), fonts, event.fonts().size(), svg, event.svgMsdfIcons().size(),
|
||||
uiScripts.changed(), uiScripts.cached(), uiScripts.errors());
|
||||
}
|
||||
|
||||
private static UiScriptStats prewarmUiScripts() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc == null || mc.getResourceManager() == null) {
|
||||
return new UiScriptStats(0, 0, 0);
|
||||
}
|
||||
var stats = HudScriptLayouts.prewarmRegistered(mc.getResourceManager());
|
||||
return new UiScriptStats(stats.changed(), stats.unchanged(), stats.errors());
|
||||
}
|
||||
|
||||
private record UiScriptStats(int changed, int cached, int errors) {
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue