├── CHANGELOG.md ├── logs └── latest.log ├── .build ├── eliorona-sign.asc.gpg ├── usrgradle.properties.gpg └── deploy.sh ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── README.md ├── src └── main │ ├── resources │ ├── assets │ │ ├── baked_minecraft_models │ │ │ └── icon.png │ │ └── minecraft │ │ │ └── shaders │ │ │ └── core │ │ │ ├── rendertype_entity_translucent_batched.json │ │ │ ├── rendertype_entity_cutout_no_cull_instanced.json │ │ │ ├── rendertype_entity_cutout_no_cull_instanced.vsh │ │ │ └── rendertype_entity_translucent_batched.vsh │ ├── fabric.mod.json │ ├── baked_minecraft_models.mixins.json │ └── baked_minecraft_models.accesswidener │ └── java │ └── graphics │ └── kiln │ └── bakedminecraftmodels │ ├── access │ ├── BakeablePart.java │ ├── BatchContainer.java │ ├── RenderLayerContainer.java │ └── BakeablePartBuilder.java │ ├── model │ ├── InstancedRenderDispatcher.java │ ├── VboBackedModel.java │ └── GlobalModelUtils.java │ ├── data │ ├── IndexWriter.java │ ├── PerInstanceData.java │ ├── MatrixEntryList.java │ ├── BakingData.java │ └── InstanceBatch.java │ ├── util │ └── AlignmentUtil.java │ ├── mixin │ ├── renderlayer │ │ ├── MultiPhaseRenderPassAccessor.java │ │ ├── RenderPhaseAccessor.java │ │ ├── RenderPhaseShaderAccessor.java │ │ ├── RenderLayerAccessor.java │ │ ├── BufferBuilderMixin.java │ │ ├── MultiPhaseParametersAccessor.java │ │ └── RenderLayerMixin.java │ ├── buffer │ │ ├── SpriteTexturedVertexConsumerAccessor.java │ │ ├── BufferBuilderAccessor.java │ │ ├── VertexBufferAccessor.java │ │ └── VertexConsumerProviderImmediateMixin.java │ ├── BufferRendererAccessor.java │ ├── model │ │ ├── MatrixStackMixin.java │ │ ├── ModelPartDataMixin.java │ │ ├── ModelMixins.java │ │ └── ModelPartMixin.java │ ├── GameRendererMixin.java │ ├── GlUniformMixin.java │ ├── WorldRendererMixin.java │ └── DebugHudMixin.java │ ├── ssbo │ ├── SectionedSyncObjects.java │ └── SectionedPersistentBuffer.java │ ├── BakedMinecraftModelsShaderManager.java │ ├── BakedMinecraftModels.java │ ├── BakedMinecraftModelsVertexFormats.java │ ├── debug │ ├── DebugInfo.java │ └── ModelExporter.java │ ├── BakedMinecraftModelsRenderLayerManager.java │ ├── vertex │ └── SmartBufferBuilderWrapper.java │ └── gl │ └── GlSsboRenderDispatcher.java ├── LICENSE-HEADER ├── .gitignore ├── settings.gradle ├── gradle.properties ├── .github └── workflows │ ├── publish.yml │ └── build.yml ├── gradlew.bat ├── gradlew └── LICENSE /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /logs/latest.log: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.build/eliorona-sign.asc.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KilnGraphics/baked-minecraft-models/HEAD/.build/eliorona-sign.asc.gpg -------------------------------------------------------------------------------- /.build/usrgradle.properties.gpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KilnGraphics/baked-minecraft-models/HEAD/.build/usrgradle.properties.gpg -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KilnGraphics/baked-minecraft-models/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Baked Minecraft Models 2 | 3 | This mod bakes minecraft models into vertex buffers and uses an animation system to reduce data uploading. -------------------------------------------------------------------------------- /src/main/resources/assets/baked_minecraft_models/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KilnGraphics/baked-minecraft-models/HEAD/src/main/resources/assets/baked_minecraft_models/icon.png -------------------------------------------------------------------------------- /LICENSE-HEADER: -------------------------------------------------------------------------------- 1 | This Source Code Form is subject to the terms of the Mozilla Public 2 | License, v. 2.0. If a copy of the MPL was not distributed with this 3 | file, You can obtain one at https://mozilla.org/MPL/2.0/. -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # gradle 2 | 3 | .gradle/ 4 | build/ 5 | out/ 6 | classes/ 7 | 8 | # eclipse 9 | 10 | *.launch 11 | 12 | # idea 13 | 14 | .idea/ 15 | *.iml 16 | *.ipr 17 | *.iws 18 | 19 | # vscode 20 | 21 | .settings/ 22 | .vscode/ 23 | bin/ 24 | .classpath 25 | .project 26 | 27 | # macos 28 | 29 | *.DS_Store 30 | 31 | # fabric 32 | 33 | run/ 34 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | maven { 4 | name = 'Fabric' 5 | url = 'https://maven.fabricmc.net/' 6 | } 7 | maven { 8 | name = "Cotton" 9 | url = uri("https://server.bbkr.space/artifactory/libs-release/") 10 | } 11 | gradlePluginPortal() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/access/BakeablePart.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.access; 8 | 9 | public interface BakeablePart { 10 | void setId(int id); 11 | int getId(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/model/InstancedRenderDispatcher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.model; 8 | 9 | public interface InstancedRenderDispatcher { 10 | 11 | void renderQueues(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/data/IndexWriter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.data; 8 | 9 | public interface IndexWriter { 10 | void writeIndices(long ptr, int startIdx, int vertsPerPrim); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/access/BatchContainer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.access; 8 | 9 | import graphics.kiln.bakedminecraftmodels.data.InstanceBatch; 10 | 11 | public interface BatchContainer { 12 | InstanceBatch getBatch(); 13 | void setBatch(InstanceBatch batch); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/access/RenderLayerContainer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.access; 8 | 9 | import net.minecraft.client.render.RenderLayer; 10 | 11 | public interface RenderLayerContainer { 12 | RenderLayer getRenderLayer(); 13 | void setRenderLayer(RenderLayer renderLayer); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/model/VboBackedModel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.model; 8 | 9 | import net.minecraft.client.gl.VertexBuffer; 10 | 11 | public interface VboBackedModel { 12 | VertexBuffer getBakedVertices(); 13 | int getVertexCount(); 14 | float[] getPrimitivePositions(); 15 | int[] getPrimitivePartIds(); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/access/BakeablePartBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.access; 8 | 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | public interface BakeablePartBuilder { 12 | void setId(int id); 13 | int getId(); 14 | 15 | void setParent(BakeablePartBuilder parent); 16 | @Nullable BakeablePartBuilder getParent(); 17 | 18 | int getNextAvailableModelId(); 19 | } 20 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Done to increase the memory available to gradle. 2 | org.gradle.jvmargs=-Xmx1G 3 | # Fabric Properties 4 | # check these on https://fabricmc.net/use 5 | minecraft_version=1.17.1 6 | yarn_mappings=1.17.1+build.64 7 | loader_version=0.11.6 8 | # Mod Properties 9 | mod_version=1.0.0-beta 10 | maven_group=com.oroarmor 11 | archives_base_name=baked-minecraft-models 12 | project_name=Baked Minecraft Models 13 | # publishing 14 | modrinth_id=null 15 | curseforge_id=null 16 | game_versions=1.17.1 17 | # Dependencies 18 | # currently not on the main fabric site, check on the maven: https://maven.fabricmc.net/net/fabricmc/fabric-api/fabric-api 19 | fabric_version=0.37.1+1.17 -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/util/AlignmentUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.util; 8 | 9 | public class AlignmentUtil { 10 | 11 | // multiple must be a power of 2 12 | public static long alignPowerOf2(long numToRound, long multiple) { 13 | // TODO: make sure this always works 14 | //noinspection UnnecessaryParentheses 15 | return (numToRound + multiple - 1) & -multiple; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/mixin/renderlayer/MultiPhaseRenderPassAccessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.mixin.renderlayer; 8 | 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.gen.Accessor; 11 | 12 | import net.minecraft.client.render.RenderLayer; 13 | 14 | @Mixin(RenderLayer.MultiPhase.class) 15 | public interface MultiPhaseRenderPassAccessor { 16 | @Accessor 17 | RenderLayer.MultiPhaseParameters getPhases(); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/mixin/buffer/SpriteTexturedVertexConsumerAccessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.mixin.buffer; 8 | 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.gen.Accessor; 11 | 12 | import net.minecraft.client.render.SpriteTexturedVertexConsumer; 13 | import net.minecraft.client.render.VertexConsumer; 14 | 15 | @Mixin(SpriteTexturedVertexConsumer.class) 16 | public interface SpriteTexturedVertexConsumerAccessor { 17 | @Accessor 18 | VertexConsumer getParent(); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/mixin/renderlayer/RenderPhaseAccessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.mixin.renderlayer; 8 | 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.gen.Accessor; 11 | 12 | import net.minecraft.client.render.RenderPhase; 13 | 14 | @Mixin(RenderPhase.class) 15 | public interface RenderPhaseAccessor { 16 | @Accessor 17 | String getName(); 18 | 19 | @Accessor("COLOR_MASK") 20 | static RenderPhase.WriteMaskState getColorMask() { 21 | throw new RuntimeException("how"); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/mixin/renderlayer/RenderPhaseShaderAccessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.mixin.renderlayer; 8 | 9 | import net.minecraft.client.render.RenderPhase; 10 | import net.minecraft.client.render.Shader; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | import org.spongepowered.asm.mixin.gen.Accessor; 13 | 14 | import java.util.Optional; 15 | import java.util.function.Supplier; 16 | 17 | @Mixin(RenderPhase.Shader.class) 18 | public interface RenderPhaseShaderAccessor { 19 | @Accessor 20 | Optional> getSupplier(); 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Mod to external sites 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | publish: 8 | 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Set up JDK 1.8 14 | uses: actions/setup-java@v1 15 | with: 16 | java-version: 1.16 17 | - name: Grant execute permission for gradlew 18 | run: chmod +x gradlew 19 | - name: Grant execute permission for .build/deploy.sh 20 | run: chmod +x .build/deploy.sh 21 | - name: Deploy 22 | run: .build/deploy.sh 23 | env: 24 | GPG_SECRET: ${{ secrets.GPG_SECRET }} 25 | CURSE_API_KEY: ${{ secrets.CURSE_API_KEY }} 26 | MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }} 27 | GITHUB_TOKEN: ${{ secrets.GH_API_KEY }} -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/mixin/renderlayer/RenderLayerAccessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.mixin.renderlayer; 8 | 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.gen.Accessor; 11 | 12 | import net.minecraft.client.render.RenderLayer; 13 | 14 | @Mixin(RenderLayer.class) 15 | public interface RenderLayerAccessor extends RenderPhaseAccessor { 16 | 17 | @Accessor 18 | boolean getHasCrumbling(); 19 | 20 | @Accessor 21 | boolean getTranslucent(); // This doesn't actually mean translucent, yarn just doesn't have a good name for this. 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/mixin/buffer/BufferBuilderAccessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.mixin.buffer; 8 | 9 | import java.nio.ByteBuffer; 10 | 11 | import org.spongepowered.asm.mixin.Mixin; 12 | import org.spongepowered.asm.mixin.gen.Accessor; 13 | 14 | import net.minecraft.client.render.BufferBuilder; 15 | import net.minecraft.client.render.VertexFormat; 16 | 17 | @Mixin(BufferBuilder.class) 18 | public interface BufferBuilderAccessor { 19 | 20 | @Accessor 21 | ByteBuffer getBuffer(); 22 | 23 | @Accessor 24 | int getElementOffset(); 25 | 26 | @Accessor 27 | int getVertexCount(); 28 | } 29 | -------------------------------------------------------------------------------- /.build/deploy.sh: -------------------------------------------------------------------------------- 1 | set -euo pipefail 2 | IFS=$'\n\t' 3 | 4 | function cleanup { 5 | echo "🧹 Cleanup..." 6 | rm -f ~/.gradle/gradle.properties eliorona-sign.asc 7 | } 8 | 9 | trap cleanup SIGINT SIGTERM ERR EXIT 10 | 11 | echo "🚀 Preparing to deploy..." 12 | 13 | echo "🔑 Decrypting files..." 14 | 15 | gpg --quiet --batch --yes --decrypt --passphrase="${GPG_SECRET}" \ 16 | --output eliorona-sign.asc .build/eliorona-sign.asc.gpg 17 | 18 | mkdir ~/.gradle -p 19 | 20 | gpg --quiet --batch --yes --decrypt --passphrase="${GPG_SECRET}" \ 21 | --output ~/.gradle/gradle.properties .build/usrgradle.properties.gpg 22 | 23 | gpg --fast-import --no-tty --batch --yes eliorona-sign.asc 24 | 25 | echo "📦 Publishing..." 26 | 27 | ./gradlew build 28 | ./gradlew generateChangelog github curseforge publishToModrinth 29 | ./gradlew publishMavenJavaPublicationToMavenCentralRepository -Psign closeAndReleaseRepository 30 | 31 | echo "✅ Done!" 32 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/mixin/BufferRendererAccessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.mixin; 8 | 9 | import net.minecraft.client.render.BufferRenderer; 10 | import net.minecraft.client.render.VertexFormat; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | import org.spongepowered.asm.mixin.gen.Invoker; 13 | 14 | import java.nio.ByteBuffer; 15 | 16 | @Mixin(BufferRenderer.class) 17 | public interface BufferRendererAccessor { 18 | @Invoker("draw") 19 | static void drawInternal(ByteBuffer buffer, VertexFormat.DrawMode drawMode, VertexFormat vertexFormat, int count, VertexFormat.IntType elementFormat, int vertexCount, boolean textured) { 20 | throw new RuntimeException("how"); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/mixin/model/MatrixStackMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.mixin.model; 8 | 9 | import graphics.kiln.bakedminecraftmodels.access.BatchContainer; 10 | import graphics.kiln.bakedminecraftmodels.data.InstanceBatch; 11 | import net.minecraft.client.util.math.MatrixStack; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | 14 | @Mixin(MatrixStack.class) 15 | public class MatrixStackMixin implements BatchContainer { 16 | 17 | private InstanceBatch batch; 18 | 19 | @Override 20 | public InstanceBatch getBatch() { 21 | return batch; 22 | } 23 | 24 | @Override 25 | public void setBatch(InstanceBatch batch) { 26 | this.batch = batch; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/mixin/renderlayer/BufferBuilderMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.mixin.renderlayer; 8 | 9 | import graphics.kiln.bakedminecraftmodels.access.RenderLayerContainer; 10 | import net.minecraft.client.render.BufferBuilder; 11 | import net.minecraft.client.render.RenderLayer; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | 14 | @Mixin(BufferBuilder.class) 15 | public class BufferBuilderMixin implements RenderLayerContainer { 16 | 17 | private RenderLayer bmm$renderLayer; 18 | 19 | @Override 20 | public RenderLayer getRenderLayer() { 21 | return bmm$renderLayer; 22 | } 23 | 24 | @Override 25 | public void setRenderLayer(RenderLayer renderLayer) { 26 | bmm$renderLayer = renderLayer; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/ssbo/SectionedSyncObjects.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.ssbo; 8 | 9 | public class SectionedSyncObjects { 10 | private final long[] syncObjects; 11 | 12 | private int currentSection = 0; 13 | 14 | public SectionedSyncObjects(int sectionCount) { 15 | this.syncObjects = new long[sectionCount]; // these are 0 (null) by default 16 | } 17 | 18 | public long getCurrentSyncObject() { 19 | return syncObjects[currentSection]; 20 | } 21 | 22 | public void setCurrentSyncObject(long pSyncObject) { 23 | syncObjects[currentSection] = pSyncObject; 24 | } 25 | 26 | public void nextSection() { 27 | currentSection++; 28 | currentSection %= syncObjects.length; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/mixin/GameRendererMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.mixin; 8 | 9 | import graphics.kiln.bakedminecraftmodels.BakedMinecraftModelsShaderManager; 10 | import org.spongepowered.asm.mixin.Mixin; 11 | import org.spongepowered.asm.mixin.injection.At; 12 | import org.spongepowered.asm.mixin.injection.Inject; 13 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 14 | 15 | import net.minecraft.client.render.GameRenderer; 16 | import net.minecraft.resource.ResourceManager; 17 | 18 | @Mixin(GameRenderer.class) 19 | public class GameRendererMixin { 20 | @Inject(method = "loadShaders", at = @At("RETURN")) 21 | public void loadShaders(ResourceManager manager, CallbackInfo ci) { 22 | BakedMinecraftModelsShaderManager.loadShaders(manager); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/resources/fabric.mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 1, 3 | "id": "baked_minecraft_models", 4 | "version": "${version}", 5 | 6 | "name": "Baked Minecraft Models", 7 | "description": "Bakes minecraft models into vertex buffers and uses an animation system to reduce data uploading.", 8 | "authors": [ 9 | "OroArmor", 10 | "Blaze4D-MC" 11 | ], 12 | "contact": { 13 | "sources": "https://github.com/Blaze4D-MC/baked-minecraft-models" 14 | }, 15 | 16 | "license": "MIT", 17 | "icon": "assets/baked_minecraft_models/icon.png", 18 | 19 | "environment": "client", 20 | "entrypoints": { 21 | "client": [ 22 | "graphics.kiln.bakedminecraftmodels.BakedMinecraftModels" 23 | ] 24 | }, 25 | "accessWidener": "baked_minecraft_models.accesswidener", 26 | "mixins": [ 27 | "baked_minecraft_models.mixins.json" 28 | ], 29 | 30 | "depends": { 31 | "fabricloader": ">=0.11.3", 32 | "minecraft": ">=1.17" 33 | }, 34 | 35 | "custom": { 36 | "sodium:options": { 37 | "mixin.features.entity.fast_render": false 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/mixin/GlUniformMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.mixin; 8 | 9 | import java.nio.FloatBuffer; 10 | 11 | import org.spongepowered.asm.mixin.Final; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.Shadow; 14 | import org.spongepowered.asm.mixin.injection.At; 15 | import org.spongepowered.asm.mixin.injection.Redirect; 16 | 17 | import net.minecraft.client.gl.GlUniform; 18 | 19 | @Mixin(GlUniform.class) 20 | public class GlUniformMixin { 21 | @Shadow 22 | @Final 23 | private int count; 24 | 25 | @Redirect(method = "set([F)V", at = @At(value = "INVOKE", target = "Ljava/nio/FloatBuffer;put([F)Ljava/nio/FloatBuffer;")) 26 | public FloatBuffer set(FloatBuffer floatBuffer, float[] src) { 27 | return floatBuffer.put(src, 0, count); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/resources/baked_minecraft_models.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "required": true, 3 | "minVersion": "0.8", 4 | "package": "graphics.kiln.bakedminecraftmodels.mixin", 5 | "compatibilityLevel": "JAVA_16", 6 | "mixins": [ 7 | ], 8 | "client": [ 9 | "BufferRendererAccessor", 10 | "DebugHudMixin", 11 | "GameRendererMixin", 12 | "GlUniformMixin", 13 | "WorldRendererMixin", 14 | "buffer.BufferBuilderAccessor", 15 | "buffer.SpriteTexturedVertexConsumerAccessor", 16 | "buffer.VertexBufferAccessor", 17 | "buffer.VertexConsumerProviderImmediateMixin", 18 | "model.MatrixStackMixin", 19 | "model.ModelMixins", 20 | "model.ModelPartDataMixin", 21 | "model.ModelPartMixin", 22 | "renderlayer.BufferBuilderMixin", 23 | "renderlayer.MultiPhaseParametersAccessor", 24 | "renderlayer.MultiPhaseRenderPassAccessor", 25 | "renderlayer.RenderLayerAccessor", 26 | "renderlayer.RenderLayerMixin", 27 | "renderlayer.RenderPhaseAccessor", 28 | "renderlayer.RenderPhaseShaderAccessor" 29 | ], 30 | "injectors": { 31 | "defaultRequire": 1 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/BakedMinecraftModelsShaderManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels; 8 | 9 | import java.io.IOException; 10 | 11 | import org.jetbrains.annotations.Nullable; 12 | 13 | import net.minecraft.client.render.Shader; 14 | import net.minecraft.resource.ResourceManager; 15 | 16 | public class BakedMinecraftModelsShaderManager { 17 | public static @Nullable Shader ENTITY_CUTOUT_NO_CULL_INSTANCED; 18 | public static @Nullable Shader ENTITY_TRANSLUCENT_BATCHED; 19 | 20 | public static void loadShaders(ResourceManager manager) { 21 | try { 22 | ENTITY_CUTOUT_NO_CULL_INSTANCED = new Shader(manager, "rendertype_entity_cutout_no_cull_instanced", BakedMinecraftModelsVertexFormats.SMART_ENTITY_FORMAT); 23 | ENTITY_TRANSLUCENT_BATCHED = new Shader(manager, "rendertype_entity_translucent_batched", BakedMinecraftModelsVertexFormats.SMART_ENTITY_FORMAT); 24 | } catch (IOException e) { 25 | e.printStackTrace(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/mixin/buffer/VertexBufferAccessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.mixin.buffer; 8 | 9 | import net.minecraft.client.gl.VertexBuffer; 10 | import net.minecraft.client.render.VertexFormat; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | import org.spongepowered.asm.mixin.gen.Accessor; 13 | import org.spongepowered.asm.mixin.gen.Invoker; 14 | 15 | @Mixin(VertexBuffer.class) 16 | public interface VertexBufferAccessor { 17 | /** 18 | * Get the index (NOT vertex) count. This is the number of items in the EBO, not the number 19 | * of primitives. 20 | */ 21 | @Accessor 22 | int getVertexCount(); 23 | 24 | @Accessor 25 | VertexFormat.DrawMode getDrawMode(); 26 | 27 | /** 28 | * Get the type of integer used to store the index data (NOT vertex data). 29 | */ 30 | @Accessor 31 | VertexFormat.IntType getVertexFormat(); 32 | 33 | @Accessor 34 | int getVertexBufferId(); 35 | 36 | @Invoker 37 | void invokeBindVertexArray(); 38 | 39 | @Invoker 40 | void invokeBind(); 41 | } 42 | -------------------------------------------------------------------------------- /src/main/resources/assets/minecraft/shaders/core/rendertype_entity_translucent_batched.json: -------------------------------------------------------------------------------- 1 | { 2 | "blend": { 3 | "func": "add", 4 | "srcrgb": "srcalpha", 5 | "dstrgb": "1-srcalpha" 6 | }, 7 | "vertex": "rendertype_entity_translucent_batched", 8 | "fragment": "rendertype_entity_translucent", 9 | "attributes": [], 10 | "samplers": [ 11 | { "name": "Sampler0" }, 12 | { "name": "Sampler1" }, 13 | { "name": "Sampler2" } 14 | ], 15 | "uniforms": [ 16 | { "name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, 17 | { "name": "ColorModulator", "type": "float", "count": 4, "values": [ 1.0, 1.0, 1.0, 1.0 ] }, 18 | { "name": "Light0_Direction", "type": "float", "count": 3, "values": [0.0, 0.0, 0.0] }, 19 | { "name": "Light1_Direction", "type": "float", "count": 3, "values": [0.0, 0.0, 0.0] }, 20 | { "name": "FogStart", "type": "float", "count": 1, "values": [ 0.0 ] }, 21 | { "name": "FogEnd", "type": "float", "count": 1, "values": [ 1.0 ] }, 22 | { "name": "FogColor", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, 23 | { "name": "InstanceOffset", "type": "int", "count": 1, "values": [ 0 ] }, 24 | { "name": "InstanceVertCount", "type": "int", "count": 1, "values": [ 0 ] } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/mixin/buffer/VertexConsumerProviderImmediateMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.mixin.buffer; 8 | 9 | import graphics.kiln.bakedminecraftmodels.access.RenderLayerContainer; 10 | import net.minecraft.client.render.RenderLayer; 11 | import net.minecraft.client.render.VertexConsumer; 12 | import net.minecraft.client.render.VertexConsumerProvider; 13 | import org.spongepowered.asm.mixin.Mixin; 14 | import org.spongepowered.asm.mixin.injection.At; 15 | import org.spongepowered.asm.mixin.injection.Inject; 16 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 17 | 18 | @Mixin(VertexConsumerProvider.Immediate.class) 19 | public abstract class VertexConsumerProviderImmediateMixin { 20 | @Inject(method = "getBuffer", at = @At("RETURN")) 21 | private void attachRenderLayerToBuffer(RenderLayer renderLayer, CallbackInfoReturnable cir) { 22 | VertexConsumer consumer = cir.getReturnValue(); 23 | if (consumer instanceof RenderLayerContainer renderLayerContainer) { 24 | renderLayerContainer.setRenderLayer(renderLayer); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/assets/minecraft/shaders/core/rendertype_entity_cutout_no_cull_instanced.json: -------------------------------------------------------------------------------- 1 | { 2 | "blend": { 3 | "func": "add", 4 | "srcrgb": "srcalpha", 5 | "dstrgb": "1-srcalpha" 6 | }, 7 | "vertex": "rendertype_entity_cutout_no_cull_instanced", 8 | "fragment": "rendertype_entity_cutout_no_cull", 9 | "attributes": [ 10 | "Position", 11 | "UV0", 12 | "Normal", 13 | "PartId" 14 | ], 15 | "samplers": [ 16 | { "name": "Sampler0" }, 17 | { "name": "Sampler1" }, 18 | { "name": "Sampler2" } 19 | ], 20 | "uniforms": [ 21 | { "name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, 22 | { "name": "ColorModulator", "type": "float", "count": 4, "values": [ 1.0, 1.0, 1.0, 1.0 ] }, 23 | { "name": "Light0_Direction", "type": "float", "count": 3, "values": [0.0, 0.0, 0.0] }, 24 | { "name": "Light1_Direction", "type": "float", "count": 3, "values": [0.0, 0.0, 0.0] }, 25 | { "name": "FogStart", "type": "float", "count": 1, "values": [ 0.0 ] }, 26 | { "name": "FogEnd", "type": "float", "count": 1, "values": [ 1.0 ] }, 27 | { "name": "FogColor", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, 28 | { "name": "InstanceOffset", "type": "int", "count": 1, "values": [ 0 ] } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # Automatically build the project and run any configured tests for every push 2 | # and submitted pull request. This can help catch issues that only occur on 3 | # certain platforms or Java versions, and provides a first line of defence 4 | # against bad commits. 5 | 6 | name: build 7 | on: [pull_request, push] 8 | 9 | jobs: 10 | build: 11 | strategy: 12 | matrix: 13 | # Use these Java versions 14 | java: [ 15 | 16 # Latest version 16 | ] 17 | # and run on both Linux and Windows 18 | os: [ubuntu-20.04, windows-latest] 19 | runs-on: ${{ matrix.os }} 20 | steps: 21 | - name: checkout repository 22 | uses: actions/checkout@v2 23 | - name: validate gradle wrapper 24 | uses: gradle/wrapper-validation-action@v1 25 | - name: setup jdk ${{ matrix.java }} 26 | uses: actions/setup-java@v1 27 | with: 28 | java-version: ${{ matrix.java }} 29 | - name: make gradle wrapper executable 30 | if: ${{ runner.os != 'Windows' }} 31 | run: chmod +x ./gradlew 32 | - name: build 33 | run: ./gradlew build 34 | - name: capture build artifacts 35 | if: ${{ runner.os == 'Linux' && matrix.java == '16' }} # Only upload artifacts built from LTS java on one OS 36 | uses: actions/upload-artifact@v2 37 | with: 38 | name: Artifacts 39 | path: build/libs/ 40 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/mixin/renderlayer/MultiPhaseParametersAccessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.mixin.renderlayer; 8 | 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.gen.Accessor; 11 | 12 | import net.minecraft.client.render.RenderLayer; 13 | import net.minecraft.client.render.RenderPhase; 14 | 15 | @Mixin(RenderLayer.MultiPhaseParameters.class) 16 | public interface MultiPhaseParametersAccessor { 17 | @Accessor 18 | RenderPhase.TextureBase getTexture(); 19 | 20 | @Accessor 21 | RenderPhase.Transparency getTransparency(); 22 | 23 | @Accessor 24 | RenderPhase.DepthTest getDepthTest(); 25 | 26 | @Accessor 27 | RenderPhase.Cull getCull(); 28 | 29 | @Accessor 30 | RenderPhase.Lightmap getLightmap(); 31 | 32 | @Accessor 33 | RenderPhase.Overlay getOverlay(); 34 | 35 | @Accessor 36 | RenderPhase.Layering getLayering(); 37 | 38 | @Accessor 39 | RenderPhase.Target getTarget(); 40 | 41 | @Accessor 42 | RenderPhase.Texturing getTexturing(); 43 | 44 | @Accessor 45 | RenderPhase.WriteMaskState getWriteMaskState(); 46 | 47 | @Accessor 48 | RenderPhase.LineWidth getLineWidth(); 49 | 50 | @Accessor 51 | RenderLayer.OutlineMode getOutlineMode(); 52 | 53 | @Accessor 54 | RenderPhase.Shader getShader(); 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/data/PerInstanceData.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.data; 8 | 9 | import graphics.kiln.bakedminecraftmodels.model.GlobalModelUtils; 10 | import graphics.kiln.bakedminecraftmodels.ssbo.SectionedPersistentBuffer; 11 | import org.lwjgl.system.MemoryUtil; 12 | 13 | public record PerInstanceData(long partArrayIndex, float red, float green, float blue, float alpha, int overlayX, 14 | int overlayY, int lightX, int lightY, int[] primitiveIndices, int skippedPrimitivesStart, 15 | int skippedPrimitivesEnd) { 16 | 17 | public void writeToBuffer(SectionedPersistentBuffer buffer) { 18 | long positionOffset = buffer.getPositionOffset().getAndAdd(GlobalModelUtils.MODEL_STRUCT_SIZE); 19 | long pointer = buffer.getSectionedPointer() + positionOffset; 20 | MemoryUtil.memPutFloat(pointer, red); 21 | MemoryUtil.memPutFloat(pointer + 4, green); 22 | MemoryUtil.memPutFloat(pointer + 8, blue); 23 | MemoryUtil.memPutFloat(pointer + 12, alpha); 24 | MemoryUtil.memPutInt(pointer + 16, overlayX); 25 | MemoryUtil.memPutInt(pointer + 20, overlayY); 26 | MemoryUtil.memPutInt(pointer + 24, lightX); 27 | MemoryUtil.memPutInt(pointer + 28, lightY); 28 | // if this overflows, we have to change it to an u64 in the shader. also, figure out how to actually calculate this as an uint. 29 | MemoryUtil.memPutInt(pointer + 44, (int) partArrayIndex); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/ssbo/SectionedPersistentBuffer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.ssbo; 8 | 9 | import java.util.concurrent.atomic.AtomicLong; 10 | 11 | // TODO: abstract this but still use long for getPointer 12 | public class SectionedPersistentBuffer { 13 | private final long pointer; 14 | private final int name; 15 | private final int sectionCount; 16 | private final long sectionSize; 17 | 18 | private int currentSection = 0; 19 | private long sectionOffset = 0; 20 | private final AtomicLong positionOffset = new AtomicLong(); 21 | 22 | public SectionedPersistentBuffer(long pointer, int name, int sectionCount, long sectionSize) { 23 | this.pointer = pointer; 24 | this.name = name; 25 | this.sectionCount = sectionCount; 26 | this.sectionSize = sectionSize; 27 | } 28 | 29 | public long getSectionedPointer() { 30 | return pointer + sectionOffset; 31 | } 32 | 33 | public int getName() { 34 | return name; 35 | } 36 | 37 | public long getSectionSize() { 38 | return sectionSize; 39 | } 40 | 41 | public int getCurrentSection() { 42 | return currentSection; 43 | } 44 | 45 | public void nextSection() { 46 | currentSection++; 47 | currentSection %= sectionCount; 48 | sectionOffset = getCurrentSection() * getSectionSize(); 49 | positionOffset.setRelease(0); 50 | } 51 | 52 | public AtomicLong getPositionOffset() { 53 | return positionOffset; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/BakedMinecraftModels.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels; 8 | 9 | import graphics.kiln.bakedminecraftmodels.debug.ModelExporter; 10 | import net.fabricmc.api.ClientModInitializer; 11 | import org.apache.logging.log4j.LogManager; 12 | import org.apache.logging.log4j.Logger; 13 | 14 | public class BakedMinecraftModels implements ClientModInitializer { 15 | public static final String MOD_ID = "baked_minecraft_models"; 16 | public static final Logger LOGGER = LogManager.getLogger(MOD_ID); 17 | private static final boolean EXPORT_MODELS_TO_OBJ = false; 18 | private static final boolean ENABLE_RENDERDOC = true; 19 | 20 | // RenderDoc Vertex Format: 21 | /* 22 | vec3 pos 23 | vec2 uv 24 | byte3 normal 25 | byte padding 26 | uint id 27 | */ 28 | 29 | // RenderDoc Part SSBO Format: 30 | /* 31 | mat4 modelViewMat; 32 | mat3x4 normalMat; 33 | */ 34 | 35 | // RenderDoc Model SSBO Format: 36 | /* 37 | vec4 Color; 38 | ivec2 UV1; 39 | ivec2 UV2; 40 | vec3 padding; 41 | uint partOffset; 42 | */ 43 | 44 | @Override 45 | public void onInitializeClient() { 46 | if (ENABLE_RENDERDOC) { 47 | try { 48 | System.loadLibrary("renderdoc"); 49 | } catch (Throwable e) { 50 | LOGGER.error("Unable to load renderdoc"); 51 | } 52 | } 53 | 54 | if (EXPORT_MODELS_TO_OBJ) { 55 | ModelExporter.exportDefaultModelsToOBJ(); 56 | } 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/BakedMinecraftModelsVertexFormats.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels; 8 | 9 | import com.google.common.collect.ImmutableMap; 10 | 11 | import net.minecraft.client.render.VertexFormat; 12 | import net.minecraft.client.render.VertexFormatElement; 13 | import net.minecraft.client.render.VertexFormats; 14 | 15 | public class BakedMinecraftModelsVertexFormats { 16 | public static final VertexFormat SMART_ENTITY_FORMAT = new VertexFormat( 17 | ImmutableMap.builder() 18 | .put("Position", VertexFormats.POSITION_ELEMENT) 19 | // Four bytes of padding - this rounds out the vec3 to a vec4 20 | // This is required because the PullVert struct in the shader uses an alignment (std140) that 21 | // aligns vec2s to the 8-byte boundary, thus leaving a gap between the position and UV. 22 | // TODO is the performance gain here worth the memory tradeoff of changing the struct packing 23 | // (whether directly or declaring a bunch of floats) and saving these four bytes per vert? 24 | .put("PosPad", new VertexFormatElement(0, VertexFormatElement.DataType.FLOAT, VertexFormatElement.Type.PADDING, 1)) 25 | .put("UV0", VertexFormats.TEXTURE_0_ELEMENT) 26 | .put("Normal", VertexFormats.NORMAL_ELEMENT) 27 | .put("NormPad", VertexFormats.PADDING_ELEMENT) 28 | .put("PartId", new VertexFormatElement(0, VertexFormatElement.DataType.UINT, VertexFormatElement.Type.UV, 1)) 29 | .build()); 30 | } 31 | -------------------------------------------------------------------------------- /src/main/resources/assets/minecraft/shaders/core/rendertype_entity_cutout_no_cull_instanced.vsh: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | #extension GL_ARB_shader_storage_buffer_object : require 3 | 4 | #moj_import 5 | 6 | in vec3 Position; 7 | in vec2 UV0; 8 | in vec3 Normal; 9 | in uint PartId; 10 | 11 | uniform sampler2D Sampler1; 12 | uniform sampler2D Sampler2; 13 | uniform mat4 ProjMat; 14 | uniform vec3 Light0_Direction; 15 | uniform vec3 Light1_Direction; 16 | 17 | uniform int InstanceOffset; // minecraft doesn't have a way to set uints 18 | 19 | struct ModelPart { 20 | mat4 modelViewMat; 21 | mat3x4 normalMat; 22 | }; 23 | layout(std140, binding = 1) readonly restrict buffer modelPartsLayout { 24 | ModelPart[] modelParts; 25 | } modelPartsSsbo; 26 | 27 | struct Model { 28 | vec4 Color; 29 | ivec2 UV1; 30 | ivec2 UV2; 31 | vec3 Padding; 32 | uint PartOffset; 33 | }; 34 | 35 | layout(std140, binding = 2) readonly restrict buffer modelsLayout { 36 | Model[] models; 37 | } modelsSsbo; 38 | 39 | out float vertexDistance; 40 | out vec4 vertexColor; 41 | out vec4 lightMapColor; 42 | out vec4 overlayColor; 43 | out vec2 texCoord0; 44 | out vec4 normal; 45 | 46 | void main() { 47 | #ifdef VULKAN 48 | Model model = modelsSsbo.models[gl_InstanceIndex]; 49 | #else 50 | Model model = modelsSsbo.models[InstanceOffset + gl_InstanceID]; 51 | #endif 52 | ModelPart modelPart = modelPartsSsbo.modelParts[model.PartOffset + PartId]; 53 | 54 | vec4 multipliedPosition = modelPart.modelViewMat * vec4(Position, 1.0); 55 | gl_Position = ProjMat * multipliedPosition; 56 | 57 | vertexDistance = length(multipliedPosition.xyz); 58 | vec3 multipliedNormal = normalize(mat3(modelPart.normalMat) * Normal); 59 | vertexColor = minecraft_mix_light(Light0_Direction, Light1_Direction, multipliedNormal, model.Color); 60 | lightMapColor = texelFetch(Sampler2, model.UV2 / 16, 0); 61 | overlayColor = texelFetch(Sampler1, model.UV1, 0); 62 | texCoord0 = UV0; 63 | normal = ProjMat * vec4(multipliedNormal, 0.0); 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/model/GlobalModelUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.model; 8 | 9 | import graphics.kiln.bakedminecraftmodels.data.BakingData; 10 | import graphics.kiln.bakedminecraftmodels.gl.GlSsboRenderDispatcher; 11 | import graphics.kiln.bakedminecraftmodels.mixin.buffer.SpriteTexturedVertexConsumerAccessor; 12 | import graphics.kiln.bakedminecraftmodels.vertex.SmartBufferBuilderWrapper; 13 | import net.minecraft.client.render.BufferBuilder; 14 | import net.minecraft.client.render.SpriteTexturedVertexConsumer; 15 | import net.minecraft.client.render.VertexConsumer; 16 | import net.minecraft.client.util.math.MatrixStack; 17 | 18 | public class GlobalModelUtils { 19 | 20 | public static final long MODEL_STRUCT_SIZE = 4 * Float.BYTES + 2 * Integer.BYTES + 2 * Integer.BYTES + 3 * Float.BYTES + Integer.BYTES; 21 | public static final long PART_STRUCT_SIZE = 16 * Float.BYTES + 12 * Float.BYTES; 22 | 23 | public static final MatrixStack.Entry IDENTITY_STACK_ENTRY = new MatrixStack().peek(); 24 | // FIXME: not thread safe, but making one per instance is slow 25 | public static final SmartBufferBuilderWrapper VBO_BUFFER_BUILDER = new SmartBufferBuilderWrapper(new BufferBuilder(32768), 256); // just some random sizes lol 26 | public static final GlSsboRenderDispatcher INSTANCED_RENDER_DISPATCHER = new GlSsboRenderDispatcher(); 27 | public static final BakingData bakingData = new BakingData(INSTANCED_RENDER_DISPATCHER.modelPersistentSsbo, INSTANCED_RENDER_DISPATCHER.partPersistentSsbo, INSTANCED_RENDER_DISPATCHER.translucencyPersistentEbo); 28 | 29 | public static VertexConsumer getNestedBufferBuilder(VertexConsumer consumer) { // TODO: add more possibilities with this method, ex outline consumers 30 | return consumer instanceof SpriteTexturedVertexConsumer ? 31 | (BufferBuilder) ((SpriteTexturedVertexConsumerAccessor) consumer).getParent() : 32 | consumer; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/mixin/WorldRendererMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.mixin; 8 | 9 | import graphics.kiln.bakedminecraftmodels.model.GlobalModelUtils; 10 | import net.minecraft.client.MinecraftClient; 11 | import net.minecraft.client.render.Camera; 12 | import net.minecraft.client.render.GameRenderer; 13 | import net.minecraft.client.render.LightmapTextureManager; 14 | import net.minecraft.client.render.WorldRenderer; 15 | import net.minecraft.client.util.math.MatrixStack; 16 | import net.minecraft.client.world.ClientWorld; 17 | import net.minecraft.util.math.Matrix4f; 18 | import org.spongepowered.asm.mixin.Mixin; 19 | import org.spongepowered.asm.mixin.Shadow; 20 | import org.spongepowered.asm.mixin.injection.At; 21 | import org.spongepowered.asm.mixin.injection.Inject; 22 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 23 | 24 | @Mixin(WorldRenderer.class) 25 | public class WorldRendererMixin { 26 | 27 | @Shadow private ClientWorld world; 28 | 29 | // TODO: this is placed here because it's supposed to render before the rest of the entity stuff, but the subsequent calls to getRenderLayer cause them to flush early, so only one or two calls happen after this. 30 | // maybe merge a batching solution in? 31 | @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/VertexConsumerProvider$Immediate;drawCurrentLayer()V", shift = At.Shift.BEFORE)) 32 | private void renderQueues(MatrixStack matrices, float tickDelta, long limitTime, boolean renderBlockOutline, Camera camera, GameRenderer gameRenderer, LightmapTextureManager lightmapTextureManager, Matrix4f matrix4f, CallbackInfo ci) { 33 | this.world.getProfiler().push("renderInstances"); 34 | GlobalModelUtils.INSTANCED_RENDER_DISPATCHER.renderQueues(); 35 | this.world.getProfiler().pop(); 36 | } 37 | 38 | @Inject(method = "close", at = @At("HEAD")) 39 | private void closeVertexBuffers(CallbackInfo ci) { 40 | GlobalModelUtils.bakingData.close(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/mixin/DebugHudMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.mixin; 8 | 9 | import graphics.kiln.bakedminecraftmodels.debug.DebugInfo; 10 | import it.unimi.dsi.fastutil.objects.Object2IntMap; 11 | import net.minecraft.client.gui.hud.DebugHud; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.injection.At; 14 | import org.spongepowered.asm.mixin.injection.Inject; 15 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 16 | 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | import java.util.Map; 20 | 21 | @Mixin(DebugHud.class) 22 | public class DebugHudMixin { 23 | 24 | @Inject(method = "getLeftText", at = @At("RETURN")) 25 | private void addInstancingText(CallbackInfoReturnable> cir) { 26 | List strings = cir.getReturnValue(); 27 | strings.add("[Baked Models] Model Buffer: " + DebugInfo.getSizeReadable(DebugInfo.currentModelBufferSize) + DebugInfo.MODEL_BUFFER_SUFFIX); 28 | strings.add("[Baked Models] Part Buffer: " + DebugInfo.getSizeReadable(DebugInfo.currentPartBufferSize) + DebugInfo.PART_BUFFER_SUFFIX); 29 | strings.add("[Baked Models] Translucent Index Buffer: " + DebugInfo.getSizeReadable(DebugInfo.currentTranslucencyEboSize) + DebugInfo.TRANSLUCENCY_EBO_SUFFIX); 30 | 31 | int totalInstances = 0; 32 | int totalSets = 0; 33 | List tempStrings = new ArrayList<>(); 34 | for (Map.Entry entry : DebugInfo.modelToDebugInfoMap.entrySet()) { 35 | DebugInfo.ModelDebugInfo modelDebugInfo = entry.getValue(); 36 | tempStrings.add("[Baked Models] " + entry.getKey() + ": " + modelDebugInfo.instances + " Instances / " + modelDebugInfo.sets + " Sets"); 37 | totalInstances += modelDebugInfo.instances; 38 | totalSets += modelDebugInfo.sets; 39 | } 40 | strings.add("[Baked Models] Total: " + totalInstances + " Instances / " + totalSets + " Sets"); 41 | strings.addAll(tempStrings); 42 | 43 | DebugInfo.currentModelBufferSize = 0; 44 | DebugInfo.currentPartBufferSize = 0; 45 | DebugInfo.currentTranslucencyEboSize = 0; 46 | DebugInfo.modelToDebugInfoMap.clear(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/debug/DebugInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.debug; 8 | 9 | import graphics.kiln.bakedminecraftmodels.gl.GlSsboRenderDispatcher; 10 | import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; 11 | 12 | import java.text.DecimalFormat; 13 | import java.util.Map; 14 | 15 | public class DebugInfo { 16 | public static final Map modelToDebugInfoMap = new Object2ObjectOpenHashMap<>(); 17 | 18 | public static final DecimalFormat sizeFormatter = new DecimalFormat("0.00"); 19 | public static final String PART_BUFFER_SUFFIX = " / " + getSizeReadable(GlSsboRenderDispatcher.PART_PBO_SIZE) + " (x" + GlSsboRenderDispatcher.BUFFER_SECTIONS + " Buffered)"; 20 | public static final String MODEL_BUFFER_SUFFIX = " / " + getSizeReadable(GlSsboRenderDispatcher.MODEL_PBO_SIZE) + " (x" + GlSsboRenderDispatcher.BUFFER_SECTIONS + " Buffered)"; 21 | public static final String TRANSLUCENCY_EBO_SUFFIX = " / " + getSizeReadable(GlSsboRenderDispatcher.TRANSLUCENT_EBO_SIZE) + " (x" + GlSsboRenderDispatcher.BUFFER_SECTIONS + " Buffered)"; 22 | public static long currentPartBufferSize; 23 | public static long currentModelBufferSize; 24 | public static long currentTranslucencyEboSize; 25 | 26 | public static String getSizeReadable(long bytes) { 27 | String suffix; 28 | double readable; 29 | 30 | if (bytes >= 0x1000000000000000L) { 31 | suffix = " EiB"; 32 | readable = (bytes >> 50); 33 | } else if (bytes >= 0x4000000000000L) { 34 | suffix = " PiB"; 35 | readable = (bytes >> 40); 36 | } else if (bytes >= 0x10000000000L) { 37 | suffix = " TiB"; 38 | readable = (bytes >> 30); 39 | } else if (bytes >= 0x40000000L) { 40 | suffix = " GiB"; 41 | readable = (bytes >> 20); 42 | } else if (bytes >= 0x100000L) { 43 | suffix = " MiB"; 44 | readable = (bytes >> 10); 45 | } else if (bytes >= 0x400L) { 46 | suffix = " KiB"; 47 | readable = bytes; 48 | } else { 49 | return bytes + " B"; 50 | } 51 | readable = (readable / 1024); 52 | return sizeFormatter.format(readable) + suffix; 53 | } 54 | 55 | public static class ModelDebugInfo { 56 | public int instances; 57 | public int sets; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/mixin/renderlayer/RenderLayerMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.mixin.renderlayer; 8 | 9 | import com.mojang.blaze3d.systems.RenderSystem; 10 | import com.mojang.datafixers.util.Pair; 11 | import graphics.kiln.bakedminecraftmodels.mixin.BufferRendererAccessor; 12 | import net.minecraft.client.render.BufferBuilder; 13 | import net.minecraft.client.render.RenderLayer; 14 | import net.minecraft.client.render.RenderPhase; 15 | import org.spongepowered.asm.mixin.Mixin; 16 | import org.spongepowered.asm.mixin.injection.At; 17 | import org.spongepowered.asm.mixin.injection.Inject; 18 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 19 | 20 | import java.nio.ByteBuffer; 21 | 22 | @Mixin(RenderLayer.class) 23 | public abstract class RenderLayerMixin extends RenderPhase { 24 | public RenderLayerMixin(String name, Runnable beginAction, Runnable endAction) { 25 | super(name, beginAction, endAction); 26 | } 27 | 28 | @Inject(method = "draw", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/RenderLayer;startDrawing()V", shift = At.Shift.BEFORE), cancellable = true) 29 | private void trySkipSetState(BufferBuilder buffer, int cameraX, int cameraY, int cameraZ, CallbackInfo ci) { 30 | Pair pair = buffer.popData(); 31 | BufferBuilder.DrawArrayParameters drawArrayParameters = pair.getFirst(); 32 | int count = drawArrayParameters.getCount(); 33 | if (count > 0) { 34 | if (!RenderSystem.isOnRenderThreadOrInit()) { 35 | RenderSystem.recordRenderCall(() -> { 36 | this.startDrawing(); 37 | BufferRendererAccessor.drawInternal(pair.getSecond(), drawArrayParameters.getMode(), drawArrayParameters.getVertexFormat(), drawArrayParameters.getCount(), drawArrayParameters.getElementFormat(), drawArrayParameters.getVertexCount(), drawArrayParameters.isTextured()); 38 | this.endDrawing(); 39 | }); 40 | } else { 41 | this.startDrawing(); 42 | BufferRendererAccessor.drawInternal(pair.getSecond(), drawArrayParameters.getMode(), drawArrayParameters.getVertexFormat(), drawArrayParameters.getCount(), drawArrayParameters.getElementFormat(), drawArrayParameters.getVertexCount(), drawArrayParameters.isTextured()); 43 | this.endDrawing(); 44 | } 45 | } 46 | ci.cancel(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /src/main/resources/baked_minecraft_models.accesswidener: -------------------------------------------------------------------------------- 1 | accessWidener v1 named 2 | accessible class net/minecraft/client/render/RenderPhase$TextureBase 3 | accessible class net/minecraft/client/render/RenderPhase$TextureBase 4 | accessible class net/minecraft/client/render/RenderPhase$Shader 5 | accessible class net/minecraft/client/render/RenderPhase$Transparency 6 | accessible class net/minecraft/client/render/RenderPhase$DepthTest 7 | accessible class net/minecraft/client/render/RenderPhase$Cull 8 | accessible class net/minecraft/client/render/RenderPhase$Lightmap 9 | accessible class net/minecraft/client/render/RenderPhase$Overlay 10 | accessible class net/minecraft/client/render/RenderPhase$Layering 11 | accessible class net/minecraft/client/render/RenderPhase$Target 12 | accessible class net/minecraft/client/render/RenderPhase$Texturing 13 | accessible class net/minecraft/client/render/RenderPhase$WriteMaskState 14 | accessible class net/minecraft/client/render/RenderPhase$LineWidth 15 | 16 | extendable class net/minecraft/client/render/RenderLayer$MultiPhase 17 | accessible class net/minecraft/client/render/RenderLayer$MultiPhaseParameters 18 | accessible class net/minecraft/client/render/RenderLayer$MultiPhaseParameters$Builder 19 | accessible class net/minecraft/client/render/RenderLayer$OutlineMode 20 | accessible class net/minecraft/client/render/RenderLayer 21 | 22 | accessible method net/minecraft/client/render/RenderLayer$MultiPhase (Ljava/lang/String;Lnet/minecraft/client/render/VertexFormat;Lnet/minecraft/client/render/VertexFormat$DrawMode;IZZLnet/minecraft/client/render/RenderLayer$MultiPhaseParameters;)V 23 | 24 | accessible field net/minecraft/util/math/Matrix3f a00 F 25 | accessible field net/minecraft/util/math/Matrix3f a01 F 26 | accessible field net/minecraft/util/math/Matrix3f a02 F 27 | accessible field net/minecraft/util/math/Matrix3f a10 F 28 | accessible field net/minecraft/util/math/Matrix3f a11 F 29 | accessible field net/minecraft/util/math/Matrix3f a12 F 30 | accessible field net/minecraft/util/math/Matrix3f a20 F 31 | accessible field net/minecraft/util/math/Matrix3f a21 F 32 | accessible field net/minecraft/util/math/Matrix3f a22 F 33 | 34 | accessible field net/minecraft/util/math/Matrix4f a00 F 35 | accessible field net/minecraft/util/math/Matrix4f a01 F 36 | accessible field net/minecraft/util/math/Matrix4f a02 F 37 | accessible field net/minecraft/util/math/Matrix4f a03 F 38 | accessible field net/minecraft/util/math/Matrix4f a10 F 39 | accessible field net/minecraft/util/math/Matrix4f a11 F 40 | accessible field net/minecraft/util/math/Matrix4f a12 F 41 | accessible field net/minecraft/util/math/Matrix4f a13 F 42 | accessible field net/minecraft/util/math/Matrix4f a20 F 43 | accessible field net/minecraft/util/math/Matrix4f a21 F 44 | accessible field net/minecraft/util/math/Matrix4f a22 F 45 | accessible field net/minecraft/util/math/Matrix4f a23 F 46 | accessible field net/minecraft/util/math/Matrix4f a30 F 47 | accessible field net/minecraft/util/math/Matrix4f a31 F 48 | accessible field net/minecraft/util/math/Matrix4f a32 F 49 | accessible field net/minecraft/util/math/Matrix4f a33 F -------------------------------------------------------------------------------- /src/main/resources/assets/minecraft/shaders/core/rendertype_entity_translucent_batched.vsh: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | #extension GL_ARB_shader_storage_buffer_object : require 3 | #extension GL_ARB_shading_language_packing : require 4 | 5 | #moj_import 6 | 7 | // We use pull rendering - these values are in vertBufferSsbo 8 | // See PullVert for more information 9 | // in vec3 Position; 10 | // in vec2 UV0; 11 | // in vec3 Normal; 12 | // in uint PartId; 13 | 14 | uniform sampler2D Sampler1; 15 | uniform sampler2D Sampler2; 16 | uniform mat4 ProjMat; 17 | uniform vec3 Light0_Direction; 18 | uniform vec3 Light1_Direction; 19 | 20 | uniform int InstanceOffset;// minecraft doesn't have a way to set uints 21 | uniform int InstanceVertCount; 22 | 23 | struct ModelPart { 24 | mat4 modelViewMat; 25 | mat3x4 normalMat; 26 | }; 27 | layout(std140, binding = 1) readonly restrict buffer modelPartsLayout { 28 | ModelPart[] modelParts; 29 | } modelPartsSsbo; 30 | 31 | struct Model { 32 | vec4 Color; 33 | ivec2 UV1; 34 | ivec2 UV2; 35 | vec3 Padding; 36 | uint PartOffset; 37 | }; 38 | 39 | layout(std140, binding = 2) readonly restrict buffer modelsLayout { 40 | Model[] models; 41 | } modelsSsbo; 42 | 43 | // This is in the same format as the VBO that would normally get passed in. Since we're doing 44 | // funny stuff with the index buffer (our instance hack requires us to reference indices multiple 45 | // times with different values in the EBO, since we have no way of telling which model we belong 46 | // to since gl_VertexID is the thing stored in the EBO), we have to use pull rendering. 47 | // 48 | // Pull rendering means that we grab the data for our vertex manually - this is designed to exactly 49 | // mirror the normal vertex attributes that we would be passed in. Specifically we have to match 50 | // the layout of BakedMinecraftModelsVertexFormats.SMART_ENTITY_FORMAT 51 | struct PullVert { 52 | vec3 Position; 53 | // Note there is an implicit four bytes of padding here from std140 54 | vec2 UV0; 55 | uint PackedNormal;// Format is byte-byte-byte-unused - see unpackSnorm4x8 56 | uint PartId; 57 | }; 58 | layout(std140, binding = 3) readonly restrict buffer vertBufferLayout { 59 | PullVert[] verts; 60 | } vertBufferSsbo; 61 | 62 | 63 | out float vertexDistance; 64 | out vec4 vertexColor; 65 | out vec4 lightMapColor; 66 | out vec4 overlayColor; 67 | out vec2 texCoord0; 68 | out vec4 normal; 69 | 70 | void main() { 71 | int instanceId = gl_VertexID / InstanceVertCount; 72 | PullVert pv = vertBufferSsbo.verts[gl_VertexID % InstanceVertCount]; 73 | vec3 Normal = unpackSnorm4x8(pv.PackedNormal).xyz;// Drop the fourth byte, it's unused 74 | Model model = modelsSsbo.models[InstanceOffset + instanceId]; 75 | ModelPart modelPart = modelPartsSsbo.modelParts[model.PartOffset + pv.PartId]; 76 | 77 | vec4 multipliedPosition = modelPart.modelViewMat * vec4(pv.Position, 1.0); 78 | gl_Position = ProjMat * multipliedPosition; 79 | 80 | vertexDistance = length(multipliedPosition.xyz); 81 | vec3 multipliedNormal = normalize(mat3(modelPart.normalMat) * Normal); 82 | vertexColor = minecraft_mix_light(Light0_Direction, Light1_Direction, multipliedNormal, model.Color); 83 | lightMapColor = texelFetch(Sampler2, model.UV2 / 16, 0); 84 | overlayColor = texelFetch(Sampler1, model.UV1, 0); 85 | texCoord0 = pv.UV0; 86 | normal = ProjMat * vec4(multipliedNormal, 0.0); 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/debug/ModelExporter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.debug; 8 | 9 | import java.io.IOException; 10 | import java.nio.ByteBuffer; 11 | import java.nio.ByteOrder; 12 | import java.nio.file.Files; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.Map; 16 | 17 | import com.mojang.datafixers.util.Pair; 18 | import graphics.kiln.bakedminecraftmodels.BakedMinecraftModelsVertexFormats; 19 | 20 | import net.minecraft.client.model.ModelPart; 21 | import net.minecraft.client.model.TexturedModelData; 22 | import net.minecraft.client.render.BufferBuilder; 23 | import net.minecraft.client.render.VertexFormat; 24 | import net.minecraft.client.render.entity.model.EntityModelLayer; 25 | import net.minecraft.client.render.entity.model.EntityModels; 26 | import net.minecraft.client.util.math.MatrixStack; 27 | 28 | import net.fabricmc.loader.api.FabricLoader; 29 | 30 | public class ModelExporter { 31 | public static void exportDefaultModelsToOBJ() { 32 | try { 33 | if (!Files.exists(FabricLoader.getInstance().getGameDir().resolve("models"))) { 34 | Files.createDirectory(FabricLoader.getInstance().getGameDir().resolve("models")); 35 | } 36 | } catch (IOException e) { 37 | e.printStackTrace(); 38 | } 39 | 40 | Map models = EntityModels.getModels(); 41 | models.forEach((entityModelLayer, modelData) -> { 42 | ModelPart model = modelData.createModel(); 43 | MatrixStack stack = new MatrixStack(); 44 | BufferBuilder consumer = new BufferBuilder(2097152); 45 | consumer.begin(VertexFormat.DrawMode.QUADS, BakedMinecraftModelsVertexFormats.SMART_ENTITY_FORMAT); 46 | model.render(stack, consumer, 0, 0); 47 | consumer.end(); 48 | Pair data = consumer.popData(); 49 | List vertices = new ArrayList<>(), normals = new ArrayList<>(), uvs = new ArrayList<>(); 50 | ByteBuffer vertexData = data.getSecond(); 51 | vertexData.order(ByteOrder.LITTLE_ENDIAN); 52 | for (int i = 0; i < vertexData.limit(); i += BakedMinecraftModelsVertexFormats.SMART_ENTITY_FORMAT.getVertexSize()) { 53 | vertices.add(String.format("v %f %f %f", 54 | vertexData.getFloat(i), 55 | vertexData.getFloat(i + 4), 56 | vertexData.getFloat(i + 8))); 57 | 58 | uvs.add(String.format("vt %f %f", 59 | vertexData.getFloat(i + 16), 60 | vertexData.getFloat(i + 20))); 61 | 62 | normals.add(String.format("vn %f %f %f", 63 | vertexData.get(i + 24) / 127.0f, 64 | vertexData.get(i + 25) / 127.0f, 65 | vertexData.get(i + 26) / 127.0f)); 66 | } 67 | 68 | List faces = new ArrayList<>(); 69 | for (int i = 1; i < vertices.size() + 1; i += 4) { 70 | faces.add(String.format("f %d/%d/%d %d/%d/%d %d/%d/%d", i, i, i, i + 1, i + 1, i + 1, i + 2, i + 2, i + 2)); 71 | faces.add(String.format("f %d/%d/%d %d/%d/%d %d/%d/%d", i + 2, i + 2, i + 2, i + 3, i + 3, i + 3, i, i, i)); 72 | } 73 | String output = String.join("\n", vertices) + "\n" + String.join("\n", uvs) + "\n" + String.join("\n", normals) + "\n" + String.join("\n", faces); 74 | try { 75 | Files.writeString(FabricLoader.getInstance().getGameDir().resolve("models").resolve("output_" + entityModelLayer.getId().getPath().replace("/", "_") + "_" + entityModelLayer.getName() + ".obj"), output); 76 | } catch (IOException e) { 77 | e.printStackTrace(); 78 | } 79 | }); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/mixin/model/ModelPartDataMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.mixin.model; 8 | 9 | import graphics.kiln.bakedminecraftmodels.access.BakeablePart; 10 | import graphics.kiln.bakedminecraftmodels.access.BakeablePartBuilder; 11 | import it.unimi.dsi.fastutil.ints.*; 12 | import net.minecraft.client.model.ModelPart; 13 | import net.minecraft.client.model.ModelPartBuilder; 14 | import net.minecraft.client.model.ModelPartData; 15 | import net.minecraft.client.model.ModelTransform; 16 | import org.jetbrains.annotations.Nullable; 17 | import org.spongepowered.asm.mixin.Mixin; 18 | import org.spongepowered.asm.mixin.Unique; 19 | import org.spongepowered.asm.mixin.injection.At; 20 | import org.spongepowered.asm.mixin.injection.Inject; 21 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 22 | import org.spongepowered.asm.mixin.injection.callback.LocalCapture; 23 | 24 | @Mixin(ModelPartData.class) 25 | public class ModelPartDataMixin implements BakeablePartBuilder { 26 | @Unique 27 | private int bmm$id = 0; 28 | 29 | @Unique 30 | private int bmm$nextId = 1; 31 | 32 | @Unique 33 | private IntSortedSet bmm$recycledIdQueue; 34 | 35 | @Unique 36 | @Nullable 37 | private BakeablePartBuilder bmm$parent; 38 | 39 | @Override 40 | public void setId(int id) { 41 | bmm$id = id; 42 | bmm$nextId = id + 1; 43 | } 44 | 45 | @Override 46 | public int getId() { 47 | return bmm$id; 48 | } 49 | 50 | @Override 51 | public void setParent(BakeablePartBuilder parent) { 52 | bmm$parent = parent; 53 | } 54 | 55 | @Override 56 | public @Nullable BakeablePartBuilder getParent() { 57 | return bmm$parent; 58 | } 59 | 60 | private IntSortedSet bmm$getOrCreateRecycledIdQueue() { 61 | if (bmm$recycledIdQueue == null) { 62 | bmm$recycledIdQueue = new IntRBTreeSet(); 63 | } 64 | return bmm$recycledIdQueue; 65 | } 66 | 67 | @Override 68 | public int getNextAvailableModelId() { 69 | ModelPartDataMixin topLevelParent = ((ModelPartDataMixin) bmm$getTopLevelParent()); 70 | IntSortedSet recycledIds = topLevelParent.bmm$getOrCreateRecycledIdQueue(); 71 | if (recycledIds.isEmpty()) { 72 | return topLevelParent.bmm$nextId++; 73 | } else { 74 | int id = recycledIds.firstInt(); 75 | recycledIds.remove(id); 76 | return id; 77 | } 78 | } 79 | 80 | @Inject(method = "addChild", at = @At("RETURN")) 81 | public void addChildID(String name, ModelPartBuilder builder, ModelTransform rotationData, CallbackInfoReturnable cir) { 82 | BakeablePartBuilder child = (BakeablePartBuilder) cir.getReturnValue(); 83 | child.setParent(this); 84 | child.setId(this.getNextAvailableModelId()); 85 | } 86 | 87 | @Inject(method = "addChild", at = @At(value = "INVOKE", target = "Ljava/util/Map;putAll(Ljava/util/Map;)V"), locals = LocalCapture.CAPTURE_FAILSOFT) 88 | public void removeOldId(String name, ModelPartBuilder builder, ModelTransform rotationData, CallbackInfoReturnable cir, ModelPartData modelPartData, ModelPartData modelPartData2){ 89 | if (modelPartData2 != null) { 90 | bmm$makeIdReusable(((BakeablePartBuilder) modelPartData2).getId()); 91 | } 92 | } 93 | 94 | @Unique 95 | private void bmm$makeIdReusable(int id) { 96 | ((ModelPartDataMixin) bmm$getTopLevelParent()).bmm$getOrCreateRecycledIdQueue().add(id); 97 | } 98 | 99 | @Unique 100 | private BakeablePartBuilder bmm$getTopLevelParent() { 101 | BakeablePartBuilder parent = this; 102 | while (parent.getParent() != null) { 103 | parent = parent.getParent(); 104 | } 105 | return parent; 106 | } 107 | 108 | @SuppressWarnings("ConstantConditions") 109 | @Inject(method = "createPart", at = @At("RETURN")) 110 | public void setModelPartID(int textureWidth, int textureHeight, CallbackInfoReturnable cir) { 111 | ((BakeablePart) (Object) cir.getReturnValue()).setId(this.bmm$id); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/BakedMinecraftModelsRenderLayerManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels; 8 | 9 | import graphics.kiln.bakedminecraftmodels.mixin.renderlayer.MultiPhaseParametersAccessor; 10 | import graphics.kiln.bakedminecraftmodels.mixin.renderlayer.MultiPhaseRenderPassAccessor; 11 | import graphics.kiln.bakedminecraftmodels.mixin.renderlayer.RenderLayerAccessor; 12 | import graphics.kiln.bakedminecraftmodels.mixin.renderlayer.RenderPhaseShaderAccessor; 13 | import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; 14 | import net.minecraft.client.render.*; 15 | import org.jetbrains.annotations.Nullable; 16 | 17 | import java.util.Map; 18 | import java.util.Optional; 19 | import java.util.function.Supplier; 20 | 21 | public class BakedMinecraftModelsRenderLayerManager { 22 | private static final Map dumbToSmart = new Object2ObjectOpenHashMap<>(); 23 | private static Map SHADER_CONVERSION_MAP; 24 | 25 | @SuppressWarnings("ConstantConditions") 26 | public static RenderLayer tryDeriveSmartRenderLayer(@Nullable RenderLayer dumbRenderLayer) { 27 | if (dumbRenderLayer == null) { 28 | return null; 29 | } 30 | 31 | RenderLayer convertedRenderLayer = null; 32 | // we check if it is contained here because null may be the intentional output for incompatible layers. 33 | if (dumbToSmart.containsKey(dumbRenderLayer)) { 34 | return dumbToSmart.get(dumbRenderLayer); 35 | } else if (dumbRenderLayer instanceof MultiPhaseRenderPassAccessor dumbMultiPhaseRenderPass) { 36 | MultiPhaseParametersAccessor dumbMultiPhaseParameters = ((MultiPhaseParametersAccessor) (Object) dumbMultiPhaseRenderPass.getPhases()); 37 | Optional> possibleSupplier = ((RenderPhaseShaderAccessor) dumbMultiPhaseParameters.getShader()).getSupplier(); 38 | if (possibleSupplier.isPresent()) { 39 | Shader dumbShader = possibleSupplier.get().get(); 40 | if (dumbShader != null) { 41 | if (SHADER_CONVERSION_MAP == null) { 42 | SHADER_CONVERSION_MAP = Map.of( 43 | GameRenderer.getRenderTypeEntityCutoutNoNullShader(), BakedMinecraftModelsShaderManager.ENTITY_CUTOUT_NO_CULL_INSTANCED, 44 | GameRenderer.getRenderTypeEntityTranslucentShader(), BakedMinecraftModelsShaderManager.ENTITY_TRANSLUCENT_BATCHED 45 | ); 46 | } 47 | 48 | Shader convertedShader = SHADER_CONVERSION_MAP.get(dumbShader); 49 | if (convertedShader != null && dumbRenderLayer instanceof RenderLayerAccessor dumbRenderLayerAccessor) { 50 | RenderLayer.MultiPhaseParameters phaseParameters = RenderLayer.MultiPhaseParameters.builder() 51 | .cull(dumbMultiPhaseParameters.getCull()) 52 | .depthTest(dumbMultiPhaseParameters.getDepthTest()) 53 | .layering(dumbMultiPhaseParameters.getLayering()) 54 | .lightmap(dumbMultiPhaseParameters.getLightmap()) 55 | .lineWidth(dumbMultiPhaseParameters.getLineWidth()) 56 | .overlay(dumbMultiPhaseParameters.getOverlay()) 57 | .shader(new RenderPhase.Shader(() -> convertedShader)) 58 | .target(dumbMultiPhaseParameters.getTarget()) 59 | .texture(dumbMultiPhaseParameters.getTexture()) 60 | .texturing(dumbMultiPhaseParameters.getTexturing()) 61 | .transparency(dumbMultiPhaseParameters.getTransparency()) 62 | .writeMaskState(dumbMultiPhaseParameters.getWriteMaskState()) 63 | .build(dumbMultiPhaseParameters.getOutlineMode()); 64 | 65 | convertedRenderLayer = new RenderLayer.MultiPhase( 66 | dumbRenderLayerAccessor.getName(), 67 | convertedShader.getFormat(), 68 | dumbRenderLayer.getDrawMode(), 69 | dumbRenderLayer.getExpectedBufferSize(), 70 | dumbRenderLayerAccessor.getHasCrumbling(), 71 | dumbRenderLayerAccessor.getTranslucent(), 72 | phaseParameters 73 | ) { 74 | /** 75 | * Minecraft spends a long time with the startDrawing and endDrawing setting opengl variables and such. 76 | * We don't want that because we know the vertex count will always be 0. 77 | */ 78 | @Override 79 | public void draw(BufferBuilder buffer, int cameraX, int cameraY, int cameraZ) { 80 | if (buffer.isBuilding()) { 81 | // TODO: i think when we're doing index based batching we don't want to do this 82 | // if (((RenderLayerAccessor) (Object) this).getTranslucent()) { 83 | // buffer.setCameraPosition((float)cameraX, (float)cameraY, (float)cameraZ); 84 | // } 85 | 86 | buffer.end(); 87 | } 88 | } 89 | }; 90 | } 91 | } 92 | } 93 | } 94 | 95 | dumbToSmart.put(dumbRenderLayer, convertedRenderLayer); 96 | return convertedRenderLayer; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/data/MatrixEntryList.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.data; 8 | 9 | import graphics.kiln.bakedminecraftmodels.model.GlobalModelUtils; 10 | import graphics.kiln.bakedminecraftmodels.ssbo.SectionedPersistentBuffer; 11 | import net.minecraft.client.util.math.MatrixStack; 12 | import net.minecraft.util.math.Matrix3f; 13 | import net.minecraft.util.math.Matrix4f; 14 | import org.lwjgl.system.MemoryUtil; 15 | 16 | import java.util.Arrays; 17 | 18 | public class MatrixEntryList { 19 | private static final int DEFAULT_SIZE = 16; 20 | 21 | private MatrixStack.Entry[] elementArray; 22 | private boolean[] elementWrittenArray; // needed to track of null entries 23 | private int largestPartId = -1; // needed to write correct amount to buffer 24 | 25 | public MatrixEntryList() { 26 | elementArray = new MatrixStack.Entry[DEFAULT_SIZE]; 27 | elementWrittenArray = new boolean[DEFAULT_SIZE]; 28 | } 29 | 30 | public MatrixEntryList(int initialPartId) { 31 | int size; 32 | if (initialPartId > DEFAULT_SIZE) { 33 | size = initialPartId; 34 | size |= (size >> 16); 35 | size |= (size >> 8); 36 | size |= (size >> 4); 37 | size |= (size >> 2); 38 | size |= (size >> 1); 39 | size++; 40 | } else { 41 | size = DEFAULT_SIZE; 42 | } 43 | 44 | elementArray = new MatrixStack.Entry[size]; 45 | elementWrittenArray = new boolean[size]; 46 | } 47 | 48 | public void set(int partId, MatrixStack.Entry element) { 49 | if (partId > largestPartId) { 50 | largestPartId = partId; 51 | 52 | if (partId >= elementArray.length) { 53 | // expand the array to the closest power of 2 that will fit the partId 54 | int newSize = partId; 55 | newSize |= (newSize >> 16); 56 | newSize |= (newSize >> 8); 57 | newSize |= (newSize >> 4); 58 | newSize |= (newSize >> 2); 59 | newSize |= (newSize >> 1); 60 | newSize++; 61 | elementWrittenArray = Arrays.copyOf(elementWrittenArray, newSize); 62 | elementArray = Arrays.copyOf(elementArray, newSize); 63 | } 64 | } 65 | elementWrittenArray[partId] = true; 66 | elementArray[partId] = element; 67 | } 68 | 69 | public boolean getElementWritten(int partId) { 70 | return elementWrittenArray[partId]; 71 | } 72 | 73 | public MatrixStack.Entry get(int partId) { 74 | return elementArray[partId]; 75 | } 76 | 77 | public void clear() { 78 | // only fill modified portions of arrays 79 | Arrays.fill(elementArray, 0, largestPartId, null); 80 | Arrays.fill(elementWrittenArray, 0, largestPartId, false); 81 | largestPartId = -1; 82 | } 83 | 84 | /** 85 | * @return the largest part id added since the last clear, or -1 if nothing has been added 86 | */ 87 | public int getLargestPartId() { 88 | return largestPartId; 89 | } 90 | 91 | public boolean isEmpty() { 92 | return largestPartId == -1; 93 | } 94 | 95 | /** 96 | * Writes the contents of this list to a buffer, with null entries represented 97 | * as a stream of 0s, and unwritten elements represented as the base entry. 98 | * 99 | * @param buffer the buffer to write to 100 | * @return the part index to be given to the struct of the model 101 | */ 102 | public long writeToBuffer(SectionedPersistentBuffer buffer, MatrixStack.Entry baseMatrixEntry) { 103 | int matrixCount = getLargestPartId() + 1; 104 | long positionOffset = buffer.getPositionOffset().getAndAdd(matrixCount * GlobalModelUtils.PART_STRUCT_SIZE); 105 | long pointer = buffer.getSectionedPointer() + positionOffset; 106 | 107 | for (int idx = 0; idx < elementArray.length; idx++) { 108 | if (elementWrittenArray[idx]) { 109 | MatrixStack.Entry matrixEntry = elementArray[idx]; 110 | if (matrixEntry != null) { 111 | writeMatrixEntry(pointer + idx * GlobalModelUtils.PART_STRUCT_SIZE, matrixEntry); 112 | } else { 113 | writeNullEntry(pointer + idx * GlobalModelUtils.PART_STRUCT_SIZE); 114 | } 115 | } else { 116 | writeMatrixEntry(pointer + idx * GlobalModelUtils.PART_STRUCT_SIZE, baseMatrixEntry); 117 | } 118 | } 119 | 120 | return positionOffset / GlobalModelUtils.PART_STRUCT_SIZE; 121 | } 122 | 123 | private static void writeMatrixEntry(long pointer, MatrixStack.Entry matrixEntry) { 124 | Matrix4f model = matrixEntry.getModel(); 125 | MemoryUtil.memPutFloat(pointer, model.a00); 126 | MemoryUtil.memPutFloat(pointer + 4, model.a10); 127 | MemoryUtil.memPutFloat(pointer + 8, model.a20); 128 | MemoryUtil.memPutFloat(pointer + 12, model.a30); 129 | MemoryUtil.memPutFloat(pointer + 16, model.a01); 130 | MemoryUtil.memPutFloat(pointer + 20, model.a11); 131 | MemoryUtil.memPutFloat(pointer + 24, model.a21); 132 | MemoryUtil.memPutFloat(pointer + 28, model.a31); 133 | MemoryUtil.memPutFloat(pointer + 32, model.a02); 134 | MemoryUtil.memPutFloat(pointer + 36, model.a12); 135 | MemoryUtil.memPutFloat(pointer + 40, model.a22); 136 | MemoryUtil.memPutFloat(pointer + 44, model.a32); 137 | MemoryUtil.memPutFloat(pointer + 48, model.a03); 138 | MemoryUtil.memPutFloat(pointer + 52, model.a13); 139 | MemoryUtil.memPutFloat(pointer + 56, model.a23); 140 | MemoryUtil.memPutFloat(pointer + 60, model.a33); 141 | 142 | Matrix3f normal = matrixEntry.getNormal(); 143 | MemoryUtil.memPutFloat(pointer + 64, normal.a00); 144 | MemoryUtil.memPutFloat(pointer + 68, normal.a10); 145 | MemoryUtil.memPutFloat(pointer + 72, normal.a20); 146 | // padding 147 | MemoryUtil.memPutFloat(pointer + 80, normal.a01); 148 | MemoryUtil.memPutFloat(pointer + 84, normal.a11); 149 | MemoryUtil.memPutFloat(pointer + 88, normal.a21); 150 | // padding 151 | MemoryUtil.memPutFloat(pointer + 96, normal.a02); 152 | MemoryUtil.memPutFloat(pointer + 100, normal.a12); 153 | MemoryUtil.memPutFloat(pointer + 104, normal.a22); 154 | // padding 155 | } 156 | 157 | private static void writeNullEntry(long pointer) { 158 | MemoryUtil.memSet(pointer, 0, GlobalModelUtils.PART_STRUCT_SIZE); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/vertex/SmartBufferBuilderWrapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.vertex; 8 | 9 | import graphics.kiln.bakedminecraftmodels.BakedMinecraftModels; 10 | import graphics.kiln.bakedminecraftmodels.mixin.buffer.BufferBuilderAccessor; 11 | import it.unimi.dsi.fastutil.floats.FloatArrayList; 12 | import it.unimi.dsi.fastutil.floats.FloatList; 13 | import it.unimi.dsi.fastutil.ints.IntArrayList; 14 | import it.unimi.dsi.fastutil.ints.IntList; 15 | import net.minecraft.client.render.BufferBuilder; 16 | import net.minecraft.client.render.VertexConsumer; 17 | import net.minecraft.client.render.VertexFormat; 18 | 19 | public class SmartBufferBuilderWrapper implements VertexConsumer { 20 | private final BufferBuilder internalBufferBuilder; 21 | private final FloatList primitivePositions; 22 | private final IntList primitivePartIds; 23 | 24 | private VertexFormat.DrawMode drawMode; 25 | private float[] primitiveVertexPositions; // vertex positions in the current primitive 26 | private int currentPosIdx; 27 | private int currentVert; // TODO: need better name for this 28 | private boolean firstPrimFinished; 29 | 30 | public SmartBufferBuilderWrapper(BufferBuilder bufferBuilder, int initialSize) { 31 | this.internalBufferBuilder = bufferBuilder; 32 | this.primitivePositions = new FloatArrayList(initialSize); 33 | this.primitivePartIds = new IntArrayList(initialSize); 34 | } 35 | 36 | @Override 37 | public VertexConsumer vertex(double x, double y, double z) { 38 | // Keep track of the vertex positions with an on-CPU buffer 39 | // Only store them as single-precision floats, that's plenty for transparency sorting 40 | 41 | primitiveVertexPositions[currentPosIdx++] = (float) x; 42 | primitiveVertexPositions[currentPosIdx++] = (float) y; 43 | primitiveVertexPositions[currentPosIdx++] = (float) z; 44 | 45 | // for modes like triangle strip and line strip, the primitive ends after the vertex count 46 | // fills, and from then it's every time the size fills. NOTE: this doesn't account for 47 | // primitive restarts, but minecraft doesn't use them anyway. 48 | boolean primFinished = false; 49 | if (firstPrimFinished) { 50 | currentVert++; 51 | if (currentVert >= drawMode.size) { 52 | currentVert = 0; 53 | primFinished = true; 54 | } 55 | } 56 | 57 | if (currentPosIdx >= primitiveVertexPositions.length) { 58 | currentPosIdx = 0; 59 | if (!firstPrimFinished) { 60 | firstPrimFinished = true; 61 | primFinished = true; 62 | } 63 | } 64 | 65 | if (primFinished) { 66 | // average vertex positions in primitive 67 | float totalX = 0.0f; 68 | float totalY = 0.0f; 69 | float totalZ = 0.0f; 70 | 71 | for (int vert = 0; vert < drawMode.vertexCount; vert++) { 72 | int startingPos = vert * 3; 73 | totalX += primitiveVertexPositions[startingPos]; 74 | totalY += primitiveVertexPositions[startingPos + 1]; 75 | totalZ += primitiveVertexPositions[startingPos + 2]; 76 | } 77 | 78 | primitivePositions.add(totalX / drawMode.vertexCount); 79 | primitivePositions.add(totalY / drawMode.vertexCount); 80 | primitivePositions.add(totalZ / drawMode.vertexCount); 81 | primitivePartIds.add(partId); 82 | } 83 | 84 | return internalBufferBuilder.vertex(x, y, z); 85 | } 86 | 87 | @Override 88 | public VertexConsumer color(int red, int green, int blue, int alpha) { 89 | return internalBufferBuilder.color(red, green, blue, alpha); 90 | } 91 | 92 | @Override 93 | public VertexConsumer texture(float u, float v) { 94 | return internalBufferBuilder.texture(u, v); 95 | } 96 | 97 | @Override 98 | public VertexConsumer overlay(int u, int v) { 99 | return internalBufferBuilder.overlay(u, v); 100 | } 101 | 102 | @Override 103 | public VertexConsumer light(int u, int v) { 104 | return internalBufferBuilder.light(u, v); 105 | } 106 | 107 | @Override 108 | public VertexConsumer normal(float x, float y, float z) { 109 | return internalBufferBuilder.normal(x, y, z); 110 | } 111 | 112 | @Override 113 | public void next() { 114 | internalBufferBuilder.next(); 115 | } 116 | 117 | @Override 118 | public void fixedColor(int red, int green, int blue, int alpha) { 119 | internalBufferBuilder.fixedColor(red, green, blue, alpha); 120 | } 121 | 122 | @Override 123 | public void unfixColor() { 124 | internalBufferBuilder.unfixColor(); 125 | } 126 | 127 | @Override 128 | public void vertex(float x, float y, float z, float red, float green, float blue, float alpha, float u, float v, int overlay, int light, float normalX, float normalY, float normalZ) { 129 | BufferBuilderAccessor originalAccessor = (BufferBuilderAccessor) internalBufferBuilder; 130 | 131 | vertex(x, y, z); // Make sure we call this, to record the verts for the transparency stuff 132 | internalBufferBuilder.texture(u, v).normal(normalX, normalY, normalZ); 133 | originalAccessor.getBuffer().putInt(originalAccessor.getElementOffset(), partId); 134 | internalBufferBuilder.nextElement(); 135 | internalBufferBuilder.next(); 136 | } 137 | 138 | private int partId; 139 | 140 | public void setId(int partId) { 141 | this.partId = partId; 142 | } 143 | 144 | public void begin(VertexFormat.DrawMode drawMode, VertexFormat vertexFormat) { 145 | internalBufferBuilder.begin(drawMode, vertexFormat); 146 | this.drawMode = drawMode; 147 | primitiveVertexPositions = new float[drawMode.vertexCount * 3]; 148 | } 149 | 150 | public int getVertexCount() { 151 | return ((BufferBuilderAccessor) internalBufferBuilder).getVertexCount(); 152 | } 153 | 154 | public void end() { 155 | internalBufferBuilder.end(); 156 | if (currentPosIdx != 0) { 157 | BakedMinecraftModels.LOGGER.warn("Primitive not finished! Pos idx at: " + currentPosIdx); 158 | currentPosIdx = 0; 159 | } 160 | primitiveVertexPositions = null; 161 | currentVert = 0; 162 | firstPrimFinished = false; 163 | } 164 | 165 | public float[] getPrimitivePositions() { 166 | return primitivePositions.toFloatArray(); 167 | } 168 | 169 | public int[] getPrimitivePartIds() { 170 | return primitivePartIds.toIntArray(); 171 | } 172 | 173 | public void clear() { 174 | internalBufferBuilder.clear(); 175 | primitivePositions.clear(); 176 | primitivePartIds.clear(); 177 | } 178 | 179 | public BufferBuilder getInternalBufferBuilder() { 180 | return internalBufferBuilder; 181 | } 182 | 183 | } 184 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/mixin/model/ModelMixins.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.mixin.model; 8 | 9 | import graphics.kiln.bakedminecraftmodels.BakedMinecraftModelsRenderLayerManager; 10 | import graphics.kiln.bakedminecraftmodels.access.BatchContainer; 11 | import graphics.kiln.bakedminecraftmodels.access.RenderLayerContainer; 12 | import graphics.kiln.bakedminecraftmodels.data.InstanceBatch; 13 | import graphics.kiln.bakedminecraftmodels.model.GlobalModelUtils; 14 | import graphics.kiln.bakedminecraftmodels.model.VboBackedModel; 15 | import net.minecraft.client.MinecraftClient; 16 | import net.minecraft.client.gl.VertexBuffer; 17 | import net.minecraft.client.render.RenderLayer; 18 | import net.minecraft.client.render.VertexConsumer; 19 | import net.minecraft.client.render.VertexFormat; 20 | import net.minecraft.client.render.block.entity.SignBlockEntityRenderer; 21 | import net.minecraft.client.render.entity.EnderDragonEntityRenderer; 22 | import net.minecraft.client.render.entity.model.*; 23 | import net.minecraft.client.util.math.MatrixStack; 24 | import org.jetbrains.annotations.Nullable; 25 | import org.spongepowered.asm.mixin.Mixin; 26 | import org.spongepowered.asm.mixin.Unique; 27 | import org.spongepowered.asm.mixin.injection.At; 28 | import org.spongepowered.asm.mixin.injection.Inject; 29 | import org.spongepowered.asm.mixin.injection.ModifyVariable; 30 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 31 | 32 | @Mixin({AnimalModel.class, 33 | BookModel.class, // TODO OPT: inject into renderBook instead of render so it works on block models 34 | CompositeEntityModel.class, 35 | EnderDragonEntityRenderer.DragonEntityModel.class, 36 | DragonHeadEntityModel.class, 37 | LlamaEntityModel.class, 38 | RabbitEntityModel.class, 39 | ShieldEntityModel.class, 40 | SignBlockEntityRenderer.SignModel.class, 41 | SinglePartEntityModel.class, 42 | SkullEntityModel.class, 43 | TintableAnimalModel.class, 44 | TintableCompositeModel.class, 45 | TridentEntityModel.class, // FIXME: enchantment glint uses dual 46 | TurtleEntityModel.class 47 | }) 48 | public class ModelMixins implements VboBackedModel { 49 | 50 | @Unique 51 | @Nullable 52 | private VertexBuffer bmm$bakedVertices; 53 | 54 | @Override 55 | @Unique 56 | public VertexBuffer getBakedVertices() { 57 | return bmm$bakedVertices; 58 | } 59 | 60 | @Unique 61 | private int bmm$vertexCount; 62 | 63 | @Override 64 | public int getVertexCount() { 65 | return bmm$vertexCount; 66 | } 67 | 68 | @Unique 69 | private float[] bmm$primitivePositions; 70 | 71 | @Override 72 | public float[] getPrimitivePositions() { 73 | return bmm$primitivePositions; 74 | } 75 | 76 | @Unique 77 | private int[] bmm$primitivePartIds; 78 | 79 | @Override 80 | public int[] getPrimitivePartIds() { 81 | return bmm$primitivePartIds; 82 | } 83 | 84 | @Unique 85 | private boolean bmm$currentPassBakeable; 86 | 87 | @Unique 88 | private VertexFormat.DrawMode bmm$drawMode; 89 | 90 | @Unique 91 | private VertexFormat bmm$vertexFormat; 92 | 93 | @Unique 94 | private MatrixStack.Entry bmm$baseMatrix; 95 | 96 | @Unique 97 | private InstanceBatch bmm$previousStoredBatch; // TODO: is this necessary anymore? should this be handled differently with batches? 98 | 99 | @Unique 100 | protected boolean bmm$childBakeable() { // this will be overridden by the lowest in the hierarchy as long as it's not private 101 | return bmm$currentPassBakeable; 102 | } 103 | 104 | @Inject(method = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumer;IIFFFF)V", at = @At("HEAD")) 105 | private void updateCurrentPass(MatrixStack matrices, VertexConsumer vertexConsumer, int light, int overlay, float red, float green, float blue, float alpha, CallbackInfo ci) { 106 | if (!bmm$childBakeable() && GlobalModelUtils.getNestedBufferBuilder(vertexConsumer) instanceof RenderLayerContainer renderLayerContainer) { 107 | RenderLayer convertedRenderLayer = BakedMinecraftModelsRenderLayerManager.tryDeriveSmartRenderLayer(renderLayerContainer.getRenderLayer()); 108 | bmm$currentPassBakeable = convertedRenderLayer != null && MinecraftClient.getInstance().getWindow() != null; 109 | if (bmm$currentPassBakeable) { 110 | bmm$drawMode = convertedRenderLayer.getDrawMode(); 111 | bmm$vertexFormat = convertedRenderLayer.getVertexFormat(); 112 | bmm$baseMatrix = matrices.peek(); 113 | 114 | BatchContainer batchContainer = (BatchContainer) matrices; 115 | bmm$previousStoredBatch = batchContainer.getBatch(); 116 | batchContainer.setBatch(GlobalModelUtils.bakingData.getOrCreateInstanceBatch(convertedRenderLayer, this)); 117 | } 118 | } 119 | } 120 | 121 | @ModifyVariable(method = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumer;IIFFFF)V", at = @At("HEAD")) 122 | private VertexConsumer changeVertexConsumer(VertexConsumer existingConsumer) { 123 | if (getBakedVertices() != null && bmm$currentPassBakeable) { 124 | return null; 125 | } else if (bmm$currentPassBakeable) { 126 | GlobalModelUtils.VBO_BUFFER_BUILDER.begin(bmm$drawMode, bmm$vertexFormat); // FIXME: not thread safe, could use a lock around it but may freeze program if nested model 127 | return GlobalModelUtils.VBO_BUFFER_BUILDER; 128 | } else { 129 | return existingConsumer; 130 | } 131 | } 132 | 133 | @Inject(method = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumer;IIFFFF)V", at = @At("TAIL")) 134 | private void createVbo(MatrixStack matrices, VertexConsumer vertexConsumer, int light, int overlay, float red, float green, float blue, float alpha, CallbackInfo ci) { 135 | if (getBakedVertices() == null && bmm$currentPassBakeable) { 136 | bmm$vertexCount = GlobalModelUtils.VBO_BUFFER_BUILDER.getVertexCount(); 137 | GlobalModelUtils.VBO_BUFFER_BUILDER.end(); 138 | bmm$primitivePositions = GlobalModelUtils.VBO_BUFFER_BUILDER.getPrimitivePositions(); 139 | bmm$primitivePartIds = GlobalModelUtils.VBO_BUFFER_BUILDER.getPrimitivePartIds(); 140 | bmm$bakedVertices = new VertexBuffer(); 141 | getBakedVertices().upload(GlobalModelUtils.VBO_BUFFER_BUILDER.getInternalBufferBuilder()); 142 | GlobalModelUtils.bakingData.addCloseable(bmm$bakedVertices); 143 | GlobalModelUtils.VBO_BUFFER_BUILDER.clear(); 144 | } 145 | } 146 | 147 | @Inject(method = "render(Lnet/minecraft/client/util/math/MatrixStack;Lnet/minecraft/client/render/VertexConsumer;IIFFFF)V", at = @At("TAIL")) 148 | private void setModelInstanceData(MatrixStack matrices, VertexConsumer vertexConsumer, int light, int overlay, float red, float green, float blue, float alpha, CallbackInfo ci) { 149 | if (bmm$currentPassBakeable) { 150 | BatchContainer batchContainer = (BatchContainer) matrices; 151 | batchContainer.getBatch().addInstance(bmm$baseMatrix, red, green, blue, alpha, overlay, light); 152 | 153 | bmm$currentPassBakeable = false; // we want this to be false by default when we start at the top again 154 | // reset variables that we don't need until next run 155 | bmm$drawMode = null; 156 | bmm$vertexFormat = null; 157 | bmm$baseMatrix = null; 158 | batchContainer.setBatch(bmm$previousStoredBatch); 159 | bmm$previousStoredBatch = null; 160 | } 161 | } 162 | 163 | } 164 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/data/BakingData.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.data; 8 | 9 | import com.google.common.collect.Iterators; 10 | import graphics.kiln.bakedminecraftmodels.BakedMinecraftModels; 11 | import graphics.kiln.bakedminecraftmodels.mixin.buffer.VertexBufferAccessor; 12 | import graphics.kiln.bakedminecraftmodels.mixin.renderlayer.MultiPhaseParametersAccessor; 13 | import graphics.kiln.bakedminecraftmodels.mixin.renderlayer.MultiPhaseRenderPassAccessor; 14 | import graphics.kiln.bakedminecraftmodels.mixin.renderlayer.RenderPhaseAccessor; 15 | import graphics.kiln.bakedminecraftmodels.model.VboBackedModel; 16 | import graphics.kiln.bakedminecraftmodels.ssbo.SectionedPersistentBuffer; 17 | import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; 18 | import net.minecraft.client.render.RenderLayer; 19 | import net.minecraft.client.render.RenderPhase; 20 | 21 | import java.io.Closeable; 22 | import java.util.*; 23 | 24 | public class BakingData implements Closeable, Iterable>> { 25 | 26 | private static final int INITIAL_BATCH_CAPACITY = 16; 27 | 28 | /** 29 | * Slice current instances on transparency where order is required. 30 | * Looks more correct, but may impact performance greatly. 31 | */ 32 | public static final boolean TRANSPARENCY_SLICING = true; 33 | 34 | private final Map> opaqueSection; 35 | /** 36 | * Each map in the deque represents a separate ordered section, which is required for transparency ordering. 37 | * For each model in the map, it has its own map where each RenderLayer has a list of instances. This is 38 | * because we can only batch instances with the same RenderLayer and model. 39 | */ 40 | private final Deque>> orderedTransparencySections; 41 | 42 | private final Set closeables; 43 | private final SectionedPersistentBuffer modelPersistentSsbo; 44 | private final SectionedPersistentBuffer partPersistentSsbo; 45 | private final SectionedPersistentBuffer translucencyPersistentEbo; 46 | private final Deque batchPool; 47 | 48 | private RenderPhase.Transparency previousTransparency; 49 | 50 | public BakingData(SectionedPersistentBuffer modelPersistentSsbo, SectionedPersistentBuffer partPersistentSsbo, SectionedPersistentBuffer translucencyPersistentEbo) { 51 | this.modelPersistentSsbo = modelPersistentSsbo; 52 | this.partPersistentSsbo = partPersistentSsbo; 53 | this.translucencyPersistentEbo = translucencyPersistentEbo; 54 | opaqueSection = new LinkedHashMap<>(); 55 | orderedTransparencySections = new ArrayDeque<>(64); 56 | closeables = new ObjectOpenHashSet<>(); 57 | batchPool = new ArrayDeque<>(64); 58 | } 59 | 60 | @SuppressWarnings("ConstantConditions") 61 | public InstanceBatch getOrCreateInstanceBatch(RenderLayer renderLayer, VboBackedModel model) { 62 | Map> renderSection; 63 | // we still use linked maps in here to try to preserve patterns for things that rely on rendering in order not based on transparency. 64 | MultiPhaseParametersAccessor multiPhaseParameters = (MultiPhaseParametersAccessor) (Object) ((MultiPhaseRenderPassAccessor) renderLayer).getPhases(); 65 | RenderPhase.Transparency currentTransparency = multiPhaseParameters.getTransparency(); 66 | if (!(currentTransparency instanceof RenderPhaseAccessor currentTransparencyAccessor) || currentTransparencyAccessor.getName().equals("no_transparency")) { 67 | renderSection = opaqueSection; 68 | } else { 69 | if (orderedTransparencySections.size() == 0) { 70 | addNewSplit(); 71 | } else if (TRANSPARENCY_SLICING && previousTransparency instanceof RenderPhaseAccessor previousTransparencyAccessor) { 72 | String currentTransparencyName = currentTransparencyAccessor.getName(); 73 | String previousTransparencyName = previousTransparencyAccessor.getName(); 74 | if (currentTransparencyName.equals(previousTransparencyName)) { 75 | addNewSplit(); 76 | } 77 | } 78 | 79 | renderSection = orderedTransparencySections.peekLast(); 80 | } 81 | previousTransparency = currentTransparency; 82 | 83 | return renderSection 84 | .computeIfAbsent(renderLayer, unused -> new LinkedHashMap<>()) 85 | .computeIfAbsent(model, model1 -> { 86 | InstanceBatch recycledBatch = batchPool.pollFirst(); 87 | if (recycledBatch != null) { 88 | recycledBatch.reset(model1, requiresIndexing(multiPhaseParameters), partPersistentSsbo); 89 | return recycledBatch; 90 | } else { 91 | return new InstanceBatch(model1, requiresIndexing(multiPhaseParameters), INITIAL_BATCH_CAPACITY, partPersistentSsbo); 92 | } 93 | }); 94 | } 95 | 96 | public void recycleInstanceBatch(InstanceBatch instanceBatch) { 97 | batchPool.add(instanceBatch); 98 | } 99 | 100 | private void addNewSplit() { 101 | orderedTransparencySections.add(new LinkedHashMap<>()); 102 | } 103 | 104 | private void writeSplitData(Map> splitData) { 105 | for (Map perRenderLayerData : splitData.values()) { 106 | for (Map.Entry perModelData : perRenderLayerData.entrySet()) { 107 | InstanceBatch instanceBatch = perModelData.getValue(); 108 | VertexBufferAccessor vertexBufferAccessor = (VertexBufferAccessor) perModelData.getKey().getBakedVertices(); 109 | instanceBatch.writeInstancesToBuffer(modelPersistentSsbo); 110 | instanceBatch.writeIndicesToBuffer(vertexBufferAccessor.getDrawMode(), translucencyPersistentEbo); 111 | } 112 | } 113 | } 114 | 115 | public void writeData() { 116 | writeSplitData(opaqueSection); 117 | 118 | for (Map> transparencySection : orderedTransparencySections) { 119 | writeSplitData(transparencySection); 120 | } 121 | } 122 | 123 | public void addCloseable(AutoCloseable closeable) { 124 | closeables.add(closeable); 125 | } 126 | 127 | public Iterator>> iterator() { 128 | return Iterators.concat(Iterators.singletonIterator(opaqueSection), orderedTransparencySections.iterator()); 129 | } 130 | 131 | public boolean isEmptyShallow() { 132 | return opaqueSection.isEmpty() && orderedTransparencySections.isEmpty(); 133 | } 134 | 135 | @SuppressWarnings("unused") 136 | public boolean isEmptyDeep() { 137 | for (Map> perOrderedSectionData : this) { 138 | for (Map perRenderLayerData : perOrderedSectionData.values()) { 139 | for (InstanceBatch instanceBatch : perRenderLayerData.values()) { 140 | if (instanceBatch.size() > 0) { 141 | return false; 142 | } 143 | } 144 | } 145 | } 146 | return true; 147 | } 148 | 149 | public void reset() { 150 | opaqueSection.clear(); 151 | orderedTransparencySections.clear(); 152 | } 153 | 154 | @Override 155 | public void close() { 156 | for (AutoCloseable closeable : closeables) { 157 | try { 158 | closeable.close(); 159 | } catch (Exception e) { 160 | BakedMinecraftModels.LOGGER.error("Error closing baking data closeables", e); 161 | } 162 | } 163 | closeables.clear(); 164 | } 165 | 166 | @SuppressWarnings("ConstantConditions") 167 | public static boolean requiresIndexing(MultiPhaseParametersAccessor multiPhaseParameters) { 168 | // instanced: opaque and additive with depth write off 169 | // index buffer: everything else 170 | String transparencyName = multiPhaseParameters.getTransparency().toString(); 171 | return !transparencyName.equals("no_transparency") 172 | && !(transparencyName.equals("additive_transparency") && multiPhaseParameters.getWriteMaskState().equals(RenderPhaseAccessor.getColorMask())); 173 | } 174 | 175 | } 176 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/mixin/model/ModelPartMixin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.mixin.model; 8 | 9 | import graphics.kiln.bakedminecraftmodels.access.BakeablePart; 10 | import graphics.kiln.bakedminecraftmodels.access.BatchContainer; 11 | import graphics.kiln.bakedminecraftmodels.data.InstanceBatch; 12 | import graphics.kiln.bakedminecraftmodels.model.GlobalModelUtils; 13 | import graphics.kiln.bakedminecraftmodels.vertex.SmartBufferBuilderWrapper; 14 | import net.minecraft.client.MinecraftClient; 15 | import net.minecraft.client.model.ModelPart; 16 | import net.minecraft.client.render.VertexConsumer; 17 | import net.minecraft.client.util.math.MatrixStack; 18 | import net.minecraft.util.math.MathHelper; 19 | import net.minecraft.util.math.Matrix3f; 20 | import net.minecraft.util.math.Matrix4f; 21 | import org.spongepowered.asm.mixin.*; 22 | import org.spongepowered.asm.mixin.injection.At; 23 | import org.spongepowered.asm.mixin.injection.Inject; 24 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 25 | 26 | import java.util.List; 27 | import java.util.Map; 28 | 29 | @Mixin(ModelPart.class) 30 | public abstract class ModelPartMixin implements BakeablePart { 31 | 32 | @Unique 33 | private int bmm$id; 34 | 35 | @Unique 36 | private boolean bmm$usingSmartRenderer; 37 | 38 | @Shadow 39 | public boolean visible; 40 | 41 | @Shadow protected abstract void renderCuboids(MatrixStack.Entry entry, VertexConsumer vertexConsumer, int light, int overlay, float red, float green, float blue, float alpha); 42 | 43 | @Shadow public float roll; 44 | 45 | @Shadow public float pitch; 46 | 47 | @Shadow public float yaw; 48 | 49 | @Shadow @Final private List cuboids; 50 | 51 | @Shadow @Final private Map children; 52 | 53 | @Shadow public abstract void rotate(MatrixStack matrix); 54 | 55 | @Override 56 | public void setId(int id) { 57 | bmm$id = id; 58 | } 59 | 60 | @Override 61 | public int getId() { 62 | return bmm$id; 63 | } 64 | 65 | @Inject(method = "rotate", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/util/math/MatrixStack;translate(DDD)V", shift = At.Shift.AFTER), cancellable = true) 66 | public void setSsboRotation(MatrixStack matrices, CallbackInfo ci) { 67 | if (bmm$usingSmartRenderer) { 68 | MatrixStack.Entry currentStackEntry = matrices.peek(); 69 | Matrix4f modelMat = currentStackEntry.getModel(); 70 | 71 | float sx = MathHelper.sin(pitch); 72 | float cx = MathHelper.cos(pitch); 73 | float sy = MathHelper.sin(yaw); 74 | float cy = MathHelper.cos(yaw); 75 | float sz = MathHelper.sin(roll); 76 | float cz = MathHelper.cos(roll); 77 | 78 | float rot00 = cy * cz; 79 | float rot01 = (sx * sy * cz) - (cx * sz); 80 | float rot02 = (cx * sy * cz) + (sx * sz); 81 | float rot10 = cy * sz; 82 | float rot11 = (sx * sy * sz) + (cx * cz); 83 | float rot12 = (cx * sy * sz) - (sx * cz); 84 | float rot20 = -sy; 85 | float rot21 = sx * cy; 86 | float rot22 = cx * cy; 87 | 88 | float newModel00 = modelMat.a00 * rot00 + modelMat.a01 * rot10 + modelMat.a02 * rot20; 89 | float newModel01 = modelMat.a00 * rot01 + modelMat.a01 * rot11 + modelMat.a02 * rot21; 90 | float newModel02 = modelMat.a00 * rot02 + modelMat.a01 * rot12 + modelMat.a02 * rot22; 91 | float newModel10 = modelMat.a10 * rot00 + modelMat.a11 * rot10 + modelMat.a12 * rot20; 92 | float newModel11 = modelMat.a10 * rot01 + modelMat.a11 * rot11 + modelMat.a12 * rot21; 93 | float newModel12 = modelMat.a10 * rot02 + modelMat.a11 * rot12 + modelMat.a12 * rot22; 94 | float newModel20 = modelMat.a20 * rot00 + modelMat.a21 * rot10 + modelMat.a22 * rot20; 95 | float newModel21 = modelMat.a20 * rot01 + modelMat.a21 * rot11 + modelMat.a22 * rot21; 96 | float newModel22 = modelMat.a20 * rot02 + modelMat.a21 * rot12 + modelMat.a22 * rot22; 97 | float newModel30 = modelMat.a30 * rot00 + modelMat.a31 * rot10 + modelMat.a32 * rot20; 98 | float newModel31 = modelMat.a30 * rot01 + modelMat.a31 * rot11 + modelMat.a32 * rot21; 99 | float newModel32 = modelMat.a30 * rot02 + modelMat.a31 * rot12 + modelMat.a32 * rot22; 100 | 101 | Matrix3f normalMat = currentStackEntry.getNormal(); 102 | // TODO: are the checks really faster? 103 | if (modelMat.a00 == normalMat.a00 && modelMat.a01 == normalMat.a01 && modelMat.a02 == normalMat.a02) { 104 | normalMat.a00 = newModel00; 105 | normalMat.a01 = newModel01; 106 | normalMat.a02 = newModel02; 107 | } else { 108 | float newNormal00 = normalMat.a00 * rot00 + normalMat.a01 * rot10 + normalMat.a02 * rot20; 109 | float newNormal01 = normalMat.a00 * rot01 + normalMat.a01 * rot11 + normalMat.a02 * rot21; 110 | float newNormal02 = normalMat.a00 * rot02 + normalMat.a01 * rot12 + normalMat.a02 * rot22; 111 | normalMat.a00 = newNormal00; 112 | normalMat.a01 = newNormal01; 113 | normalMat.a02 = newNormal02; 114 | } 115 | 116 | if (modelMat.a10 == normalMat.a10 && modelMat.a11 == normalMat.a11 && modelMat.a12 == normalMat.a12) { 117 | normalMat.a10 = newModel10; 118 | normalMat.a11 = newModel11; 119 | normalMat.a12 = newModel12; 120 | } else { 121 | float newNormal10 = normalMat.a10 * rot00 + normalMat.a11 * rot10 + normalMat.a12 * rot20; 122 | float newNormal11 = normalMat.a10 * rot01 + normalMat.a11 * rot11 + normalMat.a12 * rot21; 123 | float newNormal12 = normalMat.a10 * rot02 + normalMat.a11 * rot12 + normalMat.a12 * rot22; 124 | normalMat.a10 = newNormal10; 125 | normalMat.a11 = newNormal11; 126 | normalMat.a12 = newNormal12; 127 | } 128 | 129 | if (modelMat.a20 == normalMat.a20 && modelMat.a21 == normalMat.a21 && modelMat.a22 == normalMat.a22) { 130 | normalMat.a20 = newModel20; 131 | normalMat.a21 = newModel21; 132 | normalMat.a22 = newModel22; 133 | } else { 134 | float newNormal20 = normalMat.a20 * rot00 + normalMat.a21 * rot10 + normalMat.a22 * rot20; 135 | float newNormal21 = normalMat.a20 * rot01 + normalMat.a21 * rot11 + normalMat.a22 * rot21; 136 | float newNormal22 = normalMat.a20 * rot02 + normalMat.a21 * rot12 + normalMat.a22 * rot22; 137 | normalMat.a20 = newNormal20; 138 | normalMat.a21 = newNormal21; 139 | normalMat.a22 = newNormal22; 140 | } 141 | 142 | modelMat.a00 = newModel00; 143 | modelMat.a01 = newModel01; 144 | modelMat.a02 = newModel02; 145 | modelMat.a10 = newModel10; 146 | modelMat.a11 = newModel11; 147 | modelMat.a12 = newModel12; 148 | modelMat.a20 = newModel20; 149 | modelMat.a21 = newModel21; 150 | modelMat.a22 = newModel22; 151 | modelMat.a30 = newModel30; 152 | modelMat.a31 = newModel31; 153 | modelMat.a32 = newModel32; 154 | 155 | InstanceBatch batch = ((BatchContainer) matrices).getBatch(); 156 | // FIXME: is this always ok to do? think this is bad with skeleton holding bows 157 | if (batch != null) { 158 | batch.getMatrices().set(bmm$id, this.visible ? currentStackEntry : null); // TODO: does this method ever get called when the part is not visible? 159 | } 160 | ci.cancel(); 161 | } 162 | } 163 | 164 | /** 165 | * Used to manipulate visibility, matrices, and drawing 166 | * 167 | * @author burgerdude 168 | */ 169 | @Overwrite 170 | public void render(MatrixStack matrices, VertexConsumer vertexConsumer, int light, int overlay, float red, float green, float blue, float alpha) { 171 | if (!this.cuboids.isEmpty() || !this.children.isEmpty()) { 172 | boolean rotateOnly = vertexConsumer == null; 173 | SmartBufferBuilderWrapper smartBufferBuilderWrapper = null; 174 | if (vertexConsumer instanceof SmartBufferBuilderWrapper converted) { 175 | smartBufferBuilderWrapper = converted; 176 | } 177 | bmm$usingSmartRenderer = (rotateOnly || smartBufferBuilderWrapper != null) && MinecraftClient.getInstance().getWindow() != null; 178 | 179 | // force render when constructing the vbo 180 | if (this.visible || (!rotateOnly && bmm$usingSmartRenderer)) { 181 | matrices.push(); 182 | 183 | this.rotate(matrices); 184 | 185 | if (bmm$usingSmartRenderer) { 186 | if (!rotateOnly) { 187 | // this will never be null because the check for smart render only passes if this isn't null 188 | //noinspection ConstantConditions 189 | smartBufferBuilderWrapper.setId(this.getId()); 190 | this.renderCuboids(GlobalModelUtils.IDENTITY_STACK_ENTRY, vertexConsumer, light, overlay, red, green, blue, alpha); 191 | } 192 | } else { 193 | this.renderCuboids(matrices.peek(), vertexConsumer, light, overlay, red, green, blue, alpha); 194 | } 195 | 196 | for(ModelPart modelPart : this.children.values()) { 197 | modelPart.render(matrices, vertexConsumer, light, overlay, red, green, blue, alpha); 198 | } 199 | 200 | matrices.pop(); 201 | } else if (bmm$usingSmartRenderer) { 202 | recurseSetNullMatrix(((BatchContainer) matrices).getBatch(), (ModelPart) (Object) this); 203 | } 204 | } 205 | } 206 | 207 | private void recurseSetNullMatrix(InstanceBatch batch, ModelPart modelPart) { 208 | if ((Object) modelPart instanceof ModelPartMixin modelPartMixin) { 209 | batch.getMatrices().set(modelPartMixin.getId(), null); 210 | for (ModelPart child : modelPartMixin.children.values()) { 211 | recurseSetNullMatrix(batch, child); 212 | } 213 | } 214 | } 215 | 216 | } 217 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/gl/GlSsboRenderDispatcher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.gl; 8 | 9 | import com.mojang.blaze3d.platform.GlStateManager; 10 | import com.mojang.blaze3d.systems.RenderSystem; 11 | import graphics.kiln.bakedminecraftmodels.BakedMinecraftModels; 12 | import graphics.kiln.bakedminecraftmodels.data.InstanceBatch; 13 | import graphics.kiln.bakedminecraftmodels.debug.DebugInfo; 14 | import graphics.kiln.bakedminecraftmodels.mixin.buffer.VertexBufferAccessor; 15 | import graphics.kiln.bakedminecraftmodels.model.GlobalModelUtils; 16 | import graphics.kiln.bakedminecraftmodels.model.InstancedRenderDispatcher; 17 | import graphics.kiln.bakedminecraftmodels.model.VboBackedModel; 18 | import graphics.kiln.bakedminecraftmodels.ssbo.SectionedPersistentBuffer; 19 | import graphics.kiln.bakedminecraftmodels.ssbo.SectionedSyncObjects; 20 | import net.minecraft.client.MinecraftClient; 21 | import net.minecraft.client.gl.GlUniform; 22 | import net.minecraft.client.gl.VertexBuffer; 23 | import net.minecraft.client.render.BufferRenderer; 24 | import net.minecraft.client.render.RenderLayer; 25 | import net.minecraft.client.render.Shader; 26 | import net.minecraft.client.render.VertexFormat; 27 | import net.minecraft.client.util.Window; 28 | import org.lwjgl.opengl.*; 29 | import org.lwjgl.system.MemoryUtil; 30 | 31 | import java.util.Map; 32 | 33 | public class GlSsboRenderDispatcher implements InstancedRenderDispatcher { 34 | 35 | public static final int BUFFER_CREATION_FLAGS = GL30C.GL_MAP_WRITE_BIT | ARBBufferStorage.GL_MAP_PERSISTENT_BIT; 36 | public static final int BUFFER_MAP_FLAGS = GL30C.GL_MAP_WRITE_BIT | GL30C.GL_MAP_FLUSH_EXPLICIT_BIT | ARBBufferStorage.GL_MAP_PERSISTENT_BIT; 37 | public static final int BUFFER_SECTIONS = 3; 38 | public static final long PART_PBO_SIZE = 9175040L; // 8.75 MiB 39 | public static final long MODEL_PBO_SIZE = 524288L; // 512 KiB 40 | public static final long TRANSLUCENT_EBO_SIZE = 1048576L; // 1 MiB 41 | 42 | public final SectionedPersistentBuffer partPersistentSsbo; 43 | public final SectionedPersistentBuffer modelPersistentSsbo; 44 | public final SectionedPersistentBuffer translucencyPersistentEbo; 45 | public final SectionedSyncObjects syncObjects; 46 | 47 | public GlSsboRenderDispatcher() { 48 | partPersistentSsbo = createPersistentBuffer(ARBShaderStorageBufferObject.GL_SHADER_STORAGE_BUFFER, PART_PBO_SIZE); 49 | modelPersistentSsbo = createPersistentBuffer(ARBShaderStorageBufferObject.GL_SHADER_STORAGE_BUFFER, MODEL_PBO_SIZE); 50 | translucencyPersistentEbo = createPersistentBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, TRANSLUCENT_EBO_SIZE); 51 | syncObjects = new SectionedSyncObjects(BUFFER_SECTIONS); 52 | } 53 | 54 | private static SectionedPersistentBuffer createPersistentBuffer(int bufferType, long ssboSize) { 55 | int name = GlStateManager._glGenBuffers(); 56 | GlStateManager._glBindBuffer(bufferType, name); 57 | long fullSize = ssboSize * BUFFER_SECTIONS; 58 | ARBBufferStorage.nglBufferStorage(bufferType, fullSize, MemoryUtil.NULL, BUFFER_CREATION_FLAGS); 59 | return new SectionedPersistentBuffer( 60 | GL30C.nglMapBufferRange(bufferType, 0, fullSize, BUFFER_MAP_FLAGS), 61 | name, 62 | BUFFER_SECTIONS, 63 | ssboSize 64 | ); 65 | } 66 | 67 | @SuppressWarnings("ConstantConditions") 68 | public void renderQueues() { 69 | if (GlobalModelUtils.bakingData.isEmptyShallow()) return; 70 | 71 | long currentPartSyncObject = syncObjects.getCurrentSyncObject(); 72 | 73 | if (currentPartSyncObject != MemoryUtil.NULL) { 74 | int waitResult = GL32C.glClientWaitSync(currentPartSyncObject, GL32C.GL_SYNC_FLUSH_COMMANDS_BIT, 10000000); // 10 seconds 75 | if (waitResult == GL32C.GL_WAIT_FAILED || waitResult == GL32C.GL_TIMEOUT_EXPIRED) { 76 | BakedMinecraftModels.LOGGER.error("OpenGL sync failed, err code: " + waitResult); 77 | } 78 | } 79 | 80 | GlobalModelUtils.bakingData.writeData(); 81 | 82 | long partSectionStartPos = partPersistentSsbo.getCurrentSection() * partPersistentSsbo.getSectionSize(); 83 | long modelSectionStartPos = modelPersistentSsbo.getCurrentSection() * modelPersistentSsbo.getSectionSize(); 84 | long translucencySectionStartPos = translucencyPersistentEbo.getCurrentSection() * translucencyPersistentEbo.getSectionSize(); 85 | long partLength = partPersistentSsbo.getPositionOffset().getAcquire(); 86 | long modelLength = modelPersistentSsbo.getPositionOffset().getAcquire(); 87 | long translucencyLength = translucencyPersistentEbo.getPositionOffset().getAcquire(); 88 | 89 | GlStateManager._glBindBuffer(ARBShaderStorageBufferObject.GL_SHADER_STORAGE_BUFFER, partPersistentSsbo.getName()); 90 | GL30C.glFlushMappedBufferRange(ARBShaderStorageBufferObject.GL_SHADER_STORAGE_BUFFER, partSectionStartPos, partLength); 91 | 92 | GlStateManager._glBindBuffer(ARBShaderStorageBufferObject.GL_SHADER_STORAGE_BUFFER, modelPersistentSsbo.getName()); 93 | GL30C.glFlushMappedBufferRange(ARBShaderStorageBufferObject.GL_SHADER_STORAGE_BUFFER, modelSectionStartPos, modelLength); 94 | 95 | GlStateManager._glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, translucencyPersistentEbo.getName()); 96 | GL30C.glFlushMappedBufferRange(GL15.GL_ELEMENT_ARRAY_BUFFER, translucencySectionStartPos, translucencyLength); 97 | 98 | GL30C.glBindBufferRange(ARBShaderStorageBufferObject.GL_SHADER_STORAGE_BUFFER, 1, partPersistentSsbo.getName(), partSectionStartPos, partLength); 99 | GL30C.glBindBufferRange(ARBShaderStorageBufferObject.GL_SHADER_STORAGE_BUFFER, 2, modelPersistentSsbo.getName(), modelSectionStartPos, modelLength); 100 | 101 | DebugInfo.currentPartBufferSize = partLength; 102 | DebugInfo.currentModelBufferSize = modelLength; 103 | DebugInfo.currentTranslucencyEboSize = translucencyLength; 104 | partPersistentSsbo.nextSection(); 105 | modelPersistentSsbo.nextSection(); 106 | translucencyPersistentEbo.nextSection(); 107 | 108 | int instanceOffset = 0; 109 | 110 | RenderLayer currentRenderLayer = null; 111 | VertexBuffer currentVertexBuffer = null; 112 | BufferRenderer.unbindAll(); 113 | 114 | for (Map> perOrderedSectionData : GlobalModelUtils.bakingData) { 115 | 116 | for (Map.Entry> perRenderLayerData : perOrderedSectionData.entrySet()) { 117 | RenderLayer nextRenderLayer = perRenderLayerData.getKey(); 118 | boolean firstLayer = currentRenderLayer == null; 119 | if (firstLayer || !currentRenderLayer.equals(nextRenderLayer)) { 120 | if (!firstLayer) { 121 | currentRenderLayer.endDrawing(); 122 | } 123 | currentRenderLayer = nextRenderLayer; 124 | currentRenderLayer.startDrawing(); 125 | } 126 | 127 | Shader shader = RenderSystem.getShader(); 128 | 129 | for (Map.Entry perModelData : perRenderLayerData.getValue().entrySet()) { 130 | VboBackedModel model = perModelData.getKey(); 131 | VertexBuffer nextVertexBuffer = model.getBakedVertices(); 132 | VertexBufferAccessor vertexBufferAccessor = (VertexBufferAccessor) nextVertexBuffer; 133 | int vertexCount = vertexBufferAccessor.getVertexCount(); 134 | if (vertexCount <= 0) continue; 135 | 136 | InstanceBatch instanceBatch = perModelData.getValue(); 137 | boolean isIndexed = instanceBatch.isIndexed(); 138 | 139 | boolean firstVbo = currentVertexBuffer == null; 140 | if (firstVbo || !currentVertexBuffer.equals(nextVertexBuffer)) { 141 | if (!firstVbo) { 142 | currentVertexBuffer.getElementFormat().endDrawing(); 143 | } 144 | currentVertexBuffer = nextVertexBuffer; 145 | vertexBufferAccessor.invokeBindVertexArray(); 146 | if (isIndexed) { 147 | GL30C.glBindBufferBase(ARBShaderStorageBufferObject.GL_SHADER_STORAGE_BUFFER, 3, vertexBufferAccessor.getVertexBufferId()); 148 | } else { 149 | vertexBufferAccessor.invokeBind(); 150 | currentVertexBuffer.getElementFormat().startDrawing(); 151 | } 152 | } 153 | 154 | VertexFormat.DrawMode drawMode = vertexBufferAccessor.getDrawMode(); 155 | int instanceCount = instanceBatch.size(); 156 | if (instanceCount <= 0) continue; 157 | 158 | for (int i = 0; i < 12; ++i) { 159 | int j = RenderSystem.getShaderTexture(i); 160 | shader.addSampler("Sampler" + i, j); 161 | } 162 | 163 | if (shader.projectionMat != null) { 164 | shader.projectionMat.set(RenderSystem.getProjectionMatrix()); 165 | } 166 | 167 | if (shader.colorModulator != null) { 168 | shader.colorModulator.set(RenderSystem.getShaderColor()); 169 | } 170 | 171 | if (shader.fogStart != null) { 172 | shader.fogStart.set(RenderSystem.getShaderFogStart()); 173 | } 174 | 175 | if (shader.fogEnd != null) { 176 | shader.fogEnd.set(RenderSystem.getShaderFogEnd()); 177 | } 178 | 179 | if (shader.fogColor != null) { 180 | shader.fogColor.set(RenderSystem.getShaderFogColor()); 181 | } 182 | 183 | if (shader.textureMat != null) { 184 | shader.textureMat.set(RenderSystem.getTextureMatrix()); 185 | } 186 | 187 | if (shader.gameTime != null) { 188 | shader.gameTime.set(RenderSystem.getShaderGameTime()); 189 | } 190 | 191 | if (shader.screenSize != null) { 192 | Window window = MinecraftClient.getInstance().getWindow(); 193 | shader.screenSize.set((float) window.getFramebufferWidth(), (float) window.getFramebufferHeight()); 194 | } 195 | 196 | if (shader.lineWidth != null && (drawMode == VertexFormat.DrawMode.LINES || drawMode == VertexFormat.DrawMode.LINE_STRIP)) { 197 | shader.lineWidth.set(RenderSystem.getShaderLineWidth()); 198 | } 199 | 200 | // we have to manually get it from the shader every time because different shaders have different uniform objects for the same uniform. 201 | GlUniform instanceOffsetUniform = shader.getUniform("InstanceOffset"); 202 | if (instanceOffsetUniform != null) { 203 | instanceOffsetUniform.set(instanceOffset); 204 | } 205 | 206 | if (isIndexed) { 207 | drawSortedFakeInstanced(instanceBatch, shader, vertexBufferAccessor, model.getVertexCount(), translucencySectionStartPos); 208 | } else { 209 | RenderSystem.setupShaderLights(shader); 210 | shader.bind(); 211 | if (instanceCount > 1) { 212 | GL31C.glDrawElementsInstanced(drawMode.mode, vertexCount, vertexBufferAccessor.getVertexFormat().count, MemoryUtil.NULL, instanceCount); 213 | } else { 214 | GL11.glDrawElements(drawMode.mode, vertexCount, vertexBufferAccessor.getVertexFormat().count, MemoryUtil.NULL); 215 | } 216 | shader.unbind(); 217 | } 218 | 219 | instanceOffset += instanceCount; 220 | 221 | DebugInfo.ModelDebugInfo currentDebugInfo = DebugInfo.modelToDebugInfoMap.computeIfAbsent(model.getClass().getSimpleName(), ignored -> new DebugInfo.ModelDebugInfo()); 222 | currentDebugInfo.instances += instanceCount; 223 | currentDebugInfo.sets++; 224 | 225 | GlobalModelUtils.bakingData.recycleInstanceBatch(instanceBatch); 226 | } 227 | } 228 | } 229 | 230 | if (currentVertexBuffer != null) { 231 | currentVertexBuffer.getElementFormat().endDrawing(); 232 | } 233 | 234 | if (currentRenderLayer != null) { 235 | currentRenderLayer.endDrawing(); 236 | } 237 | 238 | if (currentPartSyncObject != MemoryUtil.NULL) { 239 | GL32C.glDeleteSync(currentPartSyncObject); 240 | } 241 | syncObjects.setCurrentSyncObject(GL32C.glFenceSync(GL32C.GL_SYNC_GPU_COMMANDS_COMPLETE, 0)); 242 | syncObjects.nextSection(); 243 | 244 | GlobalModelUtils.bakingData.reset(); 245 | } 246 | 247 | /** 248 | * Do a 'fake' glDrawElementsInstanced call, with vertex sorting. 249 | *

250 | * This builds a EBO containing all the vertices for all the elements to draw. The notable 251 | * thing here is that we have control of the draw order - we can sort the elements by depth 252 | * from the camera, and use this to batch the rendering of transparent objects. 253 | */ 254 | private void drawSortedFakeInstanced(InstanceBatch batch, Shader shader, VertexBufferAccessor vba, int vertexCount, long sectionStartPos) { 255 | GlUniform countUniform = shader.getUniform("InstanceVertCount"); 256 | if (countUniform != null) { 257 | countUniform.set(vertexCount); 258 | } 259 | 260 | // this needs to be re-bound because normal instanced stuff will probably be rendered before this 261 | GlStateManager._glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, translucencyPersistentEbo.getName()); 262 | 263 | // TODO do we need to disable the VAO here? It's not bound in the shader, can it still 264 | // cause issues? 265 | // (from burger) probably not 266 | 267 | RenderSystem.setupShaderLights(shader); 268 | shader.bind(); 269 | VertexFormat.DrawMode drawMode = vba.getDrawMode(); 270 | VertexFormat.IntType indexType = batch.getIndexType(); 271 | GL11.glDrawElements(drawMode.mode, batch.getIndexCount(), indexType.count, sectionStartPos + batch.getIndexStartingPos()); 272 | shader.unbind(); 273 | 274 | // TODO Unbind EBO? 275 | // (from burger) the next thing that comes across will replace the binding, so it's probably not needed 276 | } 277 | 278 | } 279 | -------------------------------------------------------------------------------- /src/main/java/graphics/kiln/bakedminecraftmodels/data/InstanceBatch.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | package graphics.kiln.bakedminecraftmodels.data; 8 | 9 | import graphics.kiln.bakedminecraftmodels.model.VboBackedModel; 10 | import graphics.kiln.bakedminecraftmodels.ssbo.SectionedPersistentBuffer; 11 | import it.unimi.dsi.fastutil.ints.IntArrays; 12 | import net.minecraft.client.render.LightmapTextureManager; 13 | import net.minecraft.client.render.VertexFormat; 14 | import net.minecraft.client.util.math.MatrixStack; 15 | import net.minecraft.util.math.Matrix4f; 16 | import org.lwjgl.system.MemoryUtil; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | public class InstanceBatch { 22 | 23 | private final List instances; 24 | private final MatrixEntryList matrices; 25 | 26 | private VboBackedModel model; 27 | private boolean indexed; 28 | private SectionedPersistentBuffer partBuffer; 29 | 30 | private VertexFormat.IntType indexType; 31 | private long indexStartingPos; 32 | private int indexCount; 33 | 34 | public InstanceBatch(VboBackedModel model, boolean indexed, int initialSize, SectionedPersistentBuffer partBuffer) { 35 | this.instances = new ArrayList<>(initialSize); 36 | this.matrices = new MatrixEntryList(initialSize); 37 | this.model = model; 38 | this.indexed = indexed; 39 | this.partBuffer = partBuffer; 40 | } 41 | 42 | public void reset(VboBackedModel model, boolean indexed, SectionedPersistentBuffer partBuffer) { 43 | this.model = model; 44 | this.indexed = indexed; 45 | this.partBuffer = partBuffer; 46 | 47 | instances.clear(); 48 | matrices.clear(); 49 | 50 | indexType = null; 51 | indexStartingPos = 0; 52 | indexCount = 0; 53 | } 54 | 55 | public boolean isIndexed() { 56 | return indexed; 57 | } 58 | 59 | public MatrixEntryList getMatrices() { 60 | return matrices; 61 | } 62 | 63 | public void writeInstancesToBuffer(SectionedPersistentBuffer buffer) { 64 | for (PerInstanceData perInstanceData : instances) { 65 | perInstanceData.writeToBuffer(buffer); 66 | } 67 | } 68 | 69 | public void addInstance(MatrixStack.Entry baseMatrixEntry, float red, float green, float blue, float alpha, int overlay, int light) { 70 | int overlayX = overlay & 0xFFFF; 71 | int overlayY = overlay >> 16 & 0xFFFF; 72 | int lightX = light & (LightmapTextureManager.MAX_BLOCK_LIGHT_COORDINATE | 0xFF0F); 73 | int lightY = light >> 16 & (LightmapTextureManager.MAX_BLOCK_LIGHT_COORDINATE | 0xFF0F); 74 | 75 | // this can happen if the model didn't render any modelparts, 76 | // in which case it makes sense to not try to render it anyway. 77 | if (matrices.isEmpty()) return; 78 | long partIndex = matrices.writeToBuffer(partBuffer, baseMatrixEntry); 79 | 80 | int[] primitiveIndices = null; 81 | int skippedPrimitivesStart = 0; 82 | if (indexed) { 83 | // Build the camera transforms from all the part transforms 84 | // This is how we quickly measure the depth of each primitive - find the 85 | // camera's position in model space, rather than applying the matrix multiply 86 | // to the primitive's position. 87 | int partIds = matrices.getLargestPartId() + 1; 88 | float[] cameraPositions = new float[partIds * 3]; 89 | for (int partId = 0; partId < partIds; partId++) { 90 | Matrix4f mv; 91 | if (!matrices.getElementWritten(partId)) { 92 | mv = baseMatrixEntry.getModel(); 93 | } else { 94 | MatrixStack.Entry entry = matrices.get(partId); 95 | if (entry != null) { 96 | mv = entry.getModel(); 97 | } else { 98 | // skip empty part 99 | continue; 100 | } 101 | } 102 | 103 | // The translation of the inverse of a transform matrix is the negation of 104 | // the transposed rotation times the transform of the original matrix. 105 | // 106 | // The above only works if there's no scaling though - to correct for that, we 107 | // can find the length of each column is the scaling factor for x, y or z depending 108 | // on the column number. We then divide each of the output components by the 109 | // square of the scaling factor - since we're multiplying the scaling factor in a 110 | // second time with the matrix multiply, we have to divide twice (same as divide by sq root) 111 | // to get the actual inverse. 112 | 113 | // Using fastInverseSqrt might be playing with fire here 114 | double undoScaleX = 1.0 / Math.sqrt(mv.a00 * mv.a00 + mv.a10 * mv.a10 + mv.a20 * mv.a20); 115 | double undoScaleY = 1.0 / Math.sqrt(mv.a01 * mv.a01 + mv.a11 * mv.a11 + mv.a21 * mv.a21); 116 | double undoScaleZ = 1.0 / Math.sqrt(mv.a02 * mv.a02 + mv.a12 * mv.a12 + mv.a22 * mv.a22); 117 | 118 | int arrayIdx = partId * 3; 119 | cameraPositions[arrayIdx] = (float) (-(mv.a00 * mv.a03 + mv.a10 * mv.a13 + mv.a20 * mv.a23) * undoScaleX * undoScaleX); 120 | cameraPositions[arrayIdx + 1] = (float) (-(mv.a01 * mv.a03 + mv.a11 * mv.a13 + mv.a21 * mv.a23) * undoScaleY * undoScaleY); 121 | cameraPositions[arrayIdx + 2] = (float) (-(mv.a02 * mv.a03 + mv.a12 * mv.a13 + mv.a22 * mv.a23) * undoScaleZ * undoScaleZ); 122 | } 123 | 124 | float[] primitivePositions = model.getPrimitivePositions(); 125 | int[] primitivePartIds = model.getPrimitivePartIds(); 126 | int totalPrimitives = primitivePartIds.length; 127 | 128 | float[] primitiveSqDistances = new float[totalPrimitives]; 129 | primitiveIndices = new int[totalPrimitives]; 130 | for (int i = 0; i < totalPrimitives; i++) { 131 | primitiveIndices[i] = i; 132 | } 133 | 134 | for (int prim = 0; prim < totalPrimitives; prim++) { 135 | // skip if written as null 136 | int partId = primitivePartIds[prim]; 137 | if (matrices.getElementWritten(partId) && matrices.get(partId) == null) { 138 | primitiveSqDistances[prim] = Float.MIN_VALUE; 139 | skippedPrimitivesStart++; 140 | } 141 | 142 | int primPosIdx = prim * 3; 143 | float x = primitivePositions[primPosIdx]; 144 | float y = primitivePositions[primPosIdx + 1]; 145 | float z = primitivePositions[primPosIdx + 2]; 146 | 147 | int camPosIdx = partId * 3; 148 | float deltaX = x - cameraPositions[camPosIdx]; 149 | float deltaY = y - cameraPositions[camPosIdx + 1]; 150 | float deltaZ = z - cameraPositions[camPosIdx + 2]; 151 | primitiveSqDistances[prim] = deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ; 152 | } 153 | 154 | // sort distances closest to furthest for front to back order 155 | IntArrays.quickSort(primitiveIndices, (i1, i2) -> Float.compare(primitiveSqDistances[i1], primitiveSqDistances[i2])); 156 | } 157 | 158 | // int skippedPrimitivesEnd = primitiveIndices == null ? 0 : (primitiveIndices.length - skippedPrimitivesStart) / 2; 159 | int skippedPrimitivesEnd = 0; 160 | instances.add(new PerInstanceData(partIndex, red, green, blue, alpha, overlayX, overlayY, lightX, lightY, primitiveIndices, skippedPrimitivesStart, skippedPrimitivesEnd)); 161 | } 162 | 163 | public int size() { 164 | return instances.size(); 165 | } 166 | 167 | public void writeIndicesToBuffer(VertexFormat.DrawMode drawMode, SectionedPersistentBuffer buffer) { 168 | if (!isIndexed()) return; 169 | 170 | // this is pretty slow 171 | indexCount = drawMode.getSize(drawMode.vertexCount * instances.stream().mapToInt(p -> p.primitiveIndices().length - p.skippedPrimitivesStart() - p.skippedPrimitivesEnd()).sum()); 172 | indexType = VertexFormat.IntType.getSmallestTypeFor(indexCount); 173 | long sizeBytes = (long) indexCount * indexType.size; 174 | // add with alignment 175 | indexStartingPos = buffer.getPositionOffset().getAndAccumulate(sizeBytes, Long::sum); 176 | // sectioned pointer also has to be aligned 177 | long ptr = buffer.getSectionedPointer() + indexStartingPos; 178 | 179 | IndexWriter indexWriter = getIndexFunction(indexType, drawMode); 180 | int lastIndex = 0; 181 | for (PerInstanceData instanceData : instances) { 182 | int[] primitiveIndices = instanceData.primitiveIndices(); 183 | int skippedPrimitivesStart = instanceData.skippedPrimitivesStart(); 184 | int skippedPrimitivesEnd = instanceData.skippedPrimitivesEnd(); 185 | for (int i = skippedPrimitivesStart; i < primitiveIndices.length - skippedPrimitivesEnd; i++) { 186 | int indexStart = lastIndex + primitiveIndices[i] * drawMode.vertexCount; 187 | indexWriter.writeIndices(ptr, indexStart, drawMode.vertexCount); 188 | ptr += (long) drawMode.getSize(drawMode.vertexCount) * indexType.size; 189 | } 190 | // we want to include the skipped primitives because the index needs to be calculated to the corresponding instance 191 | lastIndex += primitiveIndices.length * drawMode.vertexCount; 192 | // (primitiveIndices.length - skippedPrimitivesStart - skippedPrimitivesEnd) * drawMode.vertexCount; 193 | } 194 | } 195 | 196 | // The Cool Way(tm) to do index writing 197 | private static IndexWriter getIndexFunction(VertexFormat.IntType indexType, VertexFormat.DrawMode drawMode) { 198 | IndexWriter function; 199 | switch (indexType) { 200 | case BYTE -> { 201 | switch (drawMode) { 202 | case LINES -> function = (ptr, startIdx, ignored) -> { 203 | MemoryUtil.memPutByte(ptr, (byte) startIdx); 204 | MemoryUtil.memPutByte(ptr + 1, (byte) (startIdx + 1)); 205 | MemoryUtil.memPutByte(ptr + 2, (byte) (startIdx + 2)); 206 | MemoryUtil.memPutByte(ptr + 3, (byte) (startIdx + 3)); 207 | MemoryUtil.memPutByte(ptr + 4, (byte) (startIdx + 2)); 208 | MemoryUtil.memPutByte(ptr + 5, (byte) (startIdx + 1)); 209 | }; 210 | case QUADS -> function = (ptr, startIdx, ignored) -> { 211 | MemoryUtil.memPutByte(ptr, (byte) startIdx); 212 | MemoryUtil.memPutByte(ptr + 1, (byte) (startIdx + 1)); 213 | MemoryUtil.memPutByte(ptr + 2, (byte) (startIdx + 2)); 214 | MemoryUtil.memPutByte(ptr + 3, (byte) (startIdx + 2)); 215 | MemoryUtil.memPutByte(ptr + 4, (byte) (startIdx + 3)); 216 | MemoryUtil.memPutByte(ptr + 5, (byte) startIdx); 217 | }; 218 | default -> function = (ptr, startIdx, vertsPerPrim) -> { 219 | for (int i = 0; i < drawMode.vertexCount; i++) { 220 | MemoryUtil.memPutByte(ptr + i, (byte) (startIdx + i)); 221 | } 222 | }; 223 | } 224 | } 225 | case SHORT -> { 226 | switch (drawMode) { 227 | case LINES -> function = (ptr, startIdx, ignored) -> { 228 | MemoryUtil.memPutShort(ptr, (short) startIdx); 229 | MemoryUtil.memPutShort(ptr + 2, (short) (startIdx + 1)); 230 | MemoryUtil.memPutShort(ptr + 4, (short) (startIdx + 2)); 231 | MemoryUtil.memPutShort(ptr + 6, (short) (startIdx + 3)); 232 | MemoryUtil.memPutShort(ptr + 8, (short) (startIdx + 2)); 233 | MemoryUtil.memPutShort(ptr + 10, (short) (startIdx + 1)); 234 | }; 235 | case QUADS -> function = (ptr, startIdx, ignored) -> { 236 | MemoryUtil.memPutShort(ptr, (short) startIdx); 237 | MemoryUtil.memPutShort(ptr + 2, (short) (startIdx + 1)); 238 | MemoryUtil.memPutShort(ptr + 4, (short) (startIdx + 2)); 239 | MemoryUtil.memPutShort(ptr + 6, (short) (startIdx + 2)); 240 | MemoryUtil.memPutShort(ptr + 8, (short) (startIdx + 3)); 241 | MemoryUtil.memPutShort(ptr + 10, (short) startIdx); 242 | }; 243 | default -> function = (ptr, startIdx, vertsPerPrim) -> { 244 | for (int i = 0; i < drawMode.vertexCount; i++) { 245 | MemoryUtil.memPutShort(ptr + i * 2L, (short) (startIdx + i)); 246 | } 247 | }; 248 | } 249 | } 250 | case INT -> { 251 | switch (drawMode) { 252 | case LINES -> function = (ptr, startIdx, ignored) -> { 253 | MemoryUtil.memPutInt(ptr, startIdx); 254 | MemoryUtil.memPutInt(ptr + 4, startIdx + 1); 255 | MemoryUtil.memPutInt(ptr + 8, startIdx + 2); 256 | MemoryUtil.memPutInt(ptr + 12, startIdx + 3); 257 | MemoryUtil.memPutInt(ptr + 16, startIdx + 2); 258 | MemoryUtil.memPutInt(ptr + 20, startIdx + 1); 259 | }; 260 | case QUADS -> function = (ptr, startIdx, ignored) -> { 261 | MemoryUtil.memPutInt(ptr, startIdx); 262 | MemoryUtil.memPutInt(ptr + 4, startIdx + 1); 263 | MemoryUtil.memPutInt(ptr + 8, startIdx + 2); 264 | MemoryUtil.memPutInt(ptr + 12,startIdx + 2); 265 | MemoryUtil.memPutInt(ptr + 16,startIdx + 3); 266 | MemoryUtil.memPutInt(ptr + 20, startIdx); 267 | }; 268 | default -> function = (ptr, startIdx, vertsPerPrim) -> { 269 | for (int i = 0; i < drawMode.vertexCount; i++) { 270 | MemoryUtil.memPutInt(ptr + i * 4L, startIdx + i); 271 | } 272 | }; 273 | } 274 | } 275 | default -> throw new IllegalArgumentException("Index type " + indexType.name() + " unknown"); 276 | } 277 | return function; 278 | } 279 | 280 | public long getIndexStartingPos() { 281 | return indexStartingPos; 282 | } 283 | 284 | public VertexFormat.IntType getIndexType() { 285 | return indexType; 286 | } 287 | 288 | public int getIndexCount() { 289 | return indexCount; 290 | } 291 | 292 | } 293 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | --------------------------------------------------------------------------------