From 71237caa8191b16f40daa4aa228d2a1ff11eba41 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Mon, 9 Mar 2026 16:28:57 -0500 Subject: [PATCH] Start moving OpenGL rendering to common 1 --- .../common/render/nativeGl/DebugRenderer.java | 281 +++++++ .../nativeGl/DhTerrainShaderProgram.java | 227 ++++++ .../common/render/nativeGl/LodRenderer.java | 766 ++++++++++++++++++ .../generic/GenericObjectRenderer.java | 735 +++++++++++++++++ .../generic/GenericObjectShaderProgram.java | 232 ++++++ .../generic/GenericRenderObjectFactory.java | 75 ++ .../NativeGlGenericObjectVertexContainer.java | 180 ++++ .../nativeGl/generic/RenderableBoxGroup.java | 361 +++++++++ .../render/nativeGl/glObject/GLEnums.java | 261 ++++++ .../render/nativeGl/glObject/GLProxy.java | 351 ++++++++ .../render/nativeGl/glObject/GLState.java | 259 ++++++ .../nativeGl/glObject/buffer/GLBuffer.java | 345 ++++++++ .../glObject/buffer/GLElementBuffer.java | 60 ++ .../glObject/buffer/GLVertexBuffer.java | 88 ++ .../glObject/buffer/QuadElementBuffer.java | 192 +++++ .../nativeGl/glObject/shader/Shader.java | 184 +++++ .../glObject/shader/ShaderProgram.java | 225 +++++ .../glObject/texture/DHDepthTexture.java | 62 ++ .../glObject/texture/DhColorTexture.java | 183 +++++ .../glObject/texture/DhFramebuffer.java | 152 ++++ .../texture/EDhDepthBufferFormat.java | 114 +++ .../texture/EDhInternalTextureFormat.java | 130 +++ .../glObject/texture/EDhPixelFormat.java | 60 ++ .../glObject/texture/EDhPixelType.java | 64 ++ .../nativeGl/glObject/texture/EGlVersion.java | 9 + .../AbstractVertexAttribute.java | 92 +++ .../VertexAttributePostGL43.java | 155 ++++ .../VertexAttributePreGL43.java | 253 ++++++ .../vertexAttribute/VertexPointer.java | 72 ++ .../nativeGl/postProcessing/ScreenQuad.java | 96 +++ .../postProcessing/VanillaFadeRenderer.java | 188 +++++ .../postProcessing/apply/DhApplyShader.java | 199 +++++ .../postProcessing/fade/DhFadeRenderer.java | 161 ++++ .../postProcessing/fade/DhFadeShader.java | 165 ++++ .../postProcessing/fade/FadeApplyShader.java | 118 +++ .../fade/VanillaFadeShader.java | 198 +++++ .../postProcessing/fog/FogApplyShader.java | 118 +++ .../postProcessing/fog/FogRenderer.java | 140 ++++ .../postProcessing/fog/FogSettings.java | 72 ++ .../postProcessing/fog/FogShader.java | 287 +++++++ .../postProcessing/ssao/SSAOApplyShader.java | 145 ++++ .../postProcessing/ssao/SSAORenderer.java | 140 ++++ .../postProcessing/ssao/SSAOShader.java | 142 ++++ .../render/nativeGl/test/GlTestRenderer.java | 141 ++++ .../nativeGl/util/AbstractShaderRenderer.java | 77 ++ .../util/vertexFormat/LodVertexFormat.java | 110 +++ .../vertexFormat/LodVertexFormatElement.java | 168 ++++ .../util/vertexFormat/VertexFormats.java | 50 ++ 48 files changed, 8883 insertions(+) create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/DebugRenderer.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/DhTerrainShaderProgram.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/LodRenderer.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/generic/GenericObjectRenderer.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/generic/GenericObjectShaderProgram.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/generic/GenericRenderObjectFactory.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/generic/NativeGlGenericObjectVertexContainer.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/generic/RenderableBoxGroup.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/GLEnums.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/GLProxy.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/GLState.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/buffer/GLBuffer.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/buffer/GLElementBuffer.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/buffer/GLVertexBuffer.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/buffer/QuadElementBuffer.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/shader/Shader.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/shader/ShaderProgram.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/texture/DHDepthTexture.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/texture/DhColorTexture.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/texture/DhFramebuffer.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/texture/EDhDepthBufferFormat.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/texture/EDhInternalTextureFormat.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/texture/EDhPixelFormat.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/texture/EDhPixelType.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/texture/EGlVersion.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/vertexAttribute/AbstractVertexAttribute.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/vertexAttribute/VertexAttributePostGL43.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/vertexAttribute/VertexAttributePreGL43.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/vertexAttribute/VertexPointer.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/ScreenQuad.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/VanillaFadeRenderer.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/apply/DhApplyShader.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/fade/DhFadeRenderer.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/fade/DhFadeShader.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/fade/FadeApplyShader.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/fade/VanillaFadeShader.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/fog/FogApplyShader.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/fog/FogRenderer.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/fog/FogSettings.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/fog/FogShader.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/ssao/SSAOApplyShader.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/ssao/SSAORenderer.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/ssao/SSAOShader.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/test/GlTestRenderer.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/util/AbstractShaderRenderer.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/util/vertexFormat/LodVertexFormat.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/util/vertexFormat/LodVertexFormatElement.java create mode 100644 common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/util/vertexFormat/VertexFormats.java diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/DebugRenderer.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/DebugRenderer.java new file mode 100644 index 000000000..e39876121 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/DebugRenderer.java @@ -0,0 +1,281 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl; + +import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod; +import com.seibel.distanthorizons.common.render.nativeGl.glObject.buffer.GLElementBuffer; +import com.seibel.distanthorizons.common.render.nativeGl.glObject.shader.ShaderProgram; +import com.seibel.distanthorizons.common.render.nativeGl.glObject.vertexAttribute.AbstractVertexAttribute; +import com.seibel.distanthorizons.common.render.nativeGl.glObject.vertexAttribute.VertexPointer; +import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftGLWrapper; +import com.seibel.distanthorizons.core.config.Config; +import com.seibel.distanthorizons.core.config.types.ConfigEntry; +import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; +import com.seibel.distanthorizons.core.logging.DhLogger; +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import com.seibel.distanthorizons.core.pos.DhSectionPos; +import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable; +import com.seibel.distanthorizons.core.render.RenderParams; +import com.seibel.distanthorizons.core.util.math.Mat4f; +import com.seibel.distanthorizons.core.util.math.Vec3d; +import com.seibel.distanthorizons.core.util.math.Vec3f; +import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; +import com.seibel.distanthorizons.core.wrapperInterfaces.render.IMcDebugRenderer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.lwjgl.opengl.GL32; + +import java.awt.*; +import java.lang.ref.WeakReference; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.concurrent.PriorityBlockingQueue; + +/** + * Handles rendering the wireframe particles + * that are used for seeing what the system's doing. + */ +public class DebugRenderer +{ + public static DebugRenderer INSTANCE = new DebugRenderer(); + + public static final DhLogger LOGGER = new DhLoggerBuilder().build(); + public static final DhLogger RATE_LIMITED_LOGGER = new DhLoggerBuilder() + .maxCountPerSecond(1) + .build(); + + private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); + private static final MinecraftGLWrapper GLMC = MinecraftGLWrapper.INSTANCE; + + + + // rendering setup + private ShaderProgram basicShader; + private GLElementBuffer outlineIndexBuffer; + private AbstractVertexAttribute va; + private boolean init = false; + + // used when rendering + private Mat4f dhMvmProjMatrixThisFrame; + private Vec3f camPosFloatThisFrame; + + + private final IMcDebugRenderer.RendererLists rendererLists = new IMcDebugRenderer.RendererLists(); + private final PriorityBlockingQueue particles = new PriorityBlockingQueue<>(); + + + + + /** A box from 0,0,0 to 1,1,1 */ + private static final float[] BOX_VERTICES = { + //region + // Pos x y z + 0, 0, 0, + 1, 0, 0, + 1, 1, 0, + 0, 1, 0, + 0, 0, 1, + 1, 0, 1, + 1, 1, 1, + 0, 1, 1, + //endregion + }; + + private static final int[] BOX_OUTLINE_INDICES = { + //region + 0, 1, + 1, 2, + 2, 3, + 3, 0, + + 4, 5, + 5, 6, + 6, 7, + 7, 4, + + 0, 4, + 1, 5, + 2, 6, + 3, 7, + //endregion + }; + + + + //=============// + // constructor // + //=============// + //region + + private DebugRenderer() { } + + public void init() + { + if (this.init) + { + return; + } + this.init = true; + + this.va = AbstractVertexAttribute.create(); + this.va.bind(); + // Pos + this.va.setVertexAttribute(0, 0, VertexPointer.addVec3Pointer(false)); + this.va.completeAndCheck(Float.BYTES * 3); + this.basicShader = new ShaderProgram( + "shaders/debug/vert.vert", + "shaders/debug/frag.frag", + "vPosition" + ); + this.createBuffer(); + } + + private void createBuffer() + { + // box vertices + ByteBuffer boxVerticesBuffer = ByteBuffer.allocateDirect(BOX_VERTICES.length * Float.BYTES); + boxVerticesBuffer.order(ByteOrder.nativeOrder()); + boxVerticesBuffer.asFloatBuffer().put(BOX_VERTICES); + boxVerticesBuffer.rewind(); + + + // outline vertex indexes + ByteBuffer boxOutlineBuffer = ByteBuffer.allocateDirect(BOX_OUTLINE_INDICES.length * Integer.BYTES); + boxOutlineBuffer.order(ByteOrder.nativeOrder()); + boxOutlineBuffer.asIntBuffer().put(BOX_OUTLINE_INDICES); + boxOutlineBuffer.rewind(); + this.outlineIndexBuffer = new GLElementBuffer(false); + this.outlineIndexBuffer.uploadBuffer(boxOutlineBuffer, EDhApiGpuUploadMethod.DATA, BOX_OUTLINE_INDICES.length * Integer.BYTES, GL32.GL_STATIC_DRAW); + + } + + //endregion + + + + + + //===========// + // rendering // + //===========// + //region + + public void render(RenderParams renderEventParam) + { + //this.dhMvmProjMatrixThisFrame = dhMvmProjMatrix; // TODO + Vec3d camPos = MC_RENDER.getCameraExactPosition(); + this.camPosFloatThisFrame = new Vec3f((float) camPos.x, (float) camPos.y, (float) camPos.z); + + this.init(); + + GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_LINE); + GLMC.enableDepthTest(); + + this.basicShader.bind(); + this.va.bind(); + + + this.outlineIndexBuffer.bind(); + //this.rendererLists.render(this); // TODO + + + // particle rendering + IMcDebugRenderer.BoxParticle head = null; + while ((head = this.particles.poll()) != null && head.isDead()) + { /* remove dead particles */ } + if (head != null) + { + // re-add the popped off head + this.particles.add(head); + } + + IMcDebugRenderer renderer = SingletonInjector.INSTANCE.get(IMcDebugRenderer.class); + renderer.render(renderEventParam, this.particles); + + } + + @Deprecated // TODO this should add all the boxes to a list so we can render them as a batch instead of individual draw calls + public void renderBox(IMcDebugRenderer.Box box) + { + IMcDebugRenderer renderer = SingletonInjector.INSTANCE.get(IMcDebugRenderer.class); + renderer.render(box); + } + + //public void render(Mat4f dhMvmProjMatrix) + //{ + // this.dhMvmProjMatrixThisFrame = dhMvmProjMatrix; + // Vec3d camPos = MC_RENDER.getCameraExactPosition(); + // this.camPosFloatThisFrame = new Vec3f((float) camPos.x, (float) camPos.y, (float) camPos.z); + // + // this.init(); + // + // GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_LINE); + // GLMC.enableDepthTest(); + // + // this.basicShader.bind(); + // this.va.bind(); + // + // + // this.outlineIndexBuffer.bind(); + // this.rendererLists.render(this); + // + // + // // particle rendering + // BoxParticle head = null; + // while ((head = this.particles.poll()) != null && head.isDead()) + // { /* remove dead particles */ } + // if (head != null) + // { + // // re-add the popped off head + // this.particles.add(head); + // } + // + // + // // box rendering + // GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_FILL); + // for (BoxParticle particle : this.particles) + // { + // // a new box is created each time since the height will be different based on the time it's lived + // this.renderBox(particle.createNewRenderBox()); + // } + //} + // + //public void renderBox(Box box) + //{ + // Mat4f boxTransform = Mat4f.createTranslateMatrix(box.minPos.x - this.camPosFloatThisFrame.x, box.minPos.y - this.camPosFloatThisFrame.y, box.minPos.z - this.camPosFloatThisFrame.z); + // boxTransform.multiply(Mat4f.createScaleMatrix(box.maxPos.x - box.minPos.x, box.maxPos.y - box.minPos.y, box.maxPos.z - box.minPos.z)); + // + // Mat4f transformMatrix = this.dhMvmProjMatrixThisFrame.copy(); + // transformMatrix.multiply(boxTransform); + // this.basicShader.setUniform(this.basicShader.getUniformLocation("uTransform"), transformMatrix); + // + // this.basicShader.setUniform(this.basicShader.getUniformLocation("uColor"), box.color); + // + // GL32.glDrawElements(GL32.GL_LINES, BOX_OUTLINE_INDICES.length, GL32.GL_UNSIGNED_INT, 0); + //} + + //endregion + + + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/DhTerrainShaderProgram.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/DhTerrainShaderProgram.java new file mode 100644 index 000000000..81e45c6b0 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/DhTerrainShaderProgram.java @@ -0,0 +1,227 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl; + +import com.seibel.distanthorizons.api.interfaces.override.rendering.IDhApiShaderProgram; +import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam; +import com.seibel.distanthorizons.api.objects.math.DhApiVec3f; +import com.seibel.distanthorizons.common.render.nativeGl.glObject.GLProxy; +import com.seibel.distanthorizons.common.render.nativeGl.glObject.shader.ShaderProgram; +import com.seibel.distanthorizons.common.render.nativeGl.glObject.vertexAttribute.AbstractVertexAttribute; +import com.seibel.distanthorizons.common.render.nativeGl.glObject.vertexAttribute.VertexAttributePostGL43; +import com.seibel.distanthorizons.common.render.nativeGl.glObject.vertexAttribute.VertexAttributePreGL43; +import com.seibel.distanthorizons.common.render.nativeGl.glObject.vertexAttribute.VertexPointer; +import com.seibel.distanthorizons.common.render.nativeGl.util.vertexFormat.LodVertexFormat; +import com.seibel.distanthorizons.common.wrappers.misc.LightMapWrapper; +import com.seibel.distanthorizons.core.config.Config; +import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.LodQuadBuilder; +import com.seibel.distanthorizons.core.util.LodUtil; +import com.seibel.distanthorizons.core.util.RenderUtil; +import com.seibel.distanthorizons.core.util.math.Mat4f; +import com.seibel.distanthorizons.core.util.math.Vec3f; +import com.seibel.distanthorizons.core.wrapperInterfaces.misc.ILightMapWrapper; + +/** + * Handles rendering the normal LOD terrain. + * @see LodQuadBuilder + */ +public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShaderProgram +{ + public final AbstractVertexAttribute vao; + + // Uniforms + public int uCombinedMatrix = -1; + public int uModelOffset = -1; + public int uWorldYOffset = -1; + + public int uMircoOffset = -1; + public int uEarthRadius = -1; + public int uLightMap = -1; + + // fragment shader uniforms + public int uClipDistance = -1; + public int uDitherDhRendering = -1; + + // Noise Uniforms + public int uNoiseEnabled = -1; + public int uNoiseSteps = -1; + public int uNoiseIntensity = -1; + public int uNoiseDropoff = -1; + + // Debug Uniform + public int uIsWhiteWorld = -1; + + + + //=============// + // constructor // + //=============// + + // This will bind AbstractVertexAttribute + public DhTerrainShaderProgram() + { + super( + "shaders/standard.vert", + "shaders/flat_shaded.frag", + new String[]{"vPosition", "color"} + ); + + this.uCombinedMatrix = this.getUniformLocation("uCombinedMatrix"); + this.uModelOffset = this.getUniformLocation("uModelOffset"); + this.uWorldYOffset = this.getUniformLocation("uWorldYOffset"); + this.uDitherDhRendering = this.getUniformLocation("uDitherDhRendering"); + this.uMircoOffset = this.getUniformLocation("uMircoOffset"); + this.uEarthRadius = this.getUniformLocation("uEarthRadius"); + + this.uLightMap = this.getUniformLocation("uLightMap"); + + // Fog/Clip Uniforms + this.uClipDistance = this.getUniformLocation("uClipDistance"); + + // Noise Uniforms + this.uNoiseEnabled = this.getUniformLocation("uNoiseEnabled"); + this.uNoiseSteps = this.getUniformLocation("uNoiseSteps"); + this.uNoiseIntensity = this.getUniformLocation("uNoiseIntensity"); + this.uNoiseDropoff = this.getUniformLocation("uNoiseDropoff"); + + // Debug Uniform + this.uIsWhiteWorld = this.getUniformLocation("uIsWhiteWorld"); + + + if (GLProxy.getInstance().vertexAttributeBufferBindingSupported) + { + this.vao = new VertexAttributePostGL43(); // also binds AbstractVertexAttribute + } + else + { + this.vao = new VertexAttributePreGL43(); // also binds AbstractVertexAttribute + } + this.vao.bind(); + + // short: x, y, z, meta + // meta: byte skylight, byte blocklight, byte microOffset + this.vao.setVertexAttribute(0, 0, VertexPointer.addUnsignedShortsPointer(4, false, true)); + // byte: r, g, b, a + this.vao.setVertexAttribute(0, 1, VertexPointer.addUnsignedBytesPointer(4, true, false)); + // byte: iris material ID, normal index, 2 spacers + this.vao.setVertexAttribute(0, 2, VertexPointer.addUnsignedBytesPointer(4, true, true)); + + try + { + int vertexByteCount = LodVertexFormat.DH_VERTEX_FORMAT.getByteSize(); + this.vao.completeAndCheck(vertexByteCount); + } + catch (RuntimeException e) + { + System.out.println(LodVertexFormat.DH_VERTEX_FORMAT); + throw e; + } + + } + + + + //=========// + // methods // + //=========// + + @Override + public void bind() + { + super.bind(); + this.vao.bind(); + } + @Override + public void unbind() + { + super.unbind(); + this.vao.unbind(); + } + + @Override + public void free() + { + this.vao.free(); + super.free(); + } + + @Override + public void bindVertexBuffer(int vbo) { this.vao.bindBufferToAllBindingPoints(vbo); } + + @Override + public void fillUniformData(DhApiRenderParam renderParameters) + { + Mat4f combinedMatrix = new Mat4f(renderParameters.dhProjectionMatrix); + combinedMatrix.multiply(renderParameters.dhModelViewMatrix); + + super.bind(); + + // uniforms + this.setUniform(this.uCombinedMatrix, combinedMatrix); + this.setUniform(this.uMircoOffset, 0.01f); // 0.01 block offset + + this.setUniform(this.uLightMap, LightMapWrapper.GL_BOUND_INDEX); + + this.setUniform(this.uWorldYOffset, (float) renderParameters.worldYOffset); + + this.setUniform(this.uDitherDhRendering, Config.Client.Advanced.Graphics.Quality.ditherDhFade.get()); + + float curveRatio = Config.Client.Advanced.Graphics.Experimental.earthCurveRatio.get(); + if (curveRatio < -1.0f || curveRatio > 1.0f) + { + curveRatio = /*6371KM*/ 6371000.0f / curveRatio; + } + else + { + // disable curvature if the config value is between -1 and 1 + curveRatio = 0.0f; + } + this.setUniform(this.uEarthRadius, curveRatio); + + // Noise Uniforms + this.setUniform(this.uNoiseEnabled, Config.Client.Advanced.Graphics.NoiseTexture.enableNoiseTexture.get()); + this.setUniform(this.uNoiseSteps, Config.Client.Advanced.Graphics.NoiseTexture.noiseSteps.get()); + this.setUniform(this.uNoiseIntensity, Config.Client.Advanced.Graphics.NoiseTexture.noiseIntensity.get()); + this.setUniform(this.uNoiseDropoff, Config.Client.Advanced.Graphics.NoiseTexture.noiseDropoff.get()); + + // Debug + this.setUniform(this.uIsWhiteWorld, Config.Client.Advanced.Debugging.enableWhiteWorld.get()); + + // Clip Uniform + float dhNearClipDistance = RenderUtil.getNearClipPlaneInBlocks(); + if (!Config.Client.Advanced.Debugging.lodOnlyMode.get()) + { + // this added value prevents the near clip plane and discard circle from touching, which looks bad + dhNearClipDistance += 16f; + } + this.setUniform(this.uClipDistance, dhNearClipDistance); + } + + @Override + public void setModelOffsetPos(DhApiVec3f modelOffsetPos) { this.setUniform(this.uModelOffset, new Vec3f(modelOffsetPos)); } + + @Override + public int getId() { return this.id; } + + /** The base DH render program should always render */ + @Override + public boolean overrideThisFrame() { return true; } + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/LodRenderer.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/LodRenderer.java new file mode 100644 index 000000000..e9a165109 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/LodRenderer.java @@ -0,0 +1,766 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl; + +import com.seibel.distanthorizons.api.interfaces.override.rendering.IDhApiFramebuffer; +import com.seibel.distanthorizons.api.interfaces.override.rendering.IDhApiShaderProgram; +import com.seibel.distanthorizons.api.methods.events.abstractEvents.*; +import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam; +import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiTextureCreatedParam; +import com.seibel.distanthorizons.common.render.nativeGl.glObject.GLProxy; +import com.seibel.distanthorizons.common.render.nativeGl.glObject.buffer.QuadElementBuffer; +import com.seibel.distanthorizons.common.render.nativeGl.glObject.texture.*; +import com.seibel.distanthorizons.common.render.nativeGl.postProcessing.apply.DhApplyShader; +import com.seibel.distanthorizons.common.render.nativeGl.postProcessing.fade.DhFadeRenderer; +import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftGLWrapper; +import com.seibel.distanthorizons.core.config.Config; +import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.LodBufferContainer; +import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector; +import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; +import com.seibel.distanthorizons.core.logging.DhLogger; +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos; +import com.seibel.distanthorizons.core.render.DhApiRenderProxy; +import com.seibel.distanthorizons.core.render.RenderBufferHandler; +import com.seibel.distanthorizons.common.render.nativeGl.postProcessing.fog.FogRenderer; +import com.seibel.distanthorizons.core.render.RenderParams; +import com.seibel.distanthorizons.common.render.nativeGl.postProcessing.ssao.SSAORenderer; +import com.seibel.distanthorizons.core.util.math.Mat4f; +import com.seibel.distanthorizons.core.util.math.Vec3d; +import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; +import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; +import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper; +import com.seibel.distanthorizons.core.wrapperInterfaces.misc.ILightMapWrapper; +import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.AbstractOptifineAccessor; +import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IIrisAccessor; +import com.seibel.distanthorizons.core.wrapperInterfaces.render.IMcGenericRenderer; +import com.seibel.distanthorizons.core.wrapperInterfaces.render.IMcTestRenderer; +import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector; +import com.seibel.distanthorizons.coreapi.DependencyInjection.OverrideInjector; +import com.seibel.distanthorizons.core.util.math.Vec3f; +import org.jetbrains.annotations.Nullable; +import org.lwjgl.opengl.GL32; + +/** + * This is where all the magic happens.
+ * This is where LODs are draw to the world. + */ +public class LodRenderer +{ + public static final DhLogger LOGGER = new DhLoggerBuilder() + .fileLevelConfig(Config.Common.Logging.logRendererEventToFile) + .build(); + + public static final DhLogger RATE_LIMITED_LOGGER = new DhLoggerBuilder() + .fileLevelConfig(Config.Common.Logging.logRendererEventToFile) + .maxCountPerSecond(4) + .build(); + + private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); + private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); + private static final MinecraftGLWrapper GLMC = MinecraftGLWrapper.INSTANCE; + private static final IIrisAccessor IRIS_ACCESSOR = ModAccessorInjector.INSTANCE.get(IIrisAccessor.class); + + public static final LodRenderer INSTANCE = new LodRenderer(); + + + + // these ID's either what any render is currently using (since only one renderer can be active at a time), or just used previously + private int activeFramebufferId = -1; + private int activeColorTextureId = -1; + private int activeDepthTextureId = -1; + private int textureWidth; + private int textureHeight; + + + private IDhApiShaderProgram lodRenderProgram = null; + public QuadElementBuffer quadIBO = null; + private boolean renderObjectsCreated = false; + + // framebuffer and texture ID's for this renderer + private IDhApiFramebuffer framebuffer; + /** will be null if MC's framebuffer is being used since MC already has a color texture */ + @Nullable + private DhColorTexture nullableColorTexture; + private DHDepthTexture depthTexture; + /** + * If true the {@link LodRenderer#framebuffer} is the same as MC's. + * This should only be true in the case of Optifine so LODs won't be overwritten when shaders are enabled. + */ + private boolean usingMcFramebuffer = false; + + + + //=============// + // constructor // + //=============// + + private LodRenderer() { } + + + + //===========// + // rendering // + //===========// + //region + + /** + * This will draw both opaque and transparent LODs if + * {@link DhApiRenderProxy#getDeferTransparentRendering()} is false, + * otherwise it will only render opaque LODs. + */ + public void render(RenderParams renderParams, IProfilerWrapper profiler) + { this.renderLodPass(renderParams, profiler, false); } + + /** + * This method is designed for Iris to be able + * to draw water in a deferred rendering context. + * It needs to be updated with any major changes, + * but shouldn't be activated as per deferWaterRendering. + */ + public void renderDeferred(RenderParams renderParams, IProfilerWrapper profiler) + { this.renderLodPass(renderParams, profiler, true); } + + private void renderLodPass(RenderParams renderParams, IProfilerWrapper profiler, boolean runningDeferredPass) + { + //====================// + // validate rendering // + //====================// + + boolean deferTransparentRendering = DhApiRenderProxy.INSTANCE.getDeferTransparentRendering(); + if (runningDeferredPass + && !deferTransparentRendering) + { + return; + } + boolean firstPass = !runningDeferredPass; + + // RenderParams parameter validation should be done before this + if (!renderParams.validationRun) + { + throw new IllegalArgumentException("Render parameters validation"); + } + + RenderBufferHandler renderBufferHandler = renderParams.renderBufferHandler; + IMcGenericRenderer genericRenderer = renderParams.genericRenderer; + ILightMapWrapper lightmap = renderParams.lightmap; + + + + //=================// + // rendering setup // + //=================// + + ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderSetupEvent.class, renderParams); + profiler.push("LOD GL setup"); + + if (!this.renderObjectsCreated) + { + boolean setupSuccess = this.createRenderObjects(); + if (!setupSuccess) + { + // shouldn't normally happen, but just in case + return; + } + + // only do this once, that way they can still be reverted if desired + if (Config.Client.Advanced.Graphics.overrideVanillaGraphicsSettings.get()) + { + LOGGER.info("Overriding vanilla MC settings to better fit Distant Horizons... This behavior can be disabled in the Distant Horizons config."); + + MC.disableVanillaClouds(); + MC.disableVanillaChunkFadeIn(); + MC.disableFabulousTransparency(); + } + + this.renderObjectsCreated = true; + } + + this.setGLState(renderParams, firstPass); + + lightmap.bind(); + this.quadIBO.bind(); + + if (firstPass) + { + // we only need to sort/cull the LODs during the first frame + profiler.popPush("LOD build render list"); + renderBufferHandler.buildRenderList(renderParams); + } + + IDhApiShaderProgram lodShaderProgram = this.lodRenderProgram; + IDhApiShaderProgram lodShaderProgramOverride = OverrideInjector.INSTANCE.get(IDhApiShaderProgram.class); + if (lodShaderProgramOverride != null && lodShaderProgram.overrideThisFrame()) + { + lodShaderProgram = lodShaderProgramOverride; + } + + + + //===========// + // rendering // + //===========// + + if (!runningDeferredPass) + { + //=========================// + // opaque and non-deferred // + // transparent rendering // + //=========================// + + // opaque LODs + profiler.popPush("LOD Opaque"); + this.renderLodPass(lodShaderProgram, renderBufferHandler, renderParams, /*opaquePass*/ true); + + // custom objects with SSAO + if (Config.Client.Advanced.Graphics.GenericRendering.enableGenericRendering.get()) + { + profiler.popPush("Custom Objects"); + genericRenderer.render(renderParams, profiler, true); + } + + // SSAO + if (Config.Client.Advanced.Graphics.Ssao.enableSsao.get()) + { + profiler.popPush("LOD SSAO"); + SSAORenderer.INSTANCE.render(new Mat4f(renderParams.dhProjectionMatrix), renderParams.partialTicks); + } + + // custom objects without SSAO + if (Config.Client.Advanced.Graphics.GenericRendering.enableGenericRendering.get()) + { + profiler.popPush("Custom Objects"); + genericRenderer.render(renderParams, profiler, false); + } + + // combined pass transparent rendering + if (!deferTransparentRendering + && Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled) + { + profiler.popPush("LOD Transparent"); + this.renderLodPass(lodShaderProgram, renderBufferHandler, renderParams, /*opaquePass*/ false); + } + + // far plane clip fading + if (Config.Client.Advanced.Graphics.Quality.dhFadeFarClipPlane.get() + // the fade shader messes with the GL state in a way Iris doesn't like, + // so skip it if a shader is active + && (IRIS_ACCESSOR == null || !IRIS_ACCESSOR.isShaderPackInUse())) + { + profiler.popPush("Fade Far Clip Fade"); + DhFadeRenderer.INSTANCE.render( + new Mat4f(renderParams.mcModelViewMatrix), new Mat4f(renderParams.mcProjectionMatrix), + renderParams.partialTicks, profiler); + } + + // fog + if (Config.Client.Advanced.Graphics.Fog.enableDhFog.get() + // this is done to fix issues with: underwater fog, blindness effect, etc. + || renderParams.vanillaFogEnabled) + { + profiler.popPush("LOD Fog"); + + Mat4f combinedMatrix = new Mat4f(renderParams.dhProjectionMatrix); + combinedMatrix.multiply(renderParams.dhModelViewMatrix); + + FogRenderer.INSTANCE.render(combinedMatrix, renderParams.partialTicks); + } + + + + //=================// + // debug rendering // + //=================// + + if (Config.Client.Advanced.Debugging.DebugWireframe.enableRendering.get()) + { + profiler.popPush("Debug wireframes"); + + Mat4f combinedMatrix = new Mat4f(renderParams.dhProjectionMatrix); + combinedMatrix.multiply(renderParams.dhModelViewMatrix); + + // Note: this can be very slow if a lot of boxes are being rendered + //DebugRenderer.INSTANCE.render(combinedMatrix); // TODO + } + + + + //===================// + // optifine clean up // + //===================// + + if (this.usingMcFramebuffer) + { + // If MC's framebuffer is being used the depth needs to be cleared to prevent rendering on top of MC. + // This should only happen when Optifine shaders are being used. + GL32.glClear(GL32.GL_DEPTH_BUFFER_BIT); + } + + + + //=============================// + // Apply to the MC Framebuffer // + //=============================// + + boolean cancelApplyShader = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeApplyShaderRenderEvent.class, renderParams); + if (!cancelApplyShader) + { + profiler.popPush("LOD Apply"); + + // Copy the LOD framebuffer to Minecraft's framebuffer + DhApplyShader.INSTANCE.render(renderParams.partialTicks); + } + } + else + { + //====================// + // deferred rendering // + //====================// + + if (Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled) + { + profiler.popPush("LOD Transparent"); + this.renderLodPass(lodShaderProgram, renderBufferHandler, renderParams, /*opaquePass*/ false); + + + if (Config.Client.Advanced.Graphics.Fog.enableDhFog.get() + // this is done to fix issues with: underwater fog, blindness effect, etc. + || renderParams.vanillaFogEnabled) + { + profiler.popPush("LOD Fog"); + + Mat4f combinedMatrix = new Mat4f(renderParams.dhProjectionMatrix); + combinedMatrix.multiply(renderParams.dhModelViewMatrix); + + FogRenderer.INSTANCE.render(combinedMatrix, renderParams.partialTicks); + } + } + } + + + + //================// + // render cleanup // + //================// + + profiler.popPush("LOD cleanup"); + ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderCleanupEvent.class, renderParams); + + lightmap.unbind(); + this.quadIBO.unbind(); + lodShaderProgram.unbind(); + + + // end of internal LOD profiling + profiler.pop(); + } + + //endregion + + + + //=================// + // Setup Functions // + //=================// + //region + + private void setGLState( + DhApiRenderParam renderEventParam, + boolean firstPass) + { + //===================// + // framebuffer setup // + //===================// + + // get the active framebuffer + IDhApiFramebuffer framebuffer = this.framebuffer; + IDhApiFramebuffer framebufferOverride = OverrideInjector.INSTANCE.get(IDhApiFramebuffer.class); + if (framebufferOverride != null && framebufferOverride.overrideThisFrame()) + { + framebuffer = framebufferOverride; + } + this.activeFramebufferId = framebuffer.getId(); + framebuffer.bind(); + + + + //==========// + // bindings // + //==========// + + // by default draw everything as triangles + GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_FILL); + GLMC.enableFaceCulling(); + + GLMC.glBlendFunc(GL32.GL_SRC_ALPHA, GL32.GL_ONE_MINUS_SRC_ALPHA); + GLMC.glBlendFuncSeparate(GL32.GL_SRC_ALPHA, GL32.GL_ONE_MINUS_SRC_ALPHA, GL32.GL_ONE, GL32.GL_ZERO); + + GL32.glDisable(GL32.GL_SCISSOR_TEST); + + // Enable depth test and depth mask + GLMC.enableDepthTest(); + GLMC.glDepthFunc(GL32.GL_LESS); + GLMC.enableDepthMask(); + + // This is required for MC versions 1.21.5+ + // due to MC updating the lightmap by changing the viewport size + GL32.glViewport(0, 0, this.textureWidth, this.textureHeight); + + this.lodRenderProgram.bind(); + + + + //==========// + // uniforms // + //==========// + + IDhApiShaderProgram shaderProgramOverride = OverrideInjector.INSTANCE.get(IDhApiShaderProgram.class); + if (shaderProgramOverride != null) + { + shaderProgramOverride.fillUniformData(renderEventParam); + } + + this.lodRenderProgram.fillUniformData(renderEventParam); + + + + + //===============// + // texture setup // + //===============// + + // resize the textures if needed + if (MC_RENDER.getTargetFramebufferViewportWidth() != this.textureWidth + || MC_RENDER.getTargetFramebufferViewportHeight() != this.textureHeight) + { + // just resizing the textures doesn't work when Optifine is present, + // so recreate the textures with the new size instead + this.createAndBindTextures(); + } + + + // set the active textures + this.activeDepthTextureId = this.depthTexture.getTextureId(); + + if (this.nullableColorTexture != null) + { + this.activeColorTextureId = this.nullableColorTexture.getTextureId(); + } + else + { + // get MC's color texture + this.activeColorTextureId = GL32.glGetFramebufferAttachmentParameteri(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME); + } + + + // needs to be fired after all the textures have been created/bound + boolean clearTextures = !ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeTextureClearEvent.class, renderEventParam); + if (clearTextures) + { + GL32.glClearDepth(1.0); + + float[] clearColorValues = new float[4]; + GL32.glGetFloatv(GL32.GL_COLOR_CLEAR_VALUE, clearColorValues); + GL32.glClearColor(clearColorValues[0], clearColorValues[1], clearColorValues[2], 1.0f); + + if (this.usingMcFramebuffer && framebufferOverride == null) + { + // Due to using MC/Optifine's framebuffer we need to re-bind the depth texture, + // otherwise we'll be writing to MC/Optifine's depth texture which causes rendering issues + framebuffer.addDepthAttachment(this.depthTexture.getTextureId(), EDhDepthBufferFormat.DEPTH32F.isCombinedStencil()); + + + // don't clear the color texture, that removes the sky + GL32.glClear(GL32.GL_DEPTH_BUFFER_BIT); + } + else if (firstPass) + { + GL32.glClear(GL32.GL_COLOR_BUFFER_BIT | GL32.GL_DEPTH_BUFFER_BIT); + } + } + + + } + + private boolean createRenderObjects() + { + if (this.renderObjectsCreated) + { + LOGGER.warn("Renderer setup called but it has already completed setup!"); + return false; + } + + // GLProxy should have already been created by this point, but just in case create it now + GLProxy.getInstance(); + + + + LOGGER.info("Setting up renderer"); + this.lodRenderProgram = new DhTerrainShaderProgram(); + + this.quadIBO = new QuadElementBuffer(); + this.quadIBO.reserve(LodBufferContainer.MAX_QUADS_PER_BUFFER); + + + // create or get the frame buffer + if (AbstractOptifineAccessor.optifinePresent()) + { + // use MC/Optifine's default Framebuffer so shaders won't remove the LODs + int currentFramebufferId = MC_RENDER.getTargetFramebuffer(); + this.framebuffer = new DhFramebuffer(currentFramebufferId); + this.usingMcFramebuffer = true; + } + else + { + // normal use case + this.framebuffer = new DhFramebuffer(); + this.usingMcFramebuffer = false; + } + + // create and bind the necessary textures + this.createAndBindTextures(); + + if(this.framebuffer.getStatus() != GL32.GL_FRAMEBUFFER_COMPLETE) + { + // This generally means something wasn't bound, IE missing either the color or depth texture + LOGGER.warn("Framebuffer ["+this.framebuffer.getId()+"] isn't complete."); + return false; + } + + + + LOGGER.info("Renderer setup complete"); + return true; + } + + @SuppressWarnings( "deprecation" ) // done to ignore DhApiColorDepthTextureCreatedEvent + private void createAndBindTextures() + { + int oldWidth = this.textureWidth; + int oldHeight = this.textureHeight; + this.textureWidth = MC_RENDER.getTargetFramebufferViewportWidth(); + this.textureHeight = MC_RENDER.getTargetFramebufferViewportHeight(); + + DhApiTextureCreatedParam textureCreatedParam = new DhApiTextureCreatedParam( + oldWidth, oldHeight, + this.textureWidth, this.textureHeight + ); + + + // DhApiColorDepthTextureCreatedEvent needs to be kept around since old versions of Iris need it + ApiEventInjector.INSTANCE.fireAllEvents(DhApiColorDepthTextureCreatedEvent.class, new DhApiColorDepthTextureCreatedEvent.EventParam(textureCreatedParam)); + ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeColorDepthTextureCreatedEvent.class, textureCreatedParam); + + + // also update the framebuffer override if present + IDhApiFramebuffer framebufferOverride = OverrideInjector.INSTANCE.get(IDhApiFramebuffer.class); + + + this.depthTexture = new DHDepthTexture(this.textureWidth, this.textureHeight, EDhDepthBufferFormat.DEPTH32F); + this.framebuffer.addDepthAttachment(this.depthTexture.getTextureId(), EDhDepthBufferFormat.DEPTH32F.isCombinedStencil()); + if (framebufferOverride != null) + { + framebufferOverride.addDepthAttachment(this.depthTexture.getTextureId(), EDhDepthBufferFormat.DEPTH32F.isCombinedStencil()); + } + + // if we are using MC's frame buffer, a color texture is already present and shouldn't need to be bound + if (!this.usingMcFramebuffer) + { + this.nullableColorTexture = DhColorTexture.builder().setDimensions(this.textureWidth, this.textureHeight) + .setInternalFormat(EDhInternalTextureFormat.RGBA8) + .setPixelType(EDhPixelType.UNSIGNED_BYTE) + .setPixelFormat(EDhPixelFormat.RGBA) + .build(); + + this.framebuffer.addColorAttachment(0, this.nullableColorTexture.getTextureId()); + if (framebufferOverride != null) + { + framebufferOverride.addColorAttachment(0, this.nullableColorTexture.getTextureId()); + } + } + else + { + this.nullableColorTexture = null; + } + + + ApiEventInjector.INSTANCE.fireAllEvents(DhApiAfterColorDepthTextureCreatedEvent.class, textureCreatedParam); + } + + //endregion + + + + //===============// + // LOD rendering // + //===============// + //region + + private void renderLodPass(IDhApiShaderProgram shaderProgram, RenderBufferHandler lodBufferHandler, RenderParams renderEventParam, boolean opaquePass) + { + //=======================// + // debug wireframe setup // + //=======================// + + boolean renderWireframe = Config.Client.Advanced.Debugging.renderWireframe.get(); + if (renderWireframe) + { + GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_LINE); + GLMC.disableFaceCulling(); + } + else + { + GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_FILL); + GLMC.enableFaceCulling(); + } + + if (!opaquePass) + { + GLMC.enableBlend(); + GLMC.enableDepthTest(); + GL32.glBlendEquation(GL32.GL_FUNC_ADD); + GLMC.glBlendFuncSeparate(GL32.GL_SRC_ALPHA, GL32.GL_ONE_MINUS_SRC_ALPHA, GL32.GL_ONE, GL32.GL_ONE_MINUS_SRC_ALPHA); + } + else + { + GLMC.disableBlend(); + } + + + + + //===========// + // rendering // + //===========// + + ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderPassEvent.class, renderEventParam); + + if (IRIS_ACCESSOR != null) + { + // done to fix a bug with Iris where face culling isn't properly set or reverted in the MC state manager + // which causes Sodium to render some water chunks with their normal inverted + // https://github.com/IrisShaders/Iris/issues/2582 + // https://github.com/IrisShaders/Iris/blob/1.21.9/common/src/main/java/net/irisshaders/iris/compat/dh/LodRendererEvents.java#L346 + GLMC.enableFaceCulling(); + } + + //if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEFAULT) + //{ + // // Normal LOD rendering + // + // SortedArraySet lodBufferContainer = lodBufferHandler.getColumnRenderBuffers(); + // if (lodBufferContainer != null) + // { + // for (int lodIndex = 0; lodIndex < lodBufferContainer.size(); lodIndex++) + // { + // LodBufferContainer bufferContainer = lodBufferContainer.get(lodIndex); + // this.setShaderProgramMvmOffset(bufferContainer.minCornerBlockPos, shaderProgram, renderEventParam); + // + // GLVertexBuffer[] vbos = opaquePass ? bufferContainer.vbos : bufferContainer.vbosTransparent; + // for (int vboIndex = 0; vboIndex < vbos.length; vboIndex++) + // { + // GLVertexBuffer vbo = vbos[vboIndex]; + // if (vbo == null) + // { + // continue; + // } + // + // if (vbo.getVertexCount() == 0) + // { + // continue; + // } + // + // vbo.bind(); + // shaderProgram.bindVertexBuffer(vbo.getId()); + // GL32.glDrawElements( + // GL32.GL_TRIANGLES, + // vbo.getVertexCount(), + // this.quadIBO.getType(), 0); + // vbo.unbind(); + // } + // } + // } + //} + //else + { + // basic quad rendering + + IMcTestRenderer testRenderer = SingletonInjector.INSTANCE.get(IMcTestRenderer.class); + testRenderer.render(); + + //TestRenderer.INSTANCE.render(); + //McTestRenderer.INSTANCE.render(); + } + + + //=========================// + // debug wireframe cleanup // + //=========================// + + if (renderWireframe) + { + // default back to GL_FILL since all other rendering uses it + GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_FILL); + GLMC.enableFaceCulling(); + } + + } + + /** + * the MVM offset is needed so LODs can be rendered anywhere in the MC world + * without running into floating point percision loss. + */ + private void setShaderProgramMvmOffset(DhBlockPos pos, IDhApiShaderProgram shaderProgram, RenderParams renderEventParam) throws IllegalStateException + { + Vec3d camPos = renderEventParam.exactCameraPosition; + Vec3f modelPos = new Vec3f( + (float) (pos.getX() - camPos.x), + (float) (pos.getY() - camPos.y), + (float) (pos.getZ() - camPos.z)); + + shaderProgram.bind(); + shaderProgram.setModelOffsetPos(modelPos); + + ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeBufferRenderEvent.class, new DhApiBeforeBufferRenderEvent.EventParam(renderEventParam, modelPos)); + } + + //endregion + + + + //===============// + // API functions // + //===============// + //region + + /** @return -1 if no frame buffer has been bound yet */ + public int getActiveFramebufferId() { return this.activeFramebufferId; } + + /** @return -1 if no texture has been bound yet */ + public int getActiveColorTextureId() { return this.activeColorTextureId; } + + /** @return -1 if no texture has been bound yet */ + public int getActiveDepthTextureId() { return this.activeDepthTextureId; } + + //endregion + + + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/generic/GenericObjectRenderer.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/generic/GenericObjectRenderer.java new file mode 100644 index 000000000..4a449ee95 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/generic/GenericObjectRenderer.java @@ -0,0 +1,735 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl.generic; + +import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod; +import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial; +import com.seibel.distanthorizons.api.interfaces.override.rendering.IDhApiGenericObjectShaderProgram; +import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup; +import com.seibel.distanthorizons.api.interfaces.render.IDhApiCustomRenderRegister; +import com.seibel.distanthorizons.api.methods.events.abstractEvents.*; +import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam; +import com.seibel.distanthorizons.api.objects.math.DhApiVec3d; +import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox; +import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShading; +import com.seibel.distanthorizons.common.render.nativeGl.glObject.GLProxy; +import com.seibel.distanthorizons.common.render.nativeGl.glObject.buffer.GLElementBuffer; +import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftGLWrapper; +import com.seibel.distanthorizons.core.config.Config; +import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector; +import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; +import com.seibel.distanthorizons.core.jar.EPlatform; +import com.seibel.distanthorizons.core.logging.DhLogger; +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import com.seibel.distanthorizons.core.logging.f3.F3Screen; +import com.seibel.distanthorizons.core.render.RenderParams; +import com.seibel.distanthorizons.core.util.LodUtil; +import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; +import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper; +import com.seibel.distanthorizons.core.util.math.Vec3d; +import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.ISodiumAccessor; +import com.seibel.distanthorizons.core.wrapperInterfaces.render.IMcGenericRenderer; +import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector; +import com.seibel.distanthorizons.coreapi.DependencyInjection.OverrideInjector; +import com.seibel.distanthorizons.coreapi.ModInfo; +import org.lwjgl.opengl.ARBInstancedArrays; +import org.lwjgl.opengl.GL32; +import org.lwjgl.opengl.GL33; +import org.lwjgl.system.MemoryUtil; + +import java.awt.*; +import java.nio.ByteBuffer; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Handles rendering generic groups of {@link DhApiRenderableBox}. + * + * @see IDhApiCustomRenderRegister + * @see DhApiRenderableBox + */ +public class GenericObjectRenderer implements IMcGenericRenderer +{ + private static final DhLogger LOGGER = new DhLoggerBuilder().build(); + + private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); + private static final ISodiumAccessor SODIUM = ModAccessorInjector.INSTANCE.get(ISodiumAccessor.class); + private static final MinecraftGLWrapper GLMC = MinecraftGLWrapper.INSTANCE; + + private static final DhApiRenderableBoxGroupShading DEFAULT_SHADING = DhApiRenderableBoxGroupShading.getUnshaded(); + + /** + * Can be used to troubleshoot the renderer. + * If enabled several debug objects will render around (0,150,0). + */ + public static final boolean RENDER_DEBUG_OBJECTS = false; + + + // rendering setup + private boolean init = false; + + private IDhApiGenericObjectShaderProgram instancedShaderProgram; + private IDhApiGenericObjectShaderProgram directShaderProgram; + //private GLVertexBuffer boxVertexBuffer; + private GLElementBuffer boxIndexBuffer; + + private boolean instancedRenderingAvailable; + private boolean vertexAttribDivisorSupported; + private boolean instancedArraysSupported; + + + + private final ConcurrentHashMap boxGroupById = new ConcurrentHashMap<>(); + + + + /** A box from 0,0,0 to 1,1,1 */ + private static final float[] BOX_VERTICES = { + //region + // Pos x y z + + // min X, vertical face + 0, 0, 0, + 1, 0, 0, + 1, 1, 0, + 0, 1, 0, + // max X, vertical face + 0, 1, 1, + 1, 1, 1, + 1, 0, 1, + 0, 0, 1, + + // min Z, vertical face + 0, 0, 1, + 0, 0, 0, + 0, 1, 0, + 0, 1, 1, + // max Z, vertical face + 1, 0, 1, + 1, 1, 1, + 1, 1, 0, + 1, 0, 0, + + // min Y, horizontal face + 0, 0, 1, + 1, 0, 1, + 1, 0, 0, + 0, 0, 0, + // max Y, horizontal face + 0, 1, 1, + 1, 1, 1, + 1, 1, 0, + 0, 1, 0, + //endregion + }; + + + private static final int[] BOX_INDICES = { + //region + // min X, vertical face + 2, 1, 0, + 0, 3, 2, + // max X, vertical face + 6, 5, 4, + 4, 7, 6, + + // min Z, vertical face + 10, 9, 8, + 8, 11, 10, + // max Z, vertical face + 14, 13, 12, + 12, 15, 14, + + // min Y, horizontal face + 18, 17, 16, + 16, 19, 18, + // max Y, horizontal face + 20, 21, 22, + 22, 23, 20, + //endregion + }; + + + + //=============// + // constructor // + //=============// + //region + + public GenericObjectRenderer() { } + + public void init() + { + if (this.init) + { + return; + } + this.init = true; + + + + //===================================// + // is instanced rendering available? // + //===================================// + + this.vertexAttribDivisorSupported = GLProxy.getInstance().vertexAttribDivisorSupported; + this.instancedArraysSupported = GLProxy.getInstance().instancedArraysSupported; + boolean isMac = (EPlatform.get() == EPlatform.MACOS); + this.instancedRenderingAvailable = (this.vertexAttribDivisorSupported || this.instancedArraysSupported) && !isMac; + if (!this.instancedRenderingAvailable) + { + LOGGER.warn("Instanced rendering not supported by this GPU, falling back to direct rendering. Generic object rendering will be slow and some effects may be disabled."); + } + + + + //======================// + // startup the renderer // + //======================// + + this.instancedShaderProgram = new GenericObjectShaderProgram(true); + this.directShaderProgram = new GenericObjectShaderProgram(false); + + this.createBuffers(); + + if (RENDER_DEBUG_OBJECTS) + { + this.addGenericDebugObjects(); + } + } + private void createBuffers() + { + // box vertices + ByteBuffer boxVerticesBuffer = MemoryUtil.memAlloc(BOX_VERTICES.length * Float.BYTES); + boxVerticesBuffer.asFloatBuffer().put(BOX_VERTICES); + boxVerticesBuffer.rewind(); + //this.boxVertexBuffer = new GLVertexBuffer(false); + //this.boxVertexBuffer.bind(); + //this.boxVertexBuffer.uploadBuffer(boxVerticesBuffer, 8, EDhApiGpuUploadMethod.DATA, BOX_VERTICES.length * Float.BYTES); + MemoryUtil.memFree(boxVerticesBuffer); + + // box vertex indexes + ByteBuffer solidIndexBuffer = MemoryUtil.memAlloc(BOX_INDICES.length * Integer.BYTES); + solidIndexBuffer.asIntBuffer().put(BOX_INDICES); + solidIndexBuffer.rewind(); + this.boxIndexBuffer = new GLElementBuffer(false); + this.boxIndexBuffer.uploadBuffer(solidIndexBuffer, EDhApiGpuUploadMethod.DATA, BOX_INDICES.length * Integer.BYTES, GL32.GL_STATIC_DRAW); + this.boxIndexBuffer.bind(); + MemoryUtil.memFree(solidIndexBuffer); + } + private void addGenericDebugObjects() + { + GenericRenderObjectFactory factory = GenericRenderObjectFactory.INSTANCE; + + + // single giant box + IDhApiRenderableBoxGroup singleGiantBoxGroup = factory.createForSingleBox( + ModInfo.NAME + ":CyanChunkBox", + new DhApiRenderableBox( + new DhApiVec3d(0,0,0), new DhApiVec3d(16,190,16), + new Color(Color.CYAN.getRed(), Color.CYAN.getGreen(), Color.CYAN.getBlue(), 125), + EDhApiBlockMaterial.WATER) + ); + singleGiantBoxGroup.setSkyLight(LodUtil.MAX_MC_LIGHT); + singleGiantBoxGroup.setBlockLight(LodUtil.MAX_MC_LIGHT); + this.add(singleGiantBoxGroup); + + + // single slender box + IDhApiRenderableBoxGroup singleTallBoxGroup = factory.createForSingleBox( + ModInfo.NAME + ":GreenBeacon", + new DhApiRenderableBox( + new DhApiVec3d(16,0,31), new DhApiVec3d(17,2000,32), + new Color(Color.GREEN.getRed(), Color.GREEN.getGreen(), Color.GREEN.getBlue(), 125), + EDhApiBlockMaterial.ILLUMINATED) + ); + singleTallBoxGroup.setSkyLight(LodUtil.MAX_MC_LIGHT); + singleTallBoxGroup.setBlockLight(LodUtil.MAX_MC_LIGHT); + this.add(singleTallBoxGroup); + + + // absolute box group + ArrayList absBoxList = new ArrayList<>(); + for (int i = 0; i < 18; i++) + { + absBoxList.add(new DhApiRenderableBox( + new DhApiVec3d(i,150+i,24), new DhApiVec3d(1+i,151+i,25), + new Color(Color.ORANGE.getRed(), Color.ORANGE.getGreen(), Color.ORANGE.getBlue()), + EDhApiBlockMaterial.LAVA + ) + ); + } + IDhApiRenderableBoxGroup absolutePosBoxGroup = factory.createAbsolutePositionedGroup(ModInfo.NAME + ":OrangeStairs", absBoxList); + this.add(absolutePosBoxGroup); + + + // relative box group + ArrayList relBoxList = new ArrayList<>(); + for (int i = 0; i < 8; i+=2) + { + relBoxList.add(new DhApiRenderableBox( + new DhApiVec3d(0,i,0), new DhApiVec3d(1,1+i,1), + new Color(Color.MAGENTA.getRed(), Color.MAGENTA.getGreen(), Color.MAGENTA.getBlue()), + EDhApiBlockMaterial.METAL + ) + ); + } + IDhApiRenderableBoxGroup relativePosBoxGroup = factory.createRelativePositionedGroup( + ModInfo.NAME + ":MovingMagentaGroup", + new DhApiVec3d(24, 140, 24), + relBoxList); + relativePosBoxGroup.setPreRenderFunc((event) -> + { + DhApiVec3d pos = relativePosBoxGroup.getOriginBlockPos(); + pos.x += event.partialTicks / 2; + pos.x %= 32; + relativePosBoxGroup.setOriginBlockPos(pos); + }); + this.add(relativePosBoxGroup); + + + // massive relative box group + ArrayList massRelBoxList = new ArrayList<>(); + for (int x = 0; x < 50*2; x+=2) + { + for (int z = 0; z < 50*2; z+=2) + { + massRelBoxList.add(new DhApiRenderableBox( + new DhApiVec3d(-x, 0, -z), new DhApiVec3d(1-x, 1, 1-z), + new Color(Color.RED.getRed(), Color.RED.getGreen(), Color.RED.getBlue()), + EDhApiBlockMaterial.TERRACOTTA + ) + ); + } + } + IDhApiRenderableBoxGroup massRelativePosBoxGroup = factory.createRelativePositionedGroup( + ModInfo.NAME + ":MassRedGroup", + new DhApiVec3d(-25, 140, 0), + massRelBoxList); + massRelativePosBoxGroup.setPreRenderFunc((event) -> + { + DhApiVec3d blockPos = massRelativePosBoxGroup.getOriginBlockPos(); + blockPos.y += event.partialTicks / 4; + if (blockPos.y > 150f) + { + blockPos.y = 140f; + + Color newColor = (massRelativePosBoxGroup.get(0).color == Color.RED) ? Color.RED.darker() : Color.RED; + massRelativePosBoxGroup.forEach((box) -> { box.color = newColor; }); + massRelativePosBoxGroup.triggerBoxChange(); + } + + massRelativePosBoxGroup.setOriginBlockPos(blockPos); + }); + this.add(massRelativePosBoxGroup); + } + + //endregion + + + + //==============// + // registration // + //==============// + //region + + @Override + public void add(IDhApiRenderableBoxGroup iBoxGroup) throws IllegalArgumentException + { + if (!(iBoxGroup instanceof RenderableBoxGroup)) + { + throw new IllegalArgumentException("Box group must be of type ["+ RenderableBoxGroup.class.getSimpleName()+"], type received: ["+(iBoxGroup != null ? iBoxGroup.getClass() : "NULL")+"]."); + } + RenderableBoxGroup boxGroup = (RenderableBoxGroup) iBoxGroup; + + + long id = boxGroup.getId(); + if (this.boxGroupById.containsKey(id)) + { + throw new IllegalArgumentException("A box group with the ID [" + id + "] is already present."); + } + + this.boxGroupById.put(id, boxGroup); + } + + @Override + public IDhApiRenderableBoxGroup remove(long id) { return this.boxGroupById.remove(id); } + + public void clear() { this.boxGroupById.clear(); } + + //endregion + + + + //===========// + // rendering // + //===========// + //region + + /** + * @param renderingWithSsao + * if true that means this render call is happening before the SSAO pass + * and any objects rendered in this pass will have SSAO applied to them. + */ + @Override + public void render(RenderParams renderEventParam, IProfilerWrapper profiler, boolean renderingWithSsao) + { + // render setup // + profiler.push("setup"); + + this.init(); + + boolean useInstancedRendering = this.instancedRenderingAvailable + && Config.Client.Advanced.Graphics.GenericRendering.enableInstancedRendering.get(); + + ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeGenericRenderSetupEvent.class, renderEventParam); + + + boolean renderWireframe = Config.Client.Advanced.Debugging.renderWireframe.get(); + if (renderWireframe) + { + GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_LINE); + GLMC.disableFaceCulling(); + } + else + { + GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_FILL); + GLMC.enableFaceCulling(); + } + + GLMC.enableBlend(); + GL32.glBlendEquation(GL32.GL_FUNC_ADD); + GLMC.glBlendFuncSeparate(GL32.GL_SRC_ALPHA, GL32.GL_ONE_MINUS_SRC_ALPHA, GL32.GL_ONE, GL32.GL_ONE_MINUS_SRC_ALPHA); + + IDhApiGenericObjectShaderProgram shaderProgram = useInstancedRendering ? this.instancedShaderProgram : this.directShaderProgram; + IDhApiGenericObjectShaderProgram shaderProgramOverride = OverrideInjector.INSTANCE.get(IDhApiGenericObjectShaderProgram.class); + if (shaderProgramOverride != null && shaderProgram.overrideThisFrame()) + { + shaderProgram = shaderProgramOverride; + } + + shaderProgram.bind(renderEventParam); + //shaderProgram.bindVertexBuffer(this.boxVertexBuffer.getId()); + + this.boxIndexBuffer.bind(); + + Vec3d camPos = MC_RENDER.getCameraExactPosition(); + + + + // rendering // + + Collection boxList = this.boxGroupById.values(); + for (RenderableBoxGroup boxGroup : boxList) + { + // validation // + + // shouldn't happen, but just in case + if (boxGroup == null) + { + continue; + } + + // skip boxes that shouldn't render this pass + if (boxGroup.ssaoEnabled != renderingWithSsao) + { + continue; + } + + profiler.popPush("render prep"); + boxGroup.preRender(renderEventParam); // called even if the group is inactive, so the group can be activate if desired + + // ignore inactive groups + if (!boxGroup.active) + { + continue; + } + + // allow API users to cancel this object's rendering + boolean cancelRendering = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeGenericObjectRenderEvent.class, new DhApiBeforeGenericObjectRenderEvent.EventParam(renderEventParam, boxGroup)); + if (cancelRendering) + { + continue; + } + + // update instanced data if needed + if (useInstancedRendering) + { + boxGroup.tryUpdateInstancedDataAsync(); + + // skip groups that haven't been uploaded yet + if (boxGroup.vertexBufferContainer.getState() != NativeGlGenericObjectVertexContainer.EState.RENDER) + { + continue; + } + } + + + + // render // + + profiler.popPush("rendering"); + profiler.push(boxGroup.getResourceLocationNamespace()); + profiler.push(boxGroup.getResourceLocationPath()); + if (useInstancedRendering) + { + this.renderBoxGroupInstanced(shaderProgram, renderEventParam, boxGroup, camPos, profiler); + } + else + { + this.renderBoxGroupDirect(shaderProgram, renderEventParam, boxGroup, camPos, profiler); + } + profiler.pop(); // resource path + profiler.pop(); // resource namespace + + boxGroup.postRender(renderEventParam); + } + + + //==========// + // clean up // + //==========// + + profiler.popPush("cleanup"); + + ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeGenericRenderCleanupEvent.class, renderEventParam); + + if (renderWireframe) + { + // default back to GL_FILL since all other rendering uses it + GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_FILL); + GLMC.enableFaceCulling(); + } + + shaderProgram.unbind(); + + profiler.pop(); + } + + //endregion + + + + //=====================// + // instanced rendering // + //=====================// + //region + + private void renderBoxGroupInstanced( + IDhApiGenericObjectShaderProgram shaderProgram, DhApiRenderParam renderEventParam, + RenderableBoxGroup boxGroup, Vec3d camPos, + IProfilerWrapper profiler) + { + // update instance data // + + profiler.push("vertex setup"); + + DhApiRenderableBoxGroupShading shading = boxGroup.shading; + if (shading == null) + { + shading = DEFAULT_SHADING; + } + + shaderProgram.fillIndirectUniformData( + renderEventParam, + shading, boxGroup, + camPos); + + + + // Bind instance data // + profiler.popPush("binding"); + + NativeGlGenericObjectVertexContainer container = (NativeGlGenericObjectVertexContainer)(boxGroup.vertexBufferContainer); + + GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, container.color); + GL32.glEnableVertexAttribArray(1); + GL32.glVertexAttribPointer(1, 4, GL32.GL_FLOAT, false, 4 * Float.BYTES, 0); + this.vertexAttribDivisor(1, 1); + + GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, container.scale); + GL32.glEnableVertexAttribArray(2); + this.vertexAttribDivisor(2, 1); + GL32.glVertexAttribPointer(2, 3, GL32.GL_FLOAT, false, 3 * Float.BYTES, 0); + + GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, container.chunkPos); + GL32.glEnableVertexAttribArray(3); + this.vertexAttribDivisor(3, 1); + GL32.glVertexAttribIPointer(3, 3, GL32.GL_INT, 3 * Integer.BYTES, 0); + + GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, container.subChunkPos); + GL32.glEnableVertexAttribArray(4); + this.vertexAttribDivisor(4, 1); + GL32.glVertexAttribPointer(4, 3, GL32.GL_FLOAT, false, 3 * Float.BYTES, 0); + + GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, container.material); + GL32.glEnableVertexAttribArray(5); + this.vertexAttribDivisor(5, 1); + GL32.glVertexAttribIPointer(5, 1, GL32.GL_BYTE, Byte.BYTES, 0); + + + // Draw instanced + profiler.popPush("render"); + if (container.uploadedBoxCount > 0) + { + GL32.glDrawElementsInstanced(GL32.GL_TRIANGLES, BOX_INDICES.length, GL32.GL_UNSIGNED_INT, 0, container.uploadedBoxCount); + } + + + // Clean up + profiler.popPush("cleanup"); + + GL32.glDisableVertexAttribArray(1); + GL32.glDisableVertexAttribArray(2); + GL32.glDisableVertexAttribArray(3); + GL32.glDisableVertexAttribArray(4); + GL32.glDisableVertexAttribArray(5); + + profiler.pop(); + } + /** + * Clean way to handle both {@link GL33#glVertexAttribDivisor} and {@link ARBInstancedArrays#glVertexAttribDivisorARB} + * based on which one is supported. + */ + private void vertexAttribDivisor(int index, int divisor) + { + if (this.vertexAttribDivisorSupported) + { + GL33.glVertexAttribDivisor(index, divisor); + } + else if(this.instancedArraysSupported) + { + ARBInstancedArrays.glVertexAttribDivisorARB(index, divisor); + } + else + { + throw new IllegalStateException("Instanced rendering isn't supported by this machine. Direct rendering should have been used instead."); + } + } + + //endregion + + + + //==================// + // direct rendering // + //==================// + //region + + private void renderBoxGroupDirect( + IDhApiGenericObjectShaderProgram shaderProgram, + DhApiRenderParam renderEventParam, + RenderableBoxGroup boxGroup, Vec3d camPos, + IProfilerWrapper profiler) + { + profiler.popPush("shared uniforms"); + DhApiRenderableBoxGroupShading shading = boxGroup.shading; + if (shading == null) + { + shading = DhApiRenderableBoxGroupShading.getUnshaded(); + } + + shaderProgram.fillSharedDirectUniformData(renderEventParam, shading, boxGroup, camPos); + + for (int i = 0; i < boxGroup.size(); i++) + { + try + { + DhApiRenderableBox box = boxGroup.get(i); + if (box != null) + { + profiler.popPush("direct uniforms"); + shaderProgram.fillDirectUniformData(renderEventParam, boxGroup, box, camPos); + + profiler.popPush("render"); + GL32.glDrawElements(GL32.GL_TRIANGLES, BOX_INDICES.length, GL32.GL_UNSIGNED_INT, 0); + } + } + catch (IndexOutOfBoundsException e) + { + // Concurrency issue, the list was modified while rendering + // this can probably be ignored. + // However, if it does become a problem we can add locks to the box group. + break; + } + } + + profiler.pop(); + } + + //endregion + + + + //=========// + // getters // + //=========// + //region + + /** @throws IllegalStateException if {@link #init()} function hasn't been called yet */ + public boolean getInstancedRenderingAvailable() throws IllegalStateException + { + if (!this.init) + { + throw new IllegalStateException("GL initialization hasn't been completed."); + } + + return this.instancedRenderingAvailable; + } + + //endregion + + + + //=========// + // F3 menu // + //=========// + //region + + public String getVboRenderDebugMenuString() + { + // get counts + int totalGroupCount = this.boxGroupById.size(); + int totalBoxCount = 0; + + int activeGroupCount = 0; + int activeBoxCount = 0; + + for (long key : this.boxGroupById.keySet()) + { + RenderableBoxGroup renderGroup = this.boxGroupById.get(key); + if (renderGroup.active) + { + activeGroupCount++; + activeBoxCount += renderGroup.size(); + } + totalBoxCount += renderGroup.size(); + } + + + return "Generic Obj #: " + F3Screen.NUMBER_FORMAT.format(activeGroupCount) + "/" + F3Screen.NUMBER_FORMAT.format(totalGroupCount) + ", " + + "Cube #: " + F3Screen.NUMBER_FORMAT.format(activeBoxCount) + "/" + F3Screen.NUMBER_FORMAT.format(totalBoxCount); + } + + //endregion + + + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/generic/GenericObjectShaderProgram.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/generic/GenericObjectShaderProgram.java new file mode 100644 index 000000000..8635e7554 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/generic/GenericObjectShaderProgram.java @@ -0,0 +1,232 @@ +package com.seibel.distanthorizons.common.render.nativeGl.generic; + +import com.seibel.distanthorizons.api.interfaces.override.rendering.IDhApiGenericObjectShaderProgram; +import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup; +import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam; +import com.seibel.distanthorizons.api.objects.math.DhApiVec3d; +import com.seibel.distanthorizons.api.objects.math.DhApiVec3i; +import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox; +import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShading; +import com.seibel.distanthorizons.common.render.nativeGl.glObject.shader.ShaderProgram; +import com.seibel.distanthorizons.common.render.nativeGl.glObject.vertexAttribute.AbstractVertexAttribute; +import com.seibel.distanthorizons.common.render.nativeGl.glObject.vertexAttribute.VertexPointer; +import com.seibel.distanthorizons.common.wrappers.misc.LightMapWrapper; +import com.seibel.distanthorizons.core.util.LodUtil; +import com.seibel.distanthorizons.core.util.math.Mat4f; +import com.seibel.distanthorizons.core.util.math.Vec3f; +import com.seibel.distanthorizons.core.wrapperInterfaces.misc.ILightMapWrapper; + +public class GenericObjectShaderProgram extends ShaderProgram implements IDhApiGenericObjectShaderProgram +{ + public static final String VERTEX_SHADER_INSTANCED_PATH = "shaders/genericObject/instanced/vert.vert"; + public static final String VERTEX_SHADER_DIRECT_PATH = "shaders/genericObject/direct/vert.vert"; + public static final String FRAGMENT_SHADER_INSTANCED_PATH = "shaders/genericObject/instanced/frag.frag"; + public static final String FRAGMENT_SHADER_DIRECT_PATH = "shaders/genericObject/direct/frag.frag"; + + public final AbstractVertexAttribute va; + + + // shader uniforms + private final int directShaderTransformUniform; + private final int directShaderColorUniform; + + private final int instancedShaderOffsetChunkUniform; + private final int instancedShaderOffsetSubChunkUniform; + private final int instancedShaderCameraChunkPosUniform; + private final int instancedShaderCameraSubChunkPosUniform; + private final int instancedShaderProjectionModelViewMatrixUniform; + + private final int lightMapUniform; + private final int skyLightUniform; + private final int blockLightUniform; + + private final int northShadingUniform; + private final int southShadingUniform; + private final int eastShadingUniform; + private final int westShadingUniform; + private final int topShadingUniform; + private final int bottomShadingUniform; + + + + //=============// + // constructor // + //=============// + + public GenericObjectShaderProgram(boolean useInstancedRendering) + { + super( + useInstancedRendering ? VERTEX_SHADER_INSTANCED_PATH : VERTEX_SHADER_DIRECT_PATH, + useInstancedRendering ? FRAGMENT_SHADER_INSTANCED_PATH : FRAGMENT_SHADER_DIRECT_PATH, + "vPosition" + ); + + this.va = AbstractVertexAttribute.create(); + this.va.bind(); + // Pos + this.va.setVertexAttribute(0, 0, VertexPointer.addVec3Pointer(false)); + this.va.completeAndCheck(Float.BYTES * 3); + + this.directShaderTransformUniform = this.tryGetUniformLocation("uTransform"); + this.directShaderColorUniform = this.tryGetUniformLocation("uColor"); + + this.instancedShaderOffsetChunkUniform = this.tryGetUniformLocation("uOffsetChunk"); + this.instancedShaderOffsetSubChunkUniform = this.tryGetUniformLocation("uOffsetSubChunk"); + this.instancedShaderCameraChunkPosUniform = this.tryGetUniformLocation("uCameraPosChunk"); + this.instancedShaderCameraSubChunkPosUniform = this.tryGetUniformLocation("uCameraPosSubChunk"); + this.instancedShaderProjectionModelViewMatrixUniform = this.tryGetUniformLocation("uProjectionMvm"); + + this.lightMapUniform = this.getUniformLocation("uLightMap"); + this.skyLightUniform = this.getUniformLocation("uSkyLight"); + this.blockLightUniform = this.getUniformLocation("uBlockLight"); + this.northShadingUniform = this.getUniformLocation("uNorthShading"); + this.southShadingUniform = this.getUniformLocation("uSouthShading"); + this.eastShadingUniform = this.getUniformLocation("uEastShading"); + this.westShadingUniform = this.getUniformLocation("uWestShading"); + this.topShadingUniform = this.getUniformLocation("uTopShading"); + this.bottomShadingUniform = this.getUniformLocation("uBottomShading"); + + } + + + + //=========// + // methods // + //=========// + + @Override + public void bind(DhApiRenderParam renderEventParam) + { + super.bind(); + this.va.bind(); + } + @Override + public void unbind() + { + super.unbind(); + this.va.unbind(); + } + + @Override + public void free() + { + this.va.free(); + super.free(); + } + + @Override + public void bindVertexBuffer(int vbo) { this.va.bindBufferToAllBindingPoints(vbo); } + + @Override + public void fillIndirectUniformData( + DhApiRenderParam renderParameters, + DhApiRenderableBoxGroupShading shading, IDhApiRenderableBoxGroup boxGroup, + DhApiVec3d camPos + ) + { + Mat4f projectionMvmMatrix = new Mat4f(renderParameters.dhProjectionMatrix); + projectionMvmMatrix.multiply(renderParameters.dhModelViewMatrix); + + super.bind(); + + + + + this.setUniform(this.instancedShaderOffsetChunkUniform, + new DhApiVec3i( + LodUtil.getChunkPosFromDouble(boxGroup.getOriginBlockPos().x), + LodUtil.getChunkPosFromDouble(boxGroup.getOriginBlockPos().y), + LodUtil.getChunkPosFromDouble(boxGroup.getOriginBlockPos().z) + )); + this.setUniform(this.instancedShaderOffsetSubChunkUniform, + new Vec3f( + LodUtil.getSubChunkPosFromDouble(boxGroup.getOriginBlockPos().x), + LodUtil.getSubChunkPosFromDouble(boxGroup.getOriginBlockPos().y), + LodUtil.getSubChunkPosFromDouble(boxGroup.getOriginBlockPos().z) + )); + + this.setUniform(this.instancedShaderCameraChunkPosUniform, + new DhApiVec3i( + LodUtil.getChunkPosFromDouble(camPos.x), + LodUtil.getChunkPosFromDouble(camPos.y), + LodUtil.getChunkPosFromDouble(camPos.z) + )); + this.setUniform(this.instancedShaderCameraSubChunkPosUniform, + new Vec3f( + LodUtil.getSubChunkPosFromDouble(camPos.x), + LodUtil.getSubChunkPosFromDouble(camPos.y), + LodUtil.getSubChunkPosFromDouble(camPos.z) + )); + + this.setUniform(this.instancedShaderProjectionModelViewMatrixUniform, projectionMvmMatrix); + + this.setUniform(this.lightMapUniform, LightMapWrapper.GL_BOUND_INDEX); + this.setUniform(this.skyLightUniform, boxGroup.getSkyLight()); + this.setUniform(this.blockLightUniform, boxGroup.getBlockLight()); + + + this.setUniform(this.northShadingUniform, shading.north); + this.setUniform(this.southShadingUniform, shading.south); + this.setUniform(this.eastShadingUniform, shading.east); + this.setUniform(this.westShadingUniform, shading.west); + this.setUniform(this.topShadingUniform, shading.top); + this.setUniform(this.bottomShadingUniform, shading.bottom); + + + } + + + @Override + public void fillSharedDirectUniformData( + DhApiRenderParam renderParameters, + DhApiRenderableBoxGroupShading shading, IDhApiRenderableBoxGroup boxGroup, + DhApiVec3d camPos) + { + + this.setUniform(this.lightMapUniform, LightMapWrapper.GL_BOUND_INDEX); + this.setUniform(this.skyLightUniform, boxGroup.getSkyLight()); + this.setUniform(this.blockLightUniform, boxGroup.getBlockLight()); + + + this.setUniform(this.northShadingUniform, shading.north); + this.setUniform(this.southShadingUniform, shading.south); + this.setUniform(this.eastShadingUniform, shading.east); + this.setUniform(this.westShadingUniform, shading.west); + this.setUniform(this.topShadingUniform, shading.top); + this.setUniform(this.bottomShadingUniform, shading.bottom); + + } + + public void fillDirectUniformData( + DhApiRenderParam renderParameters, + IDhApiRenderableBoxGroup boxGroup, DhApiRenderableBox box, + DhApiVec3d camPos) + { + Mat4f projectionMvmMatrix = new Mat4f(renderParameters.dhProjectionMatrix); + projectionMvmMatrix.multiply(renderParameters.dhModelViewMatrix); + + Mat4f boxTransform = Mat4f.createTranslateMatrix( + (float) (box.minPos.x + boxGroup.getOriginBlockPos().x - camPos.x), + (float) (box.minPos.y + boxGroup.getOriginBlockPos().y - camPos.y), + (float) (box.minPos.z + boxGroup.getOriginBlockPos().z - camPos.z)); + boxTransform.multiply(Mat4f.createScaleMatrix( + (float) (box.maxPos.x - box.minPos.x), + (float) (box.maxPos.y - box.minPos.y), + (float) (box.maxPos.z - box.minPos.z))); + projectionMvmMatrix.multiply(boxTransform); + this.setUniform(this.directShaderTransformUniform, projectionMvmMatrix); + + this.setUniform(this.directShaderColorUniform, box.color); + + } + + + + @Override + public int getId() { return this.id; } + + /** The base DH render program should always render */ + @Override + public boolean overrideThisFrame() { return true; } + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/generic/GenericRenderObjectFactory.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/generic/GenericRenderObjectFactory.java new file mode 100644 index 000000000..7eb2dbb36 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/generic/GenericRenderObjectFactory.java @@ -0,0 +1,75 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl.generic; + +import com.seibel.distanthorizons.api.interfaces.render.IDhApiCustomRenderObjectFactory; +import com.seibel.distanthorizons.api.interfaces.render.IDhApiCustomRenderRegister; +import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup; +import com.seibel.distanthorizons.api.objects.math.DhApiVec3d; +import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox; +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import com.seibel.distanthorizons.core.logging.DhLogger; + +import java.util.List; +import java.util.*; + +/** + * Handles creating {@link DhApiRenderableBox}. + * + * @see IDhApiCustomRenderRegister + * @see DhApiRenderableBox + */ +public class GenericRenderObjectFactory implements IDhApiCustomRenderObjectFactory +{ + private static final DhLogger LOGGER = new DhLoggerBuilder().build(); + + public static final GenericRenderObjectFactory INSTANCE = new GenericRenderObjectFactory(); + + + + //=============// + // constructor // + //=============// + + private GenericRenderObjectFactory() { } + + + + //================// + // group creation // + //================// + + @Override + public IDhApiRenderableBoxGroup createForSingleBox(String resourceLocation, DhApiRenderableBox box) + { + ArrayList list = new ArrayList<>(); + list.add(box); + return this.createAbsolutePositionedGroup(resourceLocation, list); + } + + @Override + public IDhApiRenderableBoxGroup createRelativePositionedGroup(String resourceLocation, DhApiVec3d originBlockPos, List boxList) + { return new RenderableBoxGroup(resourceLocation, new DhApiVec3d(originBlockPos.x, originBlockPos.y, originBlockPos.z), boxList, true); } + + @Override + public IDhApiRenderableBoxGroup createAbsolutePositionedGroup(String resourceLocation, List boxList) + { return new RenderableBoxGroup(resourceLocation, new DhApiVec3d(0, 0, 0), boxList, false); } + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/generic/NativeGlGenericObjectVertexContainer.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/generic/NativeGlGenericObjectVertexContainer.java new file mode 100644 index 000000000..87ec6eb80 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/generic/NativeGlGenericObjectVertexContainer.java @@ -0,0 +1,180 @@ +package com.seibel.distanthorizons.common.render.nativeGl.generic; + +import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox; +import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftGLWrapper; +import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; +import com.seibel.distanthorizons.core.logging.DhLogger; +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import com.seibel.distanthorizons.core.util.LodUtil; +import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; +import com.seibel.distanthorizons.core.wrapperInterfaces.render.IGenericObjectVertexBufferContainer; +import org.lwjgl.opengl.GL32; + +import java.awt.*; +import java.util.List; + +/** + * For use by {@link RenderableBoxGroup} + * + * @see RenderableBoxGroup + */ +public class NativeGlGenericObjectVertexContainer implements IGenericObjectVertexBufferContainer +{ + private static final DhLogger LOGGER = new DhLoggerBuilder().build(); + + private static final MinecraftGLWrapper GLMC = MinecraftGLWrapper.INSTANCE; + + + + public int chunkPos = 0; + public int subChunkPos = 0; + public int scale = 0; + public int color = 0; + public int material = 0; + + public int[] chunkPosData = new int[0]; + public float[] subChunkPosData = new float[0]; + public float[] scalingData = new float[0]; + public float[] colorData = new float[0]; + public int[] materialData = new int[0]; + + public int uploadedBoxCount = 0; + + private EState state = EState.NEW; + @Override + public EState getState() { return this.state; } + @Override + public void setState(EState state) { this.state = state; } + + + + //===========================// + // render building/uploading // + //===========================// + //region + + /** needs to be done on the render thread */ + public void tryRunRenderThreadSetup() + { + if (this.chunkPos == 0) + { + this.chunkPos = GLMC.glGenBuffers(); + this.subChunkPos = GLMC.glGenBuffers(); + this.scale = GLMC.glGenBuffers(); + this.color = GLMC.glGenBuffers(); + this.material = GLMC.glGenBuffers(); + } + } + + public void updateVertexData(List uploadBoxList) + { + int boxCount = uploadBoxList.size(); + + + // recreate the data arrays if their size is different + if (this.uploadedBoxCount != boxCount) + { + this.uploadedBoxCount = boxCount; + + this.chunkPosData = new int[boxCount * 3]; // 3 elements XYZ + this.subChunkPosData = new float[boxCount * 3]; // 3 elements XYZ + this.scalingData = new float[boxCount * 3]; // 3 elements XYZ + + this.colorData = new float[boxCount * 4]; // 4 elements, RGBA + this.materialData = new int[boxCount]; + } + + + // transformation / scaling // + for (int i = 0; i < boxCount; i++) + { + DhApiRenderableBox box = uploadBoxList.get(i); + + int dataIndex = i * 3; + + this.chunkPosData[dataIndex] = LodUtil.getChunkPosFromDouble(box.minPos.x); + this.chunkPosData[dataIndex + 1] = LodUtil.getChunkPosFromDouble(box.minPos.y); + this.chunkPosData[dataIndex + 2] = LodUtil.getChunkPosFromDouble(box.minPos.z); + + this.subChunkPosData[dataIndex] = LodUtil.getSubChunkPosFromDouble(box.minPos.x); + this.subChunkPosData[dataIndex + 1] = LodUtil.getSubChunkPosFromDouble(box.minPos.y); + this.subChunkPosData[dataIndex + 2] = LodUtil.getSubChunkPosFromDouble(box.minPos.z); + + this.scalingData[dataIndex] = (float) (box.maxPos.x - box.minPos.x); + this.scalingData[dataIndex + 1] = (float) (box.maxPos.y - box.minPos.y); + this.scalingData[dataIndex + 2] = (float) (box.maxPos.z - box.minPos.z); + } + + + // colors/materials // + for (int i = 0; i < boxCount; i++) + { + DhApiRenderableBox box = uploadBoxList.get(i); + Color color = box.color; + int colorIndex = i * 4; + this.colorData[colorIndex] = color.getRed() / 255.0f; + this.colorData[colorIndex + 1] = color.getGreen() / 255.0f; + this.colorData[colorIndex + 2] = color.getBlue() / 255.0f; + this.colorData[colorIndex + 3] = color.getAlpha() / 255.0f; + + this.materialData[i] = box.material; + } + + this.state = NativeGlGenericObjectVertexContainer.EState.READY_TO_UPLOAD; + } + + public void uploadDataToGpu() + { + // Upload transformation matrices + GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, this.chunkPos); + GL32.glBufferData(GL32.GL_ARRAY_BUFFER, this.chunkPosData, GL32.GL_DYNAMIC_DRAW); + GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, this.subChunkPos); + GL32.glBufferData(GL32.GL_ARRAY_BUFFER, this.subChunkPosData, GL32.GL_DYNAMIC_DRAW); + GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, this.scale); + GL32.glBufferData(GL32.GL_ARRAY_BUFFER, this.scalingData, GL32.GL_DYNAMIC_DRAW); + + // Upload colors + GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, this.color); + GL32.glBufferData(GL32.GL_ARRAY_BUFFER, this.colorData, GL32.GL_DYNAMIC_DRAW); + + // Upload materials + GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, this.material); + GL32.glBufferData(GL32.GL_ARRAY_BUFFER, this.materialData, GL32.GL_DYNAMIC_DRAW); + + this.state = EState.RENDER; + } + + //endregion + + + + //================// + // base overrides // + //================// + //region + + @Override + public void close() + { + tryDeleteBuffer(this.chunkPos); + tryDeleteBuffer(this.subChunkPos); + tryDeleteBuffer(this.scale); + tryDeleteBuffer(this.color); + tryDeleteBuffer(this.material); + } + private static void tryDeleteBuffer(int bufferId) + { + // usually unnecessary, but just in case + if (bufferId != 0) + { + GLMC.glDeleteBuffers(bufferId); + } + } + + //endregion + + + + + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/generic/RenderableBoxGroup.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/generic/RenderableBoxGroup.java new file mode 100644 index 000000000..2e354598e --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/generic/RenderableBoxGroup.java @@ -0,0 +1,361 @@ +package com.seibel.distanthorizons.common.render.nativeGl.generic; + +import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup; +import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam; +import com.seibel.distanthorizons.api.objects.math.DhApiVec3d; +import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox; +import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShading; +import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; +import com.seibel.distanthorizons.core.logging.DhLogger; +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler; +import com.seibel.distanthorizons.core.util.LodUtil; +import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker; +import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; +import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; +import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; +import com.seibel.distanthorizons.core.wrapperInterfaces.render.IGenericObjectVertexBufferContainer; +import org.jetbrains.annotations.Nullable; + +import java.io.Closeable; +import java.util.*; +import java.util.List; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.function.UnaryOperator; +import java.util.stream.Stream; + +public class RenderableBoxGroup + extends AbstractList + implements IDhApiRenderableBoxGroup, Closeable +{ + private static final DhLogger LOGGER = new DhLoggerBuilder().build(); + + private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); + private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class); + + public static final AtomicInteger NEXT_ID_ATOMIC_INT = new AtomicInteger(0); + + + + public final long id; + + public final String resourceLocationNamespace; + public final String resourceLocationPath; + + /** If false the boxes will be positioned relative to the level's origin */ + public final boolean positionBoxesRelativeToGroupOrigin; + + private final List boxList; + /** backup list which allows for uploading the boxes even it the main list is being modified on a different thread. */ + private final List uploadBoxList; + private final DhApiVec3d originBlockPos; + + + public boolean active = true; + public boolean ssaoEnabled = true; + private boolean vertexDataDirty = true; + + public byte skyLight = LodUtil.MAX_MC_LIGHT; + public byte blockLight = LodUtil.MIN_MC_LIGHT; + public DhApiRenderableBoxGroupShading shading = DhApiRenderableBoxGroupShading.getDefaultShaded(); + + @Nullable + public Consumer beforeRenderFunc; + public Consumer afterRenderFunc; + + // instance data + public IGenericObjectVertexBufferContainer vertexBufferContainer = WRAPPER_FACTORY.createInstancedVboContainer(); + /** double buffering for thread safety and to prevent locking the render thread during update */ + private IGenericObjectVertexBufferContainer altVertexBufferContainer = WRAPPER_FACTORY.createInstancedVboContainer(); + + + + //=================// + // getters/setters // + //=================// + //region + + @Override + public long getId() { return this.id; } + + @Override + public String getResourceLocationNamespace() { return this.resourceLocationNamespace; } + @Override + public String getResourceLocationPath() { return this.resourceLocationPath; } + + @Override + public void setOriginBlockPos(DhApiVec3d pos) + { + this.originBlockPos.x = pos.x; + this.originBlockPos.y = pos.y; + this.originBlockPos.z = pos.z; + } + + @Override + public DhApiVec3d getOriginBlockPos() { return new DhApiVec3d(this.originBlockPos.x, this.originBlockPos.y, this.originBlockPos.z); } + + + @Override + public void setSkyLight(int skyLight) + { + if (skyLight < LodUtil.MIN_MC_LIGHT + || skyLight > LodUtil.MAX_MC_LIGHT) + { + throw new IllegalArgumentException("Sky light ["+skyLight+"] must be between ["+LodUtil.MIN_MC_LIGHT+"] and ["+LodUtil.MAX_MC_LIGHT+"] (inclusive)."); + } + this.skyLight = (byte)skyLight; + } + @Override + public int getSkyLight() { return this.skyLight; } + + @Override + public void setBlockLight(int blockLight) + { + if (blockLight < LodUtil.MIN_MC_LIGHT + || blockLight > LodUtil.MAX_MC_LIGHT) + { + throw new IllegalArgumentException("Block light ["+blockLight+"] must be between ["+LodUtil.MIN_MC_LIGHT+"] and ["+LodUtil.MAX_MC_LIGHT+"] (inclusive)."); + } + this.blockLight = (byte)blockLight; + } + @Override + public int getBlockLight() { return this.blockLight; } + + @Override + public void setPreRenderFunc(Consumer func) { this.beforeRenderFunc = func; } + + @Override + public void setPostRenderFunc(Consumer func) { this.afterRenderFunc = func; } + + @Override + public void setActive(boolean active) { this.active = active; } + @Override + public boolean isActive() { return this.active; } + + @Override + public void setSsaoEnabled(boolean ssaoEnabled) { this.ssaoEnabled = ssaoEnabled; } + @Override + public boolean isSsaoEnabled() { return this.ssaoEnabled; } + + @Override + public void setShading(DhApiRenderableBoxGroupShading shading) { this.shading = shading; } + @Override + public DhApiRenderableBoxGroupShading getShading() { return this.shading; } + + //endregion + + + + //=============// + // constructor // + //=============// + //region + + public RenderableBoxGroup( + String resourceLocation, + DhApiVec3d originBlockPos, List boxList, + boolean positionBoxesRelativeToGroupOrigin) throws IllegalArgumentException + { + String[] splitResourceLocation = resourceLocation.split(":"); + if (splitResourceLocation.length != 2) + { + throw new IllegalArgumentException("Resource Location must be a string that's separated by a single colon, for example: [DistantHorizons:Beacons], your namespace ["+resourceLocation+"], contains ["+(splitResourceLocation.length-1)+"] colons."); + } + + this.resourceLocationNamespace = splitResourceLocation[0]; + this.resourceLocationPath = splitResourceLocation[1]; + + this.id = NEXT_ID_ATOMIC_INT.getAndIncrement(); + this.boxList = Collections.synchronizedList(new ArrayList<>(boxList)); + this.uploadBoxList = Collections.synchronizedList(new ArrayList<>(boxList)); + + this.originBlockPos = originBlockPos; + this.positionBoxesRelativeToGroupOrigin = positionBoxesRelativeToGroupOrigin; + } + + //endregion + + + + //=================// + // render building // + //=================// + //region + + @Override + public void triggerBoxChange() { this.vertexDataDirty = true; } + + /** + * Does nothing if the vertex data is already up-to-date + * and is meaningless if using direct rendering. + */ + public void tryUpdateInstancedDataAsync() + { + // if the alt container is done, swap it in + if (this.altVertexBufferContainer.getState() == NativeGlGenericObjectVertexContainer.EState.READY_TO_UPLOAD) + { + this.altVertexBufferContainer.uploadDataToGpu(); + + // swap VBO references for rendering + IGenericObjectVertexBufferContainer temp = this.vertexBufferContainer; + this.vertexBufferContainer = this.altVertexBufferContainer; + this.altVertexBufferContainer = temp; + + this.vertexDataDirty = false; + + return; + } + + + + // if the vertex data is already up to date, do nothing + if (!this.vertexDataDirty) + { + return; + } + + PriorityTaskPicker.Executor executor = ThreadPoolUtil.getRenderLoadingExecutor(); + if (executor == null || executor.isTerminated()) + { + return; + } + + // if the alternate container is already updating, don't double-queue it + if (this.altVertexBufferContainer.getState() == NativeGlGenericObjectVertexContainer.EState.UPDATING_DATA) + { + return; + } + this.altVertexBufferContainer.setState(NativeGlGenericObjectVertexContainer.EState.UPDATING_DATA); + + + + //this.altInstancedVbos.tryRunRenderThreadSetup(); + + // copy over the box list so we can upload without concurrent modification issues + this.uploadBoxList.clear(); + synchronized (this.uploadBoxList) + { + this.uploadBoxList.addAll(this.boxList); + } + + try + { + executor.runTask(() -> + { + try + { + this.altVertexBufferContainer.updateVertexData(this.uploadBoxList); + } + catch (Exception e) + { + LOGGER.error("Unexpected error updating instanced VBO data for: ["+this+"], error: ["+e.getMessage()+"].", e); + this.altVertexBufferContainer.setState(NativeGlGenericObjectVertexContainer.EState.ERROR); + } + }); + } + catch (RejectedExecutionException ignore) + { + // the executor was shut down, it should be back up shortly and able to accept new jobs + this.altVertexBufferContainer.setState(NativeGlGenericObjectVertexContainer.EState.NEW); + } + } + + //endregion + + + + //===============// + // render events // + //===============// + //region + + /** + * This is called before every frame, even if {@link this#isActive()} returns false.
+ * {@link this#isActive()} can be changed at this point before the object is rendered to the frame. + */ + public void preRender(DhApiRenderParam renderEventParam) + { + if (this.beforeRenderFunc != null) + { + this.beforeRenderFunc.accept(renderEventParam); + } + } + /** + * Called after rendering is completed.
+ * Can be used to handle any necessary cleanup. + */ + public void postRender(DhApiRenderParam renderEventParam) + { + if (this.afterRenderFunc != null) + { + this.afterRenderFunc.accept(renderEventParam); + } + } + + //endregion + + + + //================// + // List Overrides // + //================// + //region + + @Override + public boolean add(DhApiRenderableBox box) { return this.boxList.add(box); } + @Override + public DhApiRenderableBox get(int index) { return this.boxList.get(index); } + @Override + public int size() { return this.boxList.size(); } + @Override + public boolean removeIf(Predicate filter) { return this.boxList.removeIf(filter); } + @Override + public boolean remove(Object obj) { return this.boxList.remove(obj); } + @Override + public DhApiRenderableBox remove(int index) { return this.boxList.remove(index); } + @Override + public void replaceAll(UnaryOperator operator) { this.boxList.replaceAll(operator); } + @Override + public void sort(Comparator comparator) { this.boxList.sort(comparator); } + @Override + public void forEach(Consumer action) { this.boxList.forEach(action); } + @Override + public Spliterator spliterator() { return this.boxList.spliterator(); } + @Override + public Stream stream() { return this.boxList.stream(); } + @Override + public Stream parallelStream() { return this.boxList.parallelStream(); } + @Override + public void clear() { this.boxList.clear(); } + + //endregion + + + + //================// + // base overrides // + //================// + //region + + @Override + public String toString() { return "["+this.resourceLocationNamespace+":"+this.resourceLocationPath+"] ID:["+this.id+"], pos:[("+this.originBlockPos.x+", "+this.originBlockPos.y+", "+this.originBlockPos.z+")], size:["+this.size()+"], active:["+this.active+"]"; } + + @Override + public void close() + { + RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread(() -> + { + this.vertexBufferContainer.close(); + this.altVertexBufferContainer.close(); + }); + } + + //endregion + + + +} + \ No newline at end of file diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/GLEnums.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/GLEnums.java new file mode 100644 index 000000000..f9fa9b921 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/GLEnums.java @@ -0,0 +1,261 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl.glObject; + +import static org.lwjgl.opengl.GL46.*; + +// Turns GL int enums back to readable strings +public class GLEnums +{ + + public static String getString(int glEnum) + { + // blend stuff + switch (glEnum) + { + case GL_ZERO: + return "GL_ZERO"; + case GL_ONE: + return "GL_ONE"; + case GL_SRC_COLOR: + return "GL_SRC_COLOR"; + case GL_ONE_MINUS_SRC_COLOR: + return "GL_ONE_MINUS_SRC_COLOR"; + case GL_DST_COLOR: + return "GL_DST_COLOR"; + case GL_ONE_MINUS_DST_COLOR: + return "GL_ONE_MINUS_DST_COLOR"; + case GL_SRC_ALPHA: + return "GL_SRC_ALPHA"; + case GL_ONE_MINUS_SRC_ALPHA: + return "GL_ONE_MINUS_SRC_ALPHA"; + case GL_DST_ALPHA: + return "GL_DST_ALPHA"; + case GL_ONE_MINUS_DST_ALPHA: + return "GL_ONE_MINUS_DST_ALPHA"; + case GL_CONSTANT_COLOR: + return "GL_CONSTANT_COLOR"; + case GL_ONE_MINUS_CONSTANT_COLOR: + return "GL_ONE_MINUS_CONSTANT_COLOR"; + case GL_CONSTANT_ALPHA: + return "GL_CONSTANT_ALPHA"; + case GL_ONE_MINUS_CONSTANT_ALPHA: + return "GL_ONE_MINUS_CONSTANT_ALPHA"; + default: + } + + // shader stuff + switch (glEnum) + { + case GL_VERTEX_SHADER: + return "GL_VERTEX_SHADER"; + case GL_GEOMETRY_SHADER: + return "GL_GEOMETRY_SHADER"; + case GL_FRAGMENT_SHADER: + return "GL_FRAGMENT_SHADER"; + default: + } + + // stencil stuff + switch (glEnum) + { + case GL_KEEP: + return "GL_KEEP"; + case GL_ZERO: + return "GL_ZERO"; + case GL_REPLACE: + return "GL_REPLACE"; + case GL_INCR: + return "GL_INCR"; + case GL_DECR: + return "GL_DECR"; + case GL_INVERT: + return "GL_INVERT"; + case GL_INCR_WRAP: + return "GL_INCR_WRAP"; + case GL_DECR_WRAP: + return "GL_DECR_WRAP"; + default: + } + + // depth stuff + switch (glEnum) + { + case GL_NEVER: + return "GL_NEVER"; + case GL_LESS: + return "GL_LESS"; + case GL_EQUAL: + return "GL_EQUAL"; + case GL_LEQUAL: + return "GL_LEQUAL"; + case GL_GREATER: + return "GL_GREATER"; + case GL_NOTEQUAL: + return "GL_NOTEQUAL"; + case GL_GEQUAL: + return "GL_GEQUAL"; + case GL_ALWAYS: + return "GL_ALWAYS"; + default: + } + + // Texture binding points + switch (glEnum) + { + case GL_TEXTURE0: + return "GL_TEXTURE0"; + case GL_TEXTURE1: + return "GL_TEXTURE1"; + case GL_TEXTURE2: + return "GL_TEXTURE2"; + case GL_TEXTURE3: + return "GL_TEXTURE3"; + case GL_TEXTURE4: + return "GL_TEXTURE4"; + case GL_TEXTURE5: + return "GL_TEXTURE5"; + case GL_TEXTURE6: + return "GL_TEXTURE6"; + case GL_TEXTURE7: + return "GL_TEXTURE7"; + case GL_TEXTURE8: + return "GL_TEXTURE8"; + case GL_TEXTURE9: + return "GL_TEXTURE9"; + case GL_TEXTURE10: + return "GL_TEXTURE10"; + case GL_TEXTURE11: + return "GL_TEXTURE11"; + case GL_TEXTURE12: + return "GL_TEXTURE12"; + case GL_TEXTURE13: + return "GL_TEXTURE13"; + case GL_TEXTURE14: + return "GL_TEXTURE14"; + case GL_TEXTURE15: + return "GL_TEXTURE15"; + case GL_TEXTURE16: + return "GL_TEXTURE16"; + case GL_TEXTURE17: + return "GL_TEXTURE17"; + case GL_TEXTURE18: + return "GL_TEXTURE18"; + case GL_TEXTURE19: + return "GL_TEXTURE19"; + case GL_TEXTURE20: + return "GL_TEXTURE20"; + case GL_TEXTURE21: + return "GL_TEXTURE21"; + case GL_TEXTURE22: + return "GL_TEXTURE22"; + case GL_TEXTURE23: + return "GL_TEXTURE23"; + case GL_TEXTURE24: + return "GL_TEXTURE24"; + case GL_TEXTURE25: + return "GL_TEXTURE25"; + case GL_TEXTURE26: + return "GL_TEXTURE26"; + case GL_TEXTURE27: + return "GL_TEXTURE27"; + case GL_TEXTURE28: + return "GL_TEXTURE28"; + case GL_TEXTURE29: + return "GL_TEXTURE29"; + case GL_TEXTURE30: + return "GL_TEXTURE30"; + case GL_TEXTURE31: + return "GL_TEXTURE31"; + default: + } + + // Polygon modes + switch (glEnum) + { + case GL_POINT: + return "GL_POINT"; + case GL_LINE: + return "GL_LINE"; + case GL_FILL: + return "GL_FILL"; + default: + } + + // Culling modes + switch (glEnum) + { + case GL_FRONT: + return "GL_FRONT"; + case GL_BACK: + return "GL_BACK"; + case GL_FRONT_AND_BACK: + return "GL_FRONT_AND_BACK"; + default: + } + + // Types + switch (glEnum) + { + case GL_BYTE: + return "GL_BYTE"; + case GL_UNSIGNED_BYTE: + return "GL_UNSIGNED_BYTE"; + case GL_SHORT: + return "GL_SHORT"; + case GL_UNSIGNED_SHORT: + return "GL_UNSIGNED_SHORT"; + case GL_INT: + return "GL_INT"; + case GL_UNSIGNED_INT: + return "GL_UNSIGNED_INT"; + case GL_FLOAT: + return "GL_FLOAT"; + case GL_DOUBLE: + return "GL_DOUBLE"; + default: + } + + return "GL_UNKNOWN(" + glEnum + ")"; + } + + public static int getTypeSize(int glTypeEnum) + { + switch (glTypeEnum) + { + case GL_BYTE: + case GL_UNSIGNED_BYTE: + return 1; + case GL_SHORT: + case GL_UNSIGNED_SHORT: + return 2; + case GL_INT: + case GL_UNSIGNED_INT: + return 4; + case GL_FLOAT: + return 4; + case GL_DOUBLE: + return 8; + default: + throw new IllegalArgumentException("Unknown type enum: " + getString(glTypeEnum)); + } + } + +} \ No newline at end of file diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/GLProxy.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/GLProxy.java new file mode 100644 index 000000000..1e0280cc8 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/GLProxy.java @@ -0,0 +1,351 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl.glObject; + +import com.seibel.distanthorizons.api.enums.config.EDhApiGLErrorHandlingMode; +import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod; +import com.seibel.distanthorizons.core.config.Config; +import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; +import com.seibel.distanthorizons.core.jar.EPlatform; +import com.seibel.distanthorizons.core.logging.DhLogger; +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import com.seibel.distanthorizons.core.util.TimerUtil; +import com.seibel.distanthorizons.core.util.objects.GLMessages.*; +import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; +import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; +import com.seibel.distanthorizons.coreapi.ModInfo; +import org.lwjgl.glfw.GLFW; +import org.lwjgl.opengl.GL; +import org.lwjgl.opengl.GL32; +import org.lwjgl.opengl.GLCapabilities; +import org.lwjgl.opengl.GLUtil; + +import java.io.PrintStream; +import java.util.Collections; +import java.util.Set; +import java.util.Timer; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; + +/** + * A singleton that holds references to different openGL contexts + * and GPU capabilities. + */ +public class GLProxy +{ + public static final DhLogger LOGGER = new DhLoggerBuilder() + .fileLevelConfig(Config.Common.Logging.logRendererGLEventToFile) + .chatLevelConfig(Config.Common.Logging.logRendererGLEventToChat) + .build(); + + public static final Set LOGGED_GL_MESSAGES = Collections.newSetFromMap(new ConcurrentHashMap()); + + + + private static GLProxy instance = null; + + + /** Minecraft's GL capabilities */ + public final GLCapabilities glCapabilities; + + public boolean namedObjectSupported = false; // ~OpenGL 4.5 (UNUSED CURRENTLY) + public boolean bufferStorageSupported = false; // ~OpenGL 4.4 + public boolean vertexAttributeBufferBindingSupported = false; // ~OpenGL 4.3 + public boolean instancedArraysSupported = false; + public boolean vertexAttribDivisorSupported = false; // OpenGL 3.3 or newer + + private final EDhApiGpuUploadMethod preferredUploadMethod; + + public final GLMessageBuilder vanillaDebugMessageBuilder = + new GLMessageBuilder( + (type) -> + { + if (type == EGLMessageType.POP_GROUP) + return false; + else if (type == EGLMessageType.PUSH_GROUP) + return false; + else if (type == EGLMessageType.MARKER) + return false; + else + return true; + }, + (severity) -> + { + // notifications can generally be ignored (if they are logged at all) + if (severity == EGLMessageSeverity.NOTIFICATION) + return false; + else + return true; + }, + null + ); + + + + //=============// + // constructor // + //=============// + //region + + private GLProxy() throws IllegalStateException + { + // this must be created on minecraft's render context to work correctly + if (GLFW.glfwGetCurrentContext() == 0L) + { + throw new IllegalStateException(GLProxy.class.getSimpleName() + " was created outside the render thread!"); + } + + LOGGER.info("Creating " + GLProxy.class.getSimpleName() + "... If this is the last message you see there must have been an OpenGL error."); + LOGGER.info("Lod Render OpenGL version [" + GL32.glGetString(GL32.GL_VERSION) + "]."); + + + + + //============================// + // get Minecraft's GL context // + //============================// + + // get Minecraft's capabilities + this.glCapabilities = GL.getCapabilities(); + + // crash the game if the GPU doesn't support OpenGL 3.2 + if (!this.glCapabilities.OpenGL32) + { + String supportedVersionInfo = this.getFailedVersionInfo(this.glCapabilities); + + // See full requirement at above. + String errorMessage = ModInfo.READABLE_NAME + " was initializing " + GLProxy.class.getSimpleName() + + " and discovered this GPU doesn't meet the OpenGL requirements. Sorry I couldn't tell you sooner :(\n" + + "Additional info:\n" + supportedVersionInfo; + IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); + MC.crashMinecraft(errorMessage, new UnsupportedOperationException("Distant Horizon OpenGL requirements not met")); + } + LOGGER.info("minecraftGlCapabilities:\n" + this.versionInfoToString(this.glCapabilities)); + + if (Config.Client.Advanced.Debugging.OpenGl.overrideVanillaGLLogger.get()) + { + GLUtil.setupDebugMessageCallback(new PrintStream(new GLMessageOutputStream(GLProxy::logMessage, this.vanillaDebugMessageBuilder), true)); + } + + + + //======================// + // get GPU capabilities // + //======================// + + // UNUSED currently + // Check if we can use the named version of all calls, which is available in GL4.5 or after + this.namedObjectSupported = this.glCapabilities.glNamedBufferData != 0L; //Nullptr + + // Check if we can use the Buffer Storage, which is available in GL4.4 or after + this.bufferStorageSupported = this.glCapabilities.glBufferStorage != 0L; // Nullptr + if (!this.bufferStorageSupported) + { + LOGGER.info("This GPU doesn't support Buffer Storage (OpenGL 4.4), falling back to using other methods."); + } + + // Check if we can use the make-over version of Vertex Attribute, which is available in GL4.3 or after + this.vertexAttributeBufferBindingSupported = this.glCapabilities.glBindVertexBuffer != 0L; // Nullptr + + // used by instanced rendering + this.vertexAttribDivisorSupported = this.glCapabilities.OpenGL33; + // denotes if ARBInstancedArrays.glVertexAttribDivisorARB() is available or not + // can be used as a backup if MC didn't create a GL 3.3+ context + this.instancedArraysSupported = this.glCapabilities.GL_ARB_instanced_arrays; + + // get the best automatic upload method + String vendor = GL32.glGetString(GL32.GL_VENDOR).toUpperCase(); // example return: "NVIDIA CORPORATION" + if (EPlatform.get() != EPlatform.MACOS) + { + if (vendor.contains("NVIDIA") || vendor.contains("GEFORCE")) + { + // NVIDIA card + this.preferredUploadMethod = this.bufferStorageSupported ? EDhApiGpuUploadMethod.BUFFER_STORAGE : EDhApiGpuUploadMethod.SUB_DATA; + } + else + { + // AMD or Intel card + this.preferredUploadMethod = this.bufferStorageSupported ? EDhApiGpuUploadMethod.BUFFER_STORAGE : EDhApiGpuUploadMethod.DATA; + } + } + else + { + // Mac may have an issue with Buffer Storage, so default to the most basic + // form of uploading + this.preferredUploadMethod = EDhApiGpuUploadMethod.DATA; + } + LOGGER.info("GPU Vendor [" + vendor + "] with OS [" + EPlatform.get().getName() + "], Preferred upload method is [" + this.preferredUploadMethod + "]."); + + + + //==========// + // clean up // + //==========// + + // GLProxy creation success + LOGGER.info(GLProxy.class.getSimpleName() + " creation successful. OpenGL smiles upon you this day."); + } + + //endregion + + + + //=========// + // getters // + //=========// + //region + + public static boolean hasInstance() { return instance != null; } + /** @throws IllegalStateException if the Proxy hasn't been created yet and this is called outside the render thread */ + public static GLProxy getInstance() throws IllegalStateException + { + if (instance == null) + { + instance = new GLProxy(); + } + + return instance; + } + + public EDhApiGpuUploadMethod getGpuUploadMethod() + { + EDhApiGpuUploadMethod uploadOverride = Config.Client.Advanced.Debugging.OpenGl.glUploadMode.get(); + if (uploadOverride == EDhApiGpuUploadMethod.AUTO) + { + return this.preferredUploadMethod; + } + + return uploadOverride; + } + + public static boolean runningOnRenderThread() + { + long currentContext = GLFW.glfwGetCurrentContext(); + return currentContext != 0L; // if the context isn't null, it's the MC context + } + + //endregion + + + + //=========// + // logging // + //=========// + //region + + /** this method is called on the render thread at the point of the GL Error */ + private static void logMessage(GLMessage msg) + { + EDhApiGLErrorHandlingMode errorHandlingMode = Config.Client.Advanced.Debugging.OpenGl.glErrorHandlingMode.get(); + if (errorHandlingMode == EDhApiGLErrorHandlingMode.IGNORE) + { + return; + } + + + + boolean onlyLogOnce = Config.Client.Advanced.Debugging.OpenGl.onlyLogGlErrorsOnce.get(); + String errorMessage = "GL ERROR [" + msg.id + "] from [" + msg.source + "]: [" + msg.message + "]"+(onlyLogOnce ? " this message will only be logged once" : "")+"."; + if (onlyLogOnce + && !LOGGED_GL_MESSAGES.add(errorMessage)) + { + // this message has already been logged + return; + } + + + // create an exception so we get a stacktrace of where the message was triggered from + RuntimeException exception = new RuntimeException(errorMessage); + + if (msg.type == EGLMessageType.ERROR || msg.type == EGLMessageType.UNDEFINED_BEHAVIOR) + { + // critical error + + LOGGER.error(exception.getMessage(), exception); + + if (errorHandlingMode == EDhApiGLErrorHandlingMode.LOG_THROW) + { + // will probably crash the game, + // good for quickly checking if there's a problem while preventing log spam + throw exception; + } + } + else + { + // non-critical log + + EGLMessageSeverity severity = msg.severity; + if (severity == null) + { + // just in case the message was malformed + severity = EGLMessageSeverity.LOW; + } + + switch (severity) + { + case HIGH: + LOGGER.error(exception.getMessage(), exception); + break; + case MEDIUM: + LOGGER.warn(exception.getMessage(), exception); + break; + case LOW: + LOGGER.info(exception.getMessage(), exception); + break; + case NOTIFICATION: + LOGGER.debug(exception.getMessage(), exception); + break; + } + } + } + + //endregion + + + + //================// + // helper methods // + //================// + //region + + private String getFailedVersionInfo(GLCapabilities c) + { + return "Your OpenGL support:\n" + + "openGL version 3.2+: [" + c.OpenGL32 + "] <- REQUIRED\n" + + "Vertex Attribute Buffer Binding: [" + (c.glVertexAttribBinding != 0) + "] <- optional improvement\n" + + "Buffer Storage: [" + (c.glBufferStorage != 0) + "] <- optional improvement\n" + + "If you noticed that your computer supports higher OpenGL versions" + + " but not the required version, try running the game in compatibility mode." + + " (How you turn that on, I have no clue~)"; + } + + private String versionInfoToString(GLCapabilities c) + { + return "Your OpenGL support:\n" + + "openGL version 3.2+: [" + c.OpenGL32 + "] <- REQUIRED\n" + + "Vertex Attribute Buffer Binding: [" + (c.glVertexAttribBinding != 0) + "] <- optional improvement\n" + + "Buffer Storage: [" + (c.glBufferStorage != 0) + "] <- optional improvement\n"; + } + + //endregion + + + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/GLState.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/GLState.java new file mode 100644 index 000000000..d3511539b --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/GLState.java @@ -0,0 +1,259 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl.glObject; + +import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftGLWrapper; +import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; +import org.lwjgl.opengl.GL32; + +public class GLState implements AutoCloseable +{ + private static final MinecraftGLWrapper GLMC = MinecraftGLWrapper.INSTANCE; + + + public int program; + public int vao; + public int vbo; + public int ebo; + public int fbo; + public int texture2D; + /** IE: GL_TEXTURE0, GL_TEXTURE1, etc. */ + public int activeTextureNumber; + public int texture0; + public int texture1; + public int texture2; + public int texture3; + public int frameBufferTexture0; + public int frameBufferTexture1; + public int frameBufferDepthTexture; + public boolean blend; + public boolean scissor; + public int blendEqRGB; + public int blendEqAlpha; + public int blendSrcColor; + public int blendSrcAlpha; + public int blendDstColor; + public int blendDstAlpha; + public boolean depth; + public boolean writeToDepthBuffer; + public int depthFunc; + public boolean stencil; + public int stencilFunc; + public int stencilRef; + public int stencilMask; + public int[] view; + public boolean cull; + public int cullMode; + public int polyMode; + + + + public GLState() { this.saveState(); } + + public void saveState() + { + this.program = GL32.glGetInteger(GL32.GL_CURRENT_PROGRAM); + this.vao = GL32.glGetInteger(GL32.GL_VERTEX_ARRAY_BINDING); + this.vbo = GL32.glGetInteger(GL32.GL_ARRAY_BUFFER_BINDING); + this.ebo = GL32.glGetInteger(GL32.GL_ELEMENT_ARRAY_BUFFER_BINDING); + + this.fbo = GL32.glGetInteger(GL32.GL_FRAMEBUFFER_BINDING); + + this.texture2D = GL32.glGetInteger(GL32.GL_TEXTURE_BINDING_2D); + this.activeTextureNumber = GL32.glGetInteger(GL32.GL_ACTIVE_TEXTURE); + + GLMC.glActiveTexture(GL32.GL_TEXTURE0); + this.texture0 = GL32.glGetInteger(GL32.GL_TEXTURE_BINDING_2D); + + GLMC.glActiveTexture(GL32.GL_TEXTURE1); + this.texture1 = GL32.glGetInteger(GL32.GL_TEXTURE_BINDING_2D); + + GLMC.glActiveTexture(GL32.GL_TEXTURE2); // problem with Iris + this.texture2 = GL32.glGetInteger(GL32.GL_TEXTURE_BINDING_2D); + + GLMC.glActiveTexture(GL32.GL_TEXTURE3); + this.texture3 = GL32.glGetInteger(GL32.GL_TEXTURE_BINDING_2D); + + GLMC.glActiveTexture(this.activeTextureNumber); + + if (this.fbo != 0) + { + this.frameBufferTexture0 = GL32.glGetFramebufferAttachmentParameteri(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME); + this.frameBufferTexture1 = GL32.glGetFramebufferAttachmentParameteri(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT1, GL32.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME); + this.frameBufferDepthTexture = GL32.glGetFramebufferAttachmentParameteri(GL32.GL_FRAMEBUFFER, GL32.GL_DEPTH_ATTACHMENT, GL32.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME); + } + else + { + // attempting to get values from the default framebuffer can throw errors on Linux + this.frameBufferTexture0 = 0; + this.frameBufferTexture1 = 0; + this.frameBufferDepthTexture = 0; + } + + this.blend = GL32.glIsEnabled(GL32.GL_BLEND); + this.scissor = GL32.glIsEnabled(GL32.GL_SCISSOR_TEST); + this.blendEqRGB = GL32.glGetInteger(GL32.GL_BLEND_EQUATION_RGB); + this.blendEqAlpha = GL32.glGetInteger(GL32.GL_BLEND_EQUATION_ALPHA); + this.blendSrcColor = GL32.glGetInteger(GL32.GL_BLEND_SRC_RGB); + this.blendSrcAlpha = GL32.glGetInteger(GL32.GL_BLEND_SRC_ALPHA); + this.blendDstColor = GL32.glGetInteger(GL32.GL_BLEND_DST_RGB); + this.blendDstAlpha = GL32.glGetInteger(GL32.GL_BLEND_DST_ALPHA); + this.depth = GL32.glIsEnabled(GL32.GL_DEPTH_TEST); + this.writeToDepthBuffer = GL32.glGetInteger(GL32.GL_DEPTH_WRITEMASK) == GL32.GL_TRUE; + this.depthFunc = GL32.glGetInteger(GL32.GL_DEPTH_FUNC); + this.stencil = GL32.glIsEnabled(GL32.GL_STENCIL_TEST); + this.stencilFunc = GL32.glGetInteger(GL32.GL_STENCIL_FUNC); + this.stencilRef = GL32.glGetInteger(GL32.GL_STENCIL_REF); + this.stencilMask = GL32.glGetInteger(GL32.GL_STENCIL_VALUE_MASK); + this.view = new int[4]; + GL32.glGetIntegerv(GL32.GL_VIEWPORT, this.view); + this.cull = GL32.glIsEnabled(GL32.GL_CULL_FACE); + this.cullMode = GL32.glGetInteger(GL32.GL_CULL_FACE_MODE); + this.polyMode = GL32.glGetInteger(GL32.GL_POLYGON_MODE); + } + + @Override + public void close() + { + // explicitly unbinding the frame buffer is necessary to prevent GL_CLEAR calls from hitting the wrong buffer + GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, 0); + boolean frameBufferSet = false; + + if (this.fbo != 0 && GL32.glIsFramebuffer(this.fbo)) + { + GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, this.fbo); + frameBufferSet = true; + } + + + if (this.blend) + { + GLMC.enableBlend(); + } + else + { + GLMC.disableBlend(); + } + + if (this.scissor) + { + GLMC.enableScissorTest(); + } + else + { + GLMC.disableScissorTest(); + } + + GLMC.glActiveTexture(GL32.GL_TEXTURE0); + GLMC.glBindTexture(GL32.glIsTexture(this.texture0) ? this.texture0 : 0); + + GLMC.glActiveTexture(GL32.GL_TEXTURE1); + GLMC.glBindTexture(GL32.glIsTexture(this.texture1) ? this.texture1 : 0); + + GLMC.glActiveTexture(GL32.GL_TEXTURE2); + GLMC.glBindTexture(GL32.glIsTexture(this.texture2) ? this.texture2 : 0); + + GLMC.glActiveTexture(GL32.GL_TEXTURE3); + GLMC.glBindTexture(GL32.glIsTexture(this.texture3) ? this.texture3 : 0); + + GLMC.glActiveTexture(this.activeTextureNumber); + GLMC.glBindTexture(GL32.glIsTexture(this.texture2D) ? this.texture2D : 0); + + // attempting to set textures on the default frame buffer (ID 0) will throw errors + if (frameBufferSet) + { + GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_TEXTURE_2D, this.frameBufferTexture0, 0); + GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT1, GL32.GL_TEXTURE_2D, this.frameBufferTexture1, 0); + GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_DEPTH_ATTACHMENT, GL32.GL_TEXTURE_2D, this.frameBufferDepthTexture, 0); + } + + GL32.glBindVertexArray(GL32.glIsVertexArray(this.vao) ? this.vao : 0); + GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, GL32.glIsBuffer(this.vbo) ? this.vbo : 0); + GL32.glBindBuffer(GL32.GL_ELEMENT_ARRAY_BUFFER, GL32.glIsBuffer(this.ebo) ? this.ebo: 0); + GL32.glUseProgram(GL32.glIsProgram(this.program) ? this.program : 0); + + if (this.writeToDepthBuffer) + { + GLMC.enableDepthMask(); + } + else + { + GLMC.disableDepthMask(); + } + + GLMC.glBlendFunc(this.blendSrcColor, this.blendDstColor); + GL32.glBlendEquationSeparate(this.blendEqRGB, this.blendEqAlpha); + GLMC.glBlendFuncSeparate(this.blendSrcColor, this.blendDstColor, this.blendSrcAlpha, this.blendDstAlpha); + + if (this.depth) + { + GLMC.enableDepthTest(); + } + else + { + GLMC.disableDepthTest(); + } + GLMC.glDepthFunc(this.depthFunc); + + if (this.stencil) + { + GL32.glEnable(GL32.GL_STENCIL_TEST); + } + else + { + GL32.glDisable(GL32.GL_STENCIL_TEST); + } + GL32.glStencilFunc(this.stencilFunc, this.stencilRef, this.stencilMask); + + GL32.glViewport(this.view[0], this.view[1], this.view[2], this.view[3]); + if (this.cull) + { + GLMC.enableFaceCulling(); + } + else + { + GLMC.disableFaceCulling(); + } + GL32.glCullFace(this.cullMode); + GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, this.polyMode); + } + + @Override + public String toString() + { + return "GLState{" + + "program=" + this.program + ", vao=" + this.vao + ", vbo=" + this.vbo + ", ebo=" + this.ebo + ", fbo=" + this.fbo + + ", text=" + GLEnums.getString(this.texture2D) + "@" + this.activeTextureNumber + ", text0=" + GLEnums.getString(this.texture0) + + ", FB text0=" + this.frameBufferTexture0 + + ", FB text1=" + this.frameBufferTexture1 + + ", FB depth=" + this.frameBufferDepthTexture + + ", blend=" + this.blend + ", scissor=" + this.scissor + ", blendMode=" + GLEnums.getString(this.blendSrcColor) + "," + GLEnums.getString(this.blendDstColor) + + ", depth=" + this.depth + + ", depthFunc=" + GLEnums.getString(this.depthFunc) + ", stencil=" + this.stencil + + ", stencilFunc=" + GLEnums.getString(this.stencilFunc) + ", stencilRef=" + this.stencilRef + ", stencilMask=" + this.stencilMask + + ", view={x:" + this.view[0] + ", y:" + this.view[1] + + ", w:" + this.view[2] + ", h:" + this.view[3] + "}" + ", cull=" + this.cull + + ", cullMode=" + GLEnums.getString(this.cullMode) + ", polyMode=" + GLEnums.getString(this.polyMode) + + '}'; + } + + + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/buffer/GLBuffer.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/buffer/GLBuffer.java new file mode 100644 index 000000000..258b35bf6 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/buffer/GLBuffer.java @@ -0,0 +1,345 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl.glObject.buffer; + +import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod; +import com.seibel.distanthorizons.common.render.nativeGl.glObject.GLProxy; +import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftGLWrapper; +import com.seibel.distanthorizons.core.config.Config; +import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; +import com.seibel.distanthorizons.core.logging.DhLogger; +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler; +import com.seibel.distanthorizons.core.util.LodUtil; +import com.seibel.distanthorizons.core.util.ThreadUtil; +import org.lwjgl.opengl.GL32; +import org.lwjgl.opengl.GL44; + +import java.lang.ref.PhantomReference; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.nio.ByteBuffer; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.atomic.AtomicInteger; + +public class GLBuffer implements AutoCloseable +{ + private static final DhLogger LOGGER = new DhLoggerBuilder() + .fileLevelConfig(Config.Common.Logging.logRendererGLEventToFile) + .chatLevelConfig(Config.Common.Logging.logRendererGLEventToChat) + .build(); + + private static final MinecraftGLWrapper GLMC = MinecraftGLWrapper.INSTANCE; + + + public static final double BUFFER_EXPANSION_MULTIPLIER = 1.3; + public static final double BUFFER_SHRINK_TRIGGER = BUFFER_EXPANSION_MULTIPLIER * BUFFER_EXPANSION_MULTIPLIER; + /** the number of active buffers, can be used for debugging */ + public static AtomicInteger bufferCount = new AtomicInteger(0); + + private static final int PHANTOM_REF_CHECK_TIME_IN_MS = 5 * 1000; + private static final ConcurrentHashMap, Integer> PHANTOM_TO_BUFFER_ID = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap> BUFFER_ID_TO_PHANTOM = new ConcurrentHashMap<>(); + private static final ReferenceQueue PHANTOM_REFERENCE_QUEUE = new ReferenceQueue<>(); + private static final ThreadPoolExecutor CLEANUP_THREAD = ThreadUtil.makeSingleDaemonThreadPool("GLBuffer Cleanup"); + + + protected int id; + public final int getId() { return this.id; } + protected int size = 0; + public int getSize() { return this.size; } + protected boolean bufferStorage; + public final boolean isBufferStorage() { return this.bufferStorage; } + protected boolean isMapped = false; + + + + //==============// + // constructors // + //==============// + + static { CLEANUP_THREAD.execute(() -> runPhantomReferenceCleanupLoop()); } + + public GLBuffer(boolean isBufferStorage) { this.create(isBufferStorage); } + + + + //=========// + // methods // + //=========// + + // Should be override by subclasses + public int getBufferBindingTarget() { return GL32.GL_COPY_READ_BUFFER; } + + public void bind() { GL32.glBindBuffer(this.getBufferBindingTarget(), this.id); } + public void unbind() { GL32.glBindBuffer(this.getBufferBindingTarget(), 0); } + + + + //====================// + // create and destroy // + //====================// + + protected void create(boolean asBufferStorage) + { + if (!GLProxy.runningOnRenderThread()) + { + LodUtil.assertNotReach("Thread ["+Thread.currentThread()+"] tried to create a GLBuffer outside the MC render thread."); + } + + // destroy the old buffer if one is present + // (as of 2024-12-31 James didn't see this happen, but just in case) + if (this.id != 0) + { + destroyBufferIdAsync(this.id); + } + + this.id = GLMC.glGenBuffers(); + this.bufferStorage = asBufferStorage; + bufferCount.getAndIncrement(); + + PhantomReference phantom = new PhantomReference<>(this, PHANTOM_REFERENCE_QUEUE); + PHANTOM_TO_BUFFER_ID.put(phantom, this.id); + BUFFER_ID_TO_PHANTOM.put(this.id, phantom); + + } + + protected void destroyAsync() + { + if (this.id == 0) + { + // the buffer has already been closed + return; + } + + destroyBufferIdAsync(this.id); + + this.id = 0; + this.size = 0; + } + private static void destroyBufferIdAsync(int id) + { + // remove and clear the phantom reference if present + if (BUFFER_ID_TO_PHANTOM.containsKey(id)) + { + Reference phantom = BUFFER_ID_TO_PHANTOM.get(id); + + // if we are manually closing this buffer, we don't want the phantom reference to accidentally close it again + // this can cause a race condition were we accidentally delete an in-use buffer and cause NVIDIA + // to throw an EXCEPTION_ACCESS_VIOLATION when we attempt to render it + phantom.clear(); + + PHANTOM_TO_BUFFER_ID.remove(phantom); + BUFFER_ID_TO_PHANTOM.remove(id); + } + + RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread(() -> + { + // destroy the buffer if it exists, + // the buffer may not exist if the destroy method is called twice + if (GL32.glIsBuffer(id)) + { + GLMC.glDeleteBuffers(id); + bufferCount.decrementAndGet(); + + if (Config.Client.Advanced.Debugging.logBufferGarbageCollection.get()) + { + LOGGER.info("destroyed buffer [" + id + "], remaining: [" + BUFFER_ID_TO_PHANTOM.size() + "]"); + } + } + }); + } + + + + //==================// + // buffer uploading // + //==================// + + /** + * Assumes the GL Context is already bound.
+ * Will create the VBO if one exist. + */ + public void uploadBuffer(ByteBuffer bb, EDhApiGpuUploadMethod uploadMethod, int maxExpansionSize, int bufferHint) + { + LodUtil.assertTrue(!uploadMethod.useEarlyMapping, "UploadMethod signal that this should use Mapping instead of uploadBuffer!"); + int bbSize = bb.limit() - bb.position(); + if (bbSize > maxExpansionSize) + { + LodUtil.assertNotReach("maxExpansionSize is [" + maxExpansionSize + "] but buffer size is [" + bbSize + "]!"); + } + + // Don't upload an empty buffer + if (bbSize == 0) + { + return; + } + + // make sure the buffer is ready for uploading + this.createOrChangeBufferTypeForUpload(uploadMethod); + + switch (uploadMethod) + { + //case NONE: + // return; + case AUTO: + LodUtil.assertNotReach("GpuUploadMethod AUTO must be resolved before call to uploadBuffer()!"); + case BUFFER_STORAGE: + this.uploadBufferStorage(bb, bufferHint); + break; + case DATA: + this.uploadBufferData(bb, bufferHint); + break; + case SUB_DATA: + this.uploadSubData(bb, maxExpansionSize, bufferHint); + break; + default: + LodUtil.assertNotReach("Unknown GpuUploadMethod!"); + } + } + /** Requires the buffer to be bound */ + protected void uploadBufferStorage(ByteBuffer bb, int bufferStorageHint) + { + LodUtil.assertTrue(this.bufferStorage, "Buffer is not bufferStorage but its trying to use bufferStorage upload method!"); + + int bbSize = bb.limit() - bb.position(); + this.destroyAsync(); + this.create(true); + this.bind(); + GL44.glBufferStorage(this.getBufferBindingTarget(), bb, 0); + this.size = bbSize; + } + /** Requires the buffer to be bound */ + protected void uploadBufferData(ByteBuffer bb, int bufferDataHint) + { + LodUtil.assertTrue(!this.bufferStorage, "Buffer is bufferStorage but its trying to use bufferData upload method!"); + + int bbSize = bb.limit() - bb.position(); + GL32.glBufferData(this.getBufferBindingTarget(), bb, bufferDataHint); + this.size = bbSize; + } + /** Requires the buffer to be bound */ + protected void uploadSubData(ByteBuffer bb, int maxExpansionSize, int bufferDataHint) + { + LodUtil.assertTrue(!this.bufferStorage, "Buffer is bufferStorage but its trying to use subData upload method!"); + + int bbSize = bb.limit() - bb.position(); + if (this.size < bbSize || this.size > bbSize * BUFFER_SHRINK_TRIGGER) + { + int newSize = (int) (bbSize * BUFFER_EXPANSION_MULTIPLIER); + if (newSize > maxExpansionSize) newSize = maxExpansionSize; + GL32.glBufferData(this.getBufferBindingTarget(), newSize, bufferDataHint); + this.size = newSize; + } + GL32.glBufferSubData(this.getBufferBindingTarget(), 0, bb); + } + + + + //===========// + // overrides // + //===========// + + @Override + public void close() { this.destroyAsync(); } + + @Override + public String toString() + { + return (this.bufferStorage ? "" : "Static-") + this.getClass().getSimpleName() + + "[id:" + this.id + ",size:" + this.size + (this.isMapped ? ",MAPPED" : "") + "]"; + } + + + + //================// + // helper methods // + //================// + + /** + * Makes sure the buffer exists and is of the correct format + * before uploading. + */ + private void createOrChangeBufferTypeForUpload(EDhApiGpuUploadMethod uploadMethod) + { + // create/change the buffer type if necessary + if (uploadMethod.useBufferStorage != this.bufferStorage) + { + // recreate if the buffer storage type changed + this.bind(); + this.destroyAsync(); + this.create(uploadMethod.useBufferStorage); + this.bind(); + } + else + { + // Prevent uploading to the null buffer (ID 0). + // This can happen if the buffer was deleted previously. + if (this.id == 0) + { + this.create(this.bufferStorage); + } + + this.bind(); + } + } + + + + //================// + // static cleanup // + //================// + + private static void runPhantomReferenceCleanupLoop() + { + while (true) + { + try + { + try + { + Thread.sleep(PHANTOM_REF_CHECK_TIME_IN_MS); + } + catch (InterruptedException ignore) { } + + + Reference phantomRef = PHANTOM_REFERENCE_QUEUE.poll(); + while (phantomRef != null) + { + // destroy the buffer if it hasn't been cleared yet + if (PHANTOM_TO_BUFFER_ID.containsKey(phantomRef)) + { + int id = PHANTOM_TO_BUFFER_ID.get(phantomRef); + destroyBufferIdAsync(id); + //LOGGER.warn("Buffer Phantom collected, ID: ["+id+"]"); + } + + phantomRef = PHANTOM_REFERENCE_QUEUE.poll(); + } + } + catch (Exception e) + { + LOGGER.error("Unexpected error in buffer cleanup thread: [" + e.getMessage() + "].", e); + } + } + } + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/buffer/GLElementBuffer.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/buffer/GLElementBuffer.java new file mode 100644 index 000000000..d69818bf6 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/buffer/GLElementBuffer.java @@ -0,0 +1,60 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl.glObject.buffer; + +import org.lwjgl.opengl.GL32; + +/** + * This is a container for a OpenGL + * VBO (Vertex Buffer Object). + * + * @author James Seibel + * @version 11-20-2021 + */ +public class GLElementBuffer extends GLBuffer +{ + /** + * When uploading to a buffer that is too small, recreate it this many times + * bigger than the upload payload + */ + protected int indicesCount = 0; + public int getIndicesCount() { return this.indicesCount; } + protected int type = GL32.GL_UNSIGNED_INT; + public int getType() { return type; } + + public GLElementBuffer(boolean isBufferStorage) + { + super(isBufferStorage); + } + + @Override + public void destroyAsync() + { + super.destroyAsync(); + this.indicesCount = 0; + } + + @Override + public int getBufferBindingTarget() + { + return GL32.GL_ELEMENT_ARRAY_BUFFER; + } + +} \ No newline at end of file diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/buffer/GLVertexBuffer.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/buffer/GLVertexBuffer.java new file mode 100644 index 000000000..ad9582239 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/buffer/GLVertexBuffer.java @@ -0,0 +1,88 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl.glObject.buffer; + +import java.nio.ByteBuffer; + +import org.lwjgl.opengl.GL32; + +import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod; + +/** + * This is a container for a OpenGL + * VBO (Vertex Buffer Object). + * + * @author James Seibel + * @version 11-20-2021 + */ +public class GLVertexBuffer extends GLBuffer +{ + /** + * When uploading to a buffer that is too small, recreate it this many times + * bigger than the upload payload + */ + protected int vertexCount = 0; + public int getVertexCount() { return this.vertexCount; } + // FIXME: This setter is needed for premapping buffer to manually set the vertexCount. Fix this. + public void setVertexCount(int vertexCount) { this.vertexCount = vertexCount; } + + + public GLVertexBuffer(boolean isBufferStorage) + { + super(isBufferStorage); + } + + + + @Override + public void destroyAsync() + { + super.destroyAsync(); + this.vertexCount = 0; + } + + @Override + public int getBufferBindingTarget() { return GL32.GL_ARRAY_BUFFER; } + + /** + * bufferSize is the number of shared verticies.
+ * This number will be higher when actually rendered since each box's face needs 2 triangles + * with 2 shared verticies. + */ + public void uploadBuffer(ByteBuffer byteBuffer, int bufferSize, EDhApiGpuUploadMethod uploadMethod, int maxExpensionSize) + { + if (bufferSize < 0) + { + throw new IllegalArgumentException("VertCount is negative!"); + } + + // If size is zero, just ignore it. + if (byteBuffer.limit() - byteBuffer.position() != 0) + { + boolean useBuffStorage = uploadMethod.useBufferStorage; + super.uploadBuffer(byteBuffer, uploadMethod, maxExpensionSize, useBuffStorage ? 0 : GL32.GL_STATIC_DRAW); + } + + // /4 to get the number of cubes + // *6 to get the number of verticies (2 triangles, 3 verticies each) + this.vertexCount = (bufferSize / 4) * 6; + } + +} \ No newline at end of file diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/buffer/QuadElementBuffer.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/buffer/QuadElementBuffer.java new file mode 100644 index 000000000..953d4dae4 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/buffer/QuadElementBuffer.java @@ -0,0 +1,192 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl.glObject.buffer; + +import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod; +import com.seibel.distanthorizons.common.render.nativeGl.glObject.GLEnums; +import com.seibel.distanthorizons.core.logging.DhLogger; +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import org.lwjgl.opengl.GL32; +import org.lwjgl.system.MemoryUtil; + +import java.nio.ByteBuffer; + +/** AKA Index Buffer TODO RENAME */ +public class QuadElementBuffer extends GLElementBuffer +{ + private static final DhLogger LOGGER = new DhLoggerBuilder().build(); + + + + //=============// + // constructor // + //=============// + //region + + public QuadElementBuffer() { super(false); } + + public void reserve(int quadCount) + { + if (quadCount < 0) + { + throw new IllegalArgumentException("quadCount must be greater than 0"); + } + if (quadCount == 0) + { + // shouldn't happen, but just in case + return; + } + + this.indicesCount = quadCount * 6; // 2 triangles per quad + if (this.indicesCount >= this.getCapacity() + && this.indicesCount < this.getCapacity() * BUFFER_SHRINK_TRIGGER) + { + return; + } + int vertexCount = quadCount * 4; // 4 vertices per quad + + if (vertexCount < 255) + { + // Reserve 1 for the reset index + this.type = GL32.GL_UNSIGNED_BYTE; + } + else if (vertexCount < 65535) + { + // Reserve 1 for the reset index + this.type = GL32.GL_UNSIGNED_SHORT; + } + else + { + this.type = GL32.GL_UNSIGNED_INT; + } + + ByteBuffer buffer = MemoryUtil.memAlloc(this.indicesCount * GLEnums.getTypeSize(this.type)); + buildBuffer(quadCount, buffer, this.type); + this.bind(); + super.uploadBuffer(buffer, EDhApiGpuUploadMethod.DATA, + this.indicesCount * GLEnums.getTypeSize(this.type), GL32.GL_STATIC_DRAW); + + MemoryUtil.memFree(buffer); + } + + //endregion + + + + //=========// + // getters // + //=========// + //region + + public int getCapacity() { return super.getSize() / GLEnums.getTypeSize(this.getType()); } + + //endregion + + + + //==========// + // building // + //==========// + //region + + public static void buildBuffer(int quadCount, ByteBuffer buffer, int type) + { + switch (type) + { + case GL32.GL_UNSIGNED_BYTE: + buildBufferByte(quadCount, buffer); + break; + case GL32.GL_UNSIGNED_SHORT: + buildBufferShort(quadCount, buffer); + break; + case GL32.GL_UNSIGNED_INT: + buildBufferInt(quadCount, buffer); + break; + default: + throw new IllegalStateException("Unknown buffer type: [" + type + "]."); + } + } + + private static void buildBufferByte(int quadCount, ByteBuffer buffer) + { + for (int i = 0; i < quadCount; i++) + { + int vIndex = i * 4; + // First triangle + buffer.put((byte) (vIndex)); + buffer.put((byte) (vIndex + 1)); + buffer.put((byte) (vIndex + 2)); + // Second triangle + buffer.put((byte) (vIndex + 2)); + buffer.put((byte) (vIndex + 3)); + buffer.put((byte) (vIndex)); + } + if (buffer.hasRemaining()) + { + throw new IllegalStateException("QuadElementBuffer is not full somehow after building"); + } + buffer.rewind(); + } + private static void buildBufferShort(int quadCount, ByteBuffer buffer) + { + for (int i = 0; i < quadCount; i++) + { + int vIndex = i * 4; + // First triangle + buffer.putShort((short) (vIndex)); + buffer.putShort((short) (vIndex + 1)); + buffer.putShort((short) (vIndex + 2)); + // Second triangle + buffer.putShort((short) (vIndex + 2)); + buffer.putShort((short) (vIndex + 3)); + buffer.putShort((short) (vIndex)); + } + if (buffer.hasRemaining()) + { + throw new IllegalStateException("QuadElementBuffer is not full somehow after building"); + } + buffer.rewind(); + } + private static void buildBufferInt(int quadCount, ByteBuffer buffer) + { + for (int i = 0; i < quadCount; i++) + { + int vIndex = i * 4; + // First triangle + buffer.putInt(vIndex); + buffer.putInt(vIndex + 1); + buffer.putInt(vIndex + 2); + // Second triangle + buffer.putInt(vIndex + 2); + buffer.putInt(vIndex + 3); + buffer.putInt(vIndex); + } + if (buffer.hasRemaining()) + { + throw new IllegalStateException("QuadElementBuffer is not full somehow after building"); + } + buffer.rewind(); + } + + //endregion + + + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/shader/Shader.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/shader/Shader.java new file mode 100644 index 000000000..cd2174386 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/shader/Shader.java @@ -0,0 +1,184 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl.glObject.shader; + +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.ByteBuffer; + +import com.seibel.distanthorizons.core.config.Config; +import com.seibel.distanthorizons.core.logging.DhLogger; +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import org.lwjgl.PointerBuffer; +import org.lwjgl.opengl.GL32; +import org.lwjgl.opengl.GL32C; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.MemoryUtil; +import org.lwjgl.system.NativeType; + +/** + * This object holds a OpenGL reference to a shader + * and allows for reading in and compiling a shader file. + */ +public class Shader +{ + private static final DhLogger LOGGER = new DhLoggerBuilder() + .fileLevelConfig(Config.Common.Logging.logRendererGLEventToFile) + .chatLevelConfig(Config.Common.Logging.logRendererGLEventToChat) + .build(); + + + /** OpenGL shader ID */ + public final int id; + + + + //==============// + // constructors // + //==============// + //region + + /** + * Creates a shader with specified type. + * + * @param type Either GL_VERTEX_SHADER or GL_FRAGMENT_SHADER. + * @param sourceString File path of the shader + * @throws RuntimeException if the shader fails to compile + */ + public Shader(int type, String sourceString) + { + LOGGER.info("Loading shader with type: ["+type+"]"); + LOGGER.debug("Source: \n["+sourceString+"]"); + if (sourceString == null || sourceString.isEmpty()) + { + throw new IllegalArgumentException("No shader source given."); + } + + // Create an empty shader object + this.id = GL32.glCreateShader(type); + if (this.id == 0) + { + throw new IllegalArgumentException("Failed to create shader with type ["+type+"] and Source: \n["+sourceString+"]."); + } + + safeShaderSource(this.id, sourceString); + GL32.glCompileShader(this.id); + // check if the shader compiled + int status = GL32.glGetShaderi(this.id, GL32.GL_COMPILE_STATUS); + if (status != GL32.GL_TRUE) + { + + String message = "Shader compiler error. Details: [" + GL32.glGetShaderInfoLog(this.id) + "]\n"; + message += "Source: \n[" + sourceString + "]"; + this.free(); // important! + throw new RuntimeException(message); + } + LOGGER.info("Shader loaded sucessfully."); + } + + //endregion + + + + //=========// + // helpers // + //=========// + //region + + /** + * Identical in function to {@link GL32C#glShaderSource(int, CharSequence)} but + * passes a null pointer for string length to force the driver to rely on the null + * terminator for string length. This is a workaround for an apparent flaw with some + * AMD drivers that don't receive or interpret the length correctly, resulting in + * an access violation when the driver tries to read past the string memory. + * + *

Hat tip to fewizz for the find and the fix. + * + *

Source: https://github.com/vram-guild/canvas/commit/820bf754092ccaf8d0c169620c2ff575722d7d96 + */ + private static void safeShaderSource(@NativeType("GLuint") int glId, @NativeType("GLchar const **") CharSequence source) + { + final MemoryStack stack = MemoryStack.stackGet(); + final int stackPointer = stack.getPointer(); + + try + { + final ByteBuffer sourceBuffer = MemoryUtil.memUTF8(source, true); + final PointerBuffer pointers = stack.mallocPointer(1); + pointers.put(sourceBuffer); + + GL32.nglShaderSource(glId, 1, pointers.address0(), 0); + org.lwjgl.system.APIUtil.apiArrayFree(pointers.address0(), 1); + } + finally + { + stack.setPointer(stackPointer); + } + } + + public void free() { GL32.glDeleteShader(this.id); } + + public static String loadFile(String path, boolean absoluteFilePath) + { + StringBuilder stringBuilder = new StringBuilder(); + + try + { + // open the file + InputStream in; + if (absoluteFilePath) + { + // Throws FileNotFoundException + in = new FileInputStream(path); // Note: this should use OS path seperator + } + else + { + in = Shader.class.getClassLoader().getResourceAsStream(path); // Note: path seperator should be '/' + if (in == null) + { + throw new FileNotFoundException("Shader file not found in resource: " + path); + } + } + BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + + // read in the file + String line; + while ((line = reader.readLine()) != null) + { + stringBuilder.append(line).append("\n"); + } + } + catch (IOException e) + { + throw new RuntimeException("Unable to load shader from file [" + path + "]. Error: " + e.getMessage()); + } + + return stringBuilder.toString(); + } + + //endregion + + + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/shader/ShaderProgram.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/shader/ShaderProgram.java new file mode 100644 index 000000000..97ef510f4 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/shader/ShaderProgram.java @@ -0,0 +1,225 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl.glObject.shader; + +import java.awt.Color; +import java.nio.FloatBuffer; + +import com.seibel.distanthorizons.api.objects.math.DhApiVec3i; +import org.lwjgl.opengl.GL32; +import org.lwjgl.system.MemoryStack; + +import com.seibel.distanthorizons.core.util.math.Mat4f; +import com.seibel.distanthorizons.core.util.math.Vec3f; + + +/** + * This object holds the reference to a OpenGL shader program + * and contains a few methods that can be used with OpenGL shader programs. + * The reason for many of these simple wrapper methods is as reminders of what + * can (and needs to be) done with a shader program. + */ +public class ShaderProgram +{ + /** Stores the handle of the program. */ + public final int id; + + + + //=============// + // constructor // + //=============// + //region + + public ShaderProgram(String vertResourcePath, String fragResourcePath, String attribute) { this(vertResourcePath, fragResourcePath, new String[]{ attribute }); } + /** + * @param vertResourcePath the relative path the vertex shader should be found + * @param fragResourcePath the relative path the fragment shader should be found + */ + public ShaderProgram(String vertResourcePath, String fragResourcePath, String[] attributes) + { + this.id = GL32.glCreateProgram(); + + { + String shaderString = Shader.loadFile(vertResourcePath, false); + Shader vertShader = new Shader(GL32.GL_VERTEX_SHADER, shaderString); + GL32.glAttachShader(this.id, vertShader.id); + vertShader.free(); + } + + { + String shaderString = Shader.loadFile(fragResourcePath, false); + Shader fragShader = new Shader(GL32.GL_FRAGMENT_SHADER, shaderString); + GL32.glAttachShader(this.id, fragShader.id); + fragShader.free(); + } + + for (int i = 0; i < attributes.length; i++) + { + GL32.glBindAttribLocation(this.id, i, attributes[i]); + } + GL32.glLinkProgram(this.id); + + int status = GL32.glGetProgrami(this.id, GL32.GL_LINK_STATUS); + if (status != GL32.GL_TRUE) + { + String message = "Shader Link Error. Details: " + GL32.glGetProgramInfoLog(this.id); + this.free(); // important! + throw new RuntimeException(message); + } + GL32.glUseProgram(this.id); // This HAVE to be a direct call to prevent calling the overloaded version + } + + //endregion + + + + //=========// + // binding // + //=========// + //region + + public void bind() { GL32.glUseProgram(this.id); } + public void unbind() { GL32.glUseProgram(0); } + + public void free() { GL32.glDeleteProgram(this.id); } + + //endregion + + + + //============// + // attributes // + //============// + //region + + /** + * WARNING: Slow native call! Cache it if possible! + * Gets the location of an attribute variable with specified name. + * Calls GL20.glGetAttribLocation(id, name) + * + * @param name Attribute name + * @return Location of the attribute + * @throws RuntimeException if attribute not found + */ + public int getAttributeLocation(CharSequence name) + { + int i = GL32.glGetAttribLocation(id, name); + if (i == -1) throw new RuntimeException("Attribute name not found: " + name); + return i; + } + /** + * Same as above but without throwing errors.
+ * Returns -1 if the attribute doesn't exist or has been optimized out. + */ + public int tryGetAttributeLocation(CharSequence name) + { return GL32.glGetAttribLocation(this.id, name); } + + //endregion + + + + //==========// + // uniforms // + //==========// + //region + + /** + * WARNING: Slow native call! Cache it if possible! + * Gets the location of a uniform variable with specified name. + * Calls GL20.glGetUniformLocation(id, name) + * + * @param name Uniform name + * @return Location of the Uniform + * @throws RuntimeException if uniform not found + */ + public int getUniformLocation(CharSequence name) throws RuntimeException + { + int i = GL32.glGetUniformLocation(id, name); + if (i == -1) + { + throw new RuntimeException("Uniform name not found: " + name); + } + return i; + } + + // Same as above but without throwing errors. + // Return -1 if uniform doesn't exist or has been optimized out + public int tryGetUniformLocation(CharSequence name) + { return GL32.glGetUniformLocation(this.id, name); } + + /** Requires a bound ShaderProgram. */ + public void setUniform(int location, boolean value) { GL32.glUniform1i(location, value ? 1 : 0); } + /** @see ShaderProgram#setUniform(int, boolean) */ + public void trySetUniform(int location, boolean value) { if (location != -1) { this.setUniform(location, value); } } + + /** Requires a bound ShaderProgram. */ + public void setUniform(int location, int value) { GL32.glUniform1i(location, value); } + /** @see ShaderProgram#setUniform(int, int) */ + public void trySetUniform(int location, int value) { if (location != -1) { this.setUniform(location, value); } } + + /** Requires a bound ShaderProgram. */ + public void setUniform(int location, float value) { GL32.glUniform1f(location, value); } + /** @see ShaderProgram#setUniform(int, float) */ + public void trySetUniform(int location, float value) { if (location != -1) { this.setUniform(location, value); } } + + /** Requires a bound ShaderProgram. */ + public void setUniform(int location, Vec3f value) { GL32.glUniform3f(location, value.x, value.y, value.z); } + /** @see ShaderProgram#setUniform(int, Vec3f) */ + public void trySetUniform(int location, Vec3f value) { if (location != -1) { this.setUniform(location, value); } } + + /** Requires a bound ShaderProgram. */ + public void setUniform(int location, DhApiVec3i value) { GL32.glUniform3i(location, value.x, value.y, value.z); } + /** @see ShaderProgram#setUniform(int, Mat4f) */ + public void trySetUniform(int location, DhApiVec3i value) { if (location != -1) { this.setUniform(location, value); } } + + /** Requires a bound ShaderProgram. */ + public void setUniform(int location, Mat4f value) + { + try (MemoryStack stack = MemoryStack.stackPush()) + { + FloatBuffer buffer = stack.mallocFloat(4 * 4); + value.store(buffer); + GL32.glUniformMatrix4fv(location, false, buffer); + } + } + /** @see ShaderProgram#setUniform(int, Mat4f) */ + public void trySetUniform(int location, Mat4f value) { if (location != -1) { this.setUniform(location, value); } } + + /** + * Converts the color's RGBA values into values between 0 and 1.
+ * Requires a bound ShaderProgram. + */ + public void setUniform(int location, Color value) + { + GL32.glUniform4f(location, + value.getRed() / 256.0f, + value.getGreen() / 256.0f, + value.getBlue() / 256.0f, + value.getAlpha() / 256.0f); + } + /** @see ShaderProgram#setUniform(int, Color) */ + public void trySetUniform(int location, Color value) { if (location != -1) { this.setUniform(location, value); } } + + //endregion + + + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/texture/DHDepthTexture.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/texture/DHDepthTexture.java new file mode 100644 index 000000000..f687f7941 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/texture/DHDepthTexture.java @@ -0,0 +1,62 @@ +package com.seibel.distanthorizons.common.render.nativeGl.glObject.texture; + +import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftGLWrapper; +import org.lwjgl.opengl.GL11C; +import org.lwjgl.opengl.GL13C; +import org.lwjgl.opengl.GL43C; + +import java.nio.ByteBuffer; + +public class DHDepthTexture +{ + private static final MinecraftGLWrapper GLMC = MinecraftGLWrapper.INSTANCE; + + + private int id; + public DHDepthTexture(int width, int height, EDhDepthBufferFormat format) + { + this.id = GL43C.glGenTextures(); + + this.resize(width, height, format); + + GL43C.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_MIN_FILTER, GL11C.GL_NEAREST); + GL43C.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_MAG_FILTER, GL11C.GL_NEAREST); + GL43C.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_WRAP_S, GL13C.GL_CLAMP_TO_EDGE); + GL43C.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_WRAP_T, GL13C.GL_CLAMP_TO_EDGE); + + // disable mip-mapping since DH is just going to draw straight to the screen + GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_BASE_LEVEL, 0); + GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_MAX_LEVEL, 0); + + GL43C.glBindTexture(GL43C.GL_TEXTURE_2D, 0); + } + + // For internal use by Iris for copying data. Do not use this in DH. + public DHDepthTexture(int id) { this.id = id; } + + public void resize(int width, int height, EDhDepthBufferFormat format) + { + GL43C.glBindTexture(GL43C.GL_TEXTURE_2D, this.getTextureId()); + GL43C.glTexImage2D(GL11C.GL_TEXTURE_2D, 0, format.getGlInternalFormat(), width, height, 0, + format.getGlType(), format.getGlFormat(), (ByteBuffer) null); + } + + public int getTextureId() + { + if (this.id == -1) + { + throw new IllegalStateException("Depth texture does not exist!"); + } + + return this.id; + } + + public void destroy() + { + GLMC.glDeleteTextures(this.getTextureId()); + this.id = -1; + } + + + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/texture/DhColorTexture.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/texture/DhColorTexture.java new file mode 100644 index 000000000..4b4a8cf1b --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/texture/DhColorTexture.java @@ -0,0 +1,183 @@ +package com.seibel.distanthorizons.common.render.nativeGl.glObject.texture; + +import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftGLWrapper; +import org.joml.Vector2i; +import org.lwjgl.opengl.GL11C; +import org.lwjgl.opengl.GL13C; +import org.lwjgl.opengl.GL43C; + +import java.nio.ByteBuffer; + +public class DhColorTexture +{ + private static final MinecraftGLWrapper GLMC = MinecraftGLWrapper.INSTANCE; + + + private final EDhInternalTextureFormat internalFormat; + private final EDhPixelFormat format; + private final EDhPixelType type; + private int width; + private int height; + + private boolean isValid; + /** AKA, the OpenGL name of this texture */ + private final int id; + + private static final ByteBuffer NULL_BUFFER = null; + + + + //=============// + // constructor // + //=============// + + public DhColorTexture(Builder builder) + { + this.isValid = true; + + this.internalFormat = builder.internalFormat; + this.format = builder.format; + this.type = builder.type; + + this.width = builder.width; + this.height = builder.height; + + this.id = GL43C.glGenTextures(); + + boolean isPixelFormatInteger = builder.internalFormat.getPixelFormat().isInteger(); + this.setupTexture(this.id, builder.width, builder.height, !isPixelFormatInteger); // this binds the texture + + // Clean up after ourselves + // This is strictly defensive to ensure that other buggy code doesn't tamper with our textures + GL43C.glBindTexture(GL43C.GL_TEXTURE_2D, 0); + } + + + + //=========// + // methods // + //=========// + + private void setupTexture(int id, int width, int height, boolean allowsLinear) + { + this.resizeTexture(id, width, height); + + GL43C.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_MIN_FILTER, allowsLinear ? GL11C.GL_LINEAR : GL11C.GL_NEAREST); + GL43C.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_MAG_FILTER, allowsLinear ? GL11C.GL_LINEAR : GL11C.GL_NEAREST); + GL43C.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_WRAP_S, GL13C.GL_CLAMP_TO_EDGE); + GL43C.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_WRAP_T, GL13C.GL_CLAMP_TO_EDGE); + + // disable mip-mapping since DH is just going to draw straight to the screen + GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_BASE_LEVEL, 0); + GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_MAX_LEVEL, 0); + } + + private void resizeTexture(int texture, int width, int height) + { + GL43C.glBindTexture(GL43C.GL_TEXTURE_2D, texture); + GL43C.glTexImage2D(GL11C.GL_TEXTURE_2D, 0, this.internalFormat.getGlFormat(), width, height, 0, this.format.getGlFormat(), this.type.getGlFormat(), NULL_BUFFER); + } + + void resize(Vector2i textureScaleOverride) { this.resize(textureScaleOverride.x, textureScaleOverride.y); } + + // Package private, call CompositeRenderTargets#resizeIfNeeded instead. + public void resize(int width, int height) + { + this.throwIfInvalid(); + + this.width = width; + this.height = height; + + this.resizeTexture(this.id, width, height); + } + + public EDhInternalTextureFormat getInternalFormat() { return this.internalFormat; } + + public int getTextureId() + { + this.throwIfInvalid(); + return this.id; + } + + public int getWidth() { return this.width; } + + public int getHeight() { return this.height; } + + public void destroy() + { + this.throwIfInvalid(); + this.isValid = false; + + GLMC.glDeleteTextures(this.id); + } + + /** @throws IllegalStateException if the texture isn't valid */ + private void throwIfInvalid() + { + if (!this.isValid) + { + throw new IllegalStateException("Attempted to use a deleted composite render target"); + } + } + + public static Builder builder() { return new Builder(); } + + + + //================// + // helper classes // + //================// + + public static class Builder + { + private EDhInternalTextureFormat internalFormat = EDhInternalTextureFormat.RGBA8; + private int width = 0; + private int height = 0; + private EDhPixelFormat format = EDhPixelFormat.RGBA; + private EDhPixelType type = EDhPixelType.UNSIGNED_BYTE; + + private Builder() + { + // No-op + } + + public Builder setInternalFormat(EDhInternalTextureFormat format) + { + this.internalFormat = format; + return this; + } + + public Builder setDimensions(int width, int height) + { + if (width <= 0) + { + throw new IllegalArgumentException("Width must be greater than zero"); + } + + if (height <= 0) + { + throw new IllegalArgumentException("Height must be greater than zero"); + } + + this.width = width; + this.height = height; + + return this; + } + + public Builder setPixelFormat(EDhPixelFormat pixelFormat) + { + this.format = pixelFormat; + return this; + } + + public Builder setPixelType(EDhPixelType pixelType) + { + this.type = pixelType; + return this; + } + + public DhColorTexture build() { return new DhColorTexture(this); } + + } +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/texture/DhFramebuffer.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/texture/DhFramebuffer.java new file mode 100644 index 000000000..2c6bc961b --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/texture/DhFramebuffer.java @@ -0,0 +1,152 @@ +package com.seibel.distanthorizons.common.render.nativeGl.glObject.texture; + +import com.seibel.distanthorizons.api.interfaces.override.rendering.IDhApiFramebuffer; +import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftGLWrapper; +import it.unimi.dsi.fastutil.ints.Int2IntArrayMap; +import it.unimi.dsi.fastutil.ints.Int2IntMap; +import org.lwjgl.opengl.GL32; + +public class DhFramebuffer implements IDhApiFramebuffer +{ + private static final MinecraftGLWrapper GLMC = MinecraftGLWrapper.INSTANCE; + + private final Int2IntMap attachments; + private final int maxDrawBuffers; + private final int maxColorAttachments; + private boolean hasDepthAttachment; + private int id; + + + + //=============// + // constructor // + //=============// + + public DhFramebuffer() + { + this.id = GL32.glGenFramebuffers(); + + this.attachments = new Int2IntArrayMap(); + this.maxDrawBuffers = GL32.glGetInteger(GL32.GL_MAX_DRAW_BUFFERS); + this.maxColorAttachments = GL32.glGetInteger(GL32.GL_MAX_COLOR_ATTACHMENTS); + this.hasDepthAttachment = false; + } + + /** For internal use by Iris, do not remove. */ + public DhFramebuffer(int id) + { + this.id = id; + + this.attachments = new Int2IntArrayMap(); + this.maxDrawBuffers = GL32.glGetInteger(GL32.GL_MAX_DRAW_BUFFERS); + this.maxColorAttachments = GL32.glGetInteger(GL32.GL_MAX_COLOR_ATTACHMENTS); + this.hasDepthAttachment = false; + } + + + + //=========// + // methods // + //=========// + + @Override + public void addDepthAttachment(int textureId, boolean isCombinedStencil) + { + this.bind(); + + int depthAttachment = isCombinedStencil ? GL32.GL_DEPTH_STENCIL_ATTACHMENT : GL32.GL_DEPTH_ATTACHMENT; + GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, depthAttachment, GL32.GL_TEXTURE_2D, textureId, 0); + + this.hasDepthAttachment = true; + } + + @Override + public void addColorAttachment(int textureIndex, int textureId) + { + this.bind(); + + GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0 + textureIndex, GL32.GL_TEXTURE_2D, textureId, 0); + this.attachments.put(textureIndex, textureId); + } + + public void noDrawBuffers() + { + this.bind(); + GL32.glDrawBuffers(new int[]{GL32.GL_NONE}); + } + + public void drawBuffers(int[] buffers) + { + int[] glBuffers = new int[buffers.length]; + int index = 0; + + if (buffers.length > this.maxDrawBuffers) + { + throw new IllegalArgumentException("Cannot write to more than " + this.maxDrawBuffers + " draw buffers on this GPU"); + } + + for (int buffer : buffers) + { + if (buffer >= this.maxColorAttachments) + { + throw new IllegalArgumentException("Only " + this.maxColorAttachments + " color attachments are supported on this GPU, but an attempt was made to write to a color attachment with index " + buffer); + } + + glBuffers[index++] = GL32.GL_COLOR_ATTACHMENT0 + buffer; + } + + this.bind(); + GL32.glDrawBuffers(new int[]{GL32.GL_NONE}); + } + + public void readBuffer(int buffer) + { + this.bind(); + GL32.glReadBuffer(GL32.GL_COLOR_ATTACHMENT0 + buffer); + } + + public int getColorAttachment(int index) { return this.attachments.get(index); } + + public boolean hasDepthAttachment() { return this.hasDepthAttachment; } + + @Override + public void bind() + { + if (this.id == -1) + { + throw new IllegalStateException("Framebuffer does not exist!"); + } + GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, this.id); + } + + public void bindAsReadBuffer() { GLMC.glBindFramebuffer(GL32.GL_READ_FRAMEBUFFER, this.id); } + + public void bindAsDrawBuffer() { GLMC.glBindFramebuffer(GL32.GL_DRAW_FRAMEBUFFER, this.id); } + + @Override + public void destroy() + { + GL32.glDeleteFramebuffers(this.id); + this.id = -1; + } + + @Override + public int getStatus() + { + this.bind(); + int status = GL32.glCheckFramebufferStatus(GL32.GL_FRAMEBUFFER); + return status; + } + + @Override + public int getId() { return this.id; } + + + + //=============// + // API methods // + //=============// + + public boolean overrideThisFrame() { return true; } + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/texture/EDhDepthBufferFormat.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/texture/EDhDepthBufferFormat.java new file mode 100644 index 000000000..734f4bb25 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/texture/EDhDepthBufferFormat.java @@ -0,0 +1,114 @@ +package com.seibel.distanthorizons.common.render.nativeGl.glObject.texture; + +import org.jetbrains.annotations.Nullable; +import org.lwjgl.opengl.GL30C; +import org.lwjgl.opengl.GL43C; + +public enum EDhDepthBufferFormat +{ + DEPTH(false), + DEPTH16(false), + DEPTH24(false), + DEPTH32(false), + DEPTH32F(false), + DEPTH_STENCIL(true), + DEPTH24_STENCIL8(true), + DEPTH32F_STENCIL8(true); + + + + private final boolean combinedStencil; + + EDhDepthBufferFormat(boolean combinedStencil) { this.combinedStencil = combinedStencil; } + + + + @Nullable + public static EDhDepthBufferFormat fromGlEnum(int glenum) + { + switch (glenum) + { + case GL30C.GL_DEPTH_COMPONENT: + return EDhDepthBufferFormat.DEPTH; + case GL30C.GL_DEPTH_COMPONENT16: + return EDhDepthBufferFormat.DEPTH16; + case GL30C.GL_DEPTH_COMPONENT24: + return EDhDepthBufferFormat.DEPTH24; + case GL30C.GL_DEPTH_COMPONENT32: + return EDhDepthBufferFormat.DEPTH32; + case GL30C.GL_DEPTH_COMPONENT32F: + return EDhDepthBufferFormat.DEPTH32F; + case GL30C.GL_DEPTH_STENCIL: + return EDhDepthBufferFormat.DEPTH_STENCIL; + case GL30C.GL_DEPTH24_STENCIL8: + return EDhDepthBufferFormat.DEPTH24_STENCIL8; + case GL30C.GL_DEPTH32F_STENCIL8: + return EDhDepthBufferFormat.DEPTH32F_STENCIL8; + default: + return null; + } + } + + public static EDhDepthBufferFormat fromGlEnumOrDefault(int glenum) + { + EDhDepthBufferFormat format = fromGlEnum(glenum); + if (format == null) + { + // yolo, just assume it's GL_DEPTH_COMPONENT + return EDhDepthBufferFormat.DEPTH; + } + return format; + } + + public int getGlInternalFormat() + { + switch (this) + { + case DEPTH: + return GL30C.GL_DEPTH_COMPONENT; + case DEPTH16: + return GL30C.GL_DEPTH_COMPONENT16; + case DEPTH24: + return GL30C.GL_DEPTH_COMPONENT24; + case DEPTH32: + return GL30C.GL_DEPTH_COMPONENT32; + case DEPTH32F: + return GL30C.GL_DEPTH_COMPONENT32F; + case DEPTH_STENCIL: + return GL30C.GL_DEPTH_STENCIL; + case DEPTH24_STENCIL8: + return GL30C.GL_DEPTH24_STENCIL8; + case DEPTH32F_STENCIL8: + return GL30C.GL_DEPTH32F_STENCIL8; + } + + throw new AssertionError("unreachable"); + } + + public int getGlType() { return isCombinedStencil() ? GL30C.GL_DEPTH_STENCIL : GL30C.GL_DEPTH_COMPONENT; } + + public int getGlFormat() + { + switch (this) + { + case DEPTH: + case DEPTH16: + return GL43C.GL_UNSIGNED_SHORT; + case DEPTH24: + case DEPTH32: + return GL43C.GL_UNSIGNED_INT; + case DEPTH32F: + return GL30C.GL_FLOAT; + case DEPTH_STENCIL: + case DEPTH24_STENCIL8: + return GL30C.GL_UNSIGNED_INT_24_8; + case DEPTH32F_STENCIL8: + return GL30C.GL_FLOAT_32_UNSIGNED_INT_24_8_REV; + } + + throw new AssertionError("unreachable"); + } + + public boolean isCombinedStencil() { return combinedStencil; } + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/texture/EDhInternalTextureFormat.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/texture/EDhInternalTextureFormat.java new file mode 100644 index 000000000..a9a4851fc --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/texture/EDhInternalTextureFormat.java @@ -0,0 +1,130 @@ +package com.seibel.distanthorizons.common.render.nativeGl.glObject.texture; + +import org.lwjgl.opengl.GL11C; +import org.lwjgl.opengl.GL30C; +import org.lwjgl.opengl.GL31C; + +import java.util.Locale; +import java.util.Optional; + +public enum EDhInternalTextureFormat +{ + RGBA(GL11C.GL_RGBA, EGlVersion.GL_11, EDhPixelFormat.RGBA), + + // 8-bit normalized + R8(GL30C.GL_R8, EGlVersion.GL_30, EDhPixelFormat.RED), + RG8(GL30C.GL_RG8, EGlVersion.GL_30, EDhPixelFormat.RG), + RGB8(GL11C.GL_RGB8, EGlVersion.GL_11, EDhPixelFormat.RGB), + RGBA8(GL11C.GL_RGBA8, EGlVersion.GL_11, EDhPixelFormat.RGBA), + + // 8-bit signed normalized + R8_SNORM(GL31C.GL_R8_SNORM, EGlVersion.GL_31, EDhPixelFormat.RED), + RG8_SNORM(GL31C.GL_RG8_SNORM, EGlVersion.GL_31, EDhPixelFormat.RG), + RGB8_SNORM(GL31C.GL_RGB8_SNORM, EGlVersion.GL_31, EDhPixelFormat.RGB), + RGBA8_SNORM(GL31C.GL_RGBA8_SNORM, EGlVersion.GL_31, EDhPixelFormat.RGBA), + + // 16-bit normalized + R16(GL30C.GL_R16, EGlVersion.GL_30, EDhPixelFormat.RED), + RG16(GL30C.GL_RG16, EGlVersion.GL_30, EDhPixelFormat.RG), + RGB16(GL11C.GL_RGB16, EGlVersion.GL_11, EDhPixelFormat.RGB), + RGBA16(GL11C.GL_RGBA16, EGlVersion.GL_11, EDhPixelFormat.RGBA), + + // 16-bit signed normalized + R16_SNORM(GL31C.GL_R16_SNORM, EGlVersion.GL_31, EDhPixelFormat.RED), + RG16_SNORM(GL31C.GL_RG16_SNORM, EGlVersion.GL_31, EDhPixelFormat.RG), + RGB16_SNORM(GL31C.GL_RGB16_SNORM, EGlVersion.GL_31, EDhPixelFormat.RGB), + RGBA16_SNORM(GL31C.GL_RGBA16_SNORM, EGlVersion.GL_31, EDhPixelFormat.RGBA), + + // 16-bit float + R16F(GL30C.GL_R16F, EGlVersion.GL_30, EDhPixelFormat.RED), + RG16F(GL30C.GL_RG16F, EGlVersion.GL_30, EDhPixelFormat.RG), + RGB16F(GL30C.GL_RGB16F, EGlVersion.GL_30, EDhPixelFormat.RGB), + RGBA16F(GL30C.GL_RGBA16F, EGlVersion.GL_30, EDhPixelFormat.RGBA), + + // 32-bit float + R32F(GL30C.GL_R32F, EGlVersion.GL_30, EDhPixelFormat.RED), + RG32F(GL30C.GL_RG32F, EGlVersion.GL_30, EDhPixelFormat.RG), + RGB32F(GL30C.GL_RGB32F, EGlVersion.GL_30, EDhPixelFormat.RGB), + RGBA32F(GL30C.GL_RGBA32F, EGlVersion.GL_30, EDhPixelFormat.RGBA), + + // 8-bit integer + R8I(GL30C.GL_R8I, EGlVersion.GL_30, EDhPixelFormat.RED_INTEGER), + RG8I(GL30C.GL_RG8I, EGlVersion.GL_30, EDhPixelFormat.RG_INTEGER), + RGB8I(GL30C.GL_RGB8I, EGlVersion.GL_30, EDhPixelFormat.RGB_INTEGER), + RGBA8I(GL30C.GL_RGBA8I, EGlVersion.GL_30, EDhPixelFormat.RGBA_INTEGER), + + // 8-bit unsigned integer + R8UI(GL30C.GL_R8UI, EGlVersion.GL_30, EDhPixelFormat.RED_INTEGER), + RG8UI(GL30C.GL_RG8UI, EGlVersion.GL_30, EDhPixelFormat.RG_INTEGER), + RGB8UI(GL30C.GL_RGB8UI, EGlVersion.GL_30, EDhPixelFormat.RGB_INTEGER), + RGBA8UI(GL30C.GL_RGBA8UI, EGlVersion.GL_30, EDhPixelFormat.RGBA_INTEGER), + + // 16-bit integer + R16I(GL30C.GL_R16I, EGlVersion.GL_30, EDhPixelFormat.RED_INTEGER), + RG16I(GL30C.GL_RG16I, EGlVersion.GL_30, EDhPixelFormat.RG_INTEGER), + RGB16I(GL30C.GL_RGB16I, EGlVersion.GL_30, EDhPixelFormat.RGB_INTEGER), + RGBA16I(GL30C.GL_RGBA16I, EGlVersion.GL_30, EDhPixelFormat.RGBA_INTEGER), + + // 16-bit unsigned integer + R16UI(GL30C.GL_R16UI, EGlVersion.GL_30, EDhPixelFormat.RED_INTEGER), + RG16UI(GL30C.GL_RG16UI, EGlVersion.GL_30, EDhPixelFormat.RG_INTEGER), + RGB16UI(GL30C.GL_RGB16UI, EGlVersion.GL_30, EDhPixelFormat.RGB_INTEGER), + RGBA16UI(GL30C.GL_RGBA16UI, EGlVersion.GL_30, EDhPixelFormat.RGBA_INTEGER), + + // 32-bit integer + R32I(GL30C.GL_R32I, EGlVersion.GL_30, EDhPixelFormat.RED_INTEGER), + RG32I(GL30C.GL_RG32I, EGlVersion.GL_30, EDhPixelFormat.RG_INTEGER), + RGB32I(GL30C.GL_RGB32I, EGlVersion.GL_30, EDhPixelFormat.RGB_INTEGER), + RGBA32I(GL30C.GL_RGBA32I, EGlVersion.GL_30, EDhPixelFormat.RGBA_INTEGER), + + // 32-bit unsigned integer + R32UI(GL30C.GL_R32UI, EGlVersion.GL_30, EDhPixelFormat.RED_INTEGER), + RG32UI(GL30C.GL_RG32UI, EGlVersion.GL_30, EDhPixelFormat.RG_INTEGER), + RGB32UI(GL30C.GL_RGB32UI, EGlVersion.GL_30, EDhPixelFormat.RGB_INTEGER), + RGBA32UI(GL30C.GL_RGBA32UI, EGlVersion.GL_30, EDhPixelFormat.RGBA_INTEGER), + + // Mixed + R3_G3_B2(GL11C.GL_R3_G3_B2, EGlVersion.GL_11, EDhPixelFormat.RGB), + RGB5_A1(GL11C.GL_RGB5_A1, EGlVersion.GL_11, EDhPixelFormat.RGBA), + RGB10_A2(GL11C.GL_RGB10_A2, EGlVersion.GL_11, EDhPixelFormat.RGBA), + R11F_G11F_B10F(GL30C.GL_R11F_G11F_B10F, EGlVersion.GL_30, EDhPixelFormat.RGB), + RGB9_E5(GL30C.GL_RGB9_E5, EGlVersion.GL_30, EDhPixelFormat.RGB); + + + + private final int glFormat; + private final EGlVersion minimumGlVersion; + private final EDhPixelFormat expectedPixelFormat; + + + + EDhInternalTextureFormat(int glFormat, EGlVersion minimumGlVersion, EDhPixelFormat expectedPixelFormat) + { + this.glFormat = glFormat; + this.minimumGlVersion = minimumGlVersion; + this.expectedPixelFormat = expectedPixelFormat; + } + + + + public static Optional fromString(String name) + { + try + { + return Optional.of(EDhInternalTextureFormat.valueOf(name.toUpperCase(Locale.US))); + } + catch (IllegalArgumentException e) + { + return Optional.empty(); + } + } + + public int getGlFormat() { return this.glFormat; } + + public EDhPixelFormat getPixelFormat() { return this.expectedPixelFormat; } + + public EGlVersion getMinimumGlVersion() { return this.minimumGlVersion; } + + + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/texture/EDhPixelFormat.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/texture/EDhPixelFormat.java new file mode 100644 index 000000000..8427e62ea --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/texture/EDhPixelFormat.java @@ -0,0 +1,60 @@ +package com.seibel.distanthorizons.common.render.nativeGl.glObject.texture; + +import org.lwjgl.opengl.GL11C; +import org.lwjgl.opengl.GL12C; +import org.lwjgl.opengl.GL30C; + +import java.util.Locale; +import java.util.Optional; + +public enum EDhPixelFormat +{ + RED(GL11C.GL_RED, EGlVersion.GL_11, false), + RG(GL30C.GL_RG, EGlVersion.GL_30, false), + RGB(GL11C.GL_RGB, EGlVersion.GL_11, false), + BGR(GL12C.GL_BGR, EGlVersion.GL_12, false), + RGBA(GL11C.GL_RGBA, EGlVersion.GL_11, false), + BGRA(GL12C.GL_BGRA, EGlVersion.GL_12, false), + RED_INTEGER(GL30C.GL_RED_INTEGER, EGlVersion.GL_30, true), + RG_INTEGER(GL30C.GL_RG_INTEGER, EGlVersion.GL_30, true), + RGB_INTEGER(GL30C.GL_RGB_INTEGER, EGlVersion.GL_30, true), + BGR_INTEGER(GL30C.GL_BGR_INTEGER, EGlVersion.GL_30, true), + RGBA_INTEGER(GL30C.GL_RGBA_INTEGER, EGlVersion.GL_30, true), + BGRA_INTEGER(GL30C.GL_BGRA_INTEGER, EGlVersion.GL_30, true); + + + + private final int glFormat; + private final EGlVersion minimumGlVersion; + private final boolean isInteger; + + + + EDhPixelFormat(int glFormat, EGlVersion minimumGlVersion, boolean isInteger) + { + this.glFormat = glFormat; + this.minimumGlVersion = minimumGlVersion; + this.isInteger = isInteger; + } + + + + public static Optional fromString(String name) + { + try + { + return Optional.of(EDhPixelFormat.valueOf(name.toUpperCase(Locale.US))); + } + catch (IllegalArgumentException e) + { + return Optional.empty(); + } + } + + public int getGlFormat() { return this.glFormat; } + + public EGlVersion getMinimumGlVersion() { return this.minimumGlVersion; } + + public boolean isInteger() { return this.isInteger; } + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/texture/EDhPixelType.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/texture/EDhPixelType.java new file mode 100644 index 000000000..5d3c5d176 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/texture/EDhPixelType.java @@ -0,0 +1,64 @@ +package com.seibel.distanthorizons.common.render.nativeGl.glObject.texture; + +import org.lwjgl.opengl.GL11C; +import org.lwjgl.opengl.GL12C; +import org.lwjgl.opengl.GL30C; + +import java.util.Locale; +import java.util.Optional; + +public enum EDhPixelType +{ + BYTE(GL11C.GL_BYTE, EGlVersion.GL_11), + SHORT(GL11C.GL_SHORT, EGlVersion.GL_11), + INT(GL11C.GL_INT, EGlVersion.GL_11), + HALF_FLOAT(GL30C.GL_HALF_FLOAT, EGlVersion.GL_30), + FLOAT(GL11C.GL_FLOAT, EGlVersion.GL_11), + UNSIGNED_BYTE(GL11C.GL_UNSIGNED_BYTE, EGlVersion.GL_11), + UNSIGNED_BYTE_3_3_2(GL12C.GL_UNSIGNED_BYTE_3_3_2, EGlVersion.GL_12), + UNSIGNED_BYTE_2_3_3_REV(GL12C.GL_UNSIGNED_BYTE_2_3_3_REV, EGlVersion.GL_12), + UNSIGNED_SHORT(GL11C.GL_UNSIGNED_SHORT, EGlVersion.GL_11), + UNSIGNED_SHORT_5_6_5(GL12C.GL_UNSIGNED_SHORT_5_6_5, EGlVersion.GL_12), + UNSIGNED_SHORT_5_6_5_REV(GL12C.GL_UNSIGNED_SHORT_5_6_5_REV, EGlVersion.GL_12), + UNSIGNED_SHORT_4_4_4_4(GL12C.GL_UNSIGNED_SHORT_4_4_4_4, EGlVersion.GL_12), + UNSIGNED_SHORT_4_4_4_4_REV(GL12C.GL_UNSIGNED_SHORT_4_4_4_4_REV, EGlVersion.GL_12), + UNSIGNED_SHORT_5_5_5_1(GL12C.GL_UNSIGNED_SHORT_5_5_5_1, EGlVersion.GL_12), + UNSIGNED_SHORT_1_5_5_5_REV(GL12C.GL_UNSIGNED_SHORT_1_5_5_5_REV, EGlVersion.GL_12), + UNSIGNED_INT(GL11C.GL_UNSIGNED_INT, EGlVersion.GL_11), + UNSIGNED_INT_8_8_8_8(GL12C.GL_UNSIGNED_INT_8_8_8_8, EGlVersion.GL_12), + UNSIGNED_INT_8_8_8_8_REV(GL12C.GL_UNSIGNED_INT_8_8_8_8_REV, EGlVersion.GL_12), + UNSIGNED_INT_10_10_10_2(GL12C.GL_UNSIGNED_INT_10_10_10_2, EGlVersion.GL_12), + UNSIGNED_INT_2_10_10_10_REV(GL12C.GL_UNSIGNED_INT_2_10_10_10_REV, EGlVersion.GL_12); + + + + private final int glFormat; + private final EGlVersion minimumGlVersion; + + + + EDhPixelType(int glFormat, EGlVersion minimumGlVersion) + { + this.glFormat = glFormat; + this.minimumGlVersion = minimumGlVersion; + } + + + + public static Optional fromString(String name) + { + try + { + return Optional.of(EDhPixelType.valueOf(name.toUpperCase(Locale.US))); + } + catch (IllegalArgumentException e) + { + return Optional.empty(); + } + } + + public int getGlFormat() { return glFormat; } + + public EGlVersion getMinimumGlVersion() { return minimumGlVersion; } + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/texture/EGlVersion.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/texture/EGlVersion.java new file mode 100644 index 000000000..6a256d2d3 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/texture/EGlVersion.java @@ -0,0 +1,9 @@ +package com.seibel.distanthorizons.common.render.nativeGl.glObject.texture; + +public enum EGlVersion +{ + GL_11, + GL_12, + GL_30, + GL_31 +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/vertexAttribute/AbstractVertexAttribute.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/vertexAttribute/AbstractVertexAttribute.java new file mode 100644 index 000000000..224ad8b0b --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/vertexAttribute/AbstractVertexAttribute.java @@ -0,0 +1,92 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl.glObject.vertexAttribute; + +import com.seibel.distanthorizons.common.render.nativeGl.glObject.GLProxy; +import org.lwjgl.opengl.GL32; + +/** + * Base for binding/unbinding Vertex Attribute objects (VAO's). + * + * @see VertexAttributePostGL43 + * @see VertexAttributePreGL43 + */ +public abstract class AbstractVertexAttribute +{ + /** Stores the handle of the AbstractVertexAttribute. */ + public final int id; + + + + //==============// + // constructors // + //==============// + + // This will bind AbstractVertexAttribute + protected AbstractVertexAttribute() + { + this.id = GL32.glGenVertexArrays(); + GL32.glBindVertexArray(this.id); + } + + public static AbstractVertexAttribute create() + { + if (GLProxy.getInstance().vertexAttributeBufferBindingSupported) + { + return new VertexAttributePostGL43(); + } + else + { + return new VertexAttributePreGL43(); + } + } + + + + //=========// + // binding // + //=========// + + public void bind() { GL32.glBindVertexArray(this.id); } + public void unbind() { GL32.glBindVertexArray(0); } + + /** Always remember to always free your resources! */ + public void free() { GL32.glDeleteVertexArrays(this.id); } + + + + //==================// + // abstract methods // + //==================// + + /** Requires both AbstractVertexAttribute and VertexBuffer to be bound */ + public abstract void bindBufferToAllBindingPoints(int buffer); + /** Requires both AbstractVertexAttribute and VertexBuffer to be bound */ + public abstract void bindBufferToBindingPoint(int buffer, int bindingPoint); + /** Requires both AbstractVertexAttribute to be bound */ + public abstract void unbindBuffersFromAllBindingPoint(); + /** Requires both AbstractVertexAttribute to be bound */ + public abstract void unbindBuffersFromBindingPoint(int bindingPoint); + /** Requires both AbstractVertexAttribute to be bound */ + public abstract void setVertexAttribute(int bindingPoint, int attributeIndex, VertexPointer attribute); + /** Requires both AbstractVertexAttribute to be bound */ + public abstract void completeAndCheck(int expectedStrideSize); + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/vertexAttribute/VertexAttributePostGL43.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/vertexAttribute/VertexAttributePostGL43.java new file mode 100644 index 000000000..7733fbf50 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/vertexAttribute/VertexAttributePostGL43.java @@ -0,0 +1,155 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl.glObject.vertexAttribute; + +import com.seibel.distanthorizons.core.config.Config; +import com.seibel.distanthorizons.core.logging.DhLogger; +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import org.lwjgl.opengl.GL43; + +/** + * In OpenGL 4.3 and later, Vertex Attribute got a make-over. + * Now it provides support for buffer binding points natively. + * This means that setting up the VAO is just use ONE native call when + * binding to a buffer.

+ * + * Since I no longer need to implement binding points, I also no + * longer needs to keep track of Pointers. + */ +public final class VertexAttributePostGL43 extends AbstractVertexAttribute +{ + private static final DhLogger LOGGER = new DhLoggerBuilder() + .fileLevelConfig(Config.Common.Logging.logRendererGLEventToFile) + .chatLevelConfig(Config.Common.Logging.logRendererGLEventToChat) + .build(); + + + int numberOfBindingPoints = 0; + int strideSize = 0; + + + + //=============// + // constructor // + //=============// + + /** This will bind the {@link AbstractVertexAttribute} */ + public VertexAttributePostGL43() + { + super(); // also bind AbstractVertexAttribute + } + + + + //=========// + // binding // + //=========// + + /** Requires both AbstractVertexAttribute and VertexBuffer to be bound */ + @Override + public void bindBufferToAllBindingPoints(int buffer) + { + for (int i = 0; i < this.numberOfBindingPoints; i++) + { + GL43.glBindVertexBuffer(i, buffer, 0, this.strideSize); + } + } + + /** Requires both AbstractVertexAttribute and VertexBuffer to be bound */ + @Override + public void bindBufferToBindingPoint(int buffer, int bindingPoint) + { + GL43.glBindVertexBuffer(bindingPoint, buffer, 0, this.strideSize); + } + + + + //===========// + // unbinding // + //===========// + + /** Requires AbstractVertexAttribute to be bound */ + @Override + public void unbindBuffersFromAllBindingPoint() + { + for (int i = 0; i < this.numberOfBindingPoints; i++) + { + GL43.glBindVertexBuffer(i, 0, 0, 0); + } + } + + /** Requires AbstractVertexAttribute to be bound */ + @Override + public void unbindBuffersFromBindingPoint(int bindingPoint) + { + GL43.glBindVertexBuffer(bindingPoint, 0, 0, 0); + } + + + + //==========================// + // manual attribute setting // + //==========================// + + /** Requires AbstractVertexAttribute to be bound */ + @Override + public void setVertexAttribute(int bindingPoint, int attributeIndex, VertexPointer attribute) + { + if (attribute.useInteger) + { + GL43.glVertexAttribIFormat(attributeIndex, attribute.elementCount, attribute.glType, this.strideSize); + } + else + { + GL43.glVertexAttribFormat(attributeIndex, attribute.elementCount, attribute.glType, + attribute.normalized, this.strideSize); // Here strideSize is new attrib offset + } + + this.strideSize += attribute.byteSize; + if (this.numberOfBindingPoints <= bindingPoint) + { + this.numberOfBindingPoints = bindingPoint + 1; + } + GL43.glVertexAttribBinding(attributeIndex, bindingPoint); + GL43.glEnableVertexAttribArray(attributeIndex); + } + + + + //============// + // validation // + //============// + + /** Requires AbstractVertexAttribute to be bound */ + @Override + public void completeAndCheck(int expectedStrideSize) + { + if (this.strideSize != expectedStrideSize) + { + LOGGER.error("Vertex Attribute calculated stride size " + this.strideSize + + " does not match the provided expected stride size " + expectedStrideSize + "!"); + throw new IllegalArgumentException("Vertex Attribute Incorrect Format"); + } + + LOGGER.info("Vertex Attribute (GL43+) completed. It contains " + this.numberOfBindingPoints + + " binding points and a stride size of " + this.strideSize); + } + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/vertexAttribute/VertexAttributePreGL43.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/vertexAttribute/VertexAttributePreGL43.java new file mode 100644 index 000000000..5c18b2b13 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/vertexAttribute/VertexAttributePreGL43.java @@ -0,0 +1,253 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl.glObject.vertexAttribute; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.TreeMap; +import java.util.TreeSet; + +import com.seibel.distanthorizons.core.config.Config; +import com.seibel.distanthorizons.core.logging.DhLogger; +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import org.lwjgl.opengl.GL32; + + +public final class VertexAttributePreGL43 extends AbstractVertexAttribute +{ + private static final DhLogger LOGGER = new DhLoggerBuilder() + .fileLevelConfig(Config.Common.Logging.logRendererGLEventToFile) + .chatLevelConfig(Config.Common.Logging.logRendererGLEventToChat) + .build(); + + + // I tried to use raw arrays as much as possible since those lookups + // happen every frame, and the speed directly affects fps + int strideSize = 0; + int[][] bindingPointsToIndex; + VertexPointer[] pointers; + int[] pointersOffset; + + TreeMap> bindingPointsToIndexBuilder; + ArrayList pointersBuilder; + + + + //=============// + // constructor // + //=============// + + /** This will bind the {@link AbstractVertexAttribute} */ + public VertexAttributePreGL43() + { + super(); // also bind AbstractVertexAttribute + this.bindingPointsToIndexBuilder = new TreeMap<>(); + this.pointersBuilder = new ArrayList<>(); + } + + + + //=========// + // binding // + //=========// + + /** Requires both AbstractVertexAttribute and VertexBuffer to be bound */ + @Override + public void bindBufferToAllBindingPoints(int buffer) + { + for (int i = 0; i < this.pointers.length; i++) + { + GL32.glEnableVertexAttribArray(i); + } + + for (int i = 0; i < this.pointers.length; i++) + { + VertexPointer pointer = this.pointers[i]; + if (pointer == null) + { + continue; + } + + if (pointer.useInteger) + { + GL32.glVertexAttribIPointer(i, pointer.elementCount, pointer.glType, + this.strideSize, this.pointersOffset[i]); + } + else + { + GL32.glVertexAttribPointer(i, pointer.elementCount, pointer.glType, + pointer.normalized, this.strideSize, this.pointersOffset[i]); + } + } + } + + /** Requires both AbstractVertexAttribute and VertexBuffer to be bound */ + @Override + public void bindBufferToBindingPoint(int buffer, int bindingPoint) + { + int[] bindingPointIndexes = this.bindingPointsToIndex[bindingPoint]; + + for (int bindingPointIndex : bindingPointIndexes) + { + GL32.glEnableVertexAttribArray(bindingPointIndex); + } + + for (int bindingPointIndex : bindingPointIndexes) + { + VertexPointer pointer = this.pointers[bindingPointIndex]; + if (pointer == null) + { + continue; + } + + if (pointer.useInteger) + { + GL32.glVertexAttribIPointer(bindingPointIndex, pointer.elementCount, pointer.glType, + this.strideSize, this.pointersOffset[bindingPointIndex]); + } + else + { + GL32.glVertexAttribPointer(bindingPointIndex, pointer.elementCount, pointer.glType, + pointer.normalized, this.strideSize, this.pointersOffset[bindingPointIndex]); + } + } + + } + + + + //===========// + // unbinding // + //===========// + + /** Requires AbstractVertexAttribute to be bound */ + @Override + public void unbindBuffersFromAllBindingPoint() + { + for (int i = 0; i < this.pointers.length; i++) + { + GL32.glDisableVertexAttribArray(i); + } + } + + /** Requires AbstractVertexAttribute to be bound */ + @Override + public void unbindBuffersFromBindingPoint(int bindingPoint) + { + int[] bindingPointIndexes = this.bindingPointsToIndex[bindingPoint]; + for (int bindingPointIndex : bindingPointIndexes) + { + GL32.glDisableVertexAttribArray(bindingPointIndex); + } + } + + + + //==========================// + // manual attribute setting // + //==========================// + + /** Requires AbstractVertexAttribute to be bound */ + @Override + public void setVertexAttribute(int bindingPoint, int attributeIndex, VertexPointer attribute) + { + TreeSet intArray = this.bindingPointsToIndexBuilder.computeIfAbsent(bindingPoint, k -> new TreeSet<>()); + intArray.add(attributeIndex); + + while (this.pointersBuilder.size() <= attributeIndex) + { + // This is dumb, but ArrayList doesn't have a resize, And this code + // should only be run when it's building the Vertex Attribute anyway. + this.pointersBuilder.add(null); + } + this.pointersBuilder.set(attributeIndex, attribute); + } + + + + //============// + // validation // + //============// + + /** Requires AbstractVertexAttribute to be bound */ + @Override + public void completeAndCheck(int expectedStrideSize) + { + int maxBindPointNumber = this.bindingPointsToIndexBuilder.lastKey(); + this.bindingPointsToIndex = new int[maxBindPointNumber + 1][]; + + this.bindingPointsToIndexBuilder.forEach((Integer i, TreeSet set) -> + { + this.bindingPointsToIndex[i] = new int[set.size()]; + Iterator iter = set.iterator(); + for (int j = 0; j < set.size(); j++) + { + this.bindingPointsToIndex[i][j] = iter.next(); + } + }); + + this.pointers = this.pointersBuilder.toArray(new VertexPointer[this.pointersBuilder.size()]); + this.pointersOffset = new int[this.pointers.length]; + this.pointersBuilder = null; // Release the builder + this.bindingPointsToIndexBuilder = null; // Release the builder + + // Check if all pointers are valid + int currentOffset = 0; + for (int i = 0; i < this.pointers.length; i++) + { + VertexPointer pointer = this.pointers[i]; + if (pointer == null) + { + LOGGER.warn("Vertex Attribute index " + i + " is not set! No index should be skipped normally!"); + continue; + } + this.pointersOffset[i] = currentOffset; + currentOffset += pointer.byteSize; + } + + if (currentOffset != expectedStrideSize) + { + LOGGER.error("Vertex Attribute calculated stride size " + currentOffset + + " does not match the provided expected stride size " + expectedStrideSize + "!"); + throw new IllegalArgumentException("Vertex Attribute Incorrect Format"); + } + this.strideSize = currentOffset; + LOGGER.info("Vertex Attribute (pre GL43) completed."); + + // Debug logging + LOGGER.debug("AttributeIndex: ElementCount, glType, normalized, strideSize, offset"); + + for (int i = 0; i < this.pointers.length; i++) + { + VertexPointer pointer = this.pointers[i]; + if (pointer == null) + { + LOGGER.debug(i + ": Null!!!!"); + } + else + { + LOGGER.debug(i + ": " + pointer.elementCount + ", " + + pointer.glType + ", " + pointer.normalized + ", " + this.strideSize + ", " + this.pointersOffset[i]); + } + } + + } + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/vertexAttribute/VertexPointer.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/vertexAttribute/VertexPointer.java new file mode 100644 index 000000000..e05b2a621 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/glObject/vertexAttribute/VertexPointer.java @@ -0,0 +1,72 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl.glObject.vertexAttribute; + +import com.seibel.distanthorizons.coreapi.util.MathUtil; +import org.lwjgl.opengl.GL32; + +public final class VertexPointer +{ + public final int elementCount; + public final int glType; + public final boolean normalized; + public final int byteSize; + public final boolean useInteger; + + + + // basic constructors // + + public VertexPointer(int elementCount, int glType, boolean normalized, int byteSize, boolean useInteger) + { + this.elementCount = elementCount; + this.glType = glType; + this.normalized = normalized; + this.byteSize = byteSize; + this.useInteger = useInteger; + } + public VertexPointer(int elementCount, int glType, boolean normalized, int byteSize) + { + this(elementCount, glType, normalized, byteSize, false); + } + private static int _align(int bytes) { return MathUtil.ceilDiv(bytes, 4) * 4; } + + + + // named constructors // + + public static VertexPointer addFloatPointer(boolean normalized) { return new VertexPointer(1, GL32.GL_FLOAT, normalized, Float.BYTES); } + public static VertexPointer addVec2Pointer(boolean normalized) { return new VertexPointer(2, GL32.GL_FLOAT, normalized, Float.BYTES * 2); } + public static VertexPointer addVec3Pointer(boolean normalized) { return new VertexPointer(3, GL32.GL_FLOAT, normalized, Float.BYTES * 3); } + public static VertexPointer addVec4Pointer(boolean normalized) { return new VertexPointer(4, GL32.GL_FLOAT, normalized, Float.BYTES * 4); } + /** Always aligned to 4 bytes */ + public static VertexPointer addUnsignedBytePointer(boolean normalized, boolean useInteger) { return new VertexPointer(1, GL32.GL_UNSIGNED_BYTE, normalized, 4, useInteger); } + /** aligned to 4 bytes */ + public static VertexPointer addUnsignedBytesPointer(int elementCount, boolean normalized, boolean useInteger) + { return new VertexPointer(elementCount, GL32.GL_UNSIGNED_BYTE, normalized, _align(elementCount), useInteger); } + public static VertexPointer addUnsignedShortsPointer(int elementCount, boolean normalized, boolean useInteger) + { return new VertexPointer(elementCount, GL32.GL_UNSIGNED_SHORT, normalized, _align(elementCount * 2), useInteger); } + public static VertexPointer addShortsPointer(int elementCount, boolean normalized, boolean useInteger) { return new VertexPointer(elementCount, GL32.GL_SHORT, normalized, _align(elementCount * 2), useInteger); } + public static VertexPointer addIntPointer(boolean normalized, boolean useInteger) { return new VertexPointer(1, GL32.GL_INT, normalized, 4, useInteger); } + public static VertexPointer addIVec2Pointer(boolean normalized, boolean useInteger) { return new VertexPointer(2, GL32.GL_INT, normalized, 8, useInteger); } + public static VertexPointer addIVec3Pointer(boolean normalized, boolean useInteger) { return new VertexPointer(3, GL32.GL_INT, normalized, 12, useInteger); } + public static VertexPointer addIVec4Pointer(boolean normalized, boolean useInteger) { return new VertexPointer(4, GL32.GL_INT, normalized, 16, useInteger); } + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/ScreenQuad.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/ScreenQuad.java new file mode 100644 index 000000000..8405d39e7 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/ScreenQuad.java @@ -0,0 +1,96 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl.postProcessing; + +import com.seibel.distanthorizons.common.render.nativeGl.glObject.vertexAttribute.AbstractVertexAttribute; +import com.seibel.distanthorizons.common.render.nativeGl.glObject.vertexAttribute.VertexPointer; +import org.lwjgl.opengl.GL32; +import org.lwjgl.system.MemoryUtil; + +import java.nio.ByteBuffer; + +/** + * Renders a full-screen textured quad to the screen. + * Used in composite / deferred rendering (IE fog). + */ +public class ScreenQuad +{ + public static ScreenQuad INSTANCE = new ScreenQuad(); + + private static final float[] box_vertices = { + -1, -1, + 1, -1, + 1, 1, + -1, -1, + 1, 1, + -1, 1, + }; + + private AbstractVertexAttribute va; + private boolean init = false; + + + //=============// + // constructor // + //=============// + + private ScreenQuad() { } + + public void init() + { + if (this.init) return; + this.init = true; + + this.va = AbstractVertexAttribute.create(); + this.va.bind(); + + // Pos + this.va.setVertexAttribute(0, 0, VertexPointer.addVec2Pointer(false)); + this.va.completeAndCheck(Float.BYTES * 2); + + // Framebuffer + this.createBuffer(); + } + + public void render() + { + this.init(); + + //this.boxBuffer.bind(); + + this.va.bind(); + //this.va.bindBufferToAllBindingPoints(this.boxBuffer.getId()); + + GL32.glDrawArrays(GL32.GL_TRIANGLES, 0, 6); + } + + private void createBuffer() + { + ByteBuffer buffer = MemoryUtil.memAlloc(box_vertices.length * Float.BYTES); + buffer.asFloatBuffer().put(box_vertices); + buffer.rewind(); + + //this.boxBuffer = new GLVertexBuffer(false); + //this.boxBuffer.bind(); + //this.boxBuffer.uploadBuffer(buffer, box_vertices.length, EDhApiGpuUploadMethod.DATA, box_vertices.length * Float.BYTES); + MemoryUtil.memFree(buffer); + } + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/VanillaFadeRenderer.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/VanillaFadeRenderer.java new file mode 100644 index 000000000..70a777c2e --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/VanillaFadeRenderer.java @@ -0,0 +1,188 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl.postProcessing; + +import com.seibel.distanthorizons.common.render.nativeGl.glObject.GLState; +import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftGLWrapper; +import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import com.seibel.distanthorizons.core.render.renderer.BlazeLodRenderer; +import com.seibel.distanthorizons.common.render.nativeGl.postProcessing.fade.DhFadeShader; +import com.seibel.distanthorizons.common.render.nativeGl.postProcessing.fade.FadeApplyShader; +import com.seibel.distanthorizons.common.render.nativeGl.postProcessing.fade.VanillaFadeShader; +import com.seibel.distanthorizons.core.util.math.Mat4f; +import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; +import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; +import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper; +import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; +import com.seibel.distanthorizons.core.logging.DhLogger; +import org.lwjgl.opengl.GL32; + +import java.nio.ByteBuffer; + +/** + * Handles fading MC and DH together via {@link VanillaFadeShader} and {@link FadeApplyShader}.

+ * + * {@link VanillaFadeShader} - draws the Fade to a texture.
+ * {@link FadeApplyShader} - draws the Fade texture to MC's FrameBuffer.
+ */ +public class VanillaFadeRenderer +{ + public static VanillaFadeRenderer INSTANCE = new VanillaFadeRenderer(); + + private static final DhLogger LOGGER = new DhLoggerBuilder().build(); + + private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); + private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); + private static final MinecraftGLWrapper GLMC = MinecraftGLWrapper.INSTANCE; + + + private boolean init = false; + + private int width = -1; + private int height = -1; + private int fadeFramebuffer = -1; + + private int fadeTexture = -1; + + + + //=============// + // constructor // + //=============// + + private VanillaFadeRenderer() { } + + public void init() + { + if (this.init) return; + this.init = true; + + VanillaFadeShader.INSTANCE.init(); + FadeApplyShader.INSTANCE.init(); + } + + private void createFramebuffer(int width, int height) + { + if (this.fadeFramebuffer != -1) + { + GL32.glDeleteFramebuffers(this.fadeFramebuffer); + this.fadeFramebuffer = -1; + } + + this.fadeFramebuffer = GL32.glGenFramebuffers(); + GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, this.fadeFramebuffer); + + + // Applying the fade texture is only needed if MC is drawing to their own frame buffer, + // otherwise we can directly render to their texture + if (MC_RENDER.mcRendersToFrameBuffer()) + { + if (this.fadeTexture != -1) + { + GLMC.glDeleteTextures(this.fadeTexture); + this.fadeTexture = -1; + } + + this.fadeTexture = GL32.glGenTextures(); + GLMC.glBindTexture(this.fadeTexture); + GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, GL32.GL_RGBA16, width, height, 0, GL32.GL_RGBA, GL32.GL_UNSIGNED_SHORT_4_4_4_4, (ByteBuffer) null); + GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_LINEAR); + GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_LINEAR); + GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_TEXTURE_2D, this.fadeTexture, 0); + } + else + { + GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_TEXTURE_2D, MC_RENDER.getColorTextureId(), 0); + } + } + + + + //========// + // render // + //========// + + public void render(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, IClientLevelWrapper level) + { + int depthTextureId = BlazeLodRenderer.INSTANCE.getActiveDepthTextureId(); + if (depthTextureId == -1) + { + // the renderer hasn't been set up yet + // trying to render fading may cause GL errors + return; + } + + + + IProfilerWrapper profiler = MC_CLIENT.getProfiler(); + profiler.pop(); // get out of "terrain" + profiler.push("DH-Vanilla Fade"); + + + try(GLState mcState = new GLState()) + { + profiler.push("Vanilla Fade Generate"); + + this.init(); + + // resize the framebuffer if necessary + int width = MC_RENDER.getTargetFramebufferViewportWidth(); + int height = MC_RENDER.getTargetFramebufferViewportHeight(); + if (this.width != width || this.height != height) + { + this.width = width; + this.height = height; + this.createFramebuffer(width, height); + } + + + VanillaFadeShader.INSTANCE.frameBuffer = this.fadeFramebuffer; + VanillaFadeShader.INSTANCE.setProjectionMatrix(mcModelViewMatrix, mcProjectionMatrix); + VanillaFadeShader.INSTANCE.setLevelMaxHeight(level.getMaxHeight()); + VanillaFadeShader.INSTANCE.render(0); + + // Applying the fade texture is only needed if MC is drawing to their own frame buffer, + // otherwise we can directly render to their texture + if (MC_RENDER.mcRendersToFrameBuffer()) + { + profiler.popPush("Vanilla Fade Apply"); + + FadeApplyShader.INSTANCE.fadeTexture = this.fadeTexture; + FadeApplyShader.INSTANCE.readFramebuffer = DhFadeShader.INSTANCE.frameBuffer; + FadeApplyShader.INSTANCE.drawFramebuffer = MC_RENDER.getTargetFramebuffer(); + FadeApplyShader.INSTANCE.render(0); + } + + profiler.pop(); + } + catch (Exception e) + { + LOGGER.error("Unexpected error during fade render, error: ["+e.getMessage()+"].", e); + } + } + + public void free() + { + VanillaFadeShader.INSTANCE.free(); + FadeApplyShader.INSTANCE.free(); + } + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/apply/DhApplyShader.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/apply/DhApplyShader.java new file mode 100644 index 000000000..718e6a3c9 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/apply/DhApplyShader.java @@ -0,0 +1,199 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl.postProcessing.apply; + +import com.seibel.distanthorizons.common.render.nativeGl.glObject.GLState; +import com.seibel.distanthorizons.common.render.nativeGl.glObject.shader.ShaderProgram; +import com.seibel.distanthorizons.common.render.nativeGl.postProcessing.ScreenQuad; +import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftGLWrapper; +import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import com.seibel.distanthorizons.core.render.renderer.BlazeLodRenderer; +import com.seibel.distanthorizons.common.render.nativeGl.util.AbstractShaderRenderer; +import com.seibel.distanthorizons.core.logging.DhLogger; +import org.lwjgl.opengl.GL32; + +/** + * Copies {@link BlazeLodRenderer}'s currently active color and depth texture to Minecraft's framebuffer. + */ +public class DhApplyShader extends AbstractShaderRenderer +{ + public static DhApplyShader INSTANCE = new DhApplyShader(); + + private static final DhLogger LOGGER = new DhLoggerBuilder().build(); + private static final MinecraftGLWrapper GLMC = MinecraftGLWrapper.INSTANCE; + + + // uniforms + public int gDhColorTextureUniform; + public int gDepthMapUniform; + + + + //=======// + // setup // + //=======// + //region + + private DhApplyShader() { } + + @Override + public void onInit() + { + this.shader = new ShaderProgram( + "shaders/quadApply.vert", + "shaders/apply.frag", + "vPosition" + ); + + // uniform setup + this.gDhColorTextureUniform = this.shader.getUniformLocation("gDhColorTexture"); + this.gDepthMapUniform = this.shader.getUniformLocation("gDhDepthTexture"); + + } + + @Override + protected void onApplyUniforms(float partialTicks) { } + + //endregion + + + + //========// + // render // + //========// + //region + + @Override + protected void onRender() + { + if (MC_RENDER.mcRendersToFrameBuffer()) + { + this.renderToFrameBuffer(); + } + else + { + this.renderToMcTexture(); + } + } + private void renderToFrameBuffer() + { + int targetFrameBuffer = MC_RENDER.getTargetFramebuffer(); + if (targetFrameBuffer == -1) + { + return; + } + + + try (GLState state = new GLState()) + { + + GLMC.disableDepthTest(); + + // blending isn't needed, we're manually merging the MC and DH textures + // Note: this prevents the sun/moon and stars from rendering through transparent LODs, + // however this also fixes transparent LODs from glowing when rendered against the sky during the day + GLMC.disableBlend(); + + // old blending logic in case it's ever needed: + //GLMC.enableBlend(); + //GL32.glBlendEquation(GL32.GL_FUNC_ADD); + //GLMC.glBlendFunc(GL32.GL_ONE, GL32.GL_ONE_MINUS_SRC_ALPHA); + + GLMC.glActiveTexture(GL32.GL_TEXTURE0); + GLMC.glBindTexture(BlazeLodRenderer.INSTANCE.getActiveColorTextureId()); + GL32.glUniform1i(this.gDhColorTextureUniform, 0); + + GLMC.glActiveTexture(GL32.GL_TEXTURE1); + GLMC.glBindTexture(BlazeLodRenderer.INSTANCE.getActiveDepthTextureId()); + GL32.glUniform1i(this.gDepthMapUniform, 1); + + // Copy to MC's framebuffer + GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, targetFrameBuffer); + + ScreenQuad.INSTANCE.render(); + } + // everything's been restored, except at this point the MC framebuffer should now be used instead + GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, targetFrameBuffer); + + } + private void renderToMcTexture() + { + int targetColorTextureId = MC_RENDER.getColorTextureId(); + if (targetColorTextureId == -1) + { + return; + } + + int dhFrameBufferId = BlazeLodRenderer.INSTANCE.getActiveFramebufferId(); + if (dhFrameBufferId == -1) + { + return; + } + + int mcFrameBufferId = MC_RENDER.getTargetFramebuffer(); + if (mcFrameBufferId == -1) + { + return; + } + + + + try (GLState state = new GLState()) + { + GLMC.disableDepthTest(); + + // blending isn't needed, we're just directly merging the MC and DH textures + // Note: this prevents the sun/moon and stars from rendering through transparent LODs, + // however this also fixes + GLMC.disableBlend(); + + // old blending logic in case it's ever needed: + //GLMC.enableBlend(); + //GL32.glBlendEquation(GL32.GL_FUNC_ADD); + //GLMC.glBlendFunc(GL32.GL_ONE, GL32.GL_ONE_MINUS_SRC_ALPHA); + + GLMC.glActiveTexture(GL32.GL_TEXTURE0); + GLMC.glBindTexture(BlazeLodRenderer.INSTANCE.getActiveColorTextureId()); + GL32.glUniform1i(this.gDhColorTextureUniform, 0); + + GLMC.glActiveTexture(GL32.GL_TEXTURE1); + GLMC.glBindTexture(BlazeLodRenderer.INSTANCE.getActiveDepthTextureId()); + GL32.glUniform1i(this.gDepthMapUniform, 1); + + + + GL32.glFramebufferTexture(GL32.GL_DRAW_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, targetColorTextureId, 0); + + // Copy to MC's texture via MC's framebuffer + GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, dhFrameBufferId); + + ScreenQuad.INSTANCE.render(); + } + // everything's been restored, except at this point the MC framebuffer should now be used instead + GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, mcFrameBufferId); + + } + + //endregion + + + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/fade/DhFadeRenderer.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/fade/DhFadeRenderer.java new file mode 100644 index 000000000..222f26fe9 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/fade/DhFadeRenderer.java @@ -0,0 +1,161 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl.postProcessing.fade; + +import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftGLWrapper; +import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; +import com.seibel.distanthorizons.core.logging.DhLogger; +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import com.seibel.distanthorizons.core.render.renderer.BlazeLodRenderer; +import com.seibel.distanthorizons.core.util.math.Mat4f; +import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; +import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper; +import org.lwjgl.opengl.GL32; +import org.lwjgl.opengl.GL43C; + +import java.nio.ByteBuffer; + +/** + * Handles fading MC and DH together via {@link DhFadeShader} and {@link FadeApplyShader}.

+ * + * {@link DhFadeShader} - draws the Fade to a texture.
+ * {@link FadeApplyShader} - draws the Fade texture to DH's framebuffer.
+ */ +public class DhFadeRenderer +{ + + public static DhFadeRenderer INSTANCE = new DhFadeRenderer(); + private static final DhLogger LOGGER = new DhLoggerBuilder().build(); + + private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); + private static final MinecraftGLWrapper GLMC = MinecraftGLWrapper.INSTANCE; + + + private boolean init = false; + + private int width = -1; + private int height = -1; + private int fadeFramebuffer = -1; + + private int fadeTexture = -1; + + + + //=============// + // constructor // + //=============// + + private DhFadeRenderer() { } + + public void init() + { + if (this.init) return; + this.init = true; + + DhFadeShader.INSTANCE.init(); + FadeApplyShader.INSTANCE.init(); + } + + private void createFramebuffer(int width, int height) + { + if (this.fadeFramebuffer != -1) + { + GL32.glDeleteFramebuffers(this.fadeFramebuffer); + this.fadeFramebuffer = -1; + } + + this.fadeFramebuffer = GL32.glGenFramebuffers(); + GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, this.fadeFramebuffer); + + + if (this.fadeTexture != -1) + { + GLMC.glDeleteTextures(this.fadeTexture); + this.fadeTexture = -1; + } + + this.fadeTexture = GL32.glGenTextures(); + { + GLMC.glBindTexture(this.fadeTexture); + GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, GL32.GL_RGBA16, width, height, 0, GL32.GL_RGBA, GL32.GL_UNSIGNED_SHORT_4_4_4_4, (ByteBuffer) null); + GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_LINEAR); + GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_LINEAR); + + // disable mip-mapping since DH is just going to draw straight to the screen + GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_BASE_LEVEL, 0); + GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_MAX_LEVEL, 0); + } + + GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_TEXTURE_2D, this.fadeTexture, 0); + + } + + + + //========// + // render // + //========// + + public void render(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix, float partialTicks, IProfilerWrapper profiler) + { + try + { + profiler.push("Fade Generate"); + + this.init(); + + // resize the framebuffer if necessary + int width = MC_RENDER.getTargetFramebufferViewportWidth(); + int height = MC_RENDER.getTargetFramebufferViewportHeight(); + if (this.width != width || this.height != height) + { + this.width = width; + this.height = height; + this.createFramebuffer(width, height); + } + + + DhFadeShader.INSTANCE.frameBuffer = this.fadeFramebuffer; + DhFadeShader.INSTANCE.setProjectionMatrix(mcModelViewMatrix, mcProjectionMatrix); + DhFadeShader.INSTANCE.render(partialTicks); + + // restored so we can write the fade texture to the main frame buffer + //mcState.restore(); + + profiler.popPush("Fade Apply"); + + FadeApplyShader.INSTANCE.fadeTexture = this.fadeTexture; + FadeApplyShader.INSTANCE.readFramebuffer = DhFadeShader.INSTANCE.frameBuffer; + FadeApplyShader.INSTANCE.drawFramebuffer = BlazeLodRenderer.INSTANCE.getActiveFramebufferId(); + FadeApplyShader.INSTANCE.render(partialTicks); + } + catch (Exception e) + { + LOGGER.error("Unexpected error during fade render, error: ["+e.getMessage()+"].", e); + } + finally + { + profiler.pop(); + } + } + + + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/fade/DhFadeShader.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/fade/DhFadeShader.java new file mode 100644 index 000000000..e8037a8d3 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/fade/DhFadeShader.java @@ -0,0 +1,165 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl.postProcessing.fade; + +import com.seibel.distanthorizons.common.render.nativeGl.glObject.shader.ShaderProgram; +import com.seibel.distanthorizons.common.render.nativeGl.postProcessing.ScreenQuad; +import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftGLWrapper; +import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; +import com.seibel.distanthorizons.core.render.renderer.BlazeLodRenderer; +import com.seibel.distanthorizons.common.render.nativeGl.util.AbstractShaderRenderer; +import com.seibel.distanthorizons.core.util.RenderUtil; +import com.seibel.distanthorizons.core.util.math.Mat4f; +import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; +import org.lwjgl.opengl.GL32; + +public class DhFadeShader extends AbstractShaderRenderer +{ + public static DhFadeShader INSTANCE = new DhFadeShader(); + + private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); + private static final MinecraftGLWrapper GLMC = MinecraftGLWrapper.INSTANCE; + + + public int frameBuffer = -1; + + private Mat4f inverseDhMvmProjMatrix; + + + // Uniforms + + /** Inverted Model View Projection matrix */ + public int uDhInvMvmProj = -1; + + public int uDhDepthTexture = -1; + public int uMcColorTexture = -1; + public int uDhColorTexture = -1; + + public int uStartFadeBlockDistance = -1; + public int uEndFadeBlockDistance = -1; + + + + //=============// + // constructor // + //=============// + + public DhFadeShader() { } + + @Override + public void onInit() + { + this.shader = new ShaderProgram( + "shaders/quadApply.vert", + "shaders/fade/dhFade.frag", + "vPosition" + ); + + // all uniforms should be tryGet... + // because disabling fade can cause the GLSL to optimize out most (if not all) uniforms + + // near fade + this.uDhInvMvmProj = this.shader.tryGetUniformLocation("uDhInvMvmProj"); + + this.uDhDepthTexture = this.shader.tryGetUniformLocation("uDhDepthTexture"); + this.uMcColorTexture = this.shader.tryGetUniformLocation("uMcColorTexture"); + this.uDhColorTexture = this.shader.tryGetUniformLocation("uDhColorTexture"); + + this.uStartFadeBlockDistance = this.shader.tryGetUniformLocation("uStartFadeBlockDistance"); + this.uEndFadeBlockDistance = this.shader.tryGetUniformLocation("uEndFadeBlockDistance"); + + } + + + + //=============// + // render prep // + //=============// + + @Override + protected void onApplyUniforms(float partialTicks) + { + this.shader.setUniform(this.uDhInvMvmProj, this.inverseDhMvmProjMatrix); + + + float dhFarClipDistance = RenderUtil.getFarClipPlaneDistanceInBlocks(); + float fadeStartDistance = dhFarClipDistance * 0.5f; + float fadeEndDistance = dhFarClipDistance * 0.9f; + + this.shader.setUniform(this.uStartFadeBlockDistance, fadeStartDistance); + this.shader.setUniform(this.uEndFadeBlockDistance, fadeEndDistance); + + } + + public void setProjectionMatrix(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix) + { + Mat4f dhProjectionMatrix = RenderUtil.createLodProjectionMatrix(mcProjectionMatrix); + Mat4f dhModelViewMatrix = RenderUtil.createLodModelViewMatrix(mcModelViewMatrix); + + Mat4f inverseDhModelViewProjectionMatrix = new Mat4f(dhProjectionMatrix); + inverseDhModelViewProjectionMatrix.multiply(dhModelViewMatrix); + inverseDhModelViewProjectionMatrix.invert(); + this.inverseDhMvmProjMatrix = inverseDhModelViewProjectionMatrix; + } + + + //========// + // render // + //========// + + @Override + protected void onRender() + { + int depthTextureId = BlazeLodRenderer.INSTANCE.getActiveDepthTextureId(); + int colorTextureId = BlazeLodRenderer.INSTANCE.getActiveColorTextureId(); + + if (depthTextureId == -1 + || colorTextureId == -1) + { + // the renderer is currently being re-built and/or inactive, + // we don't need to/can't render fading + return; + } + + + + GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, this.frameBuffer); + GLMC.disableScissorTest(); + GLMC.disableDepthTest(); + GLMC.disableBlend(); + + + GLMC.glActiveTexture(GL32.GL_TEXTURE0); + GLMC.glBindTexture(depthTextureId); + GL32.glUniform1i(this.uDhDepthTexture, 0); + + GLMC.glActiveTexture(GL32.GL_TEXTURE1); + GLMC.glBindTexture(MC_RENDER.getColorTextureId()); + GL32.glUniform1i(this.uMcColorTexture, 1); + + GLMC.glActiveTexture(GL32.GL_TEXTURE2); + GLMC.glBindTexture(colorTextureId); + GL32.glUniform1i(this.uDhColorTexture, 2); + + + ScreenQuad.INSTANCE.render(); + } + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/fade/FadeApplyShader.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/fade/FadeApplyShader.java new file mode 100644 index 000000000..366755515 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/fade/FadeApplyShader.java @@ -0,0 +1,118 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl.postProcessing.fade; + +import com.seibel.distanthorizons.common.render.nativeGl.glObject.shader.ShaderProgram; +import com.seibel.distanthorizons.common.render.nativeGl.postProcessing.ScreenQuad; +import com.seibel.distanthorizons.common.render.nativeGl.postProcessing.VanillaFadeRenderer; +import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftGLWrapper; +import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; +import com.seibel.distanthorizons.common.render.nativeGl.util.AbstractShaderRenderer; +import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; +import org.lwjgl.opengl.GL32; + +/** + * Draws the Fade texture onto Minecraft's FrameBuffer.

+ * + * See Also:
+ * {@link VanillaFadeRenderer} - Parent to this shader.
+ * {@link VanillaFadeShader} - draws the Fade texture.
+ */ +public class FadeApplyShader extends AbstractShaderRenderer +{ + public static FadeApplyShader INSTANCE = new FadeApplyShader(); + + private static final MinecraftGLWrapper GLMC = MinecraftGLWrapper.INSTANCE; + + + + public int fadeTexture; + + public int readFramebuffer; + public int drawFramebuffer; + + // uniforms + public int uFadeColorTextureUniform = -1; + + + + //=============// + // constructor // + //=============// + + @Override + public void onInit() + { + this.shader = new ShaderProgram( + "shaders/quadApply.vert", + "shaders/fade/apply.frag", + "vPosition" + ); + + // uniform setup + this.uFadeColorTextureUniform = this.shader.getUniformLocation("uFadeColorTextureUniform"); + + } + + + + //=============// + // render prep // + //=============// + + @Override + protected void onApplyUniforms(float partialTicks) + { + GLMC.glActiveTexture(GL32.GL_TEXTURE0); + GLMC.glBindTexture(this.fadeTexture); + GL32.glUniform1i(this.uFadeColorTextureUniform, 0); + + } + + + + //========// + // render // + //========// + + @Override + protected void onRender() + { + GLMC.disableBlend(); + + // Depth testing must be disabled otherwise this application shader won't apply anything. + // setting this isn't necessary in vanilla, but some mods may change this, requiring it to be set manually, + // it should be automatically restored after rendering is complete. + GLMC.disableDepthTest(); + + + // apply the rendered Fade to Minecraft's framebuffer + GLMC.glBindFramebuffer(GL32.GL_READ_FRAMEBUFFER, this.readFramebuffer); + GLMC.glBindFramebuffer(GL32.GL_DRAW_FRAMEBUFFER, this.drawFramebuffer); + + ScreenQuad.INSTANCE.render(); + + GLMC.enableDepthTest(); + + } + + + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/fade/VanillaFadeShader.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/fade/VanillaFadeShader.java new file mode 100644 index 000000000..48f625d57 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/fade/VanillaFadeShader.java @@ -0,0 +1,198 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl.postProcessing.fade; + +import com.seibel.distanthorizons.common.render.nativeGl.glObject.shader.ShaderProgram; +import com.seibel.distanthorizons.common.render.nativeGl.postProcessing.ScreenQuad; +import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftGLWrapper; +import com.seibel.distanthorizons.core.config.Config; +import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; +import com.seibel.distanthorizons.core.render.renderer.BlazeLodRenderer; +import com.seibel.distanthorizons.common.render.nativeGl.util.AbstractShaderRenderer; +import com.seibel.distanthorizons.core.util.RenderUtil; +import com.seibel.distanthorizons.core.util.math.Mat4f; +import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; +import org.lwjgl.opengl.GL32; + +public class VanillaFadeShader extends AbstractShaderRenderer +{ + public static VanillaFadeShader INSTANCE = new VanillaFadeShader(); + + private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); + private static final MinecraftGLWrapper GLMC = MinecraftGLWrapper.INSTANCE; + + + public int frameBuffer = -1; + + private Mat4f inverseMcMvmProjMatrix; + private Mat4f inverseDhMvmProjMatrix; + private float levelMaxHeight; + + + // Uniforms + public int uMcDepthTexture = -1; + public int uDhDepthTexture = -1; + public int uCombinedMcDhColorTexture = -1; + public int uDhColorTexture = -1; + + /** Inverted Model View Projection matrix */ + public int uDhInvMvmProj = -1; + public int uMcInvMvmProj = -1; + + public int uStartFadeBlockDistance = -1; + public int uEndFadeBlockDistance = -1; + public int uMaxLevelHeight = -1; + + public int uOnlyRenderLods = -1; + + + + //=============// + // constructor // + //=============// + + public VanillaFadeShader() { } + + @Override + public void onInit() + { + this.shader = new ShaderProgram( + "shaders/quadApply.vert", + "shaders/fade/vanillaFade.frag", + "vPosition" + ); + + // all uniforms should be tryGet... + // because disabling fade can cause the GLSL to optimize out most (if not all) uniforms + + // near fade + this.uDhInvMvmProj = this.shader.tryGetUniformLocation("uDhInvMvmProj"); + this.uMcInvMvmProj = this.shader.tryGetUniformLocation("uMcInvMvmProj"); + + this.uMcDepthTexture = this.shader.tryGetUniformLocation("uMcDepthTexture"); + this.uDhDepthTexture = this.shader.tryGetUniformLocation("uDhDepthTexture"); + this.uCombinedMcDhColorTexture = this.shader.tryGetUniformLocation("uCombinedMcDhColorTexture"); + this.uDhColorTexture = this.shader.tryGetUniformLocation("uDhColorTexture"); + + this.uStartFadeBlockDistance = this.shader.tryGetUniformLocation("uStartFadeBlockDistance"); + this.uEndFadeBlockDistance = this.shader.tryGetUniformLocation("uEndFadeBlockDistance"); + this.uMaxLevelHeight = this.shader.tryGetUniformLocation("uMaxLevelHeight"); + + this.uOnlyRenderLods = this.shader.tryGetUniformLocation("uOnlyRenderLods"); + + } + + + + //=============// + // render prep // + //=============// + + @Override + protected void onApplyUniforms(float partialTicks) + { + this.shader.setUniform(this.uMcInvMvmProj, this.inverseMcMvmProjMatrix); + this.shader.setUniform(this.uDhInvMvmProj, this.inverseDhMvmProjMatrix); + + + float dhNearClipDistance = RenderUtil.getNearClipPlaneInBlocks(); + // this added value prevents the near clip plane and discard circle from touching, which looks bad + dhNearClipDistance += 16f; + + // measured in blocks + // these multipliers in James' tests should provide a fairly smooth transition + // without having underdraw issues + float fadeStartDistance = dhNearClipDistance * 1.5f; + float fadeEndDistance = dhNearClipDistance * 1.9f; + + this.shader.setUniform(this.uStartFadeBlockDistance, fadeStartDistance); + this.shader.setUniform(this.uEndFadeBlockDistance, fadeEndDistance); + + this.shader.setUniform(this.uMaxLevelHeight, this.levelMaxHeight); + + this.shader.setUniform(this.uOnlyRenderLods, Config.Client.Advanced.Debugging.lodOnlyMode.get()); + } + + public void setProjectionMatrix(Mat4f mcModelViewMatrix, Mat4f mcProjectionMatrix) + { + Mat4f inverseMcModelViewProjectionMatrix = new Mat4f(mcProjectionMatrix); + inverseMcModelViewProjectionMatrix.multiply(mcModelViewMatrix); + inverseMcModelViewProjectionMatrix.invert(); + this.inverseMcMvmProjMatrix = inverseMcModelViewProjectionMatrix; + + + Mat4f dhProjectionMatrix = RenderUtil.createLodProjectionMatrix(mcProjectionMatrix); + Mat4f dhModelViewMatrix = RenderUtil.createLodModelViewMatrix(mcModelViewMatrix); + + Mat4f inverseDhModelViewProjectionMatrix = new Mat4f(dhProjectionMatrix); + inverseDhModelViewProjectionMatrix.multiply(dhModelViewMatrix); + inverseDhModelViewProjectionMatrix.invert(); + this.inverseDhMvmProjMatrix = inverseDhModelViewProjectionMatrix; + } + public void setLevelMaxHeight(int levelMaxHeight) { this.levelMaxHeight = levelMaxHeight; } + + + + //========// + // render // + //========// + + @Override + protected void onRender() + { + int depthTextureId = BlazeLodRenderer.INSTANCE.getActiveDepthTextureId(); + int colorTextureId = BlazeLodRenderer.INSTANCE.getActiveColorTextureId(); + + if (depthTextureId == -1 + || colorTextureId == -1) + { + // the renderer is currently being re-built and/or inactive, + // we don't need to/can't render fading + return; + } + + + + GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, this.frameBuffer); + GLMC.disableScissorTest(); + GLMC.disableDepthTest(); + GLMC.disableBlend(); + + GLMC.glActiveTexture(GL32.GL_TEXTURE0); + GLMC.glBindTexture(MC_RENDER.getDepthTextureId()); + GL32.glUniform1i(this.uMcDepthTexture, 0); + + GLMC.glActiveTexture(GL32.GL_TEXTURE1); + GLMC.glBindTexture(depthTextureId); + GL32.glUniform1i(this.uDhDepthTexture, 1); + + GLMC.glActiveTexture(GL32.GL_TEXTURE2); + GLMC.glBindTexture(MC_RENDER.getColorTextureId()); + GL32.glUniform1i(this.uCombinedMcDhColorTexture, 2); + + GLMC.glActiveTexture(GL32.GL_TEXTURE3); + GLMC.glBindTexture(colorTextureId); + GL32.glUniform1i(this.uDhColorTexture, 3); + + + ScreenQuad.INSTANCE.render(); + } + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/fog/FogApplyShader.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/fog/FogApplyShader.java new file mode 100644 index 000000000..58fca26d3 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/fog/FogApplyShader.java @@ -0,0 +1,118 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl.postProcessing.fog; + +import com.seibel.distanthorizons.common.render.nativeGl.glObject.shader.ShaderProgram; +import com.seibel.distanthorizons.common.render.nativeGl.postProcessing.ScreenQuad; +import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftGLWrapper; +import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; +import com.seibel.distanthorizons.core.render.renderer.BlazeLodRenderer; +import com.seibel.distanthorizons.common.render.nativeGl.util.AbstractShaderRenderer; +import org.lwjgl.opengl.GL32; + +/** + * Draws the Fog texture onto DH's FrameBuffer.

+ * + * See Also:
+ * {@link FogRenderer} - Parent to this shader.
+ * {@link FogShader} - draws the Fog texture.
+ */ +public class FogApplyShader extends AbstractShaderRenderer +{ + public static FogApplyShader INSTANCE = new FogApplyShader(); + + private static final MinecraftGLWrapper GLMC = MinecraftGLWrapper.INSTANCE; + + + public int fogTexture; + + // uniforms + public int colorTextureUniform; + public int depthTextureUniform; + + + + //=============// + // constructor // + //=============// + + @Override + public void onInit() + { + this.shader = new ShaderProgram( + "shaders/quadApply.vert", + "shaders/fog/apply.frag", + "vPosition" + ); + + // uniform setup + this.colorTextureUniform = this.shader.getUniformLocation("uColorTexture"); + this.depthTextureUniform = this.shader.getUniformLocation("uDepthTexture"); + + } + + + + //=============// + // render prep // + //=============// + + @Override + protected void onApplyUniforms(float partialTicks) + { + GLMC.glActiveTexture(GL32.GL_TEXTURE0); + GLMC.glBindTexture(this.fogTexture); + GL32.glUniform1i(this.colorTextureUniform, 0); + + GLMC.glActiveTexture(GL32.GL_TEXTURE1); + GLMC.glBindTexture(BlazeLodRenderer.INSTANCE.getActiveDepthTextureId()); + GL32.glUniform1i(this.depthTextureUniform, 1); + + } + + + + //========// + // render // + //========// + + @Override + protected void onRender() + { + GLMC.enableBlend(); + GL32.glBlendEquation(GL32.GL_FUNC_ADD); + GLMC.glBlendFuncSeparate(GL32.GL_SRC_ALPHA, GL32.GL_ONE_MINUS_SRC_ALPHA, GL32.GL_ONE, GL32.GL_ONE_MINUS_SRC_ALPHA); + + // Depth testing must be disabled otherwise this application shader won't apply anything. + // setting this isn't necessary in vanilla, but some mods may change this, requiring it to be set manually, + // it should be automatically restored after rendering is complete. + GLMC.disableDepthTest(); + + + // apply the rendered Fog to DH's framebuffer + GLMC.glBindFramebuffer(GL32.GL_READ_FRAMEBUFFER, FogShader.INSTANCE.frameBuffer); + GLMC.glBindFramebuffer(GL32.GL_DRAW_FRAMEBUFFER, BlazeLodRenderer.INSTANCE.getActiveFramebufferId()); + + ScreenQuad.INSTANCE.render(); + + GLMC.glBindFramebuffer(GL32.GL_READ_FRAMEBUFFER, 0); + } + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/fog/FogRenderer.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/fog/FogRenderer.java new file mode 100644 index 000000000..04079baf7 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/fog/FogRenderer.java @@ -0,0 +1,140 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl.postProcessing.fog; + +import com.seibel.distanthorizons.common.render.nativeGl.glObject.GLState; +import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftGLWrapper; +import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; +import com.seibel.distanthorizons.core.util.math.Mat4f; +import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; +import org.lwjgl.opengl.GL32; +import org.lwjgl.opengl.GL43C; + +import java.nio.ByteBuffer; + +/** + * Handles adding SSAO via {@link FogShader} and {@link FogApplyShader}.

+ * + * {@link FogShader} - draws the Fog to a texture.
+ * {@link FogApplyShader} - draws the Fog texture to DH's FrameBuffer.
+ */ +public class FogRenderer +{ + public static FogRenderer INSTANCE = new FogRenderer(); + + private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); + private static final MinecraftGLWrapper GLMC = MinecraftGLWrapper.INSTANCE; + + + private boolean init = false; + + private int width = -1; + private int height = -1; + private int fogFramebuffer = -1; + + private int fogTexture = -1; + + + + //=============// + // constructor // + //=============// + + private FogRenderer() { } + + public void init() + { + if (this.init) return; + this.init = true; + + FogShader.INSTANCE.init(); + FogApplyShader.INSTANCE.init(); + } + + private void createFramebuffer(int width, int height) + { + if (this.fogFramebuffer != -1) + { + GL32.glDeleteFramebuffers(this.fogFramebuffer); + this.fogFramebuffer = -1; + } + + if (this.fogTexture != -1) + { + GLMC.glDeleteTextures(this.fogTexture); + this.fogTexture = -1; + } + + this.fogFramebuffer = GL32.glGenFramebuffers(); + GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, this.fogFramebuffer); + + this.fogTexture = GLMC.glGenTextures(); + { + GLMC.glBindTexture(this.fogTexture); + GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, GL32.GL_RGBA16, width, height, 0, GL32.GL_RGBA, GL32.GL_UNSIGNED_SHORT_4_4_4_4, (ByteBuffer) null); + GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_LINEAR); + GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_LINEAR); + GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_TEXTURE_2D, this.fogTexture, 0); + + // disable mip-mapping since DH is just going to draw straight to the screen + GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_BASE_LEVEL, 0); + GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_MAX_LEVEL, 0); + } + } + + + + //========// + // render // + //========// + + public void render(Mat4f modelViewProjectionMatrix, float partialTicks) + { + // GLState needed in MC 1.16.5 probably due to MC not manually setting each GL state they need before the next rendering step + try (GLState state = new GLState()) + { + this.init(); + + // resize the framebuffer if necessary + int width = MC_RENDER.getTargetFramebufferViewportWidth(); + int height = MC_RENDER.getTargetFramebufferViewportHeight(); + if (this.width != width || this.height != height) + { + this.width = width; + this.height = height; + this.createFramebuffer(width, height); + } + + FogShader.INSTANCE.frameBuffer = this.fogFramebuffer; + FogShader.INSTANCE.setProjectionMatrix(modelViewProjectionMatrix); + FogShader.INSTANCE.render(partialTicks); + + FogApplyShader.INSTANCE.fogTexture = this.fogTexture; + FogApplyShader.INSTANCE.render(partialTicks); + } + } + + public void free() + { + FogShader.INSTANCE.free(); + FogApplyShader.INSTANCE.free(); + } + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/fog/FogSettings.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/fog/FogSettings.java new file mode 100644 index 000000000..714eab500 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/fog/FogSettings.java @@ -0,0 +1,72 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl.postProcessing.fog; + +import com.seibel.distanthorizons.api.enums.rendering.EDhApiFogFalloff; + +import java.util.Objects; + +/** + * Contains all configurable options related to fog. + * + * @version 2022-4-13 + */ +public class FogSettings +{ + /** a FogSetting object with 0 for every value */ + public static final FogSettings EMPTY = new FogSettings(0, 0, 0, 0, 0, EDhApiFogFalloff.LINEAR); + + + public final double start; + public final double end; + public final double min; + public final double max; + public final double density; + public final EDhApiFogFalloff fogType; + + public FogSettings(double start, double end, double min, double max, double density, EDhApiFogFalloff fogType) + { + this.start = start; + this.end = end; + this.min = min; + this.max = max; + this.density = density; + this.fogType = fogType; + } + + @Override + public boolean equals(Object o) + { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + FogSettings that = (FogSettings) o; + return Double.compare(that.start, start) == 0 && Double.compare(that.end, end) == 0 && Double.compare(that.min, min) == 0 && Double.compare(that.max, max) == 0 && Double.compare(that.density, density) == 0 && fogType == that.fogType; + } + + @Override + public int hashCode() + { + return Objects.hash(start, end, min, max, density, fogType); + } + + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/fog/FogShader.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/fog/FogShader.java new file mode 100644 index 000000000..0ac0b00ab --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/fog/FogShader.java @@ -0,0 +1,287 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl.postProcessing.fog; + +import com.seibel.distanthorizons.api.enums.rendering.EDhApiFogColorMode; +import com.seibel.distanthorizons.api.enums.rendering.EDhApiHeightFogDirection; +import com.seibel.distanthorizons.api.enums.rendering.EDhApiHeightFogMixMode; +import com.seibel.distanthorizons.common.render.nativeGl.glObject.shader.ShaderProgram; +import com.seibel.distanthorizons.common.render.nativeGl.postProcessing.ScreenQuad; +import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftGLWrapper; +import com.seibel.distanthorizons.core.config.Config; +import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; +import com.seibel.distanthorizons.core.render.renderer.BlazeLodRenderer; +import com.seibel.distanthorizons.common.render.nativeGl.util.AbstractShaderRenderer; +import com.seibel.distanthorizons.core.util.LodUtil; +import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; +import com.seibel.distanthorizons.core.util.math.Mat4f; +import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; +import org.lwjgl.opengl.GL32; + +import java.awt.*; + +public class FogShader extends AbstractShaderRenderer +{ + public static final FogShader INSTANCE = new FogShader(); + + private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); + private static final MinecraftGLWrapper GLMC = MinecraftGLWrapper.INSTANCE; + private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); + + + + public int frameBuffer; + + private Mat4f inverseMvmProjMatrix; + + + + //==========// + // Uniforms // + //==========// + + public int uDepthMap; + /** Inverted Model View Projection matrix */ + public int uInvMvmProj; + + // fog uniforms + public int uFogColor; + public int uFogScale; + public int uFogVerticalScale; + public int uFogDebugMode; + public int uFogFalloffType; + + // far fog + public int uFarFogStart; + public int uFarFogLength; + public int uFarFogMin; + public int uFarFogRange; + public int uFarFogDensity; + + // height fog + public int uHeightFogStart; + public int uHeightFogLength; + public int uHeightFogMin; + public int uHeightFogRange; + public int uHeightFogDensity; + + public int uHeightFogEnabled; + public int uHeightFogFalloffType; + public int uHeightBasedOnCamera; + public int uHeightFogBaseHeight; + public int uHeightFogAppliesUp; + public int uHeightFogAppliesDown; + public int uUseSphericalFog; + public int uHeightFogMixingMode; + public int uCameraBlockYPos; + + + + //=============// + // constructor // + //=============// + + public FogShader() { } + + @Override + public void onInit() + { + this.shader = new ShaderProgram( + "shaders/quadApply.vert", + "shaders/fog/fog.frag", + "vPosition" + ); + + // all uniforms should be tryGet... + // because disabling fog can cause the GLSL to optimize out most (if not all) uniforms + + this.uDepthMap = this.shader.getUniformLocation("uDepthMap"); + this.uInvMvmProj = this.shader.getUniformLocation("uInvMvmProj"); + + // Fog uniforms + this.uFogScale = this.shader.getUniformLocation("uFogScale"); + this.uFogVerticalScale = this.shader.getUniformLocation("uFogVerticalScale"); + this.uFogColor = this.shader.getUniformLocation("uFogColor"); + this.uFogDebugMode = this.shader.getUniformLocation("uFogDebugMode"); + this.uFogFalloffType = this.shader.getUniformLocation("uFogFalloffType"); + + // fog config + this.uFarFogStart = this.shader.getUniformLocation("uFarFogStart"); + this.uFarFogLength = this.shader.getUniformLocation("uFarFogLength"); + this.uFarFogMin = this.shader.getUniformLocation("uFarFogMin"); + this.uFarFogRange = this.shader.getUniformLocation("uFarFogRange"); + this.uFarFogDensity = this.shader.getUniformLocation("uFarFogDensity"); + + // height fog + this.uHeightFogStart = this.shader.getUniformLocation("uHeightFogStart"); + this.uHeightFogLength = this.shader.getUniformLocation("uHeightFogLength"); + this.uHeightFogMin = this.shader.getUniformLocation("uHeightFogMin"); + this.uHeightFogRange = this.shader.getUniformLocation("uHeightFogRange"); + this.uHeightFogDensity = this.shader.getUniformLocation("uHeightFogDensity"); + + this.uHeightFogEnabled = this.shader.getUniformLocation("uHeightFogEnabled"); + this.uHeightFogFalloffType = this.shader.getUniformLocation("uHeightFogFalloffType"); + this.uHeightBasedOnCamera = this.shader.getUniformLocation("uHeightBasedOnCamera"); + this.uHeightFogBaseHeight = this.shader.getUniformLocation("uHeightFogBaseHeight"); + this.uHeightFogAppliesUp = this.shader.getUniformLocation("uHeightFogAppliesUp"); + this.uHeightFogAppliesDown = this.shader.getUniformLocation("uHeightFogAppliesDown"); + this.uUseSphericalFog = this.shader.getUniformLocation("uUseSphericalFog"); + this.uHeightFogMixingMode = this.shader.getUniformLocation("uHeightFogMixingMode"); + this.uCameraBlockYPos = this.shader.getUniformLocation("uCameraBlockYPos"); + + } + + + + //=============// + // render prep // + //=============// + + @Override + protected void onApplyUniforms(float partialTicks) + { + int lodDrawDistance = Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistanceRadius.get() * LodUtil.CHUNK_WIDTH; + + + + if (this.inverseMvmProjMatrix != null) + { + this.shader.setUniform(this.uInvMvmProj, this.inverseMvmProjMatrix); + } + + + // Fog uniforms + this.shader.setUniform(this.uFogColor, this.getFogColor(partialTicks)); + this.shader.setUniform(this.uFogScale, 1.f / lodDrawDistance); + this.shader.setUniform(this.uFogVerticalScale, 1.f / MC.getWrappedClientLevel().getMaxHeight()); + // only used for debugging + this.shader.setUniform(this.uFogDebugMode, 0); // 1 = render everything with fog color // 7 = use debug rendering + this.shader.setUniform(this.uFogFalloffType, Config.Client.Advanced.Graphics.Fog.farFogFalloff.get().value); + + + // fog config + float farFogStart = Config.Client.Advanced.Graphics.Fog.farFogStart.get(); + float farFogEnd = Config.Client.Advanced.Graphics.Fog.farFogEnd.get(); + float farFogMin = Config.Client.Advanced.Graphics.Fog.farFogMin.get(); + float farFogMax = Config.Client.Advanced.Graphics.Fog.farFogMax.get(); + float farFogDensity = Config.Client.Advanced.Graphics.Fog.farFogDensity.get(); + + // override fog if underwater + if (MC_RENDER.isFogStateSpecial()) + { + // hide everything behind fog + farFogStart = 0.0f; + farFogEnd = 0.0f; + } + + this.shader.setUniform(this.uFarFogStart, farFogStart); + this.shader.setUniform(this.uFarFogLength, farFogEnd - farFogStart); + this.shader.setUniform(this.uFarFogMin, farFogMin); + this.shader.setUniform(this.uFarFogRange, farFogMax - farFogMin); + this.shader.setUniform(this.uFarFogDensity, farFogDensity); + + + // height config + EDhApiHeightFogMixMode heightFogMixingMode = Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogMixMode.get(); + boolean heightFogEnabled = heightFogMixingMode != EDhApiHeightFogMixMode.SPHERICAL && heightFogMixingMode != EDhApiHeightFogMixMode.CYLINDRICAL; + boolean useSphericalFog = heightFogMixingMode == EDhApiHeightFogMixMode.SPHERICAL; + EDhApiHeightFogDirection heightFogCameraDirection = Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogDirection.get(); + + float heightFogStart = Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogStart.get(); + float heightFogEnd = Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogEnd.get(); + float heightFogMin = Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogMin.get(); + float heightFogMax = Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogMax.get(); + float heightFogDensity = Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogDensity.get(); + + this.shader.setUniform(this.uHeightFogStart, heightFogStart); + this.shader.setUniform(this.uHeightFogLength, heightFogEnd - heightFogStart); + this.shader.setUniform(this.uHeightFogMin, heightFogMin); + this.shader.setUniform(this.uHeightFogRange, heightFogMax - heightFogMin); + this.shader.setUniform(this.uHeightFogDensity, heightFogDensity); + + + this.shader.setUniform(this.uHeightFogEnabled, heightFogEnabled); + this.shader.setUniform(this.uHeightFogFalloffType, Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogFalloff.get().value); + this.shader.setUniform(this.uHeightFogBaseHeight, Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogBaseHeight.get()); + this.shader.setUniform(this.uHeightBasedOnCamera, heightFogCameraDirection.basedOnCamera); + this.shader.setUniform(this.uHeightFogAppliesUp, heightFogCameraDirection.fogAppliesUp); + this.shader.setUniform(this.uHeightFogAppliesDown, heightFogCameraDirection.fogAppliesDown); + this.shader.setUniform(this.uUseSphericalFog, useSphericalFog); + this.shader.setUniform(this.uHeightFogMixingMode, heightFogMixingMode.value); + this.shader.setUniform(this.uCameraBlockYPos, (float)MC_RENDER.getCameraExactPosition().y); + + } + private Color getFogColor(float partialTicks) + { + Color fogColor; + + if (Config.Client.Advanced.Graphics.Fog.colorMode.get() == EDhApiFogColorMode.USE_SKY_COLOR) + { + fogColor = MC_RENDER.getSkyColor(); + } + else + { + fogColor = MC_RENDER.getFogColor(partialTicks); + } + + return fogColor; + } + + public void setProjectionMatrix(Mat4f modelViewProjectionMatrix) + { + this.inverseMvmProjMatrix = new Mat4f(modelViewProjectionMatrix); + this.inverseMvmProjMatrix.invert(); + } + + + + //========// + // render // + //========// + + @Override + protected void onRender() + { + GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, this.frameBuffer); + GLMC.disableScissorTest(); + GLMC.disableDepthTest(); + GLMC.disableBlend(); + + GLMC.glActiveTexture(GL32.GL_TEXTURE0); + GLMC.glBindTexture(BlazeLodRenderer.INSTANCE.getActiveDepthTextureId()); + GL32.glUniform1i(this.uDepthMap, 0); + + // this is necessary for MC 1.16 (IE Legacy OpenGL) + // otherwise the framebuffer isn't cleared correctly and the fog smears across the screen + if (MC_RENDER.runningLegacyOpenGL()) + { + // in another part of the DH code we set the fog color to opaque, here it needs to be transparent + float[] clearColorValues = new float[4]; + GL32.glGetFloatv(GL32.GL_COLOR_CLEAR_VALUE, clearColorValues); + GL32.glClearColor(clearColorValues[0], clearColorValues[1], clearColorValues[2], 0.0f); + + GL32.glClear(GL32.GL_COLOR_BUFFER_BIT | GL32.GL_DEPTH_BUFFER_BIT); + } + + + ScreenQuad.INSTANCE.render(); + } + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/ssao/SSAOApplyShader.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/ssao/SSAOApplyShader.java new file mode 100644 index 000000000..0da10356a --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/ssao/SSAOApplyShader.java @@ -0,0 +1,145 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl.postProcessing.ssao; + +import com.seibel.distanthorizons.common.render.nativeGl.glObject.shader.ShaderProgram; +import com.seibel.distanthorizons.common.render.nativeGl.postProcessing.ScreenQuad; +import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftGLWrapper; +import com.seibel.distanthorizons.core.render.renderer.BlazeLodRenderer; +import com.seibel.distanthorizons.common.render.nativeGl.util.AbstractShaderRenderer; +import com.seibel.distanthorizons.core.util.RenderUtil; +import org.lwjgl.opengl.GL32; + +/** + * Draws the SSAO texture onto DH's FrameBuffer.

+ * + * See Also:
+ * {@link SSAORenderer} - Parent to this shader.
+ * {@link SSAOShader} - draws the SSAO texture.
+ */ +public class SSAOApplyShader extends AbstractShaderRenderer +{ + public static SSAOApplyShader INSTANCE = new SSAOApplyShader(); + + private static final MinecraftGLWrapper GLMC = MinecraftGLWrapper.INSTANCE; + + + public int ssaoTexture; + + // uniforms + public int gSSAOMapUniform; + public int gDepthMapUniform; + public int gViewSizeUniform; + public int gBlurRadiusUniform; + public int gNearUniform; + public int gFarUniform; + + + + //=============// + // constructor // + //=============// + + @Override + public void onInit() + { + this.shader = new ShaderProgram( + "shaders/quadApply.vert", + "shaders/ssao/apply.frag", + "vPosition" + ); + + // uniform setup + this.gSSAOMapUniform = this.shader.getUniformLocation("gSSAOMap"); + this.gDepthMapUniform = this.shader.getUniformLocation("gDepthMap"); + this.gViewSizeUniform = this.shader.tryGetUniformLocation("gViewSize"); + this.gBlurRadiusUniform = this.shader.tryGetUniformLocation("gBlurRadius"); + this.gNearUniform = this.shader.tryGetUniformLocation("gNear"); + this.gFarUniform = this.shader.tryGetUniformLocation("gFar"); + } + + + + //=============// + // render prep // + //=============// + + @Override + protected void onApplyUniforms(float partialTicks) + { + GLMC.glActiveTexture(GL32.GL_TEXTURE0); + GLMC.glBindTexture(BlazeLodRenderer.INSTANCE.getActiveDepthTextureId()); + GL32.glUniform1i(this.gDepthMapUniform, 0); + + GLMC.glActiveTexture(GL32.GL_TEXTURE1); + GLMC.glBindTexture(this.ssaoTexture); + GL32.glUniform1i(this.gSSAOMapUniform, 1); + + GL32.glUniform1i(this.gBlurRadiusUniform, 2); + + if (this.gViewSizeUniform >= 0) + { + GL32.glUniform2f(this.gViewSizeUniform, + MC_RENDER.getTargetFramebufferViewportWidth(), + MC_RENDER.getTargetFramebufferViewportHeight()); + } + + if (this.gNearUniform >= 0) + { + GL32.glUniform1f(this.gNearUniform, + RenderUtil.getNearClipPlaneInBlocks()); + } + + if (this.gFarUniform >= 0) + { + float farClipPlane = RenderUtil.getFarClipPlaneDistanceInBlocks(); + GL32.glUniform1f(this.gFarUniform, farClipPlane); + } + } + + + + //========// + // render // + //========// + + @Override + protected void onRender() + { + GLMC.enableBlend(); + GL32.glBlendEquation(GL32.GL_FUNC_ADD); + GLMC.glBlendFuncSeparate(GL32.GL_ZERO, GL32.GL_SRC_ALPHA, GL32.GL_ZERO, GL32.GL_ONE); + + // Depth testing must be disabled otherwise this application shader won't apply anything. + // setting this isn't necessary in vanilla, but some mods may change this, requiring it to be set manually, + // it should be automatically restored after rendering is complete. + GLMC.disableDepthTest(); + + // apply the rendered SSAO to the LODs + GLMC.glBindFramebuffer(GL32.GL_READ_FRAMEBUFFER, SSAOShader.INSTANCE.frameBuffer); + GLMC.glBindFramebuffer(GL32.GL_DRAW_FRAMEBUFFER, BlazeLodRenderer.INSTANCE.getActiveFramebufferId()); + + + ScreenQuad.INSTANCE.render(); + } + + + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/ssao/SSAORenderer.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/ssao/SSAORenderer.java new file mode 100644 index 000000000..88d3172c7 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/ssao/SSAORenderer.java @@ -0,0 +1,140 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl.postProcessing.ssao; + +import com.seibel.distanthorizons.common.render.nativeGl.glObject.GLState; +import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftGLWrapper; +import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; +import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; +import com.seibel.distanthorizons.core.util.math.Mat4f; +import org.lwjgl.opengl.GL32; +import org.lwjgl.opengl.GL43C; + +import java.nio.ByteBuffer; + +/** + * Handles adding SSAO via {@link SSAOShader} and {@link SSAOApplyShader}.

+ * + * {@link SSAOShader} - draws the SSAO to a texture.
+ * {@link SSAOApplyShader} - draws the SSAO texture to DH's FrameBuffer.
+ */ +public class SSAORenderer +{ + public static SSAORenderer INSTANCE = new SSAORenderer(); + + private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); + private static final MinecraftGLWrapper GLMC = MinecraftGLWrapper.INSTANCE; + + + private boolean init = false; + + private int width = -1; + private int height = -1; + private int ssaoFramebuffer = -1; + + private int ssaoTexture = -1; + + + + //=============// + // constructor // + //=============// + + private SSAORenderer() { } + + public void init() + { + if (this.init) return; + this.init = true; + + SSAOShader.INSTANCE.init(); + SSAOApplyShader.INSTANCE.init(); + } + + private void createFramebuffer(int width, int height) + { + if (this.ssaoFramebuffer != -1) + { + GL32.glDeleteFramebuffers(this.ssaoFramebuffer); + this.ssaoFramebuffer = -1; + } + + if (this.ssaoTexture != -1) + { + GLMC.glDeleteTextures(this.ssaoTexture); + this.ssaoTexture = -1; + } + + this.ssaoFramebuffer = GL32.glGenFramebuffers(); + GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, this.ssaoFramebuffer); + + this.ssaoTexture = GLMC.glGenTextures(); + { + GLMC.glBindTexture(this.ssaoTexture); + GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, GL32.GL_R16F, width, height, 0, GL32.GL_RED, GL32.GL_HALF_FLOAT, (ByteBuffer) null); + GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_LINEAR); + GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_LINEAR); + + // disable mip-mapping since DH is just going to draw straight to the screen + GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_BASE_LEVEL, 0); + GL43C.glTexParameteri(GL43C.GL_TEXTURE_2D, GL43C.GL_TEXTURE_MAX_LEVEL, 0); + } + + GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_TEXTURE_2D, this.ssaoTexture, 0); + } + + + + //========// + // render // + //========// + + public void render(Mat4f projectionMatrix, float partialTicks) + { + try(GLState state = new GLState()) + { + this.init(); + + // resize the framebuffer if necessary + int width = MC_RENDER.getTargetFramebufferViewportWidth(); + int height = MC_RENDER.getTargetFramebufferViewportHeight(); + if (this.width != width || this.height != height) + { + this.width = width; + this.height = height; + this.createFramebuffer(width, height); + } + + SSAOShader.INSTANCE.frameBuffer = this.ssaoFramebuffer; + SSAOShader.INSTANCE.setProjectionMatrix(projectionMatrix); + SSAOShader.INSTANCE.render(partialTicks); + + SSAOApplyShader.INSTANCE.ssaoTexture = this.ssaoTexture; + SSAOApplyShader.INSTANCE.render(partialTicks); + } + } + + public void free() + { + SSAOShader.INSTANCE.free(); + SSAOApplyShader.INSTANCE.free(); + } + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/ssao/SSAOShader.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/ssao/SSAOShader.java new file mode 100644 index 000000000..36e285d24 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/postProcessing/ssao/SSAOShader.java @@ -0,0 +1,142 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl.postProcessing.ssao; + +import com.seibel.distanthorizons.common.render.nativeGl.glObject.shader.ShaderProgram; +import com.seibel.distanthorizons.common.render.nativeGl.postProcessing.ScreenQuad; +import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftGLWrapper; +import com.seibel.distanthorizons.core.render.renderer.BlazeLodRenderer; +import com.seibel.distanthorizons.common.render.nativeGl.util.AbstractShaderRenderer; +import com.seibel.distanthorizons.core.util.math.Mat4f; +import org.lwjgl.opengl.GL32; + +/** + * Draws the SSAO to a texture.

+ * + * See Also:
+ * {@link SSAORenderer} - Parent to this shader.
+ * {@link SSAOApplyShader} - draws the SSAO texture to DH's FrameBuffer.
+ */ +public class SSAOShader extends AbstractShaderRenderer +{ + public static SSAOShader INSTANCE = new SSAOShader(); + + private static final MinecraftGLWrapper GLMC = MinecraftGLWrapper.INSTANCE; + + + public int frameBuffer; + + private Mat4f projection; + private Mat4f invertedProjection; + + + // uniforms + public int uProj; + public int uInvProj; + public int uSampleCount; + public int uRadius; + public int uStrength; + public int uMinLight; + public int uBias; + public int uDepthMap; + public int uFadeDistanceInBlocks; + + + + //=============// + // constructor // + //=============// + + @Override + public void onInit() + { + this.shader = new ShaderProgram( + "shaders/quadApply.vert", + "shaders/ssao/ao.frag", + "vPosition" + ); + + // uniform setup + this.uProj = this.shader.getUniformLocation("uProj"); + this.uInvProj = this.shader.getUniformLocation("uInvProj"); + this.uSampleCount = this.shader.getUniformLocation("uSampleCount"); + this.uRadius = this.shader.getUniformLocation("uRadius"); + this.uStrength = this.shader.getUniformLocation("uStrength"); + this.uMinLight = this.shader.getUniformLocation("uMinLight"); + this.uBias = this.shader.getUniformLocation("uBias"); + this.uDepthMap = this.shader.getUniformLocation("uDepthMap"); + this.uFadeDistanceInBlocks = this.shader.getUniformLocation("uFadeDistanceInBlocks"); + } + + + + //=============// + // render prep // + //=============// + + public void setProjectionMatrix(Mat4f projectionMatrix) + { + this.projection = projectionMatrix; + + this.invertedProjection = new Mat4f(projectionMatrix); + this.invertedProjection.invert(); + } + + @Override + protected void onApplyUniforms(float partialTicks) + { + this.shader.setUniform(this.uProj, this.projection); + + this.shader.setUniform(this.uInvProj, this.invertedProjection); + + this.shader.setUniform(this.uSampleCount, 6); + this.shader.setUniform(this.uRadius, 4.0f); + this.shader.setUniform(this.uStrength, 0.2f); + this.shader.setUniform(this.uMinLight, 0.25f); + this.shader.setUniform(this.uBias, 0.02f); + this.shader.setUniform(this.uFadeDistanceInBlocks, 1_600.0f); + + GL32.glUniform1i(this.uDepthMap, 0); + + } + + + + //========// + // render // + //========// + + @Override + protected void onRender() + { + GLMC.glBindFramebuffer(GL32.GL_FRAMEBUFFER, this.frameBuffer); + GLMC.disableScissorTest(); + GLMC.disableDepthTest(); + GLMC.disableBlend(); + + GLMC.glActiveTexture(GL32.GL_TEXTURE0); + GLMC.glBindTexture(BlazeLodRenderer.INSTANCE.getActiveDepthTextureId()); + + ScreenQuad.INSTANCE.render(); + } + + + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/test/GlTestRenderer.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/test/GlTestRenderer.java new file mode 100644 index 000000000..8daab7b83 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/test/GlTestRenderer.java @@ -0,0 +1,141 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl.test; + +import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod; +import com.seibel.distanthorizons.common.wrappers.minecraft.MinecraftGLWrapper; +import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; +import com.seibel.distanthorizons.core.logging.DhLogger; +import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import com.seibel.distanthorizons.common.render.nativeGl.glObject.buffer.GLVertexBuffer; +import com.seibel.distanthorizons.common.render.nativeGl.glObject.shader.ShaderProgram; +import com.seibel.distanthorizons.common.render.nativeGl.glObject.vertexAttribute.AbstractVertexAttribute; +import com.seibel.distanthorizons.common.render.nativeGl.glObject.vertexAttribute.VertexPointer; +import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; + +import org.lwjgl.opengl.GL32; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Renders a UV colored quad + * to the center of the screen to confirm DH's + * apply shader is running correctly + */ +public class GlTestRenderer +{ + public static final DhLogger LOGGER = new DhLoggerBuilder().build(); + + private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); + private static final MinecraftGLWrapper GLMC = MinecraftGLWrapper.INSTANCE; + + public static final GlTestRenderer INSTANCE = new GlTestRenderer(); + + // Render a square with uv color + private static final float[] VERTICES = + { + // PosX,Y, ColorR,G,B,A + -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, + 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, + 0.0f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, + }; + + + + ShaderProgram basicShader; + GLVertexBuffer vbo; + AbstractVertexAttribute va; + boolean init = false; + + + + //=============// + // constructor // + //=============// + //region + + private GlTestRenderer() { } + + public void init() + { + if (this.init) + { + return; + } + + LOGGER.info("init"); + this.init = true; + this.va = AbstractVertexAttribute.create(); + this.va.bind(); + // Pos + this.va.setVertexAttribute(0, 0, VertexPointer.addVec2Pointer(false)); + // Color + this.va.setVertexAttribute(0, 1, VertexPointer.addVec4Pointer(false)); + this.va.completeAndCheck(Float.BYTES * 6); + this.basicShader = new ShaderProgram( + "shaders/test/vert.vert", + "shaders/test/frag.frag", + new String[]{"vPosition", "color"}); + + this.createBuffer(); + } + + private void createBuffer() + { + ByteBuffer buffer = ByteBuffer.allocateDirect(VERTICES.length * Float.BYTES); + // Fill buffer with vertices. + buffer.order(ByteOrder.nativeOrder()); + buffer.asFloatBuffer().put(VERTICES); + buffer.rewind(); + + this.vbo = new GLVertexBuffer(false); + this.vbo.bind(); + this.vbo.uploadBuffer(buffer, 4, EDhApiGpuUploadMethod.DATA, VERTICES.length * Float.BYTES); + } + + //endregion + + + + //========// + // render // + //========// + //region + + public void render() + { + this.init(); + + this.basicShader.bind(); + this.va.bind(); + + this.vbo.bind(); + this.va.bindBufferToAllBindingPoints(this.vbo.getId()); + + // Render the square + GL32.glDrawArrays(GL32.GL_TRIANGLE_FAN, 0, 4); + } + + //endregion + + + +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/util/AbstractShaderRenderer.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/util/AbstractShaderRenderer.java new file mode 100644 index 000000000..92e4a1e68 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/util/AbstractShaderRenderer.java @@ -0,0 +1,77 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl.util; + +import com.seibel.distanthorizons.common.render.nativeGl.glObject.shader.ShaderProgram; +import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; +import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; +import org.lwjgl.opengl.GL32; + +public abstract class AbstractShaderRenderer +{ + protected static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); + + + protected ShaderProgram shader; + + protected boolean init = false; + + + protected AbstractShaderRenderer() {} + + public void init() + { + if (this.init) return; + this.init = true; + + this.onInit(); + } + + public void render(float partialTicks) + { + this.init(); + + this.shader.bind(); + + this.onApplyUniforms(partialTicks); + + int width = MC_RENDER.getTargetFramebufferViewportWidth(); + int height = MC_RENDER.getTargetFramebufferViewportHeight(); + GL32.glViewport(0, 0, width, height); + + this.onRender(); + + this.shader.unbind(); + } + + public void free() + { + if (this.shader != null) + { + this.shader.free(); + } + } + + protected void onInit() {} + + protected void onApplyUniforms(float partialTicks) {} + + protected void onRender() {} +} diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/util/vertexFormat/LodVertexFormat.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/util/vertexFormat/LodVertexFormat.java new file mode 100644 index 000000000..509c9e872 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/util/vertexFormat/LodVertexFormat.java @@ -0,0 +1,110 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl.util.vertexFormat; + +import java.util.stream.Collectors; + +import com.google.common.collect.ImmutableList; + +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; + +/** + * This is used to represent a single vertex + * stored in GPU memory, + *

+ * A (almost) exact copy of Minecraft's + * VertexFormat class, several methods + * were commented out since we didn't need them. + * + * @author James Seibel + * @version 12-9-2021 + */ +public class LodVertexFormat +{ + /** the format of data stored in the GPU buffers */ + public static final LodVertexFormat DH_VERTEX_FORMAT = VertexFormats.POSITION_COLOR_BLOCK_LIGHT_SKY_LIGHT_MATERIAL_ID_NORMAL_INDEX; + + + private final ImmutableList elements; + private final IntList offsets = new IntArrayList(); + private final int byteSize; + + public LodVertexFormat(ImmutableList elementList) + { + this.elements = elementList; + int i = 0; + + for (LodVertexFormatElement LodVertexFormatElement : elementList) + { + this.offsets.add(i); + i += LodVertexFormatElement.getByteSize(); + } + + this.byteSize = i; + } + + public int getByteSize() + { + return this.byteSize; + } + + public ImmutableList getElements() + { + return this.elements; + } + + + // Forge added method + public int getOffset(int index) + { + return offsets.getInt(index); + } + + + + @Override + public String toString() { return "format: " + this.elements.size() + " elements: " + this.elements.stream().map(Object::toString).collect(Collectors.joining(" ")); } + + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + else if (obj != null && this.getClass() == obj.getClass()) + { + LodVertexFormat vertexFormat = (LodVertexFormat) obj; + return this.byteSize == vertexFormat.byteSize && this.elements.equals(vertexFormat.elements); + } + else + { + return false; + } + } + + @Override + public int hashCode() { return this.elements.hashCode(); } + + + +} \ No newline at end of file diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/util/vertexFormat/LodVertexFormatElement.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/util/vertexFormat/LodVertexFormatElement.java new file mode 100644 index 000000000..cdc7dc41a --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/util/vertexFormat/LodVertexFormatElement.java @@ -0,0 +1,168 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl.util.vertexFormat; + +import org.lwjgl.opengl.GL32; + +/** + * This object is used to build LodVertexFormats. + *

+ * A (almost) exact copy of Minecraft's + * VertexFormatElement class.
+ * A number of things were removed from the original + * object since we didn't need them, specifically "usage". + * + * @author James Seibel + * @version 11-13-2021 + */ +public class LodVertexFormatElement +{ + private final LodVertexFormatElement.DataType dataType; + /** James isn't sure what index is for */ + private final int index; + private final int count; + private final int byteSize; + private final boolean isPadding; + + public LodVertexFormatElement(int newIndex, LodVertexFormatElement.DataType newType, int newCount, boolean isPadding) + { + this.dataType = newType; + this.index = newIndex; + this.count = newCount; + this.byteSize = newType.getSize() * this.count; + this.isPadding = isPadding; + } + + public final boolean getIsPadding() + { + return isPadding; + } + + public final LodVertexFormatElement.DataType getType() + { + return this.dataType; + } + + public final int getIndex() + { + return this.index; + } + + public final int getByteSize() + { + return this.byteSize; + } + + // added by Forge + public int getElementCount() + { + return count; + } + + + + public enum DataType + { + FLOAT(4, "Float", GL32.GL_FLOAT), + UBYTE(1, "Unsigned Byte", GL32.GL_UNSIGNED_BYTE), + BYTE(1, "Byte", GL32.GL_BYTE), + USHORT(2, "Unsigned Short", GL32.GL_UNSIGNED_SHORT), + SHORT(2, "Short", GL32.GL_SHORT), + UINT(4, "Unsigned Int", GL32.GL_UNSIGNED_INT), + INT(4, "Int", GL32.GL_INT); + + private final int size; + private final String name; + private final int glType; + + DataType(int sizeInBytes, String newName, int openGlDataType) + { + this.size = sizeInBytes; + this.name = newName; + this.glType = openGlDataType; + } + + public int getSize() + { + return this.size; + } + + public String getName() + { + return this.name; + } + + public int getGlType() + { + return this.glType; + } + } + + + + + @Override + public int hashCode() + { + int i = this.dataType.hashCode(); + i = 31 * i + this.index; + return 31 * i + this.count; + } + + @Override + public String toString() + { + return this.count + "," + this.dataType.getName(); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + else if (obj != null && this.getClass() == obj.getClass()) + { + LodVertexFormatElement LodVertexFormatElement = (LodVertexFormatElement) obj; + if (this.count != LodVertexFormatElement.count) + { + return false; + } + else if (this.index != LodVertexFormatElement.index) + { + return false; + } + else if (this.dataType != LodVertexFormatElement.dataType) + { + return false; + } + else + { + return false; + } + } + else + { + return false; + } + } + +} \ No newline at end of file diff --git a/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/util/vertexFormat/VertexFormats.java b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/util/vertexFormat/VertexFormats.java new file mode 100644 index 000000000..dc4e04e64 --- /dev/null +++ b/common/src/main/java/com/seibel/distanthorizons/common/render/nativeGl/util/vertexFormat/VertexFormats.java @@ -0,0 +1,50 @@ +/* + * This file is part of the Distant Horizons mod + * licensed under the GNU LGPL v3 License. + * + * Copyright (C) 2020 James Seibel + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.seibel.distanthorizons.common.render.nativeGl.util.vertexFormat; + +import com.google.common.collect.ImmutableList; + +/** + * A (almost) exact copy of MC's + * DefaultVertexFormats class. + */ +public class VertexFormats +{ + public static final LodVertexFormatElement ELEMENT_POSITION = new LodVertexFormatElement(3, LodVertexFormatElement.DataType.USHORT, 3, false); + public static final LodVertexFormatElement ELEMENT_COLOR = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.UBYTE, 4, false); + public static final LodVertexFormatElement ELEMENT_BYTE_PADDING = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.BYTE, 1, true); + + public static final LodVertexFormatElement ELEMENT_LIGHT = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.UBYTE, 1, false); + public static final LodVertexFormatElement ELEMENT_IRIS_MATERIAL_INDEX = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.BYTE, 1, false); + public static final LodVertexFormatElement ELEMENT_IRIS_NORMAL_INDEX = new LodVertexFormatElement(0, LodVertexFormatElement.DataType.BYTE, 1, false); + + + public static final LodVertexFormat POSITION_COLOR_BLOCK_LIGHT_SKY_LIGHT_MATERIAL_ID_NORMAL_INDEX = new LodVertexFormat(ImmutableList.builder() + .add(ELEMENT_POSITION) + .add(ELEMENT_BYTE_PADDING) + .add(ELEMENT_LIGHT) + .add(ELEMENT_COLOR) + .add(ELEMENT_IRIS_MATERIAL_INDEX) + .add(ELEMENT_IRIS_NORMAL_INDEX) + .add(ELEMENT_BYTE_PADDING) + .add(ELEMENT_BYTE_PADDING) // padding is to make sure the format is a multiple of 4 + .build()); + +}