Compare commits

..
35 Commits
Author SHA1 Message Date
DarkShadow44 69428c12f6 Fix MixinRendererLivingEntity_GlintClock expected count (#2248) 2026-10-10 18:06:45 -07:00
Jason Mitchell 3978be161d Answer legacy GL queries from tracked state (#2247) 2026-10-10 17:31:02 -07:00
Jason Mitchell 2ed838acf1 Fix shaders that redefine a GLSL built-in failing on SDL-GPU and GLES (#2245)
* Retry a rejected compile with the shader's own functions renamed
2026-10-10 14:05:33 -07:00
Eclipse-Sol d29a8278cd Make API functions for enderstorage to use (#2244)
* Make API functions for enderstorage to use

* move it into its own package
2026-10-10 16:54:21 -04:00
Eclipse-Sol 2f1f380e8e Bug Cleanup after 2.9RC2 (#2239) 2026-10-10 13:22:49 -07:00
Jason Mitchell 990dadc03f Reject non 3.3 core with a more clear message (#2243) 2026-10-10 12:58:51 -07:00
Ancalagon 1946c2ad65 Fix dark AE2 cables when shaders enabled (#2223) 2026-10-08 23:27:24 -07:00
Jason Mitchell bfb66d84ee Fix interop with Serene Seasons and other similar mods (#2240) 2026-10-08 23:09:34 -07:00
DarkShadow44 cae4fea2bb Fix Little Tiles rendering issues (#2238) 2026-10-08 17:13:36 -07:00
Jason Mitchell 78abc05711 Update Readme 2026-10-07 20:34:33 -07:00
Eclipse-Sol 159f7ff8cc Fix NaNs on EP (#2231)
* Fix NaNs on EP

* Also add hidden writes handling

* optimize this mess

* Another optimization pass
2026-10-07 17:59:21 -04:00
Ranzu 0d959c2b90 Add Darkmode Transformer target (#2221) 2026-10-07 13:55:18 -07:00
Alexdoru ce2462cad3 Fix server thread leak in ThreadedBlockData (#2230) 2026-10-07 12:30:09 -07:00
Death Fuel 5457df5369 Marginal improvements (#2222) 2026-10-07 12:23:32 -07:00
Eclipse-Sol acedba4eac Modern Font Sheets (#2225)
* Initial commit for modern-font-sheets

* Fix dev env error log

* Fix private use characters still using their own unifont character
2026-10-07 11:19:19 -04:00
Alexdoru 21a054a4db fix world client leak in CloudRenderer (#2227) 2026-10-06 06:22:08 -07:00
Jason Mitchell d056bf6fa7 Run image atomics natively on Metal where the runtime allows (#2226) 2026-10-05 22:57:30 -07:00
Eclipse-Sol 5ed1c92590 SDL Vertex Replay + Dragon API (#2224) 2026-10-05 20:23:36 -07:00
Eclipse-Sol 3389b952d0 Fix SDL compile failure when an image atomic's result is used (#2218) 2026-10-04 18:56:12 -07:00
Jason Mitchell b49d37d827 Ignore additional tags when deriving the mod version (#2219) 2026-10-04 18:33:06 -07:00
Jason Mitchell 12857006c6 Harden SDL-GPU buffer mapping lifetime (#2217) 2026-10-04 16:31:48 -07:00
Eclipse-Sol 38af64f10c Initial commit for shader-loading-optimizations (#2213) 2026-10-04 15:36:50 -07:00
Jason Mitchell 10922edca3 Include missed scripts (#2216) 2026-10-04 15:22:09 -07:00
Jason Mitchell 2a8c8756d3 Add renderer selection to video settings - (#2215) 2026-10-04 15:16:51 -07:00
Jason Mitchell 23e36f2b76 Narrow overly broad transformer exclusions for PowerConverters (#2214) 2026-10-04 14:27:43 -07:00
Jason Mitchell fcf8ad064a Fix terrain disappearing at screen edges with compute culling (#2212) 2026-10-04 13:05:14 -07:00
Jason Mitchell 6dfb0312b1 Answer legacy stack and client state queries from GLSM (#2211) 2026-10-04 11:43:56 -07:00
Eclipse-Sol 0a4d48964e Cache Iris Shaders (#2206) 2026-10-03 20:06:39 -07:00
Ranzu 231a2ab216 Add Darkmode Transformer target (#2205) 2026-10-03 15:28:37 -07:00
Jason Mitchell 61bb779dea Fix crash when mods measure text off-thread with a custom font (#2204) 2026-10-03 15:28:15 -07:00
Jason Mitchell 7cd97d1216 Fix HUD caching flicker and match non-cached GL state (#2201) 2026-10-03 13:19:29 -07:00
Eclipse-Sol fe82cd320a Plenty of fixes for GTNH (#2202) 2026-10-03 13:19:15 -07:00
Li Xiangrong c7a0f3a208 Default raw custom textures to bilinear filtering and clamping (#2198) 2026-10-03 11:57:30 -07:00
Matt Van Horn 338d94197c Settle frame pacing after an inconsistent vsync gate (#2197) 2026-10-03 11:51:30 -07:00
Death Fuel 75a0278138 Fix splash issues (#2200) 2026-10-03 11:42:57 -07:00
566 changed files with 23648 additions and 12835 deletions

No files matched your search

+23
View File
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
# usage: companion-version.sh <name> <catalog-key>; TAG is <name>-v<version>[-N] or unset
set -euo pipefail
name="${1:?name required}"
key="${2:?catalog key required}"
cd "$(dirname "${BASH_SOURCE[0]}")/../.."
base="$(sed -nE "s/^$key = \"(.*)\"\$/\\1/p" gradle/libs.versions.toml)"
[[ -n "$base" ]] || { echo "$key not found" >&2; exit 1; }
tag="${TAG:-}"
if [[ -z "$tag" ]]; then
echo "$base"
exit 0
fi
version="${tag#"$name"-v}"
if [[ "$version" == "$tag" || ( "$version" != "$base" && ! "$version" =~ ^"$base"-[1-9][0-9]*$ ) ]]; then
echo "bad tag '$tag': want $name-v$base[-N]" >&2
exit 1
fi
echo "$version"
+9
View File
@@ -0,0 +1,9 @@
#!/usr/bin/env bash
# usage: release-companion-jar.sh <jar> <notes>; TAG is <name>-v<version>
set -euo pipefail
: "${TAG:?TAG required}"
jar="${1:?jar required}"
notes="${2:?notes required}"
gh release create "$TAG" "$jar" --latest=false --title "angelica-${TAG%%-v*} ${TAG#*-v}" --notes "$notes"
+2
View File
@@ -10,4 +10,6 @@ on:
jobs:
build-and-test:
uses: GTNewHorizons/GTNH-Actions-Workflows/.github/workflows/build-and-test.yml@master
with:
disable-spotless-pr: true
secrets: inherit
+53
View File
@@ -0,0 +1,53 @@
name: Build angelica-moltenvk
on:
push:
tags:
- "moltenvk-v*"
workflow_dispatch:
inputs:
publish:
description: Publish angelica-moltenvk.jar to the GTNH maven
type: boolean
required: false
default: false
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21
- uses: gradle/actions/setup-gradle@v4
- name: Compute version
shell: bash
env:
TAG: ${{ github.ref_type == 'tag' && github.ref_name || '' }}
run: |
version="$(bash .github/scripts/companion-version.sh moltenvk lwjgl3Release)"
echo "MOLTENVK_VERSION=$version" >> "$GITHUB_ENV"
- name: Build angelica-moltenvk.jar
run: ./gradlew -PmoltenvkVersion="$MOLTENVK_VERSION" :moltenvk:distJar
- uses: actions/upload-artifact@v4
with:
name: angelica-moltenvk
path: moltenvk/build/libs/angelica-moltenvk-*.jar
if-no-files-found: error
- name: Publish to GTNH maven
if: github.ref_type == 'tag' || inputs.publish
env:
MAVEN_USER: ${{ secrets.MAVEN_USER }}
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
run: ./gradlew -PpublishMoltenVK=true -PmoltenvkVersion="$MOLTENVK_VERSION" :moltenvk:publish
- name: Create GitHub release
if: github.ref_type == 'tag'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ github.ref_name }}
run: bash .github/scripts/release-companion-jar.sh moltenvk/build/libs/angelica-moltenvk-*.jar "MoltenVK for the SDL GPU Vulkan driver on macOS."
+18 -2
View File
@@ -12,6 +12,9 @@ on:
required: false
default: false
permissions:
contents: write
jobs:
build:
strategy:
@@ -66,8 +69,15 @@ jobs:
cp "$d"/* "$dest/"
done
find tracy-client/src/main/resources/natives -type f
- name: Compute version
shell: bash
env:
TAG: ${{ github.ref_type == 'tag' && github.ref_name || '' }}
run: |
version="$(bash .github/scripts/companion-version.sh tracy tracy)"
echo "TRACY_JAR_VERSION=$version" >> "$GITHUB_ENV"
- name: Build angelica-tracy.jar
run: ./gradlew :tracy-client:distJar
run: ./gradlew -PtracyJarVersion="$TRACY_JAR_VERSION" :tracy-client:distJar
- uses: actions/upload-artifact@v4
with:
name: angelica-tracy
@@ -78,4 +88,10 @@ jobs:
env:
MAVEN_USER: ${{ secrets.MAVEN_USER }}
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
run: ./gradlew -PpublishTracy=true :tracy-client:publish
run: ./gradlew -PpublishTracy=true -PtracyJarVersion="$TRACY_JAR_VERSION" :tracy-client:publish
- name: Create GitHub release
if: github.ref_type == 'tag'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ github.ref_name }}
run: bash .github/scripts/release-companion-jar.sh tracy-client/build/libs/angelica-tracy-*.jar "Tracy profiler backend for Angelica."
+1 -1
View File
@@ -3,7 +3,7 @@ name: Release tagged build
on:
push:
tags: [ '*', '!tracy-v*' ]
tags: [ '*', '!tracy-v*', '!moltenvk-v*' ]
permissions:
contents: write
+40 -12
View File
@@ -2,31 +2,59 @@
<sup>**[CurseForge](https://www.curseforge.com/minecraft/mc-mods/angelica) | [Modrinth](https://modrinth.com/mod/angelica)**</sup>
🌐 **Languages:** [English](README.MD) | [Українська](README_uk.MD)
* The little Angle [sic] that supports shaders while saving your fps from a certain death -boubou
* Is it obtuse, or accute? -Caedis
* It's a cute angle -mitchej123
* It's the angel that pulls you out of OptiFine's hell -Roadhog360
## **Beta Quality - Here there be Dragons**
* Shaders - Beta
## What's in it
* Shaders - Iris backport & enhancements
* Terrain - Celeritas 2.5.12 with threaded chunk building and translucency sorting
* GL 3.3 core profile with Java based fixed function pipeline emulation
* Renderer selection - OpenGL, or SDL GPU for Vulkan/Metal/Direct3D 12
* Batching and instancing for entities, tile entities, items, particles and weather
* Dynamic lights
* HUD caching
* Custom fonts and text effects
* MCPatcher - connected textures, custom item textures, custom colors, random mobs, better skies
* Bundled NotFine, zoom, FPS reducer
## Status
* OpenGL + Celeritas terrain - Default
* Shaders - Generally Available
* Complementary + Euphoria Patches is the primary compatibility target
* Sildurs, Seus, and any other shaders might work
* SDL GPU renderer - Experimental
* MCPatcher features - Alpha
* HUD caching - Semi-stable
## SDL GPU renderer
Optional, OpenGL is the default
* Vulkan, Metal (macOS) and Direct3D 12 (Windows) through SDL3
* Needs [LWJGL3ify](https://github.com/GTNewHorizons/lwjgl3ify/releases/) 3.0.25+
* Falls back to OpenGL when no device can be created
* Vulkan on macOS via MoltenVK from the optional `angelica-moltenvk` jar
## Core Profile
Targets GL 3.3+ core profile
* FFP emulation via generated GLSL shaders
* Display list emulation
* Software matrix stacks and state management through GLSM
* GL_QUADS → GL_TRIANGLES conversion
* GL_QUADS -> GL_TRIANGLES conversion
* Automatic transformation of mod shaders that use compat-only GLSL builtins (`gl_ModelViewMatrix`, `texture2D`, etc.)
# Required Dependencies
* [UniMixins](https://github.com/LegacyModdingMC/UniMixins/releases) >= 0.1.23
* [GTNHLib](https://github.com/GTNewHorizons/GTNHLib/releases) >= 0.11.19
* NOTE: Some mods are not required, but a specific version is required if present - see: `Permanent Incompatibilities` below.
* NOTE: Some mods are not required, but a specific version is required if present - see: `Minimum versions` below.
# Minimum versions (if present)
* [LWJGL3ify](https://github.com/GTNewHorizons/lwjgl3ify/releases/) 1.5.3+ (3.0.25+ for the SDL GPU renderer)
* [Hodgepodge](https://github.com/GTNewHorizons/Hodgepodge/releases) 2.4.4+
* [ArchaicFix](https://www.curseforge.com/minecraft/mc-mods/archaicfix/files/all?page=1&pageSize=20) 0.7.0+
* [CodeChickenCore](https://github.com/GTNewHorizons/CodeChickenCore/releases) 1.2.0+ - Threading issues below that
* [Amazing Trophies](https://github.com/GTNewHorizons/Amazing-Trophies/releases) 1.2.2+
# Known (temporary) Incompatibilities
* Forge Relocation - Currently non Thread Safe; safe if not in use
@@ -45,15 +73,15 @@ Targets GL 3.3+ core profile
* FastCraft [Disabled, won't fix]
* BetterFPS [Disabled, won't fix]
* OptimizationsAndTweaks [Disabled, won't fix]
* [Amazing Trophies 1.2.1 and below](https://github.com/GTNewHorizons/Amazing-Trophies/releases) - Use 1.2.2+
* [LWJGL3ify 1.5.4 and below](https://github.com/GTNewHorizons/lwjgl3ify/releases/) - Use the latest version available, 1.5.7+
* [ArchaicFix 0.6.2 and below](https://www.curseforge.com/minecraft/mc-mods/archaicfix/files/all?page=1&pageSize=20) - Use 0.7.0 or above
* [Hodgepodge 2.4.3 and below](https://github.com/GTNewHorizons/Hodgepodge/releases) - Use 2.4.5 or above
* CodeChicken[Core](https://github.com/GTNewHorizons/CodeChickenCore/releases)/[Lib](https://github.com/GTNewHorizons/CodeChickenLib/releases) <1.2.0 - Threading issues, use 1.3.0+
* Neodymium - Untested, but unlikely to be compatible. (Chunk Meshing is definitely incompatible, other features might work depending on config)
* SmoothFont - Equivalent functionality added
* ClientFixer - Use a resource pack like [GTNH-FTI-Standard-Font](https://github.com/Eldrinn-Elantey/GTNH-FTI-Standard-Font)
# Building
* `./gradlew build`, `./gradlew test`
* Subprojects: `glsm` (state manager, FFP emulation, backends), `lwjgl3-backend`, `sdl-gpu`, `tracy-client`, `moltenvk`
* Read the [AI policy](AI_POLICY.md) before opening a PR
# Credits
* ShadersMod
* Original ShadersMods: karyonix, sonic ether, id_miner, daxnitro
@@ -96,7 +124,7 @@ Targets GL 3.3+ core profile
* FalsePattern for [FalseTweaks](https://github.com/falsepattern/falsetweaks) - Item Rendering Optimizations taken from FalseTweaks under the LGPL 3.0 license
* Beddium
* Venn and Falsepattern for [Beddium](https://github.com/vfx-dev/Beddium) - MinimapRenderer, SafeChunkTracker, and some debug commands adapted under the LGPL 3.0 License
* [Mesa](https://gitlab.freedesktop.org/mesa/mesa) — Code ported and/or inspired from Mesa - under the MIT licensed
* [Mesa](https://gitlab.freedesktop.org/mesa/mesa) - Code ported and/or inspired from Mesa - under the MIT license
* Display list ID allocator ported from [util_idalloc](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/util/u_idalloc.c) (Copyright 2017 Valve Corporation)
* FFP vertex shader generation inspired by ffvertex_prog.c (Copyright 2007 VMware, Inc.)
* FFP fragment shader generation inspired by ff_fragment_shader.c (Copyright 2007, 2009 VMware, Inc. / 2010-2011 Intel Corporation)
+13
View File
@@ -6,6 +6,8 @@ plugins {
id("com.gtnewhorizons.gtnhconvention")
}
apply(from = "gradle/mod-version.gradle.kts")
val lwjglDebug = false
val gpuHud = false
val renderdoc = false
@@ -146,6 +148,17 @@ fun Test.configureAngelicaJava8() {
}
}
if (isMacOs && providers.gradleProperty("clientJvmVendor").isPresent) {
for (javaVersion in listOf(17, 21, 25)) {
tasks.named<JavaExec>("runClient$javaVersion") {
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(javaVersion)
vendor = JvmVendorSpec.matching(providers.gradleProperty("clientJvmVendor").get())
}
}
}
}
tasks.test {
useJUnitPlatform { excludeTags = setOf("gl-core") }
configureAngelicaJava8()
+3
View File
@@ -93,6 +93,9 @@ DependencyHandlerScope.of(dependencies).apply {
shadowImplementation(project(":lwjgl3-backend")) { isTransitive = false }
shadowImplementation(project(":sdl-gpu")) { isTransitive = false }
runtimeOnlyNonPublishable(project(":tracy-client")) { isTransitive = false }
if (System.getProperty("os.name").lowercase().contains("mac")) {
runtimeOnlyNonPublishable(project(":moltenvk")) { isTransitive = false }
}
devOnlyNonPublishable(libs.async.profiler)
shadowImplementation(libs.eventbus)
@@ -17,7 +17,7 @@ public final class SystemProperties {
// Backend selection
public static final String KEY_USE_SDL_GPU = "angelica.sdlgpu.enable";
public static final boolean USE_SDL_GPU = Boolean.getBoolean(KEY_USE_SDL_GPU);
public static final Boolean SDL_GPU_OVERRIDE = parseBooleanOverride(KEY_USE_SDL_GPU);
public static final String GL_PROFILE = System.getProperty("angelica.glProfile", "");
public static final boolean DISABLE_NVIDIA_WORKAROUND = Boolean.getBoolean("angelica.disableNvidiaWorkaround");
public static final boolean DISABLE_LTW_WORKAROUND = Boolean.getBoolean("angelica.disableLtwWorkaround");
@@ -28,7 +28,8 @@ public final class SystemProperties {
// SDL-GPU
public static final String KEY_SDL_GPU_DRIVER = "angelica.sdlgpu.driver";
public static final String SDL_GPU_DRIVER = System.getProperty(KEY_SDL_GPU_DRIVER, "");
public static final String SDL_GPU_DRIVER_OVERRIDE = parseStringOverride(KEY_SDL_GPU_DRIVER);
public static final String MOLTENVK_DIR = System.getProperty("angelica.moltenvk.dir", "angelica" + File.separator + "natives" + File.separator + "moltenvk");
public static final boolean SDL_GPU_DEBUG = Boolean.getBoolean("angelica.sdlgpu.debug");
private static final SdlAssertionMode ENCODER_ASSERTIONS = parseEnum("angelica.sdlgpu.encoderAssertions", SdlAssertionMode.OFF, SdlAssertionMode.WARN, SdlAssertionMode.class);
public static final boolean SDL_ENCODER_ASSERTIONS = ENCODER_ASSERTIONS != SdlAssertionMode.OFF;
@@ -61,8 +62,10 @@ public final class SystemProperties {
public static final long FLYBY_TIME_OF_DAY = Math.floorMod(Long.getLong("angelica.flyby.timeOfDay", 6000L), 24000L);
public static final String FLYBY_COMMANDS = System.getProperty("angelica.flyby.commands", "");
public static final String FLYBY_ORIGIN = System.getProperty("angelica.flyby.origin", "");
public static final Integer FLYBY_DIMENSION = Integer.getInteger("angelica.flyby.dimension");
public static final FlybyPacing FLYBY_PACING = parseEnum("angelica.flyby.pacing", FlybyPacing.UNCAPPED, FlybyPacing.UNCAPPED, FlybyPacing.class);
public static final FlybyWeather FLYBY_WEATHER = parseEnum("angelica.flyby.weather", FlybyWeather.CLEAR, FlybyWeather.CLEAR, FlybyWeather.class);
public static final FlybyCamera FLYBY_CAMERA = parseEnum("angelica.flyby.camera", FlybyCamera.FIRST_PERSON, FlybyCamera.FIRST_PERSON, FlybyCamera.class);
public static final boolean FLYBY_JFR = Boolean.getBoolean("angelica.flyby.jfr");
public static final float FLYBY_PITCH = parseFloat("angelica.flyby.pitch");
public static final int FLYBY_SCREENSHOTS = Integer.getInteger("angelica.flyby.screenshots", 0);
@@ -131,6 +134,12 @@ public final class SystemProperties {
CONFIGURED
}
public enum FlybyCamera {
FIRST_PERSON,
THIRD_PERSON_BACK,
THIRD_PERSON_FRONT
}
public enum FlybyWeather {
CLEAR,
RAIN,
@@ -158,7 +167,12 @@ public final class SystemProperties {
private static Boolean parseBooleanOverride(String key) {
final String raw = System.getProperty(key);
return raw == null ? null : Boolean.parseBoolean(raw);
return raw == null || raw.isEmpty() ? null : Boolean.parseBoolean(raw);
}
private static String parseStringOverride(String key) {
final String raw = System.getProperty(key);
return raw == null || raw.isEmpty() ? null : raw;
}
private static double parseDouble(String key) {
@@ -4,6 +4,7 @@ import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import com.gtnewhorizons.angelica.glsm.shader.ShaderDiskCache;
import com.gtnewhorizons.angelica.glsm.shader.ShaderIndex;
import com.gtnewhorizons.angelica.glsm.shader.SpirvShaderTranslator;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode;
@@ -456,12 +457,7 @@ public class CompatShaderTransformer {
continue;
}
final List<GLSLParser.Typeless_declarationContext> declarators = new ArrayList<>();
declarators.add(decl.typeless_declaration());
if (decl.getParent() instanceof GLSLParser.Init_declarator_listContext list) {
declarators.addAll(list.typeless_declaration());
}
for (GLSLParser.Typeless_declarationContext d : declarators) {
for (GLSLParser.Typeless_declarationContext d : ShaderIndex.declarators((GLSLParser.Init_declarator_listContext) decl.getParent())) {
if (d.IDENTIFIER() == null) continue;
final int slots = baseSlots(baseType) * typeArraySlots * arraySlots(d.array_specifier());
final String arraySuffix = typeArray + arraySuffix(d.array_specifier());
@@ -96,6 +96,7 @@ public class Feature {
, GLStateManager.getSecondaryColor() // Current secondary color
, ShaderManager.getNormalStack() // Current normal vector
, ShaderManager.getTexCoordStack() // Current texture coordinates
, GLStateManager.getEdgeFlagState()
// Current color index
// Current raster position
// GL_CURRENT_RASTER_POSITION_VALID flag
@@ -158,6 +159,8 @@ public class Feature {
, GLStateManager.getPolygonOffsetPointState()
, GLStateManager.getPolygonOffsetLineState()
, GLStateManager.getPolygonOffsetFillState()
, GLStateManager.getPointSpriteState()
, GLStateManager.getVertexProgramTwoSideState()
, GLStateManager.getPolygonSmoothState()
, GLStateManager.getPolygonStippleState()
, GLStateManager.getRescaleNormalState()
@@ -178,6 +181,7 @@ public class Feature {
enableBits.add(GLStateManager.getTextures().getTexture1DStates(i));
enableBits.add(GLStateManager.getTextures().getTextureUnitStates(i));
enableBits.add(GLStateManager.getTextures().getTexture3DStates(i));
enableBits.add(GLStateManager.getTextures().getTextureCubeMapStates(i));
}
// Flags GL_TEXTURE_GEN_x where x is S, T, R, or Q
@@ -216,13 +220,7 @@ public class Feature {
// ^^ GL_FOG_MODE value
));
attribToFeatures.put(GL11.GL_HINT_BIT, ImmutableSet.of(
// GL_PERSPECTIVE_CORRECTION_HINT setting
// GL_POINT_SMOOTH_HINT setting
// GL_LINE_SMOOTH_HINT setting
// GL_POLYGON_SMOOTH_HINT setting
// GL_FOG_HINT setting
// GL_GENERATE_MIPMAP_HINT setting
// GL_TEXTURE_COMPRESSION_HINT setting
GLStateManager.getHintState()
));
attribToFeatures.put(GL11.GL_LIGHTING_BIT, ImmutableSet.of(
GLStateManager.getColorMaterial()
@@ -286,6 +284,7 @@ public class Feature {
));
attribToFeatures.put(GL11.GL_POINT_BIT, ImmutableSet.of(
GLStateManager.getPointSmoothState()
, GLStateManager.getPointSpriteState()
, GLStateManager.getPointState()
));
attribToFeatures.put(GL11.GL_POLYGON_BIT, ImmutableSet.of(
@@ -328,6 +327,10 @@ public class Feature {
for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
textureAttribs.add(GLStateManager.getTextures().getTextureUnitBindings(i));
textureAttribs.add(GLStateManager.getTextures().getTexEnvState(i));
textureAttribs.add(GLStateManager.getTextures().getTexture1DStates(i));
textureAttribs.add(GLStateManager.getTextures().getTextureUnitStates(i));
textureAttribs.add(GLStateManager.getTextures().getTexture3DStates(i));
textureAttribs.add(GLStateManager.getTextures().getTextureCubeMapStates(i));
}
// Enable bits GL_TEXTURE_GEN_x where x is S, T, R, or Q
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
import com.gtnewhorizons.angelica.glsm.states.ViewportState;
import org.joml.Matrix4f;
@@ -141,6 +142,7 @@ public class FeedbackManager {
public static void processDrawArrays(int mode, int first, int count) {
computeMVP();
final GLContextState glCtx = GLStateManager.ctx();
if (!glCtx.vaos.isAttribEnabled(0)) return;
final int stride = positionStride(glCtx);
final long posOffset = positionOffset(glCtx);
final int byteCount = count * stride;
@@ -154,6 +156,7 @@ public class FeedbackManager {
public static void processDrawElements(int mode, int indexCount, int type, long eboOffset) {
computeMVP();
final GLContextState glCtx = GLStateManager.ctx();
if (!glCtx.vaos.isAttribEnabled(0)) return;
final int stride = positionStride(glCtx);
final int elementSize = GLTypes.sizeBytes(type);
@@ -210,6 +213,7 @@ public class FeedbackManager {
private static void processDrawElementsImpl(int mode, int count, IndexReader reader) {
computeMVP();
final GLContextState glCtx = GLStateManager.ctx();
if (!glCtx.vaos.isAttribEnabled(0)) return;
final int stride = positionStride(glCtx);
int minIdx = Integer.MAX_VALUE, maxIdx = Integer.MIN_VALUE;
@@ -261,6 +265,14 @@ public class FeedbackManager {
}
private static void readPositionBuffer(long offset, ByteBuffer destination, GLContextState glCtx) {
final VAOManager.Attrib position = glCtx.vaos.attrib(0);
if (position != null && position.clientPointer != null) {
final ByteBuffer source = position.clientPointer.duplicate();
source.position(source.position() + (int) offset);
if (source.remaining() > destination.remaining()) source.limit(source.position() + destination.remaining());
destination.duplicate().put(source);
return;
}
final int positionVbo = glCtx.vaos.attribVBO(0);
final int previousVbo = glCtx.boundVBO;
@@ -21,6 +21,7 @@ import com.gtnewhorizons.angelica.glsm.stacks.MaterialStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.MatrixModeStack;
import com.gtnewhorizons.angelica.glsm.stacks.PointStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.PolygonStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.RetainedState;
import com.gtnewhorizons.angelica.glsm.stacks.ScissorStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.StackIdAllocator;
import com.gtnewhorizons.angelica.glsm.stacks.StencilStateStack;
@@ -29,7 +30,7 @@ import com.gtnewhorizons.angelica.glsm.states.AlphaState;
import com.gtnewhorizons.angelica.glsm.states.BlendState;
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
import com.gtnewhorizons.angelica.glsm.states.Color4;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import com.gtnewhorizons.angelica.glsm.states.PixelStoreState;
import com.gtnewhorizons.angelica.glsm.states.ImageUnitArray;
import com.gtnewhorizons.angelica.glsm.states.SamplerUnitArray;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
@@ -46,6 +47,7 @@ import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import java.nio.DoubleBuffer;
@@ -106,6 +108,7 @@ public class GLContextState {
public int maxBoundTextureUnit = 0;
public int maxBoundImageUnit = 0;
public final StateSet[] attribSets = new StateSet[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
public RetainedState retainedOwner;
public int attribDepth = 0;
public boolean savesState(int id) {
@@ -167,6 +170,9 @@ public class GLContextState {
public final BooleanStateStack lineSmoothState = track(new BooleanStateStack(GL11.GL_LINE_SMOOTH));
public final BooleanStateStack lineStippleState = track(new BooleanStateStack(GL11.GL_LINE_STIPPLE, false, true));
public final BooleanStateStack pointSmoothState = track(new BooleanStateStack(GL11.GL_POINT_SMOOTH, false, true));
public final BooleanStateStack pointSpriteState = track(new BooleanStateStack(GL20.GL_POINT_SPRITE, false, true));
public final BooleanStateStack vertexProgramTwoSideState = track(new BooleanStateStack(GL20.GL_VERTEX_PROGRAM_TWO_SIDE, false, true));
public final BooleanStateStack edgeFlagState = track(new BooleanStateStack(GL11.GL_EDGE_FLAG, true, true));
public final BooleanStateStack polygonSmoothState = track(new BooleanStateStack(GL11.GL_POLYGON_SMOOTH));
public final BooleanStateStack polygonStippleState = track(new BooleanStateStack(GL11.GL_POLYGON_STIPPLE, false, true));
public final BooleanStateStack multisampleState = track(new BooleanStateStack(GL13.GL_MULTISAMPLE, true));
@@ -180,6 +186,7 @@ public class GLContextState {
public final BooleanStateStack polygonOffsetFillState = track(new BooleanStateStack(GL11.GL_POLYGON_OFFSET_FILL));
public final LineStateStack lineState = member(new LineStateStack(StackIdAllocator.nextId()));
public final PointStateStack pointState = member(new PointStateStack(StackIdAllocator.nextId()));
public final IntegerStateStack hintState = member(new IntegerStateStack(0, StackIdAllocator.nextId()).restoreBit(StateSet.R_HINT));
public final PolygonStateStack polygonState = member(new PolygonStateStack(StackIdAllocator.nextId()));
public final StencilStateStack stencilState = member(new StencilStateStack(StackIdAllocator.nextId()));
public int stencilBitMask = 0xFFFFFFFF;
@@ -229,10 +236,16 @@ public class GLContextState {
public int boundCopyReadBuffer;
public int boundCopyWriteBuffer;
public final Int2IntMap boundOtherBuffers = new Int2IntOpenHashMap();
public PixelUnpackState pixelUnpackState = PixelUnpackState.DEFAULT;
public final int[] clientAttribSavedTextureUnit = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public final int[] clientAttribSavedVertexFlags = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public PixelStoreState pixelUnpackState = PixelStoreState.DEFAULT;
public PixelStoreState pixelPackState = PixelStoreState.DEFAULT;
public final int[] clientAttribSavedMask = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public final PixelStoreState[] clientAttribSavedPixelUnpack = new PixelStoreState[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public final PixelStoreState[] clientAttribSavedPixelPack = new PixelStoreState[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public final int[] clientAttribSavedUnpackBuffer = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public final int[] clientAttribSavedPackBuffer = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public final VAOManager.ClientArrays[] clientAttribSavedArrays = new VAOManager.ClientArrays[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public int clientAttribStackPointer = 0;
public float maxPointSize = Float.NaN;
public int restoreChangedMask;
public long restoreUnitChangedMask;
public final BlendState vanillaBlendBefore = new BlendState();
@@ -253,7 +266,7 @@ public class GLContextState {
public int savedFilterMin = -1;
public int savedFilterMag = -1;
public float savedFilterAniso = -1.0f;
public boolean wideLineEmulationActive = false;
public boolean lineQuadsActive = false;
public boolean lineStippleActive = false;
public Instancing ffpInstancing = Instancing.NONE;
public float lastBrightnessX;
File diff suppressed because it is too large. Load diff
@@ -1,21 +1,25 @@
package com.gtnewhorizons.angelica.glsm;
import org.antlr.v4.runtime.BailErrorStrategy;
import org.antlr.v4.runtime.BufferedTokenStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonToken;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.DefaultErrorStrategy;
import org.antlr.v4.runtime.Parser;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.WritableToken;
import org.antlr.v4.runtime.atn.PredictionMode;
import org.antlr.v4.runtime.misc.ParseCancellationException;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.taumc.glsl.grammar.GLSLLexer;
import org.taumc.glsl.grammar.GLSLParser;
import org.taumc.glsl.grammar.GLSLPreParser;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -72,6 +76,10 @@ public class GlslTransformUtils {
}
public static String renameReservedWords(String source, int targetVersion) {
if (!containsWholeWord(source, "sample") && !containsWholeWord(source, "new")
&& (targetVersion < 400 || !containsWholeWord(source, "sampler"))) {
return source;
}
for (var entry : VERSIONED_RESERVED_WORDS.entrySet()) {
// Rename identifiers that become reserved words at or above this GLSL version
if (targetVersion >= entry.getKey()) {
@@ -83,6 +91,23 @@ public class GlslTransformUtils {
return source;
}
static boolean containsWholeWord(String source, String word) {
int from = 0;
while (true) {
final int match = source.indexOf(word, from);
if (match < 0) return false;
final int end = match + word.length();
if ((match == 0 || !isAsciiWordChar(source.charAt(match - 1))) && (end == source.length() || !isAsciiWordChar(source.charAt(end)))) {
return true;
}
from = match + 1;
}
}
private static boolean isAsciiWordChar(char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
}
public static String restoreReservedWords(String source) {
source = source.replace(RENAMED_PREFIX + "texture", "texture");
source = source.replace(RENAMED_PREFIX + "sampler", "sampler");
@@ -110,72 +135,35 @@ public class GlslTransformUtils {
public record QuietParse(GLSLParser.Translation_unitContext full, GLSLPreParser.Translation_unitContext pre) {}
public static GLSLParser.Translation_unitContext parseFullQuiet(String source) {
final GLSLLexer lexer = new GLSLLexer(CharStreams.fromString(source));
lexer.removeErrorListeners();
final GLSLParser parser = new GLSLParser(new CommonTokenStream(lexer));
parser.removeErrorListeners();
parser.setBuildParseTree(true);
return parser.translation_unit();
}
public static GLSLPreParser.Translation_unitContext parsePreQuiet(String source) {
final GLSLLexer lexer = new GLSLLexer(CharStreams.fromString(source));
lexer.removeErrorListeners();
final GLSLPreParser preParser = new GLSLPreParser(new CommonTokenStream(lexer, GLSLLexer.DIRECTIVES));
preParser.removeErrorListeners();
preParser.setBuildParseTree(true);
return preParser.translation_unit();
return parseQuiet(new GLSLParser(new CommonTokenStream(quietLexer(source))), GLSLParser::translation_unit);
}
public static QuietParse parseBothQuiet(String source) {
final GLSLLexer lexer = new GLSLLexer(CharStreams.fromString(source));
lexer.removeErrorListeners();
final GLSLPreParser preParser = new GLSLPreParser(new BufferedTokenStream(lexer));
preParser.removeErrorListeners();
preParser.setBuildParseTree(true);
final GLSLPreParser.Translation_unitContext pre = preParser.translation_unit();
final GLSLLexer lexer = quietLexer(source);
final GLSLPreParser.Translation_unitContext pre = parseQuiet(new GLSLPreParser(new BufferedTokenStream(lexer)), GLSLPreParser::translation_unit);
lexer.reset();
final GLSLParser parser = new GLSLParser(new CommonTokenStream(lexer));
parser.removeErrorListeners();
parser.setBuildParseTree(true);
return new QuietParse(parser.translation_unit(), pre);
return new QuietParse(parseQuiet(new GLSLParser(new CommonTokenStream(lexer)), GLSLParser::translation_unit), pre);
}
public static Set<String> identifiersInDirectiveText(String source) {
private static <P extends Parser, T> T parseQuiet(P parser, Function<P, T> rule) {
parser.removeErrorListeners();
parser.setErrorHandler(new BailErrorStrategy());
parser.getInterpreter().setPredictionMode(PredictionMode.SLL);
try {
return rule.apply(parser);
} catch (ParseCancellationException e) {
// Rewinds the token stream, which keeps every token SLL already pulled from the lexer.
parser.reset();
parser.setErrorHandler(new DefaultErrorStrategy());
parser.getInterpreter().setPredictionMode(PredictionMode.LL);
return rule.apply(parser);
}
}
public static GLSLLexer quietLexer(String source) {
final GLSLLexer lexer = new GLSLLexer(CharStreams.fromString(source));
lexer.removeErrorListeners();
final BufferedTokenStream tokens = new BufferedTokenStream(lexer);
tokens.fill();
final Set<String> names = new HashSet<>();
for (Token token : tokens.getTokens()) {
switch (token.getType()) {
case GLSLLexer.PROGRAM_TEXT, GLSLLexer.MACRO_TEXT, GLSLLexer.CONSTANT_EXPRESSION -> collectIdentifiers(token.getText(), names);
default -> { }
}
}
return names;
}
private static void collectIdentifiers(String text, Set<String> out) {
if (text == null) return;
final int len = text.length();
int i = 0;
while (i < len) {
final char c = text.charAt(i);
if (c != '_' && !Character.isLetter(c)) {
i++;
continue;
}
int end = i + 1;
while (end < len) {
final char n = text.charAt(end);
if (n != '_' && !Character.isLetterOrDigit(n)) break;
end++;
}
out.add(text.substring(i, end));
i = end;
}
return lexer;
}
public static String getFormattedShader(ParseTree tree, String header) {
@@ -1,6 +1,6 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import com.gtnewhorizons.angelica.glsm.states.PixelStoreState;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
@@ -25,10 +25,10 @@ public final class LTWWorkaround {
oneTexel = BufferUtils.createByteBuffer(4);
}
oneTexel.clear();
GLStateManager.forcePixelUnpackState(PixelUnpackState.DEFAULT);
GLStateManager.forcePixelUnpackState(PixelStoreState.DEFAULT);
GLStateManager.suspendPixelUnpackBuffer();
RENDER_BACKEND.texSubImage2D(target, level, 0, 0, 1, 1, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, oneTexel);
GLStateManager.restorePixelUnpackBuffer();
GLStateManager.restorePixelUnpackState(PixelUnpackState.DEFAULT);
GLStateManager.restorePixelUnpackState(PixelStoreState.DEFAULT);
}
}
@@ -0,0 +1,167 @@
package com.gtnewhorizons.angelica.glsm;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
final class LegacyQueries {
static final int BOOL = 1;
static final int INT = 2;
static final int FLOAT = 3;
static final int FLOATN = 4;
private static final int COUNT_SHIFT = 3;
private LegacyQueries() {}
static int desc(int pname) {
return switch (pname) {
case GL11.GL_ALPHA_TEST, GL11.GL_AUTO_NORMAL, GL11.GL_COLOR_MATERIAL, GL14.GL_COLOR_SUM, GL11.GL_FOG,
GL11.GL_INDEX_LOGIC_OP, GL11.GL_LIGHTING, GL11.GL_LIGHT0, GL11.GL_LIGHT1, GL11.GL_LIGHT2, GL11.GL_LIGHT3,
GL11.GL_LIGHT4, GL11.GL_LIGHT5, GL11.GL_LIGHT6, GL11.GL_LIGHT7, GL11.GL_LINE_STIPPLE, GL11.GL_MAP1_COLOR_4,
GL11.GL_MAP1_INDEX, GL11.GL_MAP1_NORMAL, GL11.GL_MAP1_TEXTURE_COORD_1, GL11.GL_MAP1_TEXTURE_COORD_2,
GL11.GL_MAP1_TEXTURE_COORD_3, GL11.GL_MAP1_TEXTURE_COORD_4, GL11.GL_MAP1_VERTEX_3, GL11.GL_MAP1_VERTEX_4,
GL11.GL_MAP2_COLOR_4, GL11.GL_MAP2_INDEX, GL11.GL_MAP2_NORMAL, GL11.GL_MAP2_TEXTURE_COORD_1,
GL11.GL_MAP2_TEXTURE_COORD_2, GL11.GL_MAP2_TEXTURE_COORD_3, GL11.GL_MAP2_TEXTURE_COORD_4,
GL11.GL_MAP2_VERTEX_3, GL11.GL_MAP2_VERTEX_4, GL11.GL_NORMALIZE, GL11.GL_POINT_SMOOTH,
GL11.GL_POLYGON_STIPPLE, GL12.GL_RESCALE_NORMAL, GL11.GL_TEXTURE_1D, GL12.GL_TEXTURE_3D,
GL13.GL_TEXTURE_CUBE_MAP, GL11.GL_TEXTURE_GEN_S, GL11.GL_TEXTURE_GEN_T, GL11.GL_TEXTURE_GEN_R,
GL11.GL_TEXTURE_GEN_Q, GL11.GL_LIGHT_MODEL_LOCAL_VIEWER, GL11.GL_LIGHT_MODEL_TWO_SIDE,
GL11.GL_VERTEX_ARRAY, GL11.GL_NORMAL_ARRAY, GL11.GL_COLOR_ARRAY, GL11.GL_TEXTURE_COORD_ARRAY,
GL11.GL_INDEX_ARRAY, GL11.GL_EDGE_FLAG_ARRAY, GL14.GL_SECONDARY_COLOR_ARRAY, GL14.GL_FOG_COORDINATE_ARRAY,
GL11.GL_EDGE_FLAG, GL20.GL_POINT_SPRITE, GL20.GL_VERTEX_PROGRAM_TWO_SIDE -> BOOL;
case GL11.GL_ALPHA_TEST_FUNC, GL11.GL_LIST_BASE, GL11.GL_MATRIX_MODE, GL11.GL_SHADE_MODEL,
GL11.GL_COLOR_MATERIAL_FACE, GL11.GL_COLOR_MATERIAL_PARAMETER, GL12.GL_LIGHT_MODEL_COLOR_CONTROL,
GL11.GL_FOG_MODE, GL11.GL_LINE_STIPPLE_PATTERN, GL11.GL_LINE_STIPPLE_REPEAT, GL13.GL_CLIENT_ACTIVE_TEXTURE,
GL20.GL_MAX_TEXTURE_COORDS, GL11.GL_RENDER_MODE, GL11.GL_FOG_HINT, GL11.GL_PERSPECTIVE_CORRECTION_HINT,
GL11.GL_POINT_SMOOTH_HINT, GL14.GL_GENERATE_MIPMAP_HINT, GL11.GL_LINE_SMOOTH_HINT, GL11.GL_POLYGON_SMOOTH_HINT,
GL13.GL_TEXTURE_COMPRESSION_HINT, GL20.GL_FRAGMENT_SHADER_DERIVATIVE_HINT, GL20.GL_POINT_SPRITE_COORD_ORIGIN,
GL11.GL_VERTEX_ARRAY_SIZE, GL11.GL_VERTEX_ARRAY_TYPE, GL11.GL_VERTEX_ARRAY_STRIDE, GL15.GL_VERTEX_ARRAY_BUFFER_BINDING,
GL11.GL_NORMAL_ARRAY_TYPE, GL11.GL_NORMAL_ARRAY_STRIDE, GL15.GL_NORMAL_ARRAY_BUFFER_BINDING,
GL11.GL_COLOR_ARRAY_SIZE, GL11.GL_COLOR_ARRAY_TYPE, GL11.GL_COLOR_ARRAY_STRIDE, GL15.GL_COLOR_ARRAY_BUFFER_BINDING,
GL11.GL_INDEX_ARRAY_TYPE, GL11.GL_INDEX_ARRAY_STRIDE, GL15.GL_INDEX_ARRAY_BUFFER_BINDING,
GL11.GL_TEXTURE_COORD_ARRAY_SIZE, GL11.GL_TEXTURE_COORD_ARRAY_TYPE, GL11.GL_TEXTURE_COORD_ARRAY_STRIDE,
GL15.GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING, GL11.GL_EDGE_FLAG_ARRAY_STRIDE, GL15.GL_EDGE_FLAG_ARRAY_BUFFER_BINDING,
GL14.GL_SECONDARY_COLOR_ARRAY_SIZE, GL14.GL_SECONDARY_COLOR_ARRAY_TYPE, GL14.GL_SECONDARY_COLOR_ARRAY_STRIDE,
GL15.GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING, GL14.GL_FOG_COORDINATE_ARRAY_TYPE, GL14.GL_FOG_COORDINATE_ARRAY_STRIDE,
GL15.GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING -> INT;
case GL11.GL_FOG_DENSITY, GL11.GL_FOG_START, GL11.GL_FOG_END, GL14.GL_POINT_SIZE_MIN, GL14.GL_POINT_SIZE_MAX,
GL14.GL_POINT_FADE_THRESHOLD_SIZE -> FLOAT;
case GL11.GL_ALPHA_TEST_REF -> FLOATN;
case GL14.GL_POINT_DISTANCE_ATTENUATION -> FLOAT | 2 << COUNT_SHIFT;
case GL11.GL_CURRENT_TEXTURE_COORDS -> FLOAT | 3 << COUNT_SHIFT;
case GL11.GL_MODELVIEW_MATRIX, GL11.GL_PROJECTION_MATRIX, GL11.GL_TEXTURE_MATRIX -> FLOATN | 15 << COUNT_SHIFT;
case GL11.GL_CURRENT_NORMAL -> FLOATN | 2 << COUNT_SHIFT;
case GL11.GL_CURRENT_COLOR, GL14.GL_CURRENT_SECONDARY_COLOR, GL11.GL_FOG_COLOR, GL11.GL_LIGHT_MODEL_AMBIENT -> FLOATN | 3 << COUNT_SHIFT;
default -> 0;
};
}
static int type(int desc) {
return desc & 7;
}
static int count(int desc) {
return (desc >> COUNT_SHIFT) + 1;
}
static int floatToInt(float value) {
return (int) (2147483647.0 * value);
}
static int lround(float value) {
return value < 0 ? -Math.round(-value) : Math.round(value);
}
static int toInt(int type, float value) {
return type == FLOATN ? floatToInt(value) : lround(value);
}
private static FloatBuffer fillScratch(GLContextState glCtx, int pname) {
final FloatBuffer scratch = glCtx.queryScratch;
scratch.clear();
GLStateManager.glGetFloat(pname, scratch);
return scratch;
}
private static IllegalStateException uncached(int pname) {
return new IllegalStateException("Legacy pname 0x" + Integer.toHexString(pname) + " has no cached answer for its declared type");
}
static int getInteger(GLContextState glCtx, int pname, int desc) {
final int type = type(desc);
if (count(desc) > 1) {
return toInt(type, fillScratch(glCtx, pname).get(0));
}
return switch (type) {
case BOOL -> GLStateManager.glGetBoolean(glCtx, pname) ? GL11.GL_TRUE : GL11.GL_FALSE;
case FLOAT -> lround(GLStateManager.glGetFloat(glCtx, pname));
case FLOATN -> floatToInt(GLStateManager.glGetFloat(glCtx, pname));
default -> throw uncached(pname);
};
}
static float getFloat(GLContextState glCtx, int pname, int desc) {
if (count(desc) > 1) {
return fillScratch(glCtx, pname).get(0);
}
return switch (type(desc)) {
case BOOL -> GLStateManager.glGetBoolean(glCtx, pname) ? 1.0f : 0.0f;
case INT -> (float) GLStateManager.glGetInteger(glCtx, pname);
default -> throw uncached(pname);
};
}
static boolean getBoolean(GLContextState glCtx, int pname, int desc) {
if (count(desc) > 1) {
return fillScratch(glCtx, pname).get(0) != 0.0f;
}
return switch (type(desc)) {
case INT -> GLStateManager.glGetInteger(glCtx, pname) != 0;
case FLOAT, FLOATN -> GLStateManager.glGetFloat(glCtx, pname) != 0.0f;
default -> throw uncached(pname);
};
}
static boolean getIntegerv(GLContextState glCtx, int pname, IntBuffer params) {
final int desc = desc(pname);
if (desc == 0) return false;
final int pos = params.position();
final int type = type(desc);
final FloatBuffer scratch = fillScratch(glCtx, pname);
for (int i = 0, count = count(desc); i < count; i++) {
params.put(pos + i, toInt(type, scratch.get(i)));
}
return true;
}
static boolean getIntegerv(GLContextState glCtx, int pname, int[] params) {
final int desc = desc(pname);
if (desc == 0) return false;
final int type = type(desc);
final FloatBuffer scratch = fillScratch(glCtx, pname);
for (int i = 0, count = Math.min(count(desc), params.length); i < count; i++) {
params[i] = toInt(type, scratch.get(i));
}
return true;
}
static boolean getBooleanv(GLContextState glCtx, int pname, ByteBuffer params) {
final int desc = desc(pname);
if (desc == 0) return false;
final int pos = params.position();
final FloatBuffer scratch = fillScratch(glCtx, pname);
for (int i = 0, count = count(desc); i < count; i++) {
params.put(pos + i, (byte) (scratch.get(i) != 0.0f ? GL11.GL_TRUE : GL11.GL_FALSE));
}
return true;
}
}
@@ -48,6 +48,7 @@ public class RenderSystem {
private static boolean supportsClearTexture;
private static boolean supportsTesselation;
private static boolean supportsSamplerObjects;
private static boolean supportsProgramBinary;
private static int maxImageUnits;
@Getter private static int maxCombinedTextureImageUnits;
private static int maxSSBOBindings;
@@ -159,6 +160,9 @@ public class RenderSystem {
maxCombinedTextureImageUnits = RENDER_BACKEND.getInteger(GL20.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS);
}
supportsProgramBinary = RENDER_BACKEND.supportsProgramBinary();
GLStateManager.LOGGER.info("Program Binary: {}", supportsProgramBinary);
maxGlslVersion = Integer.parseInt(parseGlVersionString(RENDER_BACKEND.getString(GL20.GL_SHADING_LANGUAGE_VERSION)));
supportsGpuShader4 = GLStateManager.capabilities.GL_EXT_gpu_shader4;
@@ -611,6 +615,10 @@ public class RenderSystem {
return supportsSamplerObjects;
}
public static boolean supportsProgramBinary() {
return supportsProgramBinary;
}
public static int genSampler() {
if (!supportsSamplerObjects) return 0;
return GLStateManager.glGenSamplers();
@@ -680,4 +688,12 @@ public class RenderSystem {
if (actualVersion < 0) return true;
return actualVersion >= requestedMajor * 10 + requestedMinor;
}
public static final int MIN_CORE_VERSION = 33;
public static int probeCapAfterRejection(int requested, int actual) {
if (actual >= MIN_CORE_VERSION && actual < requested) return actual;
if (actual >= 0 && actual < MIN_CORE_VERSION && requested > MIN_CORE_VERSION) return MIN_CORE_VERSION;
return requested - 1;
}
}
@@ -24,6 +24,7 @@ public final class StateSet {
public static final int R_ACTIVE_UNIT = 1 << 13;
public static final int R_PROGRAM = 1 << 14;
public static final int R_SCISSOR = 1 << 15;
public static final int R_HINT = 1 << 16;
public static final int B_FRAG_COLORBUF = 1;
public static final int B_FRAG_TEXTURE = 1 << 1;
@@ -107,9 +108,11 @@ public final class StateSet {
GLStateManager.getTextures().getTextureUnitBindings(0), GLStateManager.getTextures().getTextureUnitBindings(1),
GLStateManager.getActiveTextureUnitStack(), GLStateManager.getProgramStack(),
GLStateManager.getBlendMode(), GLStateManager.getAlphaTest(), GLStateManager.getDepthTest(),
GLStateManager.getTextures().getTextureUnitStates(0)
GLStateManager.getTextures().getTextureUnitStates(0),
GLStateManager.getColorMask(), GLStateManager.getCullState(),
GLStateManager.getPolygonState(), GLStateManager.getPolygonOffsetFillState()
),
R_BLEND | R_DEPTH | R_TEXTURE | R_ACTIVE_UNIT | R_PROGRAM,
R_BLEND | R_DEPTH | R_COLOR_MASK | R_TEXTURE | R_ACTIVE_UNIT | R_PROGRAM | R_POLYGON,
0,
true);
}
@@ -121,9 +124,11 @@ public final class StateSet {
GLStateManager.getTextures().getTextureUnitBindings(0), GLStateManager.getTextures().getTextureUnitBindings(1),
GLStateManager.getActiveTextureUnitStack(),
GLStateManager.getBlendMode(), GLStateManager.getAlphaTest(), GLStateManager.getDepthTest(),
GLStateManager.getTextures().getTextureUnitStates(0)
GLStateManager.getTextures().getTextureUnitStates(0),
GLStateManager.getColorMask(), GLStateManager.getCullState(),
GLStateManager.getPolygonState(), GLStateManager.getPolygonOffsetFillState()
),
R_BLEND | R_DEPTH | R_TEXTURE | R_ACTIVE_UNIT,
R_BLEND | R_DEPTH | R_COLOR_MASK | R_TEXTURE | R_ACTIVE_UNIT | R_POLYGON,
0,
true);
}
@@ -220,6 +225,9 @@ public final class StateSet {
if ((mask & GL11.GL_POINT_BIT) != 0) {
restore |= R_POINT;
}
if ((mask & GL11.GL_HINT_BIT) != 0) {
restore |= R_HINT;
}
if ((mask & GL11.GL_POLYGON_BIT) != 0) {
restore |= R_POLYGON;
}
@@ -0,0 +1,38 @@
package com.gtnewhorizons.angelica.glsm.backend;
import com.gtnewhorizons.angelica.config.SystemProperties;
public final class BackendOptions {
private static boolean configSdlGpu;
private static String configDriver = "";
private static int configGlVersion;
private BackendOptions() {}
public static void latch(boolean sdlGpu, String driver, int glVersion) {
configSdlGpu = sdlGpu;
configDriver = driver;
configGlVersion = glVersion;
}
public static boolean sdlGpuRequested() {
return SystemProperties.SDL_GPU_OVERRIDE != null ? SystemProperties.SDL_GPU_OVERRIDE : configSdlGpu;
}
public static String sdlGpuDriver() {
return SystemProperties.SDL_GPU_DRIVER_OVERRIDE != null ? SystemProperties.SDL_GPU_DRIVER_OVERRIDE : configDriver;
}
public static boolean configSdlGpu() {
return configSdlGpu;
}
public static String configDriver() {
return configDriver;
}
public static int configGlVersion() {
return configGlVersion;
}
}
@@ -0,0 +1,55 @@
package com.gtnewhorizons.angelica.glsm.backend;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File;
import java.io.IOException;
public final class BackendStartGuard {
private static final Logger LOGGER = LogManager.getLogger("Angelica/BackendStartGuard");
private static File pending;
private static boolean tripped;
private BackendStartGuard() {}
public static boolean begin(File marker, boolean sdlGpuConfigured, boolean flagForced) {
pending = null;
tripped = false;
if (!sdlGpuConfigured || flagForced) {
delete(marker);
return false;
}
if (marker.isFile()) {
delete(marker);
tripped = true;
return true;
}
try {
final File dir = marker.getParentFile();
if (dir != null && !dir.isDirectory() && !dir.mkdirs()) throw new IOException("cannot create " + dir);
if (marker.createNewFile()) pending = marker;
} catch (IOException e) {
LOGGER.warn("Could not write {}: {}", marker, e.toString());
}
return false;
}
public static void finish() {
if (pending == null) return;
delete(pending);
pending = null;
}
public static boolean tripped() {
return tripped;
}
private static void delete(File marker) {
if (marker.isFile() && !marker.delete()) {
LOGGER.warn("Could not delete {}", marker);
}
}
}
@@ -1,6 +1,7 @@
package com.gtnewhorizons.angelica.glsm.backend;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.AMDDebugOutput;
import org.lwjgl.opengl.AMDDebugOutputCallback;
@@ -23,6 +24,7 @@ import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import org.lwjgl.opengl.GL40;
import org.lwjgl.opengl.GL41;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL33;
import org.lwjgl.opengl.GL42;
@@ -807,6 +809,28 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
return GL20.glIsProgram(obj);
}
@Override
public boolean supportsProgramBinary() {
final ContextCapabilities caps = GLStateManager.capabilities;
return !RenderSystem.isGLES() && (caps.OpenGL41 || caps.GL_ARB_get_program_binary)
&& GL11.glGetInteger(GL41.GL_NUM_PROGRAM_BINARY_FORMATS) > 0;
}
@Override
public void programParameteri(int program, int pname, int value) {
GL41.glProgramParameteri(program, pname, value);
}
@Override
public void getProgramBinary(int program, IntBuffer length, IntBuffer binaryFormat, ByteBuffer binary) {
GL41.glGetProgramBinary(program, length, binaryFormat, binary);
}
@Override
public void programBinary(int program, int binaryFormat, ByteBuffer binary) {
GL41.glProgramBinary(program, binaryFormat, binary);
}
@Override
public void validateProgram(int program) {
GL20.glValidateProgram(program);
@@ -0,0 +1,125 @@
package com.gtnewhorizons.angelica.glsm.backend;
import com.gtnewhorizons.angelica.config.SystemProperties;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
public final class MoltenVK {
public enum Source {
ENV,
JAR,
SYSTEM,
NONE
}
public static final String ENV_VULKAN_LIBRARY = "SDL_VULKAN_LIBRARY";
public static final String JAR_NAME = "angelica-moltenvk";
private static final Logger LOGGER = LogManager.getLogger("Angelica/MoltenVK");
private static final String LIB_NAME = "libMoltenVK.dylib";
private static final String[] SYSTEM_PATHS = { "/opt/homebrew/lib/" + LIB_NAME, "/usr/local/lib/" + LIB_NAME };
private static final String ARCH = archName(System.getProperty("os.arch", ""));
private static Source source;
private static String path;
private static boolean located;
private MoltenVK() {}
public static synchronized Source source() {
if (source == null) {
final boolean macos = System.getProperty("os.name", "").toLowerCase().contains("mac");
source = detect(macos, System.getenv(ENV_VULKAN_LIBRARY), MoltenVK.class.getResource(bundledResource(ARCH)) != null, SYSTEM_PATHS);
}
return source;
}
public static synchronized String locate() {
if (!located) {
path = resolve(source(), System.getenv(ENV_VULKAN_LIBRARY), bundledResource(ARCH), new File(SystemProperties.MOLTENVK_DIR), ARCH, SYSTEM_PATHS);
located = true;
}
return path;
}
static String archName(String osArch) {
final String arch = osArch.toLowerCase();
return arch.contains("aarch64") || arch.contains("arm64") ? "arm64" : "x64";
}
static String bundledResource(String arch) {
return "/macos/" + arch + "/org/lwjgl/vulkan/" + LIB_NAME;
}
static Source detect(boolean macos, String env, boolean bundled, String[] systemPaths) {
if (!macos) return Source.NONE;
if (env != null && !env.isEmpty()) return Source.ENV;
if (bundled) return Source.JAR;
return systemPath(systemPaths) != null ? Source.SYSTEM : Source.NONE;
}
static String resolve(Source source, String env, String resource, File dir, String arch, String[] systemPaths) {
return switch (source) {
case ENV -> env;
case JAR -> {
final String extracted = extract(resource, dir, arch);
yield extracted != null ? extracted : systemPath(systemPaths);
}
case SYSTEM -> systemPath(systemPaths);
case NONE -> null;
};
}
private static String systemPath(String[] systemPaths) {
for (final String candidate : systemPaths) {
if (new File(candidate).isFile()) return candidate;
}
return null;
}
private static String extract(String resource, File dir, String arch) {
final byte[] lib;
try (InputStream in = MoltenVK.class.getResourceAsStream(resource)) {
if (in == null) return null;
lib = readAll(in);
} catch (IOException e) {
LOGGER.warn("MoltenVK: failed reading {}: {}", resource, e.toString());
return null;
}
final File target = new File(dir, "libMoltenVK-" + arch + ".dylib");
File tmp = null;
try {
if (!target.isFile() || target.length() != lib.length || !Arrays.equals(Files.readAllBytes(target.toPath()), lib)) {
if (!dir.isDirectory() && !dir.mkdirs()) throw new IOException("cannot create " + dir);
tmp = File.createTempFile("libMoltenVK", ".dylib", dir);
Files.write(tmp.toPath(), lib);
Files.move(tmp.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
} catch (IOException e) {
LOGGER.warn("MoltenVK: failed extracting to {}: {}", target, e.toString());
if (tmp != null) tmp.delete();
return null;
}
return target.getAbsolutePath();
}
private static byte[] readAll(InputStream in) throws IOException {
final ByteArrayOutputStream out = new ByteArrayOutputStream(1 << 20);
final byte[] buf = new byte[16384];
int n;
while ((n = in.read(buf)) != -1) {
out.write(buf, 0, n);
}
return out.toByteArray();
}
}
@@ -36,6 +36,9 @@ public abstract class RenderBackend {
/** Returns true if the current thread has a valid render context. */
public abstract boolean hasContext();
/** Returns true if the calling thread may issue render commands. */
public boolean hasContextOnThread() { return hasContext(); }
/** Returns true if the calling thread owns the "GL Context" */
public abstract boolean isCurrent();
@@ -56,6 +59,7 @@ public abstract class RenderBackend {
public boolean framebufferCompletenessIsMeaningful() { return true; }
public void onPersistentBufferWrite(int glId, long offset, long size) {}
public boolean isBufferImmutable(int glId) { return false; }
public boolean isAnisotropicSupported() { return true; }
@@ -272,6 +276,12 @@ public abstract class RenderBackend {
public void voxelizeRegion(long pass, boolean rebindVertexBuffer, float x, float y, float z, int rangeBase, int rangeCount, int vertexTotal) {}
public void endVoxelizationBatch(long pass) {}
public void setVertexWriteReplay(int graphicsProgram, VertexWriteReplaySetup setup) {}
public boolean supportsComputeImageAtomics() { return true; }
public boolean supportsComputeImageAtomics(int width, int height, int depth) { return supportsComputeImageAtomics(); }
public abstract void enable(int cap);
public abstract void enablei(int cap, int index);
public abstract void disable(int cap);
@@ -504,6 +514,10 @@ public abstract class RenderBackend {
public abstract int getUniformLocation(int program, ByteBuffer name);
public boolean isShader(int obj) { return false; }
public boolean isProgram(int obj) { return false; }
public boolean supportsProgramBinary() { return false; }
public void programParameteri(int program, int pname, int value) {}
public void getProgramBinary(int program, IntBuffer length, IntBuffer binaryFormat, ByteBuffer binary) {}
public void programBinary(int program, int binaryFormat, ByteBuffer binary) {}
public void validateProgram(int program) {}
public void getAttachedShaders(int program, IntBuffer count, IntBuffer shaders) {
if (count != null) count.put(0, 0);
@@ -0,0 +1,23 @@
package com.gtnewhorizons.angelica.glsm.backend;
import java.util.function.IntSupplier;
/**
* How to replay a graphics program's vertex-stage image writes.
*
* @param inputLocations per input, the declared location or -1 for whatever the graphics program linked
* @param images per image the replay uses, in binding order: the written ones, then the read-only ones
* @param writtenImageCount how many of {@code images} the replay writes
* @param samplerNames samplers the graphics program may not have, because only the stripped writes read them
* @param samplerUnits per sampler name, the texture unit it reads at each draw, or -1 to use samplerTextures
* @param samplerTextures per sampler name, its texture when samplerUnits is -1
* @param samplerObjects per sampler name, the GL sampler object to sample it with when samplerUnits is -1, or 0
* @param vertexBufferBinding storage buffer binding of the first vertex buffer the replay program declares
* @param vertexBufferCount how many vertex buffers it declares, at consecutive bindings
* @param indexBufferBinding storage buffer binding of its index buffer
*/
public record VertexWriteReplaySetup(int replayProgram, String[] inputNames, int[] inputLocations,
IntSupplier[] images, int writtenImageCount,
String[] samplerNames, int[] samplerUnits, IntSupplier[] samplerTextures, int[] samplerObjects,
int vertexBufferBinding, int vertexBufferCount, int indexBufferBinding) {
}
@@ -133,11 +133,13 @@ public class DSAARB extends DSAUnsupported {
@Override
public int createFramebuffer() {
GLStateManager.requireContext("glCreateFramebuffers");
return RENDER_BACKEND.createFramebuffers();
}
@Override
public int createTexture(int target) {
GLStateManager.requireContext("glCreateTextures");
return RENDER_BACKEND.createTextures(target);
}
}
@@ -165,7 +165,7 @@ public class DSAUnsupported implements DSAAccess {
@Override
public int createTexture(int target) {
final int texture = RENDER_BACKEND.genTextures();
final int texture = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
return texture;
}
@@ -1,107 +0,0 @@
package com.gtnewhorizons.angelica.glsm.ffp;
/**
* Generates GLSL 330 core geometry shaders for wide line emulation.
*/
public final class GeometryShaderGenerator {
private GeometryShaderGenerator() {}
public static String generate(VertexKey key) {
final StringBuilder sb = new StringBuilder(1024);
sb.append("#version 330 core\n\n");
sb.append("layout(lines) in;\n");
sb.append("layout(triangle_strip, max_vertices = 4) out;\n\n");
sb.append(FFPUniformBlock.GLSL_DECL);
sb.append('\n');
sb.append("// Pass-through varyings\n");
sb.append("in vec4 v_Color_gs[];\n");
if (key.separateSpecular()) {
sb.append("in vec3 v_SpecularColor_gs[];\n");
}
if (key.unitTexCoordEnabled(0) || key.texGenEnabled()) sb.append("in vec4 v_TexCoord0_gs[];\n");
if (key.lightmapEnabled()) sb.append("in vec4 v_TexCoord1_gs[];\n");
if (key.unitTexCoordEnabled(2)) sb.append("in vec4 v_TexCoord2_gs[];\n");
if (key.unitTexCoordEnabled(3)) sb.append("in vec4 v_TexCoord3_gs[];\n");
if (key.fogEnabled()) {
sb.append("in float v_FogCoord_gs[];\n");
}
if (key.lineStipple()) {
sb.append("flat in vec2 v_LineStart_gs[];\n");
}
sb.append('\n');
sb.append("out vec4 v_Color;\n");
if (key.separateSpecular()) {
sb.append("out vec3 v_SpecularColor;\n");
}
if (key.unitTexCoordEnabled(0) || key.texGenEnabled()) sb.append("out vec4 v_TexCoord0;\n");
if (key.lightmapEnabled()) sb.append("out vec4 v_TexCoord1;\n");
if (key.unitTexCoordEnabled(2)) sb.append("out vec4 v_TexCoord2;\n");
if (key.unitTexCoordEnabled(3)) sb.append("out vec4 v_TexCoord3;\n");
if (key.fogEnabled()) {
sb.append("out float v_FogCoord;\n");
}
if (key.lineStipple()) {
sb.append("flat out vec2 v_LineStart;\n");
}
sb.append('\n');
sb.append("void main() {\n");
sb.append(" vec4 c0 = gl_in[0].gl_Position;\n");
sb.append(" vec4 c1 = gl_in[1].gl_Position;\n\n");
sb.append(" // NDC positions\n");
sb.append(" vec2 n0 = c0.xy / c0.w;\n");
sb.append(" vec2 n1 = c1.xy / c1.w;\n\n");
sb.append(" // Screen-space direction and perpendicular\n");
sb.append(" vec2 dir = normalize((n1 - n0) * u_ViewportSize);\n");
sb.append(" vec2 offset = vec2(-dir.y, dir.x) * u_LineWidth / u_ViewportSize;\n\n");
emitVertex(sb, key, 0, "+offset");
emitVertex(sb, key, 0, "-offset");
emitVertex(sb, key, 1, "+offset");
emitVertex(sb, key, 1, "-offset");
sb.append(" EndPrimitive();\n");
sb.append("}\n");
return sb.toString();
}
private static void emitVertex(StringBuilder sb, VertexKey key, int endpointIdx, String offsetExpr) {
final String ci = "c" + endpointIdx;
final String ni = "n" + endpointIdx;
final String idx = "[" + endpointIdx + "]";
sb.append(" // Endpoint ").append(endpointIdx).append(' ').append(offsetExpr).append('\n');
sb.append(" v_Color = v_Color_gs").append(idx).append(";\n");
if (key.separateSpecular()) {
sb.append(" v_SpecularColor = v_SpecularColor_gs").append(idx).append(";\n");
}
if (key.unitTexCoordEnabled(0) || key.texGenEnabled()) sb.append(" v_TexCoord0 = v_TexCoord0_gs").append(idx).append(";\n");
if (key.lightmapEnabled()) sb.append(" v_TexCoord1 = v_TexCoord1_gs").append(idx).append(";\n");
if (key.unitTexCoordEnabled(2)) sb.append(" v_TexCoord2 = v_TexCoord2_gs").append(idx).append(";\n");
if (key.unitTexCoordEnabled(3)) sb.append(" v_TexCoord3 = v_TexCoord3_gs").append(idx).append(";\n");
if (key.fogEnabled()) {
sb.append(" v_FogCoord = v_FogCoord_gs").append(idx).append(";\n");
}
if (key.lineStipple()) {
sb.append(" v_LineStart = v_LineStart_gs[0];\n");
}
if (key.clipPlanesEnabled()) {
for (int i = 0; i < 8; i++) {
sb.append(" gl_ClipDistance[").append(i).append("] = gl_in").append(idx).append(".gl_ClipDistance[").append(i).append("];\n");
}
}
sb.append(" gl_Position = vec4((").append(ni).append(' ').append(offsetExpr).append(") * ").append(ci).append(".w, ").append(ci).append(".z, ").append(ci).append(".w);\n");
sb.append(" EmitVertex();\n\n");
}
}
@@ -23,7 +23,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* A compiled and linked FFP emulation program (vertex + fragment + optional geometry shaders). Owns the GL program handle and caches uniform locations.
* A compiled and linked FFP emulation program (vertex + fragment shaders). Owns the GL program handle and caches uniform locations.
*/
public class Program {
@@ -49,23 +49,19 @@ public class Program {
}
/**
* Compile vertex + fragment + optional geometry shaders, link into a program, and return the FFPProgram.
* Compile vertex + fragment shaders, link into a program, and return the FFPProgram.
*/
static Program create(VertexKey vk, FragmentKey fk, String vertSrc, String fragSrc, String geomSrc) {
static Program create(VertexKey vk, FragmentKey fk, String vertSrc, String fragSrc) {
final RenderBackend backend = RENDER_BACKEND;
final int id = PROGRAM_COUNTER.getAndIncrement();
final String vkHex = Long.toHexString(vk.pack());
final boolean hasGeom = geomSrc != null;
int shaderCount = 0;
final int[] shaders = new int[5]; // vertex, fragment, geometry, tess control, tess evaluation
final int[] shaders = new int[2];
int program = 0;
try {
shaders[shaderCount++] = compileShader(GL20.GL_VERTEX_SHADER, vertSrc, "ffp_v_" + vkHex);
shaders[shaderCount++] = compileShader(GL20.GL_FRAGMENT_SHADER, fragSrc, "ffp_f_" + id);
if (geomSrc != null) {
shaders[shaderCount++] = compileShader(GL32.GL_GEOMETRY_SHADER, geomSrc, "ffp_g_" + id);
}
program = backend.createProgram();
for (int i = 0; i < shaderCount; i++) backend.attachShader(program, shaders[i]);
@@ -80,7 +76,7 @@ public class Program {
throw new RuntimeException("FFP shader link failed (vk=0x" + vkHex + ", fk=" + fk + "): " + log);
}
final String debugName = "FFP(v=0x" + vkHex + ",f=" + id + (hasGeom ? ",g" : "") + ")";
final String debugName = "FFP(v=0x" + vkHex + ",f=" + id + ")";
GLDebug.nameObject(KHRDebug.GL_PROGRAM, program, debugName);
// Detach and delete individual shaders - they're linked into the program
@@ -32,7 +32,6 @@ public class ShaderCache {
private static final long EMPTY_MARKER = 0L; // slot [1] == 0 means empty (fkLen >= 1 guarantees nonzero)
private final Long2ObjectOpenHashMap<String> vertexSourceCache = new Long2ObjectOpenHashMap<>();
private final Long2ObjectOpenHashMap<String> geometrySourceCache = new Long2ObjectOpenHashMap<>();
// Open-addressed table — parallel arrays
private long[] keys;
@@ -103,7 +102,6 @@ public class ShaderCache {
final FragmentKey fk = FragmentKey.fromPacked(fkPacked, fkLen);
final String vertSrc;
String fragSrc;
final String geomSrc;
final Program program;
if (Tracy.ENABLED) Tracy.beginZone(Z_FFP_SHADER_GEN);
try {
@@ -113,8 +111,7 @@ public class ShaderCache {
fragSrc = FragmentShaderGenerator.generate(fk);
uniqueFragCount++;
}
geomSrc = vk.wideLineEmulation() ? getOrGenerateGeometry(vk) : null;
program = Program.create(vk, fk, vertSrc, fragSrc, geomSrc);
program = Program.create(vk, fk, vertSrc, fragSrc);
} finally {
if (Tracy.ENABLED) Tracy.endZone();
}
@@ -135,9 +132,6 @@ public class ShaderCache {
if (dumpDir != null) {
dumpShader(Long.toHexString(vkPacked), ".vert.glsl", vertSrc);
dumpShader("frag_" + fragmentDumpCounter++, ".frag.glsl", fragSrc);
if (geomSrc != null) {
dumpShader(Long.toHexString(vkPacked), ".geom.glsl", geomSrc);
}
}
return program;
@@ -228,10 +222,6 @@ public class ShaderCache {
return vertexSourceCache.computeIfAbsent(vk.pack(), k -> VertexShaderGenerator.generate(vk));
}
private String getOrGenerateGeometry(VertexKey vk) {
return geometrySourceCache.computeIfAbsent(vk.pack(), k -> GeometryShaderGenerator.generate(vk));
}
private void dumpShader(String name, String suffix, String source) {
if (dumpDir == null) return;
try {
@@ -260,11 +250,8 @@ public class ShaderCache {
dumpShader("frag_" + idx, ".frag.glsl", fragSources[i]);
}
}
for (var entry : geometrySourceCache.entrySet()) {
dumpShader(Long.toHexString(entry.getKey()), ".geom.glsl", entry.getValue());
}
GLStateManager.LOGGER.info("Dumped {} vertex, {} geometry, and {} fragment FFP shader sources to {}",
vertexSourceCache.size(), geometrySourceCache.size(), seen.size(), outputDir);
GLStateManager.LOGGER.info("Dumped {} vertex and {} fragment FFP shader sources to {}",
vertexSourceCache.size(), seen.size(), outputDir);
} catch (IOException e) {
GLStateManager.LOGGER.warn("Failed to dump FFP shaders: {}", e.getMessage());
}
@@ -100,7 +100,6 @@ public final class ShaderManager {
VertexShaderGenerator.generate(VertexKey.fromPacked(VertexKey.withInstancing(vkPacked, Instancing.CUBE)));
VertexShaderGenerator.generate(VertexKey.fromPacked(VertexKey.withInstancing(vkPacked, Instancing.PARTICLE)));
FragmentShaderGenerator.generate(FragmentKey.fromPacked(fkScratch, fkLen));
GeometryShaderGenerator.generate(vk);
final Class<?>[] touched = { Program.class, ShaderCache.class, TessellatorStreamingDrawer.class, QuadConverter.class };
for (Class<?> c : touched) {
c.getName();
@@ -1,13 +1,20 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GLTypes;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL33;
import org.lwjgl.opengl.GL41;
import javax.annotation.Nullable;
import java.nio.ByteBuffer;
import java.util.Arrays;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
@@ -19,12 +26,116 @@ public final class VAOManager {
final Int2ObjectOpenHashMap<VAOData> eboOwners = new Int2ObjectOpenHashMap<>();
private VAOData current = null;
public static final int CONV_POSITION = 0;
public static final int CONV_NORMAL = 1;
public static final int CONV_COLOR = 2;
public static final int CONV_SECONDARY_COLOR = 3;
public static final int CONV_FOG_COORD = 4;
public static final int CONV_INDEX = 5;
public static final int CONV_EDGE_FLAG = 6;
public static final int CONV_TEX0 = 7;
public static final int TEX_COORD_UNITS = 8;
public static final int CONV_COUNT = CONV_TEX0 + TEX_COORD_UNITS;
private static final int[] CONV_LOCATION = new int[CONV_COUNT];
private static final int[] LOCATION_CONV = new int[MAX_ATTRIBS];
static {
Arrays.fill(CONV_LOCATION, -1);
Arrays.fill(LOCATION_CONV, -1);
mapConventional(CONV_POSITION, Usage.POSITION);
mapConventional(CONV_NORMAL, Usage.NORMAL);
mapConventional(CONV_COLOR, Usage.COLOR);
mapConventional(CONV_TEX0, Usage.PRIMARY_UV);
mapConventional(CONV_TEX0 + 1, Usage.SECONDARY_UV);
}
private static void mapConventional(int conv, Usage usage) {
final int location = usage.getAttributeLocation();
CONV_LOCATION[conv] = location;
LOCATION_CONV[location] = conv;
}
public static int conventionalLocation(int conv) {
return CONV_LOCATION[conv];
}
private static final int T_BYTE = 1, T_UBYTE = 1 << 1, T_SHORT = 1 << 2, T_USHORT = 1 << 3, T_INT = 1 << 4, T_UINT = 1 << 5, T_FLOAT = 1 << 6;
private static final int T_DOUBLE = 1 << 10, T_HALF = 1 << 11, T_FIXED = 1 << 12, T_PACKED = 1 << 13, T_PACKED_FLOAT = 1 << 14;
private static final int T_INTEGER = T_BYTE | T_UBYTE | T_SHORT | T_USHORT | T_INT | T_UINT;
private static final int T_COLOR = T_INTEGER | T_HALF | T_FLOAT | T_DOUBLE | T_PACKED;
private static final int T_GENERIC = T_COLOR | T_FIXED | T_PACKED_FLOAT;
private static final int[] LEGAL_TYPES = new int[CONV_COUNT];
private static final int[] MIN_SIZE = new int[CONV_COUNT];
private static final int[] MAX_SIZE = new int[CONV_COUNT];
static {
legal(CONV_POSITION, T_SHORT | T_INT | T_FLOAT | T_DOUBLE | T_HALF | T_PACKED, 2, 4);
legal(CONV_NORMAL, T_BYTE | T_SHORT | T_INT | T_HALF | T_FLOAT | T_DOUBLE | T_PACKED, 3, 3);
legal(CONV_COLOR, T_COLOR, 3, 4);
legal(CONV_SECONDARY_COLOR, T_COLOR, 3, 4);
legal(CONV_FOG_COORD, T_HALF | T_FLOAT | T_DOUBLE, 1, 1);
legal(CONV_INDEX, T_UBYTE | T_SHORT | T_INT | T_FLOAT | T_DOUBLE, 1, 1);
legal(CONV_EDGE_FLAG, T_UBYTE, 1, 1);
for (int unit = 0; unit < TEX_COORD_UNITS; unit++) {
legal(CONV_TEX0 + unit, T_SHORT | T_INT | T_HALF | T_FLOAT | T_DOUBLE | T_PACKED, 1, 4);
}
}
private static void legal(int conv, int types, int minSize, int maxSize) {
LEGAL_TYPES[conv] = types;
MIN_SIZE[conv] = minSize;
MAX_SIZE[conv] = maxSize;
}
private static int typeBit(int type) {
if (type >= GL11.GL_BYTE && type <= GL30.GL_HALF_FLOAT) return 1 << type - GL11.GL_BYTE;
return switch (type) {
case GL41.GL_FIXED -> T_FIXED;
case GL12.GL_UNSIGNED_INT_2_10_10_10_REV, GL33.GL_INT_2_10_10_10_REV -> T_PACKED;
case GL30.GL_UNSIGNED_INT_10F_11F_11F_REV -> T_PACKED_FLOAT;
default -> 0;
};
}
private static boolean isLegal(int types, int minSize, int maxSize, boolean bgra, int size, int type) {
final int bit = typeBit(type);
if ((types & bit) == 0) return false;
if (size == GL12.GL_BGRA) return bgra && (bit & (T_UBYTE | T_PACKED)) != 0;
return size >= minSize && size <= maxSize;
}
public static boolean isLegalConventional(int conv, int size, int type) {
return isLegal(LEGAL_TYPES[conv], MIN_SIZE[conv], MAX_SIZE[conv], conv == CONV_COLOR || conv == CONV_SECONDARY_COLOR, size, type);
}
public static boolean isLegalGeneric(int index, int size, int type, boolean integer) {
return index >= 0 && index < MAX_ATTRIBS && isLegal(integer ? T_INTEGER : T_GENERIC, 1, 4, !integer, size, type);
}
private int currentVertexFlags = 0;
private int genericEnabled = 0;
private int conventionalEnabled = 0;
private Attrib[] currentAttribs = null;
private Attrib[] currentGeneric = null;
private Attrib[] currentConventional = null;
public int boundEBO = 0;
private int clientSideEnabledCount = 0;
private int arrayVersion = 0;
private int versionClock = 0;
private int bufferEpoch = 0;
private final Int2IntOpenHashMap bufferDeletedAt = new Int2IntOpenHashMap();
private void touch() {
arrayVersion = ++versionClock;
}
private boolean isBufferLive(int buffer, int sinceEpoch) {
return bufferDeletedAt.get(buffer) <= sinceEpoch;
}
public boolean isBufferLive(Attrib a) {
return isBufferLive(a.vboId, a.epoch);
}
public void init(int defaultVAO) {
final VAOData data = new VAOData();
@@ -45,6 +156,10 @@ public final class VAOManager {
// Save old VAO data
if (current != null) {
current.vertexFlags = currentVertexFlags;
current.genericEnabled = genericEnabled;
current.conventionalEnabled = conventionalEnabled;
current.generic = currentGeneric;
current.conventional = currentConventional;
if (current.ebo != boundEBO) {
removeEboOwner(current.ebo, current);
current.ebo = boundEBO;
@@ -52,6 +167,7 @@ public final class VAOManager {
}
current.attribs = currentAttribs;
current.clientSideEnabledCount = clientSideEnabledCount;
current.arrayVersion = arrayVersion;
}
VAOData data = vaoMap.get(vaoId);
@@ -61,9 +177,14 @@ public final class VAOManager {
}
current = data;
currentVertexFlags = data.vertexFlags;
genericEnabled = data.genericEnabled;
conventionalEnabled = data.conventionalEnabled;
currentAttribs = data.attribs;
currentGeneric = data.generic;
currentConventional = data.conventional;
boundEBO = data.ebo;
clientSideEnabledCount = data.clientSideEnabledCount;
arrayVersion = data.arrayVersion;
}
public void onDeleteVertexArray(int vaoId) {
@@ -73,8 +194,25 @@ public final class VAOManager {
if (data == current) current = null;
}
private static boolean detach(Attrib[] slots, int buffer) {
boolean detached = false;
for (final Attrib a : slots) {
if (a != null && a.vboId == buffer) {
a.vboId = 0;
detached = true;
}
}
return detached;
}
public void onDeleteBuffer(int buffer) {
if (buffer == 0) return;
bufferDeletedAt.put(buffer, ++bufferEpoch);
if (currentAttribs != null) {
boolean detached = detach(currentGeneric != null ? currentGeneric : currentAttribs, buffer);
if (currentConventional != null) detached |= detach(currentConventional, buffer);
if (detached) touch();
}
VAOData owner = eboOwners.remove(buffer);
while (owner != null) {
final VAOData next = owner.nextEboOwner;
@@ -112,14 +250,19 @@ public final class VAOManager {
public void enableClientVertexFlag(int flag) { currentVertexFlags |= flag; }
public void disableClientVertexFlag(int flag) { currentVertexFlags &= ~flag; }
public int getVertexFlags() { return currentVertexFlags; }
public void setVertexFlags(int flags) { currentVertexFlags = flags; }
public boolean isConventionalEnabled(int conv) { return (conventionalEnabled >> conv & 1) != 0; }
public static void setCurrentVertexFlags(int flags) { GLStateManager.ctx().vaos.currentVertexFlags = flags; }
public static final class VAOData {
public Attrib[] attribs;
public int vertexFlags;
public int genericEnabled;
public int conventionalEnabled;
public Attrib[] generic;
public Attrib[] conventional;
public int ebo;
public int clientSideEnabledCount;
int arrayVersion;
private VAOData nextEboOwner;
public VAOData() {
@@ -127,92 +270,307 @@ public final class VAOManager {
}
}
public void setAttribute(int index, int size, int type, boolean normalized, int stride, long offset, int vboId) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
final boolean wasClient = a.clientPointer != null;
public boolean setAttribute(int index, int size, int type, boolean normalized, int stride, long offset, int vboId) {
return setGeneric(index, size, type, normalized, false, stride, offset, vboId, null);
}
public boolean setAttribute(int index, int size, int type, boolean normalized, int stride, ByteBuffer pointer) {
return setGeneric(index, size, type, normalized, false, stride, 0, 0, pointer);
}
public boolean setIntegerAttribute(int index, int size, int type, int stride, long offset, int vboId) {
return setGeneric(index, size, type, false, true, stride, offset, vboId, null);
}
public boolean setIntegerAttribute(int index, int size, int type, int stride, ByteBuffer pointer) {
return setGeneric(index, size, type, false, true, stride, 0, 0, pointer);
}
public boolean setConventional(int conv, int size, int type, boolean normalized, int stride, long offset, int vboId) {
return setConventional(conv, size, type, normalized, stride, offset, vboId, null);
}
public boolean setConventional(int conv, int size, int type, boolean normalized, int stride, ByteBuffer pointer) {
return setConventional(conv, size, type, normalized, stride, 0, 0, pointer);
}
private boolean setGeneric(int index, int size, int type, boolean normalized, boolean integer, int stride, long offset, int vboId, ByteBuffer pointer) {
if (index < 0 || index >= MAX_ATTRIBS) return false;
final Attrib g = genericSlot(index);
store(g, size, type, normalized, integer, stride, offset, vboId, pointer);
if (currentAttribs[index] == g) return true;
if (!isGenericEnabled(index) && isLocationConventionalEnabled(index)) return false;
apply(index, g);
return true;
}
private boolean setConventional(int conv, int size, int type, boolean normalized, int stride, long offset, int vboId, ByteBuffer pointer) {
if (conv < 0 || conv >= CONV_COUNT) return false;
final Attrib c = conventionalSlot(conv);
store(c, size, type, normalized, false, stride, offset, vboId, pointer);
final int index = CONV_LOCATION[conv];
if (index < 0) return false;
if (currentAttribs[index] == c) return true;
if (isGenericEnabled(index)) return false;
apply(index, c);
return true;
}
private void store(Attrib a, int size, int type, boolean normalized, boolean integer, int stride, long offset, int vboId, ByteBuffer pointer) {
final boolean wasClient = a.isClientSide();
a.size = size;
a.type = type;
a.normalized = normalized;
a.integer = integer;
a.stride = stride;
a.offset = offset;
a.vboId = vboId;
a.clientPointer = null;
a.genericPointer = true;
if (wasClient && a.enabled) {
clientSideEnabledCount--;
}
}
public void setAttribute(int index, int size, int type, boolean normalized, int stride, ByteBuffer pointer) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
final boolean wasClient = a.clientPointer != null;
a.size = size;
a.type = type;
a.normalized = normalized;
a.stride = stride;
a.offset = 0;
a.vboId = 0;
a.epoch = bufferEpoch;
a.clientPointer = pointer;
a.genericPointer = true;
if (!wasClient && a.enabled) {
clientSideEnabledCount++;
if (wasClient != a.isClientSide()) clientSideEnabledCount += wasClient ? -1 : 1;
touch();
}
private void apply(int index, Attrib source) {
final Attrib old = currentAttribs[index];
if (old != null) {
if (old.isClientSide()) clientSideEnabledCount--;
source.enabled = old.enabled;
old.enabled = false;
if (source.isClientSide()) clientSideEnabledCount++;
}
currentAttribs[index] = source;
}
private boolean isGenericEnabled(int index) {
return (genericEnabled >> index & 1) != 0;
}
private boolean isLocationConventionalEnabled(int index) {
final int conv = LOCATION_CONV[index];
return conv >= 0 && isConventionalEnabled(conv);
}
private Attrib generic(int index) {
return (currentGeneric != null ? currentGeneric : currentAttribs)[index];
}
private Attrib genericSlot(int index) {
final Attrib[] slots = currentGeneric != null ? currentGeneric : currentAttribs;
Attrib g = slots[index];
if (g == null) {
g = new Attrib();
g.genericPointer = true;
slots[index] = g;
if (currentAttribs[index] == null) currentAttribs[index] = g;
}
return g;
}
private Attrib conventionalSlot(int conv) {
if (currentConventional == null) {
currentConventional = new Attrib[CONV_COUNT];
currentGeneric = currentAttribs.clone();
}
Attrib c = currentConventional[conv];
if (c == null) {
c = new Attrib();
currentConventional[conv] = c;
}
return c;
}
private boolean refresh(int index) {
touch();
boolean repoint = false;
final boolean conventional = isLocationConventionalEnabled(index);
final Attrib winner = isGenericEnabled(index) ? genericSlot(index) : conventional ? currentConventional[LOCATION_CONV[index]] : null;
if (winner != null && winner != currentAttribs[index]) {
apply(index, winner);
repoint = true;
}
final boolean enabled = winner != null;
Attrib e = currentAttribs[index];
if (e == null) {
if (!enabled) return repoint;
e = genericSlot(index);
}
if (e.enabled != enabled) {
if (e.clientPointer != null) clientSideEnabledCount += enabled ? 1 : -1;
e.enabled = enabled;
}
return repoint;
}
public boolean enableAttribute(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return false;
genericEnabled |= 1 << index;
return refresh(index);
}
public boolean disableAttribute(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return false;
genericEnabled &= ~(1 << index);
return refresh(index);
}
public boolean setConventionalEnabled(int conv, boolean enabled) {
if (conv < 0 || conv >= CONV_COUNT) return false;
conventionalSlot(conv);
if (enabled) {
conventionalEnabled |= 1 << conv;
} else {
conventionalEnabled &= ~(1 << conv);
}
final int index = CONV_LOCATION[conv];
if (index >= 0) return refresh(index);
touch();
return false;
}
public boolean setDivisor(int index, int divisor) {
if (index < 0 || index >= MAX_ATTRIBS) return true;
final Attrib g = genericSlot(index);
g.divisor = divisor;
touch();
return currentAttribs[index] == g;
}
public @Nullable Attrib conventional(int conv) {
return conv >= 0 && currentConventional != null ? currentConventional[conv] : null;
}
public static final class ClientArrays {
public int boundVAO;
public int boundVBO;
public int boundEBO;
public int clientActiveTextureUnit;
int arrayVersion;
int bufferEpoch;
int vertexFlags;
int genericEnabled;
int conventionalEnabled;
int genericPresent;
int conventionalPresent;
int effectiveGeneric;
final Attrib[] generic = new Attrib[MAX_ATTRIBS];
final Attrib[] conventional = new Attrib[CONV_COUNT];
}
private static void saveSlot(Attrib[] saved, int i, Attrib source) {
if (saved[i] == null) saved[i] = new Attrib();
saved[i].copyPointer(source);
}
public boolean isBufferLive(int buffer, ClientArrays saved) {
return isBufferLive(buffer, saved.bufferEpoch);
}
public void saveClientArrays(ClientArrays out) {
out.arrayVersion = arrayVersion;
out.bufferEpoch = bufferEpoch;
out.vertexFlags = currentVertexFlags;
out.genericEnabled = genericEnabled;
out.conventionalEnabled = conventionalEnabled;
out.genericPresent = 0;
out.conventionalPresent = 0;
out.effectiveGeneric = 0;
for (int i = 0; i < MAX_ATTRIBS; i++) {
final Attrib g = generic(i);
if (g != null) {
out.genericPresent |= 1 << i;
saveSlot(out.generic, i, g);
}
final Attrib e = currentAttribs[i];
if (e != null && e.genericPointer) out.effectiveGeneric |= 1 << i;
}
if (currentConventional == null) return;
for (int i = 0; i < CONV_COUNT; i++) {
final Attrib c = currentConventional[i];
if (c != null) {
out.conventionalPresent |= 1 << i;
saveSlot(out.conventional, i, c);
}
}
}
public boolean isGenericPointerEnabled(int index) {
private final Attrib[] restoreBefore = new Attrib[MAX_ATTRIBS];
public int restoreClientArrays(ClientArrays in) {
currentVertexFlags = in.vertexFlags;
if (arrayVersion == in.arrayVersion) return 0;
for (int i = 0; i < MAX_ATTRIBS; i++) {
if (restoreBefore[i] == null) restoreBefore[i] = new Attrib();
final Attrib e = currentAttribs[i];
if (e == null) {
restoreBefore[i].clearPointer();
restoreBefore[i].enabled = false;
} else {
restoreBefore[i].copyPointer(e);
restoreBefore[i].enabled = e.enabled;
}
}
genericEnabled = in.genericEnabled;
conventionalEnabled = in.conventionalEnabled;
for (int i = 0; i < MAX_ATTRIBS; i++) {
if ((in.genericPresent >> i & 1) != 0) {
genericSlot(i).copyPointer(in.generic[i]);
} else {
final Attrib g = generic(i);
if (g != null) g.clearPointer();
}
}
if (currentConventional != null || in.conventionalPresent != 0) {
for (int i = 0; i < CONV_COUNT; i++) {
if ((in.conventionalPresent >> i & 1) != 0) {
conventionalSlot(i).copyPointer(in.conventional[i]);
} else if (currentConventional != null && currentConventional[i] != null) {
currentConventional[i].clearPointer();
}
}
}
clientSideEnabledCount = 0;
for (int i = 0; i < MAX_ATTRIBS; i++) {
final int conv = LOCATION_CONV[i];
final Attrib g = generic(i);
final Attrib c = conventional(conv);
final boolean conventional = isLocationConventionalEnabled(i);
Attrib e;
if (isGenericEnabled(i)) {
e = genericSlot(i);
} else if (conventional) {
e = conventionalSlot(conv);
} else {
e = c == null || (in.effectiveGeneric >> i & 1) != 0 ? g : c;
}
if (g != null) g.enabled = false;
if (c != null) c.enabled = false;
currentAttribs[i] = e;
if (e != null) {
e.enabled = isGenericEnabled(i) || conventional;
if (e.isClientSide()) clientSideEnabledCount++;
}
}
int changed = 0;
for (int i = 0; i < MAX_ATTRIBS; i++) {
final Attrib e = currentAttribs[i];
final Attrib before = restoreBefore[i];
if (e == null) {
if (before.enabled) changed |= 0x10000 << i;
} else {
if (e.enabled != before.enabled) changed |= 0x10000 << i;
if (!e.samePointer(before)) changed |= 1 << i;
}
before.clientPointer = null;
}
arrayVersion = in.arrayVersion;
return changed;
}
public boolean isAttribEnabled(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return false;
final Attrib a = currentAttribs[index];
return a != null && a.genericPointer && a.enabled;
}
public void markConventional(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = currentAttribs[index];
if (a != null) a.genericPointer = false;
}
public void setEnabled(int index, boolean enabled) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
if (a.clientPointer != null && a.enabled != enabled) {
clientSideEnabledCount += enabled ? 1 : -1;
}
a.enabled = enabled;
}
public void enableAttribute(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
if (a.clientPointer != null && !a.enabled) {
clientSideEnabledCount++;
}
a.enabled = true;
}
public void disableAttribute(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
if (a.clientPointer != null && a.enabled) {
clientSideEnabledCount--;
}
a.enabled = false;
}
// Lazily allocate each attrib. Most VAOs don't even have 4 attribs, let alone 16.
private Attrib getAttrib(int index) {
Attrib a = currentAttribs[index];
if (a == null) {
a = new Attrib();
currentAttribs[index] = a;
}
return a;
}
public boolean isClientSideAttrib(int index) {
final Attrib attrib = currentAttribs[index];
return attrib != null && attrib.isClientSide();
return a != null && a.enabled;
}
public Attrib attrib(int index) {
@@ -286,7 +644,11 @@ public final class VAOManager {
if (clientArraysVBOOffsets[i] < 0) continue;
final VAOManager.Attrib a = currentAttribs[i];
RENDER_BACKEND.bufferSubData(GL15.GL_ARRAY_BUFFER, clientArraysVBOOffsets[i], a.clientPointer.duplicate());
RENDER_BACKEND.vertexAttribPointer(i, a.size, a.type, a.normalized, a.stride, (long) clientArraysVBOOffsets[i]);
if (a.integer) {
RENDER_BACKEND.vertexAttribIPointer(i, a.size, a.type, a.stride, (long) clientArraysVBOOffsets[i]);
} else {
RENDER_BACKEND.vertexAttribPointer(i, a.size, a.type, a.normalized, a.stride, (long) clientArraysVBOOffsets[i]);
}
}
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, savedVBO);
@@ -298,9 +660,12 @@ public final class VAOManager {
public int size;
public int type;
public boolean normalized;
public boolean integer;
public int stride;
public long offset;
public int vboId;
public int divisor;
int epoch;
public ByteBuffer clientPointer;
public boolean genericPointer;
@@ -308,16 +673,35 @@ public final class VAOManager {
return enabled && clientPointer != null;
}
public void reset() {
enabled = false;
void copyPointer(Attrib source) {
size = source.size;
type = source.type;
normalized = source.normalized;
integer = source.integer;
stride = source.stride;
offset = source.offset;
vboId = source.vboId;
divisor = source.divisor;
epoch = source.epoch;
clientPointer = source.clientPointer;
}
boolean samePointer(Attrib other) {
return size == other.size && type == other.type && normalized == other.normalized && integer == other.integer
&& stride == other.stride && offset == other.offset && vboId == other.vboId && divisor == other.divisor
&& clientPointer == other.clientPointer;
}
void clearPointer() {
size = 0;
type = 0;
normalized = false;
integer = false;
stride = 0;
offset = 0;
vboId = 0;
divisor = 0;
clientPointer = null;
genericPointer = false;
}
public int effectiveStride() {
@@ -46,8 +46,8 @@ public final class VertexKey {
private static final int BIT_TEXGEN_R = BIT_TEXGEN_T + 3;
private static final int BIT_TEXGEN_Q = BIT_TEXGEN_R + 3;
private static final int BIT_CLIP_PLANES = BIT_TEXGEN_Q + 3;
private static final int BIT_WIDE_LINE = BIT_CLIP_PLANES + 1;
private static final int BIT_UNIT_TEXMAT_BASE = BIT_WIDE_LINE + 1;
private static final int BIT_LINE_QUADS = BIT_CLIP_PLANES + 1;
private static final int BIT_UNIT_TEXMAT_BASE = BIT_LINE_QUADS + 1;
private static final int BIT_UNIT23_UV_FROM_UNIT0 = BIT_UNIT_TEXMAT_BASE + MAX_UNITS;
private static final int BIT_LINE_STIPPLE = BIT_UNIT23_UV_FROM_UNIT0 + 1;
private static final int BIT_INSTANCING = BIT_LINE_STIPPLE + 1;
@@ -103,8 +103,8 @@ public final class VertexKey {
public int texGenModeQ() { return (int)((packed >> BIT_TEXGEN_Q) & 0x7); }
public boolean texGenEnabled() { return texGenModeS() != TG_NONE || texGenModeT() != TG_NONE || texGenModeR() != TG_NONE || texGenModeQ() != TG_NONE; }
public boolean clipPlanesEnabled() { return bit(BIT_CLIP_PLANES); }
public boolean wideLineEmulation() { return bit(BIT_WIDE_LINE); }
public boolean lineStipple() { return bit(BIT_LINE_STIPPLE); }
public boolean lineQuads() { return bit(BIT_LINE_QUADS); }
public Instancing instancing() { return Instancing.VALUES[(int) ((packed >> BIT_INSTANCING) & INSTANCING_MASK)]; }
public boolean cmReplacesAmbient() { final int m = colorMaterialMode(); return m == CM_AMBIENT || m == CM_AMBIENT_AND_DIFFUSE; }
@@ -226,7 +226,7 @@ public final class VertexKey {
// Vertex format flags
if (hasColor) bits |= (1L << BIT_HAS_VERTEX_COLOR);
if (hasNormal) bits |= (1L << BIT_HAS_VERTEX_NORMAL);
if (hasNormal && !glCtx.lineQuadsActive) bits |= (1L << BIT_HAS_VERTEX_NORMAL);
if (hasTexCoord) bits |= (1L << BIT_HAS_VERTEX_TEX);
if (hasLightmap) bits |= (1L << BIT_HAS_VERTEX_LIGHTMAP);
@@ -238,14 +238,14 @@ public final class VertexKey {
bits |= (1L << BIT_CLIP_PLANES);
}
if (glCtx.wideLineEmulationActive) {
bits |= (1L << BIT_WIDE_LINE);
}
if (glCtx.lineStippleActive) {
bits |= (1L << BIT_LINE_STIPPLE);
}
if (glCtx.lineQuadsActive) {
bits |= (1L << BIT_LINE_QUADS);
}
bits |= (long) glCtx.ffpInstancing.ordinal() << BIT_INSTANCING;
return bits;
@@ -277,6 +277,6 @@ public final class VertexKey {
@Override
public String toString() {
return String.format("FFPVertexKey[0x%011X: lit=%b l0=%b l1=%b cm=%b fog=%b tex=%d%d%d%d texmat=%d%d%d%d col=%b nrm=%b vtex=%b vlm=%b tg=%d/%d/%d/%d clip=%b wline=%b inst=%s]", packed,lightingEnabled(), lightEnabled(0), lightEnabled(1), colorMaterialEnabled(), fogEnabled(), unitTexCoordEnabled(0)?1:0, unitTexCoordEnabled(1)?1:0, unitTexCoordEnabled(2)?1:0, unitTexCoordEnabled(3)?1:0, unitTexMatEnabled(0)?1:0, unitTexMatEnabled(1)?1:0, unitTexMatEnabled(2)?1:0, unitTexMatEnabled(3)?1:0, hasVertexColor(), hasVertexNormal(), hasVertexTexCoord(), hasVertexLightmap(), texGenModeS(), texGenModeT(), texGenModeR(), texGenModeQ(), clipPlanesEnabled(), wideLineEmulation(), instancing());
return String.format("FFPVertexKey[0x%012X: lit=%b l0=%b l1=%b cm=%b fog=%b tex=%d%d%d%d texmat=%d%d%d%d col=%b nrm=%b vtex=%b vlm=%b tg=%d/%d/%d/%d clip=%b lquads=%b inst=%s]", packed,lightingEnabled(), lightEnabled(0), lightEnabled(1), colorMaterialEnabled(), fogEnabled(), unitTexCoordEnabled(0)?1:0, unitTexCoordEnabled(1)?1:0, unitTexCoordEnabled(2)?1:0, unitTexCoordEnabled(3)?1:0, unitTexMatEnabled(0)?1:0, unitTexMatEnabled(1)?1:0, unitTexMatEnabled(2)?1:0, unitTexMatEnabled(3)?1:0, hasVertexColor(), hasVertexNormal(), hasVertexTexCoord(), hasVertexLightmap(), texGenModeS(), texGenModeT(), texGenModeR(), texGenModeQ(), clipPlanesEnabled(), lineQuads(), instancing());
}
}
@@ -34,28 +34,16 @@ public final class VertexShaderGenerator {
}
emitTexCoordPassthrough(sb, key);
if (key.lineStipple()) {
emitLineStart(sb);
emitLineStart(sb, key.lineQuads() ? "lineCenter" : "gl_Position");
}
if (key.fogEnabled()) {
emitFogDistance(sb, key);
}
if (key.clipPlanesEnabled()) {
emitClipDistances(sb);
emitClipDistances(sb, key);
}
sb.append("}\n");
String source = sb.toString();
if (key.wideLineEmulation()) {
source = source.replace("v_SpecularColor", "v_SpecularColor_gs")
.replace("v_TexCoord0", "v_TexCoord0_gs")
.replace("v_TexCoord1", "v_TexCoord1_gs")
.replace("v_TexCoord2", "v_TexCoord2_gs")
.replace("v_TexCoord3", "v_TexCoord3_gs")
.replace("v_FogCoord", "v_FogCoord_gs")
.replace("v_LineStart", "v_LineStart_gs")
.replace("v_Color", "v_Color_gs");
}
return source;
return sb.toString();
}
private static void emitAttributes(StringBuilder sb, VertexKey key) {
@@ -78,6 +66,8 @@ public final class VertexShaderGenerator {
}
if (key.hasVertexNormal() || kind == Instancing.CUBE) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.NORMAL.getAttributeLocation()).append(") in vec3 a_Normal;\n");
} else if (key.lineQuads()) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.NORMAL.getAttributeLocation()).append(") in vec3 a_LineOtherEnd;\n");
}
}
for (String d : InstancedGlslHelpers.attributeDecls("a_", kind)) {
@@ -97,8 +87,8 @@ public final class VertexShaderGenerator {
};
}
private static void emitLineStart(StringBuilder sb) {
sb.append(" vec2 ndc = gl_Position.xy / gl_Position.w;\n");
private static void emitLineStart(StringBuilder sb, String clipPosition) {
sb.append(" vec2 ndc = ").append(clipPosition).append(".xy / ").append(clipPosition).append(".w;\n");
sb.append(" v_LineStart = u_Viewport.xy + (ndc * 0.5 + 0.5) * u_Viewport.zw;\n\n");
}
@@ -151,15 +141,59 @@ public final class VertexShaderGenerator {
sb.append(" v_Overlay = a_InstOverlay;\n");
} else {
sb.append(" gl_Position = u_MVPMatrix * pos4;\n");
if (key.lineQuads()) {
emitLineQuadWidening(sb);
}
// Eye position needed for lighting, fog, EYE_LINEAR texgen, and clip planes
if (key.lightingEnabled() || key.fogEnabled() || texGenNeedsEyePos(key) || key.clipPlanesEnabled()) {
sb.append(" vec4 eyePos = u_ModelViewMatrix * pos4;\n");
}
if (key.lineQuads() && (key.fogEnabled() || key.clipPlanesEnabled())) {
sb.append(" vec4 lineEyeOther = u_ModelViewMatrix * vec4(a_LineOtherEnd, 1.0);\n");
}
}
sb.append('\n');
}
private static void emitLineQuadWidening(StringBuilder sb) {
sb.append(" int lineCorner = gl_VertexID % 6;\n");
sb.append(" bool lineStart = lineCorner == 0 || lineCorner == 1 || lineCorner == 3;\n");
sb.append(" vec4 lineOther = u_MVPMatrix * vec4(a_LineOtherEnd, 1.0);\n");
sb.append(" vec4 lineC0 = lineStart ? gl_Position : lineOther;\n");
sb.append(" vec4 lineC1 = lineStart ? lineOther : gl_Position;\n");
sb.append(" vec4 lineCenter = lineC0;\n");
sb.append(" float lineT = 0.0;\n");
sb.append(" float lineD0 = lineC0.z + lineC0.w;\n");
sb.append(" float lineD1 = lineC1.z + lineC1.w;\n");
sb.append(" if (lineD0 < 0.0 && lineD1 < 0.0) {\n");
sb.append(" gl_Position = vec4(0.0, 0.0, 2.0, 1.0);\n");
sb.append(" } else {\n");
sb.append(" float lineT0 = lineD0 < 0.0 ? lineD0 / (lineD0 - lineD1) : 0.0;\n");
sb.append(" float lineT1 = lineD1 < 0.0 ? lineD0 / (lineD0 - lineD1) : 1.0;\n");
sb.append(" lineT = lineStart ? lineT0 : lineT1;\n");
sb.append(" vec4 lineP0 = mix(lineC0, lineC1, lineT0);\n");
sb.append(" vec4 lineP1 = mix(lineC0, lineC1, lineT1);\n");
sb.append(" vec2 lineN0 = lineP0.xy / lineP0.w;\n");
sb.append(" vec2 lineN1 = lineP1.xy / lineP1.w;\n");
sb.append(" vec2 linePixel = 2.0 / u_ViewportSize;\n");
sb.append(" float lineHalfWidth = 0.5 * u_LineWidth;\n");
sb.append(" vec2 lineOffset;\n");
sb.append(" vec2 lineShift;\n");
sb.append(" vec2 lineSpan = abs(lineN1 - lineN0) * u_ViewportSize;\n");
sb.append(" if (lineSpan.x > lineSpan.y) {\n");
sb.append(" lineOffset = vec2(0.0, lineHalfWidth * linePixel.y);\n");
sb.append(" lineShift = vec2(lineN0.x < lineN1.x ? -0.5 : 0.5, -0.125) * linePixel;\n");
sb.append(" } else {\n");
sb.append(" lineOffset = vec2(lineHalfWidth * linePixel.x, 0.0);\n");
sb.append(" lineShift = vec2(0.125, lineN0.y < lineN1.y ? -0.5 : 0.5) * linePixel;\n");
sb.append(" }\n");
sb.append(" vec4 lineP = lineStart ? lineP0 : lineP1;\n");
sb.append(" vec2 lineN = lineStart ? lineN0 : lineN1;\n");
sb.append(" gl_Position = vec4((lineN + (lineCorner % 2 == 0 ? lineOffset : -lineOffset) + lineShift) * lineP.w, lineP.z, lineP.w);\n");
sb.append(" }\n");
}
private static void emitNormalTransform(StringBuilder sb, VertexKey key) {
sb.append(" // Normal transform\n");
final String srcNormal = key.hasVertexNormal() && key.instancing() != Instancing.PARTICLE ? "a_Normal" : "u_CurrentNormal";
@@ -380,24 +414,30 @@ public final class VertexShaderGenerator {
private static void emitFogDistance(StringBuilder sb, VertexKey key) {
sb.append(" // Fog distance\n");
// Mesa ffvertex_prog.c build_fog(): FDM_EYE_RADIAL/EYE_PLANE/EYE_PLANE_ABS
final int fogDistMode = key.fogDistanceMode();
switch (fogDistMode) {
case 0 -> // FDM_EYE_RADIAL: Euclidean distance
sb.append(" v_FogCoord = length(eyePos.xyz);\n");
case 1 -> // FDM_EYE_PLANE: raw Z (can be negative, clamped by fog factor)
sb.append(" v_FogCoord = eyePos.z;\n");
case 2 -> // FDM_EYE_PLANE_ABS: absolute Z
sb.append(" v_FogCoord = abs(eyePos.z);\n");
default ->
sb.append(" v_FogCoord = abs(eyePos.z);\n");
final String own = fogDistance(key, "eyePos");
final String fog = key.lineQuads() ? acrossLine(own, fogDistance(key, "lineEyeOther")) : own;
sb.append(" v_FogCoord = ").append(fog).append(";\n");
}
// Mesa ffvertex_prog.c build_fog(): FDM_EYE_RADIAL/EYE_PLANE/EYE_PLANE_ABS
private static String fogDistance(VertexKey key, String eye) {
return switch (key.fogDistanceMode()) {
case 0 -> "length(" + eye + ".xyz)"; // FDM_EYE_RADIAL: Euclidean distance
case 1 -> eye + ".z"; // FDM_EYE_PLANE: raw Z (can be negative, clamped by fog factor)
default -> "abs(" + eye + ".z)"; // FDM_EYE_PLANE_ABS: absolute Z
};
}
private static void emitClipDistances(StringBuilder sb, VertexKey key) {
sb.append(" // Clip distances\n");
for (int i = 0; i < 8; i++) {
final String own = "dot(u_ClipPlane[" + i + "], eyePos)";
final String distance = key.lineQuads() ? acrossLine(own, "dot(u_ClipPlane[" + i + "], lineEyeOther)") : own;
sb.append(" gl_ClipDistance[").append(i).append("] = ").append(distance).append(";\n");
}
}
private static void emitClipDistances(StringBuilder sb) {
sb.append(" // Clip distances\n");
for (int i = 0; i < 8; i++) {
sb.append(" gl_ClipDistance[").append(i).append("] = dot(u_ClipPlane[").append(i).append("], eyePos);\n");
}
private static String acrossLine(String own, String other) {
return "mix(lineStart ? " + own + " : " + other + ", lineStart ? " + other + " : " + own + ", lineT)";
}
}
@@ -11,6 +11,7 @@ public final class GLSMHooks {
public static DeferredDepthColorHandler depthColorHandler;
public static GlintColorHandler glintColorHandler;
public static ImmediateExtendedAttribHandler immediateExtendedHandler;
public static LineDrawHandler lineDrawHandler;
public static ShaderTransformPostProcessor postTransformProcessor;
public static PerFrameUniformBlock perFrameUniformBlock;
public static PerFrameUniformBlock perPassUniformBlock;
@@ -0,0 +1,8 @@
package com.gtnewhorizons.angelica.glsm.hooks;
public interface LineDrawHandler {
boolean bindLineProgram();
void restoreProgram();
}
@@ -85,6 +85,11 @@ public final class EcosystemNarrowRules {
"com.tihyo.legends.management.asm.AbstractTransformer",
"com.tihyo.legends.management.asm.ASMHelper",
"com.tihyo.legends.management.asm.Transformer"
)),
new NarrowRule("PowerConverters", "covers1624.powerconverters.", List.of(
"covers1624.powerconverters.asm",
"covers1624.powerconverters.randomdebugshit.CoFHDevEnvHacks"
))
);
@@ -1,8 +1,11 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IVertexArrayObject;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
import com.gtnewhorizons.angelica.glsm.streaming.LineQuads;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import com.gtnewhorizons.angelica.glsm.ffp.FfpExtendedAttribs;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
@@ -55,9 +58,20 @@ public final class DisplayListVBO {
private final int count;
private final int vertexFlags;
private int pendingExtVbo = 0;
private IVertexArrayObject lineQuads;
private int lineQuadStart;
private int lineQuadCount;
private int lineQuadFlags;
void setPendingExtVbo(int extVbo) { this.pendingExtVbo = extVbo; }
void setLineQuads(IVertexArrayObject quads, int start, int count, int flags) {
this.lineQuads = quads;
this.lineQuadStart = start;
this.lineQuadCount = count;
this.lineQuadFlags = flags;
}
public SubVBO(IVertexArrayObject vao, int drawMode, int start, int count, int vertexFlags) {
this.vao = vao;
this.drawMode = drawMode;
@@ -80,10 +94,15 @@ public final class DisplayListVBO {
public void delete() {
vao.delete();
if (lineQuads != null) lineQuads.delete();
}
public void render() {
if (vao == null) return;
if (lineQuads != null && GLStateManager.ffpWidensLineQuads()) {
renderLineQuads();
return;
}
vao.bind();
if (pendingExtVbo != 0) {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, pendingExtVbo);
@@ -102,6 +121,22 @@ public final class DisplayListVBO {
vao.unbind();
}
private void renderLineQuads() {
final GLContextState glCtx = GLStateManager.ctx();
lineQuads.bind();
VAOManager.setCurrentVertexFlags(lineQuadFlags);
final boolean culled = LineQuads.disableCulling();
glCtx.lineQuadsActive = true;
FfpExtendedAttribs.beginInternalDraw();
try {
lineQuads.draw(GL11.GL_TRIANGLES, lineQuadStart, lineQuadCount);
} finally {
FfpExtendedAttribs.endInternalDraw();
glCtx.lineQuadsActive = false;
LineQuads.restoreCulling(culled);
}
lineQuads.unbind();
}
public IVertexArrayObject getVAO() {
return vao;
@@ -13,6 +13,7 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
import com.gtnewhorizons.angelica.glsm.streaming.LineQuads;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
@@ -78,6 +79,8 @@ public final class DisplayListVBOBuilder {
final IntArrayList extStarts = wantExt ? new IntArrayList() : null;
final IntArrayList extCounts = wantExt ? new IntArrayList() : null;
final IntArrayList extPrims = wantExt ? new IntArrayList() : null;
// drawIndex, start, vertexCount, drawMode for each line draw
final IntArrayList lineDraws = GLStateManager.wideLineEmulationEnabled ? new IntArrayList() : null;
for (FormatData data : formatData) {
int vertexCount;
@@ -123,6 +126,12 @@ public final class DisplayListVBOBuilder {
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(vao, GL11.GL_TRIANGLE_FAN, start, vertexCount, flags);
} else {
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(vao, data.drawMode, start, vertexCount, flags);
if (lineDraws != null && LineQuads.segmentCount(data.drawMode, vertexCount) > 0) {
lineDraws.add(data.drawIndex);
lineDraws.add(start);
lineDraws.add(vertexCount);
lineDraws.add(data.drawMode);
}
}
@@ -130,6 +139,9 @@ public final class DisplayListVBOBuilder {
}
ByteBuffer bigBuffer = mergeAndDelete(allBuffers);
vbo.allocate(bigBuffer, start);
if (lineDraws != null && !lineDraws.isEmpty()) {
attachLineQuads(vbos, format, bigBuffer, lineDraws);
}
if (wantExt && !extDrawIndexes.isEmpty()) {
final int extVbo = buildExt(extHandler, format, bigBuffer, extStarts, extCounts, extPrims);
@@ -148,6 +160,34 @@ public final class DisplayListVBOBuilder {
return new DisplayListVBO(vbos, extVbos.toIntArray());
}
// The line width is only known at playback, so lines keep their own draw and also get quads to widen
private static void attachLineQuads(DisplayListVBO.SubVBO[] vbos, VertexFormat format, ByteBuffer vertices, IntArrayList lineDraws) {
final VertexFormat quadFormat = LineQuads.quadFormat(format);
final int quadSize = quadFormat.getVertexSize();
int quadVertices = 0;
for (int i = 0; i < lineDraws.size(); i += 4) {
quadVertices += LineQuads.segmentCount(lineDraws.getInt(i + 3), lineDraws.getInt(i + 2)) * LineQuads.VERTICES_PER_SEGMENT;
}
final ByteBuffer quads = memAlloc(quadVertices * quadSize);
final long src = memAddress0(vertices);
final long dst = memAddress0(quads);
final int stride = format.getVertexSize();
final IVertexArrayObject quadVao = VAOManager.createStorageVAO(quadFormat, -1, 0);
int quadStart = 0;
for (int i = 0; i < lineDraws.size(); i += 4) {
final int start = lineDraws.getInt(i + 1);
final int count = lineDraws.getInt(i + 2);
final int mode = lineDraws.getInt(i + 3);
final int corners = LineQuads.segmentCount(mode, count) * LineQuads.VERTICES_PER_SEGMENT;
LineQuads.expandWithOtherEnd(src + (long) start * stride, format, mode, count, dst + (long) quadStart * quadSize);
vbos[lineDraws.getInt(i)].setLineQuads(quadVao, quadStart, corners, quadFormat.getVertexFlags());
quadStart += corners;
}
quadVao.getVBO().allocate(quads, quadVertices);
memFree(quads);
}
private static int buildExt(ImmediateExtendedAttribHandler handler, VertexFormat format, ByteBuffer bigBuffer,
IntArrayList extStarts, IntArrayList extCounts, IntArrayList extPrims) {
final int extStride = ImmediateExtendedAttribHandler.EXT_STRIDE;
@@ -5,6 +5,8 @@ import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GLTypes;
import com.gtnewhorizons.angelica.glsm.QuadConverter;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import org.lwjgl.opengl.GL11;
@@ -13,6 +15,10 @@ import java.util.Arrays;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetByte;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetShort;
/**
* Records immediate mode GL calls (glBegin/glEnd/glVertex) via {@link DirectTessellator}.
* Used both during display list compilation and for live immediate mode emulation in core profile.
@@ -213,6 +219,34 @@ public final class ImmediateModeRecorder {
}
}
public static DirectTessellator processDrawElementsFromAttribs(int mode, int count, int indexType, long indices) {
final long minMax = QuadConverter.scanMinMaxIndex(indices, indexType, count);
if (minMax == -1L) return null;
final int minVertex = (int) (minMax & 0xFFFFFFFFL);
final int maxVertex = (int) (minMax >>> 32);
final AttribSnapshot snap = AttribSnapshot.snapshot(minVertex, maxVertex - minVertex + 1);
if (snap == null) return null;
try {
prepareReaders(snap);
if (positionReaderIndex < 0) return null;
begin(mode);
final DirectTessellator t = tessellator();
final int indexSize = GLTypes.sizeBytes(indexType);
for (int i = 0; i < count; i++) {
final long at = indices + (long) i * indexSize;
final int vertex = switch (indexType) {
case GL11.GL_UNSIGNED_BYTE -> memGetByte(at) & 0xFF;
case GL11.GL_UNSIGNED_SHORT -> memGetShort(at) & 0xFFFF;
default -> memGetInt(at);
};
readVertexFromBuffer(t, vertex);
}
return end();
} finally {
snap.free();
}
}
private static void prepareReaders(AttribSnapshot snap) {
nonPositionCount = 0;
positionReaderIndex = -1;
@@ -3,7 +3,7 @@ package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GLTypes;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import com.gtnewhorizons.angelica.glsm.states.PixelStoreState;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -28,7 +28,7 @@ public record TexImage2DCmd(
int format,
int type,
@Nullable ByteBuffer pixels,
@Nullable PixelUnpackState unpack
@Nullable PixelStoreState unpack
) implements DisplayListCommand {
public TexImage2DCmd {
@@ -37,19 +37,19 @@ public record TexImage2DCmd(
}
}
public static TexImage2DCmd fromIntBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable IntBuffer pixels, PixelUnpackState unpack) {
public static TexImage2DCmd fromIntBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable IntBuffer pixels, PixelStoreState unpack) {
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
public static TexImage2DCmd fromFloatBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable FloatBuffer pixels, PixelUnpackState unpack) {
public static TexImage2DCmd fromFloatBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable FloatBuffer pixels, PixelStoreState unpack) {
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
public static TexImage2DCmd fromDoubleBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable DoubleBuffer pixels, PixelUnpackState unpack) {
public static TexImage2DCmd fromDoubleBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable DoubleBuffer pixels, PixelStoreState unpack) {
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
public static TexImage2DCmd fromByteBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ByteBuffer pixels, PixelUnpackState unpack) {
public static TexImage2DCmd fromByteBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ByteBuffer pixels, PixelStoreState unpack) {
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
@@ -3,7 +3,7 @@ package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GLTypes;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import com.gtnewhorizons.angelica.glsm.states.PixelStoreState;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -28,7 +28,7 @@ public record TexSubImage2DCmd(
int format,
int type,
@Nullable ByteBuffer pixels,
@Nullable PixelUnpackState unpack
@Nullable PixelStoreState unpack
) implements DisplayListCommand {
public TexSubImage2DCmd {
@@ -37,19 +37,19 @@ public record TexSubImage2DCmd(
}
}
public static TexSubImage2DCmd fromIntBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable IntBuffer pixels, PixelUnpackState unpack) {
public static TexSubImage2DCmd fromIntBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable IntBuffer pixels, PixelStoreState unpack) {
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
public static TexSubImage2DCmd fromFloatBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable FloatBuffer pixels, PixelUnpackState unpack) {
public static TexSubImage2DCmd fromFloatBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable FloatBuffer pixels, PixelStoreState unpack) {
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
public static TexSubImage2DCmd fromDoubleBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable DoubleBuffer pixels, PixelUnpackState unpack) {
public static TexSubImage2DCmd fromDoubleBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable DoubleBuffer pixels, PixelStoreState unpack) {
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
public static TexSubImage2DCmd fromByteBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable ByteBuffer pixels, PixelUnpackState unpack) {
public static TexSubImage2DCmd fromByteBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable ByteBuffer pixels, PixelStoreState unpack) {
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
@@ -143,6 +143,7 @@ public class GLSMRedirector {
private static final String OpenGlHelper = "net/minecraft/client/renderer/OpenGlHelper";
private static final String EXTBlendFunc = "org/lwjgl/opengl/EXTBlendFuncSeparate";
private static final String EXTSecondaryColor = "org/lwjgl/opengl/EXTSecondaryColor";
private static final String EXTFogCoord = "org/lwjgl/opengl/EXTFogCoord";
private static final String ARBMultiTexture = "org/lwjgl/opengl/ARBMultitexture";
private static final String ARBShaderObjects = "org/lwjgl/opengl/ARBShaderObjects";
private static final String ARBInstancedArrays = "org/lwjgl/opengl/ARBInstancedArrays";
@@ -378,6 +379,8 @@ public class GLSMRedirector {
.add("glEnableClientState")
.add("glDisableClientState")
.add("glInterleavedArrays")
.add("glEdgeFlagPointer")
.add("glGetPointer")
.add("glPushClientAttrib")
.add("glPopClientAttrib")
.add("glViewport")
@@ -408,11 +411,16 @@ public class GLSMRedirector {
.add("glMultiDrawArrays")
.add("glPointParameterf")
.add("glPointParameteri")
.add("glPointParameter")
.add("glPointParameterfv", "glPointParameter")
.add("glPointParameteriv", "glPointParameter")
// Removed in core profile - emulated by the FFP shaders, calling through would abort the JVM
.add("glSecondaryColor3f")
.add("glSecondaryColor3d")
.add("glSecondaryColor3b")
.add("glSecondaryColor3ub");
.add("glSecondaryColor3ub")
.add("glSecondaryColorPointer")
.add("glFogCoordPointer");
final var gl15 = RedirectMap.newMap()
.add("glGenBuffers")
.add("glBindBuffer")
@@ -604,7 +612,9 @@ public class GLSMRedirector {
.add("glSecondaryColor3bEXT", "glSecondaryColor3b")
.add("glSecondaryColor3fEXT", "glSecondaryColor3f")
.add("glSecondaryColor3dEXT", "glSecondaryColor3d")
.add("glSecondaryColor3ubEXT", "glSecondaryColor3ub"));
.add("glSecondaryColor3ubEXT", "glSecondaryColor3ub")
.add("glSecondaryColorPointerEXT", "glSecondaryColorPointer"));
methodRedirects.put(EXTFogCoord, RedirectMap.newMap().add("glFogCoordPointerEXT", "glFogCoordPointer"));
// ARB
methodRedirects.put(ARBMultiTexture, RedirectMap.newMap()
@@ -626,6 +636,7 @@ public class GLSMRedirector {
.add("glGenRenderbuffers")
.add("glRenderbufferStorage")
.add("glFramebufferRenderbuffer")
.add("glGenerateMipmap")
);
methodRedirects.put(EXTFramebufferObject, RedirectMap.newMap()
.add("glBindFramebufferEXT", "glBindFramebuffer")
@@ -638,6 +649,7 @@ public class GLSMRedirector {
.add("glGenRenderbuffersEXT", "glGenRenderbuffers")
.add("glRenderbufferStorageEXT", "glRenderbufferStorage")
.add("glFramebufferRenderbufferEXT", "glFramebufferRenderbuffer")
.add("glGenerateMipmapEXT", "glGenerateMipmap")
);
methodRedirects.put(ARBOcclusionQuery, RedirectMap.newMap()
.add("glGenQueriesARB", "glGenQueries")
@@ -1,20 +1,17 @@
package com.gtnewhorizons.angelica.glsm.shader;
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.opengl.GL20;
import org.taumc.glsl.grammar.GLSLLexer;
import org.taumc.glsl.grammar.GLSLParser;
import org.taumc.glsl.grammar.GLSLPreParser;
import org.taumc.glsl.grammar.GLSLParserBaseListener;
import java.util.ArrayList;
import java.util.Comparator;
@@ -99,7 +96,7 @@ public final class GlslVulkanPreprocess {
}
final List<Edit> edits = new ArrayList<>();
final Metadata meta = collectEdits(source, root, glShaderType, debugName, separateReadOnlyImages, edits);
final Metadata meta = collectEdits(new ShaderIndex(root, source), glShaderType, debugName, separateReadOnlyImages, edits);
final Result out = new Result(applyEdits(source, edits), meta.boolUniforms(), meta.explicitVsInputs());
synchronized (CACHE) {
@@ -111,129 +108,100 @@ public final class GlslVulkanPreprocess {
public record Metadata(Set<String> boolUniforms, Set<String> explicitVsInputs, boolean needsSamplerless) {}
public static Metadata collectEdits(String source, GLSLParser.Translation_unitContext root, int glShaderType, String debugName, boolean separateReadOnlyImages, List<Edit> edits) {
public static Metadata collectEdits(ShaderIndex index, int glShaderType, String debugName, boolean separateReadOnlyImages, List<Edit> edits) {
final boolean isVertex = glShaderType == GL20.GL_VERTEX_SHADER;
final Set<String> bools = new HashSet<>();
final Set<String> explicitInputs = new HashSet<>();
final Set<String> readOnlyImages = new HashSet<>();
final boolean[] needsSamplerless = { false };
boolean needsSamplerless = false;
final int[] maxExplicitLoc = { -1 };
int maxExplicitLoc = -1;
final List<Integer> unlocatedVsInputStarts = new ArrayList<>();
ParseTreeWalker.DEFAULT.walk(new GLSLParserBaseListener() {
@Override
public void enterInit_declarator_list(GLSLParser.Init_declarator_listContext ctx) {
final GLSLParser.Single_declarationContext single = ctx.single_declaration();
if (single == null || single.fully_specified_type() == null) return;
for (GLSLParser.DeclarationContext declaration : index.globalDeclarations()) {
final GLSLParser.Init_declarator_listContext list = declaration.init_declarator_list();
if (list == null) continue;
final GLSLParser.Single_declarationContext single = list.single_declaration();
if (single == null || single.fully_specified_type() == null) continue;
final GLSLParser.Fully_specified_typeContext fst = single.fully_specified_type();
final GLSLParser.Type_qualifierContext tq = fst.type_qualifier();
if (tq == null) return;
final GLSLParser.Fully_specified_typeContext fst = single.fully_specified_type();
final GLSLParser.Type_qualifierContext tq = fst.type_qualifier();
if (tq == null) continue;
boolean hasUniform = false, hasIn = false, hasLocation = false, hasReadonly = false;
int locValue = -1;
int bindingValue = -1;
for (GLSLParser.Single_type_qualifierContext stq : tq.single_type_qualifier()) {
if (stq.storage_qualifier() != null) {
final String s = stq.storage_qualifier().getText();
if ("uniform".equals(s)) hasUniform = true;
else if ("in".equals(s)) hasIn = true;
else if ("readonly".equals(s)) hasReadonly = true;
} else if (stq.layout_qualifier() != null) {
for (GLSLParser.Layout_qualifier_idContext id : stq.layout_qualifier().layout_qualifier_id_list().layout_qualifier_id()) {
if (id.IDENTIFIER() == null) continue;
final String qualifier = id.IDENTIFIER().getText();
if ("location".equals(qualifier)) {
hasLocation = true;
if (id.constant_expression() != null) {
try { locValue = Integer.parseInt(id.constant_expression().getText()); } catch (NumberFormatException ignored) {}
}
} else if ("binding".equals(qualifier) && id.constant_expression() != null) {
try { bindingValue = Integer.parseInt(id.constant_expression().getText()); } catch (NumberFormatException ignored) {}
boolean hasUniform = false, hasIn = false, hasLocation = false, hasReadonly = false;
int locValue = -1;
int bindingValue = -1;
for (GLSLParser.Single_type_qualifierContext stq : tq.single_type_qualifier()) {
if (stq.storage_qualifier() != null) {
final String s = stq.storage_qualifier().getText();
if ("uniform".equals(s)) hasUniform = true;
else if ("in".equals(s)) hasIn = true;
else if ("readonly".equals(s)) hasReadonly = true;
} else if (stq.layout_qualifier() != null) {
for (GLSLParser.Layout_qualifier_idContext id : stq.layout_qualifier().layout_qualifier_id_list().layout_qualifier_id()) {
if (id.IDENTIFIER() == null) continue;
final String qualifier = id.IDENTIFIER().getText();
if ("location".equals(qualifier)) {
hasLocation = true;
if (id.constant_expression() != null) {
try { locValue = Integer.parseInt(id.constant_expression().getText()); } catch (NumberFormatException ignored) {}
}
} else if ("binding".equals(qualifier) && id.constant_expression() != null) {
try { bindingValue = Integer.parseInt(id.constant_expression().getText()); } catch (NumberFormatException ignored) {}
}
}
}
}
final boolean boolDecl = hasUniform && isBool(fst);
final boolean explicitInputDecl = isVertex && hasIn && hasLocation;
final boolean boolDecl = hasUniform && isBool(fst);
final boolean explicitInputDecl = isVertex && hasIn && hasLocation;
final List<GLSLParser.Typeless_declarationContext> declarators = ShaderIndex.declarators(list);
for (GLSLParser.Typeless_declarationContext td : declarators) {
handleDeclarator(td, hasUniform, boolDecl, explicitInputDecl, bools, explicitInputs, edits);
}
if (single.typeless_declaration() != null) {
handleDeclarator(single.typeless_declaration(), hasUniform, boolDecl, explicitInputDecl, bools, explicitInputs, edits);
}
for (GLSLParser.Typeless_declarationContext td : ctx.typeless_declaration()) {
handleDeclarator(td, hasUniform, boolDecl, explicitInputDecl, bools, explicitInputs, edits);
}
if (separateReadOnlyImages && hasUniform && fst.type_specifier() != null) {
final String typeText = fst.type_specifier().getText();
if (separateTextureType(typeText) != null) {
needsSamplerless[0] = true;
} else if (hasReadonly) {
final String asTexture = separateTextureTypeForImage(typeText);
if (asTexture != null) {
needsSamplerless[0] = true;
collectDeclaredNames(single, ctx, readOnlyImages);
final String binding = bindingValue >= 0 ? "layout(binding = " + bindingValue + ") " : "";
edits.add(new Edit(startIdx(fst), stopIdx(fst), binding + "uniform " + asTexture));
if (separateReadOnlyImages && hasUniform && fst.type_specifier() != null) {
final String typeText = fst.type_specifier().getText();
if (separateTextureType(typeText) != null) {
needsSamplerless = true;
} else if (hasReadonly) {
final String asTexture = separateTextureTypeForImage(typeText);
if (asTexture != null) {
needsSamplerless = true;
for (GLSLParser.Typeless_declarationContext td : declarators) {
if (td.IDENTIFIER() != null) readOnlyImages.add(td.IDENTIFIER().getText());
}
}
}
if (isVertex && hasIn) {
if (hasLocation && locValue >= 0) {
if (locValue > maxExplicitLoc[0]) maxExplicitLoc[0] = locValue;
} else if (!hasLocation) {
unlocatedVsInputStarts.add(ctx.getStart().getStartIndex());
final String binding = bindingValue >= 0 ? "layout(binding = " + bindingValue + ") " : "";
edits.add(new Edit(startIdx(fst), stopIdx(fst), binding + "uniform " + asTexture));
}
}
}
@Override
public void enterPostfix_expression(GLSLParser.Postfix_expressionContext ctx) {
if (readOnlyImages.isEmpty() || ctx.LEFT_PAREN() == null || ctx.RIGHT_PAREN() == null) return;
final GLSLParser.Function_call_parametersContext params = ctx.function_call_parameters();
if (params == null || params.assignment_expression() == null || params.assignment_expression().isEmpty()) return;
if (!readOnlyImages.contains(params.assignment_expression(0).getText())) return;
final ParseTree callee = ctx.getChild(0);
if (!(callee instanceof ParserRuleContext calleeCtx)) return;
final String replacement = switch (calleeCtx.getText()) {
case "imageLoad" -> "texelFetch";
case "imageSize" -> "textureSize";
default -> null;
};
if (replacement == null) return;
edits.add(new Edit(startIdx(calleeCtx), stopIdx(calleeCtx), replacement));
final int rparen = ctx.RIGHT_PAREN().getSymbol().getStartIndex();
edits.add(new Edit(rparen, rparen - 1, ", 0"));
}
@Override
public void visitTerminal(TerminalNode node) {
final Token tok = node.getSymbol();
if (tok.getType() != GLSLLexer.IDENTIFIER) return;
final String text = tok.getText();
switch (text) {
case "sampler" -> edits.add(new Edit(tok.getStartIndex(), tok.getStopIndex(), SAMPLER_RENAMED));
case "gl_VertexID" -> edits.add(new Edit(tok.getStartIndex(), tok.getStopIndex(), "gl_VertexIndex"));
case "gl_InstanceID" -> edits.add(new Edit(tok.getStartIndex(), tok.getStopIndex(), "gl_InstanceIndex"));
default -> {
if (TARGET_RESERVED.contains(text)) edits.add(new Edit(tok.getStartIndex(), tok.getStopIndex(), RESERVED_RENAMED_PREFIX + text));
}
if (isVertex && hasIn) {
if (hasLocation && locValue >= 0) {
if (locValue > maxExplicitLoc) maxExplicitLoc = locValue;
} else if (!hasLocation) {
unlocatedVsInputStarts.add(list.getStart().getStartIndex());
}
}
}, root);
if (needsSamplerless[0] && source != null) {
final int at = versionDirectiveEnd(source);
if (at >= 0) edits.add(new Edit(at, at - 1, "\n" + SAMPLERLESS_EXTENSION));
}
if (maxExplicitLoc[0] >= 0 && !unlocatedVsInputStarts.isEmpty()) {
int next = maxExplicitLoc[0] + 1;
if (!readOnlyImages.isEmpty()) {
rewriteReadOnlyImageCalls(index.callsTo("imageLoad"), "texelFetch", readOnlyImages, edits);
rewriteReadOnlyImageCalls(index.callsTo("imageSize"), "textureSize", readOnlyImages, edits);
}
if (needsSamplerless) {
final int versionEnd = index.directives().versionEnd();
if (versionEnd >= 0) edits.add(new Edit(versionEnd, versionEnd - 1, "\n" + SAMPLERLESS_EXTENSION));
}
rename(index, "sampler", SAMPLER_RENAMED, edits);
rename(index, "gl_VertexID", "gl_VertexIndex", edits);
rename(index, "gl_InstanceID", "gl_InstanceIndex", edits);
for (String reserved : TARGET_RESERVED) rename(index, reserved, RESERVED_RENAMED_PREFIX + reserved, edits);
if (maxExplicitLoc >= 0 && !unlocatedVsInputStarts.isEmpty()) {
int next = maxExplicitLoc + 1;
int dropped = 0;
for (int start : unlocatedVsInputStarts) {
if (next >= MAX_VS_INPUT_LOCATIONS) { dropped++; continue; }
@@ -245,22 +213,65 @@ public final class GlslVulkanPreprocess {
}
}
return new Metadata(bools, explicitInputs, needsSamplerless[0]);
return new Metadata(bools, explicitInputs, needsSamplerless);
}
private static void rename(ShaderIndex index, String name, String replacement, List<Edit> edits) {
for (Token tok : index.occurrences(name)) edits.add(new Edit(tok.getStartIndex(), tok.getStopIndex(), replacement));
}
static @Nullable String renameUserFunctions(String source) {
final GLSLParser.Translation_unitContext root;
try {
root = GlslTransformUtils.parseFullQuiet(source);
} catch (Exception e) {
return null;
}
Object2IntOpenHashMap<String> prototypes = null;
for (GLSLParser.External_declarationContext external : root.external_declaration()) {
final GLSLParser.Function_definitionContext function = external.function_definition();
final GLSLParser.DeclarationContext declaration = function == null ? external.declaration() : null;
final GLSLParser.Function_prototypeContext prototype = function != null ? function.function_prototype() : declaration != null ? declaration.function_prototype() : null;
final TerminalNode identifier = prototype == null ? null : prototype.IDENTIFIER();
if (identifier == null) continue;
final String name = identifier.getText();
if ("main".equals(name)) continue;
if (prototypes == null) prototypes = new Object2IntOpenHashMap<>();
prototypes.addTo(name, 1);
}
if (prototypes == null) return null;
final ShaderIndex index = new ShaderIndex(root, source);
final Set<String> inDirectives = index.directives().identifiers();
final List<Edit> edits = new ArrayList<>();
for (Object2IntMap.Entry<String> prototype : prototypes.object2IntEntrySet()) {
final String name = prototype.getKey();
if (inDirectives.contains(name)) continue;
if (index.occurrences(name).size() != prototype.getIntValue() + index.callsTo(name).size()) continue;
rename(index, name, RESERVED_RENAMED_PREFIX + name, edits);
}
return edits.isEmpty() ? null : applyEdits(source, edits);
}
private static void rewriteReadOnlyImageCalls(List<GLSLParser.Postfix_expressionContext> calls, String replacement, Set<String> readOnlyImages, List<Edit> edits) {
for (GLSLParser.Postfix_expressionContext call : calls) {
if (call.RIGHT_PAREN() == null) continue;
final GLSLParser.Function_call_parametersContext params = call.function_call_parameters();
if (params == null || params.assignment_expression().isEmpty()) continue;
if (!readOnlyImages.contains(params.assignment_expression(0).getText())) continue;
final ParserRuleContext callee = (ParserRuleContext) call.getChild(0);
edits.add(new Edit(startIdx(callee), stopIdx(callee), replacement));
final int rparen = call.RIGHT_PAREN().getSymbol().getStartIndex();
edits.add(new Edit(rparen, rparen - 1, ", 0"));
}
}
public static void clearCache() {
synchronized (CACHE) { CACHE.clear(); }
}
private static void collectDeclaredNames(GLSLParser.Single_declarationContext single, GLSLParser.Init_declarator_listContext ctx, Set<String> out) {
if (single.typeless_declaration() != null && single.typeless_declaration().IDENTIFIER() != null) {
out.add(single.typeless_declaration().IDENTIFIER().getText());
}
for (GLSLParser.Typeless_declarationContext td : ctx.typeless_declaration()) {
if (td.IDENTIFIER() != null) out.add(td.IDENTIFIER().getText());
}
}
private static @Nullable String separateTextureTypeForImage(String typeText) {
final String dim = switch (typeText) {
case "image1D", "iimage1D", "uimage1D" -> "1D";
@@ -281,26 +292,6 @@ public final class GlslVulkanPreprocess {
};
}
private static int versionDirectiveEnd(String source) {
final GLSLPreParser.Translation_unitContext pre;
try {
pre = GlslTransformUtils.parsePreQuiet(source);
} catch (Exception e) {
return -1;
}
final GLSLPreParser.Version_directiveContext ctx = firstVersionDirective(pre);
return ctx == null || ctx.getStop() == null ? -1 : ctx.getStop().getStopIndex() + 1;
}
private static GLSLPreParser.@Nullable Version_directiveContext firstVersionDirective(ParseTree node) {
if (node instanceof GLSLPreParser.Version_directiveContext v) return v;
for (int i = 0; i < node.getChildCount(); i++) {
final GLSLPreParser.Version_directiveContext found = firstVersionDirective(node.getChild(i));
if (found != null) return found;
}
return null;
}
private static void handleDeclarator(GLSLParser.Typeless_declarationContext td, boolean hasUniform, boolean asBool, boolean asInput, Set<String> bools, Set<String> inputs, List<Edit> edits) {
if (td.IDENTIFIER() == null) return;
final String name = td.IDENTIFIER().getText();
@@ -0,0 +1,255 @@
package com.gtnewhorizons.angelica.glsm.shader;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.threading.AngelicaWorkers;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL41;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Saves linked GL programs to disk and loads them back with glProgramBinary, skipping compile and link.
*/
public final class ProgramBinaryCache {
private static final Logger LOGGER = LogManager.getLogger("ProgramBinaryCache");
static final String LAYER = "gl-program";
private static final int PACKS_KEPT = 3;
private static volatile boolean enabled;
private static volatile String packLayer;
private static String driverId;
private static String fullCompileLayer;
private static Set<String> keysInUse;
private static List<CompletableFuture<Boolean>> fullCompileWrites;
private static Object fullCompileGeneration;
private static final AtomicBoolean rejectWarned = new AtomicBoolean();
private static final Object WRITE_LOCK = new Object();
private static final Map<String, Object> compileGenerations = new HashMap<>();
private static int deleteCount;
private ProgramBinaryCache() {}
public static void setEnabled(boolean value) {
enabled = value;
}
public static boolean isEnabled() {
return enabled && packLayer != null && RenderSystem.supportsProgramBinary() && ShaderDiskCache.isEnabled();
}
public static void usePack(String packName) {
if (packName == null) {
packLayer = null;
return;
}
final String layer = LAYER + '/' + folderName(packName);
packLayer = layer;
if (!enabled) return;
ShaderDiskCache.touchLayer(layer);
AngelicaWorkers.run(() -> ShaderDiskCache.keepNewestSubLayers(LAYER, PACKS_KEPT));
}
public static Key key() {
return new Key(ShaderDiskCache.key(packLayer).str(driverId()).b(ShaderManager.isEnabled()));
}
public static void markRetrievable(int program) {
GLStateManager.glProgramParameteri(program, GL41.GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL11.GL_TRUE);
}
public static int load(Key key) {
final ShaderDiskCache.Key diskKey = key.diskKey;
final ShaderDiskCache.Blob blob = ShaderDiskCache.getBlob(diskKey);
if (blob == null) return 0;
final int format;
try {
format = Integer.parseInt(blob.tag());
} catch (NumberFormatException e) {
ShaderDiskCache.remove(diskKey);
return 0;
}
final int program = GLStateManager.glCreateProgram();
final ByteBuffer data = MemoryUtilities.memAlloc(blob.data().length);
try {
data.put(blob.data()).flip();
GLStateManager.glProgramBinary(program, format, data);
} finally {
MemoryUtilities.memFree(data);
}
if (GLStateManager.glGetProgrami(program, GL20.GL_LINK_STATUS) != GL11.GL_TRUE) {
GLStateManager.glDeleteProgram(program);
ShaderDiskCache.remove(diskKey);
if (rejectWarned.compareAndSet(false, true)) LOGGER.error("Driver rejected a saved shader program!! Recompiling rejected programs from source");
return 0;
}
keep(diskKey);
return program;
}
public static void save(Key key, int program) {
if (!saveBinary(key, program) && fullCompileWrites != null) {
fullCompileWrites.add(CompletableFuture.completedFuture(false));
}
}
private static boolean saveBinary(Key key, int program) {
final int length = GLStateManager.glGetProgrami(program, GL41.GL_PROGRAM_BINARY_LENGTH);
if (length <= 0) return false;
final ByteBuffer data = MemoryUtilities.memAlloc(length);
final IntBuffer written = MemoryUtilities.memAllocInt(1);
final IntBuffer format = MemoryUtilities.memAllocInt(1);
try {
GLStateManager.glGetProgramBinary(program, written, format, data);
if (written.get(0) <= 0) return false;
data.limit(written.get(0));
final byte[] binary = ShaderCacheIO.toHeap(data);
final String formatTag = Integer.toString(format.get(0));
final ShaderDiskCache.Key diskKey = key.diskKey;
keep(diskKey);
final int deletesBefore;
synchronized (WRITE_LOCK) {
deletesBefore = deleteCount;
}
final CompletableFuture<Boolean> write = AngelicaWorkers.submit(() -> {
synchronized (WRITE_LOCK) {
return deletesBefore == deleteCount && ShaderDiskCache.putBlob(diskKey, formatTag, binary);
}
});
final List<CompletableFuture<Boolean>> writes = fullCompileWrites;
if (writes != null) writes.add(write);
return true;
} finally {
MemoryUtilities.memFree(data);
MemoryUtilities.memFree(written);
MemoryUtilities.memFree(format);
}
}
public static boolean isFullCompileRunning() {
return keysInUse != null;
}
public static boolean saveRetained(Key key, int program) {
if (ShaderDiskCache.getBlob(key.diskKey) != null) {
keep(key.diskKey);
return true;
}
return saveBinary(key, program);
}
public static void beginFullCompile() {
fullCompileLayer = packLayer;
keysInUse = new HashSet<>();
fullCompileWrites = new ArrayList<>();
synchronized (WRITE_LOCK) {
fullCompileGeneration = new Object();
if (fullCompileLayer != null) compileGenerations.put(fullCompileLayer, fullCompileGeneration);
}
}
public static void finishFullCompile(@Nullable String completedSettings) {
final String layer = fullCompileLayer;
final Set<String> used = keysInUse;
final List<CompletableFuture<Boolean>> writes = fullCompileWrites;
final Object generation = fullCompileGeneration;
fullCompileLayer = null;
keysInUse = null;
fullCompileWrites = null;
fullCompileGeneration = null;
if (layer == null || used == null || writes == null) return;
final ShaderDiskCache.Key marker = completedSettings != null ? compiledSettingsKey(layer, completedSettings) : null;
final String markerHex = marker != null ? marker.hex() : null;
CompletableFuture.allOf(writes.toArray(new CompletableFuture<?>[0])).whenComplete((ignored, failure) -> {
synchronized (WRITE_LOCK) {
if (compileGenerations.get(layer) != generation) return;
if (marker == null || failure != null) return;
for (CompletableFuture<Boolean> write : writes) {
if (!write.join()) return;
}
ShaderDiskCache.retainLayer(layer, used);
ShaderDiskCache.retainLayer(layer + "/settings", Collections.singleton(markerHex));
ShaderDiskCache.putString(marker, "");
}
});
}
public static void deleteAll() {
synchronized (WRITE_LOCK) {
deleteCount++;
compileGenerations.clear();
ShaderDiskCache.deleteLayer(LAYER);
}
}
public static boolean wasCompiledFor(String settings) {
final String layer = packLayer;
return layer != null && ShaderDiskCache.getString(compiledSettingsKey(layer, settings)) != null;
}
private static ShaderDiskCache.Key compiledSettingsKey(String layer, String settings) {
return ShaderDiskCache.key(layer + "/settings").str(driverId()).b(ShaderManager.isEnabled()).str(settings);
}
private static String folderName(String packName) {
final StringBuilder name = new StringBuilder(Math.min(packName.length(), 64));
for (int i = 0; i < packName.length() && name.length() < 64; i++) {
final char c = packName.charAt(i);
name.append(c < 128 && (Character.isLetterOrDigit(c) || c == '.' || c == '-' || c == '_') ? c : '_');
}
return name.toString();
}
private static void keep(ShaderDiskCache.Key diskKey) {
final String hex = diskKey.hex();
final Set<String> used = keysInUse;
if (used != null) used.add(hex);
}
private static String driverId() {
String id = driverId;
if (id == null) {
id = RENDER_BACKEND.getString(GL11.GL_VENDOR) + '|' + RENDER_BACKEND.getString(GL11.GL_RENDERER) + '|'
+ RENDER_BACKEND.getString(GL11.GL_VERSION) + '|' + RENDER_BACKEND.getString(GL20.GL_SHADING_LANGUAGE_VERSION) + '|'
+ RenderSystem.isGLES() + '|' + RENDER_BACKEND.getMinGLSLVersion();
driverId = id;
}
return id;
}
public static final class Key {
private final ShaderDiskCache.Key diskKey;
private Key(ShaderDiskCache.Key diskKey) {
this.diskKey = diskKey;
}
public Key stage(int shaderType, String source) {
diskKey.str("stage").i(shaderType).str(source);
return this;
}
public Key attribute(String name, int index) {
diskKey.str("attribute").str(name).i(index);
return this;
}
}
}
@@ -19,14 +19,18 @@ import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;
@@ -49,7 +53,10 @@ public final class ShaderDiskCache {
private ShaderDiskCache() {}
public static void configure(Path dir, String salt) {
private static String configuredSalt;
public static synchronized void configure(Path dir, String salt) {
if (dir.equals(root) && salt.equals(configuredSalt)) return;
try {
Files.createDirectories(dir);
final Path saltFile = dir.resolve("salt");
@@ -59,6 +66,7 @@ public final class ShaderDiskCache {
Files.write(saltFile, salt.getBytes(StandardCharsets.UTF_8));
}
root = dir;
configuredSalt = salt;
AngelicaWorkers.run(() -> enforceCap(dir, MAX_BYTES));
LOGGER.info("Shader disk cache at {}", dir);
} catch (IOException | RuntimeException e) {
@@ -116,9 +124,9 @@ public final class ShaderDiskCache {
}
}
public static void put(Key key, byte[] payload) {
public static boolean put(Key key, byte[] payload) {
final Path r = root;
if (r == null) return;
if (r == null) return false;
final Path target = key.path(r);
final Path tmp = target.resolveSibling(key.hex() + '.' + TMP_SEQ.getAndIncrement() + ".tmp");
try {
@@ -129,11 +137,13 @@ public final class ShaderDiskCache {
write(tmp, payload);
}
Files.move(tmp, target, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
return true;
} catch (IOException e) {
try {
Files.deleteIfExists(tmp);
} catch (IOException ignored) {}
if (writeWarned.compareAndSet(false, true)) LOGGER.warn("Shader disk cache write failed for {}", target, e);
return false;
}
}
@@ -202,7 +212,7 @@ public final class ShaderDiskCache {
}
}
public static void putBlob(Key key, String tag, byte[] data) {
public static boolean putBlob(Key key, String tag, byte[] data) {
final ByteArrayOutputStream bytes = new ByteArrayOutputStream(data.length + 64);
try (DataOutputStream out = new DataOutputStream(bytes)) {
writeStr(out, tag);
@@ -210,11 +220,101 @@ public final class ShaderDiskCache {
} catch (IOException e) {
throw new UncheckedIOException(e);
}
put(key, bytes.toByteArray());
return put(key, bytes.toByteArray());
}
public record Blob(String tag, byte[] data) {}
public static void remove(Key key) {
final Path r = root;
if (r == null) return;
try {
Files.deleteIfExists(key.path(r));
} catch (IOException ignored) {}
}
public static void retainLayer(String layer, Set<String> keep) {
final Path r = root;
if (r == null) return;
final Path dir = r.resolve(layer);
if (!Files.isDirectory(dir)) return;
final List<Path> files = new ArrayList<>();
try (Stream<Path> list = Files.list(dir)) {
list.forEach(files::add);
} catch (IOException e) {
LOGGER.warn("Shader disk cache could not list {}", dir, e);
return;
}
for (Path p : files) {
final String name = p.getFileName().toString();
if (!name.endsWith(".bin")) continue; // in-flight writes are still .tmp
if (keep != null && keep.contains(name.substring(0, name.length() - 4))) continue;
try {
Files.deleteIfExists(p);
} catch (IOException ignored) {}
}
}
public static void touchLayer(String layer) {
final Path r = root;
if (r == null) return;
final Path dir = r.resolve(layer);
try {
Files.createDirectories(dir);
Files.setLastModifiedTime(dir, FileTime.fromMillis(System.currentTimeMillis()));
} catch (IOException e) {
LOGGER.warn("Shader disk cache could not mark {} as used", dir, e);
}
}
public static void keepNewestSubLayers(String layer, int keep) {
final Path r = root;
if (r == null) return;
final Path dir = r.resolve(layer);
if (!Files.isDirectory(dir)) return;
final List<Path> subLayers = new ArrayList<>();
try (Stream<Path> list = Files.list(dir)) {
list.filter(Files::isDirectory).forEach(subLayers::add);
} catch (IOException e) {
LOGGER.warn("Shader disk cache could not list {}", dir, e);
return;
}
subLayers.sort(Comparator.comparingLong(ShaderDiskCache::lastModifiedMillis).reversed());
for (int i = keep; i < subLayers.size(); i++) {
deleteRecursively(subLayers.get(i));
}
}
public static void deleteLayer(String layer) {
final Path r = root;
if (r != null) deleteRecursively(r.resolve(layer));
}
private static long lastModifiedMillis(Path path) {
try {
return Files.getLastModifiedTime(path).toMillis();
} catch (IOException e) {
return 0L;
}
}
private static void deleteRecursively(Path dir) {
if (!Files.exists(dir)) return;
final List<Path> paths = new ArrayList<>();
try (Stream<Path> walk = Files.walk(dir)) {
walk.forEach(paths::add);
} catch (IOException e) {
LOGGER.warn("Shader disk cache could not list {}", dir, e);
return;
}
Collections.reverse(paths);
for (Path p : paths) {
try {
Files.deleteIfExists(p);
} catch (IOException ignored) {}
}
}
static void enforceCap(Path dir, long maxBytes) {
try {
final long[] total = {0};
@@ -0,0 +1,153 @@
package com.gtnewhorizons.angelica.glsm.shader;
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.jetbrains.annotations.Nullable;
import org.taumc.glsl.grammar.GLSLLexer;
import org.taumc.glsl.grammar.GLSLParser;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* What the source passes look up in a parsed shader.
*/
public final class ShaderIndex {
public record Directives(Set<String> identifiers, boolean hasConditionalOrMacro, int versionEnd) {
private static final Directives NONE = new Directives(Set.of(), false, -1);
}
private final GLSLParser.Translation_unitContext root;
private final String source;
private final Map<String, List<Token>> identifiers = new HashMap<>();
private final Map<String, List<GLSLParser.Postfix_expressionContext>> calls = new HashMap<>();
private final List<GLSLParser.DeclarationContext> globalDeclarations = new ArrayList<>();
private @Nullable Directives directives;
public ShaderIndex(GLSLParser.Translation_unitContext root, String source) {
this.root = root;
this.source = source;
collect(root);
for (GLSLParser.External_declarationContext external : root.external_declaration()) {
if (external.declaration() != null) globalDeclarations.add(external.declaration());
}
}
private void collect(ParseTree node) {
if (node instanceof TerminalNode terminal) {
final Token token = terminal.getSymbol();
if (token.getType() == GLSLLexer.IDENTIFIER) identifiers.computeIfAbsent(token.getText(), k -> new ArrayList<>(2)).add(token);
return;
}
if (node instanceof GLSLParser.Postfix_expressionContext p && p.LEFT_PAREN() != null) {
calls.computeIfAbsent(calleeName(p), k -> new ArrayList<>(2)).add(p);
}
for (int i = 0, n = node.getChildCount(); i < n; i++) collect(node.getChild(i));
}
public GLSLParser.Translation_unitContext root() {
return root;
}
public String source() {
return source;
}
public List<Token> occurrences(String name) {
return identifiers.getOrDefault(name, List.of());
}
public List<GLSLParser.Postfix_expressionContext> callsTo(String name) {
return calls.getOrDefault(name, List.of());
}
public List<GLSLParser.DeclarationContext> globalDeclarations() {
return globalDeclarations;
}
/** Lexes the source on first use. */
public Directives directives() {
if (directives == null) directives = scanDirectives(source);
return directives;
}
private static Directives scanDirectives(String source) {
if (source.indexOf('#') < 0) return Directives.NONE;
final GLSLLexer lexer = GlslTransformUtils.quietLexer(source);
final Set<String> names = new HashSet<>();
boolean conditionalOrMacro = false;
int versionEnd = -1;
boolean inVersion = false;
for (Token token = lexer.nextToken(); token.getType() != Token.EOF; token = lexer.nextToken()) {
final int type = token.getType();
if (type == GLSLLexer.VERSION_DIRECTIVE && versionEnd < 0) {
inVersion = true;
versionEnd = token.getStopIndex() + 1;
continue;
}
if (inVersion && (type == GLSLLexer.NUMBER || type == GLSLLexer.PROFILE)) {
versionEnd = token.getStopIndex() + 1;
continue;
}
inVersion = false;
switch (type) {
case GLSLLexer.PROGRAM_TEXT, GLSLLexer.MACRO_TEXT, GLSLLexer.CONSTANT_EXPRESSION -> collectIdentifiers(token.getText(), names);
case GLSLLexer.IF_DIRECTIVE, GLSLLexer.IFDEF_DIRECTIVE, GLSLLexer.IFNDEF_DIRECTIVE, GLSLLexer.DEFINE_DIRECTIVE -> conditionalOrMacro = true;
default -> { }
}
}
return new Directives(names, conditionalOrMacro, versionEnd);
}
private static void collectIdentifiers(String text, Set<String> out) {
if (text == null) return;
final int len = text.length();
int i = 0;
while (i < len) {
final char c = text.charAt(i);
if (c != '_' && !Character.isLetter(c)) {
i++;
continue;
}
int end = i + 1;
while (end < len) {
final char n = text.charAt(end);
if (n != '_' && !Character.isLetterOrDigit(n)) break;
end++;
}
out.add(text.substring(i, end));
i = end;
}
}
public static String calleeName(GLSLParser.Postfix_expressionContext call) {
final ParseTree callee = call.getChild(0);
return callee instanceof ParserRuleContext ? callee.getText() : "";
}
public static List<GLSLParser.Typeless_declarationContext> declarators(GLSLParser.Init_declarator_listContext list) {
final GLSLParser.Single_declarationContext single = list.single_declaration();
final List<GLSLParser.Typeless_declarationContext> rest = list.typeless_declaration();
if (single == null || single.typeless_declaration() == null) return rest;
final List<GLSLParser.Typeless_declarationContext> all = new ArrayList<>(rest.size() + 1);
all.add(single.typeless_declaration());
all.addAll(rest);
return all;
}
public static boolean hasStorageQualifier(@Nullable GLSLParser.Type_qualifierContext qualifier, String keyword) {
if (qualifier == null) return false;
for (GLSLParser.Single_type_qualifierContext q : qualifier.single_type_qualifier()) {
if (q.storage_qualifier() != null && keyword.equals(q.storage_qualifier().getText())) return true;
}
return false;
}
}
@@ -81,7 +81,16 @@ public final class SpirvCompiler {
}
}
final Result result = compileUncached(cleanSrc, shaderKind, debugName, opts);
Result result = compileUncached(cleanSrc, shaderKind, debugName, opts);
if (result.spirv() == null) {
final String renamed = GlslVulkanPreprocess.renameUserFunctions(cleanSrc);
final Result retry = renamed == null ? null : compileUncached(renamed, shaderKind, debugName, opts);
if (retry == null || retry.spirv() == null) {
return new Result(null, result.error(), writeFailureDump(debugName, shaderKind, result.error(), cleanSrc));
}
LOGGER.info("'{}' compiled after renaming its functions; shaderc rejected the original: {}", debugName, result.error());
result = retry;
}
final ByteBuffer spirv = result.spirv();
if (spirv != null) {
final byte[] heap = ShaderCacheIO.toHeap(spirv);
@@ -121,8 +130,7 @@ public final class SpirvCompiler {
final int status = Shaderc.shaderc_result_get_compilation_status(result);
if (status != Shaderc.shaderc_compilation_status_success) {
final String msg = Shaderc.shaderc_result_get_error_message(result);
final Path dumpPath = writeFailureDump(debugName, shaderKind, msg, cleanSrc);
return new Result(null, msg == null ? "(no error message)" : msg, dumpPath);
return new Result(null, msg == null ? "(no error message)" : msg, null);
}
final ByteBuffer spirv = Shaderc.shaderc_result_get_bytes(result);
if (spirv == null || spirv.remaining() == 0) {
@@ -47,14 +47,6 @@ public final class SpirvShaderTranslator {
private SpirvShaderTranslator() {}
public static @Nullable String glslToGlslEs(String source, int glShaderType, String debugName) {
return glslToGlslEsImpl(source, glShaderType, debugName, false);
}
static @Nullable String glslToGlslEsRawForTest(String source, int glShaderType, String debugName) {
return glslToGlslEsImpl(source, glShaderType, debugName, true);
}
private static @Nullable String glslToGlslEsImpl(String source, int glShaderType, String debugName, boolean rawOutput) {
final int shaderKind = shaderKindForGlType(glShaderType);
if (shaderKind < 0) {
LOGGER.warn("Unknown GL shader type 0x{} for '{}' - skipping SPIR-V translation", Integer.toHexString(glShaderType), debugName);
@@ -67,14 +59,13 @@ public final class SpirvShaderTranslator {
final SpirvCompiler.Result cr = SpirvCompiler.compile(pre.rewrittenSource(), shaderKind, debugName, SpirvCompiler.Options.vulkanRelaxed());
if (cr.spirv() == null) {
final String firstErr = cr.error() == null ? "(no error)" : cr.error().split("\\R", 2)[0];
LOGGER.error("shaderc failed for '{}' (kind=0x{}): {} (dumped to {})",
debugName, Integer.toHexString(shaderKind), firstErr, cr.dumpPath() == null ? "<dump failed>" : cr.dumpPath());
LOGGER.error("shaderc failed for '{}' (kind=0x{}): {} (dumped to {})", debugName, Integer.toHexString(shaderKind), firstErr, cr.dumpPath() == null ? "<dump failed>" : cr.dumpPath());
return null;
}
try {
final String emitted = emitGlslEs(cr.spirv(), debugName, glShaderType, pre.explicitVsInputs());
if (emitted == null) return null;
return rawOutput ? emitted : postProcessEsOutput(emitted, pre.boolUniforms());
return postProcessEsOutput(emitted, pre.boolUniforms());
} finally {
memFree(cr.spirv());
}
@@ -163,16 +154,7 @@ public final class SpirvShaderTranslator {
final GLSLParser.Single_declarationContext single = ctx.single_declaration();
if (single == null || single.typeless_declaration() == null) return;
final GLSLParser.Fully_specified_typeContext fst = single.fully_specified_type();
if (fst == null || fst.type_qualifier() == null) return;
boolean isUniform = false;
for (GLSLParser.Single_type_qualifierContext stq : fst.type_qualifier().single_type_qualifier()) {
if (stq.storage_qualifier() != null && "uniform".equals(stq.storage_qualifier().getText())) {
isUniform = true;
break;
}
}
if (!isUniform) return;
if (fst == null || !ShaderIndex.hasStorageQualifier(fst.type_qualifier(), "uniform")) return;
if (fst.type_specifier() == null || fst.type_specifier().type_specifier_nonarray() == null) return;
final GLSLParser.Type_specifier_nonarrayContext ts = fst.type_specifier().type_specifier_nonarray();
@@ -14,8 +14,8 @@ public final class AlphaStateStack extends AlphaState implements CowStateStack<A
public AlphaStateStack(int id) {
cow.id = id;
stack = new AlphaState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new AlphaState[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new AlphaState();
}
}
@@ -25,6 +25,11 @@ public final class AlphaStateStack extends AlphaState implements CowStateStack<A
return cow;
}
@Override
public boolean slotChanged(int slot) {
return VanillaStateLayer.isHeld(vanillaLayer) || !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
VanillaStateLayer.capture(vanillaLayer, stack[s].set(this));
@@ -17,8 +17,8 @@ public final class BlendStateStack extends BlendState implements CowStateStack<B
public BlendStateStack(int id) {
cow.id = id;
stack = new BlendState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new BlendState[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new BlendState();
}
}
@@ -28,6 +28,11 @@ public final class BlendStateStack extends BlendState implements CowStateStack<B
return cow;
}
@Override
public boolean slotChanged(int slot) {
return funcUnknown || VanillaStateLayer.isHeld(vanillaLayer) || !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
VanillaStateLayer.capture(vanillaLayer, stack[s].set(this));
@@ -44,7 +44,7 @@ public class BooleanStateStack extends BooleanState implements CowStateStack<Boo
public BooleanStateStack(int glCap, boolean initialState, boolean ffpStateOnly) {
super(glCap, ffpStateOnly);
this.enabled = initialState;
stack = new boolean[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
stack = new boolean[GLStateManager.STATE_SLOTS];
}
@Override
@@ -52,6 +52,11 @@ public class BooleanStateStack extends BooleanState implements CowStateStack<Boo
return cow;
}
@Override
public boolean slotChanged(int slot) {
return stateUnknown || (vanillaLayer != null && vanillaLayer.isOverrideHeld()) || enabled != stack[slot];
}
@Override
public void captureSlot(int slot) {
stack[slot] = vanillaValue();
@@ -10,8 +10,8 @@ public final class Color4Stack extends Color4 implements CowStateStack<Color4Sta
public Color4Stack(int id) {
cow.id = id;
stack = new Color4[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new Color4[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new Color4();
}
}
@@ -33,6 +33,11 @@ public final class Color4Stack extends Color4 implements CowStateStack<Color4Sta
return cow;
}
@Override
public boolean slotChanged(int slot) {
return !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
stack[s].set(this);
@@ -15,8 +15,8 @@ public final class ColorMaskStack extends ColorMask implements CowStateStack<Col
public ColorMaskStack(int id) {
cow.id = id;
stack = new ColorMask[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new ColorMask[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new ColorMask();
}
}
@@ -26,6 +26,11 @@ public final class ColorMaskStack extends ColorMask implements CowStateStack<Col
return cow;
}
@Override
public boolean slotChanged(int slot) {
return VanillaStateLayer.isHeld(vanillaLayer) || !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
VanillaStateLayer.capture(vanillaLayer, stack[s].set(this));
@@ -101,6 +101,36 @@ public final class CowDispatch {
};
}
public static boolean slotChanged(byte kind, CowStateStack<?> s, int slot) {
return switch (kind) {
case BLEND -> ((BlendStateStack) s).slotChanged(slot);
case BOOLEAN -> ((BooleanStateStack) s).slotChanged(slot);
case CLIP_PLANE_BOOLEAN -> ((ClipPlaneBooleanState) s).slotChanged(slot);
case TEXTURE_UNIT_BOOLEAN -> ((TextureUnitBooleanStateStack) s).slotChanged(slot);
case COLOR_MASK -> ((ColorMaskStack) s).slotChanged(slot);
case LIGHT_STATE -> ((LightStateStack) s).slotChanged(slot);
case MATERIAL -> ((MaterialStateStack) s).slotChanged(slot);
case STENCIL -> ((StencilStateStack) s).slotChanged(slot);
case MATRIX_MODE -> ((MatrixModeStack) s).slotChanged(slot);
case LIGHT_MODEL -> ((LightModelStateStack) s).slotChanged(slot);
case ALPHA -> ((AlphaStateStack) s).slotChanged(slot);
case POINT -> ((PointStateStack) s).slotChanged(slot);
case FOG -> ((FogStateStack) s).slotChanged(slot);
case VEC4F -> ((Vec4fStack) s).slotChanged(slot);
case VEC3F -> ((Vec3fStack) s).slotChanged(slot);
case INTEGER -> ((IntegerStateStack) s).slotChanged(slot);
case DEPTH -> ((DepthStateStack) s).slotChanged(slot);
case VIEWPORT -> ((ViewPortStateStack) s).slotChanged(slot);
case POLYGON -> ((PolygonStateStack) s).slotChanged(slot);
case COLOR4 -> ((Color4Stack) s).slotChanged(slot);
case LINE -> ((LineStateStack) s).slotChanged(slot);
case TEXTURE_BINDING -> ((TextureBindingStack) s).slotChanged(slot);
case TEX_ENV -> ((TexEnvState) s).slotChanged(slot);
case SCISSOR -> ((ScissorStateStack) s).slotChanged(slot);
default -> throw new IllegalStateException("Unknown kind: " + kind);
};
}
public static void restoreSlot(byte kind, CowStateStack<?> s, int slot) {
switch (kind) {
case BLEND -> ((BlendStateStack) s).restoreSlot(slot);
@@ -20,6 +20,8 @@ public interface CowStateStack<T> extends IStateStack<T> {
void restoreSlot(int slot);
boolean slotChanged(int slot);
default int stackId() {
return cowDepths().id;
}
@@ -17,8 +17,8 @@ public final class DepthStateStack extends DepthState implements CowStateStack<D
public DepthStateStack(int id) {
cow.id = id;
stack = new DepthState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new DepthState[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new DepthState();
}
}
@@ -28,6 +28,11 @@ public final class DepthStateStack extends DepthState implements CowStateStack<D
return cow;
}
@Override
public boolean slotChanged(int slot) {
return VanillaStateLayer.isHeld(vanillaLayer) || !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
VanillaStateLayer.capture(vanillaLayer, stack[s].set(this));
@@ -10,8 +10,8 @@ public final class FogStateStack extends FogState implements CowStateStack<FogSt
public FogStateStack(int id) {
cow.id = id;
stack = new FogState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new FogState[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new FogState();
}
}
@@ -21,6 +21,11 @@ public final class FogStateStack extends FogState implements CowStateStack<FogSt
return cow;
}
@Override
public boolean slotChanged(int slot) {
return !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
stack[s].set(this);
@@ -11,8 +11,8 @@ public final class IntegerStateStack extends IntegerState implements CowStateSta
public IntegerStateStack(int val, int id) {
cow.id = id;
setValue(val);
stack = new IntegerState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new IntegerState[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new IntegerState();
stack[i].setValue(val);
}
@@ -30,6 +30,11 @@ public final class IntegerStateStack extends IntegerState implements CowStateSta
return cow;
}
@Override
public boolean slotChanged(int slot) {
return !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
stack[s].set(this);
@@ -11,8 +11,8 @@ public final class LightModelStateStack extends LightModelState implements CowSt
public LightModelStateStack(int id) {
super();
cow.id = id;
stack = new LightModelState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new LightModelState[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new LightModelState();
}
}
@@ -22,6 +22,11 @@ public final class LightModelStateStack extends LightModelState implements CowSt
return cow;
}
@Override
public boolean slotChanged(int slot) {
return !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
stack[s].set(this);
@@ -11,8 +11,8 @@ public final class LightStateStack extends LightState implements CowStateStack<L
public LightStateStack(int light, int id) {
super(light);
cow.id = id;
stack = new LightState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new LightState[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new LightState(light);
}
}
@@ -22,6 +22,11 @@ public final class LightStateStack extends LightState implements CowStateStack<L
return cow;
}
@Override
public boolean slotChanged(int slot) {
return !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
stack[s].set(this);
@@ -11,8 +11,8 @@ public final class LineStateStack extends LineState implements CowStateStack<Lin
public LineStateStack(int id) {
cow.id = id;
stack = new LineState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new LineState[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new LineState();
}
}
@@ -22,6 +22,11 @@ public final class LineStateStack extends LineState implements CowStateStack<Lin
return cow;
}
@Override
public boolean slotChanged(int slot) {
return !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
stack[s].set(this);
@@ -11,8 +11,8 @@ public final class MaterialStateStack extends MaterialState implements CowStateS
public MaterialStateStack(int face, int id) {
super(face);
cow.id = id;
stack = new MaterialState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new MaterialState[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new MaterialState(face);
}
}
@@ -22,6 +22,11 @@ public final class MaterialStateStack extends MaterialState implements CowStateS
return cow;
}
@Override
public boolean slotChanged(int slot) {
return !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
stack[s].set(this);
@@ -10,8 +10,8 @@ public final class MatrixModeStack extends MatrixMode implements CowStateStack<M
public MatrixModeStack(int id) {
cow.id = id;
stack = new MatrixMode[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new MatrixMode[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new MatrixMode();
}
}
@@ -21,6 +21,11 @@ public final class MatrixModeStack extends MatrixMode implements CowStateStack<M
return cow;
}
@Override
public boolean slotChanged(int slot) {
return !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
stack[s].set(this);
@@ -11,8 +11,8 @@ public final class PointStateStack extends PointState implements CowStateStack<P
public PointStateStack(int id) {
cow.id = id;
stack = new PointState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new PointState[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new PointState();
}
}
@@ -22,6 +22,11 @@ public final class PointStateStack extends PointState implements CowStateStack<P
return cow;
}
@Override
public boolean slotChanged(int slot) {
return !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
stack[s].set(this);
@@ -11,8 +11,8 @@ public final class PolygonStateStack extends PolygonState implements CowStateSta
public PolygonStateStack(int id) {
cow.id = id;
stack = new PolygonState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new PolygonState[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new PolygonState();
}
}
@@ -22,6 +22,11 @@ public final class PolygonStateStack extends PolygonState implements CowStateSta
return cow;
}
@Override
public boolean slotChanged(int slot) {
return !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
stack[s].set(this);
@@ -0,0 +1,12 @@
package com.gtnewhorizons.angelica.glsm.stacks;
public final class RetainedState {
public int[] ids = new int[64];
public int count;
public int program;
public long programGen;
public void ensureCapacity(int capacity) {
if (ids.length < capacity) ids = new int[capacity];
}
}
@@ -11,8 +11,8 @@ public final class ScissorStateStack extends ScissorState implements CowStateSta
public ScissorStateStack(int id) {
cow.id = id;
stack = new ScissorState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new ScissorState[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new ScissorState();
}
}
@@ -22,6 +22,11 @@ public final class ScissorStateStack extends ScissorState implements CowStateSta
return cow;
}
@Override
public boolean slotChanged(int slot) {
return !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
stack[s].set(this);
@@ -11,8 +11,8 @@ public final class StencilStateStack extends StencilState implements CowStateSta
public StencilStateStack(int id) {
cow.id = id;
stack = new StencilState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new StencilState[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new StencilState();
}
}
@@ -22,6 +22,11 @@ public final class StencilStateStack extends StencilState implements CowStateSta
return cow;
}
@Override
public boolean slotChanged(int slot) {
return !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
stack[s].set(this);
@@ -12,8 +12,8 @@ public final class TextureBindingStack extends TextureBinding implements CowStat
public TextureBindingStack(int id, int unit) {
cow = new CowDepths(StateSet.R_TEXTURE, unit);
cow.id = id;
stack = new TextureBinding[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new TextureBinding[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new TextureBinding();
}
}
@@ -23,6 +23,11 @@ public final class TextureBindingStack extends TextureBinding implements CowStat
return cow;
}
@Override
public boolean slotChanged(int slot) {
return !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
stack[s].set(this);
@@ -15,7 +15,7 @@ public final class Vec3fStack implements CowStateStack<Vec3fStack> {
public Vec3fStack(Vector3f value, int id) {
this.value = value;
cow.id = id;
stack = new float[GLStateManager.MAX_ATTRIB_STACK_DEPTH][3];
stack = new float[GLStateManager.STATE_SLOTS][3];
}
@Override
@@ -23,6 +23,11 @@ public final class Vec3fStack implements CowStateStack<Vec3fStack> {
return cow;
}
@Override
public boolean slotChanged(int slot) {
return value.x != stack[slot][0] || value.y != stack[slot][1] || value.z != stack[slot][2];
}
@Override
public void captureSlot(int s) {
stack[s][0] = value.x;
@@ -15,7 +15,7 @@ public final class Vec4fStack implements CowStateStack<Vec4fStack> {
public Vec4fStack(Vector4f value, int id) {
this.value = value;
cow.id = id;
stack = new float[GLStateManager.MAX_ATTRIB_STACK_DEPTH][4];
stack = new float[GLStateManager.STATE_SLOTS][4];
}
@Override
@@ -23,6 +23,11 @@ public final class Vec4fStack implements CowStateStack<Vec4fStack> {
return cow;
}
@Override
public boolean slotChanged(int slot) {
return value.x != stack[slot][0] || value.y != stack[slot][1] || value.z != stack[slot][2] || value.w != stack[slot][3];
}
@Override
public void captureSlot(int s) {
stack[s][0] = value.x;
@@ -11,8 +11,8 @@ public final class ViewPortStateStack extends ViewportState implements CowStateS
public ViewPortStateStack(int id) {
cow.id = id;
stack = new ViewportState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new ViewportState[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new ViewportState();
}
}
@@ -22,6 +22,11 @@ public final class ViewPortStateStack extends ViewportState implements CowStateS
return cow;
}
@Override
public boolean slotChanged(int slot) {
return !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
stack[s].set(this);
@@ -0,0 +1,57 @@
package com.gtnewhorizons.angelica.glsm.states;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
public record PixelStoreState(
int alignment,
int rowLength,
int skipRows,
int skipPixels,
int imageHeight,
int skipImages,
boolean swapBytes,
boolean lsbFirst
) {
public static final PixelStoreState DEFAULT = new PixelStoreState(4, 0, 0, 0, 0, 0, false, false);
public boolean isDefault() {
return this.equals(DEFAULT);
}
public static boolean isPack(int pname) {
return switch (pname) {
case GL11.GL_PACK_ALIGNMENT, GL11.GL_PACK_ROW_LENGTH, GL11.GL_PACK_SKIP_ROWS, GL11.GL_PACK_SKIP_PIXELS,
GL12.GL_PACK_IMAGE_HEIGHT, GL12.GL_PACK_SKIP_IMAGES, GL11.GL_PACK_SWAP_BYTES, GL11.GL_PACK_LSB_FIRST -> true;
default -> false;
};
}
public PixelStoreState with(int pname, int param) {
return switch (pname) {
case GL11.GL_UNPACK_ALIGNMENT, GL11.GL_PACK_ALIGNMENT -> (param == alignment || (param != 1 && param != 2 && param != 4 && param != 8)) ? this : new PixelStoreState(param, rowLength, skipRows, skipPixels, imageHeight, skipImages, swapBytes, lsbFirst);
case GL11.GL_UNPACK_ROW_LENGTH, GL11.GL_PACK_ROW_LENGTH -> (param == rowLength || param < 0) ? this : new PixelStoreState(alignment, param, skipRows, skipPixels, imageHeight, skipImages, swapBytes, lsbFirst);
case GL11.GL_UNPACK_SKIP_ROWS, GL11.GL_PACK_SKIP_ROWS -> (param == skipRows || param < 0) ? this : new PixelStoreState(alignment, rowLength, param, skipPixels, imageHeight, skipImages, swapBytes, lsbFirst);
case GL11.GL_UNPACK_SKIP_PIXELS, GL11.GL_PACK_SKIP_PIXELS -> (param == skipPixels || param < 0) ? this : new PixelStoreState(alignment, rowLength, skipRows, param, imageHeight, skipImages, swapBytes, lsbFirst);
case GL12.GL_UNPACK_IMAGE_HEIGHT, GL12.GL_PACK_IMAGE_HEIGHT -> (param == imageHeight || param < 0) ? this : new PixelStoreState(alignment, rowLength, skipRows, skipPixels, param, skipImages, swapBytes, lsbFirst);
case GL12.GL_UNPACK_SKIP_IMAGES, GL12.GL_PACK_SKIP_IMAGES -> (param == skipImages || param < 0) ? this : new PixelStoreState(alignment, rowLength, skipRows, skipPixels, imageHeight, param, swapBytes, lsbFirst);
case GL11.GL_UNPACK_SWAP_BYTES, GL11.GL_PACK_SWAP_BYTES -> (param != 0) == swapBytes ? this : new PixelStoreState(alignment, rowLength, skipRows, skipPixels, imageHeight, skipImages, param != 0, lsbFirst);
case GL11.GL_UNPACK_LSB_FIRST, GL11.GL_PACK_LSB_FIRST -> (param != 0) == lsbFirst ? this : new PixelStoreState(alignment, rowLength, skipRows, skipPixels, imageHeight, skipImages, swapBytes, param != 0);
default -> this;
};
}
public static void applyDiff(PixelStoreState from, PixelStoreState to, boolean pack) {
if (from == to) return;
if (from.alignment != to.alignment) RENDER_BACKEND.pixelStorei(pack ? GL11.GL_PACK_ALIGNMENT : GL11.GL_UNPACK_ALIGNMENT, to.alignment);
if (from.rowLength != to.rowLength) RENDER_BACKEND.pixelStorei(pack ? GL11.GL_PACK_ROW_LENGTH : GL11.GL_UNPACK_ROW_LENGTH, to.rowLength);
if (from.skipRows != to.skipRows) RENDER_BACKEND.pixelStorei(pack ? GL11.GL_PACK_SKIP_ROWS : GL11.GL_UNPACK_SKIP_ROWS, to.skipRows);
if (from.skipPixels != to.skipPixels) RENDER_BACKEND.pixelStorei(pack ? GL11.GL_PACK_SKIP_PIXELS : GL11.GL_UNPACK_SKIP_PIXELS, to.skipPixels);
if (from.imageHeight != to.imageHeight) RENDER_BACKEND.pixelStorei(pack ? GL12.GL_PACK_IMAGE_HEIGHT : GL12.GL_UNPACK_IMAGE_HEIGHT, to.imageHeight);
if (from.skipImages != to.skipImages) RENDER_BACKEND.pixelStorei(pack ? GL12.GL_PACK_SKIP_IMAGES : GL12.GL_UNPACK_SKIP_IMAGES, to.skipImages);
if (from.swapBytes != to.swapBytes) RENDER_BACKEND.pixelStorei(pack ? GL11.GL_PACK_SWAP_BYTES : GL11.GL_UNPACK_SWAP_BYTES, to.swapBytes ? 1 : 0);
if (from.lsbFirst != to.lsbFirst) RENDER_BACKEND.pixelStorei(pack ? GL11.GL_PACK_LSB_FIRST : GL11.GL_UNPACK_LSB_FIRST, to.lsbFirst ? 1 : 0);
}
}
@@ -1,51 +0,0 @@
package com.gtnewhorizons.angelica.glsm.states;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Immutable client-side pixel unpack state (glPixelStore UNPACK_* params)
*/
public record PixelUnpackState(
int alignment,
int rowLength,
int skipRows,
int skipPixels,
int imageHeight,
int skipImages,
boolean swapBytes,
boolean lsbFirst
) {
public static final PixelUnpackState DEFAULT = new PixelUnpackState(4, 0, 0, 0, 0, 0, false, false);
public boolean isDefault() {
return this.equals(DEFAULT);
}
public PixelUnpackState with(int pname, int param) {
return switch (pname) {
case GL11.GL_UNPACK_ALIGNMENT -> (param == 1 || param == 2 || param == 4 || param == 8) ? new PixelUnpackState(param, rowLength, skipRows, skipPixels, imageHeight, skipImages, swapBytes, lsbFirst) : this;
case GL11.GL_UNPACK_ROW_LENGTH -> param >= 0 ? new PixelUnpackState(alignment, param, skipRows, skipPixels, imageHeight, skipImages, swapBytes, lsbFirst) : this;
case GL11.GL_UNPACK_SKIP_ROWS -> param >= 0 ? new PixelUnpackState(alignment, rowLength, param, skipPixels, imageHeight, skipImages, swapBytes, lsbFirst) : this;
case GL11.GL_UNPACK_SKIP_PIXELS -> param >= 0 ? new PixelUnpackState(alignment, rowLength, skipRows, param, imageHeight, skipImages, swapBytes, lsbFirst) : this;
case GL12.GL_UNPACK_IMAGE_HEIGHT -> param >= 0 ? new PixelUnpackState(alignment, rowLength, skipRows, skipPixels, param, skipImages, swapBytes, lsbFirst) : this;
case GL12.GL_UNPACK_SKIP_IMAGES -> param >= 0 ? new PixelUnpackState(alignment, rowLength, skipRows, skipPixels, imageHeight, param, swapBytes, lsbFirst) : this;
case GL11.GL_UNPACK_SWAP_BYTES -> new PixelUnpackState(alignment, rowLength, skipRows, skipPixels, imageHeight, skipImages, param != 0, lsbFirst);
case GL11.GL_UNPACK_LSB_FIRST -> new PixelUnpackState(alignment, rowLength, skipRows, skipPixels, imageHeight, skipImages, swapBytes, param != 0);
default -> this;
};
}
public static void applyDiff(PixelUnpackState from, PixelUnpackState to) {
if (from.alignment != to.alignment) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_ALIGNMENT, to.alignment);
if (from.rowLength != to.rowLength) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_ROW_LENGTH, to.rowLength);
if (from.skipRows != to.skipRows) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_SKIP_ROWS, to.skipRows);
if (from.skipPixels != to.skipPixels) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_SKIP_PIXELS, to.skipPixels);
if (from.imageHeight != to.imageHeight) RENDER_BACKEND.pixelStorei(GL12.GL_UNPACK_IMAGE_HEIGHT, to.imageHeight);
if (from.skipImages != to.skipImages) RENDER_BACKEND.pixelStorei(GL12.GL_UNPACK_SKIP_IMAGES, to.skipImages);
if (from.swapBytes != to.swapBytes) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_SWAP_BYTES, to.swapBytes ? 1 : 0);
if (from.lsbFirst != to.lsbFirst) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_LSB_FIRST, to.lsbFirst ? 1 : 0);
}
}
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.glsm.states;
import lombok.Getter;
import lombok.Setter;
import org.lwjgl.opengl.GL20;
/**
* Tracks GL point state: size.
@@ -10,10 +11,24 @@ import lombok.Setter;
@Getter @Setter
public class PointState implements ISettableState<PointState> {
private float size = 1.0f;
private float minSize = 0.0f;
private float maxSize = Float.NaN;
private float fadeThreshold = 1.0f;
private float attenuationConstant = 1.0f;
private float attenuationLinear = 0.0f;
private float attenuationQuadratic = 0.0f;
private int spriteCoordOrigin = GL20.GL_UPPER_LEFT;
@Override
public PointState set(PointState state) {
this.size = state.size;
this.minSize = state.minSize;
this.maxSize = state.maxSize;
this.fadeThreshold = state.fadeThreshold;
this.attenuationConstant = state.attenuationConstant;
this.attenuationLinear = state.attenuationLinear;
this.attenuationQuadratic = state.attenuationQuadratic;
this.spriteCoordOrigin = state.spriteCoordOrigin;
return this;
}
@@ -21,7 +36,14 @@ public class PointState implements ISettableState<PointState> {
public boolean sameAs(Object state) {
if (this == state) return true;
if (!(state instanceof PointState pointState)) return false;
return Float.compare(size, pointState.size) == 0;
return Float.compare(size, pointState.size) == 0
&& Float.compare(minSize, pointState.minSize) == 0
&& Float.compare(maxSize, pointState.maxSize) == 0
&& Float.compare(fadeThreshold, pointState.fadeThreshold) == 0
&& Float.compare(attenuationConstant, pointState.attenuationConstant) == 0
&& Float.compare(attenuationLinear, pointState.attenuationLinear) == 0
&& Float.compare(attenuationQuadratic, pointState.attenuationQuadratic) == 0
&& spriteCoordOrigin == pointState.spriteCoordOrigin;
}
@Override
@@ -6,6 +6,8 @@ import com.gtnewhorizons.angelica.glsm.stacks.CowStateStack;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import java.util.Arrays;
/**
* Per-texture-unit GL_TEXTURE_ENV state for FFP emulation. Tracks the simple texenv mode and all GL_COMBINE sub-parameters.
*/
@@ -45,8 +47,8 @@ public final class TexEnvState implements CowStateStack<TexEnvState> {
stack = null;
cow = null;
} else {
stack = new TexEnvState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack = new TexEnvState[GLStateManager.STATE_SLOTS];
for (int i = 0; i < GLStateManager.STATE_SLOTS; i++) {
stack[i] = new TexEnvState(true);
}
cow = new CowDepths();
@@ -70,6 +72,14 @@ public final class TexEnvState implements CowStateStack<TexEnvState> {
this.envColorA = other.envColorA;
}
private boolean sameAs(TexEnvState other) {
return mode == other.mode && combineRgb == other.combineRgb && combineAlpha == other.combineAlpha
&& Arrays.equals(sourceRgb, other.sourceRgb) && Arrays.equals(sourceAlpha, other.sourceAlpha)
&& Arrays.equals(operandRgb, other.operandRgb) && Arrays.equals(operandAlpha, other.operandAlpha)
&& scaleRgb == other.scaleRgb && scaleAlpha == other.scaleAlpha
&& envColorR == other.envColorR && envColorG == other.envColorG && envColorB == other.envColorB && envColorA == other.envColorA;
}
public void reset() {
mode = GL11.GL_MODULATE;
combineRgb = GL11.GL_MODULATE;
@@ -89,6 +99,11 @@ public final class TexEnvState implements CowStateStack<TexEnvState> {
return cow;
}
@Override
public boolean slotChanged(int slot) {
return !sameAs(stack[slot]);
}
@Override
public void captureSlot(int s) {
stack[s].copyFrom(this);
@@ -9,6 +9,7 @@ import lombok.Getter;
import org.joml.Matrix4fStack;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import java.util.ArrayList;
import java.util.List;
@@ -19,6 +20,7 @@ public class TextureUnitArray {
private final TextureUnitBooleanStateStack[] states;
private final TextureUnitBooleanStateStack[] texture1DStates;
private final TextureUnitBooleanStateStack[] texture3DStates;
private final TextureUnitBooleanStateStack[] textureCubeMapStates;
private final TextureUnitBooleanStateStack[] texGenSStates;
private final TextureUnitBooleanStateStack[] texGenTStates;
private final TextureUnitBooleanStateStack[] texGenRStates;
@@ -36,6 +38,7 @@ public class TextureUnitArray {
states = new TextureUnitBooleanStateStack[GLStateManager.MAX_TEXTURE_UNITS];
texture1DStates = new TextureUnitBooleanStateStack[GLStateManager.MAX_TEXTURE_UNITS];
texture3DStates = new TextureUnitBooleanStateStack[GLStateManager.MAX_TEXTURE_UNITS];
textureCubeMapStates = new TextureUnitBooleanStateStack[GLStateManager.MAX_TEXTURE_UNITS];
texGenSStates = new TextureUnitBooleanStateStack[GLStateManager.MAX_TEXTURE_UNITS];
texGenTStates = new TextureUnitBooleanStateStack[GLStateManager.MAX_TEXTURE_UNITS];
texGenRStates = new TextureUnitBooleanStateStack[GLStateManager.MAX_TEXTURE_UNITS];
@@ -49,6 +52,7 @@ public class TextureUnitArray {
states[i] = track(new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_2D, i));
texture1DStates[i] = track(new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_1D, i));
texture3DStates[i] = track(new TextureUnitBooleanStateStack(GL12.GL_TEXTURE_3D, i));
textureCubeMapStates[i] = track(new TextureUnitBooleanStateStack(GL13.GL_TEXTURE_CUBE_MAP, i));
texGenSStates[i] = track(new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_GEN_S, i));
texGenTStates[i] = track(new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_GEN_T, i));
texGenRStates[i] = track(new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_GEN_R, i));
@@ -90,6 +94,10 @@ public class TextureUnitArray {
return texture3DStates[index];
}
public TextureUnitBooleanStateStack getTextureCubeMapStates(int index) {
return textureCubeMapStates[index];
}
public TextureUnitBooleanStateStack getTexGenSStates(int index) {
return texGenSStates[index];
}
@@ -127,6 +135,7 @@ public class TextureUnitArray {
if (!states[i].sameAs(textureUnitArray.states[i])) return false;
if (!texture1DStates[i].sameAs(textureUnitArray.texture1DStates[i])) return false;
if (!texture3DStates[i].sameAs(textureUnitArray.texture3DStates[i])) return false;
if (!textureCubeMapStates[i].sameAs(textureUnitArray.textureCubeMapStates[i])) return false;
if (!texGenSStates[i].sameAs(textureUnitArray.texGenSStates[i])) return false;
if (!texGenTStates[i].sameAs(textureUnitArray.texGenTStates[i])) return false;
if (!texGenRStates[i].sameAs(textureUnitArray.texGenRStates[i])) return false;
@@ -0,0 +1,154 @@
package com.gtnewhorizons.angelica.glsm.streaming;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.GL11;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCopy;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetFloat;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat;
/**
* Rewrites line primitives as quads for shaders that widen lines in the vertex stage.
*/
public final class LineQuads {
public static final int VERTICES_PER_SEGMENT = 6;
private static final VertexFormatElement NORMAL_SLOT = new VertexFormatElement(0, VertexFormatElement.Type.FLOAT,
VertexFormatElement.Usage.NORMAL, 3, VertexFlags.NORMAL_BIT, DefaultVertexFormat.NORMAL_ELEMENT.getWriter());
private static final VertexFormat[] QUAD_FORMATS = new VertexFormat[VertexFlags.BITSET_SIZE];
private static final int[][] SOURCE_OFFSETS = new int[VertexFlags.BITSET_SIZE][];
static {
for (int flags = 0; flags < QUAD_FORMATS.length; flags++) {
if ((flags & VertexFlags.NORMAL_BIT) == 0) continue;
final VertexFormatElement[] elements = DefaultVertexFormat.ALL_FORMATS[flags].elementsArray.clone();
for (int i = 0; i < elements.length; i++) {
if (elements[i] == DefaultVertexFormat.NORMAL_ELEMENT) elements[i] = NORMAL_SLOT;
}
QUAD_FORMATS[flags] = new VertexFormat(elements);
}
for (int flags = 0; flags < SOURCE_OFFSETS.length; flags++) {
final VertexFormatElement[] quadElements = QUAD_FORMATS[flags | VertexFlags.NORMAL_BIT].elementsArray;
final int[] offsets = new int[quadElements.length];
for (int i = 0; i < quadElements.length; i++) {
offsets[i] = offsetOf(DefaultVertexFormat.ALL_FORMATS[flags], quadElements[i]);
}
SOURCE_OFFSETS[flags] = offsets;
}
}
private LineQuads() {}
public static boolean isLineMode(int drawMode) {
return drawMode == GL11.GL_LINES || drawMode == GL11.GL_LINE_STRIP || drawMode == GL11.GL_LINE_LOOP;
}
public static int segmentCount(int drawMode, int vertexCount) {
return switch (drawMode) {
case GL11.GL_LINES -> vertexCount / 2;
case GL11.GL_LINE_STRIP -> Math.max(0, vertexCount - 1);
case GL11.GL_LINE_LOOP -> vertexCount < 2 ? 0 : vertexCount;
default -> 0;
};
}
public static boolean disableCulling() {
final boolean wasEnabled = GLStateManager.getCullState().isEnabled();
if (wasEnabled) {
GLStateManager.disableCull();
}
return wasEnabled;
}
public static void restoreCulling(boolean wasEnabled) {
if (wasEnabled) {
GLStateManager.enableCull();
}
}
public static VertexFormat quadFormat(VertexFormat lineFormat) {
return QUAD_FORMATS[lineFormat.getVertexFlags() | VertexFlags.NORMAL_BIT];
}
public static boolean isQuadFormat(VertexFormat format) {
return QUAD_FORMATS[format.getVertexFlags()] == format;
}
public static void expandWithDirection(long src, VertexFormat srcFormat, int drawMode, int vertexCount, long dst) {
expand(src, srcFormat, drawMode, vertexCount, dst, false);
}
public static void expandWithOtherEnd(long src, VertexFormat srcFormat, int drawMode, int vertexCount, long dst) {
expand(src, srcFormat, drawMode, vertexCount, dst, true);
}
private static void expand(long src, VertexFormat srcFormat, int drawMode, int vertexCount, long dst, boolean otherEnd) {
final VertexFormatElement[] dstElements = quadFormat(srcFormat).elementsArray;
final int[] srcOffsets = SOURCE_OFFSETS[srcFormat.getVertexFlags()];
final int srcStride = srcFormat.getVertexSize();
final int segments = segmentCount(drawMode, vertexCount);
long out = dst;
for (int s = 0; s < segments; s++) {
final int a = drawMode == GL11.GL_LINES ? s * 2 : s;
final int b = drawMode == GL11.GL_LINE_LOOP && s == vertexCount - 1 ? 0 : a + 1;
final long srcA = src + (long) a * srcStride;
final long srcB = src + (long) b * srcStride;
// What A's and B's corners carry in the normal slot
final float ax, ay, az, bx, by, bz;
if (otherEnd) {
ax = memGetFloat(srcB); ay = memGetFloat(srcB + 4); az = memGetFloat(srcB + 8);
bx = memGetFloat(srcA); by = memGetFloat(srcA + 4); bz = memGetFloat(srcA + 8);
} else {
final float x = memGetFloat(srcB) - memGetFloat(srcA);
final float y = memGetFloat(srcB + 4) - memGetFloat(srcA + 4);
final float z = memGetFloat(srcB + 8) - memGetFloat(srcA + 8);
final float lengthSquared = x * x + y * y + z * z;
final float scale = lengthSquared == 0.0f ? 0.0f : 1.0f / (float) Math.sqrt(lengthSquared);
ax = bx = x * scale;
ay = by = y * scale;
az = bz = z * scale;
}
out = writeVertex(srcA, out, dstElements, srcOffsets, ax, ay, az);
out = writeVertex(srcA, out, dstElements, srcOffsets, ax, ay, az);
out = writeVertex(srcB, out, dstElements, srcOffsets, bx, by, bz);
out = writeVertex(srcA, out, dstElements, srcOffsets, ax, ay, az);
out = writeVertex(srcB, out, dstElements, srcOffsets, bx, by, bz);
out = writeVertex(srcB, out, dstElements, srcOffsets, bx, by, bz);
}
}
private static long writeVertex(long src, long dst, VertexFormatElement[] dstElements, int[] srcOffsets, float x, float y, float z) {
long out = dst;
for (int i = 0; i < dstElements.length; i++) {
final VertexFormatElement element = dstElements[i];
final int size = element.getByteSize();
if (element == NORMAL_SLOT) {
memPutFloat(out, x);
memPutFloat(out + 4, y);
memPutFloat(out + 8, z);
} else {
memCopy(src + srcOffsets[i], out, size);
}
out += size;
}
return out;
}
private static int offsetOf(VertexFormat format, VertexFormatElement element) {
int offset = 0;
for (VertexFormatElement candidate : format.elementsArray) {
if (candidate == element) {
return offset;
}
offset += candidate.getByteSize();
}
return -1;
}
}
@@ -10,4 +10,7 @@ public final class StreamingVaos {
final int[] extendedPersistentVAOs = new int[FORMAT_COUNT];
final int[] extendedOrphanVAOs = new int[FORMAT_COUNT];
final int[] lineQuadPersistentVAOs = new int[FORMAT_COUNT];
final int[] lineQuadOrphanVAOs = new int[FORMAT_COUNT];
}
@@ -15,6 +15,7 @@ import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
import com.gtnewhorizons.angelica.glsm.hooks.LineDrawHandler;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import net.minecraft.client.renderer.Tessellator;
import org.apache.logging.log4j.LogManager;
@@ -56,6 +57,10 @@ public class TessellatorStreamingDrawer {
private static long repackAddress;
private static int repackCapacity;
private static ByteBuffer lineQuadBuffer;
private static long lineQuadAddress;
private static int lineQuadCapacity;
private static boolean initialized = false;
// Tracy profiling counters
@@ -315,12 +320,18 @@ public class TessellatorStreamingDrawer {
* Tries the persistent ring buffer first, falls back to orphan buffer on overflow.
*/
private static void uploadAndDraw(ByteBuffer packed, int flags, VertexFormat format, int vertexSize, int drawMode, int vertexCount) {
if (LineQuads.isLineMode(drawMode) && drawLinesAsQuads(packed, format, drawMode, vertexCount)) {
return;
}
Tracy.beginZone(Z_SDL_STREAM_DRAW);
final boolean locked = GLStateManager.acquireDrawLock();
try {
final GLContextState glCtx = GLStateManager.ctx();
final StreamingVaos vaos = glCtx.streamingVaos;
ensureVAO(vaos, flags, format);
final boolean lineQuads = LineQuads.isQuadFormat(format);
final int[] persistentVAOs = lineQuads ? vaos.lineQuadPersistentVAOs : vaos.persistentVAOs;
final int[] orphanVAOs = lineQuads ? vaos.lineQuadOrphanVAOs : vaos.orphanVAOs;
ensureVAO(persistentVAOs, orphanVAOs, flags, format);
if (Tracy.ENABLED) {
streamedBytes += packed.remaining();
@@ -330,15 +341,16 @@ public class TessellatorStreamingDrawer {
int firstVertex = -1;
if (persistentBuffer != null) {
firstVertex = persistentBuffer.upload(packed, vertexSize, drawMode == GL11.GL_QUADS ? 4 : 1);
final int alignment = drawMode == GL11.GL_QUADS ? 4 : lineQuads ? LineQuads.VERTICES_PER_SEGMENT : 1;
firstVertex = persistentBuffer.upload(packed, vertexSize, alignment);
}
final boolean fromRing = firstVertex >= 0;
if (fromRing) {
GLStateManager.glBindVertexArray(vaos.persistentVAOs[flags]);
GLStateManager.glBindVertexArray(persistentVAOs[flags]);
} else {
if (Tracy.ENABLED && persistentBuffer != null) orphanFallbacks++;
GLStateManager.glBindVertexArray(vaos.orphanVAOs[flags]);
GLStateManager.glBindVertexArray(orphanVAOs[flags]);
orphanBuffers[flags].upload(packed);
firstVertex = 0;
}
@@ -357,6 +369,72 @@ public class TessellatorStreamingDrawer {
}
}
private static boolean drawLinesAsQuads(ByteBuffer packed, VertexFormat format, int drawMode, int vertexCount) {
final int segments = LineQuads.segmentCount(drawMode, vertexCount);
if (segments == 0) {
return false;
}
final LineDrawHandler handler = GLSMHooks.lineDrawHandler;
if (handler != null && handler.bindLineProgram()) {
try {
drawLineQuads(packed, format, drawMode, vertexCount, segments, false);
} finally {
handler.restoreProgram();
}
return true;
}
if (GLStateManager.ffpWidensLineQuads()) {
final GLContextState glCtx = GLStateManager.ctx();
glCtx.lineQuadsActive = true;
try {
drawLineQuads(packed, format, drawMode, vertexCount, segments, true);
} finally {
glCtx.lineQuadsActive = false;
}
return true;
}
return false;
}
private static void drawLineQuads(ByteBuffer packed, VertexFormat format, int drawMode, int vertexCount, int segments, boolean otherEnd) {
final VertexFormat quadFormat = LineQuads.quadFormat(format);
final int quadVertices = segments * LineQuads.VERTICES_PER_SEGMENT;
final int quadBytes = quadVertices * quadFormat.getVertexSize();
ensureLineQuadCapacity(quadBytes);
final long src = memAddress0(packed) + packed.position();
if (otherEnd) {
LineQuads.expandWithOtherEnd(src, format, drawMode, vertexCount, lineQuadAddress);
} else {
LineQuads.expandWithDirection(src, format, drawMode, vertexCount, lineQuadAddress);
}
lineQuadBuffer.position(0);
lineQuadBuffer.limit(quadBytes);
final boolean culled = LineQuads.disableCulling();
try {
uploadAndDraw(lineQuadBuffer, quadFormat.getVertexFlags(), quadFormat, quadFormat.getVertexSize(), GL11.GL_TRIANGLES, quadVertices);
} finally {
LineQuads.restoreCulling(culled);
}
if (lineQuadCapacity > 0x20000 && quadBytes < (lineQuadCapacity >> 3)) {
memFree(lineQuadBuffer);
lineQuadBuffer = null;
lineQuadCapacity = 0;
}
}
private static void ensureLineQuadCapacity(int requiredBytes) {
if (lineQuadBuffer != null && requiredBytes <= lineQuadCapacity) return;
int newCapacity = Math.max(0x4000, lineQuadCapacity);
while (newCapacity < requiredBytes) {
newCapacity *= 2;
}
if (lineQuadBuffer != null) memFree(lineQuadBuffer);
lineQuadBuffer = memAlloc(newCapacity);
lineQuadAddress = memAddress0(lineQuadBuffer);
lineQuadCapacity = newCapacity;
}
public static long ringWraps() {
return persistentBuffer == null ? 0L : persistentBuffer.getWraps();
}
@@ -377,7 +455,7 @@ public class TessellatorStreamingDrawer {
private static void uploadAndDrawExtended(ByteBuffer combined, int flags, VertexFormat format, int combinedStride, int drawMode, int vertexCount) {
final GLContextState glCtx = GLStateManager.ctx();
final StreamingVaos vaos = glCtx.streamingVaos;
ensureVAO(vaos, flags, format);
ensureVAO(vaos.persistentVAOs, vaos.orphanVAOs, flags, format);
ensureExtendedVAOs(vaos, flags, format);
int firstVertex = -1;
@@ -480,11 +558,9 @@ public class TessellatorStreamingDrawer {
return repackBuffer;
}
private static void ensureVAO(StreamingVaos vaos, int flags, VertexFormat format) {
private static void ensureVAO(int[] persistentVAOs, int[] orphanVAOs, int flags, VertexFormat format) {
init();
if (orphanBuffers[flags] == null) orphanBuffers[flags] = new OrphanStreamingBuffer();
final int[] orphanVAOs = vaos.orphanVAOs;
final int[] persistentVAOs = vaos.persistentVAOs;
if (orphanVAOs[flags] == 0) {
orphanVAOs[flags] = GLStateManager.glGenVertexArrays();
@@ -8,6 +8,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
@@ -16,6 +17,7 @@ import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
public final class AngelicaWorkers {
@@ -153,6 +155,51 @@ public final class AngelicaWorkers {
}
}
/**
* Runs every task and returns the results in task order.
*/
public static <T> List<T> invokeAll(List<? extends Supplier<? extends T>> tasks) {
final int count = tasks.size();
final Object[] results = new Object[count];
if (count == 0) return new ArrayList<>();
final AtomicInteger next = new AtomicInteger();
final CountDownLatch finished = new CountDownLatch(count);
final AtomicReference<Throwable> failure = new AtomicReference<>();
final Runnable drain = () -> {
int i;
while ((i = next.getAndIncrement()) < count) {
try {
results[i] = tasks.get(i).get();
} catch (Throwable t) {
failure.compareAndSet(null, t);
} finally {
finished.countDown();
}
}
};
final int helpers = Math.min(threads(), count - 1);
for (int h = 0; h < helpers; h++) {
run(drain);
}
drain.run();
try {
finished.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException("Interrupted waiting for worker tasks", e);
}
final Throwable t = failure.get();
if (t instanceof RuntimeException re) throw re;
if (t instanceof Error err) throw err;
if (t != null) throw new IllegalStateException(t);
final List<T> out = new ArrayList<>(count);
for (Object result : results) {
@SuppressWarnings("unchecked") final T typed = (T) result;
out.add(typed);
}
return out;
}
private static void checkIdleShutdown() {
synchronized (lock) {
final ExecutorService current = executor;
@@ -2,7 +2,6 @@ package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.Test;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import java.nio.FloatBuffer;
@@ -14,29 +13,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
@GLCoreTest
class ArrayUniformUploadGLTest {
private static int compile(int type, String src) {
final int s = GL20.glCreateShader(type);
GL20.glShaderSource(s, src);
GL20.glCompileShader(s);
assertEquals(GL11.GL_TRUE, GL20.glGetShaderi(s, GL20.GL_COMPILE_STATUS), () -> "shader compile failed: " + GL20.glGetShaderInfoLog(s, 4096));
return s;
}
private static int link(String vs, String fs) {
final int v = compile(GL20.GL_VERTEX_SHADER, vs);
final int f = compile(GL20.GL_FRAGMENT_SHADER, fs);
final int p = GL20.glCreateProgram();
GL20.glAttachShader(p, v);
GL20.glAttachShader(p, f);
GL20.glLinkProgram(p);
assertEquals(GL11.GL_TRUE, GL20.glGetProgrami(p, GL20.GL_LINK_STATUS), () -> "link failed: " + GL20.glGetProgramInfoLog(p, 4096));
GL20.glDeleteShader(v);
GL20.glDeleteShader(f);
return p;
}
private static final String VS = "#version 330 core\nvoid main(){ gl_Position = vec4(0.0); }\n";
@Test
void glUniform2FloatBufferUploadsWholeArray() {
// Runtime index keeps the whole array active so element locations stay queryable.
@@ -45,7 +21,7 @@ class ArrayUniformUploadGLTest {
+ "uniform int idx;\n"
+ "out vec4 o;\n"
+ "void main(){ o = vec4(cosmicuvs[idx], cosmicuvs[(idx+1)%10]); }\n";
final int p = link(VS, fs);
final int p = GlTestSupport.program(GlTestSupport.TRIVIAL_VS, fs);
try {
GL20.glUseProgram(p);
@@ -66,7 +42,7 @@ class ArrayUniformUploadGLTest {
}
} finally {
GL20.glUseProgram(0);
GL20.glDeleteProgram(p);
GLStateManager.glDeleteProgram(p);
}
}
@@ -77,7 +53,7 @@ class ArrayUniformUploadGLTest {
+ "uniform int idx;\n"
+ "out vec4 o;\n"
+ "void main(){ o = vec4(float(vals[idx])); }\n";
final int p = link(VS, fs);
final int p = GlTestSupport.program(GlTestSupport.TRIVIAL_VS, fs);
try {
GL20.glUseProgram(p);
@@ -96,7 +72,7 @@ class ArrayUniformUploadGLTest {
}
} finally {
GL20.glUseProgram(0);
GL20.glDeleteProgram(p);
GLStateManager.glDeleteProgram(p);
}
}
}
@@ -5,30 +5,24 @@ import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class CaptureGateTest {
@Test
void gateOffWhenNothingAttached() {
assertFalse(CaptureGate.gateValue(false, false, false));
}
@Test
void gateOnWhenForced() {
assertTrue(CaptureGate.gateValue(true, false, false));
}
@Test
void gateOnWhenToolAttached() {
assertTrue(CaptureGate.gateValue(false, true, false));
}
@Test
void gateOnWhenTracyConnected() {
assertTrue(CaptureGate.gateValue(false, false, true));
@ParameterizedTest(name = "{0}")
@CsvSource({
"nothing attached, false, false, false, false",
"forced, true, false, false, true",
"tool attached, false, true, false, true",
"tracy connected, false, false, true, true"
})
void gateIsOnWhenAnySourceIs(String name, boolean forced, boolean toolAttached, boolean tracyConnected, boolean expected) {
assertEquals(expected, CaptureGate.gateValue(forced, toolAttached, tracyConnected));
}
@Test
@@ -5,9 +5,12 @@ import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import java.nio.file.Path;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -17,66 +20,104 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
class CompatShaderTransformerTest {
@Test
void testModelViewMatrixReplacement() {
String src = """
#version 120
void main() {
vec4 pos = gl_ModelViewMatrix * gl_Vertex;
gl_Position = gl_ProjectionMatrix * pos;
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertTrue(result.contains("angelica_ModelViewMatrix"));
assertTrue(result.contains("angelica_ProjectionMatrix"));
assertFalse(result.contains("gl_ModelViewMatrix"));
assertFalse(result.contains("gl_ProjectionMatrix"));
assertTrue(result.contains("#version 330 core"));
private static String[] strings(String... values) {
return values;
}
@Test
void testNormalMatrixReplacement() {
String src = """
#version 120
void main() {
vec3 n = gl_NormalMatrix * gl_Normal;
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertTrue(result.contains("uniform mat3 angelica_NormalMatrix"));
assertFalse(result.contains("gl_NormalMatrix"));
private static void assertRewrite(boolean fragment, String src, String[] present, String[] absent) {
final String result = CompatShaderTransformer.transform(src, fragment);
for (final String s : present) {
assertTrue(result.contains(s), () -> "expected '" + s + "' in\n\n" + result);
}
for (final String s : absent) {
assertFalse(result.contains(s), () -> "'" + s + "' must not remain in\n\n" + result);
}
}
@Test
void testModelViewProjectionMatrixExpression() {
String src = """
#version 120
void main() {
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertFalse(result.contains("gl_ModelViewProjectionMatrix"));
assertTrue(result.contains("angelica_ProjectionMatrix"));
static Stream<Arguments> matrixBuiltins() {
return Stream.of(
Arguments.of("modelview and projection matrices", false, """
#version 120
void main() {
vec4 pos = gl_ModelViewMatrix * gl_Vertex;
gl_Position = gl_ProjectionMatrix * pos;
}
""", strings("angelica_ModelViewMatrix", "angelica_ProjectionMatrix", "#version 330 core"), strings("gl_ModelViewMatrix", "gl_ProjectionMatrix")),
Arguments.of("normal matrix", false, """
#version 120
void main() {
vec3 n = gl_NormalMatrix * gl_Normal;
}
""", strings("uniform mat3 angelica_NormalMatrix"), strings("gl_NormalMatrix")),
Arguments.of("modelview-projection matrix", false, """
#version 120
void main() {
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""", strings("angelica_ProjectionMatrix"), strings("gl_ModelViewProjectionMatrix")),
Arguments.of("inverse matrices", false, """
#version 120
void main() {
vec4 pos = gl_ModelViewMatrixInverse * vec4(0.0, 0.0, 0.0, 1.0);
vec4 proj = gl_ProjectionMatrixInverse * vec4(0.0, 0.0, -1.0, 1.0);
}
""", strings(), strings("gl_ModelViewMatrixInverse", "gl_ProjectionMatrixInverse")),
Arguments.of("texture matrix", false, """
#version 120
void main() {
vec4 lmCoord = gl_TextureMatrix[1] * gl_MultiTexCoord1;
}
""", strings(), strings("gl_TextureMatrix")),
Arguments.of("several builtins in one fragment shader", true, """
#version 120
uniform sampler2D tex;
void main() {
vec3 n = gl_NormalMatrix * gl_Normal;
vec4 pos = gl_ModelViewMatrix * gl_Vertex;
gl_Position = gl_ProjectionMatrix * pos;
vec4 color = texture2D(tex, vec2(0.0));
gl_FragColor = color;
}
""", strings("angelica_ModelViewMatrix", "angelica_ProjectionMatrix", "angelica_NormalMatrix", "#version 330 core"), strings("gl_ModelViewMatrix", "gl_ProjectionMatrix", "gl_NormalMatrix", "texture2D", "gl_FragColor")));
}
@Test
void testTextureFunctionRenames() {
String src = """
#version 120
uniform sampler2D tex;
void main() {
vec4 color = texture2D(tex, vec2(0.0));
}
""";
@ParameterizedTest(name = "{0}")
@MethodSource("matrixBuiltins")
void matrixBuiltinsAreRewritten(String name, boolean fragment, String src, String[] present, String[] absent) {
assertRewrite(fragment, src, present, absent);
}
String result = CompatShaderTransformer.transform(src, false);
// texture2D -> texture
assertFalse(result.contains("texture2D"));
assertTrue(result.contains("texture"));
static Stream<Arguments> textureFunctions() {
return Stream.of(
Arguments.of("texture2D", false, """
#version 120
uniform sampler2D tex;
void main() {
vec4 color = texture2D(tex, vec2(0.0));
}
""", strings("texture"), strings("texture2D")),
Arguments.of("shadow2D", true, """
#version 120
uniform sampler2DShadow shadowMap;
void main() {
float s = shadow2D(shadowMap, vec3(0.0)).r;
gl_FragColor = vec4(s);
}
""", strings("texture"), strings("shadow2D(")),
Arguments.of("texelFetch3D", true, """
#version 120
uniform sampler3D vol;
void main() {
vec4 v = texelFetch3D(vol, ivec3(0), 0);
gl_FragColor = v;
}
""", strings("texelFetch"), strings("texelFetch3D")));
}
@ParameterizedTest(name = "{0}")
@MethodSource("textureFunctions")
void legacyTextureFunctionsAreRenamed(String name, boolean fragment, String src, String[] present, String[] absent) {
assertRewrite(fragment, src, present, absent);
}
@Test
@@ -95,32 +136,26 @@ class CompatShaderTransformerTest {
assertTrue(result.contains("layout"));
}
@Test
void testVersionUpgradeTo330() {
String src = """
#version 110
void main() {
gl_FragColor = vec4(1.0);
}
""";
String result = CompatShaderTransformer.transform(src, true);
assertTrue(result.contains("#version 330 core"));
assertFalse(result.contains("#version 110"));
static Stream<Arguments> versionDirectives() {
return Stream.of(
Arguments.of("110 becomes 330 core", true, """
#version 110
void main() {
gl_FragColor = vec4(1.0);
}
""", strings("#version 330 core"), strings("#version 110")),
Arguments.of("compatibility profile is stripped", false, """
#version 330 compatibility
void main() {
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""", strings("#version 330 core"), strings("compatibility")));
}
@Test
void testCompatibilityProfileStripped() {
String src = """
#version 330 compatibility
void main() {
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertTrue(result.contains("#version 330 core"));
assertFalse(result.contains("compatibility"));
@ParameterizedTest(name = "{0}")
@MethodSource("versionDirectives")
void versionIsUpgradedToCore(String name, boolean fragment, String src, String[] present, String[] absent) {
assertRewrite(fragment, src, present, absent);
}
@Test
@@ -139,60 +174,6 @@ class CompatShaderTransformerTest {
assertEquals(src, result);
}
@Test
void testCombinedBuiltinsInSingleShader() {
String src = """
#version 120
uniform sampler2D tex;
void main() {
vec3 n = gl_NormalMatrix * gl_Normal;
vec4 pos = gl_ModelViewMatrix * gl_Vertex;
gl_Position = gl_ProjectionMatrix * pos;
vec4 color = texture2D(tex, vec2(0.0));
gl_FragColor = color;
}
""";
String result = CompatShaderTransformer.transform(src, true);
assertTrue(result.contains("angelica_ModelViewMatrix"));
assertTrue(result.contains("angelica_ProjectionMatrix"));
assertTrue(result.contains("angelica_NormalMatrix"));
assertFalse(result.contains("gl_ModelViewMatrix"));
assertFalse(result.contains("gl_ProjectionMatrix"));
assertFalse(result.contains("gl_NormalMatrix"));
assertFalse(result.contains("texture2D"));
assertFalse(result.contains("gl_FragColor"));
assertTrue(result.contains("#version 330 core"));
}
@Test
void testInverseMatrixReplacement() {
String src = """
#version 120
void main() {
vec4 pos = gl_ModelViewMatrixInverse * vec4(0.0, 0.0, 0.0, 1.0);
vec4 proj = gl_ProjectionMatrixInverse * vec4(0.0, 0.0, -1.0, 1.0);
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertFalse(result.contains("gl_ModelViewMatrixInverse"));
assertFalse(result.contains("gl_ProjectionMatrixInverse"));
}
@Test
void testTextureMatrixReplacement() {
String src = """
#version 120
void main() {
vec4 lmCoord = gl_TextureMatrix[1] * gl_MultiTexCoord1;
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertFalse(result.contains("gl_TextureMatrix"));
}
@Test
void testFogStructAccess() {
String src = """
@@ -211,34 +192,27 @@ class CompatShaderTransformerTest {
assertTrue(result.contains("uniform vec4 angelica_FogColor"), "Fog color uniform should be injected");
}
@Test
void testFogFragCoordVertex() {
String src = """
#version 120
void main() {
gl_FogFragCoord = length(gl_ModelViewMatrix * gl_Vertex);
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertTrue(result.contains("out float angelica_FogFragCoord"), "Vertex shader should get 'out' declaration");
assertTrue(result.contains("angelica_FogFragCoord"), "gl_FogFragCoord should be renamed");
assertFalse(result.contains("gl_FogFragCoord"), "gl_FogFragCoord should not remain");
static Stream<Arguments> fogFragCoord() {
return Stream.of(
Arguments.of("vertex shader declares an out", false, """
#version 120
void main() {
gl_FogFragCoord = length(gl_ModelViewMatrix * gl_Vertex);
}
""", strings("out float angelica_FogFragCoord"), strings("gl_FogFragCoord")),
Arguments.of("fragment shader declares an in", true, """
#version 120
void main() {
float f = gl_FogFragCoord;
gl_FragColor = vec4(f);
}
""", strings("in float angelica_FogFragCoord"), strings("gl_FogFragCoord")));
}
@Test
void testFogFragCoordFragment() {
String src = """
#version 120
void main() {
float f = gl_FogFragCoord;
gl_FragColor = vec4(f);
}
""";
String result = CompatShaderTransformer.transform(src, true);
assertTrue(result.contains("in float angelica_FogFragCoord"), "Fragment shader should get 'in' declaration");
assertFalse(result.contains("gl_FogFragCoord"), "gl_FogFragCoord should not remain");
@ParameterizedTest(name = "{0}")
@MethodSource("fogFragCoord")
void fogFragCoordBecomesAVarying(String name, boolean fragment, String src, String[] present, String[] absent) {
assertRewrite(fragment, src, present, absent);
}
@Test
@@ -255,23 +229,6 @@ class CompatShaderTransformerTest {
assertFalse(result.contains("gl_FrontColor"), "gl_FrontColor should not remain");
}
@Test
void testShadow2DRename() {
String src = """
#version 120
uniform sampler2DShadow shadowMap;
void main() {
float s = shadow2D(shadowMap, vec3(0.0)).r;
gl_FragColor = vec4(s);
}
""";
String result = CompatShaderTransformer.transform(src, true);
assertFalse(result.contains("shadow2D("), "shadow2D should be renamed");
// shadow2D -> texture with vec4 wrapping
assertTrue(result.contains("texture"), "shadow2D should be renamed to texture");
}
@Test
void testReservedWordTextureAsVariable() {
// Shader declares 'texture' as a sampler variable - this is valid in GLSL 120
@@ -321,155 +278,81 @@ class CompatShaderTransformerTest {
"Alpha test discard should be appended to main()");
}
@Test
void testTexelFetch3DTriggersTransformation() {
String src = """
#version 120
uniform sampler3D vol;
void main() {
vec4 v = texelFetch3D(vol, ivec3(0), 0);
gl_FragColor = v;
}
""";
String result = CompatShaderTransformer.transform(src, true);
// texelFetch3D should be renamed to texelFetch
assertFalse(result.contains("texelFetch3D"), "texelFetch3D should be renamed");
assertTrue(result.contains("texelFetch"), "texelFetch3D should become texelFetch");
static Stream<Arguments> vertexAttributes() {
return Stream.of(
Arguments.of("gl_Vertex at location 0", false, """
#version 120
void main() {
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""", strings("in vec4 angelica_Vertex", "location = 0"), strings("gl_Vertex")),
Arguments.of("gl_Color at location 1", false, """
#version 120
varying vec4 v_Color;
void main() {
v_Color = gl_Color;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""", strings("in vec4 angelica_Color", "location = 1"), strings("gl_Color")),
Arguments.of("gl_MultiTexCoord0 at location 2", false, """
#version 120
void main() {
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""", strings("in vec4 angelica_MultiTexCoord0", "location = 2"), strings("gl_MultiTexCoord0")),
Arguments.of("gl_MultiTexCoord1 at location 3", false, """
#version 120
void main() {
vec4 lm = gl_TextureMatrix[1] * gl_MultiTexCoord1;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""", strings("angelica_MultiTexCoord1", "location = 3"), strings("gl_MultiTexCoord1")),
Arguments.of("gl_Normal at location 4", false, """
#version 120
void main() {
vec3 n = gl_NormalMatrix * gl_Normal;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""", strings("in vec3 angelica_Normal", "location = 4"), strings("gl_Normal")));
}
@Test
void testVertexAttributeGlVertex() {
String src = """
#version 120
void main() {
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertFalse(result.contains("gl_Vertex"), "gl_Vertex should be replaced");
assertTrue(result.contains("angelica_Vertex"), "should become angelica_Vertex");
assertTrue(result.contains("in vec4 angelica_Vertex"), "should have in declaration");
assertTrue(result.contains("location = 0"), "should use attribute location 0");
@ParameterizedTest(name = "{0}")
@MethodSource("vertexAttributes")
void vertexAttributeBuiltinsGetFixedLocations(String name, boolean fragment, String src, String[] present, String[] absent) {
assertRewrite(fragment, src, present, absent);
}
@Test
void testVertexAttributeGlColorInVertexShader() {
// gl_Color in a vertex shader is the per-vertex color attribute, not the fragment varying
String src = """
#version 120
varying vec4 v_Color;
void main() {
v_Color = gl_Color;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertTrue(result.contains("angelica_Color"), "gl_Color -> angelica_Color (vertex attribute)");
assertTrue(result.contains("in vec4 angelica_Color"), "should have in declaration");
assertTrue(result.contains("location = 1"), "should use attribute location 1");
assertFalse(result.contains("gl_Color"), "gl_Color should not remain");
static Stream<Arguments> texCoordVaryings() {
return Stream.of(
Arguments.of("vertex shader declares an out", false, """
#version 120
void main() {
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""", strings("out vec4 angelica_TexCoord0"), strings("gl_TexCoord")),
Arguments.of("fragment shader declares an in", true, """
#version 120
void main() {
vec2 uv = gl_TexCoord[0].xy;
gl_FragColor = vec4(uv, 0.0, 1.0);
}
""", strings("in vec4 angelica_TexCoord0"), strings("gl_TexCoord")),
Arguments.of("one varying per index", false, """
#version 120
void main() {
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_TexCoord[1] = gl_MultiTexCoord1;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""", strings("out vec4 angelica_TexCoord0", "out vec4 angelica_TexCoord1"), strings("gl_TexCoord")));
}
@Test
void testVertexAttributeMultiTexCoord0() {
String src = """
#version 120
void main() {
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertFalse(result.contains("gl_MultiTexCoord0"), "gl_MultiTexCoord0 should be replaced");
assertTrue(result.contains("angelica_MultiTexCoord0"), "should become angelica_MultiTexCoord0");
assertTrue(result.contains("in vec4 angelica_MultiTexCoord0"), "should have in declaration");
assertTrue(result.contains("location = 2"), "should use attribute location 2");
}
@Test
void testVertexAttributeMultiTexCoord1() {
String src = """
#version 120
void main() {
vec4 lm = gl_TextureMatrix[1] * gl_MultiTexCoord1;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertFalse(result.contains("gl_MultiTexCoord1"), "gl_MultiTexCoord1 should be replaced");
assertTrue(result.contains("angelica_MultiTexCoord1"), "should become angelica_MultiTexCoord1");
assertTrue(result.contains("location = 3"), "should use attribute location 3");
}
@Test
void testVertexAttributeGlNormal() {
String src = """
#version 120
void main() {
vec3 n = gl_NormalMatrix * gl_Normal;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertFalse(result.contains("gl_Normal"), "gl_Normal should be replaced");
assertTrue(result.contains("angelica_Normal"), "should become angelica_Normal");
assertTrue(result.contains("in vec3 angelica_Normal"), "should have in vec3 declaration");
assertTrue(result.contains("location = 4"), "should use attribute location 4");
}
@Test
void testTexCoordVaryingVertexShader() {
String src = """
#version 120
void main() {
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertFalse(result.contains("gl_TexCoord"), "gl_TexCoord should be replaced");
assertTrue(result.contains("angelica_TexCoord0"), "should become angelica_TexCoord0");
assertTrue(result.contains("out vec4 angelica_TexCoord0"), "vertex shader should have 'out' declaration");
}
@Test
void testTexCoordVaryingFragmentShader() {
String src = """
#version 120
void main() {
vec2 uv = gl_TexCoord[0].xy;
gl_FragColor = vec4(uv, 0.0, 1.0);
}
""";
String result = CompatShaderTransformer.transform(src, true);
assertFalse(result.contains("gl_TexCoord"), "gl_TexCoord should be replaced");
assertTrue(result.contains("angelica_TexCoord0"), "should become angelica_TexCoord0");
assertTrue(result.contains("in vec4 angelica_TexCoord0"), "fragment shader should have 'in' declaration");
}
@Test
void testTexCoordMultipleIndices() {
String src = """
#version 120
void main() {
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_TexCoord[1] = gl_MultiTexCoord1;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertTrue(result.contains("out vec4 angelica_TexCoord0"), "should have TexCoord0 out");
assertTrue(result.contains("out vec4 angelica_TexCoord1"), "should have TexCoord1 out");
assertFalse(result.contains("gl_TexCoord"), "no gl_TexCoord should remain");
@ParameterizedTest(name = "{0}")
@MethodSource("texCoordVaryings")
void texCoordBecomesPerIndexVaryings(String name, boolean fragment, String src, String[] present, String[] absent) {
assertRewrite(fragment, src, present, absent);
}
@Test
@@ -533,7 +416,6 @@ class CompatShaderTransformerTest {
String result = CompatShaderTransformer.transform(src, false);
assertFalse(result.contains("gl_FrontLightModelProduct"), "gl_FrontLightModelProduct should be replaced");
assertTrue(result.contains("uniform vec4 angelica_SceneColor"), "angelica_SceneColor uniform should be injected");
assertTrue(result.contains("angelica_SceneColor"), "should reference angelica_SceneColor");
}
@Test
@@ -9,6 +9,7 @@ import org.lwjgl.opengl.GL11;
import java.nio.FloatBuffer;
import static com.gtnewhorizons.angelica.glsm.GlTestSupport.assertMatrixEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -64,12 +65,6 @@ class DisplayListCompilationParityTest {
return buf;
}
private static void assertMatrixEquals(FloatBuffer expected, FloatBuffer actual, String message) {
for (int i = 0; i < 16; i++) {
assertEquals(expected.get(i), actual.get(i), 0.001f, message + " (element " + i + ")");
}
}
@BeforeEach
void setup() {
if (DisplayListManager.isRecording()) {
@@ -3,11 +3,9 @@ package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import java.nio.FloatBuffer;
import static com.gtnewhorizons.angelica.glsm.GlTestSupport.matrix;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -48,12 +46,6 @@ class DisplayListCompilationTest {
GLStateManager.glLoadIdentity();
}
private static FloatBuffer matrix(int pname) {
final FloatBuffer buf = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(pname, buf);
return buf;
}
@Test
void glsm_compileAndExecute_matrixModeWithTransforms() {
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
@@ -102,69 +94,4 @@ class DisplayListCompilationTest {
assertFalse(DisplayListManager.isRecording(), "frame-begin backstop must clear a leaked open list");
}
@Test
void rawGL_compile_immediateModeWithTransforms() {
GL11.glLoadIdentity();
GL11.glTranslatef(100.0f, 0.0f, 0.0f);
testList = GL11.glGenLists(1);
GL11.glNewList(testList, GL11.GL_COMPILE);
GL11.glPushMatrix();
GL11.glTranslatef(50.0f, 25.0f, 0.0f);
GL11.glScalef(0.5f, 0.5f, 1.0f);
GL11.glBegin(GL11.GL_QUADS);
GL11.glVertex3f(0.0f, 0.0f, 0.0f);
GL11.glVertex3f(16.0f, 0.0f, 0.0f);
GL11.glVertex3f(16.0f, 16.0f, 0.0f);
GL11.glVertex3f(0.0f, 16.0f, 0.0f);
GL11.glEnd();
GL11.glPopMatrix();
GL11.glEndList();
final FloatBuffer afterCompile = BufferUtils.createFloatBuffer(16);
GL11.glGetFloat(GL11.GL_MODELVIEW_MATRIX, afterCompile);
assertEquals(100.0f, afterCompile.get(12), 0.001f, "After GL_COMPILE: matrix unchanged");
GL11.glCallList(testList);
final FloatBuffer afterPlayback = BufferUtils.createFloatBuffer(16);
GL11.glGetFloat(GL11.GL_MODELVIEW_MATRIX, afterPlayback);
assertEquals(100.0f, afterPlayback.get(12), 0.001f, "After playback: base position preserved");
assertEquals(1.0f, afterPlayback.get(0), 0.001f, "After playback: no scale (popped)");
GL11.glLoadIdentity();
}
@Test
void rawGL_compileAndExecute_immediateModeExecutesImmediately() {
GL11.glLoadIdentity();
testList = GL11.glGenLists(1);
GL11.glNewList(testList, GL11.GL_COMPILE_AND_EXECUTE);
GL11.glScalef(0.5f, 0.5f, 0.5f);
final FloatBuffer during = BufferUtils.createFloatBuffer(16);
GL11.glGetFloat(GL11.GL_MODELVIEW_MATRIX, during);
assertEquals(0.5f, during.get(0), 0.001f, "During COMPILE_AND_EXECUTE: scale applied");
GL11.glBegin(GL11.GL_QUADS);
GL11.glVertex3f(0.0f, 0.0f, 0.0f);
GL11.glVertex3f(1.0f, 0.0f, 0.0f);
GL11.glVertex3f(1.0f, 1.0f, 0.0f);
GL11.glVertex3f(0.0f, 1.0f, 0.0f);
GL11.glEnd();
GL11.glEndList();
final FloatBuffer after = BufferUtils.createFloatBuffer(16);
GL11.glGetFloat(GL11.GL_MODELVIEW_MATRIX, after);
assertEquals(0.5f, after.get(0), 0.001f, "After COMPILE_AND_EXECUTE: scale remains");
GL11.glLoadIdentity();
}
}
@@ -1,16 +1,15 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.testutil.TestThreads;
import org.junit.jupiter.api.Test;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -39,25 +38,14 @@ class DisplayListIdAllocatorTest {
}
@Test
void zeroNeverReturned() {
DisplayListIDAllocator alloc = new DisplayListIDAllocator(1);
// Allocate enough IDs to fill the first word (32 IDs). None should be 0.
for (int i = 0; i < 31; i++) {
int id = alloc.allocRange(1);
assertNotEquals(0, id, "ID 0 must never be returned");
assertTrue(id > 0, "All returned IDs must be positive");
void singleAllocsAreSequentialFromOne() {
// Sequential from 1 across several words: 0 is never handed out and no id repeats
final DisplayListIDAllocator alloc = new DisplayListIDAllocator(1);
for (int expected = 1; expected <= 500; expected++) {
assertEquals(expected, alloc.allocRange(1));
}
}
@Test
void singleAllocReturnsSequential() {
DisplayListIDAllocator alloc = new DisplayListIDAllocator();
// First alloc should be 1 (0 is reserved)
assertEquals(1, alloc.allocRange(1));
assertEquals(2, alloc.allocRange(1));
assertEquals(3, alloc.allocRange(1));
}
@Test
void isAllocatedTracksState() {
DisplayListIDAllocator alloc = new DisplayListIDAllocator();
@@ -159,9 +147,9 @@ class DisplayListIdAllocatorTest {
@Test
void freeZeroIsNoOp() {
DisplayListIDAllocator alloc = new DisplayListIDAllocator();
// Should not throw
alloc.free(0);
alloc.free(-1);
assertEquals(1, alloc.allocRange(1), "freeing the reserved id 0 must not make it allocatable");
}
@Test
@@ -205,17 +193,6 @@ class DisplayListIdAllocatorTest {
}
}
@Test
void noDoubleAllocation() {
DisplayListIDAllocator alloc = new DisplayListIDAllocator();
// Allocate many IDs and verify uniqueness
Set<Integer> seen = new HashSet<>();
for (int i = 0; i < 500; i++) {
int id = alloc.allocRange(1);
assertTrue(seen.add(id), "Duplicate ID returned: " + id);
}
}
@Test
void rangeAfterSingleAllocs() {
DisplayListIDAllocator alloc = new DisplayListIDAllocator();
@@ -54,9 +54,7 @@ class DisplayListMatrixRestorationTest extends DisplayListTestFixture {
}
private static float translation(int target) {
FloatBuffer b = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(target, b);
return b.get(12);
return GlTestSupport.matrix(target).get(12);
}
@Test
@@ -1,10 +1,17 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
import com.gtnewhorizons.angelica.glsm.recording.CompiledDisplayList;
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
import net.minecraft.client.renderer.Tessellator;
import org.joml.Matrix4f;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import java.nio.FloatBuffer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
/**
@@ -62,4 +69,58 @@ public final class DisplayListTestHelper {
assertNotNull(compiled, "Display list should be compiled");
return compiled;
}
/** Calls {@code list} on an identity modelview and returns the resulting matrix. The caller's matrix is restored. */
public static FloatBuffer modelviewAfterCall(int list) {
GLStateManager.glPushMatrix();
try {
GLStateManager.glLoadIdentity();
GLStateManager.glCallList(list);
return GlTestSupport.modelview();
} finally {
GLStateManager.glPopMatrix();
}
}
public static void assertMatrixEquals(Matrix4f expected, FloatBuffer actual, String message) {
final FloatBuffer expectedBuf = BufferUtils.createFloatBuffer(16);
expected.get(expectedBuf);
for (int i = 0; i < 16; i++) {
assertEquals(expectedBuf.get(i), actual.get(i), 0.0001f, message + " (element " + i + ")");
}
}
public static void assertModelviewAfterCall(int list, Matrix4f expected, String message) {
assertMatrixEquals(expected, modelviewAfterCall(list), message);
}
/** Immediate-mode quad from the origin to (size, size) at z = 0. */
public static void immediateQuad(float size) {
GLStateManager.glBegin(GL11.GL_QUADS);
GLStateManager.glVertex3f(0.0f, 0.0f, 0.0f);
GLStateManager.glVertex3f(size, 0.0f, 0.0f);
GLStateManager.glVertex3f(size, size, 0.0f);
GLStateManager.glVertex3f(0.0f, size, 0.0f);
GLStateManager.glEnd();
}
/** Textured quad written straight into the vanilla Tessellator's raw buffer and handed to the draw interceptor. */
public static void rawQuad(float size) {
final Tessellator tess = Tessellator.instance;
final float[][] corners = { { 0.0f, 0.0f }, { size, 0.0f }, { size, size }, { 0.0f, size } };
tess.rawBuffer = new int[4 * 8];
for (int i = 0; i < 4; i++) {
final int base = i * 8;
tess.rawBuffer[base] = Float.floatToRawIntBits(corners[i][0]);
tess.rawBuffer[base + 1] = Float.floatToRawIntBits(corners[i][1]);
tess.rawBuffer[base + 3] = Float.floatToRawIntBits(i == 1 || i == 2 ? 1.0f : 0.0f);
tess.rawBuffer[base + 4] = Float.floatToRawIntBits(i >= 2 ? 1.0f : 0.0f);
}
tess.isDrawing = true;
tess.drawMode = GL11.GL_QUADS;
tess.hasTexture = true;
tess.rawBufferIndex = 4 * 8;
tess.vertexCount = 4;
TessellatorManager.interceptDraw(tess);
}
}
@@ -3,12 +3,16 @@ package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL30;
import java.nio.FloatBuffer;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -97,68 +101,23 @@ class FeedbackManagerTest {
assertEquals(42.0f, buf.get(1), "Second token should be the pass-through value");
}
@Test
void testTriangleFeedback_GL2D() {
final FloatBuffer buf = BufferUtils.createFloatBuffer(128);
FeedbackManager.glFeedbackBuffer(GL11.GL_2D, buf);
FeedbackManager.glRenderMode(GL11.GL_FEEDBACK);
// Identity MV/proj, viewport (0,0,800,600)
GLStateManager.getModelViewMatrix().identity();
GLStateManager.getProjectionMatrix().identity();
GLStateManager.getViewportState().setViewPort(0, 0, 800, 600);
GLStateManager.getViewportState().setDepthRange(0.0, 1.0);
// Upload 3 vertices forming a triangle
final int vao = GL30.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
// NDC vertices: (0,0,0), (1,0,0), (0,1,0)
// With identity MVP and viewport 800x600:
// NDC(0,0) → window(400, 300)
// NDC(1,0) → window(800, 300)
// NDC(0,1) → window(400, 600)
final float[] verts = { 0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f, 0f };
final FloatBuffer vertBuf = BufferUtils.createFloatBuffer(9);
vertBuf.put(verts).flip();
final int vbo = GL15.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertBuf, GL15.GL_STATIC_DRAW);
GLStateManager.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 12, 0);
GLStateManager.glEnableVertexAttribArray(0);
FeedbackManager.processDrawArrays(GL11.GL_TRIANGLES, 0, 3);
final int count = FeedbackManager.glRenderMode(GL11.GL_RENDER);
// GL_2D: POLYGON_TOKEN, 3, x0, y0, x1, y1, x2, y2 = 8 floats
assertEquals(8, count, "Triangle in GL_2D should produce 8 floats");
assertEquals(GL11.GL_POLYGON_TOKEN, buf.get(0), 0.001f);
assertEquals(3.0f, buf.get(1), 0.001f);
// Vertex 0: NDC(0,0) → window(400, 300)
assertEquals(400.0f, buf.get(2), 0.01f);
assertEquals(300.0f, buf.get(3), 0.01f);
// Vertex 1: NDC(1,0) → window(800, 300)
assertEquals(800.0f, buf.get(4), 0.01f);
assertEquals(300.0f, buf.get(5), 0.01f);
// Vertex 2: NDC(0,1) → window(400, 600)
assertEquals(400.0f, buf.get(6), 0.01f);
assertEquals(600.0f, buf.get(7), 0.01f);
// Cleanup
GLStateManager.glDisableVertexAttribArray(0);
GLStateManager.glBindVertexArray(0);
GL15.glDeleteBuffers(vbo);
GLStateManager.glDeleteVertexArrays(vao);
// Identity MVP on an 800x600 viewport: NDC (0,0) -> window (400,300), (1,0) -> (800,300), (0,1) -> (400,600)
static Stream<Arguments> primitives() {
return Stream.of(
Arguments.of("triangle", GL11.GL_TRIANGLES,
new float[] { 0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f, 0f },
new float[] { GL11.GL_POLYGON_TOKEN, 3f, 400f, 300f, 800f, 300f, 400f, 600f }),
Arguments.of("polygon", GL11.GL_POLYGON,
new float[] { 0f, 0f, 0f, 1f, 0f, 0f, 1f, 1f, 0f, 0f, 1f, 0f },
new float[] { GL11.GL_POLYGON_TOKEN, 4f, 400f, 300f, 800f, 300f, 800f, 600f, 400f, 600f }),
Arguments.of("quad strip", GL11.GL_QUAD_STRIP,
new float[] { 0f, 0f, 0f, 0f, 1f, 0f, 1f, 0f, 0f, 1f, 1f, 0f },
new float[] { GL11.GL_POLYGON_TOKEN, 4f, 400f, 300f, 400f, 600f, 800f, 600f, 800f, 300f }));
}
@Test
void testPolygonFeedback_GL2D() {
@ParameterizedTest(name = "{0}")
@MethodSource("primitives")
void testPrimitiveFeedback_GL2D(String name, int mode, float[] verts, float[] expected) {
final FloatBuffer buf = BufferUtils.createFloatBuffer(128);
FeedbackManager.glFeedbackBuffer(GL11.GL_2D, buf);
FeedbackManager.glRenderMode(GL11.GL_FEEDBACK);
@@ -171,12 +130,6 @@ class FeedbackManagerTest {
final int vao = GL30.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
final float[] verts = {
0f, 0f, 0f,
1f, 0f, 0f,
1f, 1f, 0f,
0f, 1f, 0f
};
final FloatBuffer vertBuf = BufferUtils.createFloatBuffer(verts.length);
vertBuf.put(verts).flip();
@@ -186,72 +139,15 @@ class FeedbackManagerTest {
GLStateManager.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 12, 0);
GLStateManager.glEnableVertexAttribArray(0);
FeedbackManager.processDrawArrays(GL11.GL_POLYGON, 0, 4);
FeedbackManager.processDrawArrays(mode, 0, verts.length / 3);
final int count = FeedbackManager.glRenderMode(GL11.GL_RENDER);
assertEquals(10, count, "Polygon in GL_2D should produce 10 floats");
assertEquals(GL11.GL_POLYGON_TOKEN, buf.get(0), 0.001f);
assertEquals(4.0f, buf.get(1), 0.001f);
assertEquals(400.0f, buf.get(2), 0.01f);
assertEquals(300.0f, buf.get(3), 0.01f);
assertEquals(800.0f, buf.get(4), 0.01f);
assertEquals(300.0f, buf.get(5), 0.01f);
assertEquals(800.0f, buf.get(6), 0.01f);
assertEquals(600.0f, buf.get(7), 0.01f);
assertEquals(400.0f, buf.get(8), 0.01f);
assertEquals(600.0f, buf.get(9), 0.01f);
GLStateManager.glDisableVertexAttribArray(0);
GLStateManager.glBindVertexArray(0);
GL15.glDeleteBuffers(vbo);
GLStateManager.glDeleteVertexArrays(vao);
}
@Test
void testQuadStripFeedback_GL2D() {
final FloatBuffer buf = BufferUtils.createFloatBuffer(128);
FeedbackManager.glFeedbackBuffer(GL11.GL_2D, buf);
FeedbackManager.glRenderMode(GL11.GL_FEEDBACK);
GLStateManager.getModelViewMatrix().identity();
GLStateManager.getProjectionMatrix().identity();
GLStateManager.getViewportState().setViewPort(0, 0, 800, 600);
GLStateManager.getViewportState().setDepthRange(0.0, 1.0);
final int vao = GL30.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
final float[] verts = {
0f, 0f, 0f,
0f, 1f, 0f,
1f, 0f, 0f,
1f, 1f, 0f
};
final FloatBuffer vertBuf = BufferUtils.createFloatBuffer(verts.length);
vertBuf.put(verts).flip();
final int vbo = GL15.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertBuf, GL15.GL_STATIC_DRAW);
GLStateManager.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 12, 0);
GLStateManager.glEnableVertexAttribArray(0);
FeedbackManager.processDrawArrays(GL11.GL_QUAD_STRIP, 0, 4);
final int count = FeedbackManager.glRenderMode(GL11.GL_RENDER);
assertEquals(10, count, "Quad strip in GL_2D should produce 10 floats");
assertEquals(GL11.GL_POLYGON_TOKEN, buf.get(0), 0.001f);
assertEquals(4.0f, buf.get(1), 0.001f);
assertEquals(400.0f, buf.get(2), 0.01f);
assertEquals(300.0f, buf.get(3), 0.01f);
assertEquals(400.0f, buf.get(4), 0.01f);
assertEquals(600.0f, buf.get(5), 0.01f);
assertEquals(800.0f, buf.get(6), 0.01f);
assertEquals(600.0f, buf.get(7), 0.01f);
assertEquals(800.0f, buf.get(8), 0.01f);
assertEquals(300.0f, buf.get(9), 0.01f);
// GL_2D: POLYGON_TOKEN, vertex count, then x, y per vertex
assertEquals(expected.length, count, name + " float count");
for (int i = 0; i < expected.length; i++) {
assertEquals(expected[i], buf.get(i), 0.01f, name + " feedback value " + i);
}
GLStateManager.glDisableVertexAttribArray(0);
GLStateManager.glBindVertexArray(0);
@@ -22,7 +22,6 @@ class GLSM_BlendFuncUnknownState_GLTest {
@BeforeEach
void requireBufferBlending() {
assumeTrue(RenderSystem.supportsBufferBlending());
drainGlErrors();
}
@AfterEach
@@ -30,11 +29,6 @@ class GLSM_BlendFuncUnknownState_GLTest {
if (!RenderSystem.supportsBufferBlending()) return;
GLStateManager.tryBlendFuncSeparate(GL11.GL_ZERO, GL11.GL_ZERO, GL11.GL_ZERO, GL11.GL_ZERO);
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
drainGlErrors();
}
private static void drainGlErrors() {
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
private static void seedVanillaSeparate() {
@@ -59,7 +53,6 @@ class GLSM_BlendFuncUnknownState_GLTest {
assertBufferBlend(buffer, VANILLA_SRC_RGB, VANILLA_DST_RGB, VANILLA_SRC_ALPHA, VANILLA_DST_ALPHA);
}
assertFalse(GLStateManager.getBlendState().isFuncUnknown(), "forward must clear the unknown flag");
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
}
@Test
@@ -3,11 +3,13 @@ package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
import static com.gtnewhorizons.angelica.util.GLSMUtil.resetGLState;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
@@ -51,46 +53,28 @@ class GLSM_BlendUnknownState_UnitTest {
assertTrue(GL11.glIsEnabled(GL11.GL_BLEND), "GL blend should be re-enabled after setUnknownState");
}
@Test
void testDisableBufferBlendInvalidatesCache() {
GLStateManager.enableBlend();
assertTrue(GL11.glIsEnabled(GL11.GL_BLEND));
@ParameterizedTest(name = "global blend enabled={0}")
@ValueSource(booleans = {true, false})
void perBufferBlendToggleInvalidatesCache(boolean enabled) {
setBlend(enabled);
assertEquals(enabled, GL11.glIsEnabled(GL11.GL_BLEND));
// Use RenderSystem which should call setUnknownState internally
RenderSystem.disableBufferBlend(0);
// Flips buffer 0 behind the cache and marks it unknown, so the repeated global call must reach the driver
if (enabled) {
RenderSystem.disableBufferBlend(0);
} else {
RenderSystem.enableBufferBlend(0);
}
// Now re-enable via GLSM — should issue the GL call
GLStateManager.enableBlend();
assertTrue(GL11.glIsEnabled(GL11.GL_BLEND), "Blend should be re-enabled after disableBufferBlend");
setBlend(enabled);
assertEquals(enabled, GL11.glIsEnabled(GL11.GL_BLEND), "global blend state must be re-issued after a per-buffer toggle");
}
@Test
void testEnableBufferBlendInvalidatesCache() {
// Start with blend disabled
GLStateManager.disableBlend();
assertFalse(GL11.glIsEnabled(GL11.GL_BLEND));
// Per-buffer enable bypasses cache
RenderSystem.enableBufferBlend(0);
// Disable via GLSM — should issue the GL call despite cache saying disabled
GLStateManager.disableBlend();
assertFalse(GL11.glIsEnabled(GL11.GL_BLEND), "Blend should be disabled after enableBufferBlend + disableBlend");
}
@Test
void testUnknownStateClearedAfterSetEnabled() {
GLStateManager.enableBlend();
// Mark unknown
GLStateManager.getBlendMode().setUnknownState();
// First setEnabled clears the unknown flag
GLStateManager.enableBlend();
// Second setEnabled should use normal caching (no redundant GL call)
// Verify state is consistent
assertTrue(GL11.glIsEnabled(GL11.GL_BLEND));
assertTrue(GLStateManager.getBlendMode().isEnabled());
private static void setBlend(boolean enabled) {
if (enabled) {
GLStateManager.enableBlend();
} else {
GLStateManager.disableBlend();
}
}
}
@@ -0,0 +1,672 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL33;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import static com.gtnewhorizons.angelica.glsm.LegacyQueryFixture.CLIENT_ARRAY_CAPS;
import static com.gtnewhorizons.angelica.glsm.LegacyQueryFixture.assertEnableRoundTrip;
import static com.gtnewhorizons.angelica.glsm.LegacyQueryFixture.drainClientAttribStack;
import static com.gtnewhorizons.angelica.glsm.LegacyQueryFixture.hex;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeFalse;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
/** Conventional client arrays and the client attrib stack, emulated on a core-profile vertex array object. */
@GLCoreTest
public class GLSM_ClientArrays_GLTest {
private int savedVao;
private int vao;
private int vbo;
@BeforeEach
void bindFreshVertexArray() {
savedVao = GLStateManager.getBoundVAO();
vao = GLStateManager.glGenVertexArrays();
vbo = GLStateManager.glGenBuffers();
GLStateManager.glBindVertexArray(vao);
}
@AfterEach
void deleteVertexArray() {
GLStateManager.glBindVertexArray(savedVao);
GLStateManager.glDeleteVertexArrays(vao);
GLStateManager.glDeleteBuffers(vbo);
}
private static boolean attribHasSize3(Usage usage) {
final VAOManager.Attrib attrib = GLStateManager.ctx().vaos.attrib(usage.getAttributeLocation());
return attrib != null && attrib.size == 3;
}
private static boolean positionArrayEnabled() {
final VAOManager.Attrib position = GLStateManager.ctx().vaos.attrib(Usage.POSITION.getAttributeLocation());
return position != null && position.enabled;
}
private static boolean colorArrayEnabled() {
return (GLStateManager.ctx().vaos.getVertexFlags() & VertexFlags.COLOR_BIT) != 0;
}
private static int driverAttrib(int index, int pname) {
final IntBuffer out = BufferUtils.createIntBuffer(16);
GL20.glGetVertexAttrib(index, pname, out);
return out.get(0);
}
private static void assertClientArray(String name, int sizePname, int size, int typePname, int type, int stridePname, int stride, int bindingPname, int binding) {
if (sizePname != 0) assertEquals(size, GLStateManager.glGetInteger(sizePname), name + " size");
if (typePname != 0) assertEquals(type, GLStateManager.glGetInteger(typePname), name + " type");
assertEquals(stride, GLStateManager.glGetInteger(stridePname), name + " stride");
assertEquals(binding, GLStateManager.glGetInteger(bindingPname), name + " buffer binding");
}
private static void assertVertexArray(String name, int size, int type, int stride, int binding) {
assertClientArray(name, GL11.GL_VERTEX_ARRAY_SIZE, size, GL11.GL_VERTEX_ARRAY_TYPE, type, GL11.GL_VERTEX_ARRAY_STRIDE, stride, GL15.GL_VERTEX_ARRAY_BUFFER_BINDING, binding);
}
private static void assertTexCoordArray(String name, int size, int type, int stride, int binding) {
assertClientArray(name, GL11.GL_TEXTURE_COORD_ARRAY_SIZE, size, GL11.GL_TEXTURE_COORD_ARRAY_TYPE, type, GL11.GL_TEXTURE_COORD_ARRAY_STRIDE, stride,
GL15.GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING, binding);
}
@Test
void vertexArrayStateRestoredOnlyWithVertexArrayBit() {
final int position = Usage.POSITION.getAttributeLocation();
try {
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
final int flagsBefore = GLStateManager.ctx().vaos.getVertexFlags();
assertFalse(positionArrayEnabled());
assertFalse(colorArrayEnabled());
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
GLStateManager.glEnableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glEnableClientState(GL11.GL_COLOR_ARRAY);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE1);
assertTrue(positionArrayEnabled());
assertTrue(colorArrayEnabled());
GLStateManager.glPopClientAttrib();
assertFalse(positionArrayEnabled(), "pop disables an array enabled inside the push");
assertEquals(GL11.GL_FALSE, driverAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_ENABLED));
assertFalse(colorArrayEnabled());
assertEquals(flagsBefore, GLStateManager.ctx().vaos.getVertexFlags());
assertEquals(0, GLStateManager.getClientActiveTextureUnit());
GLStateManager.glEnableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glPopClientAttrib();
assertTrue(positionArrayEnabled(), "pop re-enables an array disabled inside the push");
assertEquals(GL11.GL_TRUE, driverAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_ENABLED));
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_PIXEL_STORE_BIT);
GLStateManager.glEnableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glEnableClientState(GL11.GL_COLOR_ARRAY);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.glPopClientAttrib();
assertTrue(positionArrayEnabled(), "a pixel-store push must not restore array enables");
assertTrue(colorArrayEnabled());
assertEquals(1, GLStateManager.getClientActiveTextureUnit());
} finally {
drainClientAttribStack();
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glDisableClientState(GL11.GL_COLOR_ARRAY);
}
}
@Test
void clientActiveTextureRoundTrips() {
try {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
assertEquals(GLStateManager.MAX_TEXTURE_COORDS, GLStateManager.glGetInteger(GL20.GL_MAX_TEXTURE_COORDS));
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE1);
assertEquals(GL13.GL_TEXTURE1, GLStateManager.glGetInteger(GL13.GL_CLIENT_ACTIVE_TEXTURE));
GLStateManager.glClientActiveTexture(0);
assertEquals(GL13.GL_TEXTURE1, GLStateManager.glGetInteger(GL13.GL_CLIENT_ACTIVE_TEXTURE));
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0 + GLStateManager.MAX_TEXTURE_UNITS);
assertEquals(GL13.GL_TEXTURE1, GLStateManager.glGetInteger(GL13.GL_CLIENT_ACTIVE_TEXTURE));
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0 + GLStateManager.MAX_TEXTURE_COORDS);
assertEquals(GL13.GL_TEXTURE1, GLStateManager.glGetInteger(GL13.GL_CLIENT_ACTIVE_TEXTURE), "units past GL_MAX_TEXTURE_COORDS are rejected");
assertEquals(1, GLStateManager.getClientActiveTextureUnit());
GLStateManager.glTexCoordPointer(2, GL11.GL_FLOAT, 0, 0L);
final VAOManager.Attrib attrib = GLStateManager.ctx().vaos.attrib(Usage.SECONDARY_UV.getAttributeLocation());
assertNotNull(attrib);
assertEquals(2, attrib.size);
assumeTrue(GLStateManager.MAX_TEXTURE_COORDS > 5);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE5);
assertEquals(GL13.GL_TEXTURE5, GLStateManager.glGetInteger(GL13.GL_CLIENT_ACTIVE_TEXTURE));
GLStateManager.glTexCoordPointer(3, GL11.GL_FLOAT, 0, 0L);
assertEquals(3, GLStateManager.glGetInteger(GL11.GL_TEXTURE_COORD_ARRAY_SIZE));
assertFalse(attribHasSize3(Usage.PRIMARY_UV));
assertFalse(attribHasSize3(Usage.SECONDARY_UV));
} finally {
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
}
@Test
void conventionalPointerSurvivesGenericOverride() {
final int position = Usage.POSITION.getAttributeLocation();
try {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glVertexPointer(3, GL11.GL_FLOAT, 12, 0L);
GLStateManager.glEnableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glVertexAttribPointer(position, 2, GL11.GL_FLOAT, false, 8, 4L);
assertEquals(3, VAOManager.get(position).size, "disabled generic pointer must not displace an enabled conventional array");
assertEquals(3, driverAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_SIZE));
GLStateManager.glEnableVertexAttribArray(position);
assertEquals(2, VAOManager.get(position).size, "enabled generic attribute wins");
assertTrue(VAOManager.get(position).genericPointer);
assertEquals(2, driverAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_SIZE));
assertEquals(8, driverAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_STRIDE));
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glDisableVertexAttribArray(position);
assertEquals(3, VAOManager.get(position).size, "conventional pointer must come back when the generic attribute is disabled");
assertEquals(12, VAOManager.get(position).stride);
assertFalse(VAOManager.get(position).genericPointer);
assertEquals(3, driverAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_SIZE));
assertEquals(12, driverAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_STRIDE));
assertEquals(vbo, driverAttrib(position, GL15.GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING));
assertEquals(GL11.GL_TRUE, driverAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_ENABLED), "conventional array stays enabled at draw level");
assertTrue(GLStateManager.glIsEnabled(GL11.GL_VERTEX_ARRAY));
assertEquals(0, GLStateManager.glGetInteger(GL15.GL_ARRAY_BUFFER_BINDING), "repoint must restore the array buffer binding");
} finally {
GLStateManager.glDisableVertexAttribArray(position);
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
}
@Test
void conventionalPointerSetUnderEnabledGenericAppliesOnceGenericIsDisabled() {
final int position = Usage.POSITION.getAttributeLocation();
try {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glVertexAttribPointer(position, 2, GL11.GL_FLOAT, false, 8, 0L);
GLStateManager.glEnableVertexAttribArray(position);
GLStateManager.glVertexPointer(3, GL11.GL_FLOAT, 12, 0L);
GLStateManager.glEnableClientState(GL11.GL_VERTEX_ARRAY);
assertEquals(2, VAOManager.get(position).size);
assertEquals(2, driverAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_SIZE));
GLStateManager.glDisableVertexAttribArray(position);
assertEquals(3, VAOManager.get(position).size);
assertEquals(3, driverAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_SIZE));
assertEquals(GL11.GL_TRUE, driverAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_ENABLED));
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
assertEquals(GL11.GL_FALSE, driverAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_ENABLED));
assertFalse(positionArrayEnabled());
} finally {
GLStateManager.glDisableVertexAttribArray(position);
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
}
@Test
void drawVertexFlagsDoNotAnswerClientArrayEnables() {
try {
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
VAOManager.setCurrentVertexFlags(VertexFlags.COLOR_BIT | VertexFlags.NORMAL_BIT | VertexFlags.TEXTURE_BIT);
assertFalse(GLStateManager.glIsEnabled(GL11.GL_COLOR_ARRAY));
assertFalse(GLStateManager.glIsEnabled(GL11.GL_NORMAL_ARRAY));
assertFalse(GLStateManager.glIsEnabled(GL11.GL_TEXTURE_COORD_ARRAY));
GLStateManager.glEnableClientState(GL11.GL_COLOR_ARRAY);
VAOManager.setCurrentVertexFlags(0);
assertTrue(GLStateManager.glIsEnabled(GL11.GL_COLOR_ARRAY));
} finally {
GLStateManager.glDisableClientState(GL11.GL_COLOR_ARRAY);
VAOManager.setCurrentVertexFlags(0);
}
}
@Test
void clientArrayPointerQueriesAreAnsweredFromCache() {
final int vaoB = GLStateManager.glGenVertexArrays();
final IntBuffer ints = BufferUtils.createIntBuffer(16);
try {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
assertVertexArray("default vertex", 4, GL11.GL_FLOAT, 0, 0);
assertClientArray("default normal", 0, 0, GL11.GL_NORMAL_ARRAY_TYPE, GL11.GL_FLOAT, GL11.GL_NORMAL_ARRAY_STRIDE, 0, GL15.GL_NORMAL_ARRAY_BUFFER_BINDING, 0);
assertClientArray("default color", GL11.GL_COLOR_ARRAY_SIZE, 4, GL11.GL_COLOR_ARRAY_TYPE, GL11.GL_FLOAT, GL11.GL_COLOR_ARRAY_STRIDE, 0, GL15.GL_COLOR_ARRAY_BUFFER_BINDING, 0);
assertClientArray("default secondary color", GL14.GL_SECONDARY_COLOR_ARRAY_SIZE, 3, GL14.GL_SECONDARY_COLOR_ARRAY_TYPE, GL11.GL_FLOAT,
GL14.GL_SECONDARY_COLOR_ARRAY_STRIDE, 0, GL15.GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING, 0);
assertClientArray("default fog coord", 0, 0, GL14.GL_FOG_COORDINATE_ARRAY_TYPE, GL11.GL_FLOAT, GL14.GL_FOG_COORDINATE_ARRAY_STRIDE, 0,
GL15.GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING, 0);
assertClientArray("default index", 0, 0, GL11.GL_INDEX_ARRAY_TYPE, GL11.GL_FLOAT, GL11.GL_INDEX_ARRAY_STRIDE, 0, GL15.GL_INDEX_ARRAY_BUFFER_BINDING, 0);
assertClientArray("default edge flag", 0, 0, 0, 0, GL11.GL_EDGE_FLAG_ARRAY_STRIDE, 0, GL15.GL_EDGE_FLAG_ARRAY_BUFFER_BINDING, 0);
assertTexCoordArray("default texcoord", 4, GL11.GL_FLOAT, 0, 0);
assertNull(GLStateManager.glGetPointer(GL11.GL_VERTEX_ARRAY_POINTER, 4));
GLStateManager.glVertexPointer(3, GL11.GL_SHORT, 20, 8L);
GLStateManager.glNormalPointer(GL11.GL_BYTE, 16, 4L);
GLStateManager.glColorPointer(4, GL11.GL_UNSIGNED_BYTE, 16, 12L);
GLStateManager.glSecondaryColorPointer(3, GL11.GL_FLOAT, 12, 0L);
GLStateManager.glFogCoordPointer(GL11.GL_FLOAT, 4, 0L);
GLStateManager.glEdgeFlagPointer(7, 0L);
GLStateManager.glTexCoordPointer(2, GL11.GL_FLOAT, 8, 0L);
assertVertexArray("vertex", 3, GL11.GL_SHORT, 20, vbo);
assertClientArray("normal", 0, 0, GL11.GL_NORMAL_ARRAY_TYPE, GL11.GL_BYTE, GL11.GL_NORMAL_ARRAY_STRIDE, 16, GL15.GL_NORMAL_ARRAY_BUFFER_BINDING, vbo);
assertClientArray("color", GL11.GL_COLOR_ARRAY_SIZE, 4, GL11.GL_COLOR_ARRAY_TYPE, GL11.GL_UNSIGNED_BYTE, GL11.GL_COLOR_ARRAY_STRIDE, 16,
GL15.GL_COLOR_ARRAY_BUFFER_BINDING, vbo);
assertClientArray("secondary color", GL14.GL_SECONDARY_COLOR_ARRAY_SIZE, 3, GL14.GL_SECONDARY_COLOR_ARRAY_TYPE, GL11.GL_FLOAT,
GL14.GL_SECONDARY_COLOR_ARRAY_STRIDE, 12, GL15.GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING, vbo);
assertClientArray("fog coord", 0, 0, GL14.GL_FOG_COORDINATE_ARRAY_TYPE, GL11.GL_FLOAT, GL14.GL_FOG_COORDINATE_ARRAY_STRIDE, 4,
GL15.GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING, vbo);
assertClientArray("edge flag", 0, 0, 0, 0, GL11.GL_EDGE_FLAG_ARRAY_STRIDE, 7, GL15.GL_EDGE_FLAG_ARRAY_BUFFER_BINDING, vbo);
assertTexCoordArray("texcoord unit 0", 2, GL11.GL_FLOAT, 8, vbo);
assertEquals(3.0f, GLStateManager.glGetFloat(GL11.GL_VERTEX_ARRAY_SIZE));
GLStateManager.glGetInteger(GL11.GL_VERTEX_ARRAY_STRIDE, ints);
assertEquals(20, ints.get(0));
assertEquals(8L, MemoryUtilities.memAddress(GLStateManager.glGetPointer(GL11.GL_VERTEX_ARRAY_POINTER, 4)));
assertEquals(12L, MemoryUtilities.memAddress(GLStateManager.glGetPointer(GL11.GL_COLOR_ARRAY_POINTER, 4)));
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0 + GLStateManager.MAX_TEXTURE_COORDS - 1);
assertTexCoordArray("unset texcoord unit", 4, GL11.GL_FLOAT, 0, 0);
GLStateManager.glTexCoordPointer(3, GL11.GL_SHORT, 6, 0L);
assertTexCoordArray("last texcoord unit", 3, GL11.GL_SHORT, 6, vbo);
assertFalse(attribHasSize3(Usage.PRIMARY_UV));
assertFalse(attribHasSize3(Usage.SECONDARY_UV));
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
assertTexCoordArray("texcoord unit 0 again", 2, GL11.GL_FLOAT, 8, vbo);
GLStateManager.glBindVertexArray(vaoB);
assertVertexArray("other vao", 4, GL11.GL_FLOAT, 0, 0);
final FloatBuffer client = BufferUtils.createFloatBuffer(12);
GLStateManager.glVertexPointer(2, 0, client);
assertVertexArray("client pointer", 2, GL11.GL_FLOAT, 0, 0);
assertEquals(MemoryUtilities.memAddress(client), MemoryUtilities.memAddress(GLStateManager.glGetPointer(GL11.GL_VERTEX_ARRAY_POINTER, 4)));
GLStateManager.glBindVertexArray(vao);
assertVertexArray("first vao again", 3, GL11.GL_SHORT, 20, vbo);
} finally {
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glDeleteVertexArrays(vaoB);
}
}
@Test
void interleavedArraysSetClientStateWithoutAnActiveFfpProgram() {
try {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
assumeFalse(GLStateManager.ctx().ffp.isActive());
GLStateManager.glInterleavedArrays(GL11.GL_C4UB_V3F, 0, 0L);
assertTrue(GLStateManager.glIsEnabled(GL11.GL_VERTEX_ARRAY));
assertTrue(GLStateManager.glIsEnabled(GL11.GL_COLOR_ARRAY));
assertFalse(GLStateManager.glIsEnabled(GL11.GL_NORMAL_ARRAY));
assertVertexArray("interleaved vertex", 3, GL11.GL_FLOAT, 16, vbo);
assertEquals(GL11.GL_UNSIGNED_BYTE, GLStateManager.glGetInteger(GL11.GL_COLOR_ARRAY_TYPE));
} finally {
for (final int cap : CLIENT_ARRAY_CAPS) {
GLStateManager.glDisableClientState(cap);
}
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
}
@Test
void clientVertexArrayBitRestoresPointersEnablesAndBindings() {
final int vaoB = GLStateManager.glGenVertexArrays();
final int vbo2 = GLStateManager.glGenBuffers();
final int ebo = GLStateManager.glGenBuffers();
final int position = Usage.POSITION.getAttributeLocation();
final int color = Usage.COLOR.getAttributeLocation();
final int normal = Usage.NORMAL.getAttributeLocation();
try {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glVertexPointer(3, GL11.GL_FLOAT, 12, 0L);
GLStateManager.glColorPointer(4, GL11.GL_UNSIGNED_BYTE, 16, 12L);
GLStateManager.glFogCoordPointer(GL11.GL_FLOAT, 4, 0L);
GLStateManager.glEnableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glEnableClientState(GL11.GL_COLOR_ARRAY);
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo2);
GLStateManager.glVertexPointer(2, GL11.GL_SHORT, 4, 4L);
GLStateManager.glFogCoordPointer(GL11.GL_FLOAT, 8, 0L);
GLStateManager.glDisableClientState(GL11.GL_COLOR_ARRAY);
GLStateManager.glEnableClientState(GL11.GL_NORMAL_ARRAY);
GLStateManager.glVertexAttribPointer(5, 1, GL11.GL_FLOAT, false, 4, 0L);
GLStateManager.glEnableVertexAttribArray(5);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.glBindVertexArray(vaoB);
GLStateManager.glPopClientAttrib();
assertEquals(vao, GLStateManager.getBoundVAO());
assertEquals(vbo, GLStateManager.glGetInteger(GL15.GL_ARRAY_BUFFER_BINDING));
assertEquals(ebo, GLStateManager.glGetInteger(GL15.GL_ELEMENT_ARRAY_BUFFER_BINDING));
assertEquals(ebo, GL11.glGetInteger(GL15.GL_ELEMENT_ARRAY_BUFFER_BINDING));
assertEquals(GL13.GL_TEXTURE0, GLStateManager.glGetInteger(GL13.GL_CLIENT_ACTIVE_TEXTURE));
assertVertexArray("restored vertex", 3, GL11.GL_FLOAT, 12, vbo);
assertEquals(4, GLStateManager.glGetInteger(GL14.GL_FOG_COORDINATE_ARRAY_STRIDE));
assertEquals(vbo, GLStateManager.glGetInteger(GL15.GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING));
assertTrue(GLStateManager.glIsEnabled(GL11.GL_COLOR_ARRAY));
assertFalse(GLStateManager.glIsEnabled(GL11.GL_NORMAL_ARRAY));
assertTrue(colorArrayEnabled());
assertEquals(3, driverAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_SIZE));
assertEquals(GL11.GL_FLOAT, driverAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_TYPE));
assertEquals(12, driverAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_STRIDE));
assertEquals(vbo, driverAttrib(position, GL15.GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING));
assertEquals(GL11.GL_TRUE, driverAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_ENABLED));
assertEquals(GL11.GL_TRUE, driverAttrib(color, GL20.GL_VERTEX_ATTRIB_ARRAY_ENABLED));
assertEquals(GL11.GL_FALSE, driverAttrib(normal, GL20.GL_VERTEX_ATTRIB_ARRAY_ENABLED));
assertEquals(GL11.GL_FALSE, driverAttrib(5, GL20.GL_VERTEX_ATTRIB_ARRAY_ENABLED));
GLStateManager.glBindVertexArray(vaoB);
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
GLStateManager.glBindVertexArray(vao);
GLStateManager.glDeleteVertexArrays(vaoB);
GLStateManager.glPopClientAttrib();
assertEquals(vao, GLStateManager.getBoundVAO(), "a deleted VAO must not be restored");
assertVertexArray("after dead-vao pop", 3, GL11.GL_FLOAT, 12, vbo);
} finally {
drainClientAttribStack();
GLStateManager.glBindVertexArray(vao);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
for (final int cap : CLIENT_ARRAY_CAPS) {
GLStateManager.glDisableClientState(cap);
}
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glDeleteVertexArrays(vaoB);
GLStateManager.glDeleteBuffers(vbo2);
GLStateManager.glDeleteBuffers(ebo);
}
}
@Test
void enabledGenericAttributeWinsWithoutAPointer() {
final int position = Usage.POSITION.getAttributeLocation();
try {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glVertexPointer(3, GL11.GL_FLOAT, 12, 0L);
GLStateManager.glEnableClientState(GL11.GL_VERTEX_ARRAY);
assertFalse(VAOManager.get(position).genericPointer);
GLStateManager.glEnableVertexAttribArray(position);
assertTrue(VAOManager.get(position).genericPointer, "generic 0 supersedes position on its enable bit alone");
GLStateManager.glDisableVertexAttribArray(position);
assertFalse(VAOManager.get(position).genericPointer);
assertEquals(3, VAOManager.get(position).size);
} finally {
GLStateManager.glDisableVertexAttribArray(position);
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
}
@Test
void deletedBufferIsDetachedFromTheBoundVertexArray() {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glVertexPointer(3, GL11.GL_FLOAT, 12, 4L);
GLStateManager.glFogCoordPointer(GL11.GL_FLOAT, 4, 0L);
assertEquals(vbo, GLStateManager.glGetInteger(GL15.GL_VERTEX_ARRAY_BUFFER_BINDING));
GLStateManager.glDeleteBuffers(vbo);
assertEquals(0, GLStateManager.glGetInteger(GL15.GL_VERTEX_ARRAY_BUFFER_BINDING));
assertEquals(0, GLStateManager.glGetInteger(GL15.GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING));
assertEquals(12, GLStateManager.glGetInteger(GL11.GL_VERTEX_ARRAY_STRIDE), "offset and stride survive the detach");
assertEquals(3, GLStateManager.glGetInteger(GL11.GL_VERTEX_ARRAY_SIZE));
}
@Test
void shadowedPointerIsNotRepointedAtAReusedBufferName() {
final int other = GLStateManager.glGenVertexArrays();
final int conventional = GLStateManager.glGenBuffers();
final int generic = GLStateManager.glGenBuffers();
final int position = Usage.POSITION.getAttributeLocation();
int reused = 0;
try {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, conventional);
GLStateManager.glVertexPointer(3, GL11.GL_FLOAT, 12, 0L);
GLStateManager.glEnableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, generic);
GLStateManager.glVertexAttribPointer(position, 2, GL11.GL_FLOAT, false, 8, 0L);
GLStateManager.glEnableVertexAttribArray(position);
GLStateManager.glBindVertexArray(other);
GLStateManager.glDeleteBuffers(conventional);
reused = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, reused);
assumeTrue(reused == conventional, "driver did not reuse the buffer name");
GLStateManager.glBindVertexArray(vao);
GLStateManager.glDisableVertexAttribArray(position);
assertEquals(generic, driverAttrib(position, GL15.GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING), "a pointer set before the delete must not attach to the new buffer");
} finally {
GLStateManager.glBindVertexArray(vao);
GLStateManager.glDisableVertexAttribArray(position);
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glDeleteVertexArrays(other);
GLStateManager.glDeleteBuffers(generic);
if (reused != 0) GLStateManager.glDeleteBuffers(reused);
}
}
@Test
void popKeepsCurrentArraysWhenTheSavedArrayBufferWasDeleted() {
final int vbo2 = GLStateManager.glGenBuffers();
try {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glVertexPointer(3, GL11.GL_FLOAT, 12, 0L);
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo2);
GLStateManager.glVertexPointer(2, GL11.GL_SHORT, 4, 0L);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.glDeleteBuffers(vbo);
GLStateManager.glPopClientAttrib();
assertEquals(2, GLStateManager.glGetInteger(GL11.GL_VERTEX_ARRAY_SIZE), "array state is not restored once its array buffer is gone");
assertEquals(vbo2, GLStateManager.glGetInteger(GL15.GL_ARRAY_BUFFER_BINDING));
assertEquals(GL13.GL_TEXTURE0, GLStateManager.glGetInteger(GL13.GL_CLIENT_ACTIVE_TEXTURE));
} finally {
drainClientAttribStack();
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glDeleteBuffers(vbo2);
}
}
@Test
void clientAttribStackIsNotCompiledIntoDisplayLists() {
final int other = GLStateManager.glGenVertexArrays();
final int list = GLStateManager.glGenLists(1);
try {
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
GLStateManager.glPopClientAttrib();
GLStateManager.glEndList();
GLStateManager.glBindVertexArray(other);
GLStateManager.glCallList(list);
assertEquals(other, GLStateManager.getBoundVAO(), "replaying the list must not rebind the vertex array that was bound while compiling");
} finally {
drainClientAttribStack();
GLStateManager.glDeleteLists(list, 1);
GLStateManager.glDeleteVertexArrays(other);
}
}
@Test
void popRestoresDivisorAndStateWithoutAnAttributeLocation() {
try {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glVertexAttribPointer(5, 1, GL11.GL_FLOAT, false, 4, 0L);
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
GLStateManager.glPopClientAttrib();
assertEquals(0, driverAttrib(5, GL33.GL_VERTEX_ATTRIB_ARRAY_DIVISOR));
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
GLStateManager.glVertexAttribDivisor(5, 3);
assertEquals(3, driverAttrib(5, GL33.GL_VERTEX_ATTRIB_ARRAY_DIVISOR));
GLStateManager.glPopClientAttrib();
assertEquals(0, driverAttrib(5, GL33.GL_VERTEX_ATTRIB_ARRAY_DIVISOR));
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
GLStateManager.glEnableClientState(GL14.GL_FOG_COORDINATE_ARRAY);
GLStateManager.glPopClientAttrib();
assertFalse(GLStateManager.glIsEnabled(GL14.GL_FOG_COORDINATE_ARRAY));
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
GLStateManager.glEnableVertexAttribArray(5);
GLStateManager.glPopClientAttrib();
assertEquals(GL11.GL_FALSE, driverAttrib(5, GL20.GL_VERTEX_ATTRIB_ARRAY_ENABLED));
} finally {
drainClientAttribStack();
GLStateManager.glDisableVertexAttribArray(5);
GLStateManager.glDisableClientState(GL14.GL_FOG_COORDINATE_ARRAY);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
}
@Test
void interleavedArraysFromAClientBufferRegisterClientPointers() {
final FloatBuffer data = BufferUtils.createFloatBuffer(64);
final int position = Usage.POSITION.getAttributeLocation();
try {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
data.position(2);
GLStateManager.glInterleavedArrays(GL11.GL_C4UB_V3F, 0, data);
final long base = MemoryUtilities.memAddress(data);
assertNotNull(VAOManager.get(position).clientPointer);
assertEquals(base + 4, MemoryUtilities.memAddress(VAOManager.get(position).clientPointer));
assertEquals(base + 4, MemoryUtilities.memAddress(GLStateManager.glGetPointer(GL11.GL_VERTEX_ARRAY_POINTER, 4)));
assertEquals(base, MemoryUtilities.memAddress(GLStateManager.glGetPointer(GL11.GL_COLOR_ARRAY_POINTER, 4)));
assertEquals(0, GLStateManager.glGetInteger(GL15.GL_VERTEX_ARRAY_BUFFER_BINDING));
assertEquals(16, GLStateManager.glGetInteger(GL11.GL_VERTEX_ARRAY_STRIDE));
assertTrue(GLStateManager.ctx().vaos.hasAnyClientSideEnabledAttrib());
} finally {
for (final int cap : CLIENT_ARRAY_CAPS) {
GLStateManager.glDisableClientState(cap);
}
}
}
@Test
void enableAndDisableRouteClientArrayCapsToClientState() {
for (final int cap : CLIENT_ARRAY_CAPS) {
GLStateManager.glEnable(cap);
assertTrue(GLStateManager.glIsEnabled(cap), hex(cap));
GLStateManager.glDisable(cap);
assertFalse(GLStateManager.glIsEnabled(cap), hex(cap));
}
GLStateManager.glEnable(GL11.GL_VERTEX_ARRAY);
assertTrue(positionArrayEnabled());
GLStateManager.glDisable(GL11.GL_VERTEX_ARRAY);
}
@Test
void illegalPointerArgumentsAreIgnored() {
try {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glVertexPointer(3, GL11.GL_FLOAT, 12, 0L);
GLStateManager.glNormalPointer(GL11.GL_FLOAT, 12, 0L);
GLStateManager.glVertexPointer(1, GL11.GL_FLOAT, 4, 0L);
GLStateManager.glVertexPointer(3, GL11.GL_UNSIGNED_BYTE, 3, 0L);
GLStateManager.glNormalPointer(GL11.GL_UNSIGNED_BYTE, 3, 0L);
assertVertexArray("vertex after illegal calls", 3, GL11.GL_FLOAT, 12, vbo);
assertEquals(GL11.GL_FLOAT, GLStateManager.glGetInteger(GL11.GL_NORMAL_ARRAY_TYPE));
GLStateManager.glColorPointer(GL12.GL_BGRA, GL11.GL_UNSIGNED_BYTE, 4, 0L);
assertEquals(GL12.GL_BGRA, GLStateManager.glGetInteger(GL11.GL_COLOR_ARRAY_SIZE));
GLStateManager.glColorPointer(GL12.GL_BGRA, GL11.GL_FLOAT, 16, 0L);
assertEquals(GL11.GL_UNSIGNED_BYTE, GLStateManager.glGetInteger(GL11.GL_COLOR_ARRAY_TYPE));
GLStateManager.glVertexAttribPointer(VAOManager.MAX_ATTRIBS, 3, GL11.GL_FLOAT, false, 0, 0L);
GLStateManager.glEnableVertexAttribArray(VAOManager.MAX_ATTRIBS);
GLStateManager.glDisableVertexAttribArray(VAOManager.MAX_ATTRIBS);
GLStateManager.glVertexAttribPointer(5, 5, GL11.GL_FLOAT, false, 0, 0L);
assertNull(VAOManager.get(5));
} finally {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
}
@Test
void clientArrayEnablesAreAnsweredFromCache() {
final int vaoB = GLStateManager.glGenVertexArrays();
try {
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
for (final int cap : CLIENT_ARRAY_CAPS) {
assertFalse(GLStateManager.glIsEnabled(cap), "default isEnabled " + hex(cap));
assertEnableRoundTrip(cap, true);
}
GLStateManager.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE1);
assertFalse(GLStateManager.glIsEnabled(GL11.GL_TEXTURE_COORD_ARRAY), "texcoord array enable is per client texture unit");
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
assertTrue(GLStateManager.glIsEnabled(GL11.GL_TEXTURE_COORD_ARRAY));
GLStateManager.glDisableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
GLStateManager.glEnableClientState(GL11.GL_INDEX_ARRAY);
GLStateManager.glBindVertexArray(vaoB);
assertFalse(GLStateManager.glIsEnabled(GL11.GL_INDEX_ARRAY), "array enables are per vertex array object");
GLStateManager.glBindVertexArray(vao);
assertTrue(GLStateManager.glIsEnabled(GL11.GL_INDEX_ARRAY));
GLStateManager.glDisableClientState(GL11.GL_INDEX_ARRAY);
GLStateManager.glBindVertexArray(vaoB);
GLStateManager.glEnableVertexAttribArray(0);
assertFalse(GLStateManager.glIsEnabled(GL11.GL_VERTEX_ARRAY), "an enabled generic attribute 0 is not GL_VERTEX_ARRAY");
GLStateManager.glDisableVertexAttribArray(0);
GLStateManager.glBindVertexArray(vao);
} finally {
GLStateManager.glBindVertexArray(vaoB);
GLStateManager.glDisableVertexAttribArray(0);
GLStateManager.glBindVertexArray(vao);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
for (final int cap : CLIENT_ARRAY_CAPS) {
GLStateManager.glDisableClientState(cap);
}
GLStateManager.glDeleteVertexArrays(vaoB);
}
}
}
@@ -76,7 +76,6 @@ public class GLSM_CoreRemovedCaps_GLTest {
assertFalse(GLStateManager.glIsEnabled(cap));
} finally {
GLStateManager.glDisable(cap);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
}
@@ -91,7 +90,6 @@ public class GLSM_CoreRemovedCaps_GLTest {
assertFalse(GLStateManager.glIsEnabled(cap));
} finally {
GLStateManager.glDisable(cap);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
}
@@ -105,7 +103,6 @@ public class GLSM_CoreRemovedCaps_GLTest {
assertSecondaryColor(0.25F, 0.5F, 0.75F);
} finally {
GLStateManager.glSecondaryColor3f(0.0F, 0.0F, 0.0F);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
}
@@ -121,7 +118,6 @@ public class GLSM_CoreRemovedCaps_GLTest {
assertSecondaryColor(0.125F, 0.25F, 0.375F);
} finally {
GLStateManager.glSecondaryColor3f(0.0F, 0.0F, 0.0F);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
}
@@ -135,7 +131,6 @@ public class GLSM_CoreRemovedCaps_GLTest {
assertFalse(GLStateManager.glIsEnabled(GL14.GL_COLOR_SUM));
} finally {
GLStateManager.glDisable(GL14.GL_COLOR_SUM);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
}
@@ -156,7 +151,6 @@ public class GLSM_CoreRemovedCaps_GLTest {
} finally {
GLStateManager.glDeleteLists(list, 1);
GLStateManager.glSecondaryColor3f(0.0F, 0.0F, 0.0F);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
}
@@ -176,7 +170,6 @@ public class GLSM_CoreRemovedCaps_GLTest {
} finally {
GLStateManager.glDeleteLists(list, 1);
GLStateManager.glDisable(GL11.GL_LINE_STIPPLE);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
}
}
@@ -0,0 +1,62 @@
package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL30;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
@GLCoreTest
public class GLSM_DeferredTextureDelete_GLTest {
private static int immutableTexture() {
assumeTrue(GLStateManager.capabilities.OpenGL42, "no glTexStorage2D on this context");
final int tex = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
GLStateManager.glTexStorage2D(GL11.GL_TEXTURE_2D, 1, GL30.GL_R8, 4, 4);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
return tex;
}
@Test
void deletingAnImmutableTextureRaisesNoError() {
final int tex = immutableTexture();
GLStateManager.glDeleteTextures(tex);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
}
@Test
void deletingARescuedImmutableTextureRaisesNoError() {
final int tex = immutableTexture();
GLStateManager.glDeleteTextures(tex);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
GLStateManager.glDeleteTextures(tex);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
}
@Test
void deletingA2DTextureWhileA3DTextureIsBoundLeavesNothingBoundTo2D() {
final int t2d = GLStateManager.glGenTextures();
final int t3d = GLStateManager.glGenTextures();
try {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, t2d);
GLStateManager.glBindTexture(GL12.GL_TEXTURE_3D, t3d);
GLStateManager.glDeleteTextures(t2d);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
assertEquals(0, GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D), "the deleted texture must not stay bound");
assertEquals(t3d, GL11.glGetInteger(GL12.GL_TEXTURE_BINDING_3D));
} finally {
GLStateManager.glBindTexture(GL12.GL_TEXTURE_3D, 0);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
GLStateManager.glDeleteTextures(t3d);
}
}
}
@@ -6,18 +6,12 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import static com.gtnewhorizons.angelica.glsm.GlTestSupport.floats;
import static org.junit.jupiter.api.Assertions.assertEquals;
@GLCoreTest
public class GLSM_DirtyTracking_UnitTest {
static final FloatBuffer f4b = ByteBuffer.allocateDirect(4 << 2).order(ByteOrder.nativeOrder()).asFloatBuffer();
static final FloatBuffer f3b = ByteBuffer.allocateDirect(3 << 2).order(ByteOrder.nativeOrder()).asFloatBuffer();
@BeforeEach
void pushTrackedState() {
GLStateManager.glPushAttrib(GL11.GL_LIGHTING_BIT | GL11.GL_FOG_BIT | GL11.GL_CURRENT_BIT);
@@ -30,38 +24,31 @@ public class GLSM_DirtyTracking_UnitTest {
@Test
void materialBumpsOnlyOnChange() {
newf4b(0.1F, 0.2F, 0.3F, 0.4F);
GLStateManager.glMaterial(GL11.GL_FRONT, GL11.GL_AMBIENT, f4b);
GLStateManager.glMaterial(GL11.GL_FRONT, GL11.GL_AMBIENT, floats(0.1F, 0.2F, 0.3F, 0.4F));
final int gen = GLStateManager.getLightingGeneration();
newf4b(0.1F, 0.2F, 0.3F, 0.4F);
GLStateManager.glMaterial(GL11.GL_FRONT, GL11.GL_AMBIENT, f4b);
GLStateManager.glMaterial(GL11.GL_FRONT, GL11.GL_AMBIENT, floats(0.1F, 0.2F, 0.3F, 0.4F));
assertEquals(gen, GLStateManager.getLightingGeneration(), "identical material must not bump lightingGeneration");
newf4b(0.9F, 0.2F, 0.3F, 0.4F);
GLStateManager.glMaterial(GL11.GL_FRONT, GL11.GL_AMBIENT, f4b);
GLStateManager.glMaterial(GL11.GL_FRONT, GL11.GL_AMBIENT, floats(0.9F, 0.2F, 0.3F, 0.4F));
assertEquals(gen + 1, GLStateManager.getLightingGeneration(), "changed material must bump lightingGeneration once");
}
@Test
void lightModelBumpsOnlyOnChange() {
newf4b(0.5F, 0.5F, 0.5F, 1.0F);
GLStateManager.glLightModel(GL11.GL_LIGHT_MODEL_AMBIENT, f4b);
GLStateManager.glLightModel(GL11.GL_LIGHT_MODEL_AMBIENT, floats(0.5F, 0.5F, 0.5F, 1.0F));
final int gen = GLStateManager.getLightingGeneration();
newf4b(0.5F, 0.5F, 0.5F, 1.0F);
GLStateManager.glLightModel(GL11.GL_LIGHT_MODEL_AMBIENT, f4b);
GLStateManager.glLightModel(GL11.GL_LIGHT_MODEL_AMBIENT, floats(0.5F, 0.5F, 0.5F, 1.0F));
assertEquals(gen, GLStateManager.getLightingGeneration(), "identical light model ambient must not bump");
}
@Test
void lightColorBumpsOnlyOnChange() {
newf4b(0.25F, 0.5F, 0.75F, 1.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_DIFFUSE, f4b);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_DIFFUSE, floats(0.25F, 0.5F, 0.75F, 1.0F));
final int gen = GLStateManager.getLightingGeneration();
newf4b(0.25F, 0.5F, 0.75F, 1.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_DIFFUSE, f4b);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_DIFFUSE, floats(0.25F, 0.5F, 0.75F, 1.0F));
assertEquals(gen, GLStateManager.getLightingGeneration(), "identical light diffuse must not bump");
}
@@ -72,17 +59,14 @@ public class GLSM_DirtyTracking_UnitTest {
GLStateManager.glLoadIdentity();
GLStateManager.glTranslatef(1.0F, 2.0F, 3.0F);
newf4b(0.1F, 0.2F, 0.3F, 1.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, f4b);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, floats(0.1F, 0.2F, 0.3F, 1.0F));
final int gen = GLStateManager.getLightingGeneration();
newf4b(0.1F, 0.2F, 0.3F, 1.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, f4b);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, floats(0.1F, 0.2F, 0.3F, 1.0F));
assertEquals(gen, GLStateManager.getLightingGeneration(), "same position under same modelview must not bump");
GLStateManager.glTranslatef(5.0F, 0.0F, 0.0F);
newf4b(0.1F, 0.2F, 0.3F, 1.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, f4b);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, floats(0.1F, 0.2F, 0.3F, 1.0F));
assertEquals(gen + 1, GLStateManager.getLightingGeneration(), "same raw position under changed modelview must bump");
GLStateManager.glPopMatrix();
@@ -128,18 +112,15 @@ public class GLSM_DirtyTracking_UnitTest {
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
newf3b(0.0F, 0.0F, -1.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPOT_DIRECTION, f3b);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPOT_DIRECTION, floats(0.0F, 0.0F, -1.0F));
final int gen = GLStateManager.getLightingGeneration();
GLStateManager.glTranslatef(5.0F, 0.0F, 0.0F);
newf3b(0.0F, 0.0F, -1.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPOT_DIRECTION, f3b);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPOT_DIRECTION, floats(0.0F, 0.0F, -1.0F));
assertEquals(gen, GLStateManager.getLightingGeneration(), "spot direction under translation-only must not bump");
GLStateManager.glRotatef(90.0F, 0.0F, 1.0F, 0.0F);
newf3b(0.0F, 0.0F, -1.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPOT_DIRECTION, f3b);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPOT_DIRECTION, floats(0.0F, 0.0F, -1.0F));
assertEquals(gen + 1, GLStateManager.getLightingGeneration(), "same raw spot direction under changed 3x3 must bump");
GLStateManager.glPopMatrix();
@@ -158,16 +139,4 @@ public class GLSM_DirtyTracking_UnitTest {
assertEquals(0.0F, diffuse.y, "green channel must be 0");
assertEquals(1.0F, diffuse.z, "blue channel must be 1");
}
static void newf4b(float x, float y, float z, float w) {
f4b.clear();
f4b.put(x).put(y).put(z).put(w);
f4b.flip();
}
static void newf3b(float x, float y, float z) {
f3b.clear();
f3b.put(x).put(y).put(z);
f3b.flip();
}
}
@@ -2,6 +2,8 @@ package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.lwjgl.opengl.GL11;
import static com.gtnewhorizons.angelica.util.GLSMUtil.verifyState;
@@ -28,106 +30,26 @@ class GLSM_DisplayList_FormatOptimization_Test {
GLStateManager.glNormal3f(0.0f, 0.0f, 1.0f);
}
@Test
void testPositionOnlyFormat() {
// Create a display list with only position data (no color, texture, brightness, or normals)
// Use GLStateManager for all calls to ensure they go through GLSM (test env doesn't redirect GL11)
private static final float[][] CORNERS = {{0, 0}, {1, 0}, {1, 1}, {0, 1}};
private static final float[][] COLORS = {{1, 1, 1, 1}, {1, 0, 0, 1}, {0, 1, 0, 1}, {0, 0, 1, 1}};
@ParameterizedTest(name = "normals={0}")
@ValueSource(booleans = {false, true})
void colorTextureFormatLeavesLastColorCurrent(boolean normals) {
displayList = GL11.glGenLists(1);
GLStateManager.glNewList(displayList, GL11.GL_COMPILE);
GLStateManager.glBegin(GL11.GL_QUADS);
GLStateManager.glVertex3f(0, 0, 0);
GLStateManager.glVertex3f(1, 0, 0);
GLStateManager.glVertex3f(1, 1, 0);
GLStateManager.glVertex3f(0, 1, 0);
for (int i = 0; i < 4; i++) {
if (normals) GLStateManager.glNormal3f(0, 0, 1);
GLStateManager.glColor4f(COLORS[i][0], COLORS[i][1], COLORS[i][2], COLORS[i][3]);
GLStateManager.glTexCoord2f(CORNERS[i][0], CORNERS[i][1]);
GLStateManager.glVertex3f(CORNERS[i][0], CORNERS[i][1], 0);
}
GLStateManager.glEnd();
GLStateManager.glEndList();
// Call the list to ensure it doesn't crash (verifies compilation succeeded)
GLStateManager.glCallList(displayList);
}
@Test
void testPositionTextureFormat() {
// Create a display list with position and texture coordinates
displayList = GL11.glGenLists(1);
GLStateManager.glNewList(displayList, GL11.GL_COMPILE);
GLStateManager.glBegin(GL11.GL_QUADS);
GLStateManager.glTexCoord2f(0, 0);
GLStateManager.glVertex3f(0, 0, 0);
GLStateManager.glTexCoord2f(1, 0);
GLStateManager.glVertex3f(1, 0, 0);
GLStateManager.glTexCoord2f(1, 1);
GLStateManager.glVertex3f(1, 1, 0);
GLStateManager.glTexCoord2f(0, 1);
GLStateManager.glVertex3f(0, 1, 0);
GLStateManager.glEnd();
GLStateManager.glEndList();
// Call the list to ensure it doesn't crash (verifies compilation succeeded)
GLStateManager.glCallList(displayList);
}
@Test
void testPositionColorTextureFormat() {
// Create a display list with position, color, and texture
displayList = GL11.glGenLists(1);
GLStateManager.glNewList(displayList, GL11.GL_COMPILE);
GLStateManager.glBegin(GL11.GL_QUADS);
GLStateManager.glColor4f(1, 1, 1, 1);
GLStateManager.glTexCoord2f(0, 0);
GLStateManager.glVertex3f(0, 0, 0);
GLStateManager.glColor4f(1, 0, 0, 1);
GLStateManager.glTexCoord2f(1, 0);
GLStateManager.glVertex3f(1, 0, 0);
GLStateManager.glColor4f(0, 1, 0, 1);
GLStateManager.glTexCoord2f(1, 1);
GLStateManager.glVertex3f(1, 1, 0);
GLStateManager.glColor4f(0, 0, 1, 1);
GLStateManager.glTexCoord2f(0, 1);
GLStateManager.glVertex3f(0, 1, 0);
GLStateManager.glEnd();
GLStateManager.glEndList();
// Call the list and verify color is restored to last value (blue)
GLStateManager.glCallList(displayList);
verifyState(GL11.GL_CURRENT_COLOR, new float[]{0f, 0f, 1f, 1f},
"After playback, color should be blue (last color in immediate mode block)");
}
@Test
void testFullFormat() {
// Create a display list with all attributes: position, color, texture, and normal
displayList = GL11.glGenLists(1);
GLStateManager.glNewList(displayList, GL11.GL_COMPILE);
GLStateManager.glBegin(GL11.GL_QUADS);
GLStateManager.glNormal3f(0, 0, 1);
GLStateManager.glColor4f(1, 1, 1, 1);
GLStateManager.glTexCoord2f(0, 0);
GLStateManager.glVertex3f(0, 0, 0);
GLStateManager.glNormal3f(0, 0, 1);
GLStateManager.glColor4f(1, 0, 0, 1);
GLStateManager.glTexCoord2f(1, 0);
GLStateManager.glVertex3f(1, 0, 0);
GLStateManager.glNormal3f(0, 0, 1);
GLStateManager.glColor4f(0, 1, 0, 1);
GLStateManager.glTexCoord2f(1, 1);
GLStateManager.glVertex3f(1, 1, 0);
GLStateManager.glNormal3f(0, 0, 1);
GLStateManager.glColor4f(0, 0, 1, 1);
GLStateManager.glTexCoord2f(0, 1);
GLStateManager.glVertex3f(0, 1, 0);
GLStateManager.glEnd();
GLStateManager.glEndList();
// Call the list and verify color is restored to last value (blue)
GLStateManager.glCallList(displayList);
verifyState(GL11.GL_CURRENT_COLOR, new float[]{0f, 0f, 1f, 1f},
"After playback, color should be blue (last color in immediate mode block)");
@@ -2,6 +2,8 @@ package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
@@ -15,6 +17,23 @@ public class GLSM_DisplayList_PixelStore_Test {
private static final int SRC = 8;
private static final int SUB = 4;
enum Upload {
TEX_SUB_IMAGE_2D {
@Override
void run(ByteBuffer src) {
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, SUB, SUB, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, src);
}
},
TEX_IMAGE_2D {
@Override
void run(ByteBuffer src) {
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, SUB, SUB, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, src);
}
};
abstract void run(ByteBuffer src);
}
@AfterEach
void resetUnpackState() {
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 4);
@@ -96,8 +115,9 @@ public class GLSM_DisplayList_PixelStore_Test {
GLStateManager.glDeleteLists(list, 1);
}
@Test
void stridedTexSubImage2DCompile() {
@ParameterizedTest
@EnumSource(Upload.class)
void stridedUploadCompile(Upload upload) {
final int tex = makeTexture(SUB, ByteBuffer.allocateDirect(SUB * SUB * 4).order(ByteOrder.nativeOrder()));
final ByteBuffer src = makeSource(SRC);
@@ -105,7 +125,7 @@ public class GLSM_DisplayList_PixelStore_Test {
final int list = GLStateManager.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, SUB, SUB, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, src);
upload.run(src);
GLStateManager.glEndList();
assertEquals(0, red(readback(SUB), SUB, 0, 0), "GL_COMPILE must not execute the upload");
@@ -146,58 +166,15 @@ public class GLSM_DisplayList_PixelStore_Test {
GLStateManager.glDeleteTextures(tex);
}
@Test
void stridedTexImage2DCompile() {
final int tex = makeTexture(SUB, ByteBuffer.allocateDirect(SUB * SUB * 4).order(ByteOrder.nativeOrder()));
final ByteBuffer src = makeSource(SRC);
setStridedUnpack();
final int list = GLStateManager.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, SUB, SUB, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, src);
GLStateManager.glEndList();
setUnpack(0, 0, 0);
GLStateManager.glCallList(list);
assertStridedSubImage(readback(SUB));
assertUnpack(0, 0, 0);
GLStateManager.glDeleteLists(list, 1);
GLStateManager.glDeleteTextures(tex);
}
@Test
void defaultRecordedTexSubImage2DReplaysWithDefaults() {
@ParameterizedTest
@EnumSource(Upload.class)
void defaultRecordedUploadReplaysWithDefaults(Upload upload) {
final int tex = makeTexture(SUB, ByteBuffer.allocateDirect(SUB * SUB * 4).order(ByteOrder.nativeOrder()));
final ByteBuffer src = makeSource(SUB);
final int list = GLStateManager.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, SUB, SUB, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, src);
GLStateManager.glEndList();
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
setStridedUnpack();
GLStateManager.glCallList(list);
assertTightSubImage(readback(SUB));
assertEquals(1, GL11.glGetInteger(GL11.GL_UNPACK_ALIGNMENT), "live state must survive replay");
assertUnpack(SRC, 1, 2);
GLStateManager.glDeleteLists(list, 1);
GLStateManager.glDeleteTextures(tex);
}
@Test
void defaultRecordedTexImage2DReplaysWithDefaults() {
final int tex = makeTexture(SUB, ByteBuffer.allocateDirect(SUB * SUB * 4).order(ByteOrder.nativeOrder()));
final ByteBuffer src = makeSource(SUB);
final int list = GLStateManager.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, SUB, SUB, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, src);
upload.run(src);
GLStateManager.glEndList();
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
Loaded 100 of 566 files, more files were not shown because too many files have changed in this diff. Show more