mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-15 01:34:48 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9bbb0537ee | ||
|
|
a019b7eed8 | ||
|
|
1772e9eae2 | ||
|
|
17a2ed7f37 | ||
|
|
34ed1d101b | ||
|
|
7cc9cea337 | ||
|
|
6c5e146f96 | ||
|
|
ea50e0a0b6 | ||
|
|
713509a87b | ||
|
|
102c6cad86 | ||
|
|
8af9340be6 | ||
|
|
fc8d3e8b89 | ||
|
|
e84c8c8d7a | ||
|
|
73d61d385c | ||
|
|
69124e3f2f | ||
|
|
98703a11be | ||
|
|
67ca46a220 | ||
|
|
a037a96fa4 | ||
|
|
ed260a34c4 | ||
|
|
e98c927a93 | ||
|
|
b446968e66 | ||
|
|
32f8eef15b | ||
|
|
2d9d1a45b7 | ||
|
|
fa8842a64b | ||
|
|
69019b9a62 | ||
|
|
482dece0f7 | ||
|
|
ab3bf17696 | ||
|
|
cfc6e12e00 | ||
|
|
8537835d0c | ||
|
|
8e1809622c | ||
|
|
f81ee4b03a | ||
|
|
49db8e111d | ||
|
|
bd9a3c3c5c | ||
|
|
01ff612d1d | ||
|
|
0cae2be1f6 | ||
|
|
85d0641a73 | ||
|
|
46f8157087 | ||
|
|
d3dee841e8 | ||
|
|
3d3d0f9e18 | ||
|
|
23aa04361d | ||
|
|
69428c12f6 | ||
|
|
3978be161d | ||
|
|
2ed838acf1 | ||
|
|
d29a8278cd | ||
|
|
2f1f380e8e | ||
|
|
990dadc03f | ||
|
|
1946c2ad65 | ||
|
|
bfb66d84ee | ||
|
|
cae4fea2bb | ||
|
|
78abc05711 |
No files matched your search
@@ -1,14 +1,14 @@
|
||||
# Description of your *PR* and other info
|
||||
<!-- Please include a summary of the change -->
|
||||
## Summary
|
||||
<!-- Briefly describe what this PR changes and why. -->
|
||||
<!-- Any details that you think are important to review this PR? -->
|
||||
<!-- Are there any Issues / PRs related to this one? -->
|
||||
|
||||
<!-- Add a screenshot or a video demonstration for new features -->
|
||||
|
||||
<details open><summary>Template</summary> <!-- OPEN is intentional -->
|
||||
|
||||
### Before submitting a *pull request* make sure you have:
|
||||
- [ ] Read the [AI Contribution Policy](https://github.com/GTNewHorizons/Angelica/blob/master/AI_POLICY.md)
|
||||
- [ ] At least [skimmed](https://github.com/GTNewHorizons/Angelica/search?q=is%3Apr&type=Issues) the tracker for similar pull requests
|
||||
|
||||
</details>
|
||||
## Checklist
|
||||
- [ ] I have tested this PR in DevEnv
|
||||
- [ ] I have tested this PR in Fullpack
|
||||
- [ ] This PR is in compliance with the [GTNH AI Policy](https://github.com/GTNewHorizons/GTNH-Dev-Doc/blob/master/AI_POLICY.md)
|
||||
- [ ] I have at least [skimmed](https://github.com/GTNewHorizons/Angelica/search?q=is%3Apr&type=Issues) the tracker for similar pull requests
|
||||
- [ ] This PR requires another PR in order to merge
|
||||
@@ -10,4 +10,41 @@ on:
|
||||
jobs:
|
||||
build-and-test:
|
||||
uses: GTNewHorizons/GTNH-Actions-Workflows/.github/workflows/build-and-test.yml@master
|
||||
with:
|
||||
disable-spotless-pr: true
|
||||
java-distribution: temurin
|
||||
cache-rfg: true
|
||||
secrets: inherit
|
||||
|
||||
sdl-gpu-tests:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20
|
||||
concurrency:
|
||||
group: sdl-gpu-tests-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- name: Install a software Vulkan driver
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y xvfb mesa-vulkan-drivers
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: |
|
||||
21
|
||||
25
|
||||
- uses: gradle/actions/setup-gradle@v5
|
||||
- name: Run SDL-GPU tests
|
||||
run: xvfb-run -a --server-args="-screen 0 1366x768x24" ./gradlew --build-cache --stacktrace --continue :sdl-gpu:glsmSdlTest :sdl-gpu:sdlGpuDeviceTest
|
||||
- name: Check LWJGL did not extract natives to the shared temp dir
|
||||
run: test ! -e "/tmp/lwjgl_$USER"
|
||||
- name: Attach SDL-GPU test results
|
||||
if: ${{ !cancelled() }}
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: sdl-gpu-test-results
|
||||
path: |
|
||||
sdl-gpu/build/test-results/
|
||||
sdl-gpu/build/reports/tests/
|
||||
sdl-gpu/hs_err_pid*.log
|
||||
@@ -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)
|
||||
|
||||
+58
-2
@@ -89,8 +89,11 @@ tasks.register<Copy>("copyDependencies") {
|
||||
val embedOnly: Configuration by configurations
|
||||
val shadowImplementation: Configuration by configurations
|
||||
|
||||
val dependencyClasspath: FileCollection = sourceSets.main.get().runtimeClasspath - sourceSets.main.get().output
|
||||
|
||||
val downgradeEmbedOnlyForTest by tasks.registering(DowngradeFiles::class) {
|
||||
inputCollection = embedOnly
|
||||
classpath = dependencyClasspath
|
||||
outputs.dir(temporaryDir)
|
||||
}
|
||||
|
||||
@@ -104,6 +107,7 @@ val stripModuleInfoFromShadow by tasks.registering(Jar::class) {
|
||||
|
||||
val downgradeShadowImplForTest by tasks.registering(DowngradeFiles::class) {
|
||||
inputCollection = files(stripModuleInfoFromShadow.map { it.archiveFile })
|
||||
classpath = dependencyClasspath
|
||||
outputs.dir(temporaryDir)
|
||||
}
|
||||
|
||||
@@ -118,6 +122,7 @@ val glsmTestFixtures: Configuration by configurations.creating {
|
||||
|
||||
val downgradeGlsmTestFixturesForTest by tasks.registering(DowngradeFiles::class) {
|
||||
inputCollection = glsmTestFixtures
|
||||
classpath = dependencyClasspath
|
||||
outputs.dir(temporaryDir)
|
||||
}
|
||||
|
||||
@@ -159,9 +164,38 @@ if (isMacOs && providers.gradleProperty("clientJvmVendor").isPresent) {
|
||||
}
|
||||
}
|
||||
|
||||
// Forge's asm-debug-all 5.0.3 cannot read the Java 25 class files ArchScan walks; this ASM has to resolve first.
|
||||
val archScanAsm: Configuration by configurations.creating {
|
||||
isCanBeConsumed = false
|
||||
isCanBeResolved = true
|
||||
}
|
||||
|
||||
val glsmRedirectAgent: Configuration by configurations.creating {
|
||||
isCanBeConsumed = false
|
||||
isCanBeResolved = true
|
||||
}
|
||||
|
||||
val glsmMutableStaticsAgent: Configuration by configurations.creating {
|
||||
isCanBeConsumed = false
|
||||
isCanBeResolved = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
glsmRedirectAgent(project(":glsm", "redirectAgent"))
|
||||
glsmMutableStaticsAgent(project(":glsm", "mutableStaticsAgent"))
|
||||
archScanAsm(libs.asm.tree.test)
|
||||
archScanAsm(libs.asm.commons.test)
|
||||
}
|
||||
|
||||
sourceSets.test {
|
||||
compileClasspath = archScanAsm + compileClasspath
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform { excludeTags = setOf("gl-core") }
|
||||
configureAngelicaJava8()
|
||||
classpath = archScanAsm + classpath
|
||||
javaAgent(glsmMutableStaticsAgent)
|
||||
jvmArgs("-Dceleritas.lwjglService=org.taumc.celeritas.lwjgl.HeadlessTestLWJGLService")
|
||||
dependsOn(":glsm:classes", ":lwjgl3-backend:classes", ":sdl-gpu:classes")
|
||||
}
|
||||
@@ -169,6 +203,13 @@ tasks.test {
|
||||
val downgradeRootMainClasses = tasks.named<DowngradeFiles>("downgradeMainClasses")
|
||||
val downgradeRootTestClasses = tasks.named<DowngradeFiles>("downgradeTestClasses")
|
||||
|
||||
// JVMDowngrader only declares outputs for inputs that exist at configuration, so a clean tree has none.
|
||||
for (name in listOf("main", "mixin", "test")) {
|
||||
tasks.named<DowngradeFiles>("downgrade${name.replaceFirstChar { it.uppercase() }}Classes") {
|
||||
sourceSets[name].output.classesDirs.files.forEach { outputs.dir(temporaryDir.resolve(it.name)) }
|
||||
}
|
||||
}
|
||||
|
||||
val glCoreTest by tasks.registering(Test::class) {
|
||||
description = "Runs the gl-core tests in their own JVM with a real GL context."
|
||||
group = "verification"
|
||||
@@ -184,11 +225,26 @@ val glCoreTest by tasks.registering(Test::class) {
|
||||
.plus(downgraded(downgradeRootMainClasses))
|
||||
.plus(sourceSets["test"].runtimeClasspath.minus(rawMain).minus(rawTest))
|
||||
|
||||
javaAgent(glsmRedirectAgent)
|
||||
|
||||
configureAngelicaJava8()
|
||||
mustRunAfter(tasks.test)
|
||||
}
|
||||
|
||||
tasks.test { finalizedBy(glCoreTest) }
|
||||
val verifyTestsRan by tasks.registering
|
||||
listOf(tasks.test, glCoreTest).forEach { testTask ->
|
||||
val name = testTask.name
|
||||
val verify = tasks.register("verify${name.replaceFirstChar { it.uppercase() }}Ran") {
|
||||
val resultsDir = layout.buildDirectory.dir("test-results/$name")
|
||||
dependsOn(testTask)
|
||||
doLast {
|
||||
val dir = resultsDir.get().asFile
|
||||
val xmls = dir.listFiles { f -> f.name.startsWith("TEST-") && f.name.endsWith(".xml") } ?: emptyArray()
|
||||
check(xmls.isNotEmpty()) { ":$name produced no TEST-*.xml in $dir - test task likely went NO-SOURCE" }
|
||||
}
|
||||
}
|
||||
verifyTestsRan.configure { dependsOn(verify) }
|
||||
}
|
||||
tasks.check { dependsOn(verifyTestsRan) }
|
||||
|
||||
apply(from = "gradle/angelica-shadow-common.gradle.kts")
|
||||
apply(from = "experimental-surround/surround.root.gradle.kts")
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import org.gradle.api.file.FileCollection
|
||||
import org.gradle.api.tasks.testing.Test
|
||||
import org.gradle.process.CommandLineArgumentProvider
|
||||
|
||||
fun Test.javaAgent(jar: FileCollection) {
|
||||
inputs.files(jar).withPropertyName("javaAgent")
|
||||
jvmArgumentProviders.add(CommandLineArgumentProvider { listOf("-javaagent:${jar.singleFile.path}") })
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.file.ArchiveOperations
|
||||
import org.gradle.api.tasks.SourceSetContainer
|
||||
import org.gradle.api.tasks.Sync
|
||||
import org.gradle.api.tasks.TaskProvider
|
||||
import org.gradle.api.tasks.testing.Test
|
||||
import org.gradle.kotlin.dsl.get
|
||||
import org.gradle.kotlin.dsl.register
|
||||
import org.gradle.kotlin.dsl.the
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class UnpackLwjgl3Natives : Sync() {
|
||||
@get:Inject
|
||||
abstract val archives: ArchiveOperations
|
||||
}
|
||||
|
||||
// LWJGL extracts jar-packed natives to a shared temp dir with no cross-process safety; plain files it loads in place.
|
||||
fun Project.registerLwjgl3NativesUnpack(): TaskProvider<UnpackLwjgl3Natives> {
|
||||
val jars = the<SourceSetContainer>()["test"].runtimeClasspath.filter {
|
||||
it.name.startsWith("lwjgl") && it.name.contains("-natives-") && !it.name.startsWith("lwjgl-platform-")
|
||||
}
|
||||
return tasks.register<UnpackLwjgl3Natives>("unpackLwjgl3Natives") {
|
||||
val archives = archives
|
||||
from(jars.elements.map { files -> files.map { archives.zipTree(it) } })
|
||||
exclude("META-INF/**")
|
||||
into(layout.buildDirectory.dir("lwjgl3-natives"))
|
||||
}
|
||||
}
|
||||
|
||||
fun Test.loadLwjgl3NativesFrom(unpack: TaskProvider<UnpackLwjgl3Natives>) {
|
||||
classpath = project.files(unpack) + classpath
|
||||
}
|
||||
@@ -62,9 +62,10 @@ DependencyHandlerScope.of(dependencies).apply {
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:BetterLoadingScreen:1.7.9-GTNH:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:BetterQuesting:3.8.72-GTNH:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:BuildCraft:7.1.63:dev")
|
||||
compileOnly("com.github.GTNewHorizons:Botania:1.13.32-GTNH:dev") { isTransitive = false }
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Botania:1.13.32-GTNH:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:LogisticsPipes:1.5.32-GTNH:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:OpenComputers:1.12.55-GTNH:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:OpenComputers:1.12.64-GTNH:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Mobs-Info:0.5.18-GTNH:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:IguanaTweaksTConstruct:2.7.10:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:OpenBlocks:1.12.18-GTNH:dev")
|
||||
@@ -204,7 +205,6 @@ afterEvaluate {
|
||||
testImplementation(libs.junit.platform.reporting)
|
||||
testImplementation(libs.junit.platform.launcher)
|
||||
|
||||
testImplementation(libs.archunit.junit5)
|
||||
testImplementation(libs.retrofuturabootstrap) { isTransitive = false }
|
||||
|
||||
glsmTestFixtures(testFixtures(project(":glsm")) as ModuleDependency) { isTransitive = false }
|
||||
|
||||
@@ -48,6 +48,7 @@ repositories {
|
||||
}
|
||||
|
||||
tasks.withType<DowngradeFiles>().configureEach {
|
||||
outputs.cacheIf { true }
|
||||
downgradeTo.set(JavaVersion.VERSION_1_8)
|
||||
multiReleaseOriginal.set(false)
|
||||
multiReleaseVersions.set(emptySet())
|
||||
|
||||
@@ -158,6 +158,7 @@ dependencies {
|
||||
testFixturesApi(platform(libs.junit.bom))
|
||||
testFixturesApi(libs.junit.jupiter.api)
|
||||
testFixturesApi(libs.lwjgl2)
|
||||
testFixturesCompileOnly(libs.asm.tree.test)
|
||||
|
||||
// Test
|
||||
testImplementation(sourceSets["stubs"].output)
|
||||
@@ -183,6 +184,7 @@ dependencies {
|
||||
}
|
||||
|
||||
tasks.withType<DowngradeFiles>().configureEach {
|
||||
outputs.cacheIf { true }
|
||||
downgradeTo.set(JavaVersion.VERSION_1_8)
|
||||
multiReleaseOriginal.set(false)
|
||||
multiReleaseVersions.set(emptySet())
|
||||
@@ -256,6 +258,13 @@ fun Test.configureGlsmJava8() {
|
||||
}
|
||||
}
|
||||
|
||||
val mutableStaticsAgentJar by tasks.registering(Jar::class) {
|
||||
archiveClassifier = "mutable-statics-agent"
|
||||
manifest {
|
||||
attributes("Premain-Class" to "com.gtnewhorizons.angelica.glsm.testutil.MutableStatics")
|
||||
}
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform {
|
||||
excludeTags = GL_TASK_TAGS.values.toSet() + "lwjgl3"
|
||||
@@ -264,6 +273,29 @@ tasks.test {
|
||||
excludeTestsMatching("com.gtnewhorizons.angelica.glsm.shader.SpirvShaderTranslator*Test")
|
||||
}
|
||||
configureGlsmJava8()
|
||||
javaAgent(files(mutableStaticsAgentJar))
|
||||
}
|
||||
|
||||
val redirectAgentJar by tasks.registering(Jar::class) {
|
||||
archiveClassifier = "redirect-agent"
|
||||
manifest {
|
||||
attributes("Premain-Class" to "com.gtnewhorizons.angelica.glsm.GlsmRedirectAgent")
|
||||
}
|
||||
}
|
||||
|
||||
val redirectAgent by configurations.creating {
|
||||
isCanBeConsumed = true
|
||||
isCanBeResolved = false
|
||||
}
|
||||
|
||||
val mutableStaticsAgent by configurations.creating {
|
||||
isCanBeConsumed = true
|
||||
isCanBeResolved = false
|
||||
}
|
||||
|
||||
artifacts {
|
||||
add(redirectAgent.name, redirectAgentJar)
|
||||
add(mutableStaticsAgent.name, mutableStaticsAgentJar)
|
||||
}
|
||||
|
||||
val glTestTasks = GL_TASK_TAGS.map { (taskName, tag) ->
|
||||
@@ -274,6 +306,7 @@ val glTestTasks = GL_TASK_TAGS.map { (taskName, tag) ->
|
||||
includeTags = setOf(tag)
|
||||
}
|
||||
configureGlsmJava8()
|
||||
javaAgent(files(redirectAgentJar))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -302,6 +335,8 @@ val verifyTestsRan by tasks.registering {
|
||||
}
|
||||
tasks.check { dependsOn(verifyTestsRan) }
|
||||
|
||||
val unpackLwjgl3Natives = registerLwjgl3NativesUnpack()
|
||||
|
||||
val spirvTest by tasks.registering(Test::class) {
|
||||
description = "Runs SpirvShaderTranslator* tests against native shaderc + spvc on Java 21."
|
||||
group = "verification"
|
||||
@@ -330,6 +365,7 @@ val spirvTest by tasks.registering(Test::class) {
|
||||
"-Djava.library.path=${extractNatives3.get().property("destinationFolder").let { (it as DirectoryProperty).asFile.get().path }}",
|
||||
"--enable-native-access=ALL-UNNAMED"
|
||||
)
|
||||
loadLwjgl3NativesFrom(unpackLwjgl3Natives)
|
||||
|
||||
testLogging {
|
||||
showStandardStreams = true
|
||||
|
||||
@@ -69,7 +69,13 @@ public final class SystemProperties {
|
||||
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);
|
||||
public static final boolean FLYBY_DEBUG_HUD = Boolean.getBoolean("angelica.flyby.debugHud");
|
||||
public static final Boolean FLYBY_DEBUG_HUD = parseBooleanOverride("angelica.flyby.debugHud");
|
||||
public static final String FLYBY_DEBUG_PROFILE = System.getProperty("angelica.flyby.debugProfile", "");
|
||||
public static final String FLYBY_DEBUG_ENTRIES = System.getProperty("angelica.flyby.debugEntries", "");
|
||||
public static final Boolean FLYBY_DEBUG_FLAT = parseBooleanOverride("angelica.flyby.debugFlat");
|
||||
public static final Boolean FLYBY_HUD_CACHING = parseBooleanOverride("angelica.flyby.hudCaching");
|
||||
public static final Boolean FLYBY_FPS_GRAPH = parseBooleanOverride("angelica.flyby.fpsGraph");
|
||||
public static final boolean FLYBY_DEBUG_DUMP = Boolean.getBoolean("angelica.flyby.debugDump");
|
||||
public static final String FLYBY_CRASH_TEST = System.getProperty("angelica.flyby.crashTest", "");
|
||||
|
||||
// Debug
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.FragmentKey;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.Uniforms;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.FogStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.LightStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
|
||||
@@ -66,7 +68,13 @@ public class CompatUniformManager {
|
||||
static final int LOC_IRIS_MODELVIEW_INVERSE = LOC_MAT_BASE + MAT_FIELDS; // 48
|
||||
static final int LOC_IRIS_PROJECTION_INVERSE = LOC_IRIS_MODELVIEW_INVERSE + 1; // 49
|
||||
|
||||
static final int LOC_COUNT = LOC_IRIS_PROJECTION_INVERSE + 1; // 50
|
||||
static final int LOC_FOG_MODE = LOC_IRIS_PROJECTION_INVERSE + 1; // 50
|
||||
static final int LOC_FOG_PARAMS = LOC_FOG_MODE + 1; // 51
|
||||
static final int LOC_FOG_DISTANCE_MODE = LOC_FOG_PARAMS + 1; // 52
|
||||
|
||||
static final int LOC_MODELVIEW_PROJECTION = LOC_FOG_DISTANCE_MODE + 1; // 53
|
||||
|
||||
static final int LOC_COUNT = LOC_MODELVIEW_PROJECTION + 1; // 54
|
||||
|
||||
private static final String[] LIGHT_FIELD_NAMES = {
|
||||
"ambient", "diffuse", "specular", "position", "halfVector",
|
||||
@@ -84,6 +92,7 @@ public class CompatUniformManager {
|
||||
UNIFORM_NAMES[LOC_MODELVIEW] = "angelica_ModelViewMatrix";
|
||||
UNIFORM_NAMES[LOC_MODELVIEW_INVERSE] = "angelica_ModelViewMatrixInverse";
|
||||
UNIFORM_NAMES[LOC_PROJECTION] = "angelica_ProjectionMatrix";
|
||||
UNIFORM_NAMES[LOC_MODELVIEW_PROJECTION] = "angelica_ModelViewProjectionMatrix";
|
||||
UNIFORM_NAMES[LOC_PROJECTION_INVERSE] = "angelica_ProjectionMatrixInverse";
|
||||
UNIFORM_NAMES[LOC_NORMAL] = "angelica_NormalMatrix";
|
||||
UNIFORM_NAMES[LOC_LIGHTMAP_TEXTURE_MATRIX] = "angelica_LightmapTextureMatrix";
|
||||
@@ -98,6 +107,9 @@ public class CompatUniformManager {
|
||||
UNIFORM_NAMES[LOC_IRIS_TEXTURE_MATRIX] = "iris_TextureMatrix";
|
||||
UNIFORM_NAMES[LOC_IRIS_MODELVIEW_INVERSE] = "iris_ModelViewMatrixInverse";
|
||||
UNIFORM_NAMES[LOC_IRIS_PROJECTION_INVERSE] = "iris_ProjectionMatrixInverse";
|
||||
UNIFORM_NAMES[LOC_FOG_MODE] = "angelica_FogMode";
|
||||
UNIFORM_NAMES[LOC_FOG_PARAMS] = "angelica_FogParams";
|
||||
UNIFORM_NAMES[LOC_FOG_DISTANCE_MODE] = "angelica_FogDistanceMode";
|
||||
UNIFORM_NAMES[LOC_ALPHA_TEST_REF] = "angelica_currentAlphaTest";
|
||||
UNIFORM_NAMES[LOC_SCENE_COLOR] = "angelica_SceneColor";
|
||||
UNIFORM_NAMES[LOC_CLIP_PLANES] = "angelica_ClipPlane[0]";
|
||||
@@ -120,7 +132,14 @@ public class CompatUniformManager {
|
||||
GLContextState owner;
|
||||
int lastMvGen = -1, lastMvLinearGen = -1, lastProjGen = -1, lastTexMatGen = -1;
|
||||
int lastFragmentGen = -1, lastLightingGen = -1, lastClipPlaneGen = -1;
|
||||
ProgramUniforms(int[] locs) { this.locs = locs; }
|
||||
int lastFogMode = -1, lastFogDistanceMode = -1;
|
||||
final boolean lights;
|
||||
ProgramUniforms(int[] locs) {
|
||||
this.locs = locs;
|
||||
boolean any = false;
|
||||
for (int i = LOC_LIGHT_BASE; i < LOC_MAT_BASE; i++) any |= locs[i] != -1;
|
||||
this.lights = any;
|
||||
}
|
||||
}
|
||||
|
||||
// Reusable NIO buffers for upload
|
||||
@@ -186,6 +205,7 @@ public class CompatUniformManager {
|
||||
pu.owner = glCtx;
|
||||
pu.lastMvGen = pu.lastMvLinearGen = pu.lastProjGen = pu.lastTexMatGen = -1;
|
||||
pu.lastFragmentGen = pu.lastLightingGen = pu.lastClipPlaneGen = -1;
|
||||
pu.lastFogMode = pu.lastFogDistanceMode = -1;
|
||||
}
|
||||
|
||||
// Matrix uniforms — skip if this program's storage already holds the current generation
|
||||
@@ -212,12 +232,15 @@ public class CompatUniformManager {
|
||||
uploadFragmentUniforms(locs);
|
||||
}
|
||||
|
||||
pu.lastFogMode = uploadIfChanged(locs[LOC_FOG_MODE], FragmentKey.activeFogMode(glCtx), pu.lastFogMode);
|
||||
pu.lastFogDistanceMode = uploadIfChanged(locs[LOC_FOG_DISTANCE_MODE], glCtx.fogState.getFogDistanceMode(), pu.lastFogDistanceMode);
|
||||
|
||||
// Lighting-derived uniforms (scene color, light sources, material)
|
||||
final int litGen = glCtx.lightingGeneration;
|
||||
if (litGen != pu.lastLightingGen) {
|
||||
pu.lastLightingGen = litGen;
|
||||
if (locs[LOC_SCENE_COLOR] != -1) uploadSceneColor(locs);
|
||||
uploadLightSources(locs);
|
||||
if (pu.lights) uploadLightSources(locs);
|
||||
uploadMaterial(locs);
|
||||
}
|
||||
|
||||
@@ -262,6 +285,11 @@ public class CompatUniformManager {
|
||||
}
|
||||
}
|
||||
|
||||
if ((mvChanged || projChanged) && locs[LOC_MODELVIEW_PROJECTION] != -1) {
|
||||
Uniforms.modelViewProjection(proj, mv, scratchMatrix).get(mat4Buf);
|
||||
RENDER_BACKEND.uniformMatrix4(locs[LOC_MODELVIEW_PROJECTION], false, mat4Buf);
|
||||
}
|
||||
|
||||
if (mvLinearChanged) {
|
||||
// Normal Matrix (inverse-transpose of upper-left 3x3 of ModelView)
|
||||
if (locs[LOC_NORMAL] != -1 || locs[LOC_IRIS_NORMAL] != -1) {
|
||||
@@ -324,6 +352,11 @@ public class CompatUniformManager {
|
||||
}
|
||||
}
|
||||
|
||||
private static int uploadIfChanged(int location, int value, int last) {
|
||||
if (location != -1 && value != last) RENDER_BACKEND.uniform1i(location, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
private static void uploadFragmentUniforms(int[] locs) {
|
||||
// Fog uniforms
|
||||
FogStateStack fog = GLStateManager.getFogState();
|
||||
@@ -336,6 +369,9 @@ public class CompatUniformManager {
|
||||
if (locs[LOC_FOG_END] != -1) {
|
||||
RENDER_BACKEND.uniform1f(locs[LOC_FOG_END], fog.getEnd());
|
||||
}
|
||||
if (locs[LOC_FOG_PARAMS] != -1) {
|
||||
RENDER_BACKEND.uniform4f(locs[LOC_FOG_PARAMS], fog.linearScale(), fog.linearBias(), fog.expScale(), fog.exp2Scale());
|
||||
}
|
||||
if (locs[LOC_FOG_COLOR] != -1) {
|
||||
vec4Buf.clear();
|
||||
vec4Buf.put((float) fog.getFogColor().x).put((float) fog.getFogColor().y).put((float) fog.getFogColor().z).put(fog.getFogAlpha());
|
||||
@@ -372,8 +408,6 @@ public class CompatUniformManager {
|
||||
}
|
||||
|
||||
private static void uploadLightSources(int[] locs) {
|
||||
// Early exit: if the first light field isn't present, no lighting uniforms exist
|
||||
if (locs[LOC_LIGHT_BASE] == -1) return;
|
||||
final LightStateStack[] lights = GLStateManager.getLightDataStates();
|
||||
for (int li = 0; li < 2; li++) {
|
||||
final int base = LOC_LIGHT_BASE + li * LIGHT_FIELDS;
|
||||
|
||||
@@ -1025,7 +1025,7 @@ public class DisplayListManager {
|
||||
}
|
||||
|
||||
final DisplayListCommand[] complexObjects = commandBuffer.getComplexObjects();
|
||||
compiled = new CompiledDisplayList(commandBuffer.finish(), complexObjects, compiledBuffers, indexedBatches);
|
||||
compiled = new CompiledDisplayList(commandBuffer.finish(), complexObjects, compiledBuffers, indexedBatches, GLStateManager.ctx());
|
||||
currentRecorder = null;
|
||||
} catch (Exception e) {
|
||||
GLStateManager.LOGGER.error("Encountered a fatal issue while trying to compile a display list.");
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.states.AlphaState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.BlendState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.Color4;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ColorMask;
|
||||
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
||||
import org.joml.Vector4f;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
public final class ExitState {
|
||||
public static final int POLYGON_OFFSET_ENABLE_WRITTEN = 1;
|
||||
public static final int POLYGON_OFFSET_VALUES_WRITTEN = 2;
|
||||
final Values entry = new Values();
|
||||
final Values value = new Values();
|
||||
final MaterialState frontMaterial = new MaterialState(GL11.GL_FRONT);
|
||||
final MaterialState backMaterial = new MaterialState(GL11.GL_BACK);
|
||||
long writes, dependencies;
|
||||
boolean valid;
|
||||
int textureDeleteCount;
|
||||
|
||||
public void clear() {
|
||||
valid = false;
|
||||
writes = dependencies = 0;
|
||||
}
|
||||
|
||||
static final class Values {
|
||||
final Color4 color = new Color4();
|
||||
final Vector3f normal = new Vector3f();
|
||||
final Vector4f texCoord = new Vector4f();
|
||||
final Vector4f shaderColor = new Vector4f();
|
||||
final Vector4f overlayColor = new Vector4f();
|
||||
final BlendState blend = new BlendState();
|
||||
final AlphaState alpha = new AlphaState();
|
||||
final ColorMask colorMask = new ColorMask();
|
||||
final Matrix4f textureMatrix = new Matrix4f();
|
||||
long enabled;
|
||||
int activeUnit, clientActiveUnit, matrixMode, texture, depthFunction, cullFace, frontFace, shadeModel;
|
||||
int materialFace, materialParameter;
|
||||
boolean depthMask;
|
||||
float lightmapX, lightmapY, offsetFactor, offsetUnits, offsetClamp;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,597 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.DeferredAlphaHandler;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.DeferredBlendHandler;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.DeferredDepthColorHandler;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.CowStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2LongOpenHashMap;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Matrix4fStack;
|
||||
import org.joml.Vector4f;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Records the GL state a block of rendering code leaves behind, so a later run of the same block can be skipped and
|
||||
* its exit state applied instead.
|
||||
*/
|
||||
final class ExitStateCapture {
|
||||
// Fields 0..12 are the capabilities, in CAPS order. FRONT_* and BACK_* are four contiguous fields each, ordered like
|
||||
// vector(): apply() and finish() address them as 15L << FRONT_AMBIENT and (field - FRONT_AMBIENT) & 3.
|
||||
static final int BLEND = 0, ALPHA = 1, DEPTH = 2, CULL = 3, LIGHTING = 4, NORMALIZE = 5, RESCALE = 6, LIGHT0 = 7, LIGHT1 = 8, TEXTURE0 = 9, TEXTURE1 = 10, OFFSET = 11, COLOR_MATERIAL = 12, COLOR = 13, NORMAL = 14, TEXCOORD = 15,
|
||||
LIGHTMAP = 16, SHADER_COLOR = 17, BLEND_FUNC = 18, ALPHA_FUNC = 19, DEPTH_FUNC = 20, DEPTH_MASK = 21, COLOR_MASK = 22, CULL_FACE = 23, FRONT_FACE = 24, SHADE_MODEL = 25, ACTIVE_UNIT = 26, TEXTURE_BINDING = 27, MATRIX_MODE = 28,
|
||||
TEXTURE_MATRIX = 29, OFFSET_VALUES = 30, COLOR_MATERIAL_SETTINGS = 31, FRONT_AMBIENT = 32, FRONT_DIFFUSE = 33, FRONT_SPECULAR = 34, FRONT_EMISSION = 35, BACK_AMBIENT = 36, BACK_DIFFUSE = 37, BACK_SPECULAR = 38,
|
||||
BACK_EMISSION = 39, OVERLAY_COLOR = 40, CLIENT_ACTIVE_UNIT = 41;
|
||||
private static final int FIELD_COUNT = 42;
|
||||
private static final long IMPLICIT_INPUTS = bits(ACTIVE_UNIT, MATRIX_MODE);
|
||||
private static final long MATERIAL_BAKE_INPUTS = bits(COLOR, COLOR_MATERIAL_SETTINGS);
|
||||
private static final long DRAW_INPUTS = bits(CULL, TEXTURE0, TEXTURE1, COLOR, LIGHTMAP, OVERLAY_COLOR, CULL_FACE, FRONT_FACE, TEXTURE_BINDING);
|
||||
private static final long VERTEX_INPUTS = bits(NORMAL, TEXCOORD);
|
||||
private static final long MATERIAL_INPUTS = bits(BLEND, ALPHA, DEPTH, BLEND_FUNC, ALPHA_FUNC, DEPTH_FUNC, DEPTH_MASK, COLOR_MASK);
|
||||
private static final int[] CAPS = {GL11.GL_BLEND, GL11.GL_ALPHA_TEST, GL11.GL_DEPTH_TEST, GL11.GL_CULL_FACE, GL11.GL_LIGHTING, GL11.GL_NORMALIZE, GL12.GL_RESCALE_NORMAL, GL11.GL_LIGHT0, GL11.GL_LIGHT1, GL11.GL_TEXTURE_2D,
|
||||
GL11.GL_TEXTURE_2D, GL11.GL_POLYGON_OFFSET_FILL, GL11.GL_COLOR_MATERIAL};
|
||||
|
||||
private static final AtomicInteger openCaptures = new AtomicInteger();
|
||||
|
||||
private final GLContextState context;
|
||||
private final Matrix4f modelView = new Matrix4f();
|
||||
private final long[] attribWrites = new long[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
private final long[] matrixWrites = new long[GLStateManager.MAX_TEXTURE_STACK_DEPTH];
|
||||
private final boolean[] matrixInherited = new boolean[GLStateManager.MAX_TEXTURE_STACK_DEPTH];
|
||||
private final ExitState.Values current = new ExitState.Values();
|
||||
private final Reference2LongOpenHashMap<StateSet> restoredFieldsBySet = new Reference2LongOpenHashMap<>();
|
||||
private ExitState result;
|
||||
private int suspended, attribDepth, modelViewDepth, textureDepth, entryOverrides;
|
||||
private int filterTexture, filterMin, filterMag;
|
||||
private float filterAniso;
|
||||
private boolean filterOperation;
|
||||
private boolean textureInherited;
|
||||
|
||||
ExitStateCapture(GLContextState context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
static boolean anyOpen() {
|
||||
return openCaptures.get() != 0;
|
||||
}
|
||||
|
||||
private static boolean blendWritesReachVanilla() {
|
||||
final DeferredBlendHandler handler = GLSMHooks.blendHandler;
|
||||
return handler == null || !handler.isOverrideHeld() || handler.isBlendLocked();
|
||||
}
|
||||
|
||||
private static boolean alphaWritesReachVanilla() {
|
||||
final DeferredAlphaHandler handler = GLSMHooks.alphaHandler;
|
||||
return handler == null || !handler.isOverrideHeld() || handler.isAlphaTestLocked();
|
||||
}
|
||||
|
||||
private static boolean depthColorWritesReachVanilla() {
|
||||
final DeferredDepthColorHandler handler = GLSMHooks.depthColorHandler;
|
||||
return handler == null || !handler.isOverrideHeld() || handler.isDepthColorLocked();
|
||||
}
|
||||
|
||||
private static int overridesInControl() {
|
||||
return (blendWritesReachVanilla() ? 0 : 1) | (alphaWritesReachVanilla() ? 0 : 2) | (depthColorWritesReachVanilla() ? 0 : 4);
|
||||
}
|
||||
|
||||
private static boolean same(int field, ExitState.Values a, ExitState.Values b) {
|
||||
return switch (field) {
|
||||
case COLOR -> a.color.sameAs(b.color);
|
||||
case NORMAL -> a.normal.equals(b.normal);
|
||||
case TEXCOORD -> a.texCoord.equals(b.texCoord);
|
||||
case LIGHTMAP -> a.lightmapX == b.lightmapX && a.lightmapY == b.lightmapY;
|
||||
case OVERLAY_COLOR -> a.overlayColor.equals(b.overlayColor);
|
||||
case BLEND_FUNC -> a.blend.sameAs(b.blend);
|
||||
case ALPHA_FUNC -> a.alpha.sameAs(b.alpha);
|
||||
case DEPTH_FUNC -> a.depthFunction == b.depthFunction;
|
||||
case DEPTH_MASK -> a.depthMask == b.depthMask;
|
||||
case COLOR_MASK -> a.colorMask.sameAs(b.colorMask);
|
||||
case CULL_FACE -> a.cullFace == b.cullFace;
|
||||
case FRONT_FACE -> a.frontFace == b.frontFace;
|
||||
case ACTIVE_UNIT -> a.activeUnit == b.activeUnit;
|
||||
case TEXTURE_BINDING -> a.texture == b.texture;
|
||||
case MATRIX_MODE -> a.matrixMode == b.matrixMode;
|
||||
case TEXTURE_MATRIX -> a.textureMatrix.equals(b.textureMatrix);
|
||||
case COLOR_MATERIAL_SETTINGS -> a.materialFace == b.materialFace && a.materialParameter == b.materialParameter;
|
||||
default -> throw new IllegalArgumentException("Unsupported renderer state dependency " + field);
|
||||
};
|
||||
}
|
||||
|
||||
private static Vector4f vector(MaterialState state, int component) {
|
||||
return switch (component) {
|
||||
case 0 -> state.ambient;
|
||||
case 1 -> state.diffuse;
|
||||
case 2 -> state.specular;
|
||||
default -> state.emission;
|
||||
};
|
||||
}
|
||||
|
||||
private static boolean has(ExitState state, int field) {
|
||||
return has(state.writes, field);
|
||||
}
|
||||
|
||||
private static boolean has(long fields, int field) {
|
||||
return (fields & (1L << field)) != 0;
|
||||
}
|
||||
|
||||
private static long bits(int... fields) {
|
||||
long bits = 0;
|
||||
for (int field : fields) bits |= 1L << field;
|
||||
return bits;
|
||||
}
|
||||
|
||||
boolean active() {
|
||||
return result != null && result.valid && suspended == 0 && !context.poppingAttributes && !BatchStateGuard.isSuspended();
|
||||
}
|
||||
|
||||
void suspend() {
|
||||
suspended++;
|
||||
}
|
||||
|
||||
void resume() {
|
||||
if (suspended == 0) throw new IllegalStateException("Exit capture suspension underflow");
|
||||
suspended--;
|
||||
}
|
||||
|
||||
void begin(ExitState out) {
|
||||
out.clear();
|
||||
if (result != null) {
|
||||
result.valid = false;
|
||||
return;
|
||||
}
|
||||
result = out;
|
||||
openCaptures.incrementAndGet();
|
||||
filterTexture = 0;
|
||||
filterOperation = false;
|
||||
out.valid = suspended == 0 && !BatchStateGuard.isSuspended() && GLStateManager.isCachingEnabled() && !DisplayListManager.isRecording() && context.savedFilterTextureId == 0;
|
||||
if (!out.valid) return;
|
||||
out.dependencies = IMPLICIT_INPUTS;
|
||||
capture(out.entry, IMPLICIT_INPUTS | DRAW_INPUTS | MATERIAL_INPUTS | MATERIAL_BAKE_INPUTS | VERTEX_INPUTS | (1L << TEXTURE_MATRIX));
|
||||
textureInherited = true;
|
||||
modelView.set(context.modelViewMatrix);
|
||||
attribDepth = context.attribDepth;
|
||||
modelViewDepth = GLStateManager.getMatrixStackDepth(context.modelViewMatrix);
|
||||
textureDepth = GLStateManager.getMatrixStackDepth(context.textures.getTextureUnitMatrix(0));
|
||||
entryOverrides = overridesInControl();
|
||||
}
|
||||
|
||||
boolean finish(ExitState out) {
|
||||
if (result != out) {
|
||||
out.clear();
|
||||
return false;
|
||||
}
|
||||
if (textureInherited && has(out, TEXTURE_MATRIX)) out.dependencies |= 1L << TEXTURE_MATRIX;
|
||||
result = null;
|
||||
openCaptures.decrementAndGet();
|
||||
if (out.valid) out.valid = suspended == 0 && filterTexture == 0 && stacksMatchEntry() && overridesInControl() == entryOverrides;
|
||||
if (out.valid) {
|
||||
capture(out.value, out.writes);
|
||||
if ((out.writes & (15L << FRONT_AMBIENT)) != 0) out.frontMaterial.set(context.frontMaterial);
|
||||
if ((out.writes & (15L << BACK_AMBIENT)) != 0) out.backMaterial.set(context.backMaterial);
|
||||
out.textureDeleteCount = TextureInfoCache.INSTANCE.deleteCount();
|
||||
}
|
||||
filterTexture = 0;
|
||||
return out.valid;
|
||||
}
|
||||
|
||||
private boolean stacksMatchEntry() {
|
||||
return context.attribDepth == attribDepth && GLStateManager.getMatrixStackDepth(context.modelViewMatrix) == modelViewDepth
|
||||
&& GLStateManager.getMatrixStackDepth(context.textures.getTextureUnitMatrix(0)) == textureDepth && modelView.equals(context.modelViewMatrix);
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
if (result != null) {
|
||||
result.clear();
|
||||
openCaptures.decrementAndGet();
|
||||
}
|
||||
result = null;
|
||||
filterTexture = 0;
|
||||
filterOperation = false;
|
||||
}
|
||||
|
||||
void reject() {
|
||||
if (active()) result.valid = false;
|
||||
}
|
||||
|
||||
// Callers record before their cache's unchanged-value early return
|
||||
void write(int field) {
|
||||
if (active()) record(field);
|
||||
}
|
||||
|
||||
void writeBlend() {
|
||||
if (active() && blendWritesReachVanilla()) record(ExitStateCapture.BLEND_FUNC);
|
||||
}
|
||||
|
||||
void writeAlpha() {
|
||||
if (active() && alphaWritesReachVanilla()) record(ExitStateCapture.ALPHA_FUNC);
|
||||
}
|
||||
|
||||
void writeDepthColor(int field) {
|
||||
if (active() && depthColorWritesReachVanilla()) record(field);
|
||||
}
|
||||
|
||||
private void record(int field) {
|
||||
result.writes |= 1L << field;
|
||||
}
|
||||
|
||||
void requireDrawState(boolean materialFromState) {
|
||||
if (!active()) return;
|
||||
long fields = (materialFromState ? DRAW_INPUTS | MATERIAL_INPUTS : DRAW_INPUTS) & ~(result.writes | result.dependencies);
|
||||
if ((fields & (1L << BLEND_FUNC)) != 0 && !context.blendMode.isEffectivelyEnabled()) fields &= ~(1L << BLEND_FUNC);
|
||||
if ((fields & (1L << ALPHA_FUNC)) != 0 && !context.alphaTest.isEffectivelyEnabled()) fields &= ~(1L << ALPHA_FUNC);
|
||||
result.dependencies |= fields;
|
||||
if (textureInherited) result.dependencies |= 1L << TEXTURE_MATRIX;
|
||||
}
|
||||
|
||||
void requireMaterialBakeInputs() {
|
||||
if (active()) result.dependencies |= MATERIAL_BAKE_INPUTS & ~result.writes;
|
||||
}
|
||||
|
||||
void requireVertexDefaults(boolean normal, boolean texCoord, boolean color, boolean lightmap) {
|
||||
if (!active()) return;
|
||||
final long fields = (normal ? 1L << NORMAL : 0) | (texCoord ? 1L << TEXCOORD : 0) | (color ? 1L << COLOR : 0) | (lightmap ? 1L << LIGHTMAP : 0);
|
||||
result.dependencies |= fields & ~result.writes;
|
||||
}
|
||||
|
||||
boolean beginTextureFilter(boolean nearest) {
|
||||
if (filterOperationBlocked()) return false;
|
||||
if (!nearest || filterTexture != 0 || context.savedFilterTextureId <= 0 || context.activeTextureUnit.getValue() != 0 || context.savedFilterMin < 0 || context.savedFilterMag < 0 || !(context.savedFilterAniso >= 1)) {
|
||||
reject();
|
||||
return false;
|
||||
}
|
||||
filterTexture = context.savedFilterTextureId;
|
||||
filterMin = context.savedFilterMin;
|
||||
filterMag = context.savedFilterMag;
|
||||
filterAniso = context.savedFilterAniso;
|
||||
startFilterOperation();
|
||||
return true;
|
||||
}
|
||||
|
||||
void finishTextureFilterSave() {
|
||||
endFilterOperation();
|
||||
if (active() && (savedFilterDiffers() || context.activeTextureUnit.getValue() != 0 || context.textures.getTextureUnitBindings(0).getBinding() != filterTexture || filterDiffersFrom(GL11.GL_NEAREST, GL11.GL_NEAREST, 1))) reject();
|
||||
}
|
||||
|
||||
boolean beginTextureFilterRestore() {
|
||||
if (filterOperationBlocked()) return false;
|
||||
if (savedFilterDiffers()) {
|
||||
reject();
|
||||
return false;
|
||||
}
|
||||
startFilterOperation();
|
||||
return true;
|
||||
}
|
||||
|
||||
void finishTextureFilterRestore() {
|
||||
endFilterOperation();
|
||||
if (active() && (savedFilterDiffers() || filterDiffersFrom(filterMin, filterMag, filterAniso))) reject();
|
||||
filterTexture = 0;
|
||||
}
|
||||
|
||||
private boolean filterOperationBlocked() {
|
||||
if (filterOperation) {
|
||||
failTextureFilter();
|
||||
return true;
|
||||
}
|
||||
return !active();
|
||||
}
|
||||
|
||||
private void startFilterOperation() {
|
||||
filterOperation = true;
|
||||
suspend();
|
||||
}
|
||||
|
||||
private void endFilterOperation() {
|
||||
resume();
|
||||
filterOperation = false;
|
||||
}
|
||||
|
||||
void failTextureFilter() {
|
||||
if (result != null) result.valid = false;
|
||||
}
|
||||
|
||||
void requireTextureFilter(int texture, boolean nearest) {
|
||||
if (filterTexture != 0 && texture == filterTexture && !nearest && active()) reject();
|
||||
}
|
||||
|
||||
private boolean savedFilterDiffers() {
|
||||
return filterTexture == 0 || context.savedFilterTextureId != filterTexture || context.savedFilterMin != filterMin || context.savedFilterMag != filterMag || context.savedFilterAniso != filterAniso;
|
||||
}
|
||||
|
||||
private boolean filterDiffersFrom(int min, int mag, float aniso) {
|
||||
final TextureInfo info = TextureInfoCache.INSTANCE.getInfo(filterTexture);
|
||||
return info == null || info.getMinFilter() != min || info.getMagFilter() != mag || info.getMaxAnisotropy() != aniso;
|
||||
}
|
||||
|
||||
boolean lightingWritten() {
|
||||
return active() && has(result, LIGHTING);
|
||||
}
|
||||
|
||||
int polygonOffsetWrites() {
|
||||
if (!active()) return 0;
|
||||
return (has(result, OFFSET) ? ExitState.POLYGON_OFFSET_ENABLE_WRITTEN : 0) | (has(result, OFFSET_VALUES) ? ExitState.POLYGON_OFFSET_VALUES_WRITTEN : 0);
|
||||
}
|
||||
|
||||
void capability(int cap, int unit) {
|
||||
if (!active()) return;
|
||||
int field = -1;
|
||||
for (int i = 0; i < CAPS.length; i++) {
|
||||
if (CAPS[i] == cap) {
|
||||
field = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (field == TEXTURE0) field = unit == 0 ? TEXTURE0 : unit == 1 ? TEXTURE1 : -1;
|
||||
if (field < 0) result.valid = false;
|
||||
else if (field == BLEND ? blendWritesReachVanilla() : field != ALPHA || alphaWritesReachVanilla()) record(field);
|
||||
}
|
||||
|
||||
void material(int face, int parameter) {
|
||||
if (!active()) return;
|
||||
int first = switch (parameter) {
|
||||
case GL11.GL_AMBIENT, GL11.GL_AMBIENT_AND_DIFFUSE -> 0;
|
||||
case GL11.GL_DIFFUSE -> 1;
|
||||
case GL11.GL_SPECULAR -> 2;
|
||||
case GL11.GL_EMISSION -> 3;
|
||||
default -> -1;
|
||||
};
|
||||
if (first < 0) {
|
||||
result.valid = false;
|
||||
return;
|
||||
}
|
||||
if (face != GL11.GL_BACK) {
|
||||
record(FRONT_AMBIENT + first);
|
||||
if (parameter == GL11.GL_AMBIENT_AND_DIFFUSE) record(FRONT_DIFFUSE);
|
||||
}
|
||||
if (face != GL11.GL_FRONT) {
|
||||
record(BACK_AMBIENT + first);
|
||||
if (parameter == GL11.GL_AMBIENT_AND_DIFFUSE) record(BACK_DIFFUSE);
|
||||
}
|
||||
}
|
||||
|
||||
// Pushes and pops check result rather than active()
|
||||
void pushAttrib() {
|
||||
if (result == null) return;
|
||||
if (context.attribDepth < attribWrites.length) attribWrites[context.attribDepth] = result.writes;
|
||||
else result.valid = false;
|
||||
}
|
||||
|
||||
void popAttrib(StateSet set) {
|
||||
if (result == null) return;
|
||||
if (context.attribDepth <= attribDepth) {
|
||||
result.valid = false;
|
||||
return;
|
||||
}
|
||||
final long restored = restoredFields(set);
|
||||
result.writes = (result.writes & ~restored) | (attribWrites[context.attribDepth - 1] & restored);
|
||||
}
|
||||
|
||||
private long restoredFields(StateSet set) {
|
||||
long restored = restoredFieldsBySet.getOrDefault(set, -1L);
|
||||
if (restored >= 0) return restored;
|
||||
restored = 0;
|
||||
for (int field = 0; field < FIELD_COUNT; field++) {
|
||||
final CowStateStack<?> stack = stack(field);
|
||||
if (stack != null && set.contains(stack.stackId())) restored |= 1L << field;
|
||||
}
|
||||
restoredFieldsBySet.put(set, restored);
|
||||
return restored;
|
||||
}
|
||||
|
||||
private CowStateStack<?> stack(int field) {
|
||||
return switch (field) {
|
||||
case BLEND -> context.blendMode;
|
||||
case ALPHA -> context.alphaTest;
|
||||
case DEPTH -> context.depthTest;
|
||||
case CULL -> context.cullState;
|
||||
case LIGHTING -> context.lightingState;
|
||||
case NORMALIZE -> context.normalizeState;
|
||||
case RESCALE -> context.rescaleNormalState;
|
||||
case LIGHT0, LIGHT1 -> context.lightStates[field - LIGHT0];
|
||||
case TEXTURE0, TEXTURE1 -> context.textures.getTextureUnitStates(field - TEXTURE0);
|
||||
case OFFSET -> context.polygonOffsetFillState;
|
||||
case COLOR_MATERIAL -> context.colorMaterial;
|
||||
case COLOR -> context.color;
|
||||
case NORMAL -> context.ffp.normalStack;
|
||||
case TEXCOORD -> context.ffp.texCoordStack;
|
||||
case BLEND_FUNC -> context.blendState;
|
||||
case ALPHA_FUNC -> context.alphaState;
|
||||
case DEPTH_FUNC, DEPTH_MASK -> context.depthState;
|
||||
case COLOR_MASK -> context.colorMask;
|
||||
case CULL_FACE, FRONT_FACE, OFFSET_VALUES -> context.polygonState;
|
||||
case SHADE_MODEL -> context.shadeModelState;
|
||||
case ACTIVE_UNIT -> context.activeTextureUnit;
|
||||
case TEXTURE_BINDING -> context.textures.getTextureUnitBindings(0);
|
||||
case MATRIX_MODE -> context.matrixMode;
|
||||
// Face and parameter are restored together (GL_LIGHTING_BIT and the batch set hold both).
|
||||
case COLOR_MATERIAL_SETTINGS -> context.colorMaterialFace;
|
||||
case FRONT_AMBIENT, FRONT_DIFFUSE, FRONT_SPECULAR, FRONT_EMISSION -> context.frontMaterial;
|
||||
case BACK_AMBIENT, BACK_DIFFUSE, BACK_SPECULAR, BACK_EMISSION -> context.backMaterial;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
void matrixChange(boolean absolute) {
|
||||
if (result == null) return;
|
||||
final int mode = context.matrixMode.getMode();
|
||||
if ((absolute || mode != GL11.GL_MODELVIEW) && active()) recordMatrixChange(mode, context.activeTextureUnit.getValue(), absolute);
|
||||
}
|
||||
|
||||
void matrixChange(int mode, int unit, boolean absolute) {
|
||||
if (active()) recordMatrixChange(mode, unit, absolute);
|
||||
}
|
||||
|
||||
private void recordMatrixChange(int mode, int unit, boolean absolute) {
|
||||
if (mode == GL11.GL_MODELVIEW) {
|
||||
if (absolute) result.valid = false;
|
||||
return;
|
||||
}
|
||||
if (mode != GL11.GL_TEXTURE || unit != 0) {
|
||||
result.valid = false;
|
||||
return;
|
||||
}
|
||||
record(TEXTURE_MATRIX);
|
||||
if (absolute) textureInherited = false;
|
||||
}
|
||||
|
||||
void pushMatrix() {
|
||||
if (result == null) return;
|
||||
final int mode = context.matrixMode.getMode();
|
||||
if (mode == GL11.GL_TEXTURE && context.activeTextureUnit.getValue() == 0) {
|
||||
final int depth = GLStateManager.getMatrixStackDepth(context.textures.getTextureUnitMatrix(0));
|
||||
if (depth < matrixWrites.length) {
|
||||
matrixWrites[depth] = result.writes;
|
||||
matrixInherited[depth] = textureInherited;
|
||||
} else {
|
||||
result.valid = false;
|
||||
}
|
||||
} else if (mode != GL11.GL_MODELVIEW) {
|
||||
reject();
|
||||
}
|
||||
}
|
||||
|
||||
void popMatrix() {
|
||||
if (result == null) return;
|
||||
final int mode = context.matrixMode.getMode();
|
||||
final boolean texture = mode == GL11.GL_TEXTURE && context.activeTextureUnit.getValue() == 0;
|
||||
final Matrix4fStack matrix;
|
||||
final int entryDepth;
|
||||
if (mode == GL11.GL_MODELVIEW) {
|
||||
matrix = context.modelViewMatrix;
|
||||
entryDepth = modelViewDepth;
|
||||
} else if (texture) {
|
||||
matrix = context.textures.getTextureUnitMatrix(0);
|
||||
entryDepth = textureDepth;
|
||||
} else {
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
final int depth = GLStateManager.getMatrixStackDepth(matrix);
|
||||
if (depth <= entryDepth) {
|
||||
result.valid = false;
|
||||
return;
|
||||
}
|
||||
if (texture) {
|
||||
final long bit = 1L << TEXTURE_MATRIX;
|
||||
result.writes = (result.writes & ~bit) | (matrixWrites[depth - 1] & bit);
|
||||
textureInherited = matrixInherited[depth - 1];
|
||||
}
|
||||
}
|
||||
|
||||
private void capture(ExitState.Values out, long fields) {
|
||||
out.enabled = 0;
|
||||
for (int field = 0; field < CAPS.length; field++) {
|
||||
if (has(fields, field) && ((BooleanStateStack) Objects.requireNonNull(stack(field))).isEffectivelyEnabled()) out.enabled |= 1L << field;
|
||||
}
|
||||
if (has(fields, COLOR)) out.color.set(context.color);
|
||||
if (has(fields, NORMAL)) out.normal.set(context.ffp.currentNormal);
|
||||
if (has(fields, TEXCOORD)) out.texCoord.set(context.ffp.currentTexCoords[0]);
|
||||
if (has(fields, LIGHTMAP)) {
|
||||
out.lightmapX = context.lastBrightnessX;
|
||||
out.lightmapY = context.lastBrightnessY;
|
||||
}
|
||||
if (has(fields, SHADER_COLOR)) out.shaderColor.set(context.shaderColorR, context.shaderColorG, context.shaderColorB, context.shaderColorA);
|
||||
if (has(fields, OVERLAY_COLOR)) out.overlayColor.set(context.overlayR, context.overlayG, context.overlayB, context.overlayA);
|
||||
if (has(fields, BLEND_FUNC)) context.blendState.readEffective(out.blend);
|
||||
if (has(fields, ALPHA_FUNC)) context.alphaState.readEffective(out.alpha);
|
||||
if (has(fields, COLOR_MASK)) context.colorMask.readEffective(out.colorMask);
|
||||
if (has(fields, DEPTH_FUNC)) out.depthFunction = context.depthState.getFunc();
|
||||
if (has(fields, DEPTH_MASK)) out.depthMask = context.depthState.isEffectiveMaskEnabled();
|
||||
if (has(fields, CULL_FACE)) out.cullFace = context.polygonState.getCullFaceMode();
|
||||
if (has(fields, FRONT_FACE)) out.frontFace = context.polygonState.getFrontFace();
|
||||
if (has(fields, OFFSET_VALUES)) {
|
||||
out.offsetFactor = context.polygonState.getOffsetFactor();
|
||||
out.offsetUnits = context.polygonState.getOffsetUnits();
|
||||
out.offsetClamp = context.polygonState.getOffsetClamp();
|
||||
}
|
||||
if (has(fields, SHADE_MODEL)) out.shadeModel = context.shadeModelState.getValue();
|
||||
if (has(fields, ACTIVE_UNIT)) out.activeUnit = context.activeTextureUnit.getValue();
|
||||
if (has(fields, CLIENT_ACTIVE_UNIT)) out.clientActiveUnit = context.clientActiveTextureUnit;
|
||||
if (has(fields, MATRIX_MODE)) out.matrixMode = context.matrixMode.getMode();
|
||||
if (has(fields, TEXTURE_BINDING)) out.texture = context.textures.getTextureUnitBindings(0).getBinding();
|
||||
if (has(fields, TEXTURE_MATRIX)) out.textureMatrix.set(context.textures.getTextureUnitMatrix(0));
|
||||
if (has(fields, COLOR_MATERIAL_SETTINGS)) {
|
||||
out.materialFace = context.colorMaterialFace.getValue();
|
||||
out.materialParameter = context.colorMaterialParameter.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
boolean canApply(ExitState in) {
|
||||
if (!in.valid || result != null || !GLStateManager.isCachingEnabled() || DisplayListManager.isRecording() || context.savedFilterTextureId != 0) return false;
|
||||
if (in.textureDeleteCount != TextureInfoCache.INSTANCE.deleteCount()) return false;
|
||||
capture(current, in.dependencies);
|
||||
if (((current.enabled ^ in.entry.enabled) & in.dependencies & ((1L << CAPS.length) - 1)) != 0) return false;
|
||||
long remaining = in.dependencies & -(1L << CAPS.length);
|
||||
while (remaining != 0) {
|
||||
final int field = Long.numberOfTrailingZeros(remaining);
|
||||
if (!same(field, in.entry, current)) return false;
|
||||
remaining &= remaining - 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void apply(ExitState state) {
|
||||
if (!state.valid) throw new IllegalStateException("Invalid renderer exit state");
|
||||
if (result != null) throw new IllegalStateException("Exit state applied while a capture is open");
|
||||
final ExitState.Values in = state.value;
|
||||
final long fields = state.writes;
|
||||
final int liveUnit = context.activeTextureUnit.getValue();
|
||||
// Capabilities go first so the glColor4f below bakes into the material only if color material is on at exit.
|
||||
for (int field = 0; field < CAPS.length; field++) {
|
||||
if (!has(fields, field)) continue;
|
||||
if (field == TEXTURE0 || field == TEXTURE1) GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + field - TEXTURE0);
|
||||
// glEnable would bake the live color, not the exit color, into the material.
|
||||
if (field == COLOR_MATERIAL) {
|
||||
context.colorMaterial.setEnabled((in.enabled & (1L << field)) != 0);
|
||||
} else if ((in.enabled & (1L << field)) != 0) GLStateManager.glEnable(CAPS[field]);
|
||||
else GLStateManager.glDisable(CAPS[field]);
|
||||
}
|
||||
if (has(fields, COLOR)) GLStateManager.glColor4f(in.color.getRed(), in.color.getGreen(), in.color.getBlue(), in.color.getAlpha());
|
||||
if (has(fields, NORMAL)) GLStateManager.glNormal3f(in.normal.x, in.normal.y, in.normal.z);
|
||||
if (has(fields, TEXCOORD)) GLStateManager.glTexCoord4f(in.texCoord.x, in.texCoord.y, in.texCoord.z, in.texCoord.w);
|
||||
if (has(fields, LIGHTMAP)) GLStateManager.setLightmapTextureCoords(GL13.GL_TEXTURE1, in.lightmapX, in.lightmapY);
|
||||
if (has(fields, SHADER_COLOR)) GLStateManager.setShaderColor(in.shaderColor.x, in.shaderColor.y, in.shaderColor.z, in.shaderColor.w);
|
||||
if (has(fields, OVERLAY_COLOR)) GLStateManager.setOverlayColor(in.overlayColor.x, in.overlayColor.y, in.overlayColor.z, in.overlayColor.w);
|
||||
if (has(fields, BLEND_FUNC)) GLStateManager.tryBlendFuncSeparate(in.blend.getSrcRgb(), in.blend.getDstRgb(), in.blend.getSrcAlpha(), in.blend.getDstAlpha());
|
||||
if (has(fields, ALPHA_FUNC)) GLStateManager.glAlphaFunc(in.alpha.getFunction(), in.alpha.getReference());
|
||||
if (has(fields, DEPTH_FUNC)) GLStateManager.glDepthFunc(in.depthFunction);
|
||||
if (has(fields, DEPTH_MASK)) GLStateManager.glDepthMask(in.depthMask);
|
||||
if (has(fields, COLOR_MASK)) GLStateManager.glColorMask(in.colorMask.red, in.colorMask.green, in.colorMask.blue, in.colorMask.alpha);
|
||||
if (has(fields, CULL_FACE)) GLStateManager.glCullFace(in.cullFace);
|
||||
if (has(fields, FRONT_FACE)) GLStateManager.glFrontFace(in.frontFace);
|
||||
if (has(fields, SHADE_MODEL)) GLStateManager.glShadeModel(in.shadeModel);
|
||||
if (has(fields, OFFSET_VALUES)) GLStateManager.glPolygonOffsetClamp(in.offsetFactor, in.offsetUnits, in.offsetClamp);
|
||||
if (has(fields, TEXTURE_BINDING)) {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, in.texture);
|
||||
}
|
||||
if (has(fields, TEXTURE_MATRIX)) GLStateManager.setTextureMatrix(0, in.textureMatrix);
|
||||
if (has(fields, COLOR_MATERIAL_SETTINGS)) GLStateManager.glColorMaterial(in.materialFace, in.materialParameter);
|
||||
boolean materialChanged = false;
|
||||
for (int field = FRONT_AMBIENT; field <= BACK_EMISSION; field++) {
|
||||
if (!has(fields, field)) continue;
|
||||
final boolean front = field < BACK_AMBIENT;
|
||||
final int component = (field - FRONT_AMBIENT) & 3;
|
||||
final Vector4f source = vector(front ? state.frontMaterial : state.backMaterial, component);
|
||||
final var target = front ? context.frontMaterial : context.backMaterial;
|
||||
if (vector(target, component).equals(source)) continue;
|
||||
target.beforeModify();
|
||||
vector(target, component).set(source);
|
||||
materialChanged = true;
|
||||
}
|
||||
if (materialChanged) context.lightingGeneration++;
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + (has(fields, ACTIVE_UNIT) ? in.activeUnit : liveUnit));
|
||||
if (has(fields, CLIENT_ACTIVE_UNIT)) GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0 + in.clientActiveUnit);
|
||||
if (has(fields, MATRIX_MODE)) GLStateManager.glMatrixMode(in.matrixMode);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.gtnewhorizons.angelica.glsm;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.AlphaStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.BlendStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
|
||||
@@ -34,6 +35,7 @@ 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;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.InstancedAttribs;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
|
||||
@@ -47,6 +49,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;
|
||||
@@ -56,6 +59,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class GLContextState {
|
||||
final ExitStateCapture exitCapture = new ExitStateCapture(this);
|
||||
private final List<IStateStack<?>> allStacksBuilder = new ArrayList<>();
|
||||
public IStateStack<?>[] allStacks;
|
||||
final List<BooleanStateStack> allBooleanStates = new ArrayList<>();
|
||||
@@ -103,6 +107,8 @@ public class GLContextState {
|
||||
public boolean dirtyNormalAttrib;
|
||||
public boolean dirtyTexCoordAttrib;
|
||||
public boolean dirtyLightmapAttrib = true;
|
||||
public long entityIds = InstancedAttribs.DEFAULT_ENTITY_IDS;
|
||||
public boolean dirtyEntityIdsAttrib = true;
|
||||
public boolean unit23TexCoordSetDuringDraw = false;
|
||||
public int maxBoundTextureUnit = 0;
|
||||
public int maxBoundImageUnit = 0;
|
||||
@@ -169,6 +175,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));
|
||||
@@ -182,6 +191,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;
|
||||
@@ -231,6 +241,8 @@ public class GLContextState {
|
||||
public int boundCopyReadBuffer;
|
||||
public int boundCopyWriteBuffer;
|
||||
public final Int2IntMap boundOtherBuffers = new Int2IntOpenHashMap();
|
||||
public final int[] uniformBufferBindings = new int[GLStateManager.INDEXED_BUFFER_SLOTS];
|
||||
public final int[] storageBufferBindings = new int[GLStateManager.INDEXED_BUFFER_SLOTS];
|
||||
public PixelStoreState pixelUnpackState = PixelStoreState.DEFAULT;
|
||||
public PixelStoreState pixelPackState = PixelStoreState.DEFAULT;
|
||||
public final int[] clientAttribSavedMask = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
|
||||
@@ -238,9 +250,7 @@ public class GLContextState {
|
||||
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 int[] clientAttribSavedTextureUnit = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
|
||||
public final int[] clientAttribSavedVertexFlags = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
|
||||
public final boolean[] clientAttribSavedVertexArray = new boolean[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
|
||||
public final VAOManager.ClientArrays[] clientAttribSavedArrays = new VAOManager.ClientArrays[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
|
||||
public int clientAttribStackPointer = 0;
|
||||
public int restoreChangedMask;
|
||||
public long restoreUnitChangedMask;
|
||||
@@ -249,6 +259,7 @@ public class GLContextState {
|
||||
public boolean vanillaBlendEnabledBefore;
|
||||
public int drawFramebuffer = 0;
|
||||
public int readFramebuffer = 0;
|
||||
public final Int2ObjectOpenHashMap<int[]> framebufferTextures = new Int2ObjectOpenHashMap<>();
|
||||
public int texGenGeneration;
|
||||
public int defaultVAO;
|
||||
public final VAOManager vaos = new VAOManager();
|
||||
@@ -262,8 +273,9 @@ 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 int textureSupersamplingSamples = 1;
|
||||
public Instancing ffpInstancing = Instancing.NONE;
|
||||
public float lastBrightnessX;
|
||||
public float lastBrightnessY;
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,8 @@ public class RenderSystem {
|
||||
private static boolean supportsMultiDrawIndirect;
|
||||
private static boolean supportsBufferStorage;
|
||||
private static boolean supportsClearTexture;
|
||||
private static boolean supportsStencilTexturing;
|
||||
private static boolean supportsTextureStorage;
|
||||
private static boolean supportsTesselation;
|
||||
private static boolean supportsSamplerObjects;
|
||||
private static boolean supportsProgramBinary;
|
||||
@@ -128,6 +130,8 @@ public class RenderSystem {
|
||||
supportsMultiDrawIndirect = GLStateManager.capabilities.OpenGL43 || GLStateManager.capabilities.GL_ARB_multi_draw_indirect;
|
||||
supportsBufferStorage = !isGLES && (GLStateManager.capabilities.OpenGL44 || GLStateManager.capabilities.GL_ARB_buffer_storage);
|
||||
supportsClearTexture = GLStateManager.capabilities.OpenGL44 || GLStateManager.capabilities.GL_ARB_clear_texture;
|
||||
supportsStencilTexturing = GLStateManager.capabilities.OpenGL43 || GLStateManager.capabilities.GL_ARB_stencil_texturing;
|
||||
supportsTextureStorage = isGLES ? glesVersion >= 300 : GLStateManager.capabilities.OpenGL42 || GLStateManager.capabilities.GL_ARB_texture_storage;
|
||||
hasClipCullDistance = !isGLES || hasExtension("GL_EXT_clip_cull_distance");
|
||||
|
||||
// Cache maximum image units
|
||||
@@ -154,8 +158,8 @@ public class RenderSystem {
|
||||
maxSSBOBindings = 0;
|
||||
}
|
||||
|
||||
// Check for sampler objects support (GL 3.3+ or ARB extension)
|
||||
supportsSamplerObjects = GLStateManager.capabilities.OpenGL33 || GLStateManager.capabilities.GL_ARB_sampler_objects;
|
||||
supportsSamplerObjects = isGLES ? glesVersion >= 300
|
||||
: GLStateManager.capabilities.OpenGL33 || GLStateManager.capabilities.GL_ARB_sampler_objects;
|
||||
if (supportsSamplerObjects) {
|
||||
maxCombinedTextureImageUnits = RENDER_BACKEND.getInteger(GL20.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS);
|
||||
}
|
||||
@@ -194,14 +198,17 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static void generateMipmaps(int texture, int mipmapTarget) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
dsaState.generateMipmaps(texture, mipmapTarget);
|
||||
}
|
||||
|
||||
public static void bindAttributeLocation(int program, int index, CharSequence name) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
RENDER_BACKEND.bindAttribLocation(program, index, name);
|
||||
}
|
||||
|
||||
public static void texImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ByteBuffer pixels) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
final GLStateManager.GLESTexImageRemap remap = GLStateManager.remapTexImageForGLES(internalformat, format, type);
|
||||
internalformat = remap.internalFormat();
|
||||
format = remap.format();
|
||||
@@ -216,6 +223,7 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static void uniformMatrix4fv(int location, boolean transpose, FloatBuffer matrix) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
RENDER_BACKEND.uniformMatrix4(location, transpose, matrix);
|
||||
}
|
||||
|
||||
@@ -224,118 +232,151 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static void uniform1f(int location, float v0) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
RENDER_BACKEND.uniform1f(location, v0);
|
||||
}
|
||||
|
||||
public static void uniform1i(int location, int v0) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
RENDER_BACKEND.uniform1i(location, v0);
|
||||
}
|
||||
|
||||
public static void uniform2f(int location, float v0, float v1) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
RENDER_BACKEND.uniform2f(location, v0, v1);
|
||||
}
|
||||
|
||||
public static void uniform2i(int location, int v0, int v1) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
RENDER_BACKEND.uniform2i(location, v0, v1);
|
||||
}
|
||||
|
||||
public static void uniform3f(int location, float v0, float v1, float v2) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
RENDER_BACKEND.uniform3f(location, v0, v1, v2);
|
||||
}
|
||||
|
||||
public static void uniform3i(int location, int v0, int v1, int v2) {RENDER_BACKEND.uniform3i(location, v0, v1, v2);}
|
||||
public static void uniform3i(int location, int v0, int v1, int v2) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
RENDER_BACKEND.uniform3i(location, v0, v1, v2);
|
||||
}
|
||||
|
||||
public static void uniform4f(int location, float v0, float v1, float v2, float v3) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
RENDER_BACKEND.uniform4f(location, v0, v1, v2, v3);
|
||||
}
|
||||
|
||||
public static void uniform4i(int location, int v0, int v1, int v2, int v3) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
RENDER_BACKEND.uniform4i(location, v0, v1, v2, v3);
|
||||
}
|
||||
|
||||
public static int getAttribLocation(int programId, String name) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
return RENDER_BACKEND.getAttribLocation(programId, name);
|
||||
}
|
||||
|
||||
public static int getUniformLocation(int programId, String name) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
return RENDER_BACKEND.getUniformLocation(programId, name);
|
||||
}
|
||||
|
||||
public static void texParameteriv(int texture, int target, int pname, IntBuffer params) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
dsaState.texParameteriv(texture, target, pname, params);
|
||||
}
|
||||
|
||||
public static void copyTexSubImage2D(int destTexture, int target, int i, int i1, int i2, int i3, int i4, int width, int height) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
dsaState.copyTexSubImage2D(destTexture, target, i, i1, i2, i3, i4, width, height);
|
||||
if (i == 0) TextureInfoCache.INSTANCE.onContentsChanged(destTexture);
|
||||
}
|
||||
|
||||
public static void texParameteri(int texture, int target, int pname, int param) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
dsaState.texParameteri(texture, target, pname, param);
|
||||
}
|
||||
|
||||
public static void texParameterf(int texture, int target, int pname, float param) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
dsaState.texParameterf(texture, target, pname, param);
|
||||
}
|
||||
|
||||
public static void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
GLStateManager.suspendPixelUnpackBuffer();
|
||||
dsaState.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
|
||||
GLStateManager.restorePixelUnpackBuffer();
|
||||
TextureInfoCache.INSTANCE.onTextureImage2D(texture, target, level, internalformat, width, height);
|
||||
}
|
||||
|
||||
public static void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
GLStateManager.suspendPixelUnpackBuffer();
|
||||
dsaState.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
|
||||
GLStateManager.restorePixelUnpackBuffer();
|
||||
TextureInfoCache.INSTANCE.onTextureImage2D(texture, target, level, internalformat, width, height);
|
||||
}
|
||||
|
||||
public static void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
GLStateManager.suspendPixelUnpackBuffer();
|
||||
dsaState.textureSubImage2D(texture, target, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
GLStateManager.restorePixelUnpackBuffer();
|
||||
if (level == 0) TextureInfoCache.INSTANCE.onContentsChanged(texture);
|
||||
}
|
||||
|
||||
public static void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
GLStateManager.suspendPixelUnpackBuffer();
|
||||
dsaState.textureSubImage2D(texture, target, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
GLStateManager.restorePixelUnpackBuffer();
|
||||
if (level == 0) TextureInfoCache.INSTANCE.onContentsChanged(texture);
|
||||
}
|
||||
|
||||
public static void texImage1D(int texture, int target, int level, int internalformat, int width, int border, int format, int type, ByteBuffer pixels) {
|
||||
RENDER_BACKEND.bindTexture(target, texture);
|
||||
GLStateManager.rejectExitCapture();
|
||||
GLStateManager.glBindTexture(target, texture);
|
||||
GLStateManager.suspendPixelUnpackBuffer();
|
||||
RENDER_BACKEND.texImage1D(target, level, internalformat, width, border, format, type, pixels);
|
||||
GLStateManager.restorePixelUnpackBuffer();
|
||||
}
|
||||
|
||||
public static void texImage3D(int texture, int target, int level, int internalformat, int width, int height, int depth, int border, int format, int type, ByteBuffer pixels) {
|
||||
RENDER_BACKEND.bindTexture(target, texture);
|
||||
GLStateManager.rejectExitCapture();
|
||||
GLStateManager.glBindTexture(target, texture);
|
||||
GLStateManager.suspendPixelUnpackBuffer();
|
||||
RENDER_BACKEND.texImage3D(target, level, internalformat, width, height, depth, border, format, type, pixels);
|
||||
GLStateManager.restorePixelUnpackBuffer();
|
||||
}
|
||||
|
||||
public static String getProgramInfoLog(int program) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
return RENDER_BACKEND.getProgramInfoLog(program, RENDER_BACKEND.getProgrami(program, GL20.GL_INFO_LOG_LENGTH));
|
||||
}
|
||||
|
||||
public static String getShaderInfoLog(int shader) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
return RENDER_BACKEND.getShaderInfoLog(shader, RENDER_BACKEND.getShaderi(shader, GL20.GL_INFO_LOG_LENGTH));
|
||||
}
|
||||
|
||||
public static void drawBuffers(int framebuffer, IntBuffer buffers) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
dsaState.drawBuffers(framebuffer, buffers);
|
||||
}
|
||||
|
||||
public static void readBuffer(int framebuffer, int buffer) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
dsaState.readBuffer(framebuffer, buffer);
|
||||
}
|
||||
|
||||
public static String getActiveUniform(int program, int index, int maxLength, IntBuffer sizeType) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
return RENDER_BACKEND.getActiveUniform(program, index, maxLength, sizeType);
|
||||
}
|
||||
|
||||
public static void readPixels(int x, int y, int width, int height, int format, int type, FloatBuffer pixels) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
GLStateManager.suspendPixelPackBuffer();
|
||||
RENDER_BACKEND.readPixels(x, y, width, height, format, type, pixels);
|
||||
GLStateManager.restorePixelPackBuffer();
|
||||
@@ -346,30 +387,37 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static int bufferStorage(int target, FloatBuffer data, int usage) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
return dsaState.bufferStorage(target, data, usage);
|
||||
}
|
||||
|
||||
public static void vertexAttrib4f(int index, float v0, float v1, float v2, float v3) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
RENDER_BACKEND.vertexAttrib4f(index, v0, v1, v2, v3);
|
||||
}
|
||||
|
||||
public static void detachShader(int program, int shader) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
RENDER_BACKEND.detachShader(program, shader);
|
||||
}
|
||||
|
||||
public static void framebufferTexture2D(int fb, int fbtarget, int attachment, int target, int texture, int levels) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
dsaState.framebufferTexture2D(fb, fbtarget, attachment, target, texture, levels);
|
||||
}
|
||||
|
||||
public static int getTexParameteri(int texture, int target, int pname) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
return dsaState.getTexParameteri(texture, target, pname);
|
||||
}
|
||||
|
||||
public static float getTexParameterf(int texture, int target, int pname) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
return dsaState.getTexParameterf(texture, target, pname);
|
||||
}
|
||||
|
||||
public static int getTexLevelParameteri(int texture, int level, int pname) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
return dsaState.getTexLevelParameteri(texture, level, pname);
|
||||
}
|
||||
|
||||
@@ -453,23 +501,34 @@ public class RenderSystem {
|
||||
return supportsClearTexture;
|
||||
}
|
||||
|
||||
public static boolean supportsStencilTexturing() {
|
||||
return supportsStencilTexturing;
|
||||
}
|
||||
|
||||
public static boolean supportsTextureStorage() {
|
||||
return supportsTextureStorage;
|
||||
}
|
||||
|
||||
public static int getMaxSSBOBindings() {
|
||||
return maxSSBOBindings;
|
||||
}
|
||||
|
||||
public static void getProgramiv(int program, int value, IntBuffer storage) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
RENDER_BACKEND.getProgramiv(program, value, storage);
|
||||
}
|
||||
|
||||
public static void dispatchCompute(int workX, int workY, int workZ) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
RENDER_BACKEND.dispatchCompute(workX, workY, workZ);
|
||||
}
|
||||
|
||||
public static void dispatchCompute(Vector3i workGroups) {
|
||||
RENDER_BACKEND.dispatchCompute(workGroups.x, workGroups.y, workGroups.z);
|
||||
dispatchCompute(workGroups.x, workGroups.y, workGroups.z);
|
||||
}
|
||||
|
||||
public static void dispatchComputeIndirect(long offset) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
RENDER_BACKEND.dispatchComputeIndirect(offset);
|
||||
}
|
||||
|
||||
@@ -478,6 +537,7 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static void memoryBarrier(int barriers) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
if (supportsCompute) {
|
||||
RENDER_BACKEND.memoryBarrier(barriers);
|
||||
}
|
||||
@@ -500,12 +560,14 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static void bindTextureToUnit(int unit, int texture) {
|
||||
if (unit != 0 && GLStateManager.getBoundTextureForServerState(unit) != texture) GLStateManager.beforeUncapturedStateChange();
|
||||
if (unit == 0) GLStateManager.recordExitTextureBind(GL11.GL_TEXTURE_2D, 0);
|
||||
else if (GLStateManager.getBoundTextureForServerState(unit) != texture) GLStateManager.beforeUncapturedStateChange();
|
||||
dsaState.bindTextureToUnit(unit, texture);
|
||||
}
|
||||
|
||||
public static void bindTextureToUnit(int target, int unit, int texture) {
|
||||
if (unit != 0 && GLStateManager.getBoundTextureForServerState(unit) != texture) GLStateManager.beforeUncapturedStateChange();
|
||||
if (unit == 0) GLStateManager.recordExitTextureBind(target, 0);
|
||||
else if (GLStateManager.getBoundTextureForServerState(unit) != texture) GLStateManager.beforeUncapturedStateChange();
|
||||
dsaState.bindTextureToUnit(target, unit, texture);
|
||||
}
|
||||
|
||||
@@ -525,6 +587,7 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static void blitFramebuffer(int source, int dest, int offsetX, int offsetY, int width, int height, int offsetX2, int offsetY2, int width2, int height2, int bufferChoice, int filter) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
final boolean scissor = GLStateManager.glIsEnabled(GL11.GL_SCISSOR_TEST);
|
||||
if (scissor) GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
try {
|
||||
@@ -535,10 +598,12 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static int createFramebuffer() {
|
||||
GLStateManager.rejectExitCapture();
|
||||
return dsaState.createFramebuffer();
|
||||
}
|
||||
|
||||
public static int createTexture(int target) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
return dsaState.createTexture(target);
|
||||
}
|
||||
|
||||
@@ -556,7 +621,8 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static int getIndexedBufferBinding(int target, int index) {
|
||||
return RENDER_BACKEND.getIndexedBufferBinding(target, index);
|
||||
GLStateManager.rejectExitCapture();
|
||||
return GLStateManager.getIndexedBufferBinding(target, index);
|
||||
}
|
||||
|
||||
public static void bindBufferBase(int target, int index, int buffer) {
|
||||
@@ -587,6 +653,7 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static void clearTexImage(int texture, int target, int level, int format, int type) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
RENDER_BACKEND.clearTexImage(texture, level, format, type);
|
||||
}
|
||||
|
||||
@@ -646,12 +713,7 @@ public class RenderSystem {
|
||||
|
||||
public static void unbindAllSamplers() {
|
||||
if (!supportsSamplerObjects) return;
|
||||
final int count = GLStateManager.getSamplerUnitCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (GLStateManager.getSamplerBinding(i) != 0) {
|
||||
GLStateManager.glBindSampler(i, 0);
|
||||
}
|
||||
}
|
||||
GLStateManager.unbindAllSamplers();
|
||||
}
|
||||
|
||||
/** Parse a GL version string (e.g. "4.6.0 NVIDIA ...") into concatenated digits "460". Strips
|
||||
@@ -688,4 +750,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;
|
||||
@@ -224,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;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,11 @@ public final class BackendOptions {
|
||||
}
|
||||
|
||||
public static String sdlGpuDriver() {
|
||||
return SystemProperties.SDL_GPU_DRIVER_OVERRIDE != null ? SystemProperties.SDL_GPU_DRIVER_OVERRIDE : configDriver;
|
||||
return sdlGpuDriver(configDriver);
|
||||
}
|
||||
|
||||
public static String sdlGpuDriver(String driver) {
|
||||
return SystemProperties.SDL_GPU_DRIVER_OVERRIDE != null ? SystemProperties.SDL_GPU_DRIVER_OVERRIDE : driver;
|
||||
}
|
||||
|
||||
public static boolean configSdlGpu() {
|
||||
|
||||
@@ -1,55 +1,96 @@
|
||||
package com.gtnewhorizons.angelica.glsm.backend;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
|
||||
public final class BackendStartGuard {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("Angelica/BackendStartGuard");
|
||||
|
||||
private static File pending;
|
||||
static final int STRIKE_LIMIT = 3;
|
||||
|
||||
private static File marker;
|
||||
private static File verified;
|
||||
private static String key;
|
||||
private static int strikes;
|
||||
private static boolean pending;
|
||||
private static boolean tripped;
|
||||
|
||||
private BackendStartGuard() {}
|
||||
|
||||
public static boolean begin(File marker, boolean sdlGpuConfigured, boolean flagForced) {
|
||||
pending = null;
|
||||
tripped = false;
|
||||
if (!sdlGpuConfigured || flagForced) {
|
||||
public static boolean begin(File dir, boolean sdlGpuConfigured, boolean flagForced, int requirementsRevision, String requestedDriver) {
|
||||
marker = new File(dir, "sdlgpu-starting");
|
||||
verified = new File(dir, "sdlgpu-verified");
|
||||
key = requirementsRevision + "/" + requestedDriver;
|
||||
pending = tripped = false;
|
||||
if (flagForced) return false;
|
||||
if (!sdlGpuConfigured) {
|
||||
delete(marker);
|
||||
return false;
|
||||
}
|
||||
if (marker.isFile()) {
|
||||
if (key.equals(read(verified))) return false;
|
||||
final String count = read(marker);
|
||||
final Integer parsed = count == null ? null : Ints.tryParse(count.trim());
|
||||
strikes = parsed == null ? 0 : parsed;
|
||||
if (strikes >= STRIKE_LIMIT) {
|
||||
LOGGER.warn("SDL GPU failed to bring up the device {} times in a row; resetting the render backend to OpenGL", strikes);
|
||||
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());
|
||||
if (strikes == STRIKE_LIMIT - 1) {
|
||||
LOGGER.warn("SDL GPU failed to bring up the device {} times in a row; one more and the render backend switches to OpenGL", strikes);
|
||||
} else if (strikes > 0) {
|
||||
LOGGER.info("The last start ended while bringing up the SDL GPU device");
|
||||
}
|
||||
pending = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void finish() {
|
||||
if (pending == null) return;
|
||||
delete(pending);
|
||||
pending = null;
|
||||
public static void arm() {
|
||||
if (pending) write(marker, Integer.toString(strikes + 1));
|
||||
}
|
||||
|
||||
public static void disarm() {
|
||||
if (pending) delete(marker);
|
||||
}
|
||||
|
||||
public static void latch() {
|
||||
if (!pending) return;
|
||||
pending = false;
|
||||
write(verified, key);
|
||||
LOGGER.info("SDL GPU booted; the startup guard stays off until the requirements revision or driver changes");
|
||||
}
|
||||
|
||||
public static boolean tripped() {
|
||||
return tripped;
|
||||
}
|
||||
|
||||
private static void delete(File marker) {
|
||||
if (marker.isFile() && !marker.delete()) {
|
||||
LOGGER.warn("Could not delete {}", marker);
|
||||
private static String read(File file) {
|
||||
try {
|
||||
return file.isFile() ? new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8) : null;
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Could not read {}: {}", file, e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void write(File file, String content) {
|
||||
try {
|
||||
Files.createDirectories(file.toPath().getParent());
|
||||
Files.write(file.toPath(), content.getBytes(StandardCharsets.UTF_8));
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Could not write {}: {}", file, e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static void delete(File file) {
|
||||
if (file.isFile() && !file.delete()) LOGGER.warn("Could not delete {}", file);
|
||||
}
|
||||
}
|
||||
+21
-1
@@ -42,6 +42,9 @@ import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetAddress;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.Pointer.POINTER_SIZE;
|
||||
import static org.taumc.celeritas.lwjgl.LWJGLServiceProvider.LWJGL;
|
||||
|
||||
/**
|
||||
@@ -380,7 +383,9 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
|
||||
|
||||
@Override
|
||||
public void multiDrawElementsBaseVertex(int mode, long pCount, int type, long pIndices, int drawcount, long pBaseVertex) {
|
||||
throw new UnsupportedOperationException("GLRenderBackend.multiDrawElementsBaseVertex should not be called — LWJGL service handles this");
|
||||
for (int i = 0; i < drawcount; i++) {
|
||||
GL32.glDrawElementsBaseVertex(mode, memGetInt(pCount + 4L * i), type, memGetAddress(pIndices + (long) i * POINTER_SIZE), memGetInt(pBaseVertex + 4L * i));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -609,6 +614,21 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
|
||||
GL30.glFramebufferTexture2D(target, attachment, textarget, texture, level);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void framebufferTexture1D(int target, int attachment, int textarget, int texture, int level) {
|
||||
GL30.glFramebufferTexture1D(target, attachment, textarget, texture, level);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void framebufferTexture3D(int target, int attachment, int textarget, int texture, int level, int zoffset) {
|
||||
GL30.glFramebufferTexture3D(target, attachment, textarget, texture, level, zoffset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void framebufferTextureLayer(int target, int attachment, int texture, int level, int layer) {
|
||||
GL30.glFramebufferTextureLayer(target, attachment, texture, level, layer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void framebufferTexture(int target, int attachment, int texture, int level) {
|
||||
GL32.glFramebufferTexture(target, attachment, texture, level);
|
||||
|
||||
@@ -26,6 +26,8 @@ import java.util.function.Predicate;
|
||||
*/
|
||||
public abstract class RenderBackend {
|
||||
|
||||
public static final int MAX_FRAMES_IN_FLIGHT = 3;
|
||||
|
||||
public void onPostWindowCreate(long window) {}
|
||||
|
||||
public abstract void init();
|
||||
@@ -99,7 +101,7 @@ public abstract class RenderBackend {
|
||||
|
||||
static VSyncMode resolveTearFreeMode(VSyncMode preferred, Predicate<VSyncMode> supported) {
|
||||
if (preferred != null && preferred.tearFree() && supported.test(preferred)) return preferred;
|
||||
return supported.test(VSyncMode.MAILBOX) ? VSyncMode.MAILBOX : VSyncMode.ON;
|
||||
return VSyncMode.ON;
|
||||
}
|
||||
|
||||
protected abstract VSyncMode applyVSyncMode(VSyncMode preferred);
|
||||
@@ -457,6 +459,9 @@ public abstract class RenderBackend {
|
||||
public abstract void deleteFramebuffers(int framebuffer);
|
||||
public abstract void bindFramebuffer(int target, int framebuffer);
|
||||
public abstract void framebufferTexture2D(int target, int attachment, int textarget, int texture, int level);
|
||||
public abstract void framebufferTexture1D(int target, int attachment, int textarget, int texture, int level);
|
||||
public abstract void framebufferTexture3D(int target, int attachment, int textarget, int texture, int level, int zoffset);
|
||||
public abstract void framebufferTextureLayer(int target, int attachment, int texture, int level, int layer);
|
||||
public abstract void framebufferTexture(int target, int attachment, int texture, int level);
|
||||
public abstract int genRenderbuffers();
|
||||
public abstract void deleteRenderbuffers(int renderbuffer);
|
||||
|
||||
+3
-2
@@ -11,13 +11,14 @@ import java.util.function.IntSupplier;
|
||||
* @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 samplerObjects per sampler name, supplies its current GL sampler object when samplerUnits is -1;
|
||||
* null for unassigned or external bindings, and returns 0 to use the texture's own filtering
|
||||
* @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,
|
||||
String[] samplerNames, int[] samplerUnits, IntSupplier[] samplerTextures, IntSupplier[] samplerObjects,
|
||||
int vertexBufferBinding, int vertexBufferCount, int indexBufferBinding) {
|
||||
}
|
||||
@@ -128,7 +128,7 @@ public class DSAARB extends DSAUnsupported {
|
||||
|
||||
@Override
|
||||
public void framebufferTexture2D(int fb, int fbtarget, int attachment, int target, int texture, int levels) {
|
||||
RENDER_BACKEND.namedFramebufferTexture(fb, attachment, texture, levels);
|
||||
GLStateManager.glNamedFramebufferTexture(fb, attachment, texture, levels);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -139,7 +139,6 @@ public class DSAARB extends DSAUnsupported {
|
||||
|
||||
@Override
|
||||
public int createTexture(int target) {
|
||||
GLStateManager.requireContext("glCreateTextures");
|
||||
return RENDER_BACKEND.createTextures(target);
|
||||
return GLStateManager.glCreateTextures(target);
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,12 @@ package com.gtnewhorizons.angelica.glsm.dsa;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.TextureBindingStack;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureTargets;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
@@ -153,7 +152,7 @@ public class DSAUnsupported implements DSAAccess {
|
||||
@Override
|
||||
public void framebufferTexture2D(int fb, int fbtarget, int attachment, int target, int texture, int levels) {
|
||||
GLStateManager.glBindFramebuffer(fbtarget, fb);
|
||||
RENDER_BACKEND.framebufferTexture2D(fbtarget, attachment, target, texture, levels);
|
||||
GLStateManager.glFramebufferTexture2D(fbtarget, attachment, target, texture, levels);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -166,23 +165,12 @@ public class DSAUnsupported implements DSAAccess {
|
||||
@Override
|
||||
public int createTexture(int target) {
|
||||
final int texture = GLStateManager.glGenTextures();
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
GLStateManager.glBindTexture(target, texture);
|
||||
return texture;
|
||||
}
|
||||
|
||||
private static int bindingQuery(int target) {
|
||||
return switch (target) {
|
||||
case GL11.GL_TEXTURE_1D -> GL11.GL_TEXTURE_BINDING_1D;
|
||||
case GL12.GL_TEXTURE_3D -> GL12.GL_TEXTURE_BINDING_3D;
|
||||
case GL13.GL_TEXTURE_CUBE_MAP -> GL13.GL_TEXTURE_BINDING_CUBE_MAP;
|
||||
case GL30.GL_TEXTURE_2D_ARRAY -> GL30.GL_TEXTURE_BINDING_2D_ARRAY;
|
||||
case GL31.GL_TEXTURE_RECTANGLE -> GL31.GL_TEXTURE_BINDING_RECTANGLE;
|
||||
default -> GL11.GL_TEXTURE_BINDING_2D;
|
||||
};
|
||||
}
|
||||
|
||||
private static int previousBinding(int target) {
|
||||
return target == GL11.GL_TEXTURE_2D ? previous2D() : RENDER_BACKEND.getInteger(bindingQuery(target));
|
||||
return target == GL11.GL_TEXTURE_2D ? previous2D() : RENDER_BACKEND.getInteger(TextureTargets.bindingQuery(target));
|
||||
}
|
||||
|
||||
private static int previous2D() {
|
||||
|
||||
@@ -5,6 +5,7 @@ import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat;
|
||||
public record CubeParams(float minX, float minY, float minZ, float spanX, float spanY, float spanZ, float u0, float v0, float uScaleX, float vScaleY, float uScaleZ, float vScaleZ) {
|
||||
|
||||
private static final float MIN_SPAN = 1.0e-4f;
|
||||
private static final float UV_EPSILON = 1.0e-5f;
|
||||
|
||||
public CubeParams {
|
||||
if (!(spanX > MIN_SPAN && spanY > MIN_SPAN && spanZ > MIN_SPAN)) throw new IllegalArgumentException("degenerate cube span");
|
||||
@@ -43,12 +44,25 @@ public record CubeParams(float minX, float minY, float minZ, float spanX, float
|
||||
}
|
||||
}
|
||||
|
||||
public void writeTexture(long ptr) {
|
||||
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_LIGHTMAP_SCALE + 8, uScaleZ);
|
||||
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_LIGHTMAP_SCALE + 12, vScaleZ);
|
||||
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX, u0);
|
||||
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX + 4, v0);
|
||||
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX + 8, uScaleX);
|
||||
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX + 12, vScaleY);
|
||||
public boolean uvInsideTexture() {
|
||||
final float u1 = u0 + 2.0f * (Math.abs(uScaleX) + uScaleZ);
|
||||
final float v1 = v0 + vScaleY + vScaleZ;
|
||||
return u0 >= 0.0f && v0 >= 0.0f && u1 <= 1.0f + UV_EPSILON && v1 <= 1.0f + UV_EPSILON;
|
||||
}
|
||||
|
||||
public static boolean allInsideTexture(CubeParams[] cubes) {
|
||||
for (CubeParams cube : cubes) {
|
||||
if (!cube.uvInsideTexture()) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void writeTexture(long ptr, float regionU, float regionV, float regionW, float regionH) {
|
||||
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_LIGHTMAP_SCALE + 8, uScaleZ * regionW);
|
||||
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_LIGHTMAP_SCALE + 12, vScaleZ * regionH);
|
||||
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX, regionU + u0 * regionW);
|
||||
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX + 4, regionV + v0 * regionH);
|
||||
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX + 8, uScaleX * regionW);
|
||||
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX + 12, vScaleY * regionH);
|
||||
}
|
||||
}
|
||||
@@ -104,6 +104,7 @@ public final class FFPUniformBlock {
|
||||
public static final int TEX_ENV_COLOR_3 = vec4("u_TexEnvColor3");
|
||||
public static final int OVERLAY_COLOR = vec4("u_OverlayColor");
|
||||
public static final int SECONDARY_COLOR = vec3("u_SecondaryColor");
|
||||
public static final int ATLAS_GUTTER = flt("u_AtlasGutter");
|
||||
public static final int FOG_PARAMS = vec4("u_FogParams");
|
||||
public static final int FOG_COLOR = vec4("u_FogColor");
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.states.FogState;
|
||||
|
||||
public final class FfpGlsl {
|
||||
|
||||
private FfpGlsl() {}
|
||||
|
||||
// Mesa ffvertex_prog.c build_fog(): FDM_EYE_RADIAL/EYE_PLANE/EYE_PLANE_ABS
|
||||
public static String fogDistance(int mode, String eye) {
|
||||
return switch (mode) {
|
||||
case FogState.DISTANCE_EYE_RADIAL -> "length(" + eye + ".xyz)";
|
||||
case FogState.DISTANCE_EYE_PLANE -> eye + ".z";
|
||||
default -> "abs(" + eye + ".z)";
|
||||
};
|
||||
}
|
||||
|
||||
public static void appendFogFactor(StringBuilder sb, String indent, int fogMode, String coord, String params) {
|
||||
switch (fogMode) {
|
||||
case FragmentKey.FOG_LINEAR -> sb.append(indent).append("f = ").append(coord).append(" * ").append(params).append(".x + ").append(params).append(".y;\n");
|
||||
case FragmentKey.FOG_EXP -> sb.append(indent).append("f = exp2(-(").append(coord).append(" * ").append(params).append(".z));\n");
|
||||
case FragmentKey.FOG_EXP2 -> {
|
||||
sb.append(indent).append("float fogTmp = ").append(coord).append(" * ").append(params).append(".w;\n");
|
||||
sb.append(indent).append("f = exp2(-(fogTmp * fogTmp));\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String quantizeAlpha(String alpha) {
|
||||
return "round(" + alpha + " * 255.0)";
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import com.gtnewhorizons.angelica.glsm.GLContextState;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TexEnvState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
@@ -65,6 +66,16 @@ public final class FragmentKey {
|
||||
public static final int FOG_EXP = 2;
|
||||
public static final int FOG_EXP2 = 3;
|
||||
|
||||
public static int activeFogMode(GLContextState glCtx) {
|
||||
if (!glCtx.fogMode.isEnabled()) return FOG_NONE;
|
||||
return switch (glCtx.fogState.getFogMode()) {
|
||||
case GL11.GL_LINEAR -> FOG_LINEAR;
|
||||
case GL11.GL_EXP -> FOG_EXP;
|
||||
case GL11.GL_EXP2 -> FOG_EXP2;
|
||||
default -> FOG_NONE;
|
||||
};
|
||||
}
|
||||
|
||||
private static final int GLOBAL_BITS = 14;
|
||||
private static final int BIT_COMBINED_GLINT = 61;
|
||||
private static final int BIT_GLINT_REPLACE_ALPHA = 62;
|
||||
@@ -72,7 +83,7 @@ public final class FragmentKey {
|
||||
private static final int BIT_ALPHA_TEST = 2; // 1 bit
|
||||
private static final int BIT_ALPHA_FUNC = 3; // 3 bits
|
||||
private static final int BIT_SEPARATE_SPECULAR = 6; // 1 bit
|
||||
private static final int BIT_NR_ENABLED_UNITS = 7; // 3 bits
|
||||
private static final int BIT_TEXTURE_SUPERSAMPLING = 7; // 3 bits, log2(samples)
|
||||
private static final int BIT_OVERLAY_ENABLED = 10; // 1 bit
|
||||
private static final int BIT_LINE_STIPPLE = 11; // 1 bit
|
||||
private static final int BIT_COLOR_SUM = 12; // 1 bit
|
||||
@@ -117,15 +128,7 @@ public final class FragmentKey {
|
||||
if (CombinedGlint.replacesAlpha()) global |= 1L << BIT_GLINT_REPLACE_ALPHA;
|
||||
|
||||
// Fog
|
||||
if (glCtx.fogMode.isEnabled()) {
|
||||
final int fogMode = switch (glCtx.fogState.getFogMode()) {
|
||||
case GL11.GL_LINEAR -> FOG_LINEAR;
|
||||
case GL11.GL_EXP -> FOG_EXP;
|
||||
case GL11.GL_EXP2 -> FOG_EXP2;
|
||||
default -> FOG_NONE;
|
||||
};
|
||||
global |= (long) fogMode;
|
||||
}
|
||||
global |= activeFogMode(glCtx);
|
||||
|
||||
// Alpha test
|
||||
if (glCtx.alphaTest.isEnabled()) {
|
||||
@@ -170,7 +173,10 @@ public final class FragmentKey {
|
||||
}
|
||||
|
||||
final int nrEnabled = highestEnabled + 1;
|
||||
global |= ((long) (nrEnabled & 0x7)) << BIT_NR_ENABLED_UNITS;
|
||||
if (glCtx.textureSupersamplingSamples > 1 && (unit0Bits & 1L) != 0
|
||||
&& TextureInfoCache.INSTANCE.getInfo(textures.getTextureUnitBindings(0).getBinding()).getAtlasGutter() > 0) {
|
||||
global |= (long) Integer.numberOfTrailingZeros(glCtx.textureSupersamplingSamples) << BIT_TEXTURE_SUPERSAMPLING;
|
||||
}
|
||||
global |= (unit0Bits << GLOBAL_BITS);
|
||||
out[0] = global;
|
||||
|
||||
@@ -254,12 +260,13 @@ public final class FragmentKey {
|
||||
public int fogMode() { return (int) (packed[0] & 0x3); }
|
||||
public boolean combinedGlint() { return (packed[0] & (1L << BIT_COMBINED_GLINT)) != 0; }
|
||||
public boolean glintReplaceAlpha() { return (packed[0] & (1L << BIT_GLINT_REPLACE_ALPHA)) != 0; }
|
||||
public int textureSupersamplingSamples() { return 1 << (int) ((packed[0] >> BIT_TEXTURE_SUPERSAMPLING) & 0x7); }
|
||||
public boolean alphaTestEnabled() { return ((packed[0] >> BIT_ALPHA_TEST) & 1) != 0; }
|
||||
public int alphaTestFunc() { return (int) ((packed[0] >> BIT_ALPHA_FUNC) & 0x7); }
|
||||
public boolean separateSpecular() { return ((packed[0] >> BIT_SEPARATE_SPECULAR) & 1) != 0; }
|
||||
public boolean colorSum() { return ((packed[0] >> BIT_COLOR_SUM) & 1) != 0; }
|
||||
public boolean lineStipple() { return ((packed[0] >> BIT_LINE_STIPPLE) & 1) != 0; }
|
||||
public int nrEnabledUnits() { return (int) ((packed[0] >> BIT_NR_ENABLED_UNITS) & 0x7); }
|
||||
public int nrEnabledUnits() { return packed.length == 1 && !unitEnabled(0) ? 0 : packed.length; }
|
||||
public boolean overlayEnabled() { return ((packed[0] >> BIT_OVERLAY_ENABLED) & 1) != 0; }
|
||||
public boolean overlayInstanced() { return ((packed[0] >> BIT_OVERLAY_INSTANCED) & 1) != 0; }
|
||||
|
||||
@@ -398,6 +405,7 @@ public final class FragmentKey {
|
||||
sb.append(String.format("FFPFragmentKey[fog=%s alpha=%b(%s) specSep=%b colorSum=%b overlay=%b units=%d", fogName, alphaTestEnabled(),
|
||||
alphaTestEnabled() ? String.format("0x%04X", decodeAlphaFunc(alphaTestFunc())) : "-", separateSpecular(), colorSum(),
|
||||
overlayEnabled() || overlayInstanced(), nrEnabledUnits()));
|
||||
if (textureSupersamplingSamples() > 1) sb.append(" samples=").append(textureSupersamplingSamples());
|
||||
for (int i = 0; i < nrEnabledUnits(); i++) {
|
||||
if (!unitEnabled(i)) {
|
||||
sb.append(String.format(" u%d=OFF", i));
|
||||
|
||||
+8
-14
@@ -28,6 +28,10 @@ public final class FragmentShaderGenerator {
|
||||
return sb.toString();
|
||||
}
|
||||
sb.append("out vec4 fragColor;\n\n");
|
||||
if (key.textureSupersamplingSamples() > 1) {
|
||||
sb.append("#define TEXTURE_SUPERSAMPLING_SAMPLES ").append(key.textureSupersamplingSamples()).append('\n');
|
||||
sb.append(ShaderLoader.getShaderSource("angelica:texture_supersampling.glsl"));
|
||||
}
|
||||
|
||||
sb.append("void main() {\n");
|
||||
emitLineStipple(sb, key);
|
||||
@@ -86,7 +90,8 @@ public final class FragmentShaderGenerator {
|
||||
private static void emitTextureSampling(StringBuilder sb, FragmentKey key) {
|
||||
for (int i = 0; i < key.nrEnabledUnits(); i++) {
|
||||
if (!key.unitEnabled(i)) continue;
|
||||
sb.append(" vec4 tex").append(i).append("Color = textureProj(u_Sampler").append(i).append(", v_TexCoord").append(i).append(");\n");
|
||||
final String sample = i == 0 && key.textureSupersamplingSamples() > 1 ? "sampleSupersampledTexture" : "textureProj";
|
||||
sb.append(" vec4 tex").append(i).append("Color = ").append(sample).append("(u_Sampler").append(i).append(", v_TexCoord").append(i).append(");\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,7 +310,7 @@ public final class FragmentShaderGenerator {
|
||||
final int func = FragmentKey.decodeAlphaFunc(key.alphaTestFunc());
|
||||
|
||||
|
||||
sb.append(" float qa = round(color.a * 255.0);\n");
|
||||
sb.append(" float qa = ").append(FfpGlsl.quantizeAlpha("color.a")).append(";\n");
|
||||
switch (func) {
|
||||
case GL11.GL_NEVER ->
|
||||
sb.append(" discard;\n");
|
||||
@@ -335,18 +340,7 @@ public final class FragmentShaderGenerator {
|
||||
|
||||
sb.append(" // Fog\n");
|
||||
sb.append(" float f;\n");
|
||||
|
||||
switch (fogMode) {
|
||||
case FragmentKey.FOG_LINEAR ->
|
||||
sb.append(" f = v_FogCoord * u_FogParams.x + u_FogParams.y;\n");
|
||||
case FragmentKey.FOG_EXP ->
|
||||
sb.append(" f = exp2(-(v_FogCoord * u_FogParams.z));\n");
|
||||
case FragmentKey.FOG_EXP2 -> {
|
||||
sb.append(" float fogTmp = v_FogCoord * u_FogParams.w;\n");
|
||||
sb.append(" f = exp2(-(fogTmp * fogTmp));\n");
|
||||
}
|
||||
}
|
||||
|
||||
FfpGlsl.appendFogFactor(sb, " ", fogMode, "v_FogCoord", "u_FogParams");
|
||||
sb.append(" f = clamp(f, 0.0, 1.0);\n");
|
||||
sb.append(" color.rgb = mix(u_FogColor.rgb, color.rgb, f);\n");
|
||||
}
|
||||
|
||||
@@ -1,119 +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(" float d0 = c0.z + c0.w;\n");
|
||||
sb.append(" float d1 = c1.z + c1.w;\n");
|
||||
sb.append(" if (d0 < 0.0 && d1 < 0.0) return;\n");
|
||||
sb.append(" float t0 = d0 < 0.0 ? d0 / (d0 - d1) : 0.0;\n");
|
||||
sb.append(" float t1 = d1 < 0.0 ? d0 / (d0 - d1) : 1.0;\n");
|
||||
sb.append(" vec4 p0 = mix(c0, c1, t0);\n");
|
||||
sb.append(" vec4 p1 = mix(c0, c1, t1);\n\n");
|
||||
|
||||
sb.append(" // NDC positions\n");
|
||||
sb.append(" vec2 n0 = p0.xy / p0.w;\n");
|
||||
sb.append(" vec2 n1 = p1.xy / p1.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 pi = "p" + endpointIdx;
|
||||
final String ni = "n" + endpointIdx;
|
||||
final String ti = "t" + endpointIdx;
|
||||
|
||||
sb.append(" // Endpoint ").append(endpointIdx).append(' ').append(offsetExpr).append('\n');
|
||||
|
||||
sb.append(" v_Color = ").append(lerp("v_Color_gs", ti)).append(";\n");
|
||||
if (key.separateSpecular()) {
|
||||
sb.append(" v_SpecularColor = ").append(lerp("v_SpecularColor_gs", ti)).append(";\n");
|
||||
}
|
||||
if (key.unitTexCoordEnabled(0) || key.texGenEnabled()) sb.append(" v_TexCoord0 = ").append(lerp("v_TexCoord0_gs", ti)).append(";\n");
|
||||
if (key.lightmapEnabled()) sb.append(" v_TexCoord1 = ").append(lerp("v_TexCoord1_gs", ti)).append(";\n");
|
||||
if (key.unitTexCoordEnabled(2)) sb.append(" v_TexCoord2 = ").append(lerp("v_TexCoord2_gs", ti)).append(";\n");
|
||||
if (key.unitTexCoordEnabled(3)) sb.append(" v_TexCoord3 = ").append(lerp("v_TexCoord3_gs", ti)).append(";\n");
|
||||
if (key.fogEnabled()) {
|
||||
sb.append(" v_FogCoord = ").append(lerp("v_FogCoord_gs", ti)).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("] = mix(gl_in[0].gl_ClipDistance[").append(i).append("], gl_in[1].gl_ClipDistance[").append(i).append("], ").append(ti).append(");\n");
|
||||
}
|
||||
}
|
||||
|
||||
sb.append(" gl_Position = vec4((").append(ni).append(' ').append(offsetExpr).append(") * ").append(pi).append(".w, ").append(pi).append(".z, ").append(pi).append(".w);\n");
|
||||
sb.append(" EmitVertex();\n\n");
|
||||
}
|
||||
|
||||
private static String lerp(String input, String t) {
|
||||
return "mix(" + input + "[0], " + input + "[1], " + t + ")";
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,8 @@ public final class InstancedAttribs {
|
||||
public static final int OFFSET_LIGHTMAP = HEAD_SIZE;
|
||||
public static final int STRIDE = 72;
|
||||
|
||||
public static final long DEFAULT_ENTITY_IDS = packEntityInfo(-1, 0, -1);
|
||||
|
||||
public static void writeHead(long ptr, float[] colMajor, int off, int colorABGR, int overlayABGR, long entityInfo) {
|
||||
writeRows(ptr, colMajor, off);
|
||||
writeTail(ptr, colorABGR, overlayABGR, entityInfo);
|
||||
@@ -60,13 +62,21 @@ public final class InstancedAttribs {
|
||||
return id == 0xFFFF ? -1 : id;
|
||||
}
|
||||
|
||||
public static float normalizedId(long info, int shift) {
|
||||
return ((info >>> shift) & 0xFFFF) / 65535.0f;
|
||||
}
|
||||
|
||||
public static void pointHead(long base, int stride) {
|
||||
for (int r = 0; r < 3; r++) {
|
||||
GLStateManager.glVertexAttribPointer(LOC_ROW0 + r, 4, GL11.GL_FLOAT, false, stride, base + OFFSET_ROWS + r * 16L);
|
||||
}
|
||||
GLStateManager.glVertexAttribPointer(LOC_COLOR, 4, GL11.GL_UNSIGNED_BYTE, true, stride, base + OFFSET_COLOR);
|
||||
GLStateManager.glVertexAttribPointer(LOC_OVERLAY, 4, GL11.GL_UNSIGNED_BYTE, true, stride, base + OFFSET_OVERLAY);
|
||||
GLStateManager.glVertexAttribIPointer(LOC_ENTITY, 4, GL11.GL_UNSIGNED_SHORT, stride, base + OFFSET_ENTITY);
|
||||
pointEntityIds(LOC_ENTITY, stride, base + OFFSET_ENTITY);
|
||||
}
|
||||
|
||||
public static void pointEntityIds(int location, int stride, long offset) {
|
||||
GLStateManager.glVertexAttribPointer(location, 4, GL11.GL_UNSIGNED_SHORT, true, stride, offset);
|
||||
}
|
||||
|
||||
public static void pointTemplate(long base) {
|
||||
|
||||
@@ -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();
|
||||
@@ -140,6 +139,7 @@ public final class ShaderManager {
|
||||
GLSMHooks.resolvePendingProgram();
|
||||
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
|
||||
if (bh != null) bh.flushDeferredBlend();
|
||||
GLStateManager.flushEntityIds(glCtx);
|
||||
|
||||
// Handle FFP & Iris uniforms
|
||||
final int currentProgramId = glCtx.activeProgram;
|
||||
@@ -190,6 +190,10 @@ public final class ShaderManager {
|
||||
}
|
||||
}
|
||||
|
||||
boolean isTextureSupersampling() {
|
||||
return currentProgram != null && currentProgram.getFragmentKey().textureSupersamplingSamples() > 1;
|
||||
}
|
||||
|
||||
public static void endFrame() {
|
||||
final ShaderManager sm = GLStateManager.ctx().ffp;
|
||||
sm.lastFramePreDrawCalls = sm.preDrawCalls;
|
||||
@@ -199,18 +203,21 @@ public final class ShaderManager {
|
||||
}
|
||||
|
||||
public void setNormal(float x, float y, float z) {
|
||||
GLStateManager.recordExitNormal();
|
||||
normalStack.beforeModify();
|
||||
currentNormal.set(x, y, z);
|
||||
normalGeneration++;
|
||||
}
|
||||
|
||||
public void setTexCoord(float s, float t, float r, float q) {
|
||||
GLStateManager.recordExitTexCoord(0);
|
||||
texCoordStack.beforeModify();
|
||||
currentTexCoords[0].set(s, t, r, q);
|
||||
texCoordGeneration++;
|
||||
}
|
||||
|
||||
public void setTexCoord(int unit, float s, float t, float r, float q) {
|
||||
GLStateManager.recordExitTexCoord(unit);
|
||||
if (unit == 0) texCoordStack.beforeModify();
|
||||
currentTexCoords[unit].set(s, t, r, q);
|
||||
texCoordGeneration++;
|
||||
|
||||
@@ -11,6 +11,7 @@ import com.gtnewhorizons.angelica.glsm.states.TexGenState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ViewportState;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.UniformRingBuffer;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
|
||||
import org.joml.Math;
|
||||
import org.joml.Matrix3f;
|
||||
import org.joml.Matrix4f;
|
||||
@@ -49,6 +50,7 @@ public class Uniforms {
|
||||
private int viewportX = Integer.MIN_VALUE, viewportY = Integer.MIN_VALUE, viewportWidth = -1, viewportHeight = -1;
|
||||
private int lineStipple = -1;
|
||||
private int weatherGen = -1;
|
||||
private int atlasGutter = -1;
|
||||
|
||||
int blockWrites;
|
||||
int blockSkips;
|
||||
@@ -80,9 +82,6 @@ public class Uniforms {
|
||||
private final Vector3f tempVec3 = new Vector3f();
|
||||
private final FloatBuffer clipPlaneBuf = memAllocFloat(32); // 8 planes * vec4
|
||||
|
||||
private static final double LN2 = java.lang.Math.log(2.0);
|
||||
private static final double SQRT_LN2 = java.lang.Math.sqrt(LN2);
|
||||
|
||||
public void upload(GLContextState glCtx) {
|
||||
boolean dirty = false;
|
||||
contentChanged = false;
|
||||
@@ -173,6 +172,15 @@ public class Uniforms {
|
||||
stagedFragment++;
|
||||
dirty = true;
|
||||
}
|
||||
if (glCtx.textureSupersamplingSamples > 1 && ffp.isTextureSupersampling()) {
|
||||
final int gutter = TextureInfoCache.INSTANCE.getInfo(glCtx.textures.getTextureUnitBindings(0).getBinding()).getAtlasGutter();
|
||||
if (gutter != atlasGutter) {
|
||||
putFloat(FFPUniformBlock.ATLAS_GUTTER, gutter);
|
||||
atlasGutter = gutter;
|
||||
stagedMisc++;
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
final LineState line = glCtx.lineState;
|
||||
final float lw = line.getWidth();
|
||||
if (lw != lineWidth) {
|
||||
@@ -233,8 +241,11 @@ public class Uniforms {
|
||||
if (projChanged) {
|
||||
putMat4(FFPUniformBlock.PROJECTION_MATRIX, proj);
|
||||
}
|
||||
proj.mul(mv, mvpMatrix);
|
||||
putMat4(FFPUniformBlock.MVP_MATRIX, mvpMatrix);
|
||||
putMat4(FFPUniformBlock.MVP_MATRIX, modelViewProjection(proj, mv, mvpMatrix));
|
||||
}
|
||||
|
||||
public static Matrix4f modelViewProjection(Matrix4f proj, Matrix4f mv, Matrix4f dest) {
|
||||
return proj.mul(mv, dest);
|
||||
}
|
||||
|
||||
private void stageNormalMatrix(GLContextState glCtx) {
|
||||
@@ -343,17 +354,8 @@ public class Uniforms {
|
||||
final var secondary = glCtx.secondaryColor;
|
||||
putVec3(FFPUniformBlock.SECONDARY_COLOR, secondary.getRed(), secondary.getGreen(), secondary.getBlue());
|
||||
|
||||
// Mesa STATE_FOG_PARAMS_OPTIMIZED
|
||||
final FogState fog = glCtx.fogState;
|
||||
final float start = fog.getStart();
|
||||
final float end = fog.getEnd();
|
||||
final float density = fog.getDensity();
|
||||
final float range = end - start;
|
||||
putVec4(FFPUniformBlock.FOG_PARAMS,
|
||||
range != 0.0f ? -1.0f / range : 0.0f,
|
||||
range != 0.0f ? end / range : 1.0f,
|
||||
(float) (density / LN2),
|
||||
(float) (density / SQRT_LN2));
|
||||
putVec4(FFPUniformBlock.FOG_PARAMS, fog.linearScale(), fog.linearBias(), fog.expScale(), fog.exp2Scale());
|
||||
putVec4(FFPUniformBlock.FOG_COLOR,
|
||||
(float) fog.getFogColor().x, (float) fog.getFogColor().y, (float) fog.getFogColor().z,
|
||||
fog.getFogAlpha());
|
||||
@@ -420,6 +422,7 @@ public class Uniforms {
|
||||
if (ring != null) {
|
||||
ring.endFrame();
|
||||
}
|
||||
invalidateBinding();
|
||||
lastFrameBlockWrites = blockWrites;
|
||||
lastFrameBlockSkips = blockSkips;
|
||||
lastFrameStagedMatrices = stagedMatrices;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
|
||||
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
|
||||
|
||||
/**
|
||||
* Generates GLSL 330 core vertex shaders for FFP emulation.
|
||||
@@ -19,6 +20,9 @@ public final class VertexShaderGenerator {
|
||||
emitAttributes(sb, key);
|
||||
emitUniforms(sb, key);
|
||||
emitOutputs(sb, key);
|
||||
if (key.lineQuads()) {
|
||||
sb.append(ShaderLoader.getShaderSource("angelica:include/line_quads.glsl")).append('\n');
|
||||
}
|
||||
|
||||
sb.append("void main() {\n");
|
||||
if (key.instancing() == Instancing.WEATHER) {
|
||||
@@ -34,28 +38,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 +70,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 +91,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");
|
||||
}
|
||||
|
||||
@@ -110,9 +104,9 @@ public final class VertexShaderGenerator {
|
||||
private static void emitOutputs(StringBuilder sb, VertexKey key) {
|
||||
sb.append("// Outputs\n");
|
||||
sb.append("out vec4 v_Color;\n");
|
||||
sb.append("invariant gl_Position;\n");
|
||||
if (key.instancing().hasInstanceHead()) {
|
||||
sb.append("out vec4 v_Overlay;\n");
|
||||
sb.append("invariant gl_Position;\n");
|
||||
}
|
||||
if (key.lineStipple()) {
|
||||
sb.append("flat out vec2 v_LineStart;\n");
|
||||
@@ -151,15 +145,32 @@ 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, key);
|
||||
}
|
||||
|
||||
// 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, VertexKey key) {
|
||||
sb.append(" int lineCorner = gl_VertexID % 6;\n");
|
||||
sb.append(" bool lineStart = angelica_lineStart(lineCorner);\n");
|
||||
sb.append(" vec4 lineOther = u_MVPMatrix * vec4(a_LineOtherEnd, 1.0);\n");
|
||||
if (key.lineStipple()) {
|
||||
sb.append(" vec4 lineCenter = lineStart ? gl_Position : lineOther;\n");
|
||||
}
|
||||
sb.append(" float lineT;\n");
|
||||
sb.append(" gl_Position = angelica_lineQuad(gl_Position, lineOther, lineCorner, u_ViewportSize, u_LineWidth, lineT);\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 +391,21 @@ 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 = FfpGlsl.fogDistance(key.fogDistanceMode(), "eyePos");
|
||||
final String fog = key.lineQuads() ? acrossLine(own, FfpGlsl.fogDistance(key.fogDistanceMode(), "lineEyeOther")) : own;
|
||||
sb.append(" v_FogCoord = ").append(fog).append(";\n");
|
||||
}
|
||||
|
||||
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)";
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.glsm.hooks;
|
||||
|
||||
public interface DeferredAlphaHandler {
|
||||
boolean isAlphaTestLocked();
|
||||
boolean isOverrideHeld();
|
||||
boolean deferAlphaTestToggle(boolean enabled);
|
||||
boolean deferAlphaFunc(int function, float reference);
|
||||
}
|
||||
@@ -26,6 +26,7 @@ public final class FrameHooks {
|
||||
}
|
||||
|
||||
public static void frameEnd() {
|
||||
GLStateManager.onFrameEnd();
|
||||
popFrameGroup();
|
||||
RENDER_BACKEND.onFrameEnd();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
+10
@@ -3,15 +3,18 @@ package com.gtnewhorizons.angelica.glsm.hooks;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.InstancedAttribs;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
public interface ImmediateExtendedAttribHandler {
|
||||
|
||||
int EXT_STRIDE = 12;
|
||||
int ENTITY_IDS_STRIDE = 8;
|
||||
int LOC_MC_ENTITY = 11;
|
||||
int LOC_MID_TEX = 12;
|
||||
int LOC_TANGENT = 13;
|
||||
int LOC_MIDBLOCK = 14;
|
||||
int LOC_ENTITY_IDS = 15;
|
||||
|
||||
int RAW_NORMAL_INDEX = 6;
|
||||
|
||||
@@ -19,6 +22,8 @@ public interface ImmediateExtendedAttribHandler {
|
||||
|
||||
boolean wantsTangent();
|
||||
|
||||
boolean wantsEntityIds();
|
||||
|
||||
boolean wantsExtendedCapture();
|
||||
|
||||
boolean wantsFaceNormals();
|
||||
@@ -65,4 +70,9 @@ public interface ImmediateExtendedAttribHandler {
|
||||
GLStateManager.glEnableVertexAttribArray(LOC_TANGENT);
|
||||
GLStateManager.glVertexAttribPointer(LOC_TANGENT, 4, GL11.GL_BYTE, true, stride, baseOffset + 8);
|
||||
}
|
||||
|
||||
static void setupEntityIdsPointer(long offset, int stride) {
|
||||
GLStateManager.glEnableVertexAttribArray(LOC_ENTITY_IDS);
|
||||
InstancedAttribs.pointEntityIds(LOC_ENTITY_IDS, stride, offset);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks;
|
||||
|
||||
public interface LineDrawHandler {
|
||||
|
||||
boolean bindLineProgram();
|
||||
|
||||
void restoreProgram();
|
||||
}
|
||||
@@ -47,6 +47,31 @@ public final class TextureUtilHooks {
|
||||
GLStateManager.ctx().savedFilterTextureId = 0;
|
||||
}
|
||||
|
||||
public static boolean beginFilterSave(boolean blurred, boolean mipmap, float aniso) {
|
||||
captureBoundFilterTexture();
|
||||
return GLStateManager.beginExitTextureFilter(!blurred && !mipmap && aniso == 1);
|
||||
}
|
||||
|
||||
public static void endFilterSave(boolean captured) {
|
||||
if (captured) GLStateManager.finishExitTextureFilterSave();
|
||||
}
|
||||
|
||||
public static boolean beginFilterRestore() {
|
||||
return GLStateManager.beginExitTextureFilterRestore();
|
||||
}
|
||||
|
||||
public static void endFilterRestore(boolean captured) {
|
||||
try {
|
||||
if (captured) GLStateManager.finishExitTextureFilterRestore();
|
||||
} finally {
|
||||
clearSavedFilterTexture();
|
||||
}
|
||||
}
|
||||
|
||||
public static void filterFailed(boolean captured) {
|
||||
if (captured) GLStateManager.failExitTextureFilter();
|
||||
}
|
||||
|
||||
public static void restoreFilter(int min, int mag) {
|
||||
final int id = GLStateManager.ctx().savedFilterTextureId;
|
||||
if (id != 0 && DisplayListManager.getRecordMode() == DisplayListManager.RecordMode.NONE) {
|
||||
|
||||
+2
-1
@@ -525,8 +525,9 @@ public final class CommandBufferExecutor {
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.BIND_VAO -> {
|
||||
GLStateManager.glBindVertexArray(memGetInt(ptr));
|
||||
final int vao = memGetInt(ptr);
|
||||
ptr += 4;
|
||||
if (compiledDisplayList.replaysVertexArrayBind(vao)) GLStateManager.glBindVertexArray(vao);
|
||||
}
|
||||
|
||||
// === Complex object reference ===
|
||||
|
||||
+10
-2
@@ -1,6 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
|
||||
import com.gtnewhorizons.angelica.glsm.GLContextState;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawBatch;
|
||||
@@ -30,12 +31,13 @@ public final class CompiledDisplayList {
|
||||
* Empty display list singleton. Per OpenGL spec, an empty list is still valid.
|
||||
* This instance does nothing when rendered and is never deleted (shared singleton).
|
||||
*/
|
||||
public static final CompiledDisplayList EMPTY = new CompiledDisplayList(null, null, null, Collections.emptyList());
|
||||
public static final CompiledDisplayList EMPTY = new CompiledDisplayList(null, null, null, Collections.emptyList(), null);
|
||||
|
||||
private final ByteBuffer commandBuffer; // Off-heap command storage, must be freed
|
||||
private final DisplayListCommand[] complexObjects; // Complex commands (TexImage2D, etc.)
|
||||
private final DisplayListVBO ownedVbos; // GPU resources referenced by index
|
||||
private final List<IndexedDrawBatch> indexedBatches; // Shared VAO/VBO/EBO triples per layout group
|
||||
private GLContextState compileCtx;
|
||||
private Int2IntMap replayPrograms;
|
||||
private long programLifetimeGeneration = -1;
|
||||
|
||||
@@ -55,11 +57,16 @@ public final class CompiledDisplayList {
|
||||
return valid ? program : 0;
|
||||
}
|
||||
|
||||
public CompiledDisplayList(ByteBuffer commandBuffer, DisplayListCommand[] complexObjects, DisplayListVBO ownedVbos, List<IndexedDrawBatch> indexedBatches) {
|
||||
public CompiledDisplayList(ByteBuffer commandBuffer, DisplayListCommand[] complexObjects, DisplayListVBO ownedVbos, List<IndexedDrawBatch> indexedBatches, GLContextState compileCtx) {
|
||||
this.commandBuffer = commandBuffer;
|
||||
this.complexObjects = complexObjects;
|
||||
this.ownedVbos = ownedVbos;
|
||||
this.indexedBatches = indexedBatches;
|
||||
this.compileCtx = compileCtx;
|
||||
}
|
||||
|
||||
boolean replaysVertexArrayBind(int vao) {
|
||||
return vao == 0 || compileCtx == GLStateManager.ctx();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,6 +108,7 @@ public final class CompiledDisplayList {
|
||||
if (commandBuffer != null) {
|
||||
memFree(commandBuffer);
|
||||
}
|
||||
compileCtx = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.gtnewhorizons.angelica.glsm.recording;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IVertexArrayObject;
|
||||
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 +57,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 +93,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 +120,18 @@ public final class DisplayListVBO {
|
||||
vao.unbind();
|
||||
}
|
||||
|
||||
private void renderLineQuads() {
|
||||
lineQuads.bind();
|
||||
final boolean culled = LineQuads.beginDraw(lineQuadFlags);
|
||||
FfpExtendedAttribs.beginInternalDraw();
|
||||
try {
|
||||
lineQuads.draw(GL11.GL_TRIANGLES, lineQuadStart, lineQuadCount);
|
||||
} finally {
|
||||
FfpExtendedAttribs.endInternalDraw();
|
||||
LineQuads.endDraw(culled);
|
||||
}
|
||||
lineQuads.unbind();
|
||||
}
|
||||
|
||||
public IVertexArrayObject getVAO() {
|
||||
return vao;
|
||||
|
||||
+40
@@ -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;
|
||||
|
||||
+34
@@ -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;
|
||||
|
||||
+6
-7
@@ -3,21 +3,20 @@ package com.gtnewhorizons.angelica.glsm.recording.commands;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
|
||||
public final class BatchedIndexedDrawCmd implements DisplayListCommand {
|
||||
private int sharedVAO;
|
||||
private IndexedDrawBatch batch;
|
||||
private int drawMode;
|
||||
private int indexCount;
|
||||
private int indexType;
|
||||
private long indexByteOffset;
|
||||
|
||||
public void fill(int sharedVAO, int drawMode, int indexCount, int indexType, long indexByteOffset) {
|
||||
this.sharedVAO = sharedVAO;
|
||||
public void fill(IndexedDrawBatch batch, int drawMode, int indexCount, int indexType, long indexByteOffset) {
|
||||
this.batch = batch;
|
||||
this.drawMode = drawMode;
|
||||
this.indexCount = indexCount;
|
||||
this.indexType = indexType;
|
||||
this.indexByteOffset = indexByteOffset;
|
||||
}
|
||||
|
||||
public int getSharedVAO() { return sharedVAO; }
|
||||
public int getDrawMode() { return drawMode; }
|
||||
public int getIndexCount() { return indexCount; }
|
||||
public int getIndexType() { return indexType; }
|
||||
@@ -25,13 +24,13 @@ public final class BatchedIndexedDrawCmd implements DisplayListCommand {
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
if (sharedVAO == 0) return;
|
||||
GLStateManager.glBindVertexArray(sharedVAO);
|
||||
if (batch == null) return;
|
||||
batch.bind();
|
||||
GLStateManager.glDrawElements(drawMode, indexCount, indexType, indexByteOffset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BatchedIndexedDrawCmd(vao=" + sharedVAO + ", mode=" + drawMode + ", count=" + indexCount + ", type=" + indexType + ", offset=" + indexByteOffset + ")";
|
||||
return "BatchedIndexedDrawCmd(batch=" + batch + ", mode=" + drawMode + ", count=" + indexCount + ", type=" + indexType + ", offset=" + indexByteOffset + ")";
|
||||
}
|
||||
}
|
||||
+33
-7
@@ -1,34 +1,60 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording.commands;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLContextState;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
public final class IndexedDrawBatch {
|
||||
private int sharedVAO;
|
||||
private final AttribLayoutKey layout;
|
||||
private int sharedVBO;
|
||||
private int sharedEBO;
|
||||
private int vao;
|
||||
private GLContextState vaoCtx;
|
||||
private boolean deleted;
|
||||
|
||||
public IndexedDrawBatch(int sharedVAO, int sharedVBO, int sharedEBO) {
|
||||
this.sharedVAO = sharedVAO;
|
||||
public IndexedDrawBatch(AttribLayoutKey layout, int sharedVBO, int sharedEBO) {
|
||||
this.layout = layout;
|
||||
this.sharedVBO = sharedVBO;
|
||||
this.sharedEBO = sharedEBO;
|
||||
}
|
||||
|
||||
public int getSharedVAO() { return sharedVAO; }
|
||||
public int getSharedVBO() { return sharedVBO; }
|
||||
public int getSharedEBO() { return sharedEBO; }
|
||||
|
||||
public void bind() {
|
||||
final GLContextState glCtx = GLStateManager.ctx();
|
||||
if (vaoCtx == glCtx) {
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
return;
|
||||
}
|
||||
final int prevVBO = GLStateManager.getBoundVBO();
|
||||
final int v = GLStateManager.glGenVertexArrays();
|
||||
GLStateManager.glBindVertexArray(v);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, sharedVBO);
|
||||
final int stride = layout.stride();
|
||||
for (int i = 0; i < layout.locations.length; i++) {
|
||||
final int loc = layout.locations[i];
|
||||
GLStateManager.glVertexAttribPointer(loc, layout.sizes[i], layout.types[i], layout.normalized[i], stride, layout.offset(i));
|
||||
GLStateManager.glEnableVertexAttribArray(loc);
|
||||
}
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, sharedEBO);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, prevVBO);
|
||||
vao = v;
|
||||
vaoCtx = glCtx;
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
if (deleted) return;
|
||||
deleted = true;
|
||||
if (sharedVAO != 0) GLStateManager.glDeleteVertexArrays(sharedVAO);
|
||||
if (vaoCtx == GLStateManager.ctx()) GLStateManager.glDeleteVertexArrays(vao);
|
||||
if (sharedVBO != 0) GLStateManager.glDeleteBuffers(sharedVBO);
|
||||
if (sharedEBO != 0) GLStateManager.glDeleteBuffers(sharedEBO);
|
||||
sharedVAO = sharedVBO = sharedEBO = 0;
|
||||
vao = sharedVBO = sharedEBO = 0;
|
||||
vaoCtx = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "IndexedDrawBatch(vao=" + sharedVAO + ", vbo=" + sharedVBO + ", ebo=" + sharedEBO + ")";
|
||||
return "IndexedDrawBatch(vao=" + vao + ", vbo=" + sharedVBO + ", ebo=" + sharedEBO + ")";
|
||||
}
|
||||
}
|
||||
+2
-19
@@ -35,7 +35,6 @@ public final class IndexedDrawBatchBuilder {
|
||||
final List<IndexedDrawBatch> out = new ArrayList<>();
|
||||
if (captures.isEmpty()) return out;
|
||||
|
||||
final int prevVAO = GLStateManager.getBoundVAO();
|
||||
final int prevVBO = GLStateManager.getBoundVBO();
|
||||
final int prevEBO = GLStateManager.getBoundEBO();
|
||||
|
||||
@@ -58,7 +57,6 @@ public final class IndexedDrawBatchBuilder {
|
||||
if (!success) {
|
||||
for (GroupResult r : results) r.batch.delete();
|
||||
}
|
||||
GLStateManager.glBindVertexArray(prevVAO);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEBO);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, prevVBO);
|
||||
}
|
||||
@@ -69,9 +67,8 @@ public final class IndexedDrawBatchBuilder {
|
||||
private record GroupResult(IndexedDrawBatch batch, List<IndexedDrawCapture> captures,
|
||||
int[] drawModes, int[] indexCounts, long[] indexOffsets) {
|
||||
void commit() {
|
||||
final int sharedVAO = batch.getSharedVAO();
|
||||
for (int i = 0; i < captures.size(); i++) {
|
||||
captures.get(i).placeholder.fill(sharedVAO, drawModes[i], indexCounts[i], GL11.GL_UNSIGNED_INT, indexOffsets[i]);
|
||||
captures.get(i).placeholder.fill(batch, drawModes[i], indexCounts[i], GL11.GL_UNSIGNED_INT, indexOffsets[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,7 +84,6 @@ public final class IndexedDrawBatchBuilder {
|
||||
throw new IllegalStateException("batch exceeds 2GiB — split needed");
|
||||
}
|
||||
|
||||
int sharedVAO = 0;
|
||||
int sharedVBO = 0;
|
||||
int sharedEBO = 0;
|
||||
ByteBuffer eboData = null;
|
||||
@@ -99,12 +95,10 @@ public final class IndexedDrawBatchBuilder {
|
||||
final long[] indexOffsets = new long[n];
|
||||
|
||||
try {
|
||||
sharedVAO = GLStateManager.glGenVertexArrays();
|
||||
sharedVBO = GLStateManager.glGenBuffers();
|
||||
sharedEBO = GLStateManager.glGenBuffers();
|
||||
eboData = MemoryUtilities.memAlloc((int) totalIndexBytes);
|
||||
|
||||
GLStateManager.glBindVertexArray(sharedVAO);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, sharedVBO);
|
||||
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, totalVertexBytes, GL15.GL_STATIC_DRAW);
|
||||
|
||||
@@ -146,23 +140,12 @@ public final class IndexedDrawBatchBuilder {
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, sharedEBO);
|
||||
GLStateManager.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, eboData, GL15.GL_STATIC_DRAW);
|
||||
|
||||
final AttribLayoutKey layout = group.get(0).layoutKey;
|
||||
final int stride = layout.stride();
|
||||
for (int i = 0; i < layout.locations.length; i++) {
|
||||
final int loc = layout.locations[i];
|
||||
final int size = layout.sizes[i];
|
||||
final int type = layout.types[i];
|
||||
GLStateManager.glVertexAttribPointer(loc, size, type, layout.normalized[i], stride, layout.offset(i));
|
||||
GLStateManager.glEnableVertexAttribArray(loc);
|
||||
}
|
||||
|
||||
success = true;
|
||||
return new GroupResult(new IndexedDrawBatch(sharedVAO, sharedVBO, sharedEBO),
|
||||
return new GroupResult(new IndexedDrawBatch(group.get(0).layoutKey, sharedVBO, sharedEBO),
|
||||
group, drawModes, indexCounts, indexOffsets);
|
||||
} finally {
|
||||
if (eboData != null) MemoryUtilities.memFree(eboData);
|
||||
if (!success) {
|
||||
if (sharedVAO != 0) GLStateManager.glDeleteVertexArrays(sharedVAO);
|
||||
if (sharedVBO != 0) GLStateManager.glDeleteBuffers(sharedVBO);
|
||||
if (sharedEBO != 0) GLStateManager.glDeleteBuffers(sharedEBO);
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
@@ -166,6 +167,14 @@ public class GLSMRedirector {
|
||||
private static final String EXTTimerQuery = "org/lwjgl/opengl/EXTTimerQuery";
|
||||
private static final String ARBSamplerObjects = "org/lwjgl/opengl/ARBSamplerObjects";
|
||||
private static final String ARBMultiBind = "org/lwjgl/opengl/ARBMultiBind";
|
||||
private static final String ARBDirectStateAccess = "org/lwjgl/opengl/ARBDirectStateAccess";
|
||||
private static final String ARBGeometryShader4 = "org/lwjgl/opengl/ARBGeometryShader4";
|
||||
private static final String EXTGeometryShader4 = "org/lwjgl/opengl/EXTGeometryShader4";
|
||||
private static final String NVGeometryProgram4 = "org/lwjgl/opengl/NVGeometryProgram4";
|
||||
private static final String EXTTextureArray = "org/lwjgl/opengl/EXTTextureArray";
|
||||
private static final String EXTDirectStateAccess = "org/lwjgl/opengl/EXTDirectStateAccess";
|
||||
private static final String ARBShaderImageLoadStore = "org/lwjgl/opengl/ARBShaderImageLoadStore";
|
||||
private static final String EXTShaderImageLoadStore = "org/lwjgl/opengl/EXTShaderImageLoadStore";
|
||||
|
||||
// Redirect VAO related calls from NHLib
|
||||
private static final String UniversalVAO = "com/gtnewhorizon/gtnhlib/client/opengl/UniversalVAO";
|
||||
@@ -378,6 +387,8 @@ public class GLSMRedirector {
|
||||
.add("glEnableClientState")
|
||||
.add("glDisableClientState")
|
||||
.add("glInterleavedArrays")
|
||||
.add("glEdgeFlagPointer")
|
||||
.add("glGetPointer")
|
||||
.add("glPushClientAttrib")
|
||||
.add("glPopClientAttrib")
|
||||
.add("glViewport")
|
||||
@@ -408,11 +419,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")
|
||||
@@ -505,7 +521,10 @@ public class GLSMRedirector {
|
||||
.add("glDeleteFramebuffers")
|
||||
.add("glGenFramebuffers")
|
||||
.add("glCheckFramebufferStatus")
|
||||
.add("glFramebufferTexture1D")
|
||||
.add("glFramebufferTexture2D")
|
||||
.add("glFramebufferTexture3D")
|
||||
.add("glFramebufferTextureLayer")
|
||||
.add("glVertexAttribIPointer")
|
||||
.add("glGenerateMipmap")
|
||||
.add("glGetFramebufferAttachmentParameteri")
|
||||
@@ -559,11 +578,18 @@ public class GLSMRedirector {
|
||||
final var gl44 = RedirectMap.newMap()
|
||||
.add("glBufferStorage")
|
||||
.add("glBindSamplers")
|
||||
.add("glClearTexImage");
|
||||
.add("glClearTexImage")
|
||||
.add("glBindTextures")
|
||||
.add("glBindImageTextures");
|
||||
final var gl45 = RedirectMap.newMap()
|
||||
.add("glCreateBuffers")
|
||||
.add("glNamedBufferData")
|
||||
.add("glNamedBufferSubData");
|
||||
.add("glNamedBufferSubData")
|
||||
.add("glNamedFramebufferTexture")
|
||||
.add("glNamedFramebufferTextureLayer")
|
||||
.add("glNamedFramebufferRenderbuffer")
|
||||
.add("glCreateTextures")
|
||||
.add("glBindTextureUnit");
|
||||
final var gl46 = RedirectMap.newMap()
|
||||
.add("glPolygonOffsetClamp")
|
||||
.add("glMultiDrawArraysIndirectCount")
|
||||
@@ -604,7 +630,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()
|
||||
@@ -620,7 +648,10 @@ public class GLSMRedirector {
|
||||
.add("glDeleteFramebuffers")
|
||||
.add("glGenFramebuffers")
|
||||
.add("glCheckFramebufferStatus")
|
||||
.add("glFramebufferTexture1D")
|
||||
.add("glFramebufferTexture2D")
|
||||
.add("glFramebufferTexture3D")
|
||||
.add("glFramebufferTextureLayer")
|
||||
.add("glBindRenderbuffer")
|
||||
.add("glDeleteRenderbuffers")
|
||||
.add("glGenRenderbuffers")
|
||||
@@ -633,7 +664,9 @@ public class GLSMRedirector {
|
||||
.add("glDeleteFramebuffersEXT", "glDeleteFramebuffers")
|
||||
.add("glGenFramebuffersEXT", "glGenFramebuffers")
|
||||
.add("glCheckFramebufferStatusEXT", "glCheckFramebufferStatus")
|
||||
.add("glFramebufferTexture1DEXT", "glFramebufferTexture1D")
|
||||
.add("glFramebufferTexture2DEXT", "glFramebufferTexture2D")
|
||||
.add("glFramebufferTexture3DEXT", "glFramebufferTexture3D")
|
||||
.add("glBindRenderbufferEXT", "glBindRenderbuffer")
|
||||
.add("glDeleteRenderbuffersEXT", "glDeleteRenderbuffers")
|
||||
.add("glGenRenderbuffersEXT", "glGenRenderbuffers")
|
||||
@@ -783,6 +816,45 @@ public class GLSMRedirector {
|
||||
);
|
||||
methodRedirects.put(ARBMultiBind, RedirectMap.newMap()
|
||||
.add("glBindSamplers")
|
||||
.add("glBindTextures")
|
||||
.add("glBindImageTextures")
|
||||
);
|
||||
methodRedirects.put(ARBDirectStateAccess, RedirectMap.newMap()
|
||||
.add("glNamedFramebufferTexture")
|
||||
.add("glNamedFramebufferTextureLayer")
|
||||
.add("glNamedFramebufferRenderbuffer")
|
||||
.add("glCreateTextures")
|
||||
.add("glBindTextureUnit")
|
||||
);
|
||||
methodRedirects.put(ARBGeometryShader4, RedirectMap.newMap()
|
||||
.add("glFramebufferTextureARB", "glFramebufferTexture")
|
||||
.add("glFramebufferTextureLayerARB", "glFramebufferTextureLayer")
|
||||
.add("glFramebufferTextureFaceARB", "glFramebufferTextureFace")
|
||||
);
|
||||
final var geometryShader4 = RedirectMap.newMap()
|
||||
.add("glFramebufferTextureEXT", "glFramebufferTexture")
|
||||
.add("glFramebufferTextureLayerEXT", "glFramebufferTextureLayer")
|
||||
.add("glFramebufferTextureFaceEXT", "glFramebufferTextureFace");
|
||||
methodRedirects.put(EXTGeometryShader4, geometryShader4);
|
||||
methodRedirects.put(NVGeometryProgram4, geometryShader4);
|
||||
methodRedirects.put(EXTTextureArray, RedirectMap.newMap()
|
||||
.add("glFramebufferTextureLayerEXT", "glFramebufferTextureLayer")
|
||||
);
|
||||
methodRedirects.put(EXTDirectStateAccess, RedirectMap.newMap()
|
||||
.add("glNamedFramebufferTexture1DEXT")
|
||||
.add("glNamedFramebufferTexture2DEXT")
|
||||
.add("glNamedFramebufferTexture3DEXT")
|
||||
.add("glNamedFramebufferTextureEXT")
|
||||
.add("glNamedFramebufferTextureLayerEXT", "glNamedFramebufferTextureLayer")
|
||||
.add("glNamedFramebufferTextureFaceEXT")
|
||||
.add("glNamedFramebufferRenderbufferEXT", "glNamedFramebufferRenderbuffer")
|
||||
.add("glBindMultiTextureEXT")
|
||||
);
|
||||
methodRedirects.put(ARBShaderImageLoadStore, RedirectMap.newMap()
|
||||
.add("glBindImageTexture")
|
||||
);
|
||||
methodRedirects.put(EXTShaderImageLoadStore, RedirectMap.newMap()
|
||||
.add("glBindImageTextureEXT", "glBindImageTexture")
|
||||
);
|
||||
|
||||
// APPLE
|
||||
|
||||
+62
-6
@@ -1,9 +1,12 @@
|
||||
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.TerminalNode;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -31,6 +34,17 @@ public final class GlslVulkanPreprocess {
|
||||
"const_cast", "dynamic_cast", "reinterpret_cast", "static_cast", "export",
|
||||
"and", "or", "xor", "bitand", "bitor", "compl", "and_eq", "or_eq", "xor_eq", "not_eq");
|
||||
|
||||
private static final Object2ObjectLinkedOpenHashMap<String, String> IDENTIFIER_RENAMES = identifierRenames();
|
||||
|
||||
private static Object2ObjectLinkedOpenHashMap<String, String> identifierRenames() {
|
||||
final Object2ObjectLinkedOpenHashMap<String, String> renames = new Object2ObjectLinkedOpenHashMap<>();
|
||||
renames.put("sampler", SAMPLER_RENAMED);
|
||||
renames.put("gl_VertexID", "gl_VertexIndex");
|
||||
renames.put("gl_InstanceID", "gl_InstanceIndex");
|
||||
for (String reserved : TARGET_RESERVED) renames.put(reserved, RESERVED_RENAMED_PREFIX + reserved);
|
||||
return renames;
|
||||
}
|
||||
|
||||
public static final String SAMPLERLESS_EXTENSION = "#extension GL_EXT_samplerless_texture_functions : require";
|
||||
|
||||
private static final int MAX_VS_INPUT_LOCATIONS = 16;
|
||||
@@ -188,14 +202,13 @@ public final class GlslVulkanPreprocess {
|
||||
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));
|
||||
addSamplerlessExtension(index, edits);
|
||||
}
|
||||
|
||||
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);
|
||||
for (var it = IDENTIFIER_RENAMES.object2ObjectEntrySet().fastIterator(); it.hasNext(); ) {
|
||||
final var rename = it.next();
|
||||
rename(index, rename.getKey(), rename.getValue(), edits);
|
||||
}
|
||||
|
||||
if (maxExplicitLoc >= 0 && !unlocatedVsInputStarts.isEmpty()) {
|
||||
int next = maxExplicitLoc + 1;
|
||||
@@ -213,10 +226,53 @@ public final class GlslVulkanPreprocess {
|
||||
return new Metadata(bools, explicitInputs, needsSamplerless);
|
||||
}
|
||||
|
||||
public static boolean renamesIdentifier(String name) {
|
||||
return IDENTIFIER_RENAMES.containsKey(name);
|
||||
}
|
||||
|
||||
public static void addSamplerlessExtension(ShaderIndex index, List<Edit> edits) {
|
||||
final int versionEnd = index.directives().versionEnd();
|
||||
if (versionEnd >= 0) edits.add(new Edit(versionEnd, versionEnd - 1, "\n" + SAMPLERLESS_EXTENSION));
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -21,8 +21,8 @@ import java.util.Set;
|
||||
*/
|
||||
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);
|
||||
public record Directives(Set<String> identifiers, Set<String> definedMacros, boolean hasConditionalOrMacro, int versionEnd) {
|
||||
private static final Directives NONE = new Directives(Set.of(), Set.of(), false, -1);
|
||||
}
|
||||
|
||||
private final GLSLParser.Translation_unitContext root;
|
||||
@@ -83,6 +83,7 @@ public final class ShaderIndex {
|
||||
if (source.indexOf('#') < 0) return Directives.NONE;
|
||||
final GLSLLexer lexer = GlslTransformUtils.quietLexer(source);
|
||||
final Set<String> names = new HashSet<>();
|
||||
final Set<String> macros = new HashSet<>();
|
||||
boolean conditionalOrMacro = false;
|
||||
int versionEnd = -1;
|
||||
boolean inVersion = false;
|
||||
@@ -100,11 +101,17 @@ public final class ShaderIndex {
|
||||
inVersion = false;
|
||||
switch (type) {
|
||||
case GLSLLexer.PROGRAM_TEXT, GLSLLexer.MACRO_TEXT, GLSLLexer.CONSTANT_EXPRESSION -> collectIdentifiers(token.getText(), names);
|
||||
case GLSLLexer.MACRO_NAME -> macros.add(leadingIdentifier(token.getText()));
|
||||
case GLSLLexer.IF_DIRECTIVE, GLSLLexer.IFDEF_DIRECTIVE, GLSLLexer.IFNDEF_DIRECTIVE, GLSLLexer.DEFINE_DIRECTIVE -> conditionalOrMacro = true;
|
||||
default -> { }
|
||||
}
|
||||
}
|
||||
return new Directives(names, conditionalOrMacro, versionEnd);
|
||||
return new Directives(names, macros, conditionalOrMacro, versionEnd);
|
||||
}
|
||||
|
||||
private static String leadingIdentifier(String text) {
|
||||
final int args = text.indexOf('(');
|
||||
return args < 0 ? text : text.substring(0, args);
|
||||
}
|
||||
|
||||
private static void collectIdentifiers(String text, Set<String> out) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+1
-2
@@ -59,8 +59,7 @@ 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 {
|
||||
|
||||
@@ -96,6 +96,7 @@ public class BooleanStateStack extends BooleanState implements CowStateStack<Boo
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
final GLContextState o = cow.owner;
|
||||
GLStateManager.recordExitCapability(o, glCap, -1);
|
||||
if (o != null && o.attribDepth > 0
|
||||
&& (stateUnknown || !GLStateManager.isCachingEnabled() || enabled != vanillaValue())) {
|
||||
beforeModify();
|
||||
|
||||
+1
@@ -24,6 +24,7 @@ public final class TextureUnitBooleanStateStack extends BooleanStateStack {
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
GLStateManager.recordExitCapability(cowDepths().owner, glCap, unitIndex);
|
||||
if (!GLStateManager.isCachingEnabled() || enabled != this.enabled) {
|
||||
GLStateManager.beforeUncapturedTextureUnitChange(glCap, unitIndex);
|
||||
beforeModify();
|
||||
|
||||
@@ -9,6 +9,13 @@ import java.nio.FloatBuffer;
|
||||
|
||||
@Getter @Setter
|
||||
public class FogState implements ISettableState<FogState> {
|
||||
public static final int DISTANCE_EYE_RADIAL = 0;
|
||||
public static final int DISTANCE_EYE_PLANE = 1;
|
||||
public static final int DISTANCE_EYE_PLANE_ABS = 2;
|
||||
|
||||
private static final double LN2 = Math.log(2.0);
|
||||
private static final double SQRT_LN2 = Math.sqrt(LN2);
|
||||
|
||||
protected int fogMode = GL11.GL_EXP;
|
||||
protected final Vector3d fogColor = new Vector3d(0.0F, 0.0F, 0.0F);
|
||||
protected float fogAlpha = 1.0F;
|
||||
@@ -16,8 +23,26 @@ public class FogState implements ISettableState<FogState> {
|
||||
protected float density = 1.0F;
|
||||
protected float start;
|
||||
protected float end = 1.0F;
|
||||
/** Fog distance computation mode: 0=EYE_RADIAL, 1=EYE_PLANE, 2=EYE_PLANE_ABS */
|
||||
protected int fogDistanceMode = 2;
|
||||
protected int fogDistanceMode = DISTANCE_EYE_PLANE_ABS;
|
||||
|
||||
// Mesa STATE_FOG_PARAMS_OPTIMIZED
|
||||
public float linearScale() {
|
||||
final float range = end - start;
|
||||
return range != 0.0f ? -1.0f / range : 0.0f;
|
||||
}
|
||||
|
||||
public float linearBias() {
|
||||
final float range = end - start;
|
||||
return range != 0.0f ? end / range : 1.0f;
|
||||
}
|
||||
|
||||
public float expScale() {
|
||||
return (float) (density / LN2);
|
||||
}
|
||||
|
||||
public float exp2Scale() {
|
||||
return (float) (density / SQRT_LN2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FogState set(FogState state) {
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
package com.gtnewhorizons.angelica.glsm.states;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
public class ImageUnitBinding implements ISettableState<ImageUnitBinding> {
|
||||
@Getter protected int texture;
|
||||
@Getter protected int level;
|
||||
@Getter protected boolean layered;
|
||||
@Getter protected int layer;
|
||||
@Getter protected int access;
|
||||
@Getter protected int format;
|
||||
@Getter protected int access = GL15.GL_READ_ONLY;
|
||||
@Getter protected int format = GL30.GL_R8;
|
||||
|
||||
public void setBinding(int texture, int level, boolean layered, int layer, int access, int format) {
|
||||
this.texture = texture;
|
||||
@@ -19,6 +21,10 @@ public class ImageUnitBinding implements ISettableState<ImageUnitBinding> {
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
setBinding(0, 0, false, 0, GL15.GL_READ_ONLY, GL30.GL_R8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageUnitBinding set(ImageUnitBinding state) {
|
||||
this.texture = state.texture;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package com.gtnewhorizons.angelica.glsm.states;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import lombok.Getter;
|
||||
|
||||
public class SamplerUnitArray {
|
||||
|
||||
private static final int MIN_UNITS = 16;
|
||||
|
||||
private int[] samplers;
|
||||
@Getter
|
||||
private int highestBoundUnit = -1;
|
||||
|
||||
public int get(int unit) {
|
||||
final int[] local = samplers;
|
||||
@@ -22,15 +25,27 @@ public class SamplerUnitArray {
|
||||
}
|
||||
if (unit >= local.length) return false;
|
||||
local[unit] = sampler;
|
||||
if (sampler != 0) {
|
||||
highestBoundUnit = Math.max(highestBoundUnit, unit);
|
||||
} else if (unit == highestBoundUnit) {
|
||||
trimUnboundUnits();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void unbindEverywhere(int sampler) {
|
||||
final int[] local = samplers;
|
||||
if (local == null) return;
|
||||
for (int i = 0; i < local.length; i++) {
|
||||
for (int i = 0; i <= highestBoundUnit; i++) {
|
||||
if (local[i] == sampler) local[i] = 0;
|
||||
}
|
||||
trimUnboundUnits();
|
||||
}
|
||||
|
||||
private void trimUnboundUnits() {
|
||||
while (highestBoundUnit >= 0 && samplers[highestBoundUnit] == 0) {
|
||||
highestBoundUnit--;
|
||||
}
|
||||
}
|
||||
|
||||
public int size() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -25,6 +25,7 @@ final class GlStreamingRing {
|
||||
|
||||
private static final long SYNC_WAIT_SLICE_NANOS = 10_000_000L;
|
||||
private static final long SYNC_WAIT_TOTAL_NANOS = 5_000_000_000L;
|
||||
private static long totalFencesIssued;
|
||||
|
||||
private final FenceQueue fences = new FenceQueue();
|
||||
private int writePos;
|
||||
@@ -68,6 +69,9 @@ final class GlStreamingRing {
|
||||
/** Byte offset of a reserved range, or -1 when it can never be served. */
|
||||
long reserve(int bytes, int alignment) {
|
||||
if (bytes > capacity) return -1;
|
||||
if (pendingBytes >= capacity / 4) {
|
||||
fence();
|
||||
}
|
||||
if (remaining < capacity / 2) {
|
||||
reclaim();
|
||||
}
|
||||
@@ -82,9 +86,6 @@ final class GlStreamingRing {
|
||||
writePos = offset + bytes;
|
||||
remaining -= waste + bytes;
|
||||
pendingBytes += waste + bytes;
|
||||
if (pendingBytes >= capacity / 4) {
|
||||
fence();
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
@@ -104,6 +105,7 @@ final class GlStreamingRing {
|
||||
fences.enqueue(GLStateManager.glFenceSync(GL32.GL_SYNC_GPU_COMMANDS_COMPLETE, 0), pendingBytes);
|
||||
pendingBytes = 0;
|
||||
fencesIssued++;
|
||||
totalFencesIssued++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,6 +156,7 @@ final class GlStreamingRing {
|
||||
int remaining() { return remaining; }
|
||||
int wraps() { return wraps; }
|
||||
int fencesIssued() { return fencesIssued; }
|
||||
static long totalFencesIssued() { return totalFencesIssued; }
|
||||
int forcedReclaims() { return forcedReclaims; }
|
||||
|
||||
void destroy() {
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
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 com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
|
||||
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 boolean beginDraw(int vertexFlags) {
|
||||
VAOManager.setCurrentVertexFlags(vertexFlags);
|
||||
final boolean culled = disableCulling();
|
||||
GLStateManager.ctx().lineQuadsActive = true;
|
||||
return culled;
|
||||
}
|
||||
|
||||
public static void endDraw(boolean culled) {
|
||||
GLStateManager.ctx().lineQuadsActive = false;
|
||||
restoreCulling(culled);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -45,12 +45,14 @@ public final class PersistentStreamingBuffer {
|
||||
public int getBufferId() { return ring.bufferId(); }
|
||||
public int getCapacity() { return ring.capacity(); }
|
||||
public int getRemaining() { return ring.remaining(); }
|
||||
public int getFencesIssued() { return ring.fencesIssued(); }
|
||||
public int getForcedReclaims() { return ring.forcedReclaims(); }
|
||||
public int getWraps() { return ring.wraps(); }
|
||||
public static long getFencesIssuedByAllRings() { return GlStreamingRing.totalFencesIssued(); }
|
||||
|
||||
ByteBuffer getMappedBuffer() { return ring.mapped(); }
|
||||
|
||||
public void postDraw() {
|
||||
public void endFrame() {
|
||||
ring.endFrame();
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,25 @@ import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
|
||||
public final class StreamingVaos {
|
||||
private static final int FORMAT_COUNT = VertexFlags.BITSET_SIZE;
|
||||
|
||||
static final int MID_TEX_AND_TANGENT = 1;
|
||||
static final int ENTITY_IDS = 2;
|
||||
private static final int EXTENDED_LAYOUTS = MID_TEX_AND_TANGENT | ENTITY_IDS;
|
||||
|
||||
final int[] persistentVAOs = new int[FORMAT_COUNT];
|
||||
final int[] orphanVAOs = new int[FORMAT_COUNT];
|
||||
|
||||
final int[] extendedPersistentVAOs = new int[FORMAT_COUNT];
|
||||
final int[] extendedOrphanVAOs = new int[FORMAT_COUNT];
|
||||
// An extended draw always carries at least one layout, so row 0 is layout 1
|
||||
private final int[][] extendedPersistentVAOs = new int[EXTENDED_LAYOUTS][FORMAT_COUNT];
|
||||
private final int[][] extendedOrphanVAOs = new int[EXTENDED_LAYOUTS][FORMAT_COUNT];
|
||||
|
||||
final int[] lineQuadPersistentVAOs = new int[FORMAT_COUNT];
|
||||
final int[] lineQuadOrphanVAOs = new int[FORMAT_COUNT];
|
||||
|
||||
int[] extendedPersistent(int layout) {
|
||||
return extendedPersistentVAOs[layout - 1];
|
||||
}
|
||||
|
||||
int[] extendedOrphan(int layout) {
|
||||
return extendedOrphanVAOs[layout - 1];
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
package com.gtnewhorizons.angelica.glsm.streaming;
|
||||
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutLong;
|
||||
|
||||
/**
|
||||
* Entity, block entity, and item ids of the vertices in the main Tessellator batch.
|
||||
*/
|
||||
public final class TessellatorEntityIds {
|
||||
|
||||
private static Tessellator owner;
|
||||
private static int runCount;
|
||||
private static int[] runStarts = new int[8];
|
||||
private static long[] runIds = new long[8];
|
||||
|
||||
private TessellatorEntityIds() {}
|
||||
|
||||
public static void onIdsChanged(long previous, long next) {
|
||||
final Tessellator tess = Tessellator.instance;
|
||||
if (tess == null || !tess.isDrawing || tess.vertexCount == 0) return;
|
||||
final int start = tess.vertexCount;
|
||||
if (owner != tess) {
|
||||
owner = tess;
|
||||
runCount = 0;
|
||||
}
|
||||
if (runCount == 0) addRun(0, previous);
|
||||
if (runStarts[runCount - 1] == start) {
|
||||
runIds[runCount - 1] = next;
|
||||
if (runCount > 1 && runIds[runCount - 2] == next) runCount--;
|
||||
} else {
|
||||
addRun(start, next);
|
||||
}
|
||||
}
|
||||
|
||||
public static void onReset(Tessellator tess) {
|
||||
if (tess != owner) return;
|
||||
owner = null;
|
||||
runCount = 0;
|
||||
}
|
||||
|
||||
public static boolean hasIdChanges(Tessellator tess) {
|
||||
return tess == owner && runCount > 1;
|
||||
}
|
||||
|
||||
static int runsFor(Tessellator tess, int vertexCount) {
|
||||
if (tess != owner) return 0;
|
||||
int runs = runCount;
|
||||
while (runs > 0 && runStarts[runs - 1] >= vertexCount) runs--;
|
||||
return runs;
|
||||
}
|
||||
|
||||
static long firstRunIds() {
|
||||
return runIds[0];
|
||||
}
|
||||
|
||||
static void write(long dst, int stride, int vertexCount, int runs) {
|
||||
for (int run = 0; run < runs; run++) {
|
||||
final int end = run + 1 < runs ? runStarts[run + 1] : vertexCount;
|
||||
final long ids = runIds[run];
|
||||
for (int vertex = runStarts[run]; vertex < end; vertex++) {
|
||||
memPutLong(dst + (long) vertex * stride, ids);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void addRun(int start, long ids) {
|
||||
if (runCount == runStarts.length) {
|
||||
runStarts = Arrays.copyOf(runStarts, runCount * 2);
|
||||
runIds = Arrays.copyOf(runIds, runCount * 2);
|
||||
}
|
||||
runStarts[runCount] = start;
|
||||
runIds[runCount] = ids;
|
||||
runCount++;
|
||||
}
|
||||
}
|
||||
+203
-84
@@ -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
|
||||
@@ -104,53 +109,7 @@ public class TessellatorStreamingDrawer {
|
||||
if (GLSMConfig.expandVertexFormats) {
|
||||
effectiveFlags = VertexFlags.COLOR_BIT | VertexFlags.TEXTURE_BIT | VertexFlags.NORMAL_BIT | VertexFlags.BRIGHTNESS_BIT;
|
||||
if (effectiveFlags != flags) {
|
||||
final int[] rawBuffer = tess.rawBuffer;
|
||||
final int defaultColor;
|
||||
if (!tess.hasColor) {
|
||||
final var c = GLStateManager.getColor();
|
||||
defaultColor = ((int)(clamp(0f, 1f, c.getAlpha()) * 255) << 24) | ((int)(clamp(0f, 1f, c.getBlue()) * 255) << 16)
|
||||
| ((int)(clamp(0f, 1f, c.getGreen()) * 255) << 8) | (int)(clamp(0f, 1f, c.getRed()) * 255);
|
||||
} else {
|
||||
defaultColor = 0;
|
||||
}
|
||||
|
||||
final int defaultBrightness;
|
||||
if (!tess.hasBrightness) {
|
||||
defaultBrightness = GLSMConfig.packedLastBrightness();
|
||||
} else {
|
||||
defaultBrightness = 0;
|
||||
}
|
||||
|
||||
final ImmediateExtendedAttribHandler normalHandler = GLSMHooks.immediateExtendedHandler;
|
||||
final boolean faceNormals = !tess.hasNormals && normalHandler != null
|
||||
&& ImmediateExtendedAttribHandler.extPrimVerts(tess.drawMode, vertexCount) == 4 && normalHandler.wantsFaceNormals();
|
||||
final boolean fillNormal = !tess.hasNormals && !faceNormals;
|
||||
|
||||
final int defaultNormal;
|
||||
if (fillNormal) {
|
||||
final var n = ShaderManager.getCurrentNormal();
|
||||
defaultNormal = ((int)(clamp(-1f, 1f, n.z) * 127) << 16) | (((int)(clamp(-1f, 1f, n.y) * 127) & 0xFF) << 8) | ((int)(clamp(-1f, 1f, n.x) * 127) & 0xFF);
|
||||
} else {
|
||||
defaultNormal = 0;
|
||||
}
|
||||
|
||||
final int defaultTexU, defaultTexV;
|
||||
if (!tess.hasTexture) {
|
||||
final var tc = ShaderManager.getCurrentTexCoord();
|
||||
defaultTexU = Float.floatToRawIntBits(tc.x);
|
||||
defaultTexV = Float.floatToRawIntBits(tc.y);
|
||||
} else {
|
||||
defaultTexU = 0; defaultTexV = 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < vertexCount; i++) {
|
||||
final int base = i * 8;
|
||||
if (!tess.hasTexture) { rawBuffer[base + 3] = defaultTexU; rawBuffer[base + 4] = defaultTexV; }
|
||||
if (!tess.hasColor) { rawBuffer[base + 5] = defaultColor; }
|
||||
if (fillNormal) { rawBuffer[base + 6] = defaultNormal; }
|
||||
if (!tess.hasBrightness) { rawBuffer[base + 7] = defaultBrightness; }
|
||||
}
|
||||
if (faceNormals) normalHandler.writeFaceNormals(rawBuffer, vertexCount);
|
||||
fillMissingAttributes(tess);
|
||||
}
|
||||
} else {
|
||||
effectiveFlags = flags;
|
||||
@@ -171,20 +130,34 @@ public class TessellatorStreamingDrawer {
|
||||
final int extPrim = extHandler == null ? 0 : ImmediateExtendedAttribHandler.extPrimVerts(tess.drawMode, vertexCount);
|
||||
final boolean wantsExt = extHandler != null && extHandler.wantsExtended();
|
||||
final boolean doExt = extPrim != 0 && tess.hasTexture && wantsExt;
|
||||
if (doExt) {
|
||||
final int combinedStride = vertexSize + ImmediateExtendedAttribHandler.EXT_STRIDE;
|
||||
final int combinedBytes = vertexCount * combinedStride;
|
||||
ensureExtScratch(combinedBytes);
|
||||
interleaveBase(repackAddress, extScratchAddress, vertexCount, vertexSize, combinedStride);
|
||||
// format expansion backfills a normal into rawBuffer, so trust effectiveFlags rather than tess.hasNormals
|
||||
final int normalIndex = (effectiveFlags & VertexFlags.NORMAL_BIT) != 0 ? ImmediateExtendedAttribHandler.RAW_NORMAL_INDEX : -1;
|
||||
extHandler.build(tess.rawBuffer, vertexCount, extPrim, normalIndex, extScratchAddress + vertexSize, combinedStride);
|
||||
extScratch.position(0);
|
||||
extScratch.limit(combinedBytes);
|
||||
uploadAndDrawExtended(extScratch, effectiveFlags, format, combinedStride, tess.drawMode, vertexCount);
|
||||
shrinkExtScratchIfOversized(combinedBytes);
|
||||
} else {
|
||||
uploadAndDraw(repackBuffer, effectiveFlags, format, vertexSize, tess.drawMode, vertexCount);
|
||||
final int idRuns = TessellatorEntityIds.runsFor(tess, vertexCount);
|
||||
final boolean doIds = idRuns > 1 && extHandler != null && extHandler.wantsEntityIds() && !LineQuads.isLineMode(tess.drawMode);
|
||||
final GLContextState swappedCtx = idRuns >= 1 && !doIds ? GLStateManager.ctx() : null;
|
||||
final long restoreIds = swappedCtx != null ? swapEntityIds(swappedCtx, TessellatorEntityIds.firstRunIds()) : 0L;
|
||||
try {
|
||||
if (doExt || doIds) {
|
||||
final int layout = (doExt ? StreamingVaos.MID_TEX_AND_TANGENT : 0) | (doIds ? StreamingVaos.ENTITY_IDS : 0);
|
||||
final int combinedStride = combinedStride(format, layout);
|
||||
final int combinedBytes = vertexCount * combinedStride;
|
||||
ensureExtScratch(combinedBytes);
|
||||
interleaveBase(repackAddress, extScratchAddress, vertexCount, vertexSize, combinedStride);
|
||||
long extAddress = extScratchAddress + vertexSize;
|
||||
if (doExt) {
|
||||
// format expansion backfills a normal into rawBuffer, so trust effectiveFlags rather than tess.hasNormals
|
||||
final int normalIndex = (effectiveFlags & VertexFlags.NORMAL_BIT) != 0 ? ImmediateExtendedAttribHandler.RAW_NORMAL_INDEX : -1;
|
||||
extHandler.build(tess.rawBuffer, vertexCount, extPrim, normalIndex, extAddress, combinedStride);
|
||||
extAddress += ImmediateExtendedAttribHandler.EXT_STRIDE;
|
||||
}
|
||||
if (doIds) TessellatorEntityIds.write(extAddress, combinedStride, vertexCount, idRuns);
|
||||
extScratch.position(0);
|
||||
extScratch.limit(combinedBytes);
|
||||
uploadAndDrawExtended(extScratch, effectiveFlags, format, layout, combinedStride, tess.drawMode, vertexCount);
|
||||
shrinkExtScratchIfOversized(combinedBytes);
|
||||
} else {
|
||||
uploadAndDraw(repackBuffer, effectiveFlags, format, vertexSize, tess.drawMode, vertexCount);
|
||||
}
|
||||
} finally {
|
||||
if (swappedCtx != null) swapEntityIds(swappedCtx, restoreIds);
|
||||
}
|
||||
} finally {
|
||||
if (locked) GLStateManager.releaseDrawLock();
|
||||
@@ -201,6 +174,58 @@ public class TessellatorStreamingDrawer {
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void fillMissingAttributes(Tessellator tess) {
|
||||
final int vertexCount = tess.vertexCount;
|
||||
final int[] rawBuffer = tess.rawBuffer;
|
||||
final int defaultColor;
|
||||
if (!tess.hasColor) {
|
||||
final var c = GLStateManager.getColor();
|
||||
defaultColor = ((int)(clamp(0f, 1f, c.getAlpha()) * 255) << 24) | ((int)(clamp(0f, 1f, c.getBlue()) * 255) << 16)
|
||||
| ((int)(clamp(0f, 1f, c.getGreen()) * 255) << 8) | (int)(clamp(0f, 1f, c.getRed()) * 255);
|
||||
} else {
|
||||
defaultColor = 0;
|
||||
}
|
||||
|
||||
final int defaultBrightness;
|
||||
if (!tess.hasBrightness) {
|
||||
defaultBrightness = GLSMConfig.packedLastBrightness();
|
||||
} else {
|
||||
defaultBrightness = 0;
|
||||
}
|
||||
|
||||
final ImmediateExtendedAttribHandler normalHandler = GLSMHooks.immediateExtendedHandler;
|
||||
final boolean faceNormals = !tess.hasNormals && normalHandler != null
|
||||
&& ImmediateExtendedAttribHandler.extPrimVerts(tess.drawMode, vertexCount) == 4 && normalHandler.wantsFaceNormals();
|
||||
final boolean fillNormal = !tess.hasNormals && !faceNormals;
|
||||
GLStateManager.requireExitVertexDefaults(fillNormal, !tess.hasTexture, !tess.hasColor, !tess.hasBrightness);
|
||||
|
||||
final int defaultNormal;
|
||||
if (fillNormal) {
|
||||
final var n = ShaderManager.getCurrentNormal();
|
||||
defaultNormal = ((int)(clamp(-1f, 1f, n.z) * 127) << 16) | (((int)(clamp(-1f, 1f, n.y) * 127) & 0xFF) << 8) | ((int)(clamp(-1f, 1f, n.x) * 127) & 0xFF);
|
||||
} else {
|
||||
defaultNormal = 0;
|
||||
}
|
||||
|
||||
final int defaultTexU, defaultTexV;
|
||||
if (!tess.hasTexture) {
|
||||
final var tc = ShaderManager.getCurrentTexCoord();
|
||||
defaultTexU = Float.floatToRawIntBits(tc.x);
|
||||
defaultTexV = Float.floatToRawIntBits(tc.y);
|
||||
} else {
|
||||
defaultTexU = 0; defaultTexV = 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < vertexCount; i++) {
|
||||
final int base = i * 8;
|
||||
if (!tess.hasTexture) { rawBuffer[base + 3] = defaultTexU; rawBuffer[base + 4] = defaultTexV; }
|
||||
if (!tess.hasColor) { rawBuffer[base + 5] = defaultColor; }
|
||||
if (fillNormal) { rawBuffer[base + 6] = defaultNormal; }
|
||||
if (!tess.hasBrightness) { rawBuffer[base + 7] = defaultBrightness; }
|
||||
}
|
||||
if (faceNormals) normalHandler.writeFaceNormals(rawBuffer, vertexCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw DirectTessellator data via streaming VBO+VAO. Used for live immediate mode emulation.
|
||||
*/
|
||||
@@ -243,7 +268,7 @@ public class TessellatorStreamingDrawer {
|
||||
final long srcBase = memAddress0(packed) + packed.position();
|
||||
final int texOffset = ImmediateExtendedAttribHandler.texOffset(format);
|
||||
final int normalOffset = ImmediateExtendedAttribHandler.normalOffset(format);
|
||||
final int combinedStride = vertexSize + ImmediateExtendedAttribHandler.EXT_STRIDE;
|
||||
final int combinedStride = combinedStride(format, StreamingVaos.MID_TEX_AND_TANGENT);
|
||||
final int combinedBytes = vertexCount * combinedStride;
|
||||
final boolean locked = GLStateManager.acquireDrawLock();
|
||||
try {
|
||||
@@ -252,7 +277,7 @@ public class TessellatorStreamingDrawer {
|
||||
extHandler.buildPacked(srcBase, vertexSize, 0, texOffset, normalOffset, vertexCount, extPrim, extScratchAddress + vertexSize, combinedStride);
|
||||
extScratch.position(0);
|
||||
extScratch.limit(combinedBytes);
|
||||
uploadAndDrawExtended(extScratch, flags, format, combinedStride, drawMode, vertexCount);
|
||||
uploadAndDrawExtended(extScratch, flags, format, StreamingVaos.MID_TEX_AND_TANGENT, combinedStride, drawMode, vertexCount);
|
||||
shrinkExtScratchIfOversized(combinedBytes);
|
||||
} finally {
|
||||
if (locked) GLStateManager.releaseDrawLock();
|
||||
@@ -306,7 +331,7 @@ public class TessellatorStreamingDrawer {
|
||||
|
||||
public static void endFrame() {
|
||||
if (persistentBuffer != null) {
|
||||
persistentBuffer.postDraw();
|
||||
persistentBuffer.endFrame();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,12 +340,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 +361,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 +389,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();
|
||||
}
|
||||
@@ -365,6 +463,7 @@ public class TessellatorStreamingDrawer {
|
||||
FfpExtendedAttribs.beginInternalDraw();
|
||||
try {
|
||||
if (drawMode == GL11.GL_QUADS) {
|
||||
GLStateManager.drawCalls++;
|
||||
QuadConverter.drawQuadsAsTriangles(glCtx, firstVertex, vertexCount);
|
||||
} else {
|
||||
GLStateManager.glDrawArrays(drawMode, firstVertex, vertexCount);
|
||||
@@ -374,11 +473,11 @@ public class TessellatorStreamingDrawer {
|
||||
}
|
||||
}
|
||||
|
||||
private static void uploadAndDrawExtended(ByteBuffer combined, int flags, VertexFormat format, int combinedStride, int drawMode, int vertexCount) {
|
||||
private static void uploadAndDrawExtended(ByteBuffer combined, int flags, VertexFormat format, int layout, int combinedStride, int drawMode, int vertexCount) {
|
||||
final GLContextState glCtx = GLStateManager.ctx();
|
||||
final StreamingVaos vaos = glCtx.streamingVaos;
|
||||
ensureVAO(vaos, flags, format);
|
||||
ensureExtendedVAOs(vaos, flags, format);
|
||||
ensureVAO(vaos.persistentVAOs, vaos.orphanVAOs, flags, format);
|
||||
ensureExtendedVAOs(vaos, flags, format, layout);
|
||||
|
||||
int firstVertex = -1;
|
||||
|
||||
@@ -387,9 +486,9 @@ public class TessellatorStreamingDrawer {
|
||||
}
|
||||
|
||||
if (firstVertex >= 0) {
|
||||
GLStateManager.glBindVertexArray(vaos.extendedPersistentVAOs[flags]);
|
||||
GLStateManager.glBindVertexArray(vaos.extendedPersistent(layout)[flags]);
|
||||
} else {
|
||||
GLStateManager.glBindVertexArray(vaos.extendedOrphanVAOs[flags]);
|
||||
GLStateManager.glBindVertexArray(vaos.extendedOrphan(layout)[flags]);
|
||||
orphanBuffers[flags].upload(combined);
|
||||
firstVertex = 0;
|
||||
}
|
||||
@@ -398,14 +497,14 @@ public class TessellatorStreamingDrawer {
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
|
||||
private static void ensureExtendedVAOs(StreamingVaos vaos, int flags, VertexFormat format) {
|
||||
final int[] extendedOrphanVAOs = vaos.extendedOrphanVAOs;
|
||||
final int[] extendedPersistentVAOs = vaos.extendedPersistentVAOs;
|
||||
private static void ensureExtendedVAOs(StreamingVaos vaos, int flags, VertexFormat format, int layout) {
|
||||
final int[] extendedOrphanVAOs = vaos.extendedOrphan(layout);
|
||||
final int[] extendedPersistentVAOs = vaos.extendedPersistent(layout);
|
||||
if (extendedOrphanVAOs[flags] == 0) {
|
||||
extendedOrphanVAOs[flags] = GLStateManager.glGenVertexArrays();
|
||||
GLStateManager.glBindVertexArray(extendedOrphanVAOs[flags]);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, orphanBuffers[flags].getBufferId());
|
||||
setupExtendedBufferState(format);
|
||||
setupExtendedBufferState(format, layout);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
@@ -414,20 +513,42 @@ public class TessellatorStreamingDrawer {
|
||||
extendedPersistentVAOs[flags] = GLStateManager.glGenVertexArrays();
|
||||
GLStateManager.glBindVertexArray(extendedPersistentVAOs[flags]);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, persistentBuffer.getBufferId());
|
||||
setupExtendedBufferState(format);
|
||||
setupExtendedBufferState(format, layout);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setupExtendedBufferState(VertexFormat format) {
|
||||
final int combinedStride = format.getVertexSize() + ImmediateExtendedAttribHandler.EXT_STRIDE;
|
||||
private static long swapEntityIds(GLContextState glCtx, long ids) {
|
||||
final long previous = glCtx.entityIds;
|
||||
if (previous != ids) {
|
||||
glCtx.entityIds = ids;
|
||||
glCtx.dirtyEntityIdsAttrib = true;
|
||||
}
|
||||
return previous;
|
||||
}
|
||||
|
||||
private static int combinedStride(VertexFormat format, int layout) {
|
||||
int stride = format.getVertexSize();
|
||||
if ((layout & StreamingVaos.MID_TEX_AND_TANGENT) != 0) stride += ImmediateExtendedAttribHandler.EXT_STRIDE;
|
||||
if ((layout & StreamingVaos.ENTITY_IDS) != 0) stride += ImmediateExtendedAttribHandler.ENTITY_IDS_STRIDE;
|
||||
return stride;
|
||||
}
|
||||
|
||||
private static void setupExtendedBufferState(VertexFormat format, int layout) {
|
||||
final int combinedStride = combinedStride(format, layout);
|
||||
long offset = 0;
|
||||
for (final VertexFormatElement element : format.elementsArray) {
|
||||
element.setupBufferState(offset, combinedStride);
|
||||
offset += element.getByteSize();
|
||||
}
|
||||
ImmediateExtendedAttribHandler.setupExtAttribPointers(offset, combinedStride);
|
||||
if ((layout & StreamingVaos.MID_TEX_AND_TANGENT) != 0) {
|
||||
ImmediateExtendedAttribHandler.setupExtAttribPointers(offset, combinedStride);
|
||||
offset += ImmediateExtendedAttribHandler.EXT_STRIDE;
|
||||
}
|
||||
if ((layout & StreamingVaos.ENTITY_IDS) != 0) {
|
||||
ImmediateExtendedAttribHandler.setupEntityIdsPointer(offset, combinedStride);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ensureExtScratch(int requiredBytes) {
|
||||
@@ -480,11 +601,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();
|
||||
|
||||
@@ -21,9 +21,10 @@ public class TextureInfo {
|
||||
protected int resolvedInternalFormat = -1;
|
||||
protected int width = -1;
|
||||
protected int height = -1;
|
||||
@Getter @Setter private int atlasGutter;
|
||||
|
||||
@Setter protected int minFilter = UNSET_INT;
|
||||
@Setter protected int magFilter = UNSET_INT;
|
||||
protected int minFilter = UNSET_INT;
|
||||
protected int magFilter = UNSET_INT;
|
||||
@Setter protected int wrapS = UNSET_INT;
|
||||
@Setter protected int wrapT = UNSET_INT;
|
||||
@Setter protected int maxLevel = UNSET_INT;
|
||||
@@ -37,9 +38,12 @@ public class TextureInfo {
|
||||
@Getter @Setter protected boolean generateMipmap = false;
|
||||
|
||||
@Getter protected int uploadGeneration;
|
||||
@Getter protected boolean writtenOutsideUploads;
|
||||
@Getter protected int changeStamp;
|
||||
|
||||
void bumpUploadGeneration() {
|
||||
uploadGeneration++;
|
||||
changeStamp++;
|
||||
}
|
||||
|
||||
protected TextureInfo(int id) {
|
||||
@@ -78,6 +82,16 @@ public class TextureInfo {
|
||||
return height;
|
||||
}
|
||||
|
||||
public void setMinFilter(int minFilter) {
|
||||
this.minFilter = minFilter;
|
||||
changeStamp++;
|
||||
}
|
||||
|
||||
public void setMagFilter(int magFilter) {
|
||||
this.magFilter = magFilter;
|
||||
changeStamp++;
|
||||
}
|
||||
|
||||
public int getMinFilter() {
|
||||
if (minFilter == UNSET_INT) minFilter = queryParameteri(GL11.GL_TEXTURE_MIN_FILTER);
|
||||
return minFilter;
|
||||
|
||||
@@ -14,7 +14,10 @@ import org.lwjgl.opengl.GL32;
|
||||
import org.lwjgl.opengl.GL40;
|
||||
|
||||
import java.nio.Buffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.IntConsumer;
|
||||
|
||||
public class TextureInfoCache {
|
||||
/**
|
||||
@@ -28,10 +31,45 @@ public class TextureInfoCache {
|
||||
|
||||
private final Int2ObjectMap<TextureInfo> cache = new Int2ObjectOpenHashMap<>();
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
private final List<IntConsumer> deleteListeners = new ArrayList<>();
|
||||
private int deleteCount;
|
||||
|
||||
private TextureInfoCache() {
|
||||
}
|
||||
|
||||
public int deleteCount() {
|
||||
return deleteCount;
|
||||
}
|
||||
|
||||
public void addDeleteListener(IntConsumer listener) {
|
||||
deleteListeners.add(listener);
|
||||
}
|
||||
|
||||
public void onContentsChanged(int texture) {
|
||||
if (texture <= 0) return;
|
||||
final TextureInfo info = getInfo(texture);
|
||||
if (info != null) info.bumpUploadGeneration();
|
||||
}
|
||||
|
||||
public void onWrittenOutsideUploads(int texture) {
|
||||
if (texture <= 0) return;
|
||||
final TextureInfo info = getInfo(texture);
|
||||
if (info == null || info.writtenOutsideUploads) return;
|
||||
info.writtenOutsideUploads = true;
|
||||
info.changeStamp++;
|
||||
}
|
||||
|
||||
private void defineLevelZero(int texture, int target, int internalformat, int width, int height) {
|
||||
final TextureInfo info = getInfo(texture);
|
||||
if (info == null) return;
|
||||
info.setTarget(target);
|
||||
info.internalFormat = internalformat;
|
||||
info.resolvedInternalFormat = isGenericCompressedInternalFormat(internalformat) ? -1 : internalformat;
|
||||
info.width = width;
|
||||
info.height = height;
|
||||
info.bumpUploadGeneration();
|
||||
}
|
||||
|
||||
public TextureInfo getInfo(int id) {
|
||||
if (id < 0) return null;
|
||||
|
||||
@@ -48,48 +86,27 @@ public class TextureInfoCache {
|
||||
}
|
||||
|
||||
public void onTexImage2D(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable Buffer pixels) {
|
||||
if (isTrackedTarget(target) && level == 0) {
|
||||
final TextureInfo info = getInfo(GLStateManager.getBoundTextureForServerState());
|
||||
if (info == null) return;
|
||||
info.setTarget(target);
|
||||
info.internalFormat = internalformat;
|
||||
info.resolvedInternalFormat = isGenericCompressedInternalFormat(internalformat) ? -1 : internalformat;
|
||||
info.width = width;
|
||||
info.height = height;
|
||||
info.bumpUploadGeneration();
|
||||
}
|
||||
if (isTrackedTarget(target) && level == 0) defineLevelZero(GLStateManager.getBoundTextureForServerState(), target, internalformat, width, height);
|
||||
}
|
||||
|
||||
public void onTexImage2D(int target, int level, int internalformat, int width, int height, int border, int format, int type, long pixels_buffer_offset) {
|
||||
if (isTrackedTarget(target) && level == 0) {
|
||||
final TextureInfo info = getInfo(GLStateManager.getBoundTextureForServerState());
|
||||
if (info == null) return;
|
||||
info.setTarget(target);
|
||||
info.internalFormat = internalformat;
|
||||
info.resolvedInternalFormat = isGenericCompressedInternalFormat(internalformat) ? -1 : internalformat;
|
||||
info.width = width;
|
||||
info.height = height;
|
||||
info.bumpUploadGeneration();
|
||||
}
|
||||
if (isTrackedTarget(target) && level == 0) defineLevelZero(GLStateManager.getBoundTextureForServerState(), target, internalformat, width, height);
|
||||
}
|
||||
|
||||
public void onCopyTexImage2D(int target, int level, int internalformat, int width, int height) {
|
||||
if (isTrackedTarget(target) && level == 0) defineLevelZero(GLStateManager.getBoundTextureForServerState(), target, internalformat, width, height);
|
||||
}
|
||||
|
||||
public void onTextureImage2D(int texture, int target, int level, int internalformat, int width, int height) {
|
||||
if (isTrackedTarget(target) && level == 0) defineLevelZero(texture, target, internalformat, width, height);
|
||||
}
|
||||
|
||||
public void onTexStorage2D(int target, int internalformat, int width, int height) {
|
||||
if (!isTrackedTarget(target)) return;
|
||||
final TextureInfo info = getInfo(GLStateManager.getBoundTextureForServerState());
|
||||
if (info == null) return;
|
||||
info.setTarget(target);
|
||||
info.internalFormat = internalformat;
|
||||
info.resolvedInternalFormat = isGenericCompressedInternalFormat(internalformat) ? -1 : internalformat;
|
||||
info.width = width;
|
||||
info.height = height;
|
||||
info.bumpUploadGeneration();
|
||||
if (isTrackedTarget(target)) defineLevelZero(GLStateManager.getBoundTextureForServerState(), target, internalformat, width, height);
|
||||
}
|
||||
|
||||
public void onTexSubImage2D(int target, int level) {
|
||||
if (!isTrackedTarget(target) || level != 0) return;
|
||||
final TextureInfo info = getInfo(GLStateManager.getBoundTextureForServerState());
|
||||
if (info == null) return;
|
||||
info.bumpUploadGeneration();
|
||||
if (isTrackedTarget(target) && level == 0) onContentsChanged(GLStateManager.getBoundTextureForServerState());
|
||||
}
|
||||
|
||||
public static boolean isGenericCompressedInternalFormat(int internalFormat) {
|
||||
@@ -131,9 +148,11 @@ public class TextureInfoCache {
|
||||
|
||||
public void onDeleteTexture(int id) {
|
||||
if (id < 0) return;
|
||||
deleteCount++;
|
||||
|
||||
if (GLStateManager.isSplashComplete()) {
|
||||
cache.remove(id);
|
||||
for (IntConsumer listener : deleteListeners) listener.accept(id);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,4 +27,20 @@ public final class TextureTargets {
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
public static int bindingQuery(int target) {
|
||||
return switch (target) {
|
||||
case GL11.GL_TEXTURE_1D -> GL11.GL_TEXTURE_BINDING_1D;
|
||||
case GL12.GL_TEXTURE_3D -> GL12.GL_TEXTURE_BINDING_3D;
|
||||
case GL13.GL_TEXTURE_CUBE_MAP -> GL13.GL_TEXTURE_BINDING_CUBE_MAP;
|
||||
case GL30.GL_TEXTURE_1D_ARRAY -> GL30.GL_TEXTURE_BINDING_1D_ARRAY;
|
||||
case GL30.GL_TEXTURE_2D_ARRAY -> GL30.GL_TEXTURE_BINDING_2D_ARRAY;
|
||||
case GL31.GL_TEXTURE_RECTANGLE -> GL31.GL_TEXTURE_BINDING_RECTANGLE;
|
||||
case GL31.GL_TEXTURE_BUFFER -> GL31.GL_TEXTURE_BINDING_BUFFER;
|
||||
case GL32.GL_TEXTURE_2D_MULTISAMPLE -> GL32.GL_TEXTURE_BINDING_2D_MULTISAMPLE;
|
||||
case GL32.GL_TEXTURE_2D_MULTISAMPLE_ARRAY -> GL32.GL_TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY;
|
||||
case GL40.GL_TEXTURE_CUBE_MAP_ARRAY -> GL40.GL_TEXTURE_BINDING_CUBE_MAP_ARRAY;
|
||||
default -> GL11.GL_TEXTURE_BINDING_2D;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
bool angelica_lineStart(int corner) {
|
||||
return corner == 0 || corner == 1 || corner == 3;
|
||||
}
|
||||
|
||||
vec4 angelica_lineQuad(vec4 clipThis, vec4 clipOther, int corner, vec2 viewport, float width, out float lineT) {
|
||||
bool lineStart = angelica_lineStart(corner);
|
||||
vec4 lineC0 = lineStart ? clipThis : clipOther;
|
||||
vec4 lineC1 = lineStart ? clipOther : clipThis;
|
||||
lineT = 0.0;
|
||||
float lineD0 = lineC0.z + lineC0.w;
|
||||
float lineD1 = lineC1.z + lineC1.w;
|
||||
if (lineD0 < 0.0 && lineD1 < 0.0) {
|
||||
return vec4(0.0, 0.0, 2.0, 1.0);
|
||||
} else {
|
||||
float lineT0 = lineD0 < 0.0 ? lineD0 / (lineD0 - lineD1) : 0.0;
|
||||
float lineT1 = lineD1 < 0.0 ? lineD0 / (lineD0 - lineD1) : 1.0;
|
||||
lineT = lineStart ? lineT0 : lineT1;
|
||||
vec4 lineP0 = mix(lineC0, lineC1, lineT0);
|
||||
vec4 lineP1 = mix(lineC0, lineC1, lineT1);
|
||||
vec2 lineN0 = lineP0.xy / lineP0.w;
|
||||
vec2 lineN1 = lineP1.xy / lineP1.w;
|
||||
vec2 linePixel = 2.0 / viewport;
|
||||
float lineHalfWidth = 0.5 * width;
|
||||
vec2 lineOffset;
|
||||
vec2 lineShift;
|
||||
vec2 lineSpan = abs(lineN1 - lineN0) * viewport;
|
||||
if (lineSpan.x > lineSpan.y) {
|
||||
lineOffset = vec2(0.0, lineHalfWidth * linePixel.y);
|
||||
lineShift = vec2(lineN0.x < lineN1.x ? -0.5 : 0.5, -0.125) * linePixel;
|
||||
} else {
|
||||
lineOffset = vec2(lineHalfWidth * linePixel.x, 0.0);
|
||||
lineShift = vec2(0.125, lineN0.y < lineN1.y ? -0.5 : 0.5) * linePixel;
|
||||
}
|
||||
vec4 lineP = lineStart ? lineP0 : lineP1;
|
||||
vec2 lineN = lineStart ? lineN0 : lineN1;
|
||||
return vec4((lineN + (corner % 2 == 0 ? lineOffset : -lineOffset) + lineShift) * lineP.w, lineP.z, lineP.w);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
const vec2 SAMPLE_OFFSETS[TEXTURE_SUPERSAMPLING_SAMPLES] = vec2[](
|
||||
#if TEXTURE_SUPERSAMPLING_SAMPLES == 2
|
||||
vec2(0.25, 0.25), vec2(-0.25, -0.25)
|
||||
#elif TEXTURE_SUPERSAMPLING_SAMPLES == 4
|
||||
vec2(0.125, 0.375), vec2(-0.125, -0.375),
|
||||
vec2(0.375, -0.125), vec2(-0.375, 0.125)
|
||||
#elif TEXTURE_SUPERSAMPLING_SAMPLES == 8
|
||||
vec2(-0.1875, -0.0625), vec2( 0.3125, 0.0625),
|
||||
vec2(-0.0625, 0.1875), vec2(-0.3125, -0.1875),
|
||||
vec2( 0.1875, -0.3125), vec2( 0.4375, -0.4375),
|
||||
vec2(-0.4375, 0.3125), vec2( 0.0625, 0.4375)
|
||||
#elif TEXTURE_SUPERSAMPLING_SAMPLES == 16
|
||||
vec2( 0.0625, 0.0625), vec2(-0.0625, -0.1875),
|
||||
vec2(-0.1875, 0.1250), vec2( 0.2500, -0.0625),
|
||||
vec2(-0.3125, -0.1250), vec2( 0.1250, 0.3125),
|
||||
vec2( 0.3125, 0.1875), vec2( 0.1875, -0.3125),
|
||||
vec2(-0.1250, 0.3750), vec2( 0.0000, -0.4375),
|
||||
vec2(-0.2500, -0.3750), vec2(-0.3750, 0.2500),
|
||||
vec2(-0.5000, 0.0000), vec2( 0.4375, -0.2500),
|
||||
vec2( 0.3750, 0.4375), vec2(-0.4375, -0.5000)
|
||||
#endif
|
||||
);
|
||||
|
||||
vec4 sampleSupersampledTexture(sampler2D tex, vec4 coord) {
|
||||
vec2 uv = coord.xy / coord.w;
|
||||
vec2 dx = dFdx(uv);
|
||||
vec2 dy = dFdy(uv);
|
||||
ivec2 size = textureSize(tex, 0);
|
||||
vec2 offsets[TEXTURE_SUPERSAMPLING_SAMPLES];
|
||||
vec2 extent = vec2(0.0);
|
||||
for (int i = 0; i < TEXTURE_SUPERSAMPLING_SAMPLES; i++) {
|
||||
offsets[i] = SAMPLE_OFFSETS[i].x * dx + SAMPLE_OFFSETS[i].y * dy;
|
||||
extent = max(extent, abs(offsets[i]));
|
||||
}
|
||||
extent *= vec2(size);
|
||||
float footprint = max(extent.x, extent.y);
|
||||
if (footprint >= u_AtlasGutter) return textureGrad(tex, uv, dx, dy);
|
||||
float supersampling = 1.0 - smoothstep(0.75 * u_AtlasGutter, u_AtlasGutter, footprint);
|
||||
|
||||
// The atlas gutter is 2^maxMipLevel.
|
||||
float texelsPerPixel = max(length(dx * vec2(size)), length(dy * vec2(size)));
|
||||
int lod = int(min(ceil(log2(max(texelsPerPixel / sqrt(float(TEXTURE_SUPERSAMPLING_SAMPLES)), 1.0))),
|
||||
log2(u_AtlasGutter)));
|
||||
|
||||
// Transparent texels must not tint the visible samples.
|
||||
vec4 color = vec4(0.0);
|
||||
for (int i = 0; i < TEXTURE_SUPERSAMPLING_SAMPLES; i++) {
|
||||
ivec2 texel = min(ivec2(fract(uv + offsets[i]) * vec2(size)), size - 1);
|
||||
vec4 sampleColor = texelFetch(tex, texel >> lod, lod);
|
||||
color += vec4(sampleColor.rgb * sampleColor.a, sampleColor.a);
|
||||
}
|
||||
color /= float(TEXTURE_SUPERSAMPLING_SAMPLES);
|
||||
if (supersampling < 1.0) {
|
||||
vec4 filtered = textureGrad(tex, uv, dx, dy);
|
||||
color = mix(vec4(filtered.rgb * filtered.a, filtered.a), color, supersampling);
|
||||
}
|
||||
return vec4(color.rgb / max(color.a, 0.00001), color.a);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
+409
-762
File diff suppressed because it is too large.
Load diff
+4
-22
@@ -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()) {
|
||||
@@ -104,22 +99,6 @@ class DisplayListCompilationParityTest {
|
||||
GLStateManager.glLoadIdentity();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(Caller.class)
|
||||
void compile_doesNotChangeState(Caller c) {
|
||||
testList = GL11.glGenLists(1);
|
||||
c.newList(testList, GL11.GL_COMPILE);
|
||||
c.enable(GL11.GL_BLEND);
|
||||
|
||||
assertFalse(GL11.glIsEnabled(GL11.GL_BLEND), "During GL_COMPILE: state unchanged");
|
||||
|
||||
c.endList();
|
||||
assertFalse(GL11.glIsEnabled(GL11.GL_BLEND), "After GL_COMPILE: state unchanged");
|
||||
|
||||
c.callList(testList);
|
||||
assertTrue(GL11.glIsEnabled(GL11.GL_BLEND), "After playback: state changed");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(Caller.class)
|
||||
void compileAndExecute_changesStateImmediately(Caller c) {
|
||||
@@ -191,6 +170,9 @@ class DisplayListCompilationParityTest {
|
||||
c.enable(GL11.GL_BLEND);
|
||||
c.enable(GL11.GL_DEPTH_TEST);
|
||||
c.enable(GL11.GL_CULL_FACE);
|
||||
|
||||
assertFalse(GL11.glIsEnabled(GL11.GL_BLEND), "During GL_COMPILE: BLEND unchanged");
|
||||
|
||||
c.endList();
|
||||
|
||||
assertFalse(GL11.glIsEnabled(GL11.GL_BLEND), "After GL_COMPILE: BLEND unchanged");
|
||||
|
||||
@@ -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();
|
||||
|
||||
+2
-3
@@ -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
|
||||
@@ -199,6 +197,7 @@ class DisplayListMatrixRestorationTest extends DisplayListTestFixture {
|
||||
GLStateManager.glCallList(list);
|
||||
String context = "compileMode=" + compileMode;
|
||||
assertEquals(GL13.GL_TEXTURE0, GLStateManager.glGetInteger(GL13.GL_ACTIVE_TEXTURE), context);
|
||||
assertEquals(GL13.GL_TEXTURE0, GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE), context);
|
||||
assertEquals(3f, translation(GL11.GL_MODELVIEW_MATRIX), context);
|
||||
assertEquals(0f, translation(GL11.GL_TEXTURE_MATRIX), context);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
|
||||
@@ -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;
|
||||
@@ -51,36 +55,7 @@ class FeedbackManagerTest {
|
||||
FeedbackManager.glFeedbackBuffer(GL11.GL_2D, buf);
|
||||
FeedbackManager.glRenderMode(GL11.GL_FEEDBACK);
|
||||
|
||||
// Set up identity matrices and a viewport
|
||||
GLStateManager.getModelViewMatrix().identity();
|
||||
GLStateManager.getProjectionMatrix().identity();
|
||||
GLStateManager.getViewportState().setViewPort(0, 0, 800, 600);
|
||||
GLStateManager.getViewportState().setDepthRange(0.0, 1.0);
|
||||
|
||||
// Upload 3 vertices to a VBO and draw
|
||||
final int vao = GL30.glGenVertexArrays();
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
|
||||
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 result = FeedbackManager.glRenderMode(GL11.GL_RENDER);
|
||||
assertEquals(-1, result, "Overflow should return -1");
|
||||
|
||||
// Cleanup
|
||||
GLStateManager.glDisableVertexAttribArray(0);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
GL15.glDeleteBuffers(vbo);
|
||||
GLStateManager.glDeleteVertexArrays(vao);
|
||||
assertEquals(-1, feedbackDraw(GL11.GL_TRIANGLES, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f, 0f), "Overflow should return -1");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -97,166 +72,34 @@ 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);
|
||||
|
||||
GLStateManager.getModelViewMatrix().identity();
|
||||
GLStateManager.getProjectionMatrix().identity();
|
||||
GLStateManager.getViewportState().setViewPort(0, 0, 800, 600);
|
||||
GLStateManager.getViewportState().setDepthRange(0.0, 1.0);
|
||||
final int count = feedbackDraw(mode, verts);
|
||||
|
||||
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();
|
||||
|
||||
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_POLYGON, 0, 4);
|
||||
|
||||
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);
|
||||
|
||||
GLStateManager.glDisableVertexAttribArray(0);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
GL15.glDeleteBuffers(vbo);
|
||||
GLStateManager.glDeleteVertexArrays(vao);
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -265,28 +108,7 @@ class FeedbackManagerTest {
|
||||
FeedbackManager.glFeedbackBuffer(GL11.GL_3D, 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);
|
||||
|
||||
// Single point at NDC origin (0,0,0)
|
||||
final float[] verts = { 0f, 0f, 0f };
|
||||
final FloatBuffer vertBuf = BufferUtils.createFloatBuffer(3);
|
||||
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_POINTS, 0, 1);
|
||||
|
||||
final int count = FeedbackManager.glRenderMode(GL11.GL_RENDER);
|
||||
final int count = feedbackDraw(GL11.GL_POINTS, 0f, 0f, 0f);
|
||||
|
||||
// GL_3D point: POINT_TOKEN, x, y, z = 4 floats
|
||||
assertEquals(4, count);
|
||||
@@ -295,11 +117,31 @@ class FeedbackManagerTest {
|
||||
assertEquals(400.0f, buf.get(1), 0.01f);
|
||||
assertEquals(300.0f, buf.get(2), 0.01f);
|
||||
assertEquals(0.5f, buf.get(3), 0.01f);
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
GLStateManager.glDisableVertexAttribArray(0);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
GL15.glDeleteBuffers(vbo);
|
||||
GLStateManager.glDeleteVertexArrays(vao);
|
||||
private static int feedbackDraw(int mode, float... verts) {
|
||||
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 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);
|
||||
try {
|
||||
FeedbackManager.processDrawArrays(mode, 0, verts.length / 3);
|
||||
return FeedbackManager.glRenderMode(GL11.GL_RENDER);
|
||||
} finally {
|
||||
GLStateManager.glDisableVertexAttribArray(0);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
GL15.glDeleteBuffers(vbo);
|
||||
GLStateManager.glDeleteVertexArrays(vao);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,17 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
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.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL14;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class GLESFormatRemapTest {
|
||||
@@ -26,82 +31,45 @@ class GLESFormatRemapTest {
|
||||
assertEquals(GL11.GL_RGBA4, remapDesktop(GL11.GL_ALPHA4, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE).internalFormat());
|
||||
}
|
||||
|
||||
@Test
|
||||
void rgb16AndRgba16_remapToHalfFloat_onGles() {
|
||||
final var r1 = remap(GL11.GL_RGB16, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE);
|
||||
assertEquals(GL30.GL_RGB16F, r1.internalFormat());
|
||||
assertEquals(GL30.GL_HALF_FLOAT, r1.type());
|
||||
|
||||
final var r2 = remap(GL11.GL_RGBA16, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE);
|
||||
assertEquals(GL30.GL_RGBA16F, r2.internalFormat());
|
||||
assertEquals(GL30.GL_HALF_FLOAT, r2.type());
|
||||
static Stream<Arguments> remappedType() {
|
||||
return Stream.of(
|
||||
Arguments.of("R16F gets half float", true, GL30.GL_R16F, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, GL30.GL_HALF_FLOAT),
|
||||
Arguments.of("RG16F gets half float", true, GL30.GL_RG16F, GL30.GL_RG, GL11.GL_UNSIGNED_BYTE, GL30.GL_HALF_FLOAT),
|
||||
Arguments.of("RGB16F gets half float", true, GL30.GL_RGB16F, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, GL30.GL_HALF_FLOAT),
|
||||
Arguments.of("RGBA16F gets half float", true, GL30.GL_RGBA16F, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, GL30.GL_HALF_FLOAT),
|
||||
Arguments.of("RGBA32F gets float", true, GL30.GL_RGBA32F, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, GL11.GL_FLOAT),
|
||||
Arguments.of("RGB32F gets float", true, GL30.GL_RGB32F, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, GL11.GL_FLOAT),
|
||||
Arguments.of("RGBA8_SNORM gets byte", true, GL31.GL_RGBA8_SNORM, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, GL11.GL_BYTE),
|
||||
Arguments.of("RGB8_SNORM gets byte", true, GL31.GL_RGB8_SNORM, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, GL11.GL_BYTE),
|
||||
Arguments.of("RGB10_A2 gets packed type", true, GL11.GL_RGB10_A2, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, GL12.GL_UNSIGNED_INT_2_10_10_10_REV),
|
||||
Arguments.of("DEPTH_COMPONENT24 gets unsigned int", true, GL14.GL_DEPTH_COMPONENT24, GL11.GL_DEPTH_COMPONENT, GL11.GL_UNSIGNED_BYTE, GL11.GL_UNSIGNED_INT),
|
||||
Arguments.of("DEPTH_COMPONENT32F gets float", true, GL30.GL_DEPTH_COMPONENT32F, GL11.GL_DEPTH_COMPONENT, GL11.GL_UNSIGNED_BYTE, GL11.GL_FLOAT),
|
||||
Arguments.of("DEPTH24_STENCIL8 gets 24_8", true, GL30.GL_DEPTH24_STENCIL8, GL30.GL_DEPTH_STENCIL, GL11.GL_UNSIGNED_BYTE, GL30.GL_UNSIGNED_INT_24_8),
|
||||
Arguments.of("already valid type is left alone", true, GL30.GL_RGBA16F, GL11.GL_RGBA, GL30.GL_HALF_FLOAT, GL30.GL_HALF_FLOAT),
|
||||
Arguments.of("packed ARGB RGBA remaps to unsigned byte on GLES", true, GL11.GL_RGBA8, GL11.GL_RGBA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, GL11.GL_UNSIGNED_BYTE),
|
||||
Arguments.of("packed ARGB BGRA remaps to unsigned byte on GLES", true, GL11.GL_RGBA8, GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, GL11.GL_UNSIGNED_BYTE),
|
||||
Arguments.of("packed ARGB untouched on desktop", false, GL11.GL_RGBA8, GL11.GL_RGBA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, GL12.GL_UNSIGNED_INT_8_8_8_8_REV));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rgb16AndRgba16_passThroughUntouched_onDesktop() {
|
||||
final var r = remapDesktop(GL11.GL_RGB16, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE);
|
||||
assertEquals(GL11.GL_RGB16, r.internalFormat());
|
||||
assertEquals(GL11.GL_UNSIGNED_BYTE, r.type());
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@MethodSource
|
||||
void remappedType(String label, boolean gles, int internal, int format, int type, int expectedType) {
|
||||
assertEquals(expectedType, GLESFormatRemap.apply(internal, format, type, gles).type());
|
||||
}
|
||||
|
||||
@Test
|
||||
void halfFloatInternalFormats_getHalfFloatType() {
|
||||
assertEquals(GL30.GL_HALF_FLOAT, remap(GL30.GL_R16F, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE).type());
|
||||
assertEquals(GL30.GL_HALF_FLOAT, remap(GL30.GL_RG16F, GL30.GL_RG, GL11.GL_UNSIGNED_BYTE).type());
|
||||
assertEquals(GL30.GL_HALF_FLOAT, remap(GL30.GL_RGB16F, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE).type());
|
||||
assertEquals(GL30.GL_HALF_FLOAT, remap(GL30.GL_RGBA16F, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE).type());
|
||||
static Stream<Arguments> remappedFormatAndType() {
|
||||
return Stream.of(
|
||||
Arguments.of("RGB16 remaps to half float on GLES", true, GL11.GL_RGB16, GL11.GL_RGB, GL30.GL_RGB16F, GL30.GL_HALF_FLOAT),
|
||||
Arguments.of("RGBA16 remaps to half float on GLES", true, GL11.GL_RGBA16, GL11.GL_RGBA, GL30.GL_RGBA16F, GL30.GL_HALF_FLOAT),
|
||||
Arguments.of("RGB16 passes through untouched on desktop", false, GL11.GL_RGB16, GL11.GL_RGB, GL11.GL_RGB16, GL11.GL_UNSIGNED_BYTE),
|
||||
Arguments.of("R11F_G11F_B10F keeps its format and gets half float", true, GL30.GL_R11F_G11F_B10F, GL11.GL_RGB, GL30.GL_R11F_G11F_B10F, GL30.GL_HALF_FLOAT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void floatInternalFormats_getFloatType() {
|
||||
assertEquals(GL11.GL_FLOAT, remap(GL30.GL_RGBA32F, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE).type());
|
||||
assertEquals(GL11.GL_FLOAT, remap(GL30.GL_RGB32F, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE).type());
|
||||
}
|
||||
|
||||
@Test
|
||||
void snormInternalFormats_getByteType() {
|
||||
assertEquals(GL11.GL_BYTE, remap(GL31.GL_RGBA8_SNORM, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE).type());
|
||||
assertEquals(GL11.GL_BYTE, remap(GL31.GL_RGB8_SNORM, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE).type());
|
||||
}
|
||||
|
||||
@Test
|
||||
void r11fG11fB10f_getsHalfFloatType() {
|
||||
final var r = remap(GL30.GL_R11F_G11F_B10F, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE);
|
||||
assertEquals(GL30.GL_R11F_G11F_B10F, r.internalFormat()); // not remapped
|
||||
assertEquals(GL30.GL_HALF_FLOAT, r.type());
|
||||
}
|
||||
|
||||
@Test
|
||||
void rgb10A2_getsPackedType() {
|
||||
final var r = remap(GL11.GL_RGB10_A2, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE);
|
||||
assertEquals(GL12.GL_UNSIGNED_INT_2_10_10_10_REV, r.type());
|
||||
}
|
||||
|
||||
@Test
|
||||
void depthFormats_getMatchingType() {
|
||||
assertEquals(GL11.GL_UNSIGNED_INT, remap(GL14.GL_DEPTH_COMPONENT24, GL11.GL_DEPTH_COMPONENT, GL11.GL_UNSIGNED_BYTE).type());
|
||||
assertEquals(GL11.GL_FLOAT, remap(GL30.GL_DEPTH_COMPONENT32F, GL11.GL_DEPTH_COMPONENT, GL11.GL_UNSIGNED_BYTE).type());
|
||||
assertEquals(GL30.GL_UNSIGNED_INT_24_8, remap(GL30.GL_DEPTH24_STENCIL8, GL30.GL_DEPTH_STENCIL, GL11.GL_UNSIGNED_BYTE).type());
|
||||
}
|
||||
|
||||
@Test
|
||||
void alreadyValidType_isLeftAlone() {
|
||||
final var r = remap(GL30.GL_RGBA16F, GL11.GL_RGBA, GL30.GL_HALF_FLOAT);
|
||||
assertEquals(GL30.GL_HALF_FLOAT, r.type());
|
||||
}
|
||||
|
||||
@Test
|
||||
void packedArgb_remapsToUnsignedByte_onGles() {
|
||||
final var r = remap(GL11.GL_RGBA8, GL11.GL_RGBA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV);
|
||||
assertEquals(GL11.GL_UNSIGNED_BYTE, r.type());
|
||||
|
||||
final var r2 = remap(GL11.GL_RGBA8, GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV);
|
||||
assertEquals(GL11.GL_UNSIGNED_BYTE, r2.type());
|
||||
}
|
||||
|
||||
@Test
|
||||
void packedArgb_untouched_onDesktop() {
|
||||
final var r = remapDesktop(GL11.GL_RGBA8, GL11.GL_RGBA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV);
|
||||
assertEquals(GL12.GL_UNSIGNED_INT_8_8_8_8_REV, r.type());
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@MethodSource
|
||||
void remappedFormatAndType(String label, boolean gles, int internal, int format, int expectedInternal, int expectedType) {
|
||||
final GLESFormatRemap.Result r = GLESFormatRemap.apply(internal, format, GL11.GL_UNSIGNED_BYTE, gles);
|
||||
assertEquals(expectedInternal, r.internalFormat());
|
||||
assertEquals(expectedType, r.type());
|
||||
}
|
||||
}
|
||||
-7
@@ -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
|
||||
|
||||
+22
-38
@@ -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,675 @@
|
||||
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.GL30;
|
||||
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(vbo2, GL11.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");
|
||||
assertEquals(other, GL11.glGetInteger(GL30.GL_VERTEX_ARRAY_BINDING), "driver vertex array unchanged by replay");
|
||||
} 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,21 +90,6 @@ public class GLSM_CoreRemovedCaps_GLTest {
|
||||
assertFalse(GLStateManager.glIsEnabled(cap));
|
||||
} finally {
|
||||
GLStateManager.glDisable(cap);
|
||||
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void secondaryColorIsCacheOnly() {
|
||||
try {
|
||||
// Calling through to the driver here aborts the JVM in a core profile - the color sum is emulated by the FFP shaders
|
||||
GLStateManager.glSecondaryColor3f(0.25F, 0.5F, 0.75F);
|
||||
assertNoGlError("glSecondaryColor3f");
|
||||
|
||||
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 +105,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 +118,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 +138,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 +157,6 @@ public class GLSM_CoreRemovedCaps_GLTest {
|
||||
} finally {
|
||||
GLStateManager.glDeleteLists(list, 1);
|
||||
GLStateManager.glDisable(GL11.GL_LINE_STIPPLE);
|
||||
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
+370
@@ -0,0 +1,370 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
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.GL30;
|
||||
import org.lwjgl.opengl.GL33;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
|
||||
@GLCoreTest
|
||||
public class GLSM_DeferredTextureDelete_GLTest {
|
||||
|
||||
private static final int RELEASE_DELAY_FRAMES = Reflect.getStatic(GLStateManager.class, "RELEASE_DELAY_FRAMES");
|
||||
private static final int SIZE = 4;
|
||||
private static final int LEVELS = 3;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private static int mipmappedTexture() {
|
||||
final int tex = GLStateManager.glGenTextures();
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
|
||||
for (int level = 0; level < LEVELS; level++) {
|
||||
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, level, GL11.GL_RGBA8, SIZE >> level, SIZE >> level, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
}
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
return tex;
|
||||
}
|
||||
|
||||
private static int levelWidth(int tex, int level) {
|
||||
final int previous = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, tex);
|
||||
final int width = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, level, GL11.GL_TEXTURE_WIDTH);
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, previous);
|
||||
return width;
|
||||
}
|
||||
|
||||
private static int cachedBinding(int unit) {
|
||||
return GLStateManager.getTextures().getTextureUnitBindings(unit).getBinding();
|
||||
}
|
||||
|
||||
private static void framesPass(int frames) {
|
||||
for (int i = 0; i < frames; i++) GLStateManager.onFrameEnd();
|
||||
}
|
||||
|
||||
enum Attachment {
|
||||
NONE {
|
||||
@Override int attach(int tex) { return 0; }
|
||||
},
|
||||
FRAMEBUFFER {
|
||||
@Override int attach(int tex) {
|
||||
final int fbo = GLStateManager.glGenFramebuffers();
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
|
||||
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, tex, 0);
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
|
||||
return fbo;
|
||||
}
|
||||
},
|
||||
NAMED_EXT {
|
||||
@Override int attach(int tex) {
|
||||
final int fbo = GLStateManager.glGenFramebuffers();
|
||||
GLStateManager.glNamedFramebufferTexture2DEXT(fbo, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, tex, 0);
|
||||
assertEquals(0, GL11.glGetInteger(GL30.GL_DRAW_FRAMEBUFFER_BINDING), "named attach must restore the draw binding");
|
||||
return fbo;
|
||||
}
|
||||
};
|
||||
|
||||
abstract int attach(int tex);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(Attachment.class)
|
||||
void storageIsReleasedOnlyAfterTheDelayFramesUnattached(Attachment attachment) {
|
||||
framesPass(RELEASE_DELAY_FRAMES);
|
||||
final int before = GLStateManager.deferredTexturesAwaitingRelease();
|
||||
final int tex = mipmappedTexture();
|
||||
final int fbo = attachment.attach(tex);
|
||||
|
||||
GLStateManager.glDeleteTextures(tex);
|
||||
assertEquals(before + 1, GLStateManager.deferredTexturesAwaitingRelease());
|
||||
framesPass(RELEASE_DELAY_FRAMES - 1);
|
||||
assertEquals(SIZE, levelWidth(tex, 0), "storage must outlive the delete by the delay frames");
|
||||
if (fbo != 0) {
|
||||
framesPass(RELEASE_DELAY_FRAMES);
|
||||
assertEquals(SIZE, levelWidth(tex, 0), "an attached texture keeps its storage");
|
||||
assertEquals(before + 1, GLStateManager.deferredTexturesAwaitingRelease());
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
|
||||
GLStateManager.glDeleteTextures(GLStateManager.glGenTextures());
|
||||
assertEquals(tex, GL30.glGetFramebufferAttachmentParameteri(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME), "a flush must not delete an attached texture");
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
|
||||
GLStateManager.glDeleteFramebuffers(BufferUtils.createIntBuffer(1).put(fbo).flip());
|
||||
framesPass(RELEASE_DELAY_FRAMES - 1);
|
||||
assertEquals(SIZE, levelWidth(tex, 0), "storage must outlive the detach by the delay frames");
|
||||
}
|
||||
framesPass(1);
|
||||
|
||||
for (int level = 0; level < LEVELS; level++) assertEquals(0, levelWidth(tex, level), "level " + level);
|
||||
assertEquals(before, GLStateManager.deferredTexturesAwaitingRelease(), "released storage leaves nothing awaiting release");
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
|
||||
}
|
||||
|
||||
enum Rejection { ATTACH, BIND_TEXTURE_UNIT }
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(Rejection.class)
|
||||
void deletedNamesAreRejected(Rejection rejection) {
|
||||
final int tex = mipmappedTexture();
|
||||
final int other = mipmappedTexture();
|
||||
GLStateManager.glDeleteTextures(tex);
|
||||
try {
|
||||
switch (rejection) {
|
||||
case ATTACH -> {
|
||||
final int fbo = GLStateManager.glGenFramebuffers();
|
||||
try {
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
|
||||
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, tex, 0);
|
||||
assertEquals(GL11.GL_NONE, GL30.glGetFramebufferAttachmentParameteri(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE));
|
||||
} finally {
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
|
||||
GLStateManager.glDeleteFramebuffers(fbo);
|
||||
}
|
||||
}
|
||||
case BIND_TEXTURE_UNIT -> {
|
||||
GLStateManager.glBindTextureUnit(2, other);
|
||||
GLStateManager.glBindTextureUnit(2, tex);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE2);
|
||||
assertEquals(other, GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D), "driver binding");
|
||||
assertEquals(other, GLStateManager.glGetInteger(GL11.GL_TEXTURE_BINDING_2D), "cached binding");
|
||||
}
|
||||
}
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
|
||||
} finally {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTextureUnit(2, 0);
|
||||
GLStateManager.glDeleteTextures(other);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void bindTexturesBindsEachTextureToItsTarget() {
|
||||
final int t2d = mipmappedTexture();
|
||||
final int t3d = GLStateManager.glGenTextures();
|
||||
GLStateManager.glBindTexture(GL12.GL_TEXTURE_3D, t3d);
|
||||
GLStateManager.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||
try {
|
||||
GLStateManager.glBindTextures(1, new int[] {t2d, t3d});
|
||||
|
||||
assertEquals(GL13.GL_TEXTURE0, GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE), "active unit unchanged");
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
assertEquals(t2d, GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D), "unit 1 driver binding");
|
||||
assertEquals(t2d, GLStateManager.glGetInteger(GL11.GL_TEXTURE_BINDING_2D), "unit 1 cached binding");
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE2);
|
||||
assertEquals(t3d, GL11.glGetInteger(GL12.GL_TEXTURE_BINDING_3D), "unit 2 driver binding");
|
||||
assertEquals(t3d, cachedBinding(2), "unit 2 cached binding");
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
|
||||
final int unit0 = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
|
||||
GLStateManager.glBindTextures(GLStateManager.MAX_TEXTURE_UNITS - 1, new int[] {t3d, t2d});
|
||||
assertEquals(unit0, GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D), "an out-of-range multi-bind must not land on the active unit");
|
||||
|
||||
GLStateManager.glDeleteTextures(t2d);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
assertEquals(0, GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D), "delete unbinds a multi-bound texture");
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
} finally {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTextures(1, new int[] {0, 0});
|
||||
GLStateManager.glDeleteTextures(t3d);
|
||||
}
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
|
||||
}
|
||||
|
||||
@Test
|
||||
void dsaCreated3DTextureIsBoundAs3D() {
|
||||
final int tex = RenderSystem.createTexture(GL12.GL_TEXTURE_3D);
|
||||
final int other = GLStateManager.glGenTextures();
|
||||
GLStateManager.glBindTexture(GL12.GL_TEXTURE_3D, other);
|
||||
RenderSystem.texImage3D(tex, GL12.GL_TEXTURE_3D, 0, GL11.GL_RGBA8, SIZE, SIZE, SIZE, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
assertEquals(tex, GL11.glGetInteger(GL12.GL_TEXTURE_BINDING_3D), "texImage3D binds the texture as 3D");
|
||||
assertEquals(tex, cachedBinding(0), "cached 3D binding");
|
||||
GLStateManager.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||
GLStateManager.glDeleteTextures(tex);
|
||||
GLStateManager.glDeleteTextures(other);
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createdNon2DTextureIsNotReleasedAs2D() {
|
||||
assumeTrue(GLStateManager.capabilities.OpenGL45, "no glCreateTextures on this context");
|
||||
final int tex = GLStateManager.glCreateTextures(GL12.GL_TEXTURE_3D);
|
||||
GLStateManager.glDeleteTextures(tex);
|
||||
framesPass(RELEASE_DELAY_FRAMES);
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
|
||||
}
|
||||
|
||||
@Test
|
||||
void deletingDetachesOnlyFromBoundFramebuffers() {
|
||||
final int tex = mipmappedTexture();
|
||||
final int unbound = Attachment.FRAMEBUFFER.attach(tex);
|
||||
final int bound = Attachment.FRAMEBUFFER.attach(tex);
|
||||
try {
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, bound);
|
||||
|
||||
GLStateManager.glDeleteTextures(tex);
|
||||
|
||||
assertEquals(GL11.GL_NONE, GL30.glGetFramebufferAttachmentParameteri(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE));
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, unbound);
|
||||
assertEquals(tex, GL30.glGetFramebufferAttachmentParameteri(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME));
|
||||
} finally {
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
|
||||
GLStateManager.glDeleteFramebuffers(bound);
|
||||
GLStateManager.glDeleteFramebuffers(unbound);
|
||||
}
|
||||
}
|
||||
|
||||
enum Rescue { PENDING, RELEASED, ATTACHED }
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(Rescue.class)
|
||||
void rescuedTextureIsEmptyWithDefaultParameters(Rescue rescue) {
|
||||
final int tex = mipmappedTexture();
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_BASE_LEVEL, 1);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 2);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL33.GL_TEXTURE_SWIZZLE_R, GL11.GL_GREEN);
|
||||
GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS, 1.5f);
|
||||
GLStateManager.glTexParameter(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_BORDER_COLOR, (FloatBuffer) BufferUtils.createFloatBuffer(4).put(new float[] {1, 1, 1, 1}).flip());
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
final int fbo = rescue == Rescue.ATTACHED ? Attachment.FRAMEBUFFER.attach(tex) : 0;
|
||||
GLStateManager.glDeleteTextures(tex);
|
||||
if (rescue != Rescue.PENDING) framesPass(RELEASE_DELAY_FRAMES);
|
||||
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
|
||||
try {
|
||||
assertEquals(rescue == Rescue.ATTACHED ? SIZE : 0, levelWidth(tex, 0), "only an attached texture keeps its storage");
|
||||
final int[][] defaults = {
|
||||
{GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST_MIPMAP_LINEAR},
|
||||
{GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR},
|
||||
{GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT},
|
||||
{GL12.GL_TEXTURE_BASE_LEVEL, 0},
|
||||
{GL12.GL_TEXTURE_MAX_LEVEL, 1000},
|
||||
{GL33.GL_TEXTURE_SWIZZLE_R, GL11.GL_RED},
|
||||
};
|
||||
for (int[] pair : defaults) {
|
||||
assertEquals(pair[1], GL11.glGetTexParameteri(GL11.GL_TEXTURE_2D, pair[0]), "driver pname 0x" + Integer.toHexString(pair[0]));
|
||||
assertEquals(pair[1], GLStateManager.glGetTexParameteri(GL11.GL_TEXTURE_2D, pair[0]), "cached pname 0x" + Integer.toHexString(pair[0]));
|
||||
}
|
||||
assertEquals(0.0f, GL11.glGetTexParameterf(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS));
|
||||
final FloatBuffer border = BufferUtils.createFloatBuffer(16);
|
||||
GL11.glGetTexParameter(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_BORDER_COLOR, border);
|
||||
assertEquals(0.0f, border.get(0) + border.get(1) + border.get(2) + border.get(3), "border color");
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
|
||||
} finally {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
GLStateManager.glDeleteTextures(tex);
|
||||
if (fbo != 0) GLStateManager.glDeleteFramebuffers(fbo);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void rescuingWithADifferentTargetLeavesTheBoundTextureAlone() {
|
||||
final int t3d = GLStateManager.glGenTextures();
|
||||
final int tex = mipmappedTexture();
|
||||
try {
|
||||
GLStateManager.glBindTexture(GL12.GL_TEXTURE_3D, t3d);
|
||||
GLStateManager.glTexParameteri(GL12.GL_TEXTURE_3D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
|
||||
GLStateManager.glDeleteTextures(tex);
|
||||
|
||||
GLStateManager.glBindTexture(GL12.GL_TEXTURE_3D, tex);
|
||||
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "GLSM drops the bind");
|
||||
assertEquals(t3d, cachedBinding(0), "cached 3D binding");
|
||||
assertEquals(GL11.GL_LINEAR, GL11.glGetTexParameteri(GL12.GL_TEXTURE_3D, GL11.GL_TEXTURE_MIN_FILTER));
|
||||
framesPass(RELEASE_DELAY_FRAMES);
|
||||
assertEquals(t3d, GL11.glGetInteger(GL12.GL_TEXTURE_BINDING_3D), "the release must not unbind the 3D texture");
|
||||
assertEquals(0, levelWidth(tex, 0), "the mismatched bind must leave the texture deferred");
|
||||
} finally {
|
||||
GLStateManager.glBindTexture(GL12.GL_TEXTURE_3D, 0);
|
||||
GLStateManager.glDeleteTextures(t3d);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void deletingAStaleNameKeepsTheBoundTexture() {
|
||||
final int live = mipmappedTexture();
|
||||
final int stale = GLStateManager.glGenTextures();
|
||||
try {
|
||||
GLStateManager.glDeleteTextures(stale);
|
||||
GLStateManager.glDeleteTextures(GLStateManager.glGenTextures());
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, live);
|
||||
|
||||
GLStateManager.glDeleteTextures(stale);
|
||||
framesPass(RELEASE_DELAY_FRAMES);
|
||||
|
||||
assertEquals(SIZE, levelWidth(live, 0), "a stale delete must not empty the bound texture");
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
|
||||
} finally {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
GLStateManager.glDeleteTextures(live);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void deletingAnImmutableTextureRaisesNoError() {
|
||||
final int tex = immutableTexture();
|
||||
|
||||
GLStateManager.glDeleteTextures(tex);
|
||||
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
|
||||
}
|
||||
|
||||
@Test
|
||||
void rescuedImmutableTextureResetsParametersAndDeletesAgain() {
|
||||
final int tex = immutableTexture();
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
GLStateManager.glDeleteTextures(tex);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
|
||||
|
||||
assertEquals(GL11.GL_NEAREST_MIPMAP_LINEAR, GL11.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER));
|
||||
GLStateManager.glDeleteTextures(tex);
|
||||
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {true, false})
|
||||
void deletingA2DTextureWhileA3DTextureIsBound(boolean deletedIsHidden2D) {
|
||||
final int t2d = GLStateManager.glGenTextures();
|
||||
final int t3d = GLStateManager.glGenTextures();
|
||||
final int other = GLStateManager.glGenTextures();
|
||||
try {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, t2d);
|
||||
GLStateManager.glBindTexture(GL12.GL_TEXTURE_3D, t3d);
|
||||
|
||||
GLStateManager.glDeleteTextures(deletedIsHidden2D ? t2d : other);
|
||||
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
|
||||
assertEquals(deletedIsHidden2D ? 0 : t2d, GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D), "only the deleted texture leaves the hidden 2D binding");
|
||||
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);
|
||||
GLStateManager.glDeleteTextures(deletedIsHidden2D ? other : t2d);
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
@@ -6,6 +6,7 @@ import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL21;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@@ -61,6 +62,18 @@ public class GLSM_DeletedBufferBinding_GLTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void deletedIndexedUniformBufferIsNotRestoredFromCache() {
|
||||
final int index = 3;
|
||||
final int ubo = makeBoundBuffer(GL31.GL_UNIFORM_BUFFER);
|
||||
GLStateManager.glBindBufferBase(GL31.GL_UNIFORM_BUFFER, index, ubo);
|
||||
assertEquals(ubo, GLStateManager.getIndexedBufferBinding(GL31.GL_UNIFORM_BUFFER, index));
|
||||
|
||||
GLStateManager.glDeleteBuffers(ubo);
|
||||
assertEquals(0, GL30.glGetInteger(GL31.GL_UNIFORM_BUFFER_BINDING, index));
|
||||
assertEquals(0, GLStateManager.getIndexedBufferBinding(GL31.GL_UNIFORM_BUFFER, index));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deletedElementArrayBufferDoesNotBlockClientDrawElements() {
|
||||
final int ebo = makeBoundBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER);
|
||||
|
||||
+14
-45
@@ -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();
|
||||
}
|
||||
}
|
||||
+11
-34
@@ -6,6 +6,8 @@ import com.gtnewhorizons.angelica.glsm.recording.DisplayListVBO;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.GLCommand;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
@@ -39,26 +41,18 @@ class GLSM_DisplayList_Batching_Test {
|
||||
|
||||
// ==================== Pure Unit Tests (No GL Context) ====================
|
||||
|
||||
@Test
|
||||
void testConsecutiveDrawsWithSameFormatShareSingleVBO() {
|
||||
@ParameterizedTest(name = "{0} draws")
|
||||
@ValueSource(ints = { 3, 100 })
|
||||
void testConsecutiveDrawsWithSameFormatShareSingleVBO(int draws) {
|
||||
CompiledDisplayList compiled = createDisplayList(() -> {
|
||||
createSimpleDraw();
|
||||
createSimpleDraw();
|
||||
createSimpleDraw();
|
||||
for (int i = 0; i < draws; i++) {
|
||||
createSimpleDraw();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Verify: 1 VBO for all draws (format-based batching)
|
||||
assertEquals(1, getVBOCount(compiled),
|
||||
"3 draws with same format should produce 1 shared VBO");
|
||||
|
||||
// Verify: 1 DrawRangeCmd covering all geometry
|
||||
Int2IntMap counts = compiled.getCommandCounts();
|
||||
int drawCount = counts.getOrDefault(GLCommand.DRAW_RANGE, 0);
|
||||
assertEquals(1, drawCount,
|
||||
"3 consecutive draws with same format and transform should produce 1 DrawRangeCmd");
|
||||
|
||||
|
||||
assertEquals(1, getVBOCount(compiled), draws + " draws with same format should produce 1 shared VBO");
|
||||
assertEquals(1, compiled.getCommandCounts().getOrDefault(GLCommand.DRAW_RANGE, 0),
|
||||
draws + " consecutive draws with same format and transform should produce 1 DrawRangeCmd");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -101,23 +95,6 @@ class GLSM_DisplayList_Batching_Test {
|
||||
assertEquals(1, multMatrixCount, "Should have 1 Translate at the end (the transforms in the draws are prebaked)");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testManyDrawsSameFormatProduceSingleVBO() {
|
||||
CompiledDisplayList compiled = createDisplayList(() -> {
|
||||
for (int i = 0; i < 100; i++) {
|
||||
createSimpleDraw();
|
||||
}
|
||||
});
|
||||
|
||||
// Verify: 1 shared VBO
|
||||
assertEquals(1, getVBOCount(compiled), "100 draws with same format should produce 1 shared VBO");
|
||||
|
||||
Int2IntMap counts = compiled.getCommandCounts();
|
||||
int drawCount = counts.getOrDefault(GLCommand.DRAW_RANGE, 0);
|
||||
assertEquals(1, drawCount,
|
||||
"100 consecutive draws with same format and transform should produce 1 DrawRangeCmd");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testCancellingTransformsProduceSeparateDrawRangeCmds() {
|
||||
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ViewportState;
|
||||
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.GL15;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/** The executed half of a GL_COMPILE_AND_EXECUTE glDrawArrays sees transforms issued since the last matrix barrier. */
|
||||
@GLCoreTest
|
||||
public class GLSM_DisplayList_CompileAndExecuteArrays_GLTest {
|
||||
|
||||
private static final int W = 32;
|
||||
|
||||
private static final int POSITION_LOC = VertexFormatElement.Usage.POSITION.getAttributeLocation();
|
||||
|
||||
private final ByteBuffer pixels = BufferUtils.createByteBuffer(W * 4);
|
||||
private int savedX, savedY, savedW, savedH;
|
||||
private int vao, vbo, list;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
final ViewportState vp = GLStateManager.getViewportState();
|
||||
savedX = vp.x;
|
||||
savedY = vp.y;
|
||||
savedW = vp.width;
|
||||
savedH = vp.height;
|
||||
|
||||
GLStateManager.glUseProgram(0);
|
||||
|
||||
vao = GLStateManager.glGenVertexArrays();
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
vbo = GLStateManager.glGenBuffers();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
||||
final ByteBuffer verts = ByteBuffer.allocateDirect(3 * 12).order(ByteOrder.nativeOrder());
|
||||
verts.putFloat(0f).putFloat(0.5f).putFloat(0f).putFloat(W).putFloat(0.5f).putFloat(0f).flip();
|
||||
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, verts, GL15.GL_STATIC_DRAW);
|
||||
GLStateManager.glVertexAttribPointer(POSITION_LOC, 3, GL11.GL_FLOAT, false, 12, 0L);
|
||||
GLStateManager.glEnableVertexAttribArray(POSITION_LOC);
|
||||
|
||||
GLStateManager.glViewport(0, 0, W, 1);
|
||||
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
|
||||
GLStateManager.glPushMatrix();
|
||||
GLStateManager.glLoadIdentity();
|
||||
GLStateManager.glOrtho(0, W, 0, 1, -1, 1);
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLStateManager.glPushMatrix();
|
||||
GLStateManager.glLoadIdentity();
|
||||
GLStateManager.glDisable(GL11.GL_DEPTH_TEST);
|
||||
GLStateManager.glDisable(GL11.GL_BLEND);
|
||||
GLStateManager.glDisable(GL11.GL_TEXTURE_2D);
|
||||
GLStateManager.glColor4f(1f, 1f, 1f, 1f);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
if (list > 0) GLStateManager.glDeleteLists(list, 1);
|
||||
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
|
||||
GLStateManager.glPopMatrix();
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLStateManager.glPopMatrix();
|
||||
GLStateManager.glViewport(savedX, savedY, savedW, savedH);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
if (vbo != 0) GLStateManager.glDeleteBuffers(vbo);
|
||||
if (vao != 0) GLStateManager.glDeleteVertexArrays(vao);
|
||||
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
|
||||
}
|
||||
|
||||
private void clear() {
|
||||
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
private boolean[] readCoverage() {
|
||||
pixels.clear();
|
||||
GLStateManager.glReadPixels(0, 0, W, 1, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "GL error during readback");
|
||||
|
||||
final boolean[] covered = new boolean[W];
|
||||
for (int i = 0; i < W; i++) {
|
||||
covered[i] = (pixels.get(i * 4) & 0xFF) > 127;
|
||||
}
|
||||
return covered;
|
||||
}
|
||||
|
||||
private static boolean[] leftHalf() {
|
||||
final boolean[] e = new boolean[W];
|
||||
for (int i = 0; i < W / 2; i++) e[i] = true;
|
||||
return e;
|
||||
}
|
||||
|
||||
/** The scale is issued inside the list, after the push that flushed everything before it - AE2 monitor's shape. */
|
||||
private void compileScaledDraw(int mode) {
|
||||
list = GLStateManager.glGenLists(1);
|
||||
GLStateManager.glNewList(list, mode);
|
||||
GLStateManager.glPushMatrix();
|
||||
GLStateManager.glScalef(0.5f, 1f, 1f);
|
||||
GLStateManager.glDrawArrays(GL11.GL_LINES, 0, 2);
|
||||
GLStateManager.glPopMatrix();
|
||||
GLStateManager.glEndList();
|
||||
}
|
||||
|
||||
// Replay is not asserted here: a compiled list plays back through FFP client arrays, which this core context lacks,
|
||||
// and in the compat context this attribute setup draws nothing at all.
|
||||
@Test
|
||||
void compileAndExecuteDrawsWithPendingScale() {
|
||||
clear();
|
||||
compileScaledDraw(GL11.GL_COMPILE_AND_EXECUTE);
|
||||
assertArrayEquals(leftHalf(), readCoverage(), "executed half of GL_COMPILE_AND_EXECUTE");
|
||||
}
|
||||
}
|
||||
+15
-98
@@ -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)");
|
||||
@@ -140,12 +62,7 @@ class GLSM_DisplayList_FormatOptimization_Test {
|
||||
GLStateManager.glNewList(displayList, GL11.GL_COMPILE);
|
||||
|
||||
// First draw: position only
|
||||
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);
|
||||
GLStateManager.glEnd();
|
||||
DisplayListTestHelper.immediateQuad(1.0f);
|
||||
|
||||
// Second draw: position + color
|
||||
GLStateManager.glBegin(GL11.GL_QUADS);
|
||||
|
||||
+37
-136
@@ -3,10 +3,12 @@ package com.gtnewhorizons.angelica.glsm;
|
||||
import org.joml.Matrix4f;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@@ -55,71 +57,42 @@ class GLSM_DisplayList_Nesting_Test {
|
||||
GLStateManager.glCallList(childList); // Should render child at (10,0,0)
|
||||
GLStateManager.glEndList();
|
||||
|
||||
// Execute parent list
|
||||
GLStateManager.glPushMatrix();
|
||||
GLStateManager.glLoadIdentity();
|
||||
GLStateManager.glCallList(parentList);
|
||||
|
||||
// Check final matrix state - should have parent's translate applied
|
||||
FloatBuffer actualBuf = BufferUtils.createFloatBuffer(16);
|
||||
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, actualBuf);
|
||||
|
||||
// Expected: translate(10, 0, 0)
|
||||
Matrix4f expected = new Matrix4f().translate(10.0f, 0.0f, 0.0f);
|
||||
FloatBuffer expectedBuf = BufferUtils.createFloatBuffer(16);
|
||||
expected.get(expectedBuf);
|
||||
|
||||
// Compare buffers directly (both are column-major OpenGL format)
|
||||
for (int i = 0; i < 16; i++) {
|
||||
assertEquals(expectedBuf.get(i), actualBuf.get(i), 0.0001f,
|
||||
String.format("Matrix[%d] mismatch", i));
|
||||
}
|
||||
|
||||
GLStateManager.glPopMatrix();
|
||||
DisplayListTestHelper.assertModelviewAfterCall(parentList, new Matrix4f().translate(10.0f, 0.0f, 0.0f), "parent transform applies to the child");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNestedDisplayListWithBothTransforms() {
|
||||
// Create child list: translate and draw
|
||||
childList = GL11.glGenLists(1);
|
||||
GLStateManager.glNewList(childList, GL11.GL_COMPILE);
|
||||
GLStateManager.glTranslatef(1.0f, 0.0f, 0.0f); // Child transform
|
||||
GL11.glBegin(GL11.GL_QUADS);
|
||||
GL11.glVertex3f(0, 0, 0);
|
||||
GL11.glVertex3f(1, 0, 0);
|
||||
GL11.glVertex3f(1, 1, 0);
|
||||
GL11.glVertex3f(0, 1, 0);
|
||||
GL11.glEnd();
|
||||
GLStateManager.glEndList();
|
||||
static Stream<Arguments> nestedTranslationsCompose() {
|
||||
return Stream.of(
|
||||
Arguments.of(new float[] { 10.0f, 1.0f }, 11.0f),
|
||||
Arguments.of(new float[] { 100.0f, 10.0f, 1.0f }, 111.0f));
|
||||
}
|
||||
|
||||
// Create parent list: translate then call child
|
||||
parentList = GL11.glGenLists(1);
|
||||
GLStateManager.glNewList(parentList, GL11.GL_COMPILE);
|
||||
GLStateManager.glTranslatef(10.0f, 0.0f, 0.0f); // Parent transform
|
||||
GLStateManager.glCallList(childList); // Child adds (1,0,0) to parent's (10,0,0)
|
||||
GLStateManager.glEndList();
|
||||
|
||||
// Execute parent list
|
||||
GLStateManager.glPushMatrix();
|
||||
GLStateManager.glLoadIdentity();
|
||||
GLStateManager.glCallList(parentList);
|
||||
|
||||
// Check final matrix state - should be translate(10,0,0) * translate(1,0,0) = translate(11,0,0)
|
||||
FloatBuffer actualBuf = BufferUtils.createFloatBuffer(16);
|
||||
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, actualBuf);
|
||||
|
||||
// Expected: translate(11, 0, 0) = parent(10) + child(1)
|
||||
Matrix4f expected = new Matrix4f().translate(11.0f, 0.0f, 0.0f);
|
||||
FloatBuffer expectedBuf = BufferUtils.createFloatBuffer(16);
|
||||
expected.get(expectedBuf);
|
||||
|
||||
// Compare buffers directly
|
||||
for (int i = 0; i < 16; i++) {
|
||||
assertEquals(expectedBuf.get(i), actualBuf.get(i), 0.0001f,
|
||||
String.format("Matrix[%d] mismatch", i));
|
||||
@ParameterizedTest(name = "{0} composes to {1}")
|
||||
@MethodSource
|
||||
void nestedTranslationsCompose(float[] chain, float expectedX) {
|
||||
final int[] lists = new int[chain.length];
|
||||
try {
|
||||
for (int depth = chain.length - 1; depth >= 0; depth--) {
|
||||
lists[depth] = GL11.glGenLists(1);
|
||||
GLStateManager.glNewList(lists[depth], GL11.GL_COMPILE);
|
||||
GLStateManager.glTranslatef(chain[depth], 0.0f, 0.0f);
|
||||
if (depth == chain.length - 1) {
|
||||
GL11.glBegin(GL11.GL_QUADS);
|
||||
GL11.glVertex3f(0, 0, 0);
|
||||
GL11.glVertex3f(1, 0, 0);
|
||||
GL11.glVertex3f(1, 1, 0);
|
||||
GL11.glVertex3f(0, 1, 0);
|
||||
GL11.glEnd();
|
||||
} else {
|
||||
GLStateManager.glCallList(lists[depth + 1]);
|
||||
}
|
||||
GLStateManager.glEndList();
|
||||
}
|
||||
DisplayListTestHelper.assertModelviewAfterCall(lists[0], new Matrix4f().translate(expectedX, 0.0f, 0.0f), "nested translations");
|
||||
} finally {
|
||||
for (int list : lists) {
|
||||
if (list > 0) GLStateManager.glDeleteLists(list, 1);
|
||||
}
|
||||
}
|
||||
|
||||
GLStateManager.glPopMatrix();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -145,79 +118,7 @@ class GLSM_DisplayList_Nesting_Test {
|
||||
GLStateManager.glCallList(childList); // Child uses push/pop
|
||||
GLStateManager.glEndList();
|
||||
|
||||
// Execute parent list
|
||||
GLStateManager.glPushMatrix();
|
||||
GLStateManager.glLoadIdentity();
|
||||
GLStateManager.glCallList(parentList);
|
||||
|
||||
// Check final matrix state - should still be translate(10,0,0) because child did push/pop
|
||||
FloatBuffer actualBuf = BufferUtils.createFloatBuffer(16);
|
||||
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, actualBuf);
|
||||
|
||||
// Expected: translate(10, 0, 0) only (child's transform was popped)
|
||||
Matrix4f expected = new Matrix4f().translate(10.0f, 0.0f, 0.0f);
|
||||
FloatBuffer expectedBuf = BufferUtils.createFloatBuffer(16);
|
||||
expected.get(expectedBuf);
|
||||
|
||||
// Compare buffers directly
|
||||
for (int i = 0; i < 16; i++) {
|
||||
assertEquals(expectedBuf.get(i), actualBuf.get(i), 0.0001f,
|
||||
String.format("Matrix[%d] mismatch", i));
|
||||
}
|
||||
|
||||
GLStateManager.glPopMatrix();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDeepNesting() {
|
||||
// Create deeply nested lists: grandchild -> child -> parent
|
||||
int grandchildList = GL11.glGenLists(1);
|
||||
GLStateManager.glNewList(grandchildList, GL11.GL_COMPILE);
|
||||
GLStateManager.glTranslatef(1.0f, 0.0f, 0.0f); // Grandchild: +1
|
||||
GL11.glBegin(GL11.GL_QUADS);
|
||||
GL11.glVertex3f(0, 0, 0);
|
||||
GL11.glVertex3f(1, 0, 0);
|
||||
GL11.glVertex3f(1, 1, 0);
|
||||
GL11.glVertex3f(0, 1, 0);
|
||||
GL11.glEnd();
|
||||
GLStateManager.glEndList();
|
||||
|
||||
childList = GL11.glGenLists(1);
|
||||
GLStateManager.glNewList(childList, GL11.GL_COMPILE);
|
||||
GLStateManager.glTranslatef(10.0f, 0.0f, 0.0f); // Child: +10
|
||||
GLStateManager.glCallList(grandchildList); // Grandchild: +1
|
||||
GLStateManager.glEndList();
|
||||
|
||||
parentList = GL11.glGenLists(1);
|
||||
GLStateManager.glNewList(parentList, GL11.GL_COMPILE);
|
||||
GLStateManager.glTranslatef(100.0f, 0.0f, 0.0f); // Parent: +100
|
||||
GLStateManager.glCallList(childList); // Child: +10, Grandchild: +1
|
||||
GLStateManager.glEndList();
|
||||
|
||||
// Execute parent list
|
||||
GLStateManager.glPushMatrix();
|
||||
GLStateManager.glLoadIdentity();
|
||||
GLStateManager.glCallList(parentList);
|
||||
|
||||
// Check final matrix state - should be 100 + 10 + 1 = 111
|
||||
FloatBuffer actualBuf = BufferUtils.createFloatBuffer(16);
|
||||
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, actualBuf);
|
||||
|
||||
// Expected: translate(111, 0, 0)
|
||||
Matrix4f expected = new Matrix4f().translate(111.0f, 0.0f, 0.0f);
|
||||
FloatBuffer expectedBuf = BufferUtils.createFloatBuffer(16);
|
||||
expected.get(expectedBuf);
|
||||
|
||||
// Compare buffers directly
|
||||
for (int i = 0; i < 16; i++) {
|
||||
assertEquals(expectedBuf.get(i), actualBuf.get(i), 0.0001f,
|
||||
String.format("Matrix[%d] mismatch", i));
|
||||
}
|
||||
|
||||
GLStateManager.glPopMatrix();
|
||||
|
||||
// Cleanup grandchild
|
||||
GLStateManager.glDeleteLists(grandchildList, 1);
|
||||
DisplayListTestHelper.assertModelviewAfterCall(parentList, new Matrix4f().translate(10.0f, 0.0f, 0.0f), "child push/pop leaves only the parent transform");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Loaded 100 of 834 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user