mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-10-25 20:22:22 +00:00
Update Celeritas (#2087)
This commit is contained in:
1 parent
2845c9e078
commit
3b4ebcf611
165 files changed
+6776
-3036
No files matched your search
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
cd "$REPO_ROOT/umbra"
|
||||
|
||||
gradle_args=(:compileJava :compileMixinJava :test --no-daemon --console=plain --stacktrace)
|
||||
if [[ -n "${UMBRA_GRADLE_ARGS:-}" ]]; then
|
||||
read -ra extra_args <<< "$UMBRA_GRADLE_ARGS"
|
||||
gradle_args+=("${extra_args[@]}")
|
||||
fi
|
||||
|
||||
sh ./gradlew "${gradle_args[@]}"
|
||||
@@ -0,0 +1,25 @@
|
||||
name: Umbra build
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master, main ]
|
||||
push:
|
||||
branches: [ master, main ]
|
||||
|
||||
jobs:
|
||||
umbra-build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: zulu
|
||||
java-version: |
|
||||
21
|
||||
25
|
||||
- uses: gradle/actions/setup-gradle@v5
|
||||
with:
|
||||
validate-wrappers: true
|
||||
- run: .github/scripts/umbra-build.sh
|
||||
@@ -1,5 +1,6 @@
|
||||
.gradle
|
||||
.kotlin/
|
||||
.lwjgl/
|
||||
.settings
|
||||
/.idea/
|
||||
/.vs/
|
||||
|
||||
+4
-82
@@ -1,19 +1,16 @@
|
||||
import com.modrinth.minotaur.TaskModrinthUpload
|
||||
import net.darkhax.curseforgegradle.TaskPublishCurseForge
|
||||
import xyz.wagyourtail.jvmdg.gradle.flags.DowngradeFlags
|
||||
import xyz.wagyourtail.jvmdg.gradle.task.DowngradeJar
|
||||
import xyz.wagyourtail.jvmdg.gradle.task.files.DowngradeFiles
|
||||
|
||||
plugins {
|
||||
id("com.gtnewhorizons.gtnhconvention")
|
||||
}
|
||||
|
||||
tasks.withType<DowngradeJar>().configureEach { logLevel.set("FATAL") }
|
||||
tasks.withType<DowngradeFiles>().configureEach { logLevel.set("FATAL") }
|
||||
val lwjglDebug = false
|
||||
val gpuHud = false
|
||||
val renderdoc = false
|
||||
val rgp = false
|
||||
extra["lwjglDebug"] = lwjglDebug
|
||||
|
||||
minecraft {
|
||||
extraRunJvmArguments.add("-Dangelica.debug.testBlocks=true")
|
||||
@@ -24,24 +21,13 @@ minecraft {
|
||||
|
||||
extraRunJvmArguments.add("-Dsun.net.client.defaultConnectTimeout=5000")
|
||||
extraRunJvmArguments.add("-Dsun.net.client.defaultReadTimeout=5000")
|
||||
// TODO: Remove once GTNHGradle includes shaderc/spvc in the default lwjgl3Bindings list
|
||||
lwjgl3Bindings.addAll("shaderc", "spvc")
|
||||
lwjgl3Version = libs.versions.lwjgl3.get()
|
||||
// extraRunJvmArguments.addAll("-Dlegacy.debugClassLoadingSave=true")
|
||||
// extraRunJvmArguments.addAll("-Drfb.dumpLoadedClasses=true", "-Drfb.dumpLoadedClassesPerTransformer=true")
|
||||
//extraRunJvmArguments.add("-Dangelica.debug.redirectorLogspam=true")
|
||||
|
||||
}
|
||||
|
||||
// Forward -Dangelica.* from the gradle invocation to the client
|
||||
tasks.withType<JavaExec>().matching { it.name.startsWith("runClient") }.configureEach {
|
||||
for ((key, value) in System.getProperties()) {
|
||||
val name = key.toString()
|
||||
if (name.startsWith("angelica.")) {
|
||||
jvmArgs("-D$name=$value")
|
||||
}
|
||||
}
|
||||
}
|
||||
apply(from = "gradle/angelica-run-common.gradle.kts")
|
||||
|
||||
if (gpuHud) {
|
||||
tasks.withType<JavaExec>().matching { it.name.startsWith("runClient") }.configureEach {
|
||||
@@ -57,47 +43,12 @@ if (gpuHud) {
|
||||
|
||||
configurations.all {
|
||||
exclude(group = "com.github.GTNewHorizons", module = "Angelica")
|
||||
resolutionStrategy.dependencySubstitution {
|
||||
substitute(module("com.github.GTNewHorizons:lwjgl3ify"))
|
||||
.using(module("com.github.GTNewHorizons:lwjgl3ify:${libs.versions.lwjgl3ify.get()}"))
|
||||
}
|
||||
}
|
||||
|
||||
// lwjgl3ify pulls a pinned lwjgl 3.4.2 snapshot transitively from here.
|
||||
repositories {
|
||||
maven {
|
||||
name = "Maven Central Snapshots"
|
||||
url = uri("https://central.sonatype.com/repository/maven-snapshots/")
|
||||
content { includeGroup("org.lwjgl") }
|
||||
}
|
||||
}
|
||||
|
||||
val osName = System.getProperty("os.name").lowercase()
|
||||
val isMacOs = org.gradle.internal.os.OperatingSystem.current().isMacOsX
|
||||
val isMacOsArm64 = isMacOs && System.getProperty("os.arch") == "aarch64"
|
||||
|
||||
tasks.withType<JavaExec>().configureEach {
|
||||
if (name.startsWith("runClient") && name != "runClient") {
|
||||
if (isMacOs) {
|
||||
// SDL3 / Cocoa / Metal must initialize on the JVM main thread.
|
||||
jvmArgs("-XstartOnFirstThread")
|
||||
jvmArgs("-Dangelica.sdlgpu.encoderAssertions=" + if (lwjglDebug) "fatal" else "warn")
|
||||
if (lwjglDebug) {
|
||||
environment("METAL_DEVICE_WRAPPER_TYPE", "1")
|
||||
environment("METAL_DEBUG_ERROR_MODE", "0")
|
||||
environment("MTL_SHADER_VALIDATION", "1")
|
||||
environment("MTL_SHADER_VALIDATION_REPORT_TO_STDERR", "1")
|
||||
environment("MTL_DEBUG_LAYER", "1")
|
||||
environment("MallocScribble", "1")
|
||||
environment("MallocPreScribble", "1")
|
||||
environment("MallocGuardEdges", "1")
|
||||
environment("MallocStackLogging", "1")
|
||||
environment("MallocStackLoggingNoCompact", "1")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Linux runClient: RenderDoc frame capture (F12) OR RADV RGP/SQTT capture. Mutually exclusive; RGP wins
|
||||
tasks.withType<JavaExec>().configureEach {
|
||||
if (name.startsWith("runClient") && !isMacOs && !osName.contains("windows")) {
|
||||
@@ -126,14 +77,6 @@ tasks.withType<JavaExec>().configureEach {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.processResources {
|
||||
val projectVersion = project.version.toString()
|
||||
inputs.property("version", projectVersion)
|
||||
filesMatching("META-INF/rfb-plugin/*") {
|
||||
expand("version" to projectVersion)
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register<Copy>("copyDependencies") {
|
||||
group = "Angelica"
|
||||
description = "Collect dependencies into the testDependencies folder"
|
||||
@@ -206,6 +149,7 @@ fun Test.configureAngelicaJava8() {
|
||||
tasks.test {
|
||||
useJUnitPlatform { excludeTags = setOf("gl-core") }
|
||||
configureAngelicaJava8()
|
||||
jvmArgs("-Dceleritas.lwjglService=org.taumc.celeritas.lwjgl.HeadlessTestLWJGLService")
|
||||
dependsOn(":glsm:classes", ":lwjgl3-backend:classes", ":sdl-gpu:classes")
|
||||
}
|
||||
|
||||
@@ -233,29 +177,7 @@ val glCoreTest by tasks.registering(Test::class) {
|
||||
|
||||
tasks.test { finalizedBy(glCoreTest) }
|
||||
|
||||
tasks.shadowJar {
|
||||
dependsOn(embedOnly)
|
||||
from(embedOnly.map(::zipTree))
|
||||
|
||||
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||
mergeServiceFiles()
|
||||
|
||||
minimize {
|
||||
exclude(project(rootProject.path))
|
||||
exclude(project(":glsm"))
|
||||
exclude(project(":lwjgl3-backend"))
|
||||
exclude(project(":sdl-gpu"))
|
||||
exclude(dependency("org.taumc:.*:.*"))
|
||||
exclude(dependency("org.antlr:.*:.*"))
|
||||
}
|
||||
|
||||
relocate("com.mitchej123", "com.mitchej123")
|
||||
relocate("org.embeddedt", "org.embeddedt")
|
||||
relocate("com.gtnewhorizons.angelica.config", "com.gtnewhorizons.angelica.config")
|
||||
relocate("com.gtnewhorizons.angelica.glsm", "com.gtnewhorizons.angelica.glsm")
|
||||
relocate("com.gtnewhorizons.angelica.lwjgl3", "com.gtnewhorizons.angelica.lwjgl3")
|
||||
relocate("com.gtnewhorizons.angelica.sdlgpu", "com.gtnewhorizons.angelica.sdlgpu")
|
||||
}
|
||||
apply(from = "gradle/angelica-shadow-common.gradle.kts")
|
||||
|
||||
tasks.withType<TaskPublishCurseForge>().configureEach {
|
||||
uploadArtifacts.forEach { it.addGameVersion("Client") }
|
||||
|
||||
@@ -52,8 +52,8 @@ configurations.configureEach {
|
||||
}
|
||||
|
||||
DependencyHandlerScope.of(dependencies).apply {
|
||||
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.187:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Hodgepodge:2.7.187:dev")
|
||||
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.197:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Hodgepodge:2.7.197:dev")
|
||||
|
||||
runtimeOnlyNonPublishable(rfg.deobf("curse.maven:biomes-o-plenty-220318:2499612"))
|
||||
|
||||
@@ -104,7 +104,6 @@ DependencyHandlerScope.of(dependencies).apply {
|
||||
|
||||
// Celeritas (isTransitive=false: deps provided by GTNHLib/runtime)
|
||||
embedOnly(libs.celeritas.common) { isTransitive = false }
|
||||
embedOnly(libs.celeritas.lwjgl2.service) { isTransitive = false }
|
||||
|
||||
// Because who doesn't want NEI
|
||||
devOnlyNonPublishable("com.github.GTNewHorizons:NotEnoughItems:2.8.109-GTNH:dev")
|
||||
@@ -177,7 +176,7 @@ DependencyHandlerScope.of(dependencies).apply {
|
||||
compileOnly("com.github.GTNewHorizons:BetterCrashes:1.4.5-GTNH:dev")
|
||||
|
||||
// Distant Horizons
|
||||
compileOnly("maven.modrinth:DistantHorizonsApi:5.1.0")
|
||||
compileOnly(libs.distant.horizons.api)
|
||||
|
||||
compileOnly("mega:fluidlogged-mc1.7.10:0.1.2")
|
||||
|
||||
@@ -208,5 +207,6 @@ afterEvaluate {
|
||||
|
||||
testRuntimeOnly(libs.fastutil)
|
||||
testRuntimeOnly(libs.joml)
|
||||
testImplementation(libs.distant.horizons.api)
|
||||
}
|
||||
}
|
||||
+21
-1
@@ -46,6 +46,27 @@ tasks.withType<Jar>().configureEach {
|
||||
exclude("net/minecraft/**")
|
||||
}
|
||||
|
||||
val stubsElements by configurations.creating {
|
||||
isCanBeConsumed = true
|
||||
isCanBeResolved = false
|
||||
attributes {
|
||||
attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage::class.java, Usage.JAVA_RUNTIME))
|
||||
attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category::class.java, Category.LIBRARY))
|
||||
attribute(rfgObfAttr, "mcp")
|
||||
attribute(rfgTransformedAttr, true)
|
||||
}
|
||||
outgoing {
|
||||
capability("${project.group}:glsm-stubs:${project.version}")
|
||||
}
|
||||
}
|
||||
|
||||
artifacts {
|
||||
add(stubsElements.name, sourceSets["stubs"].java.classesDirectory) {
|
||||
type = "java-classes-directory"
|
||||
builtBy(tasks.named("stubsClasses"))
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
name = "Mojang"
|
||||
@@ -141,7 +162,6 @@ dependencies {
|
||||
testRuntimeOnly(libs.junit.platform.launcher)
|
||||
testRuntimeOnly(libs.log4j.core)
|
||||
testImplementation(libs.celeritas.common) { isTransitive = false }
|
||||
testRuntimeOnly(libs.celeritas.lwjgl2.service) { isTransitive = false }
|
||||
testRuntimeOnly(libs.jvmdowngrader.java.api) { artifact { classifier = "downgraded-8" } }
|
||||
testRuntimeOnly(libs.commons.lang3)
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ public final class SystemProperties {
|
||||
public static final int FLYBY_LENGTH = Integer.getInteger("angelica.flyby.length", 0); // Blocks for moving routes, ticks for stationary
|
||||
public static final double FLYBY_SPEED = parseDouble("angelica.flyby.speed"); // Travel speed in blocks per tick
|
||||
public static final boolean FLYBY_EXIT_WHEN_DONE = Boolean.getBoolean("angelica.flyby.exitWhenDone");
|
||||
public static final long FLYBY_TIME_OF_DAY = Math.floorMod(Long.getLong("angelica.flyby.timeOfDay", 6000L), 24000L);
|
||||
|
||||
// Debug
|
||||
public static final boolean LWJGL_DEBUG = Boolean.getBoolean("org.lwjgl.util.Debug");
|
||||
@@ -62,12 +63,14 @@ public final class SystemProperties {
|
||||
private static final boolean DUMP_SHADERS_PROP = Boolean.getBoolean("angelica.debug.dumpShaders");
|
||||
public static final boolean REDIRECTOR_LOGSPAM = Boolean.getBoolean("angelica.debug.redirectorLogspam");
|
||||
public static final boolean DEBUG_DISPLAY_LISTS = Boolean.getBoolean("angelica.debug.displayLists");
|
||||
public static final boolean FFP_TRACE = Boolean.getBoolean("angelica.debug.ffpTrace");
|
||||
public static final boolean LOG_DISPLAY_LIST_COMPILATION = Boolean.getBoolean("angelica.debug.displayLists.compilation");
|
||||
public static final boolean FORCE_ORPHAN_STREAMING = Boolean.getBoolean("angelica.debug.forceOrphanStreaming");
|
||||
public static final String SHADER_DUMP_ROOT = "angelica_dumps";
|
||||
|
||||
// Set by us, read by celeritas
|
||||
public static final String KEY_CELERITAS_ENABLE_GL_DEBUG = "celeritas.enableGLDebug";
|
||||
public static final String KEY_CELERITAS_LWJGL_SERVICE = "celeritas.lwjglService";
|
||||
|
||||
public static boolean dumpShaders() {
|
||||
return DUMP_SHADERS_PROP || isDeobf();
|
||||
|
||||
@@ -564,7 +564,7 @@ public final class GLDebug {
|
||||
/**
|
||||
* Overload that lets you gate the String concat behind the debugState
|
||||
*/
|
||||
public static void pushGroup(String prefix, int value) {
|
||||
public static void pushGroup(String prefix, long value) {
|
||||
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
|
||||
debugState.pushGroup(prefix + value);
|
||||
}
|
||||
|
||||
@@ -493,7 +493,9 @@ public class GLStateManager {
|
||||
|
||||
public static void initialize(GLSMInitConfig config) {
|
||||
initConfig = config;
|
||||
preInit(config.getDisplayWidth(), config.getDisplayHeight());
|
||||
final int width = config.getDisplayWidth() > 0 ? config.getDisplayWidth() : Display.getWidth();
|
||||
final int height = config.getDisplayHeight() > 0 ? config.getDisplayHeight() : Display.getHeight();
|
||||
preInit(width, height);
|
||||
init(config.getPostInitCallback());
|
||||
}
|
||||
|
||||
@@ -2003,6 +2005,7 @@ public class GLStateManager {
|
||||
}
|
||||
|
||||
public static void glBindTexture(int target, int texture) {
|
||||
if (TextureInfoCache.isProxyTarget(target)) return;
|
||||
final GLContextState glCtx = ctx();
|
||||
final RecordMode mode = DisplayListManager.getRecordMode();
|
||||
if (mode != RecordMode.NONE) {
|
||||
@@ -4576,6 +4579,11 @@ public class GLStateManager {
|
||||
};
|
||||
}
|
||||
|
||||
private static int mipLevelSize(int base, int level) {
|
||||
if (base <= 0 || level < 0 || level >= Integer.SIZE - 1) return 1;
|
||||
return Math.max(base >> level, 1);
|
||||
}
|
||||
|
||||
public static int glGetTexLevelParameteri(int target, int level, int pname) {
|
||||
if (target != GL11.GL_TEXTURE_2D || !isCachingEnabled()) {
|
||||
return RENDER_BACKEND.getTexLevelParameteri(target, level, pname);
|
||||
@@ -4590,8 +4598,8 @@ public class GLStateManager {
|
||||
return RENDER_BACKEND.getTexLevelParameteri(target, level, pname);
|
||||
}
|
||||
return switch (pname) {
|
||||
case GL11.GL_TEXTURE_WIDTH -> Math.max(info.getWidth() >> level, 1);
|
||||
case GL11.GL_TEXTURE_HEIGHT -> Math.max(info.getHeight() >> level, 1);
|
||||
case GL11.GL_TEXTURE_WIDTH -> mipLevelSize(info.getWidth(), level);
|
||||
case GL11.GL_TEXTURE_HEIGHT -> mipLevelSize(info.getHeight(), level);
|
||||
case GL11.GL_TEXTURE_INTERNAL_FORMAT -> {
|
||||
if (info.needsInternalFormatResolve() && isRecordingDisplayList()) {
|
||||
throw new IllegalStateException(String.format("glGetTexLevelParameteri(GL_TEXTURE_INTERNAL_FORMAT) needs to resolve a generic compressed format for texture %d during display list recording", getBoundTextureForServerState()));
|
||||
|
||||
@@ -9,7 +9,7 @@ import com.gtnewhorizons.angelica.glsm.dsa.DSAEXT;
|
||||
import com.gtnewhorizons.angelica.glsm.dsa.DSAUnsupported;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
|
||||
import com.mitchej123.lwjgl.LWJGLServiceProvider;
|
||||
import org.taumc.celeritas.lwjgl.LWJGLServiceProvider;
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
import static com.mitchej123.lwjgl.LWJGLServiceProvider.LWJGL;
|
||||
import static org.taumc.celeritas.lwjgl.LWJGLServiceProvider.LWJGL;
|
||||
|
||||
/**
|
||||
* LWJGL2 GL implementation of {@link RenderBackend}.
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.CaptureGate;
|
||||
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
|
||||
import com.gtnewhorizons.angelica.glsm.GLDebug;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
|
||||
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
|
||||
|
||||
public final class FrameHooks {
|
||||
|
||||
private FrameHooks() {}
|
||||
|
||||
private static boolean gameLoopStarted;
|
||||
private static long frameCounter;
|
||||
private static boolean frameGroupActive;
|
||||
|
||||
public static void bootstrapFirstFrame() {
|
||||
DisplayListManager.abortIfLeaked();
|
||||
if (!gameLoopStarted) {
|
||||
gameLoopStarted = true;
|
||||
RENDER_BACKEND.onFrameBegin();
|
||||
pushFrameGroup();
|
||||
}
|
||||
}
|
||||
|
||||
public static void frameEnd() {
|
||||
popFrameGroup();
|
||||
RENDER_BACKEND.onFrameEnd();
|
||||
}
|
||||
|
||||
public static void frameBegin() {
|
||||
if (gameLoopStarted) {
|
||||
RENDER_BACKEND.onFrameBegin();
|
||||
pushFrameGroup();
|
||||
}
|
||||
}
|
||||
|
||||
public static void shutdown() {
|
||||
BackendManager.shutdown();
|
||||
}
|
||||
|
||||
public static void bindSplashVao() {
|
||||
GLStateManager.glBindVertexArray(GLStateManager.glGenVertexArrays());
|
||||
}
|
||||
|
||||
public static void splashFinished() {
|
||||
GLStateManager.markSplashComplete("SplashProgress.finish");
|
||||
}
|
||||
|
||||
private static void pushFrameGroup() {
|
||||
CaptureGate.refresh();
|
||||
GLDebug.pushGroup("frame:", frameCounter);
|
||||
frameGroupActive = true;
|
||||
}
|
||||
|
||||
private static void popFrameGroup() {
|
||||
if (frameGroupActive) {
|
||||
GLDebug.popGroup();
|
||||
frameGroupActive = false;
|
||||
frameCounter++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
@@ -14,7 +16,7 @@ public final class GLSMInitConfig {
|
||||
@Getter private final int displayWidth;
|
||||
@Getter private final int displayHeight;
|
||||
@Getter private final Runnable postInitCallback;
|
||||
@Getter private final boolean DSAEnabled;
|
||||
private final boolean dsaEnabled;
|
||||
@Getter private final boolean noErrorChecks;
|
||||
|
||||
private GLSMInitConfig(Builder builder) {
|
||||
@@ -26,7 +28,11 @@ public final class GLSMInitConfig {
|
||||
this.displayWidth = builder.displayWidth;
|
||||
this.displayHeight = builder.displayHeight;
|
||||
this.postInitCallback = builder.postInitCallback;
|
||||
this.DSAEnabled = builder.enableDSA;
|
||||
this.dsaEnabled = builder.dsaEnabled;
|
||||
}
|
||||
|
||||
public boolean isDSAEnabled() {
|
||||
return dsaEnabled;
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
@@ -34,14 +40,14 @@ public final class GLSMInitConfig {
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private boolean lwjglDebug = false;
|
||||
private boolean lwjglDebug = SystemProperties.LWJGL_DEBUG;
|
||||
private StreamingUploader.UploadStrategy streamingUploadStrategy = StreamingUploader.UploadStrategy.BUFFER_DATA;
|
||||
private Consumer<DirectTessellator> directDrawer = null;
|
||||
private Runnable streamingDrawerDestroy = null;
|
||||
private Consumer<DirectTessellator> directDrawer = TessellatorStreamingDrawer::drawDirect;
|
||||
private Runnable streamingDrawerDestroy = TessellatorStreamingDrawer::destroy;
|
||||
private int displayWidth = 0;
|
||||
private int displayHeight = 0;
|
||||
private Runnable postInitCallback = null;
|
||||
private boolean enableDSA = false;
|
||||
private boolean dsaEnabled = true;
|
||||
private boolean noErrorChecks;
|
||||
|
||||
private Builder() {}
|
||||
@@ -83,7 +89,7 @@ public final class GLSMInitConfig {
|
||||
}
|
||||
|
||||
public Builder enableDSA(boolean enableDSA) {
|
||||
this.enableDSA = enableDSA;
|
||||
this.dsaEnabled = enableDSA;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import org.lwjgl.opengl.EXTTextureFilterAnisotropic;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
|
||||
public final class TextureUtilHooks {
|
||||
|
||||
private TextureUtilHooks() {}
|
||||
|
||||
private static int savedFilterTextureId;
|
||||
|
||||
public static void setMaxLevel(int mipmapLevels) {
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, mipmapLevels);
|
||||
}
|
||||
|
||||
public static void captureBoundFilterTexture() {
|
||||
savedFilterTextureId = GLStateManager.getBoundTextureForServerState();
|
||||
}
|
||||
|
||||
public static void clearSavedFilterTexture() {
|
||||
savedFilterTextureId = 0;
|
||||
}
|
||||
|
||||
public static void restoreFilter(int min, int mag) {
|
||||
final int id = savedFilterTextureId;
|
||||
if (id != 0 && DisplayListManager.getRecordMode() == DisplayListManager.RecordMode.NONE) {
|
||||
RenderSystem.texParameteri(id, GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, min);
|
||||
RenderSystem.texParameteri(id, GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, mag);
|
||||
} else {
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, min);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, mag);
|
||||
}
|
||||
}
|
||||
|
||||
public static void restoreAniso(float aniso) {
|
||||
final int id = savedFilterTextureId;
|
||||
if (id != 0 && DisplayListManager.getRecordMode() == DisplayListManager.RecordMode.NONE) {
|
||||
RenderSystem.texParameterf(id, GL11.GL_TEXTURE_2D, EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, aniso);
|
||||
} else {
|
||||
GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, aniso);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.gtnewhorizons.angelica.glsm.loading;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.asm.ASMUtil;
|
||||
import com.gtnewhorizons.retrofuturabootstrap.api.ClassNodeHandle;
|
||||
|
||||
public final class ClassDump {
|
||||
|
||||
public static final ClassDump DISABLED = new ClassDump(false);
|
||||
|
||||
private final boolean enabled;
|
||||
|
||||
public ClassDump(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public void dumpClass(String className, byte[] originalBytes, byte[] transformedBytes, Object transformer) {
|
||||
if (!enabled) return;
|
||||
ASMUtil.saveAsRawClassFile(originalBytes, className + "_PRE", transformer);
|
||||
ASMUtil.saveAsRawClassFile(transformedBytes, className + "_POST", transformer);
|
||||
}
|
||||
|
||||
public void dumpRFBClass(String className, ClassNodeHandle classNode, Object transformer) {
|
||||
if (!enabled) return;
|
||||
dumpClass(className, classNode.getOriginalBytes(), classNode.computeBytes(), transformer);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,10 @@
|
||||
package com.gtnewhorizons.angelica.glsm.loading;
|
||||
|
||||
import net.minecraft.launchwrapper.Launch;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import javax.swing.JOptionPane;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -15,6 +20,10 @@ public final class DependencyVerifier {
|
||||
*/
|
||||
public record Check(String resourcePath, String errorMessage) {}
|
||||
|
||||
public interface FatalDialog {
|
||||
void show(String title, String message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify all checks pass. Throws RuntimeException on first failure.
|
||||
* @param anchor Class whose classloader to use for resource lookups
|
||||
@@ -28,5 +37,39 @@ public final class DependencyVerifier {
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Check> gtnhLibChecks(String productName) {
|
||||
return List.of(
|
||||
new Check("/it/unimi/dsi/fastutil/ints/Int2ObjectMap.class", "Missing dependency: " + productName + " requires GTNHLib! Download: https://modrinth.com/mod/gtnhlib"),
|
||||
new Check("/com/gtnewhorizon/gtnhlib/client/renderer/VertexCallbackManager.class", "GTNHLib is outdated: " + productName + " requires GTNHLib 0.10.0 or newer! Download: https://modrinth.com/mod/gtnhlib"),
|
||||
new Check("/it/unimi/dsi/fastutil/objects/ObjectBooleanBiConsumer.class", "GTNHLib is outdated: " + productName + " requires GTNHLib 0.11.19 or newer (fastutil 8.5.18+)! Download: https://modrinth.com/mod/gtnhlib"));
|
||||
}
|
||||
|
||||
public static void verifyOrHalt(Class<?> anchor, String productName, List<Check> checks, Logger logger, FatalDialog sdlDialog) {
|
||||
final String message;
|
||||
try {
|
||||
verify(anchor, checks);
|
||||
return;
|
||||
} catch (RuntimeException ex) {
|
||||
message = ex.getMessage();
|
||||
}
|
||||
|
||||
final String title = productName + " - Missing Dependency";
|
||||
System.err.println("FATAL: " + message);
|
||||
try {
|
||||
logger.fatal(message);
|
||||
} catch (Throwable ignored) {}
|
||||
|
||||
final int lwjgl3ifyMajor = ((Integer) Launch.blackboard.getOrDefault("lwjgl3ify:major-version", Integer.MIN_VALUE));
|
||||
try {
|
||||
if (lwjgl3ifyMajor >= 3) {
|
||||
sdlDialog.show(title, message);
|
||||
} else if (!GraphicsEnvironment.isHeadless()) {
|
||||
JOptionPane.showMessageDialog(null, message, title, JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
|
||||
Runtime.getRuntime().halt(1);
|
||||
}
|
||||
|
||||
private DependencyVerifier() {}
|
||||
}
|
||||
+2
-48
@@ -1,54 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.glsm.loading;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.redirect.GLSMRedirector;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public final class EarlyRedirectorCore {
|
||||
|
||||
private final GLSMRedirector core;
|
||||
private final String[] targets;
|
||||
private final String[] exclusions;
|
||||
public final class EarlyRedirectorCore extends ModRedirector {
|
||||
|
||||
public EarlyRedirectorCore(String... extraExclusions) {
|
||||
this.core = new GLSMRedirector();
|
||||
this.targets = EcosystemNarrowRules.EARLY_REDIRECTOR_TARGETS.clone();
|
||||
|
||||
final List<String> excl = new ArrayList<>(Arrays.asList(core.getCoreExclusions()));
|
||||
if (extraExclusions != null && extraExclusions.length > 0) {
|
||||
Collections.addAll(excl, extraExclusions);
|
||||
}
|
||||
this.exclusions = excl.toArray(new String[0]);
|
||||
}
|
||||
|
||||
public byte[] transform(String transformedName, byte[] basicClass) {
|
||||
if (basicClass == null) return null;
|
||||
|
||||
if (!startsWithAny(transformedName, targets)) return basicClass;
|
||||
if (startsWithAny(transformedName, exclusions)) return basicClass;
|
||||
if (!core.shouldTransform(basicClass)) return basicClass;
|
||||
|
||||
final ClassReader cr = new ClassReader(basicClass);
|
||||
final ClassNode cn = new ClassNode();
|
||||
cr.accept(cn, 0);
|
||||
if (core.transformClassNode(transformedName, cn)) {
|
||||
final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||
cn.accept(cw);
|
||||
return cw.toByteArray();
|
||||
}
|
||||
return basicClass;
|
||||
}
|
||||
|
||||
private static boolean startsWithAny(String name, String[] prefixes) {
|
||||
for (String p : prefixes) {
|
||||
if (name.startsWith(p)) return true;
|
||||
}
|
||||
return false;
|
||||
super(ClassDump.DISABLED, EcosystemNarrowRules.EARLY_REDIRECTOR_TARGETS.clone(), extraExclusions);
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -91,9 +91,8 @@ public final class EcosystemNarrowRules {
|
||||
public static final String[] LWJGL3IFY_EXCLUSIONS_SHARED = {
|
||||
"org.embeddedt.embeddium",
|
||||
"org.taumc.celeritas",
|
||||
"com.mitchej123.lwjgl.lwjgl3",
|
||||
"com.mitchej123.glsm",
|
||||
"com.gtnewhorizons.angelica.lwjgl3",
|
||||
"com.gtnewhorizons.angelica.sdlgpu",
|
||||
};
|
||||
|
||||
public static final String[] EARLY_REDIRECTOR_TARGETS = {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.gtnewhorizons.angelica.glsm.loading;
|
||||
|
||||
import com.gtnewhorizons.retrofuturabootstrap.SharedConfig;
|
||||
|
||||
public final class Lwjgl3ifyExclusions {
|
||||
|
||||
public static void apply() {
|
||||
final var handle = SharedConfig.getRfbTransformers().stream()
|
||||
.filter(transformer -> transformer.id().equals("lwjgl3ify:redirect"))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (handle == null) return;
|
||||
for (String exclusion : EcosystemNarrowRules.LWJGL3IFY_EXCLUSIONS_SHARED) {
|
||||
handle.exclusions().add(exclusion);
|
||||
}
|
||||
}
|
||||
|
||||
private Lwjgl3ifyExclusions() {}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.gtnewhorizons.angelica.glsm.loading;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.redirect.GLSMRedirector;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class ModRedirector {
|
||||
|
||||
private final GLSMRedirector core = new GLSMRedirector();
|
||||
private final ClassDump dump;
|
||||
private final String[] targets;
|
||||
private final String[] allExclusions;
|
||||
|
||||
public ModRedirector(ClassDump dump, String... extraExclusions) {
|
||||
this(dump, (String[]) null, extraExclusions);
|
||||
}
|
||||
|
||||
public ModRedirector(ClassDump dump, String[] targets, String[] extraExclusions) {
|
||||
this.dump = dump;
|
||||
this.targets = targets;
|
||||
final List<String> exclusions = new ArrayList<>(Arrays.asList(core.getCoreExclusions()));
|
||||
exclusions.add("com.gtnewhorizons.angelica.lwjgl3.");
|
||||
Collections.addAll(exclusions, extraExclusions);
|
||||
this.allExclusions = exclusions.toArray(new String[0]);
|
||||
}
|
||||
|
||||
public ClassDump getDump() {
|
||||
return dump;
|
||||
}
|
||||
|
||||
public String[] getTransformerExclusions() {
|
||||
return allExclusions;
|
||||
}
|
||||
|
||||
public boolean shouldTransform(byte[] basicClass) {
|
||||
return core.shouldTransform(basicClass);
|
||||
}
|
||||
|
||||
public boolean transformClassNode(String transformedName, ClassNode cn, boolean lwjgl3Aware) {
|
||||
return core.transformClassNode(transformedName, cn, lwjgl3Aware);
|
||||
}
|
||||
|
||||
public byte[] transform(String transformedName, byte[] basicClass, Object transformer) {
|
||||
if (basicClass == null) return null;
|
||||
|
||||
if (targets != null && !startsWithAny(transformedName, targets)) return basicClass;
|
||||
if (startsWithAny(transformedName, allExclusions)) return basicClass;
|
||||
|
||||
if (!core.shouldTransform(basicClass)) {
|
||||
return basicClass;
|
||||
}
|
||||
|
||||
final ClassReader cr = new ClassReader(basicClass);
|
||||
final ClassNode cn = new ClassNode();
|
||||
cr.accept(cn, 0);
|
||||
if (!core.transformClassNode(transformedName, cn)) {
|
||||
return basicClass;
|
||||
}
|
||||
final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||
cn.accept(cw);
|
||||
final byte[] bytes = cw.toByteArray();
|
||||
dump.dumpClass(transformedName, basicClass, bytes, transformer);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private static boolean startsWithAny(String name, String[] prefixes) {
|
||||
for (String prefix : prefixes) {
|
||||
if (name.startsWith(prefix)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.gtnewhorizons.angelica.glsm.loading;
|
||||
|
||||
import com.gtnewhorizons.retrofuturabootstrap.api.ClassNodeHandle;
|
||||
import com.gtnewhorizons.retrofuturabootstrap.api.ExtensibleClassLoader;
|
||||
import com.gtnewhorizons.retrofuturabootstrap.api.RfbClassTransformer;
|
||||
import org.intellij.lang.annotations.Pattern;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
public abstract class RfbModRedirector implements RfbClassTransformer {
|
||||
|
||||
private static final Attributes.Name LWJGL3_AWARE = new Attributes.Name("Lwjgl3ify-Aware");
|
||||
|
||||
private final String id;
|
||||
private final ModRedirector inner;
|
||||
|
||||
protected RfbModRedirector(@Pattern("[a-z0-9-]+") String id, ModRedirector inner) {
|
||||
this.id = id;
|
||||
this.inner = inner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String id() {
|
||||
return id;
|
||||
}
|
||||
|
||||
private static boolean isLwjgl3Aware(@Nullable Manifest manifest) {
|
||||
return manifest != null && "true".equals(manifest.getMainAttributes().getValue(LWJGL3_AWARE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String @Nullable [] sortAfter() {
|
||||
return new String[] {"*", "mixin:mixin"};
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String @Nullable [] sortBefore() {
|
||||
return new String[] {"lwjgl3ify:redirect"};
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String @Nullable [] additionalExclusions() {
|
||||
return inner.getTransformerExclusions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldTransformClass(@NotNull ExtensibleClassLoader classLoader,
|
||||
@NotNull RfbClassTransformer.Context context, @Nullable Manifest manifest, @NotNull String className,
|
||||
@NotNull ClassNodeHandle classNode) {
|
||||
if (!classNode.isPresent()) {
|
||||
return false;
|
||||
}
|
||||
return inner.shouldTransform(classNode.getOriginalBytes());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean transformClassIfNeeded(@NotNull ExtensibleClassLoader classLoader,
|
||||
@NotNull RfbClassTransformer.Context context, @Nullable Manifest manifest, @NotNull String className,
|
||||
@NotNull ClassNodeHandle classNode) {
|
||||
final boolean changed = inner.transformClassNode(className, classNode.getNode(), isLwjgl3Aware(manifest));
|
||||
if (changed) {
|
||||
classNode.computeMaxs();
|
||||
inner.getDump().dumpRFBClass(className, classNode, this);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transformClass(@NotNull ExtensibleClassLoader classLoader, @NotNull RfbClassTransformer.Context context,
|
||||
@Nullable Manifest manifest, @NotNull String className, @NotNull ClassNodeHandle classNode) {
|
||||
transformClassIfNeeded(classLoader, context, manifest, className, classNode);
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,9 @@ import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL21;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
import org.lwjgl.opengl.GL40;
|
||||
|
||||
import java.nio.Buffer;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
@@ -101,6 +104,22 @@ public class TextureInfoCache {
|
||||
};
|
||||
}
|
||||
|
||||
public static boolean isProxyTarget(int target) {
|
||||
return switch (target) {
|
||||
case GL11.GL_PROXY_TEXTURE_1D,
|
||||
GL11.GL_PROXY_TEXTURE_2D,
|
||||
GL12.GL_PROXY_TEXTURE_3D,
|
||||
GL13.GL_PROXY_TEXTURE_CUBE_MAP,
|
||||
GL30.GL_PROXY_TEXTURE_1D_ARRAY,
|
||||
GL30.GL_PROXY_TEXTURE_2D_ARRAY,
|
||||
GL31.GL_PROXY_TEXTURE_RECTANGLE,
|
||||
GL32.GL_PROXY_TEXTURE_2D_MULTISAMPLE,
|
||||
GL32.GL_PROXY_TEXTURE_2D_MULTISAMPLE_ARRAY,
|
||||
GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY -> true;
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
private static boolean isTrackedTarget(int target) {
|
||||
return target == GL11.GL_TEXTURE_2D
|
||||
|| target == GL11.GL_TEXTURE_1D
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.gtnewhorizons.angelica.glsm.texture;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
import org.lwjgl.opengl.GL40;
|
||||
|
||||
public final class TextureTargets {
|
||||
|
||||
private TextureTargets() {}
|
||||
|
||||
public static boolean isProxyTarget(int target) {
|
||||
return switch (target) {
|
||||
case GL11.GL_PROXY_TEXTURE_1D,
|
||||
GL11.GL_PROXY_TEXTURE_2D,
|
||||
GL12.GL_PROXY_TEXTURE_3D,
|
||||
GL13.GL_PROXY_TEXTURE_CUBE_MAP,
|
||||
GL30.GL_PROXY_TEXTURE_1D_ARRAY,
|
||||
GL30.GL_PROXY_TEXTURE_2D_ARRAY,
|
||||
GL31.GL_PROXY_TEXTURE_RECTANGLE,
|
||||
GL32.GL_PROXY_TEXTURE_2D_MULTISAMPLE,
|
||||
GL32.GL_PROXY_TEXTURE_2D_MULTISAMPLE_ARRAY,
|
||||
GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY -> true;
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
}
|
||||
+22
@@ -42,6 +42,28 @@ public class GLSM_TextureBindingTargetCache_UnitTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void proxyTargetBindIsNeitherForwardedNorCached() {
|
||||
drainErrors();
|
||||
final int t2d = GL11.glGenTextures();
|
||||
try {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE5);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, t2d);
|
||||
|
||||
GLStateManager.glBindTexture(GL11.GL_PROXY_TEXTURE_2D, 0);
|
||||
GLStateManager.glBindTexture(GL12.GL_PROXY_TEXTURE_3D, t2d);
|
||||
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "a proxy target must never reach the driver");
|
||||
assertEquals(t2d, GLStateManager.getBoundTextureForServerState(5), "a proxy bind must not disturb the cache");
|
||||
assertEquals(t2d, GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D));
|
||||
} finally {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glDeleteTextures(t2d);
|
||||
drainErrors();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void popAttribRestoresRecordedTarget() {
|
||||
drainErrors();
|
||||
|
||||
+15
@@ -22,6 +22,21 @@ public class GLSM_TextureUploadGeneration_GLTest {
|
||||
return texId;
|
||||
}
|
||||
|
||||
@Test
|
||||
void aLevelBeyondTheShiftWidthClampsToOne() {
|
||||
final int texId = allocate(64);
|
||||
try {
|
||||
assertEquals(64, GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH));
|
||||
assertEquals(1, GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 6, GL11.GL_TEXTURE_WIDTH));
|
||||
assertEquals(1, GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 31, GL11.GL_TEXTURE_WIDTH));
|
||||
assertEquals(1, GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 32, GL11.GL_TEXTURE_WIDTH));
|
||||
assertEquals(1, GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 64, GL11.GL_TEXTURE_HEIGHT));
|
||||
} finally {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
GLStateManager.glDeleteTextures(texId);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void anUploadLeavesANonZeroGeneration() {
|
||||
final int texId = allocate(4);
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class GLSMInitConfigDefaultsTest {
|
||||
|
||||
@Test
|
||||
void emptyBuilderLeavesDsaToTheProbe() {
|
||||
assertTrue(GLSMInitConfig.builder().build().isDSAEnabled());
|
||||
assertFalse(GLSMInitConfig.builder().enableDSA(false).build().isDSAEnabled());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package com.gtnewhorizons.angelica.glsm.loading;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.redirect.GLSMRedirector;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import org.objectweb.asm.tree.InsnNode;
|
||||
import org.objectweb.asm.tree.MethodInsnNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class ModRedirectorTest {
|
||||
|
||||
private static final String EXTRA_EXCLUSION = "com.example.excluded";
|
||||
|
||||
private static ClassNode emptyClass() {
|
||||
final ClassNode cn = new ClassNode();
|
||||
cn.version = Opcodes.V1_8;
|
||||
cn.access = Opcodes.ACC_PUBLIC;
|
||||
cn.name = "com/example/TestClass";
|
||||
cn.superName = "java/lang/Object";
|
||||
cn.methods = new ArrayList<>();
|
||||
|
||||
final MethodNode mn = new MethodNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "test", "()V", null, null);
|
||||
mn.instructions.add(new InsnNode(Opcodes.RETURN));
|
||||
cn.methods.add(mn);
|
||||
return cn;
|
||||
}
|
||||
|
||||
private static byte[] bytesWithoutGlCall() {
|
||||
return toBytes(emptyClass());
|
||||
}
|
||||
|
||||
private static byte[] bytesWithGlCall() {
|
||||
final ClassNode cn = emptyClass();
|
||||
final MethodNode mn = cn.methods.get(0);
|
||||
mn.instructions.clear();
|
||||
mn.instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "org/lwjgl/opengl/GL11", "glPopMatrix", "()V", false));
|
||||
mn.instructions.add(new InsnNode(Opcodes.RETURN));
|
||||
return toBytes(cn);
|
||||
}
|
||||
|
||||
private static byte[] toBytes(ClassNode cn) {
|
||||
final ClassWriter cw = new ClassWriter(0);
|
||||
cn.accept(cw);
|
||||
return cw.toByteArray();
|
||||
}
|
||||
|
||||
private static ModRedirector redirector() {
|
||||
return new ModRedirector(ClassDump.DISABLED, EXTRA_EXCLUSION);
|
||||
}
|
||||
|
||||
private static void assertStandardExclusions(String[] actual) {
|
||||
final List<String> exclusions = Arrays.asList(actual);
|
||||
final String[] coreExclusions = new GLSMRedirector().getCoreExclusions();
|
||||
for (String coreExclusion : coreExclusions) {
|
||||
assertTrue(exclusions.contains(coreExclusion), coreExclusion);
|
||||
}
|
||||
assertTrue(exclusions.contains("com.gtnewhorizons.angelica.lwjgl3."));
|
||||
assertTrue(exclusions.contains(EXTRA_EXCLUSION));
|
||||
assertEquals(coreExclusions.length + 2, exclusions.size(), exclusions.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void exclusionsCombineCoreLwjgl3AndExtra() {
|
||||
assertStandardExclusions(redirector().getTransformerExclusions());
|
||||
}
|
||||
|
||||
@Test
|
||||
void earlyRedirectorExclusionsCombineCoreLwjgl3AndExtra() {
|
||||
assertStandardExclusions(new EarlyRedirectorCore(EXTRA_EXCLUSION).getTransformerExclusions());
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullInputReturnsNull() {
|
||||
assertNull(redirector().transform("com.example.TestClass", null, this));
|
||||
}
|
||||
|
||||
@Test
|
||||
void excludedPrefixReturnsOriginalArray() {
|
||||
final byte[] original = bytesWithGlCall();
|
||||
assertSame(original, redirector().transform(EXTRA_EXCLUSION + ".TestClass", original, this));
|
||||
}
|
||||
|
||||
@Test
|
||||
void coreExcludedPrefixReturnsOriginalArray() {
|
||||
final byte[] original = bytesWithGlCall();
|
||||
assertSame(original, redirector().transform("org.lwjgl.opengl.TestClass", original, this));
|
||||
}
|
||||
|
||||
@Test
|
||||
void unchangedClassReturnsOriginalArray() {
|
||||
final byte[] original = bytesWithoutGlCall();
|
||||
assertSame(original, redirector().transform("com.example.TestClass", original, this));
|
||||
}
|
||||
|
||||
@Test
|
||||
void redirectedClassReturnsNewArray() {
|
||||
final byte[] original = bytesWithGlCall();
|
||||
assertNotSame(original, redirector().transform("com.example.TestClass", original, this));
|
||||
}
|
||||
|
||||
@Test
|
||||
void earlyRedirectorSkipsClassesOutsideItsTargets() {
|
||||
final byte[] original = bytesWithGlCall();
|
||||
final EarlyRedirectorCore early = new EarlyRedirectorCore(EXTRA_EXCLUSION);
|
||||
assertSame(original, early.transform("com.example.TestClass", original, this));
|
||||
}
|
||||
|
||||
@Test
|
||||
void earlyRedirectorTransformsItsTargets() {
|
||||
final byte[] original = bytesWithGlCall();
|
||||
final EarlyRedirectorCore early = new EarlyRedirectorCore(EXTRA_EXCLUSION);
|
||||
final String target = EcosystemNarrowRules.EARLY_REDIRECTOR_TARGETS[0] + "TestClass";
|
||||
assertNotSame(original, early.transform(target, original, this));
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,12 @@ public class GLSMCoreExtension implements BeforeAllCallback, BeforeEachCallback,
|
||||
new ContextAttribs(3, 3).withProfileCore(true).withForwardCompatible(true));
|
||||
|
||||
GLSMExtension.setMainThread(Thread.currentThread());
|
||||
GLStateManager.initialize(GLSMInitConfig.builder().displaySize(800, 600).build());
|
||||
GLStateManager.initialize(GLSMInitConfig.builder()
|
||||
.displaySize(800, 600)
|
||||
.directDrawer(t -> {})
|
||||
.streamingDrawerDestroy(() -> {})
|
||||
.enableDSA(false)
|
||||
.build());
|
||||
GLStateManager.setRunningSplash(false);
|
||||
GLStateManager.markSplashComplete("test");
|
||||
|
||||
|
||||
@@ -69,6 +69,9 @@ public class GLSMExtension implements BeforeAllCallback, BeforeEachCallback, Aft
|
||||
displayMode = new DisplayMode(800, 600);
|
||||
final GLSMInitConfig config = GLSMInitConfig.builder()
|
||||
.displaySize(displayMode.getWidth(), displayMode.getHeight())
|
||||
.directDrawer(t -> {})
|
||||
.streamingDrawerDestroy(() -> {})
|
||||
.enableDSA(false)
|
||||
.build();
|
||||
setMainThread(Thread.currentThread());
|
||||
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import com.gtnewhorizons.retrofuturagradle.MinecraftExtension
|
||||
import org.gradle.accessors.dm.LibrariesForLibs
|
||||
import xyz.wagyourtail.jvmdg.gradle.task.DowngradeJar
|
||||
import xyz.wagyourtail.jvmdg.gradle.task.files.DowngradeFiles
|
||||
|
||||
val libs = the<LibrariesForLibs>()
|
||||
|
||||
tasks.withType<DowngradeJar>().configureEach { logLevel.set("FATAL") }
|
||||
tasks.withType<DowngradeFiles>().configureEach { logLevel.set("FATAL") }
|
||||
|
||||
configurations.all {
|
||||
resolutionStrategy.dependencySubstitution {
|
||||
substitute(module("com.github.GTNewHorizons:lwjgl3ify"))
|
||||
.using(module("com.github.GTNewHorizons:lwjgl3ify:${libs.versions.lwjgl3ify.get()}"))
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
name = "Maven Central Snapshots"
|
||||
url = uri("https://central.sonatype.com/repository/maven-snapshots/")
|
||||
content { includeGroup("org.lwjgl") }
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<JavaExec>().matching { it.name.startsWith("runClient") }.configureEach {
|
||||
for ((key, value) in System.getProperties()) {
|
||||
val name = key.toString()
|
||||
if (name.startsWith("angelica.") || name.startsWith("quickPlay")) {
|
||||
jvmArgs("-D$name=$value")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val isMacOs = org.gradle.internal.os.OperatingSystem.current().isMacOsX
|
||||
val lwjglDebug = project.extra.has("lwjglDebug") && project.extra["lwjglDebug"] as Boolean
|
||||
|
||||
tasks.withType<JavaExec>().configureEach {
|
||||
if (name.startsWith("runClient") && name != "runClient" && isMacOs) {
|
||||
jvmArgs("-XstartOnFirstThread")
|
||||
jvmArgs("-Dangelica.sdlgpu.encoderAssertions=" + if (lwjglDebug) "fatal" else "warn")
|
||||
if (lwjglDebug) {
|
||||
environment("METAL_DEVICE_WRAPPER_TYPE", "1")
|
||||
environment("METAL_DEBUG_ERROR_MODE", "0")
|
||||
environment("MTL_SHADER_VALIDATION", "1")
|
||||
environment("MTL_SHADER_VALIDATION_REPORT_TO_STDERR", "1")
|
||||
environment("MTL_DEBUG_LAYER", "1")
|
||||
environment("MallocScribble", "1")
|
||||
environment("MallocPreScribble", "1")
|
||||
environment("MallocGuardEdges", "1")
|
||||
environment("MallocStackLogging", "1")
|
||||
environment("MallocStackLoggingNoCompact", "1")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configure<MinecraftExtension> {
|
||||
lwjgl3Bindings.addAll("shaderc", "spvc")
|
||||
lwjgl3Version = libs.versions.lwjgl3.get()
|
||||
}
|
||||
|
||||
tasks.named<Copy>("processResources") {
|
||||
val projectVersion = project.version.toString()
|
||||
inputs.property("version", projectVersion)
|
||||
filesMatching("META-INF/rfb-plugin/*") {
|
||||
expand("version" to projectVersion)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
|
||||
val embedOnly: Configuration by configurations
|
||||
|
||||
tasks.named<ShadowJar>("shadowJar") {
|
||||
dependsOn(embedOnly)
|
||||
from(embedOnly.map(::zipTree))
|
||||
|
||||
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||
mergeServiceFiles()
|
||||
|
||||
minimize {
|
||||
exclude(project(rootProject.path))
|
||||
exclude(project(":glsm"))
|
||||
exclude(project(":lwjgl3-backend"))
|
||||
exclude(project(":sdl-gpu"))
|
||||
exclude(dependency("org.taumc:.*:.*"))
|
||||
exclude(dependency("org.embeddedt.celeritas:.*:.*"))
|
||||
exclude(dependency("org.antlr:.*:.*"))
|
||||
}
|
||||
|
||||
relocate("org.taumc.celeritas", "org.taumc.celeritas")
|
||||
relocate("org.embeddedt", "org.embeddedt")
|
||||
relocate("com.gtnewhorizons.angelica.config", "com.gtnewhorizons.angelica.config")
|
||||
relocate("com.gtnewhorizons.angelica.glsm", "com.gtnewhorizons.angelica.glsm")
|
||||
relocate("com.gtnewhorizons.angelica.lwjgl3", "com.gtnewhorizons.angelica.lwjgl3")
|
||||
relocate("com.gtnewhorizons.angelica.sdlgpu", "com.gtnewhorizons.angelica.sdlgpu")
|
||||
}
|
||||
@@ -6,10 +6,10 @@ log4j = "2.0-beta9"
|
||||
asmCompile = "5.0.3"
|
||||
# End Frozen
|
||||
|
||||
lwjgl3 = "3.4.2"
|
||||
lwjgl3 = "3.4.2-SNAPSHOT"
|
||||
lwjgl3ify = "3.0.31"
|
||||
|
||||
celeritas = "2.5.10-GTNH"
|
||||
celeritas = "2.5.11-GTNH"
|
||||
gtnhlib = "0.11.36"
|
||||
eventbus = "7.0-beta.15"
|
||||
glslTransformLib = "0.2.0-33.g3ec381e"
|
||||
@@ -30,10 +30,10 @@ asmBuild = "9.7"
|
||||
mockito = "3.12.4"
|
||||
archunit = "1.4.1"
|
||||
ant = "1.8.2"
|
||||
distantHorizonsApi = "5.1.0"
|
||||
|
||||
[libraries]
|
||||
celeritas-common = { module = "org.embeddedt.celeritas:celeritas-common", version.ref = "celeritas" }
|
||||
celeritas-lwjgl2-service = { module = "org.embeddedt.celeritas:celeritas-lwjgl2-service", version.ref = "celeritas" }
|
||||
eventbus = { module = "net.minecraftforge:eventbus", version.ref = "eventbus" }
|
||||
glsl-transformation-lib = { module = "org.taumc:glsl-transformation-lib", version.ref = "glslTransformLib" }
|
||||
antlr4-runtime = { module = "org.antlr:antlr4-runtime", version.ref = "antlr4Runtime" }
|
||||
@@ -58,6 +58,7 @@ junit-platform-reporting = { module = "org.junit.platform:junit-platform-reporti
|
||||
mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito" }
|
||||
archunit-junit5 = { module = "com.tngtech.archunit:archunit-junit5", version.ref = "archunit" }
|
||||
ant = { module = "org.apache.ant:ant", version.ref = "ant" }
|
||||
distant-horizons-api = { module = "maven.modrinth:DistantHorizonsApi", version.ref = "distantHorizonsApi" }
|
||||
asm-tree-compile = { module = "org.ow2.asm:asm-tree", version.ref = "asmCompile" }
|
||||
asm-tree-test = { module = "org.ow2.asm:asm-tree", version.ref = "asmTest" }
|
||||
asm-build = { module = "org.ow2.asm:asm", version.ref = "asmBuild" }
|
||||
|
||||
@@ -5,25 +5,38 @@
|
||||
# * Game waits for the tracy connection, runs the route, returns to the original coordinates and exits.
|
||||
# * tracy-capture finalizes
|
||||
#
|
||||
# Usage: scripts/flyby-capture.sh <output.tracy> [route] [length]
|
||||
# Usage: scripts/flyby-capture.sh <output.tracy> [route] [length] [extra gradle args...]
|
||||
# route - straight | pan | circuit | static
|
||||
# length - Units: blocks (straight), blocks per leg (circuit), degrees (pan), ticks (static).
|
||||
# extra - passed through to gradle; -Dangelica.* reaches the client (e.g. -Dangelica.flyby.timeOfDay=1500).
|
||||
#
|
||||
# FLYBY_SPEED - blocks per tick. (0.5 ~= creative flight).
|
||||
# FLYBY_SPEED - blocks per tick. (0.5 ~= creative flight).
|
||||
# QUICKPLAY_WORLD - save to auto-load (default: latest).
|
||||
# SDLGPU=1 - launch on the SDL-GPU backend.
|
||||
#
|
||||
# Requires tracy-capture on PATH (TRACY_CAPTURE=/path/to/tracy-capture).
|
||||
# Launch the game to the main menu and load your benchmark save; the run starts once the world is up.
|
||||
# QuickPlay loads QUICKPLAY_WORLD on launch; the run starts once the world is up.
|
||||
#
|
||||
# Ensure the chunks in the route have been generated before capturing
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
OUT="${1:?usage: flyby-capture.sh <output.tracy> [route] [length]}"
|
||||
OUT="${1:?usage: flyby-capture.sh <output.tracy> [route] [length] [extra gradle args...]}"
|
||||
ROUTE="${2:-straight}"
|
||||
LENGTH="${3:-0}"
|
||||
if [ $# -ge 3 ]; then
|
||||
shift 3
|
||||
else
|
||||
shift $#
|
||||
fi
|
||||
WARMUP="${FLYBY_WARMUP:-400}"
|
||||
TRACY_CAPTURE="${TRACY_CAPTURE:-tracy-capture}"
|
||||
|
||||
BACKEND_ARGS=()
|
||||
if [ "${SDLGPU:-0}" = "1" ]; then
|
||||
BACKEND_ARGS+=(-Dangelica.sdlgpu.enable=true)
|
||||
fi
|
||||
|
||||
if ! command -v "$TRACY_CAPTURE" >/dev/null 2>&1 && [ ! -x "$TRACY_CAPTURE" ]; then
|
||||
cat >&2 <<'EOF'
|
||||
error: tracy-capture not found.
|
||||
@@ -56,7 +69,6 @@ cleanup() {
|
||||
trap cleanup EXIT
|
||||
|
||||
echo "==> launching game (route=$ROUTE warmup=$WARMUP length=${LENGTH:-default})"
|
||||
echo " load your benchmark save once the main menu appears"
|
||||
|
||||
./gradlew runClient25 --console=plain \
|
||||
-Dangelica.flyby.route="$ROUTE" \
|
||||
@@ -64,7 +76,10 @@ echo " load your benchmark save once the main menu appears"
|
||||
-Dangelica.flyby.speed="${FLYBY_SPEED:-0}" \
|
||||
-Dangelica.flyby.warmupTicks="$WARMUP" \
|
||||
-Dangelica.flyby.waitForTracy=true \
|
||||
-Dangelica.flyby.exitWhenDone=true
|
||||
-Dangelica.flyby.exitWhenDone=true \
|
||||
-DquickPlaySingleplayer="${QUICKPLAY_WORLD:-latest}" \
|
||||
${BACKEND_ARGS[@]+"${BACKEND_ARGS[@]}"} \
|
||||
"$@"
|
||||
|
||||
echo "==> game exited, finalising capture"
|
||||
cleanup
|
||||
|
||||
@@ -77,6 +77,9 @@ dependencies {
|
||||
testImplementation(platform(libs.junit.bom))
|
||||
testImplementation(libs.junit.jupiter)
|
||||
testImplementation(testFixtures(project(":glsm")))
|
||||
testImplementation(project(":glsm")) {
|
||||
capabilities { requireCapability("${project.group}:glsm-stubs") }
|
||||
}
|
||||
testImplementation(libs.celeritas.common) { isTransitive = false }
|
||||
testImplementation(libs.lwjgl3.opengl)
|
||||
// GL.<clinit> loads liblwjgl_opengl.so. Test scope only: a root-level runtimeOnly collides with lwjgl3Bindings.
|
||||
@@ -84,15 +87,77 @@ dependencies {
|
||||
testRuntimeOnly(libs.junit.platform.launcher)
|
||||
testRuntimeOnly(libs.log4j.core)
|
||||
testRuntimeOnly(libs.fastutil)
|
||||
testRuntimeOnly(libs.joml)
|
||||
testRuntimeOnly(libs.commons.lang3)
|
||||
testRuntimeOnly(libs.guava)
|
||||
testImplementation(libs.asm.tree.test)
|
||||
}
|
||||
|
||||
val glsmSdlTag = "glsm-sdl"
|
||||
|
||||
val glsmSdlStubs = sourceSets.create("glsmSdlStubs") {
|
||||
java.srcDir("src/glsmSdlStubs/java")
|
||||
}
|
||||
|
||||
val glsmSdlAgent = sourceSets.create("glsmSdlAgent") {
|
||||
java.srcDir("src/glsmSdlAgent/java")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
"glsmSdlAgentCompileOnly"(project(":glsm"))
|
||||
"glsmSdlAgentCompileOnly"(libs.asm.tree.test)
|
||||
testImplementation(glsmSdlAgent.output)
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
useJUnitPlatform {
|
||||
excludeTags(glsmSdlTag)
|
||||
}
|
||||
if (System.getProperty("os.name").startsWith("Mac")) {
|
||||
jvmArgs("-XstartOnFirstThread")
|
||||
}
|
||||
}
|
||||
|
||||
val glsmSdlRedirectAgentJar by tasks.registering(Jar::class) {
|
||||
archiveClassifier = "glsm-sdl-redirect-agent"
|
||||
from(glsmSdlAgent.output)
|
||||
manifest {
|
||||
attributes("Premain-Class" to "com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlRedirectAgent")
|
||||
}
|
||||
}
|
||||
|
||||
val glsmSdlTest by tasks.registering(Test::class) {
|
||||
description = "Runs the headless GLSM-on-SDL FFP rig in its own JVM, with the SDL GPU gate engaged."
|
||||
group = "verification"
|
||||
testClassesDirs = sourceSets["test"].output.classesDirs
|
||||
classpath = glsmSdlStubs.output.classesDirs.plus(
|
||||
sourceSets["test"].runtimeClasspath.filter { file ->
|
||||
!file.name.startsWith("lwjgl-2.") && !file.name.startsWith("lwjgl_util-") && !file.name.startsWith("lwjgl-platform-")
|
||||
}
|
||||
)
|
||||
useJUnitPlatform {
|
||||
includeTags(glsmSdlTag)
|
||||
}
|
||||
dependsOn(tasks.named("glsmSdlStubsClasses"), glsmSdlRedirectAgentJar)
|
||||
val agentJarPath = glsmSdlRedirectAgentJar.flatMap { it.archiveFile }.map { it.asFile.path }
|
||||
jvmArgumentProviders.add(CommandLineArgumentProvider { listOf("-javaagent:${agentJarPath.get()}") })
|
||||
jvmArgs(
|
||||
"-Dangelica.sdlgpu.enable=true",
|
||||
"-Dangelica.sdlgpu.disablePresenterThread=true",
|
||||
"-Dceleritas.lwjglService=com.gtnewhorizons.angelica.sdlgpu.SDLGPULWJGLService",
|
||||
)
|
||||
if (System.getProperty("os.name").startsWith("Mac")) {
|
||||
jvmArgs("-XstartOnFirstThread")
|
||||
}
|
||||
testLogging {
|
||||
events("passed", "skipped", "failed")
|
||||
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
|
||||
}
|
||||
mustRunAfter(tasks.test)
|
||||
}
|
||||
|
||||
tasks.check { dependsOn(glsmSdlTest) }
|
||||
|
||||
tasks.named<JavaCompile>("compileJava") {
|
||||
val classesDir = destinationDirectory
|
||||
doLast { injectLwjgl3Aware(classesDir.get().asFile) }
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.redirect.GLSMRedirector;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
|
||||
import java.lang.instrument.ClassFileTransformer;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public final class GlsmSdlRedirectAgent {
|
||||
|
||||
private static final String GTNHLIB = "com/gtnewhorizon/gtnhlib/";
|
||||
private static final List<String> FAILURES = Collections.synchronizedList(new ArrayList<>());
|
||||
private static final AtomicInteger TRANSFORMED_COUNT = new AtomicInteger();
|
||||
|
||||
private GlsmSdlRedirectAgent() {}
|
||||
|
||||
public static void premain(String args, Instrumentation instrumentation) {
|
||||
instrumentation.addTransformer(new Redirector());
|
||||
}
|
||||
|
||||
public static List<String> failures() {
|
||||
return List.copyOf(FAILURES);
|
||||
}
|
||||
|
||||
public static int transformedCount() {
|
||||
return TRANSFORMED_COUNT.get();
|
||||
}
|
||||
|
||||
private static final class Redirector implements ClassFileTransformer {
|
||||
|
||||
private final GLSMRedirector redirector = new GLSMRedirector();
|
||||
|
||||
@Override
|
||||
public byte[] transform(ClassLoader loader, String className, Class<?> redefined, ProtectionDomain domain, byte[] classfile) {
|
||||
if (className == null || !className.startsWith(GTNHLIB)) return null;
|
||||
try {
|
||||
if (!redirector.shouldTransform(classfile)) return null;
|
||||
final ClassNode node = new ClassNode();
|
||||
new ClassReader(classfile).accept(node, 0);
|
||||
if (!redirector.transformClassNode(className.replace('/', '.'), node)) return null;
|
||||
final ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||
node.accept(writer);
|
||||
TRANSFORMED_COUNT.incrementAndGet();
|
||||
return writer.toByteArray();
|
||||
} catch (Throwable t) {
|
||||
FAILURES.add(className + ": " + t);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package org.lwjgl;
|
||||
|
||||
public final class Sys {
|
||||
|
||||
private Sys() {}
|
||||
|
||||
public static boolean is64Bit() {
|
||||
return !"32".equals(System.getProperty("sun.arch.data.model"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package org.lwjgl.opengl;
|
||||
|
||||
public class ContextCapabilities {
|
||||
|
||||
public boolean OpenGL30 = true;
|
||||
public boolean OpenGL31 = true;
|
||||
public boolean OpenGL32 = true;
|
||||
public boolean OpenGL33 = true;
|
||||
public boolean OpenGL40 = true;
|
||||
public boolean OpenGL42 = true;
|
||||
public boolean OpenGL43 = true;
|
||||
public boolean OpenGL44 = true;
|
||||
public boolean OpenGL45 = true;
|
||||
|
||||
public boolean GL_ARB_buffer_storage = true;
|
||||
public boolean GL_ARB_clear_texture = true;
|
||||
public boolean GL_ARB_compute_shader = true;
|
||||
public boolean GL_ARB_direct_state_access = false;
|
||||
public boolean GL_ARB_draw_buffers_blend = false;
|
||||
public boolean GL_ARB_multi_draw_indirect = false;
|
||||
public boolean GL_ARB_sampler_objects = true;
|
||||
public boolean GL_ARB_shader_image_load_store = true;
|
||||
public boolean GL_ARB_shader_storage_buffer_object = true;
|
||||
public boolean GL_ARB_tessellation_shader = false;
|
||||
|
||||
public boolean GL_EXT_direct_state_access = false;
|
||||
public boolean GL_EXT_gpu_shader4 = true;
|
||||
public boolean GL_EXT_shader_image_load_store = true;
|
||||
|
||||
public boolean GL_NVX_gpu_memory_info = false;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.lwjgl.opengl;
|
||||
|
||||
public interface Drawable {
|
||||
|
||||
void makeCurrent();
|
||||
|
||||
void releaseContext();
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.lwjgl.opengl;
|
||||
|
||||
public final class GLContext {
|
||||
|
||||
private static final ContextCapabilities CAPABILITIES = new ContextCapabilities();
|
||||
|
||||
private GLContext() {}
|
||||
|
||||
public static ContextCapabilities getCapabilities() {
|
||||
return CAPABILITIES;
|
||||
}
|
||||
}
|
||||
@@ -150,6 +150,7 @@ public final class SDLGPUGate {
|
||||
try {
|
||||
device().claimWindow(ctx.window());
|
||||
engaged = true;
|
||||
SDLGPULWJGLService.bind();
|
||||
BackendManager.RENDER_BACKEND.onPostWindowCreate(ctx.window());
|
||||
} catch (Throwable t) {
|
||||
initFailure = t;
|
||||
@@ -158,9 +159,10 @@ public final class SDLGPUGate {
|
||||
}
|
||||
|
||||
public static void fallBackToGL() {
|
||||
if (SDLGPULWJGLService.isOfferedAsAvailable()) {
|
||||
if (SDLGPULWJGLService.isConstructed()) {
|
||||
throw new IllegalStateException("Celeritas already selected the SDL GPU LWJGL service; falling back to OpenGL would leave it bound to a dead backend");
|
||||
}
|
||||
SDLGPULWJGLService.unbind();
|
||||
disarmed = true;
|
||||
engaged = false;
|
||||
deviceReady = false;
|
||||
|
||||
@@ -5,10 +5,10 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.device.MemoryStackWrapper;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.util.MemoryAccess;
|
||||
import com.mitchej123.lwjgl.DebugMessageHandler;
|
||||
import com.mitchej123.lwjgl.GLExtension;
|
||||
import com.mitchej123.lwjgl.LWJGLService;
|
||||
import com.mitchej123.lwjgl.MemoryStack;
|
||||
import org.taumc.celeritas.lwjgl.DebugMessageHandler;
|
||||
import org.taumc.celeritas.lwjgl.GLExtension;
|
||||
import org.taumc.celeritas.lwjgl.LWJGLService;
|
||||
import org.taumc.celeritas.lwjgl.MemoryStack;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.Buffer;
|
||||
@@ -17,19 +17,32 @@ import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
/** SDL GPU implementation of {@link LWJGLService} for Celeritas */
|
||||
public final class SDLGPULWJGLService extends LWJGLService {
|
||||
public final class SDLGPULWJGLService implements LWJGLService {
|
||||
|
||||
private static volatile boolean offeredAsAvailable;
|
||||
private static final ThreadLocal<MemoryStackWrapper> STACK =
|
||||
ThreadLocal.withInitial(() -> new MemoryStackWrapper(org.lwjgl.system.MemoryStack.stackGet()));
|
||||
|
||||
public static boolean isOfferedAsAvailable() { return offeredAsAvailable; }
|
||||
private static volatile boolean constructed;
|
||||
|
||||
@Override public int getPriority() {
|
||||
if (!SystemProperties.USE_SDL_GPU || !SDLGPUGate.isSDLGPUAvailable() || !SDLGPUGate.isEngaged()) {
|
||||
return PRIORITY_UNAVAILABLE;
|
||||
public static SDLGPULWJGLService create() {
|
||||
if (!SDLGPUGate.isEngaged()) {
|
||||
throw new IllegalStateException("SDL GPU is not engaged; " + SystemProperties.KEY_CELERITAS_LWJGL_SERVICE + " must not name " + SDLGPULWJGLService.class.getName());
|
||||
}
|
||||
constructed = true;
|
||||
return new SDLGPULWJGLService();
|
||||
}
|
||||
|
||||
public static boolean isConstructed() { return constructed; }
|
||||
|
||||
static void bind() {
|
||||
System.setProperty(SystemProperties.KEY_CELERITAS_LWJGL_SERVICE, SDLGPULWJGLService.class.getName());
|
||||
}
|
||||
|
||||
static void unbind() {
|
||||
final String key = SystemProperties.KEY_CELERITAS_LWJGL_SERVICE;
|
||||
if (SDLGPULWJGLService.class.getName().equals(System.getProperty(key))) {
|
||||
System.clearProperty(key);
|
||||
}
|
||||
offeredAsAvailable = true;
|
||||
return 200;
|
||||
}
|
||||
|
||||
@Override public boolean isOpenGLVersionSupported(int major, int minor) {
|
||||
@@ -47,7 +60,6 @@ public final class SDLGPULWJGLService extends LWJGLService {
|
||||
ARB_uniform_buffer_object,
|
||||
ARB_vertex_array_object,
|
||||
ARB_shader_storage_buffer_object,
|
||||
ARB_instanced_arrays,
|
||||
KHR_debug
|
||||
-> true;
|
||||
case ARB_timer_query,
|
||||
@@ -75,11 +87,20 @@ public final class SDLGPULWJGLService extends LWJGLService {
|
||||
BackendManager.RENDER_BACKEND.bufferData(target, size, usage);
|
||||
}
|
||||
}
|
||||
@Override public void glBufferSubData(int target, long offset, ByteBuffer data) { BackendManager.RENDER_BACKEND.bufferSubData(target, offset, data); }
|
||||
@Override public void glBufferSubData(int target, long offset, long size, long data) {
|
||||
BackendManager.RENDER_BACKEND.bufferSubData(target, offset, MemoryUtil.memByteBuffer(data, (int) size));
|
||||
}
|
||||
@Override public void glBufferStorage(int target, long size, int flags) { BackendManager.RENDER_BACKEND.bufferStorage(target, size, flags); }
|
||||
@Override public void glBlitFramebuffer(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) {
|
||||
BackendManager.RENDER_BACKEND.blitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
|
||||
}
|
||||
@Override public void glDrawArrays(int mode, int first, int count) { BackendManager.RENDER_BACKEND.drawArrays(mode, first, count); }
|
||||
@Override public void glDrawBuffers(int[] buffers) {
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
BackendManager.RENDER_BACKEND.drawBuffers(stack.ints(buffers));
|
||||
}
|
||||
}
|
||||
@Override public void glTexImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
|
||||
GLStateManager.glTexImage2D(target, level, internalFormat, width, height, border, format, type, pixels);
|
||||
}
|
||||
@Override public void glTexParameteri(int target, int pname, int param) { GLStateManager.glTexParameteri(target, pname, param); }
|
||||
@Override public ByteBuffer glMapBufferRange(int target, long offset, long length, int flags) { return BackendManager.RENDER_BACKEND.mapBufferRange(target, offset, length, flags); }
|
||||
@Override public long nglMapBuffer(int target, int access) { return 0; }
|
||||
@Override public ByteBuffer glMapBuffer(int target, int access) { return BackendManager.RENDER_BACKEND.mapBuffer(target, access); }
|
||||
@@ -231,7 +252,8 @@ public final class SDLGPULWJGLService extends LWJGLService {
|
||||
@Override public int glGetAttribLocation(int program, CharSequence name) { return BackendManager.RENDER_BACKEND.getAttribLocation(program, name); }
|
||||
|
||||
@Override public MemoryStack stackPush() {
|
||||
return new MemoryStackWrapper(org.lwjgl.system.MemoryStack.stackPush());
|
||||
org.lwjgl.system.MemoryStack.stackPush();
|
||||
return STACK.get();
|
||||
}
|
||||
|
||||
@Override public long nmemAlloc(long size) { return MemoryUtil.nmemAlloc(size); }
|
||||
|
||||
+316
-114
@@ -24,11 +24,13 @@ import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager.FrameState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.Presenter;
|
||||
import com.gtnewhorizons.retrofuturabootstrap.MainStartOnFirstThread;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.DrawDispatch;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.FFPTrace;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineApplier;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineCache;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineStore;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.VertexAttribs;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.BufferParams;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.CopyRectClip;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.FBOClearTracker;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.FboState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.FormatMap;
|
||||
@@ -105,6 +107,8 @@ import com.gtnewhorizons.angelica.sdlgpu.compat.Lwjgl3GLCapabilitiesShim;
|
||||
import me.eigenraven.lwjgl3ify.api.GLCapabilitiesOverride;
|
||||
|
||||
import static org.lwjgl.sdl.SDLGPU.*;
|
||||
import static org.lwjgl.sdl.SDLSurface.SDL_FLIP_NONE;
|
||||
import static org.lwjgl.sdl.SDLSurface.SDL_FLIP_VERTICAL;
|
||||
|
||||
/** SDL GPU implementation of {@link RenderBackend}. */
|
||||
public class SDLGPURenderBackend extends RenderBackend {
|
||||
@@ -147,6 +151,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
});
|
||||
private final PipelineApplier pipelineApplier = new PipelineApplier(frameManager, resourceManager, shaderManager, pipelineStore, fboClearTracker, persistentSync, samplerBinder, storageTextureBinder, storageBufferBinder);
|
||||
private final DrawDispatch drawDispatch = new DrawDispatch(device, frameManager, resourceManager, pipelineApplier, this::enqueuePreCopied);
|
||||
private final FFPTrace ffpTrace = SystemProperties.FFP_TRACE ? new FFPTrace(resourceManager) : null;
|
||||
|
||||
private final IntOpenHashSet missingProgramWarned = new IntOpenHashSet();
|
||||
|
||||
@@ -220,7 +225,11 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
|
||||
@Override public void onPostWindowCreate(long window) {
|
||||
buildCachedStrings();
|
||||
populateGLCapabilities();
|
||||
if (window != 0L) populateGLCapabilities();
|
||||
}
|
||||
|
||||
private boolean hasWindow() {
|
||||
return device.getClaimedWindow() != 0L;
|
||||
}
|
||||
|
||||
@Override public void init() {
|
||||
@@ -280,12 +289,16 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
st.bumpAttribStateGen();
|
||||
}
|
||||
});
|
||||
DisplayEvents.addPreSwapchainInvalidatingChangeListener(SDLGPUGate.sdlGpuPreSwapchainInvalidatingCallback());
|
||||
if (hasWindow()) DisplayEvents.addPreSwapchainInvalidatingChangeListener(SDLGPUGate.sdlGpuPreSwapchainInvalidatingCallback());
|
||||
GLSMConfig.expandVertexFormats = true;
|
||||
resourceManager.cachePreferredDepthFormats();
|
||||
LOG.warn("Swapchain texture format: 0x{} (12=B8G8R8A8_UNORM, 53=B8G8R8A8_SRGB, 4=R8G8B8A8_UNORM)", Integer.toHexString(device.getSwapchainTextureFormat()));
|
||||
cachedSwapchainFormatArray = new int[]{device.getSwapchainTextureFormat()};
|
||||
cachedSwapchainFormatArray = new int[]{
|
||||
hasWindow() ? device.getSwapchainTextureFormat() : resourceManager.mapTextureFormat(GL11.GL_RGBA8)
|
||||
};
|
||||
PipelineCache.setSwapchainFormats(cachedSwapchainFormatArray);
|
||||
if (hasWindow()) {
|
||||
LOG.warn("Swapchain texture format: 0x{} (12=B8G8R8A8_UNORM, 53=B8G8R8A8_SRGB, 4=R8G8B8A8_UNORM)", Integer.toHexString(cachedSwapchainFormatArray[0]));
|
||||
}
|
||||
pipelineStore.setBufferHandleResolver(resourceManager::getBufferHandle);
|
||||
pipelineStore.setVertexVariantResolver(shaderManager::getOrBuildVertexVariant);
|
||||
resourceManager.setBufferLivenessListener(this::markAllPipelineInputDirty);
|
||||
@@ -298,15 +311,17 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
LOG.info("SDL presenter thread enabled: presents run on '{}'", device.getWindowThread() != null ? device.getWindowThread().getName() : "window thread");
|
||||
}
|
||||
|
||||
final int splashW = Math.max(1, Display.getWidth());
|
||||
final int splashH = Math.max(1, Display.getHeight());
|
||||
final int[] maxDesktop = device.getMaxDesktopSizePixels();
|
||||
final int splashTexW = Math.max(splashW, maxDesktop[0]);
|
||||
final int splashTexH = Math.max(splashH, maxDesktop[1]);
|
||||
splashTarget = new OffscreenTarget();
|
||||
splashTarget.create(device, resourceManager, splashTexW, splashTexH, device.getSwapchainTextureFormat());
|
||||
SplashDispatcher.seedDrawnSize(splashW, splashH);
|
||||
LOG.info("Splash offscreen target {}x{} (window {}x{}, max desktop {}x{})", splashTexW, splashTexH, splashW, splashH, maxDesktop[0], maxDesktop[1]);
|
||||
if (hasWindow()) {
|
||||
final int splashW = Math.max(1, Display.getWidth());
|
||||
final int splashH = Math.max(1, Display.getHeight());
|
||||
final int[] maxDesktop = device.getMaxDesktopSizePixels();
|
||||
final int splashTexW = Math.max(splashW, maxDesktop[0]);
|
||||
final int splashTexH = Math.max(splashH, maxDesktop[1]);
|
||||
splashTarget = new OffscreenTarget();
|
||||
splashTarget.create(device, resourceManager, splashTexW, splashTexH, device.getSwapchainTextureFormat());
|
||||
SplashDispatcher.seedDrawnSize(splashW, splashH);
|
||||
LOG.info("Splash offscreen target {}x{} (window {}x{}, max desktop {}x{})", splashTexW, splashTexH, splashW, splashH, maxDesktop[0], maxDesktop[1]);
|
||||
}
|
||||
|
||||
Lwjgl3GLCapabilitiesShim.installOnCurrentThread(advertisedCapabilities());
|
||||
|
||||
@@ -673,6 +688,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
|
||||
awaitUploadFlush();
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.frameEnd(f);
|
||||
frameManager.endFrame();
|
||||
frameManager.presentFinalTarget();
|
||||
fenceTracker.resolvePendingFences();
|
||||
@@ -1004,13 +1020,15 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
if (!f.frameActive) { f.droppedDrawsThisFrame++; return; }
|
||||
final ContextState st = s();
|
||||
if (mode == GL11.GL_TRIANGLE_FAN && count >= 3) {
|
||||
drawDispatch.drawTriangleFanAsTriangleList(st, first, count);
|
||||
if (!drawDispatch.drawTriangleFanAsTriangleList(st, first, count)) { f.droppedDrawsThisFrame++; return; }
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "arraysFan", mode, count, 0, first, 0);
|
||||
return;
|
||||
}
|
||||
drawDispatch.setPrimitiveTypeForDraw(st, FormatMap.mapPrimitiveType(mode));
|
||||
pipelineApplier.ensureRenderPass(st, f);
|
||||
if (f.renderPass == 0) { f.droppedDrawsThisFrame++; drawDispatch.warnDrawArraysNoRenderPass(mode, st.boundFboId); return; }
|
||||
if (!pipelineApplier.applyPipelineAndState(st, f)) { f.droppedDrawsThisFrame++; return; }
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "arrays", mode, count, 0, first, 0);
|
||||
SDL_DrawGPUPrimitives(f.renderPass, count, 1, first, 0);
|
||||
}
|
||||
|
||||
@@ -1019,6 +1037,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final long rp = prepareIndexedDrawBind(st, mode, type, "drawElements");
|
||||
if (rp == 0) return;
|
||||
final int firstIndex = (int) (indices / FormatMap.indexElementSize(type));
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "elements", mode, count, type, firstIndex, 0);
|
||||
drawDispatch.issueIndexedDraw(st, rp, st.currentVao.elementBuffer, type, count, 1, firstIndex, 0);
|
||||
}
|
||||
|
||||
@@ -1027,6 +1046,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final long rp = prepareIndexedDrawBind(st, mode, type, "drawElementsInstanced");
|
||||
if (rp == 0) return;
|
||||
final int firstIndex = (int) (indices / FormatMap.indexElementSize(type));
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "elementsInstanced", mode, count, type, firstIndex, 0);
|
||||
drawDispatch.issueIndexedDraw(st, rp, st.currentVao.elementBuffer, type, count, primcount, firstIndex, 0);
|
||||
}
|
||||
|
||||
@@ -1036,6 +1056,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final long rp = prepareIndexedDrawBind(st, mode, type, "drawElementsBaseVertex");
|
||||
if (rp == 0) return;
|
||||
final int firstIndex = (int) (indices / FormatMap.indexElementSize(type));
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "elementsBaseVertex", mode, count, type, firstIndex, baseVertex);
|
||||
drawDispatch.issueIndexedDraw(st, rp, st.currentVao.elementBuffer, type, count, 1, firstIndex, baseVertex);
|
||||
}
|
||||
|
||||
@@ -1064,7 +1085,10 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final int count = MemoryUtil.memGetInt(pCount + offset);
|
||||
if (count <= 0) continue;
|
||||
final long indices = MemoryUtil.memGetAddress(pIndices + (long) i * Pointer.POINTER_SIZE);
|
||||
drawDispatch.issueIndexedDraw(st, rp, ebo, type, count, 1, (int) (indices / elementSize), MemoryUtil.memGetInt(pBaseVertex + offset));
|
||||
final int firstIndex = (int) (indices / elementSize);
|
||||
final int baseVertex = MemoryUtil.memGetInt(pBaseVertex + offset);
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "multiElementsBaseVertex", mode, count, type, firstIndex, baseVertex);
|
||||
drawDispatch.issueIndexedDraw(st, rp, ebo, type, count, 1, firstIndex, baseVertex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1089,6 +1113,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
+ "sentinel indices in the EBO will be drawn as regular indices.");
|
||||
drawDispatch.restartMultiDrawWarned = true;
|
||||
}
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "elementsIndirect", mode, drawcount, type, (int) indirect, 0);
|
||||
SDL_DrawGPUIndexedPrimitivesIndirect(rp, indirectHandle, (int) indirect, drawcount);
|
||||
} finally {
|
||||
Tracy.endZone();
|
||||
@@ -1102,7 +1127,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
|
||||
@Override public void dispatchComputeIndirect(long offset) {
|
||||
final int indirectGlId = getBoundBuffer(GL40.GL_DRAW_INDIRECT_BUFFER);
|
||||
final int indirectGlId = getBoundBuffer(GL43.GL_DISPATCH_INDIRECT_BUFFER);
|
||||
if (indirectGlId == 0) return;
|
||||
final long indirectHandle = resourceManager.getBufferHandle(indirectGlId);
|
||||
if (indirectHandle == 0) return;
|
||||
@@ -1200,6 +1225,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
|
||||
|
||||
@Override public void bindTexture(int target, int texture) {
|
||||
if (isProxyTarget(target)) return;
|
||||
final ContextState st = s();
|
||||
if (st.activeTextureUnit >= 0 && st.activeTextureUnit < st.boundTextures.length) {
|
||||
if (st.boundTextures[st.activeTextureUnit] != texture) {
|
||||
@@ -1213,6 +1239,87 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
st.activeTextureUnit = texture - GL13.GL_TEXTURE0;
|
||||
}
|
||||
|
||||
static boolean isProxyTarget(int target) {
|
||||
return TextureInfoCache.isProxyTarget(target);
|
||||
}
|
||||
|
||||
private static boolean proxyLayersInHeight(int target) {
|
||||
return target == GL30.GL_PROXY_TEXTURE_1D_ARRAY;
|
||||
}
|
||||
|
||||
private static boolean proxyLayersInDepth(int target) {
|
||||
return isProxyArrayTarget(target) && !proxyLayersInHeight(target);
|
||||
}
|
||||
|
||||
private static boolean isProxyArrayTarget(int target) {
|
||||
return target == GL30.GL_PROXY_TEXTURE_1D_ARRAY || target == GL30.GL_PROXY_TEXTURE_2D_ARRAY || target == GL32.GL_PROXY_TEXTURE_2D_MULTISAMPLE_ARRAY || target == GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY;
|
||||
}
|
||||
|
||||
public static int proxyLevelSize(int base, int level) {
|
||||
return PixelOps.mipLevelSize(base, level);
|
||||
}
|
||||
|
||||
static int proxyDimensionLimit(int target) {
|
||||
return switch (target) {
|
||||
case GL12.GL_PROXY_TEXTURE_3D -> MAX_3D_TEXTURE_SIZE;
|
||||
case GL13.GL_PROXY_TEXTURE_CUBE_MAP, GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY -> MAX_CUBE_MAP_TEXTURE_SIZE;
|
||||
default -> MAX_TEXTURE_SIZE;
|
||||
};
|
||||
}
|
||||
|
||||
static int levelZeroEquivalentSize(int size, int level) {
|
||||
if (size <= 0 || level < 0 || level >= Integer.SIZE - 1) return 0;
|
||||
final long scaled = (long) size << level;
|
||||
return scaled > Integer.MAX_VALUE ? 0 : (int) scaled;
|
||||
}
|
||||
|
||||
private void recordProxyTexImage(ContextState st, int target, int level, int internalFormat, int width, int height, int depth) {
|
||||
final boolean layersInHeight = proxyLayersInHeight(target);
|
||||
final boolean layersInDepth = proxyLayersInDepth(target);
|
||||
final int dimLimit = proxyDimensionLimit(target);
|
||||
final int w = levelZeroEquivalentSize(width, level);
|
||||
final int h = layersInHeight ? height : levelZeroEquivalentSize(height, level);
|
||||
final int d = layersInDepth ? depth : levelZeroEquivalentSize(depth, level);
|
||||
final int hLimit = layersInHeight ? MAX_ARRAY_TEXTURE_LAYERS : dimLimit;
|
||||
final int dLimit = layersInDepth ? MAX_ARRAY_TEXTURE_LAYERS : dimLimit;
|
||||
final boolean fits = w >= 1 && w <= dimLimit && h >= 1 && h <= hLimit && d >= 1 && d <= dLimit
|
||||
&& resourceManager.isMappableTextureFormat(internalFormat);
|
||||
if (fits) {
|
||||
st.proxyTexture.accept(target, w, h, d, internalFormat);
|
||||
} else {
|
||||
st.proxyTexture.reject(target);
|
||||
}
|
||||
}
|
||||
|
||||
private static int boundTextureFor(ContextState st, int target) {
|
||||
return isProxyTarget(target) ? 0 : st.boundTextures[st.activeTextureUnit];
|
||||
}
|
||||
|
||||
private void ensureLevel0Storage(ContextState st, int glId, int target, int internalFormat, int width, int height, int depth, int format, boolean mipmapped) {
|
||||
final int numLevels;
|
||||
if (mipmapped) {
|
||||
final TextureSamplerState ss = resourceManager.getOrCreateTexSamplerState(glId);
|
||||
numLevels = ss.maxLevel >= 0 ? ss.maxLevel + 1 : PixelOps.defaultMipLevels(width, height);
|
||||
} else {
|
||||
numLevels = 1;
|
||||
}
|
||||
final boolean bgraSwizzle = format == GL12.GL_BGRA && (internalFormat == GL11.GL_RGBA || internalFormat == GL11.GL_RGBA8);
|
||||
final int targetSdlFormat = bgraSwizzle
|
||||
? SDLGPU.SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM
|
||||
: resourceManager.mapTextureFormat(internalFormat);
|
||||
if (canReuseTexture(resourceManager.getTextureMeta(glId), resourceManager.getTextureHandle(glId),
|
||||
target, internalFormat, targetSdlFormat, width, height, depth, numLevels)) {
|
||||
return;
|
||||
}
|
||||
textureOps.releaseTextureForRealloc(st, glId);
|
||||
if (bgraSwizzle) {
|
||||
resourceManager.createTextureWithSdlFormat(glId, target, SDLGPU.SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM, internalFormat, width, height, depth, numLevels);
|
||||
} else {
|
||||
resourceManager.createTexture(glId, target, internalFormat, width, height, depth, numLevels);
|
||||
}
|
||||
resourceManager.refreshTextureReferences(glId);
|
||||
}
|
||||
|
||||
private static boolean canReuseTexture(ResourceManager.TextureMeta m, long handle, int target, int glFormat, int sdlFormat, int w, int h, int d, int levels) {
|
||||
return m != null
|
||||
&& handle != 0
|
||||
@@ -1227,26 +1334,15 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
|
||||
@Override public void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
|
||||
final ContextState st = s();
|
||||
if (isProxyTarget(target)) {
|
||||
recordProxyTexImage(st, target, level, internalFormat, width, height, 1);
|
||||
return;
|
||||
}
|
||||
final int glId = st.boundTextures[st.activeTextureUnit];
|
||||
if (glId == 0) return;
|
||||
|
||||
if (level == 0) {
|
||||
final TextureSamplerState ss = resourceManager.getOrCreateTexSamplerState(glId);
|
||||
final int numLevels = ss.maxLevel >= 0 ? ss.maxLevel + 1 : PixelOps.defaultMipLevels(width, height);
|
||||
final boolean bgraSwizzle = format == GL12.GL_BGRA && (internalFormat == GL11.GL_RGBA || internalFormat == GL11.GL_RGBA8);
|
||||
final int targetSdlFormat = bgraSwizzle
|
||||
? SDLGPU.SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM
|
||||
: resourceManager.mapTextureFormat(internalFormat);
|
||||
if (!canReuseTexture(resourceManager.getTextureMeta(glId), resourceManager.getTextureHandle(glId),
|
||||
target, internalFormat, targetSdlFormat, width, height, 1, numLevels)) {
|
||||
textureOps.releaseTextureForRealloc(st, glId);
|
||||
if (bgraSwizzle) {
|
||||
resourceManager.createTextureWithSdlFormat(glId, target, SDLGPU.SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM, internalFormat, width, height, 1, numLevels);
|
||||
} else {
|
||||
resourceManager.createTexture(glId, target, internalFormat, width, height, 1, numLevels);
|
||||
}
|
||||
resourceManager.refreshTextureReferences(glId);
|
||||
}
|
||||
ensureLevel0Storage(st, glId, target, internalFormat, width, height, 1, format, true);
|
||||
}
|
||||
|
||||
textureOps.uploadTextureRegion(st, glId, resourceManager.getTextureMeta(glId), resourceManager.getTextureHandle(glId), pixels, 0, 0, width, height, level, format, type);
|
||||
@@ -1262,18 +1358,15 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
@Override public void texImage3D(int target, int level, int internalFormat, int width, int height, int depth, int border, int format, int type, ByteBuffer pixels) {
|
||||
final ContextState st = s();
|
||||
if (isProxyTarget(target)) {
|
||||
recordProxyTexImage(st, target, level, internalFormat, width, height, depth);
|
||||
return;
|
||||
}
|
||||
final int glId = st.boundTextures[st.activeTextureUnit];
|
||||
if (glId == 0) return;
|
||||
|
||||
if (level == 0) {
|
||||
final int numLevels = 1;
|
||||
final int targetSdlFormat = resourceManager.mapTextureFormat(internalFormat);
|
||||
if (!canReuseTexture(resourceManager.getTextureMeta(glId), resourceManager.getTextureHandle(glId),
|
||||
target, internalFormat, targetSdlFormat, width, height, depth, numLevels)) {
|
||||
textureOps.releaseTextureForRealloc(st, glId);
|
||||
resourceManager.createTexture(glId, target, internalFormat, width, height, depth, numLevels);
|
||||
resourceManager.refreshTextureReferences(glId);
|
||||
}
|
||||
ensureLevel0Storage(st, glId, target, internalFormat, width, height, depth, GL11.GL_NONE, false);
|
||||
}
|
||||
if (pixels == null) return;
|
||||
final long texHandle = resourceManager.getTextureHandle(glId);
|
||||
@@ -1295,18 +1388,15 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
@Override public void texImage1D(int target, int level, int internalFormat, int width, int border, int format, int type, ByteBuffer pixels) {
|
||||
final ContextState st = s();
|
||||
if (isProxyTarget(target)) {
|
||||
recordProxyTexImage(st, target, level, internalFormat, width, 1, 1);
|
||||
return;
|
||||
}
|
||||
final int glId = st.boundTextures[st.activeTextureUnit];
|
||||
if (glId == 0) return;
|
||||
|
||||
if (level == 0) {
|
||||
final int numLevels = 1;
|
||||
final int targetSdlFormat = resourceManager.mapTextureFormat(internalFormat);
|
||||
if (!canReuseTexture(resourceManager.getTextureMeta(glId), resourceManager.getTextureHandle(glId),
|
||||
target, internalFormat, targetSdlFormat, width, 1, 1, numLevels)) {
|
||||
textureOps.releaseTextureForRealloc(st, glId);
|
||||
resourceManager.createTexture(glId, target, internalFormat, width, 1, 1, numLevels);
|
||||
resourceManager.refreshTextureReferences(glId);
|
||||
}
|
||||
ensureLevel0Storage(st, glId, target, internalFormat, width, 1, 1, GL11.GL_NONE, false);
|
||||
}
|
||||
if (pixels == null) return;
|
||||
textureOps.uploadTextureRegion(st, glId, resourceManager.getTextureMeta(glId), resourceManager.getTextureHandle(glId),
|
||||
@@ -1314,18 +1404,15 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
@Override public void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, long pixelBufferOffset) {
|
||||
final ContextState st = s();
|
||||
if (isProxyTarget(target)) {
|
||||
recordProxyTexImage(st, target, level, internalFormat, width, height, 1);
|
||||
return;
|
||||
}
|
||||
final int glId = st.boundTextures[st.activeTextureUnit];
|
||||
if (glId == 0) return;
|
||||
|
||||
if (level == 0) {
|
||||
final TextureSamplerState ss = resourceManager.getOrCreateTexSamplerState(glId);
|
||||
final int numLevels = ss.maxLevel >= 0 ? ss.maxLevel + 1 : PixelOps.defaultMipLevels(width, height);
|
||||
final int targetSdlFormat = resourceManager.mapTextureFormat(internalFormat);
|
||||
if (!canReuseTexture(resourceManager.getTextureMeta(glId), resourceManager.getTextureHandle(glId), target, internalFormat, targetSdlFormat, width, height, 1, numLevels)) {
|
||||
textureOps.releaseTextureForRealloc(st, glId);
|
||||
resourceManager.createTexture(glId, target, internalFormat, width, height, 1, numLevels);
|
||||
resourceManager.refreshTextureReferences(glId);
|
||||
}
|
||||
ensureLevel0Storage(st, glId, target, internalFormat, width, height, 1, format, true);
|
||||
}
|
||||
|
||||
if (st.boundPixelUnpackBuffer == 0) return;
|
||||
@@ -1342,7 +1429,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
|
||||
@Override public void texSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
|
||||
final ContextState st = s();
|
||||
final int glId = st.boundTextures[st.activeTextureUnit];
|
||||
final int glId = boundTextureFor(st, target);
|
||||
if (glId == 0 || pixels == null) return;
|
||||
textureOps.uploadTextureRegion(st, glId, resourceManager.getTextureMeta(glId), resourceManager.getTextureHandle(glId), pixels, xoffset, yoffset, width, height, level, format, type);
|
||||
}
|
||||
@@ -1352,7 +1439,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
@Override public void texSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, long pboOffset) {
|
||||
final ContextState st = s();
|
||||
if (st.boundPixelUnpackBuffer == 0) return;
|
||||
final int glId = st.boundTextures[st.activeTextureUnit];
|
||||
final int glId = boundTextureFor(st, target);
|
||||
if (glId == 0) return;
|
||||
final ByteBuffer staging = resourceManager.getPboStaging(st.boundPixelUnpackBuffer);
|
||||
if (staging == null) return;
|
||||
@@ -1366,24 +1453,29 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
@Override public void copyTexSubImage2D(int target, int level, int xoffset, int yoffset, int x, int y, int width, int height) {
|
||||
final ContextState cs = s();
|
||||
final int destGlId = cs.boundTextures[cs.activeTextureUnit];
|
||||
textureOps.copyTexSubImageImpl(cs,destGlId, level, xoffset, yoffset, x, y, width, height);
|
||||
textureOps.copyTexSubImageImpl(cs, boundTextureFor(cs, target), level, xoffset, yoffset, x, y, width, height);
|
||||
}
|
||||
|
||||
@Override public void copyTexImage2D(int target, int level, int internalFormat, int x, int y, int width, int height, int border) {
|
||||
final ContextState cs = s();
|
||||
final int destGlId = cs.boundTextures[cs.activeTextureUnit];
|
||||
final int destGlId = boundTextureFor(cs, target);
|
||||
if (destGlId == 0) return;
|
||||
|
||||
if (level == 0) {
|
||||
ensureLevel0Storage(cs, destGlId, target, internalFormat, width, height, 1, GL11.GL_NONE, true);
|
||||
}
|
||||
|
||||
textureOps.copyTexSubImageImpl(cs, destGlId, level, 0, 0, x, y, width, height);
|
||||
}
|
||||
|
||||
@Override public void texParameteri(int target, int pname, int param) {
|
||||
final ContextState cs = s();
|
||||
textureOps.texParameteri(cs, pname, param, cs.boundTextures[cs.activeTextureUnit]);
|
||||
textureOps.texParameteri(cs, pname, param, boundTextureFor(cs, target));
|
||||
}
|
||||
|
||||
@Override public void texParameterf(int target, int pname, float param) {
|
||||
final ContextState cs = s();
|
||||
textureOps.texParameterf(cs, pname, param, cs.boundTextures[cs.activeTextureUnit]);
|
||||
textureOps.texParameterf(cs, pname, param, boundTextureFor(cs, target));
|
||||
}
|
||||
|
||||
@Override public void texParameteriv(int target, int pname, IntBuffer params) {
|
||||
@@ -1394,22 +1486,31 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
@Override public int getTexParameteri(int target, int pname) {
|
||||
final ContextState cs = s();
|
||||
return getTextureParameteri(cs.boundTextures[cs.activeTextureUnit], target, pname);
|
||||
return getTextureParameteri(boundTextureFor(cs, target), target, pname);
|
||||
}
|
||||
@Override public float getTexParameterf(int target, int pname) {
|
||||
final ContextState cs = s();
|
||||
return getTextureParameterf(cs.boundTextures[cs.activeTextureUnit], target, pname);
|
||||
return getTextureParameterf(boundTextureFor(cs, target), target, pname);
|
||||
}
|
||||
@Override public int getTexLevelParameteri(int target, int level, int pname) {
|
||||
final ContextState cs = s();
|
||||
if (isProxyTarget(target)) {
|
||||
final ContextState.ProxyTextureState proxy = cs.proxyTexture;
|
||||
if (proxy.target != target) return 0;
|
||||
return switch (pname) {
|
||||
case GL11.GL_TEXTURE_WIDTH -> proxyLevelSize(proxy.width, level);
|
||||
case GL11.GL_TEXTURE_HEIGHT -> proxyLayersInHeight(target) ? proxy.height : proxyLevelSize(proxy.height, level);
|
||||
case GL12.GL_TEXTURE_DEPTH -> proxyLayersInDepth(target) ? proxy.depth : proxyLevelSize(proxy.depth, level);
|
||||
case GL11.GL_TEXTURE_INTERNAL_FORMAT -> proxy.internalFormat;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
final int glId = cs.boundTextures[cs.activeTextureUnit];
|
||||
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(glId);
|
||||
if (meta == null) return 0;
|
||||
final int mipW = Math.max(1, meta.width() >> level);
|
||||
final int mipH = Math.max(1, meta.height() >> level);
|
||||
return switch (pname) {
|
||||
case GL11.GL_TEXTURE_WIDTH -> mipW;
|
||||
case GL11.GL_TEXTURE_HEIGHT -> mipH;
|
||||
case GL11.GL_TEXTURE_WIDTH -> PixelOps.mipLevelSize(meta.width(), level);
|
||||
case GL11.GL_TEXTURE_HEIGHT -> PixelOps.mipLevelSize(meta.height(), level);
|
||||
case GL11.GL_TEXTURE_INTERNAL_FORMAT -> {
|
||||
final int stored = meta.glFormat();
|
||||
yield TextureInfoCache.isGenericCompressedInternalFormat(stored) ? GL11.GL_RGBA8 : stored;
|
||||
@@ -1418,8 +1519,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
};
|
||||
}
|
||||
@Override public void generateMipmap(int target) {
|
||||
final ContextState st = s();
|
||||
generateTextureMipmap(st.boundTextures[st.activeTextureUnit]);
|
||||
generateTextureMipmap(boundTextureFor(s(), target));
|
||||
}
|
||||
@Override public void pixelStorei(int pname, int param) {
|
||||
final ContextState.PixelStoreState ps = s().pixelStore;
|
||||
@@ -1689,12 +1789,20 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
|
||||
|
||||
@Override public void readPixels(int x, int y, int width, int height, int format, int type, ByteBuffer pixels) {
|
||||
if (pixels == null || width <= 0 || height <= 0) return;
|
||||
final ContextState cs = s();
|
||||
final boolean fromFbo0 = cs.boundReadFboId == 0;
|
||||
final long texHandle;
|
||||
final int srcSdlFormat;
|
||||
if (cs.boundReadFboId == 0) {
|
||||
final int srcWidth;
|
||||
final int srcHeight;
|
||||
final int srcY;
|
||||
if (fromFbo0) {
|
||||
texHandle = frameManager.getFbo0Texture();
|
||||
srcSdlFormat = frameManager.getSwapchainFormat();
|
||||
srcWidth = frameManager.getFbo0Width();
|
||||
srcHeight = frameManager.getFbo0Height();
|
||||
srcY = srcHeight - y - height;
|
||||
} else {
|
||||
final FboState fbo = resourceManager.getFbo(cs.boundReadFboId);
|
||||
if (fbo == null) return;
|
||||
@@ -1702,10 +1810,58 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
texHandle = resourceManager.getTextureHandle(colorGlId);
|
||||
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(colorGlId);
|
||||
srcSdlFormat = meta != null ? meta.sdlFormat() : SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
|
||||
srcWidth = meta != null ? meta.width() : fbo.width;
|
||||
srcHeight = meta != null ? meta.height() : fbo.height;
|
||||
srcY = y;
|
||||
}
|
||||
if (texHandle == 0 || srcWidth <= 0 || srcHeight <= 0) return;
|
||||
|
||||
final long clip = CopyRectClip.clipCopyRect(x, srcY, 0, 0, width, height, srcWidth, srcHeight, width, height);
|
||||
if (clip == CopyRectClip.EMPTY) return;
|
||||
final int sx = CopyRectClip.srcX(clip);
|
||||
final int sy = CopyRectClip.srcY(clip);
|
||||
final int w = CopyRectClip.width(clip);
|
||||
final int h = CopyRectClip.height(clip);
|
||||
final int colOffset = CopyRectClip.dstX(clip, x, 0);
|
||||
final int dy = CopyRectClip.dstY(clip, srcY, 0);
|
||||
final int rowOffset = fromFbo0 ? (height - h - dy) : dy;
|
||||
|
||||
final int basePos = pixels.position();
|
||||
final int baseLimit = pixels.limit();
|
||||
final int stride = width * 4;
|
||||
if (baseLimit - basePos < height * stride) return;
|
||||
try {
|
||||
if (w == width) {
|
||||
final int start = basePos + rowOffset * stride;
|
||||
pixels.position(0);
|
||||
pixels.limit(start + h * stride);
|
||||
pixels.position(start);
|
||||
textureOps.readbackTexture(texHandle, sx, sy, w, h, 0, pixels);
|
||||
pixels.position(0);
|
||||
pixels.limit(baseLimit);
|
||||
pixels.position(start);
|
||||
PixelOps.postProcessReadback(pixels, width, h, format, srcSdlFormat, fromFbo0);
|
||||
} else {
|
||||
final ByteBuffer staging = MemoryUtil.memAlloc(w * h * 4);
|
||||
try {
|
||||
textureOps.readbackTexture(texHandle, sx, sy, w, h, 0, staging);
|
||||
staging.rewind();
|
||||
PixelOps.postProcessReadback(staging, w, h, format, srcSdlFormat, fromFbo0);
|
||||
for (int row = 0; row < h; row++) {
|
||||
staging.position(0);
|
||||
staging.limit((row + 1) * w * 4);
|
||||
staging.position(row * w * 4);
|
||||
pixels.position(basePos + (rowOffset + row) * stride + colOffset * 4);
|
||||
pixels.put(staging);
|
||||
}
|
||||
} finally {
|
||||
MemoryUtil.memFree(staging);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
pixels.limit(baseLimit);
|
||||
pixels.position(basePos);
|
||||
}
|
||||
textureOps.readbackTexture(texHandle, x, y, width, height, 0, pixels);
|
||||
pixels.rewind();
|
||||
PixelOps.postProcessReadback(pixels, width, height, format, srcSdlFormat, cs.boundReadFboId == 0);
|
||||
}
|
||||
@Override public void readPixels(int x, int y, int width, int height, int format, int type, FloatBuffer pixels) {
|
||||
readPixels(x, y, width, height, format, type, MemoryUtil.memByteBuffer(pixels));
|
||||
@@ -1715,7 +1871,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
@Override public void getTexImage(int target, int level, int format, int type, ByteBuffer pixels) {
|
||||
final ContextState cs = s();
|
||||
final int glId = cs.boundTextures[cs.activeTextureUnit];
|
||||
final int glId = boundTextureFor(cs, target);
|
||||
final long texHandle = resourceManager.getTextureHandle(glId);
|
||||
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(glId);
|
||||
if (meta == null) return;
|
||||
@@ -1730,7 +1886,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final ContextState cs = s();
|
||||
final int pbo = cs.boundPixelPackBuffer;
|
||||
if (pbo == 0) return;
|
||||
final int glId = cs.boundTextures[cs.activeTextureUnit];
|
||||
final int glId = boundTextureFor(cs, target);
|
||||
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(glId);
|
||||
if (meta == null) return;
|
||||
final ByteBuffer tmp = MemoryUtil.memAlloc(meta.width() * meta.height() * 4);
|
||||
@@ -2272,6 +2428,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
case GL30.GL_PIXEL_UNPACK_BUFFER -> st.boundPixelUnpackBuffer = buffer;
|
||||
case GL30.GL_PIXEL_PACK_BUFFER -> st.boundPixelPackBuffer = buffer;
|
||||
case GL40.GL_DRAW_INDIRECT_BUFFER -> st.boundIndirectBuffer = buffer;
|
||||
case GL43.GL_DISPATCH_INDIRECT_BUFFER -> st.boundDispatchIndirectBuffer = buffer;
|
||||
case GL31.GL_COPY_READ_BUFFER -> st.boundCopyReadBuffer = buffer;
|
||||
case GL31.GL_COPY_WRITE_BUFFER -> st.boundCopyWriteBuffer = buffer;
|
||||
case GL43.GL_SHADER_STORAGE_BUFFER -> {
|
||||
@@ -3068,14 +3225,26 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
@Override public void texStorage1D(int target, int levels, int internalFormat, int width) {
|
||||
final ContextState st = s();
|
||||
if (isProxyTarget(target)) {
|
||||
recordProxyTexImage(st, target, 0, internalFormat, width, 1, 1);
|
||||
return;
|
||||
}
|
||||
texStorageImpl(st, st.boundTextures[st.activeTextureUnit], target, internalFormat, width, 1, 1, levels);
|
||||
}
|
||||
@Override public void texStorage2D(int target, int levels, int internalFormat, int width, int height) {
|
||||
final ContextState st = s();
|
||||
if (isProxyTarget(target)) {
|
||||
recordProxyTexImage(st, target, 0, internalFormat, width, height, 1);
|
||||
return;
|
||||
}
|
||||
texStorageImpl(st, st.boundTextures[st.activeTextureUnit], target, internalFormat, width, height, 1, levels);
|
||||
}
|
||||
@Override public void texStorage3D(int target, int levels, int internalFormat, int width, int height, int depth) {
|
||||
final ContextState st = s();
|
||||
if (isProxyTarget(target)) {
|
||||
recordProxyTexImage(st, target, 0, internalFormat, width, height, depth);
|
||||
return;
|
||||
}
|
||||
texStorageImpl(st, st.boundTextures[st.activeTextureUnit], target, internalFormat, width, height, depth, levels);
|
||||
}
|
||||
@Override public void textureStorage1D(int texture, int levels, int internalFormat, int width) {
|
||||
@@ -3166,41 +3335,72 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
applyDrawBuffersFromIntBuffer(fbo, bufs, framebuffer == s().boundFboId);
|
||||
}
|
||||
@Override public void blitNamedFramebuffer(int readFramebuffer, int drawFramebuffer, int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) {
|
||||
final FboState srcFbo = resourceManager.getFbo(readFramebuffer);
|
||||
final FboState dstFbo = resourceManager.getFbo(drawFramebuffer);
|
||||
if (srcFbo == null || dstFbo == null) return;
|
||||
final boolean srcIsFbo0 = readFramebuffer == 0;
|
||||
final boolean dstIsFbo0 = drawFramebuffer == 0;
|
||||
final FboState srcFbo = srcIsFbo0 ? null : resourceManager.getFbo(readFramebuffer);
|
||||
final FboState dstFbo = dstIsFbo0 ? null : resourceManager.getFbo(drawFramebuffer);
|
||||
if ((srcFbo == null && !srcIsFbo0) || (dstFbo == null && !dstIsFbo0)) return;
|
||||
|
||||
final boolean isDepth = (mask & GL11.GL_DEPTH_BUFFER_BIT) != 0;
|
||||
long srcTex = 0, dstTex = 0;
|
||||
int dstGlId = 0;
|
||||
if (isDepth) {
|
||||
srcTex = srcFbo.depthTexture;
|
||||
dstTex = dstFbo.depthTexture;
|
||||
dstGlId = dstFbo.depthGlId;
|
||||
} else if ((mask & GL11.GL_COLOR_BUFFER_BIT) != 0) {
|
||||
srcTex = srcFbo.colorTextures[srcFbo.readBufferIndex];
|
||||
dstTex = dstFbo.colorTextures[0];
|
||||
dstGlId = dstFbo.colorGlIds[0];
|
||||
final boolean anyFbo0 = srcIsFbo0 || dstIsFbo0;
|
||||
final int fbo0Height = frameManager.getFbo0Height();
|
||||
if (anyFbo0 && fbo0Height <= 0) return;
|
||||
|
||||
final long colorSrcTex;
|
||||
final long colorDstTex;
|
||||
final int colorSrcGlId;
|
||||
final int colorDstGlId;
|
||||
if ((mask & GL11.GL_COLOR_BUFFER_BIT) != 0) {
|
||||
colorSrcTex = srcIsFbo0 ? frameManager.getFbo0Texture() : srcFbo.colorTextures[srcFbo.readBufferIndex];
|
||||
colorDstTex = dstIsFbo0 ? frameManager.getFbo0Texture() : dstFbo.colorTextures[0];
|
||||
colorSrcGlId = srcIsFbo0 ? 0 : srcFbo.colorGlIds[srcFbo.readBufferIndex];
|
||||
colorDstGlId = dstIsFbo0 ? 0 : dstFbo.colorGlIds[0];
|
||||
} else {
|
||||
colorSrcTex = 0L;
|
||||
colorDstTex = 0L;
|
||||
colorSrcGlId = 0;
|
||||
colorDstGlId = 0;
|
||||
}
|
||||
if (srcTex == 0 || dstTex == 0) return;
|
||||
final boolean blitColor = colorSrcTex != 0 && colorDstTex != 0 && colorSrcTex != colorDstTex;
|
||||
|
||||
final boolean depthRequested = (mask & (GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_STENCIL_BUFFER_BIT)) != 0;
|
||||
final long depthSrcTex = (depthRequested && !anyFbo0) ? srcFbo.depthTexture : 0L;
|
||||
final long depthDstTex = (depthRequested && !anyFbo0) ? dstFbo.depthTexture : 0L;
|
||||
final boolean blitDepth = depthSrcTex != 0 && depthDstTex != 0 && depthSrcTex != depthDstTex;
|
||||
|
||||
if (!blitColor && !blitDepth) return;
|
||||
|
||||
final ContextState cs = s();
|
||||
fboClearTracker.materializePendingClearForTexture(cs, srcTex);
|
||||
if (!fboClearTracker.discardPendingClearIfFullyCovered(cs, dstTex, dstX0, dstY0, 0, dstX1 - dstX0, dstY1 - dstY0, resourceManager.getTextureMeta(dstGlId))) {
|
||||
fboClearTracker.materializePendingClearForTexture(cs, dstTex);
|
||||
if (dstIsFbo0 || (srcIsFbo0 && (cs.pendingSwapchainClear || cs.pendingSwapchainDepthClear || cs.pendingSwapchainStencilClear))) {
|
||||
frameManager.ensureFbo0RenderPass(frameManager.frame(), cs);
|
||||
}
|
||||
|
||||
if (isDepth) {
|
||||
textureOps.copyTexture(srcTex, srcX0, srcY0, dstTex, dstX0, dstY0, srcX1 - srcX0, srcY1 - srcY0);
|
||||
} else {
|
||||
final ResourceManager.TextureMeta srcMeta = resourceManager.getTextureMeta(srcFbo.colorGlIds[srcFbo.readBufferIndex]);
|
||||
final ResourceManager.TextureMeta dstMeta = resourceManager.getTextureMeta(dstFbo.colorGlIds[0]);
|
||||
if (TextureOps.canCopyInsteadOfBlit(srcMeta, dstMeta, srcX1 - srcX0, srcY1 - srcY0, dstX1 - dstX0, dstY1 - dstY0)) {
|
||||
textureOps.copyTexture(srcTex, srcX0, srcY0, dstTex, dstX0, dstY0, srcX1 - srcX0, srcY1 - srcY0);
|
||||
final int srcW = srcX1 - srcX0;
|
||||
final int srcH = srcY1 - srcY0;
|
||||
final int dstW = dstX1 - dstX0;
|
||||
final int dstH = dstY1 - dstY0;
|
||||
final int srcY = srcIsFbo0 ? (fbo0Height - srcY0 - srcH) : srcY0;
|
||||
final int dstY = dstIsFbo0 ? (fbo0Height - dstY0 - dstH) : dstY0;
|
||||
final int flipMode = (srcIsFbo0 != dstIsFbo0) ? SDL_FLIP_VERTICAL : SDL_FLIP_NONE;
|
||||
|
||||
if (blitColor) {
|
||||
resolveBlitClears(cs, colorSrcTex, colorDstTex, colorDstGlId, dstX0, dstY, dstW, dstH);
|
||||
final ResourceManager.TextureMeta srcMeta = resourceManager.getTextureMeta(colorSrcGlId);
|
||||
final ResourceManager.TextureMeta dstMeta = resourceManager.getTextureMeta(colorDstGlId);
|
||||
if (flipMode == SDL_FLIP_NONE && TextureOps.canCopyInsteadOfBlit(srcMeta, dstMeta, srcW, srcH, dstW, dstH)) {
|
||||
textureOps.copyTexture(colorSrcTex, srcX0, srcY, colorDstTex, 0, dstX0, dstY, srcW, srcH);
|
||||
} else {
|
||||
textureOps.blitTexture(srcTex, srcX0, srcY0, srcX1 - srcX0, srcY1 - srcY0, dstTex, dstX0, dstY0, dstX1 - dstX0, dstY1 - dstY0, filter);
|
||||
textureOps.blitTexture(colorSrcTex, srcX0, srcY, srcW, srcH, colorDstTex, 0, dstX0, dstY, dstW, dstH, filter, flipMode);
|
||||
}
|
||||
}
|
||||
if (blitDepth) {
|
||||
resolveBlitClears(cs, depthSrcTex, depthDstTex, dstFbo.depthGlId, dstX0, dstY, dstW, dstH);
|
||||
textureOps.copyTexture(depthSrcTex, srcX0, srcY, depthDstTex, 0, dstX0, dstY, srcW, srcH);
|
||||
}
|
||||
}
|
||||
|
||||
private void resolveBlitClears(ContextState cs, long srcTex, long dstTex, int dstGlId, int dstX, int dstY, int dstW, int dstH) {
|
||||
fboClearTracker.materializePendingClearForTexture(cs, srcTex);
|
||||
fboClearTracker.resolveDestinationForWrite(cs, dstTex, resourceManager.getTextureMeta(dstGlId), 0, dstX, dstY, 0, dstW, dstH);
|
||||
}
|
||||
@Override public int createBuffers() { return resourceManager.genBuffer(); }
|
||||
@Override public void namedBufferData(int buffer, long size, int usage) {
|
||||
@@ -3280,11 +3480,9 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
@Override public int getTextureLevelParameteri(int texture, int level, int pname) {
|
||||
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(texture);
|
||||
if (meta == null) return 0;
|
||||
final int mipW = Math.max(1, meta.width() >> level);
|
||||
final int mipH = Math.max(1, meta.height() >> level);
|
||||
return switch (pname) {
|
||||
case GL11.GL_TEXTURE_WIDTH -> mipW;
|
||||
case GL11.GL_TEXTURE_HEIGHT -> mipH;
|
||||
case GL11.GL_TEXTURE_WIDTH -> PixelOps.mipLevelSize(meta.width(), level);
|
||||
case GL11.GL_TEXTURE_HEIGHT -> PixelOps.mipLevelSize(meta.height(), level);
|
||||
case GL11.GL_TEXTURE_INTERNAL_FORMAT -> {
|
||||
final int stored = meta.glFormat();
|
||||
yield TextureInfoCache.isGenericCompressedInternalFormat(stored) ? GL11.GL_RGBA8 : stored;
|
||||
@@ -3296,7 +3494,6 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
@Override public int getInteger(int pname) {
|
||||
final ContextState cs = s();
|
||||
return switch (pname) {
|
||||
case GL11.GL_MAX_TEXTURE_SIZE -> 16384;
|
||||
case GL20.GL_MAX_TEXTURE_IMAGE_UNITS -> 17;
|
||||
case GL20.GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS -> 17;
|
||||
case GL20.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS -> 32;
|
||||
@@ -3354,12 +3551,18 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
return switch (pname) {
|
||||
case GL20.GL_MAX_VERTEX_UNIFORM_COMPONENTS, GL20.GL_MAX_FRAGMENT_UNIFORM_COMPONENTS -> MAX_UNIFORM_COMPONENTS_PER_STAGE;
|
||||
case GL30.GL_MAX_ARRAY_TEXTURE_LAYERS -> MAX_ARRAY_TEXTURE_LAYERS;
|
||||
case GL11.GL_MAX_TEXTURE_SIZE -> MAX_TEXTURE_SIZE;
|
||||
case GL12.GL_MAX_3D_TEXTURE_SIZE -> MAX_3D_TEXTURE_SIZE;
|
||||
case GL13.GL_MAX_CUBE_MAP_TEXTURE_SIZE -> MAX_CUBE_MAP_TEXTURE_SIZE;
|
||||
default -> -1;
|
||||
};
|
||||
}
|
||||
|
||||
static final int MAX_UNIFORM_COMPONENTS_PER_STAGE = 4096 / Float.BYTES;
|
||||
static final int MAX_ARRAY_TEXTURE_LAYERS = 256;
|
||||
static final int MAX_TEXTURE_SIZE = 16384;
|
||||
static final int MAX_CUBE_MAP_TEXTURE_SIZE = 16384;
|
||||
static final int MAX_3D_TEXTURE_SIZE = 2048;
|
||||
|
||||
private static int unknownGetInteger(int pname) {
|
||||
synchronized (UNKNOWN_GET_INTEGER_SEEN) {
|
||||
@@ -3479,9 +3682,9 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
if (texHandle == 0 || frameManager.getCommandBuffer() == 0) return;
|
||||
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(texture);
|
||||
if (meta == null || level >= meta.levels()) return;
|
||||
final int mipW = Math.max(1, meta.width() >> level);
|
||||
final int mipH = Math.max(1, meta.height() >> level);
|
||||
final int mipD = Math.max(1, meta.depth() >> level);
|
||||
final int mipW = PixelOps.mipLevelSize(meta.width(), level);
|
||||
final int mipH = PixelOps.mipLevelSize(meta.height(), level);
|
||||
final int mipD = PixelOps.mipLevelSize(meta.depth(), level);
|
||||
final int bpp = PixelOps.sdlFormatTexelBytes(meta.sdlFormat());
|
||||
if (mipW <= 0 || mipH <= 0 || bpp <= 0) return;
|
||||
|
||||
@@ -3538,9 +3741,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
if (srcTex == 0 || dstTex == 0) return;
|
||||
final ContextState copySt = s();
|
||||
fboClearTracker.materializePendingClearForTexture(copySt, srcTex);
|
||||
if (dstZ != 0 || !fboClearTracker.discardPendingClearIfFullyCovered(copySt, dstTex, dstX, dstY, dstLevel, srcWidth, srcHeight, resourceManager.getTextureMeta(dstName))) {
|
||||
fboClearTracker.materializePendingClearForTexture(copySt, dstTex);
|
||||
}
|
||||
fboClearTracker.resolveDestinationForWrite(copySt, dstTex, resourceManager.getTextureMeta(dstName), dstLevel, dstX, dstY, dstZ, srcWidth, srcHeight);
|
||||
final long cp = frameManager.ensureCopyPass();
|
||||
if (cp == 0) return;
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
@@ -3685,6 +3886,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
pipelineApplier.ensureRenderPass(st);
|
||||
if (!frameManager.isRenderPassActive()) return;
|
||||
if (!pipelineApplier.applyPipelineAndState(st)) return;
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "arraysInstanced", mode, count, 0, first, 0);
|
||||
SDL_DrawGPUPrimitives(frameManager.getRenderPass(), count, primcount, first, 0);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.device;
|
||||
|
||||
import com.mitchej123.lwjgl.MemoryStack;
|
||||
import org.taumc.celeritas.lwjgl.MemoryStack;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
@@ -54,6 +54,28 @@ public final class ContextState {
|
||||
|
||||
public final PixelStoreState pixelStore = new PixelStoreState();
|
||||
|
||||
public static final class ProxyTextureState {
|
||||
public int target;
|
||||
public int width;
|
||||
public int height;
|
||||
public int depth;
|
||||
public int internalFormat;
|
||||
|
||||
public void accept(int target, int width, int height, int depth, int internalFormat) {
|
||||
this.target = target;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.depth = depth;
|
||||
this.internalFormat = internalFormat;
|
||||
}
|
||||
|
||||
public void reject(int target) {
|
||||
accept(target, 0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public final ProxyTextureState proxyTexture = new ProxyTextureState();
|
||||
|
||||
public static final class VAOState {
|
||||
public final boolean[] attribEnabled = new boolean[MAX_VERTEX_ATTRIBS];
|
||||
public int attribEnabledMask;
|
||||
@@ -211,6 +233,7 @@ public final class ContextState {
|
||||
|
||||
public int boundArrayBuffer;
|
||||
public int boundIndirectBuffer;
|
||||
public int boundDispatchIndirectBuffer;
|
||||
public int boundUniformBuffer;
|
||||
public int boundCopyReadBuffer;
|
||||
public int boundCopyWriteBuffer;
|
||||
@@ -226,6 +249,7 @@ public final class ContextState {
|
||||
case GL30.GL_PIXEL_UNPACK_BUFFER -> boundPixelUnpackBuffer;
|
||||
case GL30.GL_PIXEL_PACK_BUFFER -> boundPixelPackBuffer;
|
||||
case GL40.GL_DRAW_INDIRECT_BUFFER -> boundIndirectBuffer;
|
||||
case GL43.GL_DISPATCH_INDIRECT_BUFFER -> boundDispatchIndirectBuffer;
|
||||
case GL31.GL_COPY_READ_BUFFER -> boundCopyReadBuffer;
|
||||
case GL31.GL_COPY_WRITE_BUFFER -> boundCopyWriteBuffer;
|
||||
case GL43.GL_SHADER_STORAGE_BUFFER -> boundSSBO;
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.FboState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.util.MemoryAccess;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.util.ThreadRegistry;
|
||||
@@ -775,7 +776,10 @@ public final class FrameManager {
|
||||
}
|
||||
|
||||
public int getSwapchainFormat() {
|
||||
return device.getSwapchainTextureFormat();
|
||||
if (device.getClaimedWindow() != 0L) return device.getSwapchainTextureFormat();
|
||||
if (resourceManager == null || !finalTarget.isReady()) return 0;
|
||||
final FboState fbo = resourceManager.getFbo(finalTarget.fboId());
|
||||
return fbo != null ? fbo.getColorFormat() : 0;
|
||||
}
|
||||
|
||||
public boolean isFrameActive() {
|
||||
|
||||
@@ -131,7 +131,7 @@ public final class DrawDispatch {
|
||||
}
|
||||
}
|
||||
|
||||
public void drawTriangleFanAsTriangleList(ContextState st, int first, int count) {
|
||||
public boolean drawTriangleFanAsTriangleList(ContextState st, int first, int count) {
|
||||
final int numTriangles = count - 2;
|
||||
final boolean use32bit = (first + count - 1) > 65535;
|
||||
final int bytesPerIndex = use32bit ? 4 : 2;
|
||||
@@ -180,13 +180,14 @@ public final class DrawDispatch {
|
||||
|
||||
setPrimitiveTypeForDraw(st, SDL_GPU_PRIMITIVETYPE_TRIANGLELIST);
|
||||
pipelineApplier.ensureRenderPass(st);
|
||||
if (!frameManager.isRenderPassActive()) return;
|
||||
if (!pipelineApplier.applyPipelineAndState(st)) return;
|
||||
if (!frameManager.isRenderPassActive()) return false;
|
||||
if (!pipelineApplier.applyPipelineAndState(st)) return false;
|
||||
|
||||
final int elementSize = use32bit ? SDL_GPU_INDEXELEMENTSIZE_32BIT : SDL_GPU_INDEXELEMENTSIZE_16BIT;
|
||||
final long rp = frameManager.getRenderPass();
|
||||
bindIndexBufferIfChanged(st, rp, st.fanIndexBuffer, elementSize, uploadOffset);
|
||||
SDL_DrawGPUIndexedPrimitives(rp, numTriangles * 3, 1, 0, 0, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
private long createFanIndexBuffer(int capacity) {
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.pipeline;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.FFPUniformBlock;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.PersistentMapping;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public final class FFPTrace {
|
||||
private static final Logger LOG = LogManager.getLogger("Angelica-SDLGPU");
|
||||
private static final String PREFIX = "[FFPTrace] ";
|
||||
private static final int MAX_KEYS = 512;
|
||||
|
||||
private final ResourceManager resourceManager;
|
||||
private final LongOpenHashSet seenKeys = new LongOpenHashSet();
|
||||
private final StringBuilder sb = new StringBuilder(1024);
|
||||
private int drawsThisFrame;
|
||||
private boolean capReported;
|
||||
|
||||
public FFPTrace(ResourceManager resourceManager) {
|
||||
this.resourceManager = resourceManager;
|
||||
}
|
||||
|
||||
public synchronized void trace(ContextState st, String kind, int mode, int count, int indexType, int first, int baseVertex) {
|
||||
drawsThisFrame++;
|
||||
final long key = key(st);
|
||||
if (seenKeys.contains(key)) return;
|
||||
if (seenKeys.size() >= MAX_KEYS) {
|
||||
if (!capReported) {
|
||||
capReported = true;
|
||||
LOG.info("{}key cap {} reached; no further distinct draw states will be logged", PREFIX, MAX_KEYS);
|
||||
}
|
||||
return;
|
||||
}
|
||||
seenKeys.add(key);
|
||||
LOG.info(format(st, kind, mode, count, indexType, first, baseVertex));
|
||||
}
|
||||
|
||||
public synchronized void frameEnd(FrameManager.FrameState f) {
|
||||
LOG.info("{}frame {} end: draws={} dropped={}", PREFIX, f.frameNumber, drawsThisFrame, f.droppedDrawsThisFrame);
|
||||
drawsThisFrame = 0;
|
||||
}
|
||||
|
||||
private long key(ContextState st) {
|
||||
final PipelineCache p = st.pipeline;
|
||||
long h = p.lastKey();
|
||||
h = Hashing.fmix64(h, st.boundFboId);
|
||||
h = Hashing.fmix64(h, Hashing.packHiLo(st.boundTextures[0], st.boundTextures[1]));
|
||||
h = Hashing.fmix64(h, Hashing.packHiLo(st.scissorEnabled ? 1 : 0, 0));
|
||||
h = Hashing.fmix64(h, Hashing.packHiLo(Float.floatToRawIntBits(st.viewportX), Float.floatToRawIntBits(st.viewportY)));
|
||||
h = Hashing.fmix64(h, Hashing.packHiLo(Float.floatToRawIntBits(st.viewportW), Float.floatToRawIntBits(st.viewportH)));
|
||||
h = Hashing.fmix64(h, Hashing.packHiLo(st.scissorX, st.scissorY));
|
||||
h = Hashing.fmix64(h, Hashing.packHiLo(st.scissorW, st.scissorH));
|
||||
return h;
|
||||
}
|
||||
|
||||
private String format(ContextState st, String kind, int mode, int count, int indexType, int first, int baseVertex) {
|
||||
final PipelineCache p = st.pipeline;
|
||||
final ContextState.VAOState vao = st.currentVao;
|
||||
final ShaderManager.ProgramObject prog = st.boundProgramObj;
|
||||
sb.setLength(0);
|
||||
sb.append(PREFIX);
|
||||
sb.append("kind=").append(kind);
|
||||
sb.append(" mode=0x").append(Integer.toHexString(mode));
|
||||
sb.append(" count=").append(count);
|
||||
if (indexType != 0) sb.append(" indexType=0x").append(Integer.toHexString(indexType));
|
||||
sb.append(" first=").append(first).append(" baseVertex=").append(baseVertex);
|
||||
|
||||
sb.append(" | prog=").append(st.boundProgram);
|
||||
if (prog == null) {
|
||||
sb.append(" progObj=null");
|
||||
} else {
|
||||
sb.append(" ffpUboBinding=").append(prog.externalUboBinding);
|
||||
sb.append(" fragSamplers=").append(prog.fragmentSamplerNames);
|
||||
sb.append(" vsInputs=[");
|
||||
boolean firstName = true;
|
||||
for (int i = 0; i < prog.vertexInputName.length; i++) {
|
||||
final String n = prog.vertexInputName[i];
|
||||
if (n == null) continue;
|
||||
if (!firstName) sb.append(',');
|
||||
firstName = false;
|
||||
sb.append(i).append(':').append(n);
|
||||
}
|
||||
sb.append(']');
|
||||
}
|
||||
|
||||
sb.append(" | fbo=").append(st.boundFboId);
|
||||
sb.append(" vp=(").append(st.viewportX).append(',').append(st.viewportY).append(',')
|
||||
.append(st.viewportW).append(',').append(st.viewportH).append(')');
|
||||
sb.append(" scissor=").append(st.scissorEnabled);
|
||||
if (st.scissorEnabled) {
|
||||
sb.append('(').append(st.scissorX).append(',').append(st.scissorY).append(',')
|
||||
.append(st.scissorW).append(',').append(st.scissorH).append(')');
|
||||
}
|
||||
sb.append(" cull=").append(p.cullEnabled).append("/mode=").append(p.cullFaceMode)
|
||||
.append("/front=").append(p.frontFace).append("/all=").append(p.cullAll);
|
||||
sb.append(" depth=").append(p.depthTestEnabled).append("/func=").append(p.depthCompareOp)
|
||||
.append("/write=").append(p.depthWriteEnabled);
|
||||
sb.append(" blend=").append(p.blendEnabledPerAttachment[0])
|
||||
.append("/src=").append(p.srcColorFactor).append("/dst=").append(p.dstColorFactor)
|
||||
.append("/srcA=").append(p.srcAlphaFactor).append("/dstA=").append(p.dstAlphaFactor)
|
||||
.append("/op=").append(p.colorBlendOp).append('/').append(p.alphaBlendOp);
|
||||
sb.append(" colorMask=0x").append(Integer.toHexString(p.colorWriteMask));
|
||||
sb.append(" primitiveType=").append(p.primitiveType);
|
||||
|
||||
appendTextureUnit(st, 0);
|
||||
appendTextureUnit(st, 1);
|
||||
|
||||
sb.append(" | attribs=[");
|
||||
int enabled = vao.attribEnabledMask;
|
||||
boolean firstAttrib = true;
|
||||
while (enabled != 0) {
|
||||
final int i = Integer.numberOfTrailingZeros(enabled);
|
||||
enabled &= enabled - 1;
|
||||
final int b = vao.attribBinding[i];
|
||||
if (!firstAttrib) sb.append(' ');
|
||||
firstAttrib = false;
|
||||
sb.append(i).append(":size=").append(vao.attribSize[i])
|
||||
.append(",type=0x").append(Integer.toHexString(vao.attribType[i]))
|
||||
.append(",norm=").append(vao.attribNormalized[i])
|
||||
.append(",int=").append(vao.attribIsInteger[i])
|
||||
.append(",stride=").append(vao.attribStride[i])
|
||||
.append(",off=").append(vao.attribOffset[i])
|
||||
.append(",buf=").append(vao.bindingBuffer[b])
|
||||
.append(",bufOff=").append(vao.bindingOffset[b]);
|
||||
}
|
||||
sb.append("] enabledMask=0x").append(Integer.toHexString(vao.attribEnabledMask))
|
||||
.append(" shaderInputMask=0x").append(Integer.toHexString(p.shaderInputMask))
|
||||
.append(" ebo=").append(vao.elementBuffer);
|
||||
|
||||
appendFfpUniforms(st, prog);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void appendTextureUnit(ContextState st, int unit) {
|
||||
final int id = st.boundTextures[unit];
|
||||
sb.append(" | tex").append(unit).append("=").append(id);
|
||||
if (id != 0) {
|
||||
sb.append(" handle=").append(resourceManager.getTextureHandle(id) != 0);
|
||||
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(id);
|
||||
if (meta == null) {
|
||||
sb.append(" meta=null");
|
||||
} else {
|
||||
sb.append(" size=").append(meta.width()).append('x').append(meta.height())
|
||||
.append(" levels=").append(meta.levels())
|
||||
.append(" sdlFormat=").append(meta.sdlFormat())
|
||||
.append(" glFormat=0x").append(Integer.toHexString(meta.glFormat()));
|
||||
}
|
||||
}
|
||||
sb.append(" ffpEnabled=").append(GLStateManager.getTextures().getTextureUnitStates(unit).isEnabled());
|
||||
}
|
||||
|
||||
private void appendFfpUniforms(ContextState st, ShaderManager.ProgramObject prog) {
|
||||
if (prog == null) return;
|
||||
final int binding = prog.externalUboBinding;
|
||||
if (binding < 0 || binding >= ContextState.MAX_INDEXED_BUFFERS) return;
|
||||
final int glId = st.boundUboByIndex[binding];
|
||||
if (glId == 0) {
|
||||
sb.append(" | ffpUbo=unbound");
|
||||
return;
|
||||
}
|
||||
final PersistentMapping pm = resourceManager.getPersistentMapping(glId);
|
||||
final ByteBuffer buf = pm != null ? pm.staging : resourceManager.getUboShadow(glId);
|
||||
if (buf == null) {
|
||||
sb.append(" | ffpUbo=buffer").append(glId).append(" noStaging");
|
||||
return;
|
||||
}
|
||||
final int base = st.uboRangeOffset[binding];
|
||||
sb.append(" | ffpUbo=buffer").append(glId).append(" base=").append(base);
|
||||
appendUboFloat(buf, base, FFPUniformBlock.ALPHA_REF, " u_AlphaRef=");
|
||||
appendUboMatrix(buf, base, FFPUniformBlock.MVP_MATRIX, " u_MVPMatrix=");
|
||||
appendUboMatrix(buf, base, FFPUniformBlock.LIGHTMAP_TEXTURE_MATRIX, " u_LightmapTextureMatrix=");
|
||||
}
|
||||
|
||||
private static float floatIgnoringLimit(ByteBuffer buf, int at) {
|
||||
return MemoryUtil.memGetFloat(MemoryUtil.memAddress0(buf) + at);
|
||||
}
|
||||
|
||||
private void appendUboFloat(ByteBuffer buf, int base, int memberOffset, String label) {
|
||||
final int at = base + memberOffset;
|
||||
sb.append(label);
|
||||
if (at < 0 || at + 4 > buf.capacity()) {
|
||||
sb.append("oob");
|
||||
return;
|
||||
}
|
||||
sb.append(floatIgnoringLimit(buf, at));
|
||||
}
|
||||
|
||||
private void appendUboMatrix(ByteBuffer buf, int base, int memberOffset, String label) {
|
||||
final int at = base + memberOffset;
|
||||
sb.append(label);
|
||||
if (at < 0 || at + 64 > buf.capacity()) {
|
||||
sb.append("oob");
|
||||
return;
|
||||
}
|
||||
sb.append('[');
|
||||
for (int i = 0; i < 16; i++) {
|
||||
if (i > 0) sb.append(',');
|
||||
sb.append(floatIgnoringLimit(buf, at + i * 4));
|
||||
}
|
||||
sb.append(']');
|
||||
}
|
||||
}
|
||||
+14
-11
@@ -337,6 +337,11 @@ public final class PipelineApplier {
|
||||
st.ringChunkUsedBlocks = 0;
|
||||
}
|
||||
|
||||
private int fbo0Height(ContextState st) {
|
||||
final int h = frameManager.getFbo0Height();
|
||||
return h > 0 ? h : (int) (st.viewportY + st.viewportH);
|
||||
}
|
||||
|
||||
public boolean applyPipelineAndState(ContextState st) {
|
||||
return applyPipelineAndState(st, frameManager.frame());
|
||||
}
|
||||
@@ -435,11 +440,14 @@ public final class PipelineApplier {
|
||||
}
|
||||
|
||||
if (st.viewportDirty) {
|
||||
float vpY = st.viewportY;
|
||||
float vpH = st.viewportH;
|
||||
final float vpY;
|
||||
final float vpH;
|
||||
if (renderingToFbo) {
|
||||
vpY = st.viewportY + st.viewportH;
|
||||
vpH = -st.viewportH;
|
||||
} else {
|
||||
vpY = fbo0Height(st) - st.viewportY - st.viewportH;
|
||||
vpH = st.viewportH;
|
||||
}
|
||||
final long addr = st.cachedViewport.address();
|
||||
MemoryAccess.putFloat(addr + SDL_GPUViewport.X, st.viewportX);
|
||||
@@ -455,15 +463,9 @@ public final class PipelineApplier {
|
||||
if (st.scissorDirty) {
|
||||
final long addr = st.cachedScissor.address();
|
||||
final int sx, sy, sw, sh;
|
||||
final int fbHeight;
|
||||
if (st.scissorEnabled) {
|
||||
if (renderingToFbo) {
|
||||
fbHeight = (fboState != null && fboState.height > 0) ? fboState.height : (int)(st.viewportY + st.viewportH);
|
||||
} else {
|
||||
fbHeight = (int)(st.viewportY + st.viewportH);
|
||||
}
|
||||
sx = st.scissorX;
|
||||
sy = fbHeight - st.scissorY - st.scissorH;
|
||||
sy = renderingToFbo ? st.scissorY : (fbo0Height(st) - st.scissorY - st.scissorH);
|
||||
sw = st.scissorW;
|
||||
sh = st.scissorH;
|
||||
MemoryAccess.putInt(addr + SDL_Rect.X, sx);
|
||||
@@ -471,11 +473,12 @@ public final class PipelineApplier {
|
||||
MemoryAccess.putInt(addr + SDL_Rect.W, sw);
|
||||
MemoryAccess.putInt(addr + SDL_Rect.H, sh);
|
||||
if ((sw <= 0 || sh <= 0) && bogusScissorWarned.add(Hashing.packHiLo(sw, sh))) {
|
||||
LOG.warn("applyPipelineAndState: scissor degenerate (x={} y={} w={} h={}) - all fragments clipped; boundProgram={} boundFbo={} fbHeight={} scissorEn={} src=[{},{},{},{}]", sx, sy, sw, sh, st.boundProgram, st.boundFboId, fbHeight, st.scissorEnabled, st.scissorX, st.scissorY, st.scissorW, st.scissorH);
|
||||
LOG.warn("applyPipelineAndState: scissor degenerate (x={} y={} w={} h={}) - all fragments clipped; boundProgram={} boundFbo={} scissorEn={} src=[{},{},{},{}]", sx, sy, sw, sh, st.boundProgram, st.boundFboId, st.scissorEnabled, st.scissorX, st.scissorY, st.scissorW, st.scissorH);
|
||||
}
|
||||
} else {
|
||||
final int rectY = renderingToFbo ? (int) st.viewportY : (int) (fbo0Height(st) - st.viewportY - st.viewportH);
|
||||
MemoryAccess.putInt(addr + SDL_Rect.X, (int) st.viewportX);
|
||||
MemoryAccess.putInt(addr + SDL_Rect.Y, (int) st.viewportY);
|
||||
MemoryAccess.putInt(addr + SDL_Rect.Y, rectY);
|
||||
MemoryAccess.putInt(addr + SDL_Rect.W, (int) st.viewportW);
|
||||
MemoryAccess.putInt(addr + SDL_Rect.H, (int) st.viewportH);
|
||||
}
|
||||
|
||||
@@ -185,6 +185,10 @@ public final class PipelineCache {
|
||||
this.maxAttribs = ContextState.MAX_VERTEX_ATTRIBS;
|
||||
}
|
||||
|
||||
long lastKey() {
|
||||
return lastKey;
|
||||
}
|
||||
|
||||
public void markInputDirty() { inputDirty = true; lastKey = 0L; }
|
||||
|
||||
public boolean markInputDirtyIfLivenessChanged(PipelineStore store, int oldBuffer, int newBuffer) {
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.resource;
|
||||
|
||||
public final class CopyRectClip {
|
||||
|
||||
public static final long EMPTY = 0L;
|
||||
|
||||
private static final long FIELD_MASK = 0xFFFFL;
|
||||
|
||||
private CopyRectClip() {}
|
||||
|
||||
public static long clipCopyRect(int srcX, int srcY, int dstX, int dstY, int width, int height,
|
||||
int srcWidth, int srcHeight, int dstWidth, int dstHeight) {
|
||||
int sx = srcX;
|
||||
int sy = srcY;
|
||||
int dx = dstX;
|
||||
int dy = dstY;
|
||||
int w = width;
|
||||
int h = height;
|
||||
if (sx < 0) {
|
||||
w += sx;
|
||||
dx -= sx;
|
||||
sx = 0;
|
||||
}
|
||||
if (sy < 0) {
|
||||
h += sy;
|
||||
dy -= sy;
|
||||
sy = 0;
|
||||
}
|
||||
if (dx < 0) {
|
||||
w += dx;
|
||||
sx -= dx;
|
||||
dx = 0;
|
||||
}
|
||||
if (dy < 0) {
|
||||
h += dy;
|
||||
sy -= dy;
|
||||
dy = 0;
|
||||
}
|
||||
if (srcWidth > 0 && sx + w > srcWidth) w = srcWidth - sx;
|
||||
if (srcHeight > 0 && sy + h > srcHeight) h = srcHeight - sy;
|
||||
if (dstWidth > 0 && dx + w > dstWidth) w = dstWidth - dx;
|
||||
if (dstHeight > 0 && dy + h > dstHeight) h = dstHeight - dy;
|
||||
if (w <= 0 || h <= 0) return EMPTY;
|
||||
if ((sx | sy | w | h) > FIELD_MASK) return EMPTY;
|
||||
return (sx & FIELD_MASK) | ((sy & FIELD_MASK) << 16) | ((w & FIELD_MASK) << 32) | ((h & FIELD_MASK) << 48);
|
||||
}
|
||||
|
||||
public static int srcX(long clip) {
|
||||
return (int) (clip & FIELD_MASK);
|
||||
}
|
||||
|
||||
public static int srcY(long clip) {
|
||||
return (int) ((clip >>> 16) & FIELD_MASK);
|
||||
}
|
||||
|
||||
public static int width(long clip) {
|
||||
return (int) ((clip >>> 32) & FIELD_MASK);
|
||||
}
|
||||
|
||||
public static int height(long clip) {
|
||||
return (int) ((clip >>> 48) & FIELD_MASK);
|
||||
}
|
||||
|
||||
public static int dstX(long clip, int origSrcX, int origDstX) {
|
||||
return origDstX + (srcX(clip) - origSrcX);
|
||||
}
|
||||
|
||||
public static int dstY(long clip, int origSrcY, int origDstY) {
|
||||
return origDstY + (srcY(clip) - origSrcY);
|
||||
}
|
||||
}
|
||||
@@ -242,6 +242,12 @@ public final class FBOClearTracker {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void resolveDestinationForWrite(ContextState st, long destTex, ResourceManager.TextureMeta destMeta, int level, int dx, int dy, int dz, int w, int h) {
|
||||
if (dz != 0 || !discardPendingClearIfFullyCovered(st, destTex, dx, dy, level, w, h, destMeta)) {
|
||||
materializePendingClearForTexture(st, destTex);
|
||||
}
|
||||
}
|
||||
|
||||
public void materializePendingClearForTexture(ContextState st, long handle) {
|
||||
if (handle == 0) return;
|
||||
final boolean depthPending = st.pendingDepthTextures.remove(handle);
|
||||
|
||||
@@ -13,6 +13,12 @@ import static org.lwjgl.sdl.SDLGPU.*;
|
||||
public final class PixelOps {
|
||||
private PixelOps() {}
|
||||
|
||||
public static int mipLevelSize(int base, int level) {
|
||||
if (base <= 0 || level < 0) return 0;
|
||||
if (level >= Integer.SIZE - 1) return 1;
|
||||
return Math.max(1, base >> level);
|
||||
}
|
||||
|
||||
public static int defaultMipLevels(int width, int height) {
|
||||
final int max = Math.max(1, Math.max(width, height));
|
||||
return Math.max(1, 32 - Integer.numberOfLeadingZeros(max));
|
||||
|
||||
+18
-4
@@ -2208,7 +2208,24 @@ public final class ResourceManager {
|
||||
return mapTextureFormat(glFormat, preferredD24, preferredD24S8);
|
||||
}
|
||||
|
||||
public boolean isMappableTextureFormat(int glFormat) {
|
||||
return lookupTextureFormat(glFormat, preferredD24, preferredD24S8) != SDL_GPU_TEXTUREFORMAT_INVALID;
|
||||
}
|
||||
|
||||
public static int mapTextureFormat(int glFormat, int preferredD24, int preferredD24S8) {
|
||||
final int mapped = lookupTextureFormat(glFormat, preferredD24, preferredD24S8);
|
||||
if (mapped != SDL_GPU_TEXTUREFORMAT_INVALID) return mapped;
|
||||
synchronized (UNMAPPED_TEXTURE_FORMAT_SEEN) {
|
||||
if (UNMAPPED_TEXTURE_FORMAT_SEEN.add(glFormat)) {
|
||||
LOG.warn("Unmapped GL texture format: 0x{}", Integer.toHexString(glFormat));
|
||||
}
|
||||
}
|
||||
return SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
|
||||
}
|
||||
|
||||
private static final IntOpenHashSet UNMAPPED_TEXTURE_FORMAT_SEEN = new IntOpenHashSet();
|
||||
|
||||
private static int lookupTextureFormat(int glFormat, int preferredD24, int preferredD24S8) {
|
||||
return switch (glFormat) {
|
||||
case GL11.GL_RGBA, GL11.GL_RGBA8 -> SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
|
||||
case GL11.GL_RGB, GL11.GL_RGB8 -> SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM; // RGB -> RGBA promotion
|
||||
@@ -2262,10 +2279,7 @@ public final class ResourceManager {
|
||||
case GL31.GL_RGBA8_SNORM -> SDL_GPU_TEXTUREFORMAT_R8G8B8A8_SNORM;
|
||||
case GL31.GL_RGBA16_SNORM -> SDL_GPU_TEXTUREFORMAT_R16G16B16A16_SNORM;
|
||||
case GL31.GL_RGB16_SNORM -> SDL_GPU_TEXTUREFORMAT_R16G16B16A16_SNORM; // RGB SNORM -> RGBA SNORM promotion
|
||||
default -> {
|
||||
LOG.warn("Unmapped GL texture format: 0x{}", Integer.toHexString(glFormat));
|
||||
yield SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
|
||||
}
|
||||
default -> SDL_GPU_TEXTUREFORMAT_INVALID;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ import org.lwjgl.system.MemoryUtil;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static org.lwjgl.sdl.SDLGPU.*;
|
||||
import static org.lwjgl.sdl.SDLSurface.SDL_FLIP_NONE;
|
||||
import static org.lwjgl.sdl.SDLSurface.SDL_FLIP_VERTICAL;
|
||||
|
||||
public final class TextureOps {
|
||||
private static final Logger LOG = LogManager.getLogger("Angelica-SDLGPU");
|
||||
@@ -113,6 +115,11 @@ public final class TextureOps {
|
||||
final long destTex = resourceManager.getTextureHandle(destGlId);
|
||||
if (destTex == 0) return;
|
||||
|
||||
if (st.boundReadFboId == 0) {
|
||||
copyFromDefaultFramebuffer(st, destGlId, destTex, level, xoffset, yoffset, x, y, width, height);
|
||||
return;
|
||||
}
|
||||
|
||||
final FboState fbo = resourceManager.getFbo(st.boundReadFboId);
|
||||
if (fbo == null) return;
|
||||
|
||||
@@ -121,35 +128,81 @@ public final class TextureOps {
|
||||
final long srcTex = isDepthDest ? fbo.depthTexture : fbo.colorTextures[fbo.readBufferIndex];
|
||||
if (srcTex == 0) return;
|
||||
|
||||
if (isDepthDest) {
|
||||
copyTexture(srcTex, x, y, destTex, xoffset, yoffset, width, height);
|
||||
return;
|
||||
}
|
||||
|
||||
final int srcGlId = fbo.colorGlIds[fbo.readBufferIndex];
|
||||
final int srcGlId = isDepthDest ? fbo.depthGlId : fbo.colorGlIds[fbo.readBufferIndex];
|
||||
final ResourceManager.TextureMeta srcMeta = srcGlId != 0 ? resourceManager.getTextureMeta(srcGlId) : null;
|
||||
if (srcMeta != null && destMeta != null && srcMeta.sdlFormat() == destMeta.sdlFormat()) {
|
||||
copyTexture(srcTex, x, y, destTex, xoffset, yoffset, width, height);
|
||||
final long clip = CopyRectClip.clipCopyRect(x, y, xoffset, yoffset, width, height, srcMeta != null ? srcMeta.width() : fbo.width, srcMeta != null ? srcMeta.height() : fbo.height, levelWidth(destMeta, level), levelHeight(destMeta, level));
|
||||
if (clip == CopyRectClip.EMPTY) return;
|
||||
final int sx = CopyRectClip.srcX(clip);
|
||||
final int sy = CopyRectClip.srcY(clip);
|
||||
final int dx = CopyRectClip.dstX(clip, x, xoffset);
|
||||
final int dy = CopyRectClip.dstY(clip, y, yoffset);
|
||||
final int w = CopyRectClip.width(clip);
|
||||
final int h = CopyRectClip.height(clip);
|
||||
|
||||
fboClearTracker.materializePendingClearForTexture(st, srcTex);
|
||||
fboClearTracker.resolveDestinationForWrite(st, destTex, destMeta, level, dx, dy, 0, w, h);
|
||||
|
||||
if (isDepthDest || (srcMeta != null && destMeta != null && srcMeta.sdlFormat() == destMeta.sdlFormat())) {
|
||||
copyTexture(srcTex, sx, sy, destTex, level, dx, dy, w, h);
|
||||
} else {
|
||||
blitTexture(srcTex, x, y, width, height, destTex, xoffset, yoffset, width, height, GL11.GL_NEAREST);
|
||||
blitTexture(srcTex, sx, sy, w, h, destTex, level, dx, dy, w, h, GL11.GL_NEAREST);
|
||||
}
|
||||
}
|
||||
|
||||
private static int levelWidth(ResourceManager.TextureMeta meta, int level) {
|
||||
return meta != null ? PixelOps.mipLevelSize(meta.width(), level) : 0;
|
||||
}
|
||||
|
||||
private static int levelHeight(ResourceManager.TextureMeta meta, int level) {
|
||||
return meta != null ? PixelOps.mipLevelSize(meta.height(), level) : 0;
|
||||
}
|
||||
|
||||
private void copyFromDefaultFramebuffer(ContextState st, int destGlId, long destTex, int level, int xoffset, int yoffset, int x, int y, int width, int height) {
|
||||
final ResourceManager.TextureMeta destMeta = resourceManager.getTextureMeta(destGlId);
|
||||
if (destMeta != null && PixelOps.isDepthFormat(destMeta.sdlFormat())) return;
|
||||
|
||||
final long srcTex = frameManager.getFbo0Texture();
|
||||
if (srcTex == 0) return;
|
||||
final int srcFullW = frameManager.getFbo0Width();
|
||||
final int srcFullH = frameManager.getFbo0Height();
|
||||
if (srcFullW <= 0 || srcFullH <= 0) return;
|
||||
|
||||
final long clip = CopyRectClip.clipCopyRect(x, y, xoffset, yoffset, width, height, srcFullW, srcFullH, levelWidth(destMeta, level), levelHeight(destMeta, level));
|
||||
if (clip == CopyRectClip.EMPTY) return;
|
||||
final int sx = CopyRectClip.srcX(clip);
|
||||
final int sy = CopyRectClip.srcY(clip);
|
||||
final int dx = CopyRectClip.dstX(clip, x, xoffset);
|
||||
final int dy = CopyRectClip.dstY(clip, y, yoffset);
|
||||
final int w = CopyRectClip.width(clip);
|
||||
final int h = CopyRectClip.height(clip);
|
||||
|
||||
if (st.pendingSwapchainClear || st.pendingSwapchainDepthClear || st.pendingSwapchainStencilClear) {
|
||||
frameManager.ensureFbo0RenderPass(frameManager.frame(), st);
|
||||
}
|
||||
fboClearTracker.resolveDestinationForWrite(st, destTex, destMeta, level, dx, dy, 0, w, h);
|
||||
|
||||
blitTexture(srcTex, sx, srcFullH - sy - h, w, h, destTex, level, dx, dy, w, h, GL11.GL_NEAREST, SDL_FLIP_VERTICAL);
|
||||
}
|
||||
|
||||
public static boolean canCopyInsteadOfBlit(ResourceManager.TextureMeta src, ResourceManager.TextureMeta dst, int srcW, int srcH, int dstW, int dstH) {
|
||||
return src != null && dst != null && src.sdlFormat() == dst.sdlFormat() && srcW > 0 && srcH > 0 && srcW == dstW && srcH == dstH;
|
||||
}
|
||||
|
||||
public void copyTexture(long srcTex, int srcX, int srcY, long dstTex, int dstX, int dstY, int w, int h) {
|
||||
public void copyTexture(long srcTex, int srcX, int srcY, long dstTex, int dstLevel, int dstX, int dstY, int w, int h) {
|
||||
final long cp = frameManager.ensureCopyPass();
|
||||
if (cp == 0) return;
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
final var src = SDL_GPUTextureLocation.calloc(stack).texture(srcTex).x(srcX).y(srcY);
|
||||
final var dst = SDL_GPUTextureLocation.calloc(stack).texture(dstTex).x(dstX).y(dstY);
|
||||
final var dst = SDL_GPUTextureLocation.calloc(stack).texture(dstTex).mip_level(dstLevel).x(dstX).y(dstY);
|
||||
SDL_CopyGPUTextureToTexture(cp, src, dst, w, h, 1, false);
|
||||
}
|
||||
}
|
||||
|
||||
public void blitTexture(long srcTex, int srcX, int srcY, int srcW, int srcH, long dstTex, int dstX, int dstY, int dstW, int dstH, int glFilter) {
|
||||
public void blitTexture(long srcTex, int srcX, int srcY, int srcW, int srcH, long dstTex, int dstLevel, int dstX, int dstY, int dstW, int dstH, int glFilter) {
|
||||
blitTexture(srcTex, srcX, srcY, srcW, srcH, dstTex, dstLevel, dstX, dstY, dstW, dstH, glFilter, SDL_FLIP_NONE);
|
||||
}
|
||||
|
||||
public void blitTexture(long srcTex, int srcX, int srcY, int srcW, int srcH, long dstTex, int dstLevel, int dstX, int dstY, int dstW, int dstH, int glFilter, int flipMode) {
|
||||
frameManager.endCopyPassIfActive();
|
||||
frameManager.endRenderPassIfActive();
|
||||
final long cb = frameManager.getCommandBuffer();
|
||||
@@ -158,9 +211,9 @@ public final class TextureOps {
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
final var info = SDL_GPUBlitInfo.calloc(stack);
|
||||
info.source(s -> s.texture(srcTex).x(srcX).y(srcY).w(srcW).h(srcH));
|
||||
info.destination(d -> d.texture(dstTex).x(dstX).y(dstY).w(dstW).h(dstH));
|
||||
info.destination(d -> d.texture(dstTex).mip_level(dstLevel).x(dstX).y(dstY).w(dstW).h(dstH));
|
||||
info.filter(glFilter == GL11.GL_LINEAR ? SDL_GPU_FILTER_LINEAR : SDL_GPU_FILTER_NEAREST);
|
||||
info.flip_mode(0);
|
||||
info.flip_mode(flipMode);
|
||||
frameManager.noteBlit();
|
||||
SDL_BlitGPUTexture(cb, info);
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrays;
|
||||
import it.unimi.dsi.fastutil.ints.IntIterator;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
@@ -599,29 +600,84 @@ public final class ShaderManager {
|
||||
|
||||
private static int patchAttribLocations(ByteBuffer spirv, StageReflection vs, Object2IntOpenHashMap<String> bindings, int[] outVecSize, int[] outBaseType, String[] outName, Object2IntOpenHashMap<String> resolvedOut) {
|
||||
final IntBuffer vsBuf = spirv.asIntBuffer();
|
||||
int mask = 0;
|
||||
if (outName != null) Arrays.fill(outName, null);
|
||||
for (VsInput vi : vs.vsInputs()) {
|
||||
int finalLoc = vi.originalLocation();
|
||||
final int desired = bindings.getInt(vi.name());
|
||||
if (desired != -1 && desired != vi.originalLocation()) {
|
||||
if (vsBuf.get(vi.binaryOffset()) != vi.originalLocation()) {
|
||||
LOG.error("applyAttribLocations: sanity check failed for '{}' (expected {}, got {})", vi.name(), vi.originalLocation(), vsBuf.get(vi.binaryOffset()));
|
||||
} else {
|
||||
vsBuf.put(vi.binaryOffset(), desired);
|
||||
finalLoc = desired;
|
||||
final List<VsInput> inputs = vs.vsInputs();
|
||||
final int count = inputs.size();
|
||||
final int[] finalLoc = new int[count];
|
||||
int taken = 0;
|
||||
|
||||
for (final IntIterator it = bindings.values().iterator(); it.hasNext(); ) {
|
||||
final int bound = it.nextInt();
|
||||
if (bound >= 0 && bound < ContextState.MAX_VERTEX_ATTRIBS) taken |= 1 << bound;
|
||||
}
|
||||
for (int i = 0; i < count; i++) {
|
||||
finalLoc[i] = bindings.getInt(inputs.get(i).name());
|
||||
}
|
||||
|
||||
int movers = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (finalLoc[i] != -1) continue;
|
||||
final int original = inputs.get(i).originalLocation();
|
||||
if (original >= 0 && original < ContextState.MAX_VERTEX_ATTRIBS && (taken & (1 << original)) == 0) {
|
||||
finalLoc[i] = original;
|
||||
taken |= 1 << original;
|
||||
} else {
|
||||
movers++;
|
||||
}
|
||||
}
|
||||
|
||||
for (int n = 0; n < movers; n++) {
|
||||
int pick = -1;
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (finalLoc[i] == -1 && (pick == -1 || inputs.get(i).originalLocation() < inputs.get(pick).originalLocation())) {
|
||||
pick = i;
|
||||
}
|
||||
}
|
||||
if (resolvedOut != null) resolvedOut.put(vi.name(), finalLoc);
|
||||
if (finalLoc < 0 || finalLoc >= 16) continue;
|
||||
mask |= (1 << finalLoc);
|
||||
outVecSize[finalLoc] = vi.vecSize();
|
||||
outBaseType[finalLoc] = vi.baseType();
|
||||
if (outName != null) outName[finalLoc] = vi.name();
|
||||
final int free = Integer.numberOfTrailingZeros(~taken);
|
||||
if (free >= ContextState.MAX_VERTEX_ATTRIBS) {
|
||||
LOG.error("applyAttribLocations: no free vertex attribute slot for '{}'; leaving it at {}", inputs.get(pick).name(), inputs.get(pick).originalLocation());
|
||||
finalLoc[pick] = inputs.get(pick).originalLocation();
|
||||
} else {
|
||||
finalLoc[pick] = free;
|
||||
taken |= 1 << free;
|
||||
}
|
||||
}
|
||||
|
||||
int mask = 0;
|
||||
int seen = 0;
|
||||
if (outName != null) Arrays.fill(outName, null);
|
||||
for (int i = 0; i < count; i++) {
|
||||
final VsInput vi = inputs.get(i);
|
||||
int loc = finalLoc[i];
|
||||
if (loc != vi.originalLocation()) {
|
||||
if (vsBuf.get(vi.binaryOffset()) != vi.originalLocation()) {
|
||||
LOG.error("applyAttribLocations: sanity check failed for '{}' (expected {}, got {})", vi.name(), vi.originalLocation(), vsBuf.get(vi.binaryOffset()));
|
||||
loc = vi.originalLocation();
|
||||
} else {
|
||||
vsBuf.put(vi.binaryOffset(), loc);
|
||||
}
|
||||
}
|
||||
finalLoc[i] = loc;
|
||||
if (resolvedOut != null) resolvedOut.put(vi.name(), loc);
|
||||
if (loc < 0 || loc >= ContextState.MAX_VERTEX_ATTRIBS) continue;
|
||||
if ((seen & (1 << loc)) != 0) {
|
||||
LOG.error("applyAttribLocations: '{}' and '{}' both resolved to location {}", inputNameAtLocation(inputs, finalLoc, i, loc), vi.name(), loc);
|
||||
}
|
||||
seen |= 1 << loc;
|
||||
mask |= 1 << loc;
|
||||
outVecSize[loc] = vi.vecSize();
|
||||
outBaseType[loc] = vi.baseType();
|
||||
if (outName != null) outName[loc] = vi.name();
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
private static String inputNameAtLocation(List<VsInput> inputs, int[] finalLoc, int limit, int loc) {
|
||||
for (int i = 0; i < limit; i++) {
|
||||
if (finalLoc[i] == loc) return inputs.get(i).name();
|
||||
}
|
||||
return "<unknown>";
|
||||
}
|
||||
|
||||
public VertexVariant getOrBuildVertexVariant(int program, long key, List<UscaledRetype.Attrib> attribs) {
|
||||
final ProgramObject prog = programObjects.get(program);
|
||||
if (prog == null || !prog.linked || prog.vertexSource.isEmpty()) return null;
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
com.gtnewhorizons.angelica.sdlgpu.SDLGPULWJGLService
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu;
|
||||
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.opengl.GL40;
|
||||
import org.lwjgl.opengl.GL43;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class DispatchIndirectBindingTest {
|
||||
|
||||
@AfterEach
|
||||
void resetContextState() {
|
||||
final ContextState st = SdlTestRig.contextState();
|
||||
st.boundIndirectBuffer = 0;
|
||||
st.boundDispatchIndirectBuffer = 0;
|
||||
}
|
||||
|
||||
@Test
|
||||
void dispatchIndirectHasItsOwnBindingPoint() {
|
||||
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
|
||||
final ContextState st = SdlTestRig.contextState();
|
||||
|
||||
backend.bindBuffer(GL40.GL_DRAW_INDIRECT_BUFFER, 11);
|
||||
backend.bindBuffer(GL43.GL_DISPATCH_INDIRECT_BUFFER, 22);
|
||||
|
||||
assertEquals(11, st.getBoundBuffer(GL40.GL_DRAW_INDIRECT_BUFFER));
|
||||
assertEquals(22, st.getBoundBuffer(GL43.GL_DISPATCH_INDIRECT_BUFFER));
|
||||
}
|
||||
|
||||
@Test
|
||||
void bindingDispatchIndirectLeavesTheDrawIndirectSlotAlone() {
|
||||
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
|
||||
final ContextState st = SdlTestRig.contextState();
|
||||
|
||||
backend.bindBuffer(GL40.GL_DRAW_INDIRECT_BUFFER, 33);
|
||||
backend.bindBuffer(GL43.GL_DISPATCH_INDIRECT_BUFFER, 44);
|
||||
backend.bindBuffer(GL43.GL_DISPATCH_INDIRECT_BUFFER, 0);
|
||||
|
||||
assertEquals(33, st.getBoundBuffer(GL40.GL_DRAW_INDIRECT_BUFFER));
|
||||
assertEquals(0, st.getBoundBuffer(GL43.GL_DISPATCH_INDIRECT_BUFFER));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu;
|
||||
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.opengl.EXTTextureFilterAnisotropic;
|
||||
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.GL40;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class ProxyTextureLimitsTest {
|
||||
|
||||
@AfterEach
|
||||
void resetContextState() {
|
||||
final ContextState st = SdlTestRig.contextState();
|
||||
st.activeTextureUnit = 0;
|
||||
st.boundTextures[0] = 0;
|
||||
st.proxyTexture.reject(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void threeDimensionalProxyIsBoundedByTheVolumeLimit() {
|
||||
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
|
||||
final int limit = SDLGPURenderBackend.MAX_3D_TEXTURE_SIZE;
|
||||
|
||||
backend.texImage3D(GL12.GL_PROXY_TEXTURE_3D, 0, GL11.GL_RGBA, limit, limit, limit, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
assertEquals(limit, backend.getTexLevelParameteri(GL12.GL_PROXY_TEXTURE_3D, 0, GL11.GL_TEXTURE_WIDTH));
|
||||
assertEquals(limit, backend.getTexLevelParameteri(GL12.GL_PROXY_TEXTURE_3D, 0, GL12.GL_TEXTURE_DEPTH));
|
||||
|
||||
backend.texImage3D(GL12.GL_PROXY_TEXTURE_3D, 0, GL11.GL_RGBA, limit * 2, limit, limit, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
assertEquals(0, backend.getTexLevelParameteri(GL12.GL_PROXY_TEXTURE_3D, 0, GL11.GL_TEXTURE_WIDTH));
|
||||
|
||||
backend.texImage3D(GL12.GL_PROXY_TEXTURE_3D, 0, GL11.GL_RGBA, limit, limit, limit * 2, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
assertEquals(0, backend.getTexLevelParameteri(GL12.GL_PROXY_TEXTURE_3D, 0, GL12.GL_TEXTURE_DEPTH));
|
||||
}
|
||||
|
||||
@Test
|
||||
void everyProxyDimensionLimitIsAlsoQueryableAsADeviceLimit() {
|
||||
assertEquals(16384, SDLGPURenderBackend.deviceLimit(GL11.GL_MAX_TEXTURE_SIZE));
|
||||
assertEquals(2048, SDLGPURenderBackend.deviceLimit(GL12.GL_MAX_3D_TEXTURE_SIZE));
|
||||
assertEquals(16384, SDLGPURenderBackend.deviceLimit(GL13.GL_MAX_CUBE_MAP_TEXTURE_SIZE));
|
||||
|
||||
assertEquals(16384, SDLGPURenderBackend.proxyDimensionLimit(GL11.GL_PROXY_TEXTURE_2D));
|
||||
assertEquals(16384, SDLGPURenderBackend.proxyDimensionLimit(GL11.GL_PROXY_TEXTURE_1D));
|
||||
assertEquals(2048, SDLGPURenderBackend.proxyDimensionLimit(GL12.GL_PROXY_TEXTURE_3D));
|
||||
assertEquals(16384, SDLGPURenderBackend.proxyDimensionLimit(GL13.GL_PROXY_TEXTURE_CUBE_MAP));
|
||||
assertEquals(16384, SDLGPURenderBackend.proxyDimensionLimit(GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY));
|
||||
}
|
||||
|
||||
@Test
|
||||
void oneDimensionalArrayLayersUseTheLayerLimitNotTheDimensionLimit() {
|
||||
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
|
||||
final int layers = SDLGPURenderBackend.MAX_ARRAY_TEXTURE_LAYERS;
|
||||
|
||||
backend.texImage2D(GL30.GL_PROXY_TEXTURE_1D_ARRAY, 0, GL11.GL_RGBA, 512, layers, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
assertEquals(512, backend.getTexLevelParameteri(GL30.GL_PROXY_TEXTURE_1D_ARRAY, 0, GL11.GL_TEXTURE_WIDTH));
|
||||
assertEquals(layers, backend.getTexLevelParameteri(GL30.GL_PROXY_TEXTURE_1D_ARRAY, 0, GL11.GL_TEXTURE_HEIGHT));
|
||||
|
||||
backend.texImage2D(GL30.GL_PROXY_TEXTURE_1D_ARRAY, 0, GL11.GL_RGBA, 512, layers + 1, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
assertEquals(0, backend.getTexLevelParameteri(GL30.GL_PROXY_TEXTURE_1D_ARRAY, 0, GL11.GL_TEXTURE_WIDTH));
|
||||
}
|
||||
|
||||
@Test
|
||||
void arrayLayerCountDoesNotHalvePerLevel() {
|
||||
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
|
||||
|
||||
backend.texImage2D(GL30.GL_PROXY_TEXTURE_1D_ARRAY, 2, GL11.GL_RGBA, 128, 64, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
assertEquals(128, backend.getTexLevelParameteri(GL30.GL_PROXY_TEXTURE_1D_ARRAY, 2, GL11.GL_TEXTURE_WIDTH));
|
||||
assertEquals(64, backend.getTexLevelParameteri(GL30.GL_PROXY_TEXTURE_1D_ARRAY, 2, GL11.GL_TEXTURE_HEIGHT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void aProbedLevelIsAcceptedOnItsLevelZeroEquivalentSize() {
|
||||
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
|
||||
|
||||
backend.texImage2D(GL11.GL_PROXY_TEXTURE_2D, 3, GL11.GL_RGBA, 1024, 512, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
assertEquals(1024, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 3, GL11.GL_TEXTURE_WIDTH));
|
||||
assertEquals(512, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 3, GL11.GL_TEXTURE_HEIGHT));
|
||||
assertEquals(8192, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH));
|
||||
}
|
||||
|
||||
@Test
|
||||
void aProbedLevelWhoseBaseWouldOverflowTheLimitIsRejected() {
|
||||
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
|
||||
|
||||
backend.texImage2D(GL11.GL_PROXY_TEXTURE_2D, 2, GL11.GL_RGBA, SDLGPURenderBackend.MAX_TEXTURE_SIZE, 4, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
assertEquals(0, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 2, GL11.GL_TEXTURE_WIDTH));
|
||||
|
||||
backend.texImage2D(GL11.GL_PROXY_TEXTURE_2D, 40, GL11.GL_RGBA, 4, 4, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
assertEquals(0, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 40, GL11.GL_TEXTURE_WIDTH));
|
||||
}
|
||||
|
||||
@Test
|
||||
void levelZeroEquivalentSizeRejectsOverflowAndNonsense() {
|
||||
assertEquals(4, SDLGPURenderBackend.levelZeroEquivalentSize(4, 0));
|
||||
assertEquals(32, SDLGPURenderBackend.levelZeroEquivalentSize(4, 3));
|
||||
assertEquals(0, SDLGPURenderBackend.levelZeroEquivalentSize(4, -1));
|
||||
assertEquals(0, SDLGPURenderBackend.levelZeroEquivalentSize(0, 2));
|
||||
assertEquals(0, SDLGPURenderBackend.levelZeroEquivalentSize(4, 31));
|
||||
assertEquals(0, SDLGPURenderBackend.levelZeroEquivalentSize(4, 30));
|
||||
assertEquals(1 << 30, SDLGPURenderBackend.levelZeroEquivalentSize(1, 30));
|
||||
}
|
||||
|
||||
@Test
|
||||
void proxyTargetsAnswerTheGlParameterDefaults() {
|
||||
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
|
||||
final int target = GL11.GL_PROXY_TEXTURE_2D;
|
||||
|
||||
assertEquals(GL11.GL_NEAREST_MIPMAP_LINEAR, backend.getTexParameteri(target, GL11.GL_TEXTURE_MIN_FILTER));
|
||||
assertEquals(GL11.GL_LINEAR, backend.getTexParameteri(target, GL11.GL_TEXTURE_MAG_FILTER));
|
||||
assertEquals(GL11.GL_REPEAT, backend.getTexParameteri(target, GL11.GL_TEXTURE_WRAP_S));
|
||||
assertEquals(GL11.GL_REPEAT, backend.getTexParameteri(target, GL11.GL_TEXTURE_WRAP_T));
|
||||
assertEquals(GL11.GL_REPEAT, backend.getTexParameteri(target, GL12.GL_TEXTURE_WRAP_R));
|
||||
assertEquals(0, backend.getTexParameteri(target, GL12.GL_TEXTURE_BASE_LEVEL));
|
||||
assertEquals(1000, backend.getTexParameteri(target, GL12.GL_TEXTURE_MAX_LEVEL));
|
||||
assertEquals(-1000.0f, backend.getTexParameterf(target, GL12.GL_TEXTURE_MIN_LOD));
|
||||
assertEquals(1000.0f, backend.getTexParameterf(target, GL12.GL_TEXTURE_MAX_LOD));
|
||||
assertEquals(0.0f, backend.getTexParameterf(target, GL14.GL_TEXTURE_LOD_BIAS));
|
||||
assertEquals(1.0f, backend.getTexParameterf(target, EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void proxyParameterDefaultsIgnoreTheBoundTexturesOwnState() {
|
||||
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
|
||||
final ContextState st = SdlTestRig.contextState();
|
||||
st.activeTextureUnit = 0;
|
||||
st.boundTextures[0] = 8181;
|
||||
|
||||
backend.texParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
|
||||
assertEquals(GL11.GL_NEAREST, backend.getTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER));
|
||||
assertEquals(GL11.GL_NEAREST_MIPMAP_LINEAR, backend.getTexParameteri(GL11.GL_PROXY_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER));
|
||||
}
|
||||
|
||||
@Test
|
||||
void proxyTargetsNeverReceiveParameterWrites() {
|
||||
final SDLGPURenderBackend backend = new SDLGPURenderBackend();
|
||||
final ContextState st = SdlTestRig.contextState();
|
||||
st.activeTextureUnit = 0;
|
||||
st.boundTextures[0] = 8282;
|
||||
|
||||
backend.texParameteri(GL11.GL_PROXY_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
|
||||
backend.texParameterf(GL11.GL_PROXY_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS, 4.0f);
|
||||
|
||||
assertEquals(GL11.GL_NEAREST_MIPMAP_LINEAR, backend.getTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER));
|
||||
assertEquals(0.0f, backend.getTexParameterf(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu;
|
||||
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
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.GL13;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
import org.lwjgl.opengl.GL40;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
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 ProxyTextureTargetTest {
|
||||
|
||||
private static final int[] PROXY_TARGETS = {
|
||||
0x8063, 0x8064, 0x8070, 0x851B, 0x8C19, 0x8C1B, 0x84F7, 0x900B, 0x9101, 0x9103
|
||||
};
|
||||
|
||||
private static SDLGPURenderBackend backend;
|
||||
|
||||
@BeforeAll
|
||||
static void setUp() {
|
||||
backend = new SDLGPURenderBackend();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void resetContextState() {
|
||||
final ContextState st = SdlTestRig.contextState();
|
||||
st.activeTextureUnit = 0;
|
||||
st.boundTextures[0] = 0;
|
||||
st.boundTextures[1] = 0;
|
||||
st.proxyTexture.reject(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void everyProxyTargetIsRecognized() {
|
||||
for (int target : PROXY_TARGETS) {
|
||||
assertTrue(SDLGPURenderBackend.isProxyTarget(target), () -> "0x" + Integer.toHexString(target));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void realTargetsAreNotProxies() {
|
||||
final int[] real = {
|
||||
GL11.GL_TEXTURE_1D, GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_3D, GL13.GL_TEXTURE_CUBE_MAP,
|
||||
GL30.GL_TEXTURE_1D_ARRAY, GL30.GL_TEXTURE_2D_ARRAY, GL31.GL_TEXTURE_RECTANGLE,
|
||||
GL40.GL_TEXTURE_CUBE_MAP_ARRAY, GL32.GL_TEXTURE_2D_MULTISAMPLE, GL32.GL_TEXTURE_2D_MULTISAMPLE_ARRAY,
|
||||
0
|
||||
};
|
||||
for (int target : real) {
|
||||
assertFalse(SDLGPURenderBackend.isProxyTarget(target), () -> "0x" + Integer.toHexString(target));
|
||||
}
|
||||
}
|
||||
|
||||
static Stream<Arguments> levelSizeCases() {
|
||||
return Stream.of(
|
||||
Arguments.of("level0", 16384, 0, 16384),
|
||||
Arguments.of("level1", 16384, 1, 8192),
|
||||
Arguments.of("level14FloorsAtOne", 16384, 14, 1),
|
||||
Arguments.of("level15FloorsAtOne", 16384, 15, 1),
|
||||
Arguments.of("level64FloorsAtOne", 16384, 64, 1),
|
||||
Arguments.of("baseOneFloorsAtOne", 1, 3, 1),
|
||||
Arguments.of("oddBaseRoundsDown", 6, 1, 3),
|
||||
Arguments.of("rejectedProxyBaseZeroLevel0", 0, 0, 0),
|
||||
Arguments.of("rejectedProxyBaseZeroLevel5", 0, 5, 0),
|
||||
Arguments.of("rejectedProxyNegativeLevel", 16384, -1, 0));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@MethodSource("levelSizeCases")
|
||||
void levelSizeMatchesExpectation(String label, int base, int level, int expected) {
|
||||
assertEquals(expected, SDLGPURenderBackend.proxyLevelSize(base, level), label);
|
||||
}
|
||||
|
||||
@Test
|
||||
void proxyTexImage2DRecordsDimensionsWithoutTouchingTheBoundTexture() {
|
||||
final ContextState st = SdlTestRig.contextState();
|
||||
st.activeTextureUnit = 1;
|
||||
st.boundTextures[1] = 4242;
|
||||
|
||||
backend.texImage2D(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_RGBA, 16384, 16384, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
|
||||
assertEquals(4242, st.boundTextures[1]);
|
||||
assertEquals(16384, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH));
|
||||
assertEquals(16384, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_TEXTURE_HEIGHT));
|
||||
assertEquals(2048, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 3, GL11.GL_TEXTURE_WIDTH));
|
||||
assertEquals(GL11.GL_RGBA, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_TEXTURE_INTERNAL_FORMAT));
|
||||
assertEquals(0, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_TEXTURE_BORDER));
|
||||
}
|
||||
|
||||
@Test
|
||||
void oversizedProxyTexImage2DZeroesEveryProxyField() {
|
||||
final ContextState st = SdlTestRig.contextState();
|
||||
st.activeTextureUnit = 0;
|
||||
|
||||
backend.texImage2D(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_RGBA, 16384, 16384, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
backend.texImage2D(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_RGBA, 32768, 32768, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
|
||||
assertEquals(0, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH));
|
||||
assertEquals(0, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_TEXTURE_HEIGHT));
|
||||
assertEquals(0, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_TEXTURE_INTERNAL_FORMAT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void proxyArrayTargetIsLimitedByLayerCount() {
|
||||
final int layers = SDLGPURenderBackend.MAX_ARRAY_TEXTURE_LAYERS;
|
||||
|
||||
backend.texImage3D(GL30.GL_PROXY_TEXTURE_2D_ARRAY, 0, GL11.GL_RGBA, 64, 64, layers, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
assertEquals(layers, backend.getTexLevelParameteri(GL30.GL_PROXY_TEXTURE_2D_ARRAY, 0, GL12.GL_TEXTURE_DEPTH));
|
||||
|
||||
backend.texImage3D(GL30.GL_PROXY_TEXTURE_2D_ARRAY, 0, GL11.GL_RGBA, 64, 64, layers + 1, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
assertEquals(0, backend.getTexLevelParameteri(GL30.GL_PROXY_TEXTURE_2D_ARRAY, 0, GL12.GL_TEXTURE_DEPTH));
|
||||
assertEquals(0, backend.getTexLevelParameteri(GL30.GL_PROXY_TEXTURE_2D_ARRAY, 0, GL11.GL_TEXTURE_WIDTH));
|
||||
}
|
||||
|
||||
@Test
|
||||
void proxyStateIsScopedToTheProbedTarget() {
|
||||
backend.texImage2D(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_RGBA, 256, 256, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
|
||||
assertEquals(256, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH));
|
||||
assertEquals(0, backend.getTexLevelParameteri(GL13.GL_PROXY_TEXTURE_CUBE_MAP, 0, GL11.GL_TEXTURE_WIDTH));
|
||||
assertEquals(0, backend.getTexLevelParameteri(GL13.GL_PROXY_TEXTURE_CUBE_MAP, 0, GL11.GL_TEXTURE_HEIGHT));
|
||||
assertEquals(0, backend.getTexLevelParameteri(GL13.GL_PROXY_TEXTURE_CUBE_MAP, 0, GL11.GL_TEXTURE_INTERNAL_FORMAT));
|
||||
assertEquals(0, backend.getTexLevelParameteri(GL12.GL_PROXY_TEXTURE_3D, 0, GL12.GL_TEXTURE_DEPTH));
|
||||
}
|
||||
|
||||
@Test
|
||||
void unmappableProxyFormatIsRejected() {
|
||||
backend.texImage2D(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_RGBA, 256, 256, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
backend.texImage2D(GL11.GL_PROXY_TEXTURE_2D, 0, 0x1BADF00D, 256, 256, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
|
||||
assertEquals(0, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH));
|
||||
assertEquals(0, backend.getTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_TEXTURE_INTERNAL_FORMAT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void proxyBindIsInert() {
|
||||
final ContextState st = SdlTestRig.contextState();
|
||||
st.activeTextureUnit = 0;
|
||||
st.boundTextures[0] = 77;
|
||||
|
||||
for (int target : PROXY_TARGETS) {
|
||||
backend.bindTexture(target, 99);
|
||||
assertEquals(77, st.boundTextures[0], () -> "0x" + Integer.toHexString(target));
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
-3
@@ -1,17 +1,34 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu;
|
||||
|
||||
import com.mitchej123.lwjgl.LWJGLService;
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
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.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
class SDLGPULWJGLServiceGatingTest {
|
||||
|
||||
@Test
|
||||
void anUnengagedSdlDeclinesAndNeverArmsTheFallbackTripwire() {
|
||||
final String key = SystemProperties.KEY_CELERITAS_LWJGL_SERVICE;
|
||||
assertFalse(SDLGPUGate.isEngaged(), "test JVM must not have an engaged SDL GPU device");
|
||||
assertEquals(LWJGLService.PRIORITY_UNAVAILABLE, new SDLGPULWJGLService().getPriority());
|
||||
assertFalse(SDLGPULWJGLService.isOfferedAsAvailable(), "declining must leave fallBackToGL() usable");
|
||||
assertNull(System.getProperty(key), "an unengaged SDL must not bind the celeritas service");
|
||||
|
||||
try {
|
||||
SDLGPULWJGLService.bind();
|
||||
assertEquals(SDLGPULWJGLService.class.getName(), System.getProperty(key));
|
||||
|
||||
assertThrows(
|
||||
IllegalStateException.class,
|
||||
SDLGPULWJGLService::create,
|
||||
"constructing the service while SDL GPU is unengaged must fail loudly");
|
||||
} finally {
|
||||
SDLGPULWJGLService.unbind();
|
||||
}
|
||||
|
||||
assertNull(System.getProperty(key));
|
||||
assertFalse(SDLGPULWJGLService.isConstructed(), "declining must leave fallBackToGL() usable");
|
||||
}
|
||||
}
|
||||
+246
@@ -0,0 +1,246 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SDLGPUGate;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
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.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.SIZE;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@Tag("glsm-sdl")
|
||||
@ExtendWith(GlsmSdlFrameExtension.class)
|
||||
class GlsmSdlCopyBlitTest {
|
||||
|
||||
private static final int RED = 0xFFFF0000;
|
||||
private static final int GREEN = 0xFF00FF00;
|
||||
private static final int BLUE = 0xFF0000FF;
|
||||
|
||||
private static final int SCISSOR_ROWS = 16;
|
||||
private static final byte SENTINEL_BYTE = 0x7B;
|
||||
private static final int SENTINEL = 0x7B7B7B7B;
|
||||
|
||||
private static FrameManager frameManager;
|
||||
private static ResourceManager resourceManager;
|
||||
|
||||
@BeforeAll
|
||||
static void boot() {
|
||||
GlsmSdlHeadlessRig.boot();
|
||||
frameManager = Reflect.get(BackendManager.RENDER_BACKEND, "frameManager");
|
||||
resourceManager = Reflect.get(BackendManager.RENDER_BACKEND, "resourceManager");
|
||||
frameManager.destroyFinalTarget();
|
||||
frameManager.finalTarget().create(SDLGPUGate.device(), resourceManager, SIZE, SIZE, resourceManager.mapTextureFormat(GL11.GL_RGBA8));
|
||||
assertEquals(SIZE, frameManager.getFbo0Height());
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void dropFinalTarget() {
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
if (frameManager != null) frameManager.destroyFinalTarget();
|
||||
}
|
||||
|
||||
private static void assertRow(int[] pixels, int y, int expected, String label) {
|
||||
for (int x = 0; x < SIZE; x += 8) {
|
||||
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, x, y, expected, label + " row " + y);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void scissorOnTheUserFboClipsTheGlBottomRows() {
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GlsmSdlHeadlessRig.clearTo(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
|
||||
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
|
||||
GLStateManager.glScissor(0, 0, SIZE, SCISSOR_ROWS);
|
||||
GlsmSdlHeadlessRig.solidQuad(1.0f, 0.0f, 0.0f);
|
||||
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
assertRow(pixels, 0, RED, "inside the scissor");
|
||||
assertRow(pixels, SCISSOR_ROWS - 1, RED, "inside the scissor");
|
||||
assertRow(pixels, SCISSOR_ROWS, BLUE, "outside the scissor");
|
||||
assertRow(pixels, SIZE - 1, BLUE, "outside the scissor");
|
||||
}
|
||||
|
||||
private static int createTwoLevelTexture(int level0Argb, int level1Argb) {
|
||||
final int id = GLStateManager.glGenTextures();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, id);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST_MIPMAP_NEAREST);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 1);
|
||||
GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MIN_LOD, 0.0f);
|
||||
GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LOD, 1.0f);
|
||||
GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS, 0.0f);
|
||||
uploadLevel(0, SIZE, level0Argb);
|
||||
uploadLevel(1, SIZE / 2, level1Argb);
|
||||
return id;
|
||||
}
|
||||
|
||||
private static void uploadLevel(int level, int size, int argb) {
|
||||
final ByteBuffer texels = MemoryUtil.memAlloc(size * size * 4);
|
||||
try {
|
||||
for (int i = 0; i < size * size; i++) {
|
||||
texels.put(i * 4, (byte) ((argb >> 16) & 0xFF));
|
||||
texels.put(i * 4 + 1, (byte) ((argb >> 8) & 0xFF));
|
||||
texels.put(i * 4 + 2, (byte) (argb & 0xFF));
|
||||
texels.put(i * 4 + 3, (byte) ((argb >>> 24) & 0xFF));
|
||||
}
|
||||
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, level, GL11.GL_RGBA8, size, size, 0,
|
||||
GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, texels);
|
||||
} finally {
|
||||
MemoryUtil.memFree(texels);
|
||||
}
|
||||
}
|
||||
|
||||
private static int copyGreenIntoLevelOne() {
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GlsmSdlHeadlessRig.clearTo(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
GlsmSdlHeadlessRig.solidQuad(0.0f, 1.0f, 0.0f);
|
||||
|
||||
final int texture = createTwoLevelTexture(RED, BLUE);
|
||||
GLStateManager.glCopyTexSubImage2D(GL11.GL_TEXTURE_2D, 1, 0, 0, 0, 0, SIZE / 2, SIZE / 2);
|
||||
return texture;
|
||||
}
|
||||
|
||||
@Test
|
||||
void copyTexSubImage2DWritesTheRequestedMipLevel() {
|
||||
final int texture = copyGreenIntoLevelOne();
|
||||
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GlsmSdlHeadlessRig.clearTo(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
GlsmSdlHeadlessRig.texturedQuad(texture, 0.5f);
|
||||
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 2, SIZE / 2, GREEN, "minified sample of level 1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void copyTexSubImage2DLeavesTheOtherMipLevelsUntouched() {
|
||||
final int texture = copyGreenIntoLevelOne();
|
||||
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GlsmSdlHeadlessRig.clearTo(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
GlsmSdlHeadlessRig.texturedQuad(texture, 1.0f);
|
||||
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 4, SIZE / 4, RED, "magnified sample of the level 0 region the copy would land in");
|
||||
}
|
||||
|
||||
@Test
|
||||
void readPixelsCrossingTheTopEdgeReturnsTheInRangeRows() {
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GlsmSdlHeadlessRig.clearTo(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
GlsmSdlHeadlessRig.halfQuad(0.0f, 1.0f, 0.0f, 1.0f, 0.0f);
|
||||
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget(0, SIZE / 2, SIZE, SIZE, SENTINEL_BYTE);
|
||||
assertRow(pixels, 0, GREEN, "first in-range row");
|
||||
assertRow(pixels, SIZE / 2 - 1, GREEN, "last in-range row");
|
||||
assertRow(pixels, SIZE / 2, SENTINEL, "first out-of-range row");
|
||||
assertRow(pixels, SIZE - 1, SENTINEL, "last out-of-range row");
|
||||
}
|
||||
|
||||
private static int createSecondaryFbo() {
|
||||
final int color = GLStateManager.glGenTextures();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, color);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, SIZE, SIZE, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
|
||||
final int depth = GLStateManager.glGenRenderbuffers();
|
||||
GLStateManager.glBindRenderbuffer(GL30.GL_RENDERBUFFER, depth);
|
||||
GLStateManager.glRenderbufferStorage(GL30.GL_RENDERBUFFER, GL30.GL_DEPTH24_STENCIL8, SIZE, SIZE);
|
||||
|
||||
final int fbo = GLStateManager.glGenFramebuffers();
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
|
||||
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, color, 0);
|
||||
GLStateManager.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_STENCIL_ATTACHMENT, GL30.GL_RENDERBUFFER, depth);
|
||||
return fbo;
|
||||
}
|
||||
|
||||
@Test
|
||||
void blitFramebufferWithColorAndDepthBitsMovesColor() {
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GlsmSdlHeadlessRig.clearTo(0.0f, 1.0f, 0.0f, 1.0f);
|
||||
GlsmSdlHeadlessRig.solidQuad(0.0f, 1.0f, 0.0f);
|
||||
|
||||
final int destination = createSecondaryFbo();
|
||||
GLStateManager.glViewport(0, 0, SIZE, SIZE);
|
||||
GLStateManager.glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
|
||||
GlsmSdlHeadlessRig.solidQuad(0.0f, 0.0f, 1.0f);
|
||||
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, destination);
|
||||
GLStateManager.glBlitFramebuffer(0, 0, SIZE, SIZE, 0, 0, SIZE, SIZE,
|
||||
GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT, GL11.GL_NEAREST);
|
||||
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, destination);
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 2, SIZE / 2, GREEN, "color aspect of a color+depth blit");
|
||||
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, 4, SIZE - 5, GREEN, "color aspect of a color+depth blit");
|
||||
}
|
||||
|
||||
@Test
|
||||
void blitFramebufferIntoFbo0SurvivesTheFramesFirstFbo0Pass() {
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GlsmSdlHeadlessRig.clearTo(0.0f, 1.0f, 0.0f, 1.0f);
|
||||
GlsmSdlHeadlessRig.solidQuad(0.0f, 1.0f, 0.0f);
|
||||
|
||||
GLStateManager.glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, 0);
|
||||
GLStateManager.glBlitFramebuffer(0, 0, SIZE, SIZE, 0, 0, SIZE, SIZE, GL11.GL_COLOR_BUFFER_BIT, GL11.GL_NEAREST);
|
||||
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
|
||||
GLStateManager.glViewport(0, 0, SIZE, SIZE);
|
||||
GLStateManager.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
|
||||
GLStateManager.glScissor(0, 0, 4, 4);
|
||||
GlsmSdlHeadlessRig.solidQuad(1.0f, 0.0f, 0.0f);
|
||||
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 2, SIZE / 2, GREEN, "blit into fbo0 survived the first fbo0 pass");
|
||||
}
|
||||
|
||||
@Test
|
||||
void blitFramebufferFromFbo0ToTheUserFboKeepsGlOrientation() {
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GlsmSdlHeadlessRig.clearTo(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
GlsmSdlHeadlessRig.solidQuad(0.0f, 0.0f, 0.0f);
|
||||
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
|
||||
GLStateManager.glViewport(0, 0, SIZE, SIZE);
|
||||
GLStateManager.glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
|
||||
GlsmSdlHeadlessRig.halfQuad(-1.0f, 0.0f, 0.0f, 1.0f, 0.0f);
|
||||
GlsmSdlHeadlessRig.halfQuad(0.0f, 1.0f, 1.0f, 0.0f, 0.0f);
|
||||
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, 0);
|
||||
GLStateManager.glBlitFramebuffer(0, 0, SIZE, SIZE, 0, 0, SIZE, SIZE, GL11.GL_COLOR_BUFFER_BIT, GL11.GL_NEAREST);
|
||||
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 2, 4, GREEN, "blitted fbo0 bottom");
|
||||
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 2, SIZE - 5, RED, "blitted fbo0 top");
|
||||
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 2, SIZE / 2 - 4, GREEN, "blitted fbo0 below the split");
|
||||
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 2, SIZE / 2 + 4, RED, "blitted fbo0 above the split");
|
||||
}
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SDLGPUGate;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.SIZE;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
@Tag("glsm-sdl")
|
||||
@ExtendWith(GlsmSdlFrameExtension.class)
|
||||
class GlsmSdlFbo0ViewportTest {
|
||||
|
||||
private static final int FBO0_W = 64;
|
||||
private static final int FBO0_H = 96;
|
||||
private static final int VIEW = 32;
|
||||
|
||||
private static final int RED = 0xFFFF0000;
|
||||
private static final int GREEN = 0xFF00FF00;
|
||||
private static final int BLUE = 0xFF0000FF;
|
||||
|
||||
private static FrameManager frameManager;
|
||||
private static ResourceManager resourceManager;
|
||||
|
||||
@BeforeAll
|
||||
static void boot() {
|
||||
GlsmSdlHeadlessRig.boot();
|
||||
frameManager = Reflect.get(BackendManager.RENDER_BACKEND, "frameManager");
|
||||
resourceManager = Reflect.get(BackendManager.RENDER_BACKEND, "resourceManager");
|
||||
frameManager.destroyFinalTarget();
|
||||
frameManager.finalTarget().create(SDLGPUGate.device(), resourceManager, FBO0_W, FBO0_H, resourceManager.mapTextureFormat(GL11.GL_RGBA8));
|
||||
assertEquals(FBO0_H, frameManager.getFbo0Height());
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void dropFinalTarget() {
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
if (frameManager != null) frameManager.destroyFinalTarget();
|
||||
}
|
||||
|
||||
private static void bindFbo0WithSmallViewport() {
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
|
||||
GLStateManager.glViewport(0, 0, VIEW, VIEW);
|
||||
GLStateManager.glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
|
||||
}
|
||||
|
||||
private static int createCopyTarget() {
|
||||
final int id = GLStateManager.glGenTextures();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, id);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 0);
|
||||
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, VIEW, VIEW, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
return id;
|
||||
}
|
||||
|
||||
private static void drawTextureOverTarget(int texture) {
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GlsmSdlHeadlessRig.clearTo(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
GlsmSdlHeadlessRig.texturedQuad(texture, 1.0f);
|
||||
}
|
||||
|
||||
@Test
|
||||
void partialViewportDrawCoversOnlyTheGlViewportRows() {
|
||||
bindFbo0WithSmallViewport();
|
||||
GlsmSdlHeadlessRig.solidQuad(1.0f, 0.0f, 0.0f);
|
||||
|
||||
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTarget(0, 0, VIEW, VIEW), RED, "inside the viewport");
|
||||
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTarget(0, FBO0_H - VIEW, VIEW, VIEW), BLUE, "above the viewport");
|
||||
}
|
||||
|
||||
@Test
|
||||
void copyTexSubImage2DFromAPartialViewportKeepsGlOrientation() {
|
||||
bindFbo0WithSmallViewport();
|
||||
GlsmSdlHeadlessRig.halfQuad(-1.0f, 0.0f, 0.0f, 1.0f, 0.0f);
|
||||
GlsmSdlHeadlessRig.halfQuad(0.0f, 1.0f, 1.0f, 0.0f, 0.0f);
|
||||
|
||||
final int copy = createCopyTarget();
|
||||
GLStateManager.glCopyTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, 0, 0, VIEW, VIEW);
|
||||
|
||||
drawTextureOverTarget(copy);
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 2, 4, GREEN, "sampled copy bottom");
|
||||
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 2, SIZE - 5, RED, "sampled copy top");
|
||||
}
|
||||
|
||||
@Test
|
||||
void copyTexImage2DAllocatesTheDestinationLevel() {
|
||||
bindFbo0WithSmallViewport();
|
||||
GlsmSdlHeadlessRig.solidQuad(1.0f, 0.0f, 0.0f);
|
||||
|
||||
final int copy = GLStateManager.glGenTextures();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, copy);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 0);
|
||||
assertNull(resourceManager.getTextureMeta(copy), "destination was allocated before the copy");
|
||||
|
||||
GLStateManager.glCopyTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, 0, 0, VIEW, VIEW, 0);
|
||||
|
||||
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(copy);
|
||||
assertNotNull(meta, "copyTexImage2D did not allocate the destination");
|
||||
assertEquals(VIEW, meta.width());
|
||||
assertEquals(VIEW, meta.height());
|
||||
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
final ByteBuffer out = MemoryUtil.memAlloc(VIEW * VIEW * 4);
|
||||
try {
|
||||
GLStateManager.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, out);
|
||||
for (int i = 0; i < VIEW * VIEW; i++) {
|
||||
final int idx = i;
|
||||
assertEquals((byte) 0xFF, out.get(i * 4), () -> "R at texel " + idx);
|
||||
assertEquals((byte) 0x00, out.get(i * 4 + 1), () -> "G at texel " + idx);
|
||||
assertEquals((byte) 0x00, out.get(i * 4 + 2), () -> "B at texel " + idx);
|
||||
assertEquals((byte) 0xFF, out.get(i * 4 + 3), () -> "A at texel " + idx);
|
||||
}
|
||||
} finally {
|
||||
MemoryUtil.memFree(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.SIZE;
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.describe;
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.pixelAt;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@Tag("glsm-sdl")
|
||||
@ExtendWith(GlsmSdlFrameExtension.class)
|
||||
class GlsmSdlFfpTest {
|
||||
|
||||
private static final int TEXEL = 0xFFFF4020;
|
||||
private static final int LIGHTMAP_SIZE = 16;
|
||||
|
||||
private static int baseTexture;
|
||||
private static int lightmapTexture;
|
||||
|
||||
@BeforeAll
|
||||
static void boot() {
|
||||
GlsmSdlHeadlessRig.boot();
|
||||
GlsmSdlHeadlessRig.beginFrame();
|
||||
baseTexture = GlsmSdlHeadlessRig.createSolidTexture(TEXEL);
|
||||
lightmapTexture = createLightmapTexture();
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
}
|
||||
|
||||
private static int createLightmapTexture() {
|
||||
final int id = GLStateManager.glGenTextures();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, id);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, LIGHTMAP_SIZE, LIGHTMAP_SIZE, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
final ByteBuffer texels = MemoryUtil.memAlloc(LIGHTMAP_SIZE * LIGHTMAP_SIZE * 4);
|
||||
try {
|
||||
for (int y = 0; y < LIGHTMAP_SIZE; y++) {
|
||||
for (int x = 0; x < LIGHTMAP_SIZE; x++) {
|
||||
final boolean brightest = (x == 0 && y == 0) || (x == LIGHTMAP_SIZE - 1 && y == LIGHTMAP_SIZE - 1);
|
||||
texels.put((byte) (brightest ? 255 : 0));
|
||||
texels.put((byte) 255);
|
||||
texels.put((byte) (brightest ? 255 : 0));
|
||||
texels.put((byte) 255);
|
||||
}
|
||||
}
|
||||
texels.flip();
|
||||
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, LIGHTMAP_SIZE, LIGHTMAP_SIZE, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, texels);
|
||||
} finally {
|
||||
MemoryUtil.memFree(texels);
|
||||
}
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
return id;
|
||||
}
|
||||
|
||||
private static void bindBaseTexture() {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, baseTexture);
|
||||
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
|
||||
}
|
||||
|
||||
private static void enableLightmapUnit(boolean vanillaTextureMatrix) {
|
||||
GlsmSdlHeadlessRig.enableLightmapUnit(lightmapTexture, vanillaTextureMatrix);
|
||||
}
|
||||
|
||||
private static void quadUnit0Only() {
|
||||
GLStateManager.glBegin(GL11.GL_QUADS);
|
||||
GlsmSdlHeadlessRig.emitVertex(-1.0f, -1.0f, 0.0f, 0.0f, false, (short) 0);
|
||||
GlsmSdlHeadlessRig.emitVertex(1.0f, -1.0f, 1.0f, 0.0f, false, (short) 0);
|
||||
GlsmSdlHeadlessRig.emitVertex(1.0f, 1.0f, 1.0f, 1.0f, false, (short) 0);
|
||||
GlsmSdlHeadlessRig.emitVertex(-1.0f, 1.0f, 0.0f, 1.0f, false, (short) 0);
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
|
||||
private static void quadTwoUnits(boolean vanillaBrightness) {
|
||||
final short brightness = (short) (vanillaBrightness ? 240 : 0);
|
||||
GLStateManager.glBegin(GL11.GL_QUADS);
|
||||
GlsmSdlHeadlessRig.emitVertex(-1.0f, -1.0f, 0.0f, 0.0f, true, brightness);
|
||||
GlsmSdlHeadlessRig.emitVertex(1.0f, -1.0f, 1.0f, 0.0f, true, brightness);
|
||||
GlsmSdlHeadlessRig.emitVertex(1.0f, 1.0f, 1.0f, 1.0f, true, brightness);
|
||||
GlsmSdlHeadlessRig.emitVertex(-1.0f, 1.0f, 0.0f, 1.0f, true, brightness);
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
|
||||
private static void assertTexelEverywhere(String label) {
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
final int center = pixelAt(pixels, SIZE, SIZE / 2, SIZE / 2);
|
||||
final int corner = pixelAt(pixels, SIZE, 2, 2);
|
||||
assertEquals(TEXEL, center, () -> label + ": center " + describe(center));
|
||||
assertEquals(TEXEL, corner, () -> label + ": corner " + describe(corner));
|
||||
}
|
||||
|
||||
@Test
|
||||
void singleUnitTexturedQuadShowsTheTexel() {
|
||||
bindBaseTexture();
|
||||
quadUnit0Only();
|
||||
assertTexelEverywhere("single unit");
|
||||
}
|
||||
|
||||
@Test
|
||||
void twoUnitTerrainQuadWithVanillaLightmapMatrixShowsTheTexel() {
|
||||
bindBaseTexture();
|
||||
enableLightmapUnit(true);
|
||||
GlsmSdlHeadlessRig.terrainRenderState();
|
||||
quadTwoUnits(true);
|
||||
assertTexelEverywhere("two units, vanilla lightmap matrix");
|
||||
}
|
||||
|
||||
@Test
|
||||
void twoUnitTerrainQuadWithIdentityLightmapMatrixShowsTheTexel() {
|
||||
bindBaseTexture();
|
||||
enableLightmapUnit(false);
|
||||
GlsmSdlHeadlessRig.terrainRenderState();
|
||||
quadTwoUnits(false);
|
||||
assertTexelEverywhere("two units, identity lightmap matrix, zero brightness");
|
||||
}
|
||||
|
||||
@Test
|
||||
void twoUnitTerrainQuadThroughDisplayListsShowsTheTexel() {
|
||||
bindBaseTexture();
|
||||
enableLightmapUnit(true);
|
||||
GlsmSdlHeadlessRig.terrainRenderState();
|
||||
|
||||
final int list = GLStateManager.glGenLists(1);
|
||||
GLStateManager.glNewList(list, GL11.GL_COMPILE);
|
||||
GLStateManager.glPushMatrix();
|
||||
GLStateManager.glTranslatef(0.0f, 0.0f, 0.0f);
|
||||
quadTwoUnits(true);
|
||||
GLStateManager.glPopMatrix();
|
||||
GLStateManager.glEndList();
|
||||
|
||||
GLStateManager.glCallList(list);
|
||||
assertTexelEverywhere("two units, glCallList");
|
||||
|
||||
GlsmSdlHeadlessRig.beginFrame();
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GlsmSdlHeadlessRig.clearTo(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
|
||||
final IntBuffer lists = BufferUtils.createIntBuffer(1);
|
||||
lists.put(0, list);
|
||||
lists.position(0);
|
||||
GLStateManager.glCallLists(lists);
|
||||
assertTexelEverywhere("two units, glCallLists");
|
||||
}
|
||||
|
||||
@Test
|
||||
void litTexturedQuadIsShaded() {
|
||||
bindBaseTexture();
|
||||
enableGuiStandardItemLighting();
|
||||
GLStateManager.glEnable(GL12.GL_RESCALE_NORMAL);
|
||||
quadUnit0Only();
|
||||
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
final int center = pixelAt(pixels, SIZE, SIZE / 2, SIZE / 2);
|
||||
final int red = (center >> 16) & 0xFF;
|
||||
assertEquals(255, (center >>> 24) & 0xFF, () -> "lit quad alpha: " + describe(center));
|
||||
assertTrue(red > 100, () -> "lit quad red: " + describe(center));
|
||||
assertTrue(red < ((TEXEL >> 16) & 0xFF), () -> "lit quad is not attenuated: " + describe(center));
|
||||
}
|
||||
|
||||
private static void enableGuiStandardItemLighting() {
|
||||
GLStateManager.glPushMatrix();
|
||||
GLStateManager.glRotatef(-30.0f, 0.0f, 1.0f, 0.0f);
|
||||
GLStateManager.glRotatef(165.0f, 1.0f, 0.0f, 0.0f);
|
||||
|
||||
GLStateManager.glEnable(GL11.GL_LIGHTING);
|
||||
GLStateManager.glEnable(GL11.GL_LIGHT0);
|
||||
GLStateManager.glEnable(GL11.GL_LIGHT1);
|
||||
GLStateManager.glEnable(GL11.GL_COLOR_MATERIAL);
|
||||
GLStateManager.glColorMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_AMBIENT_AND_DIFFUSE);
|
||||
|
||||
final float[] first = normalize(0.2f, 1.0f, -0.7f);
|
||||
final float[] second = normalize(-0.2f, 1.0f, 0.7f);
|
||||
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, floats(first[0], first[1], first[2], 0.0f));
|
||||
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_DIFFUSE, floats(0.6f, 0.6f, 0.6f, 1.0f));
|
||||
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_AMBIENT, floats(0.0f, 0.0f, 0.0f, 1.0f));
|
||||
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPECULAR, floats(0.0f, 0.0f, 0.0f, 1.0f));
|
||||
GLStateManager.glLight(GL11.GL_LIGHT1, GL11.GL_POSITION, floats(second[0], second[1], second[2], 0.0f));
|
||||
GLStateManager.glLight(GL11.GL_LIGHT1, GL11.GL_DIFFUSE, floats(0.6f, 0.6f, 0.6f, 1.0f));
|
||||
GLStateManager.glLight(GL11.GL_LIGHT1, GL11.GL_AMBIENT, floats(0.0f, 0.0f, 0.0f, 1.0f));
|
||||
GLStateManager.glLight(GL11.GL_LIGHT1, GL11.GL_SPECULAR, floats(0.0f, 0.0f, 0.0f, 1.0f));
|
||||
GLStateManager.glShadeModel(GL11.GL_FLAT);
|
||||
GLStateManager.glLightModel(GL11.GL_LIGHT_MODEL_AMBIENT, floats(0.4f, 0.4f, 0.4f, 1.0f));
|
||||
|
||||
GLStateManager.glPopMatrix();
|
||||
}
|
||||
|
||||
private static float[] normalize(float x, float y, float z) {
|
||||
final float len = (float) Math.sqrt(x * x + y * y + z * z);
|
||||
return new float[]{ x / len, y / len, z / len };
|
||||
}
|
||||
|
||||
private static FloatBuffer floats(float a, float b, float c, float d) {
|
||||
final FloatBuffer buf = BufferUtils.createFloatBuffer(4);
|
||||
buf.put(a).put(b).put(c).put(d);
|
||||
buf.flip();
|
||||
return buf;
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
|
||||
import org.junit.jupiter.api.extension.AfterEachCallback;
|
||||
import org.junit.jupiter.api.extension.BeforeEachCallback;
|
||||
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public final class GlsmSdlFrameExtension implements BeforeEachCallback, AfterEachCallback {
|
||||
|
||||
@Override
|
||||
public void beforeEach(ExtensionContext context) {
|
||||
GlsmSdlHeadlessRig.beginFrameAndReset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterEach(ExtensionContext context) {
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
assertTrue(GlsmSdlRedirectAgent.failures().isEmpty(),
|
||||
() -> "redirect agent recorded failures: " + GlsmSdlRedirectAgent.failures());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,300 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SDLGPUGate;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SDLGPURenderBackend;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineCache;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.sdl.SDLError;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
|
||||
public final class GlsmSdlHeadlessRig {
|
||||
|
||||
public static final int SIZE = 64;
|
||||
|
||||
private static boolean booted;
|
||||
private static int fbo;
|
||||
private static int colorTexture;
|
||||
private static int depthRenderbuffer;
|
||||
|
||||
private GlsmSdlHeadlessRig() {}
|
||||
|
||||
public static void boot() {
|
||||
if (booted) return;
|
||||
SdlTestRig.initSdlVideo();
|
||||
assumeTrue(SDLGPUGate.device().createDevice(), () -> "no SDL GPU device: " + SDLError.SDL_GetError());
|
||||
Reflect.setStatic(SDLGPUGate.class, "engaged", true);
|
||||
|
||||
final RenderBackend backend = BackendManager.RENDER_BACKEND;
|
||||
assumeTrue(backend instanceof SDLGPURenderBackend, () -> "selected backend is " + backend.getName());
|
||||
|
||||
backend.onPostWindowCreate(0L);
|
||||
Reflect.setStaticFinal(GLStateManager.class, "MainThread", Thread.class, Thread.currentThread());
|
||||
|
||||
GLStateManager.initialize(GLSMInitConfig.builder()
|
||||
.displaySize(SIZE, SIZE)
|
||||
.directDrawer(TessellatorStreamingDrawer::drawDirect)
|
||||
.streamingDrawerDestroy(TessellatorStreamingDrawer::destroy)
|
||||
.build());
|
||||
GLStateManager.setRunningSplash(false);
|
||||
GLStateManager.markSplashComplete("glsmSdlHeadlessRig");
|
||||
|
||||
assertTrue(GlsmSdlRedirectAgent.transformedCount() > 0, "redirect agent transformed no classes");
|
||||
assertTrue(GlsmSdlRedirectAgent.failures().isEmpty(),
|
||||
() -> "redirect agent recorded failures: " + GlsmSdlRedirectAgent.failures());
|
||||
|
||||
createTarget();
|
||||
booted = true;
|
||||
}
|
||||
|
||||
private static void createTarget() {
|
||||
beginFrame();
|
||||
colorTexture = GLStateManager.glGenTextures();
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, colorTexture);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, SIZE, SIZE, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
|
||||
depthRenderbuffer = GLStateManager.glGenRenderbuffers();
|
||||
GLStateManager.glBindRenderbuffer(GL30.GL_RENDERBUFFER, depthRenderbuffer);
|
||||
GLStateManager.glRenderbufferStorage(GL30.GL_RENDERBUFFER, GL30.GL_DEPTH24_STENCIL8, SIZE, SIZE);
|
||||
|
||||
fbo = GLStateManager.glGenFramebuffers();
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
|
||||
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, colorTexture, 0);
|
||||
GLStateManager.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_STENCIL_ATTACHMENT, GL30.GL_RENDERBUFFER, depthRenderbuffer);
|
||||
GLStateManager.glViewport(0, 0, SIZE, SIZE);
|
||||
|
||||
PipelineCache.setSwapchainFormats(new int[]{ colorTargetSdlFormat() });
|
||||
endFrame();
|
||||
}
|
||||
|
||||
private static int colorTargetSdlFormat() {
|
||||
final ResourceManager rm = Reflect.get(BackendManager.RENDER_BACKEND, "resourceManager");
|
||||
final ResourceManager.TextureMeta meta = rm.getTextureMeta(colorTexture);
|
||||
return meta != null ? meta.sdlFormat() : 0;
|
||||
}
|
||||
|
||||
private static boolean frameOpen;
|
||||
|
||||
public static void beginFrame() {
|
||||
if (frameOpen) return;
|
||||
BackendManager.RENDER_BACKEND.onFrameBegin();
|
||||
frameOpen = true;
|
||||
}
|
||||
|
||||
public static void endFrame() {
|
||||
if (!frameOpen) return;
|
||||
frameOpen = false;
|
||||
BackendManager.RENDER_BACKEND.onFrameEnd();
|
||||
}
|
||||
|
||||
public static void bindTarget() {
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
|
||||
GLStateManager.glViewport(0, 0, SIZE, SIZE);
|
||||
}
|
||||
|
||||
public static void clearTo(float r, float g, float b, float a) {
|
||||
GLStateManager.glClearColor(r, g, b, a);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
|
||||
}
|
||||
|
||||
public static void beginFrameAndReset() {
|
||||
beginFrame();
|
||||
resetState();
|
||||
bindTarget();
|
||||
clearTo(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
public static void resetState() {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.glDisable(GL11.GL_TEXTURE_2D);
|
||||
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
|
||||
GLStateManager.glLoadIdentity();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glDisable(GL11.GL_TEXTURE_2D);
|
||||
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
|
||||
GLStateManager.glLoadIdentity();
|
||||
|
||||
GLStateManager.glDisable(GL11.GL_ALPHA_TEST);
|
||||
GLStateManager.glDisable(GL11.GL_FOG);
|
||||
GLStateManager.glDisable(GL11.GL_LIGHTING);
|
||||
GLStateManager.glDisable(GL11.GL_LIGHT0);
|
||||
GLStateManager.glDisable(GL11.GL_LIGHT1);
|
||||
GLStateManager.glDisable(GL11.GL_COLOR_MATERIAL);
|
||||
GLStateManager.glDisable(GL12.GL_RESCALE_NORMAL);
|
||||
GLStateManager.glDisable(GL11.GL_DEPTH_TEST);
|
||||
GLStateManager.glDisable(GL11.GL_BLEND);
|
||||
GLStateManager.glDisable(GL11.GL_CULL_FACE);
|
||||
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
GLStateManager.glCullFace(GL11.GL_BACK);
|
||||
GLStateManager.glShadeModel(GL11.GL_SMOOTH);
|
||||
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
GLStateManager.glNormal3f(0.0f, 0.0f, 1.0f);
|
||||
|
||||
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
|
||||
GLStateManager.glLoadIdentity();
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLStateManager.glLoadIdentity();
|
||||
}
|
||||
|
||||
public static void terrainRenderState() {
|
||||
GLStateManager.glEnable(GL11.GL_ALPHA_TEST);
|
||||
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1f);
|
||||
GLStateManager.glEnable(GL11.GL_FOG);
|
||||
GLStateManager.glFogi(GL11.GL_FOG_MODE, GL11.GL_LINEAR);
|
||||
GLStateManager.glFogf(GL11.GL_FOG_START, 1.0e6f);
|
||||
GLStateManager.glFogf(GL11.GL_FOG_END, 1.0e7f);
|
||||
}
|
||||
|
||||
public static int createSolidTexture(int argb) {
|
||||
final int id = GLStateManager.glGenTextures();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, id);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
|
||||
final ByteBuffer texels = MemoryUtil.memAlloc(2 * 2 * 4);
|
||||
try {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
texels.put((byte) ((argb >> 16) & 0xFF));
|
||||
texels.put((byte) ((argb >> 8) & 0xFF));
|
||||
texels.put((byte) (argb & 0xFF));
|
||||
texels.put((byte) ((argb >>> 24) & 0xFF));
|
||||
}
|
||||
texels.flip();
|
||||
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, 2, 2, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, texels);
|
||||
} finally {
|
||||
MemoryUtil.memFree(texels);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
public static void enableLightmapUnit(int lightmapTexture, boolean vanillaTextureMatrix) {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, lightmapTexture);
|
||||
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
|
||||
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
|
||||
GLStateManager.glLoadIdentity();
|
||||
if (vanillaTextureMatrix) {
|
||||
final float scale = 0.00390625f;
|
||||
GLStateManager.glScalef(scale, scale, scale);
|
||||
GLStateManager.glTranslatef(8.0f, 8.0f, 8.0f);
|
||||
}
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
}
|
||||
|
||||
public static void emitVertex(float x, float y, float u, float v, boolean lightmap, short brightness) {
|
||||
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
GLStateManager.glNormal3f(0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glTexCoord2f(u, v);
|
||||
if (lightmap) {
|
||||
GLStateManager.glMultiTexCoord2s(GL13.GL_TEXTURE1, brightness, brightness);
|
||||
}
|
||||
GLStateManager.glVertex3f(x, y, 0.0f);
|
||||
}
|
||||
|
||||
private static void vertex(float x, float y, float r, float g, float b) {
|
||||
GLStateManager.glColor4f(r, g, b, 1.0f);
|
||||
GLStateManager.glNormal3f(0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glVertex3f(x, y, 0.0f);
|
||||
}
|
||||
|
||||
private static void texVertex(float x, float y, float u, float v) {
|
||||
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
GLStateManager.glNormal3f(0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glTexCoord2f(u, v);
|
||||
GLStateManager.glVertex3f(x, y, 0.0f);
|
||||
}
|
||||
|
||||
public static void halfQuad(float y0, float y1, float r, float g, float b) {
|
||||
GLStateManager.glBegin(GL11.GL_QUADS);
|
||||
vertex(-1.0f, y0, r, g, b);
|
||||
vertex(1.0f, y0, r, g, b);
|
||||
vertex(1.0f, y1, r, g, b);
|
||||
vertex(-1.0f, y1, r, g, b);
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
|
||||
public static void solidQuad(float r, float g, float b) {
|
||||
halfQuad(-1.0f, 1.0f, r, g, b);
|
||||
}
|
||||
|
||||
public static void texturedQuad(int texture, float extent) {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
|
||||
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
GLStateManager.glBegin(GL11.GL_QUADS);
|
||||
texVertex(-extent, -extent, 0.0f, 0.0f);
|
||||
texVertex(extent, -extent, 1.0f, 0.0f);
|
||||
texVertex(extent, extent, 1.0f, 1.0f);
|
||||
texVertex(-extent, extent, 0.0f, 1.0f);
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
|
||||
public static int[] readTarget() {
|
||||
return readTarget(0, 0, SIZE, SIZE, (byte) 0);
|
||||
}
|
||||
|
||||
public static int[] readTarget(int x, int y, int w, int h) {
|
||||
return readTarget(x, y, w, h, (byte) 0);
|
||||
}
|
||||
|
||||
public static int[] readTarget(int x, int y, int w, int h, byte fill) {
|
||||
endFrame();
|
||||
final ByteBuffer pixels = MemoryUtil.memAlloc(w * h * 4);
|
||||
try {
|
||||
for (int i = 0; i < w * h * 4; i++) pixels.put(i, fill);
|
||||
GLStateManager.glReadPixels(x, y, w, h, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
|
||||
final int[] out = new int[w * h];
|
||||
for (int i = 0; i < out.length; i++) {
|
||||
final int r = pixels.get(i * 4) & 0xFF;
|
||||
final int g = pixels.get(i * 4 + 1) & 0xFF;
|
||||
final int b = pixels.get(i * 4 + 2) & 0xFF;
|
||||
final int a = pixels.get(i * 4 + 3) & 0xFF;
|
||||
out[i] = (a << 24) | (r << 16) | (g << 8) | b;
|
||||
}
|
||||
return out;
|
||||
} finally {
|
||||
MemoryUtil.memFree(pixels);
|
||||
}
|
||||
}
|
||||
|
||||
public static int pixelAt(int[] pixels, int rowStride, int x, int y) {
|
||||
return pixels[y * rowStride + x];
|
||||
}
|
||||
|
||||
public static String describe(int argb) {
|
||||
return String.format("a=%d r=%d g=%d b=%d", (argb >>> 24) & 0xFF, (argb >> 16) & 0xFF, (argb >> 8) & 0xFF, argb & 0xFF);
|
||||
}
|
||||
|
||||
public static void assertPixel(int[] pixels, int stride, int x, int y, int expected, String label) {
|
||||
final int got = pixelAt(pixels, stride, x, y);
|
||||
assertEquals(expected, got, () -> label + " at (" + x + "," + y + "): " + describe(got));
|
||||
}
|
||||
|
||||
public static void assertUniform(int[] pixels, int expected, String label) {
|
||||
for (int i = 0; i < pixels.length; i++) {
|
||||
final int got = pixels[i];
|
||||
final int idx = i;
|
||||
assertEquals(expected, got, () -> label + " at index " + idx + ": " + describe(got));
|
||||
}
|
||||
}
|
||||
}
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.SIZE;
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.describe;
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.pixelAt;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
@Tag("glsm-sdl")
|
||||
@ExtendWith(GlsmSdlFrameExtension.class)
|
||||
class GlsmSdlProxyTextureProbeTest {
|
||||
|
||||
private static final int TEXEL = 0xFFFF4020;
|
||||
private static final int LIGHTMAP_SIZE = 16;
|
||||
|
||||
private static final int[] PROXY_TARGETS = {
|
||||
0x8063, 0x8064, 0x8070, 0x851B, 0x8C19, 0x8C1B, 0x84F7, 0x900B, 0x9101, 0x9103
|
||||
};
|
||||
|
||||
private static int baseTexture;
|
||||
private static int lightmapTexture;
|
||||
|
||||
@BeforeAll
|
||||
static void boot() {
|
||||
GlsmSdlHeadlessRig.boot();
|
||||
GlsmSdlHeadlessRig.beginFrame();
|
||||
baseTexture = GlsmSdlHeadlessRig.createSolidTexture(TEXEL);
|
||||
lightmapTexture = createLightmapTexture();
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
}
|
||||
|
||||
@Test
|
||||
void vanillaMaximumTextureSizeProbeLeavesTheBoundLightmapIntact() {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, lightmapTexture);
|
||||
|
||||
final ResourceManager rm = resourceManager();
|
||||
final long handleBefore = rm.getTextureHandle(lightmapTexture);
|
||||
|
||||
assertEquals(16384, vanillaGetGLMaximumTextureSize());
|
||||
|
||||
final ResourceManager.TextureMeta meta = rm.getTextureMeta(lightmapTexture);
|
||||
assertNotNull(meta, "lightmap meta was dropped by the proxy probe");
|
||||
assertEquals(LIGHTMAP_SIZE, meta.width(), "lightmap width after probe");
|
||||
assertEquals(LIGHTMAP_SIZE, meta.height(), "lightmap height after probe");
|
||||
assertEquals(handleBefore, rm.getTextureHandle(lightmapTexture), "lightmap handle after probe");
|
||||
assertEquals(LIGHTMAP_SIZE, GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH));
|
||||
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void proxyTargetCallsLeaveTheBoundTextureIntact() {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, lightmapTexture);
|
||||
|
||||
final ResourceManager rm = resourceManager();
|
||||
final long handleBefore = rm.getTextureHandle(lightmapTexture);
|
||||
final ResourceManager.TextureMeta before = rm.getTextureMeta(lightmapTexture);
|
||||
assertNotNull(before, "lightmap meta missing before the proxy calls");
|
||||
|
||||
for (int target : PROXY_TARGETS) {
|
||||
final String where = "0x" + Integer.toHexString(target);
|
||||
GLStateManager.glTexParameteri(target, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
|
||||
GLStateManager.glTexParameterf(target, GL12.GL_TEXTURE_MIN_LOD, 4.0f);
|
||||
GLStateManager.glTexSubImage2D(target, 0, 0, 0, 1, 1, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
GLStateManager.glCopyTexSubImage2D(target, 0, 0, 0, 0, 0, 1, 1);
|
||||
GLStateManager.glCopyTexImage2D(target, 0, GL11.GL_RGBA, 0, 0, 4, 4, 0);
|
||||
GLStateManager.glGenerateMipmap(target);
|
||||
|
||||
final ResourceManager.TextureMeta after = rm.getTextureMeta(lightmapTexture);
|
||||
assertNotNull(after, where);
|
||||
assertEquals(before.width(), after.width(), where);
|
||||
assertEquals(before.height(), after.height(), where);
|
||||
assertEquals(before.levels(), after.levels(), where);
|
||||
assertEquals(before.glFormat(), after.glFormat(), where);
|
||||
assertEquals(handleBefore, rm.getTextureHandle(lightmapTexture), where);
|
||||
}
|
||||
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void proxyProbeDuringTerrainDrawStillShowsTheTexel() {
|
||||
bindBaseTexture();
|
||||
enableLightmapUnit();
|
||||
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
assertEquals(16384, vanillaGetGLMaximumTextureSize());
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
|
||||
GlsmSdlHeadlessRig.terrainRenderState();
|
||||
quadTwoUnits();
|
||||
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
final int center = pixelAt(pixels, SIZE, SIZE / 2, SIZE / 2);
|
||||
final int corner = pixelAt(pixels, SIZE, 2, 2);
|
||||
assertEquals(TEXEL, center, () -> "center " + describe(center));
|
||||
assertEquals(TEXEL, corner, () -> "corner " + describe(corner));
|
||||
}
|
||||
|
||||
private static int vanillaGetGLMaximumTextureSize() {
|
||||
for (int i = 16384; i > 0; i >>= 1) {
|
||||
GLStateManager.glTexImage2D(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_RGBA, i, i, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
final int j = GLStateManager.glGetTexLevelParameteri(GL11.GL_PROXY_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH);
|
||||
if (j != 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static ResourceManager resourceManager() {
|
||||
return Reflect.get(BackendManager.RENDER_BACKEND, "resourceManager");
|
||||
}
|
||||
|
||||
private static int createLightmapTexture() {
|
||||
final int id = GLStateManager.glGenTextures();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, id);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
|
||||
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, LIGHTMAP_SIZE, LIGHTMAP_SIZE, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
final ByteBuffer texels = MemoryUtil.memAlloc(LIGHTMAP_SIZE * LIGHTMAP_SIZE * 4);
|
||||
try {
|
||||
for (int i = 0; i < LIGHTMAP_SIZE * LIGHTMAP_SIZE; i++) {
|
||||
texels.put((byte) 255).put((byte) 255).put((byte) 255).put((byte) 255);
|
||||
}
|
||||
texels.flip();
|
||||
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, LIGHTMAP_SIZE, LIGHTMAP_SIZE, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, texels);
|
||||
} finally {
|
||||
MemoryUtil.memFree(texels);
|
||||
}
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
return id;
|
||||
}
|
||||
|
||||
private static void bindBaseTexture() {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, baseTexture);
|
||||
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
|
||||
}
|
||||
|
||||
private static void enableLightmapUnit() {
|
||||
GlsmSdlHeadlessRig.enableLightmapUnit(lightmapTexture, true);
|
||||
}
|
||||
|
||||
private static void quadTwoUnits() {
|
||||
GLStateManager.glBegin(GL11.GL_QUADS);
|
||||
GlsmSdlHeadlessRig.emitVertex(-1.0f, -1.0f, 0.0f, 0.0f, true, (short) 240);
|
||||
GlsmSdlHeadlessRig.emitVertex(1.0f, -1.0f, 1.0f, 0.0f, true, (short) 240);
|
||||
GlsmSdlHeadlessRig.emitVertex(1.0f, 1.0f, 1.0f, 1.0f, true, (short) 240);
|
||||
GlsmSdlHeadlessRig.emitVertex(-1.0f, 1.0f, 0.0f, 1.0f, true, (short) 240);
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
}
|
||||
+271
@@ -0,0 +1,271 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
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 java.nio.IntBuffer;
|
||||
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.SIZE;
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.describe;
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.pixelAt;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@Tag("glsm-sdl")
|
||||
@ExtendWith(GlsmSdlFrameExtension.class)
|
||||
class GlsmSdlVanillaTextureTest {
|
||||
|
||||
private static final int GL_TEXTURE_MAX_ANISOTROPY_EXT = 34046;
|
||||
|
||||
private static final int ATLAS_SIZE = 64;
|
||||
private static final int ATLAS_MIPMAP_LEVELS = 4;
|
||||
private static final int[] LEVEL_COLORS = {
|
||||
0xFFFF4020,
|
||||
0xFF00FF00,
|
||||
0xFF0000FF,
|
||||
0xFF20C080,
|
||||
0xFFFFFFFF,
|
||||
};
|
||||
|
||||
private static final int LIGHTMAP_SIZE = 16;
|
||||
private static final int CLEAR_COLOR = 0xFF000000;
|
||||
|
||||
private static int atlasTexture;
|
||||
private static int lightmapTexture;
|
||||
|
||||
@BeforeAll
|
||||
static void boot() {
|
||||
GlsmSdlHeadlessRig.boot();
|
||||
GlsmSdlHeadlessRig.beginFrame();
|
||||
atlasTexture = createMipmappedAtlas();
|
||||
lightmapTexture = allocateDynamicLightmap();
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
}
|
||||
|
||||
private static int createMipmappedAtlas() {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
final int id = GLStateManager.glGenTextures();
|
||||
allocateTextureImpl(id, ATLAS_MIPMAP_LEVELS, ATLAS_SIZE, ATLAS_SIZE, 1.0f);
|
||||
for (int level = 0; level <= ATLAS_MIPMAP_LEVELS; level++) {
|
||||
uploadTextureSub(level, LEVEL_COLORS[level], ATLAS_SIZE >> level, ATLAS_SIZE >> level, true);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
private static int allocateDynamicLightmap() {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
final int id = GLStateManager.glGenTextures();
|
||||
allocateTextureImpl(id, 0, LIGHTMAP_SIZE, LIGHTMAP_SIZE, 1.0f);
|
||||
return id;
|
||||
}
|
||||
|
||||
private static void allocateTextureImpl(int id, int maxLevel, int width, int height, float aniso) {
|
||||
GLStateManager.glDeleteTextures(id);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, id);
|
||||
|
||||
GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, aniso);
|
||||
|
||||
if (maxLevel > 0) {
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, maxLevel);
|
||||
GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MIN_LOD, 0.0f);
|
||||
GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LOD, (float) maxLevel);
|
||||
GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS, 0.0f);
|
||||
}
|
||||
|
||||
for (int level = 0; level <= maxLevel; level++) {
|
||||
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, level, GL11.GL_RGBA, width >> level, height >> level, 0,
|
||||
GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, (IntBuffer) null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void uploadTextureSub(int level, int argb, int width, int height, boolean mipmapped) {
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER,
|
||||
mipmapped ? GL11.GL_NEAREST_MIPMAP_LINEAR : GL11.GL_NEAREST);
|
||||
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, GL11.GL_REPEAT);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT);
|
||||
|
||||
final IntBuffer data = BufferUtils.createIntBuffer(width * height);
|
||||
for (int i = 0; i < width * height; i++) {
|
||||
data.put(i, argb);
|
||||
}
|
||||
data.position(0);
|
||||
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, level, 0, 0, width, height,
|
||||
GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, data);
|
||||
}
|
||||
|
||||
private static void uploadLightmapFrame(int argb) {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, lightmapTexture);
|
||||
uploadTextureSub(0, argb, LIGHTMAP_SIZE, LIGHTMAP_SIZE, false);
|
||||
}
|
||||
|
||||
private static void bindAtlasUnit0() {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, atlasTexture);
|
||||
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
|
||||
}
|
||||
|
||||
private static void enableLightmap() {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
|
||||
GLStateManager.glLoadIdentity();
|
||||
final float scale = 0.00390625f;
|
||||
GLStateManager.glScalef(scale, scale, scale);
|
||||
GLStateManager.glTranslatef(8.0f, 8.0f, 8.0f);
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, lightmapTexture);
|
||||
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_LINEAR);
|
||||
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_LINEAR);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP);
|
||||
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
}
|
||||
|
||||
private static void quad(float extent, boolean lightmap, boolean counterClockwise) {
|
||||
GLStateManager.glBegin(GL11.GL_QUADS);
|
||||
if (counterClockwise) {
|
||||
GlsmSdlHeadlessRig.emitVertex(-extent, -extent, 0.0f, 0.0f, lightmap, (short) 240);
|
||||
GlsmSdlHeadlessRig.emitVertex(extent, -extent, 1.0f, 0.0f, lightmap, (short) 240);
|
||||
GlsmSdlHeadlessRig.emitVertex(extent, extent, 1.0f, 1.0f, lightmap, (short) 240);
|
||||
GlsmSdlHeadlessRig.emitVertex(-extent, extent, 0.0f, 1.0f, lightmap, (short) 240);
|
||||
} else {
|
||||
GlsmSdlHeadlessRig.emitVertex(-extent, extent, 0.0f, 1.0f, lightmap, (short) 240);
|
||||
GlsmSdlHeadlessRig.emitVertex(extent, extent, 1.0f, 1.0f, lightmap, (short) 240);
|
||||
GlsmSdlHeadlessRig.emitVertex(extent, -extent, 1.0f, 0.0f, lightmap, (short) 240);
|
||||
GlsmSdlHeadlessRig.emitVertex(-extent, -extent, 0.0f, 0.0f, lightmap, (short) 240);
|
||||
}
|
||||
GLStateManager.glEnd();
|
||||
}
|
||||
|
||||
private static int center() {
|
||||
return pixelAt(GlsmSdlHeadlessRig.readTarget(), SIZE, SIZE / 2, SIZE / 2);
|
||||
}
|
||||
|
||||
private static void assertColorNear(int expected, int actual, int tolerance, String label) {
|
||||
assertEquals(255, (actual >>> 24) & 0xFF, () -> label + ": alpha " + describe(actual));
|
||||
for (int shift = 0; shift <= 16; shift += 8) {
|
||||
final int e = (expected >> shift) & 0xFF;
|
||||
final int a = (actual >> shift) & 0xFF;
|
||||
assertTrue(Math.abs(e - a) <= tolerance,
|
||||
() -> label + ": expected " + describe(expected) + " got " + describe(actual));
|
||||
}
|
||||
}
|
||||
|
||||
private static int modulate(int a, int b) {
|
||||
int out = 0xFF000000;
|
||||
for (int shift = 0; shift <= 16; shift += 8) {
|
||||
final int product = Math.round((((a >> shift) & 0xFF) / 255.0f) * (((b >> shift) & 0xFF) / 255.0f) * 255.0f);
|
||||
out |= product << shift;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@Test
|
||||
void mipmappedAtlasMagnifiedShowsLevelZero() {
|
||||
bindAtlasUnit0();
|
||||
quad(1.0f, false, true);
|
||||
|
||||
final int actual = center();
|
||||
assertColorNear(LEVEL_COLORS[0], actual, 1, "magnified level 0");
|
||||
}
|
||||
|
||||
@Test
|
||||
void mipmappedAtlasMinifiedShowsALowerLevel() {
|
||||
bindAtlasUnit0();
|
||||
quad(0.125f, false, true);
|
||||
|
||||
final int actual = center();
|
||||
assertEquals(255, (actual >>> 24) & 0xFF, () -> "minified alpha: " + describe(actual));
|
||||
assertNotEquals(CLEAR_COLOR, actual, () -> "minified is the clear color: " + describe(actual));
|
||||
assertNotEquals(LEVEL_COLORS[0], actual, () -> "minified is level 0: " + describe(actual));
|
||||
assertColorNear(LEVEL_COLORS[3], actual, 1, "minified");
|
||||
}
|
||||
|
||||
@Test
|
||||
void perFrameDynamicLightmapModulatesTheAtlas() {
|
||||
final int[] frames = { 0xFFFFFFFF, 0xFFFF8040, 0xFF40FF80 };
|
||||
for (int frame = 0; frame < frames.length; frame++) {
|
||||
if (frame > 0) GlsmSdlHeadlessRig.beginFrameAndReset();
|
||||
|
||||
uploadLightmapFrame(frames[frame]);
|
||||
bindAtlasUnit0();
|
||||
enableLightmap();
|
||||
GlsmSdlHeadlessRig.terrainRenderState();
|
||||
quad(1.0f, true, true);
|
||||
|
||||
final int expected = modulate(LEVEL_COLORS[0], frames[frame]);
|
||||
final int actual = center();
|
||||
assertColorNear(expected, actual, 2, "dynamic lightmap frame " + frame);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void pushAttribWrappedCulledDrawKeepsFrontFace() {
|
||||
uploadLightmapFrame(0xFFFFFFFF);
|
||||
bindAtlasUnit0();
|
||||
enableLightmap();
|
||||
GlsmSdlHeadlessRig.terrainRenderState();
|
||||
|
||||
GLStateManager.glEnable(GL11.GL_CULL_FACE);
|
||||
GLStateManager.glCullFace(GL11.GL_BACK);
|
||||
GLStateManager.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
|
||||
quad(1.0f, true, true);
|
||||
GLStateManager.glPopAttrib();
|
||||
|
||||
final int actual = center();
|
||||
assertColorNear(LEVEL_COLORS[0], actual, 2, "front-face winding kept");
|
||||
}
|
||||
|
||||
@Test
|
||||
void pushAttribWrappedCulledDrawDropsBackFace() {
|
||||
uploadLightmapFrame(0xFFFFFFFF);
|
||||
bindAtlasUnit0();
|
||||
enableLightmap();
|
||||
GlsmSdlHeadlessRig.terrainRenderState();
|
||||
|
||||
GLStateManager.glEnable(GL11.GL_CULL_FACE);
|
||||
GLStateManager.glCullFace(GL11.GL_BACK);
|
||||
GLStateManager.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
|
||||
quad(1.0f, true, false);
|
||||
GLStateManager.glPopAttrib();
|
||||
|
||||
final int actual = center();
|
||||
assertEquals(CLEAR_COLOR, actual, () -> "back-face winding was not culled: " + describe(actual));
|
||||
}
|
||||
|
||||
@Test
|
||||
void displayListWithBindingsOutsideShowsTheModulatedTexel() {
|
||||
final int list = GLStateManager.glGenLists(1);
|
||||
GLStateManager.glNewList(list, GL11.GL_COMPILE);
|
||||
quad(1.0f, true, true);
|
||||
GLStateManager.glEndList();
|
||||
|
||||
uploadLightmapFrame(0xFFFF8040);
|
||||
bindAtlasUnit0();
|
||||
enableLightmap();
|
||||
GlsmSdlHeadlessRig.terrainRenderState();
|
||||
|
||||
GLStateManager.glPushMatrix();
|
||||
GLStateManager.glTranslatef(0.0f, 0.0f, 0.0f);
|
||||
GLStateManager.glCallList(list);
|
||||
GLStateManager.glPopMatrix();
|
||||
|
||||
final int expected = modulate(LEVEL_COLORS[0], 0xFFFF8040);
|
||||
final int actual = center();
|
||||
assertColorNear(expected, actual, 2, "display list with bindings outside");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.pipeline;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class FFPTraceTest {
|
||||
|
||||
@Test
|
||||
void uboFloatReadIgnoresATruncatedLimit() {
|
||||
final ByteBuffer buf = MemoryUtil.memAlloc(64);
|
||||
try {
|
||||
for (int i = 0; i < 16; i++) buf.putFloat(i * 4, i + 0.5f);
|
||||
buf.position(0).limit(4);
|
||||
assertEquals(0.5f, readUboFloat(buf, 0));
|
||||
assertEquals(12.5f, readUboFloat(buf, 48));
|
||||
assertEquals(15.5f, readUboFloat(buf, 60));
|
||||
assertEquals(4, buf.limit());
|
||||
} finally {
|
||||
MemoryUtil.memFree(buf);
|
||||
}
|
||||
}
|
||||
|
||||
private static float readUboFloat(ByteBuffer buf, int at) {
|
||||
return Reflect.invokeStatic(FFPTrace.class, "floatIgnoringLimit", new Class<?>[] { ByteBuffer.class, int.class }, buf, at);
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.resource;
|
||||
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class CopyRectClipTest {
|
||||
|
||||
static Stream<Arguments> cases() {
|
||||
return Stream.of(
|
||||
Arguments.of("insideBothImages", 5, 9, 3, 2, 7, 4, 64, 64, 64, 64, false, 5, 9, 3, 2, 7, 4),
|
||||
Arguments.of("negativeSourceOrigin", -3, -2, 4, 6, 10, 8, 64, 96, 64, 64, false, 0, 0, 7, 8, 7, 6),
|
||||
Arguments.of("negativeDestinationOrigin", 10, 10, -4, -6, 20, 20, 64, 64, 64, 64, false, 14, 16, 0, 0, 16, 14),
|
||||
Arguments.of("farSourceEdge", 60, 60, 0, 0, 10, 10, 64, 64, 64, 64, false, 60, 60, 0, 0, 4, 4),
|
||||
Arguments.of("farDestinationEdge", 0, 0, 28, 30, 16, 16, 64, 64, 32, 32, false, 0, 0, 28, 30, 4, 2),
|
||||
Arguments.of("oversizeBothImages", 61, 93, 62, 61, 10, 9, 64, 96, 64, 64, false, 61, 93, 62, 61, 2, 3),
|
||||
Arguments.of("oversizeNearFarEdge", 60, 93, 61, 62, 10, 9, 64, 96, 64, 64, false, 60, 93, 61, 62, 3, 2),
|
||||
Arguments.of("pastSourceEdge", 64, 0, 0, 0, 8, 8, 64, 64, 64, 64, true, 0, 0, 0, 0, 0, 0),
|
||||
Arguments.of("beforeSourceOrigin", -20, 0, 0, 0, 8, 8, 64, 64, 64, 64, true, 0, 0, 0, 0, 0, 0),
|
||||
Arguments.of("pastDestinationEdge", 0, 0, 32, 0, 8, 8, 64, 64, 32, 32, true, 0, 0, 0, 0, 0, 0),
|
||||
Arguments.of("zeroWidth", 0, 0, 0, 0, 0, 8, 64, 64, 64, 64, true, 0, 0, 0, 0, 0, 0),
|
||||
Arguments.of("zeroHeight", 0, 0, 0, 0, 8, 0, 64, 64, 64, 64, true, 0, 0, 0, 0, 0, 0),
|
||||
Arguments.of("negativeWidth", 0, 0, 0, 0, -4, 8, 64, 64, 64, 64, true, 0, 0, 0, 0, 0, 0),
|
||||
Arguments.of("unknownDestinationExtents", 0, 0, 200, 200, 8, 8, 64, 64, 0, 0, false, 0, 0, 200, 200, 8, 8),
|
||||
Arguments.of("unknownSourceExtents", 200, 200, 0, 0, 8, 8, 0, 0, 64, 64, false, 200, 200, 0, 0, 8, 8),
|
||||
Arguments.of("mipLevelSizedDestination", 0, 0, 0, 0, 64, 64, 64, 64, 32, 32, false, 0, 0, 0, 0, 32, 32));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@MethodSource("cases")
|
||||
void clipsAsExpected(String label, int srcX, int srcY, int dstX, int dstY, int width, int height,
|
||||
int srcW, int srcH, int dstW, int dstH, boolean expectEmpty,
|
||||
int expectedSrcX, int expectedSrcY, int expectedDstX, int expectedDstY, int expectedW, int expectedH) {
|
||||
final long clip = CopyRectClip.clipCopyRect(srcX, srcY, dstX, dstY, width, height, srcW, srcH, dstW, dstH);
|
||||
if (expectEmpty) {
|
||||
assertEquals(CopyRectClip.EMPTY, clip, label);
|
||||
return;
|
||||
}
|
||||
assertEquals(expectedSrcX, CopyRectClip.srcX(clip), label + " srcX");
|
||||
assertEquals(expectedSrcY, CopyRectClip.srcY(clip), label + " srcY");
|
||||
assertEquals(expectedDstX, CopyRectClip.dstX(clip, srcX, dstX), label + " dstX");
|
||||
assertEquals(expectedDstY, CopyRectClip.dstY(clip, srcY, dstY), label + " dstY");
|
||||
assertEquals(expectedW, CopyRectClip.width(clip), label + " width");
|
||||
assertEquals(expectedH, CopyRectClip.height(clip), label + " height");
|
||||
}
|
||||
}
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.resource;
|
||||
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.Timeout;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
|
||||
|
||||
@Timeout(120)
|
||||
class CopyTexSubImageFromFbo0GpuTest {
|
||||
|
||||
private static final int SIZE = 64;
|
||||
private static final int TALL = 96;
|
||||
private static final int ROW_BYTES = SIZE * 4;
|
||||
|
||||
private static Device device;
|
||||
private static FrameManager frameManager;
|
||||
private static ResourceManager resourceManager;
|
||||
private static TextureOps textureOps;
|
||||
private static int nextGlId = 1000;
|
||||
|
||||
@BeforeAll
|
||||
static void setUp() throws Exception {
|
||||
final SdlTestRig rig = SdlTestRig.acquireRealDevice();
|
||||
device = rig.device;
|
||||
frameManager = rig.frameManager;
|
||||
resourceManager = rig.resourceManager;
|
||||
textureOps = new TextureOps(device, frameManager, resourceManager, new FBOClearTracker(frameManager, resourceManager, null));
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void tearDown() {
|
||||
if (frameManager != null && frameManager.isFrameActive()) frameManager.endFrame();
|
||||
if (frameManager != null) frameManager.destroyFinalTarget();
|
||||
SdlTestRig.releaseRealDevice();
|
||||
}
|
||||
|
||||
private static byte srcRed(int col) {
|
||||
return (byte) (16 + col * 3);
|
||||
}
|
||||
|
||||
private static byte srcGreen(int row) {
|
||||
return (byte) (7 + row * 3);
|
||||
}
|
||||
|
||||
private static ByteBuffer fbo0Pattern(int height) {
|
||||
final ByteBuffer buf = MemoryUtil.memAlloc(SIZE * height * 4);
|
||||
for (int row = 0; row < height; row++) {
|
||||
for (int col = 0; col < SIZE; col++) {
|
||||
final int i = row * ROW_BYTES + col * 4;
|
||||
buf.put(i, srcRed(col));
|
||||
buf.put(i + 1, srcGreen(row));
|
||||
buf.put(i + 2, (byte) 0x40);
|
||||
buf.put(i + 3, (byte) 0xFF);
|
||||
}
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
private static final byte[] SENTINEL = { 0x11, 0x22, 0x33, 0x44 };
|
||||
|
||||
private static ByteBuffer sentinelPattern() {
|
||||
final ByteBuffer buf = MemoryUtil.memAlloc(SIZE * SIZE * 4);
|
||||
for (int i = 0; i < SIZE * SIZE; i++) {
|
||||
for (int c = 0; c < 4; c++) buf.put(i * 4 + c, SENTINEL[c]);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
private static void upload(long texture, ByteBuffer data, int width, int height) {
|
||||
final long cp = frameManager.ensureCopyPass();
|
||||
assertNotEquals(0L, cp, "no copy pass");
|
||||
resourceManager.uploadToTexture(cp, data, texture, 0, 0, width, height, 0);
|
||||
resourceManager.flushBatchedUploads(cp);
|
||||
frameManager.endCopyPassIfActive();
|
||||
}
|
||||
|
||||
private static int createDest() {
|
||||
final int glId = nextGlId++;
|
||||
final long handle = resourceManager.createTexture(glId, GL11.GL_TEXTURE_2D, GL11.GL_RGBA8, SIZE, SIZE, 1, 1);
|
||||
assertNotEquals(0L, handle, "dest texture creation failed");
|
||||
return glId;
|
||||
}
|
||||
|
||||
private static int createSentinelDest() {
|
||||
final int destGlId = createDest();
|
||||
final ByteBuffer sentinel = sentinelPattern();
|
||||
try {
|
||||
upload(resourceManager.getTextureHandle(destGlId), sentinel, SIZE, SIZE);
|
||||
} finally {
|
||||
MemoryUtil.memFree(sentinel);
|
||||
}
|
||||
return destGlId;
|
||||
}
|
||||
|
||||
private static void prepareFbo0(int height) {
|
||||
frameManager.destroyFinalTarget();
|
||||
frameManager.finalTarget().create(device, resourceManager, SIZE, height, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM);
|
||||
assertEquals(SIZE, frameManager.getFbo0Width());
|
||||
assertEquals(height, frameManager.getFbo0Height());
|
||||
final ByteBuffer pattern = fbo0Pattern(height);
|
||||
try {
|
||||
upload(frameManager.getFbo0Texture(), pattern, SIZE, height);
|
||||
} finally {
|
||||
MemoryUtil.memFree(pattern);
|
||||
}
|
||||
}
|
||||
|
||||
private static ByteBuffer download(int destGlId) {
|
||||
final ByteBuffer out = MemoryUtil.memAlloc(SIZE * SIZE * 4);
|
||||
textureOps.readbackTexture(resourceManager.getTextureHandle(destGlId), 0, 0, SIZE, SIZE, 0, out);
|
||||
out.rewind();
|
||||
return out;
|
||||
}
|
||||
|
||||
private static void assertPixel(ByteBuffer got, int col, int row, byte r, byte g, byte b, byte a, String what) {
|
||||
final int i = row * ROW_BYTES + col * 4;
|
||||
assertEquals(r, got.get(i), what + " R at col=" + col + " row=" + row);
|
||||
assertEquals(g, got.get(i + 1), what + " G at col=" + col + " row=" + row);
|
||||
assertEquals(b, got.get(i + 2), what + " B at col=" + col + " row=" + row);
|
||||
assertEquals(a, got.get(i + 3), what + " A at col=" + col + " row=" + row);
|
||||
}
|
||||
|
||||
private static void assertCopiedRegion(ByteBuffer got, int fbo0Height, int dx, int dy, int sx, int sy, int w, int h, String what) {
|
||||
for (int row = 0; row < SIZE; row++) {
|
||||
for (int col = 0; col < SIZE; col++) {
|
||||
final boolean inside = col >= dx && col < dx + w && row >= dy && row < dy + h;
|
||||
if (inside) {
|
||||
final int srcCol = sx + (col - dx);
|
||||
final int srcStorageRow = fbo0Height - 1 - (sy + (row - dy));
|
||||
assertPixel(got, col, row, srcRed(srcCol), srcGreen(srcStorageRow), (byte) 0x40, (byte) 0xFF, what);
|
||||
} else {
|
||||
assertPixel(got, col, row, SENTINEL[0], SENTINEL[1], SENTINEL[2], SENTINEL[3], "outside " + what);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void fullCopyFromDefaultFramebufferIsFlippedIntoTheTexture() {
|
||||
frameManager.beginFrame();
|
||||
frameManager.frame().swapchainUnavailable = true;
|
||||
prepareFbo0(SIZE);
|
||||
|
||||
final int destGlId = createSentinelDest();
|
||||
|
||||
final ContextState st = new ContextState();
|
||||
st.boundReadFboId = 0;
|
||||
textureOps.copyTexSubImageImpl(st, destGlId, 0, 0, 0, 0, 0, SIZE, SIZE);
|
||||
|
||||
final ByteBuffer got = download(destGlId);
|
||||
try {
|
||||
for (int row = 0; row < SIZE; row++) {
|
||||
for (int col = 0; col < SIZE; col++) {
|
||||
assertPixel(got, col, row, srcRed(col), srcGreen(SIZE - 1 - row), (byte) 0x40, (byte) 0xFF, "full copy");
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
MemoryUtil.memFree(got);
|
||||
}
|
||||
frameManager.endFrame();
|
||||
}
|
||||
|
||||
@Test
|
||||
void subRectCopyFromATallerDefaultFramebufferUsesTheFullHeightBasis() {
|
||||
frameManager.beginFrame();
|
||||
frameManager.frame().swapchainUnavailable = true;
|
||||
prepareFbo0(TALL);
|
||||
|
||||
final int destGlId = createSentinelDest();
|
||||
|
||||
final ContextState st = new ContextState();
|
||||
st.boundReadFboId = 0;
|
||||
textureOps.copyTexSubImageImpl(st, destGlId, 0, 5, 9, 3, 2, 7, 4);
|
||||
|
||||
final ByteBuffer got = download(destGlId);
|
||||
try {
|
||||
assertCopiedRegion(got, TALL, 5, 9, 3, 2, 7, 4, "tall sub-rect copy");
|
||||
} finally {
|
||||
MemoryUtil.memFree(got);
|
||||
}
|
||||
frameManager.endFrame();
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -51,7 +51,7 @@ class ReadbackSubmitOrderGpuTest {
|
||||
}
|
||||
|
||||
assertEquals(1, f.midFrameSubmitsThisFrame, "the frame command buffer must be submitted before the download buffer, or the readback sees stale contents");
|
||||
assertNotEquals(frameCb, f.commandBuffer, "the frame must continue on a freshly acquired command buffer");
|
||||
assertNotEquals(0L, f.commandBuffer, "the frame must continue on a freshly acquired command buffer");
|
||||
|
||||
fm.endFrame();
|
||||
}
|
||||
|
||||
+70
-3
@@ -5,9 +5,12 @@ import org.lwjgl.util.spvc.Spvc;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class AttribLocationPatchTest {
|
||||
|
||||
@@ -23,10 +26,14 @@ class AttribLocationPatchTest {
|
||||
};
|
||||
|
||||
private static ShaderManager.StageReflection reflectionWithInputs(ByteBuffer spirv) {
|
||||
final ShaderManager.VsInput[] inputs = new ShaderManager.VsInput[NAMES.length];
|
||||
for (int i = 0; i < NAMES.length; i++) {
|
||||
return reflectionWithInputs(spirv, NAMES);
|
||||
}
|
||||
|
||||
private static ShaderManager.StageReflection reflectionWithInputs(ByteBuffer spirv, String[] names) {
|
||||
final ShaderManager.VsInput[] inputs = new ShaderManager.VsInput[names.length];
|
||||
for (int i = 0; i < names.length; i++) {
|
||||
spirv.asIntBuffer().put(i, i);
|
||||
inputs[i] = new ShaderManager.VsInput(NAMES[i], i, i, VEC_SIZES[i], BASE_TYPES[i]);
|
||||
inputs[i] = new ShaderManager.VsInput(names[i], i, i, i < VEC_SIZES.length ? VEC_SIZES[i] : 4, i < BASE_TYPES.length ? BASE_TYPES[i] : Spvc.SPVC_BASETYPE_FP32);
|
||||
}
|
||||
final ShaderManager.StageReflection e = ShaderManager.StageReflection.EMPTY;
|
||||
return new ShaderManager.StageReflection(e.counts(), e.samplerNames(), e.extraUniformNames(),
|
||||
@@ -71,6 +78,66 @@ class AttribLocationPatchTest {
|
||||
assertEquals(Spvc.SPVC_BASETYPE_INT32, prog.vertexInputBaseType[5]);
|
||||
}
|
||||
|
||||
@Test
|
||||
void anUnboundInputMovesOffALocationABindingClaimed() {
|
||||
final String[] names = {
|
||||
"a_PosId", "a_Color", "a_TexCoord", "a_LightCoord", "a_RdhFactor",
|
||||
"mc_midTexCoord", "at_tangent", "iris_Normal", "mc_Entity", "at_midBlock"
|
||||
};
|
||||
final ShaderManager.ProgramObject prog = new ShaderManager.ProgramObject();
|
||||
prog.vertexSpirv = ByteBuffer.allocateDirect(names.length * Integer.BYTES).order(ByteOrder.nativeOrder());
|
||||
final ShaderManager.StageReflection vs = reflectionWithInputs(prog.vertexSpirv, names);
|
||||
|
||||
int loc = 0;
|
||||
for (String name : names) {
|
||||
if (!name.equals("a_RdhFactor")) prog.attribLocationBindings.put(name, loc++);
|
||||
}
|
||||
|
||||
ShaderManager.applyAttribLocationsAndInputMask(prog, vs);
|
||||
|
||||
assertEquals(4, prog.resolvedAttribLocations.getInt("mc_midTexCoord"), "the binding is authoritative");
|
||||
assertEquals(9, prog.resolvedAttribLocations.getInt("a_RdhFactor"), "the unbound input takes the lowest free slot");
|
||||
assertEquals(0x3ff, prog.vertexInputMask, "ten inputs at 0..9");
|
||||
|
||||
final Set<Integer> seen = new HashSet<>();
|
||||
for (String name : names) {
|
||||
final int resolved = prog.resolvedAttribLocations.getInt(name);
|
||||
assertTrue(seen.add(resolved), () -> "duplicate location " + resolved + " for " + name);
|
||||
assertEquals(resolved, prog.vertexSpirv.asIntBuffer().get(indexOf(names, name)), () -> "SPIR-V Location decoration for " + name);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void aBoundNameAbsentFromTheShaderKeepsItsLocationReserved() {
|
||||
final String[] bound = {
|
||||
"a_PosId", "a_Color", "a_TexCoord", "a_LightCoord", "mc_midTexCoord",
|
||||
"at_tangent", "iris_Normal", "mc_Entity", "at_midBlock"
|
||||
};
|
||||
final String[] declared = {
|
||||
"a_PosId", "a_Color", "a_TexCoord", "a_LightCoord", "a_RdhFactor",
|
||||
"mc_midTexCoord", "iris_Normal", "mc_Entity", "at_midBlock"
|
||||
};
|
||||
final ShaderManager.ProgramObject prog = new ShaderManager.ProgramObject();
|
||||
prog.vertexSpirv = ByteBuffer.allocateDirect(declared.length * Integer.BYTES).order(ByteOrder.nativeOrder());
|
||||
final ShaderManager.StageReflection vs = reflectionWithInputs(prog.vertexSpirv, declared);
|
||||
|
||||
for (int i = 0; i < bound.length; i++) {
|
||||
prog.attribLocationBindings.put(bound[i], i);
|
||||
}
|
||||
|
||||
ShaderManager.applyAttribLocationsAndInputMask(prog, vs);
|
||||
|
||||
assertEquals(9, prog.resolvedAttribLocations.getInt("a_RdhFactor"), "location 5 stays reserved for the optimized-out at_tangent");
|
||||
assertEquals(0x3df, prog.vertexInputMask, "nine inputs at 0..4 and 6..9");
|
||||
}
|
||||
|
||||
private static int indexOf(String[] names, String name) {
|
||||
for (int i = 0; i < names.length; i++) {
|
||||
if (names[i].equals(name)) return i;
|
||||
}
|
||||
throw new IllegalArgumentException(name);
|
||||
}
|
||||
|
||||
@Test
|
||||
void unboundInputsKeepTheirOriginalLocation() {
|
||||
final ShaderManager.ProgramObject prog = new ShaderManager.ProgramObject();
|
||||
|
||||
-368
@@ -1,368 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.client.rendering;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.opengl.UniversalVAO;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.mitchej123.glsm.GLStateManagerService;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
public class AngelicaGLStateManagerService implements GLStateManagerService {
|
||||
|
||||
private IntBuffer intArrayBuffer = BufferUtils.createIntBuffer(16);
|
||||
|
||||
private IntBuffer getIntArrayBuffer(int size) {
|
||||
if (intArrayBuffer.capacity() < size) {
|
||||
intArrayBuffer = BufferUtils.createIntBuffer(size);
|
||||
}
|
||||
intArrayBuffer.clear();
|
||||
return intArrayBuffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 100;
|
||||
}
|
||||
|
||||
// ===================== QUERY OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public int glGetInteger(int pname) {
|
||||
return GLStateManager.glGetInteger(pname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String glGetString(int pname) {
|
||||
return GLStateManager.glGetString(pname);
|
||||
}
|
||||
|
||||
// ===================== FRAMEBUFFER OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public int glGenFramebuffers() {
|
||||
return GLStateManager.glGenFramebuffers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteFramebuffers(int framebuffer) {
|
||||
GLStateManager.glDeleteFramebuffers(framebuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBindFramebuffer(int target, int framebuffer) {
|
||||
GLStateManager.glBindFramebuffer(target, framebuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glCheckFramebufferStatus(int target) {
|
||||
return GLStateManager.glCheckFramebufferStatus(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glFramebufferTexture2D(int target, int attachment, int textarget, int texture, int level) {
|
||||
GLStateManager.glFramebufferTexture2D(target, attachment, textarget, texture, level);
|
||||
}
|
||||
|
||||
// ===================== SHADER OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public int glCreateShader(int type) {
|
||||
return GLStateManager.glCreateShader(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glCompileShader(int shader) {
|
||||
GLStateManager.glCompileShader(shader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glGetShaderi(int shader, int pname) {
|
||||
return GLStateManager.glGetShaderi(shader, pname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String glGetShaderInfoLog(int shader, int maxLength) {
|
||||
return GLStateManager.glGetShaderInfoLog(shader, maxLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteShader(int shader) {
|
||||
GLStateManager.glDeleteShader(shader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glCreateProgram() {
|
||||
return GLStateManager.glCreateProgram();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glAttachShader(int program, int shader) {
|
||||
GLStateManager.glAttachShader(program, shader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glLinkProgram(int program) {
|
||||
GLStateManager.glLinkProgram(program);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glGetProgrami(int program, int pname) {
|
||||
return GLStateManager.glGetProgrami(program, pname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glUseProgram(int program) {
|
||||
GLStateManager.glUseProgram(program);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteProgram(int program) {
|
||||
GLStateManager.glDeleteProgram(program);
|
||||
}
|
||||
|
||||
// ===================== UNIFORM/ATTRIBUTE OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public int glGetUniformLocation(int program, CharSequence name) {
|
||||
return GLStateManager.glGetUniformLocation(program, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glUniform1i(int location, int value) {
|
||||
GLStateManager.glUniform1i(location, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glGetAttribLocation(int program, CharSequence name) {
|
||||
return GLStateManager.glGetAttribLocation(program, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBindAttribLocation(int program, int index, CharSequence name) {
|
||||
GLStateManager.glBindAttribLocation(program, index, name);
|
||||
}
|
||||
|
||||
// ===================== VAO OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public int glGenVertexArrays() {
|
||||
return UniversalVAO.genVertexArrays();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBindVertexArray(int array) {
|
||||
GLStateManager.glBindVertexArray(array);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteVertexArrays(int array) {
|
||||
GLStateManager.glDeleteVertexArrays(array);
|
||||
}
|
||||
|
||||
// ===================== BUFFER OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public int glGenBuffers() {
|
||||
return GLStateManager.glGenBuffers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBindBuffer(int target, int buffer) {
|
||||
GLStateManager.glBindBuffer(target, buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteBuffers(int buffer) {
|
||||
GLStateManager.glDeleteBuffers(buffer);
|
||||
}
|
||||
|
||||
// ===================== TEXTURE OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public int glGenTextures() {
|
||||
return GLStateManager.glGenTextures();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glGenTextures(int[] textures) {
|
||||
final IntBuffer buffer = getIntArrayBuffer(textures.length);
|
||||
GLStateManager.glGenTextures(buffer);
|
||||
buffer.get(textures);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteTextures(int texture) {
|
||||
GLStateManager.glDeleteTextures(texture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDeleteTextures(int[] textures) {
|
||||
final IntBuffer buffer = getIntArrayBuffer(textures.length);
|
||||
buffer.put(textures);
|
||||
buffer.flip();
|
||||
GLStateManager.glDeleteTextures(buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glActiveTexture(int texture) {
|
||||
GLStateManager.glActiveTexture(texture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glGetTexLevelParameteri(int target, int level, int pname) {
|
||||
return GLStateManager.glGetTexLevelParameteri(target, level, pname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int glGetTexLevelParameter(int target, int level, int pname) {
|
||||
return GLStateManager.glGetTexLevelParameteri(target, level, pname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glCopyTexSubImage2D(int target, int level, int xoffset, int yoffset, int x, int y, int width, int height) {
|
||||
GLStateManager.glCopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glPixelStorei(int pname, int param) {
|
||||
GLStateManager.glPixelStorei(pname, param);
|
||||
}
|
||||
|
||||
// ===================== STATE OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public void enableCullFace() {
|
||||
GLStateManager.enableCull();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableCullFace() {
|
||||
GLStateManager.disableCull();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableBlend() {
|
||||
GLStateManager.enableBlend();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableBlend() {
|
||||
GLStateManager.disableBlend();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glBlendFuncSeparate(int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) {
|
||||
GLStateManager.tryBlendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableDepthTest() {
|
||||
GLStateManager.enableDepthTest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableDepthTest() {
|
||||
GLStateManager.disableDepthTest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDepthFunc(int func) {
|
||||
GLStateManager.glDepthFunc(func);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glDepthMask(boolean flag) {
|
||||
GLStateManager.glDepthMask(flag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glViewport(int x, int y, int width, int height) {
|
||||
GLStateManager.glViewport(x, y, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glColorMask(boolean red, boolean green, boolean blue, boolean alpha) {
|
||||
GLStateManager.glColorMask(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glClearColor(float red, float green, float blue, float alpha) {
|
||||
GLStateManager.glClearColor(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glClear(int mask) {
|
||||
GLStateManager.glClear(mask);
|
||||
}
|
||||
|
||||
// ===================== MOJANG ADDITIONS =====================
|
||||
|
||||
@Override
|
||||
public void clear(int mask, boolean checkError) {
|
||||
GLStateManager.glClear(mask);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindTexture(int texture) {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getActiveTexture() {
|
||||
return GLStateManager.getActiveTextureUnit() + GL13.GL_TEXTURE0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getActiveTextureAccessor() {
|
||||
return GLStateManager.getActiveTextureUnit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBoundTexture(int internalUnit) {
|
||||
return GLStateManager.getBoundTextureForServerState(internalUnit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getActiveBoundTexture() {
|
||||
return GLStateManager.getBoundTextureForServerState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewportWidth() {
|
||||
return GLStateManager.getViewportState().width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewportHeight() {
|
||||
return GLStateManager.getViewportState().height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getDepthStateMask() {
|
||||
return GLStateManager.getDepthState().isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBlendEnabled() {
|
||||
return GLStateManager.getBlendMode().isEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBoundTexture(int unit, int texture) {
|
||||
final int currentUnit = GLStateManager.getActiveTextureUnit();
|
||||
if (currentUnit != unit) {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
|
||||
}
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
if (currentUnit != unit) {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + currentUnit);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTextureBinding(int unit) {
|
||||
return GLStateManager.getBoundTextureForServerState(unit);
|
||||
}
|
||||
}
|
||||
-243
@@ -1,243 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.client.rendering;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.mitchej123.glsm.RenderSystemService;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
public class AngelicaRenderSystemService implements RenderSystemService {
|
||||
|
||||
// Shader state (Mojang-specific, tracked here)
|
||||
private final int[] shaderTextures = new int[16];
|
||||
private final float[] shaderColor = {1f, 1f, 1f, 1f};
|
||||
private final float[] shaderFogColor = {0f, 0f, 0f, 1f};
|
||||
private float shaderFogStart = 0f;
|
||||
private float shaderFogEnd = 0f;
|
||||
private int fogShape = 0;
|
||||
private float shaderLineWidth = 1f;
|
||||
private final Matrix4f projectionMatrix = new Matrix4f();
|
||||
private final FloatBuffer projectionMatrixBuffer = BufferUtils.createFloatBuffer(16);
|
||||
|
||||
@Override
|
||||
public int getPriority() {
|
||||
return 100;
|
||||
}
|
||||
|
||||
// ===================== TEXTURE OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public void glActiveTexture(int texture) {
|
||||
GLStateManager.glActiveTexture(texture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindTexture(int texture) {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
}
|
||||
|
||||
// ===================== STATE OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public void enableCullFace() {
|
||||
GLStateManager.enableCull();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableCullFace() {
|
||||
GLStateManager.disableCull();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableBlend() {
|
||||
GLStateManager.enableBlend();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableBlend() {
|
||||
GLStateManager.disableBlend();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void defaultBlendFunc() {
|
||||
GLStateManager.defaultBlendFunc();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void blendFuncSeparate(int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) {
|
||||
GLStateManager.tryBlendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUnknownBlendState() {
|
||||
// No-op: Angelica tracks blend state accurately
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableDepthTest() {
|
||||
GLStateManager.enableDepthTest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disableDepthTest() {
|
||||
GLStateManager.disableDepthTest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void depthFunc(int depthFunc) {
|
||||
GLStateManager.glDepthFunc(depthFunc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void depthMask(boolean flag) {
|
||||
GLStateManager.glDepthMask(flag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glViewport(int x, int y, int width, int height) {
|
||||
GLStateManager.glViewport(x, y, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glClearColor(float red, float green, float blue, float alpha) {
|
||||
GLStateManager.glClearColor(red, green, blue, alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear(int mask, boolean checkError) {
|
||||
GLStateManager.glClear(mask);
|
||||
}
|
||||
|
||||
// ===================== UNIFORM OPERATIONS =====================
|
||||
|
||||
@Override
|
||||
public void glUniform1i(int location, int value) {
|
||||
GLStateManager.glUniform1i(location, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glUniformMatrix3(int location, boolean transpose, FloatBuffer value) {
|
||||
GLStateManager.glUniformMatrix3(location, transpose, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void glUniformMatrix4(int location, boolean transpose, FloatBuffer value) {
|
||||
GLStateManager.glUniformMatrix4(location, transpose, value);
|
||||
}
|
||||
|
||||
// ===================== THREAD ASSERTIONS =====================
|
||||
|
||||
@Override
|
||||
public void assertOnRenderThread() {
|
||||
// No-op: 1.7.10 is single-threaded for rendering
|
||||
}
|
||||
|
||||
@Override
|
||||
public void assertOnRenderThreadOrInit() {
|
||||
// No-op: 1.7.10 is single-threaded for rendering
|
||||
}
|
||||
|
||||
// ===================== SHADER STATE =====================
|
||||
|
||||
@Override
|
||||
public void setShaderTexture(int shaderTexture, int textureId) {
|
||||
if (shaderTexture >= 0 && shaderTexture < 16) {
|
||||
shaderTextures[shaderTexture] = textureId;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getShaderTexture(int shaderTexture) {
|
||||
return (shaderTexture >= 0 && shaderTexture < 16) ? shaderTextures[shaderTexture] : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShaderColor(float red, float green, float blue, float alpha) {
|
||||
shaderColor[0] = red;
|
||||
shaderColor[1] = green;
|
||||
shaderColor[2] = blue;
|
||||
shaderColor[3] = alpha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] getShaderColor() {
|
||||
return shaderColor.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShaderLineWidth(float lineWidth) {
|
||||
shaderLineWidth = lineWidth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getShaderLineWidth() {
|
||||
return shaderLineWidth;
|
||||
}
|
||||
|
||||
// ===================== FOG STATE =====================
|
||||
|
||||
@Override
|
||||
public void setShaderFogColor(float red, float green, float blue, float alpha) {
|
||||
shaderFogColor[0] = red;
|
||||
shaderFogColor[1] = green;
|
||||
shaderFogColor[2] = blue;
|
||||
shaderFogColor[3] = alpha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float[] getShaderFogColor() {
|
||||
return shaderFogColor.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShaderFogStart(float start) {
|
||||
shaderFogStart = start;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getShaderFogStart() {
|
||||
return shaderFogStart;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setShaderFogEnd(float end) {
|
||||
shaderFogEnd = end;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getShaderFogEnd() {
|
||||
return shaderFogEnd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFogShape(int shape) {
|
||||
fogShape = shape;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFogShape() {
|
||||
return fogShape;
|
||||
}
|
||||
|
||||
// ===================== PROJECTION MATRIX =====================
|
||||
|
||||
@Override
|
||||
public Matrix4f getProjectionMatrix() {
|
||||
return new Matrix4f(projectionMatrix);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProjectionMatrixOrth(Matrix4f m) {
|
||||
projectionMatrix.set(m);
|
||||
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
|
||||
projectionMatrix.get(projectionMatrixBuffer);
|
||||
projectionMatrixBuffer.rewind();
|
||||
GLStateManager.glLoadMatrix(projectionMatrixBuffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProjectionMatrixOrigin(Matrix4f m) {
|
||||
setProjectionMatrixOrth(m);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
||||
import cpw.mods.fml.common.gameevent.TickEvent;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.entity.EntityClientPlayerMP;
|
||||
import net.minecraft.client.gui.GuiMainMenu;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.util.ChatComponentText;
|
||||
import net.minecraft.util.EnumChatFormatting;
|
||||
@@ -27,7 +28,6 @@ public final class FlybyRunner {
|
||||
private static final float[] NO_YAW = new float[0];
|
||||
private static final byte[] NO_PHASE = new byte[0];
|
||||
private static final int MAX_RECORDED_FRAMES = 200_000;
|
||||
private static final long FIXED_TIME_OF_DAY = 6000L;
|
||||
|
||||
private enum State { IDLE, WAITING, WARMUP, RUNNING, SETTLE, EXITING, DONE }
|
||||
|
||||
@@ -123,6 +123,14 @@ public final class FlybyRunner {
|
||||
if (event.phase != TickEvent.Phase.END || this.state == State.IDLE || this.state == State.DONE) return;
|
||||
|
||||
final Minecraft mc = Minecraft.getMinecraft();
|
||||
if (this.state == State.EXITING) {
|
||||
if (++this.tick >= EXIT_TICKS) {
|
||||
this.state = State.DONE;
|
||||
this.exitGame(mc);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final EntityClientPlayerMP player = mc.thePlayer;
|
||||
if (player == null || mc.theWorld == null) return;
|
||||
|
||||
@@ -152,13 +160,6 @@ public final class FlybyRunner {
|
||||
this.teardown(mc, player);
|
||||
}
|
||||
}
|
||||
case EXITING -> {
|
||||
if (++this.tick >= EXIT_TICKS) {
|
||||
this.state = State.DONE;
|
||||
LOGGER.info("Flyby complete, shutting down so the capture can finalise");
|
||||
mc.shutdown();
|
||||
}
|
||||
}
|
||||
default -> { }
|
||||
}
|
||||
}
|
||||
@@ -383,9 +384,9 @@ public final class FlybyRunner {
|
||||
world.getWorldInfo().setRainTime(Integer.MAX_VALUE);
|
||||
world.getWorldInfo().setThunderTime(Integer.MAX_VALUE);
|
||||
|
||||
if (world.getWorldTime() % 24000L != FIXED_TIME_OF_DAY) {
|
||||
LOGGER.info("Flyby: freezing time at {}", FIXED_TIME_OF_DAY);
|
||||
world.setWorldTime(FIXED_TIME_OF_DAY);
|
||||
if (world.getWorldTime() % 24000L != SystemProperties.FLYBY_TIME_OF_DAY) {
|
||||
LOGGER.info("Flyby: freezing time at {}", SystemProperties.FLYBY_TIME_OF_DAY);
|
||||
world.setWorldTime(SystemProperties.FLYBY_TIME_OF_DAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -419,6 +420,16 @@ public final class FlybyRunner {
|
||||
this.state = this.exitWhenDone ? State.EXITING : State.DONE;
|
||||
}
|
||||
|
||||
private void exitGame(Minecraft mc) {
|
||||
LOGGER.info("Flyby complete, shutting down.");
|
||||
if (mc.theWorld != null) {
|
||||
mc.theWorld.sendQuittingDisconnectingPacket();
|
||||
mc.loadWorld(null);
|
||||
mc.displayGuiScreen(new GuiMainMenu());
|
||||
}
|
||||
mc.shutdown();
|
||||
}
|
||||
|
||||
private void restorePauseOnLostFocus(Minecraft mc) {
|
||||
if (!this.pauseOnLostFocusOverridden) return;
|
||||
mc.gameSettings.pauseOnLostFocus = this.pauseOnLostFocusSaved;
|
||||
|
||||
@@ -10,12 +10,11 @@ import com.gtnewhorizons.angelica.config.CompatConfig;
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
import com.gtnewhorizons.angelica.glsm.loading.DependencyVerifier;
|
||||
import com.gtnewhorizons.angelica.glsm.loading.EcosystemNarrowRules;
|
||||
import com.gtnewhorizons.angelica.glsm.loading.Lwjgl3ifyExclusions;
|
||||
import com.gtnewhorizons.angelica.loading.fml.compat.CompatHandlers;
|
||||
import com.gtnewhorizons.angelica.lwjgl3.MissingDependencySdl;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SDLGPUGate;
|
||||
import com.gtnewhorizons.angelica.mixins.Mixins;
|
||||
import com.gtnewhorizons.retrofuturabootstrap.SharedConfig;
|
||||
import cpw.mods.fml.relauncher.FMLLaunchHandler;
|
||||
import cpw.mods.fml.relauncher.FMLRelaunchLog;
|
||||
import cpw.mods.fml.relauncher.IFMLLoadingPlugin;
|
||||
@@ -33,8 +32,6 @@ import org.apache.logging.log4j.core.config.LoggerConfig;
|
||||
import org.spongepowered.asm.launch.GlobalProperties;
|
||||
import org.spongepowered.asm.service.mojang.MixinServiceLaunchWrapper;
|
||||
|
||||
import javax.swing.JOptionPane;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -57,7 +54,7 @@ public final class AngelicaClientTweaker implements IFMLLoadingPlugin, IEarlyMix
|
||||
|
||||
final boolean rfbLoaded = Launch.blackboard.getOrDefault("angelica.rfbPluginLoaded", Boolean.FALSE) == Boolean.TRUE;
|
||||
if (rfbLoaded) {
|
||||
addLwjgl3ifyExclusions();
|
||||
Lwjgl3ifyExclusions.apply();
|
||||
} else {
|
||||
// Fucking java 8 and non RFB
|
||||
try {
|
||||
@@ -86,7 +83,9 @@ public final class AngelicaClientTweaker implements IFMLLoadingPlugin, IEarlyMix
|
||||
} catch (ConfigException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
verifyDependencies();
|
||||
DependencyVerifier.verifyOrHalt(AngelicaTweaker.class, "Angelica",
|
||||
DependencyVerifier.gtnhLibChecks("Angelica"), LOGGER,
|
||||
(title, message) -> MissingDependencySdl.showFatal(title, message));
|
||||
|
||||
if (SystemProperties.USE_SDL_GPU) {
|
||||
if (SDLGPUGate.isSDLGPUAvailable()) {
|
||||
@@ -109,56 +108,6 @@ public final class AngelicaClientTweaker implements IFMLLoadingPlugin, IEarlyMix
|
||||
}
|
||||
}
|
||||
|
||||
private static void addLwjgl3ifyExclusions() {
|
||||
final var handle = SharedConfig.getRfbTransformers().stream()
|
||||
.filter(transformer -> transformer.id().equals("lwjgl3ify:redirect"))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (handle != null) {
|
||||
for (String exclusion : EcosystemNarrowRules.LWJGL3IFY_EXCLUSIONS_SHARED) {
|
||||
handle.exclusions().add(exclusion);
|
||||
}
|
||||
handle.exclusions().add("com.gtnewhorizons.angelica.sdlgpu");
|
||||
}
|
||||
}
|
||||
|
||||
private static void verifyDependencies() {
|
||||
try {
|
||||
DependencyVerifier.verify(AngelicaTweaker.class, List.of(
|
||||
new DependencyVerifier.Check(
|
||||
"/it/unimi/dsi/fastutil/ints/Int2ObjectMap.class",
|
||||
"Missing dependency: Angelica requires GTNHLib! Download: https://modrinth.com/mod/gtnhlib"),
|
||||
new DependencyVerifier.Check(
|
||||
"/com/gtnewhorizon/gtnhlib/client/renderer/VertexCallbackManager.class",
|
||||
"GTNHLib is outdated: Angelica requires GTNHLib 0.10.0 or newer! Download: https://modrinth.com/mod/gtnhlib"),
|
||||
new DependencyVerifier.Check(
|
||||
"/it/unimi/dsi/fastutil/objects/ObjectBooleanBiConsumer.class",
|
||||
"GTNHLib is outdated: Angelica requires GTNHLib 0.11.19 or newer (fastutil 8.5.18+)! Download: https://modrinth.com/mod/gtnhlib")
|
||||
));
|
||||
} catch (RuntimeException ex) {
|
||||
fatalDependencyError(ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void fatalDependencyError(String message) {
|
||||
final String title = "Angelica - Missing Dependency";
|
||||
System.err.println("FATAL: " + message);
|
||||
try {
|
||||
LOGGER.fatal(message);
|
||||
} catch (Throwable ignored) {}
|
||||
|
||||
final int lwjgl3ifyMajor = ((Integer) Launch.blackboard.getOrDefault("lwjgl3ify:major-version", Integer.MIN_VALUE));
|
||||
try {
|
||||
if (lwjgl3ifyMajor >= 3) {
|
||||
MissingDependencySdl.showFatal(title, message);
|
||||
} else if (!GraphicsEnvironment.isHeadless()) {
|
||||
JOptionPane.showMessageDialog(null, message, title, JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
} catch (Throwable ignored) {}
|
||||
|
||||
Runtime.getRuntime().halt(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getASMTransformerClass() {
|
||||
if (transformerClasses == null) {
|
||||
|
||||
+3
-36
@@ -1,48 +1,15 @@
|
||||
package com.gtnewhorizons.angelica.loading.fml.transformers;
|
||||
|
||||
import com.gtnewhorizons.angelica.loading.shared.AngelicaClassDump;
|
||||
import com.gtnewhorizons.angelica.glsm.loading.ModRedirector;
|
||||
import com.gtnewhorizons.angelica.loading.shared.transformers.AngelicaRedirector;
|
||||
import net.minecraft.launchwrapper.IClassTransformer;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
|
||||
/** IClassTransformer wrapper for {@link AngelicaRedirector} */
|
||||
public class AngelicaRedirectorTransformer implements IClassTransformer {
|
||||
|
||||
private final AngelicaRedirector inner;
|
||||
private final String[] exclusions;
|
||||
|
||||
public AngelicaRedirectorTransformer() {
|
||||
inner = new AngelicaRedirector();
|
||||
exclusions = inner.getTransformerExclusions();
|
||||
}
|
||||
private final ModRedirector inner = AngelicaRedirector.create();
|
||||
|
||||
@Override
|
||||
public byte[] transform(final String className, String transformedName, byte[] basicClass) {
|
||||
if (basicClass == null) return null;
|
||||
|
||||
// Ignore classes that are excluded from transformation - Doesn't fully work without the
|
||||
// TransformerExclusions due to some nested classes
|
||||
for (String exclusion : exclusions) {
|
||||
if (transformedName.startsWith(exclusion)) return basicClass;
|
||||
}
|
||||
|
||||
if (!inner.shouldTransform(basicClass)) {
|
||||
return basicClass;
|
||||
}
|
||||
|
||||
final ClassReader cr = new ClassReader(basicClass);
|
||||
final ClassNode cn = new ClassNode();
|
||||
cr.accept(cn, 0);
|
||||
final boolean changed = inner.transformClassNode(transformedName, cn);
|
||||
if (changed) {
|
||||
final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||
cn.accept(cw);
|
||||
final byte[] bytes = cw.toByteArray();
|
||||
AngelicaClassDump.dumpClass(transformedName, basicClass, bytes, this);
|
||||
return bytes;
|
||||
}
|
||||
return basicClass;
|
||||
return inner.transform(transformedName, basicClass, this);
|
||||
}
|
||||
}
|
||||
+2
-5
@@ -18,13 +18,10 @@ import net.minecraft.launchwrapper.IClassTransformer;
|
||||
*/
|
||||
public class EarlyRedirectorTransformer implements IClassTransformer {
|
||||
|
||||
private final EarlyRedirectorCore impl = new EarlyRedirectorCore(
|
||||
"com.gtnewhorizons.angelica.lwjgl3.",
|
||||
"com.gtnewhorizons.angelica.transform"
|
||||
);
|
||||
private final EarlyRedirectorCore impl = new EarlyRedirectorCore("com.gtnewhorizons.angelica.transform");
|
||||
|
||||
@Override
|
||||
public byte[] transform(String name, String transformedName, byte[] basicClass) {
|
||||
return impl.transform(transformedName, basicClass);
|
||||
return impl.transform(transformedName, basicClass, this);
|
||||
}
|
||||
}
|
||||
+3
-71
@@ -1,79 +1,11 @@
|
||||
package com.gtnewhorizons.angelica.loading.rfb.transformers;
|
||||
|
||||
import com.gtnewhorizons.angelica.loading.shared.AngelicaClassDump;
|
||||
import com.gtnewhorizons.angelica.glsm.loading.RfbModRedirector;
|
||||
import com.gtnewhorizons.angelica.loading.shared.transformers.AngelicaRedirector;
|
||||
import com.gtnewhorizons.retrofuturabootstrap.api.ClassNodeHandle;
|
||||
import com.gtnewhorizons.retrofuturabootstrap.api.ExtensibleClassLoader;
|
||||
import com.gtnewhorizons.retrofuturabootstrap.api.RfbClassTransformer;
|
||||
import org.intellij.lang.annotations.Pattern;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
/** RfbClassTransformer wrapper for {@link AngelicaRedirector} */
|
||||
public class RFBAngelicaRedirector implements RfbClassTransformer {
|
||||
|
||||
private final AngelicaRedirector inner;
|
||||
public class RFBAngelicaRedirector extends RfbModRedirector {
|
||||
|
||||
public RFBAngelicaRedirector() {
|
||||
inner = new AngelicaRedirector();
|
||||
}
|
||||
|
||||
@Pattern("[a-z0-9-]+")
|
||||
@Override
|
||||
public @NotNull String id() {
|
||||
return "redirector";
|
||||
}
|
||||
|
||||
private static final Attributes.Name LWJGL3_AWARE = new Attributes.Name("Lwjgl3ify-Aware");
|
||||
|
||||
/** We sort before lwjgl3ify:redirect, so an aware caller's descriptors are still org.lwjgl here and stay that way. */
|
||||
private static boolean isLwjgl3Aware(@Nullable Manifest manifest) {
|
||||
return manifest != null && "true".equals(manifest.getMainAttributes().getValue(LWJGL3_AWARE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String @Nullable [] sortAfter() {
|
||||
return new String[] {"*", "mixin:mixin"};
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String @Nullable [] sortBefore() {
|
||||
return new String[] {"lwjgl3ify:redirect"};
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String @Nullable [] additionalExclusions() {
|
||||
return inner.getTransformerExclusions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldTransformClass(@NotNull ExtensibleClassLoader classLoader,
|
||||
@NotNull RfbClassTransformer.Context context, @Nullable Manifest manifest, @NotNull String className,
|
||||
@NotNull ClassNodeHandle classNode) {
|
||||
if (!classNode.isPresent()) {
|
||||
return false;
|
||||
}
|
||||
return inner.shouldTransform(classNode.getOriginalBytes());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean transformClassIfNeeded(@NotNull ExtensibleClassLoader classLoader,
|
||||
@NotNull RfbClassTransformer.Context context, @Nullable Manifest manifest,
|
||||
@NotNull String className, @NotNull ClassNodeHandle classNode) {
|
||||
final boolean changed = inner.transformClassNode(className, classNode.getNode(), isLwjgl3Aware(manifest));
|
||||
if (changed) {
|
||||
classNode.computeMaxs();
|
||||
AngelicaClassDump.dumpRFBClass(className, classNode, this);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transformClass(@NotNull ExtensibleClassLoader classLoader, @NotNull RfbClassTransformer.Context context,
|
||||
@Nullable Manifest manifest, @NotNull String className, @NotNull ClassNodeHandle classNode) {
|
||||
transformClassIfNeeded(classLoader, context, manifest, className, classNode);
|
||||
super("redirector", AngelicaRedirector.create());
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,18 @@
|
||||
package com.gtnewhorizons.angelica.loading.shared;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.asm.ASMUtil;
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
import com.gtnewhorizons.angelica.glsm.loading.ClassDump;
|
||||
import com.gtnewhorizons.retrofuturabootstrap.api.ClassNodeHandle;
|
||||
|
||||
public class AngelicaClassDump {
|
||||
|
||||
public static final ClassDump INSTANCE = new ClassDump(SystemProperties.DUMP_CLASS);
|
||||
|
||||
public static void dumpClass(String className, byte[] originalBytes, byte[] transformedBytes, Object transformer) {
|
||||
if (SystemProperties.DUMP_CLASS) {
|
||||
ASMUtil.saveAsRawClassFile(originalBytes, className + "_PRE", transformer);
|
||||
ASMUtil.saveAsRawClassFile(transformedBytes, className + "_POST", transformer);
|
||||
}
|
||||
INSTANCE.dumpClass(className, originalBytes, transformedBytes, transformer);
|
||||
}
|
||||
|
||||
public static void dumpRFBClass(String className, ClassNodeHandle classNode, Object transformer) {
|
||||
if (SystemProperties.DUMP_CLASS) {
|
||||
final byte[] originalBytes = classNode.getOriginalBytes();
|
||||
final byte[] transformedBytes = classNode.computeBytes();
|
||||
ASMUtil.saveAsRawClassFile(originalBytes, className + "_PRE", transformer);
|
||||
ASMUtil.saveAsRawClassFile(transformedBytes, className + "_POST", transformer);
|
||||
}
|
||||
INSTANCE.dumpRFBClass(className, classNode, transformer);
|
||||
}
|
||||
}
|
||||
+5
-37
@@ -1,45 +1,13 @@
|
||||
package com.gtnewhorizons.angelica.loading.shared.transformers;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.redirect.GLSMRedirector;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import com.gtnewhorizons.angelica.glsm.loading.ModRedirector;
|
||||
import com.gtnewhorizons.angelica.loading.shared.AngelicaClassDump;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Mod-level wrapper around {@link GLSMRedirector}. Adds mod-specific transformer exclusions and delegates all redirect logic to GLSM.
|
||||
* <p>
|
||||
* THIS CLASS MIGHT BE LOADED ON A DIFFERENT CLASS LOADER,
|
||||
* IT SHOULD NOT CALL ANY CODE FROM THE MAIN MOD
|
||||
*/
|
||||
public final class AngelicaRedirector {
|
||||
|
||||
private final GLSMRedirector core = new GLSMRedirector();
|
||||
private final String[] allExclusions;
|
||||
|
||||
public AngelicaRedirector() {
|
||||
final List<String> exclusions = new ArrayList<>(Arrays.asList(core.getCoreExclusions()));
|
||||
exclusions.add("com.gtnewhorizons.angelica.lwjgl3.");
|
||||
exclusions.add("com.gtnewhorizons.angelica.transform");
|
||||
allExclusions = exclusions.toArray(new String[0]);
|
||||
public static ModRedirector create() {
|
||||
return new ModRedirector(AngelicaClassDump.INSTANCE, "com.gtnewhorizons.angelica.transform");
|
||||
}
|
||||
|
||||
public String[] getTransformerExclusions() {
|
||||
return allExclusions;
|
||||
}
|
||||
|
||||
public boolean shouldTransform(byte[] basicClass) {
|
||||
return core.shouldTransform(basicClass);
|
||||
}
|
||||
|
||||
/** @return Was the class changed? */
|
||||
public boolean transformClassNode(String transformedName, ClassNode cn) {
|
||||
return core.transformClassNode(transformedName, cn);
|
||||
}
|
||||
|
||||
/** @return Was the class changed? */
|
||||
public boolean transformClassNode(String transformedName, ClassNode cn, boolean lwjgl3Aware) {
|
||||
return core.transformClassNode(transformedName, cn, lwjgl3Aware);
|
||||
}
|
||||
private AngelicaRedirector() {}
|
||||
}
|
||||
@@ -103,12 +103,6 @@ public enum Mixins implements IMixins {
|
||||
)
|
||||
),
|
||||
|
||||
ANGELICA_SDL_GPU_SHADOW_VOXEL_PREPASS(new MixinBuilder("Voxelization compute pre-pass before shadow raster (SDL_GPU)")
|
||||
.setPhase(Phase.EARLY)
|
||||
.setApplyIf(() -> SystemProperties.USE_SDL_GPU && SDLGPUGate.isSDLGPUAvailable())
|
||||
.addClientMixins("sdlgpu.MixinDefaultChunkRenderer_ShadowVoxelization")
|
||||
),
|
||||
|
||||
ANGELICA_VBO_CLOUDS(
|
||||
new MixinBuilder()
|
||||
.setApplyIf(() -> AngelicaConfig.enableVBOClouds)
|
||||
@@ -367,7 +361,6 @@ public enum Mixins implements IMixins {
|
||||
"celeritas.terrain.MixinChunkProviderClient"
|
||||
, "celeritas.terrain.MixinMinecraft_ChunkUpdates"
|
||||
, "celeritas.terrain.MixinRenderGlobal"
|
||||
, "celeritas.terrain.MixinRenderListManager"
|
||||
, "celeritas.terrain.MixinRenderSectionManager"
|
||||
, "celeritas.terrain.MixinWorldClient"
|
||||
, "celeritas.frustum.MixinClippingHelper"
|
||||
@@ -391,7 +384,6 @@ public enum Mixins implements IMixins {
|
||||
, "celeritas.terrain.MixinWorld_AwaitingDescriptor"
|
||||
, "celeritas.terrain.MixinRenderRegion"
|
||||
, "celeritas.terrain.MixinSectionRenderDataStorage"
|
||||
, "celeritas.terrain.MixinDefaultChunkRenderer"
|
||||
, "celeritas.terrain.MixinDefaultChunkShaderInterface"
|
||||
)
|
||||
),
|
||||
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.mixins.interfaces;
|
||||
|
||||
public interface RenderListManagerAccessor {
|
||||
boolean angelica$hasOcclusionFutureInFlight();
|
||||
}
|
||||
-3
@@ -2,13 +2,10 @@ package com.gtnewhorizons.angelica.mixins.interfaces;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ReferenceMap;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.RenderSection;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.region.RenderRegionManager;
|
||||
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
|
||||
public interface RenderSectionManagerAccessor {
|
||||
Long2ReferenceMap<RenderSection> angelica$getSectionByPosition();
|
||||
ConcurrentLinkedDeque<Runnable> angelica$getAsyncSubmittedTasks();
|
||||
RenderRegionManager angelica$getRegions();
|
||||
float angelica$getSearchDistance();
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import com.gtnewhorizons.angelica.dynamiclights.DynamicLights;
|
||||
import com.gtnewhorizons.angelica.glsm.CaptureGate;
|
||||
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.rendering.culling.GpuIndirectMultiDrawEmitter;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.TracyBackend;
|
||||
@@ -22,20 +21,18 @@ import net.coderbot.iris.pipeline.ShadowRenderer;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.WorldRenderer;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.SharedQuadIndexBuffer;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.multidraw.IndirectMultiDrawEmitter;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.region.RenderRegionManager;
|
||||
|
||||
public final class TracyFramePlots {
|
||||
private static long lastChunkUpdates;
|
||||
private static long lastDrawCalls, lastTexBindMisses, lastProgramSwitches, lastListPlaybacks;
|
||||
private static long lastStreamedBytes, lastStreamDraws, lastOrphanFallbacks, lastStreamContiguous;
|
||||
private static long lastSectionMetaBytes, lastRingWraps, lastVariantSwitches;
|
||||
private static long lastRingWraps, lastVariantSwitches;
|
||||
private static long lastTesrRebuilds, lastTesrRetainedDraws, lastTesrInstDraws, lastTesrInstInstances;
|
||||
private static long lastTesrStreamedInstances, lastTesrPromotions, lastTesrCacheHits, lastTesrCacheMisses;
|
||||
private static long lastBatcherParts, lastBatcherLiveFallbacks;
|
||||
private static final long[] lastBails = new long[ModelPartBatcher.BailReason.VALUES.length];
|
||||
private static long lastSectionsUploaded, lastBytesUploaded;
|
||||
private static int lastCullSubmitted;
|
||||
private static long lastSectionsUploaded, lastBytesUploaded, lastIndexBufferGrowths;
|
||||
|
||||
private static final long P_ALLOC_RATE = Tracy.plotHandle("allocRate", TracyBackend.PLOT_FORMAT_MEMORY);
|
||||
private static final long P_HEAP_USED = Tracy.plotHandle("heapUsed", TracyBackend.PLOT_FORMAT_MEMORY);
|
||||
@@ -61,7 +58,7 @@ public final class TracyFramePlots {
|
||||
private static final long P_VOX_ENCODERS = Tracy.plotHandle("voxelization.encoders");
|
||||
private static final long P_VOX_DISPATCHES = Tracy.plotHandle("voxelization.dispatches");
|
||||
private static final long P_VOX_REGIONS = Tracy.plotHandle("voxelization.regions");
|
||||
private static final long P_DRAW_ARENA_GROWTHS = Tracy.plotHandle("draw.arenaGrowths");
|
||||
private static final long P_DRAW_BATCH_REBUILDS = Tracy.plotHandle("draw.batchRebuilds");
|
||||
private static final long P_DRAW_INDEX_BUFFER_GROWTHS = Tracy.plotHandle("draw.indexBufferGrowths");
|
||||
|
||||
private static final long P_CULL_COMMANDS_SUBMITTED = Tracy.plotHandle("cull.commandsSubmitted");
|
||||
@@ -175,20 +172,17 @@ public final class TracyFramePlots {
|
||||
lastOrphanFallbacks = delta(P_GL_ORPHAN_FALLBACKS, TessellatorStreamingDrawer.orphanFallbacks, lastOrphanFallbacks);
|
||||
lastStreamContiguous = delta(P_GL_STREAM_CONTIGUOUS, TessellatorStreamingDrawer.streamContiguous, lastStreamContiguous);
|
||||
lastRingWraps = delta(P_GL_RING_WRAPS, TessellatorStreamingDrawer.ringWraps(), lastRingWraps);
|
||||
lastSectionMetaBytes = delta(P_GL_SECTION_META_BYTES, GpuIndirectMultiDrawEmitter.sectionMetaBytes, lastSectionMetaBytes);
|
||||
Tracy.plotInt(P_GL_SECTION_META_BYTES, TerrainDrawStats.takeSectionMetaBytes());
|
||||
Tracy.plotInt(P_SHADOW_RELAY_FRAMES, ShadowRenderer.SHADOW_TERRAIN_RELAID ? 1 : 0);
|
||||
Tracy.plotInt(P_TERRAIN_DRAW_COMMANDS, TerrainDrawStats.takeCommandsSubmitted());
|
||||
Tracy.plotInt(P_TERRAIN_REGIONS_DRAWN, TerrainDrawStats.takeRegionsDrawn());
|
||||
Tracy.plotInt(P_DRAW_ARENA_GROWTHS, IndirectMultiDrawEmitter.takeArenaGrowths());
|
||||
Tracy.plotInt(P_DRAW_INDEX_BUFFER_GROWTHS, SharedQuadIndexBuffer.takeGrowths());
|
||||
Tracy.plotInt(P_DRAW_BATCH_REBUILDS, TerrainDrawStats.takeBatchRebuilds());
|
||||
Tracy.plotInt(P_VOX_ENCODERS, SdlShadowVoxelizationSink.takeEncoders());
|
||||
Tracy.plotInt(P_VOX_DISPATCHES, SdlShadowVoxelizationSink.takeDispatches());
|
||||
Tracy.plotInt(P_VOX_REGIONS, SdlShadowVoxelizationSink.takeRegions());
|
||||
Tracy.plotInt(P_CULL_REGIONS_DRAWN, GpuIndirectMultiDrawEmitter.takeRegionsDrawn());
|
||||
Tracy.plotInt(P_CULL_MAX_REGION_COMMANDS, GpuIndirectMultiDrawEmitter.takeMaxRegionCommands());
|
||||
final int submitted = GpuIndirectMultiDrawEmitter.commandsSubmitted();
|
||||
Tracy.plotInt(P_CULL_COMMANDS_SUBMITTED, submitted - lastCullSubmitted);
|
||||
lastCullSubmitted = submitted;
|
||||
Tracy.plotInt(P_CULL_REGIONS_DRAWN, TerrainDrawStats.takeCullRegionsDrawn());
|
||||
Tracy.plotInt(P_CULL_MAX_REGION_COMMANDS, TerrainDrawStats.takeCullMaxRegionCommands());
|
||||
Tracy.plotInt(P_CULL_COMMANDS_SUBMITTED, TerrainDrawStats.takeCullCommandsSubmitted());
|
||||
|
||||
final ShaderManager ffp = ShaderManager.getInstance();
|
||||
Tracy.plotInt(P_FFP_PRE_DRAW_CALLS, ffp.statLastFramePreDrawCalls());
|
||||
@@ -231,6 +225,7 @@ public final class TracyFramePlots {
|
||||
|
||||
lastSectionsUploaded = delta(P_MESH_UPLOADED_SECTIONS, RenderRegionManager.getSectionsUploaded(), lastSectionsUploaded);
|
||||
lastBytesUploaded = delta(P_MESH_UPLOADED_BYTES, RenderRegionManager.getBytesUploaded(), lastBytesUploaded);
|
||||
lastIndexBufferGrowths = delta(P_DRAW_INDEX_BUFFER_GROWTHS, SharedQuadIndexBuffer.getGrowths(), lastIndexBufferGrowths);
|
||||
Tracy.plotInt(P_MESH_META_SLOTS, GpuCulling.sectionMeta().getHighWaterMark());
|
||||
Tracy.plotInt(P_MT_QUEUE_DEPTH, AngelicaRenderQueue.getQueueDepth());
|
||||
Tracy.plotInt(P_MT_QUEUE_TASKS, AngelicaRenderQueue.getLastFrameTasksRan());
|
||||
|
||||
+167
-51
@@ -1,16 +1,27 @@
|
||||
package com.gtnewhorizons.angelica.rendering.celeritas;
|
||||
|
||||
import com.gtnewhorizons.angelica.AngelicaMod;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.rendering.culling.GpuIndirectMultiDrawEmitter;
|
||||
import com.gtnewhorizons.angelica.rendering.culling.GpuTerrainCuller;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.proxy.ClientProxy;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProvider;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProviderHolder;
|
||||
import com.gtnewhorizons.angelica.rendering.culling.GpuCulling;
|
||||
import me.jellysquid.mods.sodium.client.gui.options.named.MultiDrawMode;
|
||||
import com.gtnewhorizons.angelica.rendering.voxelization.SdlShadowVoxelizationSink;
|
||||
import com.gtnewhorizons.angelica.rendering.voxelization.ShadowVoxelizer;
|
||||
import net.coderbot.iris.Iris;
|
||||
import net.coderbot.iris.gl.program.ComputeProgram;
|
||||
import net.coderbot.iris.pipeline.DeferredWorldRenderingPipeline;
|
||||
import net.coderbot.iris.pipeline.WorldRenderingPipeline;
|
||||
import net.coderbot.iris.shadows.ShadowRenderingState;
|
||||
import org.embeddedt.embeddium.impl.gl.device.CommandList;
|
||||
import org.embeddedt.embeddium.impl.gl.device.DirectMultiDrawBatch;
|
||||
import org.embeddedt.embeddium.impl.gl.device.IndirectMultiDrawBatch;
|
||||
import org.embeddedt.embeddium.impl.gl.device.MultiDrawBatch;
|
||||
import org.embeddedt.embeddium.impl.gl.device.RenderDevice;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.GlProgram;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.GlShader;
|
||||
@@ -19,15 +30,15 @@ import org.embeddedt.embeddium.impl.gl.shader.ShaderParser;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.ShaderType;
|
||||
import org.embeddedt.embeddium.impl.gl.tessellation.GlPrimitiveType;
|
||||
import org.embeddedt.embeddium.impl.gl.tessellation.GlTessellation;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.ChunkRenderMatrices;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.DefaultChunkRenderer;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.RenderPassConfiguration;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.data.SectionRenderDataStorage;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.lists.ChunkRenderList;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.multidraw.DrawCommandSink;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.lists.ChunkRenderListIterable;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.multidraw.BatchAssembler;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.multidraw.CachedBatch;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.region.RenderRegion;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.multidraw.DirectMultiDrawEmitter;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.multidraw.IndirectMultiDrawEmitter;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.multidraw.MultiDrawEmitter;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.shader.ChunkShaderBindingPoints;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.shader.ChunkShaderInterface;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.shader.ChunkShaderOptions;
|
||||
@@ -36,7 +47,9 @@ import org.embeddedt.embeddium.impl.render.chunk.shader.DefaultChunkShaderInterf
|
||||
import org.embeddedt.embeddium.impl.render.chunk.terrain.TerrainRenderPass;
|
||||
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
|
||||
import org.embeddedt.embeddium.impl.render.viewport.CameraTransform;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -44,20 +57,26 @@ import java.util.List;
|
||||
class AngelicaChunkRenderer extends DefaultChunkRenderer {
|
||||
private static final int BLOCK_TEXTURE_UNIT = 0;
|
||||
private static final Tracy.ZoneId Z_CHUNK_BEGIN = Tracy.zoneId("chunkBegin", Tracy.COLOR_TERRAIN);
|
||||
private static final Tracy.ZoneId Z_CHUNK_REGION_FIRST = Tracy.zoneId("chunkRegionFirst", Tracy.COLOR_TERRAIN);
|
||||
private static final Tracy.ZoneId Z_CHUNK_REGION = Tracy.zoneId("chunkRegion", Tracy.COLOR_TERRAIN);
|
||||
private static final Tracy.ZoneId Z_CHUNK_ASSEMBLE_REGION = Tracy.zoneId("chunkAssembleRegion", Tracy.COLOR_TERRAIN);
|
||||
private static final Tracy.ZoneId Z_CHUNK_EXECUTE_BATCH = Tracy.zoneId("chunkExecuteBatch", Tracy.COLOR_TERRAIN);
|
||||
|
||||
private GlProgram<? extends ChunkShaderInterface> irisProgram;
|
||||
private boolean usingIrisProgram;
|
||||
private int rgssSampler;
|
||||
private boolean rgssSamplerResolved;
|
||||
private boolean rgssSamplerBound;
|
||||
private int regionIndex;
|
||||
private final GpuTerrainCuller culler;
|
||||
private final ReusableCachedBatch gpuBatch = new ReusableCachedBatch();
|
||||
|
||||
private static final ShadowVoxelizer shadowVoxelizer = new ShadowVoxelizer();
|
||||
private static final SdlShadowVoxelizationSink shadowVoxelSink = new SdlShadowVoxelizationSink();
|
||||
private static int loggedVoxelizationSkips;
|
||||
private static MultiDrawMode installedBatchMode;
|
||||
|
||||
public AngelicaChunkRenderer(RenderDevice device, RenderPassConfiguration<?> renderPassConfiguration) {
|
||||
super(device, renderPassConfiguration, createEmitter());
|
||||
super(device, renderPassConfiguration);
|
||||
|
||||
installBatchFactory();
|
||||
this.culler = createCuller();
|
||||
|
||||
final IrisShaderProvider provider = IrisShaderProviderHolder.getProvider();
|
||||
if (provider != null) {
|
||||
@@ -65,10 +84,18 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
private static MultiDrawEmitter createEmitter() {
|
||||
private static void installBatchFactory() {
|
||||
final MultiDrawMode configured = ClientProxy.options().advanced.multiDrawMode;
|
||||
final MultiDrawMode mode = MultiDrawModeResolver.resolve();
|
||||
|
||||
if (BackendManager.RENDER_BACKEND.isIndirectRequired() && mode != MultiDrawMode.INDIRECT) {
|
||||
throw new IllegalStateException(
|
||||
"Indirect multi-draw was required by the backend but resolved mode is " + mode);
|
||||
}
|
||||
|
||||
if (mode == installedBatchMode) return;
|
||||
installedBatchMode = mode;
|
||||
|
||||
if (mode != configured) {
|
||||
if (mode == MultiDrawMode.INDIRECT) {
|
||||
AngelicaMod.LOGGER.info("Backend requires indirect draw; {} -> INDIRECT", configured);
|
||||
@@ -77,22 +104,40 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
if (BackendManager.RENDER_BACKEND.isIndirectRequired() && mode != MultiDrawMode.INDIRECT) {
|
||||
throw new IllegalStateException("Indirect multi-draw was required by the backend but resolved mode is " + mode);
|
||||
}
|
||||
BatchAssembler.setBatchFactory(switch (mode) {
|
||||
case DIRECT -> DirectMultiDrawBatch::new;
|
||||
case INDIRECT -> IndirectMultiDrawBatch::new;
|
||||
case INDIVIDUAL -> IndividualDrawBatch::new;
|
||||
});
|
||||
}
|
||||
|
||||
private enum VoxelizationSkip {
|
||||
NO_RENDER_LISTS("pass has no render lists"),
|
||||
NO_DEFERRED_PIPELINE("no deferred pipeline"),
|
||||
NO_VOXELIZATION_COMPUTE("pack declares no shadow voxelization");
|
||||
|
||||
private final String message;
|
||||
|
||||
VoxelizationSkip(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
|
||||
private static void logVoxelizationSkipOnce(VoxelizationSkip reason) {
|
||||
final int bit = 1 << reason.ordinal();
|
||||
if ((loggedVoxelizationSkips & bit) != 0) return;
|
||||
loggedVoxelizationSkips |= bit;
|
||||
AngelicaMod.LOGGER.info("shadow voxelization skipped: {}", reason.message);
|
||||
}
|
||||
|
||||
private static GpuTerrainCuller createCuller() {
|
||||
final GpuCulling.Availability availability = GpuCulling.availability();
|
||||
if (availability == GpuCulling.Availability.AVAILABLE) {
|
||||
AngelicaMod.LOGGER.info("Compute-driven chunk culling available, mode={}", GpuCulling.mode());
|
||||
return new GpuIndirectMultiDrawEmitter(GpuCulling.culler(), GpuCulling.sectionMeta());
|
||||
return new GpuTerrainCuller(GpuCulling.culler(), GpuCulling.sectionMeta());
|
||||
}
|
||||
AngelicaMod.LOGGER.warn("GPU culling unavailable ({}); terrain will use CPU culling", availability);
|
||||
|
||||
return switch (mode) {
|
||||
case DIRECT -> new DirectMultiDrawEmitter();
|
||||
case INDIRECT -> new IndirectMultiDrawEmitter();
|
||||
case INDIVIDUAL -> new IndividualDrawEmitter();
|
||||
};
|
||||
return null;
|
||||
}
|
||||
|
||||
private static GlShader loadShader(ShaderType type, String path, ShaderConstants constants) {
|
||||
@@ -102,7 +147,6 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
|
||||
|
||||
@Override
|
||||
protected void begin(TerrainRenderPass pass) {
|
||||
this.regionIndex = 0;
|
||||
if (Tracy.ENABLED) Tracy.beginZone(Z_CHUNK_BEGIN);
|
||||
try {
|
||||
final IrisShaderProvider provider = IrisShaderProviderHolder.getProvider();
|
||||
@@ -181,6 +225,10 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
|
||||
public void delete(CommandList commandList) {
|
||||
super.delete(commandList);
|
||||
|
||||
if (culler != null) {
|
||||
culler.delete();
|
||||
}
|
||||
|
||||
unbindRgssSampler();
|
||||
RenderSystem.destroySampler(rgssSampler);
|
||||
rgssSampler = 0;
|
||||
@@ -210,48 +258,116 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawRegion(ChunkShaderInterface shader, CommandList commandList, RenderRegion region,
|
||||
CameraTransform camera, long timestamp) {
|
||||
if (Tracy.ENABLED) Tracy.beginZone(regionIndex == 0 ? Z_CHUNK_REGION_FIRST : Z_CHUNK_REGION);
|
||||
public void render(ChunkRenderMatrices matrices, CommandList commandList, ChunkRenderListIterable renderLists,
|
||||
TerrainRenderPass renderPass, CameraTransform occlusionCamera, CameraTransform camera) {
|
||||
if (culler != null) {
|
||||
culler.beginRenderPass(matrices, renderLists, renderPass, occlusionCamera, camera, useBlockFaceCulling());
|
||||
}
|
||||
|
||||
try {
|
||||
regionIndex++;
|
||||
super.drawRegion(shader, commandList, region, camera, timestamp);
|
||||
voxelizeShadowTerrain(matrices, renderLists, renderPass, occlusionCamera, camera);
|
||||
super.render(matrices, commandList, renderLists, renderPass, occlusionCamera, camera);
|
||||
} finally {
|
||||
if (Tracy.ENABLED) Tracy.endZone();
|
||||
if (culler != null) {
|
||||
culler.endPass();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assembleRegion(DrawCommandSink sink, RenderRegion region, SectionRenderDataStorage storage,
|
||||
ChunkRenderList renderList, CameraTransform occlusionCamera,
|
||||
TerrainRenderPass renderPass, boolean useBlockFaceCulling) {
|
||||
if (getEmitter() instanceof GpuIndirectMultiDrawEmitter gpu && gpu.isComputeActiveThisPass()) {
|
||||
gpu.prepareRegion(region);
|
||||
protected @Nullable CachedBatch getRegionBatch(CommandList commandList, RenderRegion region,
|
||||
SectionRenderDataStorage storage, ChunkRenderList renderList,
|
||||
CameraTransform occlusionCamera, TerrainRenderPass renderPass,
|
||||
boolean useBlockFaceCulling,
|
||||
SectionRenderDataStorage.BatchCacheParams cacheParams) {
|
||||
if (culler != null && culler.isComputeActiveThisPass()) {
|
||||
final MultiDrawBatch batch = culler.batchForRegion(region);
|
||||
if (batch == null) return null;
|
||||
return this.gpuBatch.wrap(batch, prepareTessellation(commandList, region, renderPass));
|
||||
}
|
||||
|
||||
if (!Tracy.ENABLED) {
|
||||
return super.getRegionBatch(commandList, region, storage, renderList, occlusionCamera, renderPass,
|
||||
useBlockFaceCulling, cacheParams);
|
||||
}
|
||||
|
||||
final long batchesCreatedBefore = BatchAssembler.getCachedBatchesCreated();
|
||||
Tracy.beginZone(Z_CHUNK_ASSEMBLE_REGION);
|
||||
try {
|
||||
final CachedBatch cached = super.getRegionBatch(commandList, region, storage, renderList, occlusionCamera,
|
||||
renderPass, useBlockFaceCulling, cacheParams);
|
||||
if (BatchAssembler.getCachedBatchesCreated() != batchesCreatedBefore) TerrainDrawStats.recordRebuild();
|
||||
final MultiDrawBatch batch = cached != null ? cached.getBatch() : null;
|
||||
if (batch != null && !batch.isEmpty()) TerrainDrawStats.recordBatch(batch.size());
|
||||
return cached;
|
||||
} finally {
|
||||
Tracy.endZone();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void executeBatch(CommandList commandList, MultiDrawBatch batch, GlTessellation tessellation, GlPrimitiveType primitiveType) {
|
||||
TerrainDrawStats.beginExecuteZone();
|
||||
try {
|
||||
super.executeBatch(commandList, batch, tessellation, primitiveType);
|
||||
} finally {
|
||||
TerrainDrawStats.endExecuteZone();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ReusableCachedBatch extends CachedBatch {
|
||||
private static final byte[] NO_SECTIONS = new byte[0];
|
||||
|
||||
private MultiDrawBatch batch;
|
||||
private GlTessellation tessellation;
|
||||
|
||||
ReusableCachedBatch() {
|
||||
super(null, null, NO_SECTIONS, 0, 0, 0, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
CachedBatch wrap(MultiDrawBatch batch, GlTessellation tessellation) {
|
||||
this.batch = batch;
|
||||
this.tessellation = tessellation;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultiDrawBatch getBatch() {
|
||||
return this.batch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlTessellation getTessellation() {
|
||||
return this.tessellation;
|
||||
}
|
||||
}
|
||||
|
||||
private void voxelizeShadowTerrain(ChunkRenderMatrices matrices, ChunkRenderListIterable renderLists, TerrainRenderPass renderPass, CameraTransform occlusionCamera, CameraTransform camera) {
|
||||
if (!BackendManager.RENDER_BACKEND.isSDLGPU()) return;
|
||||
if (!ShadowRenderingState.areShadowsCurrentlyBeingRendered()) return;
|
||||
if (!renderLists.hasPass(renderPass)) {
|
||||
logVoxelizationSkipOnce(VoxelizationSkip.NO_RENDER_LISTS);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Tracy.ENABLED) Tracy.beginZone(Z_CHUNK_ASSEMBLE_REGION);
|
||||
try {
|
||||
super.assembleRegion(sink, region, storage, renderList, occlusionCamera, renderPass, useBlockFaceCulling);
|
||||
} finally {
|
||||
if (Tracy.ENABLED) Tracy.endZone();
|
||||
final WorldRenderingPipeline pipeline = Iris.getPipelineManager().getPipelineNullable();
|
||||
if (!(pipeline instanceof DeferredWorldRenderingPipeline deferred)) {
|
||||
logVoxelizationSkipOnce(VoxelizationSkip.NO_DEFERRED_PIPELINE);
|
||||
return;
|
||||
}
|
||||
final ComputeProgram voxelCompute = deferred.getShadowVoxelizationCompute();
|
||||
if (voxelCompute == null) {
|
||||
logVoxelizationSkipOnce(VoxelizationSkip.NO_VOXELIZATION_COMPUTE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void executeBatch(CommandList commandList, GlTessellation tessellation, GlPrimitiveType primitiveType) {
|
||||
if (Tracy.ENABLED) {
|
||||
final MultiDrawEmitter emitter = getEmitter();
|
||||
// The compute path reports itself through GpuIndirectMultiDrawEmitter; counting it here too double-counts.
|
||||
if (!(emitter instanceof GpuIndirectMultiDrawEmitter gpu && gpu.isComputeActiveThisPass())) {
|
||||
TerrainDrawStats.recordBatch(emitter.getPendingCommandCount());
|
||||
}
|
||||
Tracy.beginZone(Z_CHUNK_EXECUTE_BATCH);
|
||||
}
|
||||
final int prevProgram = GLStateManager.glGetInteger(GL20.GL_CURRENT_PROGRAM);
|
||||
voxelCompute.use();
|
||||
deferred.prepareShadowVoxelizationCompute(matrices.modelView());
|
||||
try {
|
||||
super.executeBatch(commandList, tessellation, primitiveType);
|
||||
shadowVoxelizer.walkPass(renderLists, renderPass, renderPass.vertexType().getVertexFormat(), camera, occlusionCamera, useBlockFaceCulling(), shadowVoxelSink);
|
||||
} finally {
|
||||
if (Tracy.ENABLED) Tracy.endZone();
|
||||
if (prevProgram != 0) GLStateManager.glUseProgram(prevProgram);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+17
-25
@@ -5,7 +5,6 @@ import com.gtnewhorizons.angelica.compat.ModStatus;
|
||||
import com.gtnewhorizons.angelica.compat.cubicchunks.CubicChunksAPI;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.TracyBackend;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.RenderListManagerAccessor;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.RenderSectionManagerAccessor;
|
||||
import com.gtnewhorizons.angelica.proxy.ClientProxy;
|
||||
import com.gtnewhorizons.angelica.rendering.AngelicaRenderQueue;
|
||||
@@ -50,6 +49,7 @@ public class AngelicaRenderSectionManager extends RenderSectionManager {
|
||||
private static final long P_MESH_SCHEDULED_JOBS = Tracy.plotHandle("mesh.scheduledJobs");
|
||||
private static final long P_MESH_BUSY_THREADS = Tracy.plotHandle("mesh.busyThreads");
|
||||
private static final long P_MESH_DEVICE_USED = Tracy.plotHandle("mesh.deviceUsed", TracyBackend.PLOT_FORMAT_MEMORY);
|
||||
private static final long P_DRAW_REGION_BUFFERS = Tracy.plotHandle("draw.regionBuffers");
|
||||
private static final long P_MESH_DEVICE_ALLOCATED = Tracy.plotHandle("mesh.deviceAllocated", TracyBackend.PLOT_FORMAT_MEMORY);
|
||||
|
||||
private static final Tracy.ZoneId Z_BIOME_REBUILDS = Tracy.zoneId("biomeRebuilds", Tracy.COLOR_TERRAIN);
|
||||
@@ -91,27 +91,12 @@ public class AngelicaRenderSectionManager extends RenderSectionManager {
|
||||
this.cameraPosition.set(x, y, z);
|
||||
}
|
||||
|
||||
private boolean isChunkNotLoaded(int chunkX, int chunkZ) {
|
||||
return this.world.getChunkFromChunkCoords(chunkX, chunkZ).isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSectionAdded(int x, int y, int z) {
|
||||
super.onSectionAdded(x, y, z);
|
||||
|
||||
// If chunk isn't actually loaded (EmptyChunk placeholder), make sections opaque for main pass.
|
||||
// This prevents BFS from traversing through chunks we haven't received from the server.
|
||||
if (isChunkNotLoaded(x, z)) {
|
||||
renderListManager.updateVisibilityData(x, y, z, 0L);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Viewport positionedViewport, int frame, boolean spectator) {
|
||||
if (isInShadowPass() && !needsUpdate()) {
|
||||
if (isInShadowPass()) {
|
||||
return;
|
||||
}
|
||||
if (!isInShadowPass() && !initialCameraSectionReady) {
|
||||
if (!initialCameraSectionReady) {
|
||||
var origin = positionedViewport.getChunkCoord();
|
||||
long key = PositionUtil.packSection(origin.x(), origin.y(), origin.z());
|
||||
if (!((RenderSectionManagerAccessor) this).angelica$getSectionByPosition().containsKey(key)) {
|
||||
@@ -132,6 +117,11 @@ public class AngelicaRenderSectionManager extends RenderSectionManager {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean useRasterOcclusionCulling() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean useFogOcclusion() {
|
||||
return ClientProxy.options().performance.useFogOcclusion && !IrisShaderProviderHolder.isActive();
|
||||
@@ -267,14 +257,15 @@ public class AngelicaRenderSectionManager extends RenderSectionManager {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean preSubmitShadowGraphUpdate(Viewport viewport, int frame, boolean spectator) {
|
||||
if (this.shadowRenderListManager == null || !this.shadowRenderListManager.isNeedsUpdate()) return false;
|
||||
if (((RenderListManagerAccessor) this.shadowRenderListManager).angelica$hasOcclusionFutureInFlight()) return false;
|
||||
@Override
|
||||
public boolean canSubmitShadowGraphSearch() {
|
||||
return super.canSubmitShadowGraphSearch() && this.shadowRenderListManager.isNeedsUpdate()
|
||||
&& !this.shadowRenderListManager.hasOcclusionFutureInFlight();
|
||||
}
|
||||
|
||||
final RenderSectionManagerAccessor self = (RenderSectionManagerAccessor) this;
|
||||
final int targetQueueSize = shouldRespectUpdateTaskQueueSizeLimit() ? (int) Math.min(Integer.MAX_VALUE, (long) getBuilder().getTargetQueueSize() * 10) : Integer.MAX_VALUE;
|
||||
this.shadowRenderListManager.startGraphUpdate(viewport, frame, self.angelica$getRegions().getRegionIdsLength(), self.angelica$getSearchDistance(), shouldUseOcclusionCulling(viewport, spectator), targetQueueSize);
|
||||
return true;
|
||||
@Override
|
||||
public boolean submitShadowGraphSearch(Viewport viewport, int frame) {
|
||||
return super.submitShadowGraphSearch(viewport, frame);
|
||||
}
|
||||
|
||||
public boolean isShadowGraphDirty() {
|
||||
@@ -361,5 +352,6 @@ public class AngelicaRenderSectionManager extends RenderSectionManager {
|
||||
final var mem = this.getDeviceMemoryStats();
|
||||
Tracy.plotInt(P_MESH_DEVICE_USED, mem.deviceUsed + mem.indexUsed);
|
||||
Tracy.plotInt(P_MESH_DEVICE_ALLOCATED, mem.deviceAllocated + mem.indexAllocated);
|
||||
Tracy.plotInt(P_DRAW_REGION_BUFFERS, mem.bufferCount);
|
||||
}
|
||||
}
|
||||
+12
-13
@@ -248,12 +248,19 @@ public class CeleritasWorldRenderer extends SimpleWorldRenderer<WorldClient, Ang
|
||||
|
||||
@Override
|
||||
protected void renderBlockEntityList(List<TileEntity> list, TileEntityRenderContext context) {
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
final TileEntity te = list.get(i);
|
||||
collectVisibleBlockEntities(list);
|
||||
}
|
||||
|
||||
private int collectVisibleBlockEntities(List<TileEntity> blockEntities) {
|
||||
int count = 0;
|
||||
for (int i = 0; i < blockEntities.size(); i++) {
|
||||
final TileEntity te = blockEntities.get(i);
|
||||
if (isTileEntityVisible(te)) {
|
||||
frameTEs.add(te);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private CameraState lastMainCameraState;
|
||||
@@ -290,7 +297,7 @@ public class CeleritasWorldRenderer extends SimpleWorldRenderer<WorldClient, Ang
|
||||
collectTileEntitiesForShadow();
|
||||
}
|
||||
} else if (IrisShaderProviderHolder.isActive()) {
|
||||
IrisShaderProviderHolder.getProvider().preSubmitShadowGraph(frame, spectator);
|
||||
IrisShaderProviderHolder.getProvider().preSubmitShadowGraph(frame);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,7 +400,7 @@ public class CeleritasWorldRenderer extends SimpleWorldRenderer<WorldClient, Ang
|
||||
teRenderContext.set(partialTicks, pass);
|
||||
final Viewport viewport = this.currentViewport;
|
||||
this.teTransform = viewport != null ? viewport.getTransform() : null;
|
||||
this.teFrustum = viewport != null && ClientProxy.options().performance.sectionGatedTesrCulling && viewport.getFrustum() instanceof SimpleFrustum simpleFrustum ? simpleFrustum.frustumIntersection() : null;
|
||||
this.teFrustum = viewport != null && ClientProxy.options().performance.sectionGatedTesrCulling && viewport.getFrustum() instanceof SimpleFrustum simpleFrustum ? simpleFrustum.getFrustumIntersection() : null;
|
||||
this.teCullEpoch++;
|
||||
this.teCamX = TileEntityRendererDispatcher.staticPlayerX;
|
||||
this.teCamY = TileEntityRendererDispatcher.staticPlayerY;
|
||||
@@ -470,15 +477,7 @@ public class CeleritasWorldRenderer extends SimpleWorldRenderer<WorldClient, Ang
|
||||
}
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
for (int i = 0; i < blockEntities.size(); i++) {
|
||||
final TileEntity te = blockEntities.get(i);
|
||||
if (isTileEntityVisible(te)) {
|
||||
frameTEs.add(te);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
return collectVisibleBlockEntities(blockEntities);
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.gtnewhorizons.angelica.rendering.celeritas;
|
||||
|
||||
import static org.taumc.celeritas.lwjgl.LWJGLServiceProvider.LWJGL;
|
||||
import static org.taumc.celeritas.lwjgl.LWJGLServiceProvider.POINTER_SIZE;
|
||||
|
||||
import org.embeddedt.embeddium.impl.gl.device.CommandList;
|
||||
import org.embeddedt.embeddium.impl.gl.device.DirectMultiDrawBatch;
|
||||
import org.embeddedt.embeddium.impl.gl.device.DrawCommandList;
|
||||
import org.embeddedt.embeddium.impl.gl.tessellation.GlIndexType;
|
||||
import org.embeddedt.embeddium.impl.gl.tessellation.GlPrimitiveType;
|
||||
import org.embeddedt.embeddium.impl.gl.tessellation.GlTessellation;
|
||||
|
||||
public class IndividualDrawBatch extends DirectMultiDrawBatch {
|
||||
|
||||
public IndividualDrawBatch(int capacity) {
|
||||
super(capacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandList commandList, GlTessellation tessellation, GlPrimitiveType primitiveType) {
|
||||
try (DrawCommandList ignored = commandList.beginTessellating(tessellation)) {
|
||||
final int mode = primitiveType.getId();
|
||||
final int type = GlIndexType.UNSIGNED_INT.getFormatId();
|
||||
final int commands = this.size();
|
||||
|
||||
for (int i = 0; i < commands; i++) {
|
||||
final int count = LWJGL.memGetInt(this.pElementCount + (long) i * Integer.BYTES);
|
||||
LWJGL.glDrawElementsBaseVertex(mode, count, type, LWJGL.memGetAddress(this.pElementPointer + (long) i * POINTER_SIZE), LWJGL.memGetInt(this.pBaseVertex + (long) i * Integer.BYTES));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.rendering.celeritas;
|
||||
|
||||
import static com.mitchej123.lwjgl.LWJGLServiceProvider.LWJGL;
|
||||
import static com.mitchej123.lwjgl.LWJGLServiceProvider.POINTER_SIZE;
|
||||
|
||||
import org.embeddedt.embeddium.impl.gl.device.CommandList;
|
||||
import org.embeddedt.embeddium.impl.gl.device.DrawCommandList;
|
||||
import org.embeddedt.embeddium.impl.gl.device.MultiDrawBatch;
|
||||
import org.embeddedt.embeddium.impl.gl.tessellation.GlIndexType;
|
||||
import org.embeddedt.embeddium.impl.gl.tessellation.GlPrimitiveType;
|
||||
import org.embeddedt.embeddium.impl.gl.tessellation.GlTessellation;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.multidraw.DrawCommandSink;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.multidraw.MultiDrawEmitter;
|
||||
|
||||
public record IndividualDrawEmitter(MultiDrawBatch batch) implements MultiDrawEmitter {
|
||||
public IndividualDrawEmitter() {
|
||||
this(new MultiDrawBatch(MAX_COMMAND_COUNT));
|
||||
}
|
||||
|
||||
@Override
|
||||
public DrawCommandSink getCommandSink() {
|
||||
return this.batch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeBatch(CommandList commandList, GlTessellation tessellation, GlPrimitiveType primitiveType) {
|
||||
try (DrawCommandList ignored = commandList.beginTessellating(tessellation)) {
|
||||
final int mode = primitiveType.getId();
|
||||
final int type = GlIndexType.UNSIGNED_INT.getFormatId();
|
||||
|
||||
for (int i = 0; i < batch.size; i++) {
|
||||
final int count = LWJGL.memGetInt(batch.pElementCount + (long) i * Integer.BYTES);
|
||||
if (count > 0) {
|
||||
LWJGL.glDrawElementsBaseVertex(mode, count, type, LWJGL.memGetAddress(batch.pElementPointer + (long) i * POINTER_SIZE), LWJGL.memGetInt(batch.pBaseVertex + (long) i * Integer.BYTES));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() {
|
||||
this.batch.delete();
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,55 @@
|
||||
package com.gtnewhorizons.angelica.rendering.celeritas;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
|
||||
public final class TerrainDrawStats {
|
||||
private static final Tracy.ZoneId Z_EXECUTE_BATCH = Tracy.zoneId("chunkExecuteBatch", Tracy.COLOR_TERRAIN);
|
||||
|
||||
private static int commandsSubmitted;
|
||||
private static int regionsDrawn;
|
||||
private static int batchRebuilds;
|
||||
|
||||
private static int cullCommandsSubmitted;
|
||||
private static int cullRegionsDrawn;
|
||||
private static int cullMaxRegionCommands;
|
||||
private static long sectionMetaBytes;
|
||||
|
||||
private TerrainDrawStats() {
|
||||
}
|
||||
|
||||
public static void beginExecuteZone() {
|
||||
if (Tracy.ENABLED) Tracy.beginZone(Z_EXECUTE_BATCH);
|
||||
}
|
||||
|
||||
public static void endExecuteZone() {
|
||||
if (Tracy.ENABLED) Tracy.endZone();
|
||||
}
|
||||
|
||||
public static void recordBatch(int commandCount) {
|
||||
commandsSubmitted += commandCount;
|
||||
regionsDrawn++;
|
||||
}
|
||||
|
||||
public static void recordRebuild() {
|
||||
batchRebuilds++;
|
||||
}
|
||||
|
||||
public static void recordCullRegion(int commandCount) {
|
||||
cullCommandsSubmitted += commandCount;
|
||||
cullRegionsDrawn++;
|
||||
if (commandCount > cullMaxRegionCommands) cullMaxRegionCommands = commandCount;
|
||||
}
|
||||
|
||||
public static void recordSectionMetaBytes(int bytes) {
|
||||
sectionMetaBytes += bytes;
|
||||
}
|
||||
|
||||
public static int takeBatchRebuilds() {
|
||||
final int n = batchRebuilds;
|
||||
batchRebuilds = 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
public static int takeCommandsSubmitted() {
|
||||
final int n = commandsSubmitted;
|
||||
commandsSubmitted = 0;
|
||||
@@ -23,4 +61,28 @@ public final class TerrainDrawStats {
|
||||
regionsDrawn = 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
public static int takeCullCommandsSubmitted() {
|
||||
final int n = cullCommandsSubmitted;
|
||||
cullCommandsSubmitted = 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
public static int takeCullRegionsDrawn() {
|
||||
final int n = cullRegionsDrawn;
|
||||
cullRegionsDrawn = 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
public static int takeCullMaxRegionCommands() {
|
||||
final int n = cullMaxRegionCommands;
|
||||
cullMaxRegionCommands = 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
public static long takeSectionMetaBytes() {
|
||||
final long n = sectionMetaBytes;
|
||||
sectionMetaBytes = 0;
|
||||
return n;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -28,5 +28,5 @@ public interface IrisShaderProvider {
|
||||
/** Block render layer overrides from shader pack */
|
||||
@Nullable Map<Block, BlockRenderLayer> getBlockTypeIds();
|
||||
|
||||
default void preSubmitShadowGraph(int frame, boolean spectator) {}
|
||||
default void preSubmitShadowGraph(int frame) {}
|
||||
}
|
||||
+6
-3
@@ -19,9 +19,6 @@ import org.embeddedt.embeddium.impl.render.chunk.terrain.material.Material;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.vertex.format.ChunkVertexEncoder;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
/**
|
||||
* Writes Iris-extended vertices. Delegates base 28 bytes to VANILLA_LIKE encoder.
|
||||
*/
|
||||
public class IrisExtendedChunkVertexEncoder implements ContextAwareChunkVertexEncoder {
|
||||
// Offsets derived from format definition
|
||||
private static final int MID_TEX_OFFSET = IrisExtendedChunkVertexType.VERTEX_FORMAT.getAttribute("mc_midTexCoord").getPointer();
|
||||
@@ -101,6 +98,11 @@ public class IrisExtendedChunkVertexEncoder implements ContextAwareChunkVertexEn
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsBilinearCorrection() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long write(long ptr, Material material, Vertex vertex, int sectionIndex) {
|
||||
uSum += vertex.u;
|
||||
@@ -109,6 +111,7 @@ public class IrisExtendedChunkVertexEncoder implements ContextAwareChunkVertexEn
|
||||
|
||||
final BlockRenderContext ctx = context;
|
||||
|
||||
vertex.rdhFactor = 0;
|
||||
baseEncoder.write(ptr, material, vertex, sectionIndex);
|
||||
|
||||
// Per-vertex: mc_Entity (packed blockId + renderType), midBlock, lightValue
|
||||
|
||||
+4
-11
@@ -6,22 +6,15 @@ import org.embeddedt.embeddium.impl.render.chunk.vertex.format.ChunkMeshFormats;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.vertex.format.ChunkVertexEncoder;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.vertex.format.ChunkVertexType;
|
||||
|
||||
/**
|
||||
* VANILLA_LIKE (28 bytes) + Iris extensions (20 bytes) = 48 bytes total.
|
||||
*
|
||||
* Iris extensions:
|
||||
* mc_midTexCoord ushort[2] - quad center UV (average of 4 vertices)
|
||||
* at_tangent byte[4] - tangent vector, normalized
|
||||
* iris_Normal byte[3]+1 - face normal, normalized
|
||||
* mc_Entity uint - packed as ((blockId + 1) << 1) | (renderType & 1)
|
||||
* at_midBlock byte[4] - (xyz offset from block center, lightValue)
|
||||
*/
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class IrisExtendedChunkVertexType implements ChunkVertexType {
|
||||
public static final ChunkVertexType BASE_TYPE = ChunkMeshFormats.VANILLA_LIKE;
|
||||
public static final int STRIDE = 48;
|
||||
|
||||
public static final GlVertexFormat VERTEX_FORMAT = GlVertexFormat.builder(STRIDE)
|
||||
.addAllElements(BASE_TYPE.getVertexFormat())
|
||||
.addElements(BASE_TYPE.getVertexFormat().getAttributes().stream()
|
||||
.filter(a -> !a.getName().equals("a_RdhFactor")).collect(Collectors.toList()))
|
||||
.addElement("mc_midTexCoord", GlVertexFormat.NEXT_ALIGNED_POINTER, GlVertexAttributeFormat.UNSIGNED_SHORT, 2, false, false)
|
||||
.addElement("at_tangent", GlVertexFormat.NEXT_ALIGNED_POINTER, GlVertexAttributeFormat.BYTE, 4, true, false)
|
||||
.addElement("iris_Normal", GlVertexFormat.NEXT_ALIGNED_POINTER, GlVertexAttributeFormat.BYTE, 3, true, false)
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package com.gtnewhorizons.angelica.rendering.culling;
|
||||
|
||||
import org.embeddedt.embeddium.impl.gl.device.CommandList;
|
||||
import org.embeddedt.embeddium.impl.gl.device.MultiDrawBatch;
|
||||
import org.embeddedt.embeddium.impl.gl.tessellation.GlPrimitiveType;
|
||||
import org.embeddedt.embeddium.impl.gl.tessellation.GlTessellation;
|
||||
|
||||
public final class GpuCulledMultiDrawBatch extends MultiDrawBatch {
|
||||
|
||||
private final GpuTerrainCuller culler;
|
||||
private int drawStart;
|
||||
|
||||
GpuCulledMultiDrawBatch(GpuTerrainCuller culler) {
|
||||
super(MAX_COMMAND_COUNT);
|
||||
this.culler = culler;
|
||||
}
|
||||
|
||||
void prepare(int drawStart, int drawCount, int maxElementCount) {
|
||||
this.drawStart = drawStart;
|
||||
this.size = drawCount;
|
||||
this.maxElementCount = maxElementCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendDrawCommand(int baseVertex, int elementCount, long elementPointer) {
|
||||
throw new UnsupportedOperationException("GPU-culled batches build their commands on the GPU");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mergeIntoLastCommand(int additionalElementCount) {
|
||||
throw new UnsupportedOperationException("GPU-culled batches build their commands on the GPU");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void upload(CommandList commandList) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandList commandList, GlTessellation tessellation, GlPrimitiveType primitiveType) {
|
||||
this.culler.drawRegionRange(commandList, tessellation, primitiveType, this.drawStart, this.size);
|
||||
}
|
||||
}
|
||||
-382
@@ -1,382 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.rendering.culling;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2LongOpenHashMap;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.embeddedt.embeddium.impl.gl.device.CommandList;
|
||||
import org.embeddedt.embeddium.impl.gl.device.DrawCommandList;
|
||||
import org.embeddedt.embeddium.impl.gl.tessellation.GlPrimitiveType;
|
||||
import org.embeddedt.embeddium.impl.gl.tessellation.GlTessellation;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.multidraw.DrawCommandSink;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.multidraw.IndirectMultiDrawEmitter;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.multidraw.MultiDrawEmitter;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.region.RenderRegion;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL40;
|
||||
import org.lwjgl.opengl.GL43;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
public final class GpuIndirectMultiDrawEmitter implements MultiDrawEmitter {
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger("Angelica-Culling");
|
||||
|
||||
private static final int ENTRY_BYTES = GpuDrivenChunkCuller.VISIBLE_ENTRY_BYTES;
|
||||
|
||||
private final GpuDrivenChunkCuller culler;
|
||||
private final SectionMetaBuffer sectionMeta;
|
||||
|
||||
private ByteBuffer visibleStaging;
|
||||
private ByteBuffer visibleStagingView;
|
||||
private int stagingCapacityEntries;
|
||||
|
||||
private int visibleSsboGlId;
|
||||
private int indirectSsboGlId;
|
||||
private int gpuCapacityEntries;
|
||||
|
||||
private static final class PassState {
|
||||
int entryBase;
|
||||
int entryCount;
|
||||
int indexPointerMask;
|
||||
int maxElementCount;
|
||||
boolean dispatched;
|
||||
final Reference2LongOpenHashMap<RenderRegion> regionRanges = new Reference2LongOpenHashMap<>();
|
||||
final Reference2LongOpenHashMap<RenderRegion> regionEntryRanges = new Reference2LongOpenHashMap<>();
|
||||
|
||||
PassState() {
|
||||
regionRanges.defaultReturnValue(-1L);
|
||||
regionEntryRanges.defaultReturnValue(-1L);
|
||||
}
|
||||
|
||||
void reset(int mask, int base) {
|
||||
entryBase = base;
|
||||
entryCount = 0;
|
||||
indexPointerMask = mask;
|
||||
maxElementCount = 0;
|
||||
dispatched = false;
|
||||
regionRanges.clear();
|
||||
regionEntryRanges.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private final PassState primaryPass = new PassState();
|
||||
private final PassState secondPass = new PassState();
|
||||
private PassState buildPass = primaryPass;
|
||||
private PassState current = primaryPass;
|
||||
private boolean secondPrepared;
|
||||
private int totalAppendedEntries;
|
||||
@Getter private boolean computeActiveThisPass;
|
||||
|
||||
private int currentDrawStart;
|
||||
private int currentDrawCount;
|
||||
private int currentEntryStart;
|
||||
private int currentEntryCount;
|
||||
private RenderRegion currentRegion;
|
||||
|
||||
@Setter private ByteBuffer frustumUboBytes;
|
||||
|
||||
private IndirectMultiDrawEmitter cpuFallback;
|
||||
|
||||
public static long sectionMetaBytes;
|
||||
|
||||
private static int commandsSubmitted;
|
||||
private static int regionsDrawn;
|
||||
private static int maxRegionCommands;
|
||||
public static int commandsSubmitted() { return commandsSubmitted; }
|
||||
public static int takeRegionsDrawn() { final int n = regionsDrawn; regionsDrawn = 0; return n; }
|
||||
public static int takeMaxRegionCommands() { final int n = maxRegionCommands; maxRegionCommands = 0; return n; }
|
||||
|
||||
private final SectionMetaBuffer.Sink uploadSectionMetaSink;
|
||||
|
||||
public GpuIndirectMultiDrawEmitter(GpuDrivenChunkCuller culler, SectionMetaBuffer sectionMeta) {
|
||||
this.culler = culler;
|
||||
this.sectionMeta = sectionMeta;
|
||||
this.uploadSectionMetaSink = buf -> {
|
||||
final int bytes = buf.remaining();
|
||||
if (!culler.uploadSectionMeta(buf)) return false;
|
||||
if (Tracy.ENABLED) sectionMetaBytes += bytes;
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
public void beginCullPass(int indexPointerMask) {
|
||||
this.computeActiveThisPass = GpuCulling.mode().computeEnabled();
|
||||
this.totalAppendedEntries = 0;
|
||||
this.secondPrepared = false;
|
||||
primaryPass.reset(indexPointerMask, 0);
|
||||
this.buildPass = primaryPass;
|
||||
this.current = primaryPass;
|
||||
this.currentDrawStart = 0;
|
||||
this.currentDrawCount = 0;
|
||||
this.currentEntryStart = 0;
|
||||
this.currentEntryCount = 0;
|
||||
if (computeActiveThisPass && cpuFallback != null) {
|
||||
cpuFallback.getCommandSink().clear();
|
||||
}
|
||||
}
|
||||
|
||||
private IndirectMultiDrawEmitter cpuFallback() {
|
||||
if (cpuFallback == null) cpuFallback = new IndirectMultiDrawEmitter();
|
||||
return cpuFallback;
|
||||
}
|
||||
|
||||
public void beginCombinedPasses(int firstMask, int secondMask) {
|
||||
beginCullPass(firstMask);
|
||||
secondPass.reset(secondMask, 0);
|
||||
}
|
||||
|
||||
public void startSecondPass() {
|
||||
primaryPass.entryCount = totalAppendedEntries - primaryPass.entryBase;
|
||||
secondPass.entryBase = totalAppendedEntries;
|
||||
this.buildPass = secondPass;
|
||||
this.current = secondPass;
|
||||
}
|
||||
|
||||
public void finishCombinedBuild() {
|
||||
secondPass.entryCount = totalAppendedEntries - secondPass.entryBase;
|
||||
this.secondPrepared = true;
|
||||
this.buildPass = primaryPass;
|
||||
this.current = primaryPass;
|
||||
}
|
||||
|
||||
public boolean selectPreparedSecondPass() {
|
||||
if (!secondPrepared || !computeActiveThisPass) return false;
|
||||
this.current = secondPass;
|
||||
this.currentDrawStart = 0;
|
||||
this.currentDrawCount = 0;
|
||||
this.currentEntryStart = 0;
|
||||
this.currentEntryCount = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void reserveSections(int count) {
|
||||
if (count > 0) ensureStagingCapacity(totalAppendedEntries + count);
|
||||
}
|
||||
|
||||
public int appendSection(int slot, int facingMask, int outputBase) {
|
||||
if ((outputBase & ~0x00FFFFFF) != 0) {
|
||||
throw new IllegalStateException("packed outputBase overflow: " + outputBase + " (max " + 0x00FFFFFF + ")");
|
||||
}
|
||||
if (totalAppendedEntries >= stagingCapacityEntries) ensureStagingCapacity(totalAppendedEntries + 1);
|
||||
final int idx = totalAppendedEntries;
|
||||
final int off = idx * ENTRY_BYTES;
|
||||
visibleStaging.putInt(off + 0, slot);
|
||||
visibleStaging.putInt(off + 4, (outputBase << 8) | (facingMask & 0xFF));
|
||||
totalAppendedEntries = idx + 1;
|
||||
buildPass.entryCount = totalAppendedEntries - buildPass.entryBase;
|
||||
visibleStagingView.limit(totalAppendedEntries * ENTRY_BYTES);
|
||||
return idx;
|
||||
}
|
||||
|
||||
public void recordRegion(RenderRegion region, int drawStart, int drawCount, int entryStart, int entryCount, int maxElementCount) {
|
||||
if (drawCount <= 0) return;
|
||||
buildPass.regionRanges.put(region, (((long) drawStart) << 32) | (drawCount & 0xFFFFFFFFL));
|
||||
buildPass.regionEntryRanges.put(region, (((long) entryStart) << 32) | (entryCount & 0xFFFFFFFFL));
|
||||
if (maxElementCount > buildPass.maxElementCount) buildPass.maxElementCount = maxElementCount;
|
||||
}
|
||||
|
||||
public void prepareRegion(RenderRegion region) {
|
||||
currentRegion = region;
|
||||
final long packed = current.regionRanges.getLong(region);
|
||||
if (packed < 0L) {
|
||||
currentDrawStart = 0;
|
||||
currentDrawCount = 0;
|
||||
} else {
|
||||
currentDrawStart = (int) (packed >>> 32);
|
||||
currentDrawCount = (int) (packed & 0xFFFFFFFFL);
|
||||
}
|
||||
final long entryPacked = current.regionEntryRanges.getLong(region);
|
||||
if (entryPacked < 0L) {
|
||||
currentEntryStart = 0;
|
||||
currentEntryCount = 0;
|
||||
} else {
|
||||
currentEntryStart = (int) (entryPacked >>> 32);
|
||||
currentEntryCount = (int) (entryPacked & 0xFFFFFFFFL);
|
||||
}
|
||||
}
|
||||
|
||||
public void syncSectionMetaIfDirty() {
|
||||
culler.ensureReady();
|
||||
sectionMeta.syncIfDirty(uploadSectionMetaSink);
|
||||
}
|
||||
|
||||
public void endPass() {
|
||||
currentDrawStart = 0;
|
||||
currentDrawCount = 0;
|
||||
currentEntryStart = 0;
|
||||
currentEntryCount = 0;
|
||||
if (current == secondPass) secondPrepared = false;
|
||||
}
|
||||
|
||||
public void dispatchPreparedPasses() {
|
||||
if (!computeActiveThisPass || totalAppendedEntries == 0) return;
|
||||
if (!needsDispatch(primaryPass) && (!secondPrepared || !needsDispatch(secondPass))) return;
|
||||
if (!culler.ensureReady()) return;
|
||||
if (frustumUboBytes == null) {
|
||||
LOG.warn("GpuIndirectMultiDrawEmitter: frustum UBO not set before dispatch; skipping cull");
|
||||
return;
|
||||
}
|
||||
|
||||
ensureGpuBuffers(totalAppendedEntries);
|
||||
visibleStagingView.position(0).limit(totalAppendedEntries * ENTRY_BYTES);
|
||||
GLStateManager.glBindBuffer(GL43.GL_SHADER_STORAGE_BUFFER, visibleSsboGlId);
|
||||
GLStateManager.glBufferSubData(GL43.GL_SHADER_STORAGE_BUFFER, 0L, visibleStagingView);
|
||||
|
||||
BackendManager.RENDER_BACKEND.beginComputeDispatchBatch();
|
||||
try {
|
||||
dispatchPass(primaryPass);
|
||||
if (secondPrepared) dispatchPass(secondPass);
|
||||
} finally {
|
||||
BackendManager.RENDER_BACKEND.endComputeDispatchBatch();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean needsDispatch(PassState r) {
|
||||
return !r.dispatched && r.entryCount > 0;
|
||||
}
|
||||
|
||||
private void dispatchPass(PassState r) {
|
||||
if (!needsDispatch(r)) return;
|
||||
FrustumExtractor.patchControl(r.entryCount, r.indexPointerMask, frustumUboBytes);
|
||||
FrustumExtractor.patchBatchEntryBase(r.entryBase, frustumUboBytes);
|
||||
culler.uploadFrustum(frustumUboBytes);
|
||||
culler.dispatch(visibleSsboGlId, indirectSsboGlId, r.entryCount);
|
||||
r.dispatched = true;
|
||||
}
|
||||
|
||||
private void ensureGpuBuffers(int maxVisible) {
|
||||
if (visibleSsboGlId == 0) visibleSsboGlId = GLStateManager.glGenBuffers();
|
||||
if (indirectSsboGlId == 0) indirectSsboGlId = GLStateManager.glGenBuffers();
|
||||
if (maxVisible <= gpuCapacityEntries) return;
|
||||
final int newCap = Math.max(maxVisible, Math.max(gpuCapacityEntries * 2, 64));
|
||||
GLStateManager.glBindBuffer(GL43.GL_SHADER_STORAGE_BUFFER, visibleSsboGlId);
|
||||
GLStateManager.glBufferData(GL43.GL_SHADER_STORAGE_BUFFER, (long) newCap * ENTRY_BYTES, GL15.GL_STREAM_DRAW);
|
||||
GLStateManager.glBindBuffer(GL43.GL_SHADER_STORAGE_BUFFER, indirectSsboGlId);
|
||||
final int indirectBytes = newCap * GpuDrivenChunkCuller.FACINGS_PER_SECTION * GpuDrivenChunkCuller.INDIRECT_COMMAND_BYTES;
|
||||
GLStateManager.glBufferData(GL43.GL_SHADER_STORAGE_BUFFER, ByteBuffer.allocateDirect(indirectBytes).order(ByteOrder.nativeOrder()), GL15.GL_STREAM_DRAW);
|
||||
gpuCapacityEntries = newCap;
|
||||
}
|
||||
|
||||
private void ensureStagingCapacity(int needed) {
|
||||
if (visibleStaging != null && needed <= stagingCapacityEntries) return;
|
||||
final int newCap = Math.max(needed, Math.max(stagingCapacityEntries * 2, 64));
|
||||
final ByteBuffer next = MemoryUtilities.memAlloc(newCap * ENTRY_BYTES).order(ByteOrder.nativeOrder());
|
||||
if (visibleStaging != null) {
|
||||
visibleStaging.position(0).limit(totalAppendedEntries * ENTRY_BYTES);
|
||||
next.put(visibleStaging);
|
||||
MemoryUtilities.memFree(visibleStaging);
|
||||
}
|
||||
next.position(0);
|
||||
next.limit(newCap * ENTRY_BYTES);
|
||||
visibleStaging = next;
|
||||
visibleStagingView = visibleStaging.duplicate().order(ByteOrder.nativeOrder());
|
||||
stagingCapacityEntries = newCap;
|
||||
}
|
||||
|
||||
private final DrawCommandSink computeSink = new DrawCommandSink() {
|
||||
@Override
|
||||
public void clear() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return currentDrawCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIndexBufferSize() {
|
||||
return current.maxElementCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void push(int baseVertex, int elementCount, long indexOffset) {
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public DrawCommandSink getCommandSink() {
|
||||
return computeActiveThisPass ? computeSink : cpuFallback().getCommandSink();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean batchesWholePass() {
|
||||
return !computeActiveThisPass && cpuFallback().batchesWholePass();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginPass(CommandList commandList, int sectionCount) {
|
||||
if (!computeActiveThisPass) cpuFallback().beginPass(commandList, sectionCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finishAssembly(CommandList commandList) {
|
||||
if (!computeActiveThisPass) cpuFallback().finishAssembly(commandList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void selectDrawRange(int firstCommand, int commandCount) {
|
||||
if (!computeActiveThisPass) cpuFallback().selectDrawRange(firstCommand, commandCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPendingCommandCount() {
|
||||
return computeActiveThisPass ? currentDrawCount : cpuFallback().getPendingCommandCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPassFinished(CommandList commandList) {
|
||||
if (!computeActiveThisPass) cpuFallback().onPassFinished(commandList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeBatch(CommandList commandList, GlTessellation tessellation, GlPrimitiveType primitiveType) {
|
||||
if (!computeActiveThisPass) {
|
||||
cpuFallback().executeBatch(commandList, tessellation, primitiveType);
|
||||
return;
|
||||
}
|
||||
if (currentDrawCount == 0) return;
|
||||
dispatchPreparedPasses();
|
||||
if (!current.dispatched) return;
|
||||
|
||||
if (Tracy.ENABLED) {
|
||||
commandsSubmitted += currentDrawCount;
|
||||
regionsDrawn++;
|
||||
if (currentDrawCount > maxRegionCommands) maxRegionCommands = currentDrawCount;
|
||||
}
|
||||
|
||||
final long offsetBytes = (long) currentDrawStart * GpuDrivenChunkCuller.INDIRECT_COMMAND_BYTES;
|
||||
GLStateManager.glBindBuffer(GL40.GL_DRAW_INDIRECT_BUFFER, indirectSsboGlId);
|
||||
try (DrawCommandList ignored = commandList.beginTessellating(tessellation)) {
|
||||
GLStateManager.glMultiDrawElementsIndirect(primitiveType.getId(), GL11.GL_UNSIGNED_INT, offsetBytes, currentDrawCount, 0);
|
||||
}
|
||||
GLStateManager.glBindBuffer(GL40.GL_DRAW_INDIRECT_BUFFER, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete() {
|
||||
if (visibleStaging != null) {
|
||||
MemoryUtilities.memFree(visibleStaging);
|
||||
visibleStaging = null;
|
||||
visibleStagingView = null;
|
||||
stagingCapacityEntries = 0;
|
||||
}
|
||||
if (visibleSsboGlId != 0) { GLStateManager.glDeleteBuffers(visibleSsboGlId); visibleSsboGlId = 0; }
|
||||
if (indirectSsboGlId != 0) { GLStateManager.glDeleteBuffers(indirectSsboGlId); indirectSsboGlId = 0; }
|
||||
gpuCapacityEntries = 0;
|
||||
currentRegion = null;
|
||||
frustumUboBytes = null;
|
||||
primaryPass.regionRanges.clear();
|
||||
primaryPass.regionEntryRanges.clear();
|
||||
secondPass.regionRanges.clear();
|
||||
secondPass.regionEntryRanges.clear();
|
||||
if (cpuFallback != null) cpuFallback.delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,543 @@
|
||||
package com.gtnewhorizons.angelica.rendering.culling;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.SectionRenderDataStorageRegionAccessor;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.AngelicaRenderPassConfiguration;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.TerrainDrawStats;
|
||||
import lombok.Getter;
|
||||
import net.coderbot.iris.pipeline.ShadowRenderer;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.embeddedt.embeddium.impl.gl.device.CommandList;
|
||||
import org.embeddedt.embeddium.impl.gl.device.DrawCommandList;
|
||||
import org.embeddedt.embeddium.impl.gl.tessellation.GlPrimitiveType;
|
||||
import org.embeddedt.embeddium.impl.gl.tessellation.GlTessellation;
|
||||
import org.embeddedt.embeddium.impl.model.quad.properties.ModelQuadFacing;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.ChunkRenderMatrices;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.LocalSectionIndex;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.compile.sorting.ChunkPrimitiveType;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.data.SectionRenderDataStorage;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.data.SectionRenderDataUnsafe;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.lists.ChunkRenderList;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.lists.ChunkRenderListIterable;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.multidraw.BatchAssembler;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.region.RenderRegion;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.terrain.TerrainRenderPass;
|
||||
import org.embeddedt.embeddium.impl.render.viewport.CameraTransform;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Matrix4fc;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL40;
|
||||
import org.lwjgl.opengl.GL43;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Arrays;
|
||||
|
||||
public final class GpuTerrainCuller {
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger("Angelica-Culling");
|
||||
|
||||
private static final int ENTRY_BYTES = GpuDrivenChunkCuller.VISIBLE_ENTRY_BYTES;
|
||||
|
||||
private final GpuDrivenChunkCuller culler;
|
||||
private final SectionMetaBuffer sectionMeta;
|
||||
|
||||
private ByteBuffer visibleStaging;
|
||||
private ByteBuffer visibleStagingView;
|
||||
private int stagingCapacityEntries;
|
||||
|
||||
private int visibleSsboGlId;
|
||||
private int indirectSsboGlId;
|
||||
private int gpuCapacityEntries;
|
||||
|
||||
private static final long[] EMPTY_RANGES = new long[0];
|
||||
private static final int[] EMPTY_STAMPS = new int[0];
|
||||
|
||||
private static final class PassState {
|
||||
int entryBase;
|
||||
int entryCount;
|
||||
int indexPointerMask;
|
||||
int maxElementCount;
|
||||
boolean dispatched;
|
||||
|
||||
int stamp;
|
||||
long[] regionRanges = EMPTY_RANGES;
|
||||
int[] regionStamps = EMPTY_STAMPS;
|
||||
|
||||
void reset(int mask, int base) {
|
||||
entryBase = base;
|
||||
entryCount = 0;
|
||||
indexPointerMask = mask;
|
||||
maxElementCount = 0;
|
||||
dispatched = false;
|
||||
stamp++;
|
||||
if (stamp == 0) {
|
||||
Arrays.fill(regionStamps, 0);
|
||||
stamp = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void putRange(int regionId, long packed) {
|
||||
if (regionId >= regionStamps.length) {
|
||||
final int cap = Math.max(regionId + 1, Math.max(regionStamps.length * 2, 64));
|
||||
regionRanges = Arrays.copyOf(regionRanges, cap);
|
||||
regionStamps = Arrays.copyOf(regionStamps, cap);
|
||||
}
|
||||
regionRanges[regionId] = packed;
|
||||
regionStamps[regionId] = stamp;
|
||||
}
|
||||
|
||||
long getRange(int regionId) {
|
||||
if (regionId >= regionStamps.length || regionStamps[regionId] != stamp) return -1L;
|
||||
return regionRanges[regionId];
|
||||
}
|
||||
|
||||
void releaseRanges() {
|
||||
regionRanges = EMPTY_RANGES;
|
||||
regionStamps = EMPTY_STAMPS;
|
||||
}
|
||||
}
|
||||
|
||||
private final PassState primaryPass = new PassState();
|
||||
private final PassState secondPass = new PassState();
|
||||
private PassState buildPass = primaryPass;
|
||||
private PassState current = primaryPass;
|
||||
private boolean secondPrepared;
|
||||
private int totalAppendedEntries;
|
||||
@Getter private boolean computeActiveThisPass;
|
||||
|
||||
private int currentDrawStart;
|
||||
private int currentDrawCount;
|
||||
private boolean indirectBufferBound;
|
||||
|
||||
private ByteBuffer frustumUboBytes;
|
||||
|
||||
private final GpuCulledMultiDrawBatch batch = new GpuCulledMultiDrawBatch(this);
|
||||
|
||||
private ByteBuffer uboBytes;
|
||||
private Matrix4f mvp;
|
||||
private Matrix4f mvNoTranslation;
|
||||
private int passOutputBase;
|
||||
private ChunkPrimitiveType walkPrimitiveType;
|
||||
|
||||
private final SectionMetaBuffer.Sink uploadSectionMetaSink;
|
||||
|
||||
public GpuTerrainCuller(GpuDrivenChunkCuller culler, SectionMetaBuffer sectionMeta) {
|
||||
this.culler = culler;
|
||||
this.sectionMeta = sectionMeta;
|
||||
this.uploadSectionMetaSink = buf -> {
|
||||
final int bytes = buf.remaining();
|
||||
if (!culler.uploadSectionMeta(buf)) return false;
|
||||
if (Tracy.ENABLED) TerrainDrawStats.recordSectionMetaBytes(bytes);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
void beginCullPass(int indexPointerMask) {
|
||||
this.computeActiveThisPass = GpuCulling.mode().computeEnabled();
|
||||
this.totalAppendedEntries = 0;
|
||||
this.secondPrepared = false;
|
||||
primaryPass.reset(indexPointerMask, 0);
|
||||
this.buildPass = primaryPass;
|
||||
this.current = primaryPass;
|
||||
this.currentDrawStart = 0;
|
||||
this.currentDrawCount = 0;
|
||||
}
|
||||
|
||||
void beginCombinedPasses(int firstMask, int secondMask) {
|
||||
beginCullPass(firstMask);
|
||||
secondPass.reset(secondMask, 0);
|
||||
}
|
||||
|
||||
void startSecondPass() {
|
||||
primaryPass.entryCount = totalAppendedEntries - primaryPass.entryBase;
|
||||
secondPass.entryBase = totalAppendedEntries;
|
||||
this.buildPass = secondPass;
|
||||
this.current = secondPass;
|
||||
}
|
||||
|
||||
void finishCombinedBuild() {
|
||||
secondPass.entryCount = totalAppendedEntries - secondPass.entryBase;
|
||||
this.secondPrepared = true;
|
||||
this.buildPass = primaryPass;
|
||||
this.current = primaryPass;
|
||||
}
|
||||
|
||||
boolean selectPreparedSecondPass() {
|
||||
if (!secondPrepared || !computeActiveThisPass) return false;
|
||||
this.current = secondPass;
|
||||
this.currentDrawStart = 0;
|
||||
this.currentDrawCount = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void reserveSections(int count) {
|
||||
if (count > 0) ensureStagingCapacity(totalAppendedEntries + count);
|
||||
}
|
||||
|
||||
void appendSection(int slot, int facingMask, int outputBase) {
|
||||
if ((outputBase & ~0x00FFFFFF) != 0) {
|
||||
throw new IllegalStateException("packed outputBase overflow: " + outputBase + " (max " + 0x00FFFFFF + ")");
|
||||
}
|
||||
if (totalAppendedEntries >= stagingCapacityEntries) ensureStagingCapacity(totalAppendedEntries + 1);
|
||||
final int off = totalAppendedEntries * ENTRY_BYTES;
|
||||
visibleStaging.putInt(off + 0, slot);
|
||||
visibleStaging.putInt(off + 4, (outputBase << 8) | (facingMask & 0xFF));
|
||||
totalAppendedEntries++;
|
||||
buildPass.entryCount = totalAppendedEntries - buildPass.entryBase;
|
||||
}
|
||||
|
||||
void recordRegion(RenderRegion region, int drawStart, int drawCount, int maxElementCount) {
|
||||
if (drawCount <= 0) return;
|
||||
buildPass.putRange(region.getId(), (((long) drawStart) << 32) | (drawCount & 0xFFFFFFFFL));
|
||||
if (maxElementCount > buildPass.maxElementCount) buildPass.maxElementCount = maxElementCount;
|
||||
}
|
||||
|
||||
private void prepareRegion(RenderRegion region) {
|
||||
final long packed = current.getRange(region.getId());
|
||||
if (packed < 0L) {
|
||||
currentDrawStart = 0;
|
||||
currentDrawCount = 0;
|
||||
} else {
|
||||
currentDrawStart = (int) (packed >>> 32);
|
||||
currentDrawCount = (int) (packed & 0xFFFFFFFFL);
|
||||
}
|
||||
}
|
||||
|
||||
void syncSectionMetaIfDirty() {
|
||||
culler.ensureReady();
|
||||
sectionMeta.syncIfDirty(uploadSectionMetaSink);
|
||||
}
|
||||
|
||||
public void endPass() {
|
||||
if (indirectBufferBound) {
|
||||
GLStateManager.glBindBuffer(GL40.GL_DRAW_INDIRECT_BUFFER, 0);
|
||||
indirectBufferBound = false;
|
||||
}
|
||||
currentDrawStart = 0;
|
||||
currentDrawCount = 0;
|
||||
if (current == secondPass) secondPrepared = false;
|
||||
}
|
||||
|
||||
private void dispatchPreparedPasses() {
|
||||
if (!computeActiveThisPass || totalAppendedEntries == 0) return;
|
||||
if (!needsDispatch(primaryPass) && (!secondPrepared || !needsDispatch(secondPass))) return;
|
||||
if (!culler.ensureReady()) return;
|
||||
if (frustumUboBytes == null) {
|
||||
LOG.warn("GpuTerrainCuller: frustum UBO not set before dispatch; skipping cull");
|
||||
return;
|
||||
}
|
||||
|
||||
ensureGpuBuffers(totalAppendedEntries);
|
||||
visibleStagingView.position(0).limit(totalAppendedEntries * ENTRY_BYTES);
|
||||
GLStateManager.glBindBuffer(GL43.GL_SHADER_STORAGE_BUFFER, visibleSsboGlId);
|
||||
GLStateManager.glBufferSubData(GL43.GL_SHADER_STORAGE_BUFFER, 0L, visibleStagingView);
|
||||
|
||||
BackendManager.RENDER_BACKEND.beginComputeDispatchBatch();
|
||||
try {
|
||||
dispatchPass(primaryPass);
|
||||
if (secondPrepared) dispatchPass(secondPass);
|
||||
} finally {
|
||||
BackendManager.RENDER_BACKEND.endComputeDispatchBatch();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean needsDispatch(PassState r) {
|
||||
return !r.dispatched && r.entryCount > 0;
|
||||
}
|
||||
|
||||
private void dispatchPass(PassState r) {
|
||||
if (!needsDispatch(r)) return;
|
||||
FrustumExtractor.patchControl(r.entryCount, r.indexPointerMask, frustumUboBytes);
|
||||
FrustumExtractor.patchBatchEntryBase(r.entryBase, frustumUboBytes);
|
||||
culler.uploadFrustum(frustumUboBytes);
|
||||
culler.dispatch(visibleSsboGlId, indirectSsboGlId, r.entryCount);
|
||||
r.dispatched = true;
|
||||
}
|
||||
|
||||
private void ensureGpuBuffers(int maxVisible) {
|
||||
if (visibleSsboGlId == 0) visibleSsboGlId = GLStateManager.glGenBuffers();
|
||||
if (indirectSsboGlId == 0) indirectSsboGlId = GLStateManager.glGenBuffers();
|
||||
if (maxVisible <= gpuCapacityEntries) return;
|
||||
final int newCap = Math.max(maxVisible, Math.max(gpuCapacityEntries * 2, 64));
|
||||
GLStateManager.glBindBuffer(GL43.GL_SHADER_STORAGE_BUFFER, visibleSsboGlId);
|
||||
GLStateManager.glBufferData(GL43.GL_SHADER_STORAGE_BUFFER, (long) newCap * ENTRY_BYTES, GL15.GL_STREAM_DRAW);
|
||||
GLStateManager.glBindBuffer(GL43.GL_SHADER_STORAGE_BUFFER, indirectSsboGlId);
|
||||
GLStateManager.glBufferData(GL43.GL_SHADER_STORAGE_BUFFER, (long) newCap * GpuDrivenChunkCuller.FACINGS_PER_SECTION * GpuDrivenChunkCuller.INDIRECT_COMMAND_BYTES, GL15.GL_STREAM_DRAW);
|
||||
gpuCapacityEntries = newCap;
|
||||
}
|
||||
|
||||
private void ensureStagingCapacity(int needed) {
|
||||
if (visibleStaging != null && needed <= stagingCapacityEntries) return;
|
||||
final int newCap = Math.max(needed, Math.max(stagingCapacityEntries * 2, 64));
|
||||
final ByteBuffer next = MemoryUtilities.memAlloc(newCap * ENTRY_BYTES).order(ByteOrder.nativeOrder());
|
||||
if (visibleStaging != null) {
|
||||
visibleStaging.position(0).limit(totalAppendedEntries * ENTRY_BYTES);
|
||||
next.put(visibleStaging);
|
||||
MemoryUtilities.memFree(visibleStaging);
|
||||
}
|
||||
next.position(0);
|
||||
next.limit(newCap * ENTRY_BYTES);
|
||||
visibleStaging = next;
|
||||
visibleStagingView = visibleStaging.duplicate().order(ByteOrder.nativeOrder());
|
||||
stagingCapacityEntries = newCap;
|
||||
}
|
||||
|
||||
public void beginRenderPass(ChunkRenderMatrices matrices, ChunkRenderListIterable renderLists, TerrainRenderPass renderPass, CameraTransform occlusionCamera, CameraTransform camera, boolean useBlockFaceCulling) {
|
||||
if (renderPass == AngelicaRenderPassConfiguration.CUTOUT_MIPPED_PASS && renderLists.hasPass(renderPass)
|
||||
&& selectPreparedSecondPass()) {
|
||||
return;
|
||||
}
|
||||
if (!renderLists.hasPass(renderPass)) return;
|
||||
|
||||
final boolean combined = renderPass == AngelicaRenderPassConfiguration.SOLID_PASS && AngelicaRenderPassConfiguration.CUTOUT_MIPPED_PASS != null && renderLists.hasPass(AngelicaRenderPassConfiguration.CUTOUT_MIPPED_PASS);
|
||||
|
||||
final int indexPointerMask = renderPass.isSorted() ? 0xFFFFFFFF : 0;
|
||||
if (combined) {
|
||||
beginCombinedPasses(0, 0);
|
||||
} else {
|
||||
beginCullPass(indexPointerMask);
|
||||
}
|
||||
passOutputBase = 0;
|
||||
walkPrimitiveType = null;
|
||||
|
||||
if (!computeActiveThisPass) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (uboBytes == null) {
|
||||
uboBytes = FrustumExtractor.allocateUboByteBuffer();
|
||||
}
|
||||
final boolean shadow = ShadowRenderer.ACTIVE;
|
||||
final Matrix4fc proj = matrices.projection();
|
||||
final Matrix4fc mv = matrices.modelView();
|
||||
if (mvp == null) mvp = new Matrix4f();
|
||||
if (mvNoTranslation == null) mvNoTranslation = new Matrix4f();
|
||||
mvNoTranslation.set(mv).setTranslation(0f, 0f, 0f);
|
||||
proj.mul(mvNoTranslation, mvp);
|
||||
final float camX = (float) camera.intX + camera.fracX;
|
||||
final float camY = (float) camera.intY + camera.fracY;
|
||||
final float camZ = (float) camera.intZ + camera.fracZ;
|
||||
FrustumExtractor.writeStd140(mvp, 0, 0, uboBytes);
|
||||
FrustumExtractor.patchCameraWorld(camX, camY, camZ, uboBytes);
|
||||
FrustumExtractor.patchBypassFrustum(shadow, uboBytes);
|
||||
|
||||
frustumUboBytes = uboBytes;
|
||||
|
||||
syncSectionMetaIfDirty();
|
||||
|
||||
walkPass(renderLists, renderPass, occlusionCamera, useBlockFaceCulling);
|
||||
if (combined) {
|
||||
startSecondPass();
|
||||
walkPass(renderLists, AngelicaRenderPassConfiguration.CUTOUT_MIPPED_PASS, occlusionCamera, useBlockFaceCulling);
|
||||
finishCombinedBuild();
|
||||
}
|
||||
|
||||
if (walkPrimitiveType != null) {
|
||||
FrustumExtractor.patchPrimitiveRatio(walkPrimitiveType.getVerticesPerPrimitive(), walkPrimitiveType.getIndexBufferElementsPerPrimitive(), uboBytes);
|
||||
}
|
||||
}
|
||||
|
||||
private void walkPass(ChunkRenderListIterable renderLists, TerrainRenderPass renderPass, CameraTransform occlusionCamera, boolean useBlockFaceCulling) {
|
||||
if (renderPass.isSorted()) {
|
||||
walkSortedPass(renderLists, renderPass);
|
||||
} else {
|
||||
walkMergedPass(renderLists, renderPass, occlusionCamera, useBlockFaceCulling);
|
||||
}
|
||||
}
|
||||
|
||||
private void walkMergedPass(ChunkRenderListIterable renderLists, TerrainRenderPass renderPass, CameraTransform occlusionCamera, boolean useBlockFaceCulling) {
|
||||
final boolean reverse = renderPass.isReverseOrder();
|
||||
final int step = reverse ? -1 : 1;
|
||||
final long stride = SectionRenderDataUnsafe.Strategy.COMPACT.getStride();
|
||||
final int cameraX = occlusionCamera.intX;
|
||||
final int cameraY = occlusionCamera.intY;
|
||||
final int cameraZ = occlusionCamera.intZ;
|
||||
|
||||
final int n = renderLists.getNumRegions();
|
||||
for (int regionIndex = 0; regionIndex < n; regionIndex++) {
|
||||
final ChunkRenderList list = renderLists.getRegion(reverse ? n - 1 - regionIndex : regionIndex);
|
||||
final RenderRegion region = list.getRegion();
|
||||
final SectionRenderDataStorage storage = region.getStorage(renderPass);
|
||||
if (storage == null) continue;
|
||||
final byte[] sections = list.getSectionsWithGeometry();
|
||||
final int sectionCount = list.getSectionsWithGeometryCount();
|
||||
if (sectionCount == 0) continue;
|
||||
final SectionRenderDataStorageRegionAccessor accessor = (SectionRenderDataStorageRegionAccessor) storage;
|
||||
final ChunkPrimitiveType primitiveType = storage.getPrimitiveType();
|
||||
trackPrimitiveType(primitiveType);
|
||||
final int[] slotCache = accessor.angelica$getSlotCache();
|
||||
if (slotCache == null) continue;
|
||||
|
||||
reserveSections(sectionCount);
|
||||
|
||||
final long pBase = storage.getRowBasePointer();
|
||||
final int regionOriginX = region.getChunkX();
|
||||
final int regionOriginY = region.getChunkY();
|
||||
final int regionOriginZ = region.getChunkZ();
|
||||
final int uniformMask = useBlockFaceCulling ? BatchAssembler.uniformCullMask(region, occlusionCamera) : ModelQuadFacing.ALL;
|
||||
|
||||
final int regionDrawStart = passOutputBase;
|
||||
int regionDrawCount = 0;
|
||||
int regionMaxElems = 0;
|
||||
|
||||
int cursor = reverse ? sectionCount - 1 : 0;
|
||||
|
||||
for (int i = 0; i < sectionCount; i++, cursor += step) {
|
||||
final int sectionIndex = sections[cursor] & 0xFF;
|
||||
|
||||
int slices = uniformMask != BatchAssembler.MASK_NOT_UNIFORM ? uniformMask : BatchAssembler.getVisibleFaces(cameraX, cameraY, cameraZ, regionOriginX + LocalSectionIndex.unpackX(sectionIndex), regionOriginY + LocalSectionIndex.unpackY(sectionIndex), regionOriginZ + LocalSectionIndex.unpackZ(sectionIndex));
|
||||
slices &= storage.getSliceMask(sectionIndex);
|
||||
if (slices == 0) continue;
|
||||
|
||||
final int slot = slotCache[sectionIndex];
|
||||
if (slot < 0) continue;
|
||||
|
||||
final long pMeshData = pBase + (sectionIndex * stride);
|
||||
final long runs = BatchAssembler.packRuns(slices & 0x7F);
|
||||
final int sectionDraws = BatchAssembler.runCount(runs);
|
||||
appendSection(slot, slices, passOutputBase);
|
||||
passOutputBase += sectionDraws;
|
||||
regionDrawCount += sectionDraws;
|
||||
|
||||
int maxElems = 0;
|
||||
for (int run = 0; run < sectionDraws; run++) {
|
||||
final int startVertex = SectionRenderDataUnsafe.Strategy.COMPACT.getVertexOffset(pMeshData, BatchAssembler.runFirst(runs, run));
|
||||
final int endVertex = SectionRenderDataUnsafe.Strategy.COMPACT.getRunVertexEnd(pMeshData, BatchAssembler.runLast(runs, run), primitiveType);
|
||||
final int ec = SectionRenderDataUnsafe.elementsForVertices(endVertex - startVertex, primitiveType);
|
||||
if (ec > maxElems) maxElems = ec;
|
||||
}
|
||||
if (maxElems > regionMaxElems) regionMaxElems = maxElems;
|
||||
}
|
||||
|
||||
if (regionDrawCount > 0) {
|
||||
recordRegion(region, regionDrawStart, regionDrawCount, regionMaxElems);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void walkSortedPass(ChunkRenderListIterable renderLists, TerrainRenderPass renderPass) {
|
||||
final boolean reverse = renderPass.isReverseOrder();
|
||||
final int step = reverse ? -1 : 1;
|
||||
final long stride = SectionRenderDataUnsafe.Strategy.FULL.getStride();
|
||||
|
||||
final int n = renderLists.getNumRegions();
|
||||
for (int regionIndex = 0; regionIndex < n; regionIndex++) {
|
||||
final ChunkRenderList list = renderLists.getRegion(reverse ? n - 1 - regionIndex : regionIndex);
|
||||
final RenderRegion region = list.getRegion();
|
||||
final SectionRenderDataStorage storage = region.getStorage(renderPass);
|
||||
if (storage == null) continue;
|
||||
final byte[] sections = list.getSectionsWithGeometry();
|
||||
final int sectionCount = list.getSectionsWithGeometryCount();
|
||||
if (sectionCount == 0) continue;
|
||||
final SectionRenderDataStorageRegionAccessor accessor = (SectionRenderDataStorageRegionAccessor) storage;
|
||||
final ChunkPrimitiveType primitiveType = storage.getPrimitiveType();
|
||||
trackPrimitiveType(primitiveType);
|
||||
final int[] slotCache = accessor.angelica$getSlotCache();
|
||||
if (slotCache == null) continue;
|
||||
|
||||
reserveSections(sectionCount);
|
||||
|
||||
final long pBase = storage.getRowBasePointer();
|
||||
|
||||
final int regionDrawStart = passOutputBase;
|
||||
int regionDrawCount = 0;
|
||||
int regionMaxElems = 0;
|
||||
|
||||
int cursor = reverse ? sectionCount - 1 : 0;
|
||||
|
||||
for (int i = 0; i < sectionCount; i++, cursor += step) {
|
||||
final int sectionIndex = sections[cursor] & 0xFF;
|
||||
|
||||
final int slices = ModelQuadFacing.ALL & storage.getSliceMask(sectionIndex);
|
||||
if (slices == 0) continue;
|
||||
|
||||
final int slot = slotCache[sectionIndex];
|
||||
if (slot < 0) continue;
|
||||
|
||||
final long pMeshData = pBase + (sectionIndex * stride);
|
||||
final int sectionDraws = Integer.bitCount(slices);
|
||||
appendSection(slot, slices, passOutputBase);
|
||||
passOutputBase += sectionDraws;
|
||||
regionDrawCount += sectionDraws;
|
||||
|
||||
int maxElems = 0;
|
||||
for (int f = 0; f < ModelQuadFacing.COUNT; f++) {
|
||||
final int ec = SectionRenderDataUnsafe.Strategy.FULL.getElementCount(pMeshData, f, primitiveType);
|
||||
if (ec > maxElems) maxElems = ec;
|
||||
}
|
||||
if (maxElems > regionMaxElems) regionMaxElems = maxElems;
|
||||
}
|
||||
|
||||
if (regionDrawCount > 0) {
|
||||
recordRegion(region, regionDrawStart, regionDrawCount, regionMaxElems);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void trackPrimitiveType(ChunkPrimitiveType primitiveType) {
|
||||
if (walkPrimitiveType == null) {
|
||||
walkPrimitiveType = primitiveType;
|
||||
} else if (walkPrimitiveType.getVerticesPerPrimitive() != primitiveType.getVerticesPerPrimitive() || walkPrimitiveType.getIndexBufferElementsPerPrimitive() != primitiveType.getIndexBufferElementsPerPrimitive()) {
|
||||
throw new IllegalStateException("Batched cull dispatch spans passes with differing primitive ratios: " + walkPrimitiveType + " vs " + primitiveType);
|
||||
}
|
||||
}
|
||||
|
||||
public GpuCulledMultiDrawBatch batchForRegion(RenderRegion region) {
|
||||
prepareRegion(region);
|
||||
if (currentDrawCount == 0) return null;
|
||||
batch.prepare(currentDrawStart, currentDrawCount, current.maxElementCount);
|
||||
return batch;
|
||||
}
|
||||
|
||||
void drawRegionRange(CommandList commandList, GlTessellation tessellation, GlPrimitiveType primitiveType, int drawStart, int drawCount) {
|
||||
if (drawCount == 0) return;
|
||||
dispatchPreparedPasses();
|
||||
if (!current.dispatched) return;
|
||||
|
||||
if (Tracy.ENABLED) {
|
||||
TerrainDrawStats.recordCullRegion(drawCount);
|
||||
}
|
||||
|
||||
if (!indirectBufferBound) {
|
||||
GLStateManager.glBindBuffer(GL40.GL_DRAW_INDIRECT_BUFFER, indirectSsboGlId);
|
||||
indirectBufferBound = true;
|
||||
}
|
||||
|
||||
final long offsetBytes = (long) drawStart * GpuDrivenChunkCuller.INDIRECT_COMMAND_BYTES;
|
||||
try (DrawCommandList ignored = commandList.beginTessellating(tessellation)) {
|
||||
GLStateManager.glMultiDrawElementsIndirect(primitiveType.getId(), GL11.GL_UNSIGNED_INT, offsetBytes, drawCount, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
if (indirectBufferBound) {
|
||||
GLStateManager.glBindBuffer(GL40.GL_DRAW_INDIRECT_BUFFER, 0);
|
||||
indirectBufferBound = false;
|
||||
}
|
||||
if (visibleStaging != null) {
|
||||
MemoryUtilities.memFree(visibleStaging);
|
||||
visibleStaging = null;
|
||||
visibleStagingView = null;
|
||||
stagingCapacityEntries = 0;
|
||||
}
|
||||
if (visibleSsboGlId != 0) {
|
||||
GLStateManager.glDeleteBuffers(visibleSsboGlId);
|
||||
visibleSsboGlId = 0;
|
||||
}
|
||||
if (indirectSsboGlId != 0) {
|
||||
GLStateManager.glDeleteBuffers(indirectSsboGlId);
|
||||
indirectSsboGlId = 0;
|
||||
}
|
||||
gpuCapacityEntries = 0;
|
||||
frustumUboBytes = null;
|
||||
uboBytes = null;
|
||||
primaryPass.releaseRanges();
|
||||
secondPass.releaseRanges();
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ public final class SectionMetaBuffer {
|
||||
}
|
||||
|
||||
public synchronized int update(int passIndex, int regionOriginX, int regionOriginY, int regionOriginZ, int localSectionIndex,
|
||||
long dataPtr, SectionRenderDataUnsafe.Strategy layout, ChunkPrimitiveType primitiveType) {
|
||||
long dataPtr, int sliceMask, SectionRenderDataUnsafe.Strategy layout, ChunkPrimitiveType primitiveType) {
|
||||
if (dataPtr == 0L) return -1;
|
||||
final int sectionOriginX = regionOriginX + (((localSectionIndex >> 5) & 0x07) << 4);
|
||||
final int sectionOriginY = regionOriginY + (((localSectionIndex ) & 0x03) << 4);
|
||||
@@ -51,7 +51,7 @@ public final class SectionMetaBuffer {
|
||||
slot = allocateSlot();
|
||||
keyToSlot.put(key, slot);
|
||||
}
|
||||
writeSlot(slot, sectionOriginX, sectionOriginY, sectionOriginZ, dataPtr, layout, primitiveType);
|
||||
writeSlot(slot, sectionOriginX, sectionOriginY, sectionOriginZ, dataPtr, sliceMask, layout, primitiveType);
|
||||
dirty = true;
|
||||
return slot;
|
||||
}
|
||||
@@ -97,12 +97,12 @@ public final class SectionMetaBuffer {
|
||||
capacity = newCapacity;
|
||||
}
|
||||
|
||||
private void writeSlot(int slot, int sectionOriginX, int sectionOriginY, int sectionOriginZ, long dataPtr, SectionRenderDataUnsafe.Strategy layout, ChunkPrimitiveType primitiveType) {
|
||||
private void writeSlot(int slot, int sectionOriginX, int sectionOriginY, int sectionOriginZ, long dataPtr, int sliceMask, SectionRenderDataUnsafe.Strategy layout, ChunkPrimitiveType primitiveType) {
|
||||
final int base = slot * BYTES_PER_SECTION;
|
||||
mirror.putInt(base + 0, sectionOriginX);
|
||||
mirror.putInt(base + 4, sectionOriginY);
|
||||
mirror.putInt(base + 8, sectionOriginZ);
|
||||
mirror.putInt(base + OFFSET_SLICE_MASK, SectionRenderDataUnsafe.getSliceMask(dataPtr));
|
||||
mirror.putInt(base + OFFSET_SLICE_MASK, sliceMask);
|
||||
for (int facing = 0; facing < FACING_COUNT; facing++) {
|
||||
mirror.putInt(base + OFFSET_POSTS + facing * 4, layout.getVertexOffset(dataPtr, facing));
|
||||
}
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ public final class SdlShadowVoxelizationSink implements ShadowVoxelizer.Sink {
|
||||
|
||||
@Override
|
||||
public boolean region(RenderRegion region, GlVertexFormat format, float offsetX, float offsetY, float offsetZ) {
|
||||
final RenderRegion.DeviceResources resources = region.getResources(format);
|
||||
final RenderRegion.DeviceResources resources = region.getResources();
|
||||
if (resources == null) return false;
|
||||
final GlBuffer vertexBuffer = resources.getVertexBuffer();
|
||||
if (vertexBuffer == null || vertexBuffer.handle() == 0) return false;
|
||||
|
||||
+62
-17
@@ -13,8 +13,6 @@ import org.embeddedt.embeddium.impl.render.chunk.region.RenderRegion;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.terrain.TerrainRenderPass;
|
||||
import org.embeddedt.embeddium.impl.render.viewport.CameraTransform;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
public final class ShadowVoxelizer {
|
||||
|
||||
public interface Sink {
|
||||
@@ -29,16 +27,23 @@ public final class ShadowVoxelizer {
|
||||
|
||||
public void walkPass(ChunkRenderListIterable renderLists, TerrainRenderPass renderPass, GlVertexFormat format, CameraTransform camera, CameraTransform occlusionCamera, boolean useBlockFaceCulling, Sink sink) {
|
||||
try {
|
||||
final Iterator<ChunkRenderList> it = renderLists.iterator(renderPass.isReverseOrder());
|
||||
while (it.hasNext()) {
|
||||
walkRegion(it.next(), renderPass, format, camera, occlusionCamera, useBlockFaceCulling, sink);
|
||||
final boolean reverse = renderPass.isReverseOrder();
|
||||
final int n = renderLists.getNumRegions();
|
||||
if (renderPass.isSorted()) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
walkRegionFull(renderLists.getRegion(reverse ? n - 1 - i : i), renderPass, format, camera, sink);
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < n; i++) {
|
||||
walkRegionCompact(renderLists.getRegion(reverse ? n - 1 - i : i), renderPass, format, camera, occlusionCamera, useBlockFaceCulling, sink);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
sink.finish();
|
||||
}
|
||||
}
|
||||
|
||||
private static void walkRegion(ChunkRenderList list, TerrainRenderPass renderPass, GlVertexFormat format, CameraTransform camera, CameraTransform occlusionCamera, boolean useBlockFaceCulling, Sink sink) {
|
||||
private static void walkRegionCompact(ChunkRenderList list, TerrainRenderPass renderPass, GlVertexFormat format, CameraTransform camera, CameraTransform occlusionCamera, boolean useBlockFaceCulling, Sink sink) {
|
||||
final RenderRegion region = list.getRegion();
|
||||
final SectionRenderDataStorage storage = region.getStorage(renderPass);
|
||||
if (storage == null) return;
|
||||
@@ -47,7 +52,8 @@ public final class ShadowVoxelizer {
|
||||
if (sectionCount == 0) return;
|
||||
|
||||
final byte[] sections = list.getSectionsWithGeometry();
|
||||
final SectionRenderDataUnsafe.Strategy layout = storage.getStorageStrategy();
|
||||
final long pBase = storage.getRowBasePointer();
|
||||
final long stride = SectionRenderDataUnsafe.Strategy.COMPACT.getStride();
|
||||
final ChunkPrimitiveType primitiveType = storage.getPrimitiveType();
|
||||
final int regionChunkX = region.getChunkX();
|
||||
final int regionChunkY = region.getChunkY();
|
||||
@@ -56,32 +62,27 @@ public final class ShadowVoxelizer {
|
||||
|
||||
for (int i = 0; i < sectionCount; i++) {
|
||||
final int sectionIndex = sections[i] & 0xFF;
|
||||
final long pMeshData = storage.getDataPointer(sectionIndex);
|
||||
int slices = useBlockFaceCulling
|
||||
? BatchAssembler.getVisibleFaces(occlusionCamera.intX, occlusionCamera.intY, occlusionCamera.intZ,
|
||||
regionChunkX + LocalSectionIndex.unpackX(sectionIndex),
|
||||
regionChunkY + LocalSectionIndex.unpackY(sectionIndex),
|
||||
regionChunkZ + LocalSectionIndex.unpackZ(sectionIndex))
|
||||
: ModelQuadFacing.ALL;
|
||||
slices &= SectionRenderDataUnsafe.getSliceMask(pMeshData);
|
||||
slices &= storage.getSliceMask(sectionIndex);
|
||||
slices &= 0x7F;
|
||||
if (slices == 0) continue;
|
||||
|
||||
final long pMeshData = pBase + sectionIndex * stride;
|
||||
final long runs = BatchAssembler.packRuns(slices);
|
||||
final int runCount = BatchAssembler.runCount(runs);
|
||||
for (int run = 0; run < runCount; run++) {
|
||||
final int startVertex = layout.getVertexOffset(pMeshData, BatchAssembler.runFirst(runs, run));
|
||||
final int endVertex = layout.getRunVertexEnd(pMeshData, BatchAssembler.runLast(runs, run), primitiveType);
|
||||
final int startVertex = SectionRenderDataUnsafe.Strategy.COMPACT.getVertexOffset(pMeshData, BatchAssembler.runFirst(runs, run));
|
||||
final int endVertex = SectionRenderDataUnsafe.Strategy.COMPACT.getRunVertexEnd(pMeshData, BatchAssembler.runLast(runs, run), primitiveType);
|
||||
final int vertexCount = endVertex - startVertex;
|
||||
if (vertexCount <= 0) continue;
|
||||
|
||||
if (!bound) {
|
||||
if (!sink.region(region, format,
|
||||
cameraRelative(region.getOriginX(), camera.intX, camera.fracX),
|
||||
cameraRelative(region.getOriginY(), camera.intY, camera.fracY),
|
||||
cameraRelative(region.getOriginZ(), camera.intZ, camera.fracZ))) {
|
||||
return;
|
||||
}
|
||||
if (!bindRegion(region, format, camera, sink)) return;
|
||||
bound = true;
|
||||
}
|
||||
if (!sink.range(startVertex, vertexCount)) return;
|
||||
@@ -89,6 +90,50 @@ public final class ShadowVoxelizer {
|
||||
}
|
||||
}
|
||||
|
||||
private static void walkRegionFull(ChunkRenderList list, TerrainRenderPass renderPass, GlVertexFormat format, CameraTransform camera, Sink sink) {
|
||||
final RenderRegion region = list.getRegion();
|
||||
final SectionRenderDataStorage storage = region.getStorage(renderPass);
|
||||
if (storage == null) return;
|
||||
|
||||
final int sectionCount = list.getSectionsWithGeometryCount();
|
||||
if (sectionCount == 0) return;
|
||||
|
||||
final byte[] sections = list.getSectionsWithGeometry();
|
||||
final long pBase = storage.getRowBasePointer();
|
||||
final long stride = SectionRenderDataUnsafe.Strategy.FULL.getStride();
|
||||
final ChunkPrimitiveType primitiveType = storage.getPrimitiveType();
|
||||
boolean bound = false;
|
||||
|
||||
for (int i = 0; i < sectionCount; i++) {
|
||||
final int sectionIndex = sections[i] & 0xFF;
|
||||
final int slices = ModelQuadFacing.ALL & storage.getSliceMask(sectionIndex);
|
||||
if (slices == 0) continue;
|
||||
|
||||
final long pMeshData = pBase + sectionIndex * stride;
|
||||
final long runs = BatchAssembler.packRuns(slices);
|
||||
final int runCount = BatchAssembler.runCount(runs);
|
||||
for (int run = 0; run < runCount; run++) {
|
||||
final int startVertex = SectionRenderDataUnsafe.Strategy.FULL.getVertexOffset(pMeshData, BatchAssembler.runFirst(runs, run));
|
||||
final int endVertex = SectionRenderDataUnsafe.Strategy.FULL.getRunVertexEnd(pMeshData, BatchAssembler.runLast(runs, run), primitiveType);
|
||||
final int vertexCount = endVertex - startVertex;
|
||||
if (vertexCount <= 0) continue;
|
||||
|
||||
if (!bound) {
|
||||
if (!bindRegion(region, format, camera, sink)) return;
|
||||
bound = true;
|
||||
}
|
||||
if (!sink.range(startVertex, vertexCount)) return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean bindRegion(RenderRegion region, GlVertexFormat format, CameraTransform camera, Sink sink) {
|
||||
return sink.region(region, format,
|
||||
cameraRelative(region.getOriginX(), camera.intX, camera.fracX),
|
||||
cameraRelative(region.getOriginY(), camera.intY, camera.fracY),
|
||||
cameraRelative(region.getOriginZ(), camera.intZ, camera.fracZ));
|
||||
}
|
||||
|
||||
private static float cameraRelative(int originBlock, int cameraBlock, float cameraFrac) {
|
||||
return (originBlock - cameraBlock) - cameraFrac;
|
||||
}
|
||||
|
||||
Loaded 100 of 165 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user