mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-08 12:35:01 +00:00
Fix GLES crashing at startup and rendering black (#2346)
This commit is contained in:
1 parent
a6d014a9eb
commit
3876218405
24 files changed
+668
-185
No files matched your search
@@ -26,7 +26,7 @@ jobs:
|
||||
- name: Install a software Vulkan driver
|
||||
run: |
|
||||
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/setup-java@v5
|
||||
with:
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
25
|
||||
- uses: gradle/actions/setup-gradle@v5
|
||||
- name: Run SDL-GPU tests
|
||||
run: xvfb-run -a --server-args="-screen 0 1366x768x24" ./gradlew --build-cache --stacktrace --continue :sdl-gpu:glsmSdlTest :sdl-gpu:sdlGpuDeviceTest
|
||||
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
|
||||
run: test ! -e "/tmp/lwjgl_$USER"
|
||||
- name: Attach SDL-GPU test results
|
||||
@@ -48,3 +48,6 @@ jobs:
|
||||
sdl-gpu/build/test-results/
|
||||
sdl-gpu/build/reports/tests/
|
||||
sdl-gpu/hs_err_pid*.log
|
||||
lwjgl3-backend/build/test-results/
|
||||
lwjgl3-backend/build/reports/tests/
|
||||
lwjgl3-backend/hs_err_pid*.log
|
||||
@@ -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 {
|
||||
maven {
|
||||
name = "Mojang"
|
||||
@@ -111,6 +127,7 @@ val spirvTestRuntime by configurations.creating {
|
||||
dependencies {
|
||||
// MC compile stubs
|
||||
compileOnly(sourceSets["stubs"].output)
|
||||
"lwjgl2StubsCompileOnly"(libs.lwjgl3.opengl)
|
||||
|
||||
// LWJGL
|
||||
compileOnly(libs.lwjgl2)
|
||||
|
||||
File renamed without changes.
+19
@@ -1,5 +1,7 @@
|
||||
package org.lwjgl.opengl;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
public class ContextCapabilities {
|
||||
|
||||
public boolean OpenGL30 = true;
|
||||
@@ -21,11 +23,28 @@ public class ContextCapabilities {
|
||||
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_stencil_texturing = 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_gpu_shader4 = true;
|
||||
public boolean GL_EXT_shader_image_load_store = true;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File renamed without changes.
@@ -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) {
|
||||
final FloatBuffer range = glCtx.queryScratch;
|
||||
range.clear();
|
||||
RENDER_BACKEND.getFloat(GL11.GL_POINT_SIZE_RANGE, range);
|
||||
final float smooth = range.get(1);
|
||||
float smooth = 0.0f;
|
||||
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);
|
||||
maxPointSize = Math.max(smooth, range.get(1));
|
||||
}
|
||||
|
||||
+17
-4
@@ -15,8 +15,10 @@ import org.taumc.glsl.grammar.GLSLParser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public final class GlslVulkanPreprocess {
|
||||
@@ -56,7 +58,9 @@ public final class GlslVulkanPreprocess {
|
||||
private GlslVulkanPreprocess() {}
|
||||
|
||||
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}. */
|
||||
public record Edit(int startIdx, int stopIdx, String replacement) {}
|
||||
@@ -109,7 +113,7 @@ public final class GlslVulkanPreprocess {
|
||||
final List<Edit> edits = new ArrayList<>();
|
||||
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) {
|
||||
CACHE.putAndMoveToFirst(key, out);
|
||||
while (CACHE.size() > CACHE_MAX) CACHE.removeLast();
|
||||
@@ -117,11 +121,12 @@ public final class GlslVulkanPreprocess {
|
||||
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) {
|
||||
final boolean isVertex = glShaderType == GL20.GL_VERTEX_SHADER;
|
||||
final Set<String> bools = new HashSet<>();
|
||||
Map<String, UniformBlock> uniformBlocks = Map.of();
|
||||
final Set<String> explicitInputs = new HashSet<>();
|
||||
final Set<String> readOnlyImages = new HashSet<>();
|
||||
boolean needsSamplerless = false;
|
||||
@@ -130,6 +135,14 @@ public final class GlslVulkanPreprocess {
|
||||
final List<Integer> unlocatedVsInputStarts = new ArrayList<>();
|
||||
|
||||
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();
|
||||
if (list == null) continue;
|
||||
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) {
|
||||
|
||||
+74
-37
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.glsm.shader;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.Edit;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.UniformBlock;
|
||||
import me.eigenraven.lwjgl3ify.api.Lwjgl3Aware;
|
||||
import org.antlr.v4.runtime.ParserRuleContext;
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
@@ -26,8 +27,10 @@ import org.taumc.glsl.grammar.GLSLParserBaseListener;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.startIdx;
|
||||
@@ -65,13 +68,13 @@ public final class SpirvShaderTranslator {
|
||||
try {
|
||||
final String emitted = emitGlslEs(cr.spirv(), debugName, glShaderType, pre.explicitVsInputs());
|
||||
if (emitted == null) return null;
|
||||
return postProcessEsOutput(emitted, pre.boolUniforms());
|
||||
return postProcessEsOutput(emitted, pre.boolUniforms(), pre.uniformBlocks());
|
||||
} finally {
|
||||
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;
|
||||
try {
|
||||
root = GlslTransformUtils.parseFullQuiet(glslEs);
|
||||
@@ -80,21 +83,42 @@ public final class SpirvShaderTranslator {
|
||||
return null;
|
||||
}
|
||||
|
||||
final DefaultBlockInfo block = findDefaultBlock(root);
|
||||
final List<Edit> edits = new ArrayList<>();
|
||||
final BlockFinder finder = new BlockFinder(glslEs, uniformBlocks);
|
||||
ParseTreeWalker.DEFAULT.walk(finder, root);
|
||||
|
||||
if (block != null) {
|
||||
edits.add(new Edit(startIdx(block.structExternalDecl), stopIdx(block.structExternalDecl), buildLooseUniforms(block.members, boolUniformNames)));
|
||||
final List<Edit> edits = new ArrayList<>();
|
||||
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), ""));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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) {}
|
||||
|
||||
@@ -111,31 +135,38 @@ public final class SpirvShaderTranslator {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static @Nullable DefaultBlockInfo findDefaultBlock(GLSLParser.Translation_unitContext root) {
|
||||
final DefaultBlockFinder finder = new DefaultBlockFinder();
|
||||
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 boolean isDefaultBlockStruct(String structName) {
|
||||
return structName.startsWith("_RESERVED_IDENTIFIER_FIXUP_") && structName.endsWith("DefaultUniformBlock");
|
||||
}
|
||||
|
||||
private static final class DefaultBlockFinder extends GLSLParserBaseListener {
|
||||
String structName;
|
||||
ParserRuleContext structExternalDecl;
|
||||
ParserRuleContext uniformExternalDecl;
|
||||
String blockVarName;
|
||||
final List<MemberInfo> members = new ArrayList<>();
|
||||
private static final class BlockFinder extends GLSLParserBaseListener {
|
||||
private record PendingStruct(@Nullable UniformBlock source, ParserRuleContext externalDecl, String body, List<MemberInfo> members) {}
|
||||
|
||||
private final String glslEs;
|
||||
private final Map<String, UniformBlock> sourceBlocks;
|
||||
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
|
||||
public void enterStruct_specifier(GLSLParser.Struct_specifierContext ctx) {
|
||||
if (structExternalDecl != null) return;
|
||||
final TerminalNode id = ctx.IDENTIFIER();
|
||||
if (id == null) return;
|
||||
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;
|
||||
structExternalDecl = findExternalDeclaration(ctx);
|
||||
final List<MemberInfo> members = source != null && source.instanceName() != null ? List.of() : collectMembers(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()) {
|
||||
final StringBuilder typePrefix = new StringBuilder();
|
||||
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()));
|
||||
}
|
||||
}
|
||||
return members;
|
||||
}
|
||||
|
||||
@Override
|
||||
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();
|
||||
if (single == null || single.typeless_declaration() == null) return;
|
||||
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;
|
||||
final GLSLParser.Type_specifier_nonarrayContext ts = fst.type_specifier().type_specifier_nonarray();
|
||||
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();
|
||||
uniformExternalDecl = findExternalDeclaration(ctx);
|
||||
final GLSLParser.Typeless_declarationContext declarator = single.typeless_declaration();
|
||||
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) {
|
||||
@@ -192,17 +228,18 @@ public final class SpirvShaderTranslator {
|
||||
}
|
||||
|
||||
private static final class UseSiteRewriter extends GLSLParserBaseListener {
|
||||
private final @Nullable DefaultBlockInfo block;
|
||||
private final @Nullable BlockInfo block;
|
||||
private final Set<String> boolNames;
|
||||
private final Map<String, Set<String>> bareMembersByVar;
|
||||
private final List<Edit> edits;
|
||||
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.memberNames = memberNames;
|
||||
this.boolNames = boolNames;
|
||||
this.bareMembersByVar = bareMembersByVar;
|
||||
this.edits = edits;
|
||||
this.memberNames = new HashSet<>();
|
||||
if (block != null) for (MemberInfo m : block.members) memberNames.add(m.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -240,11 +277,11 @@ public final class SpirvShaderTranslator {
|
||||
}
|
||||
}
|
||||
|
||||
if (block != null && ctx.getChildCount() == 3 && ctx.DOT() != null && ctx.field_selection() != null) {
|
||||
final ParseTree inner = ctx.getChild(0);
|
||||
if (inner.getText().equals(block.blockVarName)) {
|
||||
if (ctx.getChildCount() == 3 && ctx.DOT() != null && ctx.field_selection() != null) {
|
||||
final Set<String> bareMembers = bareMembersByVar.get(ctx.getChild(0).getText());
|
||||
if (bareMembers != null) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -254,8 +291,8 @@ public final class SpirvShaderTranslator {
|
||||
private @Nullable String resolveBoolOperand(ParseTree operand) {
|
||||
final String text = operand.getText();
|
||||
if (boolNames.contains(text)) return text;
|
||||
if (block != null && text.startsWith(block.blockVarName + ".")) {
|
||||
final String member = text.substring(block.blockVarName.length() + 1);
|
||||
if (block != null && text.startsWith(block.varName + ".")) {
|
||||
final String member = text.substring(block.varName.length() + 1);
|
||||
if (boolNames.contains(member) && memberNames.contains(member)) return member;
|
||||
}
|
||||
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 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 @Setter protected int target = GL11.GL_TEXTURE_2D;
|
||||
protected int internalFormat = -1;
|
||||
|
||||
+33
@@ -277,6 +277,39 @@ class SpirvShaderTranslatorTest {
|
||||
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
|
||||
void readonlyImage3D_staysAnImageUnderGLES() {
|
||||
final String src = """
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import java.time.Duration
|
||||
|
||||
plugins {
|
||||
`java-library`
|
||||
`maven-publish`
|
||||
@@ -75,6 +77,80 @@ dependencies {
|
||||
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") {
|
||||
val classesDir = destinationDirectory
|
||||
doLast { injectLwjgl3Aware(classesDir.get().asFile) }
|
||||
|
||||
+59
-9
@@ -10,6 +10,9 @@ import com.gtnewhorizons.angelica.glsm.GLScreenCapture;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.ScreenCapture;
|
||||
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.client.MainThreadExec;
|
||||
import org.lwjgl.PointerBuffer;
|
||||
@@ -48,6 +51,7 @@ import org.lwjgl.sdl.SDLError;
|
||||
import org.lwjgl.sdl.SDLEvents;
|
||||
import org.lwjgl.sdl.SDLTimer;
|
||||
import org.lwjgl.sdl.SDLVideo;
|
||||
import org.lwjgl.system.FunctionProvider;
|
||||
import org.lwjgl.system.JNI;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
@@ -97,14 +101,45 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
|
||||
|| normalizedVendor.startsWith("advanced micro devices");
|
||||
}
|
||||
}
|
||||
final FunctionProvider functions = GL.getFunctionProvider();
|
||||
if (RenderSystem.isGLES()) {
|
||||
pfnClearDepthf = GL.getFunctionProvider().getFunctionAddress("glClearDepthf");
|
||||
pfnDepthRangef = GL.getFunctionProvider().getFunctionAddress("glDepthRangef");
|
||||
pfnClearDepthf = functions.getFunctionAddress("glClearDepthf");
|
||||
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 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
|
||||
public void shutdown() {
|
||||
@@ -565,26 +600,31 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -605,16 +645,19 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -686,6 +729,7 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -706,27 +750,33 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
|
||||
|
||||
@Override
|
||||
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
|
||||
public void deleteSamplers(int sampler) {
|
||||
GL33C.glDeleteSamplers(sampler);
|
||||
try (MemoryStack stack = MemoryStack.stackPush()) {
|
||||
JNI.callPV(1, MemoryUtil.memAddress(stack.ints(sampler)), pfnDeleteSamplers);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindSampler(int unit, int sampler) {
|
||||
GL33C.glBindSampler(unit, sampler);
|
||||
JNI.callV(unit, sampler, pfnBindSampler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void samplerParameteri(int sampler, int pname, int param) {
|
||||
GL33C.glSamplerParameteri(sampler, pname, param);
|
||||
JNI.callV(sampler, pname, param, pfnSamplerParameteri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void samplerParameterf(int sampler, int pname, float param) {
|
||||
GL33C.glSamplerParameterf(sampler, pname, param);
|
||||
JNI.callV(sampler, pname, param, pfnSamplerParameterf);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1371,7 +1421,7 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
|
||||
|
||||
@Override public boolean isFramebuffer(int framebuffer) { return GL30C.glIsFramebuffer(framebuffer); }
|
||||
@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
|
||||
@@ -1441,7 +1491,7 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
|
||||
|
||||
@Override
|
||||
public void vertexAttribDivisor(int index, int divisor) {
|
||||
GL33C.glVertexAttribDivisor(index, divisor);
|
||||
JNI.callV(index, divisor, pfnVertexAttribDivisor);
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
@@ -106,7 +106,13 @@ val glsmSdlAgent = sourceSets.create("glsmSdlAgent") {
|
||||
java.srcDir("src/glsmSdlAgent/java")
|
||||
}
|
||||
|
||||
val glsmLwjgl2Stubs: Configuration by configurations.creating {
|
||||
isCanBeConsumed = false
|
||||
isCanBeResolved = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
glsmLwjgl2Stubs(project(":glsm", "lwjgl2Stubs"))
|
||||
"glsmSdlAgentCompileOnly"(project(":glsm"))
|
||||
"glsmSdlAgentCompileOnly"(testFixtures(project(":glsm")))
|
||||
"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."
|
||||
group = "verification"
|
||||
testClassesDirs = sourceSets["test"].output.classesDirs
|
||||
classpath = glsmSdlStubs.output.classesDirs.plus(
|
||||
classpath = glsmSdlStubs.output.classesDirs.plus(glsmLwjgl2Stubs).plus(
|
||||
sourceSets["test"].runtimeClasspath.filter { file ->
|
||||
!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.ScreenCapture;
|
||||
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.TextureStaging;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.compute.ComputeBinder;
|
||||
@@ -1507,52 +1508,8 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
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);
|
||||
}
|
||||
st.proxyTexture.record(target, level, internalFormat, width, height, depth, resourceManager.isMappableTextureFormat(internalFormat));
|
||||
}
|
||||
|
||||
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) {
|
||||
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;
|
||||
};
|
||||
}
|
||||
if (isProxyTarget(target)) return cs.proxyTexture.levelParameter(target, level, pname);
|
||||
final int glId = cs.boundTextures[cs.activeTextureUnit];
|
||||
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(glId);
|
||||
if (meta == null) return 0;
|
||||
return switch (pname) {
|
||||
case GL11.GL_TEXTURE_WIDTH -> PixelOps.mipLevelSize(meta.width(), level);
|
||||
case GL11.GL_TEXTURE_HEIGHT -> PixelOps.mipLevelSize(meta.height(), level);
|
||||
case GL11.GL_TEXTURE_WIDTH -> TextureInfo.mipLevelSize(meta.width(), level);
|
||||
case GL11.GL_TEXTURE_HEIGHT -> TextureInfo.mipLevelSize(meta.height(), level);
|
||||
case GL11.GL_TEXTURE_INTERNAL_FORMAT -> {
|
||||
final int stored = meta.glFormat();
|
||||
yield TextureInfoCache.isGenericCompressedInternalFormat(stored) ? GL11.GL_RGBA8 : stored;
|
||||
@@ -1987,8 +1934,8 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
if (meta != null) {
|
||||
fbo.colorFormats[colorIdx] = meta.sdlFormat();
|
||||
if (colorIdx == 0) {
|
||||
fbo.width = PixelOps.mipLevelSize(meta.width(), level);
|
||||
fbo.height = PixelOps.mipLevelSize(meta.height(), level);
|
||||
fbo.width = TextureInfo.mipLevelSize(meta.width(), level);
|
||||
fbo.height = TextureInfo.mipLevelSize(meta.height(), level);
|
||||
}
|
||||
}
|
||||
fbo.colorAttachmentCount = Math.max(fbo.colorAttachmentCount, colorIdx + 1);
|
||||
@@ -2216,8 +2163,8 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
out.level = fbo.colorLevels[fbo.readBufferIndex];
|
||||
out.sdlFormat = meta != null ? meta.sdlFormat() : SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
|
||||
out.glBaseFormat = meta != null ? PixelPack.glBaseFormat(meta.glFormat()) : GL11.GL_RGBA;
|
||||
out.width = meta != null ? PixelOps.mipLevelSize(meta.width(), out.level) : fbo.width;
|
||||
out.height = meta != null ? PixelOps.mipLevelSize(meta.height(), out.level) : fbo.height;
|
||||
out.width = meta != null ? TextureInfo.mipLevelSize(meta.width(), out.level) : fbo.width;
|
||||
out.height = meta != null ? TextureInfo.mipLevelSize(meta.height(), out.level) : fbo.height;
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -2283,8 +2230,8 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final int glId = boundTextureFor(cs, target);
|
||||
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(glId);
|
||||
if (meta == null || level < 0 || level >= meta.levels()) return;
|
||||
final int width = PixelOps.mipLevelSize(meta.width(), level);
|
||||
final int height = PixelOps.mipLevelSize(meta.height(), level);
|
||||
final int width = TextureInfo.mipLevelSize(meta.width(), level);
|
||||
final int height = TextureInfo.mipLevelSize(meta.height(), level);
|
||||
if (width <= 0 || height <= 0) return;
|
||||
checkReadbackCapacity(format, type, width, height, loadPackState(cs), capacity);
|
||||
final int kind = readbackKind(format);
|
||||
@@ -4166,8 +4113,8 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(texture);
|
||||
if (meta == null) return 0;
|
||||
return switch (pname) {
|
||||
case GL11.GL_TEXTURE_WIDTH -> PixelOps.mipLevelSize(meta.width(), level);
|
||||
case GL11.GL_TEXTURE_HEIGHT -> PixelOps.mipLevelSize(meta.height(), level);
|
||||
case GL11.GL_TEXTURE_WIDTH -> TextureInfo.mipLevelSize(meta.width(), level);
|
||||
case GL11.GL_TEXTURE_HEIGHT -> TextureInfo.mipLevelSize(meta.height(), level);
|
||||
case GL11.GL_TEXTURE_INTERNAL_FORMAT -> {
|
||||
final int stored = meta.glFormat();
|
||||
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_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;
|
||||
public static final int MAX_ARRAY_TEXTURE_LAYERS = 256;
|
||||
public static final int MAX_TEXTURE_SIZE = 16384;
|
||||
public static final int MAX_CUBE_MAP_TEXTURE_SIZE = 16384;
|
||||
public static final int MAX_3D_TEXTURE_SIZE = 2048;
|
||||
/** D3D12 and Metal both cap anisotropy at 16, and every desktop Vulkan driver reports 16. */
|
||||
static final float MAX_ANISOTROPY = 16.0f;
|
||||
|
||||
@@ -4412,9 +4359,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 = PixelOps.mipLevelSize(meta.width(), level);
|
||||
final int mipH = PixelOps.mipLevelSize(meta.height(), level);
|
||||
final int mipD = PixelOps.mipLevelSize(meta.depth(), level);
|
||||
final int mipW = TextureInfo.mipLevelSize(meta.width(), level);
|
||||
final int mipH = TextureInfo.mipLevelSize(meta.height(), level);
|
||||
final int mipD = TextureInfo.mipLevelSize(meta.depth(), level);
|
||||
final int bpp = PixelOps.sdlFormatTexelBytes(meta.sdlFormat());
|
||||
if (mipW <= 0 || mipH <= 0 || bpp <= 0) return;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.frame;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLContextState;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.ProxyTexture;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SDLGPURenderBackend;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineCache;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ColorSource;
|
||||
@@ -67,27 +68,8 @@ public final class ContextState {
|
||||
public final PackState packState = new PackState();
|
||||
public final ColorSource readColor = new ColorSource();
|
||||
|
||||
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 final ProxyTexture proxyTexture = new ProxyTexture(SDLGPURenderBackend.MAX_TEXTURE_SIZE, SDLGPURenderBackend.MAX_3D_TEXTURE_SIZE,
|
||||
SDLGPURenderBackend.MAX_CUBE_MAP_TEXTURE_SIZE, SDLGPURenderBackend.MAX_ARRAY_TEXTURE_LAYERS);
|
||||
|
||||
public static final class VAOState {
|
||||
public final boolean[] attribEnabled = new boolean[MAX_VERTEX_ATTRIBS];
|
||||
|
||||
+3
-3
@@ -1,10 +1,10 @@
|
||||
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.FrameManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager.FrameState;
|
||||
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.TextureOps;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.LogicOpFormats;
|
||||
@@ -121,13 +121,13 @@ public final class LogicOpEmulator {
|
||||
if (fbo == null) return frameManager.getFbo0Width();
|
||||
final int db = drawBuffer(fbo, loc);
|
||||
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) {
|
||||
if (fbo == null) return frameManager.getFbo0Height();
|
||||
final int db = drawBuffer(fbo, loc);
|
||||
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 {
|
||||
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));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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.Submits;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
@@ -156,11 +157,11 @@ public final class TextureOps {
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
|
||||
+13
-12
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.texture.ProxyTexture;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -47,11 +48,11 @@ class ProxyTextureLimitsTest {
|
||||
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));
|
||||
assertEquals(16384, SdlTestRig.contextState().proxyTexture.dimensionLimit(GL11.GL_PROXY_TEXTURE_2D));
|
||||
assertEquals(16384, SdlTestRig.contextState().proxyTexture.dimensionLimit(GL11.GL_PROXY_TEXTURE_1D));
|
||||
assertEquals(2048, SdlTestRig.contextState().proxyTexture.dimensionLimit(GL12.GL_PROXY_TEXTURE_3D));
|
||||
assertEquals(16384, SdlTestRig.contextState().proxyTexture.dimensionLimit(GL13.GL_PROXY_TEXTURE_CUBE_MAP));
|
||||
assertEquals(16384, SdlTestRig.contextState().proxyTexture.dimensionLimit(GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -99,13 +100,13 @@ class ProxyTextureLimitsTest {
|
||||
|
||||
@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));
|
||||
assertEquals(4, ProxyTexture.levelZeroEquivalentSize(4, 0));
|
||||
assertEquals(32, ProxyTexture.levelZeroEquivalentSize(4, 3));
|
||||
assertEquals(0, ProxyTexture.levelZeroEquivalentSize(4, -1));
|
||||
assertEquals(0, ProxyTexture.levelZeroEquivalentSize(0, 2));
|
||||
assertEquals(0, ProxyTexture.levelZeroEquivalentSize(4, 31));
|
||||
assertEquals(0, ProxyTexture.levelZeroEquivalentSize(4, 30));
|
||||
assertEquals(1 << 30, ProxyTexture.levelZeroEquivalentSize(1, 30));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
@@ -81,7 +82,7 @@ class ProxyTextureTargetTest {
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@MethodSource("levelSizeCases")
|
||||
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
|
||||
|
||||
Reference in new issue
Block a user