From 332a7bb11fe2f26e047eb43c87e7633b8d757b19 Mon Sep 17 00:00:00 2001 From: lax1dude Date: Sun, 6 Jul 2025 12:31:55 -0700 Subject: [PATCH] Update #53 - Improved FPS, reduced WebGL context loss crashes --- client_version | 2 +- .../net/minecraft/client/Minecraft.edit.java | 2 +- .../entity/RenderFallingBlock.edit.java | 3 +- .../net/minecraft/crash/CrashReport.edit.java | 12 +- .../assets/minecraft/lang/en_US.edit.lang | 6 +- .../v1_8/internal/PlatformOpenGL.java | 4 + .../eaglercraft/v1_8/EaglercraftVersion.java | 8 +- .../v1_8/mojang/authlib/GameProfile.java | 1 - .../v1_8/mojang/authlib/TexturesProperty.java | 2 +- .../eaglercraft/v1_8/opengl/DisplayList.java | 3 +- .../v1_8/opengl/EaglercraftGPU.java | 73 ++++++--- .../v1_8/opengl/FixedFunctionPipeline.java | 147 ++++++++++-------- .../v1_8/opengl/FixedFunctionShader.java | 2 +- .../v1_8/opengl/GlStateManager.java | 6 +- .../v1_8/opengl/InstancedFontRenderer.java | 7 +- .../opengl/InstancedParticleRenderer.java | 5 +- .../eaglercraft/v1_8/opengl/StreamBuffer.java | 113 ++++---------- .../ext/deferred/LensFlareMeshRenderer.java | 3 +- .../DynamicLightBucketLoader.java | 34 ++-- .../v1_8/internal/PlatformOpenGL.java | 2 + sources/resources/EPKVersionIdentifier.txt | 2 +- sources/resources/assets/eagler/glsl/core.fsh | 18 +-- .../accel_particle_dynamiclights.fsh | 26 +--- .../accel_particle_dynamiclights.vsh | 48 +++++- .../glsl/dynamiclights/core_dynamiclights.fsh | 38 ++--- .../glsl/dynamiclights/core_dynamiclights.vsh | 60 ++++++- .../resources/assets/eagler/silence_loop.wav | Bin 8044 -> 80044 bytes .../javascript/enableJSPIScreen.html | 16 +- .../javascript/epw_meta.txt | 6 +- .../target_teavm_wasm_gc/javascript/loader.js | 8 +- .../javascript/loader.wasm | Bin 30167 -> 30225 bytes .../v1_8/internal/PlatformAudio.java | 31 +++- .../v1_8/internal/PlatformInput.java | 13 +- .../v1_8/internal/PlatformOpenGL.java | 5 + .../teavm/WebGL2RenderingContext.java | 2 + sources/wasm-gc-teavm-loader/c/main.c | 33 ++-- sources/wasm-gc-teavm-loader/js/library.js | 4 +- .../v1_8/internal/PlatformAudio.java | 9 +- .../v1_8/internal/PlatformOpenGL.java | 15 +- .../v1_8/internal/PlatformWebView.java | 10 +- .../wasm_gc_teavm/IndexedDBFilesystem.java | 11 +- .../wasm_gc_teavm/WASMGCWebSocketClient.java | 1 - .../internal/ClientPlatformSingleplayer.java | 12 +- .../internal/ServerPlatformSingleplayer.java | 12 +- .../js/clientPlatformSingleplayer.js | 27 ++-- .../wasm-gc-teavm/js/eagruntime_entrypoint.js | 2 +- sources/wasm-gc-teavm/js/eagruntime_main.js | 2 + sources/wasm-gc-teavm/js/platformAudio.js | 35 ++++- .../wasm-gc-teavm/js/platformFilesystem.js | 17 +- sources/wasm-gc-teavm/js/platformInput.js | 12 +- sources/wasm-gc-teavm/js/platformOpenGL.js | 20 +-- sources/wasm-gc-teavm/js/platformWebView.js | 9 +- .../js/serverPlatformSingleplayer.js | 12 +- 53 files changed, 568 insertions(+), 383 deletions(-) diff --git a/client_version b/client_version index 41915701..471a6eb2 100644 --- a/client_version +++ b/client_version @@ -1 +1 @@ -u52 \ No newline at end of file +u53 \ No newline at end of file diff --git a/patches/minecraft/net/minecraft/client/Minecraft.edit.java b/patches/minecraft/net/minecraft/client/Minecraft.edit.java index a85fdf85..e7005191 100644 --- a/patches/minecraft/net/minecraft/client/Minecraft.edit.java +++ b/patches/minecraft/net/minecraft/client/Minecraft.edit.java @@ -598,7 +598,7 @@ ~ GlStateManager.viewport(0, 0, this.displayWidth, this.displayHeight); ~ GlStateManager.clearColor(0.0f, 0.0f, 0.0f, 1.0f); -> DELETE 2 @ 2 : 4 +> DELETE 1 @ 1 : 4 > DELETE 5 @ 5 : 6 diff --git a/patches/minecraft/net/minecraft/client/renderer/entity/RenderFallingBlock.edit.java b/patches/minecraft/net/minecraft/client/renderer/entity/RenderFallingBlock.edit.java index 688c5d49..78c720b3 100644 --- a/patches/minecraft/net/minecraft/client/renderer/entity/RenderFallingBlock.edit.java +++ b/patches/minecraft/net/minecraft/client/renderer/entity/RenderFallingBlock.edit.java @@ -5,13 +5,12 @@ # Version: 1.0 # Author: lax1dude -> INSERT 2 : 7 @ 2 +> INSERT 2 : 6 @ 2 + import net.lax1dude.eaglercraft.v1_8.opengl.GlStateManager; + import net.lax1dude.eaglercraft.v1_8.opengl.VertexFormat; + import net.lax1dude.eaglercraft.v1_8.opengl.WorldRenderer; + import net.lax1dude.eaglercraft.v1_8.opengl.ext.deferred.DeferredStateManager; -+ import net.lax1dude.eaglercraft.v1_8.opengl.ext.dynamiclights.DynamicLightsStateManager; > DELETE 4 @ 4 : 5 diff --git a/patches/minecraft/net/minecraft/crash/CrashReport.edit.java b/patches/minecraft/net/minecraft/crash/CrashReport.edit.java index 56319320..e3387ed7 100644 --- a/patches/minecraft/net/minecraft/crash/CrashReport.edit.java +++ b/patches/minecraft/net/minecraft/crash/CrashReport.edit.java @@ -89,7 +89,17 @@ ~ stackTrace.append("\tat ").append(s).append('\n'); ~ }); -> CHANGE 1 : 2 @ 1 : 12 +> CHANGE 1 : 8 @ 1 : 9 + +~ Throwable t = this.cause.getCause(); +~ while (t != null) { +~ stackTrace.append("Caused by: " + t.toString()).append('\n'); +~ EagRuntime.getStackTrace(t, (s) -> { +~ stackTrace.append("\tat ").append(s).append('\n'); +~ }); +~ t = t.getCause(); + +> CHANGE 2 : 3 @ 2 : 3 ~ return stackTrace.toString(); diff --git a/patches/resources/assets/minecraft/lang/en_US.edit.lang b/patches/resources/assets/minecraft/lang/en_US.edit.lang index 8cfb6304..59de5761 100644 --- a/patches/resources/assets/minecraft/lang/en_US.edit.lang +++ b/patches/resources/assets/minecraft/lang/en_US.edit.lang @@ -16,7 +16,7 @@ ~ eaglercraft.resourcePack.load.loading=Loading resource pack... ~ eaglercraft.resourcePack.load.deleting=Deleting resource pack... -> INSERT 1 : 247 @ 1 +> INSERT 1 : 243 @ 1 + eaglercraft.gui.exitKey=Use '%s' to close this screen! + eaglercraft.gui.exitKeyRetarded=Use Backtick (`) to close this screen! @@ -213,10 +213,6 @@ + eaglercraft.shaders.gui.option.SCREEN_SPACE_REFLECTIONS.desc.6=ON: enable raytracing (slower) + eaglercraft.shaders.gui.option.SCREEN_SPACE_REFLECTIONS.desc.7=OFF: disable raytracing (faster) + -+ eaglercraft.shaders.gui.option.LIGHT_SHAFTS.desc.0=Render god rays (light shafts) for sunlight and moonlight shadows -+ eaglercraft.shaders.gui.option.LIGHT_SHAFTS.desc.2=ON: render god rays (slower) -+ eaglercraft.shaders.gui.option.LIGHT_SHAFTS.desc.3=OFF: disable god rays (faster) -+ + eaglercraft.shaders.gui.option.POST_LENS_DISTORION.label=Lens Distort + + eaglercraft.shaders.gui.option.POST_LENS_DISTORION.desc.0=Renders chromatic abberation and lens distorion diff --git a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformOpenGL.java b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformOpenGL.java index 8a98382f..7e516be8 100644 --- a/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformOpenGL.java +++ b/sources/lwjgl/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformOpenGL.java @@ -573,6 +573,10 @@ public class PlatformOpenGL { glDrawElements(mode, count, type, offset); } + public static void _wglDrawRangeElements(int mode, int start, int end, int count, int type, int offset) { + glDrawRangeElements(mode, start, end, count, type, offset); + } + public static void _wglDrawElementsInstanced(int mode, int count, int type, int offset, int instanced) { switch(instancingImpl) { case INSTANCE_IMPL_CORE: diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/EaglercraftVersion.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/EaglercraftVersion.java index ef38ad90..c645ce7b 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/EaglercraftVersion.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/EaglercraftVersion.java @@ -10,7 +10,7 @@ public class EaglercraftVersion { /// Customize these to fit your fork: public static final String projectForkName = "EaglercraftX"; - public static final String projectForkVersion = "u52"; + public static final String projectForkVersion = "u53"; public static final String projectForkVendor = "lax1dude"; public static final String projectForkURL = "https://gitlab.com/lax1dude/eaglercraftx-1.8"; @@ -20,20 +20,20 @@ public class EaglercraftVersion { public static final String projectOriginName = "EaglercraftX"; public static final String projectOriginAuthor = "lax1dude"; public static final String projectOriginRevision = "1.8"; - public static final String projectOriginVersion = "u52"; + public static final String projectOriginVersion = "u53"; public static final String projectOriginURL = "https://gitlab.com/lax1dude/eaglercraftx-1.8"; // rest in peace // EPK Version Identifier - public static final String EPKVersionIdentifier = "u52"; // Set to null to disable EPK version check + public static final String EPKVersionIdentifier = "u53"; // Set to null to disable EPK version check // Updating configuration public static final boolean enableUpdateService = true; public static final String updateBundlePackageName = "net.lax1dude.eaglercraft.v1_8.client"; - public static final int updateBundlePackageVersionInt = 52; + public static final int updateBundlePackageVersionInt = 53; public static final String updateLatestLocalStorageKey = "latestUpdate_" + updateBundlePackageName; diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/mojang/authlib/GameProfile.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/mojang/authlib/GameProfile.java index 87c9bc3b..d5cbca74 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/mojang/authlib/GameProfile.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/mojang/authlib/GameProfile.java @@ -19,7 +19,6 @@ package net.lax1dude.eaglercraft.v1_8.mojang.authlib; import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID; import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.builder.ToStringBuilder; import com.google.common.collect.Multimap; import com.google.common.collect.MultimapBuilder; diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/mojang/authlib/TexturesProperty.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/mojang/authlib/TexturesProperty.java index f26fd641..50797b04 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/mojang/authlib/TexturesProperty.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/mojang/authlib/TexturesProperty.java @@ -94,7 +94,7 @@ public class TexturesProperty { if(meta != null) { String modelStr = meta.optString("model"); if(modelStr != null && modelStr.equalsIgnoreCase("slim")) { - model = SkinModel.STEVE; + model = SkinModel.ALEX; } } } diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/DisplayList.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/DisplayList.java index 8e16c1c3..d411d80b 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/DisplayList.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/DisplayList.java @@ -26,7 +26,6 @@ class DisplayList { int attribs = -1; int mode = -1; int count = 0; - boolean bindQuad16 = false; - boolean bindQuad32 = false; + byte bindQuad = 0; } \ No newline at end of file diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/EaglercraftGPU.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/EaglercraftGPU.java index d933bff6..974a7019 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/EaglercraftGPU.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/EaglercraftGPU.java @@ -40,7 +40,7 @@ import static net.lax1dude.eaglercraft.v1_8.internal.PlatformOpenGL.*; public class EaglercraftGPU { - static final GLObjectRecycler arrayBufferRecycler = new GLObjectRecycler(32) { + static final GLObjectRecycler arrayBufferRecycler = new GLObjectRecycler(256) { @Override protected IBufferGL create() { @@ -49,7 +49,14 @@ public class EaglercraftGPU { @Override protected void invalidate(IBufferGL object) { - // Don't bother + IBufferGL old = currentArrayBuffer; + if (old != object) { + _wglBindBuffer(GL_ARRAY_BUFFER, object); + } + _wglBufferData(GL_ARRAY_BUFFER, 0, GL_STATIC_DRAW); + if (old != object) { + _wglBindBuffer(GL_ARRAY_BUFFER, old); + } } @Override @@ -59,7 +66,7 @@ public class EaglercraftGPU { }; - static final GLObjectRecycler elementArrayBufferRecycler = new GLObjectRecycler(32) { + static final GLObjectRecycler elementArrayBufferRecycler = new GLObjectRecycler(256) { @Override protected IBufferGL create() { @@ -68,7 +75,22 @@ public class EaglercraftGPU { @Override protected void invalidate(IBufferGL object) { - // Don't bother + IVertexArrayGL oldArray = currentVertexArray; + boolean vao = !emulatedVAOs; + if (vao && vertexArrayCapable && oldArray != null) { + _wglBindVertexArray(null); + } + IBufferGL old = currentEmulatedVAOIndexBuffer; + if (vao || old != object) { + _wglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, object); + } + _wglBufferData(GL_ELEMENT_ARRAY_BUFFER, 0, GL_STATIC_DRAW); + if (!vao && old != object) { + _wglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, old); + } + if (vao && vertexArrayCapable && oldArray != null) { + _wglBindVertexArray(oldArray); + } } @Override @@ -211,8 +233,7 @@ public class EaglercraftGPU { if(dp.vertexArray == null) { dp.vertexArray = createGLVertexArray(); - dp.bindQuad16 = false; - dp.bindQuad32 = false; + dp.bindQuad = 0; } if(dp.vertexBuffer == null) { dp.vertexBuffer = createGLArrayBuffer(); @@ -252,8 +273,7 @@ public class EaglercraftGPU { if(dp.vertexArray == null) { dp.vertexArray = createGLVertexArray(); - dp.bindQuad16 = false; - dp.bindQuad32 = false; + dp.bindQuad = 0; } if(dp.vertexBuffer == null) { dp.vertexBuffer = createGLArrayBuffer(); @@ -280,21 +300,19 @@ public class EaglercraftGPU { if(dp.mode == GL_QUADS) { int cnt = dp.count; if(cnt > quad16MaxVertices) { - if(!dp.bindQuad32) { - dp.bindQuad16 = false; - dp.bindQuad32 = true; + if(dp.bindQuad != 32) { + dp.bindQuad = 32; attachQuad32EmulationBuffer(cnt, true); }else { attachQuad32EmulationBuffer(cnt, false); } - p.drawElements(GL_TRIANGLES, (cnt >> 2) * 6, GL_UNSIGNED_INT, 0); + p.drawRangeElements(GL_TRIANGLES, 0, cnt - 1, (cnt >> 2) * 6, GL_UNSIGNED_INT, 0); }else { - if(!dp.bindQuad16) { - dp.bindQuad16 = true; - dp.bindQuad32 = false; + if(dp.bindQuad != 16) { + dp.bindQuad = 16; attachQuad16EmulationBuffer(true); } - p.drawElements(GL_TRIANGLES, (cnt >> 2) * 6, GL_UNSIGNED_SHORT, 0); + p.drawRangeElements(GL_TRIANGLES, 0, cnt - 1, (cnt >> 2) * 6, GL_UNSIGNED_SHORT, 0); } }else { p.drawArrays(dp.mode, 0, dp.count); @@ -560,6 +578,21 @@ public class EaglercraftGPU { _wglDrawElements(mode, count, type, offset); } + public static void drawRangeElements(int mode, int start, int end, int count, int type, int offset) { + if(emulatedVAOs) { + if(currentVertexArray == null) { + logger.warn("Skipping draw call with emulated VAO because no known VAO is bound!"); + return; + } + ((SoftGLVertexArray)currentVertexArray).transitionToState(emulatedVAOState, true); + } + if(glesVers >= 300) { + _wglDrawRangeElements(mode, start, end, count, type, offset); + }else { + _wglDrawElements(mode, count, type, offset); + } + } + public static void drawArraysInstanced(int mode, int first, int count, int instances) { if(emulatedVAOs) { if(currentVertexArray == null) { @@ -768,7 +801,7 @@ public class EaglercraftGPU { displayListBuffer.put(buffer); lastRender = null; }else { - lastRender = FixedFunctionPipeline.setupDirect(buffer, attrib).update(); + lastRender = FixedFunctionPipeline.setupDirect(buffer, attrib, mode == GL_QUADS).update(); lastRender.drawDirectArrays(mode, 0, count); lastMode = mode; lastCount = count; @@ -845,7 +878,7 @@ public class EaglercraftGPU { v3 = v2 + 1; v4 = v3 + 1; buf.put(v1 | (v2 << 16)); - buf.put(v4 | (v2 << 16)); + buf.put(v3 | (v1 << 16)); buf.put(v3 | (v4 << 16)); } buf.flip(); @@ -862,7 +895,7 @@ public class EaglercraftGPU { v3 = v2 + 1; v4 = v3 + 1; buf.put(v1); buf.put(v2); - buf.put(v4); buf.put(v2); + buf.put(v3); buf.put(v1); buf.put(v3); buf.put(v4); } buf.flip(); @@ -891,7 +924,7 @@ public class EaglercraftGPU { } FixedFunctionPipeline p = FixedFunctionPipeline.setupRenderDisplayList(mesh.getAttribBits()).update(); EaglercraftGPU.bindGLVertexArray(mesh.vertexArray); - p.drawElements(GL_TRIANGLES, mesh.indexCount, GL_UNSIGNED_SHORT, 0); + p.drawRangeElements(GL_TRIANGLES, 0, mesh.vertexCount - 1, mesh.indexCount, GL_UNSIGNED_SHORT, 0); } static int glesVers = -1; diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/FixedFunctionPipeline.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/FixedFunctionPipeline.java index 2abd6c34..35a721f1 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/FixedFunctionPipeline.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/FixedFunctionPipeline.java @@ -30,12 +30,9 @@ import net.lax1dude.eaglercraft.v1_8.internal.IVertexArrayGL; import net.lax1dude.eaglercraft.v1_8.internal.IProgramGL; import net.lax1dude.eaglercraft.v1_8.internal.IShaderGL; import net.lax1dude.eaglercraft.v1_8.internal.IUniformGL; -import net.lax1dude.eaglercraft.v1_8.internal.PlatformOpenGL; import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime; import net.lax1dude.eaglercraft.v1_8.log4j.LogManager; import net.lax1dude.eaglercraft.v1_8.log4j.Logger; -import net.lax1dude.eaglercraft.v1_8.opengl.StreamBuffer.StreamBufferInstance; -import net.lax1dude.eaglercraft.v1_8.opengl.ext.dynamiclights.DynamicLightsStateManager; import net.lax1dude.eaglercraft.v1_8.vector.Matrix4f; import net.lax1dude.eaglercraft.v1_8.vector.Vector4f; import net.minecraft.util.MathHelper; @@ -61,7 +58,7 @@ public class FixedFunctionPipeline { (GlStateManager.stateEnableShaderBlendColor ? STATE_ENABLE_BLEND_ADD : 0); } - static FixedFunctionPipeline setupDirect(ByteBuffer buffer, int attrib) { + static FixedFunctionPipeline setupDirect(ByteBuffer buffer, int attrib, boolean quads) { FixedFunctionPipeline self; int baseState = attrib | getFragmentState(); if(GlStateManager.stateUseExtensionPipeline) { @@ -74,13 +71,13 @@ public class FixedFunctionPipeline { self = getPipelineInstanceCore(baseState); } - StreamBufferInstance sb = self.streamBuffer.getBuffer(buffer.remaining()); - self.currentVertexArray = sb; + EaglercraftGPU.bindGLVertexArray(self.getDirectModeVertexArray()); - EaglercraftGPU.bindGLVertexArray(sb.getVertexArray()); - EaglercraftGPU.bindGLArrayBuffer(sb.getVertexBuffer()); + int off = StreamBuffer.uploadData(self.attribStride, buffer.remaining() / self.attribStride, quads); - _wglBufferSubData(GL_ARRAY_BUFFER, 0, buffer); + _wglBufferSubData(GL_ARRAY_BUFFER, off * self.attribStride, buffer); + + self.directBaseOffset = off; return self; } @@ -132,16 +129,19 @@ public class FixedFunctionPipeline { } static FixedFunctionPipeline setupRenderDisplayList(int attribs) { + FixedFunctionPipeline self; int baseState = attribs | getFragmentState(); if(GlStateManager.stateUseExtensionPipeline) { if(extensionProvider != null) { - return getPipelineInstanceExt(baseState, extensionProvider.getCurrentExtensionStateBits(baseState)); + self = getPipelineInstanceExt(baseState, extensionProvider.getCurrentExtensionStateBits(baseState)); }else { throw new IllegalStateException("No extension pipeline is available!"); } }else { - return getPipelineInstanceCore(baseState); + self = getPipelineInstanceCore(baseState); } + + return self; } void drawArrays(int mode, int offset, int count) { @@ -151,24 +151,26 @@ public class FixedFunctionPipeline { void drawDirectArrays(int mode, int offset, int count) { EaglercraftGPU.bindGLShaderProgram(shaderProgram); + offset += directBaseOffset; if(mode == GL_QUADS) { - StreamBufferInstance sb = currentVertexArray; - if(count > EaglercraftGPU.quad16MaxVertices) { - if(!sb.bindQuad32) { - sb.bindQuad16 = false; - sb.bindQuad32 = true; + int offset2 = (offset >> 2) * 6; + int count2 = (count >> 2) * 6; + if(offset + count > EaglercraftGPU.quad16MaxVertices) { + if(directQuads != 32) { + directQuads = 32; EaglercraftGPU.attachQuad32EmulationBuffer(count, true); }else { EaglercraftGPU.attachQuad32EmulationBuffer(count, false); } - EaglercraftGPU.drawElements(GL_TRIANGLES, (count >> 2) * 6, GL_UNSIGNED_INT, 0); + EaglercraftGPU.drawRangeElements(GL_TRIANGLES, offset, offset + count - 1, count2, GL_UNSIGNED_INT, + offset2 << 2); }else { - if(!sb.bindQuad16) { - sb.bindQuad16 = true; - sb.bindQuad32 = false; + if(directQuads != 16) { + directQuads = 16; EaglercraftGPU.attachQuad16EmulationBuffer(true); } - EaglercraftGPU.drawElements(GL_TRIANGLES, (count >> 2) * 6, GL_UNSIGNED_SHORT, 0); + EaglercraftGPU.drawRangeElements(GL_TRIANGLES, offset, offset + count - 1, count2, GL_UNSIGNED_SHORT, + offset2 << 1); } }else { EaglercraftGPU.drawArrays(mode, offset, count); @@ -180,6 +182,11 @@ public class FixedFunctionPipeline { EaglercraftGPU.drawElements(mode, count, type, offset); } + void drawRangeElements(int mode, int start, int end, int count, int type, int offset) { + EaglercraftGPU.bindGLShaderProgram(shaderProgram); + EaglercraftGPU.drawRangeElements(mode, start, end, count, type, offset); + } + private static IExtPipelineCompiler extensionProvider; public static void loadExtensionPipeline(IExtPipelineCompiler provider) { @@ -423,9 +430,9 @@ public class FixedFunctionPipeline { private float stateAlphaTestRef = -999.0f; private final IUniformGL stateLightsEnabledUniform1i; - private final IUniformGL[] stateLightsVectorsArrayUniform4f = new IUniformGL[4]; + private final IUniformGL[] stateLightsVectorsArrayUniform4f = new IUniformGL[2]; private int stateLightsEnabled = -1; - private final Vector4f[] stateLightsVectors = new Vector4f[4]; + private final Vector4f[] stateLightsVectors = new Vector4f[2]; private int stateLightingSerial = -1; private final IUniformGL stateLightingAmbientUniform3f; @@ -496,8 +503,9 @@ public class FixedFunctionPipeline { private float stateAnisotropicFixH = -999.0f; private float stateAnisotropicFixSerial = 0; - private final StreamBuffer streamBuffer; - private StreamBufferInstance currentVertexArray = null; + private IVertexArrayGL directVertexArray; + private int directBaseOffset; + private byte directQuads; private static FloatBuffer matrixCopyBuffer = null; @@ -572,39 +580,6 @@ public class FixedFunctionPipeline { } throw new IllegalStateException("Program could not be linked!"); } - - streamBuffer = new StreamBuffer((vertexArray, vertexBuffer) -> { - EaglercraftGPU.bindGLVertexArray(vertexArray); - EaglercraftGPU.bindVAOGLArrayBuffer(vertexBuffer); - - EaglercraftGPU.enableVertexAttribArray(0); - EaglercraftGPU.vertexAttribPointer(0, VertexFormat.COMPONENT_POSITION_SIZE, - VertexFormat.COMPONENT_POSITION_FORMAT, false, attribStride, 0); - - if(attribTextureIndex != -1) { - EaglercraftGPU.enableVertexAttribArray(attribTextureIndex); - EaglercraftGPU.vertexAttribPointer(attribTextureIndex, VertexFormat.COMPONENT_TEX_SIZE, - VertexFormat.COMPONENT_TEX_FORMAT, false, attribStride, attribTextureOffset); - } - - if(attribColorIndex != -1) { - EaglercraftGPU.enableVertexAttribArray(attribColorIndex); - EaglercraftGPU.vertexAttribPointer(attribColorIndex, VertexFormat.COMPONENT_COLOR_SIZE, - VertexFormat.COMPONENT_COLOR_FORMAT, true, attribStride, attribColorOffset); - } - - if(attribNormalIndex != -1) { - EaglercraftGPU.enableVertexAttribArray(attribNormalIndex); - EaglercraftGPU.vertexAttribPointer(attribNormalIndex, VertexFormat.COMPONENT_NORMAL_SIZE, - VertexFormat.COMPONENT_NORMAL_FORMAT, true, attribStride, attribNormalOffset); - } - - if(attribLightmapIndex != -1) { - EaglercraftGPU.enableVertexAttribArray(attribLightmapIndex); - EaglercraftGPU.vertexAttribPointer(attribLightmapIndex, VertexFormat.COMPONENT_LIGHTMAP_SIZE, - VertexFormat.COMPONENT_LIGHTMAP_FORMAT, false, attribStride, attribLightmapOffset); - } - }); stateEnableTexture2D = (bits & STATE_ENABLE_TEXTURE2D) != 0; stateEnableLightmap = (bits & STATE_ENABLE_LIGHTMAP) != 0; @@ -899,9 +874,7 @@ public class FixedFunctionPipeline { _wglUniform3f(stateLightingAmbientUniform3f, r, g, b); } } - } - - if(stateEnableMCLighting || DynamicLightsStateManager.isInDynamicLightsPass()) { + if(!stateHasAttribNormal) { serial = GlStateManager.stateNormalSerial; if(stateNormalSerial != serial) { @@ -1089,13 +1062,51 @@ public class FixedFunctionPipeline { } pipelineListTracker.clear(); } - - public void destroy() { - PlatformOpenGL._wglDeleteProgram(shaderProgram); - streamBuffer.destroy(); - } - + public IVertexArrayGL getDirectModeVertexArray() { - return currentVertexArray.vertexArray; + if (directVertexArray == null) { + directVertexArray = EaglercraftGPU.createGLVertexArray(); + EaglercraftGPU.bindGLVertexArray(directVertexArray); + EaglercraftGPU.bindVAOGLArrayBuffer(StreamBuffer.getBuffer()); + + EaglercraftGPU.enableVertexAttribArray(0); + EaglercraftGPU.vertexAttribPointer(0, VertexFormat.COMPONENT_POSITION_SIZE, + VertexFormat.COMPONENT_POSITION_FORMAT, false, attribStride, 0); + + if (attribTextureIndex != -1) { + EaglercraftGPU.enableVertexAttribArray(attribTextureIndex); + EaglercraftGPU.vertexAttribPointer(attribTextureIndex, VertexFormat.COMPONENT_TEX_SIZE, + VertexFormat.COMPONENT_TEX_FORMAT, false, attribStride, attribTextureOffset); + } + + if (attribColorIndex != -1) { + EaglercraftGPU.enableVertexAttribArray(attribColorIndex); + EaglercraftGPU.vertexAttribPointer(attribColorIndex, VertexFormat.COMPONENT_COLOR_SIZE, + VertexFormat.COMPONENT_COLOR_FORMAT, true, attribStride, attribColorOffset); + } + + if (attribNormalIndex != -1) { + EaglercraftGPU.enableVertexAttribArray(attribNormalIndex); + EaglercraftGPU.vertexAttribPointer(attribNormalIndex, VertexFormat.COMPONENT_NORMAL_SIZE, + VertexFormat.COMPONENT_NORMAL_FORMAT, true, attribStride, attribNormalOffset); + } + + if (attribLightmapIndex != -1) { + EaglercraftGPU.enableVertexAttribArray(attribLightmapIndex); + EaglercraftGPU.vertexAttribPointer(attribLightmapIndex, VertexFormat.COMPONENT_LIGHTMAP_SIZE, + VertexFormat.COMPONENT_LIGHTMAP_FORMAT, false, attribStride, attribLightmapOffset); + } + + return directVertexArray; + } + return directVertexArray; } + + public void destroy() { + _wglDeleteProgram(shaderProgram); + if (directVertexArray != null) { + EaglercraftGPU.destroyGLVertexArray(directVertexArray); + } + } + } \ No newline at end of file diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/FixedFunctionShader.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/FixedFunctionShader.java index f6d9f342..dc9c6da0 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/FixedFunctionShader.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/FixedFunctionShader.java @@ -44,7 +44,7 @@ public class FixedFunctionShader { public static final String FILENAME_VSH = "/assets/eagler/glsl/core.vsh"; public static final String FILENAME_FSH = "/assets/eagler/glsl/core.fsh"; - public static final String PRECISION_INT = "lowp"; + public static final String PRECISION_INT = "mediump"; public static final String PRECISION_FLOAT = "highp"; public static final String PRECISION_SAMPLER = "mediump"; diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/GlStateManager.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/GlStateManager.java index 76ec9a12..ce0b8258 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/GlStateManager.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/GlStateManager.java @@ -76,9 +76,9 @@ public class GlStateManager { static boolean stateMaterial = false; static boolean stateLighting = false; static int stateLightsStackPointer = 0; - static final boolean[][] stateLightsEnabled = new boolean[4][8]; - static final Vector4f[][] stateLightsStack = new Vector4f[4][8]; - static final int[] stateLightingSerial = new int[4]; + static final boolean[][] stateLightsEnabled = new boolean[2][8]; + static final Vector4f[][] stateLightsStack = new Vector4f[2][8]; + static final int[] stateLightingSerial = new int[2]; static float stateLightingAmbientR = 0.0f; static float stateLightingAmbientG = 0.0f; diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/InstancedFontRenderer.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/InstancedFontRenderer.java index 5256ac09..4ab85505 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/InstancedFontRenderer.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/InstancedFontRenderer.java @@ -206,8 +206,7 @@ public class InstancedFontRenderer { EaglercraftGPU.vertexAttribPointer(0, 3, GL_FLOAT, false, 12, 0); EaglercraftGPU.vertexAttribDivisor(0, 0); - EaglercraftGPU.bindVAOGLArrayBufferNow(instancesBuffer); - _wglBufferData(GL_ARRAY_BUFFER, fontDataBuffer.capacity(), GL_STREAM_DRAW); + EaglercraftGPU.bindVAOGLArrayBuffer(instancesBuffer); EaglercraftGPU.enableVertexAttribArray(1); EaglercraftGPU.vertexAttribPointer(1, 2, GL_SHORT, false, 10, 0); @@ -381,7 +380,7 @@ public class InstancedFontRenderer { int l = fontDataBuffer.limit(); fontDataBuffer.flip(); - _wglBufferData(GL_ARRAY_BUFFER, fontDataBuffer.capacity(), GL_STREAM_DRAW); + _wglBufferData(GL_ARRAY_BUFFER, (fontDataBuffer.remaining() + 0x3FF) & 0xFFFFFC00, GL_STREAM_DRAW); _wglBufferSubData(GL_ARRAY_BUFFER, 0, fontDataBuffer); fontDataBuffer.position(p); @@ -395,7 +394,7 @@ public class InstancedFontRenderer { int l = fontBoldDataBuffer.limit(); fontBoldDataBuffer.flip(); - _wglBufferData(GL_ARRAY_BUFFER, fontBoldDataBuffer.capacity(), GL_STREAM_DRAW); + _wglBufferData(GL_ARRAY_BUFFER, (fontBoldDataBuffer.remaining() + 0x3FF) & 0xFFFFFC00, GL_STREAM_DRAW); _wglBufferSubData(GL_ARRAY_BUFFER, 0, fontBoldDataBuffer); fontBoldDataBuffer.position(p); diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/InstancedParticleRenderer.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/InstancedParticleRenderer.java index a3f1d052..e9f8e8e0 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/InstancedParticleRenderer.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/InstancedParticleRenderer.java @@ -187,8 +187,7 @@ public class InstancedParticleRenderer { EaglercraftGPU.vertexAttribPointer(0, 2, GL_FLOAT, false, 8, 0); EaglercraftGPU.vertexAttribDivisor(0, 0); - EaglercraftGPU.bindVAOGLArrayBufferNow(instancesBuffer); - _wglBufferData(GL_ARRAY_BUFFER, particleBuffer.capacity(), GL_STREAM_DRAW); + EaglercraftGPU.bindVAOGLArrayBuffer(instancesBuffer); EaglercraftGPU.enableVertexAttribArray(1); EaglercraftGPU.vertexAttribPointer(1, 3, GL_FLOAT, false, 24, 0); @@ -315,7 +314,7 @@ public class InstancedParticleRenderer { int l = particleBuffer.limit(); particleBuffer.flip(); - _wglBufferData(GL_ARRAY_BUFFER, particleBuffer.capacity(), GL_STREAM_DRAW); + _wglBufferData(GL_ARRAY_BUFFER, (particleBuffer.remaining() + 0xFFF) & 0xFFFFF000, GL_STREAM_DRAW); _wglBufferSubData(GL_ARRAY_BUFFER, 0, particleBuffer); particleBuffer.position(p); diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/StreamBuffer.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/StreamBuffer.java index 5629753c..1a52cae6 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/StreamBuffer.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/StreamBuffer.java @@ -16,108 +16,53 @@ package net.lax1dude.eaglercraft.v1_8.opengl; -import net.lax1dude.eaglercraft.v1_8.internal.IVertexArrayGL; import net.lax1dude.eaglercraft.v1_8.internal.IBufferGL; import static net.lax1dude.eaglercraft.v1_8.opengl.RealOpenGLEnums.*; import static net.lax1dude.eaglercraft.v1_8.internal.PlatformOpenGL.*; +/** + * This streaming implementation was designed by reverse engineering the OpenGL + * driver that powers most Intel-based Chromebooks, performance may vary on + * other platforms + */ public class StreamBuffer { - public static final int poolSize = 4; + protected static IBufferGL buffer = null; - protected static final PoolInstance[] pool = new PoolInstance[poolSize]; - protected static int poolBufferID = 0; + protected static int currentOffset = 0; + protected static int currentSize = 0; - static { - for(int i = 0; i < poolSize; ++i) { - pool[i] = new PoolInstance(); - } - } - - protected static class PoolInstance { - - protected IBufferGL vertexBuffer = null; - protected int vertexBufferSize = 0; - - } - - private static void resizeInstance(PoolInstance instance, int requiredMemory) { - IBufferGL buffer = instance.vertexBuffer; + public static IBufferGL getBuffer() { if (buffer == null) { - buffer = _wglGenBuffers(); - instance.vertexBuffer = buffer; + return buffer = _wglGenBuffers(); } - int newSize = instance.vertexBufferSize; - if (newSize < requiredMemory) { - newSize = (requiredMemory + 0xFFFF) & 0xFFFF0000; - instance.vertexBufferSize = newSize; - } - EaglercraftGPU.bindGLArrayBuffer(buffer); - _wglBufferData(GL_ARRAY_BUFFER, newSize, GL_STREAM_DRAW); + return buffer; } - protected StreamBufferInstance[] buffers; - - protected final IStreamBufferInitializer initializer; - - public static class StreamBufferInstance { - - protected PoolInstance poolInstance = null; - protected IVertexArrayGL vertexArray = null; - - public boolean bindQuad16 = false; - public boolean bindQuad32 = false; - - public IVertexArrayGL getVertexArray() { - return vertexArray; + public static int uploadData(int elSize, int elCount, boolean quads) { + EaglercraftGPU.bindGLArrayBuffer(getBuffer()); + int off = (currentOffset + elSize - 1) / elSize; + if (quads) { + off = (off + 3) & -4; } - - public IBufferGL getVertexBuffer() { - return poolInstance.vertexBuffer; - } - - } - - public static interface IStreamBufferInitializer { - void initialize(IVertexArrayGL vertexArray, IBufferGL vertexBuffer); - } - - public StreamBuffer(IStreamBufferInitializer initializer) { - this.buffers = new StreamBufferInstance[poolSize]; - for(int i = 0; i < this.buffers.length; ++i) { - StreamBufferInstance j = new StreamBufferInstance(); - j.poolInstance = pool[i]; - this.buffers[i] = j; - } - this.initializer = initializer; - } - - public StreamBufferInstance getBuffer(int requiredMemory) { - StreamBufferInstance next = buffers[poolBufferID++ % buffers.length]; - resizeInstance(next.poolInstance, requiredMemory); - if(next.vertexArray == null) { - next.vertexArray = EaglercraftGPU.createGLVertexArray(); - initializer.initialize(next.vertexArray, next.poolInstance.vertexBuffer); - } - return next; - } - - public void destroy() { - for(int i = 0; i < buffers.length; ++i) { - StreamBufferInstance next = buffers[i]; - if(next.vertexArray != null) { - EaglercraftGPU.destroyGLVertexArray(next.vertexArray); - } + int offBytes = off * elSize; + int reqBytes = elCount * elSize; + if (currentSize - offBytes >= reqBytes) { + currentOffset = offBytes + reqBytes; + return off; + } else { + currentOffset = 0; + currentSize = (reqBytes + 0xFFFF) & 0xFFFFF000; + _wglBufferData(GL_ARRAY_BUFFER, currentSize, GL_STREAM_DRAW); + return 0; } } public static void destroyPool() { - for(int i = 0; i < pool.length; ++i) { - if(pool[i].vertexBuffer != null) { - _wglDeleteBuffers(pool[i].vertexBuffer); - pool[i].vertexBuffer = null; - } + if (buffer != null) { + _wglDeleteBuffers(buffer); + buffer = null; } } diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/ext/deferred/LensFlareMeshRenderer.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/ext/deferred/LensFlareMeshRenderer.java index 3236b284..bedb57aa 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/ext/deferred/LensFlareMeshRenderer.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/ext/deferred/LensFlareMeshRenderer.java @@ -316,7 +316,8 @@ public class LensFlareMeshRenderer { _wglUniform3f(streaksProgram.uniforms.u_flareColor3f, v.x * mag * 0.5f, v.y * mag * 0.5f, v.z * mag * 0.5f); EaglercraftGPU.bindGLVertexArray(streaksVertexArray); - _wglDrawElements(GL_TRIANGLES, streaksVertexCount + (streaksVertexCount >> 1), GL_UNSIGNED_SHORT, 0); + _wglDrawRangeElements(GL_TRIANGLES, 0, streaksVertexCount - 1, streaksVertexCount + (streaksVertexCount >> 1), + GL_UNSIGNED_SHORT, 0); ghostsProgram.useProgram(); diff --git a/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/ext/dynamiclights/DynamicLightBucketLoader.java b/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/ext/dynamiclights/DynamicLightBucketLoader.java index 9c421864..7a9bbb71 100644 --- a/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/ext/dynamiclights/DynamicLightBucketLoader.java +++ b/sources/main/java/net/lax1dude/eaglercraft/v1_8/opengl/ext/dynamiclights/DynamicLightBucketLoader.java @@ -46,8 +46,8 @@ public class DynamicLightBucketLoader { private int currentLightSourceBucketId = -1; private int lightingBufferSliceLength = -1; - public static final int MAX_LIGHTS_PER_CHUNK = 12; - public static final int LIGHTING_BUFFER_LENGTH = 16 * MAX_LIGHTS_PER_CHUNK + 16; + public static final int MAX_LIGHTS_PER_CHUNK = 8; + public static final int LIGHTING_BUFFER_LENGTH = 8 * MAX_LIGHTS_PER_CHUNK + 16; private final int lightSourceBucketsWidth; private final int lightSourceBucketsHeight; @@ -114,26 +114,36 @@ public class DynamicLightBucketLoader { int ser = currentLightSourceBucket.getEaglerSerial(); int max = currentLightSourceBucket.size(); if(max > 0) { + if(max > MAX_LIGHTS_PER_CHUNK) { + max = MAX_LIGHTS_PER_CHUNK; + } EaglercraftGPU.bindGLUniformBuffer(buffer_chunkLightingData); int offset = currentLightSourceBucketId * lightingBufferSliceLength; if (lightSourceBucketsSerials[currentLightSourceBucketId] != ser || lightSourceRenderPosSerials[currentLightSourceBucketId] != currentRenderPosSerial) { lightSourceBucketsSerials[currentLightSourceBucketId] = ser; lightSourceRenderPosSerials[currentLightSourceBucketId] = currentRenderPosSerial; - if(max > MAX_LIGHTS_PER_CHUNK) { - max = MAX_LIGHTS_PER_CHUNK; - } + int bucketXOff = -relativeBlockX & -16; + int bucketYOff = -relativeBlockY & -16; + int bucketZOff = -relativeBlockZ & -16; chunkLightingDataCopyBuffer.clear(); + chunkLightingDataCopyBuffer.putInt( + ((bucketXOff & 0xFF) << 8) | + ((bucketYOff & 0xFF) << 16) | + ((bucketZOff & 0xFF) << 24) + ); chunkLightingDataCopyBuffer.putInt(max); - chunkLightingDataCopyBuffer.putInt(0); //padding - chunkLightingDataCopyBuffer.putInt(0); //padding - chunkLightingDataCopyBuffer.putInt(0); //padding + chunkLightingDataCopyBuffer.putInt(0); // padding + chunkLightingDataCopyBuffer.putInt(0); // padding for(int i = 0; i < max; ++i) { DynamicLightInstance dl = currentLightSourceBucket.get(i); - chunkLightingDataCopyBuffer.putFloat((float)(dl.posX - currentRenderX)); - chunkLightingDataCopyBuffer.putFloat((float)(dl.posY - currentRenderY)); - chunkLightingDataCopyBuffer.putFloat((float)(dl.posZ - currentRenderZ)); - chunkLightingDataCopyBuffer.putFloat(dl.radius); + int x = (int)((dl.posX - currentRenderX + bucketXOff) * (1.0f / 0.0009765923f)); + int y = (int)((dl.posY - currentRenderY + bucketYOff) * (1.0f / 0.0009765923f)); + int z = (int)((dl.posZ - currentRenderZ + bucketZOff) * (1.0f / 0.0009765923f)); + int r = (int)(dl.radius * (1.0f / 0.0009765923f)); + if (r > 32767) r = 32767; + chunkLightingDataCopyBuffer.putInt((x & 0xFFFF) | (z << 16)); + chunkLightingDataCopyBuffer.putInt((y & 0xFFFF) | (r << 16)); } chunkLightingDataCopyBuffer.flip(); _wglBufferSubData(_GL_UNIFORM_BUFFER, offset, chunkLightingDataCopyBuffer); diff --git a/sources/platform-api/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformOpenGL.java b/sources/platform-api/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformOpenGL.java index b8459b2d..06dfff7e 100644 --- a/sources/platform-api/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformOpenGL.java +++ b/sources/platform-api/java/net/lax1dude/eaglercraft/v1_8/internal/PlatformOpenGL.java @@ -204,6 +204,8 @@ public class PlatformOpenGL { public static native void _wglDrawElements(int mode, int count, int type, int offset); + public static native void _wglDrawRangeElements(int mode, int start, int end, int count, int type, int offset); + public static native void _wglDrawArraysInstanced(int mode, int first, int count, int instanced); public static native void _wglDrawElementsInstanced(int mode, int count, int type, int offset, int instanced); diff --git a/sources/resources/EPKVersionIdentifier.txt b/sources/resources/EPKVersionIdentifier.txt index 41915701..471a6eb2 100644 --- a/sources/resources/EPKVersionIdentifier.txt +++ b/sources/resources/EPKVersionIdentifier.txt @@ -1 +1 @@ -u52 \ No newline at end of file +u53 \ No newline at end of file diff --git a/sources/resources/assets/eagler/glsl/core.fsh b/sources/resources/assets/eagler/glsl/core.fsh index 660bc913..fa495f1d 100644 --- a/sources/resources/assets/eagler/glsl/core.fsh +++ b/sources/resources/assets/eagler/glsl/core.fsh @@ -63,7 +63,7 @@ uniform float u_alphaTestRef1f; #ifdef COMPILE_ENABLE_MC_LIGHTING uniform int u_lightsEnabled1i; -uniform vec4 u_lightsDirections4fv[4]; +uniform vec4 u_lightsDirections4fv[2]; uniform vec3 u_lightsAmbient3f; #ifndef COMPILE_NORMAL_ATTRIB uniform vec3 u_uniformNormal3f; @@ -166,20 +166,14 @@ void main() { #else vec3 normal = u_uniformNormal3f; #endif - float diffuse = 0.0; vec4 light; -#ifdef EAGLER_HAS_GLES_300 - for(int i = 0; i < u_lightsEnabled1i; ++i) { -#else - for(int i = 0; i < 4; ++i) { -#endif - light = u_lightsDirections4fv[i]; - diffuse += max(dot(light.xyz, normal), 0.0) * light.w; -#ifndef EAGLER_HAS_GLES_300 - if(i + 1 >= u_lightsEnabled1i) { + float diffuse = 0.0; + for(int i = 0; i < 2; ++i) { + if(i >= u_lightsEnabled1i) { break; } -#endif + light = u_lightsDirections4fv[i]; + diffuse += max(dot(light.xyz, normal), 0.0) * light.w; } color.rgb *= min(u_lightsAmbient3f + vec3(diffuse), 1.0); #endif diff --git a/sources/resources/assets/eagler/glsl/dynamiclights/accel_particle_dynamiclights.fsh b/sources/resources/assets/eagler/glsl/dynamiclights/accel_particle_dynamiclights.fsh index 0cf02c74..8b44fb15 100644 --- a/sources/resources/assets/eagler/glsl/dynamiclights/accel_particle_dynamiclights.fsh +++ b/sources/resources/assets/eagler/glsl/dynamiclights/accel_particle_dynamiclights.fsh @@ -20,7 +20,6 @@ precision lowp int; precision mediump float; precision mediump sampler2D; -in vec4 v_position4f; in vec2 v_texCoord2f; in vec4 v_color4f; in vec2 v_lightmap2f; @@ -30,16 +29,6 @@ layout(location = 0) out vec4 output4f; uniform sampler2D u_inputTexture; uniform sampler2D u_lightmapTexture; -uniform mat4 u_inverseViewMatrix4f; - -layout(std140) uniform u_chunkLightingData { - mediump int u_dynamicLightCount1i; - mediump int _paddingA_; - mediump int _paddingB_; - mediump int _paddingC_; - mediump vec4 u_dynamicLightArray[12]; -}; - void main() { vec4 color = texture(u_inputTexture, v_texCoord2f) * v_color4f; @@ -47,20 +36,7 @@ void main() { discard; } - vec4 dlight; - float blockLight = v_lightmap2f.x; - if(u_dynamicLightCount1i > 0) { - vec4 worldPosition4f = u_inverseViewMatrix4f * v_position4f; - worldPosition4f.xyz /= worldPosition4f.w; - int safeLightCount = u_dynamicLightCount1i > 12 ? 0 : u_dynamicLightCount1i; - for(int i = 0; i < safeLightCount; ++i) { - dlight = u_dynamicLightArray[i]; - dlight.xyz = dlight.xyz - worldPosition4f.xyz; - blockLight = max((dlight.w - length(dlight.xyz)) * 0.066667, blockLight); - } - } - - color *= texture(u_lightmapTexture, vec2(blockLight, v_lightmap2f.y)); + color *= texture(u_lightmapTexture, v_lightmap2f); output4f = color; } diff --git a/sources/resources/assets/eagler/glsl/dynamiclights/accel_particle_dynamiclights.vsh b/sources/resources/assets/eagler/glsl/dynamiclights/accel_particle_dynamiclights.vsh index 202bd153..fc637da2 100644 --- a/sources/resources/assets/eagler/glsl/dynamiclights/accel_particle_dynamiclights.vsh +++ b/sources/resources/assets/eagler/glsl/dynamiclights/accel_particle_dynamiclights.vsh @@ -28,21 +28,27 @@ layout(location = 3) in vec2 p_lightMap2f; layout(location = 4) in vec2 p_particleSize_texCoordsSize_2i; layout(location = 5) in vec4 p_color4f; -out vec4 v_position4f; out vec2 v_texCoord2f; out vec4 v_color4f; out vec2 v_lightmap2f; uniform mat4 u_modelViewMatrix4f; uniform mat4 u_projectionMatrix4f; +uniform mat4 u_inverseViewMatrix4f; uniform vec3 u_texCoordSize2f_particleSize1f; uniform vec3 u_transformParam_1_2_5_f; uniform vec2 u_transformParam_3_4_f; uniform vec4 u_color4f; +layout(std140) uniform u_chunkLightingData { + mediump uvec2 u_dynamicLightOffsetCount2i; + mediump int _paddingA_; + mediump int _paddingB_; + mediump uvec4 u_dynamicLightArray[4]; +}; + void main() { v_color4f = u_color4f * p_color4f.bgra; - v_lightmap2f = p_lightMap2f; vec2 tex2f = a_position2f * 0.5 + 0.5; tex2f.y = 1.0 - tex2f.y; @@ -56,6 +62,40 @@ void main() { pos3f += u_transformParam_1_2_5_f * spos2f.xyy; pos3f.zx += u_transformParam_3_4_f * spos2f; - v_position4f = u_modelViewMatrix4f * vec4(pos3f, 1.0); - gl_Position = u_projectionMatrix4f * v_position4f; + vec4 pos4f = u_modelViewMatrix4f * vec4(pos3f, 1.0); + gl_Position = u_projectionMatrix4f * pos4f; + + float blockLight = 0.0; + + vec4 dlight; + uvec4 dlighti1, dlighti2; + if(u_dynamicLightOffsetCount2i.y > 0u) { + vec3 dlightOffset = vec3(ivec3( + int(u_dynamicLightOffsetCount2i.x << 16), + int(u_dynamicLightOffsetCount2i.x << 8), + int(u_dynamicLightOffsetCount2i.x) + ) >> 24); + vec4 worldPosition4f = u_inverseViewMatrix4f * pos4f; + worldPosition4f.xyz = worldPosition4f.xyz / worldPosition4f.w + dlightOffset; + for(uint i = 0u; i < 4u; ++i) { + dlighti1 = u_dynamicLightArray[i]; + dlighti2 = dlighti1 << 16; + + dlight = vec4(ivec4(ivec2(dlighti2.xy), ivec2(dlighti1.xy)) >> 16) * 0.0009765923; + dlight.xyz = dlight.xyz - worldPosition4f.xyz; + blockLight = max((dlight.w - length(dlight.xyz)) * 0.066667, blockLight); + if(i * 2u + 1u >= u_dynamicLightOffsetCount2i.y) { + break; + } + + dlight = vec4(ivec4(ivec2(dlighti2.zw), ivec2(dlighti1.zw)) >> 16) * 0.0009765923; + dlight.xyz = dlight.xyz - worldPosition4f.xyz; + blockLight = max((dlight.w - length(dlight.xyz)) * 0.066667, blockLight); + if(i * 2u + 2u >= u_dynamicLightOffsetCount2i.y) { + break; + } + } + } + + v_lightmap2f = vec2(max(p_lightMap2f.x, blockLight), p_lightMap2f.y); } diff --git a/sources/resources/assets/eagler/glsl/dynamiclights/core_dynamiclights.fsh b/sources/resources/assets/eagler/glsl/dynamiclights/core_dynamiclights.fsh index 42897792..5097be27 100644 --- a/sources/resources/assets/eagler/glsl/dynamiclights/core_dynamiclights.fsh +++ b/sources/resources/assets/eagler/glsl/dynamiclights/core_dynamiclights.fsh @@ -16,7 +16,9 @@ * */ +#if defined(COMPILE_ENABLE_TEX_GEN) || defined(COMPILE_ENABLE_FOG) in vec4 v_position4f; +#endif #ifdef COMPILE_TEXTURE_ATTRIB in vec2 v_texture2f; @@ -61,7 +63,7 @@ uniform float u_alphaTestRef1f; #ifdef COMPILE_ENABLE_MC_LIGHTING uniform int u_lightsEnabled1i; -uniform vec4 u_lightsDirections4fv[4]; +uniform vec4 u_lightsDirections4fv[2]; uniform vec3 u_lightsAmbient3f; #endif @@ -88,15 +90,9 @@ uniform mat4 u_textureMat4f01; uniform vec2 u_textureAnisotropicFix; #endif -uniform mat4 u_inverseViewMatrix4f; - -layout(std140) uniform u_chunkLightingData { - mediump int u_dynamicLightCount1i; - mediump int _paddingA_; - mediump int _paddingB_; - mediump int _paddingC_; - mediump vec4 u_dynamicLightArray[12]; -}; +#ifdef COMPILE_ENABLE_LIGHTMAP +in float v_dynamicLight1f; +#endif layout(location = 0) out vec4 output4f; @@ -151,17 +147,9 @@ void main() { #else float blockLight = u_textureCoords02.x; #endif - vec4 dlight; - if(u_dynamicLightCount1i > 0) { - vec4 worldPosition4f = u_inverseViewMatrix4f * v_position4f; - worldPosition4f.xyz /= worldPosition4f.w; - int safeLightCount = u_dynamicLightCount1i > 12 ? 0 : u_dynamicLightCount1i; - for(int i = 0; i < safeLightCount; ++i) { - dlight = u_dynamicLightArray[i]; - dlight.xyz = dlight.xyz - worldPosition4f.xyz; - blockLight = max((dlight.w - length(dlight.xyz)) * 0.066667, blockLight); - } - } + + blockLight = max(blockLight, v_dynamicLight1f); + #ifdef COMPILE_LIGHTMAP_ATTRIB color *= texture(u_samplerLightmap, vec2(blockLight, v_lightmap2f.y)); #else @@ -179,16 +167,18 @@ void main() { #endif +#ifdef COMPILE_ENABLE_MC_LIGHTING #ifdef COMPILE_NORMAL_ATTRIB vec3 normal = v_normal3f; #else vec3 normal = u_uniformNormal3f; #endif - -#ifdef COMPILE_ENABLE_MC_LIGHTING vec4 light; float diffuse = 0.0; - for(int i = 0; i < u_lightsEnabled1i; ++i) { + for(int i = 0; i < 2; ++i) { + if(i >= u_lightsEnabled1i) { + break; + } light = u_lightsDirections4fv[i]; diffuse += max(dot(light.xyz, normal), 0.0) * light.w; } diff --git a/sources/resources/assets/eagler/glsl/dynamiclights/core_dynamiclights.vsh b/sources/resources/assets/eagler/glsl/dynamiclights/core_dynamiclights.vsh index 023b204f..6ecb9dee 100644 --- a/sources/resources/assets/eagler/glsl/dynamiclights/core_dynamiclights.vsh +++ b/sources/resources/assets/eagler/glsl/dynamiclights/core_dynamiclights.vsh @@ -18,7 +18,13 @@ in vec3 a_position3f; +#if defined(COMPILE_ENABLE_TEX_GEN) || defined(COMPILE_ENABLE_FOG) +#define _COMPILE_VARYING_POSITION +#endif + +#ifdef _COMPILE_VARYING_POSITION out vec4 v_position4f; +#endif #ifdef COMPILE_ENABLE_TEX_GEN out vec3 v_objectPosition3f; @@ -46,16 +52,32 @@ out vec2 v_lightmap2f; uniform mat4 u_textureMat4f02; #endif +#ifdef COMPILE_ENABLE_LIGHTMAP +out float v_dynamicLight1f; +#endif + uniform mat4 u_modelviewMat4f; uniform mat4 u_projectionMat4f; +uniform mat4 u_inverseViewMatrix4f; #define TEX_MAT3(mat4In) mat3(mat4In[0].xyw,mat4In[1].xyw,mat4In[3].xyw) +layout(std140) uniform u_chunkLightingData { + mediump uvec2 u_dynamicLightOffsetCount2i; + mediump int _paddingA_; + mediump int _paddingB_; + mediump uvec4 u_dynamicLightArray[4]; +}; + void main() { #ifdef COMPILE_ENABLE_TEX_GEN v_objectPosition3f = a_position3f; #endif +#ifndef _COMPILE_VARYING_POSITION + vec4 v_position4f; +#endif + v_position4f = u_modelviewMat4f * vec4(a_position3f, 1.0); #ifdef COMPILE_TEXTURE_ATTRIB @@ -75,6 +97,42 @@ void main() { vec3 v_lightmapTmp3f = TEX_MAT3(u_textureMat4f02) * vec3(a_lightmap2f, 1.0); v_lightmap2f = v_lightmapTmp3f.xy / v_lightmapTmp3f.z; #endif - + gl_Position = u_projectionMat4f * v_position4f; + +#ifdef COMPILE_ENABLE_LIGHTMAP + float blockLight = 0.0; + + vec4 dlight; + uvec4 dlighti1, dlighti2; + if(u_dynamicLightOffsetCount2i.y > 0u) { + vec3 dlightOffset = vec3(ivec3( + int(u_dynamicLightOffsetCount2i.x << 16), + int(u_dynamicLightOffsetCount2i.x << 8), + int(u_dynamicLightOffsetCount2i.x) + ) >> 24); + vec4 worldPosition4f = u_inverseViewMatrix4f * v_position4f; + worldPosition4f.xyz = worldPosition4f.xyz / worldPosition4f.w + dlightOffset; + for(uint i = 0u; i < 4u; ++i) { + dlighti1 = u_dynamicLightArray[i]; + dlighti2 = dlighti1 << 16; + + dlight = vec4(ivec4(ivec2(dlighti2.xy), ivec2(dlighti1.xy)) >> 16) * 0.0009765923; + dlight.xyz = dlight.xyz - worldPosition4f.xyz; + blockLight = max((dlight.w - length(dlight.xyz)) * 0.066667, blockLight); + if(i * 2u + 1u >= u_dynamicLightOffsetCount2i.y) { + break; + } + + dlight = vec4(ivec4(ivec2(dlighti2.zw), ivec2(dlighti1.zw)) >> 16) * 0.0009765923; + dlight.xyz = dlight.xyz - worldPosition4f.xyz; + blockLight = max((dlight.w - length(dlight.xyz)) * 0.066667, blockLight); + if(i * 2u + 2u >= u_dynamicLightOffsetCount2i.y) { + break; + } + } + } + + v_dynamicLight1f = blockLight; +#endif } diff --git a/sources/resources/assets/eagler/silence_loop.wav b/sources/resources/assets/eagler/silence_loop.wav index d1eee0e879bea20c783ad28526dd301f44cbe5a5..e3a005ef3966accf457f66bd4f1abdfe8eeb762a 100644 GIT binary patch literal 80044 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 X4C681t)QxUVwTE64Jv}Q%!>HGW-vDq literal 8044 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