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.GlyphVisitor; import net.minecraft.client.gui.font.glyphs.BakedGlyph; import net.minecraft.client.gui.font.glyphs.BakedGlyph.Effect; import net.minecraft.client.gui.font.glyphs.BakedGlyph.GlyphInstance; 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.GuiRenderState.TraverseRange; 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 SCISSOR_COMPARATOR = Comparator.nullsFirst( Comparator.comparing(ScreenRectangle::top).thenComparing(ScreenRectangle::bottom).thenComparing(ScreenRectangle::left).thenComparing(ScreenRectangle::right) ); private static final Comparator TEXTURE_COMPARATOR = Comparator.nullsFirst(Comparator.comparing(TextureSetup::getSortKey)); private static final Comparator ELEMENT_SORT_COMPARATOR = Comparator.comparing(GuiElementRenderState::scissorArea, SCISSOR_COMPARATOR) .thenComparing(GuiElementRenderState::pipeline, Comparator.comparing(RenderPipeline::getSortKey)) .thenComparing(GuiElementRenderState::textureSetup, TEXTURE_COMPARATOR); private final Map atlasPositions = new Object2ObjectOpenHashMap<>(); private final Map oversizedItemRenderers = new Object2ObjectOpenHashMap<>(); final GuiRenderState renderState; private final List draws = new ArrayList(); private final List meshesToDraw = new ArrayList(); private final ByteBufferBuilder byteBufferBuilder = new ByteBufferBuilder(786432); private final Map 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, 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> pictureInPictureRenderers) { this.renderState = renderState; this.bufferSource = bufferSource; Builder, PictureInPictureRenderer> builder = ImmutableMap.builder(); for (PictureInPictureRenderer pictureInPictureRenderer : pictureInPictureRenderers) { builder.put((Class)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> iterator = this.oversizedItemRenderers.entrySet().iterator(); while (iterator.hasNext()) { Entry entry = (Entry)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(TraverseRange.BEFORE_BLUR); this.firstDrawIndexAfterBlur = this.meshesToDraw.size(); this.addElementsToMeshes(TraverseRange.AFTER_BLUR); this.recordDraws(); } private void addElementsToMeshes(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 fogUniforms) { 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, fogUniforms, 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, fogUniforms, gpuBufferSlice, gpuBuffer, indexType, this.firstDrawIndexAfterBlur, this.draws.size() ); } } } private void executeDrawRange( Supplier debugGroup, RenderTarget renderTarget, GpuBufferSlice fogUniforms, 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", fogUniforms); 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 GlyphVisitor() { @Override public void acceptGlyph(GlyphInstance glyph) { if (glyph.glyph().textureView() != null) { GuiRenderer.this.renderState.submitGlyphToCurrentLayer(new GlyphRenderState(matrix3x2f, glyph, screenRectangle)); } } @Override public void acceptEffect(BakedGlyph glyph, 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 void preparePictureInPictureState(T state, int guiScale) { PictureInPictureRenderer pictureInPictureRenderer = (PictureInPictureRenderer)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 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 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 object2IntMap = this.calculatedRequiredVertexBufferSizes(); for (it.unimi.dsi.fastutil.objects.Object2IntMap.Entry 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 calculatedRequiredVertexBufferSizes() { Object2IntMap 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(); } } }