diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/CloudRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/CloudRenderer.java index 737d5ea177..5917b4e9dc 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/CloudRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/CloudRenderer.java @@ -1,48 +1,74 @@ package net.caffeinemc.mods.sodium.client.render.immediate; import com.mojang.blaze3d.buffers.BufferUsage; +import com.mojang.blaze3d.pipeline.RenderTarget; +import com.mojang.blaze3d.platform.GlStateManager; import com.mojang.blaze3d.platform.NativeImage; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.*; -import net.caffeinemc.mods.sodium.api.util.ColorU8; -import net.caffeinemc.mods.sodium.api.vertex.format.common.ColorVertex; -import net.caffeinemc.mods.sodium.api.vertex.buffer.VertexBufferWriter; import net.caffeinemc.mods.sodium.api.util.ColorABGR; import net.caffeinemc.mods.sodium.api.util.ColorARGB; +import net.caffeinemc.mods.sodium.api.util.ColorU8; +import net.caffeinemc.mods.sodium.api.vertex.buffer.VertexBufferWriter; +import net.caffeinemc.mods.sodium.api.vertex.format.common.ColorVertex; import net.minecraft.client.Camera; import net.minecraft.client.CloudStatus; import net.minecraft.client.Minecraft; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.renderer.*; import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.packs.resources.Resource; -import net.minecraft.server.packs.resources.ResourceManager; import net.minecraft.server.packs.resources.ResourceProvider; import net.minecraft.util.ARGB; import net.minecraft.util.Mth; import net.minecraft.world.phys.Vec3; +import org.apache.commons.lang3.Validate; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.joml.Matrix4f; -import org.joml.Vector3f; import org.joml.Vector4f; import org.lwjgl.opengl.GL32C; import org.lwjgl.system.MemoryStack; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import java.io.IOException; -import java.io.InputStream; import java.util.Objects; public class CloudRenderer { - private static final ResourceLocation CLOUDS_TEXTURE_ID = ResourceLocation.withDefaultNamespace("textures/environment/clouds.png"); - public static final ShaderProgram CLOUDS = new ShaderProgram(ResourceLocation.fromNamespaceAndPath("sodium", "clouds"), DefaultVertexFormat.POSITION_COLOR, ShaderDefines.builder().build()); - - private CloudTextureData textureData; - - private @Nullable CloudRenderer.CloudGeometry cachedGeometry; + private static final Logger LOGGER = LoggerFactory.getLogger("Sodium-CloudRenderer"); + + private static final ShaderProgram CLOUDS_SHADER = new ShaderProgram( + ResourceLocation.fromNamespaceAndPath("sodium", "clouds"), + DefaultVertexFormat.POSITION_COLOR, + ShaderDefines.builder() + .build() + ); + + private static final ResourceLocation CLOUDS_TEXTURE_ID = + ResourceLocation.withDefaultNamespace("textures/environment/clouds.png"); + + private static final float CLOUD_HEIGHT = 4.0f; // The height of the cloud cells + private static final float CLOUD_WIDTH = 12.0f; // The width/length of cloud cells + + // Bitmasks for each cloud face + private static final int FACE_MASK_NEG_Y = 1 << 0; + private static final int FACE_MASK_POS_Y = 1 << 1; + private static final int FACE_MASK_NEG_X = 1 << 2; + private static final int FACE_MASK_POS_X = 1 << 3; + private static final int FACE_MASK_NEG_Z = 1 << 4; + private static final int FACE_MASK_POS_Z = 1 << 5; + + // The brightness of each fac + // The final color of each vertex is: vec4((texel.rgb * brightness), texel.a * 0.8) + private static final int BRIGHTNESS_POS_Y = ColorU8.normalizedFloatToByte(1.0F); // used for +Y + private static final int BRIGHTNESS_NEG_Y = ColorU8.normalizedFloatToByte(0.7F); // used for -Y + private static final int BRIGHTNESS_X_AXIS = ColorU8.normalizedFloatToByte(0.9F); // used for -X and +X + private static final int BRIGHTNESS_Z_AXIS = ColorU8.normalizedFloatToByte(0.8F); // used for -Z and +Z + + private @Nullable CloudTextureData textureData; + private @Nullable CloudGeometry builtGeometry; public CloudRenderer(ResourceProvider resourceProvider) { - this.reloadTextures(resourceProvider); + this.reload(resourceProvider); } public void render(Camera camera, @@ -50,95 +76,130 @@ public void render(Camera camera, Matrix4f projectionMatrix, Matrix4f modelView, float ticks, - float tickDelta, int color) + float tickDelta, + int color) { - float cloudHeight = level.effects().getCloudHeight(); + float height = level.effects() + .getCloudHeight() + 0.33f; // arithmetic against NaN always produces NaN // Vanilla uses NaN height as a way to disable cloud rendering - if (Float.isNaN(cloudHeight)) { + if (Float.isNaN(height)) { return; } - // Skip rendering clouds if texture is completely blank - if (this.textureData.isBlank) { + // Skip rendering clouds if the texture data isn't available + // This can happen if the texture failed to load, or if the texture is completely empty + if (this.textureData == null) { return; } - Vec3 pos = camera.getPosition(); + Vec3 cameraPos = camera.getPosition(); + int renderDistance = getCloudRenderDistance(); + var renderMode = Minecraft.getInstance().options.getCloudsType(); - double cloudTime = (ticks + tickDelta) * 0.03F; - double cloudCenterX = (pos.x() + cloudTime); - double cloudCenterZ = (pos.z()) + 0.33D; + // Translation of the clouds texture in world-space + float worldX = (float) (cameraPos.x + ((ticks + tickDelta) * 0.03F)); + float worldZ = (float) (cameraPos.z + 3.96F); - int cloudDistance = getCloudRenderDistance(); + float textureWidth = this.textureData.width * CLOUD_WIDTH; + float textureHeight = this.textureData.height * CLOUD_WIDTH; + worldX -= Mth.floor(worldX / textureWidth) * textureWidth; + worldZ -= Mth.floor(worldZ / textureHeight) * textureHeight; - int centerCellX = (int) (Math.floor(cloudCenterX / 12.0)); - int centerCellZ = (int) (Math.floor(cloudCenterZ / 12.0)); + // The coordinates of the cloud cell which the camera is within + int cellX = Mth.floor(worldX / CLOUD_WIDTH); + int cellZ = Mth.floor(worldZ / CLOUD_WIDTH); - // -1 if below clouds, +1 if above clouds - var cloudType = Minecraft.getInstance().options.getCloudsType(); - var relativeCloudY = cloudHeight - (float) pos.y() + 0.33F; - int orientation = cloudType == CloudStatus.FANCY ? (int) Math.signum(-relativeCloudY) : 0; - var parameters = new CloudGeometryParameters(centerCellX, centerCellZ, cloudDistance, orientation, cloudType); + // The orientation of the camera relative to the clouds + // This is used to cull back-facing geometry + ViewOrientation orientation; - CloudGeometry geometry = this.cachedGeometry; + if (renderMode == CloudStatus.FANCY) { + orientation = ViewOrientation.getOrientation(cameraPos, height, height + CLOUD_HEIGHT); + } else { + // When fast clouds are used, there is no orientation of faces, since culling is disabled. + // To avoid unnecessary rebuilds, simply mark a null (undefined) orientation. + orientation = null; + } + + var parameters = new CloudGeometryParameters(cellX, cellZ, renderDistance, orientation, renderMode); + + CloudGeometry geometry = this.builtGeometry; + // Re-generate the cached cloud geometry if necessary if (geometry == null || !Objects.equals(geometry.params(), parameters)) { - this.cachedGeometry = (geometry = rebuildGeometry(geometry, parameters, this.textureData)); + this.builtGeometry = (geometry = rebuildGeometry(geometry, parameters, this.textureData)); } VertexBuffer vertexBuffer = geometry.vertexBuffer(); + // The vertex buffer can be empty when there are no clouds to render if (vertexBuffer == null) { return; } - final float translateX = (float) (cloudCenterX - (centerCellX * 12)); - final float translateZ = (float) (cloudCenterZ - (centerCellZ * 12)); + // Apply world->view transform + final float viewPosX = (worldX - (cellX * CLOUD_WIDTH)); + final float viewPosY = (float) cameraPos.y() - height; + final float viewPosZ = (worldZ - (cellZ * CLOUD_WIDTH)); Matrix4f modelViewMatrix = new Matrix4f(modelView); - modelViewMatrix.translate(-translateX, relativeCloudY, -translateZ); + modelViewMatrix.translate(-viewPosX, -viewPosY, -viewPosZ); - final var prevShaderFog = copyShaderFogParameters(RenderSystem.getShaderFog()); + // State setup + final var prevFogParameters = copyShaderFogParameters(RenderSystem.getShaderFog()); + final var flat = geometry.params() + .renderMode() == CloudStatus.FAST; - FogParameters fogParameters = FogRenderer.setupFog(camera, FogRenderer.FogMode.FOG_TERRAIN, new Vector4f(prevShaderFog.red(), prevShaderFog.green(), prevShaderFog.blue(), prevShaderFog.alpha()), cloudDistance * 8, shouldUseWorldFog(level, pos), tickDelta); + FogParameters fogParameters = FogRenderer.setupFog(camera, FogRenderer.FogMode.FOG_TERRAIN, new Vector4f(prevFogParameters.red(), prevFogParameters.green(), prevFogParameters.blue(), prevFogParameters.alpha()), renderDistance * 8, shouldUseWorldFog(level, cameraPos), tickDelta); + + RenderSystem.setShaderColor(ARGB.from8BitChannel(ARGB.red(color)), ARGB.from8BitChannel(ARGB.green(color)), ARGB.from8BitChannel(ARGB.blue(color)), 0.8F); RenderSystem.setShaderFog(fogParameters); - boolean fastClouds = geometry.params().renderMode() == CloudStatus.FAST; + RenderTarget renderTarget = Minecraft.getInstance().levelRenderer.getCloudsTarget(); - if (fastClouds) { - RenderSystem.disableCull(); + if (renderTarget != null) { + renderTarget.bindWrite(false); + } else { + Minecraft.getInstance() + .getMainRenderTarget() + .bindWrite(false); } - RenderSystem.setShaderColor(ARGB.from8BitChannel(ARGB.red(color)), ARGB.from8BitChannel(ARGB.green(color)), ARGB.from8BitChannel(ARGB.blue(color)), 0.8F); + RenderSystem.enableBlend(); + RenderSystem.blendFuncSeparate(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA); - vertexBuffer.bind(); + RenderSystem.setShader(CLOUDS_SHADER); - RenderType.clouds().setupRenderState(); - RenderSystem.depthFunc(GL32C.GL_LESS); + if (flat) { + RenderSystem.disableCull(); + } - RenderSystem.setShader(CLOUDS); + RenderSystem.depthFunc(GL32C.GL_LESS); + // Draw + vertexBuffer.bind(); vertexBuffer.drawWithShader(modelViewMatrix, projectionMatrix, RenderSystem.getShader()); + VertexBuffer.unbind(); + // State teardown RenderSystem.depthFunc(GL32C.GL_LEQUAL); - VertexBuffer.unbind(); - - if (fastClouds) { + if (flat) { RenderSystem.enableCull(); } - RenderType.clouds().clearRenderState(); + RenderSystem.defaultBlendFunc(); + RenderSystem.disableBlend(); + if (renderTarget != null) { + Minecraft.getInstance() + .getMainRenderTarget() + .bindWrite(false); + } + RenderSystem.setShaderFog(prevFogParameters); RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); - - RenderSystem.setShaderFog(prevShaderFog); - } - - private static FogParameters copyShaderFogParameters(FogParameters shaderFog) { - return new FogParameters(shaderFog.start(), shaderFog.end(), shaderFog.shape(), shaderFog.red(), shaderFog.green(), shaderFog.blue(), shaderFog.alpha()); } private static @NotNull CloudGeometry rebuildGeometry(@Nullable CloudGeometry existingGeometry, @@ -150,25 +211,25 @@ private static FogParameters copyShaderFogParameters(FogParameters shaderFog) { var writer = VertexBufferWriter.of(bufferBuilder); - var originCellX = parameters.originX(); - var originCellZ = parameters.originZ(); - - var orientation = parameters.orientation(); + final var radius = parameters.radius(); + final var orientation = parameters.orientation(); + final var flat = parameters.renderMode() == CloudStatus.FAST; - var radius = parameters.radius(); - var useFastGraphics = parameters.renderMode() == CloudStatus.FAST; + final var slice = textureData.slice(parameters.originX(), parameters.originZ(), radius); - addCellGeometryToBuffer(writer, textureData, originCellX, originCellZ, 0, 0, orientation, useFastGraphics); + // Iterate from the center coordinates (0, 0) outwards + // Since the geometry will be in sorted order, this avoids needing a depth pre-pass + addCellGeometryToBuffer(writer, slice, 0, 0, orientation, flat); for (int layer = 1; layer <= radius; layer++) { for (int z = -layer; z < layer; z++) { int x = Math.abs(z) - layer; - addCellGeometryToBuffer(writer, textureData, originCellX, originCellZ, x, z, orientation, useFastGraphics); + addCellGeometryToBuffer(writer, slice, x, z, orientation, flat); } for (int z = layer; z > -layer; z--) { int x = layer - Math.abs(z); - addCellGeometryToBuffer(writer, textureData, originCellX, originCellZ, x, z, orientation, useFastGraphics); + addCellGeometryToBuffer(writer, slice, x, z, orientation, flat); } } @@ -177,22 +238,22 @@ private static FogParameters copyShaderFogParameters(FogParameters shaderFog) { for (int z = -radius; z <= -l; z++) { int x = -z - layer; - addCellGeometryToBuffer(writer, textureData, originCellX, originCellZ, x, z, orientation, useFastGraphics); + addCellGeometryToBuffer(writer, slice, x, z, orientation, flat); } for (int z = l; z <= radius; z++) { int x = z - layer; - addCellGeometryToBuffer(writer, textureData, originCellX, originCellZ, x, z, orientation, useFastGraphics); + addCellGeometryToBuffer(writer, slice, x, z, orientation, flat); } for (int z = radius; z >= l; z--) { int x = layer - z; - addCellGeometryToBuffer(writer, textureData, originCellX, originCellZ, x, z, orientation, useFastGraphics); + addCellGeometryToBuffer(writer, slice, x, z, orientation, flat); } for (int z = -l; z >= -radius; z--) { int x = layer + z; - addCellGeometryToBuffer(writer, textureData, originCellX, originCellZ, x, z, orientation, useFastGraphics); + addCellGeometryToBuffer(writer, slice, x, z, orientation, flat); } } @@ -222,172 +283,235 @@ private static FogParameters copyShaderFogParameters(FogParameters shaderFog) { } private static void addCellGeometryToBuffer(VertexBufferWriter writer, - CloudTextureData textureData, - int originX, - int originZ, - int offsetX, - int offsetZ, - int orientation, - boolean useFastGraphics) { - int cellX = originX + offsetX; - int cellZ = originZ + offsetZ; - - int cellIndex = textureData.getCellIndexWrapping(cellX, cellZ); - int cellFaces = textureData.getCellFaces(cellIndex) & getVisibleFaces(offsetX, offsetZ, orientation); - - if (cellFaces == 0) { + CloudTextureData.Slice textureData, + int x, + int z, + @Nullable CloudRenderer.ViewOrientation orientation, + boolean flat) + { + int index = textureData.getCellIndex(x, z); + int faces = textureData.getCellFaces(index) & getVisibleFaces(x, z, orientation); + + if (faces == 0) { return; } - int cellColor = textureData.getCellColor(cellIndex); + int color = textureData.getCellColor(index); - if (ColorABGR.unpackAlpha(cellColor) == 0) { + if (isTransparent(color)) { return; } - float x = offsetX * 12; - float z = offsetZ * 12; - - if (useFastGraphics) { - emitCellGeometry2D(writer, cellFaces, cellColor, x, z); + if (flat) { + emitCellGeometryFlat(writer, color, x, z); } else { - emitCellGeometry3D(writer, cellFaces, cellColor, x, z, false); - - int distance = Math.abs(offsetX) + Math.abs(offsetZ); + emitCellGeometryExterior(writer, faces, color, x, z); - if (distance <= 1) { - emitCellGeometry3D(writer, CloudFaceSet.all(), cellColor, x, z, true); + if (taxicabDistance(x, z) <= 1) { + emitCellGeometryInterior(writer, color, x, z); } } } - private static int getVisibleFaces(int x, int z, int orientation) { - int faces = CloudFaceSet.all(); + private static int getVisibleFaces(int x, int z, ViewOrientation orientation) { + int faces = 0; - if (x > 0) { - faces = CloudFaceSet.remove(faces, CloudFace.POS_X); + if (x <= 0) { + faces |= FACE_MASK_POS_X; } - if (z > 0) { - faces = CloudFaceSet.remove(faces, CloudFace.POS_Z); + if (z <= 0) { + faces |= FACE_MASK_POS_Z; } - if (x < 0) { - faces = CloudFaceSet.remove(faces, CloudFace.NEG_X); + if (x >= 0) { + faces |= FACE_MASK_NEG_X; } - if (z < 0) { - faces = CloudFaceSet.remove(faces, CloudFace.NEG_Z); + if (z >= 0) { + faces |= FACE_MASK_NEG_Z; } - if (orientation < 0) { - faces = CloudFaceSet.remove(faces, CloudFace.POS_Y); + if (orientation != ViewOrientation.BELOW_CLOUDS) { + faces |= FACE_MASK_POS_Y; } - if (orientation > 0) { - faces = CloudFaceSet.remove(faces, CloudFace.NEG_Y); + if (orientation != ViewOrientation.ABOVE_CLOUDS) { + faces |= FACE_MASK_NEG_Y; } return faces; } - private static final Vector3f[][] VERTICES = new Vector3f[CloudFace.COUNT][]; - - static { - VERTICES[CloudFace.NEG_Y.ordinal()] = new Vector3f[] { - new Vector3f(12.0f, 0.0f, 12.0f), - new Vector3f( 0.0f, 0.0f, 12.0f), - new Vector3f( 0.0f, 0.0f, 0.0f), - new Vector3f(12.0f, 0.0f, 0.0f) - }; - - VERTICES[CloudFace.POS_Y.ordinal()] = new Vector3f[] { - new Vector3f( 0.0f, 4.0f, 12.0f), - new Vector3f(12.0f, 4.0f, 12.0f), - new Vector3f(12.0f, 4.0f, 0.0f), - new Vector3f( 0.0f, 4.0f, 0.0f) - }; - - VERTICES[CloudFace.NEG_X.ordinal()] = new Vector3f[] { - new Vector3f( 0.0f, 0.0f, 12.0f), - new Vector3f( 0.0f, 4.0f, 12.0f), - new Vector3f( 0.0f, 4.0f, 0.0f), - new Vector3f( 0.0f, 0.0f, 0.0f) - }; - - VERTICES[CloudFace.POS_X.ordinal()] = new Vector3f[] { - new Vector3f(12.0f, 4.0f, 12.0f), - new Vector3f(12.0f, 0.0f, 12.0f), - new Vector3f(12.0f, 0.0f, 0.0f), - new Vector3f(12.0f, 4.0f, 0.0f) - }; - - VERTICES[CloudFace.NEG_Z.ordinal()] = new Vector3f[] { - new Vector3f(12.0f, 4.0f, 0.0f), - new Vector3f(12.0f, 0.0f, 0.0f), - new Vector3f( 0.0f, 0.0f, 0.0f), - new Vector3f( 0.0f, 4.0f, 0.0f) - }; - - VERTICES[CloudFace.POS_Z.ordinal()] = new Vector3f[] { - new Vector3f(12.0f, 0.0f, 12.0f), - new Vector3f(12.0f, 4.0f, 12.0f), - new Vector3f( 0.0f, 4.0f, 12.0f), - new Vector3f( 0.0f, 0.0f, 12.0f) - }; - } - - private static void emitCellGeometry2D(VertexBufferWriter writer, int faces, int color, float x, float z) { + private static void emitCellGeometryFlat(VertexBufferWriter writer, int texel, int x, int z) { try (MemoryStack stack = MemoryStack.stackPush()) { - final long buffer = stack.nmalloc(4 * ColorVertex.STRIDE); + final long vertexBuffer = stack.nmalloc(4 * ColorVertex.STRIDE); + long ptr = vertexBuffer; - long ptr = buffer; - int count = 0; + final float x0 = (x * CLOUD_WIDTH); + final float x1 = x0 + CLOUD_WIDTH; + final float z0 = (z * CLOUD_WIDTH); + final float z1 = z0 + CLOUD_WIDTH; - int mixedColor = ColorABGR.mulRGB(color, CloudFace.POS_Y.getShade()); - - ptr = writeVertex(ptr, x + 12.0f, 0.0f, z + 12.0f, mixedColor); - ptr = writeVertex(ptr, x + 0.0f, 0.0f, z + 12.0f, mixedColor); - ptr = writeVertex(ptr, x + 0.0f, 0.0f, z + 0.0f, mixedColor); - ptr = writeVertex(ptr, x + 12.0f, 0.0f, z + 0.0f, mixedColor); - - count += 4; + { + final int color = ColorABGR.mulRGB(texel, BRIGHTNESS_POS_Y); + ptr = writeVertex(ptr, x1, 0.0f, z1, color); + ptr = writeVertex(ptr, x0, 0.0f, z1, color); + ptr = writeVertex(ptr, x0, 0.0f, z0, color); + ptr = writeVertex(ptr, x1, 0.0f, z0, color); + } - writer.push(stack, buffer, count, ColorVertex.FORMAT); + writer.push(stack, vertexBuffer, 4, ColorVertex.FORMAT); } } - private static void emitCellGeometry3D(VertexBufferWriter writer, int visibleFaces, int baseColor, float posX, float posZ, boolean interior) { + private static void emitCellGeometryExterior(VertexBufferWriter writer, int cellFaces, int cellColor, int cellX, int cellZ) { try (MemoryStack stack = MemoryStack.stackPush()) { - final long buffer = stack.nmalloc(6 * 4 * ColorVertex.STRIDE); + final long vertexBuffer = stack.nmalloc(6 * 4 * ColorVertex.STRIDE); + int vertexCount = 0; + + long ptr = vertexBuffer; + + final float x0 = cellX * CLOUD_WIDTH; + final float y0 = 0.0f; + final float z0 = cellZ * CLOUD_WIDTH; + + final float x1 = x0 + CLOUD_WIDTH; + final float y1 = y0 + CLOUD_HEIGHT; + final float z1 = z0 + CLOUD_WIDTH; + + // -Y + if ((cellFaces & FACE_MASK_NEG_Y) != 0) { + final int vertexColor = ColorABGR.mulRGB(cellColor, BRIGHTNESS_NEG_Y); + ptr = writeVertex(ptr, x1, y0, z1, vertexColor); + ptr = writeVertex(ptr, x0, y0, z1, vertexColor); + ptr = writeVertex(ptr, x0, y0, z0, vertexColor); + ptr = writeVertex(ptr, x1, y0, z0, vertexColor); + vertexCount += 4; + } + + // +Y + if ((cellFaces & FACE_MASK_POS_Y) != 0) { + final int vertexColor = ColorABGR.mulRGB(cellColor, BRIGHTNESS_POS_Y); + ptr = writeVertex(ptr, x0, y1, z1, vertexColor); + ptr = writeVertex(ptr, x1, y1, z1, vertexColor); + ptr = writeVertex(ptr, x1, y1, z0, vertexColor); + ptr = writeVertex(ptr, x0, y1, z0, vertexColor); + vertexCount += 4; + } - long ptr = buffer; - int count = 0; + if ((cellFaces & (FACE_MASK_NEG_X | FACE_MASK_POS_X)) != 0) { + final int vertexColor = ColorABGR.mulRGB(cellColor, BRIGHTNESS_X_AXIS); - for (var face : CloudFace.VALUES) { - if (!CloudFaceSet.contains(visibleFaces, face)) { - continue; + // -X + if ((cellFaces & FACE_MASK_NEG_X) != 0) { + ptr = writeVertex(ptr, x0, y0, z1, vertexColor); + ptr = writeVertex(ptr, x0, y1, z1, vertexColor); + ptr = writeVertex(ptr, x0, y1, z0, vertexColor); + ptr = writeVertex(ptr, x0, y0, z0, vertexColor); + vertexCount += 4; } - final var vertices = VERTICES[face.ordinal()]; - final int color = ColorABGR.mulRGB(baseColor, face.getShade()); + // +X + if ((cellFaces & FACE_MASK_POS_X) != 0) { + ptr = writeVertex(ptr, x1, y1, z1, vertexColor); + ptr = writeVertex(ptr, x1, y0, z1, vertexColor); + ptr = writeVertex(ptr, x1, y0, z0, vertexColor); + ptr = writeVertex(ptr, x1, y1, z0, vertexColor); + vertexCount += 4; + } + } - for (int vertexIndex = 0; vertexIndex < 4; vertexIndex++) { - Vector3f vertex = vertices[interior ? 3 - vertexIndex : vertexIndex]; + if ((cellFaces & (FACE_MASK_NEG_Z | FACE_MASK_POS_Z)) != 0) { + final int vertexColor = ColorABGR.mulRGB(cellColor, BRIGHTNESS_Z_AXIS); - final float x = vertex.x + posX; - final float y = vertex.y; - final float z = vertex.z + posZ; + // -Z + if ((cellFaces & FACE_MASK_NEG_Z) != 0) { + ptr = writeVertex(ptr, x1, y1, z0, vertexColor); + ptr = writeVertex(ptr, x1, y0, z0, vertexColor); + ptr = writeVertex(ptr, x0, y0, z0, vertexColor); + ptr = writeVertex(ptr, x0, y1, z0, vertexColor); + vertexCount += 4; + } - ptr = writeVertex(ptr, x, y, z, color); + // +Z + if ((cellFaces & FACE_MASK_POS_Z) != 0) { + ptr = writeVertex(ptr, x1, y0, z1, vertexColor); + ptr = writeVertex(ptr, x1, y1, z1, vertexColor); + ptr = writeVertex(ptr, x0, y1, z1, vertexColor); + ptr = writeVertex(ptr, x0, y0, z1, vertexColor); + vertexCount += 4; } + } - count += 4; + writer.push(stack, vertexBuffer, vertexCount, ColorVertex.FORMAT); + } + } + + private static void emitCellGeometryInterior(VertexBufferWriter writer, int baseColor, int cellX, int cellZ) { + try (MemoryStack stack = MemoryStack.stackPush()) { + final long vertexBuffer = stack.nmalloc(6 * 4 * ColorVertex.STRIDE); + long ptr = vertexBuffer; + + final float x0 = cellX * CLOUD_WIDTH; + final float y0 = 0.0f; + final float z0 = cellZ * CLOUD_WIDTH; + + final float x1 = x0 + CLOUD_WIDTH; + final float y1 = y0 + CLOUD_HEIGHT; + final float z1 = z0 + CLOUD_WIDTH; + + { + // -Y + final int vertexColor = ColorABGR.mulRGB(baseColor, BRIGHTNESS_NEG_Y); + ptr = writeVertex(ptr, x1, y0, z0, vertexColor); + ptr = writeVertex(ptr, x0, y0, z0, vertexColor); + ptr = writeVertex(ptr, x0, y0, z1, vertexColor); + ptr = writeVertex(ptr, x1, y0, z1, vertexColor); } - if (count > 0) { - writer.push(stack, buffer, count, ColorVertex.FORMAT); + { + // +Y + final int vertexColor = ColorABGR.mulRGB(baseColor, BRIGHTNESS_POS_Y); + ptr = writeVertex(ptr, x0, y1, z0, vertexColor); + ptr = writeVertex(ptr, x1, y1, z0, vertexColor); + ptr = writeVertex(ptr, x1, y1, z1, vertexColor); + ptr = writeVertex(ptr, x0, y1, z1, vertexColor); } + + { + final int vertexColor = ColorABGR.mulRGB(baseColor, BRIGHTNESS_X_AXIS); + + // -X + ptr = writeVertex(ptr, x0, y0, z0, vertexColor); + ptr = writeVertex(ptr, x0, y1, z0, vertexColor); + ptr = writeVertex(ptr, x0, y1, z1, vertexColor); + ptr = writeVertex(ptr, x0, y0, z1, vertexColor); + + // +X + ptr = writeVertex(ptr, x1, y1, z0, vertexColor); + ptr = writeVertex(ptr, x1, y0, z0, vertexColor); + ptr = writeVertex(ptr, x1, y0, z1, vertexColor); + ptr = writeVertex(ptr, x1, y1, z1, vertexColor); + } + + { + final int vertexColor = ColorABGR.mulRGB(baseColor, BRIGHTNESS_Z_AXIS); + + // -Z + ptr = writeVertex(ptr, x0, y1, z0, vertexColor); + ptr = writeVertex(ptr, x0, y0, z0, vertexColor); + ptr = writeVertex(ptr, x1, y0, z0, vertexColor); + ptr = writeVertex(ptr, x1, y1, z0, vertexColor); + + // +Z + ptr = writeVertex(ptr, x0, y0, z1, vertexColor); + ptr = writeVertex(ptr, x0, y1, z1, vertexColor); + ptr = writeVertex(ptr, x1, y1, z1, vertexColor); + ptr = writeVertex(ptr, x1, y0, z1, vertexColor); + } + + writer.push(stack, vertexBuffer, 6 * 4, ColorVertex.FORMAT); } } @@ -403,35 +527,36 @@ private static void uploadToVertexBuffer(VertexBuffer vertexBuffer, MeshData bui VertexBuffer.unbind(); } - public void reloadTextures(ResourceProvider resourceProvider) { + public void reload(ResourceProvider resourceProvider) { this.destroy(); - - this.textureData = loadTextureData(); + this.textureData = loadTextureData(resourceProvider); } public void destroy() { - if (this.cachedGeometry != null) { - var vertexBuffer = this.cachedGeometry.vertexBuffer(); + if (this.builtGeometry != null) { + var vertexBuffer = this.builtGeometry.vertexBuffer(); if (vertexBuffer != null) { vertexBuffer.close(); } - this.cachedGeometry = null; + this.builtGeometry = null; } } - private static CloudTextureData loadTextureData() { - ResourceManager resourceManager = Minecraft.getInstance().getResourceManager(); - Resource resource = resourceManager.getResource(CLOUDS_TEXTURE_ID) - .orElseThrow(); + private static @Nullable CloudTextureData loadTextureData(ResourceProvider resourceProvider) { + var resource = resourceProvider.getResource(CloudRenderer.CLOUDS_TEXTURE_ID) + .orElseThrow(); // always provided by default resource pack - try (InputStream inputStream = resource.open()){ - try (NativeImage nativeImage = NativeImage.read(inputStream)) { - return new CloudTextureData(nativeImage); - } - } catch (IOException ex) { - throw new RuntimeException("Failed to load texture data", ex); + try (var inputStream = resource.open(); + var nativeImage = NativeImage.read(inputStream)) + { + return CloudTextureData.load(nativeImage); + } + catch (Throwable t) { + LOGGER.error("Failed to load texture '{}'. The rendering of clouds in the skybox will be disabled. " + + "This may be caused by an incompatible resource pack.", CloudRenderer.CLOUDS_TEXTURE_ID, t); + return null; } } @@ -444,96 +569,107 @@ private static int getCloudRenderDistance() { return Math.max(32, (Minecraft.getInstance().options.getEffectiveRenderDistance() * 2) + 9); } - private enum CloudFace { - NEG_Y(0.7F), - POS_Y(1.0f), - NEG_X(0.9F), - POS_X(0.9F), - NEG_Z(0.8F), - POS_Z(0.8F); - - public static final CloudFace[] VALUES = CloudFace.values(); - public static final int COUNT = VALUES.length; - - private final int shade; - - CloudFace(float shade) { - this.shade = ColorU8.normalizedFloatToByte(shade); - } - - public int getShade() { - return this.shade; - } + private static boolean isTransparent(int argb) { + return ColorARGB.unpackAlpha(argb) < 10; } - private static class CloudFaceSet { - public static int empty() { - return 0; - } - - public static boolean contains(int set, CloudFace face) { - return (set & (1 << face.ordinal())) != 0; - } - - public static int add(int set, CloudFace face) { - return set | (1 << face.ordinal()); - } - - public static int remove(int set, CloudFace face) { - return set & ~(1 << face.ordinal()); - } - - public static int all() { - return (1 << CloudFace.COUNT) - 1; - } + private static int taxicabDistance(int x, int z) { + return Math.abs(x) + Math.abs(z); } - private static boolean isTransparentCell(int color) { - return ColorARGB.unpackAlpha(color) <= 1; + private static FogParameters copyShaderFogParameters(FogParameters shaderFog) { + return new FogParameters( + shaderFog.start(), + shaderFog.end(), + shaderFog.shape(), + shaderFog.red(), + shaderFog.green(), + shaderFog.blue(), + shaderFog.alpha()); } private static class CloudTextureData { private final byte[] faces; private final int[] colors; - private boolean isBlank; private final int width, height; - public CloudTextureData(NativeImage texture) { - int width = texture.getWidth(); - int height = texture.getHeight(); - + private CloudTextureData(int width, int height) { this.faces = new byte[width * height]; this.colors = new int[width * height]; - this.isBlank = true; this.width = width; this.height = height; + } + + public Slice slice(int originX, int originY, int radius) { + final var src = this; + final var dst = new CloudTextureData.Slice(radius); + + for (int dstY = 0; dstY < dst.height; dstY++) { + int srcX = Math.floorMod(originX - radius, this.width); + int srcY = Math.floorMod(originY - radius + dstY, this.height); + + int dstX = 0; + + while (dstX < dst.width) { + final int length = Math.min(src.width - srcX, dst.width - dstX); - this.loadTextureData(texture, width, height); + final int srcPos = getCellIndex(srcX, srcY, src.width); + final int dstPos = getCellIndex(dstX, dstY, dst.width); + + System.arraycopy(this.faces, srcPos, dst.faces, dstPos, length); + System.arraycopy(this.colors, srcPos, dst.colors, dstPos, length); + + srcX = 0; + dstX += length; + } + } + + return dst; } - private void loadTextureData(NativeImage texture, int width, int height) { + public static @Nullable CloudTextureData load(NativeImage image) { + final int width = image.getWidth(); + final int height = image.getHeight(); + + var data = new CloudTextureData(width, height); + + if (!data.loadTextureData(image, width, height)) { + return null; // The texture is empty, so it isn't necessary to render it + } + + return data; + } + + private boolean loadTextureData(NativeImage texture, int width, int height) { + Validate.isTrue(this.width == width); + Validate.isTrue(this.height == height); + + boolean containsData = false; + for (int x = 0; x < width; x++) { for (int z = 0; z < height; z++) { - int index = this.getCellIndex(x, z); int color = texture.getPixel(x, z); + if (isTransparent(color)) { + continue; + } + + int index = getCellIndex(x, z, width); this.colors[index] = color; + this.faces[index] = (byte) getOpenFaces(texture, color, x, z); - if (!isTransparentCell(color)) { - this.faces[index] = (byte) getOpenFaces(texture, color, x, z); - this.isBlank = false; - } + containsData = true; } } + + return containsData; } private static int getOpenFaces(NativeImage image, int color, int x, int z) { // Since the cloud texture is only 2D, nothing can hide the top or bottom faces - int faces = CloudFaceSet.empty(); - faces = CloudFaceSet.add(faces, CloudFace.NEG_Y); - faces = CloudFaceSet.add(faces, CloudFace.POS_Y); + int faces = FACE_MASK_NEG_Y | FACE_MASK_POS_Y; // Generate faces where the neighbor cell is a different color // Do not generate duplicate faces between two cells @@ -542,7 +678,7 @@ private static int getOpenFaces(NativeImage image, int color, int x, int z) { int neighbor = getNeighborTexel(image, x - 1, z); if (color != neighbor) { - faces = CloudFaceSet.add(faces, CloudFace.NEG_X); + faces |= FACE_MASK_NEG_X; } } @@ -551,7 +687,7 @@ private static int getOpenFaces(NativeImage image, int color, int x, int z) { int neighbor = getNeighborTexel(image, x + 1, z); if (color != neighbor) { - faces = CloudFaceSet.add(faces, CloudFace.POS_X); + faces |= FACE_MASK_POS_X; } } @@ -560,7 +696,7 @@ private static int getOpenFaces(NativeImage image, int color, int x, int z) { int neighbor = getNeighborTexel(image, x, z - 1); if (color != neighbor) { - faces = CloudFaceSet.add(faces, CloudFace.NEG_Z); + faces |= FACE_MASK_NEG_Z; } } @@ -569,7 +705,7 @@ private static int getOpenFaces(NativeImage image, int color, int x, int z) { int neighbor = getNeighborTexel(image, x, z + 1); if (color != neighbor) { - faces = CloudFaceSet.add(faces, CloudFace.POS_Z); + faces |= FACE_MASK_POS_Z; } } @@ -595,23 +731,35 @@ private static int wrapTexelCoord(int coord, int min, int max) { return coord; } - public int getCellFaces(int index) { - return this.faces[index]; + private static int getCellIndex(int x, int z, int pitch) { + return (z * pitch) + x; } - public int getCellColor(int index) { - return this.colors[index]; - } + public static class Slice { + private final int width, height; + private final int radius; + private final byte[] faces; + private final int[] colors; - private int getCellIndexWrapping(int x, int z) { - return this.getCellIndex( - Math.floorMod(x, this.width), - Math.floorMod(z, this.height) - ); - } + public Slice(int radius) { + this.width = 1 + (radius * 2); + this.height = 1 + (radius * 2); + this.radius = radius; + this.faces = new byte[this.width * this.height]; + this.colors = new int[this.width * this.height]; + } + + public int getCellIndex(int x, int z) { + return CloudTextureData.getCellIndex(x + this.radius, z + this.radius, this.width); + } - private int getCellIndex(int x, int z) { - return (x * this.width) + z; + public int getCellFaces(int index) { + return Byte.toUnsignedInt(this.faces[index]); + } + + public int getCellColor(int index) { + return this.colors[index]; + } } } @@ -619,7 +767,23 @@ public record CloudGeometry(@Nullable VertexBuffer vertexBuffer, CloudGeometryPa } - public record CloudGeometryParameters(int originX, int originZ, int radius, int orientation, CloudStatus renderMode) { + public record CloudGeometryParameters(int originX, int originZ, int radius, @Nullable ViewOrientation orientation, CloudStatus renderMode) { } + + private enum ViewOrientation { + BELOW_CLOUDS, // Top faces should *not* be rendered + INSIDE_CLOUDS, // All faces *must* be rendered + ABOVE_CLOUDS; // Bottom faces should *not* be rendered + + public static @NotNull ViewOrientation getOrientation(Vec3 camera, float minY, float maxY) { + if (camera.y() <= minY + 0.125f /* epsilon */) { + return ViewOrientation.BELOW_CLOUDS; + } else if (camera.y() >= maxY - 0.125f /* epsilon */) { + return ViewOrientation.ABOVE_CLOUDS; + } else { + return ViewOrientation.INSIDE_CLOUDS; + } + } + } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/world/clouds/LevelRendererMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/world/clouds/LevelRendererMixin.java index 2e58490fdc..7bd7e6c87a 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/world/clouds/LevelRendererMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/world/clouds/LevelRendererMixin.java @@ -1,7 +1,7 @@ package net.caffeinemc.mods.sodium.mixin.features.render.world.clouds; +import com.mojang.blaze3d.pipeline.RenderTarget; import com.mojang.blaze3d.resource.ResourceHandle; -import com.mojang.blaze3d.vertex.PoseStack; import net.caffeinemc.mods.sodium.client.render.immediate.CloudRenderer; import net.minecraft.client.Camera; import net.minecraft.client.CloudStatus; @@ -16,6 +16,7 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Group; import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import java.util.Objects; @@ -40,11 +41,19 @@ public class LevelRendererMixin { */ @Group(name = "sodium$cloudsOverride", min = 1, max = 1) @Dynamic - @Inject(method = "method_62205", at = @At(value = "HEAD"), cancellable = true, require = 0) - public void renderCloudsFabric(ResourceHandle resourceHandle, int color, CloudStatus cloudStatus, float f, Matrix4f modelView, Matrix4f projectionMatrix, Vec3 vec3, float tickDelta, CallbackInfo ci) { + @Inject( + method = "method_62205", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/client/renderer/CloudRenderer;render(ILnet/minecraft/client/CloudStatus;FLorg/joml/Matrix4f;Lorg/joml/Matrix4f;Lnet/minecraft/world/phys/Vec3;F)V" + ), + require = 0, + cancellable = true + ) + public void renderCloudsFabric(ResourceHandle resourceHandle, int cloudColor, CloudStatus cloudStatus, float cloudHeight, Matrix4f matModelView, Matrix4f matProjection, Vec3 camera, float partialTicks, CallbackInfo ci) { ci.cancel(); - sodium$renderClouds(modelView, projectionMatrix, color); + this.sodium$renderClouds(matModelView, matProjection, cloudColor); } @Group(name = "sodium$cloudsOverride", min = 1, max = 1) @@ -53,24 +62,28 @@ public void renderCloudsFabric(ResourceHandle resourceHandle, int color, Clou public void renderCloudsNeo(ResourceHandle resourcehandle, float p_365209_, Vec3 p_362985_, Matrix4f modelView, Matrix4f projectionMatrix, int color, CloudStatus p_364196_, float p_362337_, CallbackInfo ci) { ci.cancel(); - sodium$renderClouds(modelView, projectionMatrix, color); + this.sodium$renderClouds(modelView, projectionMatrix, color); } - private void sodium$renderClouds(Matrix4f modelView, Matrix4f projectionMatrix, int color) { + @Unique + private void sodium$renderClouds(Matrix4f matModelView, Matrix4f matProjection, int color) { if (this.cloudRenderer == null) { this.cloudRenderer = new CloudRenderer(this.minecraft.getResourceManager()); } - ClientLevel level = Objects.requireNonNull(this.level); Camera camera = this.minecraft.gameRenderer.getMainCamera(); + ClientLevel level = Objects.requireNonNull(this.level); + + var ticks = this.ticks; + var partialTicks = this.minecraft.getDeltaTracker().getGameTimeDeltaPartialTick(false); - this.cloudRenderer.render(camera, level, projectionMatrix, modelView, this.ticks, this.minecraft.getDeltaTracker().getGameTimeDeltaPartialTick(false), color); + this.cloudRenderer.render(camera, level, matProjection, matModelView, ticks, partialTicks, color); } @Inject(method = "onResourceManagerReload(Lnet/minecraft/server/packs/resources/ResourceManager;)V", at = @At("RETURN")) private void onReload(ResourceManager manager, CallbackInfo ci) { if (this.cloudRenderer != null) { - this.cloudRenderer.reloadTextures(manager); + this.cloudRenderer.reload(manager); } }