Fix GLES crashing at startup and rendering black (#2346)

This commit is contained in:
Jason Mitchell authored and GitHub committed 2026-11-06 21:30:30 -08:00
1 parent a6d014a9eb
commit 3876218405
24 files changed
+668 -185

No files matched your search

+5 -2
View File
@@ -26,7 +26,7 @@ jobs:
- name: Install a software Vulkan driver - name: Install a software Vulkan driver
run: | run: |
sudo apt-get update -y sudo apt-get update -y
sudo apt-get install -y xvfb mesa-vulkan-drivers sudo apt-get install -y xvfb mesa-vulkan-drivers libgl1-mesa-dri
- uses: actions/checkout@v5 - uses: actions/checkout@v5
- uses: actions/setup-java@v5 - uses: actions/setup-java@v5
with: with:
@@ -36,7 +36,7 @@ jobs:
25 25
- uses: gradle/actions/setup-gradle@v5 - uses: gradle/actions/setup-gradle@v5
- name: Run SDL-GPU tests - name: Run SDL-GPU tests
run: xvfb-run -a --server-args="-screen 0 1366x768x24" ./gradlew --build-cache --stacktrace --continue :sdl-gpu:glsmSdlTest :sdl-gpu:sdlGpuDeviceTest run: xvfb-run -a --server-args="-screen 0 1366x768x24" ./gradlew --build-cache --stacktrace --continue :sdl-gpu:glsmSdlTest :sdl-gpu:sdlGpuDeviceTest :lwjgl3-backend:glesTest
- name: Check LWJGL did not extract natives to the shared temp dir - name: Check LWJGL did not extract natives to the shared temp dir
run: test ! -e "/tmp/lwjgl_$USER" run: test ! -e "/tmp/lwjgl_$USER"
- name: Attach SDL-GPU test results - name: Attach SDL-GPU test results
@@ -48,3 +48,6 @@ jobs:
sdl-gpu/build/test-results/ sdl-gpu/build/test-results/
sdl-gpu/build/reports/tests/ sdl-gpu/build/reports/tests/
sdl-gpu/hs_err_pid*.log sdl-gpu/hs_err_pid*.log
lwjgl3-backend/build/test-results/
lwjgl3-backend/build/reports/tests/
lwjgl3-backend/hs_err_pid*.log
+17
View File
@@ -67,6 +67,22 @@ artifacts {
} }
} }
// LWJGL2-API stubs for test JVMs that strip the lwjgl-2 jars (glsmSdlTest, glesTest).
val lwjgl2StubsSourceSet = sourceSets.create("lwjgl2Stubs") {
java.srcDir("src/lwjgl2Stubs/java")
}
val lwjgl2Stubs by configurations.creating {
isCanBeConsumed = true
isCanBeResolved = false
}
artifacts {
add(lwjgl2Stubs.name, lwjgl2StubsSourceSet.java.classesDirectory) {
builtBy(tasks.named("lwjgl2StubsClasses"))
}
}
repositories { repositories {
maven { maven {
name = "Mojang" name = "Mojang"
@@ -111,6 +127,7 @@ val spirvTestRuntime by configurations.creating {
dependencies { dependencies {
// MC compile stubs // MC compile stubs
compileOnly(sourceSets["stubs"].output) compileOnly(sourceSets["stubs"].output)
"lwjgl2StubsCompileOnly"(libs.lwjgl3.opengl)
// LWJGL // LWJGL
compileOnly(libs.lwjgl2) compileOnly(libs.lwjgl2)
@@ -1,5 +1,7 @@
package org.lwjgl.opengl; package org.lwjgl.opengl;
import java.lang.reflect.Field;
public class ContextCapabilities { public class ContextCapabilities {
public boolean OpenGL30 = true; public boolean OpenGL30 = true;
@@ -21,11 +23,28 @@ public class ContextCapabilities {
public boolean GL_ARB_sampler_objects = true; public boolean GL_ARB_sampler_objects = true;
public boolean GL_ARB_shader_image_load_store = true; public boolean GL_ARB_shader_image_load_store = true;
public boolean GL_ARB_shader_storage_buffer_object = true; public boolean GL_ARB_shader_storage_buffer_object = true;
public boolean GL_ARB_stencil_texturing = false;
public boolean GL_ARB_tessellation_shader = false; public boolean GL_ARB_tessellation_shader = false;
public boolean GL_ARB_texture_storage = false;
public boolean GL_EXT_direct_state_access = false; public boolean GL_EXT_direct_state_access = false;
public boolean GL_EXT_gpu_shader4 = true; public boolean GL_EXT_gpu_shader4 = true;
public boolean GL_EXT_shader_image_load_store = true; public boolean GL_EXT_shader_image_load_store = true;
public boolean GL_NVX_gpu_memory_info = false; public boolean GL_NVX_gpu_memory_info = false;
public ContextCapabilities() {
if ("lwjgl3".equals(System.getProperty("glsm.stubs.capabilities"))) copyFromCurrentContext();
}
private void copyFromCurrentContext() {
final GLCapabilities caps = GL.getCapabilities();
for (Field field : ContextCapabilities.class.getFields()) {
try {
field.setBoolean(this, GLCapabilities.class.getField(field.getName()).getBoolean(caps));
} catch (ReflectiveOperationException e) {
throw new IllegalStateException("LWJGL3 GLCapabilities has no " + field.getName(), e);
}
}
}
} }
@@ -0,0 +1,13 @@
package org.lwjgl.opengl;
public final class GLContext {
private static ContextCapabilities capabilities;
private GLContext() {}
public static ContextCapabilities getCapabilities() {
if (capabilities == null) capabilities = new ContextCapabilities();
return capabilities;
}
}
@@ -88,8 +88,11 @@ public final class GLSMRasterizer {
static void probePointSizeRange(GLContextState glCtx) { static void probePointSizeRange(GLContextState glCtx) {
final FloatBuffer range = glCtx.queryScratch; final FloatBuffer range = glCtx.queryScratch;
range.clear(); range.clear();
RENDER_BACKEND.getFloat(GL11.GL_POINT_SIZE_RANGE, range); float smooth = 0.0f;
final float smooth = range.get(1); if (!RenderSystem.isGLES()) {
RENDER_BACKEND.getFloat(GL11.GL_POINT_SIZE_RANGE, range);
smooth = range.get(1);
}
RENDER_BACKEND.getFloat(GL12.GL_ALIASED_POINT_SIZE_RANGE, range); RENDER_BACKEND.getFloat(GL12.GL_ALIASED_POINT_SIZE_RANGE, range);
maxPointSize = Math.max(smooth, range.get(1)); maxPointSize = Math.max(smooth, range.get(1));
} }
@@ -15,8 +15,10 @@ import org.taumc.glsl.grammar.GLSLParser;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
public final class GlslVulkanPreprocess { public final class GlslVulkanPreprocess {
@@ -56,7 +58,9 @@ public final class GlslVulkanPreprocess {
private GlslVulkanPreprocess() {} private GlslVulkanPreprocess() {}
private record CacheKey(String source, int glShaderType, boolean separateReadOnlyImages) {} private record CacheKey(String source, int glShaderType, boolean separateReadOnlyImages) {}
public record Result(String rewrittenSource, Set<String> boolUniforms, Set<String> explicitVsInputs) {} public record Result(String rewrittenSource, Set<String> boolUniforms, Set<String> explicitVsInputs, Map<String, UniformBlock> uniformBlocks) {}
public record UniformBlock(String qualifierText, @Nullable String instanceName) {}
/** Replace {@code [startIdx, stopIdx]} with {@code replacement}. */ /** Replace {@code [startIdx, stopIdx]} with {@code replacement}. */
public record Edit(int startIdx, int stopIdx, String replacement) {} public record Edit(int startIdx, int stopIdx, String replacement) {}
@@ -109,7 +113,7 @@ public final class GlslVulkanPreprocess {
final List<Edit> edits = new ArrayList<>(); final List<Edit> edits = new ArrayList<>();
final Metadata meta = collectEdits(new ShaderIndex(root, source), glShaderType, debugName, separateReadOnlyImages, edits); final Metadata meta = collectEdits(new ShaderIndex(root, source), glShaderType, debugName, separateReadOnlyImages, edits);
final Result out = new Result(applyEdits(source, edits), meta.boolUniforms(), meta.explicitVsInputs()); final Result out = new Result(applyEdits(source, edits), meta.boolUniforms(), meta.explicitVsInputs(), meta.uniformBlocks());
synchronized (CACHE) { synchronized (CACHE) {
CACHE.putAndMoveToFirst(key, out); CACHE.putAndMoveToFirst(key, out);
while (CACHE.size() > CACHE_MAX) CACHE.removeLast(); while (CACHE.size() > CACHE_MAX) CACHE.removeLast();
@@ -117,11 +121,12 @@ public final class GlslVulkanPreprocess {
return out; return out;
} }
public record Metadata(Set<String> boolUniforms, Set<String> explicitVsInputs, boolean needsSamplerless) {} public record Metadata(Set<String> boolUniforms, Set<String> explicitVsInputs, boolean needsSamplerless, Map<String, UniformBlock> uniformBlocks) {}
public static Metadata collectEdits(ShaderIndex index, int glShaderType, String debugName, boolean separateReadOnlyImages, List<Edit> edits) { public static Metadata collectEdits(ShaderIndex index, int glShaderType, String debugName, boolean separateReadOnlyImages, List<Edit> edits) {
final boolean isVertex = glShaderType == GL20.GL_VERTEX_SHADER; final boolean isVertex = glShaderType == GL20.GL_VERTEX_SHADER;
final Set<String> bools = new HashSet<>(); final Set<String> bools = new HashSet<>();
Map<String, UniformBlock> uniformBlocks = Map.of();
final Set<String> explicitInputs = new HashSet<>(); final Set<String> explicitInputs = new HashSet<>();
final Set<String> readOnlyImages = new HashSet<>(); final Set<String> readOnlyImages = new HashSet<>();
boolean needsSamplerless = false; boolean needsSamplerless = false;
@@ -130,6 +135,14 @@ public final class GlslVulkanPreprocess {
final List<Integer> unlocatedVsInputStarts = new ArrayList<>(); final List<Integer> unlocatedVsInputStarts = new ArrayList<>();
for (GLSLParser.DeclarationContext declaration : index.globalDeclarations()) { for (GLSLParser.DeclarationContext declaration : index.globalDeclarations()) {
if (declaration.LEFT_BRACE() != null && ShaderIndex.hasStorageQualifier(declaration.type_qualifier(), "uniform")) {
final GLSLParser.Type_qualifierContext blockQualifier = declaration.type_qualifier();
final String qualifierText = index.source().substring(startIdx(blockQualifier), stopIdx(blockQualifier) + 1);
final String instanceName = declaration.IDENTIFIER().size() > 1 ? declaration.IDENTIFIER(1).getText() : null;
if (uniformBlocks.isEmpty()) uniformBlocks = new HashMap<>();
uniformBlocks.put(declaration.IDENTIFIER(0).getText(), new UniformBlock(qualifierText, instanceName));
continue;
}
final GLSLParser.Init_declarator_listContext list = declaration.init_declarator_list(); final GLSLParser.Init_declarator_listContext list = declaration.init_declarator_list();
if (list == null) continue; if (list == null) continue;
final GLSLParser.Single_declarationContext single = list.single_declaration(); final GLSLParser.Single_declarationContext single = list.single_declaration();
@@ -223,7 +236,7 @@ public final class GlslVulkanPreprocess {
} }
} }
return new Metadata(bools, explicitInputs, needsSamplerless); return new Metadata(bools, explicitInputs, needsSamplerless, uniformBlocks);
} }
public static boolean renamesIdentifier(String name) { public static boolean renamesIdentifier(String name) {
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.glsm.shader;
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils; import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.Edit; import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.Edit;
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.UniformBlock;
import me.eigenraven.lwjgl3ify.api.Lwjgl3Aware; import me.eigenraven.lwjgl3ify.api.Lwjgl3Aware;
import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.ParseTree;
@@ -26,8 +27,10 @@ import org.taumc.glsl.grammar.GLSLParserBaseListener;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.IntBuffer; import java.nio.IntBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import static com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.startIdx; import static com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.startIdx;
@@ -65,13 +68,13 @@ public final class SpirvShaderTranslator {
try { try {
final String emitted = emitGlslEs(cr.spirv(), debugName, glShaderType, pre.explicitVsInputs()); final String emitted = emitGlslEs(cr.spirv(), debugName, glShaderType, pre.explicitVsInputs());
if (emitted == null) return null; if (emitted == null) return null;
return postProcessEsOutput(emitted, pre.boolUniforms()); return postProcessEsOutput(emitted, pre.boolUniforms(), pre.uniformBlocks());
} finally { } finally {
memFree(cr.spirv()); memFree(cr.spirv());
} }
} }
private static @Nullable String postProcessEsOutput(String glslEs, Set<String> boolUniformNames) { private static @Nullable String postProcessEsOutput(String glslEs, Set<String> boolUniformNames, Map<String, UniformBlock> uniformBlocks) {
final GLSLParser.Translation_unitContext root; final GLSLParser.Translation_unitContext root;
try { try {
root = GlslTransformUtils.parseFullQuiet(glslEs); root = GlslTransformUtils.parseFullQuiet(glslEs);
@@ -80,21 +83,42 @@ public final class SpirvShaderTranslator {
return null; return null;
} }
final DefaultBlockInfo block = findDefaultBlock(root); final BlockFinder finder = new BlockFinder(glslEs, uniformBlocks);
final List<Edit> edits = new ArrayList<>(); ParseTreeWalker.DEFAULT.walk(finder, root);
if (block != null) { final List<Edit> edits = new ArrayList<>();
edits.add(new Edit(startIdx(block.structExternalDecl), stopIdx(block.structExternalDecl), buildLooseUniforms(block.members, boolUniformNames))); BlockInfo defaultBlock = null;
final Map<String, Set<String>> bareMembersByVar = new HashMap<>();
for (BlockInfo block : finder.blocks) {
final String structReplacement;
if (block.source == null) {
defaultBlock = block;
structReplacement = buildLooseUniforms(block.members, boolUniformNames);
bareMembersByVar.put(block.varName, block.memberNames());
} else if (block.source.instanceName() == null) {
structReplacement = block.source.qualifierText() + " " + block.structName + " " + block.body + ";";
bareMembersByVar.put(block.varName, block.memberNames());
} else {
structReplacement = block.source.qualifierText() + " " + block.structName + " " + block.body + " " + block.declaratorText + ";";
}
edits.add(new Edit(startIdx(block.structExternalDecl), stopIdx(block.structExternalDecl), structReplacement));
edits.add(new Edit(startIdx(block.uniformExternalDecl), stopIdx(block.uniformExternalDecl), "")); edits.add(new Edit(startIdx(block.uniformExternalDecl), stopIdx(block.uniformExternalDecl), ""));
} }
final UseSiteRewriter rewriter = new UseSiteRewriter(block, boolUniformNames, edits); final Set<String> defaultMembers = defaultBlock == null ? Set.of() : bareMembersByVar.get(defaultBlock.varName);
final UseSiteRewriter rewriter = new UseSiteRewriter(defaultBlock, defaultMembers, boolUniformNames, bareMembersByVar, edits);
ParseTreeWalker.DEFAULT.walk(rewriter, root); ParseTreeWalker.DEFAULT.walk(rewriter, root);
return GlslVulkanPreprocess.applyEdits(glslEs, edits); return GlslVulkanPreprocess.applyEdits(glslEs, edits);
} }
private record DefaultBlockInfo(ParserRuleContext structExternalDecl, ParserRuleContext uniformExternalDecl, String blockVarName, List<MemberInfo> members) {} private record BlockInfo(String structName, @Nullable UniformBlock source, ParserRuleContext structExternalDecl, String body, List<MemberInfo> members, ParserRuleContext uniformExternalDecl, String varName, String declaratorText) {
Set<String> memberNames() {
final Set<String> names = new HashSet<>();
for (MemberInfo m : members) names.add(m.name);
return names;
}
}
private record MemberInfo(String typePrefix, String name) {} private record MemberInfo(String typePrefix, String name) {}
@@ -111,31 +135,38 @@ public final class SpirvShaderTranslator {
return sb.toString(); return sb.toString();
} }
private static @Nullable DefaultBlockInfo findDefaultBlock(GLSLParser.Translation_unitContext root) { private static boolean isDefaultBlockStruct(String structName) {
final DefaultBlockFinder finder = new DefaultBlockFinder(); return structName.startsWith("_RESERVED_IDENTIFIER_FIXUP_") && structName.endsWith("DefaultUniformBlock");
ParseTreeWalker.DEFAULT.walk(finder, root);
if (finder.structExternalDecl == null || finder.uniformExternalDecl == null) return null;
return new DefaultBlockInfo(finder.structExternalDecl, finder.uniformExternalDecl, finder.blockVarName, finder.members);
} }
private static final class DefaultBlockFinder extends GLSLParserBaseListener { private static final class BlockFinder extends GLSLParserBaseListener {
String structName; private record PendingStruct(@Nullable UniformBlock source, ParserRuleContext externalDecl, String body, List<MemberInfo> members) {}
ParserRuleContext structExternalDecl;
ParserRuleContext uniformExternalDecl; private final String glslEs;
String blockVarName; private final Map<String, UniformBlock> sourceBlocks;
final List<MemberInfo> members = new ArrayList<>(); private final Map<String, PendingStruct> pending = new HashMap<>();
final List<BlockInfo> blocks = new ArrayList<>();
BlockFinder(String glslEs, Map<String, UniformBlock> sourceBlocks) {
this.glslEs = glslEs;
this.sourceBlocks = sourceBlocks;
}
@Override @Override
public void enterStruct_specifier(GLSLParser.Struct_specifierContext ctx) { public void enterStruct_specifier(GLSLParser.Struct_specifierContext ctx) {
if (structExternalDecl != null) return;
final TerminalNode id = ctx.IDENTIFIER(); final TerminalNode id = ctx.IDENTIFIER();
if (id == null) return; if (id == null) return;
final String idText = id.getText(); final String idText = id.getText();
if (!idText.startsWith("_RESERVED_IDENTIFIER_FIXUP_") || !idText.endsWith("DefaultUniformBlock")) return; final UniformBlock source = sourceBlocks.get(idText);
if (source == null && !isDefaultBlockStruct(idText)) return;
structName = idText; final List<MemberInfo> members = source != null && source.instanceName() != null ? List.of() : collectMembers(ctx);
structExternalDecl = findExternalDeclaration(ctx); final String body = glslEs.substring(ctx.LEFT_BRACE().getSymbol().getStartIndex(), ctx.RIGHT_BRACE().getSymbol().getStopIndex() + 1);
pending.putIfAbsent(idText, new PendingStruct(source, findExternalDeclaration(ctx), body, members));
}
private static List<MemberInfo> collectMembers(GLSLParser.Struct_specifierContext ctx) {
final List<MemberInfo> members = new ArrayList<>();
for (GLSLParser.Struct_declarationContext sd : ctx.struct_declaration_list().struct_declaration()) { for (GLSLParser.Struct_declarationContext sd : ctx.struct_declaration_list().struct_declaration()) {
final StringBuilder typePrefix = new StringBuilder(); final StringBuilder typePrefix = new StringBuilder();
if (sd.type_qualifier() != null) typePrefix.append(textWithSpaces(sd.type_qualifier())).append(' '); if (sd.type_qualifier() != null) typePrefix.append(textWithSpaces(sd.type_qualifier())).append(' ');
@@ -146,11 +177,12 @@ public final class SpirvShaderTranslator {
members.add(new MemberInfo(typePrefix + arraySuffix, decl.IDENTIFIER().getText())); members.add(new MemberInfo(typePrefix + arraySuffix, decl.IDENTIFIER().getText()));
} }
} }
return members;
} }
@Override @Override
public void enterInit_declarator_list(GLSLParser.Init_declarator_listContext ctx) { public void enterInit_declarator_list(GLSLParser.Init_declarator_listContext ctx) {
if (structName == null || uniformExternalDecl != null) return; if (pending.isEmpty()) return;
final GLSLParser.Single_declarationContext single = ctx.single_declaration(); final GLSLParser.Single_declarationContext single = ctx.single_declaration();
if (single == null || single.typeless_declaration() == null) return; if (single == null || single.typeless_declaration() == null) return;
final GLSLParser.Fully_specified_typeContext fst = single.fully_specified_type(); final GLSLParser.Fully_specified_typeContext fst = single.fully_specified_type();
@@ -159,10 +191,14 @@ public final class SpirvShaderTranslator {
if (fst.type_specifier() == null || fst.type_specifier().type_specifier_nonarray() == null) return; if (fst.type_specifier() == null || fst.type_specifier().type_specifier_nonarray() == null) return;
final GLSLParser.Type_specifier_nonarrayContext ts = fst.type_specifier().type_specifier_nonarray(); final GLSLParser.Type_specifier_nonarrayContext ts = fst.type_specifier().type_specifier_nonarray();
if (ts.type_name() == null) return; if (ts.type_name() == null) return;
if (!structName.equals(ts.type_name().getText())) return; final String structName = ts.type_name().getText();
final PendingStruct struct = pending.remove(structName);
if (struct == null) return;
blockVarName = single.typeless_declaration().IDENTIFIER().getText(); final GLSLParser.Typeless_declarationContext declarator = single.typeless_declaration();
uniformExternalDecl = findExternalDeclaration(ctx); final String declaratorText = glslEs.substring(startIdx(declarator), stopIdx(declarator) + 1);
blocks.add(new BlockInfo(structName, struct.source, struct.externalDecl, struct.body, struct.members,
findExternalDeclaration(ctx), declarator.IDENTIFIER().getText(), declaratorText));
} }
private static String textWithSpaces(ParseTree tree) { private static String textWithSpaces(ParseTree tree) {
@@ -192,17 +228,18 @@ public final class SpirvShaderTranslator {
} }
private static final class UseSiteRewriter extends GLSLParserBaseListener { private static final class UseSiteRewriter extends GLSLParserBaseListener {
private final @Nullable DefaultBlockInfo block; private final @Nullable BlockInfo block;
private final Set<String> boolNames; private final Set<String> boolNames;
private final Map<String, Set<String>> bareMembersByVar;
private final List<Edit> edits; private final List<Edit> edits;
private final Set<String> memberNames; private final Set<String> memberNames;
UseSiteRewriter(@Nullable DefaultBlockInfo block, Set<String> boolNames, List<Edit> edits) { UseSiteRewriter(@Nullable BlockInfo block, Set<String> memberNames, Set<String> boolNames, Map<String, Set<String>> bareMembersByVar, List<Edit> edits) {
this.block = block; this.block = block;
this.memberNames = memberNames;
this.boolNames = boolNames; this.boolNames = boolNames;
this.bareMembersByVar = bareMembersByVar;
this.edits = edits; this.edits = edits;
this.memberNames = new HashSet<>();
if (block != null) for (MemberInfo m : block.members) memberNames.add(m.name);
} }
@Override @Override
@@ -240,11 +277,11 @@ public final class SpirvShaderTranslator {
} }
} }
if (block != null && ctx.getChildCount() == 3 && ctx.DOT() != null && ctx.field_selection() != null) { if (ctx.getChildCount() == 3 && ctx.DOT() != null && ctx.field_selection() != null) {
final ParseTree inner = ctx.getChild(0); final Set<String> bareMembers = bareMembersByVar.get(ctx.getChild(0).getText());
if (inner.getText().equals(block.blockVarName)) { if (bareMembers != null) {
final String member = ctx.field_selection().getText(); final String member = ctx.field_selection().getText();
if (memberNames.contains(member) && !coveredByAncestorBoolCompare(ctx)) { if (bareMembers.contains(member) && !coveredByAncestorBoolCompare(ctx)) {
edits.add(new Edit(startIdx(ctx), stopIdx(ctx), member)); edits.add(new Edit(startIdx(ctx), stopIdx(ctx), member));
} }
} }
@@ -254,8 +291,8 @@ public final class SpirvShaderTranslator {
private @Nullable String resolveBoolOperand(ParseTree operand) { private @Nullable String resolveBoolOperand(ParseTree operand) {
final String text = operand.getText(); final String text = operand.getText();
if (boolNames.contains(text)) return text; if (boolNames.contains(text)) return text;
if (block != null && text.startsWith(block.blockVarName + ".")) { if (block != null && text.startsWith(block.varName + ".")) {
final String member = text.substring(block.blockVarName.length() + 1); final String member = text.substring(block.varName.length() + 1);
if (boolNames.contains(member) && memberNames.contains(member)) return member; if (boolNames.contains(member) && memberNames.contains(member)) return member;
} }
return null; return null;
@@ -0,0 +1,92 @@
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.GL32;
import org.lwjgl.opengl.GL40;
/** Emulates GL proxy textures for backends that lack them. */
public final class ProxyTexture {
private final int maxTextureSize;
private final int max3DTextureSize;
private final int maxCubeMapTextureSize;
private final int maxArrayTextureLayers;
private int target;
private int width;
private int height;
private int depth;
private int internalFormat;
public ProxyTexture(int maxTextureSize, int max3DTextureSize, int maxCubeMapTextureSize, int maxArrayTextureLayers) {
this.maxTextureSize = maxTextureSize;
this.max3DTextureSize = max3DTextureSize;
this.maxCubeMapTextureSize = maxCubeMapTextureSize;
this.maxArrayTextureLayers = maxArrayTextureLayers;
}
public void record(int target, int level, int internalFormat, int width, int height, int depth, boolean formatSupported) {
final boolean layersInHeight = layersInHeight(target);
final boolean layersInDepth = layersInDepth(target);
final int dimLimit = dimensionLimit(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 ? maxArrayTextureLayers : dimLimit;
final int dLimit = layersInDepth ? maxArrayTextureLayers : dimLimit;
final boolean fits = w >= 1 && w <= dimLimit && h >= 1 && h <= hLimit && d >= 1 && d <= dLimit && formatSupported;
if (fits) {
accept(target, w, h, d, internalFormat);
} else {
reject(target);
}
}
public void reject(int target) {
accept(target, 0, 0, 0, 0);
}
private 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 int levelParameter(int target, int level, int pname) {
if (this.target != target) return 0;
return switch (pname) {
case GL11.GL_TEXTURE_WIDTH -> TextureInfo.mipLevelSize(width, level);
case GL11.GL_TEXTURE_HEIGHT -> layersInHeight(target) ? height : TextureInfo.mipLevelSize(height, level);
case GL12.GL_TEXTURE_DEPTH -> layersInDepth(target) ? depth : TextureInfo.mipLevelSize(depth, level);
case GL11.GL_TEXTURE_INTERNAL_FORMAT -> internalFormat;
default -> 0;
};
}
public int dimensionLimit(int target) {
return switch (target) {
case GL12.GL_PROXY_TEXTURE_3D -> max3DTextureSize;
case GL13.GL_PROXY_TEXTURE_CUBE_MAP, GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY -> maxCubeMapTextureSize;
default -> maxTextureSize;
};
}
public 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 static boolean layersInHeight(int target) {
return target == GL30.GL_PROXY_TEXTURE_1D_ARRAY;
}
private static boolean layersInDepth(int target) {
return target == GL30.GL_PROXY_TEXTURE_2D_ARRAY || target == GL32.GL_PROXY_TEXTURE_2D_MULTISAMPLE_ARRAY || target == GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY;
}
}
@@ -15,6 +15,12 @@ public class TextureInfo {
private static final int UNSET_INT = Integer.MIN_VALUE; private static final int UNSET_INT = Integer.MIN_VALUE;
private static final float UNSET_FLOAT = Float.NEGATIVE_INFINITY; private static final float UNSET_FLOAT = Float.NEGATIVE_INFINITY;
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);
}
@Getter protected final int id; @Getter protected final int id;
@Getter @Setter protected int target = GL11.GL_TEXTURE_2D; @Getter @Setter protected int target = GL11.GL_TEXTURE_2D;
protected int internalFormat = -1; protected int internalFormat = -1;
@@ -277,6 +277,39 @@ class SpirvShaderTranslatorTest {
assertFalse(es.contains("!= 0u"), "no bool use-site should still be comparing against 0u"); assertFalse(es.contains("!= 0u"), "no bool use-site should still be comparing against 0u");
} }
@Test
void uniformBlock_staysABlockUnderGLES() {
final String src = """
#version 330 core
layout(std140) uniform Blk {
mat4 u_M;
vec4 u_C;
};
layout(std140) uniform Named {
vec4 u_N;
} named;
uniform float u_Loose;
struct L { vec4 c; };
uniform L u_Light;
in vec4 color;
out vec4 fragColor;
void main() {
fragColor = u_M * color * u_C * u_Loose + u_Light.c + named.u_N;
}
""";
final String es = SpirvShaderTranslator.glslToGlslEs(src, GL20.GL_FRAGMENT_SHADER, "uniform_block.frag");
dumpOnFailure("uniform_block.frag", src, es);
assertNotNull(es);
assertTrue(Pattern.compile("layout\\s*\\(\\s*std140\\s*\\)\\s*uniform\\s+Blk\\s*\\{").matcher(es).find(), "a source uniform block must reach GLSL ES as a block, so glUniformBlockBinding finds it by name\n\n" + es);
assertFalse(Pattern.compile("uniform\\s+Blk\\s+_").matcher(es).find(), "the block must not be left as a plain struct uniform\n\n" + es);
assertFalse(Pattern.compile("_[0-9]+\\.u_M").matcher(es).find(), "an unnamed source block's members must be accessed bare\n\n" + es);
assertTrue(Pattern.compile("layout\\s*\\(\\s*std140\\s*\\)\\s*uniform\\s+Named\\s*\\{[^}]*\\}\\s*named\\s*;").matcher(es).find(), "an instance-named block keeps its instance name\n\n" + es);
assertTrue(es.contains("named.u_N"), "members of an instance-named block stay qualified\n\n" + es);
assertContainsDecl(es, "float", "u_Loose");
assertTrue(Pattern.compile("uniform\\s+L\\s+u_Light\\s*;").matcher(es).find(), "a struct uniform is not a block and must stay a plain uniform\n\n" + es);
}
@Test @Test
void readonlyImage3D_staysAnImageUnderGLES() { void readonlyImage3D_staysAnImageUnderGLES() {
final String src = """ final String src = """
+76
View File
@@ -1,3 +1,5 @@
import java.time.Duration
plugins { plugins {
`java-library` `java-library`
`maven-publish` `maven-publish`
@@ -75,6 +77,80 @@ dependencies {
compileOnly(libs.annotations) compileOnly(libs.annotations)
} }
val glesTag = "gles"
val glsmRedirectAgent: Configuration by configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
}
val glsmLwjgl2Stubs: Configuration by configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
}
dependencies {
glsmRedirectAgent(project(":glsm", "redirectAgent"))
glsmLwjgl2Stubs(project(":glsm", "lwjgl2Stubs"))
testImplementation(platform(libs.junit.bom))
testImplementation(libs.junit.jupiter)
testImplementation(testFixtures(project(":glsm")))
testImplementation(project(":glsm")) {
capabilities { requireCapability("${project.group}:glsm-stubs") }
}
listOf(libs.lwjgl3.sdl, libs.lwjgl3.shaderc, libs.lwjgl3.spvc).forEach {
testImplementation(it)
testRuntimeOnly(it) { artifact { classifier = lwjglNatives } }
}
testRuntimeOnly(libs.junit.platform.launcher)
testRuntimeOnly(libs.log4j.core)
testRuntimeOnly(libs.fastutil)
testRuntimeOnly(libs.joml)
testRuntimeOnly(libs.commons.lang3)
testRuntimeOnly(libs.guava)
testRuntimeOnly(libs.celeritas.common) { isTransitive = false }
testRuntimeOnly(libs.asm.tree.test)
}
tasks.test {
useJUnitPlatform {
excludeTags(glesTag)
}
}
val unpackLwjgl3Natives = registerLwjgl3NativesUnpack()
val glesTest by tasks.registering(Test::class) {
description = "Boots GLSM on the LWJGL3 GL backend in a real OpenGL ES 3.2 context."
group = "verification"
testClassesDirs = sourceSets["test"].output.classesDirs
classpath = glsmLwjgl2Stubs.plus(
sourceSets["test"].runtimeClasspath.filter { file ->
!file.name.startsWith("lwjgl-2.") && !file.name.startsWith("lwjgl_util-") && !file.name.startsWith("lwjgl-platform-")
}
)
useJUnitPlatform {
includeTags(glesTag)
}
systemProperty("glsm.stubs.capabilities", "lwjgl3")
javaAgent(glsmRedirectAgent)
loadLwjgl3NativesFrom(unpackLwjgl3Natives)
outputs.cacheIf { false }
timeout = Duration.ofMinutes(5)
systemProperty("junit.jupiter.execution.timeout.default", "60s")
systemProperty("junit.jupiter.execution.timeout.threaddump.enabled", "true")
testLogging {
events("passed", "skipped", "failed")
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
}
mustRunAfter(tasks.test)
}
if (!providers.environmentVariable("CI").isPresent) {
tasks.check { dependsOn(glesTest) }
}
tasks.named<JavaCompile>("compileJava") { tasks.named<JavaCompile>("compileJava") {
val classesDir = destinationDirectory val classesDir = destinationDirectory
doLast { injectLwjgl3Aware(classesDir.get().asFile) } doLast { injectLwjgl3Aware(classesDir.get().asFile) }
@@ -10,6 +10,9 @@ import com.gtnewhorizons.angelica.glsm.GLScreenCapture;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend; import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import com.gtnewhorizons.angelica.glsm.backend.ScreenCapture; import com.gtnewhorizons.angelica.glsm.backend.ScreenCapture;
import com.gtnewhorizons.angelica.glsm.backend.VSyncMode; import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
import com.gtnewhorizons.angelica.glsm.texture.ProxyTexture;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import org.jetbrains.annotations.Nullable;
import me.eigenraven.lwjgl3ify.api.Lwjgl3Aware; import me.eigenraven.lwjgl3ify.api.Lwjgl3Aware;
import me.eigenraven.lwjgl3ify.client.MainThreadExec; import me.eigenraven.lwjgl3ify.client.MainThreadExec;
import org.lwjgl.PointerBuffer; import org.lwjgl.PointerBuffer;
@@ -48,6 +51,7 @@ import org.lwjgl.sdl.SDLError;
import org.lwjgl.sdl.SDLEvents; import org.lwjgl.sdl.SDLEvents;
import org.lwjgl.sdl.SDLTimer; import org.lwjgl.sdl.SDLTimer;
import org.lwjgl.sdl.SDLVideo; import org.lwjgl.sdl.SDLVideo;
import org.lwjgl.system.FunctionProvider;
import org.lwjgl.system.JNI; import org.lwjgl.system.JNI;
import org.lwjgl.system.MemoryStack; import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
@@ -97,14 +101,45 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
|| normalizedVendor.startsWith("advanced micro devices"); || normalizedVendor.startsWith("advanced micro devices");
} }
} }
final FunctionProvider functions = GL.getFunctionProvider();
if (RenderSystem.isGLES()) { if (RenderSystem.isGLES()) {
pfnClearDepthf = GL.getFunctionProvider().getFunctionAddress("glClearDepthf"); pfnClearDepthf = functions.getFunctionAddress("glClearDepthf");
pfnDepthRangef = GL.getFunctionProvider().getFunctionAddress("glDepthRangef"); pfnDepthRangef = functions.getFunctionAddress("glDepthRangef");
proxyTexture = new ProxyTexture(GL11C.glGetInteger(GL11C.GL_MAX_TEXTURE_SIZE), GL11C.glGetInteger(GL12C.GL_MAX_3D_TEXTURE_SIZE),
GL11C.glGetInteger(GL13C.GL_MAX_CUBE_MAP_TEXTURE_SIZE), GL11C.glGetInteger(GL30C.GL_MAX_ARRAY_TEXTURE_LAYERS));
} }
pfnGenSamplers = requireFunction(functions, "glGenSamplers");
pfnDeleteSamplers = requireFunction(functions, "glDeleteSamplers");
pfnBindSampler = requireFunction(functions, "glBindSampler");
pfnSamplerParameteri = requireFunction(functions, "glSamplerParameteri");
pfnSamplerParameterf = requireFunction(functions, "glSamplerParameterf");
pfnIsSampler = requireFunction(functions, "glIsSampler");
pfnVertexAttribDivisor = requireFunction(functions, "glVertexAttribDivisor");
}
private static long requireFunction(FunctionProvider functions, String name) {
final long address = functions.getFunctionAddress(name);
if (address == 0L) throw new IllegalStateException(name + " is not exported by the GL driver");
return address;
} }
private long pfnClearDepthf; private long pfnClearDepthf;
private long pfnDepthRangef; private long pfnDepthRangef;
private @Nullable ProxyTexture proxyTexture;
// GL33C slots stay unloaded on an ES 3.2 context: LWJGL builds desktop caps only up to the reported 3.2.
private long pfnGenSamplers;
private long pfnDeleteSamplers;
private long pfnBindSampler;
private long pfnSamplerParameteri;
private long pfnSamplerParameterf;
private long pfnIsSampler;
private long pfnVertexAttribDivisor;
private boolean recordProxy(int target, int level, int internalFormat, int width, int height, int depth) {
if (proxyTexture == null || !TextureInfoCache.isProxyTarget(target)) return false;
proxyTexture.record(target, level, internalFormat, width, height, depth, true);
return true;
}
@Override @Override
public void shutdown() { public void shutdown() {
@@ -565,26 +600,31 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
@Override @Override
public void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, ByteBuffer pixels) { public void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
if (recordProxy(target, level, internalFormat, width, height, 1)) return;
GL11C.glTexImage2D(target, level, internalFormat, width, height, border, format, type, pixels); GL11C.glTexImage2D(target, level, internalFormat, width, height, border, format, type, pixels);
} }
@Override @Override
public void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, DoubleBuffer pixels) { public void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, DoubleBuffer pixels) {
if (recordProxy(target, level, internalFormat, width, height, 1)) return;
GL11C.glTexImage2D(target, level, internalFormat, width, height, border, format, type, pixels); GL11C.glTexImage2D(target, level, internalFormat, width, height, border, format, type, pixels);
} }
@Override @Override
public void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, FloatBuffer pixels) { public void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, FloatBuffer pixels) {
if (recordProxy(target, level, internalFormat, width, height, 1)) return;
GL11C.glTexImage2D(target, level, internalFormat, width, height, border, format, type, pixels); GL11C.glTexImage2D(target, level, internalFormat, width, height, border, format, type, pixels);
} }
@Override @Override
public void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, IntBuffer pixels) { public void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, IntBuffer pixels) {
if (recordProxy(target, level, internalFormat, width, height, 1)) return;
GL11C.glTexImage2D(target, level, internalFormat, width, height, border, format, type, pixels); GL11C.glTexImage2D(target, level, internalFormat, width, height, border, format, type, pixels);
} }
@Override @Override
public void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, long pixelBufferOffset) { public void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, long pixelBufferOffset) {
if (recordProxy(target, level, internalFormat, width, height, 1)) return;
GL11C.glTexImage2D(target, level, internalFormat, width, height, border, format, type, pixelBufferOffset); GL11C.glTexImage2D(target, level, internalFormat, width, height, border, format, type, pixelBufferOffset);
} }
@@ -605,16 +645,19 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
@Override @Override
public void texImage1D(int target, int level, int internalFormat, int width, int border, int format, int type, ByteBuffer pixels) { public void texImage1D(int target, int level, int internalFormat, int width, int border, int format, int type, ByteBuffer pixels) {
if (recordProxy(target, level, internalFormat, width, 1, 1)) return;
GL11C.glTexImage1D(target, level, internalFormat, width, border, format, type, pixels); GL11C.glTexImage1D(target, level, internalFormat, width, border, format, type, pixels);
} }
@Override @Override
public void texImage3D(int target, int level, int internalFormat, int width, int height, int depth, int border, int format, int type, ByteBuffer pixels) { public void texImage3D(int target, int level, int internalFormat, int width, int height, int depth, int border, int format, int type, ByteBuffer pixels) {
if (recordProxy(target, level, internalFormat, width, height, depth)) return;
GL12C.glTexImage3D(target, level, internalFormat, width, height, depth, border, format, type, pixels); GL12C.glTexImage3D(target, level, internalFormat, width, height, depth, border, format, type, pixels);
} }
@Override @Override
public void texImage3D(int target, int level, int internalFormat, int width, int height, int depth, int border, int format, int type, IntBuffer pixels) { public void texImage3D(int target, int level, int internalFormat, int width, int height, int depth, int border, int format, int type, IntBuffer pixels) {
if (recordProxy(target, level, internalFormat, width, height, depth)) return;
GL12C.glTexImage3D(target, level, internalFormat, width, height, depth, border, format, type, pixels); GL12C.glTexImage3D(target, level, internalFormat, width, height, depth, border, format, type, pixels);
} }
@@ -686,6 +729,7 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
@Override @Override
public int getTexLevelParameteri(int target, int level, int pname) { public int getTexLevelParameteri(int target, int level, int pname) {
if (proxyTexture != null && TextureInfoCache.isProxyTarget(target)) return proxyTexture.levelParameter(target, level, pname);
return GL11C.glGetTexLevelParameteri(target, level, pname); return GL11C.glGetTexLevelParameteri(target, level, pname);
} }
@@ -706,27 +750,33 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
@Override @Override
public int genSamplers() { public int genSamplers() {
return GL33C.glGenSamplers(); try (MemoryStack stack = MemoryStack.stackPush()) {
final IntBuffer ids = stack.mallocInt(1);
JNI.callPV(1, MemoryUtil.memAddress(ids), pfnGenSamplers);
return ids.get(0);
}
} }
@Override @Override
public void deleteSamplers(int sampler) { public void deleteSamplers(int sampler) {
GL33C.glDeleteSamplers(sampler); try (MemoryStack stack = MemoryStack.stackPush()) {
JNI.callPV(1, MemoryUtil.memAddress(stack.ints(sampler)), pfnDeleteSamplers);
}
} }
@Override @Override
public void bindSampler(int unit, int sampler) { public void bindSampler(int unit, int sampler) {
GL33C.glBindSampler(unit, sampler); JNI.callV(unit, sampler, pfnBindSampler);
} }
@Override @Override
public void samplerParameteri(int sampler, int pname, int param) { public void samplerParameteri(int sampler, int pname, int param) {
GL33C.glSamplerParameteri(sampler, pname, param); JNI.callV(sampler, pname, param, pfnSamplerParameteri);
} }
@Override @Override
public void samplerParameterf(int sampler, int pname, float param) { public void samplerParameterf(int sampler, int pname, float param) {
GL33C.glSamplerParameterf(sampler, pname, param); JNI.callV(sampler, pname, param, pfnSamplerParameterf);
} }
@Override @Override
@@ -1371,7 +1421,7 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
@Override public boolean isFramebuffer(int framebuffer) { return GL30C.glIsFramebuffer(framebuffer); } @Override public boolean isFramebuffer(int framebuffer) { return GL30C.glIsFramebuffer(framebuffer); }
@Override public boolean isRenderbuffer(int renderbuffer) { return GL30C.glIsRenderbuffer(renderbuffer); } @Override public boolean isRenderbuffer(int renderbuffer) { return GL30C.glIsRenderbuffer(renderbuffer); }
@Override public boolean isSampler(int sampler) { return GL33C.glIsSampler(sampler); } @Override public boolean isSampler(int sampler) { return JNI.callZ(sampler, pfnIsSampler); }
@Override public boolean isQuery(int query) { return GL15C.glIsQuery(query); } @Override public boolean isQuery(int query) { return GL15C.glIsQuery(query); }
@Override @Override
@@ -1441,7 +1491,7 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
@Override @Override
public void vertexAttribDivisor(int index, int divisor) { public void vertexAttribDivisor(int index, int divisor) {
GL33C.glVertexAttribDivisor(index, divisor); JNI.callV(index, divisor, pfnVertexAttribDivisor);
} }
@Override @Override
@@ -0,0 +1,201 @@
package com.gtnewhorizons.angelica.lwjgl3;
import com.gtnewhorizons.angelica.glsm.GLSMCurrentAttribs;
import com.gtnewhorizons.angelica.glsm.GLSMBlend;
import com.gtnewhorizons.angelica.glsm.GLSMDraws;
import com.gtnewhorizons.angelica.glsm.GLSMEnable;
import com.gtnewhorizons.angelica.glsm.GLSMFramebuffer;
import com.gtnewhorizons.angelica.glsm.GLSMRasterizer;
import com.gtnewhorizons.angelica.glsm.GLSMTextures;
import com.gtnewhorizons.angelica.glsm.GLSMVertexInput;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.OS;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL11C;
import org.lwjgl.opengl.GL12C;
import org.lwjgl.opengl.GL20C;
import org.lwjgl.opengl.GL30C;
import org.lwjgl.opengl.GL33C;
import org.lwjgl.sdl.SDLError;
import org.lwjgl.sdl.SDLInit;
import org.lwjgl.sdl.SDLVideo;
import org.lwjgl.system.Configuration;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Tag("gles")
@DisabledOnOs(OS.MAC)
class GlesSmokeTest {
private static final int SIZE = 64;
private static int fbo;
@BeforeAll
static void boot() {
Configuration.OPENGL_EXPLICIT_INIT.set(true);
assertTrue(SDLInit.SDL_Init(SDLInit.SDL_INIT_VIDEO), () -> "SDL_Init failed: " + SDLError.SDL_GetError());
SDLVideo.SDL_GL_SetAttribute(SDLVideo.SDL_GL_CONTEXT_PROFILE_MASK, SDLVideo.SDL_GL_CONTEXT_PROFILE_ES);
SDLVideo.SDL_GL_SetAttribute(SDLVideo.SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDLVideo.SDL_GL_SetAttribute(SDLVideo.SDL_GL_CONTEXT_MINOR_VERSION, 2);
SDLVideo.SDL_GL_SetAttribute(SDLVideo.SDL_GL_DEPTH_SIZE, 24);
SDLVideo.SDL_GL_SetAttribute(SDLVideo.SDL_GL_STENCIL_SIZE, 8);
final long window = SDLVideo.SDL_CreateWindow("glesSmoke", SIZE, SIZE, SDLVideo.SDL_WINDOW_OPENGL | SDLVideo.SDL_WINDOW_HIDDEN);
assertNotEquals(0L, window, () -> "SDL_CreateWindow failed: " + SDLError.SDL_GetError());
final long context = SDLVideo.SDL_GL_CreateContext(window);
assertNotEquals(0L, context, () -> "SDL_GL_CreateContext failed: " + SDLError.SDL_GetError());
assertTrue(SDLVideo.SDL_GL_MakeCurrent(window, context), () -> "SDL_GL_MakeCurrent failed: " + SDLError.SDL_GetError());
GL.create(SDLVideo::SDL_GL_GetProcAddress);
GL.createCapabilities();
// LWJGL's GL_CONTEXT_PROFILE_MASK probe in createCapabilities leaves INVALID_ENUM on an ES context.
while (GL11C.glGetError() != GL11C.GL_NO_ERROR) {}
final String version = GL11C.glGetString(GL11C.GL_VERSION);
assertTrue(version != null && version.startsWith("OpenGL ES 3."), () -> "not an ES 3.x context: " + version);
assertTrue(BackendManager.RENDER_BACKEND instanceof Lwjgl3GLRenderBackend, () -> "selected backend is " + BackendManager.RENDER_BACKEND.getName());
GLStateManager.initialize(GLSMInitConfig.builder().displaySize(SIZE, SIZE).build());
GLStateManager.setRunningSplash(false);
GLStateManager.markSplashComplete("glesSmoke");
assertTrue(RenderSystem.isGLES());
assertTrue(RenderSystem.supportsSamplerObjects());
fbo = createTarget();
assertEquals(GL11C.GL_NO_ERROR, GL11C.glGetError());
}
@Test
void samplerLifecycle() {
final int sampler = RenderSystem.genSampler();
assertNotEquals(0, sampler);
RenderSystem.samplerParameteri(sampler, GL11C.GL_TEXTURE_MIN_FILTER, GL11C.GL_LINEAR);
RenderSystem.samplerParameterf(sampler, GL12C.GL_TEXTURE_MAX_LOD, 4.0f);
RenderSystem.bindSamplerToUnit(0, sampler);
assertEquals(sampler, GL11C.glGetInteger(GL33C.GL_SAMPLER_BINDING));
assertTrue(BackendManager.RENDER_BACKEND.isSampler(sampler));
RenderSystem.destroySampler(sampler);
assertFalse(BackendManager.RENDER_BACKEND.isSampler(sampler));
assertEquals(GL11C.GL_NO_ERROR, GL11C.glGetError());
}
@Test
void vertexAttribDivisor() {
final int vao = GLSMVertexInput.glGenVertexArrays();
GLSMVertexInput.glBindVertexArray(vao);
GLSMVertexInput.glVertexAttribDivisor(5, 1);
assertEquals(1, GL20C.glGetVertexAttribi(5, GL33C.GL_VERTEX_ATTRIB_ARRAY_DIVISOR));
GLSMVertexInput.glBindVertexArray(0);
assertEquals(GL11C.GL_NO_ERROR, GL11C.glGetError());
}
@Test
void ffpQuadRenders() {
bindClearedTarget();
GLSMDraws.glBegin(GL11C.GL_QUADS);
GLSMCurrentAttribs.glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
GLSMDraws.glVertex3f(-1.0f, -1.0f, 0.0f);
GLSMDraws.glVertex3f(1.0f, -1.0f, 0.0f);
GLSMDraws.glVertex3f(1.0f, 1.0f, 0.0f);
GLSMDraws.glVertex3f(-1.0f, 1.0f, 0.0f);
GLSMDraws.glEnd();
assertEquals(0xFF00FF00, readCenterPixel());
assertEquals(GL11C.GL_NO_ERROR, GL11C.glGetError());
}
@Test
void ffpTexturedQuadRenders() {
final int texel = 0xFF2060A0;
final int texture = GLSMTextures.glGenTextures();
GLSMTextures.glBindTexture(GL11C.GL_TEXTURE_2D, texture);
GLSMTextures.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_MIN_FILTER, GL11C.GL_NEAREST);
GLSMTextures.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_MAG_FILTER, GL11C.GL_NEAREST);
final IntBuffer pixels = MemoryUtil.memAllocInt(16);
try {
for (int i = 0; i < 16; i++) pixels.put(i, texel);
GLSMTextures.glTexImage2D(GL11C.GL_TEXTURE_2D, 0, GL11C.GL_RGBA, 4, 4, 0, GL12C.GL_BGRA, GL12C.GL_UNSIGNED_INT_8_8_8_8_REV, pixels);
} finally {
MemoryUtil.memFree(pixels);
}
bindClearedTarget();
GLSMTextures.glBindTexture(GL11C.GL_TEXTURE_2D, texture);
GLSMEnable.glEnable(GL11C.GL_TEXTURE_2D);
GLSMDraws.glBegin(GL11C.GL_QUADS);
GLSMCurrentAttribs.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
GLSMCurrentAttribs.glTexCoord2f(0.0f, 0.0f);
GLSMDraws.glVertex3f(-1.0f, -1.0f, 0.0f);
GLSMCurrentAttribs.glTexCoord2f(1.0f, 0.0f);
GLSMDraws.glVertex3f(1.0f, -1.0f, 0.0f);
GLSMCurrentAttribs.glTexCoord2f(1.0f, 1.0f);
GLSMDraws.glVertex3f(1.0f, 1.0f, 0.0f);
GLSMCurrentAttribs.glTexCoord2f(0.0f, 1.0f);
GLSMDraws.glVertex3f(-1.0f, 1.0f, 0.0f);
GLSMDraws.glEnd();
GLSMEnable.glDisable(GL11C.GL_TEXTURE_2D);
assertEquals(texel, readCenterPixel());
assertEquals(GL11C.GL_NO_ERROR, GL11C.glGetError());
}
@Test
void proxyTextureProbeFindsTheMaxTextureSize() {
final int maxSize = Integer.highestOneBit(GL11C.glGetInteger(GL11C.GL_MAX_TEXTURE_SIZE));
int found = -1;
for (int size = maxSize * 2; size > 0 && found < 0; size >>= 1) {
GLSMTextures.glTexImage2D(GL11C.GL_PROXY_TEXTURE_2D, 0, GL11C.GL_RGBA, size, size, 0, GL11C.GL_RGBA, GL11C.GL_UNSIGNED_BYTE, (ByteBuffer) null);
if (GLSMTextures.glGetTexLevelParameteri(GL11C.GL_PROXY_TEXTURE_2D, 0, GL11C.GL_TEXTURE_WIDTH) != 0) found = size;
}
assertEquals(maxSize, found);
assertEquals(GL11C.GL_NO_ERROR, GL11C.glGetError());
}
private static void bindClearedTarget() {
GLSMFramebuffer.glBindFramebuffer(GL30C.GL_FRAMEBUFFER, fbo);
GLSMRasterizer.glViewport(0, 0, SIZE, SIZE);
GLSMBlend.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
GLSMFramebuffer.glClear(GL11C.GL_COLOR_BUFFER_BIT | GL11C.GL_DEPTH_BUFFER_BIT);
}
private static int readCenterPixel() {
final ByteBuffer pixel = MemoryUtil.memAlloc(4);
try {
GLSMFramebuffer.glReadPixels(SIZE / 2, SIZE / 2, 1, 1, GL11C.GL_RGBA, GL11C.GL_UNSIGNED_BYTE, pixel);
return (pixel.get(3) & 0xFF) << 24 | (pixel.get(0) & 0xFF) << 16 | (pixel.get(1) & 0xFF) << 8 | pixel.get(2) & 0xFF;
} finally {
MemoryUtil.memFree(pixel);
}
}
private static int createTarget() {
final int color = GLSMTextures.glGenTextures();
GLSMTextures.glBindTexture(GL11C.GL_TEXTURE_2D, color);
GLSMTextures.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_MIN_FILTER, GL11C.GL_NEAREST);
GLSMTextures.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_MAG_FILTER, GL11C.GL_NEAREST);
GLSMTextures.glTexImage2D(GL11C.GL_TEXTURE_2D, 0, GL11C.GL_RGBA8, SIZE, SIZE, 0, GL11C.GL_RGBA, GL11C.GL_UNSIGNED_BYTE, (ByteBuffer) null);
final int depth = GLSMFramebuffer.glGenRenderbuffers();
GLSMFramebuffer.glBindRenderbuffer(GL30C.GL_RENDERBUFFER, depth);
GLSMFramebuffer.glRenderbufferStorage(GL30C.GL_RENDERBUFFER, GL30C.GL_DEPTH24_STENCIL8, SIZE, SIZE);
final int target = GLSMFramebuffer.glGenFramebuffers();
GLSMFramebuffer.glBindFramebuffer(GL30C.GL_FRAMEBUFFER, target);
GLSMFramebuffer.glFramebufferTexture2D(GL30C.GL_FRAMEBUFFER, GL30C.GL_COLOR_ATTACHMENT0, GL11C.GL_TEXTURE_2D, color, 0);
GLSMFramebuffer.glFramebufferRenderbuffer(GL30C.GL_FRAMEBUFFER, GL30C.GL_DEPTH_STENCIL_ATTACHMENT, GL30C.GL_RENDERBUFFER, depth);
GLSMFramebuffer.glBindFramebuffer(GL30C.GL_FRAMEBUFFER, 0);
return target;
}
}
+7 -1
View File
@@ -106,7 +106,13 @@ val glsmSdlAgent = sourceSets.create("glsmSdlAgent") {
java.srcDir("src/glsmSdlAgent/java") java.srcDir("src/glsmSdlAgent/java")
} }
val glsmLwjgl2Stubs: Configuration by configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
}
dependencies { dependencies {
glsmLwjgl2Stubs(project(":glsm", "lwjgl2Stubs"))
"glsmSdlAgentCompileOnly"(project(":glsm")) "glsmSdlAgentCompileOnly"(project(":glsm"))
"glsmSdlAgentCompileOnly"(testFixtures(project(":glsm"))) "glsmSdlAgentCompileOnly"(testFixtures(project(":glsm")))
"glsmSdlAgentCompileOnly"(libs.asm.tree.test) "glsmSdlAgentCompileOnly"(libs.asm.tree.test)
@@ -148,7 +154,7 @@ 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." description = "Runs the headless GLSM-on-SDL FFP rig in its own JVM, with the SDL GPU gate engaged."
group = "verification" group = "verification"
testClassesDirs = sourceSets["test"].output.classesDirs testClassesDirs = sourceSets["test"].output.classesDirs
classpath = glsmSdlStubs.output.classesDirs.plus( classpath = glsmSdlStubs.output.classesDirs.plus(glsmLwjgl2Stubs).plus(
sourceSets["test"].runtimeClasspath.filter { file -> sourceSets["test"].runtimeClasspath.filter { file ->
!file.name.startsWith("lwjgl-2.") && !file.name.startsWith("lwjgl_util-") && !file.name.startsWith("lwjgl-platform-") !file.name.startsWith("lwjgl-2.") && !file.name.startsWith("lwjgl_util-") && !file.name.startsWith("lwjgl-platform-")
} }
@@ -1,12 +0,0 @@
package org.lwjgl.opengl;
public final class GLContext {
private static final ContextCapabilities CAPABILITIES = new ContextCapabilities();
private GLContext() {}
public static ContextCapabilities getCapabilities() {
return CAPABILITIES;
}
}
@@ -20,6 +20,7 @@ import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend; import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import com.gtnewhorizons.angelica.glsm.backend.ScreenCapture; import com.gtnewhorizons.angelica.glsm.backend.ScreenCapture;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig; import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache; import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import com.gtnewhorizons.angelica.glsm.texture.TextureStaging; import com.gtnewhorizons.angelica.glsm.texture.TextureStaging;
import com.gtnewhorizons.angelica.sdlgpu.compute.ComputeBinder; import com.gtnewhorizons.angelica.sdlgpu.compute.ComputeBinder;
@@ -1507,52 +1508,8 @@ public class SDLGPURenderBackend extends RenderBackend {
return TextureInfoCache.isProxyTarget(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) { private void recordProxyTexImage(ContextState st, int target, int level, int internalFormat, int width, int height, int depth) {
final boolean layersInHeight = proxyLayersInHeight(target); st.proxyTexture.record(target, level, internalFormat, width, height, depth, resourceManager.isMappableTextureFormat(internalFormat));
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) { private static int boundTextureFor(ContextState st, int target) {
@@ -1816,23 +1773,13 @@ public class SDLGPURenderBackend extends RenderBackend {
} }
@Override public int getTexLevelParameteri(int target, int level, int pname) { @Override public int getTexLevelParameteri(int target, int level, int pname) {
final ContextState cs = s(); final ContextState cs = s();
if (isProxyTarget(target)) { if (isProxyTarget(target)) return cs.proxyTexture.levelParameter(target, level, pname);
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 int glId = cs.boundTextures[cs.activeTextureUnit];
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(glId); final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(glId);
if (meta == null) return 0; if (meta == null) return 0;
return switch (pname) { return switch (pname) {
case GL11.GL_TEXTURE_WIDTH -> PixelOps.mipLevelSize(meta.width(), level); case GL11.GL_TEXTURE_WIDTH -> TextureInfo.mipLevelSize(meta.width(), level);
case GL11.GL_TEXTURE_HEIGHT -> PixelOps.mipLevelSize(meta.height(), level); case GL11.GL_TEXTURE_HEIGHT -> TextureInfo.mipLevelSize(meta.height(), level);
case GL11.GL_TEXTURE_INTERNAL_FORMAT -> { case GL11.GL_TEXTURE_INTERNAL_FORMAT -> {
final int stored = meta.glFormat(); final int stored = meta.glFormat();
yield TextureInfoCache.isGenericCompressedInternalFormat(stored) ? GL11.GL_RGBA8 : stored; yield TextureInfoCache.isGenericCompressedInternalFormat(stored) ? GL11.GL_RGBA8 : stored;
@@ -1987,8 +1934,8 @@ public class SDLGPURenderBackend extends RenderBackend {
if (meta != null) { if (meta != null) {
fbo.colorFormats[colorIdx] = meta.sdlFormat(); fbo.colorFormats[colorIdx] = meta.sdlFormat();
if (colorIdx == 0) { if (colorIdx == 0) {
fbo.width = PixelOps.mipLevelSize(meta.width(), level); fbo.width = TextureInfo.mipLevelSize(meta.width(), level);
fbo.height = PixelOps.mipLevelSize(meta.height(), level); fbo.height = TextureInfo.mipLevelSize(meta.height(), level);
} }
} }
fbo.colorAttachmentCount = Math.max(fbo.colorAttachmentCount, colorIdx + 1); fbo.colorAttachmentCount = Math.max(fbo.colorAttachmentCount, colorIdx + 1);
@@ -2216,8 +2163,8 @@ public class SDLGPURenderBackend extends RenderBackend {
out.level = fbo.colorLevels[fbo.readBufferIndex]; out.level = fbo.colorLevels[fbo.readBufferIndex];
out.sdlFormat = meta != null ? meta.sdlFormat() : SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM; out.sdlFormat = meta != null ? meta.sdlFormat() : SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
out.glBaseFormat = meta != null ? PixelPack.glBaseFormat(meta.glFormat()) : GL11.GL_RGBA; out.glBaseFormat = meta != null ? PixelPack.glBaseFormat(meta.glFormat()) : GL11.GL_RGBA;
out.width = meta != null ? PixelOps.mipLevelSize(meta.width(), out.level) : fbo.width; out.width = meta != null ? TextureInfo.mipLevelSize(meta.width(), out.level) : fbo.width;
out.height = meta != null ? PixelOps.mipLevelSize(meta.height(), out.level) : fbo.height; out.height = meta != null ? TextureInfo.mipLevelSize(meta.height(), out.level) : fbo.height;
return out; return out;
} }
@@ -2283,8 +2230,8 @@ public class SDLGPURenderBackend extends RenderBackend {
final int glId = boundTextureFor(cs, target); final int glId = boundTextureFor(cs, target);
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(glId); final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(glId);
if (meta == null || level < 0 || level >= meta.levels()) return; if (meta == null || level < 0 || level >= meta.levels()) return;
final int width = PixelOps.mipLevelSize(meta.width(), level); final int width = TextureInfo.mipLevelSize(meta.width(), level);
final int height = PixelOps.mipLevelSize(meta.height(), level); final int height = TextureInfo.mipLevelSize(meta.height(), level);
if (width <= 0 || height <= 0) return; if (width <= 0 || height <= 0) return;
checkReadbackCapacity(format, type, width, height, loadPackState(cs), capacity); checkReadbackCapacity(format, type, width, height, loadPackState(cs), capacity);
final int kind = readbackKind(format); final int kind = readbackKind(format);
@@ -4166,8 +4113,8 @@ public class SDLGPURenderBackend extends RenderBackend {
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(texture); final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(texture);
if (meta == null) return 0; if (meta == null) return 0;
return switch (pname) { return switch (pname) {
case GL11.GL_TEXTURE_WIDTH -> PixelOps.mipLevelSize(meta.width(), level); case GL11.GL_TEXTURE_WIDTH -> TextureInfo.mipLevelSize(meta.width(), level);
case GL11.GL_TEXTURE_HEIGHT -> PixelOps.mipLevelSize(meta.height(), level); case GL11.GL_TEXTURE_HEIGHT -> TextureInfo.mipLevelSize(meta.height(), level);
case GL11.GL_TEXTURE_INTERNAL_FORMAT -> { case GL11.GL_TEXTURE_INTERNAL_FORMAT -> {
final int stored = meta.glFormat(); final int stored = meta.glFormat();
yield TextureInfoCache.isGenericCompressedInternalFormat(stored) ? GL11.GL_RGBA8 : stored; yield TextureInfoCache.isGenericCompressedInternalFormat(stored) ? GL11.GL_RGBA8 : stored;
@@ -4246,10 +4193,10 @@ public class SDLGPURenderBackend extends RenderBackend {
} }
static final int MAX_UNIFORM_COMPONENTS_PER_STAGE = 4096 / Float.BYTES; static final int MAX_UNIFORM_COMPONENTS_PER_STAGE = 4096 / Float.BYTES;
static final int MAX_ARRAY_TEXTURE_LAYERS = 256; public static final int MAX_ARRAY_TEXTURE_LAYERS = 256;
static final int MAX_TEXTURE_SIZE = 16384; public static final int MAX_TEXTURE_SIZE = 16384;
static final int MAX_CUBE_MAP_TEXTURE_SIZE = 16384; public static final int MAX_CUBE_MAP_TEXTURE_SIZE = 16384;
static final int MAX_3D_TEXTURE_SIZE = 2048; public static final int MAX_3D_TEXTURE_SIZE = 2048;
/** D3D12 and Metal both cap anisotropy at 16, and every desktop Vulkan driver reports 16. */ /** D3D12 and Metal both cap anisotropy at 16, and every desktop Vulkan driver reports 16. */
static final float MAX_ANISOTROPY = 16.0f; static final float MAX_ANISOTROPY = 16.0f;
@@ -4412,9 +4359,9 @@ public class SDLGPURenderBackend extends RenderBackend {
if (texHandle == 0 || frameManager.getCommandBuffer() == 0) return; if (texHandle == 0 || frameManager.getCommandBuffer() == 0) return;
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(texture); final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(texture);
if (meta == null || level >= meta.levels()) return; if (meta == null || level >= meta.levels()) return;
final int mipW = PixelOps.mipLevelSize(meta.width(), level); final int mipW = TextureInfo.mipLevelSize(meta.width(), level);
final int mipH = PixelOps.mipLevelSize(meta.height(), level); final int mipH = TextureInfo.mipLevelSize(meta.height(), level);
final int mipD = PixelOps.mipLevelSize(meta.depth(), level); final int mipD = TextureInfo.mipLevelSize(meta.depth(), level);
final int bpp = PixelOps.sdlFormatTexelBytes(meta.sdlFormat()); final int bpp = PixelOps.sdlFormatTexelBytes(meta.sdlFormat());
if (mipW <= 0 || mipH <= 0 || bpp <= 0) return; if (mipW <= 0 || mipH <= 0 || bpp <= 0) return;
@@ -1,6 +1,7 @@
package com.gtnewhorizons.angelica.sdlgpu.frame; package com.gtnewhorizons.angelica.sdlgpu.frame;
import com.gtnewhorizons.angelica.glsm.GLContextState; import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.texture.ProxyTexture;
import com.gtnewhorizons.angelica.sdlgpu.SDLGPURenderBackend; import com.gtnewhorizons.angelica.sdlgpu.SDLGPURenderBackend;
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineCache; import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineCache;
import com.gtnewhorizons.angelica.sdlgpu.resource.ColorSource; import com.gtnewhorizons.angelica.sdlgpu.resource.ColorSource;
@@ -67,27 +68,8 @@ public final class ContextState {
public final PackState packState = new PackState(); public final PackState packState = new PackState();
public final ColorSource readColor = new ColorSource(); public final ColorSource readColor = new ColorSource();
public static final class ProxyTextureState { public final ProxyTexture proxyTexture = new ProxyTexture(SDLGPURenderBackend.MAX_TEXTURE_SIZE, SDLGPURenderBackend.MAX_3D_TEXTURE_SIZE,
public int target; SDLGPURenderBackend.MAX_CUBE_MAP_TEXTURE_SIZE, SDLGPURenderBackend.MAX_ARRAY_TEXTURE_LAYERS);
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 static final class VAOState {
public final boolean[] attribEnabled = new boolean[MAX_VERTEX_ATTRIBS]; public final boolean[] attribEnabled = new boolean[MAX_VERTEX_ATTRIBS];
@@ -1,10 +1,10 @@
package com.gtnewhorizons.angelica.sdlgpu.pipeline; package com.gtnewhorizons.angelica.sdlgpu.pipeline;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState; import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager; import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager.FrameState; import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager.FrameState;
import com.gtnewhorizons.angelica.sdlgpu.resource.FboState; import com.gtnewhorizons.angelica.sdlgpu.resource.FboState;
import com.gtnewhorizons.angelica.sdlgpu.resource.PixelOps;
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager; import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
import com.gtnewhorizons.angelica.sdlgpu.resource.TextureOps; import com.gtnewhorizons.angelica.sdlgpu.resource.TextureOps;
import com.gtnewhorizons.angelica.sdlgpu.shader.LogicOpFormats; import com.gtnewhorizons.angelica.sdlgpu.shader.LogicOpFormats;
@@ -121,13 +121,13 @@ public final class LogicOpEmulator {
if (fbo == null) return frameManager.getFbo0Width(); if (fbo == null) return frameManager.getFbo0Width();
final int db = drawBuffer(fbo, loc); final int db = drawBuffer(fbo, loc);
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(fbo.colorGlIds[db]); final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(fbo.colorGlIds[db]);
return meta != null ? PixelOps.mipLevelSize(meta.width(), fbo.colorLevels[db]) : fbo.width; return meta != null ? TextureInfo.mipLevelSize(meta.width(), fbo.colorLevels[db]) : fbo.width;
} }
private int targetHeight(FboState fbo, int loc) { private int targetHeight(FboState fbo, int loc) {
if (fbo == null) return frameManager.getFbo0Height(); if (fbo == null) return frameManager.getFbo0Height();
final int db = drawBuffer(fbo, loc); final int db = drawBuffer(fbo, loc);
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(fbo.colorGlIds[db]); final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(fbo.colorGlIds[db]);
return meta != null ? PixelOps.mipLevelSize(meta.height(), fbo.colorLevels[db]) : fbo.height; return meta != null ? TextureInfo.mipLevelSize(meta.height(), fbo.colorLevels[db]) : fbo.height;
} }
} }
@@ -14,12 +14,6 @@ import static org.lwjgl.sdl.SDLGPU.*;
public final class PixelOps { public final class PixelOps {
private 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) { public static int defaultMipLevels(int width, int height) {
final int max = Math.max(1, Math.max(width, height)); final int max = Math.max(1, Math.max(width, height));
return Math.max(1, 32 - Integer.numberOfLeadingZeros(max)); return Math.max(1, 32 - Integer.numberOfLeadingZeros(max));
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.sdlgpu.resource; package com.gtnewhorizons.angelica.sdlgpu.resource;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
import com.gtnewhorizons.angelica.sdlgpu.device.Device; import com.gtnewhorizons.angelica.sdlgpu.device.Device;
import com.gtnewhorizons.angelica.sdlgpu.device.Submits; import com.gtnewhorizons.angelica.sdlgpu.device.Submits;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState; import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
@@ -156,11 +157,11 @@ public final class TextureOps {
} }
public static int levelWidth(ResourceManager.TextureMeta meta, int level) { public static int levelWidth(ResourceManager.TextureMeta meta, int level) {
return meta != null ? PixelOps.mipLevelSize(meta.width(), level) : 0; return meta != null ? TextureInfo.mipLevelSize(meta.width(), level) : 0;
} }
public static int levelHeight(ResourceManager.TextureMeta meta, int level) { public static int levelHeight(ResourceManager.TextureMeta meta, int level) {
return meta != null ? PixelOps.mipLevelSize(meta.height(), level) : 0; return meta != null ? TextureInfo.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) { private void copyFromDefaultFramebuffer(ContextState st, int destGlId, long destTex, int level, int xoffset, int yoffset, int x, int y, int width, int height) {
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.sdlgpu; package com.gtnewhorizons.angelica.sdlgpu;
import com.gtnewhorizons.angelica.glsm.texture.ProxyTexture;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState; import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -47,11 +48,11 @@ class ProxyTextureLimitsTest {
assertEquals(2048, SDLGPURenderBackend.deviceLimit(GL12.GL_MAX_3D_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.deviceLimit(GL13.GL_MAX_CUBE_MAP_TEXTURE_SIZE));
assertEquals(16384, SDLGPURenderBackend.proxyDimensionLimit(GL11.GL_PROXY_TEXTURE_2D)); assertEquals(16384, SdlTestRig.contextState().proxyTexture.dimensionLimit(GL11.GL_PROXY_TEXTURE_2D));
assertEquals(16384, SDLGPURenderBackend.proxyDimensionLimit(GL11.GL_PROXY_TEXTURE_1D)); assertEquals(16384, SdlTestRig.contextState().proxyTexture.dimensionLimit(GL11.GL_PROXY_TEXTURE_1D));
assertEquals(2048, SDLGPURenderBackend.proxyDimensionLimit(GL12.GL_PROXY_TEXTURE_3D)); assertEquals(2048, SdlTestRig.contextState().proxyTexture.dimensionLimit(GL12.GL_PROXY_TEXTURE_3D));
assertEquals(16384, SDLGPURenderBackend.proxyDimensionLimit(GL13.GL_PROXY_TEXTURE_CUBE_MAP)); assertEquals(16384, SdlTestRig.contextState().proxyTexture.dimensionLimit(GL13.GL_PROXY_TEXTURE_CUBE_MAP));
assertEquals(16384, SDLGPURenderBackend.proxyDimensionLimit(GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY)); assertEquals(16384, SdlTestRig.contextState().proxyTexture.dimensionLimit(GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY));
} }
@Test @Test
@@ -99,13 +100,13 @@ class ProxyTextureLimitsTest {
@Test @Test
void levelZeroEquivalentSizeRejectsOverflowAndNonsense() { void levelZeroEquivalentSizeRejectsOverflowAndNonsense() {
assertEquals(4, SDLGPURenderBackend.levelZeroEquivalentSize(4, 0)); assertEquals(4, ProxyTexture.levelZeroEquivalentSize(4, 0));
assertEquals(32, SDLGPURenderBackend.levelZeroEquivalentSize(4, 3)); assertEquals(32, ProxyTexture.levelZeroEquivalentSize(4, 3));
assertEquals(0, SDLGPURenderBackend.levelZeroEquivalentSize(4, -1)); assertEquals(0, ProxyTexture.levelZeroEquivalentSize(4, -1));
assertEquals(0, SDLGPURenderBackend.levelZeroEquivalentSize(0, 2)); assertEquals(0, ProxyTexture.levelZeroEquivalentSize(0, 2));
assertEquals(0, SDLGPURenderBackend.levelZeroEquivalentSize(4, 31)); assertEquals(0, ProxyTexture.levelZeroEquivalentSize(4, 31));
assertEquals(0, SDLGPURenderBackend.levelZeroEquivalentSize(4, 30)); assertEquals(0, ProxyTexture.levelZeroEquivalentSize(4, 30));
assertEquals(1 << 30, SDLGPURenderBackend.levelZeroEquivalentSize(1, 30)); assertEquals(1 << 30, ProxyTexture.levelZeroEquivalentSize(1, 30));
} }
@Test @Test
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.sdlgpu; package com.gtnewhorizons.angelica.sdlgpu;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState; import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
@@ -81,7 +82,7 @@ class ProxyTextureTargetTest {
@ParameterizedTest(name = "{0}") @ParameterizedTest(name = "{0}")
@MethodSource("levelSizeCases") @MethodSource("levelSizeCases")
void levelSizeMatchesExpectation(String label, int base, int level, int expected) { void levelSizeMatchesExpectation(String label, int base, int level, int expected) {
assertEquals(expected, SDLGPURenderBackend.proxyLevelSize(base, level), label); assertEquals(expected, TextureInfo.mipLevelSize(base, level), label);
} }
@Test @Test