717 lines
		
	
	
	
		
			27 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			717 lines
		
	
	
	
		
			27 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| package net.minecraft.client.gui.render;
 | |
| 
 | |
| import com.google.common.collect.ImmutableMap;
 | |
| import com.google.common.collect.ImmutableMap.Builder;
 | |
| import com.mojang.blaze3d.ProjectionType;
 | |
| import com.mojang.blaze3d.buffers.GpuBuffer;
 | |
| import com.mojang.blaze3d.buffers.GpuBufferSlice;
 | |
| import com.mojang.blaze3d.buffers.GpuBuffer.MappedView;
 | |
| import com.mojang.blaze3d.pipeline.RenderPipeline;
 | |
| import com.mojang.blaze3d.pipeline.RenderTarget;
 | |
| import com.mojang.blaze3d.platform.Window;
 | |
| import com.mojang.blaze3d.systems.CommandEncoder;
 | |
| import com.mojang.blaze3d.systems.GpuDevice;
 | |
| import com.mojang.blaze3d.systems.RenderPass;
 | |
| import com.mojang.blaze3d.systems.RenderSystem;
 | |
| import com.mojang.blaze3d.systems.RenderSystem.AutoStorageIndexBuffer;
 | |
| import com.mojang.blaze3d.textures.FilterMode;
 | |
| import com.mojang.blaze3d.textures.GpuTexture;
 | |
| import com.mojang.blaze3d.textures.GpuTextureView;
 | |
| import com.mojang.blaze3d.textures.TextureFormat;
 | |
| import com.mojang.blaze3d.vertex.BufferBuilder;
 | |
| import com.mojang.blaze3d.vertex.ByteBufferBuilder;
 | |
| import com.mojang.blaze3d.vertex.MeshData;
 | |
| import com.mojang.blaze3d.vertex.PoseStack;
 | |
| import com.mojang.blaze3d.vertex.VertexFormat;
 | |
| import com.mojang.blaze3d.vertex.MeshData.DrawState;
 | |
| import com.mojang.logging.LogUtils;
 | |
| import it.unimi.dsi.fastutil.objects.Object2IntMap;
 | |
| import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
 | |
| import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
 | |
| import java.nio.ByteBuffer;
 | |
| import java.util.ArrayList;
 | |
| import java.util.Comparator;
 | |
| import java.util.Iterator;
 | |
| import java.util.List;
 | |
| import java.util.Map;
 | |
| import java.util.OptionalDouble;
 | |
| import java.util.OptionalInt;
 | |
| import java.util.Set;
 | |
| import java.util.Map.Entry;
 | |
| import java.util.function.Supplier;
 | |
| import net.fabricmc.api.EnvType;
 | |
| import net.fabricmc.api.Environment;
 | |
| import net.minecraft.client.Minecraft;
 | |
| import net.minecraft.client.gui.Font;
 | |
| import net.minecraft.client.gui.font.glyphs.BakedGlyph;
 | |
| import net.minecraft.client.gui.navigation.ScreenRectangle;
 | |
| import net.minecraft.client.gui.render.pip.OversizedItemRenderer;
 | |
| import net.minecraft.client.gui.render.pip.PictureInPictureRenderer;
 | |
| import net.minecraft.client.gui.render.state.BlitRenderState;
 | |
| import net.minecraft.client.gui.render.state.GlyphEffectRenderState;
 | |
| import net.minecraft.client.gui.render.state.GlyphRenderState;
 | |
| import net.minecraft.client.gui.render.state.GuiElementRenderState;
 | |
| import net.minecraft.client.gui.render.state.GuiItemRenderState;
 | |
| import net.minecraft.client.gui.render.state.GuiRenderState;
 | |
| import net.minecraft.client.gui.render.state.pip.OversizedItemRenderState;
 | |
| import net.minecraft.client.gui.render.state.pip.PictureInPictureRenderState;
 | |
| import net.minecraft.client.renderer.CachedOrthoProjectionMatrixBuffer;
 | |
| import net.minecraft.client.renderer.MappableRingBuffer;
 | |
| import net.minecraft.client.renderer.RenderPipelines;
 | |
| import net.minecraft.client.renderer.MultiBufferSource.BufferSource;
 | |
| import net.minecraft.client.renderer.item.TrackingItemStackRenderState;
 | |
| import net.minecraft.client.renderer.texture.OverlayTexture;
 | |
| import net.minecraft.util.Mth;
 | |
| import org.apache.commons.lang3.mutable.MutableBoolean;
 | |
| import org.jetbrains.annotations.Nullable;
 | |
| import org.joml.Matrix3x2f;
 | |
| import org.joml.Matrix4f;
 | |
| import org.joml.Vector3f;
 | |
| import org.joml.Vector4f;
 | |
| import org.lwjgl.system.MemoryUtil;
 | |
| import org.slf4j.Logger;
 | |
| 
 | |
| @Environment(EnvType.CLIENT)
 | |
| public class GuiRenderer implements AutoCloseable {
 | |
| 	private static final Logger LOGGER = LogUtils.getLogger();
 | |
| 	private static final float MAX_GUI_Z = 10000.0F;
 | |
| 	private static final float MIN_GUI_Z = 0.0F;
 | |
| 	private static final float GUI_Z_NEAR = 1000.0F;
 | |
| 	public static final int GUI_3D_Z_FAR = 1000;
 | |
| 	public static final int GUI_3D_Z_NEAR = -1000;
 | |
| 	public static final int DEFAULT_ITEM_SIZE = 16;
 | |
| 	private static final int MINIMUM_ITEM_ATLAS_SIZE = 512;
 | |
| 	private static final int MAXIMUM_ITEM_ATLAS_SIZE = RenderSystem.getDevice().getMaxTextureSize();
 | |
| 	public static final int CLEAR_COLOR = 0;
 | |
| 	private static final Comparator<ScreenRectangle> SCISSOR_COMPARATOR = Comparator.nullsFirst(
 | |
| 		Comparator.comparing(ScreenRectangle::top).thenComparing(ScreenRectangle::bottom).thenComparing(ScreenRectangle::left).thenComparing(ScreenRectangle::right)
 | |
| 	);
 | |
| 	private static final Comparator<TextureSetup> TEXTURE_COMPARATOR = Comparator.nullsFirst(Comparator.comparing(TextureSetup::getSortKey));
 | |
| 	private static final Comparator<GuiElementRenderState> ELEMENT_SORT_COMPARATOR = Comparator.comparing(GuiElementRenderState::scissorArea, SCISSOR_COMPARATOR)
 | |
| 		.thenComparing(GuiElementRenderState::pipeline, Comparator.comparing(RenderPipeline::getSortKey))
 | |
| 		.thenComparing(GuiElementRenderState::textureSetup, TEXTURE_COMPARATOR);
 | |
| 	private final Map<Object, GuiRenderer.AtlasPosition> atlasPositions = new Object2ObjectOpenHashMap<>();
 | |
| 	private final Map<Object, OversizedItemRenderer> oversizedItemRenderers = new Object2ObjectOpenHashMap<>();
 | |
| 	final GuiRenderState renderState;
 | |
| 	private final List<GuiRenderer.Draw> draws = new ArrayList();
 | |
| 	private final List<GuiRenderer.MeshToDraw> meshesToDraw = new ArrayList();
 | |
| 	private final ByteBufferBuilder byteBufferBuilder = new ByteBufferBuilder(786432);
 | |
| 	private final Map<VertexFormat, MappableRingBuffer> vertexBuffers = new Object2ObjectOpenHashMap<>();
 | |
| 	private int firstDrawIndexAfterBlur = Integer.MAX_VALUE;
 | |
| 	private final CachedOrthoProjectionMatrixBuffer guiProjectionMatrixBuffer = new CachedOrthoProjectionMatrixBuffer("gui", 1000.0F, 11000.0F, true);
 | |
| 	private final CachedOrthoProjectionMatrixBuffer itemsProjectionMatrixBuffer = new CachedOrthoProjectionMatrixBuffer("items", -1000.0F, 1000.0F, true);
 | |
| 	private final BufferSource bufferSource;
 | |
| 	private final Map<Class<? extends PictureInPictureRenderState>, PictureInPictureRenderer<?>> pictureInPictureRenderers;
 | |
| 	@Nullable
 | |
| 	private GpuTexture itemsAtlas;
 | |
| 	@Nullable
 | |
| 	private GpuTextureView itemsAtlasView;
 | |
| 	@Nullable
 | |
| 	private GpuTexture itemsAtlasDepth;
 | |
| 	@Nullable
 | |
| 	private GpuTextureView itemsAtlasDepthView;
 | |
| 	private int itemAtlasX;
 | |
| 	private int itemAtlasY;
 | |
| 	private int cachedGuiScale;
 | |
| 	private int frameNumber;
 | |
| 	@Nullable
 | |
| 	private ScreenRectangle previousScissorArea = null;
 | |
| 	@Nullable
 | |
| 	private RenderPipeline previousPipeline = null;
 | |
| 	@Nullable
 | |
| 	private TextureSetup previousTextureSetup = null;
 | |
| 	@Nullable
 | |
| 	private BufferBuilder bufferBuilder = null;
 | |
| 
 | |
| 	public GuiRenderer(GuiRenderState renderState, BufferSource bufferSource, List<PictureInPictureRenderer<?>> pictureInPictureRenderers) {
 | |
| 		this.renderState = renderState;
 | |
| 		this.bufferSource = bufferSource;
 | |
| 		Builder<Class<? extends PictureInPictureRenderState>, PictureInPictureRenderer<?>> builder = ImmutableMap.builder();
 | |
| 
 | |
| 		for (PictureInPictureRenderer<?> pictureInPictureRenderer : pictureInPictureRenderers) {
 | |
| 			builder.put((Class<? extends PictureInPictureRenderState>)pictureInPictureRenderer.getRenderStateClass(), pictureInPictureRenderer);
 | |
| 		}
 | |
| 
 | |
| 		this.pictureInPictureRenderers = builder.buildOrThrow();
 | |
| 	}
 | |
| 
 | |
| 	public void incrementFrameNumber() {
 | |
| 		this.frameNumber++;
 | |
| 	}
 | |
| 
 | |
| 	public void render(GpuBufferSlice bufferSlice) {
 | |
| 		this.prepare();
 | |
| 		this.draw(bufferSlice);
 | |
| 
 | |
| 		for (MappableRingBuffer mappableRingBuffer : this.vertexBuffers.values()) {
 | |
| 			mappableRingBuffer.rotate();
 | |
| 		}
 | |
| 
 | |
| 		this.draws.clear();
 | |
| 		this.meshesToDraw.clear();
 | |
| 		this.renderState.reset();
 | |
| 		this.firstDrawIndexAfterBlur = Integer.MAX_VALUE;
 | |
| 		this.clearUnusedOversizedItemRenderers();
 | |
| 	}
 | |
| 
 | |
| 	private void clearUnusedOversizedItemRenderers() {
 | |
| 		Iterator<Entry<Object, OversizedItemRenderer>> iterator = this.oversizedItemRenderers.entrySet().iterator();
 | |
| 
 | |
| 		while (iterator.hasNext()) {
 | |
| 			Entry<Object, OversizedItemRenderer> entry = (Entry<Object, OversizedItemRenderer>)iterator.next();
 | |
| 			OversizedItemRenderer oversizedItemRenderer = (OversizedItemRenderer)entry.getValue();
 | |
| 			if (!oversizedItemRenderer.usedOnThisFrame()) {
 | |
| 				oversizedItemRenderer.close();
 | |
| 				iterator.remove();
 | |
| 			} else {
 | |
| 				oversizedItemRenderer.resetUsedOnThisFrame();
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private void prepare() {
 | |
| 		this.bufferSource.endBatch();
 | |
| 		this.preparePictureInPicture();
 | |
| 		this.prepareItemElements();
 | |
| 		this.prepareText();
 | |
| 		this.renderState.sortElements(ELEMENT_SORT_COMPARATOR);
 | |
| 		this.addElementsToMeshes(GuiRenderState.TraverseRange.BEFORE_BLUR);
 | |
| 		this.firstDrawIndexAfterBlur = this.meshesToDraw.size();
 | |
| 		this.addElementsToMeshes(GuiRenderState.TraverseRange.AFTER_BLUR);
 | |
| 		this.recordDraws();
 | |
| 	}
 | |
| 
 | |
| 	private void addElementsToMeshes(GuiRenderState.TraverseRange traverseRange) {
 | |
| 		this.previousScissorArea = null;
 | |
| 		this.previousPipeline = null;
 | |
| 		this.previousTextureSetup = null;
 | |
| 		this.bufferBuilder = null;
 | |
| 		this.renderState.forEachElement(this::addElementToMesh, traverseRange);
 | |
| 		if (this.bufferBuilder != null) {
 | |
| 			this.recordMesh(this.bufferBuilder, this.previousPipeline, this.previousTextureSetup, this.previousScissorArea);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private void draw(GpuBufferSlice bufferSlice) {
 | |
| 		if (!this.draws.isEmpty()) {
 | |
| 			Minecraft minecraft = Minecraft.getInstance();
 | |
| 			Window window = minecraft.getWindow();
 | |
| 			RenderSystem.setProjectionMatrix(
 | |
| 				this.guiProjectionMatrixBuffer.getBuffer((float)window.getWidth() / window.getGuiScale(), (float)window.getHeight() / window.getGuiScale()),
 | |
| 				ProjectionType.ORTHOGRAPHIC
 | |
| 			);
 | |
| 			RenderTarget renderTarget = minecraft.getMainRenderTarget();
 | |
| 			int i = 0;
 | |
| 
 | |
| 			for (GuiRenderer.Draw draw : this.draws) {
 | |
| 				if (draw.indexCount > i) {
 | |
| 					i = draw.indexCount;
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			AutoStorageIndexBuffer autoStorageIndexBuffer = RenderSystem.getSequentialBuffer(VertexFormat.Mode.QUADS);
 | |
| 			GpuBuffer gpuBuffer = autoStorageIndexBuffer.getBuffer(i);
 | |
| 			VertexFormat.IndexType indexType = autoStorageIndexBuffer.type();
 | |
| 			GpuBufferSlice gpuBufferSlice = RenderSystem.getDynamicUniforms()
 | |
| 				.writeTransform(new Matrix4f().setTranslation(0.0F, 0.0F, -11000.0F), new Vector4f(1.0F, 1.0F, 1.0F, 1.0F), new Vector3f(), new Matrix4f(), 0.0F);
 | |
| 			if (this.firstDrawIndexAfterBlur > 0) {
 | |
| 				this.executeDrawRange(
 | |
| 					() -> "GUI before blur", renderTarget, bufferSlice, gpuBufferSlice, gpuBuffer, indexType, 0, Math.min(this.firstDrawIndexAfterBlur, this.draws.size())
 | |
| 				);
 | |
| 			}
 | |
| 
 | |
| 			if (this.draws.size() > this.firstDrawIndexAfterBlur) {
 | |
| 				RenderSystem.getDevice().createCommandEncoder().clearDepthTexture(renderTarget.getDepthTexture(), 1.0);
 | |
| 				minecraft.gameRenderer.processBlurEffect();
 | |
| 				this.executeDrawRange(
 | |
| 					() -> "GUI after blur", renderTarget, bufferSlice, gpuBufferSlice, gpuBuffer, indexType, this.firstDrawIndexAfterBlur, this.draws.size()
 | |
| 				);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private void executeDrawRange(
 | |
| 		Supplier<String> debugGroup,
 | |
| 		RenderTarget renderTarget,
 | |
| 		GpuBufferSlice fog,
 | |
| 		GpuBufferSlice dynamicTransforms,
 | |
| 		GpuBuffer buffer,
 | |
| 		VertexFormat.IndexType indexType,
 | |
| 		int start,
 | |
| 		int end
 | |
| 	) {
 | |
| 		try (RenderPass renderPass = RenderSystem.getDevice()
 | |
| 				.createCommandEncoder()
 | |
| 				.createRenderPass(
 | |
| 					debugGroup,
 | |
| 					renderTarget.getColorTextureView(),
 | |
| 					OptionalInt.empty(),
 | |
| 					renderTarget.useDepth ? renderTarget.getDepthTextureView() : null,
 | |
| 					OptionalDouble.empty()
 | |
| 				)) {
 | |
| 			RenderSystem.bindDefaultUniforms(renderPass);
 | |
| 			renderPass.setUniform("Fog", fog);
 | |
| 			renderPass.setUniform("DynamicTransforms", dynamicTransforms);
 | |
| 
 | |
| 			for (int i = start; i < end; i++) {
 | |
| 				GuiRenderer.Draw draw = (GuiRenderer.Draw)this.draws.get(i);
 | |
| 				this.executeDraw(draw, renderPass, buffer, indexType);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private void addElementToMesh(GuiElementRenderState renderState, int layer) {
 | |
| 		RenderPipeline renderPipeline = renderState.pipeline();
 | |
| 		TextureSetup textureSetup = renderState.textureSetup();
 | |
| 		ScreenRectangle screenRectangle = renderState.scissorArea();
 | |
| 		if (renderPipeline != this.previousPipeline
 | |
| 			|| this.scissorChanged(screenRectangle, this.previousScissorArea)
 | |
| 			|| !textureSetup.equals(this.previousTextureSetup)) {
 | |
| 			if (this.bufferBuilder != null) {
 | |
| 				this.recordMesh(this.bufferBuilder, this.previousPipeline, this.previousTextureSetup, this.previousScissorArea);
 | |
| 			}
 | |
| 
 | |
| 			this.bufferBuilder = this.getBufferBuilder(renderPipeline);
 | |
| 			this.previousPipeline = renderPipeline;
 | |
| 			this.previousTextureSetup = textureSetup;
 | |
| 			this.previousScissorArea = screenRectangle;
 | |
| 		}
 | |
| 
 | |
| 		renderState.buildVertices(this.bufferBuilder, 0.0F + layer);
 | |
| 	}
 | |
| 
 | |
| 	private void prepareText() {
 | |
| 		this.renderState.forEachText(guiTextRenderState -> {
 | |
| 			final Matrix3x2f matrix3x2f = guiTextRenderState.pose;
 | |
| 			final ScreenRectangle screenRectangle = guiTextRenderState.scissor;
 | |
| 			guiTextRenderState.ensurePrepared().visit(new Font.GlyphVisitor() {
 | |
| 				@Override
 | |
| 				public void acceptGlyph(BakedGlyph.GlyphInstance glyph) {
 | |
| 					if (glyph.glyph().textureView() != null) {
 | |
| 						GuiRenderer.this.renderState.submitGlyphToCurrentLayer(new GlyphRenderState(matrix3x2f, glyph, screenRectangle));
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				@Override
 | |
| 				public void acceptEffect(BakedGlyph glyph, BakedGlyph.Effect effect) {
 | |
| 					if (glyph.textureView() != null) {
 | |
| 						GuiRenderer.this.renderState.submitGlyphToCurrentLayer(new GlyphEffectRenderState(matrix3x2f, glyph, effect, screenRectangle));
 | |
| 					}
 | |
| 				}
 | |
| 			});
 | |
| 		});
 | |
| 	}
 | |
| 
 | |
| 	private void prepareItemElements() {
 | |
| 		if (!this.renderState.getItemModelIdentities().isEmpty()) {
 | |
| 			int i = this.getGuiScaleInvalidatingItemAtlasIfChanged();
 | |
| 			int j = 16 * i;
 | |
| 			int k = this.calculateAtlasSizeInPixels(j);
 | |
| 			if (this.itemsAtlas == null) {
 | |
| 				this.createAtlasTextures(k);
 | |
| 			}
 | |
| 
 | |
| 			RenderSystem.outputColorTextureOverride = this.itemsAtlasView;
 | |
| 			RenderSystem.outputDepthTextureOverride = this.itemsAtlasDepthView;
 | |
| 			RenderSystem.setProjectionMatrix(this.itemsProjectionMatrixBuffer.getBuffer(k, k), ProjectionType.ORTHOGRAPHIC);
 | |
| 			Minecraft.getInstance().gameRenderer.getLighting().setupFor(com.mojang.blaze3d.platform.Lighting.Entry.ITEMS_3D);
 | |
| 			PoseStack poseStack = new PoseStack();
 | |
| 			MutableBoolean mutableBoolean = new MutableBoolean(false);
 | |
| 			MutableBoolean mutableBoolean2 = new MutableBoolean(false);
 | |
| 			this.renderState
 | |
| 				.forEachItem(
 | |
| 					guiItemRenderState -> {
 | |
| 						if (guiItemRenderState.oversizedItemBounds() != null) {
 | |
| 							mutableBoolean2.setTrue();
 | |
| 						} else {
 | |
| 							TrackingItemStackRenderState trackingItemStackRenderState = guiItemRenderState.itemStackRenderState();
 | |
| 							GuiRenderer.AtlasPosition atlasPosition = (GuiRenderer.AtlasPosition)this.atlasPositions.get(trackingItemStackRenderState.getModelIdentity());
 | |
| 							if (atlasPosition == null || trackingItemStackRenderState.isAnimated() && atlasPosition.lastAnimatedOnFrame != this.frameNumber) {
 | |
| 								if (this.itemAtlasX + j > k) {
 | |
| 									this.itemAtlasX = 0;
 | |
| 									this.itemAtlasY += j;
 | |
| 								}
 | |
| 
 | |
| 								boolean bl = trackingItemStackRenderState.isAnimated() && atlasPosition != null;
 | |
| 								if (!bl && this.itemAtlasY + j > k) {
 | |
| 									if (mutableBoolean.isFalse()) {
 | |
| 										LOGGER.warn("Trying to render too many items in GUI at the same time. Skipping some of them.");
 | |
| 										mutableBoolean.setTrue();
 | |
| 									}
 | |
| 								} else {
 | |
| 									int kx = bl ? atlasPosition.x : this.itemAtlasX;
 | |
| 									int l = bl ? atlasPosition.y : this.itemAtlasY;
 | |
| 									if (bl) {
 | |
| 										RenderSystem.getDevice().createCommandEncoder().clearColorAndDepthTextures(this.itemsAtlas, 0, this.itemsAtlasDepth, 1.0, kx, k - l - j, j, j);
 | |
| 									}
 | |
| 
 | |
| 									this.renderItemToAtlas(trackingItemStackRenderState, poseStack, kx, l, j);
 | |
| 									float f = (float)kx / k;
 | |
| 									float g = (float)(k - l) / k;
 | |
| 									this.submitBlitFromItemAtlas(guiItemRenderState, f, g, j, k);
 | |
| 									if (bl) {
 | |
| 										atlasPosition.lastAnimatedOnFrame = this.frameNumber;
 | |
| 									} else {
 | |
| 										this.atlasPositions
 | |
| 											.put(
 | |
| 												guiItemRenderState.itemStackRenderState().getModelIdentity(),
 | |
| 												new GuiRenderer.AtlasPosition(this.itemAtlasX, this.itemAtlasY, f, g, this.frameNumber)
 | |
| 											);
 | |
| 										this.itemAtlasX += j;
 | |
| 									}
 | |
| 								}
 | |
| 							} else {
 | |
| 								this.submitBlitFromItemAtlas(guiItemRenderState, atlasPosition.u, atlasPosition.v, j, k);
 | |
| 							}
 | |
| 						}
 | |
| 					}
 | |
| 				);
 | |
| 			RenderSystem.outputColorTextureOverride = null;
 | |
| 			RenderSystem.outputDepthTextureOverride = null;
 | |
| 			if (mutableBoolean2.getValue()) {
 | |
| 				this.renderState
 | |
| 					.forEachItem(
 | |
| 						guiItemRenderState -> {
 | |
| 							if (guiItemRenderState.oversizedItemBounds() != null) {
 | |
| 								TrackingItemStackRenderState trackingItemStackRenderState = guiItemRenderState.itemStackRenderState();
 | |
| 								OversizedItemRenderer oversizedItemRenderer = (OversizedItemRenderer)this.oversizedItemRenderers
 | |
| 									.computeIfAbsent(trackingItemStackRenderState.getModelIdentity(), object -> new OversizedItemRenderer(this.bufferSource));
 | |
| 								ScreenRectangle screenRectangle = guiItemRenderState.oversizedItemBounds();
 | |
| 								OversizedItemRenderState oversizedItemRenderState = new OversizedItemRenderState(
 | |
| 									guiItemRenderState, screenRectangle.left(), screenRectangle.top(), screenRectangle.right(), screenRectangle.bottom()
 | |
| 								);
 | |
| 								oversizedItemRenderer.prepare(oversizedItemRenderState, this.renderState, i);
 | |
| 							}
 | |
| 						}
 | |
| 					);
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private void preparePictureInPicture() {
 | |
| 		int i = Minecraft.getInstance().getWindow().getGuiScale();
 | |
| 		this.renderState.forEachPictureInPicture(pictureInPictureRenderState -> this.preparePictureInPictureState(pictureInPictureRenderState, i));
 | |
| 	}
 | |
| 
 | |
| 	private <T extends PictureInPictureRenderState> void preparePictureInPictureState(T state, int guiScale) {
 | |
| 		PictureInPictureRenderer<T> pictureInPictureRenderer = (PictureInPictureRenderer<T>)this.pictureInPictureRenderers.get(state.getClass());
 | |
| 		if (pictureInPictureRenderer != null) {
 | |
| 			pictureInPictureRenderer.prepare(state, this.renderState, guiScale);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private void renderItemToAtlas(TrackingItemStackRenderState renderState, PoseStack poseStack, int x, int y, int size) {
 | |
| 		poseStack.pushPose();
 | |
| 		poseStack.translate(x + size / 2.0F, y + size / 2.0F, 0.0F);
 | |
| 		poseStack.scale(size, -size, size);
 | |
| 		boolean bl = !renderState.usesBlockLight();
 | |
| 		if (bl) {
 | |
| 			Minecraft.getInstance().gameRenderer.getLighting().setupFor(com.mojang.blaze3d.platform.Lighting.Entry.ITEMS_FLAT);
 | |
| 		} else {
 | |
| 			Minecraft.getInstance().gameRenderer.getLighting().setupFor(com.mojang.blaze3d.platform.Lighting.Entry.ITEMS_3D);
 | |
| 		}
 | |
| 
 | |
| 		RenderSystem.enableScissorForRenderTypeDraws(x, this.itemsAtlas.getHeight(0) - y - size, size, size);
 | |
| 		renderState.render(poseStack, this.bufferSource, 15728880, OverlayTexture.NO_OVERLAY);
 | |
| 		this.bufferSource.endBatch();
 | |
| 		RenderSystem.disableScissorForRenderTypeDraws();
 | |
| 		poseStack.popPose();
 | |
| 	}
 | |
| 
 | |
| 	private void submitBlitFromItemAtlas(GuiItemRenderState renderState, float x, float y, int itemSize, int atlasSize) {
 | |
| 		float f = x + (float)itemSize / atlasSize;
 | |
| 		float g = y + (float)(-itemSize) / atlasSize;
 | |
| 		this.renderState
 | |
| 			.submitBlitToCurrentLayer(
 | |
| 				new BlitRenderState(
 | |
| 					RenderPipelines.GUI_TEXTURED_PREMULTIPLIED_ALPHA,
 | |
| 					TextureSetup.singleTexture(this.itemsAtlasView),
 | |
| 					renderState.pose(),
 | |
| 					renderState.x(),
 | |
| 					renderState.y(),
 | |
| 					renderState.x() + 16,
 | |
| 					renderState.y() + 16,
 | |
| 					x,
 | |
| 					f,
 | |
| 					y,
 | |
| 					g,
 | |
| 					-1,
 | |
| 					renderState.scissorArea(),
 | |
| 					null
 | |
| 				)
 | |
| 			);
 | |
| 	}
 | |
| 
 | |
| 	private void createAtlasTextures(int atlasSize) {
 | |
| 		GpuDevice gpuDevice = RenderSystem.getDevice();
 | |
| 		this.itemsAtlas = gpuDevice.createTexture("UI items atlas", 12, TextureFormat.RGBA8, atlasSize, atlasSize, 1, 1);
 | |
| 		this.itemsAtlas.setTextureFilter(FilterMode.NEAREST, false);
 | |
| 		this.itemsAtlasView = gpuDevice.createTextureView(this.itemsAtlas);
 | |
| 		this.itemsAtlasDepth = gpuDevice.createTexture("UI items atlas depth", 8, TextureFormat.DEPTH32, atlasSize, atlasSize, 1, 1);
 | |
| 		this.itemsAtlasDepthView = gpuDevice.createTextureView(this.itemsAtlasDepth);
 | |
| 		gpuDevice.createCommandEncoder().clearColorAndDepthTextures(this.itemsAtlas, 0, this.itemsAtlasDepth, 1.0);
 | |
| 	}
 | |
| 
 | |
| 	private int calculateAtlasSizeInPixels(int itemWidth) {
 | |
| 		Set<Object> set = this.renderState.getItemModelIdentities();
 | |
| 		int i;
 | |
| 		if (this.atlasPositions.isEmpty()) {
 | |
| 			i = set.size();
 | |
| 		} else {
 | |
| 			i = this.atlasPositions.size();
 | |
| 
 | |
| 			for (Object object : set) {
 | |
| 				if (!this.atlasPositions.containsKey(object)) {
 | |
| 					i++;
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if (this.itemsAtlas != null) {
 | |
| 			int j = this.itemsAtlas.getWidth(0) / itemWidth;
 | |
| 			int k = j * j;
 | |
| 			if (i < k) {
 | |
| 				return this.itemsAtlas.getWidth(0);
 | |
| 			}
 | |
| 
 | |
| 			this.invalidateItemAtlas();
 | |
| 		}
 | |
| 
 | |
| 		int j = set.size();
 | |
| 		int k = Mth.smallestSquareSide(j + j / 2);
 | |
| 		return Math.clamp(Mth.smallestEncompassingPowerOfTwo(k * itemWidth), 512, MAXIMUM_ITEM_ATLAS_SIZE);
 | |
| 	}
 | |
| 
 | |
| 	private int getGuiScaleInvalidatingItemAtlasIfChanged() {
 | |
| 		int i = Minecraft.getInstance().getWindow().getGuiScale();
 | |
| 		if (i != this.cachedGuiScale) {
 | |
| 			this.invalidateItemAtlas();
 | |
| 
 | |
| 			for (OversizedItemRenderer oversizedItemRenderer : this.oversizedItemRenderers.values()) {
 | |
| 				oversizedItemRenderer.invalidateTexture();
 | |
| 			}
 | |
| 
 | |
| 			this.cachedGuiScale = i;
 | |
| 		}
 | |
| 
 | |
| 		return i;
 | |
| 	}
 | |
| 
 | |
| 	private void invalidateItemAtlas() {
 | |
| 		this.itemAtlasX = 0;
 | |
| 		this.itemAtlasY = 0;
 | |
| 		this.atlasPositions.clear();
 | |
| 		if (this.itemsAtlas != null) {
 | |
| 			this.itemsAtlas.close();
 | |
| 			this.itemsAtlas = null;
 | |
| 		}
 | |
| 
 | |
| 		if (this.itemsAtlasView != null) {
 | |
| 			this.itemsAtlasView.close();
 | |
| 			this.itemsAtlasView = null;
 | |
| 		}
 | |
| 
 | |
| 		if (this.itemsAtlasDepth != null) {
 | |
| 			this.itemsAtlasDepth.close();
 | |
| 			this.itemsAtlasDepth = null;
 | |
| 		}
 | |
| 
 | |
| 		if (this.itemsAtlasDepthView != null) {
 | |
| 			this.itemsAtlasDepthView.close();
 | |
| 			this.itemsAtlasDepthView = null;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private void recordMesh(BufferBuilder bufferBuilder, RenderPipeline pipeline, TextureSetup textureSetup, @Nullable ScreenRectangle scissorArea) {
 | |
| 		MeshData meshData = bufferBuilder.buildOrThrow();
 | |
| 		this.meshesToDraw.add(new GuiRenderer.MeshToDraw(meshData, pipeline, textureSetup, scissorArea));
 | |
| 	}
 | |
| 
 | |
| 	private void recordDraws() {
 | |
| 		this.ensureVertexBufferSizes();
 | |
| 		CommandEncoder commandEncoder = RenderSystem.getDevice().createCommandEncoder();
 | |
| 		Object2IntMap<VertexFormat> object2IntMap = new Object2IntOpenHashMap<>();
 | |
| 
 | |
| 		for (GuiRenderer.MeshToDraw meshToDraw : this.meshesToDraw) {
 | |
| 			MeshData meshData = meshToDraw.mesh;
 | |
| 			DrawState drawState = meshData.drawState();
 | |
| 			VertexFormat vertexFormat = drawState.format();
 | |
| 			MappableRingBuffer mappableRingBuffer = (MappableRingBuffer)this.vertexBuffers.get(vertexFormat);
 | |
| 			if (!object2IntMap.containsKey(vertexFormat)) {
 | |
| 				object2IntMap.put(vertexFormat, 0);
 | |
| 			}
 | |
| 
 | |
| 			ByteBuffer byteBuffer = meshData.vertexBuffer();
 | |
| 			int i = byteBuffer.remaining();
 | |
| 			int j = object2IntMap.getInt(vertexFormat);
 | |
| 
 | |
| 			try (MappedView mappedView = commandEncoder.mapBuffer(mappableRingBuffer.currentBuffer().slice(j, i), false, true)) {
 | |
| 				MemoryUtil.memCopy(byteBuffer, mappedView.data());
 | |
| 			}
 | |
| 
 | |
| 			object2IntMap.put(vertexFormat, j + i);
 | |
| 			this.draws
 | |
| 				.add(
 | |
| 					new GuiRenderer.Draw(
 | |
| 						mappableRingBuffer.currentBuffer(),
 | |
| 						j / vertexFormat.getVertexSize(),
 | |
| 						drawState.mode(),
 | |
| 						drawState.indexCount(),
 | |
| 						meshToDraw.pipeline,
 | |
| 						meshToDraw.textureSetup,
 | |
| 						meshToDraw.scissorArea
 | |
| 					)
 | |
| 				);
 | |
| 			meshToDraw.close();
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private void ensureVertexBufferSizes() {
 | |
| 		Object2IntMap<VertexFormat> object2IntMap = this.calculatedRequiredVertexBufferSizes();
 | |
| 
 | |
| 		for (it.unimi.dsi.fastutil.objects.Object2IntMap.Entry<VertexFormat> entry : object2IntMap.object2IntEntrySet()) {
 | |
| 			VertexFormat vertexFormat = (VertexFormat)entry.getKey();
 | |
| 			int i = entry.getIntValue();
 | |
| 			MappableRingBuffer mappableRingBuffer = (MappableRingBuffer)this.vertexBuffers.get(vertexFormat);
 | |
| 			if (mappableRingBuffer == null || mappableRingBuffer.size() < i) {
 | |
| 				if (mappableRingBuffer != null) {
 | |
| 					mappableRingBuffer.close();
 | |
| 				}
 | |
| 
 | |
| 				this.vertexBuffers.put(vertexFormat, new MappableRingBuffer(() -> "GUI vertex buffer for " + vertexFormat, 34, i));
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private Object2IntMap<VertexFormat> calculatedRequiredVertexBufferSizes() {
 | |
| 		Object2IntMap<VertexFormat> object2IntMap = new Object2IntOpenHashMap<>();
 | |
| 
 | |
| 		for (GuiRenderer.MeshToDraw meshToDraw : this.meshesToDraw) {
 | |
| 			DrawState drawState = meshToDraw.mesh.drawState();
 | |
| 			VertexFormat vertexFormat = drawState.format();
 | |
| 			if (!object2IntMap.containsKey(vertexFormat)) {
 | |
| 				object2IntMap.put(vertexFormat, 0);
 | |
| 			}
 | |
| 
 | |
| 			object2IntMap.put(vertexFormat, object2IntMap.getInt(vertexFormat) + drawState.vertexCount() * vertexFormat.getVertexSize());
 | |
| 		}
 | |
| 
 | |
| 		return object2IntMap;
 | |
| 	}
 | |
| 
 | |
| 	private void executeDraw(GuiRenderer.Draw draw, RenderPass renderPass, GpuBuffer buffer, VertexFormat.IndexType indexType) {
 | |
| 		RenderPipeline renderPipeline = draw.pipeline();
 | |
| 		renderPass.setPipeline(renderPipeline);
 | |
| 		renderPass.setVertexBuffer(0, draw.vertexBuffer);
 | |
| 		ScreenRectangle screenRectangle = draw.scissorArea();
 | |
| 		if (screenRectangle != null) {
 | |
| 			this.enableScissor(screenRectangle, renderPass);
 | |
| 		} else {
 | |
| 			renderPass.disableScissor();
 | |
| 		}
 | |
| 
 | |
| 		if (draw.textureSetup.texure0() != null) {
 | |
| 			renderPass.bindSampler("Sampler0", draw.textureSetup.texure0());
 | |
| 		}
 | |
| 
 | |
| 		if (draw.textureSetup.texure1() != null) {
 | |
| 			renderPass.bindSampler("Sampler1", draw.textureSetup.texure1());
 | |
| 		}
 | |
| 
 | |
| 		if (draw.textureSetup.texure2() != null) {
 | |
| 			renderPass.bindSampler("Sampler2", draw.textureSetup.texure2());
 | |
| 		}
 | |
| 
 | |
| 		renderPass.setIndexBuffer(buffer, indexType);
 | |
| 		renderPass.drawIndexed(draw.baseVertex, 0, draw.indexCount, 1);
 | |
| 	}
 | |
| 
 | |
| 	private BufferBuilder getBufferBuilder(RenderPipeline pipeline) {
 | |
| 		return new BufferBuilder(this.byteBufferBuilder, pipeline.getVertexFormatMode(), pipeline.getVertexFormat());
 | |
| 	}
 | |
| 
 | |
| 	private boolean scissorChanged(@Nullable ScreenRectangle scissorArea, @Nullable ScreenRectangle oldScissorArea) {
 | |
| 		if (scissorArea == oldScissorArea) {
 | |
| 			return false;
 | |
| 		} else {
 | |
| 			return scissorArea != null ? !scissorArea.equals(oldScissorArea) : true;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	private void enableScissor(ScreenRectangle scissorArea, RenderPass renderPass) {
 | |
| 		Window window = Minecraft.getInstance().getWindow();
 | |
| 		int i = window.getHeight();
 | |
| 		int j = window.getGuiScale();
 | |
| 		double d = scissorArea.left() * j;
 | |
| 		double e = i - scissorArea.bottom() * j;
 | |
| 		double f = scissorArea.width() * j;
 | |
| 		double g = scissorArea.height() * j;
 | |
| 		renderPass.enableScissor((int)d, (int)e, Math.max(0, (int)f), Math.max(0, (int)g));
 | |
| 	}
 | |
| 
 | |
| 	public void close() {
 | |
| 		this.byteBufferBuilder.close();
 | |
| 		if (this.itemsAtlas != null) {
 | |
| 			this.itemsAtlas.close();
 | |
| 		}
 | |
| 
 | |
| 		if (this.itemsAtlasView != null) {
 | |
| 			this.itemsAtlasView.close();
 | |
| 		}
 | |
| 
 | |
| 		if (this.itemsAtlasDepth != null) {
 | |
| 			this.itemsAtlasDepth.close();
 | |
| 		}
 | |
| 
 | |
| 		if (this.itemsAtlasDepthView != null) {
 | |
| 			this.itemsAtlasDepthView.close();
 | |
| 		}
 | |
| 
 | |
| 		this.pictureInPictureRenderers.values().forEach(PictureInPictureRenderer::close);
 | |
| 		this.guiProjectionMatrixBuffer.close();
 | |
| 		this.itemsProjectionMatrixBuffer.close();
 | |
| 
 | |
| 		for (MappableRingBuffer mappableRingBuffer : this.vertexBuffers.values()) {
 | |
| 			mappableRingBuffer.close();
 | |
| 		}
 | |
| 
 | |
| 		this.oversizedItemRenderers.values().forEach(PictureInPictureRenderer::close);
 | |
| 	}
 | |
| 
 | |
| 	@Environment(EnvType.CLIENT)
 | |
| 	static final class AtlasPosition {
 | |
| 		final int x;
 | |
| 		final int y;
 | |
| 		final float u;
 | |
| 		final float v;
 | |
| 		int lastAnimatedOnFrame;
 | |
| 
 | |
| 		AtlasPosition(int x, int y, float u, float v, int lastAnimatedOnFrame) {
 | |
| 			this.x = x;
 | |
| 			this.y = y;
 | |
| 			this.u = u;
 | |
| 			this.v = v;
 | |
| 			this.lastAnimatedOnFrame = lastAnimatedOnFrame;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@Environment(EnvType.CLIENT)
 | |
| 	record Draw(
 | |
| 		GpuBuffer vertexBuffer,
 | |
| 		int baseVertex,
 | |
| 		VertexFormat.Mode mode,
 | |
| 		int indexCount,
 | |
| 		RenderPipeline pipeline,
 | |
| 		TextureSetup textureSetup,
 | |
| 		@Nullable ScreenRectangle scissorArea
 | |
| 	) {
 | |
| 	}
 | |
| 
 | |
| 	@Environment(EnvType.CLIENT)
 | |
| 	record MeshToDraw(MeshData mesh, RenderPipeline pipeline, TextureSetup textureSetup, @Nullable ScreenRectangle scissorArea) implements AutoCloseable {
 | |
| 
 | |
| 		public void close() {
 | |
| 			this.mesh.close();
 | |
| 		}
 | |
| 	}
 | |
| }
 |